/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. Copyright (C) 2000-2013 Darklegion Development This file is part of Tremulous. Quake III Arena 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 2 of the License, or (at your option) any later version. Quake III Arena 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 Quake III Arena source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "tr_local.h" /* This file does all of the processing necessary to turn a raw grid of points read from the map file into a srfGridMesh_t ready for rendering. The level of detail solution is direction independent, based only on subdivided distance from the true curve. Only a single entry point: srfGridMesh_t *R_SubdividePatchToGrid( int width, int height, srfVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE] ) { */ /* ============ LerpDrawVert ============ */ static void LerpDrawVert( srfVert_t *a, srfVert_t *b, srfVert_t *out ) { out->xyz[0] = 0.5f * (a->xyz[0] + b->xyz[0]); out->xyz[1] = 0.5f * (a->xyz[1] + b->xyz[1]); out->xyz[2] = 0.5f * (a->xyz[2] + b->xyz[2]); out->st[0] = 0.5f * (a->st[0] + b->st[0]); out->st[1] = 0.5f * (a->st[1] + b->st[1]); out->lightmap[0] = 0.5f * (a->lightmap[0] + b->lightmap[0]); out->lightmap[1] = 0.5f * (a->lightmap[1] + b->lightmap[1]); out->vertexColors[0] = 0.5f * (a->vertexColors[0] + b->vertexColors[0]); out->vertexColors[1] = 0.5f * (a->vertexColors[1] + b->vertexColors[1]); out->vertexColors[2] = 0.5f * (a->vertexColors[2] + b->vertexColors[2]); out->vertexColors[3] = 0.5f * (a->vertexColors[3] + b->vertexColors[3]); } /* ============ Transpose ============ */ static void Transpose( int width, int height, srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE] ) { int i, j; srfVert_t temp; if ( width > height ) { for ( i = 0 ; i < height ; i++ ) { for ( j = i + 1 ; j < width ; j++ ) { if ( j < height ) { // swap the value temp = ctrl[j][i]; ctrl[j][i] = ctrl[i][j]; ctrl[i][j] = temp; } else { // just copy ctrl[j][i] = ctrl[i][j]; } } } } else { for ( i = 0 ; i < width ; i++ ) { for ( j = i + 1 ; j < height ; j++ ) { if ( j < width ) { // swap the value temp = ctrl[i][j]; ctrl[i][j] = ctrl[j][i]; ctrl[j][i] = temp; } else { // just copy ctrl[i][j] = ctrl[j][i]; } } } } } /* ================= MakeMeshNormals Handles all the complicated wrapping and degenerate cases ================= */ static void MakeMeshNormals( int width, int height, srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE] ) { int i, j, k, dist; vec3_t normal; vec3_t sum; int count = 0; vec3_t base; vec3_t delta; int x, y; srfVert_t *dv; vec3_t around[8], temp; qboolean good[8]; qboolean wrapWidth, wrapHeight; float len; static int neighbors[8][2] = { {0,1}, {1,1}, {1,0}, {1,-1}, {0,-1}, {-1,-1}, {-1,0}, {-1,1} }; wrapWidth = qfalse; for ( i = 0 ; i < height ; i++ ) { VectorSubtract( ctrl[i][0].xyz, ctrl[i][width-1].xyz, delta ); len = VectorLengthSquared( delta ); if ( len > 1.0 ) { break; } } if ( i == height ) { wrapWidth = qtrue; } wrapHeight = qfalse; for ( i = 0 ; i < width ; i++ ) { VectorSubtract( ctrl[0][i].xyz, ctrl[height-1][i].xyz, delta ); len = VectorLengthSquared( delta ); if ( len > 1.0 ) { break; } } if ( i == width) { wrapHeight = qtrue; } for ( i = 0 ; i < width ; i++ ) { for ( j = 0 ; j < height ; j++ ) { count = 0; dv = &ctrl[j][i]; VectorCopy( dv->xyz, base ); for ( k = 0 ; k < 8 ; k++ ) { VectorClear( around[k] ); good[k] = qfalse; for ( dist = 1 ; dist <= 3 ; dist++ ) { x = i + neighbors[k][0] * dist; y = j + neighbors[k][1] * dist; if ( wrapWidth ) { if ( x < 0 ) { x = width - 1 + x; } else if ( x >= width ) { x = 1 + x - width; } } if ( wrapHeight ) { if ( y < 0 ) { y = height - 1 + y; } else if ( y >= height ) { y = 1 + y - height; } } if ( x < 0 || x >= width || y < 0 || y >= height ) { break; // edge of patch } VectorSubtract( ctrl[y][x].xyz, base, temp ); if ( VectorNormalize2( temp, temp ) == 0 ) { continue; // degenerate edge, get more dist } else { good[k] = qtrue; VectorCopy( temp, around[k] ); break; // good edge } } } VectorClear( sum ); for ( k = 0 ; k < 8 ; k++ ) { if ( !good[k] || !good[(k+1)&7] ) { continue; // didn't get two points } CrossProduct( around[(k+1)&7], around[k], normal ); if ( VectorNormalize2( normal, normal ) == 0 ) { continue; } VectorAdd( normal, sum, sum ); count++; } //if ( count == 0 ) { // printf("bad normal\n"); //} VectorNormalize2( sum, dv->normal ); } } } #ifdef USE_VERT_TANGENT_SPACE static void MakeMeshTangentVectors(int width, int height, srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE], int numTriangles, srfTriangle_t triangles[(MAX_GRID_SIZE-1)*(MAX_GRID_SIZE-1)*2]) { int i, j; srfVert_t *dv[3]; static srfVert_t ctrl2[MAX_GRID_SIZE * MAX_GRID_SIZE]; srfTriangle_t *tri; // FIXME: use more elegant way for(i = 0; i < width; i++) { for(j = 0; j < height; j++) { dv[0] = &ctrl2[j * width + i]; *dv[0] = ctrl[j][i]; } } for(i = 0, tri = triangles; i < numTriangles; i++, tri++) { dv[0] = &ctrl2[tri->indexes[0]]; dv[1] = &ctrl2[tri->indexes[1]]; dv[2] = &ctrl2[tri->indexes[2]]; R_CalcTangentVectors(dv); } #if 0 for(i = 0; i < (width * height); i++) { dv0 = &ctrl2[i]; VectorNormalize(dv0->normal); #if 0 VectorNormalize(dv0->tangent); VectorNormalize(dv0->bitangent); #else d = DotProduct(dv0->tangent, dv0->normal); VectorMA(dv0->tangent, -d, dv0->normal, dv0->tangent); VectorNormalize(dv0->tangent); d = DotProduct(dv0->bitangent, dv0->normal); VectorMA(dv0->bitangent, -d, dv0->normal, dv0->bitangent); VectorNormalize(dv0->bitangent); #endif } #endif #if 0 // do another extra smoothing for normals to avoid flat shading for(i = 0; i < (width * height); i++) { for(j = 0; j < (width * height); j++) { if(R_CompareVert(&ctrl2[i], &ctrl2[j], qfalse)) { VectorAdd(ctrl2[i].normal, ctrl2[j].normal, ctrl2[i].normal); } } VectorNormalize(ctrl2[i].normal); } #endif for(i = 0; i < width; i++) { for(j = 0; j < height; j++) { dv[0] = &ctrl2[j * width + i]; dv[1] = &ctrl[j][i]; VectorCopy(dv[0]->tangent, dv[1]->tangent); VectorCopy(dv[0]->bitangent, dv[1]->bitangent); } } } #endif static int MakeMeshTriangles(int width, int height, srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE], srfTriangle_t triangles[(MAX_GRID_SIZE-1)*(MAX_GRID_SIZE-1)*2]) { int i, j; int numTriangles; int w, h; srfVert_t *dv; static srfVert_t ctrl2[MAX_GRID_SIZE * MAX_GRID_SIZE]; h = height - 1; w = width - 1; numTriangles = 0; for(i = 0; i < h; i++) { for(j = 0; j < w; j++) { int v1, v2, v3, v4; // vertex order to be reckognized as tristrips v1 = i * width + j + 1; v2 = v1 - 1; v3 = v2 + width; v4 = v3 + 1; triangles[numTriangles].indexes[0] = v2; triangles[numTriangles].indexes[1] = v3; triangles[numTriangles].indexes[2] = v1; numTriangles++; triangles[numTriangles].indexes[0] = v1; triangles[numTriangles].indexes[1] = v3; triangles[numTriangles].indexes[2] = v4; numTriangles++; } } R_CalcSurfaceTriangleNeighbors(numTriangles, triangles); // FIXME: use more elegant way for(i = 0; i < width; i++) { for(j = 0; j < height; j++) { dv = &ctrl2[j * width + i]; *dv = ctrl[j][i]; } } R_CalcSurfaceTrianglePlanes(numTriangles, triangles, ctrl2); return numTriangles; } /* ============ InvertCtrl ============ */ static void InvertCtrl( int width, int height, srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE] ) { int i, j; srfVert_t temp; for ( i = 0 ; i < height ; i++ ) { for ( j = 0 ; j < width/2 ; j++ ) { temp = ctrl[i][j]; ctrl[i][j] = ctrl[i][width-1-j]; ctrl[i][width-1-j] = temp; } } } /* ================= InvertErrorTable ================= */ static void InvertErrorTable( float errorTable[2][MAX_GRID_SIZE], int width, int height ) { int i; float copy[2][MAX_GRID_SIZE]; Com_Memcpy( copy, errorTable, sizeof( copy ) ); for ( i = 0 ; i < width ; i++ ) { errorTable[1][i] = copy[0][i]; //[width-1-i]; } for ( i = 0 ; i < height ; i++ ) { errorTable[0][i] = copy[1][height-1-i]; } } /* ================== PutPointsOnCurve ================== */ static void PutPointsOnCurve( srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE], int width, int height ) { int i, j; srfVert_t prev, next; for ( i = 0 ; i < width ; i++ ) { for ( j = 1 ; j < height ; j += 2 ) { LerpDrawVert( &ctrl[j][i], &ctrl[j+1][i], &prev ); LerpDrawVert( &ctrl[j][i], &ctrl[j-1][i], &next ); LerpDrawVert( &prev, &next, &ctrl[j][i] ); } } for ( j = 0 ; j < height ; j++ ) { for ( i = 1 ; i < width ; i += 2 ) { LerpDrawVert( &ctrl[j][i], &ctrl[j][i+1], &prev ); LerpDrawVert( &ctrl[j][i], &ctrl[j][i-1], &next ); LerpDrawVert( &prev, &next, &ctrl[j][i] ); } } } /* ================= R_CreateSurfaceGridMesh ================= */ srfGridMesh_t *R_CreateSurfaceGridMesh(int width, int height, srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE], float errorTable[2][MAX_GRID_SIZE], int numTriangles, srfTriangle_t triangles[(MAX_GRID_SIZE-1)*(MAX_GRID_SIZE-1)*2]) { int i, j, size; srfVert_t *vert; vec3_t tmpVec; srfGridMesh_t *grid; // copy the results out to a grid size = (width * height - 1) * sizeof( srfVert_t ) + sizeof( *grid ); #ifdef PATCH_STITCHING grid = /*ri.Hunk_Alloc*/ ri.Malloc( size ); Com_Memset(grid, 0, size); grid->widthLodError = /*ri.Hunk_Alloc*/ ri.Malloc( width * 4 ); Com_Memcpy( grid->widthLodError, errorTable[0], width * 4 ); grid->heightLodError = /*ri.Hunk_Alloc*/ ri.Malloc( height * 4 ); Com_Memcpy( grid->heightLodError, errorTable[1], height * 4 ); grid->numTriangles = numTriangles; grid->triangles = ri.Malloc(grid->numTriangles * sizeof(srfTriangle_t)); Com_Memcpy(grid->triangles, triangles, numTriangles * sizeof(srfTriangle_t)); grid->numVerts = (width * height); grid->verts = ri.Malloc(grid->numVerts * sizeof(srfVert_t)); #else grid = ri.Hunk_Alloc( size ); Com_Memset(grid, 0, size); grid->widthLodError = ri.Hunk_Alloc( width * 4 ); Com_Memcpy( grid->widthLodError, errorTable[0], width * 4 ); grid->heightLodError = ri.Hunk_Alloc( height * 4 ); Com_Memcpy( grid->heightLodError, errorTable[1], height * 4 ); grid->numTriangles = numTriangles; grid->triangles = ri.Hunk_Alloc(grid->numTriangles * sizeof(srfTriangle_t), h_low); Com_Memcpy(grid->triangles, triangles, numTriangles * sizeof(srfTriangle_t)); grid->numVerts = (width * height); grid->verts = ri.Hunk_Alloc(grid->numVerts * sizeof(srfVert_t), h_low); #endif grid->width = width; grid->height = height; grid->surfaceType = SF_GRID; ClearBounds( grid->meshBounds[0], grid->meshBounds[1] ); for ( i = 0 ; i < width ; i++ ) { for ( j = 0 ; j < height ; j++ ) { vert = &grid->verts[j*width+i]; *vert = ctrl[j][i]; AddPointToBounds( vert->xyz, grid->meshBounds[0], grid->meshBounds[1] ); } } // compute local origin and bounds VectorAdd( grid->meshBounds[0], grid->meshBounds[1], grid->localOrigin ); VectorScale( grid->localOrigin, 0.5f, grid->localOrigin ); VectorSubtract( grid->meshBounds[0], grid->localOrigin, tmpVec ); grid->meshRadius = VectorLength( tmpVec ); VectorCopy( grid->localOrigin, grid->lodOrigin ); grid->lodRadius = grid->meshRadius; // return grid; } /* ================= R_FreeSurfaceGridMesh ================= */ void R_FreeSurfaceGridMesh( srfGridMesh_t *grid ) { ri.Free(grid->widthLodError); ri.Free(grid->heightLodError); ri.Free(grid->triangles); ri.Free(grid->verts); ri.Free(grid); } /* ================= R_SubdividePatchToGrid ================= */ srfGridMesh_t *R_SubdividePatchToGrid( int width, int height, srfVert_t points[MAX_PATCH_SIZE*MAX_PATCH_SIZE] ) { int i, j, k, l; srfVert_t_cleared( prev ); srfVert_t_cleared( next ); srfVert_t_cleared( mid ); float len, maxLen; int dir; int t; srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE]; float errorTable[2][MAX_GRID_SIZE]; int numTriangles; static srfTriangle_t triangles[(MAX_GRID_SIZE-1)*(MAX_GRID_SIZE-1)*2]; int consecutiveComplete; for ( i = 0 ; i < width ; i++ ) { for ( j = 0 ; j < height ; j++ ) { ctrl[j][i] = points[j*width+i]; } } for ( dir = 0 ; dir < 2 ; dir++ ) { for ( j = 0 ; j < MAX_GRID_SIZE ; j++ ) { errorTable[dir][j] = 0; } consecutiveComplete = 0; // horizontal subdivisions for ( j = 0 ; ; j = (j + 2) % (width - 1) ) { // check subdivided midpoints against control points // FIXME: also check midpoints of adjacent patches against the control points // this would basically stitch all patches in the same LOD group together. maxLen = 0; for ( i = 0 ; i < height ; i++ ) { vec3_t midxyz; vec3_t midxyz2; vec3_t dir; vec3_t projected; float d; // calculate the point on the curve for ( l = 0 ; l < 3 ; l++ ) { midxyz[l] = (ctrl[i][j].xyz[l] + ctrl[i][j+1].xyz[l] * 2 + ctrl[i][j+2].xyz[l] ) * 0.25f; } // see how far off the line it is // using dist-from-line will not account for internal // texture warping, but it gives a lot less polygons than // dist-from-midpoint VectorSubtract( midxyz, ctrl[i][j].xyz, midxyz ); VectorSubtract( ctrl[i][j+2].xyz, ctrl[i][j].xyz, dir ); VectorNormalize( dir ); d = DotProduct( midxyz, dir ); VectorScale( dir, d, projected ); VectorSubtract( midxyz, projected, midxyz2); len = VectorLengthSquared( midxyz2 ); // we will do the sqrt later if ( len > maxLen ) { maxLen = len; } } maxLen = sqrt(maxLen); // if all the points are on the lines, remove the entire columns if ( maxLen < 0.1f ) { errorTable[dir][j+1] = 999; // if we go over the whole grid twice without adding any columns, stop if (++consecutiveComplete >= width) break; continue; } // see if we want to insert subdivided columns if ( width + 2 > MAX_GRID_SIZE ) { errorTable[dir][j+1] = 1.0f/maxLen; break; // can't subdivide any more } if ( maxLen <= r_subdivisions->value ) { errorTable[dir][j+1] = 1.0f/maxLen; // if we go over the whole grid twice without adding any columns, stop if (++consecutiveComplete >= width) break; continue; // didn't need subdivision } errorTable[dir][j+2] = 1.0f/maxLen; consecutiveComplete = 0; // insert two columns and replace the peak width += 2; for ( i = 0 ; i < height ; i++ ) { LerpDrawVert( &ctrl[i][j], &ctrl[i][j+1], &prev ); LerpDrawVert( &ctrl[i][j+1], &ctrl[i][j+2], &next ); LerpDrawVert( &prev, &next, &mid ); for ( k = width - 1 ; k > j + 3 ; k-- ) { ctrl[i][k] = ctrl[i][k-2]; } ctrl[i][j + 1] = prev; ctrl[i][j + 2] = mid; ctrl[i][j + 3] = next; } // skip the new one, we'll get it on the next pass j += 2; } Transpose( width, height, ctrl ); t = width; width = height; height = t; } // put all the aproximating points on the curve PutPointsOnCurve( ctrl, width, height ); // cull out any rows or columns that are colinear for ( i = 1 ; i < width-1 ; i++ ) { if ( errorTable[0][i] != 999 ) { continue; } for ( j = i+1 ; j < width ; j++ ) { for ( k = 0 ; k < height ; k++ ) { ctrl[k][j-1] = ctrl[k][j]; } errorTable[0][j-1] = errorTable[0][j]; } width--; } for ( i = 1 ; i < height-1 ; i++ ) { if ( errorTable[1][i] != 999 ) { continue; } for ( j = i+1 ; j < height ; j++ ) { for ( k = 0 ; k < width ; k++ ) { ctrl[j-1][k] = ctrl[j][k]; } errorTable[1][j-1] = errorTable[1][j]; } height--; } #if 1 // flip for longest tristrips as an optimization // the results should be visually identical with or // without this step if ( height > width ) { Transpose( width, height, ctrl ); InvertErrorTable( errorTable, width, height ); t = width; width = height; height = t; InvertCtrl( width, height, ctrl ); } #endif // calculate triangles numTriangles = MakeMeshTriangles(width, height, ctrl, triangles); // calculate normals MakeMeshNormals( width, height, ctrl ); #ifdef USE_VERT_TANGENT_SPACE MakeMeshTangentVectors(width, height, ctrl, numTriangles, triangles); #endif return R_CreateSurfaceGridMesh(width, height, ctrl, errorTable, numTriangles, triangles); } /* =============== R_GridInsertColumn =============== */ srfGridMesh_t *R_GridInsertColumn( srfGridMesh_t *grid, int column, int row, vec3_t point, float loderror ) { int i, j; int width, height, oldwidth; srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE]; float errorTable[2][MAX_GRID_SIZE]; float lodRadius; vec3_t lodOrigin; int numTriangles; static srfTriangle_t triangles[(MAX_GRID_SIZE-1)*(MAX_GRID_SIZE-1)*2]; oldwidth = 0; width = grid->width + 1; if (width > MAX_GRID_SIZE) return NULL; height = grid->height; for (i = 0; i < width; i++) { if (i == column) { //insert new column for (j = 0; j < grid->height; j++) { LerpDrawVert( &grid->verts[j * grid->width + i-1], &grid->verts[j * grid->width + i], &ctrl[j][i] ); if (j == row) VectorCopy(point, ctrl[j][i].xyz); } errorTable[0][i] = loderror; continue; } errorTable[0][i] = grid->widthLodError[oldwidth]; for (j = 0; j < grid->height; j++) { ctrl[j][i] = grid->verts[j * grid->width + oldwidth]; } oldwidth++; } for (j = 0; j < grid->height; j++) { errorTable[1][j] = grid->heightLodError[j]; } // put all the aproximating points on the curve //PutPointsOnCurve( ctrl, width, height ); // calculate triangles numTriangles = MakeMeshTriangles(width, height, ctrl, triangles); // calculate normals MakeMeshNormals( width, height, ctrl ); VectorCopy(grid->lodOrigin, lodOrigin); lodRadius = grid->lodRadius; // free the old grid R_FreeSurfaceGridMesh(grid); // create a new grid grid = R_CreateSurfaceGridMesh(width, height, ctrl, errorTable, numTriangles, triangles); grid->lodRadius = lodRadius; VectorCopy(lodOrigin, grid->lodOrigin); return grid; } /* =============== R_GridInsertRow =============== */ srfGridMesh_t *R_GridInsertRow( srfGridMesh_t *grid, int row, int column, vec3_t point, float loderror ) { int i, j; int width, height, oldheight; srfVert_t ctrl[MAX_GRID_SIZE][MAX_GRID_SIZE]; float errorTable[2][MAX_GRID_SIZE]; float lodRadius; vec3_t lodOrigin; int numTriangles; static srfTriangle_t triangles[(MAX_GRID_SIZE-1)*(MAX_GRID_SIZE-1)*2]; oldheight = 0; width = grid->width; height = grid->height + 1; if (height > MAX_GRID_SIZE) return NULL; for (i = 0; i < height; i++) { if (i == row) { //insert new row for (j = 0; j < grid->width; j++) { LerpDrawVert( &grid->verts[(i-1) * grid->width + j], &grid->verts[i * grid->width + j], &ctrl[i][j] ); if (j == column) VectorCopy(point, ctrl[i][j].xyz); } errorTable[1][i] = loderror; continue; } errorTable[1][i] = grid->heightLodError[oldheight]; for (j = 0; j < grid->width; j++) { ctrl[i][j] = grid->verts[oldheight * grid->width + j]; } oldheight++; } for (j = 0; j < grid->width; j++) { errorTable[0][j] = grid->widthLodError[j]; } // put all the aproximating points on the curve //PutPointsOnCurve( ctrl, width, height ); // calculate triangles numTriangles = MakeMeshTriangles(width, height, ctrl, triangles); // calculate normals MakeMeshNormals( width, height, ctrl ); VectorCopy(grid->lodOrigin, lodOrigin); lodRadius = grid->lodRadius; // free the old grid R_FreeSurfaceGridMesh(grid); // create a new grid grid = R_CreateSurfaceGridMesh(width, height, ctrl, errorTable, numTriangles, triangles); grid->lodRadius = lodRadius; VectorCopy(lodOrigin, grid->lodOrigin); return grid; }