Files
2026-06-01 12:46:52 +02:00

1143 lines
47 KiB
C++

///////////////////////////////////////////////////////////////////////
// Name: Branch.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 "TreeEngine.h"
#include <fstream>
#include "FileAccess.h"
#include "LibRandom_Source/IdvRandom.h"
#include "BillboardLeaf.h"
#include "SpeedTreeRT.h"
#include "IndexedGeometry.h"
#include "LightingEngine.h"
#include "FrondEngine.h"
#include <algorithm>
#include "Random.h"
// branch-related constants
const CVec3 g_cOut(1.0f, 0.0f, 0.0f);
const CVec3 g_cDown(0.0f, 0.0f, -1.0f);
const CVec3 g_cUp(0.0f, 0.0f, 1.0f);
const float c_fInverseOf90 = 1.0f / 90.0f;
const float c_fParentRadiusFactor = 0.85f;
// static variables
vector<int> CIdvBranch::m_vBlossoms;
vector<int> CIdvBranch::m_vNonBlossoms;
vector<const SIdvBranchInfo*> CIdvBranch::m_vBranchInfo;
bool CIdvBranch::m_bComputeLeaves = true;
SIdvLeafInfo* CIdvBranch::m_pLeafInfo = NULL;
vector<CBillboardLeaf*> CIdvBranch::m_vLocalLeaves;
CLightingEngine* CIdvBranch::m_pLightingEngine = NULL;
CIdvRandom CIdvBranch::m_cRandom;
int CIdvBranch::m_nWeightLevel = 1;
int CIdvBranch::m_nRoundRobinWindLevel = 0;
CFrondEngine* CIdvBranch::m_pFrondEngine = NULL;
///////////////////////////////////////////////////////////////////////
// CIdvBranch::CIdvBranch definition
CIdvBranch::CIdvBranch(CIdvBranch* pParent) :
m_pParent(pParent),
m_fPercentOfParent(0.0f),
m_pVertices(NULL),
m_nVertexCount(-1),
m_usCrossSectionSegments(0),
m_nStartingOffset(-1),
m_fVolume(0.0f),
m_fFuzzyVolume(0.0f)
{
}
///////////////////////////////////////////////////////////////////////
// CIdvBranch::~CIdvBranch definition
CIdvBranch::~CIdvBranch( )
{
delete[] m_pVertices;
m_pVertices = NULL;
for (unsigned int i = 0; i < m_vChildren.size( ); ++i)
{
delete m_vChildren[i].m_pBranch;
m_vChildren[i].m_pBranch = NULL;
}
}
///////////////////////////////////////////////////////////////////////
// CIdvBranch::Compute definition
void CIdvBranch::Compute(unsigned int nSeed,
float fSize,
int nLevel,
const CVec3& cBasePos,
float fPercentOfParent,
const CRotTransform& cBaseTransform,
const CVec3& cZAxis,
CIndexedGeometry* pBranchGeometry,
vector<CBillboardLeaf*>& vLeaves,
float fWindWeight,
int nWindGroup,
float fParentRadius)
{
// find out if fronds instead of branches will be generated from this level
bool bFrond = m_pFrondEngine->Enabled( ) && nLevel >= m_pFrondEngine->GetLevel( );
// determine which wind group will be assigned to this branch (uses round-robin approach).
// the wind group will be adjusted to fit into the specified wind matrix start and span
// values after Compute()
//static int s_nWindGroup = nWindGroup;
if (nLevel == m_nWeightLevel)
nWindGroup = m_nRoundRobinWindLevel++;
// since level 0 only occurs once per tree, we use this condition to initialize
// the blossom vectors used in creating the leaves
if (nLevel == 0)
BuildBlossomVectors( );
// setup up the texture tiling parameters
const SIdvBranchInfo& sInfo = *m_vBranchInfo[nLevel];
if (!bFrond)
m_nStartingOffset = pBranchGeometry->GetVertexCounter( ); // remember where this branch's geometry is stored in the branch geometry class
m_fPercentOfParent = fPercentOfParent;
// compute the flare entries
if (!bFrond)
ComputeFlareEntries(sInfo);
// this branch was created from a parent - fPercentOfParent is the location
// (from 0.0 to 1.0) along the length of the parent where this branch was created.
// the attributes that govern this branch start by looking at the parent's
// attributes at the point of the child's creation.
float fLength = sInfo.GetLength( )->Evaluate(fPercentOfParent) * fSize;
float fSegmentLength = fLength / sInfo.m_nSegments;
float fAzimuth = m_cRandom.GetUniform(-180.0f, 180.0f);
float fStartAngle = sInfo.GetStartAngle( )->Evaluate(fPercentOfParent);
float fGravity = sInfo.GetGravity( )->Evaluate(fPercentOfParent);
float fRadius = sInfo.GetRadius( )->Evaluate(fPercentOfParent) * fSize;
float fFlexibility = sInfo.GetFlexibility( )->Evaluate(fPercentOfParent);
m_usCrossSectionSegments = static_cast<unsigned short>(sInfo.m_nCrossSectionSegments);
// set tex coord tiling
float fTileOffset = sInfo.m_bRandomTCoordOffset ? ((fAzimuth + 180.0f / 360.0f) + fPercentOfParent) : 0.0f;
const CVec3 cTile(sInfo.m_bSTileAbsolute ? sInfo.m_fSTile : sInfo.m_fSTile * fRadius * c_fTwoPi,
sInfo.m_bTTileAbsolute ? sInfo.m_fTTile : sInfo.m_fTTile * (fLength / fSize),
(nSeed + m_vChildren.size( )) % 2 == 0 ? sInfo.m_fTwist : -sInfo.m_fTwist);
// make sure radius of this child branch does not exceed radius at parent
float fFirstRadius = fRadius * sInfo.GetRadiusScale( )->Evaluate(0.0f);
if (fParentRadius > 0.0f &&
fFirstRadius > fParentRadius * c_fParentRadiusFactor)
fRadius = fFirstRadius = fParentRadius * c_fParentRadiusFactor;
// setup geometry information, passing indices into pBranchGeometry
m_nVertexCount = sInfo.m_nSegments + 1;
if (m_nVertexCount >= 2)
{
m_pVertices = new SIdvBranchVertex[m_nVertexCount];
if (!bFrond)
{
unsigned short nIndexCount = static_cast<unsigned short>((m_nVertexCount - 1) * (2 + 2 * (sInfo.m_nCrossSectionSegments + 1)));
unsigned short* pBranchStrip = new unsigned short[nIndexCount];
unsigned short nStart = pBranchGeometry->GetVertexCounter( );
int nVertexIndex = 0, nIndex2 = 0;
for (int i = 0; i < m_nVertexCount - 1; ++i)
{
int nLoopAround = nIndex2;
for (int j = 0; j < sInfo.m_nCrossSectionSegments + 1; ++j)
{
pBranchStrip[nVertexIndex++] = static_cast<unsigned short>(nStart + (sInfo.m_nCrossSectionSegments + 1) + nIndex2);
pBranchStrip[nVertexIndex++] = static_cast<unsigned short>(nStart + nIndex2);
nIndex2++;
}
// repeat this vertex so we can combine the multiple segments of one branch
pBranchStrip[nVertexIndex++] = static_cast<unsigned short>(nStart + (sInfo.m_nCrossSectionSegments + 1) + nLoopAround);
// repeat it again to counteract the clockwise vertex ordering that would result otherwise
pBranchStrip[nVertexIndex++] = static_cast<unsigned short>(nStart + (sInfo.m_nCrossSectionSegments + 1) + nLoopAround);
}
pBranchGeometry->AddStrip(0, pBranchStrip, nIndexCount);
pBranchGeometry->AdvanceStripCounter( );
}
// compute the first vertex of the branch - it's different than the
// subsequent vertices.
SIdvBranchVertex* pFirstVertex = m_pVertices;
pFirstVertex->m_cPos = cBasePos;
float fAzimuthDisturbance = sInfo.GetDisturbance( )->ScaledVariance(0.0f);
float fPitchDisturbance = sInfo.GetDisturbance( )->ScaledVariance(0.0f);
pFirstVertex->m_cTrans = cBaseTransform;
CVec3 cRotAxis = cBaseTransform * cZAxis;
pFirstVertex->m_cTrans.RotateAxis(fAzimuth, cRotAxis);
pFirstVertex->m_cTrans.RotateYZ(fStartAngle + fPitchDisturbance, fAzimuthDisturbance);
pFirstVertex->m_cDirection = g_cOut * pFirstVertex->m_cTrans;
pFirstVertex->m_fRadius = fRadius * sInfo.GetRadiusScale( )->Evaluate(0.0f);
float fVertexFlexibility = fFlexibility * (sInfo.GetFlexibilityScale( )->Evaluate(0.0f));
// adjust first vertex for gravity effect
float fGravityAngle(VecRad2Deg(pFirstVertex->m_cDirection.AngleBetween(g_cDown)));
float fFacingFactor(1.0f - c_fInverseOf90 * (fabsf(90.0f - fGravityAngle)));
CVec3 cAdjustmentAxis(pFirstVertex->m_cDirection * g_cDown);
cAdjustmentAxis.Normalize( );
float fGravityProfile = -1.0f * ((sInfo.GetAngleProfile( )->Evaluate(0.0f) - 0.5f) * 2.0f);
CRotTransform cTransformAdjustment;
cTransformAdjustment.RotateAxisFromIdentity(fGravityProfile * fGravity * fGravityAngle * fFacingFactor, cAdjustmentAxis);
pFirstVertex->m_cTrans = pFirstVertex->m_cTrans * cTransformAdjustment;
pFirstVertex->m_cDirection = g_cOut * pFirstVertex->m_cTrans;
// build the cross section associated with this first vertex
float fCrossSectionWeight = fWindWeight;
if (nLevel <= m_nWeightLevel)
fCrossSectionWeight = pFirstVertex->m_fWindWeight = ComputeVertexWeight(nLevel, 0.0f, fVertexFlexibility);
if (!bFrond)
{
// regular branch geometry
BuildCrossSection(pFirstVertex, 0.0f, m_usCrossSectionSegments, pBranchGeometry, cTile, fCrossSectionWeight, nWindGroup, fTileOffset);
}
else
{
// start a new frond and add a vertex
m_pFrondEngine->StartGuide( );
m_pFrondEngine->AddGuideVertex(pFirstVertex->m_cPos, pFirstVertex->m_cTrans, fCrossSectionWeight, nWindGroup);
}
float fRunningLength = 0.0f;
m_pVertices[0].m_fRunningLength = 0.0f;
// calculate the rest of the branch's vertices
SIdvBranchVertex* pLastVertex = pFirstVertex;
int i;
for (i = 1; i < m_nVertexCount; ++i)
{
SIdvBranchVertex* pVertex = m_pVertices + i;
// calculate how far along the branch's length we are
float fLinearProgress = i / static_cast<float>(m_nVertexCount - 1);
float fProgress = powf(fLinearProgress, sInfo.m_fSegmentPackingExponent);
fSegmentLength = (fProgress * fLength) - fRunningLength;
// evaluate radius and flexibility
pVertex->m_fRadius = fRadius * sInfo.GetRadiusScale( )->Evaluate(fProgress);
float fLocalVertexFlexibility = fFlexibility * (sInfo.GetFlexibilityScale( )->Evaluate(fProgress));
// adjust growth direction, taking disturbance into account
pVertex->m_cTrans = pLastVertex->m_cTrans;
pVertex->m_cDirection = g_cOut * pVertex->m_cTrans;
// adjust for gravity effect
float fLocalGravityAngle = VecRad2Deg(pVertex->m_cDirection.AngleBetween(g_cDown));
float fLocalFacingFactor = (1.0f - c_fInverseOf90 * (fabsf(90.0f - fLocalGravityAngle)));
CVec3 cThisAdjustmentAxis(pVertex->m_cDirection * g_cDown);
cThisAdjustmentAxis.Normalize( );
float fLocalGravityProfile = -1.0f * ((sInfo.GetAngleProfile( )->Evaluate(fProgress) - 0.5f) * 2.0f);
cTransformAdjustment.RotateAxisFromIdentity(fLocalGravityProfile * fGravity * fLocalGravityAngle * fLocalFacingFactor, cThisAdjustmentAxis);
pVertex->m_cTrans = pVertex->m_cTrans * cTransformAdjustment;
// keep the branch from being perfectly straight
float fLocalAzimuthDisturbance = sInfo.GetDisturbance( )->ScaledVariance(fProgress);
float fLocalPitchDisturbance = sInfo.GetDisturbance( )->ScaledVariance(fProgress);
pVertex->m_cTrans.RotateYZ(fLocalPitchDisturbance, fLocalAzimuthDisturbance);
pVertex->m_cDirection = g_cOut * pVertex->m_cTrans;
// apply final vertex position
pVertex->m_cPos = pLastVertex ? pLastVertex->m_cPos + (pLastVertex->m_cDirection * fSegmentLength) : cBasePos;
// pass geometry data into the pBranchGeometry class via BuildCrossSection()
float fLocalCrossSectionWeight = fWindWeight;
if (nLevel <= m_nWeightLevel)
fLocalCrossSectionWeight = pVertex->m_fWindWeight = ComputeVertexWeight(nLevel, fProgress, fLocalVertexFlexibility);
if (!bFrond)
{
// regular branch geometry
BuildCrossSection(pVertex, fProgress, m_usCrossSectionSegments, pBranchGeometry, cTile, fLocalCrossSectionWeight, nWindGroup, fTileOffset);
}
else
{
// frond geometry
m_pFrondEngine->AddGuideVertex(pVertex->m_cPos, pVertex->m_cTrans, fLocalCrossSectionWeight, nWindGroup);
}
pLastVertex = pVertex;
fRunningLength += fSegmentLength;
m_pVertices[i].m_fRunningLength = fRunningLength;
}
if (bFrond)
{
m_pFrondEngine->EndGuide(fFirstRadius);
}
else
{
pBranchGeometry->SetVertexCounter(static_cast<unsigned short>(m_nStartingOffset));
ComputeBranchNormals(pBranchGeometry, m_usCrossSectionSegments);
}
// this branch's geometry is complete, now determine the number of children -
// children can be additional branches or "buds" that contain no actual geometry
// but are used as placeholders for leaf clusters
int nNumChildBranches = static_cast<int>((sInfo.m_fFrequency / fSize) * fLength);
++nLevel; // level 0 = trunk, level 1 = trunk's immediate children, etc.
// bChildrenAreBuds tells us if buds will be created from this branch
bool bChildrenAreBuds = nLevel >= static_cast<int>(m_vBranchInfo.size( ) - 1);
if (!bChildrenAreBuds || m_bComputeLeaves)
{
for (i = 0; i < nNumChildBranches; ++i)
{
SIdvBranch sBranch;
// the random number generator is reseeded here in order to make EstimateCompute give
// exact predictions of the size of the tree. it is only reseeded for normal branches
// and not buds because it is a relatively expensive operation
if (!bChildrenAreBuds)
{
nSeed += 3;
m_cRandom.Reseed(nSeed);
}
// make sure that at least one branch (the first one) grows near the end of the parent.
// trust us, this makes for better looking trees.
float fPercent = 0.0f;
if (!bChildrenAreBuds && i == 0)
fPercent = m_cRandom.GetUniform(VecInterpolate(sInfo.m_fFirstBranch, sInfo.m_fLastBranch, 0.85f), VecInterpolate(sInfo.m_fFirstBranch, sInfo.m_fLastBranch, 0.95f));
else
fPercent = m_cRandom.GetUniform(sInfo.m_fFirstBranch, sInfo.m_fLastBranch);
// find out which of the parent's vertexes the child is created between
FillBranch(sBranch, fPercent * fLength);
// what's the vertex weight where the child will be spawned?
float fWeightAtChild = fWindWeight;
if (nLevel - 1 == m_nWeightLevel)
fWeightAtChild = VecInterpolate(m_pVertices[sBranch.m_nPreVertexIndex].m_fWindWeight, m_pVertices[sBranch.m_nPreVertexIndex + 1].m_fWindWeight, sBranch.m_fPercent);
// another reseeding operation (for predictability in EstimateCompute)
if (!bChildrenAreBuds)
{
m_cRandom.Reseed(nSeed);
(void) m_cRandom.GetUniform(0.0f, 100.0f);
}
// allocate child CIdvBranch
CVec3 cPos = VecInterpolate(m_pVertices[sBranch.m_nPreVertexIndex].m_cPos, m_pVertices[sBranch.m_nPreVertexIndex + 1].m_cPos, sBranch.m_fPercent);
float fProfilePercent = (fPercent - sInfo.m_fFirstBranch) / (sInfo.m_fLastBranch - sInfo.m_fFirstBranch);
if (bChildrenAreBuds)
{
// create the bud and possibly the leaf
ComputeBud(this, fSize, nLevel, cPos, fProfilePercent, m_pVertices[sBranch.m_nPreVertexIndex].m_cTrans, m_pVertices[sBranch.m_nPreVertexIndex].m_cDirection, vLeaves, fWeightAtChild, nWindGroup);
}
else
{
// make a new child branch and recusively call CIdvBranch::Compute()
float fRadiusOfParentAtBranch = VecInterpolate(m_pVertices[sBranch.m_nPreVertexIndex].m_fRadius, m_pVertices[sBranch.m_nPreVertexIndex + 1].m_fRadius, sBranch.m_fPercent);
sBranch.m_pBranch = new CIdvBranch(this);
sBranch.m_pBranch->Compute(nSeed, fSize, nLevel, cPos, fProfilePercent, m_pVertices[sBranch.m_nPreVertexIndex].m_cTrans, m_pVertices[sBranch.m_nPreVertexIndex].m_cDirection, pBranchGeometry, vLeaves, fWeightAtChild, nWindGroup, fRadiusOfParentAtBranch);
if (!m_pFrondEngine->Enabled( ) || (nLevel < m_pFrondEngine->GetLevel( )))
{
m_vChildren.push_back(sBranch);
}
else
{
delete sBranch.m_pBranch;
sBranch.m_pBranch = NULL;
}
}
}
}
if (bChildrenAreBuds)
m_vLocalLeaves.clear( );
ComputeVolume( );
}
}
///////////////////////////////////////////////////////////////////////
// CIdvBranch::ComputeBud definition
void CIdvBranch::ComputeBud(const CIdvBranch* pParent,
float fSize,
int nLevel,
const CVec3& cBasePos,
float fPercentOfParent,
const CRotTransform& cBaseTransform,
const CVec3& cZAxis,
vector<CBillboardLeaf*>& vLeaves,
float fWindWeight,
int nWindGroup) const
{
const SIdvBranchInfo& sInfo = *m_vBranchInfo[nLevel];
const float c_fStartAngle = 60.0f;
// always need 2 vertices for a bud - one on the parent branch and another for the leaf location
const int c_nBudVertexCount = 2;
SIdvBranchVertex acVertices[c_nBudVertexCount];
float fLength = sInfo.GetLength( )->Evaluate(fPercentOfParent) * fSize;
float fSegmentLength = fLength / sInfo.m_nSegments;
float fAzimuth = m_cRandom.GetUniform(-180.0f, 180.0f);
CVec3 cRotAxis = cBaseTransform * cZAxis;
// compute position and orientation of first vertex
SIdvBranchVertex *pFirstVertex = acVertices + 0;
pFirstVertex->m_cPos = cBasePos;
pFirstVertex->m_cTrans = cBaseTransform;
pFirstVertex->m_cTrans.RotateAxis(fAzimuth, cRotAxis);
pFirstVertex->m_cTrans.RotateY(c_fStartAngle);
pFirstVertex->m_cDirection = g_cOut * pFirstVertex->m_cTrans;
// compute leaf vertex
SIdvBranchVertex *pVertex = acVertices + 1;
pVertex->m_cPos = pFirstVertex->m_cPos + (pFirstVertex->m_cDirection * fSegmentLength);
// compute geometric normal of leaf
CVec3 cNormal = acVertices[1].m_cPos - acVertices[0].m_cPos;
cNormal.Normalize( );
// compute the general direction the parent branch was growing
CVec3 cParentDir;
if (pParent == NULL || pParent->m_nVertexCount == 0)
cParentDir = cNormal;
else
{
CVec3 cBase = pParent->m_pVertices[0].m_cPos;
// back up to the dimming level if necessary
if (m_pLeafInfo->m_nBlossomLevel != 0)
{
int nLevels = 0;
const CIdvBranch* pBranch = pParent;
while (pBranch && nLevels < m_pLeafInfo->m_nBlossomLevel)
{
pBranch = pBranch->GetParent( );
nLevels++;
}
if (pBranch)
cBase = pBranch->m_pVertices[0].m_cPos;
}
cParentDir = acVertices[1].m_cPos - cBase;
}
cParentDir.Normalize( );
// attempt to grow leaf based on this bud's parameters
MakeLeaf(acVertices[c_nBudVertexCount - 1].m_cPos,
fPercentOfParent,
this,
cNormal,
cParentDir,
fWindWeight,
nWindGroup,
vLeaves);
}
///////////////////////////////////////////////////////////////////////
// CIdvBranch::ComputeLod definition
//
// Each of the discrete branch LOD's share the same vertex table. A lower
// LOD of the branch structure simply drops some of the lower-volume branches.
// ComputeLod() assigns the new indexes for the requested LOD.
void CIdvBranch::ComputeLod(int nDiscreteLodLevel, CIndexedGeometry* pGeometry) const
{
// new cross section count
if (m_usCrossSectionSegments >= 2)
{
int nTargetCrossSection = m_usCrossSectionSegments;
float fCrossSectionStep = !nTargetCrossSection ? 1.0f : float(m_usCrossSectionSegments) / nTargetCrossSection;
// new vertex count
int nTargetVertexCount = m_nVertexCount;
int nIndexCount = (nTargetVertexCount - 1) * (2 + 2 * (nTargetCrossSection + 1));
unsigned short* pStrip = new unsigned short[nIndexCount];
pGeometry->AddStrip(static_cast<unsigned short>(nDiscreteLodLevel), pStrip, static_cast<unsigned short>(nIndexCount));
int nInterleavedIndex = 0;
float fCrossSectionIndex = 0.0f;
int i;
for (i = 0; i < nTargetVertexCount - 1; ++i)
{
int nLoopAround = int(fCrossSectionIndex);
// first vertex in cross section
pStrip[nInterleavedIndex++] = static_cast<unsigned short>(m_nStartingOffset + (m_usCrossSectionSegments + 1) + int(fCrossSectionIndex));
pStrip[nInterleavedIndex++] = static_cast<unsigned short>(m_nStartingOffset + int(fCrossSectionIndex));
fCrossSectionIndex += fCrossSectionStep;
// we use crosssection + 1 because we must loop around and include the first point again (tex coords)
for (int j = 1; j < nTargetCrossSection; ++j)
{
pStrip[nInterleavedIndex++] = static_cast<unsigned short>(m_nStartingOffset + (m_usCrossSectionSegments + 1) + int(fCrossSectionIndex));
pStrip[nInterleavedIndex++] = static_cast<unsigned short>(m_nStartingOffset + int(fCrossSectionIndex));
fCrossSectionIndex += fCrossSectionStep;
}
// last vertex in cross section
pStrip[nInterleavedIndex++] = static_cast<unsigned short>(m_nStartingOffset + (m_usCrossSectionSegments + 1) + nLoopAround + m_usCrossSectionSegments);
pStrip[nInterleavedIndex++] = static_cast<unsigned short>(m_nStartingOffset + nLoopAround + m_usCrossSectionSegments);
// get ready for next cross section
fCrossSectionIndex = static_cast<float>(nLoopAround + (m_usCrossSectionSegments + 1));
// repeat this vertex so we can combine the multiple segments of one branch
pStrip[nInterleavedIndex++] = static_cast<unsigned short>(m_nStartingOffset + (m_usCrossSectionSegments + 1) + nLoopAround);
// repeat it again to counteract the clockwise vertex ordering that would result otherwise
pStrip[nInterleavedIndex++] = static_cast<unsigned short>(m_nStartingOffset + (m_usCrossSectionSegments + 1) + nLoopAround);
}
pGeometry->AdvanceStripCounter( );
}
}
///////////////////////////////////////////////////////////////////////
// CIdvBranch::BuildBranchVector definition
//
// This is a convenience function that builds a linear vector from the
// branch tree structure.
void CIdvBranch::BuildBranchVector(vector<CIdvBranch*>& vAllBranches)
{
vAllBranches.push_back(this);
for (unsigned int i = 0; i < m_vChildren.size( ); ++i)
m_vChildren[i].m_pBranch->BuildBranchVector(vAllBranches);
}
///////////////////////////////////////////////////////////////////////
// class CBranchVolumeSorter definition
//
// Function object used to sort the branches by volume.
class CBranchVolumeSorter
{
public:
bool operator()(const CIdvBranch* pLeft, const CIdvBranch* pRight)
{
return (pLeft->GetFuzzyVolume( ) > pRight->GetFuzzyVolume( ));
}
};
///////////////////////////////////////////////////////////////////////
// CIdvBranch::SortBranchVector definition
void CIdvBranch::SortBranchVector(vector<CIdvBranch*>& vAllBranches)
{
sort(vAllBranches.begin( ), vAllBranches.end( ), CBranchVolumeSorter( ));
}
///////////////////////////////////////////////////////////////////////
// CIdvBranch::Parse definition
void CIdvBranch::Parse(CTreeFileAccess& cFile)
{
ClearBranchInfo( );
int nSize = cFile.ParseToken( );
for (int i = 0; i < nSize; ++i)
{
SIdvBranchInfo sInfo;
int nToken = cFile.ParseToken( );
if (nToken != File_BeginBranchLevel)
throw(IdvFileError("malformed branch data"));
nToken = cFile.ParseToken( );
do
{
switch (nToken)
{
case File_Branch_Disturbance:
sInfo.SetDisturbance(cFile.ParseBranchParameter( ));
break;
case File_Branch_EndAngle:
sInfo.SetGravity(cFile.ParseBranchParameter( ));
break;
case File_Branch_Flexibility:
sInfo.SetFlexibility(cFile.ParseBranchParameter( ));
break;
case File_Branch_FlexibilityScale:
sInfo.SetFlexibilityScale(cFile.ParseBranchParameter( ));
break;
case File_Branch_Length:
sInfo.SetLength(cFile.ParseBranchParameter( ));
break;
case File_Branch_Radius:
sInfo.SetRadius(cFile.ParseBranchParameter( ));
break;
case File_Branch_RadiusScale:
sInfo.SetRadiusScale(cFile.ParseBranchParameter( ));
break;
case File_Branch_StartAngle:
sInfo.SetStartAngle(cFile.ParseBranchParameter( ));
break;
case File_Branch_CrossSectionSegments:
sInfo.m_nCrossSectionSegments = cFile.ParseInt( );
break;
case File_Branch_Segments:
sInfo.m_nSegments = cFile.ParseInt( );
break;
case File_Branch_FirstBranch:
sInfo.m_fFirstBranch = cFile.ParseFloat( );
break;
case File_Branch_LastBranch:
sInfo.m_fLastBranch = cFile.ParseFloat( );
break;
case File_Branch_Frequency:
sInfo.m_fFrequency = cFile.ParseFloat( );
break;
case File_Branch_STile:
sInfo.m_fSTile = cFile.ParseFloat( );
break;
case File_Branch_TTile:
sInfo.m_fTTile = cFile.ParseFloat( );
break;
case File_Branch_STileAbsolute:
sInfo.m_bSTileAbsolute = cFile.ParseBool( );
break;
case File_Branch_TTileAbsolute:
sInfo.m_bTTileAbsolute = cFile.ParseBool( );
break;
default:
throw(IdvFileError("malformed general branch information"));
}
nToken = cFile.ParseToken( );
} while (nToken != File_EndBranchLevel);
}
int nToken = cFile.ParseToken( );
if (nToken != File_EndBranchInfo)
throw(IdvFileError("malformed branch data"));
}
///////////////////////////////////////////////////////////////////////
// CIdvBranch::BuildBlossomVectors definition
//
// Each leaf texture's index is added either to m_vBlossoms or
// m_vNonBlossoms. Each texture index also has a mirror that is the
// same texture with the s coordinates mirrored.
void CIdvBranch::BuildBlossomVectors(void)
{
m_vBlossoms.clear( );
m_vNonBlossoms.clear( );
for (unsigned int i = 0; i < m_pLeafInfo->m_vLeafTextures.size( ); ++i)
if (m_pLeafInfo->m_vLeafTextures[i].m_bBlossom)
{
m_vBlossoms.push_back(i * 2);
m_vBlossoms.push_back(i * 2 + 1);
}
else
{
m_vNonBlossoms.push_back(i * 2);
m_vNonBlossoms.push_back(i * 2 + 1);
}
}
///////////////////////////////////////////////////////////////////////
// CIdvBranch::IsBlossom definition
//
// Uses the parameters specified in SpeedTreeCAD to determine if position
// fPercentOfParent on branch pParent will result in a blossom.
bool CIdvBranch::IsBlossom(const CIdvBranch* pParent, float fPercentOfParent) const
{
bool bIsBlossom = false;
float fTestPercent = fPercentOfParent;
if (!m_pLeafInfo->m_nBlossomLevel)
{
// blossoms are created at the branch level m_pLeafInfo->m_nBlossomLevel - pBranch
// is advanced to the correct level
int nLevels = 1; // "this" is already pointing to the parent of the bud so start one level up
const CIdvBranch* pBranch = pParent;
while (pBranch &&
pBranch->GetParent( ) &&
nLevels < m_pLeafInfo->m_nBlossomLevel)
{
pBranch = pBranch->GetParent( );
nLevels++;
}
if (pBranch)
fTestPercent = pBranch->GetPercentOfParent( );
}
// random numbers are used because not every leaf should be a blossom - the weighting
// of which is specified by the user in SpeedTreeCAD
if (fTestPercent > m_pLeafInfo->m_fBlossomDistance &&
m_cRandom.GetUniform(0.0f, 1.0f) <= m_pLeafInfo->m_fBlossomWeighting)
bIsBlossom = true;
return bIsBlossom;
}
///////////////////////////////////////////////////////////////////////
// CIdvBranch::MakeLeaf definition
void CIdvBranch::MakeLeaf(const CVec3& cPos,
float fPercent,
const CIdvBranch* pParent,
const CVec3& cNormal,
const CVec3& cParentDir,
float fWindWeight,
int nWindGroup,
vector<CBillboardLeaf*>& vLeaves) const
{
// are there any leaf textures to use?
if (!m_pLeafInfo->m_vLeafTextures.empty( ))
{
int nTextureIndex = 0;
// determine if this leaf will be a blossom
bool bIsBlossom = m_vBlossoms.empty( ) ? false : IsBlossom(pParent, fPercent);
if (bIsBlossom)
{
// randomly pick which blossom it will be based on the m_vBlossoms vector
unsigned int nIndex = 0;
if (m_vBlossoms.size( ) > 1)
nIndex = static_cast<unsigned int>(m_cRandom.GetUniform(0.0, 100000.0)) % m_vBlossoms.size( );
nTextureIndex = m_vBlossoms[nIndex];
}
else
{
// randomly pick which non-blossom texture will be used
unsigned int nIndex = 0;
if (m_vNonBlossoms.size( ) > 1)
{
float fRandom = m_cRandom.GetUniform(0.0, 1000000.0);
nIndex = static_cast<unsigned int>(fRandom) % m_vNonBlossoms.size( );
nTextureIndex = m_vNonBlossoms[nIndex];
}
else if (m_vNonBlossoms.empty( ))
return;
else
nTextureIndex = m_vNonBlossoms[0];
}
// test to see if the leaf will be too crowded
bool bValid = true;
switch (m_pLeafInfo->m_eCollisionType)
{
case SIdvLeafInfo::NONE: // no leaf will ever be crowded
break;
case SIdvLeafInfo::BRANCH: // test only against leaves that are spawned from a common branch
bValid = RoomForLeaf(cPos, nTextureIndex / 2, m_vLocalLeaves);
break;
case SIdvLeafInfo::TREE: // test against all leaves in the tree
bValid = RoomForLeaf(cPos, nTextureIndex / 2, vLeaves);
break;
default:
st_assert("invalid case in switch in CIdvBranch::MakeLeaf()");
break;
}
if (bValid)
{
float fColorScale = 1.0f;
// if dimming is enabled, modify the color scalar to darken it an appropriate amount.
// m_nBlossomLevel is also used as the dimming level
float fDimPercent = fPercent;
if (m_pLeafInfo->m_bDimming)
{
if (m_pLeafInfo->m_nBlossomLevel != 0)
{
int nLevels = 0;
const CIdvBranch* pBranch = pParent;
while (pBranch && nLevels < m_pLeafInfo->m_nBlossomLevel)
{
fDimPercent *= pBranch->GetPercentOfParent( );
pBranch = pBranch->GetParent( );
nLevels++;
}
}
fColorScale = VecInterpolate(fDimPercent, 1.0f, 1.0f - m_pLeafInfo->m_fDimmingScalar);
}
// actually make the leaf now
//lint -esym(429,pLeaf) { pLeaf is not freed or returned from MakeLeaf(), but it is stored in vLeaves for later deletion }
CBillboardLeaf* pLeaf = new CBillboardLeaf(cPos, short(255.0f * fColorScale),
static_cast<int>(m_cRandom.GetUniform(0.0, 10000.0)) % m_pLeafInfo->m_nNumRockingGroups, fWindWeight, nWindGroup);
vLeaves.push_back(pLeaf);
if (m_pLeafInfo->m_eCollisionType == SIdvLeafInfo::BRANCH)
m_vLocalLeaves.push_back(pLeaf);
const SIdvLeafTexture& sTexture = m_pLeafInfo->m_vLeafTextures[nTextureIndex / 2];
pLeaf->SetTextureIndex(nTextureIndex);
// let the color variance perturb the leaf normal (for dynamic lighting)
CVec3 cAdjustedNormal;
if (cNormal.Magnitude( ) < 0.0001)
cAdjustedNormal = cParentDir;
else
cAdjustedNormal = VecInterpolate(cParentDir, cNormal, sTexture.m_fColorVariance);
cAdjustedNormal.Normalize( );
CVec3 cDown(0.0f, 0.0f, -1.0f);
if (m_pLeafInfo->m_bDimming && m_pLightingEngine->GetLeafLightingMethod( ) == CSpeedTreeRT::LIGHT_DYNAMIC)
{
cDown.Set(0.0f, 0.0f, -(1.0f - fDimPercent) * m_pLeafInfo->m_fDimmingScalar * 2.0f);
cAdjustedNormal = cAdjustedNormal + cDown;
cAdjustedNormal.Normalize( );
}
pLeaf->SetNormal(cAdjustedNormal);
// bump mapping
CVec3 cTangent = cAdjustedNormal * cDown;
cTangent.Normalize( );
pLeaf->SetTangent(cTangent);
CVec3 cBinormal = cTangent * cAdjustedNormal;
cBinormal.Normalize( );
pLeaf->SetBinormal(cBinormal);
// directly adjust the color (for static lighting)
float fOffset = m_cRandom.GetUniform(-sTexture.m_fColorVariance, sTexture.m_fColorVariance);
CVec3 cColor(sTexture.m_cColor[0] + fOffset, sTexture.m_cColor[1] + fOffset, sTexture.m_cColor[2] + fOffset);
pLeaf->SetColor(cColor);
}
}
}
///////////////////////////////////////////////////////////////////////
// CIdvBranch::RoomForLeaf definition
//
// This function checks against the leaves in the vLeaves vector to see if
// the leaf at position cPos will be too crowded.
bool CIdvBranch::RoomForLeaf(const CVec3& cPos, int nTextureIndex, const vector<CBillboardLeaf*>& vLeaves) const
{
bool bIsRoom = true;
if (!m_pLeafInfo->m_vLeafTextures.empty( ))
{
// get the larger of the width and height of the leaf
const SIdvLeafTexture& sTexture = m_pLeafInfo->m_vLeafTextures[nTextureIndex];
float fLargerSide = sTexture.m_cSizeUsed[0] > sTexture.m_cSizeUsed[1] ? sTexture.m_cSizeUsed[0] : sTexture.m_cSizeUsed[1];
// adjust the size by the spacing tolerance (allows adjustment of leaf density)
fLargerSide *= m_pLeafInfo->m_fSpacingTolerance;
for (unsigned int i = 0; i < vLeaves.size( ) && bIsRoom; ++i)
{
const CVec3& cOther = vLeaves[i]->GetPosition( );
if ( ((cPos[0] < cOther[0] + fLargerSide) && (cPos[0] > cOther[0] - fLargerSide)) &&
((cPos[1] < cOther[1] + fLargerSide) && (cPos[1] > cOther[1] - fLargerSide)) &&
((cPos[2] < cOther[2] + fLargerSide) && (cPos[2] > cOther[2] - fLargerSide)) )
bIsRoom = false;
}
}
else
bIsRoom = false;
return bIsRoom;
}
///////////////////////////////////////////////////////////////////////
// CIdvBranch::BuildCrossSection definition
//
// This function "skins" the branch at pVertex.
void CIdvBranch::BuildCrossSection(const SIdvBranchVertex* pVertex,
float fBranchProgress,
int nSegments,
CIndexedGeometry* pBranchGeometry,
const CVec3& cTile,
float fWindWeight,
int nWindGroup,
float fTileOffset) const
{
float fProgress = 0.0f;
const float fProgressInc = 1.0f / nSegments;
for (int i = 0; i <= nSegments; ++i)
{
float fAngle = c_fTwoPi * fProgress;
// compute and add tex coords
float afTexCoords[2];
afTexCoords[0] = fProgress * cTile[0];
// twist the map
afTexCoords[0] += cTile[2] * fBranchProgress;
afTexCoords[1] = (fBranchProgress + fTileOffset) * cTile[1];
pBranchGeometry->AddVertexTexCoord0(afTexCoords);
// compute and add normal
float fCosine = cosf(fAngle);
float fSine = sinf(fAngle);
CVec3 cNormal;
cNormal[0] = fCosine * pVertex->m_cTrans.m_afData[1][0] +
fSine * pVertex->m_cTrans.m_afData[2][0];
cNormal[1] = fCosine * pVertex->m_cTrans.m_afData[1][1] +
fSine * pVertex->m_cTrans.m_afData[2][1];
cNormal[2] = fCosine * pVertex->m_cTrans.m_afData[1][2] +
fSine * pVertex->m_cTrans.m_afData[2][2];
// bump mapping (compute tangent and binormal)
fCosine = cosf(fAngle + c_fHalfPi);
fSine = sinf(fAngle + c_fHalfPi);
CVec3 cTangent;
cTangent[0] = fCosine * pVertex->m_cTrans.m_afData[1][0] +
fSine * pVertex->m_cTrans.m_afData[2][0];
cTangent[1] = fCosine * pVertex->m_cTrans.m_afData[1][1] +
fSine * pVertex->m_cTrans.m_afData[2][1];
cTangent[2] = fCosine * pVertex->m_cTrans.m_afData[1][2] +
fSine * pVertex->m_cTrans.m_afData[2][2];
pBranchGeometry->AddVertexTangent(cTangent);
pBranchGeometry->AddVertexBinormal(cTangent * cNormal);
// adjust for flares
float fFlareAdjust = 1.0f, fFlareAdditions = 0.0f;
for (unsigned int j = 0; j < m_vFlares.size( ); ++j)
fFlareAdditions += m_vFlares[j].Distance(fAngle, fBranchProgress);
fFlareAdjust += fFlareAdditions;
// position (uses previous normal calculation)
float afOriginalPos[3];
afOriginalPos[0] = pVertex->m_cPos.m_afData[0] + (cNormal[0] * pVertex->m_fRadius);
afOriginalPos[1] = pVertex->m_cPos.m_afData[1] + (cNormal[1] * pVertex->m_fRadius);
afOriginalPos[2] = pVertex->m_cPos.m_afData[2] + (cNormal[2] * pVertex->m_fRadius);
if (fFlareAdjust != 1.0f)
{
float afPos[3];
afPos[0] = pVertex->m_cPos.m_afData[0] + (cNormal[0] * pVertex->m_fRadius * fFlareAdjust);
afPos[1] = pVertex->m_cPos.m_afData[1] + (cNormal[1] * pVertex->m_fRadius * fFlareAdjust);
afPos[2] = pVertex->m_cPos.m_afData[2] + (cNormal[2] * pVertex->m_fRadius * fFlareAdjust);
CVec3 cNewNormal(afPos[0] - afOriginalPos[0], afPos[1] - afOriginalPos[1], afPos[2] - afOriginalPos[2]);
cNewNormal.Normalize( );
cNormal = cNewNormal;
memcpy(afOriginalPos, afPos, 3 * sizeof(float));
}
pBranchGeometry->AddVertexCoord(afOriginalPos);
// set wind information
if (pBranchGeometry->IsVertexWeightingEnabled( ))
pBranchGeometry->AddVertexWind(fWindWeight, static_cast<unsigned char>(nWindGroup));
pBranchGeometry->AdvanceVertexCounter( );
fProgress += fProgressInc;
}
}
///////////////////////////////////////////////////////////////////////
// CIdvBranch::ComputeVertexWeight definition
float CIdvBranch::ComputeVertexWeight(int nLevel, float fProgress, float fFlexibility)
{
float fWeight = 1.0f;
if (nLevel == m_nWeightLevel)
fWeight -= fProgress * fFlexibility;
return fWeight;
}
///////////////////////////////////////////////////////////////////////
// CIdvBranch::ComputeVertexWeight definition
//
// To compute the volume of the branch, we will add the volumes
// of each segment. Each segment is rougly a cone with a non-zero
// radius at the top. The volume formula for this type of cone is:
//
// Volume = PI * (R2^2 + R1 * R2 + R1^2) * Height / 3
// Cross Sectional Area = Height * (R1 + R2)
void CIdvBranch::ComputeVolume(void)
{
if (m_pVertices && m_nVertexCount > 1)
{
m_fVolume = 0.0f;
for (int i = 0; i < m_nVertexCount - 1; ++i)
{
SIdvBranchVertex* pVertex1 = m_pVertices + i;
SIdvBranchVertex* pVertex2 = m_pVertices + i + 1;
float fDistance = pVertex1->m_cPos.Distance(pVertex2->m_cPos);
m_fVolume += fDistance * (pVertex1->m_fRadius + pVertex2->m_fRadius);
}
}
}
///////////////////////////////////////////////////////////////////////
// CIdvBranch::FillBranch definition
//
// FillBranch() determines the vertex of the parent just before the child
// is grown - stored as sBranch.m_nPreVertexIndex. sBranch.m_fPercent
// is the percentage distance from this vertex to the next where the child
// is created.
void CIdvBranch::FillBranch(SIdvBranch& sBranch, float fChildGrowthPos) const
{
if (m_pVertices && m_nVertexCount >= 2)
{
sBranch.m_nPreVertexIndex = 0;
for (int i = 1; i < m_nVertexCount; ++i)
{
if (fChildGrowthPos < m_pVertices[i].m_fRunningLength)
{
sBranch.m_nPreVertexIndex = i - 1;
break;
}
}
sBranch.m_fPercent = (fChildGrowthPos - m_pVertices[sBranch.m_nPreVertexIndex].m_fRunningLength) /
(m_pVertices[sBranch.m_nPreVertexIndex + 1].m_fRunningLength - m_pVertices[sBranch.m_nPreVertexIndex].m_fRunningLength);
}
}
///////////////////////////////////////////////////////////////////////
// CIdvBranch::ComputeFlareEntries definition
//
// This function uses the system random numbers in an attempt to preserve
// seed compatibility and not disturb EstimateCompute( ).
void CIdvBranch::ComputeFlareEntries(const SIdvBranchInfo& sInfo)
{
if (sInfo.m_nNumFlares != 0)
{
float fInitialAngle = GetRandom(0.0f, c_fTwoPi);
float fBalanceAngle = c_fTwoPi / (sInfo.m_nNumFlares);
for (int i = 0; i < sInfo.m_nNumFlares; ++i)
{
SIdvBranchFlare sFlare;
// andgle
sFlare.m_fAngle = fInitialAngle + (i * GetRandom(sInfo.m_fFlareBalance * fBalanceAngle, fBalanceAngle));
if (sFlare.m_fAngle > c_fTwoPi)
sFlare.m_fAngle -= c_fTwoPi;
// focus
sFlare.m_fRadialExponent = sInfo.m_fRadialExponent;
sFlare.m_fLengthExponent = sInfo.m_fLengthExponent;
// angle influence
sFlare.m_fRadialInfluence = VecDeg2Rad(GetRandom(sInfo.m_fRadialInfluence - sInfo.m_fRadialInfluenceVariance, sInfo.m_fRadialInfluence + sInfo.m_fRadialInfluenceVariance));
// distance influence
sFlare.m_fLengthInfluence = GetRandom(sInfo.m_fLengthDistance - sInfo.m_fLengthVariance, sInfo.m_fLengthDistance + sInfo.m_fLengthVariance);
// distance
sFlare.m_fDistance = GetRandom(sInfo.m_fRadialDistance - sInfo.m_fRadialVariance, sInfo.m_fRadialDistance + sInfo.m_fRadialVariance);
m_vFlares.push_back(sFlare);
}
}
}
///////////////////////////////////////////////////////////////////////
// CIdvBranch::ComputeBranchNormals definition
void CIdvBranch::ComputeBranchNormals(CIndexedGeometry* pBranchGeometry, unsigned short usSegments)
{
for (int i = 0; i < m_nVertexCount; ++i)
{
int nBase = i * (usSegments + 1);
for (int j = 0; j <= usSegments; ++j)
{
// get horizontal vector
int nHorizPrev;
if (j == 0)
nHorizPrev = nBase + usSegments - 1;
else
nHorizPrev = nBase + j - 1;
int nHorizNext;
if (j == usSegments)
nHorizNext = nBase + 1;
else
nHorizNext = nBase + j + 1;
nHorizPrev += m_nStartingOffset;
nHorizNext += m_nStartingOffset;
const float* pHorizPrev = pBranchGeometry->GetVertexCoord(nHorizPrev);
const float* pHorizNext = pBranchGeometry->GetVertexCoord(nHorizNext);
CVec3 cHoriz = CVec3(pHorizNext[0] - pHorizPrev[0], pHorizNext[1] - pHorizPrev[1], pHorizNext[2] - pHorizPrev[2]);
cHoriz.Normalize( );
// get vertical vector
int nVertPrev;
if (i == 0)
nVertPrev = nBase + j;
else
nVertPrev = ((i - 1) * (usSegments + 1)) + j;
int nVertNext;
if (i == m_nVertexCount - 1)
nVertNext = nBase + j;
else
nVertNext = ((i + 1) * (usSegments + 1)) + j;
nVertPrev += m_nStartingOffset;
nVertNext += m_nStartingOffset;
const float* pVertPrev = pBranchGeometry->GetVertexCoord(nVertPrev);
const float* pVertNext = pBranchGeometry->GetVertexCoord(nVertNext);
CVec3 cVert = CVec3(pVertNext[0] - pVertPrev[0], pVertNext[1] - pVertPrev[1], pVertNext[2] - pVertPrev[2]);
cVert.Normalize( );
// compute new normal
CVec3 cNormal = cHoriz * cVert;
if (m_pLightingEngine->GetBranchLightingMethod( ) == CSpeedTreeRT::LIGHT_STATIC)
{
float afColor[4];
m_pLightingEngine->ComputeStandardStaticLighting(cNormal, pBranchGeometry->GetVertexCoord(nBase), afColor);
pBranchGeometry->AddVertexColor(afColor);
}
else
pBranchGeometry->AddVertexNormal(cNormal);
pBranchGeometry->AdvanceVertexCounter( );
}
}
}