/*
===========================================================================
Doom 3 GPL Source Code
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
Doom 3 Source Code is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Doom 3 Source Code is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Doom 3 Source Code. If not, see .
In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#include "../idlib/precompiled.h"
#pragma hdrstop
#include "tr_local.h"
#include "Model_local.h"
#define LIQUID_MAX_SKIP_FRAMES 5
#define LIQUID_MAX_TYPES 3
/*
====================
idRenderModelLiquid::idRenderModelLiquid
====================
*/
idRenderModelLiquid::idRenderModelLiquid() {
verts_x = 32;
verts_y = 32;
scale_x = 256.0f;
scale_y = 256.0f;
liquid_type = 0;
density = 0.97f;
drop_height = 4;
drop_radius = 4;
drop_delay = 1000;
shader = declManager->FindMaterial( NULL );
update_tics = 33; // ~30 hz
time = 0;
seed = 0;
random.SetSeed( 0 );
}
/*
====================
idRenderModelLiquid::GenerateSurface
====================
*/
modelSurface_t idRenderModelLiquid::GenerateSurface( float lerp ) {
srfTriangles_t *tri;
int i, base;
idDrawVert *vert;
modelSurface_t surf;
float inv_lerp;
inv_lerp = 1.0f - lerp;
vert = verts.Ptr();
for( i = 0; i < verts.Num(); i++, vert++ ) {
vert->xyz.z = page1[ i ] * lerp + page2[ i ] * inv_lerp;
}
tr.pc.c_deformedSurfaces++;
tr.pc.c_deformedVerts += deformInfo->numOutputVerts;
tr.pc.c_deformedIndexes += deformInfo->numIndexes;
tri = R_AllocStaticTriSurf();
// note that some of the data is references, and should not be freed
tri->deformedSurface = true;
tri->numIndexes = deformInfo->numIndexes;
tri->indexes = deformInfo->indexes;
tri->silIndexes = deformInfo->silIndexes;
tri->numMirroredVerts = deformInfo->numMirroredVerts;
tri->mirroredVerts = deformInfo->mirroredVerts;
tri->numDupVerts = deformInfo->numDupVerts;
tri->dupVerts = deformInfo->dupVerts;
tri->numSilEdges = deformInfo->numSilEdges;
tri->silEdges = deformInfo->silEdges;
tri->dominantTris = deformInfo->dominantTris;
tri->numVerts = deformInfo->numOutputVerts;
R_AllocStaticTriSurfVerts( tri, tri->numVerts );
SIMDProcessor->Memcpy( tri->verts, verts.Ptr(), deformInfo->numSourceVerts * sizeof(tri->verts[0]) );
// replicate the mirror seam vertexes
base = deformInfo->numOutputVerts - deformInfo->numMirroredVerts;
for ( i = 0 ; i < deformInfo->numMirroredVerts ; i++ ) {
tri->verts[base + i] = tri->verts[deformInfo->mirroredVerts[i]];
}
R_BoundTriSurf( tri );
// If a surface is going to be have a lighting interaction generated, it will also have to call
// R_DeriveTangents() to get normals, tangents, and face planes. If it only
// needs shadows generated, it will only have to generate face planes. If it only
// has ambient drawing, or is culled, no additional work will be necessary
if ( !r_useDeferredTangents.GetBool() ) {
// set face planes, vertex normals, tangents
R_DeriveTangents( tri );
}
surf.geometry = tri;
surf.shader = shader;
return surf;
}
/*
====================
idRenderModelLiquid::WaterDrop
====================
*/
void idRenderModelLiquid::WaterDrop( int x, int y, float *page ) {
int cx, cy;
int left,top,right,bottom;
int square;
int radsquare = drop_radius * drop_radius;
float invlength = 1.0f / ( float )radsquare;
float dist;
if ( x < 0 ) {
x = 1 + drop_radius + random.RandomInt( verts_x - 2 * drop_radius - 1 );
}
if ( y < 0 ) {
y = 1 + drop_radius + random.RandomInt( verts_y - 2 * drop_radius - 1 );
}
left=-drop_radius; right = drop_radius;
top=-drop_radius; bottom = drop_radius;
// Perform edge clipping...
if ( x - drop_radius < 1 ) {
left -= (x-drop_radius-1);
}
if ( y - drop_radius < 1 ) {
top -= (y-drop_radius-1);
}
if ( x + drop_radius > verts_x - 1 ) {
right -= (x+drop_radius-verts_x+1);
}
if ( y + drop_radius > verts_y - 1 ) {
bottom-= (y+drop_radius-verts_y+1);
}
for ( cy = top; cy < bottom; cy++ ) {
for ( cx = left; cx < right; cx++ ) {
square = cy*cy + cx*cx;
if ( square < radsquare ) {
dist = idMath::Sqrt( (float)square * invlength );
page[verts_x*(cy+y) + cx+x] += idMath::Cos16( dist * idMath::PI * 0.5f ) * drop_height;
}
}
}
}
/*
====================
idRenderModelLiquid::IntersectBounds
====================
*/
void idRenderModelLiquid::IntersectBounds( const idBounds &bounds, float displacement ) {
int cx, cy;
int left,top,right,bottom;
//float up;
float down;
float *pos;
left = ( int )( bounds[ 0 ].x / scale_x );
right = ( int )( bounds[ 1 ].x / scale_x );
top = ( int )( bounds[ 0 ].y / scale_y );
bottom = ( int )( bounds[ 1 ].y / scale_y );
down = bounds[ 0 ].z;
//up = bounds[ 1 ].z;
if ( ( right < 1 ) || ( left >= verts_x ) || ( bottom < 1 ) || ( top >= verts_x ) ) {
return;
}
// Perform edge clipping...
if ( left < 1 ) {
left = 1;
}
if ( right >= verts_x ) {
right = verts_x - 1;
}
if ( top < 1 ) {
top = 1;
}
if ( bottom >= verts_y ) {
bottom = verts_y - 1;
}
for ( cy = top; cy < bottom; cy++ ) {
for ( cx = left; cx < right; cx++ ) {
pos = &page1[ verts_x * cy + cx ];
if ( *pos > down ) {//&& ( *pos < up ) ) {
*pos = down;
}
}
}
}
/*
====================
idRenderModelLiquid::Update
====================
*/
void idRenderModelLiquid::Update( void ) {
int x, y;
float *p2;
float *p1;
float value;
time += update_tics;
idSwap( page1, page2 );
if ( time > nextDropTime ) {
WaterDrop( -1, -1, page2 );
nextDropTime = time + drop_delay;
} else if ( time < nextDropTime - drop_delay ) {
nextDropTime = time + drop_delay;
}
p1 = page1;
p2 = page2;
switch( liquid_type ) {
case 0 :
for ( y = 1; y < verts_y - 1; y++ ) {
p2 += verts_x;
p1 += verts_x;
for ( x = 1; x < verts_x - 1; x++ ) {
value =
( p2[ x + verts_x ] +
p2[ x - verts_x ] +
p2[ x + 1 ] +
p2[ x - 1 ] +
p2[ x - verts_x - 1 ] +
p2[ x - verts_x + 1 ] +
p2[ x + verts_x - 1 ] +
p2[ x + verts_x + 1 ] +
p2[ x ] ) * ( 2.0f / 9.0f ) -
p1[ x ];
p1[ x ] = value * density;
}
}
break;
case 1 :
for ( y = 1; y < verts_y - 1; y++ ) {
p2 += verts_x;
p1 += verts_x;
for ( x = 1; x < verts_x - 1; x++ ) {
value =
( p2[ x + verts_x ] +
p2[ x - verts_x ] +
p2[ x + 1 ] +
p2[ x - 1 ] +
p2[ x - verts_x - 1 ] +
p2[ x - verts_x + 1 ] +
p2[ x + verts_x - 1 ] +
p2[ x + verts_x + 1 ] ) * 0.25f -
p1[ x ];
p1[ x ] = value * density;
}
}
break;
case 2 :
for ( y = 1; y < verts_y - 1; y++ ) {
p2 += verts_x;
p1 += verts_x;
for ( x = 1; x < verts_x - 1; x++ ) {
value =
( p2[ x + verts_x ] +
p2[ x - verts_x ] +
p2[ x + 1 ] +
p2[ x - 1 ] +
p2[ x - verts_x - 1 ] +
p2[ x - verts_x + 1 ] +
p2[ x + verts_x - 1 ] +
p2[ x + verts_x + 1 ] +
p2[ x ] ) * ( 1.0f / 9.0f );
p1[ x ] = value * density;
}
}
break;
}
}
/*
====================
idRenderModelLiquid::Reset
====================
*/
void idRenderModelLiquid::Reset() {
int i, x, y;
if ( pages.Num() < 2 * verts_x * verts_y ) {
return;
}
nextDropTime = 0;
time = 0;
random.SetSeed( seed );
page1 = pages.Ptr();
page2 = page1 + verts_x * verts_y;
for ( i = 0, y = 0; y < verts_y; y++ ) {
for ( x = 0; x < verts_x; x++, i++ ) {
page1[ i ] = 0.0f;
page2[ i ] = 0.0f;
verts[ i ].xyz.z = 0.0f;
}
}
}
/*
====================
idRenderModelLiquid::InitFromFile
====================
*/
void idRenderModelLiquid::InitFromFile( const char *fileName ) {
int i, x, y;
idToken token;
idParser parser( LEXFL_ALLOWPATHNAMES | LEXFL_NOSTRINGESCAPECHARS );
idList tris;
float size_x, size_y;
float rate;
name = fileName;
if ( !parser.LoadFile( fileName ) ) {
MakeDefaultModel();
return;
}
size_x = scale_x * verts_x;
size_y = scale_y * verts_y;
while( parser.ReadToken( &token ) ) {
if ( !token.Icmp( "seed" ) ) {
seed = parser.ParseInt();
} else if ( !token.Icmp( "size_x" ) ) {
size_x = parser.ParseFloat();
} else if ( !token.Icmp( "size_y" ) ) {
size_y = parser.ParseFloat();
} else if ( !token.Icmp( "verts_x" ) ) {
verts_x = parser.ParseFloat();
if ( verts_x < 2 ) {
parser.Warning( "Invalid # of verts. Using default model." );
MakeDefaultModel();
return;
}
} else if ( !token.Icmp( "verts_y" ) ) {
verts_y = parser.ParseFloat();
if ( verts_y < 2 ) {
parser.Warning( "Invalid # of verts. Using default model." );
MakeDefaultModel();
return;
}
} else if ( !token.Icmp( "liquid_type" ) ) {
liquid_type = parser.ParseInt() - 1;
if ( ( liquid_type < 0 ) || ( liquid_type >= LIQUID_MAX_TYPES ) ) {
parser.Warning( "Invalid liquid_type. Using default model." );
MakeDefaultModel();
return;
}
} else if ( !token.Icmp( "density" ) ) {
density = parser.ParseFloat();
} else if ( !token.Icmp( "drop_height" ) ) {
drop_height = parser.ParseFloat();
} else if ( !token.Icmp( "drop_radius" ) ) {
drop_radius = parser.ParseInt();
} else if ( !token.Icmp( "drop_delay" ) ) {
drop_delay = SEC2MS( parser.ParseFloat() );
} else if ( !token.Icmp( "shader" ) ) {
parser.ReadToken( &token );
shader = declManager->FindMaterial( token );
} else if ( !token.Icmp( "seed" ) ) {
seed = parser.ParseInt();
} else if ( !token.Icmp( "update_rate" ) ) {
rate = parser.ParseFloat();
if ( ( rate <= 0.0f ) || ( rate > 60.0f ) ) {
parser.Warning( "Invalid update_rate. Must be between 0 and 60. Using default model." );
MakeDefaultModel();
return;
}
update_tics = 1000 / rate;
} else {
parser.Warning( "Unknown parameter '%s'. Using default model.", token.c_str() );
MakeDefaultModel();
return;
}
}
scale_x = size_x / ( verts_x - 1 );
scale_y = size_y / ( verts_y - 1 );
pages.SetNum( 2 * verts_x * verts_y );
page1 = pages.Ptr();
page2 = page1 + verts_x * verts_y;
verts.SetNum( verts_x * verts_y );
for ( i = 0, y = 0; y < verts_y; y++ ) {
for ( x = 0; x < verts_x; x++, i++ ) {
page1[ i ] = 0.0f;
page2[ i ] = 0.0f;
verts[ i ].Clear();
verts[ i ].xyz.Set( x * scale_x, y * scale_y, 0.0f );
verts[ i ].st.Set( (float) x / (float)( verts_x - 1 ), (float) -y / (float)( verts_y - 1 ) );
}
}
tris.SetNum( ( verts_x - 1 ) * ( verts_y - 1 ) * 6 );
for( i = 0, y = 0; y < verts_y - 1; y++ ) {
for( x = 1; x < verts_x; x++, i += 6 ) {
tris[ i + 0 ] = y * verts_x + x;
tris[ i + 1 ] = y * verts_x + x - 1;
tris[ i + 2 ] = ( y + 1 ) * verts_x + x - 1;
tris[ i + 3 ] = ( y + 1 ) * verts_x + x - 1;
tris[ i + 4 ] = ( y + 1 ) * verts_x + x;
tris[ i + 5 ] = y * verts_x + x;
}
}
// build the information that will be common to all animations of this mesh:
// sil edge connectivity and normal / tangent generation information
deformInfo = R_BuildDeformInfo( verts.Num(), verts.Ptr(), tris.Num(), tris.Ptr(), true );
bounds.Clear();
bounds.AddPoint( idVec3( 0.0f, 0.0f, drop_height * -10.0f ) );
bounds.AddPoint( idVec3( ( verts_x - 1 ) * scale_x, ( verts_y - 1 ) * scale_y, drop_height * 10.0f ) );
// set the timestamp for reloadmodels
fileSystem->ReadFile( name, NULL, &timeStamp );
Reset();
}
/*
====================
idRenderModelLiquid::InstantiateDynamicModel
====================
*/
idRenderModel *idRenderModelLiquid::InstantiateDynamicModel( const struct renderEntity_s *ent, const struct viewDef_s *view, idRenderModel *cachedModel ) {
idRenderModelStatic *staticModel;
int frames;
int t;
float lerp;
if ( cachedModel ) {
delete cachedModel;
cachedModel = NULL;
}
if ( !deformInfo ) {
return NULL;
}
if ( !view ) {
t = 0;
} else {
t = view->renderView.time;
}
// update the liquid model
frames = ( t - time ) / update_tics;
if ( frames > LIQUID_MAX_SKIP_FRAMES ) {
// don't let time accumalate when skipping frames
time += update_tics * ( frames - LIQUID_MAX_SKIP_FRAMES );
frames = LIQUID_MAX_SKIP_FRAMES;
}
while( frames > 0 ) {
Update();
frames--;
}
// create the surface
lerp = ( float )( t - time ) / ( float )update_tics;
modelSurface_t surf = GenerateSurface( lerp );
staticModel = new idRenderModelStatic;
staticModel->AddSurface( surf );
staticModel->bounds = surf.geometry->bounds;
return staticModel;
}
/*
====================
idRenderModelLiquid::IsDynamicModel
====================
*/
dynamicModel_t idRenderModelLiquid::IsDynamicModel() const {
return DM_CONTINUOUS;
}
/*
====================
idRenderModelLiquid::Bounds
====================
*/
idBounds idRenderModelLiquid::Bounds(const struct renderEntity_s *ent) const {
// FIXME: need to do this better
return bounds;
}