1075 lines
36 KiB
C++
Executable File
1075 lines
36 KiB
C++
Executable File
//-----------------------------------------------------------------------------
|
|
// Torque Game Engine
|
|
// Copyright (C) GarageGames.com, Inc.
|
|
//-----------------------------------------------------------------------------
|
|
|
|
#include "terrain/fluid.h"
|
|
#include "dgl/dgl.h"
|
|
|
|
//==============================================================================
|
|
// VARIABLES
|
|
//==============================================================================
|
|
|
|
fluid::vertex* fluid::m_pVertex = NULL;
|
|
s32 fluid::m_VAllocated = 0;
|
|
s32 fluid::m_VUsed = 0;
|
|
|
|
s16* fluid::m_pIndex = NULL;
|
|
s16* fluid::m_pINext = NULL;
|
|
s16 fluid::m_IOffset = 0;
|
|
s32 fluid::m_IAllocated = 0;
|
|
s32 fluid::m_IUsed = 0;
|
|
|
|
static f32 sSurfaceAtEye;
|
|
static f32 sFogZ;
|
|
//atic f32 sFogTable[64];
|
|
//atic f32 sFogAccessFactor;
|
|
|
|
//==============================================================================
|
|
// FUNCTIONS
|
|
//==============================================================================
|
|
|
|
#define L0 150.0f
|
|
#define L1 75.0f
|
|
|
|
f32 fluid::ComputeLOD( f32 Distance )
|
|
{
|
|
f32 LOD;
|
|
if( Distance > L0 ) return( 0.0f );
|
|
if( Distance < L1 ) return( 1.0f );
|
|
LOD = (L0 - Distance) / (L0-L1);
|
|
return( LOD );
|
|
}
|
|
|
|
#undef L0
|
|
#undef L1
|
|
|
|
//==============================================================================
|
|
// Frustrum clip planes: 0=T 1=B 2=L 3=R 4=N 5=F
|
|
|
|
byte fluid::ComputeClipBits( f32 X, f32 Y, f32 Z )
|
|
{
|
|
byte Result = 0x00;
|
|
s32 i;
|
|
|
|
for( i = 0; i < 6; i++ )
|
|
if( ((X * m_Plane[i].A) + (Y * m_Plane[i].B) + (Z * m_Plane[i].C)) < -m_Plane[i].D )
|
|
Result |= (1<<i);
|
|
|
|
return( Result );
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
fluid::vertex* fluid::AcquireVertices( s32 Count )
|
|
{
|
|
vertex* pResult;
|
|
|
|
if( m_VAllocated == 0 )
|
|
{
|
|
m_VAllocated = 100;
|
|
m_pVertex = (vertex*)MALLOC( m_VAllocated * sizeof(vertex) );
|
|
ASSERT( m_pVertex );
|
|
}
|
|
|
|
while( Count + m_VUsed > m_VAllocated )
|
|
{
|
|
m_VAllocated += 100;
|
|
m_pVertex = (vertex*)REALLOC( m_pVertex, m_VAllocated * sizeof(vertex) );
|
|
ASSERT( m_pVertex );
|
|
}
|
|
|
|
pResult = m_pVertex + m_VUsed;
|
|
m_IOffset = m_VUsed;
|
|
m_VUsed += Count;
|
|
|
|
return( pResult );
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
void fluid::AddTriangleIndices( s16 I1, s16 I2, s16 I3 )
|
|
{
|
|
if( m_IAllocated == 0 )
|
|
{
|
|
m_IAllocated = 100;
|
|
m_IUsed = 0;
|
|
m_pIndex = (s16*)MALLOC( m_IAllocated * sizeof(s16) );
|
|
m_pINext = m_pIndex;
|
|
ASSERT( m_pIndex );
|
|
}
|
|
|
|
if( m_IUsed+3 > m_IAllocated )
|
|
{
|
|
s32 Next = m_pINext - m_pIndex;
|
|
m_IAllocated += 100;
|
|
m_pIndex = (s16*)REALLOC( m_pIndex, m_IAllocated * sizeof(s16) );
|
|
m_pINext = m_pIndex + Next;
|
|
ASSERT( m_pIndex );
|
|
}
|
|
|
|
m_IUsed += 3;
|
|
|
|
*m_pINext = m_IOffset + I1; m_pINext++;
|
|
*m_pINext = m_IOffset + I2; m_pINext++;
|
|
*m_pINext = m_IOffset + I3; m_pINext++;
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
void fluid::ReleaseVertexMemory( void )
|
|
{
|
|
if( m_pVertex )
|
|
{
|
|
FREE( m_pVertex );
|
|
m_pVertex = NULL;
|
|
m_VAllocated = 0;
|
|
m_VUsed = 0;
|
|
}
|
|
|
|
if( m_pIndex )
|
|
{
|
|
FREE( m_pIndex );
|
|
m_pINext = NULL;
|
|
m_pIndex = NULL;
|
|
m_IAllocated = 0;
|
|
m_IUsed = 0;
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
/*
|
|
void fluid::BuildFogTable( void )
|
|
{
|
|
f32 Distance;
|
|
f32 Delta;
|
|
f32 Z;
|
|
s32 i;
|
|
|
|
Z = m_SurfaceZ - m_Eye.z;
|
|
Delta = m_VisibleDistance / 64.0f;
|
|
Distance = 0.0f;
|
|
|
|
for( i = 0; i < 64; i++ )
|
|
{
|
|
Distance += Delta;
|
|
sFogTable[i] = m_pFogFn( Distance, Z );
|
|
}
|
|
|
|
// And, create a multiplier we will use to access the table.
|
|
sFogAccessFactor = 64.0f / m_VisibleDistance;
|
|
}
|
|
*/
|
|
//==============================================================================
|
|
|
|
void fluid::RunQuadTree( bool& EyeSubmerged )
|
|
{
|
|
node Stack[ 32 ];
|
|
s32 Top = -1;
|
|
s32 i, j;
|
|
s32 I, J;
|
|
s32 BlocksPerRep = m_HighResMode ? 64 : 32;
|
|
|
|
// Build a fog sample table.
|
|
// BuildFogTable();
|
|
|
|
// We will use a single 'altitude' for all fog (regardless of waves).
|
|
sFogZ = m_SurfaceZ - m_Eye.z;
|
|
|
|
// Determine where fluid surface is directly above (or above) the eye.
|
|
sSurfaceAtEye = m_SurfaceZ + (SINE( (m_Eye.x * 0.05f) + m_Seconds ) +
|
|
SINE( (m_Eye.y * 0.05f) + m_Seconds )) * m_WaveFactor;
|
|
|
|
// Set a flag to be returned to the caller.
|
|
EyeSubmerged = (m_Eye.z < sSurfaceAtEye) && IsFluidAtXY( m_Eye.x, m_Eye.y );
|
|
|
|
// Prepare to accumulate vertices and indices.
|
|
m_VUsed = 0;
|
|
m_IUsed = 0;
|
|
m_pINext = m_pIndex;
|
|
|
|
// Determine what "rep" of the terrain we are in.
|
|
I = (s32)(m_Eye.x / F32(m_TerrainSize));
|
|
J = (s32)(m_Eye.y / F32(m_TerrainSize));
|
|
if( m_Eye.x < 0.0f ) I--;
|
|
if( m_Eye.y < 0.0f ) J--;
|
|
|
|
// Push 9 reps of the fluid onto the stack.
|
|
for( j = J-1; j <= J+1; j++ )
|
|
for( i = I-1; i <= I+1; i++ )
|
|
{
|
|
// New stack node.
|
|
Top++;
|
|
|
|
// Set up the level based masking.
|
|
Stack[Top].Level = 0;
|
|
Stack[Top].MaskIndexX = 0;
|
|
Stack[Top].MaskIndexY = 0;
|
|
|
|
// Set up "block" coordinates and factor in the rep of the terrain.
|
|
Stack[Top].BlockX0 = (i * BlocksPerRep);
|
|
Stack[Top].BlockY0 = (j * BlocksPerRep);
|
|
|
|
// Note that the MaskIndexX and the BlockX0 are similar, but not the
|
|
// same. The BlockX0 is affected by the rep of the terrain, whereas
|
|
// MaskIndexX is not. Note that neither takes into consideration
|
|
// the offset of the fluid within a terrain rep.
|
|
|
|
// Compute world coordinates. Account for fluid offset within terrain.
|
|
Stack[Top].X0 = (Stack[Top].BlockX0 * m_Step[4]) + (m_SquareX0 * F32(m_TerrainBlockSize));
|
|
Stack[Top].Y0 = (Stack[Top].BlockY0 * m_Step[4]) + (m_SquareY0 * F32(m_TerrainBlockSize));
|
|
Stack[Top].X1 = Stack[Top].X0 + F32(m_TerrainSize);
|
|
Stack[Top].Y1 = Stack[Top].Y0 + F32(m_TerrainSize);
|
|
|
|
// Compute clip bits for each corner.
|
|
Stack[Top].ClipBits[0] = ComputeClipBits( Stack[Top].X0, Stack[Top].Y0, m_SurfaceZ );
|
|
Stack[Top].ClipBits[1] = ComputeClipBits( Stack[Top].X1, Stack[Top].Y0, m_SurfaceZ );
|
|
Stack[Top].ClipBits[2] = ComputeClipBits( Stack[Top].X0, Stack[Top].Y1, m_SurfaceZ );
|
|
Stack[Top].ClipBits[3] = ComputeClipBits( Stack[Top].X1, Stack[Top].Y1, m_SurfaceZ );
|
|
}
|
|
|
|
//
|
|
// Let the "recursion" begin.
|
|
//
|
|
|
|
while( Top >= 0 )
|
|
{
|
|
node& Node = Stack[Top];
|
|
|
|
ASSERT( Top < 32 );
|
|
ASSERT( Node.MaskIndexX < (1 << Node.Level) );
|
|
ASSERT( Node.MaskIndexY < (1 << Node.Level) );
|
|
|
|
// Attempt to trivially reject the whole node. See if the clip bits
|
|
// indicate that all 4 verts are outside one of the frustrum planes.
|
|
if( Node.ClipBits[0] &
|
|
Node.ClipBits[1] &
|
|
Node.ClipBits[2] &
|
|
Node.ClipBits[3] )
|
|
{
|
|
Top--;
|
|
continue;
|
|
}
|
|
|
|
// Attempt to trivially reject the whole node based on the mask bits.
|
|
if( GetRejectBit( Node.Level, Node.MaskIndexX, Node.MaskIndexY ) )
|
|
{
|
|
Top--;
|
|
continue;
|
|
}
|
|
|
|
// If we have reached level 5 (the node is a single block), then
|
|
// accept it.
|
|
if( Node.Level == 5 )
|
|
{
|
|
ProcessNode( Node );
|
|
Top--;
|
|
continue;
|
|
}
|
|
|
|
// Attempt to trivially accept the whole node. It must pass BOTH the
|
|
// clip bits and the accept mask.
|
|
if( ((Node.ClipBits[0] | Node.ClipBits[1] |
|
|
Node.ClipBits[2] | Node.ClipBits[3]) == 0x00) &&
|
|
GetAcceptBit( Node.Level, Node.MaskIndexX, Node.MaskIndexY ) )
|
|
{
|
|
ProcessNode( Node );
|
|
Top--;
|
|
continue;
|
|
}
|
|
|
|
// If we are here, then we need to subdivide the node. We will break
|
|
// the node into 4 equal parts.
|
|
//
|
|
// *-----* *--@--*
|
|
// | | | 2| 3|
|
|
// | Top | becomes... @--@--@
|
|
// | | | 0| 1|
|
|
// *-----* *--@--*
|
|
//
|
|
{
|
|
node& Node0 = Stack[Top+0];
|
|
node& Node1 = Stack[Top+1];
|
|
node& Node2 = Stack[Top+2];
|
|
node& Node3 = Stack[Top+3];
|
|
s32 Blocks;
|
|
|
|
// We're going to copy the original node onto all of the new nodes
|
|
// in a moment. Before we do that, we might as well make some
|
|
// changes to the original node that will need to be made in all
|
|
// of the new nodes anyways. (Node and Node0 are the same thing.)
|
|
|
|
Node.Level += 1;
|
|
Node.MaskIndexX = Node.MaskIndexX << 1;
|
|
Node.MaskIndexY = Node.MaskIndexY << 1;
|
|
|
|
// The number of blocks across a new node is needed repeatedly
|
|
// below. Go ahead and compute it now.
|
|
|
|
Blocks = (32 >> Node.Level);
|
|
|
|
// Now, just copy everything from the original node into all of
|
|
// the new nodes.
|
|
|
|
Node3 = Node2 = Node1 = Node0;
|
|
|
|
// Update Node 0.
|
|
|
|
Node0.X1 = Node0.X0 + (Blocks * m_Step[4]);
|
|
Node0.Y1 = Node0.Y0 + (Blocks * m_Step[4]);
|
|
Node0.ClipBits[1] = ComputeClipBits( Node0.X1, Node0.Y0, m_SurfaceZ );
|
|
Node0.ClipBits[2] = ComputeClipBits( Node0.X0, Node0.Y1, m_SurfaceZ );
|
|
Node0.ClipBits[3] = ComputeClipBits( Node0.X1, Node0.Y1, m_SurfaceZ );
|
|
|
|
// Update Node 1. Take advantage of updated Node 0.
|
|
|
|
Node1.MaskIndexX += 1;
|
|
Node1.BlockX0 += Blocks;
|
|
Node1.X0 = Node0.X1;
|
|
Node1.Y1 = Node0.Y1;
|
|
Node1.ClipBits[0] = Node0.ClipBits[1];
|
|
Node1.ClipBits[2] = Node0.ClipBits[3];
|
|
Node1.ClipBits[3] = ComputeClipBits( Node1.X1, Node1.Y1, m_SurfaceZ );
|
|
|
|
// Update Node 2. Take advantage of updated Node 0.
|
|
|
|
Node2.MaskIndexY += 1;
|
|
Node2.BlockY0 += Blocks;
|
|
Node2.X1 = Node0.X1;
|
|
Node2.Y0 = Node0.Y1;
|
|
Node2.ClipBits[0] = Node0.ClipBits[2];
|
|
Node2.ClipBits[1] = Node0.ClipBits[3];
|
|
Node2.ClipBits[3] = ComputeClipBits( Node2.X1, Node2.Y1, m_SurfaceZ );
|
|
|
|
// Update Node 3. Take advantage of updated Nodes 0, 1, and 2.
|
|
|
|
Node3.MaskIndexX += 1;
|
|
Node3.MaskIndexY += 1;
|
|
Node3.BlockX0 += Blocks;
|
|
Node3.BlockY0 += Blocks;
|
|
Node3.X0 = Node0.X1;
|
|
Node3.Y0 = Node0.Y1;
|
|
Node3.ClipBits[0] = Node0.ClipBits[3];
|
|
Node3.ClipBits[1] = Node1.ClipBits[3];
|
|
Node3.ClipBits[2] = Node2.ClipBits[3];
|
|
|
|
Top += 3;
|
|
}
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
void fluid::ProcessNode( node& Node )
|
|
{
|
|
// Quick debug render.
|
|
if( m_ShowNodes )
|
|
{
|
|
glDisable ( GL_TEXTURE_2D );
|
|
glDisable ( GL_DEPTH_TEST );
|
|
glEnable ( GL_BLEND );
|
|
glBlendFunc ( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
|
|
glPolygonMode ( GL_FRONT_AND_BACK, GL_LINE );
|
|
glColor4f ( 0.5f, 0.5f, 0.0f, 0.5f );
|
|
glBegin ( GL_QUADS );
|
|
glVertex3f ( Node.X0, Node.Y0, m_SurfaceZ );
|
|
glVertex3f ( Node.X1, Node.Y0, m_SurfaceZ );
|
|
glVertex3f ( Node.X1, Node.Y1, m_SurfaceZ );
|
|
glVertex3f ( Node.X0, Node.Y1, m_SurfaceZ );
|
|
glEnd ();
|
|
glEnable ( GL_DEPTH_TEST );
|
|
glColor4f ( 0.8f, 0.8f, 0.3f, 0.8f );
|
|
glBegin ( GL_QUADS );
|
|
glVertex3f ( Node.X0, Node.Y0, m_SurfaceZ );
|
|
glVertex3f ( Node.X1, Node.Y0, m_SurfaceZ );
|
|
glVertex3f ( Node.X1, Node.Y1, m_SurfaceZ );
|
|
glVertex3f ( Node.X0, Node.Y1, m_SurfaceZ );
|
|
glEnd ();
|
|
}
|
|
|
|
// For each node, render all the blocks it contains.
|
|
|
|
s32 X, Y;
|
|
s32 Blocks;
|
|
|
|
Blocks = (32 >> Node.Level);
|
|
|
|
for( Y = 0; Y < Blocks; Y++ )
|
|
for( X = 0; X < Blocks; X++ )
|
|
{
|
|
block Block;
|
|
|
|
Block.X0 = Node.X0 + X * m_Step[4];
|
|
Block.Y0 = Node.Y0 + Y * m_Step[4];
|
|
Block.X1 = Block.X0 + m_Step[4];
|
|
Block.Y1 = Block.Y0 + m_Step[4];
|
|
|
|
Block.Distance[0] = DISTANCE( Block.X0, Block.Y0, m_SurfaceZ );
|
|
Block.Distance[1] = DISTANCE( Block.X1, Block.Y0, m_SurfaceZ );
|
|
Block.Distance[2] = DISTANCE( Block.X0, Block.Y1, m_SurfaceZ );
|
|
Block.Distance[3] = DISTANCE( Block.X1, Block.Y1, m_SurfaceZ );
|
|
|
|
Block.LOD[0] = ComputeLOD( Block.Distance[0] );
|
|
Block.LOD[1] = ComputeLOD( Block.Distance[1] );
|
|
Block.LOD[2] = ComputeLOD( Block.Distance[2] );
|
|
Block.LOD[3] = ComputeLOD( Block.Distance[3] );
|
|
|
|
ProcessBlock( Block );
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
void fluid::ProcessBlock( block& Block )
|
|
{
|
|
// Quick debug render.
|
|
if( m_ShowBlocks )
|
|
{
|
|
glDisable ( GL_TEXTURE_2D );
|
|
glDisable ( GL_DEPTH_TEST );
|
|
glEnable ( GL_BLEND );
|
|
glBlendFunc ( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
|
|
glPolygonMode ( GL_FRONT_AND_BACK, GL_LINE );
|
|
glColor4f ( 0.0f, 0.0f, 0.0f, 0.5f );
|
|
glBegin ( GL_QUADS );
|
|
glVertex3f ( Block.X0, Block.Y0, m_SurfaceZ );
|
|
glVertex3f ( Block.X1, Block.Y0, m_SurfaceZ );
|
|
glVertex3f ( Block.X1, Block.Y1, m_SurfaceZ );
|
|
glVertex3f ( Block.X0, Block.Y1, m_SurfaceZ );
|
|
glEnd ();
|
|
glEnable ( GL_DEPTH_TEST );
|
|
glColor4f ( 0.3f, 0.3f, 0.3f, 0.8f );
|
|
glBegin ( GL_QUADS );
|
|
glVertex3f ( Block.X0, Block.Y0, m_SurfaceZ );
|
|
glVertex3f ( Block.X1, Block.Y0, m_SurfaceZ );
|
|
glVertex3f ( Block.X1, Block.Y1, m_SurfaceZ );
|
|
glVertex3f ( Block.X0, Block.Y1, m_SurfaceZ );
|
|
glEnd ();
|
|
}
|
|
|
|
if( (Block.LOD[0] > 0.0f) &&
|
|
(Block.LOD[1] > 0.0f) &&
|
|
(Block.LOD[2] > 0.0f) &&
|
|
(Block.LOD[3] > 0.0f) )
|
|
{
|
|
if( (Block.LOD[0] == 1.0f) &&
|
|
(Block.LOD[1] == 1.0f) &&
|
|
(Block.LOD[2] == 1.0f) &&
|
|
(Block.LOD[3] == 1.0f) )
|
|
{
|
|
// LOD on ALL verts is ONE!
|
|
ProcessBlockLODHigh( Block );
|
|
}
|
|
else
|
|
{
|
|
// LOD on ALL verts is NON-ZERO, but it is NOT ONE on ALL verts!
|
|
ProcessBlockLODMorph( Block );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( (Block.LOD[0] > 0.0f) ||
|
|
(Block.LOD[1] > 0.0f) ||
|
|
(Block.LOD[2] > 0.0f) ||
|
|
(Block.LOD[3] > 0.0f) )
|
|
{
|
|
// LOD on SOME verts is NON-ZERO, and on SOME it is ZERO!
|
|
ProcessBlockLODTrans( Block );
|
|
}
|
|
else
|
|
{
|
|
// LOD on ALL verts is ZERO!
|
|
ProcessBlockLODLow( Block );
|
|
}
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
static s16 HighLODIndices[96] =
|
|
{
|
|
0, 1, 6, 6, 5, 0, // +Y
|
|
2, 7, 6, 6, 1, 2, // 20--21--22--23--24
|
|
2, 3, 8, 8, 7, 2, // | \ | / | \ | / |
|
|
4, 9, 8, 8, 3, 4, // 15--16--17--18--19
|
|
// | / | \ | / | \ |
|
|
6, 11, 10, 10, 5, 6, // 10--11--12--13--14
|
|
6, 7, 12, 12, 11, 6, // | \ | / | \ | / |
|
|
8, 13, 12, 12, 7, 8, // 05--06--07--08--09
|
|
8, 9, 14, 14, 13, 8, // | / | \ | / | \ |
|
|
// [00]-01--02--03--04 +X
|
|
10, 11, 16, 16, 15, 10, //
|
|
12, 17, 16, 16, 11, 12,
|
|
12, 13, 18, 18, 17, 12,
|
|
14, 19, 18, 18, 13, 14,
|
|
|
|
16, 21, 20, 20, 15, 16,
|
|
16, 17, 22, 22, 21, 16,
|
|
18, 23, 22, 22, 17, 18,
|
|
18, 19, 24, 24, 23, 18,
|
|
};
|
|
|
|
//==============================================================================
|
|
|
|
void fluid::ProcessBlockLODHigh( block& Block )
|
|
{
|
|
vertex* pV;
|
|
s32 X, Y, i;
|
|
f32 x, y;
|
|
s16* pIndex = HighLODIndices;
|
|
f32 Distance;
|
|
|
|
// Get vetices.
|
|
pV = AcquireVertices( 25 );
|
|
|
|
// Put data in the corner verts.
|
|
SetupVert( Block.X0, Block.Y0, Block.Distance[0], pV + 0 );
|
|
SetupVert( Block.X1, Block.Y0, Block.Distance[1], pV + 4 );
|
|
SetupVert( Block.X0, Block.Y1, Block.Distance[2], pV + 20 );
|
|
SetupVert( Block.X1, Block.Y1, Block.Distance[3], pV + 24 );
|
|
|
|
// Put data in all verts but the corners.
|
|
i = 0;
|
|
for( Y = 0; Y < 5; Y++ )
|
|
for( X = 0; X < 5; X++ )
|
|
{
|
|
if( (i != 0) && (i != 4) && (i != 20) && (i != 24) )
|
|
{
|
|
x = Block.X0 + m_Step[X];
|
|
y = Block.Y0 + m_Step[Y];
|
|
Distance = DISTANCE( x, y, m_SurfaceZ );
|
|
SetupVert( x, y, Distance, pV+i );
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
// Add the triangle indices.
|
|
for( i = 0; i < 32; i++ )
|
|
{
|
|
AddTriangleIndices( *(pIndex+0), *(pIndex+1), *(pIndex+2) );
|
|
pIndex += 3;
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
void fluid::ProcessBlockLODMorph( block& Block )
|
|
{
|
|
vertex* pV;
|
|
s32 X, Y, i;
|
|
f32 x, y;
|
|
s16* pIndex = HighLODIndices;
|
|
f32 Distance;
|
|
f32 LOD;
|
|
f32 MiddleX = Block.X0 + m_Step[2];
|
|
f32 MiddleY = Block.Y0 + m_Step[2];
|
|
f32 MiddleD = DISTANCE( MiddleX, MiddleY, m_SurfaceZ );
|
|
|
|
// Get vetices.
|
|
pV = AcquireVertices( 25 );
|
|
|
|
// Put data in the corner verts.
|
|
SetupVert( Block.X0, Block.Y0, Block.Distance[0], pV + 0 );
|
|
SetupVert( Block.X1, Block.Y0, Block.Distance[1], pV + 4 );
|
|
SetupVert( Block.X0, Block.Y1, Block.Distance[2], pV + 20 );
|
|
SetupVert( Block.X1, Block.Y1, Block.Distance[3], pV + 24 );
|
|
|
|
// Put data in all verts but the corners.
|
|
i = 0;
|
|
for( Y = 0; Y < 5; Y++ )
|
|
for( X = 0; X < 5; X++ )
|
|
{
|
|
if( (i != 0) && (i != 4) && (i != 20) && (i != 24) )
|
|
{
|
|
x = Block.X0 + m_Step[X];
|
|
y = Block.Y0 + m_Step[Y];
|
|
Distance = DISTANCE( x, y, m_SurfaceZ );
|
|
SetupVert( x, y, Distance, pV+i );
|
|
}
|
|
|
|
i++;
|
|
}
|
|
|
|
// Get LOD values for the center AND the centers of each edge.
|
|
LOD = Block.LOD[1] < Block.LOD[0] ? Block.LOD[1] : Block.LOD[0];
|
|
LOD = Block.LOD[2] < LOD ? Block.LOD[2] : LOD;
|
|
LOD = Block.LOD[3] < LOD ? Block.LOD[3] : LOD;
|
|
|
|
// Interpolate all of the edges.
|
|
InterpolateVerts( pV + 0, pV + 1, pV + 2, pV + 3, pV + 4, Block.LOD[0], Block.LOD[1] );
|
|
InterpolateVerts( pV + 4, pV + 9, pV + 14, pV + 19, pV + 24, Block.LOD[1], Block.LOD[3] );
|
|
InterpolateVerts( pV + 24, pV + 23, pV + 22, pV + 21, pV + 20, Block.LOD[3], Block.LOD[2] );
|
|
InterpolateVerts( pV + 20, pV + 15, pV + 10, pV + 5, pV + 0, Block.LOD[2], Block.LOD[0] );
|
|
|
|
// Now interpolate from the center out to get the remaining 8 verts.
|
|
InterpolateVert( pV + 12, pV + 6, pV + 0, LOD );
|
|
InterpolateVert( pV + 12, pV + 7, pV + 2, LOD );
|
|
InterpolateVert( pV + 12, pV + 8, pV + 4, LOD );
|
|
InterpolateVert( pV + 12, pV + 13, pV + 14, LOD );
|
|
InterpolateVert( pV + 12, pV + 18, pV + 24, LOD );
|
|
InterpolateVert( pV + 12, pV + 17, pV + 22, LOD );
|
|
InterpolateVert( pV + 12, pV + 16, pV + 20, LOD );
|
|
InterpolateVert( pV + 12, pV + 11, pV + 10, LOD );
|
|
|
|
// Add the triangle indices.
|
|
for( i = 0; i < 32; i++ )
|
|
{
|
|
AddTriangleIndices( *(pIndex+0), *(pIndex+1), *(pIndex+2) );
|
|
pIndex += 3;
|
|
}
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
void fluid::ProcessBlockLODTrans( block& Block )
|
|
{
|
|
vertex* pV;
|
|
s32 Verts;
|
|
s32 I;
|
|
f32 X, Y;
|
|
f32 Distance;
|
|
f32 MiddleX = Block.X0 + m_Step[2];
|
|
f32 MiddleY = Block.Y0 + m_Step[2];
|
|
f32 MiddleD = DISTANCE( MiddleX, MiddleY, m_SurfaceZ );
|
|
|
|
// Determine how many verts we need.
|
|
Verts = 5;
|
|
if( (Block.LOD[0] > 0.0f) && (Block.LOD[0] > 0.0f) ) Verts += 3;
|
|
if( (Block.LOD[1] > 0.0f) && (Block.LOD[3] > 0.0f) ) Verts += 3;
|
|
if( (Block.LOD[3] > 0.0f) && (Block.LOD[2] > 0.0f) ) Verts += 3;
|
|
if( (Block.LOD[2] > 0.0f) && (Block.LOD[0] > 0.0f) ) Verts += 3;
|
|
|
|
// Get vetices.
|
|
pV = AcquireVertices( Verts );
|
|
|
|
// Build the corner and center vertices.
|
|
SetupVert( Block.X0, Block.Y0, Block.Distance[0], pV+0 );
|
|
SetupVert( Block.X1, Block.Y0, Block.Distance[1], pV+1 );
|
|
SetupVert( Block.X0, Block.Y1, Block.Distance[2], pV+2 );
|
|
SetupVert( Block.X1, Block.Y1, Block.Distance[3], pV+3 );
|
|
SetupVert( MiddleX, MiddleY, MiddleD, pV+4 );
|
|
|
|
I = 5;
|
|
|
|
//------------------------------------------------------
|
|
|
|
if( (Block.LOD[0] > 0.0f) && (Block.LOD[1] > 0.0f) )
|
|
{
|
|
X = Block.X0 + m_Step[1];
|
|
Distance = DISTANCE( X, Block.Y0, m_SurfaceZ );
|
|
SetupVert( X, Block.Y0, Distance, pV+I+0 );
|
|
|
|
X = Block.X0 + m_Step[2];
|
|
Distance = DISTANCE( X, Block.Y0, m_SurfaceZ );
|
|
SetupVert( X, Block.Y0, Distance, pV+I+1 );
|
|
|
|
X = Block.X0 + m_Step[3];
|
|
Distance = DISTANCE( X, Block.Y0, m_SurfaceZ );
|
|
SetupVert( X, Block.Y0, Distance, pV+I+2 );
|
|
|
|
InterpolateVerts( pV+0, pV+I+0, pV+I+1, pV+I+2, pV+1, Block.LOD[0], Block.LOD[1] );
|
|
AddTriangleIndices( 4, 0, I+0 );
|
|
AddTriangleIndices( 4, I+0, I+1 );
|
|
AddTriangleIndices( 4, I+1, I+2 );
|
|
AddTriangleIndices( 4, I+2, 1 );
|
|
I += 3;
|
|
}
|
|
else
|
|
{
|
|
AddTriangleIndices( 4, 0, 1 );
|
|
}
|
|
|
|
//------------------------------------------------------
|
|
|
|
if( (Block.LOD[1] > 0.0f) && (Block.LOD[3] > 0.0f) )
|
|
{
|
|
Y = Block.Y0 + m_Step[1];
|
|
Distance = DISTANCE( Block.X1, Y, m_SurfaceZ );
|
|
SetupVert( Block.X1, Y, Distance, pV+I+0 );
|
|
|
|
Y = Block.Y0 + m_Step[2];
|
|
Distance = DISTANCE( Block.X1, Y, m_SurfaceZ );
|
|
SetupVert( Block.X1, Y, Distance, pV+I+1 );
|
|
|
|
Y = Block.Y0 + m_Step[3];
|
|
Distance = DISTANCE( Block.X1, Y, m_SurfaceZ );
|
|
SetupVert( Block.X1, Y, Distance, pV+I+2 );
|
|
|
|
InterpolateVerts( pV+1, pV+I+0, pV+I+1, pV+I+2, pV+3, Block.LOD[1], Block.LOD[3] );
|
|
AddTriangleIndices( 4, 1, I+0 );
|
|
AddTriangleIndices( 4, I+0, I+1 );
|
|
AddTriangleIndices( 4, I+1, I+2 );
|
|
AddTriangleIndices( 4, I+2, 3 );
|
|
I += 3;
|
|
}
|
|
else
|
|
{
|
|
AddTriangleIndices( 4, 1, 3 );
|
|
}
|
|
|
|
//------------------------------------------------------
|
|
|
|
if( (Block.LOD[3] > 0.0f) && (Block.LOD[2] > 0.0f) )
|
|
{
|
|
X = Block.X1 - m_Step[1];
|
|
Distance = DISTANCE( X, Block.Y1, m_SurfaceZ );
|
|
SetupVert( X, Block.Y1, Distance, pV+I+0 );
|
|
|
|
X = Block.X1 - m_Step[2];
|
|
Distance = DISTANCE( X, Block.Y1, m_SurfaceZ );
|
|
SetupVert( X, Block.Y1, Distance, pV+I+1 );
|
|
|
|
X = Block.X1 - m_Step[3];
|
|
Distance = DISTANCE( X, Block.Y1, m_SurfaceZ );
|
|
SetupVert( X, Block.Y1, Distance, pV+I+2 );
|
|
|
|
InterpolateVerts( pV+3, pV+I+0, pV+I+1, pV+I+2, pV+2, Block.LOD[3], Block.LOD[2] );
|
|
AddTriangleIndices( 4, 3, I+0 );
|
|
AddTriangleIndices( 4, I+0, I+1 );
|
|
AddTriangleIndices( 4, I+1, I+2 );
|
|
AddTriangleIndices( 4, I+2, 2 );
|
|
I += 3;
|
|
}
|
|
else
|
|
{
|
|
AddTriangleIndices( 4, 3, 2 );
|
|
}
|
|
|
|
//------------------------------------------------------
|
|
|
|
if( (Block.LOD[2] > 0.0f) && (Block.LOD[0] > 0.0f) )
|
|
{
|
|
Y = Block.Y1 - m_Step[1];
|
|
Distance = DISTANCE( Block.X0, Y, m_SurfaceZ );
|
|
SetupVert( Block.X0, Y, Distance, pV+I+0 );
|
|
|
|
Y = Block.Y1 - m_Step[2];
|
|
Distance = DISTANCE( Block.X0, Y, m_SurfaceZ );
|
|
SetupVert( Block.X0, Y, Distance, pV+I+1 );
|
|
|
|
Y = Block.Y1 - m_Step[3];
|
|
Distance = DISTANCE( Block.X0, Y, m_SurfaceZ );
|
|
SetupVert( Block.X0, Y, Distance, pV+I+2 );
|
|
|
|
InterpolateVerts( pV+2, pV+I+0, pV+I+1, pV+I+2, pV+0, Block.LOD[2], Block.LOD[0] );
|
|
AddTriangleIndices( 4, 2, I+0 );
|
|
AddTriangleIndices( 4, I+0, I+1 );
|
|
AddTriangleIndices( 4, I+1, I+2 );
|
|
AddTriangleIndices( 4, I+2, 0 );
|
|
I += 3;
|
|
}
|
|
else
|
|
{
|
|
AddTriangleIndices( 4, 2, 0 );
|
|
}
|
|
|
|
//------------------------------------------------------
|
|
}
|
|
|
|
//==============================================================================
|
|
//
|
|
// [2]-----[3]
|
|
// |\ 3 /|
|
|
// | \ / |
|
|
// |4 [4] 2| Rendered as four triangles.
|
|
// | / \ |
|
|
// |/ 1 \|
|
|
// [0]-----[1]
|
|
//
|
|
|
|
void fluid::ProcessBlockLODLow( block& Block )
|
|
{
|
|
vertex* pV;
|
|
f32 MiddleX = Block.X0 + m_Step[2];
|
|
f32 MiddleY = Block.Y0 + m_Step[2];
|
|
f32 MiddleD = DISTANCE( MiddleX, MiddleY, m_SurfaceZ );
|
|
|
|
// Get vetices.
|
|
pV = AcquireVertices( 5 );
|
|
|
|
// Put data in the verts.
|
|
SetupVert( Block.X0, Block.Y0, Block.Distance[0], pV+0 );
|
|
SetupVert( Block.X1, Block.Y0, Block.Distance[1], pV+1 );
|
|
SetupVert( Block.X0, Block.Y1, Block.Distance[2], pV+2 );
|
|
SetupVert( Block.X1, Block.Y1, Block.Distance[3], pV+3 );
|
|
SetupVert( MiddleX, MiddleY, MiddleD, pV+4 );
|
|
|
|
//--Attempt to reject if all fogged.
|
|
|
|
// Add the triangle indices.
|
|
AddTriangleIndices( 0, 1, 4 );
|
|
AddTriangleIndices( 1, 3, 4 );
|
|
AddTriangleIndices( 3, 2, 4 );
|
|
AddTriangleIndices( 2, 0, 4 );
|
|
|
|
// Quick debug render.
|
|
#if 0
|
|
if( 0 )
|
|
{
|
|
glDisable ( GL_TEXTURE_2D );
|
|
glBegin ( GL_QUADS );
|
|
glColor4f ( Block.LOD[0], 1.0f, Block.LOD[3], 1.0f );
|
|
glVertex3f ( pV[0].XYZ.x, pV[0].XYZ.y, pV[0].XYZ.z );
|
|
glVertex3f ( pV[1].XYZ.x, pV[1].XYZ.y, pV[1].XYZ.z );
|
|
glVertex3f ( pV[4].XYZ.x, pV[4].XYZ.y, pV[4].XYZ.z );
|
|
glVertex3f ( pV[2].XYZ.x, pV[2].XYZ.y, pV[2].XYZ.z );
|
|
glVertex3f ( pV[4].XYZ.x, pV[4].XYZ.y, pV[4].XYZ.z );
|
|
glVertex3f ( pV[1].XYZ.x, pV[1].XYZ.y, pV[1].XYZ.z );
|
|
glVertex3f ( pV[3].XYZ.x, pV[3].XYZ.y, pV[3].XYZ.z );
|
|
glVertex3f ( pV[2].XYZ.x, pV[2].XYZ.y, pV[2].XYZ.z );
|
|
glEnd ();
|
|
}
|
|
#endif
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
void fluid::SetupVert( f32 X, f32 Y, f32 Distance, vertex* pV )
|
|
{
|
|
f32 Z;
|
|
|
|
//
|
|
// Compute a Z value.
|
|
//
|
|
{
|
|
f32 WarpZ, WarpD0, WarpD1;
|
|
f32 WarpFactor;
|
|
f32 Delta;
|
|
|
|
Delta = SINE( (X * 0.05f) + m_Seconds ) +
|
|
SINE( (Y * 0.05f) + m_Seconds );
|
|
|
|
Z = m_SurfaceZ + Delta * m_WaveFactor;
|
|
|
|
// When the camera is close to the fluid surface, warp the surface to
|
|
// prevent the fluid from intersecting the camera glass.
|
|
|
|
WarpD0 = m_Step[2];
|
|
if( Distance < WarpD0 )
|
|
{
|
|
WarpD1 = m_Step[1];
|
|
|
|
if( m_Eye.z > sSurfaceAtEye )
|
|
{
|
|
WarpZ = m_Eye.z - 0.25f;
|
|
if( Z > WarpZ )
|
|
{
|
|
if( Distance < WarpD1 )
|
|
{
|
|
// Close enough to completely warp the vert.
|
|
Z = WarpZ;
|
|
}
|
|
else
|
|
{
|
|
// Range is between WarpD0 and WarpD1. Interpolate.
|
|
WarpFactor = ((WarpD0 - Distance) / WarpD1);
|
|
Z = (WarpZ * WarpFactor) + (Z * (1.0f-WarpFactor));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WarpZ = m_Eye.z + 0.25f;
|
|
if( Z < WarpZ )
|
|
{
|
|
if( Distance < WarpD1 )
|
|
{
|
|
// Close enough to completely warp the vert.
|
|
Z = WarpZ;
|
|
}
|
|
else
|
|
{
|
|
// Range is between WarpD0 and WarpD1. Interpolate.
|
|
WarpFactor = ((WarpD0 - Distance) / WarpD1);
|
|
Z = (WarpZ * WarpFactor) + (Z * (1.0f-WarpFactor));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// These fields are easy!
|
|
|
|
pV->XYZ.set( X, Y, Z );
|
|
pV->RGBA4.R = m_FogColor.R;
|
|
pV->RGBA4.G = m_FogColor.G;
|
|
pV->RGBA4.B = m_FogColor.B;
|
|
pV->RGBA4.A = m_pFogFn( Distance, sFogZ );
|
|
|
|
/*
|
|
pV->RGBA4.A = Distance < m_VisibleDistance
|
|
? sFogTable[ (s32)(Distance * sFogAccessFactor) ]
|
|
: 1.0f;
|
|
*/
|
|
|
|
//
|
|
// Compute the UV for the environment map / specular pass.
|
|
//
|
|
{
|
|
xyz Vector;
|
|
|
|
// The reflection vector is the "eye to point" vector, but with a positive
|
|
// Z value.
|
|
|
|
// MM: I've 'rotated' these so that we get the environment map reflected in the
|
|
// correct orientation.
|
|
Vector.X = X - m_Eye.x;
|
|
Vector.Y = m_Eye.y - Y;
|
|
Vector.Z = Z - m_Eye.z;
|
|
|
|
if( Vector.Z < 0.0f )
|
|
Vector.Z = -Vector.Z;
|
|
|
|
if( Vector.Z < 0.001f )
|
|
Vector.Z = 0.001f;
|
|
|
|
// The UV values are simply the XY values from the normalized reflection
|
|
// vector.
|
|
|
|
if( Distance < 0.001f )
|
|
{
|
|
pV->UV3.U = 0.0f;
|
|
pV->UV3.V = 0.0f;
|
|
}
|
|
else
|
|
{
|
|
// The standard UV reflection mapping tends to over emphasize the
|
|
// outer edges of the environment map. Try to "adjust" the values
|
|
// to get more efficient use of the map. Oh, and normalize, too.
|
|
|
|
f32 Value = (Distance - Vector.Z) / (Distance * Distance);
|
|
|
|
pV->UV3.U = Vector.X * Value;
|
|
pV->UV3.V = Vector.Y * Value;
|
|
}
|
|
|
|
// Convert from [+1,-1] to [0,1].
|
|
pV->UV3.U = pV->UV3.U * 0.5f + 0.5f;
|
|
pV->UV3.V = pV->UV3.V * 0.5f + 0.5f;
|
|
|
|
// Now, based on the XY, tweak the UV in some liquid manner.
|
|
|
|
volatile static f32 Q1 = 150.00f;
|
|
volatile static f32 Q2 = 2.00f;
|
|
volatile static f32 Q3 = 0.01f;
|
|
|
|
// MM: Modified the environment distortion times to match the surface distortion.
|
|
f32 A1 = COSINE( ((X / Q1) + (m_Seconds / m_DistortTime)));
|
|
f32 A2 = SINE ( ((Y / Q1) + (m_Seconds / m_DistortTime)));
|
|
|
|
pV->UV3.U += A1 * Q3;
|
|
pV->UV3.V += A2 * Q3;
|
|
|
|
// MM: Removed the fresnel style blending as it makes the depth-map difficult
|
|
// to control.
|
|
//f32 Swing = (A1+A2) * 0.15f + 0.5f;
|
|
//pV->RGBA1a.A = ((1.0f-Swing) * m_Opacity) / (1.0f - (Swing * m_Opacity));
|
|
//pV->RGBA1b.A = Swing * m_Opacity;
|
|
|
|
// MM: Added Fresnel style blending to the environment map.
|
|
f32 Swing = (A1+A2) * 0.15f + 0.5f;
|
|
pV->RGBA3.R = pV->RGBA3.G = pV->RGBA3.B = 1.0f;
|
|
pV->RGBA3.A = Swing * m_EnvMapIntensity;
|
|
}
|
|
|
|
// MM: Calculate Depth-map Position.
|
|
f32 S1 = m_DistortMagnitude * COSINE( (X * m_DistortGridScale) + (m_Seconds / m_DistortTime) );
|
|
f32 T1 = m_DistortMagnitude * SINE ( (Y * m_DistortGridScale) + (m_Seconds / m_DistortTime) );
|
|
|
|
pV->UV1.U = ((X-(m_SquareX0*m_TerrainBlockSize)) * m_DepthTexelX) * m_TessellationSurface + S1;
|
|
pV->UV1.V = ((Y-(m_SquareY0*m_TerrainBlockSize)) * m_DepthTexelY) * m_TessellationSurface + T1;
|
|
pV->UV2.U = ((X-(m_SquareX0*m_TerrainBlockSize)) * m_DepthTexelX) * m_TessellationShore + S1;
|
|
pV->UV2.V = ((Y-(m_SquareY0*m_TerrainBlockSize)) * m_DepthTexelY) * m_TessellationShore + T1;
|
|
|
|
// MM: Calculate Depth-map Position.
|
|
pV->UV4.U = (X-(m_SquareX0*m_TerrainBlockSize)) * m_DepthTexelX;
|
|
pV->UV4.V = (Y-(m_SquareY0*m_TerrainBlockSize)) * m_DepthTexelY;
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
void fluid::InterpolateVerts( vertex* pV0,
|
|
vertex* pV1,
|
|
vertex* pV2,
|
|
vertex* pV3,
|
|
vertex* pV4,
|
|
f32 LOD0,
|
|
f32 LOD4 )
|
|
{
|
|
f32 DeltaLOD = (LOD4 - LOD0 ) * 0.25f;
|
|
f32 DeltaZ = (pV4->XYZ.z - pV0->XYZ.z) * 0.25f;
|
|
f32 DeltaU = (pV4->UV3.U - pV0->UV3.U) * 0.25f;
|
|
f32 DeltaV = (pV4->UV3.V - pV0->UV3.V) * 0.25f;
|
|
|
|
f32 Z = pV0->XYZ.z;
|
|
uv UV = pV0->UV3;
|
|
|
|
f32 LODa = LOD0;
|
|
f32 LODb = 1.0f;
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Compute interpolated low detail data.
|
|
Z += DeltaZ;
|
|
UV.U += DeltaU;
|
|
UV.V += DeltaV;
|
|
LODa += DeltaLOD;
|
|
LODb = 1.0f - LODa;
|
|
|
|
// Interpolate based on LOD.
|
|
pV1->UV3.U = (pV1->UV3.U * LODa) + (UV.U * LODb);
|
|
pV1->UV3.V = (pV1->UV3.V * LODa) + (UV.V * LODb);
|
|
pV1->XYZ.z = (pV1->XYZ.z * LODa) + (Z * LODb);
|
|
//--------------------------------------------------------------------------
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Compute complete interpolated low detail data.
|
|
Z += DeltaZ;
|
|
UV.U += DeltaU;
|
|
UV.V += DeltaV;
|
|
LODa += DeltaLOD;
|
|
LODb = 1.0f - LODa;
|
|
|
|
// Interpolate based on LOD.
|
|
pV2->UV3.U = (pV2->UV3.U * LODa) + (UV.U * LODb);
|
|
pV2->UV3.V = (pV2->UV3.V * LODa) + (UV.V * LODb);
|
|
pV2->XYZ.z = (pV2->XYZ.z * LODa) + (Z * LODb);
|
|
//--------------------------------------------------------------------------
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Compute complete interpolated low detail data.
|
|
Z += DeltaZ;
|
|
UV.U += DeltaU;
|
|
UV.V += DeltaV;
|
|
LODa += DeltaLOD;
|
|
LODb = 1.0f - LODa;
|
|
|
|
// Interpolate based on LOD.
|
|
pV3->UV3.U = (pV3->UV3.U * LODa) + (UV.U * LODb);
|
|
pV3->UV3.V = (pV3->UV3.V * LODa) + (UV.V * LODb);
|
|
pV3->XYZ.z = (pV3->XYZ.z * LODa) + (Z * LODb);
|
|
//--------------------------------------------------------------------------
|
|
}
|
|
|
|
//==============================================================================
|
|
|
|
void fluid::InterpolateVert( vertex* pV0,
|
|
vertex* pV1,
|
|
vertex* pV2,
|
|
f32 LODa )
|
|
{
|
|
f32 Z = (pV2->XYZ.z + pV0->XYZ.z) * 0.5f;
|
|
f32 U = (pV2->UV3.U + pV0->UV3.U) * 0.5f;
|
|
f32 V = (pV2->UV3.V + pV0->UV3.V) * 0.5f;
|
|
f32 LODb = 1.0f - LODa;
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Interpolate based on LOD.
|
|
pV1->UV3.U = (pV1->UV3.U * LODa) + (U * LODb);
|
|
pV1->UV3.V = (pV1->UV3.V * LODa) + (V * LODb);
|
|
pV1->XYZ.z = (pV1->XYZ.z * LODa) + (Z * LODb);
|
|
//--------------------------------------------------------------------------
|
|
}
|
|
|
|
//==============================================================================
|