/////////////////////////////////////////////////////////////////////// // Name: FrondEngine.cpp // // *** INTERACTIVE DATA VISUALIZATION (IDV) PROPRIETARY INFORMATION *** // // Copyright (c) 2001-2004 IDV, Inc. // All Rights Reserved. // // IDV, Inc. // 1233 Washington St. Suite 610 // Columbia, SC 29201 // Voice: (803) 799-1699 // Fax: (803) 931-0320 // Web: http://www.idvinc.com // // This software is supplied under the terms of a license agreement or // nondisclosure agreement with Interactive Data Visualization and may not // be copied or disclosed except in accordance with the terms of that // agreement. #include "Debug.h" #include "FrondEngine.h" #include "IndexedGeometry.h" #include "LightingEngine.h" #include using namespace std; /////////////////////////////////////////////////////////////////////// // Constants const float c_fRepeatPreventionFactor = 0.999f; const float c_fStabilityThreshold = 0.1f; /////////////////////////////////////////////////////////////////////// // CFrondEngine::CFrondEngine definition CFrondEngine::CFrondEngine( ) : m_nLevel(1), m_pGeometry(NULL), m_pLightingEngine(NULL), m_eFrondType(CFrondEngine::FROND_EXTRUSION), m_nNumBlades(2), m_nProfileSegments(4), m_bEnabled(false), m_nNumLodLevels(4), m_fMaxSurfaceAreaPercent(1.0f), m_fMinSurfaceAreaPercent(0.0f), m_fReductionFuzziness(0.0f), m_fLargeRetentionPercent(0.05f), m_nMinLengthSegments(2), m_nMinCrossSegments(1) { m_pProfile = new CIdvBezierSpline(string("BezierSpline 0.0 1.0 0.0 { 3 0 0.00138887 0.337009 0.941501 0.132767 0.493215 0.998903 1 0.00102074 0.23702 1 -6.24607e-008 0.307222 -0.951638 0.126974 }")); } /////////////////////////////////////////////////////////////////////// // CFrondEngine::CFrondEngine CFrondEngine::CFrondEngine(const CFrondEngine& cRight) { *this = cRight; } /////////////////////////////////////////////////////////////////////// // CFrondEngine::~CFrondEngine definition CFrondEngine::~CFrondEngine( ) { delete m_pProfile; m_pProfile = NULL; } /////////////////////////////////////////////////////////////////////// // CFrondEngine::GetLevel definition int CFrondEngine::GetLevel(void) const { return m_nLevel; } /////////////////////////////////////////////////////////////////////// // CFrondEngine::Enabled definition bool CFrondEngine::Enabled(void) const { return m_bEnabled; } /////////////////////////////////////////////////////////////////////// // SIdvFrondInfo::Parse definition void CFrondEngine::Parse(CTreeFileAccess& cFile) { int nToken = cFile.ParseToken( ); do { switch (nToken) { case File_FrondLevel: m_nLevel = cFile.ParseInt( ); break; case File_FrondType: m_eFrondType = static_cast(cFile.ParseInt( )); break; case File_FrondNumBlades: m_nNumBlades = cFile.ParseInt( ); break; case File_FrondProfile: SetProfile(cFile.ParseBranchParameter( )); break; case File_FrondProfileSegments: m_nProfileSegments = cFile.ParseInt( ); break; case File_FrondEnabled: m_bEnabled = cFile.ParseBool( ); break; case File_FrondNumLodLevels: m_nNumLodLevels = cFile.ParseInt( ); break; case File_FrondMaxSurfaceAreaPercent: m_fMaxSurfaceAreaPercent = cFile.ParseFloat( ); break; case File_FrondMinSurfaceAreaPercent: m_fMinSurfaceAreaPercent = cFile.ParseFloat( ); break; case File_FrondReductionFuzziness: m_fReductionFuzziness = cFile.ParseFloat( ); break; case File_FrondLargeFrondPercent: m_fLargeRetentionPercent = cFile.ParseFloat( ); break; case File_FrondMinLengthSegments: m_nMinLengthSegments = cFile.ParseInt( ); break; case File_FrondMinCrossSegments: m_nMinCrossSegments = cFile.ParseInt( ); break; case File_FrondNumTextures: { m_vTextures.clear( ); int nNumTextures = cFile.ParseInt( ); for (int i = 0; i < nNumTextures; ++i) { SFrondTexture sFrond; nToken = cFile.ParseToken( ); // File_BeginFrondTexture nToken = cFile.ParseToken( ); do { switch (nToken) { case File_FrondTextureFilename: sFrond.m_strFilename = cFile.ParseString( ); sFrond.m_strFilename = sFrond.m_strFilename.NoPath( ); break; case File_FrondTextureAspectRatio: sFrond.m_fAspectRatio = cFile.ParseFloat( ); break; case File_FrondTextureSizeScale: sFrond.m_fSizeScale = cFile.ParseFloat( ); break; case File_FrondTextureMinAngleOffset: sFrond.m_fMinAngleOffset = cFile.ParseFloat( ); break; case File_FrondTextureMaxAngleOffset: sFrond.m_fMaxAngleOffset = cFile.ParseFloat( ); break; default: throw(IdvFileError(IdvFormatString("malformed frond texture information (token %d)", nToken))); break; } nToken = cFile.ParseToken( ); } while (nToken != File_EndFrondTexture); m_vTextures.push_back(sFrond); } } break; default: throw(IdvFileError(IdvFormatString("malformed frond info (token %d)", nToken))); } nToken = cFile.ParseToken( ); } while (nToken != File_EndFrondInfo); } /////////////////////////////////////////////////////////////////////// // CFrondEngine::Save definition void CFrondEngine::Save(CTreeFileAccess& cFile) const { cFile.SaveToken(File_BeginFrondInfo); cFile.SaveToken(File_FrondLevel); cFile.SaveInt(m_nLevel); cFile.SaveToken(File_FrondType); cFile.SaveInt(static_cast(m_eFrondType)); cFile.SaveToken(File_FrondNumBlades); cFile.SaveInt(m_nNumBlades); cFile.SaveToken(File_FrondProfile); cFile.SaveBranchParameter(m_pProfile); cFile.SaveToken(File_FrondProfileSegments); cFile.SaveInt(m_nProfileSegments); cFile.SaveToken(File_FrondEnabled); cFile.SaveBool(m_bEnabled); cFile.SaveToken(File_FrondNumLodLevels); cFile.SaveInt(m_nNumLodLevels); cFile.SaveToken(File_FrondMaxSurfaceAreaPercent); cFile.SaveFloat(m_fMaxSurfaceAreaPercent); cFile.SaveToken(File_FrondMinSurfaceAreaPercent); cFile.SaveFloat(m_fMinSurfaceAreaPercent); cFile.SaveToken(File_FrondReductionFuzziness); cFile.SaveFloat(m_fReductionFuzziness); cFile.SaveToken(File_FrondLargeFrondPercent); cFile.SaveFloat(m_fLargeRetentionPercent); cFile.SaveToken(File_FrondMinLengthSegments); cFile.SaveInt(m_nMinLengthSegments); cFile.SaveToken(File_FrondMinCrossSegments); cFile.SaveInt(m_nMinCrossSegments); cFile.SaveToken(File_FrondNumTextures); cFile.SaveInt(m_vTextures.size( )); for (unsigned int i = 0; i < m_vTextures.size( ); ++i) { cFile.SaveToken(File_BeginFrondTexture); cFile.SaveToken(File_FrondTextureFilename); cFile.SaveString(m_vTextures[i].m_strFilename); cFile.SaveToken(File_FrondTextureAspectRatio); cFile.SaveFloat(m_vTextures[i].m_fAspectRatio); cFile.SaveToken(File_FrondTextureSizeScale); cFile.SaveFloat(m_vTextures[i].m_fSizeScale); cFile.SaveToken(File_FrondTextureMinAngleOffset); cFile.SaveFloat(m_vTextures[i].m_fMinAngleOffset); cFile.SaveToken(File_FrondTextureMaxAngleOffset); cFile.SaveFloat(m_vTextures[i].m_fMinAngleOffset); cFile.SaveToken(File_EndFrondTexture); } cFile.SaveToken(File_EndFrondInfo); } /////////////////////////////////////////////////////////////////////// // CFrondEngine::ClearGuides definition void CFrondEngine::ClearGuides(void) { m_vGuides.clear( ); } /////////////////////////////////////////////////////////////////////// // CFrondEngine::StartGuide definition void CFrondEngine::StartGuide(void) { SFrondGuide sGuide; m_vGuides.push_back(sGuide); } /////////////////////////////////////////////////////////////////////// // CFrondEngine::AddGuideVertex definition void CFrondEngine::AddGuideVertex(CVec3 cPos, CRotTransform cTrans, float fCrossSectionWeight, int nWindGroup) { SFrondVertex sVertex; sVertex.m_cPos = cPos; sVertex.m_cTrans = cTrans; sVertex.m_fCrossSectionWeight = fCrossSectionWeight; sVertex.m_nWindGroup = nWindGroup; m_vGuides[m_vGuides.size( ) - 1].m_vVertices.push_back(sVertex); } /////////////////////////////////////////////////////////////////////// // CFrondEngine::EndGuide definition void CFrondEngine::EndGuide(float fLodSizeScalar) { SFrondGuide& sGuide = m_vGuides[m_vGuides.size( ) - 1]; unsigned int i; for (i = 0; i < sGuide.m_vVertices.size( ) - 1; ++i) sGuide.m_fLength += sGuide.m_vVertices[i].m_cPos.Distance(sGuide.m_vVertices[i + 1].m_cPos); // choose a frond map, compute the radius and start angle CIdvRandom cRandom; if (m_vTextures.size( ) == 0) { m_vGuides[i].m_chFrondMapIndex = 0; sGuide.m_fRadius = sGuide.m_fLength * 0.5f; sGuide.m_fOffsetAngle = 0.0f; } else { sGuide.m_chFrondMapIndex = static_cast(static_cast(cRandom.GetUniform(0.0f, 100000.0f)) % m_vTextures.size( )); sGuide.m_fRadius = sGuide.m_fLength * m_vTextures[static_cast(sGuide.m_chFrondMapIndex)].m_fAspectRatio * 0.5f; sGuide.m_fRadius *= m_vTextures[sGuide.m_chFrondMapIndex].m_fSizeScale; sGuide.m_fOffsetAngle = cRandom.GetUniform(m_vTextures[sGuide.m_chFrondMapIndex].m_fMinAngleOffset, m_vTextures[sGuide.m_chFrondMapIndex].m_fMaxAngleOffset); if (m_vGuides.size( ) % 2 == 1) sGuide.m_fOffsetAngle *= -1.0f; } sGuide.m_fSurfaceArea = sGuide.m_fLength * /* sGuide.m_fRadius * */ fLodSizeScalar; } /////////////////////////////////////////////////////////////////////// // CFrondEngine::SetProfile definition void CFrondEngine::SetProfile(CIdvBezierSpline* pProfile) { if (m_pProfile != pProfile) { delete m_pProfile; m_pProfile = pProfile; } } /////////////////////////////////////////////////////////////////////// // CFrondEngine::Compute definition void CFrondEngine::Compute(CIndexedGeometry* pGeometry, CLightingEngine* pLightingEngine) { m_pGeometry = pGeometry; m_pLightingEngine = pLightingEngine; st_assert(m_pGeometry); st_assert(m_pLightingEngine); int nVertices = 0; BuildGuideLods( ); for (unsigned int j = 0; j < m_vGuideLods[0].size( ); ++j) { switch (m_eFrondType) { case FROND_BLADES: // all blade lods reference the same vertices so only report them once nVertices += m_vGuideLods[0][j].m_vVertices.size( ) * 2 * m_nNumBlades; break; case FROND_EXTRUSION: // all extrusion lods reference the same vertices so only report them once nVertices += m_vGuideLods[0][j].m_vVertices.size( ) * ((m_nProfileSegments * 2) - 1); break; default: throw(IdvFileError("default reached in CFrondEngine::Compute()")); } } if (nVertices > USHRT_MAX) throw(runtime_error(IdvFormatString("frond vertices exceed %d", USHRT_MAX))); m_pGeometry->SetNumLodLevels(static_cast(m_nNumLodLevels)); switch (m_eFrondType) { case FROND_BLADES: { // build vertices for highest lod (lower lods share them) for (unsigned int i = 0; i < m_vGuideLods[0].size( ); ++i) BuildBladeVertices(m_vGuideLods[0][i]); // build all strips for (unsigned short m = 0; m < m_vGuideLods.size( ); ++m) { m_pGeometry->ResetStripCounter(m); for (unsigned int k = 0; k < m_vGuideLods[m].size( ); ++k) ComputeBlade(m, m_vGuideLods[0][k].m_nVertexStartIndex, m_vGuideLods[m][k]); } break; } case FROND_EXTRUSION: { // build vertices for highest lod (lower lods share them) for (unsigned int i = 0; i < m_vGuideLods[0].size( ); ++i) BuildExtrusionVertices(m_vGuideLods[0][i]); for (unsigned short l = 0; l < m_vGuideLods.size( ); ++l) { m_pGeometry->ResetStripCounter(l); for (unsigned int k = 0; k < m_vGuideLods[l].size( ); ++k) { m_vGuideLods[l][k].m_nVerticesPerGuideVertex = m_vGuideLods[0][k].m_nVerticesPerGuideVertex; ComputeExtrusion(l, m_vGuideLods[0][k].m_nVertexStartIndex, m_vGuideLods[l][k]); } } break; } default: throw(IdvFileError("default reached in CFrondEngine::Compute()")); } } /////////////////////////////////////////////////////////////////////// // CFrondEngine::BuildBladeVertices definition void CFrondEngine::BuildBladeVertices(SFrondGuide& sGuide) { if (m_pGeometry && m_pLightingEngine) { float fAngleBetween = 180.0f / m_nNumBlades; sGuide.m_nVertexStartIndex = m_pGeometry->GetVertexCounter( ); sGuide.m_nVerticesPerGuideVertex = 2; unsigned int i, k; for (k = 0; k < m_nNumBlades; ++k) { int nLocalStart = m_pGeometry->GetVertexCounter( ); // add vertices vector vRightDirections; for (i = 0; i < sGuide.m_vVertices.size( ); ++i) { // get the matrix for these vertices CRotTransform cTrans; if (i < sGuide.m_vVertices.size( ) - 1) cTrans = sGuide.m_vVertices[i].m_cTrans; else cTrans = sGuide.m_vVertices[i - 1].m_cTrans; cTrans.RotateX(fAngleBetween * k + sGuide.m_fOffsetAngle); // compute directions CVec3 cRightDir = CVec3(0.0f, 1.0f, 0.0f) * cTrans; vRightDirections.push_back(cRightDir); // compute normal CVec3 cNormal; if (i == 0) cNormal = sGuide.m_vVertices[i + 1].m_cPos - sGuide.m_vVertices[0].m_cPos; else cNormal = sGuide.m_vVertices[i].m_cPos - sGuide.m_vVertices[0].m_cPos; cNormal.Normalize( ); // compute vertices CVec3 cRight, cLeft; if (i == sGuide.m_vVertices.size( ) - 1 && sGuide.m_vVertices.size( ) > 0) { cRight = sGuide.m_vVertices[i].m_cPos + vRightDirections[i - 1] * sGuide.m_fRadius; cLeft = sGuide.m_vVertices[i].m_cPos - vRightDirections[i - 1] * sGuide.m_fRadius; } else if (i > 0 && i < sGuide.m_vVertices.size( ) - 1) { CVec3 cTemp; // right cTemp = sGuide.m_vVertices[i].m_cPos + vRightDirections[i - 1] * sGuide.m_fRadius; cRight = sGuide.m_vVertices[i].m_cPos + cRightDir * sGuide.m_fRadius; cRight = (cRight + cTemp) * 0.5f; // left cTemp = sGuide.m_vVertices[i].m_cPos - vRightDirections[i - 1] * sGuide.m_fRadius; cLeft = sGuide.m_vVertices[i].m_cPos - cRightDir * sGuide.m_fRadius; cLeft = (cLeft + cTemp) * 0.5f; } else { cRight = sGuide.m_vVertices[i].m_cPos + cRightDir * sGuide.m_fRadius; cLeft = sGuide.m_vVertices[i].m_cPos - cRightDir * sGuide.m_fRadius; } float fLengthPercent = static_cast(i) / (sGuide.m_vVertices.size( ) - 1.0f); // add right vertex m_pGeometry->AddVertexCoord(cRight.m_afData); float afTexCoords[2]; afTexCoords[0] = 1.0f; afTexCoords[1] = fLengthPercent; m_pGeometry->AddVertexTexCoord0(afTexCoords, sGuide.m_chFrondMapIndex); if (m_pLightingEngine->GetFrondLightingMethod( ) == CSpeedTreeRT::LIGHT_STATIC) { float afColor[4]; m_pLightingEngine->ComputeStandardStaticLighting(cNormal.m_afData, cRight.m_afData, afColor, CLightingEngine::MATERIAL_FROND); m_pGeometry->AddVertexColor(afColor); } else { m_pGeometry->AddVertexNormal(cNormal.m_afData); // bump mapping m_pGeometry->AddVertexTangent(cRightDir); CVec3 cBinormal = cRightDir * cNormal; if (cBinormal.Magnitude( ) < c_fStabilityThreshold) cBinormal= cNormal; else cBinormal.Normalize( ); m_pGeometry->AddVertexBinormal(cBinormal); } if (m_pGeometry->IsVertexWeightingEnabled( )) m_pGeometry->AddVertexWind(sGuide.m_vVertices[i].m_fCrossSectionWeight, static_cast(sGuide.m_vVertices[i].m_nWindGroup)); m_pGeometry->AdvanceVertexCounter( ); // add left vertex m_pGeometry->AddVertexCoord(cLeft.m_afData); afTexCoords[0] = 0.0f; m_pGeometry->AddVertexTexCoord0(afTexCoords, sGuide.m_chFrondMapIndex); if (m_pLightingEngine->GetFrondLightingMethod( ) == CSpeedTreeRT::LIGHT_STATIC) { float afColor[4]; m_pLightingEngine->ComputeStandardStaticLighting(cNormal.m_afData, cLeft.m_afData, afColor, CLightingEngine::MATERIAL_FROND); m_pGeometry->AddVertexColor(afColor); } else { m_pGeometry->AddVertexNormal(cNormal.m_afData); // bump mapping m_pGeometry->AddVertexTangent(cRightDir); CVec3 cBinormal = cRightDir * cNormal; if (cBinormal.Magnitude( ) < c_fStabilityThreshold) cBinormal= cNormal; else cBinormal.Normalize( ); m_pGeometry->AddVertexBinormal(cBinormal); } if (m_pGeometry->IsVertexWeightingEnabled( )) m_pGeometry->AddVertexWind(sGuide.m_vVertices[i].m_fCrossSectionWeight, static_cast(sGuide.m_vVertices[i].m_nWindGroup)); m_pGeometry->AdvanceVertexCounter( ); } // compute lengths for t coord adjustment float fRightLength = 0.0f, fLeftLength = 0.0f; vector vRightLengths, vLeftLengths; unsigned int nSize = sGuide.m_vVertices.size( ); for (i = 0; i < nSize - 1; ++i) { const float* pThisCoord = m_pGeometry->GetVertexCoord(i * 2 + 0 + sGuide.m_nVertexStartIndex); const float* pNextCoord = m_pGeometry->GetVertexCoord(i * 2 + 2 + sGuide.m_nVertexStartIndex); CVec3 cThis(pThisCoord[0], pThisCoord[1], pThisCoord[2]); CVec3 cNext(pNextCoord[0], pNextCoord[1], pNextCoord[2]); fRightLength += cThis.Distance(cNext); vRightLengths.push_back(fRightLength); pThisCoord = m_pGeometry->GetVertexCoord(i * 2 + 1 + sGuide.m_nVertexStartIndex); pNextCoord = m_pGeometry->GetVertexCoord(i * 2 + 3 + sGuide.m_nVertexStartIndex); cThis.Set(pThisCoord[0], pThisCoord[1], pThisCoord[2]); cNext.Set(pNextCoord[0], pNextCoord[1], pNextCoord[2]); fLeftLength += cThis.Distance(cNext); vLeftLengths.push_back(fLeftLength); } // set new, "unpinched" t coords (skip the first one since it is always zero) m_pGeometry->SetVertexCounter(static_cast(nLocalStart + 2)); for (i = 1; i < nSize - 1; ++i) { const float* pTexCoord = m_pGeometry->GetVertexTexCoord0(i * 2 + 0 + sGuide.m_nVertexStartIndex); float afRightTexCoord[ ] = { pTexCoord[0], c_fRepeatPreventionFactor * (vRightLengths[i - 1] / fRightLength) }; m_pGeometry->AddVertexTexCoord0(afRightTexCoord, sGuide.m_chFrondMapIndex); m_pGeometry->AdvanceVertexCounter( ); pTexCoord = m_pGeometry->GetVertexTexCoord0(i * 2 + 1 + sGuide.m_nVertexStartIndex); float afLeftTexCoord[ ] = { pTexCoord[0], c_fRepeatPreventionFactor * (vLeftLengths[i - 1] / fLeftLength) }; m_pGeometry->AddVertexTexCoord0(afLeftTexCoord, sGuide.m_chFrondMapIndex); m_pGeometry->AdvanceVertexCounter( ); } // acount for unmodified end coords m_pGeometry->AdvanceVertexCounter( ); m_pGeometry->AdvanceVertexCounter( ); } } } /////////////////////////////////////////////////////////////////////// // CFrondEngine::ComputeBlade definition void CFrondEngine::ComputeBlade(unsigned int nLod, unsigned int nStart, SFrondGuide& sGuide) { if (m_pGeometry) { for (unsigned int k = 0; k < m_nNumBlades; ++k) { // build strips unsigned short usIndexCount = static_cast(sGuide.m_vVertices.size( ) * 2); unsigned short* pStrip = new unsigned short[usIndexCount]; int nVertexIndex = 0; for (unsigned int i = 0; i < sGuide.m_vVertices.size( ) * 2; ++i) pStrip[nVertexIndex++] = static_cast(nStart + (k * 2 * sGuide.m_vVertices.size( )) + i); m_pGeometry->AddStrip(static_cast(nLod), pStrip, usIndexCount); m_pGeometry->AdvanceStripCounter( ); } } } /////////////////////////////////////////////////////////////////////// // CFrondEngine::BuildExtrusionVertices definition void CFrondEngine::BuildExtrusionVertices(SFrondGuide& sGuide) { if (m_pGeometry && m_pLightingEngine) { vector vProfile, vNormals; BuildProfileVectors(sGuide, vProfile, vNormals); // build vertex table sGuide.m_nVerticesPerGuideVertex = vProfile.size( ); sGuide.m_nVertexStartIndex = m_pGeometry->GetVertexCounter( ); // run through points defining the spine of the frond unsigned int i; for (i = 0; i < sGuide.m_vVertices.size( ); ++i) { // T tex coord float fT = static_cast(i) / (sGuide.m_vVertices.size( ) - 1.0f); // run through the points on either side of point in spine (calculate // two points, one on each side) for (unsigned int j = 0; j < sGuide.m_nVerticesPerGuideVertex; ++j) { // S tex coord float fS = j / (sGuide.m_nVerticesPerGuideVertex - 1.0f); // position CRotTransform cTrans = sGuide.m_vVertices[i].m_cTrans; CRotTransform cPreviousTrans = i ? sGuide.m_vVertices[i - 1].m_cTrans : cTrans; cTrans.RotateX(sGuide.m_fOffsetAngle); cPreviousTrans.RotateX(sGuide.m_fOffsetAngle); CVec3 cPos1 = vProfile[j] * cTrans, cPos2 = vProfile[j] * cPreviousTrans; CVec3 cPos = sGuide.m_vVertices[i].m_cPos + ((cPos1 + cPos2) * 0.5f); CVec3 cTangent = cPos1 + cPos2; cTangent.Normalize( ); m_pGeometry->AddVertexCoord(cPos.m_afData); float afTexCoords[2]; afTexCoords[0] = fS; afTexCoords[1] = fT; m_pGeometry->AddVertexTexCoord0(afTexCoords, sGuide.m_chFrondMapIndex); // normal CVec3 cNormal1 = vNormals[j] * cTrans, cNormal2 = vNormals[j] * cPreviousTrans; CVec3 cNormal = (cNormal1 + cNormal2) * 0.5f; cNormal.Normalize( ); if (m_pLightingEngine->GetFrondLightingMethod( ) == CSpeedTreeRT::LIGHT_STATIC) { float afColor[4]; m_pLightingEngine->ComputeStandardStaticLighting(cNormal.m_afData, cPos.m_afData, afColor, CLightingEngine::MATERIAL_FROND); m_pGeometry->AddVertexColor(afColor); } else { m_pGeometry->AddVertexNormal(cNormal); // bump mapping m_pGeometry->AddVertexTangent(cTangent); CVec3 cBinormal = cNormal * cTangent; // if (cBinormal.Magnitude( ) < c_fStabilityThreshold) // cBinormal= cNormal; // else cBinormal.Normalize( ); m_pGeometry->AddVertexBinormal(cBinormal); } if (m_pGeometry->IsVertexWeightingEnabled( )) m_pGeometry->AddVertexWind(sGuide.m_vVertices[i].m_fCrossSectionWeight, static_cast(sGuide.m_vVertices[i].m_nWindGroup)); m_pGeometry->AdvanceVertexCounter( ); } } // compute lengths for t coord adjustment vector vLengths; vector< vector > vRunningLengths; for (i = 0; i < sGuide.m_nVerticesPerGuideVertex; ++i) { float fLength = 0.0f; vector vRunning; vRunning.push_back(0.0f); for (unsigned int j = 1; j < sGuide.m_vVertices.size( ); ++j) { int nThisIndex = sGuide.m_nVertexStartIndex + (j * sGuide.m_nVerticesPerGuideVertex) + i; int nPrevIndex = sGuide.m_nVertexStartIndex + ((j - 1) * sGuide.m_nVerticesPerGuideVertex) + i; const float* pThisCoord = m_pGeometry->GetVertexCoord(nThisIndex); const float* pPrevCoord = m_pGeometry->GetVertexCoord(nPrevIndex); CVec3 cThis(pThisCoord[0], pThisCoord[1], pThisCoord[2]); CVec3 cPrev(pPrevCoord[0], pPrevCoord[1], pPrevCoord[2]); fLength += cThis.Distance(cPrev); vRunning.push_back(fLength); } vRunningLengths.push_back(vRunning); vLengths.push_back(fLength); } // set new, "unpinched" t coords (skip the first one since it is always zero) for (i = 0; i < sGuide.m_nVerticesPerGuideVertex; ++i) { for (unsigned int j = 1; j < sGuide.m_vVertices.size( ); ++j) { unsigned short usThisIndex = static_cast(sGuide.m_nVertexStartIndex + (j * sGuide.m_nVerticesPerGuideVertex) + i); m_pGeometry->SetVertexCounter(usThisIndex); const float* pTexCoord = m_pGeometry->GetVertexTexCoord0(usThisIndex); float afNewTexCoord[ ] = { pTexCoord[0], c_fRepeatPreventionFactor * (vRunningLengths[i][j] / vLengths[i]) }; m_pGeometry->AddVertexTexCoord0(afNewTexCoord, sGuide.m_chFrondMapIndex); m_pGeometry->AdvanceVertexCounter( ); } } } } /////////////////////////////////////////////////////////////////////// // CFrondEngine::ComputeExtrusion definition void CFrondEngine::ComputeExtrusion(unsigned int nLod, unsigned int nStart, SFrondGuide& sGuide) { if (m_pGeometry) { // compute lod info float fLodPercent; if (m_nNumLodLevels == 1) fLodPercent = 1.0f; else fLodPercent = 1.0f - (nLod / (m_nNumLodLevels - 1.0f)); int nNumLengthVertices = static_cast(VecInterpolate(static_cast(m_nMinLengthSegments + 1), static_cast(sGuide.m_vVertices.size( )), fLodPercent)); // plus one for vertex conversion float fLengthStep = sGuide.m_vVertices.size( ) / static_cast(nNumLengthVertices - 1); // -1 because one of them doesn't "step" int nNumCrossVertices = static_cast(VecInterpolate(static_cast((m_nMinCrossSegments * 2) + 1), static_cast(sGuide.m_nVerticesPerGuideVertex), fLodPercent)); // * 2 for mirroring, plus one for vertex conversion float fCrossStep = (sGuide.m_nVerticesPerGuideVertex) / static_cast(nNumCrossVertices - 1); // -1 because one of them doesn't "step" // build strips int nSegments = nNumLengthVertices - 1; int nIndexCount = (nSegments * (nNumCrossVertices * 2 + 1)) - 1; // + 1 for repeated vertex, - 1 because we do not need to repeat the last one unsigned short* pStrip = new unsigned short[nIndexCount]; int nVertexIndex = 0; for (int i = 0; i < nNumLengthVertices - 1; ++i) { int nThisLengthIndex = static_cast(i * fLengthStep); int nNextLengthIndex = static_cast((i + 1) * fLengthStep); if (i == nNumLengthVertices - 2 || nNextLengthIndex > static_cast(sGuide.m_vVertices.size( ) - 1)) nNextLengthIndex = sGuide.m_vVertices.size( ) - 1; int nSkipFactor = nNextLengthIndex - nThisLengthIndex; if (i % 2 == 0) { int nBase = nThisLengthIndex * sGuide.m_nVerticesPerGuideVertex; for (int j = 0; j < nNumCrossVertices; ++j) { unsigned int nThisCrossVertex = static_cast(j * fCrossStep); if (j == nNumCrossVertices - 1) nThisCrossVertex = sGuide.m_nVerticesPerGuideVertex - 1; if (nThisCrossVertex > sGuide.m_nVerticesPerGuideVertex - 1) nThisCrossVertex = sGuide.m_nVerticesPerGuideVertex - 1; pStrip[nVertexIndex++] = static_cast(nStart + nBase + nThisCrossVertex); pStrip[nVertexIndex++] = static_cast(nStart + nBase + nThisCrossVertex + (nSkipFactor * sGuide.m_nVerticesPerGuideVertex)); } // repeat this vertex so we can combine the multiple segments of one frond if (i < nNumLengthVertices - 2) { pStrip[nVertexIndex] = pStrip[nVertexIndex - 1]; ++nVertexIndex; } } else { int nBase = nThisLengthIndex * sGuide.m_nVerticesPerGuideVertex; for (int j = nNumCrossVertices - 1; j >= 0; --j) { unsigned int nThisCrossVertex = static_cast((j * fCrossStep)); if (j == nNumCrossVertices - 1) nThisCrossVertex = sGuide.m_nVerticesPerGuideVertex - 1; if (nThisCrossVertex > sGuide.m_nVerticesPerGuideVertex - 1) nThisCrossVertex = sGuide.m_nVerticesPerGuideVertex - 1; pStrip[nVertexIndex++] = static_cast(nStart + nBase + nThisCrossVertex); pStrip[nVertexIndex++] = static_cast(nStart + nBase + nThisCrossVertex + (nSkipFactor * sGuide.m_nVerticesPerGuideVertex)); } // repeat this vertex so we can combine the multiple segments of one frond if (i < nNumLengthVertices - 2) { pStrip[nVertexIndex] = pStrip[nVertexIndex - 1]; ++nVertexIndex; } } } m_pGeometry->AddStrip(static_cast(nLod), pStrip, static_cast(nIndexCount)); m_pGeometry->AdvanceStripCounter( ); } } /////////////////////////////////////////////////////////////////////// // CFrondEngine::BuildProfileVectors definition void CFrondEngine::BuildProfileVectors(SFrondGuide& sGuide, vector& vProfile, vector& vNormals) { // left side float fAdjust = m_pProfile->Evaluate(0.0f) * sGuide.m_fRadius; unsigned int i; for (i = 0; i < m_nProfileSegments; ++i) { float fPercent = 1.0f - (i / (m_nProfileSegments - 1.0f)); CVec3 cVertex; cVertex[0] = 0.0f; cVertex[1] = -fPercent * sGuide.m_fRadius; cVertex[2] = (m_pProfile->Evaluate(fPercent) * sGuide.m_fRadius) - fAdjust; vProfile.push_back(cVertex); } // reverse for right side w/o duplicating center point int j; for (j = m_nProfileSegments - 2; j >= 0; --j) { CVec3 cVertex = vProfile[j]; cVertex[1] *= -1.0f; vProfile.push_back(cVertex); } // build normal profile for (i = 0; i < vProfile.size( ); ++i) { float fAngle; if (i == static_cast(m_nProfileSegments - 1)) { fAngle = c_fHalfPi; } else if (i == 0) { float fNextAngle = atan2f(vProfile[i + 1][2] - vProfile[i][2], vProfile[i + 1][1] - vProfile[i][1]); fAngle = fNextAngle + c_fHalfPi; } else if (i == vProfile.size( ) - 1) { float fPreviousAngle = atan2f(vProfile[i][2] - vProfile[i - 1][2], vProfile[i][1] - vProfile[i - 1][1]); fAngle = fPreviousAngle + c_fQuarterPi; } else { float fNextAngle = atan2f(vProfile[i + 1][2] - vProfile[i][2], vProfile[i + 1][1] - vProfile[i][1]); float fPreviousAngle = atan2f(vProfile[i][2] - vProfile[i - 1][2], vProfile[i][1] - vProfile[i - 1][1]); fAngle = (0.5f * (fPreviousAngle + fNextAngle)) + c_fHalfPi; } CVec3 cNormal(0.0f, cosf(fAngle), sinf(fAngle)); cNormal.Normalize( ); vNormals.push_back(cNormal); } } /////////////////////////////////////////////////////////////////////// // CFrondEngine::SetTextureCoords definition void CFrondEngine::SetTextureCoords(CIndexedGeometry* pGeometry, unsigned int nFrondMapIndex, const float* pTexCoords, bool bFlip) { // flip the coords if necessary float afCoords[8]; memcpy(afCoords, pTexCoords, 8 * sizeof(float)); if (bFlip) { for (int i = 1; i < 8; i +=2) afCoords[i] = -afCoords[i]; } // check all of the vertices unsigned char chIndex = static_cast(nFrondMapIndex); pGeometry->ResetVertexCounter( ); for (int i = 0; i < pGeometry->GetVertexCount( ); ++i) { pGeometry->ChangeTexCoord(chIndex, afCoords); pGeometry->AdvanceVertexCounter( ); } } /////////////////////////////////////////////////////////////////////// // CFrondEngine::operator= definition const CFrondEngine& CFrondEngine::operator=(const CFrondEngine& cRight) { if (&cRight != this) { /* // general //lint -e{ 1555 } { pointer copy OK here } m_pGeometry = cRight.m_pGeometry; //lint -e{ 1555 } { pointer copy OK here } m_pLightingEngine = cRight.m_pLightingEngine; // frond guides m_vGuides = cRight.m_vGuides; m_vGuideLods = cRight.m_vGuideLods; */ // frond parameters m_eFrondType = cRight.m_eFrondType; m_nNumBlades = cRight.m_nNumBlades; //delete m_pProfile; //m_pProfile = new CIdvBezierSpline; *m_pProfile = *cRight.m_pProfile; m_nProfileSegments = cRight.m_nProfileSegments; m_nLevel = cRight.m_nLevel; m_bEnabled = cRight.m_bEnabled; m_vTextures = cRight.m_vTextures; m_nNumLodLevels = cRight.m_nNumLodLevels; m_fMaxSurfaceAreaPercent = cRight.m_fMaxSurfaceAreaPercent; m_nNumLodLevels = cRight.m_nNumLodLevels; m_fMaxSurfaceAreaPercent = cRight.m_fMaxSurfaceAreaPercent; m_fMinSurfaceAreaPercent = cRight.m_fMinSurfaceAreaPercent; m_fReductionFuzziness = cRight.m_fReductionFuzziness; m_fLargeRetentionPercent = cRight.m_fLargeRetentionPercent; m_nMinLengthSegments = cRight.m_nMinLengthSegments; m_nMinCrossSegments = cRight.m_nMinLengthSegments; } return *this; } /////////////////////////////////////////////////////////////////////// // class CGuideSorter definition // // Function object used to sort the guides by surface area. class CGuideSorter { public: bool operator()(const SFrondGuide& cLeft, const SFrondGuide& cRight) { return (cLeft.m_fFuzzySurfaceArea > cRight.m_fFuzzySurfaceArea); } }; /////////////////////////////////////////////////////////////////////// // CFrondEngine::BuildGuideLods definition void CFrondEngine::BuildGuideLods( ) { // compute total surface area of all fronds float fTotalArea = 0.0f; float fLargestArea = 0.0f; unsigned int i; for (i = 0; i < m_vGuides.size( ); ++i) { fTotalArea += m_vGuides[i].m_fSurfaceArea; if (m_vGuides[i].m_fSurfaceArea > fLargestArea) fLargestArea = m_vGuides[i].m_fSurfaceArea; } // increase area to make fuzziness more effective (fronds do not typically benefit from the huge trunk) float fSquaredArea = fLargestArea * fLargestArea; // implement fuzziness CIdvRandom cRandom; float fSavePercent = 1.0f - m_fLargeRetentionPercent; vector vSavedFronds; for (i = 0; i < m_vGuides.size( ); ++i) { float fArea = m_vGuides[i].m_fSurfaceArea; if (fArea > fLargestArea * fSavePercent) { vSavedFronds.push_back(m_vGuides[i]); (void) m_vGuides.erase(m_vGuides.begin( ) + i--); } else { // compute a new, fuzzy volume float fFuzziness = cRandom.GetUniform(0.0f, m_fReductionFuzziness), fOneMinusFuzziness = 1.0f - fFuzziness; fArea = (fOneMinusFuzziness * fArea) + (fFuzziness * fSquaredArea); m_vGuides[i].m_fFuzzySurfaceArea = fArea; } } // sort branches in descending order of volume and add insert save branches at the beginning sort(m_vGuides.begin( ), m_vGuides.end( ), CGuideSorter( )); sort(vSavedFronds.begin( ), vSavedFronds.end( ), CGuideSorter( )); for (i = 0; i < vSavedFronds.size( ); ++i) (void) m_vGuides.insert(m_vGuides.begin( ), vSavedFronds[i]); // for each LOD level, compute a target volume and use enough // branches to reach that target for (i = 0; i < m_nNumLodLevels; ++i) { // figure what part of the branch structure stays for lod level i, computed by volume float fPercent; if (m_nNumLodLevels == 1) fPercent = 1.0f; else fPercent = VecInterpolate(m_fMaxSurfaceAreaPercent, m_fMinSurfaceAreaPercent, i / float(m_nNumLodLevels - 1)); float fTargetVolume = fPercent * fTotalArea; // which branches will contribute to lod level i float fLodArea = 0.0f; vector vLodLevel; if (fTargetVolume > 0.0f) { for (unsigned int j = 0; j < m_vGuides.size( ) && fLodArea <= fTargetVolume; ++j) { fLodArea += m_vGuides[j].m_fSurfaceArea; vLodLevel.push_back(m_vGuides[j]); } } m_vGuideLods.push_back(vLodLevel); } }