1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642 |
- /*
- ===========================================================================
- 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 <http://www.gnu.org/licenses/>.
- 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
- #ifdef WIN32
- #include <windows.h>
- #include <GL/gl.h>
- #include <GL/glu.h>
- #include "../../../sys/win32/win_local.h"
- #endif
- #include "../../../renderer/tr_local.h"
- /*
- render a normalmap tga file from an ase model for bump mapping
- To make ray-tracing into the high poly mesh efficient, we preconstruct
- a 3D hash table of the triangles that need to be tested for a given source
- point.
- This task is easier than a general ray tracing optimization, because we
- known that all of the triangles are going to be "near" the source point.
- TraceFraction determines the maximum distance in any direction that
- a trace will go. It is expressed as a fraction of the largest axis of
- the bounding box, so it doesn't matter what units are used for modeling.
- */
- #define MAX_QPATH 256
- #define DEFAULT_TRACE_FRACTION 0.05
- #define INITIAL_TRI_TO_LINK_EXPANSION 16 // can grow as needed
- #define HASH_AXIS_BINS 100
- typedef struct {
- int faceNum;
- int nextLink;
- } triLink_t;
- typedef struct {
- int triLink;
- int rayNumber; // don't need to test again if still on same ray
- } binLink_t;
- #define MAX_LINKS_PER_BLOCK 0x100000
- #define MAX_LINK_BLOCKS 0x100
- typedef struct {
- idBounds bounds;
- float binSize[3];
- int numLinkBlocks;
- triLink_t *linkBlocks[MAX_LINK_BLOCKS];
- binLink_t binLinks[HASH_AXIS_BINS][HASH_AXIS_BINS][HASH_AXIS_BINS];
- } triHash_t;
- typedef struct {
- char outputName[MAX_QPATH];
- char highName[MAX_QPATH];
- byte *localPic;
- byte *globalPic;
- byte *colorPic;
- float *edgeDistances; // starts out -1 for untraced, for each texel, 0 = true interior, >0 = off-edge rasterization
- int width, height;
- int antiAlias;
- int outline;
- bool saveGlobalMap;
- bool saveColorMap;
- float traceFrac;
- float traceDist;
- srfTriangles_t *mesh; // high poly mesh
- idRenderModel *highModel;
- triHash_t *hash;
- } renderBump_t;
- static float traceFraction;
- static int rayNumber; // for avoiding retests of bins and faces
- static int oldWidth, oldHeight;
- /*
- ===============
- SaveWindow
- ===============
- */
- static void SaveWindow( void ) {
- oldWidth = glConfig.vidWidth;
- oldHeight = glConfig.vidHeight;
- }
- /*
- ===============
- ResizeWindow
- ===============
- */
- static void ResizeWindow( int width, int height ) {
- #ifdef WIN32
- int winWidth, winHeight;
- if ( glConfig.isFullscreen ) {
- winWidth = width;
- winHeight = height;
- } else {
- RECT r;
- // adjust width and height for window border
- r.bottom = height;
- r.left = 0;
- r.top = 0;
- r.right = width;
- AdjustWindowRect (&r, WINDOW_STYLE|WS_SYSMENU, FALSE);
- winHeight = r.bottom - r.top;
- winWidth = r.right - r.left;
- }
- SetWindowPos( win32.hWnd, HWND_TOP, 0, 0, winWidth, winHeight, SWP_SHOWWINDOW );
- qwglMakeCurrent( win32.hDC, win32.hGLRC );
- #endif
- }
- /*
- ===============
- RestoreWindow
- ===============
- */
- static void RestoreWindow( void ) {
- #ifdef WIN32
- int winWidth, winHeight;
- if ( glConfig.isFullscreen ) {
- winWidth = oldWidth;
- winHeight = oldHeight;
- } else {
- RECT r;
- // adjust width and height for window border
- r.bottom = oldHeight;
- r.left = 0;
- r.top = 0;
- r.right = oldWidth;
- AdjustWindowRect (&r, WINDOW_STYLE|WS_SYSMENU, FALSE);
- winHeight = r.bottom - r.top;
- winWidth = r.right - r.left;
- }
- SetWindowPos( win32.hWnd, HWND_TOP, 0, 0, winWidth, winHeight, SWP_SHOWWINDOW );
- #endif
- }
- /*
- ================
- OutlineNormalMap
- Puts a single pixel border around all non-empty pixels
- Does NOT copy the alpha channel, so it can be used as
- an alpha test map.
- ================
- */
- static void OutlineNormalMap( byte *data, int width, int height, int emptyR, int emptyG, int emptyB ) {
- byte *orig;
- int i, j, k, l;
- idVec3 normal;
- byte *out;
- orig = (byte *)Mem_Alloc( width * height * 4 );
- memcpy( orig, data, width * height * 4 );
- for ( i = 0 ; i < width ; i++ ) {
- for ( j = 0 ; j < height ; j++ ) {
- out = data + ( j * width + i ) * 4;
- if ( out[0] != emptyR || out[1] != emptyG || out[2] != emptyB ) {
- continue;
- }
- normal = vec3_origin;
- for ( k = -1 ; k < 2 ; k++ ) {
- for ( l = -1 ; l < 2 ; l++ ) {
- byte *in;
- in = orig + ( ((j+l)&(height-1))*width + ((i+k)&(width-1)) ) * 4;
- if ( in[0] == emptyR && in[1] == emptyG && in[2] == emptyB ) {
- continue;
- }
- normal[0] += ( in[0] - 128 );
- normal[1] += ( in[1] - 128 );
- normal[2] += ( in[2] - 128 );
- }
- }
- if ( normal.Normalize() < 0.5 ) {
- continue; // no valid samples
- }
- out[0] = 128 + 127 * normal[0];
- out[1] = 128 + 127 * normal[1];
- out[2] = 128 + 127 * normal[2];
- }
- }
- Mem_Free( orig );
- }
- /*
- ================
- OutlineColorMap
- Puts a single pixel border around all non-empty pixels
- Does NOT copy the alpha channel, so it can be used as
- an alpha test map.
- ================
- */
- static void OutlineColorMap( byte *data, int width, int height, int emptyR, int emptyG, int emptyB ) {
- byte *orig;
- int i, j, k, l;
- idVec3 normal;
- byte *out;
- orig = (byte *)Mem_Alloc( width * height * 4 );
- memcpy( orig, data, width * height * 4 );
- for ( i = 0 ; i < width ; i++ ) {
- for ( j = 0 ; j < height ; j++ ) {
- out = data + ( j * width + i ) * 4;
- if ( out[0] != emptyR || out[1] != emptyG || out[2] != emptyB ) {
- continue;
- }
- normal = vec3_origin;
- int count = 0;
- for ( k = -1 ; k < 2 ; k++ ) {
- for ( l = -1 ; l < 2 ; l++ ) {
- byte *in;
- in = orig + ( ((j+l)&(height-1))*width + ((i+k)&(width-1)) ) * 4;
- if ( in[0] == emptyR && in[1] == emptyG && in[2] == emptyB ) {
- continue;
- }
- normal[0] += in[0];
- normal[1] += in[1];
- normal[2] += in[2];
- count++;
- }
- }
- if ( !count ) {
- continue;
- }
- normal *= (1.0 / count );
- out[0] = normal[0];
- out[1] = normal[1];
- out[2] = normal[2];
- }
- }
- Mem_Free( orig );
- }
- /*
- ================
- FreeTriHash
- ================
- */
- static void FreeTriHash( triHash_t *hash ) {
- for ( int i = 0 ; i < hash->numLinkBlocks ; i++ ) {
- Mem_Free( hash->linkBlocks[i] );
- }
- Mem_Free( hash );
- }
- /*
- ================
- CreateTriHash
- ================
- */
- static triHash_t *CreateTriHash( const srfTriangles_t *highMesh ) {
- triHash_t *hash;
- int i, j, k, l;
- idBounds bounds, triBounds;
- int iBounds[2][3];
- int maxLinks, numLinks;
- hash = (triHash_t *)Mem_Alloc( sizeof( *hash ) );
- memset( hash, 0, sizeof( *hash ) );
- // find the bounding volume for the mesh
- bounds.Clear();
- for ( i = 0 ; i < highMesh->numVerts ; i++ ) {
- bounds.AddPoint( highMesh->verts[i].xyz );
- }
- hash->bounds = bounds;
- // divide each axis as needed
- for ( i = 0 ; i < 3 ; i++ ) {
- hash->binSize[i] = ( bounds[1][i] - bounds[0][i] ) / HASH_AXIS_BINS;
- if ( hash->binSize[i] <= 0 ) {
- common->FatalError( "CreateTriHash: bad bounds: (%f %f %f) to (%f %f %f)",
- bounds[0][0],bounds[0][1],bounds[0][2],
- bounds[1][0],bounds[1][1],bounds[1][2] );
- }
- }
- // a -1 link number terminated the link chain
- memset( hash->binLinks, -1, sizeof( hash->binLinks ) );
- numLinks = 0;
- hash->linkBlocks[hash->numLinkBlocks] = (triLink_t *)Mem_Alloc( MAX_LINKS_PER_BLOCK * sizeof( triLink_t ) );
- hash->numLinkBlocks++;
- maxLinks = hash->numLinkBlocks * MAX_LINKS_PER_BLOCK;
- // for each triangle, place a triLink in each bin that might reference it
- for ( i = 0 ; i < highMesh->numIndexes ; i+=3 ) {
- // determine which hash bins the triangle will need to be in
- triBounds.Clear();
- for ( j = 0 ; j < 3 ; j++ ) {
- triBounds.AddPoint( highMesh->verts[ highMesh->indexes[i+j] ].xyz );
- }
- for ( j = 0 ; j < 3 ; j++ ) {
- iBounds[0][j] = ( triBounds[0][j] - hash->bounds[0][j] ) / hash->binSize[j];
- iBounds[0][j] -= 0.001; // epsilon
- if ( iBounds[0][j] < 0 ) {
- iBounds[0][j] = 0;
- } else if ( iBounds[0][j] >= HASH_AXIS_BINS ) {
- iBounds[0][j] = HASH_AXIS_BINS-1;
- }
- iBounds[1][j] = ( triBounds[1][j] - hash->bounds[0][j] ) / hash->binSize[j];
- iBounds[0][j] += 0.001; // epsilon
- if ( iBounds[1][j] < 0 ) {
- iBounds[1][j] = 0;
- } else if ( iBounds[1][j] >= HASH_AXIS_BINS ) {
- iBounds[1][j] = HASH_AXIS_BINS-1;
- }
- }
- // add the links
- for ( j = iBounds[0][0] ; j <= iBounds[1][0] ; j++ ) {
- for ( k = iBounds[0][1] ; k <= iBounds[1][1] ; k++ ) {
- for ( l = iBounds[0][2] ; l <= iBounds[1][2] ; l++ ) {
- if ( numLinks == maxLinks ) {
- hash->linkBlocks[hash->numLinkBlocks] = (triLink_t *)Mem_Alloc( MAX_LINKS_PER_BLOCK * sizeof( triLink_t ) );
- hash->numLinkBlocks++;
- maxLinks = hash->numLinkBlocks * MAX_LINKS_PER_BLOCK;
- }
- triLink_t *link = &hash->linkBlocks[ numLinks / MAX_LINKS_PER_BLOCK ][ numLinks % MAX_LINKS_PER_BLOCK ];
- link->faceNum = i / 3;
- link->nextLink = hash->binLinks[j][k][l].triLink;
- hash->binLinks[j][k][l].triLink = numLinks;
- numLinks++;
- }
- }
- }
- }
- common->Printf( "%i triangles made %i links\n", highMesh->numIndexes / 3, numLinks );
- return hash;
- }
- /*
- =================
- TraceToMeshFace
- Returns the distance from the point to the intersection, or DIST_NO_INTERSECTION
- =================
- */
- #define DIST_NO_INTERSECTION -999999999.0f
- static float TraceToMeshFace( const srfTriangles_t *highMesh, int faceNum,
- float minDist, float maxDist,
- const idVec3 &point, const idVec3 &normal, idVec3 &sampledNormal,
- byte sampledColor[4] ) {
- int j;
- float dist;
- const idVec3 *v[3];
- const idPlane *plane;
- idVec3 edge;
- float d;
- idVec3 dir[3];
- float baseArea;
- float bary[3];
- idVec3 testVert;
- v[0] = &highMesh->verts[ highMesh->indexes[ faceNum * 3 + 0 ] ].xyz;
- v[1] = &highMesh->verts[ highMesh->indexes[ faceNum * 3 + 1 ] ].xyz;
- v[2] = &highMesh->verts[ highMesh->indexes[ faceNum * 3 + 2 ] ].xyz;
- plane = highMesh->facePlanes + faceNum;
- // only test against planes facing the same direction as our normal
- d = plane->Normal() * normal;
- if ( d <= 0.0001f ) {
- return DIST_NO_INTERSECTION;
- }
- // find the point of impact on the plane
- dist = plane->Distance( point );
- dist /= -d;
- testVert = point + dist * normal;
- // if this would be beyond our requested trace distance,
- // don't even check it
- if ( dist > maxDist ) {
- return DIST_NO_INTERSECTION;
- }
- if ( dist < minDist ) {
- return DIST_NO_INTERSECTION;
- }
- // if normal is inside all edge planes, this face is hit
- VectorSubtract( *v[0], point, dir[0] );
- VectorSubtract( *v[1], point, dir[1] );
- edge = dir[0].Cross( dir[1] );
- d = DotProduct( normal, edge );
- if ( d > 0.0f ) {
- return DIST_NO_INTERSECTION;
- }
- VectorSubtract( *v[2], point, dir[2] );
- edge = dir[1].Cross( dir[2] );
- d = DotProduct( normal, edge );
- if ( d > 0.0f ) {
- return DIST_NO_INTERSECTION;
- }
- edge = dir[2].Cross( dir[0] );
- d = DotProduct( normal, edge );
- if ( d > 0.0f ) {
- return DIST_NO_INTERSECTION;
- }
- // calculate barycentric coordinates of the impact point
- // on the high poly triangle
- bary[0] = idWinding::TriangleArea( testVert, *v[1], *v[2] );
- bary[1] = idWinding::TriangleArea( *v[0], testVert, *v[2] );
- bary[2] = idWinding::TriangleArea( *v[0], *v[1], testVert );
- baseArea = idWinding::TriangleArea( *v[0], *v[1], *v[2] );
- bary[0] /= baseArea;
- bary[1] /= baseArea;
- bary[2] /= baseArea;
- if ( bary[0] + bary[1] + bary[2] > 1.1 ) {
- bary[0] = bary[0];
- return DIST_NO_INTERSECTION;
- }
- // triangularly interpolate the normals to the sample point
- sampledNormal = vec3_origin;
- for ( j = 0 ; j < 3 ; j++ ) {
- sampledNormal += bary[j] * highMesh->verts[ highMesh->indexes[ faceNum * 3 + j ] ].normal;
- }
- sampledNormal.Normalize();
- sampledColor[0] = sampledColor[1] = sampledColor[2] = sampledColor[3] = 0;
- for ( int i = 0 ; i < 4 ; i++ ) {
- float color = 0.0f;
- for ( j = 0 ; j < 3 ; j++ ) {
- color += bary[j] * highMesh->verts[ highMesh->indexes[ faceNum * 3 + j ] ].color[i];
- }
- sampledColor[i] = color;
- }
- return dist;
- }
- /*
- ================
- SampleHighMesh
- Find the best surface normal in the high poly mesh
- for a ray coming from the surface of the low poly mesh
- Returns false if the trace doesn't hit anything
- ================
- */
- static bool SampleHighMesh( const renderBump_t *rb,
- const idVec3 &point, const idVec3 &direction, idVec3 &sampledNormal,
- byte sampledColor[4] ) {
- idVec3 p;
- binLink_t *bl;
- int linkNum;
- int faceNum;
- float dist, bestDist;
- int block[3];
- float maxDist;
- int c_hits;
- int i;
- idVec3 normal;
- // we allow non-normalized directions on input
- normal = direction;
- normal.Normalize();
- // increment our uniqueness counter (FIXME: make thread safe?)
- rayNumber++;
- // the max distance will be the traceFrac times the longest axis of the high poly model
- bestDist = -rb->traceDist;
- maxDist = rb->traceDist;
- sampledNormal = vec3_origin;
- c_hits = 0;
- // this is a pretty damn lazy way to walk through a 3D grid, and has a (very slight)
- // chance of missing a triangle in a corner crossing case
- #define RAY_STEPS 100
- for ( i = 0 ; i < RAY_STEPS ; i++ ) {
- p = point - rb->hash->bounds[0] + normal * ( -1.0 + 2.0 * i / RAY_STEPS ) * rb->traceDist;
- block[0] = floor( p[0] / rb->hash->binSize[0] );
- block[1] = floor( p[1] / rb->hash->binSize[1] );
- block[2] = floor( p[2] / rb->hash->binSize[2] );
- if ( block[0] < 0 || block[0] >= HASH_AXIS_BINS ) {
- continue;
- }
- if ( block[1] < 0 || block[1] >= HASH_AXIS_BINS ) {
- continue;
- }
- if ( block[2] < 0 || block[2] >= HASH_AXIS_BINS ) {
- continue;
- }
- // FIXME: casting away const
- bl = (binLink_t *)&rb->hash->binLinks[block[0]][block[1]][block[2]];
- if ( bl->rayNumber == rayNumber ) {
- continue; // already tested this block
- }
- bl->rayNumber = rayNumber;
- linkNum = bl->triLink;
- triLink_t *link;
- for ( ; linkNum != -1 ; linkNum = link->nextLink ) {
- link = &rb->hash->linkBlocks[ linkNum / MAX_LINKS_PER_BLOCK ][ linkNum % MAX_LINKS_PER_BLOCK ];
- faceNum = link->faceNum;
- dist = TraceToMeshFace( rb->mesh, faceNum,
- bestDist, maxDist, point, normal, sampledNormal, sampledColor );
- if ( dist == DIST_NO_INTERSECTION ) {
- continue;
- }
- c_hits++;
- // continue looking for a better match
- bestDist = dist;
- }
- }
- return (bool)( bestDist > -rb->traceDist );
- }
- /*
- =============
- TriTextureArea
- This may be negatove
- =============
- */
- static float TriTextureArea( const float a[2], const float b[2], const float c[2] ) {
- idVec3 d1, d2;
- idVec3 cross;
- float area;
- d1[0] = b[0] - a[0];
- d1[1] = b[1] - a[1];
- d1[2] = 0;
- d2[0] = c[0] - a[0];
- d2[1] = c[1] - a[1];
- d2[2] = 0;
-
- cross = d1.Cross( d2 );
- area = 0.5 * cross.Length();
- if ( cross[2] < 0 ) {
- return -area;
- } else {
- return area;
- }
- }
- /*
- ================
- RasterizeTriangle
- It is ok for the texcoords to wrap around, the rasterization
- will deal with it properly.
- ================
- */
- static void RasterizeTriangle( const srfTriangles_t *lowMesh, const idVec3 *lowMeshNormals, int lowFaceNum,
- renderBump_t *rb ) {
- int i, j, k;
- float bounds[2][2];
- float ibounds[2][2];
- float verts[3][2];
- float testVert[2];
- float bary[3];
- byte *localDest, *globalDest, *colorDest;
- float edge[3][3];
- idVec3 sampledNormal;
- byte sampledColor[4];
- idVec3 point, normal, traceNormal, tangents[2];
- float baseArea, totalArea;
- int r, g, b;
- idVec3 localNormal;
- // this is a brain-dead rasterizer, but compared to the ray trace,
- // nothing we do here is going to matter performance-wise
- // adjust for resolution and texel centers
- verts[0][0] = lowMesh->verts[ lowMesh->indexes[lowFaceNum*3+0] ].st[0] * rb->width - 0.5;
- verts[1][0] = lowMesh->verts[ lowMesh->indexes[lowFaceNum*3+1] ].st[0] * rb->width - 0.5;
- verts[2][0] = lowMesh->verts[ lowMesh->indexes[lowFaceNum*3+2] ].st[0] * rb->width - 0.5;
- verts[0][1] = lowMesh->verts[ lowMesh->indexes[lowFaceNum*3+0] ].st[1] * rb->height - 0.5;
- verts[1][1] = lowMesh->verts[ lowMesh->indexes[lowFaceNum*3+1] ].st[1] * rb->height - 0.5;
- verts[2][1] = lowMesh->verts[ lowMesh->indexes[lowFaceNum*3+2] ].st[1] * rb->height - 0.5;
- // find the texcoord bounding box
- bounds[0][0] = 99999;
- bounds[0][1] = 99999;
- bounds[1][0] = -99999;
- bounds[1][1] = -99999;
- for ( i = 0 ; i < 2 ; i++ ) {
- for ( j = 0 ; j < 3 ; j++ ) {
- if ( verts[j][i] < bounds[0][i] ) {
- bounds[0][i] = verts[j][i];
- }
- if ( verts[j][i] > bounds[1][i] ) {
- bounds[1][i] = verts[j][i];
- }
- }
- }
- // we intentionally rasterize somewhat outside the triangles, so
- // the bilerp support texels (which may be anti-aliased down)
- // are not just duplications of what is on the interior
- const float edgeOverlap = 4.0;
- ibounds[0][0] = floor( bounds[0][0] - edgeOverlap );
- ibounds[1][0] = ceil( bounds[1][0] + edgeOverlap );
- ibounds[0][1] = floor( bounds[0][1] - edgeOverlap );
- ibounds[1][1] = ceil( bounds[1][1] + edgeOverlap );
- // calculate edge vectors
- for ( i = 0 ; i < 3 ; i++ ) {
- float *v1, *v2;
-
- v1 = verts[i];
- v2 = verts[(i+1)%3];
- edge[i][0] = v2[1] - v1[1];
- edge[i][1] = v1[0] - v2[0];
- float len = sqrt( edge[i][0] * edge[i][0] + edge[i][1] * edge[i][1] );
- edge[i][0] /= len;
- edge[i][1] /= len;
- edge[i][2] = -( v1[0] * edge[i][0] + v1[1] * edge[i][1] );
- }
- // itterate over the bounding box, testing against edge vectors
- for ( i = ibounds[0][1] ; i < ibounds[1][1] ; i++ ) {
- for ( j = ibounds[0][0] ; j < ibounds[1][0] ; j++ ) {
- float dists[3];
- k = ( ( i & (rb->height-1) ) * rb->width + ( j & (rb->width-1) ) ) * 4;
- colorDest = &rb->colorPic[k];
- localDest = &rb->localPic[k];
- globalDest = &rb->globalPic[k];
- #define SKIP_MIRRORS
- float *edgeDistance = &rb->edgeDistances[k/4];
- #ifdef SKIP_MIRRORS
- // if this texel has already been filled by a true interior pixel, don't overwrite it
- if ( *edgeDistance == 0 ) {
- continue;
- }
- #endif
- // check against the three edges to see if the pixel is inside the triangle
- for ( k = 0 ; k < 3 ; k++ ) {
- float v;
- v = i * edge[k][1] + j * edge[k][0] + edge[k][2];
- dists[k] = v;
- }
- // the edge polarities might be either way
- if ( ! ( ( dists[0] >= -edgeOverlap && dists[1] >= -edgeOverlap && dists[2] >= -edgeOverlap )
- || ( dists[0] <= edgeOverlap && dists[1] <= edgeOverlap && dists[2] <= edgeOverlap ) ) ) {
- continue;
- }
- bool edgeTexel;
- if ( ( dists[0] >= 0 && dists[1] >= 0 && dists[2] >= 0 )
- || ( dists[0] <= 0 && dists[1] <= 0 && dists[2] <= 0 ) ) {
- edgeTexel = false;
- } else {
- edgeTexel = true;
- #ifdef SKIP_MIRRORS
- // if this texel has already been filled by another edge pixel, don't overwrite it
- if ( *edgeDistance == 1 ) {
- continue;
- }
- #endif
- }
- // calculate the barycentric coordinates in the triangle for this sample
- testVert[0] = j;
- testVert[1] = i;
- baseArea = TriTextureArea( verts[0], verts[1], verts[2] );
- bary[0] = TriTextureArea( testVert, verts[1], verts[2] ) / baseArea;
- bary[1] = TriTextureArea( verts[0], testVert, verts[2] ) / baseArea;
- bary[2] = TriTextureArea( verts[0], verts[1], testVert ) / baseArea;
- totalArea = bary[0] + bary[1] + bary[2];
- if ( totalArea < 0.99 || totalArea > 1.01 ) {
- continue; // should never happen
- }
- // calculate the interpolated xyz, normal, and tangents of this sample
- point = vec3_origin;
- traceNormal = vec3_origin;
- normal = vec3_origin;
- tangents[0] = vec3_origin;
- tangents[1] = vec3_origin;
- for ( k = 0 ; k < 3 ; k++ ) {
- int index;
- index = lowMesh->indexes[lowFaceNum*3+k];
- point += bary[k] * lowMesh->verts[ index ].xyz;
- // traceNormal will differ from normal if the surface uses unsmoothedTangents
- traceNormal += bary[k] * lowMeshNormals[ index ];
- normal += bary[k] * lowMesh->verts[ index ].normal;
- tangents[0] += bary[k] * lowMesh->verts[ index ].tangents[0];
- tangents[1] += bary[k] * lowMesh->verts[ index ].tangents[1];
- }
- #if 0
- // this doesn't seem to make much difference
- // an argument can be made that these should not be normalized, because the interpolation
- // of the light position at rasterization time will be linear, not spherical
- normal.Normalize();
- tangents[0].Normalize();
- tangents[1].Normalize();
- #endif
- // find the best triangle in the high poly model for this
- // sampledNormal will normalized
- if ( !SampleHighMesh( rb, point, traceNormal, sampledNormal, sampledColor ) ) {
- #if 0
- // put bright red where all traces missed for debugging.
- // for production use, it is better to leave it blank so
- // the outlining fills it in
- globalDest[0] = 255;
- globalDest[1] = 0;
- globalDest[2] = 0;
- globalDest[3] = 255;
- localDest[0] = 255;
- localDest[1] = 0;
- localDest[2] = 0;
- localDest[3] = 255;
- #endif
- continue;
- }
- // mark whether this is an interior or edge texel
- *edgeDistance = ( edgeTexel ? 1.0 : 0 );
- // fill the object space normal map spot
- r = 128 + 127 * sampledNormal[0];
- g = 128 + 127 * sampledNormal[1];
- b = 128 + 127 * sampledNormal[2];
- globalDest[0] = r;
- globalDest[1] = g;
- globalDest[2] = b;
- globalDest[3] = 255;
- // transform to local tangent space
- idMat3 mat;
- mat[0] = tangents[0];
- mat[1] = tangents[1];
- mat[2] = normal;
- mat.InverseSelf();
- localNormal = mat * sampledNormal;
- localNormal.Normalize();
- r = 128 + 127 * localNormal[0];
- g = 128 + 127 * localNormal[1];
- b = 128 + 127 * localNormal[2];
- localDest[0] = r;
- localDest[1] = g;
- localDest[2] = b;
- localDest[3] = 255;
- colorDest[0] = sampledColor[0];
- colorDest[1] = sampledColor[1];
- colorDest[2] = sampledColor[2];
- colorDest[3] = sampledColor[3];
- }
- }
- }
- /*
- ================
- CombineModelSurfaces
- Frees the model and returns a new model with all triangles combined
- into one surface
- ================
- */
- static idRenderModel *CombineModelSurfaces( idRenderModel *model ) {
- int totalVerts;
- int totalIndexes;
- int numIndexes;
- int numVerts;
- int i, j;
- totalVerts = 0;
- totalIndexes = 0;
- for ( i = 0 ; i < model->NumSurfaces() ; i++ ) {
- const modelSurface_t *surf = model->Surface(i);
- totalVerts += surf->geometry->numVerts;
- totalIndexes += surf->geometry->numIndexes;
- }
- srfTriangles_t *newTri = R_AllocStaticTriSurf();
- R_AllocStaticTriSurfVerts( newTri, totalVerts );
- R_AllocStaticTriSurfIndexes( newTri, totalIndexes );
- newTri->numVerts = totalVerts;
- newTri->numIndexes = totalIndexes;
- newTri->bounds.Clear();
- idDrawVert *verts = newTri->verts;
- glIndex_t *indexes = newTri->indexes;
- numIndexes = 0;
- numVerts = 0;
- for ( i = 0 ; i < model->NumSurfaces() ; i++ ) {
- const modelSurface_t *surf = model->Surface(i);
- const srfTriangles_t *tri = surf->geometry;
- memcpy( verts + numVerts, tri->verts, tri->numVerts * sizeof( tri->verts[0] ) );
- for ( j = 0 ; j < tri->numIndexes ; j++ ) {
- indexes[numIndexes+j] = numVerts + tri->indexes[j];
- }
- newTri->bounds.AddBounds( tri->bounds );
- numIndexes += tri->numIndexes;
- numVerts += tri->numVerts;
- }
- modelSurface_t surf;
- surf.id = 0;
- surf.geometry = newTri;
- surf.shader = tr.defaultMaterial;
- idRenderModel *newModel = renderModelManager->AllocModel();
- newModel->AddSurface( surf );
- renderModelManager->FreeModel( model );
- return newModel;
- }
- /*
- ==============
- RenderBumpTriangles
- ==============
- */
- static void RenderBumpTriangles( srfTriangles_t *lowMesh, renderBump_t *rb ) {
- int i, j;
- RB_SetGL2D();
- qglDisable( GL_CULL_FACE );
- qglColor3f( 1, 1, 1 );
- qglMatrixMode( GL_PROJECTION );
- qglLoadIdentity();
- qglOrtho( 0, 1, 1, 0, -1, 1 );
- qglDisable( GL_BLEND );
- qglMatrixMode( GL_MODELVIEW );
- qglLoadIdentity();
- qglDisable( GL_DEPTH_TEST );
- qglClearColor(1,0,0,1);
- qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
- qglColor3f( 1, 1, 1 );
- // create smoothed normals for the surface, which might be
- // different than the normals at the vertexes if the
- // surface uses unsmoothedNormals, which only takes the
- // normal from a single triangle. We need properly smoothed
- // normals to make sure that the traces always go off normal
- // to the true surface.
- idVec3 *lowMeshNormals = (idVec3 *)Mem_ClearedAlloc( lowMesh->numVerts * sizeof( *lowMeshNormals ) );
- R_DeriveFacePlanes( lowMesh );
- R_CreateSilIndexes( lowMesh ); // recreate, merging the mirrored verts back together
- const idPlane *planes = lowMesh->facePlanes;
- for ( i = 0 ; i < lowMesh->numIndexes ; i += 3, planes++ ) {
- for ( j = 0 ; j < 3 ; j++ ) {
- int index;
- index = lowMesh->silIndexes[i+j];
- lowMeshNormals[index] += (*planes).Normal();
- }
- }
- // normalize and replicate from silIndexes to all indexes
- for ( i = 0 ; i < lowMesh->numIndexes ; i++ ) {
- lowMeshNormals[lowMesh->indexes[i]] = lowMeshNormals[lowMesh->silIndexes[i]];
- lowMeshNormals[lowMesh->indexes[i]].Normalize();
- }
- // rasterize each low poly face
- for ( j = 0 ; j < lowMesh->numIndexes ; j+=3 ) {
- // pump the event loop so the window can be dragged around
- Sys_GenerateEvents();
- RasterizeTriangle( lowMesh, lowMeshNormals, j/3, rb );
- qglClearColor(1,0,0,1);
- qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
- qglRasterPos2f( 0, 1 );
- qglPixelZoom( glConfig.vidWidth / (float)rb->width, glConfig.vidHeight / (float)rb->height );
- qglDrawPixels( rb->width, rb->height, GL_RGBA, GL_UNSIGNED_BYTE, rb->localPic );
- qglPixelZoom( 1, 1 );
- qglFlush();
- GLimp_SwapBuffers();
- }
- Mem_Free( lowMeshNormals );
- }
- /*
- ==============
- WriteRenderBump
- ==============
- */
- static void WriteRenderBump( renderBump_t *rb, int outLinePixels ) {
- int width, height;
- int i;
- idStr filename;
- renderModelManager->FreeModel( rb->highModel );
- FreeTriHash( rb->hash );
- width = rb->width;
- height = rb->height;
- #if 0
- // save the non-outlined version
- filename = source;
- filename.setFileExtension();
- filename.append( "_nooutline.tga" );
- common->Printf( "writing %s\n", filename.c_str() );
- WriteTGA( filename, globalPic, width, height );
- #endif
- // outline the image several times to help bilinear filtering across disconnected
- // edges, and mip-mapping
- for ( i = 0 ; i < outLinePixels ; i++ ) {
- OutlineNormalMap( rb->localPic, width, height, 128, 128, 128 );
- OutlineNormalMap( rb->globalPic, width, height, 128, 128, 128 );
- OutlineColorMap( rb->colorPic, width, height, 128, 128, 128 );
- }
- // filter down if we are anti-aliasing
- for ( i = 0 ; i < rb->antiAlias ; i++ ) {
- byte *old;
- old = rb->localPic;
- rb->localPic = R_MipMap( rb->localPic, width, height, false );
- Mem_Free( old );
- old = rb->globalPic;
- rb->globalPic = R_MipMap( rb->globalPic, width, height, false );
- Mem_Free( old );
- old = rb->colorPic;
- rb->colorPic = R_MipMap( rb->colorPic, width, height, false );
- Mem_Free( old );
- width >>= 1;
- height >>= 1;
- }
- // write out the local map
- filename = rb->outputName;
- filename.SetFileExtension( ".tga" );
- common->Printf( "writing %s (%i,%i)\n", filename.c_str(), width, height );
- R_WriteTGA( filename, rb->localPic, width, height );
- if ( rb->saveGlobalMap ) {
- filename = rb->outputName;
- filename.StripFileExtension();
- filename.Append( "_global.tga" );
- common->Printf( "writing %s (%i,%i)\n", filename.c_str(), width, height );
- R_WriteTGA( filename, rb->globalPic, width, height );
- }
- if ( rb->saveColorMap ) {
- filename = rb->outputName;
- filename.StripFileExtension();
- filename.Append( "_color.tga" );
- common->Printf( "writing %s (%i,%i)\n", filename.c_str(), width, height );
- R_WriteTGA( filename, rb->colorPic, width, height );
- }
- Mem_Free( rb->localPic );
- Mem_Free( rb->globalPic );
- Mem_Free( rb->colorPic );
- Mem_Free( rb->edgeDistances );
- }
- /*
- ===============
- InitRenderBump
- ===============
- */
- static void InitRenderBump( renderBump_t *rb ) {
- srfTriangles_t *mesh;
- idBounds bounds;
- int i, c;
- // load the ase file
- common->Printf( "loading %s...\n", rb->highName );
- rb->highModel = renderModelManager->AllocModel();
- rb->highModel->PartialInitFromFile( rb->highName );
- if ( !rb->highModel ) {
- common->Error( "failed to load %s", rb->highName );
- }
- // combine the high poly model into a single polyset
- if ( rb->highModel->NumSurfaces() != 1 ) {
- rb->highModel = CombineModelSurfaces( rb->highModel );
- }
- const modelSurface_t *surf = rb->highModel->Surface( 0 );
- mesh = surf->geometry;
- rb->mesh = mesh;
- R_DeriveFacePlanes( mesh );
- // create a face hash table to accelerate the tracing
- rb->hash = CreateTriHash( mesh );
- // bound the entire file
- R_BoundTriSurf( mesh );
- bounds = mesh->bounds;
- // the traceDist will be the traceFrac times the larges bounds axis
- rb->traceDist = 0;
- for ( i = 0 ; i < 3 ; i++ ) {
- float d;
- d = rb->traceFrac * ( bounds[1][i] - bounds[0][i] );
- if ( d > rb->traceDist ) {
- rb->traceDist = d;
- }
- }
- common->Printf( "trace fraction %4.2f = %6.2f model units\n", rb->traceFrac, rb->traceDist );
- c = rb->width * rb->height * 4;
- // local normal map
- rb->localPic = (byte *)Mem_Alloc( c );
- // global (object space, not surface space) normal map
- rb->globalPic = (byte *)Mem_Alloc( c );
- // color pic for artist reference
- rb->colorPic = (byte *)Mem_Alloc( c );
- // edgeDistance for marking outside-the-triangle traces
- rb->edgeDistances = (float *)Mem_Alloc( c );
- for ( i = 0 ; i < c ; i+=4 ) {
- rb->localPic[i+0] = 128;
- rb->localPic[i+1] = 128;
- rb->localPic[i+2] = 128;
- rb->localPic[i+3] = 0; // the artists use this for masking traced pixels sometimes
- rb->globalPic[i+0] = 128;
- rb->globalPic[i+1] = 128;
- rb->globalPic[i+2] = 128;
- rb->globalPic[i+3] = 0;
- rb->colorPic[i+0] = 128;
- rb->colorPic[i+1] = 128;
- rb->colorPic[i+2] = 128;
- rb->colorPic[i+3] = 0;
- rb->edgeDistances[i/4] = -1; // not traced yet
- }
- }
- /*
- ==============
- RenderBump_f
- ==============
- */
- void RenderBump_f( const idCmdArgs &args ) {
- idRenderModel *lowPoly;
- idStr source;
- int i, j;
- const char *cmdLine;
- int numRenderBumps;
- renderBump_t *renderBumps, *rb;
- renderBump_t opt;
- int startTime, endTime;
- // update the screen as we print
- common->SetRefreshOnPrint( true );
- // there should be a single parameter, the filename for a game loadable low-poly model
- if ( args.Argc() != 2 ) {
- common->Error( "Usage: renderbump <lowPolyModel>" );
- }
- common->Printf( "----- Renderbump %s -----\n", args.Argv( 1 ) );
- startTime = Sys_Milliseconds();
- // get the lowPoly model
- source = args.Argv( 1 );
- lowPoly = renderModelManager->CheckModel( source );
- if ( !lowPoly ) {
- common->Error( "Can't load model %s", source.c_str() );
- }
- renderBumps = (renderBump_t *)R_StaticAlloc( lowPoly->NumSurfaces() * sizeof( *renderBumps ) );
- numRenderBumps = 0;
- for ( i = 0 ; i < lowPoly->NumSurfaces() ; i++ ) {
- const modelSurface_t *ms = lowPoly->Surface( i );
- // default options
- memset( &opt, 0, sizeof( opt ) );
- opt.width = 512;
- opt.height = 512;
- opt.antiAlias = 1;
- opt.outline = 8;
- opt.traceFrac = 0.05f;
- // parse the renderbump parameters for this surface
- cmdLine = ms->shader->GetRenderBump();
- common->Printf( "surface %i, shader %s\nrenderBump = %s ", i,
- ms->shader->GetName(), cmdLine );
- if ( !ms->geometry ) {
- common->Printf( "(no geometry)\n" );
- continue;
- }
- idCmdArgs localArgs;
- localArgs.TokenizeString( cmdLine, false );
- if ( localArgs.Argc() < 2 ) {
- common->Printf( "(no action)\n" );
- continue;
- }
- common->Printf( "(rendering)\n" );
- for ( j = 0 ; j < localArgs.Argc() - 2; j++ ) {
- const char *s;
- s = localArgs.Argv( j );
- if ( s[0] == '-' ) {
- j++;
- s = localArgs.Argv( j );
- if ( s[0] == '\0' ) {
- continue;
- }
- }
- if ( !idStr::Icmp( s, "size" ) ) {
- if ( j + 2 >= localArgs.Argc() ) {
- j = localArgs.Argc();
- break;
- }
- opt.width = atoi( localArgs.Argv( j + 1 ) );
- opt.height = atoi( localArgs.Argv( j + 2 ) );
- j += 2;
- } else if ( !idStr::Icmp( s, "trace" ) ) {
- opt.traceFrac = atof( localArgs.Argv( j + 1 ) );
- j += 1;
- } else if ( !idStr::Icmp( s, "globalMap" ) ) {
- opt.saveGlobalMap = true;
- } else if ( !idStr::Icmp( s, "colorMap" ) ) {
- opt.saveColorMap = true;
- } else if ( !idStr::Icmp( s, "outline" ) ) {
- opt.outline = atoi( localArgs.Argv( j + 1 ) );
- j += 1;
- } else if ( !idStr::Icmp( s, "aa" ) ) {
- opt.antiAlias = atoi( localArgs.Argv( j + 1 ) );
- j += 1;
- } else {
- common->Printf( "WARNING: Unknown option \"%s\"\n", s );
- break;
- }
- }
- if ( j != ( localArgs.Argc() - 2 ) ) {
- common->Error( "usage: renderBump [-size width height] [-aa <1-2>] [globalMap] [colorMap] [-trace <0.01 - 1.0>] normalMapImageFile highPolyAseFile" );
- }
- idStr::Copynz( opt.outputName, localArgs.Argv( j ), sizeof( opt.outputName ) );
- idStr::Copynz( opt.highName, localArgs.Argv( j + 1 ), sizeof( opt.highName ) );
- // adjust size for anti-aliasing
- opt.width <<= opt.antiAlias;
- opt.height <<= opt.antiAlias;
- // see if we already have a renderbump going for another surface that this should use
- for ( j = 0 ; j < numRenderBumps ; j++ ) {
- rb = &renderBumps[j];
- if ( idStr::Icmp( rb->outputName, opt.outputName ) ) {
- continue;
- }
- // all the other parameters must match, or it is an error
- if ( idStr::Icmp( rb->highName, opt.highName) || rb->width != opt.width ||
- rb->height != opt.height || rb->antiAlias != opt.antiAlias ||
- rb->traceFrac != opt.traceFrac ) {
- common->Error( "mismatched renderbump parameters on image %s", rb->outputName );
- continue;
- }
- // saveGlobalMap will be a sticky option
- rb->saveGlobalMap = rb->saveGlobalMap | opt.saveGlobalMap;
- break;
- }
- // create a new renderbump if needed
- if ( j == numRenderBumps ) {
- numRenderBumps++;
- rb = &renderBumps[j];
- *rb = opt;
- InitRenderBump( rb );
- }
- // render the triangles for this surface
- RenderBumpTriangles( ms->geometry, rb );
- }
- //
- // anti-alias and write out all renderbumps that we have completed
- //
- for ( i = 0 ; i < numRenderBumps ; i++ ) {
- WriteRenderBump( &renderBumps[i], opt.outline << opt.antiAlias );
- }
- R_StaticFree( renderBumps );
- endTime = Sys_Milliseconds();
- common->Printf( "%5.2f seconds for renderBump\n", ( endTime - startTime ) / 1000.0 );
- common->Printf( "---------- RenderBump Completed ----------\n" );
- // stop updating the screen as we print
- common->SetRefreshOnPrint( false );
- }
- /*
- ==================================================================================
- FLAT
- The flat case is trivial, and accomplished with hardware rendering
- ==================================================================================
- */
- /*
- ==============
- RenderBumpFlat_f
- ==============
- */
- void RenderBumpFlat_f( const idCmdArgs &args ) {
- int width, height;
- idStr source;
- int i;
- idBounds bounds;
- srfTriangles_t *mesh;
- float boundsScale;
- // update the screen as we print
- common->SetRefreshOnPrint( true );
- width = height = 256;
- boundsScale = 0;
- // check options
- for ( i = 1 ; i < args.Argc() - 1; i++ ) {
- const char *s;
- s = args.Argv( i );
- if ( s[0] == '-' ) {
- i++;
- s = args.Argv( i );
- }
- if ( !idStr::Icmp( s, "size" ) ) {
- if ( i + 2 >= args.Argc() ) {
- i = args.Argc();
- break;
- }
- width = atoi( args.Argv( i + 1 ) );
- height = atoi( args.Argv( i + 2 ) );
- i += 2;
- } else {
- common->Printf( "WARNING: Unknown option \"%s\"\n", s );
- break;
- }
- }
- if ( i != ( args.Argc() - 1 ) ) {
- common->Error( "usage: renderBumpFlat [-size width height] asefile" );
- }
- common->Printf( "Final image size: %i, %i\n", width, height );
- // load the source in "fastload" mode, because we don't
- // need tangent and shadow information
- source = args.Argv( i );
- idRenderModel *highPolyModel = renderModelManager->AllocModel();
- highPolyModel->PartialInitFromFile( source );
- if ( highPolyModel->IsDefaultModel() ) {
- common->Error( "failed to load %s", source.c_str() );
- }
- // combine the high poly model into a single polyset
- if ( highPolyModel->NumSurfaces() != 1 ) {
- highPolyModel = CombineModelSurfaces( highPolyModel );
- }
- // create normals if not present in file
- const modelSurface_t *surf = highPolyModel->Surface( 0 );
- mesh = surf->geometry;
- // bound the entire file
- R_BoundTriSurf( mesh );
- bounds = mesh->bounds;
- SaveWindow();
- ResizeWindow( width, height );
- // for small images, the viewport may be less than the minimum window
- qglViewport( 0, 0, width, height );
- qglEnable( GL_CULL_FACE );
- qglCullFace( GL_FRONT );
- qglDisable( GL_STENCIL_TEST );
- qglDisable( GL_SCISSOR_TEST );
- qglDisable( GL_ALPHA_TEST );
- qglDisable( GL_BLEND );
- qglEnable( GL_DEPTH_TEST );
- qglDisable( GL_TEXTURE_2D );
- qglDepthMask( GL_TRUE );
- qglDepthFunc( GL_LEQUAL );
- qglColor3f( 1, 1, 1 );
- qglMatrixMode( GL_PROJECTION );
- qglLoadIdentity();
- qglOrtho( bounds[0][0], bounds[1][0], bounds[0][2],
- bounds[1][2], -( bounds[0][1] - 1 ), -( bounds[1][1] + 1 ) );
- qglMatrixMode( GL_MODELVIEW );
- qglLoadIdentity();
- // flat maps are automatically anti-aliased
- idStr filename;
- int j, k, c;
- byte *buffer;
- int *sumBuffer, *colorSumBuffer;
- bool flat;
- int sample;
- sumBuffer = (int *)Mem_Alloc( width * height * 4 * 4 );
- memset( sumBuffer, 0, width * height * 4 * 4 );
- buffer = (byte *)Mem_Alloc( width * height * 4 );
- colorSumBuffer = (int *)Mem_Alloc( width * height * 4 * 4 );
- memset( sumBuffer, 0, width * height * 4 * 4 );
- flat = false;
- //flat = true;
- for ( sample = 0 ; sample < 16 ; sample++ ) {
- float xOff, yOff;
- xOff = ( ( sample & 3 ) / 4.0 ) * ( bounds[1][0] - bounds[0][0] ) / width;
- yOff = ( ( sample / 4 ) / 4.0 ) * ( bounds[1][2] - bounds[0][2] ) / height;
- for ( int colorPass = 0 ; colorPass < 2 ; colorPass++ ) {
- qglClearColor(0.5,0.5,0.5,0);
- qglClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
- qglBegin( GL_TRIANGLES );
- for ( i = 0 ; i < highPolyModel->NumSurfaces() ; i++ ) {
- const modelSurface_t *surf = highPolyModel->Surface( i );
- mesh = surf->geometry;
- if ( colorPass ) {
- // just render the surface color for artist visualization
- for ( j = 0 ; j < mesh->numIndexes ; j+=3 ) {
- for ( k = 0 ; k < 3 ; k++ ) {
- int v;
- float *a;
- v = mesh->indexes[j+k];
- qglColor3ubv( mesh->verts[v].color );
- a = mesh->verts[v].xyz.ToFloatPtr();
- qglVertex3f( a[0] + xOff, a[2] + yOff, a[1] );
- }
- }
- } else {
- // render as normal map
- // we can either flat shade from the plane,
- // or smooth shade from the vertex normals
- for ( j = 0 ; j < mesh->numIndexes ; j+=3 ) {
- if ( flat ) {
- idPlane plane;
- idVec3 *a, *b, *c;
- int v1, v2, v3;
- v1 = mesh->indexes[j+0];
- v2 = mesh->indexes[j+1];
- v3 = mesh->indexes[j+2];
- a = &mesh->verts[ v1 ].xyz;
- b = &mesh->verts[ v2 ].xyz;
- c = &mesh->verts[ v3 ].xyz;
- plane.FromPoints( *a, *b, *c );
- // NULLNORMAL is used by the artists to force an area to reflect no
- // light at all
- if ( surf->shader->GetSurfaceFlags() & SURF_NULLNORMAL ) {
- qglColor3f( 0.5, 0.5, 0.5 );
- } else {
- qglColor3f( 0.5 + 0.5*plane[0], 0.5 - 0.5*plane[2], 0.5 - 0.5*plane[1] );
- }
- qglVertex3f( (*a)[0] + xOff, (*a)[2] + yOff, (*a)[1] );
- qglVertex3f( (*b)[0] + xOff, (*b)[2] + yOff, (*b)[1] );
- qglVertex3f( (*c)[0] + xOff, (*c)[2] + yOff, (*c)[1] );
- } else {
- for ( k = 0 ; k < 3 ; k++ ) {
- int v;
- float *n;
- float *a;
- v = mesh->indexes[j+k];
- n = mesh->verts[v].normal.ToFloatPtr();
- // NULLNORMAL is used by the artists to force an area to reflect no
- // light at all
- if ( surf->shader->GetSurfaceFlags() & SURF_NULLNORMAL ) {
- qglColor3f( 0.5, 0.5, 0.5 );
- } else {
- // we are going to flip the normal Z direction
- qglColor3f( 0.5 + 0.5*n[0], 0.5 - 0.5*n[2], 0.5 - 0.5*n[1] );
- }
- a = mesh->verts[v].xyz.ToFloatPtr();
- qglVertex3f( a[0] + xOff, a[2] + yOff, a[1] );
- }
- }
- }
- }
- }
- qglEnd();
- qglFlush();
- GLimp_SwapBuffers();
- qglReadPixels( 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, buffer );
- if ( colorPass ) {
- // add to the sum buffer
- for ( i = 0 ; i < c ; i++ ) {
- colorSumBuffer[i*4+0] += buffer[i*4+0];
- colorSumBuffer[i*4+1] += buffer[i*4+1];
- colorSumBuffer[i*4+2] += buffer[i*4+2];
- colorSumBuffer[i*4+3] += buffer[i*4+3];
- }
- } else {
- // normalize
- c = width * height;
- for ( i = 0 ; i < c ; i++ ) {
- idVec3 v;
- v[0] = ( buffer[i*4+0] - 128 ) / 127.0;
- v[1] = ( buffer[i*4+1] - 128 ) / 127.0;
- v[2] = ( buffer[i*4+2] - 128 ) / 127.0;
- v.Normalize();
- buffer[i*4+0] = 128 + 127 * v[0];
- buffer[i*4+1] = 128 + 127 * v[1];
- buffer[i*4+2] = 128 + 127 * v[2];
- }
- // outline into non-drawn areas
- for ( i = 0 ; i < 8 ; i++ ) {
- OutlineNormalMap( buffer, width, height, 128, 128, 128 );
- }
- // add to the sum buffer
- for ( i = 0 ; i < c ; i++ ) {
- sumBuffer[i*4+0] += buffer[i*4+0];
- sumBuffer[i*4+1] += buffer[i*4+1];
- sumBuffer[i*4+2] += buffer[i*4+2];
- sumBuffer[i*4+3] += buffer[i*4+3];
- }
- }
- }
- }
- c = width * height;
- // save out the color map
- for ( i = 0 ; i < c ; i++ ) {
- buffer[i*4+0] = colorSumBuffer[i*4+0] / 16;
- buffer[i*4+1] = colorSumBuffer[i*4+1] / 16;
- buffer[i*4+2] = colorSumBuffer[i*4+2] / 16;
- buffer[i*4+3] = colorSumBuffer[i*4+3] / 16;
- }
- filename = source;
- filename.StripFileExtension();
- filename.Append( "_color.tga" );
- R_VerticalFlip( buffer, width, height );
- R_WriteTGA( filename, buffer, width, height );
- // save out the local map
- // scale the sum buffer back down to the sample buffer
- // we allow this to denormalize
- for ( i = 0 ; i < c ; i++ ) {
- buffer[i*4+0] = sumBuffer[i*4+0] / 16;
- buffer[i*4+1] = sumBuffer[i*4+1] / 16;
- buffer[i*4+2] = sumBuffer[i*4+2] / 16;
- buffer[i*4+3] = sumBuffer[i*4+3] / 16;
- }
- filename = source;
- filename.StripFileExtension();
- filename.Append( "_local.tga" );
- common->Printf( "writing %s (%i,%i)\n", filename.c_str(), width, height );
- R_VerticalFlip( buffer, width, height );
- R_WriteTGA( filename, buffer, width, height );
- // free the model
- renderModelManager->FreeModel( highPolyModel );
- // free our work buffer
- Mem_Free( buffer );
- Mem_Free( sumBuffer );
- Mem_Free( colorSumBuffer );
- RestoreWindow();
- // stop updating the screen as we print
- common->SetRefreshOnPrint( false );
- common->Error( "Completed." );
- }
|