1060 lines
39 KiB
C++
1060 lines
39 KiB
C++
///////////////////////////////////////////////////////////////////////
|
|
// 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 <algorithm>
|
|
|
|
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<EFrondType>(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<int>(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<unsigned char>(static_cast<unsigned int>(cRandom.GetUniform(0.0f, 100000.0f)) % m_vTextures.size( ));
|
|
sGuide.m_fRadius = sGuide.m_fLength * m_vTextures[static_cast<int>(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<unsigned short>(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<CVec3> 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<float>(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<unsigned char>(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<unsigned char>(sGuide.m_vVertices[i].m_nWindGroup));
|
|
|
|
m_pGeometry->AdvanceVertexCounter( );
|
|
}
|
|
|
|
// compute lengths for t coord adjustment
|
|
float fRightLength = 0.0f, fLeftLength = 0.0f;
|
|
vector<float> 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<unsigned short>(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<unsigned short>(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<unsigned short>(nStart + (k * 2 * sGuide.m_vVertices.size( )) + i);
|
|
|
|
m_pGeometry->AddStrip(static_cast<unsigned short>(nLod), pStrip, usIndexCount);
|
|
m_pGeometry->AdvanceStripCounter( );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// CFrondEngine::BuildExtrusionVertices definition
|
|
|
|
void CFrondEngine::BuildExtrusionVertices(SFrondGuide& sGuide)
|
|
{
|
|
if (m_pGeometry && m_pLightingEngine)
|
|
{
|
|
vector<CVec3> 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<float>(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<unsigned char>(sGuide.m_vVertices[i].m_nWindGroup));
|
|
|
|
m_pGeometry->AdvanceVertexCounter( );
|
|
}
|
|
}
|
|
|
|
// compute lengths for t coord adjustment
|
|
vector<float> vLengths;
|
|
vector< vector<float> > vRunningLengths;
|
|
|
|
for (i = 0; i < sGuide.m_nVerticesPerGuideVertex; ++i)
|
|
{
|
|
float fLength = 0.0f;
|
|
vector<float> 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<unsigned short>(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<int>(VecInterpolate(static_cast<float>(m_nMinLengthSegments + 1), static_cast<float>(sGuide.m_vVertices.size( )), fLodPercent)); // plus one for vertex conversion
|
|
float fLengthStep = sGuide.m_vVertices.size( ) / static_cast<float>(nNumLengthVertices - 1); // -1 because one of them doesn't "step"
|
|
|
|
int nNumCrossVertices = static_cast<int>(VecInterpolate(static_cast<float>((m_nMinCrossSegments * 2) + 1), static_cast<float>(sGuide.m_nVerticesPerGuideVertex), fLodPercent)); // * 2 for mirroring, plus one for vertex conversion
|
|
float fCrossStep = (sGuide.m_nVerticesPerGuideVertex) / static_cast<float>(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<int>(i * fLengthStep);
|
|
int nNextLengthIndex = static_cast<int>((i + 1) * fLengthStep);
|
|
|
|
if (i == nNumLengthVertices - 2 || nNextLengthIndex > static_cast<int>(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<unsigned int>(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<unsigned short>(nStart + nBase + nThisCrossVertex);
|
|
pStrip[nVertexIndex++] = static_cast<unsigned short>(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<unsigned int>((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<unsigned short>(nStart + nBase + nThisCrossVertex);
|
|
pStrip[nVertexIndex++] = static_cast<unsigned short>(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<unsigned short>(nLod), pStrip, static_cast<unsigned short>(nIndexCount));
|
|
m_pGeometry->AdvanceStripCounter( );
|
|
}
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
// CFrondEngine::BuildProfileVectors definition
|
|
|
|
void CFrondEngine::BuildProfileVectors(SFrondGuide& sGuide, vector<CVec3>& vProfile, vector<CVec3>& 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<unsigned int>(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<unsigned char>(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<SFrondGuide> 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<SFrondGuide> 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);
|
|
}
|
|
}
|