/* =========================================================================== IceTech GPL Source Code Copyright (C) 2026 Justin Marshall This file is part of the IceTech GPL Source Code (?IceTech Source Code?). IceTech 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. IceTech 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 IceTech Source Code. If not, see . In addition, the IceTech 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 IceTech 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 Justin Marshall, justinmarshall20@gmail.com =========================================================================== */ #include "precompiled.h" #pragma hdrstop #include "tr_local.h" #include "../models/Model_local.h" /* ================ idRenderWorldLocal::FreeWorld ================ */ void idRenderWorldLocal::FreeWorld() { int i; // this will free all the lightDefs and entityDefs FreeDefs(); // A map reload is a hard boundary for path-tracing state. Clear any // persistent lights / temporal history before replacing proc geometry. glRaytracingLightingClearLights(true); ClearWorldTerrain(); // Delete proc-created DXR meshes before clearing the scene, so the ray // tracing layer can invalidate any instances that still reference them. for (i = 0; i < worldDXRmodels.Num(); i++) { glRaytracingDeleteMesh(worldDXRmodels[i].dxrBottomAcel); } glRaytracingClearScene(dxrWorldId); worldDXRmodels.Clear(); // free all the portals and check light/model references for ( i = 0 ; i < numPortalAreas ; i++ ) { portalArea_t *area; portal_t *portal, *nextPortal; area = &portalAreas[i]; for ( portal = area->portals ; portal ; portal = nextPortal ) { nextPortal = portal->next; delete portal->w; R_StaticFree( portal ); } // there shouldn't be any remaining lightRefs or entityRefs if ( area->lightRefs.areaNext != &area->lightRefs ) { common->Error( "FreeWorld: unexpected remaining lightRefs" ); } if ( area->entityRefs.areaNext != &area->entityRefs ) { common->Error( "FreeWorld: unexpected remaining entityRefs" ); } } if ( portalAreas ) { R_StaticFree( portalAreas ); portalAreas = NULL; numPortalAreas = 0; R_StaticFree( areaScreenRect ); areaScreenRect = NULL; } if ( doublePortals ) { R_StaticFree( doublePortals ); doublePortals = NULL; numInterAreaPortals = 0; } portalHandleGroups.Clear(); pathTraceAreas.Clear(); if ( areaNodes ) { R_StaticFree( areaNodes ); areaNodes = NULL; } // free all the inline idRenderModels for ( i = 0 ; i < localModels.Num() ; i++ ) { renderModelManager->RemoveModel( localModels[i] ); delete localModels[i]; } localModels.Clear(); worldDecals.Clear(); areaReferenceAllocator.Shutdown(); //interactionAllocator.Shutdown(); areaNumRefAllocator.Shutdown(); mapName = ""; } static void R_NormalizeDecalBasis(renderWorldDecal_t& decal) { if (decal.normal.LengthSqr() < 0.0001f) { decal.normal.Set(0.0f, 0.0f, 1.0f); } decal.normal.Normalize(); decal.axisU -= decal.normal * (decal.axisU * decal.normal); if (decal.axisU.LengthSqr() < 0.0001f) { decal.axisU = decal.normal.Cross(idVec3(0.0f, 0.0f, 1.0f)); if (decal.axisU.LengthSqr() < 0.0001f) { decal.axisU = decal.normal.Cross(idVec3(0.0f, 1.0f, 0.0f)); } } decal.axisU.Normalize(); decal.axisV = decal.normal.Cross(decal.axisU); decal.axisV.Normalize(); if (decal.size <= 0.0f) { decal.size = 64.0f; } const float minProjectorDepth = decal.size * 2.0f; if (decal.depth <= 0.0f) { decal.depth = minProjectorDepth; } else if (decal.depth < minProjectorDepth) { decal.depth = minProjectorDepth; } if (decal.opacity <= 0.0f) { decal.opacity = 1.0f; } } void idRenderWorldLocal::ClearWorldDecals() { worldDecals.Clear(); } int idRenderWorldLocal::AddWorldDecal(const renderWorldDecal_t& inDecal) { renderWorldDecal_t decal = inDecal; R_NormalizeDecalBasis(decal); return worldDecals.Append(decal); } bool idRenderWorldLocal::UpdateWorldDecal(int decalIndex, const renderWorldDecal_t& inDecal) { if (decalIndex < 0 || decalIndex >= worldDecals.Num()) { return false; } renderWorldDecal_t decal = inDecal; R_NormalizeDecalBasis(decal); worldDecals[decalIndex] = decal; return true; } void idRenderWorldLocal::RemoveWorldDecal(int decalIndex) { if (decalIndex >= 0 && decalIndex < worldDecals.Num()) { worldDecals.RemoveIndex(decalIndex); } } int idRenderWorldLocal::NumWorldDecals() const { return worldDecals.Num(); } const renderWorldDecal_t* idRenderWorldLocal::GetWorldDecal(int decalIndex) const { if (decalIndex < 0 || decalIndex >= worldDecals.Num()) { return NULL; } return &worldDecals[decalIndex]; } void idRenderWorldLocal::SubmitWorldDecalsToScreenSpace() const { glScreenSpaceDecal_t gpuDecals[64]; const int count = min(worldDecals.Num(), 64); for (int i = 0; i < count; ++i) { const renderWorldDecal_t& src = worldDecals[i]; glScreenSpaceDecal_t& dst = gpuDecals[i]; memset(&dst, 0, sizeof(dst)); dst.origin.x = src.origin.x; dst.origin.y = src.origin.y; dst.origin.z = src.origin.z; dst.normal.x = src.normal.x; dst.normal.y = src.normal.y; dst.normal.z = src.normal.z; dst.axisU.x = src.axisU.x; dst.axisU.y = src.axisU.y; dst.axisU.z = src.axisU.z; dst.axisV.x = src.axisV.x; dst.axisV.y = src.axisV.y; dst.axisV.z = src.axisV.z; dst.halfSize = src.size * 0.5f; const float minProjectorDepth = src.size * 2.0f; dst.depth = (src.depth > minProjectorDepth) ? src.depth : minProjectorDepth; dst.opacity = src.opacity; dst.color[0] = src.color.x; dst.color[1] = src.color.y; dst.color[2] = src.color.z; dst.color[3] = src.color.w; dst.textureId = 0; dst.blendMode = 0; const idMaterial* mat = declManager->FindMaterial(src.material.c_str()); idImage* image = mat ? mat->GetEditorImage() : NULL; if (mat && mat->GetNumStages() > 0) { const shaderStage_t* stage = mat->GetStage(0); const int srcBlend = stage->drawStateBits & GLS_SRCBLEND_BITS; const int dstBlend = stage->drawStateBits & GLS_DSTBLEND_BITS; if (srcBlend == GLS_SRCBLEND_ZERO && dstBlend == GLS_DSTBLEND_ONE_MINUS_SRC_COLOR) { dst.blendMode = 1; } } #ifndef QUAKE4 if (mat && src.color.x == 1.0f && src.color.y == 1.0f && src.color.z == 1.0f && src.color.w == 1.0f) { decalInfo_t decalInfo = mat->GetDecalInfo(); dst.color[0] *= decalInfo.start[0]; dst.color[1] *= decalInfo.start[1]; dst.color[2] *= decalInfo.start[2]; dst.color[3] *= decalInfo.start[3]; } #endif if (image) { image->Bind(); dst.textureId = image->texnum; } } glSetScreenSpaceDecalsQD3D12(gpuDecals, count); glDrawScreenSpaceDecalsQD3D12(); } static idStr R_WorldDecalFileName(const char* mapName) { idStr filename = mapName ? mapName : ""; filename.StripFileExtension(); filename.SetFileExtension(".stamp"); return filename; } void idRenderWorldLocal::LoadWorldDecals() { ClearWorldDecals(); idStr filename = R_WorldDecalFileName(mapName.c_str()); const bool osPath = strstr(filename.c_str(), ":") != NULL; idLexer src(LEXFL_NOSTRINGCONCAT | LEXFL_NODOLLARPRECOMPILE | LEXFL_ALLOWPATHNAMES); src.LoadFile(filename.c_str(), osPath); if (!src.IsLoaded()) { return; } idToken token; if (!src.ReadToken(&token) || token.Icmp("worldDecals")) { return; } src.ReadToken(&token); if (token != "{") { src.ExpectTokenString("{"); } while (src.ReadToken(&token)) { if (token == "}") { break; } if (token.Icmp("decal")) { src.Error("idRenderWorldLocal::LoadWorldDecals: bad token \"%s\"", token.c_str()); break; } renderWorldDecal_t decal; decal.material = "textures/decals/default"; decal.origin.Zero(); decal.normal.Set(0.0f, 0.0f, 1.0f); decal.axisU.Set(1.0f, 0.0f, 0.0f); decal.axisV.Set(0.0f, 1.0f, 0.0f); decal.size = 64.0f; decal.depth = 8.0f; decal.opacity = 1.0f; decal.color.Set(0.05f, 0.05f, 0.05f, 1.0f); src.ExpectTokenString("{"); while (src.ReadToken(&token)) { if (token == "}") { break; } if (!token.Icmp("material")) { src.ExpectAnyToken(&token); decal.material = token; } else if (!token.Icmp("origin")) { src.Parse1DMatrix(3, decal.origin.ToFloatPtr()); } else if (!token.Icmp("normal")) { src.Parse1DMatrix(3, decal.normal.ToFloatPtr()); } else if (!token.Icmp("axisU")) { src.Parse1DMatrix(3, decal.axisU.ToFloatPtr()); } else if (!token.Icmp("axisV")) { src.Parse1DMatrix(3, decal.axisV.ToFloatPtr()); } else if (!token.Icmp("size")) { decal.size = src.ParseFloat(); } else if (!token.Icmp("depth")) { decal.depth = src.ParseFloat(); } else if (!token.Icmp("opacity")) { decal.opacity = src.ParseFloat(); } else if (!token.Icmp("color")) { src.Parse1DMatrix(4, decal.color.ToFloatPtr()); } else { src.Error("idRenderWorldLocal::LoadWorldDecals: bad decal key \"%s\"", token.c_str()); } } AddWorldDecal(decal); } } /* ================ idRenderWorldLocal::TouchWorldModels ================ */ void idRenderWorldLocal::TouchWorldModels( void ) { int i; for ( i = 0 ; i < localModels.Num() ; i++ ) { renderModelManager->CheckModel( localModels[i]->Name() ); } } /* ================ idRenderWorldLocal::ParseModel ================ */ typedef struct procRaytraceSurfaceMetadata_s { bool valid; uint32_t flags; int areaNum; float averageColor[4]; float emissiveColor[4]; idBounds bounds; } procRaytraceSurfaceMetadata_t; static void ClearProcRaytraceSurfaceMetadata( procRaytraceSurfaceMetadata_t &meta ) { meta.valid = false; meta.flags = 0; meta.areaNum = -1; meta.averageColor[0] = meta.averageColor[1] = meta.averageColor[2] = meta.averageColor[3] = 1.0f; meta.emissiveColor[0] = meta.emissiveColor[1] = meta.emissiveColor[2] = meta.emissiveColor[3] = 0.0f; meta.bounds.Clear(); } static int ProcAreaNumFromModelName( const idStr &modelName ) { if ( modelName.Icmpn( "_area", 5 ) != 0 ) { return -1; } const char *areaText = modelName.c_str() + 5; if ( *areaText < '0' || *areaText > '9' ) { return -1; } return atoi( areaText ); } static void ParseProcRaytraceSurfaceMetadata( idLexer *src, procRaytraceSurfaceMetadata_t &meta ) { idToken token; src->ExpectTokenString( "{" ); meta.valid = true; while ( src->ReadToken( &token ) ) { if ( token == "}" ) { return; } if ( token == "flags" ) { meta.flags = src->ParseInt(); continue; } if ( token == "area" ) { meta.areaNum = src->ParseInt(); continue; } if ( token == "averageColor" ) { src->Parse1DMatrix( 4, meta.averageColor ); continue; } if ( token == "emissive" ) { src->Parse1DMatrix( 4, meta.emissiveColor ); continue; } if ( token == "bounds" ) { float b[6]; src->Parse1DMatrix( 6, b ); meta.bounds[0].Set( b[0], b[1], b[2] ); meta.bounds[1].Set( b[3], b[4], b[5] ); continue; } src->Error( "R_ParseModel: bad raytraceSurface token \"%s\"", token.c_str() ); } src->Error( "R_ParseModel: EOF in raytraceSurface" ); } idRenderModel *idRenderWorldLocal::ParseModel( idLexer *src ) { idRenderModel *model; idToken token; int i, j; srfTriangles_t *tri; modelSurface_t surf; src->ExpectTokenString( "{" ); // parse the name src->ExpectAnyToken( &token ); idStr modelName = token; model = renderModelManager->AllocModel(); model->InitEmpty( token ); const int modelAreaNum = ProcAreaNumFromModelName( modelName ); int numSurfaces = src->ParseInt(); if ( numSurfaces < 0 ) { src->Error( "R_ParseModel: bad numSurfaces" ); } #ifdef QUAKE4 // jmarshall - quake 4 proc format if (!src->PeekTokenString("{") && !src->PeekTokenString("}")) { int sky = src->ParseInt(); } // jmarshall end #endif for ( i = 0 ; i < numSurfaces ; i++ ) { procRaytraceSurfaceMetadata_t raytraceMeta; ClearProcRaytraceSurfaceMetadata( raytraceMeta ); raytraceMeta.areaNum = modelAreaNum; src->ExpectTokenString( "{" ); src->ExpectAnyToken( &token ); surf.shader = declManager->FindMaterial( token ); ((idMaterial*)surf.shader)->AddReference(); tri = R_AllocStaticTriSurf(); surf.geometry = tri; tri->numVerts = src->ParseInt(); tri->numIndexes = src->ParseInt(); R_AllocStaticTriSurfVerts( tri, tri->numVerts ); for ( j = 0 ; j < tri->numVerts ; j++ ) { #ifdef QUAKE4 // jmarshall - quake 4 proc format float vec[12]; const int numFloats = src->Parse1DMatrixOpenEnded( 12, vec ); if ( numFloats != 8 && numFloats != 12 ) { src->Error( "R_ParseModel: bad vertex read" ); } tri->verts[j].xyz[0] = vec[0]; tri->verts[j].xyz[1] = vec[1]; tri->verts[j].xyz[2] = vec[2]; tri->verts[j].st[0] = vec[3]; tri->verts[j].st[1] = vec[4]; tri->verts[j].normal[0] = vec[5]; tri->verts[j].normal[1] = vec[6]; tri->verts[j].normal[2] = vec[7]; if ( numFloats == 12 ) { tri->verts[j].color[0] = idMath::Ftob( vec[8] ); tri->verts[j].color[1] = idMath::Ftob( vec[9] ); tri->verts[j].color[2] = idMath::Ftob( vec[10] ); tri->verts[j].color[3] = idMath::Ftob( vec[11] ); } else { tri->verts[j].color[0] = 0; tri->verts[j].color[1] = 0; tri->verts[j].color[2] = 0; tri->verts[j].color[3] = 255; } // jmarshall end #else float vec[8]; src->Parse1DMatrix( 8, vec ); tri->verts[j].xyz[0] = vec[0]; tri->verts[j].xyz[1] = vec[1]; tri->verts[j].xyz[2] = vec[2]; tri->verts[j].st[0] = vec[3]; tri->verts[j].st[1] = vec[4]; tri->verts[j].normal[0] = vec[5]; tri->verts[j].normal[1] = vec[6]; tri->verts[j].normal[2] = vec[7]; #endif } R_AllocStaticTriSurfIndexes( tri, tri->numIndexes ); for ( j = 0 ; j < tri->numIndexes ; j++ ) { tri->indexes[j] = src->ParseInt(); } while ( !src->PeekTokenString( "}" ) ) { src->ExpectAnyToken( &token ); if ( token == "raytraceSurface" ) { ParseProcRaytraceSurfaceMetadata( src, raytraceMeta ); continue; } src->Error( "R_ParseModel: bad surface token \"%s\"", token.c_str() ); } src->ExpectTokenString( "}" ); // add the completed surface to the model model->AddSurface( surf ); const bool raytraceMetadataSurface = raytraceMeta.valid && ( raytraceMeta.flags & ( GL_RAYTRACING_PROC_SURFACE_EMISSIVE | GL_RAYTRACING_PROC_SURFACE_UNLIT ) ) != 0; if ( !surf.shader->IsSky() && ( ( surf.shader->IsLitMaterial() && surf.shader->SurfaceCastsShadow() ) || raytraceMetadataSurface ) ) { idRenderModelStatic* modelStatic = (idRenderModelStatic*)model; dxrWorldModel_t dxrModel; dxrModel.tri = tri; modelStatic->UpdateDXR(dxrModel.dxrBottomAcel, model->NumSurfaces() - 1); if ( raytraceMeta.valid ) { glRaytracingSetMeshAverageColor( dxrModel.dxrBottomAcel, raytraceMeta.averageColor[0], raytraceMeta.averageColor[1], raytraceMeta.averageColor[2], raytraceMeta.averageColor[3] ); glRaytracingSetMeshProcMetadata( dxrModel.dxrBottomAcel, raytraceMeta.flags, raytraceMeta.areaNum, raytraceMeta.emissiveColor[0], raytraceMeta.emissiveColor[1], raytraceMeta.emissiveColor[2], raytraceMeta.emissiveColor[3] ); } glUpdateTopLevelAceelStructure(dxrWorldId, dxrModel.dxrBottomAcel, NULL, dxrModel.topAccelStruct); worldDXRmodels.Append(dxrModel); } } src->ExpectTokenString( "}" ); model->FinishSurfaces(); return model; } /* ================ idRenderWorldLocal::ParseShadowModel ================ */ idRenderModel *idRenderWorldLocal::ParseShadowModel( idLexer *src ) { idRenderModel *model; idToken token; int j; srfTriangles_t *tri; modelSurface_t surf; src->ExpectTokenString( "{" ); // parse the name src->ExpectAnyToken( &token ); model = renderModelManager->AllocModel(); model->InitEmpty( token ); surf.shader = tr.defaultMaterial; tri = R_AllocStaticTriSurf(); surf.geometry = tri; tri->numVerts = src->ParseInt(); tri->numShadowIndexesNoCaps = src->ParseInt(); tri->numShadowIndexesNoFrontCaps = src->ParseInt(); tri->numIndexes = src->ParseInt(); tri->shadowCapPlaneBits = src->ParseInt(); tri->bounds.Clear(); for ( j = 0 ; j < tri->numVerts ; j++ ) { float vec[8]; src->Parse1DMatrix( 3, vec ); tri->bounds.AddPoint( idVec3( vec[0], vec[1], vec[2] ) ); } for ( j = 0 ; j < tri->numIndexes ; j++ ) { src->ParseInt(); } // add the completed surface to the model model->AddSurface( surf ); src->ExpectTokenString( "}" ); // we do NOT do a model->FinishSurfaceces, because we don't need sil edges, planes, tangents, etc. // model->FinishSurfaces(); return model; } /* ================ idRenderWorldLocal::SetupAreaRefs ================ */ void idRenderWorldLocal::SetupAreaRefs() { int i; connectedAreaNum = 0; for ( i = 0 ; i < numPortalAreas ; i++ ) { portalAreas[i].areaNum = i; portalAreas[i].lightRefs.areaNext = portalAreas[i].lightRefs.areaPrev = &portalAreas[i].lightRefs; portalAreas[i].entityRefs.areaNext = portalAreas[i].entityRefs.areaPrev = &portalAreas[i].entityRefs; } } /* ================ idRenderWorldLocal::ParseInterAreaPortals ================ */ void idRenderWorldLocal::ParseInterAreaPortals( idLexer *src ) { int i, j; src->ExpectTokenString( "{" ); numPortalAreas = src->ParseInt(); if ( numPortalAreas < 0 ) { src->Error( "R_ParseInterAreaPortals: bad numPortalAreas" ); return; } portalAreas = (portalArea_t *)R_ClearedStaticAlloc( numPortalAreas * sizeof( portalAreas[0] ) ); areaScreenRect = (idScreenRect *) R_ClearedStaticAlloc( numPortalAreas * sizeof( idScreenRect ) ); // set the doubly linked lists SetupAreaRefs(); numInterAreaPortals = src->ParseInt(); if ( numInterAreaPortals < 0 ) { src->Error( "R_ParseInterAreaPortals: bad numInterAreaPortals" ); return; } doublePortals = (doublePortal_t *)R_ClearedStaticAlloc( numInterAreaPortals * sizeof( doublePortals [0] ) ); for ( i = 0 ; i < numInterAreaPortals ; i++ ) { int numPoints, a1, a2; idWinding *w; portal_t *p; numPoints = src->ParseInt(); a1 = src->ParseInt(); a2 = src->ParseInt(); w = new idWinding( numPoints ); w->SetNumPoints( numPoints ); for ( j = 0 ; j < numPoints ; j++ ) { src->Parse1DMatrix( 3, (*w)[j].ToFloatPtr() ); // no texture coordinates (*w)[j][3] = 0; (*w)[j][4] = 0; } // add the portal to a1 p = (portal_t *)R_ClearedStaticAlloc( sizeof( *p ) ); p->intoArea = a2; p->doublePortal = &doublePortals[i]; p->w = w; p->w->GetPlane( p->plane ); p->next = portalAreas[a1].portals; portalAreas[a1].portals = p; doublePortals[i].portals[0] = p; // reverse it for a2 p = (portal_t *)R_ClearedStaticAlloc( sizeof( *p ) ); p->intoArea = a1; p->doublePortal = &doublePortals[i]; p->w = w->Reverse(); p->w->GetPlane( p->plane ); p->next = portalAreas[a2].portals; portalAreas[a2].portals = p; doublePortals[i].portals[1] = p; } src->ExpectTokenString( "}" ); } /* ================ idRenderWorldLocal::ParseNodes ================ */ void idRenderWorldLocal::ParseNodes( idLexer *src ) { int i; src->ExpectTokenString( "{" ); numAreaNodes = src->ParseInt(); if ( numAreaNodes < 0 ) { src->Error( "R_ParseNodes: bad numAreaNodes" ); } areaNodes = (areaNode_t *)R_ClearedStaticAlloc( numAreaNodes * sizeof( areaNodes[0] ) ); for ( i = 0 ; i < numAreaNodes ; i++ ) { areaNode_t *node; node = &areaNodes[i]; src->Parse1DMatrix( 4, node->plane.ToFloatPtr() ); node->children[0] = src->ParseInt(); node->children[1] = src->ParseInt(); } src->ExpectTokenString( "}" ); } /* ================ idRenderWorldLocal::ParsePathTraceAreas ================ */ void idRenderWorldLocal::ParsePathTraceAreas( idLexer *src ) { int i, j; src->ExpectTokenString( "{" ); const int numAreas = src->ParseInt(); if ( numAreas < 0 ) { src->Error( "R_ParsePathTraceAreas: bad numAreas" ); return; } pathTraceAreas.Clear(); pathTraceAreas.SetNum( numAreas ); for ( i = 0 ; i < numAreas ; i++ ) { pathTraceAreas[i].bounds.Clear(); pathTraceAreas[i].neighbors.Clear(); pathTraceAreas[i].staticLightNames.Clear(); pathTraceAreas[i].surfaceGroups = 0; pathTraceAreas[i].valid = false; } for ( i = 0 ; i < numAreas ; i++ ) { const int areaNum = src->ParseInt(); const int surfaceGroups = src->ParseInt(); float boundsVec[6]; src->Parse1DMatrix( 6, boundsVec ); const int neighborCount = src->ParseInt(); if ( areaNum < 0 || areaNum >= numAreas ) { for ( j = 0 ; j < neighborCount ; j++ ) { src->ParseInt(); } const int lightCount = src->ParseInt(); for ( j = 0 ; j < lightCount ; j++ ) { idToken lightName; src->ExpectAnyToken( &lightName ); } continue; } pathTraceArea_t &area = pathTraceAreas[areaNum]; area.bounds[0].Set( boundsVec[0], boundsVec[1], boundsVec[2] ); area.bounds[1].Set( boundsVec[3], boundsVec[4], boundsVec[5] ); area.surfaceGroups = surfaceGroups; area.valid = true; for ( j = 0 ; j < neighborCount ; j++ ) { const int neighbor = src->ParseInt(); if ( neighbor >= 0 && neighbor < numAreas && area.neighbors.FindIndex( neighbor ) < 0 ) { area.neighbors.Append( neighbor ); } } const int lightCount = src->ParseInt(); for ( j = 0 ; j < lightCount ; j++ ) { idToken lightName; src->ExpectAnyToken( &lightName ); if ( area.staticLightNames.Find( lightName ) == NULL ) { area.staticLightNames.Append( lightName ); } } } src->ExpectTokenString( "}" ); } /* ================ idRenderWorldLocal::CommonChildrenArea_r ================ */ int idRenderWorldLocal::CommonChildrenArea_r( areaNode_t *node ) { int nums[2]; for ( int i = 0 ; i < 2 ; i++ ) { if ( node->children[i] <= 0 ) { nums[i] = -1 - node->children[i]; } else { nums[i] = CommonChildrenArea_r( &areaNodes[ node->children[i] ] ); } } // solid nodes will match any area if ( nums[0] == AREANUM_SOLID ) { nums[0] = nums[1]; } if ( nums[1] == AREANUM_SOLID ) { nums[1] = nums[0]; } int common; if ( nums[0] == nums[1] ) { common = nums[0]; } else { common = CHILDREN_HAVE_MULTIPLE_AREAS; } node->commonChildrenArea = common; return common; } /* ================= idRenderWorldLocal::ClearWorld Sets up for a single area world ================= */ void idRenderWorldLocal::ClearWorld() { portalHandleGroups.Clear(); numPortalAreas = 1; portalAreas = (portalArea_t *)R_ClearedStaticAlloc( sizeof( portalAreas[0] ) ); areaScreenRect = (idScreenRect *) R_ClearedStaticAlloc( sizeof( idScreenRect ) ); SetupAreaRefs(); // even though we only have a single area, create a node // that has both children pointing at it so we don't need to // areaNodes = (areaNode_t *)R_ClearedStaticAlloc( sizeof( areaNodes[0] ) ); areaNodes[0].plane[3] = 1; areaNodes[0].children[0] = -1; areaNodes[0].children[1] = -1; } /* ================= idRenderWorldLocal::FreeDefs dump all the interactions ================= */ void idRenderWorldLocal::FreeDefs() { int i; // free all lightDefs for ( i = 0 ; i < lightDefs.Num() ; i++ ) { idRenderLightLocal *light; light = lightDefs[i]; if ( light && light->world == this ) { FreeLightDef( i ); lightDefs[i] = NULL; } } // free all entityDefs for ( i = 0 ; i < entityDefs.Num() ; i++ ) { idRenderEntityLocal *mod; mod = entityDefs[i]; if ( mod && mod->world == this ) { FreeEntityDef( i ); entityDefs[i] = NULL; } } } /* ================= idRenderWorldLocal::InitFromMap A NULL or empty name will make a world without a map model, which is still useful for displaying a bare model ================= */ bool idRenderWorldLocal::InitFromMap( const char *name ) { idLexer * src; idToken token; idStr filename; idRenderModel * lastModel; // if this is an empty world, initialize manually if ( !name || !name[0] ) { FreeWorld(); glRaytracingLightingClearLights(true); mapName.Clear(); ClearWorld(); return true; } // load it filename = name; filename.SetFileExtension( PROC_FILE_EXT ); // if we are reloading the same map, check the timestamp // and try to skip all the work ID_TIME_T currentTimeStamp; fileSystem->ReadFile( filename, NULL, ¤tTimeStamp ); idStr terrainFileName = idTerrainModel::FileNameForMap( name ); ID_TIME_T currentTerrainTimeStamp; fileSystem->ReadFile( terrainFileName, NULL, ¤tTerrainTimeStamp ); if ( name == mapName ) { if ( currentTimeStamp != FILE_NOT_FOUND_TIMESTAMP && currentTimeStamp == mapTimeStamp && currentTerrainTimeStamp == terrainTimeStamp ) { common->Printf( "idRenderWorldLocal::InitFromMap: retaining existing map\n" ); glRaytracingLightingClearLights(true); FreeDefs(); TouchWorldModels(); TouchWorldTerrainMaterials(); LoadWorldDecals(); AddWorldModelEntities(); ClearPortalStates(); return true; } common->Printf( "idRenderWorldLocal::InitFromMap: timestamp has changed, reloading.\n" ); } FreeWorld(); src = new idLexer( filename, LEXFL_NOSTRINGCONCAT | LEXFL_NODOLLARPRECOMPILE ); if ( !src->IsLoaded() ) { common->Printf( "idRenderWorldLocal::InitFromMap: %s not found\n", filename.c_str() ); ClearWorld(); return false; } mapName = name; mapTimeStamp = currentTimeStamp; LoadWorldDecals(); LoadWorldTerrain(); // if we are writing a demo, archive the load command if ( session->writeDemo ) { WriteLoadMap(); } if ( !src->ReadToken( &token ) || token.Icmp( PROC_FILE_ID ) ) { common->Printf( "idRenderWorldLocal::InitFromMap: bad id '%s' instead of '%s'\n", token.c_str(), PROC_FILE_ID ); delete src; return false; } #ifdef QUAKE4 // jmarshall: quake 4 proc format if (!src->ReadToken(&token)) { common->Printf("idRenderWorldLocal::InitFromMap: bad version '%s' instead of '%s'\n", token.c_str(), PROC_FILEVERSION); delete src; return false; } // Map CRC, we aren't going to use it. src->ReadToken(&token); // jmarshall end #endif // parse the file while ( 1 ) { if ( !src->ReadToken( &token ) ) { break; } if ( token == "model" ) { lastModel = ParseModel( src ); // add it to the model manager list renderModelManager->AddModel( lastModel ); // save it in the list to free when clearing this map localModels.Append( lastModel ); continue; } if ( token == "shadowModel" ) { lastModel = ParseShadowModel( src ); // add it to the model manager list renderModelManager->AddModel( lastModel ); // save it in the list to free when clearing this map localModels.Append( lastModel ); continue; } if ( token == "interAreaPortals" ) { ParseInterAreaPortals( src ); continue; } if ( token == "nodes" ) { ParseNodes( src ); continue; } if ( token == "pathTraceAreas" ) { ParsePathTraceAreas( src ); continue; } src->Error( "idRenderWorldLocal::InitFromMap: bad token \"%s\"", token.c_str() ); } delete src; // if it was a trivial map without any areas, create a single area if ( !numPortalAreas ) { ClearWorld(); } // find the points where we can early-our of reference pushing into the BSP tree CommonChildrenArea_r( &areaNodes[0] ); AddWorldModelEntities(); ClearPortalStates(); // done! return true; } /* ===================== idRenderWorldLocal::ClearPortalStates ===================== */ void idRenderWorldLocal::ClearPortalStates() { int i, j; // all portals start off open for ( i = 0 ; i < numInterAreaPortals ; i++ ) { doublePortals[i].blockingBits = PS_BLOCK_NONE; } // flood fill all area connections for ( i = 0 ; i < numPortalAreas ; i++ ) { for ( j = 0 ; j < NUM_PORTAL_ATTRIBUTES ; j++ ) { connectedAreaNum++; FloodConnectedAreas( &portalAreas[i], j ); } } } /* ===================== idRenderWorldLocal::AddWorldModelEntities ===================== */ void idRenderWorldLocal::AddWorldModelEntities() { int i; // add the world model for each portal area // we can't just call AddEntityDef, because that would place the references // based on the bounding box, rather than explicitly into the correct area for ( i = 0 ; i < numPortalAreas ; i++ ) { idRenderEntityLocal *def; int index; def = new idRenderEntityLocal; // try and reuse a free spot index = entityDefs.FindNull(); if ( index == -1 ) { index = entityDefs.Append(def); } else { entityDefs[index] = def; } def->index = index; def->world = this; def->parms.hModel = renderModelManager->FindModel( va("_area%i", i ) ); if ( def->parms.hModel->IsDefaultModel() || !def->parms.hModel->IsStaticWorldModel() ) { common->Error( "idRenderWorldLocal::InitFromMap: bad area model lookup" ); } idRenderModel *hModel = def->parms.hModel; for ( int j = 0; j < hModel->NumSurfaces(); j++ ) { const modelSurface_t *surf = hModel->Surface( j ); if ( surf->shader->GetName() == idStr( "textures/smf/portal_sky" ) ) { def->needsPortalSky = true; } } def->referenceBounds = def->parms.hModel->Bounds(); def->parms.axis[0][0] = 1; def->parms.axis[1][1] = 1; def->parms.axis[2][2] = 1; R_AxisToModelMatrix( def->parms.axis, def->parms.origin, def->modelMatrix ); // in case an explicit shader is used on the world, we don't // want it to have a 0 alpha or color def->parms.shaderParms[0] = def->parms.shaderParms[1] = def->parms.shaderParms[2] = def->parms.shaderParms[3] = 1; AddEntityRefToArea( def, &portalAreas[i] ); } } /* ===================== CheckAreaForPortalSky ===================== */ bool idRenderWorldLocal::CheckAreaForPortalSky( int areaNum ) { areaReference_t *ref; assert( areaNum >= 0 && areaNum < numPortalAreas ); for ( ref = portalAreas[areaNum].entityRefs.areaNext; ref->entity; ref = ref->areaNext ) { assert( ref->area == &portalAreas[areaNum] ); if ( ref->entity && ref->entity->needsPortalSky ) { return true; } } return false; }