/* ============== CaptureRenderToFile ============== */ void idRenderSystemLocal::CaptureRenderToFile( const char *fileName, bool fixAlpha ) { if ( !glConfig.isInitialized ) { return; } renderCrop_t *rc = &renderCrops[currentRenderCrop]; guiModel->EmitFullScreen(); guiModel->Clear(); R_IssueRenderCommands(); qglReadBuffer( GL_BACK ); // include extra space for OpenGL padding to word boundaries int c = ( rc->width + 3 ) * rc->height; byte *data = (byte *)R_StaticAlloc( c * 3 ); qglReadPixels( rc->x, rc->y, rc->width, rc->height, GL_RGB, GL_UNSIGNED_BYTE, data ); byte *data2 = (byte *)R_StaticAlloc( c * 4 ); for ( int i = 0 ; i < c ; i++ ) { data2[ i * 4 ] = data[ i * 3 ]; data2[ i * 4 + 1 ] = data[ i * 3 + 1 ]; data2[ i * 4 + 2 ] = data[ i * 3 + 2 ]; data2[ i * 4 + 3 ] = 0xff; } R_WriteTGA( fileName, data2, rc->width, rc->height, true ); R_StaticFree( data ); R_StaticFree( data2 ); }
/* ==================== GenerateMegaPreview Make a 2k x 2k preview image for a mega texture that can be used in modeling programs ==================== */ void idMegaTexture::GenerateMegaPreview( const char *fileName ) { idFile *fileHandle = fileSystem->OpenFileRead( fileName ); if ( !fileHandle ) { common->Printf( "idMegaTexture: failed to open %s\n", fileName ); return; } idStr outName = fileName; outName.StripFileExtension(); outName += "_preview.tga"; common->Printf( "Creating %s.\n", outName.c_str() ); megaTextureHeader_t header; fileHandle->Read( &header, sizeof( header ) ); if ( header.tileSize < 64 || header.tilesWide < 1 || header.tilesHigh < 1 ) { common->Printf( "idMegaTexture: bad header on %s\n", fileName ); return; } int tileSize = header.tileSize; int width = header.tilesWide; int height = header.tilesHigh; int tileOffset = 1; int tileBytes = tileSize * tileSize * 4; // find the level that fits while ( width * tileSize > 2048 || height * tileSize > 2048 ) { tileOffset += width * height; width >>= 1; if ( width < 1 ) { width = 1; } height >>= 1; if ( height < 1 ) { height = 1; } } byte *pic = (byte *)R_StaticAlloc( width * height * tileBytes ); byte *oldBlock = (byte *)_alloca( tileBytes ); for ( int y = 0 ; y < height ; y++ ) { for ( int x = 0 ; x < width ; x++ ) { int tileNum = tileOffset + y * width + x; fileHandle->Seek( tileNum * tileBytes, FS_SEEK_SET ); fileHandle->Read( oldBlock, tileBytes ); for ( int yy = 0 ; yy < tileSize ; yy++ ) { memcpy( pic + ( ( y * tileSize + yy ) * width * tileSize + x * tileSize ) * 4, oldBlock + yy * tileSize * 4, tileSize * 4 ); } } } R_WriteTGA( outName.c_str(), pic, width * tileSize, height * tileSize, false ); R_StaticFree( pic ); delete fileHandle; }
/* ============== idRenderSystemLocal::CaptureRenderToFile ============== */ void idRenderSystemLocal::CaptureRenderToFile( const char *fileName, bool fixAlpha ) { if ( !R_IsInitialized() ) { return; } idScreenRect & rc = renderCrops[currentRenderCrop]; guiModel->EmitFullScreen(); guiModel->Clear(); RenderCommandBuffers( frameData->cmdHead ); if ( !vr->useFBO ) // koz { glReadBuffer( GL_BACK ); } // include extra space for OpenGL padding to word boundaries int c = ( rc.GetWidth() + 3 ) * rc.GetHeight(); byte *data = (byte *)R_StaticAlloc( c * 3 ); qglReadPixels( rc.x1, rc.y1, rc.GetWidth(), rc.GetHeight(), GL_RGB, GL_UNSIGNED_BYTE, data ); byte *data2 = (byte *)R_StaticAlloc( c * 4 ); for ( int i = 0 ; i < c ; i++ ) { data2[ i * 4 ] = data[ i * 3 ]; data2[ i * 4 + 1 ] = data[ i * 3 + 1 ]; data2[ i * 4 + 2 ] = data[ i * 3 + 2 ]; data2[ i * 4 + 3 ] = 0xff; } R_WriteTGA( fileName, data2, rc.GetWidth(), rc.GetHeight(), true ); R_StaticFree( data ); R_StaticFree( data2 ); }
/* =============== idRenderSystemLocal::WriteTGA =============== */ void idRenderSystemLocal::WriteTGA( const char *filename, const byte *data, int width, int height, bool flipVertical ) { R_WriteTGA( filename, data, width, height, flipVertical ); }
/* ============== 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 ); }
/* ============== RenderBumpFlat_f ============== */ void RenderBumpFlat_f( const idCmdArgs &args ) { int width, height; idStr source; int i; idBounds bounds; srfTriangles_t *mesh; // update the screen as we print common->SetRefreshOnPrint( true ); width = height = 256; // 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" ); return; } 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 ); c = width * height; 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 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." ); }
/* =============== R_CombineCubeImages_f Used to combine animations of six separate tga files into a serials of 6x taller tga files, for preparation to roq compress =============== */ void R_CombineCubeImages_f( const idCmdArgs &args ) { if ( args.Argc() != 2 ) { common->Printf( "usage: combineCubeImages <baseName>\n" ); common->Printf( " combines basename[1-6][0001-9999].tga to basenameCM[0001-9999].tga\n" ); common->Printf( " 1: forward 2:right 3:back 4:left 5:up 6:down\n" ); return; } idStr baseName = args.Argv( 1 ); common->SetRefreshOnPrint( true ); for ( int frameNum = 1 ; frameNum < 10000 ; frameNum++ ) { char filename[MAX_IMAGE_NAME]; byte *pics[6]; int width = 0, height = 0; int side; int orderRemap[6] = { 1,3,4,2,5,6 }; for ( side = 0 ; side < 6 ; side++ ) { sprintf( filename, "%s%i%04i.tga", baseName.c_str(), orderRemap[side], frameNum ); common->Printf( "reading %s\n", filename ); R_LoadImage( filename, &pics[side], &width, &height, NULL, true ); if ( !pics[side] ) { common->Printf( "not found.\n" ); break; } // convert from "camera" images to native cube map images switch( side ) { case 0: // forward R_RotatePic( pics[side], width); break; case 1: // back R_RotatePic( pics[side], width); R_HorizontalFlip( pics[side], width, height ); R_VerticalFlip( pics[side], width, height ); break; case 2: // left R_VerticalFlip( pics[side], width, height ); break; case 3: // right R_HorizontalFlip( pics[side], width, height ); break; case 4: // up R_RotatePic( pics[side], width); break; case 5: // down R_RotatePic( pics[side], width); break; } } if ( side != 6 ) { for ( int i = 0 ; i < side ; side++ ) { Mem_Free( pics[side] ); } break; } idTempArray<byte> buf( width*height*6*4 ); byte *combined = (byte *)buf.Ptr(); for ( side = 0 ; side < 6 ; side++ ) { memcpy( combined+width*height*4*side, pics[side], width*height*4 ); Mem_Free( pics[side] ); } sprintf( filename, "%sCM%04i.tga", baseName.c_str(), frameNum ); common->Printf( "writing %s\n", filename ); R_WriteTGA( filename, combined, width, height*6 ); } common->SetRefreshOnPrint( false ); }