/*
===========================================================================
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 "sys/platform.h"
#include "idlib/containers/BinSearch.h"
#include "renderer/VertexCache.h"
#include "renderer/tr_local.h"
/*
=================
R_FinishDeform
The ambientCache is on the stack, so we don't want to leave a reference
to it that would try to be freed later. Create the ambientCache immediately.
=================
*/
static void R_FinishDeform( drawSurf_t *drawSurf, srfTriangles_t *newTri, idDrawVert *ac ) {
if ( !newTri ) {
return;
}
// generate current normals, tangents, and bitangents
// We might want to support the possibility of deform functions generating
// explicit normals, and we might also want to allow the cached deformInfo
// optimization for these.
// FIXME: this doesn't work, because the deformed surface is just the
// ambient one, and there isn't an opportunity to generate light interactions
if ( drawSurf->material->ReceivesLighting() ) {
newTri->verts = ac;
R_DeriveTangents( newTri, false );
newTri->verts = NULL;
}
newTri->ambientCache = vertexCache.AllocFrameTemp( ac, newTri->numVerts * sizeof( idDrawVert ) );
// if we are out of vertex cache, leave it the way it is
if ( newTri->ambientCache ) {
drawSurf->geo = newTri;
}
}
/*
=====================
R_AutospriteDeform
Assuming all the triangles for this shader are independant
quads, rebuild them as forward facing sprites
=====================
*/
static void R_AutospriteDeform( drawSurf_t *surf ) {
int i;
const idDrawVert *v;
idVec3 mid, delta;
float radius;
idVec3 left, up;
idVec3 leftDir, upDir;
const srfTriangles_t *tri;
srfTriangles_t *newTri;
tri = surf->geo;
if ( tri->numVerts & 3 ) {
common->Warning( "R_AutospriteDeform: shader had odd vertex count" );
return;
}
if ( tri->numIndexes != ( tri->numVerts >> 2 ) * 6 ) {
common->Warning( "R_AutospriteDeform: autosprite had odd index count" );
return;
}
R_GlobalVectorToLocal( surf->space->modelMatrix, tr.viewDef->renderView.viewaxis[1], leftDir );
R_GlobalVectorToLocal( surf->space->modelMatrix, tr.viewDef->renderView.viewaxis[2], upDir );
if ( tr.viewDef->isMirror ) {
leftDir = vec3_origin - leftDir;
}
// this srfTriangles_t and all its indexes and caches are in frame
// memory, and will be automatically disposed of
newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ) );
newTri->numVerts = tri->numVerts;
newTri->numIndexes = tri->numIndexes;
newTri->indexes = (glIndex_t *)R_FrameAlloc( newTri->numIndexes * sizeof( newTri->indexes[0] ) );
idDrawVert *ac = (idDrawVert *)_alloca16( newTri->numVerts * sizeof( idDrawVert ) );
for ( i = 0 ; i < tri->numVerts ; i+=4 ) {
// find the midpoint
v = &tri->verts[i];
mid[0] = 0.25 * (v->xyz[0] + (v+1)->xyz[0] + (v+2)->xyz[0] + (v+3)->xyz[0]);
mid[1] = 0.25 * (v->xyz[1] + (v+1)->xyz[1] + (v+2)->xyz[1] + (v+3)->xyz[1]);
mid[2] = 0.25 * (v->xyz[2] + (v+1)->xyz[2] + (v+2)->xyz[2] + (v+3)->xyz[2]);
delta = v->xyz - mid;
radius = delta.Length() * 0.707; // / sqrt(2)
left = leftDir * radius;
up = upDir * radius;
ac[i+0].xyz = mid + left + up;
ac[i+0].st[0] = 0;
ac[i+0].st[1] = 0;
ac[i+1].xyz = mid - left + up;
ac[i+1].st[0] = 1;
ac[i+1].st[1] = 0;
ac[i+2].xyz = mid - left - up;
ac[i+2].st[0] = 1;
ac[i+2].st[1] = 1;
ac[i+3].xyz = mid + left - up;
ac[i+3].st[0] = 0;
ac[i+3].st[1] = 1;
newTri->indexes[6*(i>>2)+0] = i;
newTri->indexes[6*(i>>2)+1] = i+1;
newTri->indexes[6*(i>>2)+2] = i+2;
newTri->indexes[6*(i>>2)+3] = i;
newTri->indexes[6*(i>>2)+4] = i+2;
newTri->indexes[6*(i>>2)+5] = i+3;
}
R_FinishDeform( surf, newTri, ac );
}
/*
=====================
R_TubeDeform
will pivot a rectangular quad along the center of its long axis
Note that a geometric tube with even quite a few sides tube will almost certainly render much faster
than this, so this should only be for faked volumetric tubes.
Make sure this is used with twosided translucent shaders, because the exact side
order may not be correct.
=====================
*/
static void R_TubeDeform( drawSurf_t *surf ) {
int i, j;
int indexes;
const srfTriangles_t *tri;
static int edgeVerts[6][2] = {
{ 0, 1 },
{ 1, 2 },
{ 2, 0 },
{ 3, 4 },
{ 4, 5 },
{ 5, 3 }
};
tri = surf->geo;
if ( tri->numVerts & 3 ) {
common->Error( "R_AutospriteDeform: shader had odd vertex count" );
}
if ( tri->numIndexes != ( tri->numVerts >> 2 ) * 6 ) {
common->Error( "R_AutospriteDeform: autosprite had odd index count" );
}
// we need the view direction to project the minor axis of the tube
// as the view changes
idVec3 localView;
R_GlobalPointToLocal( surf->space->modelMatrix, tr.viewDef->renderView.vieworg, localView );
// this srfTriangles_t and all its indexes and caches are in frame
// memory, and will be automatically disposed of
srfTriangles_t *newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ) );
newTri->numVerts = tri->numVerts;
newTri->numIndexes = tri->numIndexes;
newTri->indexes = (glIndex_t *)R_FrameAlloc( newTri->numIndexes * sizeof( newTri->indexes[0] ) );
memcpy( newTri->indexes, tri->indexes, newTri->numIndexes * sizeof( newTri->indexes[0] ) );
idDrawVert *ac = (idDrawVert *)_alloca16( newTri->numVerts * sizeof( idDrawVert ) );
memset( ac, 0, sizeof( idDrawVert ) * newTri->numVerts );
// this is a lot of work for two triangles...
// we could precalculate a lot if it is an issue, but it would mess up
// the shader abstraction
for ( i = 0, indexes = 0 ; i < tri->numVerts ; i+=4, indexes+=6 ) {
float lengths[2];
int nums[2];
idVec3 mid[2];
idVec3 major, minor;
const idDrawVert *v1, *v2;
// identify the two shortest edges out of the six defined by the indexes
nums[0] = nums[1] = 0;
lengths[0] = lengths[1] = 999999;
for ( j = 0 ; j < 6 ; j++ ) {
float l;
v1 = &tri->verts[tri->indexes[i+edgeVerts[j][0]]];
v2 = &tri->verts[tri->indexes[i+edgeVerts[j][1]]];
l = ( v1->xyz - v2->xyz ).Length();
if ( l < lengths[0] ) {
nums[1] = nums[0];
lengths[1] = lengths[0];
nums[0] = j;
lengths[0] = l;
} else if ( l < lengths[1] ) {
nums[1] = j;
lengths[1] = l;
}
}
// find the midpoints of the two short edges, which
// will give us the major axis in object coordinates
for ( j = 0 ; j < 2 ; j++ ) {
v1 = &tri->verts[tri->indexes[i+edgeVerts[nums[j]][0]]];
v2 = &tri->verts[tri->indexes[i+edgeVerts[nums[j]][1]]];
mid[j][0] = 0.5 * (v1->xyz[0] + v2->xyz[0]);
mid[j][1] = 0.5 * (v1->xyz[1] + v2->xyz[1]);
mid[j][2] = 0.5 * (v1->xyz[2] + v2->xyz[2]);
}
// find the vector of the major axis
major = mid[1] - mid[0];
// re-project the points
for ( j = 0 ; j < 2 ; j++ ) {
float l;
int i1 = tri->indexes[i+edgeVerts[nums[j]][0]];
int i2 = tri->indexes[i+edgeVerts[nums[j]][1]];
idDrawVert *av1 = &ac[i1];
idDrawVert *av2 = &ac[i2];
*av1 = *(idDrawVert *)&tri->verts[i1];
*av2 = *(idDrawVert *)&tri->verts[i2];
l = 0.5 * lengths[j];
// cross this with the view direction to get minor axis
idVec3 dir = mid[j] - localView;
minor.Cross( major, dir );
minor.Normalize();
if ( j ) {
av1->xyz = mid[j] - l * minor;
av2->xyz = mid[j] + l * minor;
} else {
av1->xyz = mid[j] + l * minor;
av2->xyz = mid[j] - l * minor;
}
}
}
R_FinishDeform( surf, newTri, ac );
}
/*
=====================
R_WindingFromTriangles
=====================
*/
#define MAX_TRI_WINDING_INDEXES 16
int R_WindingFromTriangles( const srfTriangles_t *tri, glIndex_t indexes[MAX_TRI_WINDING_INDEXES] ) {
int i, j, k, l;
indexes[0] = tri->indexes[0];
int numIndexes = 1;
int numTris = tri->numIndexes / 3;
do {
// find an edge that goes from the current index to another
// index that isn't already used, and isn't an internal edge
for ( i = 0 ; i < numTris ; i++ ) {
for ( j = 0 ; j < 3 ; j++ ) {
if ( tri->indexes[i*3+j] != indexes[numIndexes-1] ) {
continue;
}
int next = tri->indexes[i*3+(j+1)%3];
// make sure it isn't already used
if ( numIndexes == 1 ) {
if ( next == indexes[0] ) {
continue;
}
} else {
for ( k = 1 ; k < numIndexes ; k++ ) {
if ( indexes[k] == next ) {
break;
}
}
if ( k != numIndexes ) {
continue;
}
}
// make sure it isn't an interior edge
for ( k = 0 ; k < numTris ; k++ ) {
if ( k == i ) {
continue;
}
for ( l = 0 ; l < 3 ; l++ ) {
int a, b;
a = tri->indexes[k*3+l];
if ( a != next ) {
continue;
}
b = tri->indexes[k*3+(l+1)%3];
if ( b != indexes[numIndexes-1] ) {
continue;
}
// this is an interior edge
break;
}
if ( l != 3 ) {
break;
}
}
if ( k != numTris ) {
continue;
}
// add this to the list
indexes[numIndexes] = next;
numIndexes++;
break;
}
if ( j != 3 ) {
break;
}
}
if ( numIndexes == tri->numVerts ) {
break;
}
} while ( i != numTris );
return numIndexes;
}
/*
=====================
R_FlareDeform
=====================
*/
/*
static void R_FlareDeform( drawSurf_t *surf ) {
const srfTriangles_t *tri;
srfTriangles_t *newTri;
idPlane plane;
float dot;
idVec3 localViewer;
int j;
tri = surf->geo;
if ( tri->numVerts != 4 || tri->numIndexes != 6 ) {
//FIXME: temp hack for flares on tripleted models
common->Warning( "R_FlareDeform: not a single quad" );
return;
}
// this srfTriangles_t and all its indexes and caches are in frame
// memory, and will be automatically disposed of
newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ) );
newTri->numVerts = 4;
newTri->numIndexes = 2*3;
newTri->indexes = (glIndex_t *)R_FrameAlloc( newTri->numIndexes * sizeof( newTri->indexes[0] ) );
idDrawVert *ac = (idDrawVert *)_alloca16( newTri->numVerts * sizeof( idDrawVert ) );
// find the plane
plane.FromPoints( tri->verts[tri->indexes[0]].xyz, tri->verts[tri->indexes[1]].xyz, tri->verts[tri->indexes[2]].xyz );
// if viewer is behind the plane, draw nothing
R_GlobalPointToLocal( surf->space->modelMatrix, tr.viewDef->renderView.vieworg, localViewer );
float distFromPlane = localViewer * plane.Normal() + plane[3];
if ( distFromPlane <= 0 ) {
newTri->numIndexes = 0;
surf->geo = newTri;
return;
}
idVec3 center;
center = tri->verts[0].xyz;
for ( j = 1 ; j < tri->numVerts ; j++ ) {
center += tri->verts[j].xyz;
}
center *= 1.0/tri->numVerts;
idVec3 dir = localViewer - center;
dir.Normalize();
dot = dir * plane.Normal();
// set vertex colors based on plane angle
int color = (int)(dot * 8 * 256);
if ( color > 255 ) {
color = 255;
}
for ( j = 0 ; j < newTri->numVerts ; j++ ) {
ac[j].color[0] =
ac[j].color[1] =
ac[j].color[2] = color;
ac[j].color[3] = 255;
}
float spread = surf->shaderRegisters[ surf->material->GetDeformRegister(0) ] * r_flareSize.GetFloat();
idVec3 edgeDir[4][3];
glIndex_t indexes[MAX_TRI_WINDING_INDEXES];
int numIndexes = R_WindingFromTriangles( tri, indexes );
surf->material = declManager->FindMaterial( "textures/smf/anamorphicFlare" );
// only deal with quads
if ( numIndexes != 4 ) {
return;
}
// compute centroid
idVec3 centroid, toeye, forward, up, left;
centroid.Set( 0, 0, 0 );
for ( int i = 0; i < 4; i++ ) {
centroid += tri->verts[ indexes[i] ].xyz;
}
centroid /= 4;
// compute basis vectors
up.Set( 0, 0, 1 );
toeye = centroid - localViewer;
toeye.Normalize();
left = toeye.Cross( up );
up = left.Cross( toeye );
left = left * 40 * 6;
up = up * 40;
// compute flares
struct flare_t {
float angle;
float length;
};
static flare_t flares[] = {
{ 0, 100 },
{ 90, 100 }
};
for ( int i = 0; i < 4; i++ ) {
memset( ac + i, 0, sizeof( ac[i] ) );
}
ac[0].xyz = centroid - left;
ac[0].st[0] = 0; ac[0].st[1] = 0;
ac[1].xyz = centroid + up;
ac[1].st[0] = 1; ac[1].st[1] = 0;
ac[2].xyz = centroid + left;
ac[2].st[0] = 1; ac[2].st[1] = 1;
ac[3].xyz = centroid - up;
ac[3].st[0] = 0; ac[3].st[1] = 1;
// setup colors
for ( j = 0 ; j < newTri->numVerts ; j++ ) {
ac[j].color[0] =
ac[j].color[1] =
ac[j].color[2] = 255;
ac[j].color[3] = 255;
}
// setup indexes
static glIndex_t triIndexes[2*3] = {
0,1,2, 0,2,3
};
memcpy( newTri->indexes, triIndexes, sizeof( triIndexes ) );
R_FinishDeform( surf, newTri, ac );
}
*/
static void R_FlareDeform( drawSurf_t *surf ) {
const srfTriangles_t *tri;
srfTriangles_t *newTri;
idPlane plane;
float dot;
idVec3 localViewer;
int j;
tri = surf->geo;
if ( tri->numVerts != 4 || tri->numIndexes != 6 ) {
//FIXME: temp hack for flares on tripleted models
common->DPrintf( "R_FlareDeform: not a single quad\n" );
return;
}
// this srfTriangles_t and all its indexes and caches are in frame
// memory, and will be automatically disposed of
newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ) );
newTri->numVerts = 16;
newTri->numIndexes = 18*3;
newTri->indexes = (glIndex_t *)R_FrameAlloc( newTri->numIndexes * sizeof( newTri->indexes[0] ) );
idDrawVert *ac = (idDrawVert *)_alloca16( newTri->numVerts * sizeof( idDrawVert ) );
// find the plane
if (!plane.FromPoints( tri->verts[tri->indexes[0]].xyz, tri->verts[tri->indexes[1]].xyz, tri->verts[tri->indexes[2]].xyz )) {
common->Warning( "R_FlareDeform: plane.FromPoints failed" );
return;
}
// if viewer is behind the plane, draw nothing
R_GlobalPointToLocal( surf->space->modelMatrix, tr.viewDef->renderView.vieworg, localViewer );
float distFromPlane = localViewer * plane.Normal() + plane[3];
if ( distFromPlane <= 0 ) {
newTri->numIndexes = 0;
surf->geo = newTri;
return;
}
idVec3 center;
center = tri->verts[0].xyz;
for ( j = 1 ; j < tri->numVerts ; j++ ) {
center += tri->verts[j].xyz;
}
center *= 1.0/tri->numVerts;
idVec3 dir = localViewer - center;
dir.Normalize();
dot = dir * plane.Normal();
// set vertex colors based on plane angle
int color = (int)(dot * 8 * 256);
if ( color > 255 ) {
color = 255;
}
for ( j = 0 ; j < newTri->numVerts ; j++ ) {
ac[j].color[0] =
ac[j].color[1] =
ac[j].color[2] = color;
ac[j].color[3] = 255;
}
float spread = surf->shaderRegisters[ surf->material->GetDeformRegister(0) ] * r_flareSize.GetFloat();
idVec3 edgeDir[4][3];
glIndex_t indexes[MAX_TRI_WINDING_INDEXES];
int numIndexes = R_WindingFromTriangles( tri, indexes );
// only deal with quads
if ( numIndexes != 4 ) {
return;
}
int i;
// calculate vector directions
for ( i = 0 ; i < 4 ; i++ ) {
ac[i].xyz = tri->verts[ indexes[i] ].xyz;
ac[i].st[0] =
ac[i].st[1] = 0.5;
idVec3 toEye = tri->verts[ indexes[i] ].xyz - localViewer;
toEye.Normalize();
idVec3 d1 = tri->verts[ indexes[(i+1)%4] ].xyz - localViewer;
d1.Normalize();
edgeDir[i][1].Cross( toEye, d1 );
edgeDir[i][1].Normalize();
edgeDir[i][1] = vec3_origin - edgeDir[i][1];
idVec3 d2 = tri->verts[ indexes[(i+3)%4] ].xyz - localViewer;
d2.Normalize();
edgeDir[i][0].Cross( toEye, d2 );
edgeDir[i][0].Normalize();
edgeDir[i][2] = edgeDir[i][0] + edgeDir[i][1];
edgeDir[i][2].Normalize();
}
// build all the points
ac[4].xyz = tri->verts[ indexes[0] ].xyz + spread * edgeDir[0][0];
ac[4].st[0] = 0;
ac[4].st[1] = 0.5;
ac[5].xyz = tri->verts[ indexes[0] ].xyz + spread * edgeDir[0][2];
ac[5].st[0] = 0;
ac[5].st[1] = 0;
ac[6].xyz = tri->verts[ indexes[0] ].xyz + spread * edgeDir[0][1];
ac[6].st[0] = 0.5;
ac[6].st[1] = 0;
ac[7].xyz = tri->verts[ indexes[1] ].xyz + spread * edgeDir[1][0];
ac[7].st[0] = 0.5;
ac[7].st[1] = 0;
ac[8].xyz = tri->verts[ indexes[1] ].xyz + spread * edgeDir[1][2];
ac[8].st[0] = 1;
ac[8].st[1] = 0;
ac[9].xyz = tri->verts[ indexes[1] ].xyz + spread * edgeDir[1][1];
ac[9].st[0] = 1;
ac[9].st[1] = 0.5;
ac[10].xyz = tri->verts[ indexes[2] ].xyz + spread * edgeDir[2][0];
ac[10].st[0] = 1;
ac[10].st[1] = 0.5;
ac[11].xyz = tri->verts[ indexes[2] ].xyz + spread * edgeDir[2][2];
ac[11].st[0] = 1;
ac[11].st[1] = 1;
ac[12].xyz = tri->verts[ indexes[2] ].xyz + spread * edgeDir[2][1];
ac[12].st[0] = 0.5;
ac[12].st[1] = 1;
ac[13].xyz = tri->verts[ indexes[3] ].xyz + spread * edgeDir[3][0];
ac[13].st[0] = 0.5;
ac[13].st[1] = 1;
ac[14].xyz = tri->verts[ indexes[3] ].xyz + spread * edgeDir[3][2];
ac[14].st[0] = 0;
ac[14].st[1] = 1;
ac[15].xyz = tri->verts[ indexes[3] ].xyz + spread * edgeDir[3][1];
ac[15].st[0] = 0;
ac[15].st[1] = 0.5;
for ( i = 4 ; i < 16 ; i++ ) {
idVec3 dir = ac[i].xyz - localViewer;
float len = dir.Normalize();
float ang = dir * plane.Normal();
// ac[i].xyz -= dir * spread * 2;
float newLen = -( distFromPlane / ang );
if ( newLen > 0 && newLen < len ) {
ac[i].xyz = localViewer + dir * newLen;
}
ac[i].st[0] = 0;
ac[i].st[1] = 0.5;
}
#if 1
static glIndex_t triIndexes[18*3] = {
0,4,5, 0,5,6, 0,6,7, 0,7,1, 1,7,8, 1,8,9,
15,4,0, 15,0,3, 3,0,1, 3,1,2, 2,1,9, 2,9,10,
14,15,3, 14,3,13, 13,3,2, 13,2,12, 12,2,11, 11,2,10
};
#else
newTri->numIndexes = 12;
static glIndex_t triIndexes[4*3] = {
0,1,2, 0,2,3, 0,4,5,0,5,6
};
#endif
memcpy( newTri->indexes, triIndexes, sizeof( triIndexes ) );
R_FinishDeform( surf, newTri, ac );
}
/*
=====================
R_ExpandDeform
Expands the surface along it's normals by a shader amount
=====================
*/
static void R_ExpandDeform( drawSurf_t *surf ) {
int i;
const srfTriangles_t *tri;
srfTriangles_t *newTri;
tri = surf->geo;
// this srfTriangles_t and all its indexes and caches are in frame
// memory, and will be automatically disposed of
newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ) );
newTri->numVerts = tri->numVerts;
newTri->numIndexes = tri->numIndexes;
newTri->indexes = tri->indexes;
idDrawVert *ac = (idDrawVert *)_alloca16( newTri->numVerts * sizeof( idDrawVert ) );
float dist = surf->shaderRegisters[ surf->material->GetDeformRegister(0) ];
for ( i = 0 ; i < tri->numVerts ; i++ ) {
ac[i] = *(idDrawVert *)&tri->verts[i];
ac[i].xyz = tri->verts[i].xyz + tri->verts[i].normal * dist;
}
R_FinishDeform( surf, newTri, ac );
}
/*
=====================
R_MoveDeform
Moves the surface along the X axis, mostly just for demoing the deforms
=====================
*/
static void R_MoveDeform( drawSurf_t *surf ) {
int i;
const srfTriangles_t *tri;
srfTriangles_t *newTri;
tri = surf->geo;
// this srfTriangles_t and all its indexes and caches are in frame
// memory, and will be automatically disposed of
newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ) );
newTri->numVerts = tri->numVerts;
newTri->numIndexes = tri->numIndexes;
newTri->indexes = tri->indexes;
idDrawVert *ac = (idDrawVert *)_alloca16( newTri->numVerts * sizeof( idDrawVert ) );
float dist = surf->shaderRegisters[ surf->material->GetDeformRegister(0) ];
for ( i = 0 ; i < tri->numVerts ; i++ ) {
ac[i] = *(idDrawVert *)&tri->verts[i];
ac[i].xyz[0] += dist;
}
R_FinishDeform( surf, newTri, ac );
}
//=====================================================================================
/*
=====================
R_TurbulentDeform
Turbulently deforms the XYZ, S, and T values
=====================
*/
static void R_TurbulentDeform( drawSurf_t *surf ) {
int i;
const srfTriangles_t *tri;
srfTriangles_t *newTri;
tri = surf->geo;
// this srfTriangles_t and all its indexes and caches are in frame
// memory, and will be automatically disposed of
newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ) );
newTri->numVerts = tri->numVerts;
newTri->numIndexes = tri->numIndexes;
newTri->indexes = tri->indexes;
idDrawVert *ac = (idDrawVert *)_alloca16( newTri->numVerts * sizeof( idDrawVert ) );
idDeclTable *table = (idDeclTable *)surf->material->GetDeformDecl();
float range = surf->shaderRegisters[ surf->material->GetDeformRegister(0) ];
float timeOfs = surf->shaderRegisters[ surf->material->GetDeformRegister(1) ];
float domain = surf->shaderRegisters[ surf->material->GetDeformRegister(2) ];
float tOfs = 0.5;
for ( i = 0 ; i < tri->numVerts ; i++ ) {
float f = tri->verts[i].xyz[0] * 0.003 + tri->verts[i].xyz[1] * 0.007 + tri->verts[i].xyz[2] * 0.011;
f = timeOfs + domain * f;
f += timeOfs;
ac[i] = *(idDrawVert *)&tri->verts[i];
ac[i].st[0] += range * table->TableLookup( f );
ac[i].st[1] += range * table->TableLookup( f + tOfs );
}
R_FinishDeform( surf, newTri, ac );
}
//=====================================================================================
/*
=====================
AddTriangleToIsland_r
=====================
*/
#define MAX_EYEBALL_TRIS 10
#define MAX_EYEBALL_ISLANDS 6
typedef struct {
int tris[MAX_EYEBALL_TRIS];
int numTris;
idBounds bounds;
idVec3 mid;
} eyeIsland_t;
static void AddTriangleToIsland_r( const srfTriangles_t *tri, int triangleNum, bool *usedList, eyeIsland_t *island ) {
int a, b, c;
usedList[triangleNum] = true;
// add to the current island
if ( island->numTris == MAX_EYEBALL_TRIS ) {
common->Error( "MAX_EYEBALL_TRIS" );
}
island->tris[island->numTris] = triangleNum;
island->numTris++;
// recurse into all neighbors
a = tri->indexes[triangleNum*3];
b = tri->indexes[triangleNum*3+1];
c = tri->indexes[triangleNum*3+2];
island->bounds.AddPoint( tri->verts[a].xyz );
island->bounds.AddPoint( tri->verts[b].xyz );
island->bounds.AddPoint( tri->verts[c].xyz );
int numTri = tri->numIndexes / 3;
for ( int i = 0 ; i < numTri ; i++ ) {
if ( usedList[i] ) {
continue;
}
if ( tri->indexes[i*3+0] == a
|| tri->indexes[i*3+1] == a
|| tri->indexes[i*3+2] == a
|| tri->indexes[i*3+0] == b
|| tri->indexes[i*3+1] == b
|| tri->indexes[i*3+2] == b
|| tri->indexes[i*3+0] == c
|| tri->indexes[i*3+1] == c
|| tri->indexes[i*3+2] == c ) {
AddTriangleToIsland_r( tri, i, usedList, island );
}
}
}
/*
=====================
R_EyeballDeform
Each eyeball surface should have an separate upright triangle behind it, long end
pointing out the eye, and another single triangle in front of the eye for the focus point.
=====================
*/
static void R_EyeballDeform( drawSurf_t *surf ) {
int i, j, k;
const srfTriangles_t *tri;
srfTriangles_t *newTri;
eyeIsland_t islands[MAX_EYEBALL_ISLANDS];
int numIslands;
bool triUsed[MAX_EYEBALL_ISLANDS*MAX_EYEBALL_TRIS];
tri = surf->geo;
// separate all the triangles into islands
int numTri = tri->numIndexes / 3;
if ( numTri > MAX_EYEBALL_ISLANDS*MAX_EYEBALL_TRIS ) {
common->Printf( "R_EyeballDeform: too many triangles in surface" );
return;
}
memset( triUsed, 0, sizeof( triUsed ) );
for ( numIslands = 0 ; numIslands < MAX_EYEBALL_ISLANDS ; numIslands++ ) {
islands[numIslands].numTris = 0;
islands[numIslands].bounds.Clear();
for ( i = 0 ; i < numTri ; i++ ) {
if ( !triUsed[i] ) {
AddTriangleToIsland_r( tri, i, triUsed, &islands[numIslands] );
break;
}
}
if ( i == numTri ) {
break;
}
}
// assume we always have two eyes, two origins, and two targets
if ( numIslands != 3 ) {
common->Printf( "R_EyeballDeform: %i triangle islands\n", numIslands );
return;
}
// this srfTriangles_t and all its indexes and caches are in frame
// memory, and will be automatically disposed of
// the surface cannot have more indexes or verts than the original
newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ) );
memset( newTri, 0, sizeof( *newTri ) );
newTri->numVerts = tri->numVerts;
newTri->numIndexes = tri->numIndexes;
newTri->indexes = (glIndex_t *)R_FrameAlloc( tri->numIndexes * sizeof( newTri->indexes[0] ) );
idDrawVert *ac = (idDrawVert *)_alloca16( tri->numVerts * sizeof( idDrawVert ) );
newTri->numIndexes = 0;
// decide which islands are the eyes and points
for ( i = 0 ; i < numIslands ; i++ ) {
islands[i].mid = islands[i].bounds.GetCenter();
}
for ( i = 0 ; i < numIslands ; i++ ) {
eyeIsland_t *island = &islands[i];
if ( island->numTris == 1 ) {
continue;
}
// the closest single triangle point will be the eye origin
// and the next-to-farthest will be the focal point
idVec3 origin, focus;
int originIsland = 0;
float dist[MAX_EYEBALL_ISLANDS];
int sortOrder[MAX_EYEBALL_ISLANDS];
for ( j = 0 ; j < numIslands ; j++ ) {
idVec3 dir = islands[j].mid - island->mid;
dist[j] = dir.Length();
sortOrder[j] = j;
for ( k = j-1 ; k >= 0 ; k-- ) {
if ( dist[k] > dist[k+1] ) {
int temp = sortOrder[k];
sortOrder[k] = sortOrder[k+1];
sortOrder[k+1] = temp;
float ftemp = dist[k];
dist[k] = dist[k+1];
dist[k+1] = ftemp;
}
}
}
originIsland = sortOrder[1];
origin = islands[originIsland].mid;
focus = islands[sortOrder[2]].mid;
// determine the projection directions based on the origin island triangle
idVec3 dir = focus - origin;
dir.Normalize();
const idVec3 &p1 = tri->verts[tri->indexes[islands[originIsland].tris[0]+0]].xyz;
const idVec3 &p2 = tri->verts[tri->indexes[islands[originIsland].tris[0]+1]].xyz;
const idVec3 &p3 = tri->verts[tri->indexes[islands[originIsland].tris[0]+2]].xyz;
idVec3 v1 = p2 - p1;
v1.Normalize();
idVec3 v2 = p3 - p1;
v2.Normalize();
// texVec[0] will be the normal to the origin triangle
idVec3 texVec[2];
texVec[0].Cross( v1, v2 );
texVec[1].Cross( texVec[0], dir );
for ( j = 0 ; j < 2 ; j++ ) {
texVec[j] -= dir * ( texVec[j] * dir );
texVec[j].Normalize();
}
// emit these triangles, generating the projected texcoords
for ( j = 0 ; j < islands[i].numTris ; j++ ) {
for ( k = 0 ; k < 3 ; k++ ) {
int index = islands[i].tris[j] * 3;
index = tri->indexes[index+k];
newTri->indexes[newTri->numIndexes++] = index;
ac[index].xyz = tri->verts[index].xyz;
idVec3 local = tri->verts[index].xyz - origin;
ac[index].st[0] = 0.5 + local * texVec[0];
ac[index].st[1] = 0.5 + local * texVec[1];
}
}
}
R_FinishDeform( surf, newTri, ac );
}
//==========================================================================================
/*
=====================
R_ParticleDeform
Emit particles from the surface instead of drawing it
=====================
*/
static void R_ParticleDeform( drawSurf_t *surf, bool useArea ) {
const struct renderEntity_s *renderEntity = &surf->space->entityDef->parms;
const struct viewDef_s *viewDef = tr.viewDef;
const idDeclParticle *particleSystem = (idDeclParticle *)surf->material->GetDeformDecl();
if ( r_skipParticles.GetBool() ) {
return;
}
#if 0
if ( renderEntity->shaderParms[SHADERPARM_PARTICLE_STOPTIME] &&
viewDef->renderView.time*0.001 >= renderEntity->shaderParms[SHADERPARM_PARTICLE_STOPTIME] ) {
// the entire system has faded out
return NULL;
}
#endif
//
// calculate the area of all the triangles
//
int numSourceTris = surf->geo->numIndexes / 3;
float totalArea = 0;
float *sourceTriAreas = NULL;
const srfTriangles_t *srcTri = surf->geo;
if ( useArea ) {
sourceTriAreas = (float *)_alloca( sizeof( *sourceTriAreas ) * numSourceTris );
int triNum = 0;
for ( int i = 0 ; i < srcTri->numIndexes ; i += 3, triNum++ ) {
float area;
area = idWinding::TriangleArea( srcTri->verts[srcTri->indexes[i]].xyz, srcTri->verts[srcTri->indexes[i+1]].xyz, srcTri->verts[srcTri->indexes[i+2]].xyz );
sourceTriAreas[triNum] = totalArea;
totalArea += area;
}
}
//
// create the particles almost exactly the way idRenderModelPrt does
//
particleGen_t g;
g.renderEnt = renderEntity;
g.renderView = &viewDef->renderView;
g.origin.Zero();
g.axis = mat3_identity;
for ( int currentTri = 0; currentTri < ( ( useArea ) ? 1 : numSourceTris ); currentTri++ ) {
for ( int stageNum = 0 ; stageNum < particleSystem->stages.Num() ; stageNum++ ) {
idParticleStage *stage = particleSystem->stages[stageNum];
if ( !stage->material ) {
continue;
}
if ( !stage->cycleMsec ) {
continue;
}
if ( stage->hidden ) { // just for gui particle editor use
continue;
}
// we interpret stage->totalParticles as "particles per map square area"
// so the systems look the same on different size surfaces
int totalParticles = ( useArea ) ? stage->totalParticles * totalArea / 4096.0 : ( stage->totalParticles );
int count = totalParticles * stage->NumQuadsPerParticle();
// allocate a srfTriangles in temp memory that can hold all the particles
srfTriangles_t *tri;
tri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *tri ) );
tri->numVerts = 4 * count;
tri->numIndexes = 6 * count;
tri->verts = (idDrawVert *)R_FrameAlloc( tri->numVerts * sizeof( tri->verts[0] ) );
tri->indexes = (glIndex_t *)R_FrameAlloc( tri->numIndexes * sizeof( tri->indexes[0] ) );
// just always draw the particles
tri->bounds = stage->bounds;
tri->numVerts = 0;
idRandom steppingRandom, steppingRandom2;
int stageAge = g.renderView->time + renderEntity->shaderParms[SHADERPARM_TIMEOFFSET] * 1000 - stage->timeOffset * 1000;
int stageCycle = stageAge / stage->cycleMsec;
// some particles will be in this cycle, some will be in the previous cycle
steppingRandom.SetSeed( (( stageCycle << 10 ) & idRandom::MAX_RAND) ^ (int)( renderEntity->shaderParms[SHADERPARM_DIVERSITY] * idRandom::MAX_RAND ) );
steppingRandom2.SetSeed( (( (stageCycle-1) << 10 ) & idRandom::MAX_RAND) ^ (int)( renderEntity->shaderParms[SHADERPARM_DIVERSITY] * idRandom::MAX_RAND ) );
for ( int index = 0 ; index < totalParticles ; index++ ) {
g.index = index;
// bump the random
steppingRandom.RandomInt();
steppingRandom2.RandomInt();
// calculate local age for this index
int bunchOffset = stage->particleLife * 1000 * stage->spawnBunching * index / totalParticles;
int particleAge = stageAge - bunchOffset;
int particleCycle = particleAge / stage->cycleMsec;
if ( particleCycle < 0 ) {
// before the particleSystem spawned
continue;
}
if ( stage->cycles && particleCycle >= stage->cycles ) {
// cycled systems will only run cycle times
continue;
}
if ( particleCycle == stageCycle ) {
g.random = steppingRandom;
} else {
g.random = steppingRandom2;
}
int inCycleTime = particleAge - particleCycle * stage->cycleMsec;
if ( renderEntity->shaderParms[SHADERPARM_PARTICLE_STOPTIME] &&
g.renderView->time - inCycleTime >= renderEntity->shaderParms[SHADERPARM_PARTICLE_STOPTIME]*1000 ) {
// don't fire any more particles
continue;
}
// supress particles before or after the age clamp
g.frac = (float)inCycleTime / ( stage->particleLife * 1000 );
if ( g.frac < 0 ) {
// yet to be spawned
continue;
}
if ( g.frac > 1.0 ) {
// this particle is in the deadTime band
continue;
}
//---------------
// locate the particle origin and axis somewhere on the surface
//---------------
int pointTri = currentTri;
if ( useArea ) {
// select a triangle based on an even area distribution
pointTri = idBinSearch_LessEqual( sourceTriAreas, numSourceTris, g.random.RandomFloat() * totalArea );
}
// now pick a random point inside pointTri
const idDrawVert *v1 = &srcTri->verts[ srcTri->indexes[ pointTri * 3 + 0 ] ];
const idDrawVert *v2 = &srcTri->verts[ srcTri->indexes[ pointTri * 3 + 1 ] ];
const idDrawVert *v3 = &srcTri->verts[ srcTri->indexes[ pointTri * 3 + 2 ] ];
float f1 = g.random.RandomFloat();
float f2 = g.random.RandomFloat();
float f3 = g.random.RandomFloat();
float ft = 1.0f / ( f1 + f2 + f3 + 0.0001f );
f1 *= ft;
f2 *= ft;
f3 *= ft;
g.origin = v1->xyz * f1 + v2->xyz * f2 + v3->xyz * f3;
g.axis[0] = v1->tangents[0] * f1 + v2->tangents[0] * f2 + v3->tangents[0] * f3;
g.axis[1] = v1->tangents[1] * f1 + v2->tangents[1] * f2 + v3->tangents[1] * f3;
g.axis[2] = v1->normal * f1 + v2->normal * f2 + v3->normal * f3;
//-----------------------
// this is needed so aimed particles can calculate origins at different times
g.originalRandom = g.random;
g.age = g.frac * stage->particleLife;
// if the particle doesn't get drawn because it is faded out or beyond a kill region,
// don't increment the verts
tri->numVerts += stage->CreateParticle( &g, tri->verts + tri->numVerts );
}
if ( tri->numVerts > 0 ) {
// build the index list
int indexes = 0;
for ( int i = 0 ; i < tri->numVerts ; i += 4 ) {
tri->indexes[indexes+0] = i;
tri->indexes[indexes+1] = i+2;
tri->indexes[indexes+2] = i+3;
tri->indexes[indexes+3] = i;
tri->indexes[indexes+4] = i+3;
tri->indexes[indexes+5] = i+1;
indexes += 6;
}
tri->numIndexes = indexes;
tri->ambientCache = vertexCache.AllocFrameTemp( tri->verts, tri->numVerts * sizeof( idDrawVert ) );
if ( tri->ambientCache ) {
// add the drawsurf
R_AddDrawSurf( tri, surf->space, renderEntity, stage->material, surf->scissorRect );
}
}
}
}
}
//========================================================================================
/*
=================
R_DeformDrawSurf
=================
*/
void R_DeformDrawSurf( drawSurf_t *drawSurf ) {
if ( !drawSurf->material ) {
return;
}
if ( r_skipDeforms.GetBool() ) {
return;
}
switch ( drawSurf->material->Deform() ) {
case DFRM_NONE:
return;
case DFRM_SPRITE:
R_AutospriteDeform( drawSurf );
break;
case DFRM_TUBE:
R_TubeDeform( drawSurf );
break;
case DFRM_FLARE:
R_FlareDeform( drawSurf );
break;
case DFRM_EXPAND:
R_ExpandDeform( drawSurf );
break;
case DFRM_MOVE:
R_MoveDeform( drawSurf );
break;
case DFRM_TURB:
R_TurbulentDeform( drawSurf );
break;
case DFRM_EYEBALL:
R_EyeballDeform( drawSurf );
break;
case DFRM_PARTICLE:
R_ParticleDeform( drawSurf, true );
break;
case DFRM_PARTICLE2:
R_ParticleDeform( drawSurf, false );
break;
}
}