diff --git a/src/ObjLoading/Game/T6/BSP/CustomMapLinker.h b/src/ObjLoading/Game/T6/BSP/CustomMapLinker.h deleted file mode 100644 index 33ea1dce..00000000 --- a/src/ObjLoading/Game/T6/BSP/CustomMapLinker.h +++ /dev/null @@ -1,1987 +0,0 @@ -#pragma once - -#include "BSP.h" -#include "BSPCalculation.h" -#include "BSPUtil.h" - -#include "Game/T6/Material/JsonMaterialLoaderT6.h" -#include "Utils/Pack.h" - -#include -using namespace nlohmann; - -namespace BSP -{ - class CustomMapLinker - { - public: - CustomMapLinker(MemoryManager& memory, ISearchPath& searchPath, Zone& zone, AssetCreationContext& context) - : m_memory(memory), - m_search_path(searchPath), - m_zone(zone), - m_context(context) - { - hasLinkFailed = false; - } - - bool linkCustomMap(BSPData* projInfo) - { - _ASSERT(projInfo != NULL); - - checkAndAddDefaultRequiredAssets(projInfo); - if (hasLinkFailed) - { - printf("Custom Map link has failed.\n"); - return false; - } - - - createComWorld(projInfo); - createMapEnts(projInfo); - createGameWorldMp(projInfo); - createSkinnedVerts(projInfo); - createGfxWorld(projInfo); // requires mapents asset - createClipMap(projInfo); // must go last (requires gfx and mapents asset) - - if (hasLinkFailed) - { - printf("Custom Map link has failed.\n"); - return false; - } - - return true; - } - - private: - struct entModelBounds - { - vec3_t mins; - vec3_t maxs; - }; - - MemoryManager& m_memory; - ISearchPath& m_search_path; - Zone& m_zone; - AssetCreationContext& m_context; - - bool hasLinkFailed; - std::vector entityModelList; - - json materialTemplateJson; - - // TODO vd1: - // used for UVs of sub-textures, when it is set to empty all of them turn a blank colour - // could fix by removing sub textures or figure out how they are created and redo that - // its not an important issue though - bool overwriteDrawData(BSPData* projInfo, GfxWorld* gfxWorld) - { - int vertexCount = projInfo->gfxWorld.vertices.size(); - - gfxWorld->draw.vertexCount = vertexCount; - gfxWorld->draw.vertexDataSize0 = vertexCount * sizeof(GfxPackedWorldVertex); - GfxPackedWorldVertex* vertexBuffer = new GfxPackedWorldVertex[vertexCount]; - for (int i = 0; i < vertexCount; i++) - { - BSPVertex* WorldVertex = &projInfo->gfxWorld.vertices[i]; - GfxPackedWorldVertex* GfxVertex = &vertexBuffer[i]; - - GfxVertex->xyz = BSPUtil::convertToBO2Coords(WorldVertex->pos); - //GfxVertex->xyz = WorldVertex->pos; - - GfxVertex->color.packed = pack32::Vec4PackGfxColor(WorldVertex->color.v); - - GfxVertex->texCoord.packed = pack32::Vec2PackTexCoordsUV(WorldVertex->texCoord.v); - - GfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(BSPUtil::convertToBO2Coords(WorldVertex->normal).v); - //GfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(WorldVertex->normal.v); - - GfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(BSPUtil::convertToBO2Coords(WorldVertex->tangent).v); - //GfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(WorldVertex->tangent.v); - - // unknown use variables - // binormalSign may be bitangent of the vertex - // lmapCoord may be the lightmap coordinate of the vertex - GfxVertex->binormalSign = 0.0f; - GfxVertex->lmapCoord.packed = 0; - } - gfxWorld->draw.vd0.data = (char*)vertexBuffer; - - // we don't use vd1 but still needs to be initialised - // the data type varies and 0x20 is enough for all types - gfxWorld->draw.vertexDataSize1 = 0x20; - gfxWorld->draw.vd1.data = new char[gfxWorld->draw.vertexDataSize1]; - memset(gfxWorld->draw.vd1.data, 0, gfxWorld->draw.vertexDataSize1); - - int indexCount = projInfo->gfxWorld.indices.size(); - _ASSERT(indexCount % 3 == 0); - gfxWorld->draw.indexCount = indexCount; - gfxWorld->draw.indices = new uint16_t[indexCount]; - for (int i = 0; i < indexCount; i += 3) - { - // the editor orders their vertices opposite to bo2, so its converted here - gfxWorld->draw.indices[i + 2] = projInfo->gfxWorld.indices[i + 0]; - gfxWorld->draw.indices[i + 1] = projInfo->gfxWorld.indices[i + 1]; - gfxWorld->draw.indices[i + 0] = projInfo->gfxWorld.indices[i + 2]; - } - - return true; - } - - struct s_sortedSurf - { - int surfaceIndex; - int vertexCount; - }; - - bool compareSurfaces(s_sortedSurf& surf0, s_sortedSurf& surf1) - { - return surf0.vertexCount > surf1.vertexCount; - } - - Material* loadImageIntoMaterial(std::string& imageName) - { - std::string imagePath = std::format("images/{}.iwi", imageName); - auto imageFile = m_search_path.Open(imagePath); - if (!imageFile.IsOpen()) - { - printf("WARN: failed to find image %s.\n", imageName.c_str()); - return NULL; - } - - Material* material = new Material; - material->info.name = m_memory.Dup(imageName.c_str()); - - // parse the template file and replace the image name - materialTemplateJson["textures"][1]["image"] = imageName; - - AssetRegistration registration(imageName, material); - if (!LoadMaterialAsJson(materialTemplateJson, *material, m_memory, m_context, registration)) - { - printf("WARN: failed to convert image %s to a material.\n", imageName.c_str()); - return NULL; - } - - m_context.AddAsset(std::move(registration)); - - return material; - } - - void overwriteMapSurfaces(BSPData* projInfo, GfxWorld* gfxWorld) - { - bool overwriteResult = overwriteDrawData(projInfo, gfxWorld); - if (!overwriteResult) - return; - - unsigned int surfaceCount = projInfo->gfxWorld.surfaces.size(); - gfxWorld->surfaceCount = surfaceCount; - gfxWorld->dpvs.staticSurfaceCount = surfaceCount; - gfxWorld->dpvs.surfaces = new GfxSurface[surfaceCount]; - for (unsigned int i = 0; i < surfaceCount; i++) - { - auto currSurface = &gfxWorld->dpvs.surfaces[i]; - auto objSurface = &projInfo->gfxWorld.surfaces[i]; - - currSurface->primaryLightIndex = BSPEditableConstants::DEFAULT_SURFACE_LIGHT; - currSurface->lightmapIndex = BSPEditableConstants::DEFAULT_SURFACE_LIGHTMAP; - currSurface->reflectionProbeIndex = BSPEditableConstants::DEFAULT_SURFACE_REFLECTION_PROBE; - currSurface->flags = BSPEditableConstants::DEFAULT_SURFACE_FLAGS; - - currSurface->tris.triCount = objSurface->triCount; - currSurface->tris.baseIndex = objSurface->indexOfFirstIndex; - - currSurface->tris.vertexDataOffset0 = objSurface->indexOfFirstVertex * sizeof(GfxPackedWorldVertex); - currSurface->tris.vertexDataOffset1 = 0; - - std::string surfMaterialName; - switch (objSurface->material.materialType) - { - case MATERIAL_TYPE_TEXTURE: - surfMaterialName = objSurface->material.materialName; - break; - - case MATERIAL_TYPE_COLOUR: - case MATERIAL_TYPE_EMPTY: - surfMaterialName = BSPLinkingConstants::COLOR_ONLY_IMAGE_NAME; - break; - - default: - _ASSERT(false); - } - Material* surfMaterial = loadImageIntoMaterial(surfMaterialName); - if (surfMaterial == NULL) - { - std::string missingImageName = std::string(BSPLinkingConstants::MISSING_IMAGE_NAME); - surfMaterial = loadImageIntoMaterial(missingImageName); - if (surfMaterial == NULL) - { - printf("Error: unable to find the missing image texture!\n"); - hasLinkFailed = true; - return; - } - } - currSurface->material = surfMaterial; - - GfxPackedWorldVertex* firstVert = (GfxPackedWorldVertex*)&gfxWorld->draw.vd0.data[currSurface->tris.vertexDataOffset0]; - currSurface->bounds[0].x = firstVert[0].xyz.x; - currSurface->bounds[0].y = firstVert[0].xyz.y; - currSurface->bounds[0].z = firstVert[0].xyz.z; - currSurface->bounds[1].x = firstVert[0].xyz.x; - currSurface->bounds[1].y = firstVert[0].xyz.y; - currSurface->bounds[1].z = firstVert[0].xyz.z; - for (int k = 0; k < currSurface->tris.triCount * 3; k++) - { - uint16_t vertIndex = gfxWorld->draw.indices[currSurface->tris.baseIndex + k]; - BSPUtil::calcNewBoundsWithPoint(&firstVert[vertIndex].xyz, &currSurface->bounds[0], &currSurface->bounds[1]); - } - - // unused values - currSurface->tris.mins.x = 0.0f; - currSurface->tris.mins.y = 0.0f; - currSurface->tris.mins.z = 0.0f; - currSurface->tris.maxs.x = 0.0f; - currSurface->tris.maxs.y = 0.0f; - currSurface->tris.maxs.z = 0.0f; - currSurface->tris.himipRadiusInvSq = 0.0f; - currSurface->tris.vertexCount = 0; - currSurface->tris.firstVertex = 0; - } - - // doesn't seem to matter what order the sorted surfs go in - gfxWorld->dpvs.sortedSurfIndex = new uint16_t[surfaceCount]; - for (unsigned int i = 0; i < surfaceCount; i++) - { - gfxWorld->dpvs.sortedSurfIndex[i] = i; - } - - gfxWorld->dpvs.surfaceMaterials = new GfxDrawSurf_align4[surfaceCount]; - memset(gfxWorld->dpvs.surfaceMaterials, 0, sizeof(GfxDrawSurf_align4) * surfaceCount); - - // all visdata is alligned by 128 - gfxWorld->dpvs.surfaceVisDataCount = BSPUtil::allignBy128(surfaceCount); - gfxWorld->dpvs.surfaceVisData[0] = new char[surfaceCount]; - gfxWorld->dpvs.surfaceVisData[1] = new char[surfaceCount]; - gfxWorld->dpvs.surfaceVisData[2] = new char[surfaceCount]; - gfxWorld->dpvs.surfaceVisDataCameraSaved = new char[surfaceCount]; - gfxWorld->dpvs.surfaceCastsShadow = new char[surfaceCount]; - gfxWorld->dpvs.surfaceCastsSunShadow = new char[surfaceCount]; - memset(gfxWorld->dpvs.surfaceVisData[0], 0, surfaceCount); - memset(gfxWorld->dpvs.surfaceVisData[1], 0, surfaceCount); - memset(gfxWorld->dpvs.surfaceVisData[2], 0, surfaceCount); - memset(gfxWorld->dpvs.surfaceVisDataCameraSaved, 0, surfaceCount); - memset(gfxWorld->dpvs.surfaceCastsShadow, 0, surfaceCount); - memset(gfxWorld->dpvs.surfaceCastsSunShadow, 0, surfaceCount); - - gfxWorld->dpvs.litSurfsBegin = 0; - gfxWorld->dpvs.litSurfsEnd = surfaceCount; - gfxWorld->dpvs.emissiveOpaqueSurfsBegin = surfaceCount; - gfxWorld->dpvs.emissiveOpaqueSurfsEnd = surfaceCount; - gfxWorld->dpvs.emissiveTransSurfsBegin = surfaceCount; - gfxWorld->dpvs.emissiveTransSurfsEnd = surfaceCount; - gfxWorld->dpvs.litTransSurfsBegin = surfaceCount; - gfxWorld->dpvs.litTransSurfsEnd = surfaceCount; - } - - void overwriteMapSModels(BSPData* projInfo, GfxWorld* gfxWorld) - { - /* - Models are unsupported right now - Code is left in in case it is supported later on - */ - //unsigned int modelCount = projInfo->modelCount; - //gfxWorld->dpvs.smodelCount = modelCount; - //gfxWorld->dpvs.smodelInsts = new GfxStaticModelInst[modelCount]; - //gfxWorld->dpvs.smodelDrawInsts = new GfxStaticModelDrawInst[modelCount]; - // - //for (unsigned int i = 0; i < modelCount; i++) - //{ - // auto currModel = &gfxWorld->dpvs.smodelDrawInsts[i]; - // auto currModelInst = &gfxWorld->dpvs.smodelInsts[i]; - // customMapModel* inModel = &projInfo->models[i]; - // - // auto xModelAsset = m_context.LoadDependency(inModel->name); - // if (xModelAsset == NULL) - // { - // printf("XModel %s not found!\n", inModel->name.c_str()); - // currModel->model = NULL; - // } - // else - // currModel->model = (XModel*)xModelAsset->Asset(); - // - // currModel->placement.origin.x = inModel->origin.x; - // currModel->placement.origin.y = inModel->origin.y; - // currModel->placement.origin.z = inModel->origin.z; - // currModel->placement.origin = BSPUtil::convertToBO2Coords(currModel->placement.origin); - // currModel->placement.scale = inModel->scale; - // - // BSPUtil::convertAnglesToAxis(&inModel->rotation, currModel->placement.axis); - // - // // mins and maxs are calculated in world space not local space - // // TODO: this does not account for model rotation or scale - // currModelInst->mins.x = currModel->model->mins.x + currModel->placement.origin.x; - // currModelInst->mins.y = currModel->model->mins.y + currModel->placement.origin.y; - // currModelInst->mins.z = currModel->model->mins.z + currModel->placement.origin.z; - // currModelInst->maxs.x = currModel->model->maxs.x + currModel->placement.origin.x; - // currModelInst->maxs.y = currModel->model->maxs.y + currModel->placement.origin.y; - // currModelInst->maxs.z = currModel->model->maxs.z + currModel->placement.origin.z; - // - // currModel->cullDist = DEFAULT_SMODEL_CULL_DIST; - // currModel->flags = DEFAULT_SMODEL_FLAGS; - // currModel->primaryLightIndex = DEFAULT_SMODEL_LIGHT; - // currModel->reflectionProbeIndex = DEFAULT_SMODEL_REFLECTION_PROBE; - // - // // unknown use / unused - // currModel->smid = i; - // memset(&currModel->lightingSH, 0, sizeof(GfxLightingSHQuantized)); - // currModel->invScaleSq = 0.0f; - // currModel->lightingHandle = 0; - // currModel->colorsIndex = 0; - // currModel->visibility = 0; - // - // // setting these to NULL makes any static/baked lighting go black when not rendered by real-time lighting or in a shadow - // // TODO: calculate lighting and store it here - // currModel->lmapVertexInfo[0].numLmapVertexColors = 0; - // currModel->lmapVertexInfo[0].lmapVertexColors = NULL; - // currModel->lmapVertexInfo[1].numLmapVertexColors = 0; - // currModel->lmapVertexInfo[1].lmapVertexColors = NULL; - // currModel->lmapVertexInfo[2].numLmapVertexColors = 0; - // currModel->lmapVertexInfo[2].lmapVertexColors = NULL; - // currModel->lmapVertexInfo[3].numLmapVertexColors = 0; - // currModel->lmapVertexInfo[3].lmapVertexColors = NULL; - //} - - unsigned int modelCount = 0; - gfxWorld->dpvs.smodelCount = modelCount; - gfxWorld->dpvs.smodelInsts = new GfxStaticModelInst[modelCount]; - gfxWorld->dpvs.smodelDrawInsts = new GfxStaticModelDrawInst[modelCount]; - - // all visdata is alligned by 128 - int allignedModelCount = BSPUtil::allignBy128(modelCount); - gfxWorld->dpvs.smodelVisDataCount = allignedModelCount; - gfxWorld->dpvs.smodelVisData[0] = new char[allignedModelCount]; - gfxWorld->dpvs.smodelVisData[1] = new char[allignedModelCount]; - gfxWorld->dpvs.smodelVisData[2] = new char[allignedModelCount]; - gfxWorld->dpvs.smodelVisDataCameraSaved = new char[allignedModelCount]; - gfxWorld->dpvs.smodelCastsShadow = new char[allignedModelCount]; - memset(gfxWorld->dpvs.smodelVisData[0], 0, allignedModelCount); - memset(gfxWorld->dpvs.smodelVisData[1], 0, allignedModelCount); - memset(gfxWorld->dpvs.smodelVisData[2], 0, allignedModelCount); - memset(gfxWorld->dpvs.smodelVisDataCameraSaved, 0, allignedModelCount); - memset(gfxWorld->dpvs.smodelCastsShadow, 0, allignedModelCount); - for (unsigned int i = 0; i < modelCount; i++) - { - if ((gfxWorld->dpvs.smodelDrawInsts[i].flags & STATIC_MODEL_FLAG_NO_SHADOW) == 0) - gfxWorld->dpvs.smodelCastsShadow[i] = 1; - else - gfxWorld->dpvs.smodelCastsShadow[i] = 0; - } - - // always set to 0 - gfxWorld->dpvs.usageCount = 0; - } - - void cleanGfxWorld(GfxWorld* gfxWorld) - { - // checksum is generated by the game - gfxWorld->checksum = 0; - - // Remove Coronas - gfxWorld->coronaCount = 0; - gfxWorld->coronas = NULL; - - // Remove exposure volumes - gfxWorld->exposureVolumeCount = 0; - gfxWorld->exposureVolumes = NULL; - gfxWorld->exposureVolumePlaneCount = 0; - gfxWorld->exposureVolumePlanes = NULL; - - // Remove hero lights - gfxWorld->heroLightCount = 0; - gfxWorld->heroLights = NULL; - gfxWorld->heroLightTreeCount = 0; - gfxWorld->heroLightTree = NULL; - - // remove LUT data - gfxWorld->lutVolumeCount = 0; - gfxWorld->lutVolumes = NULL; - gfxWorld->lutVolumePlaneCount = 0; - gfxWorld->lutVolumePlanes = NULL; - - // remove occluders - gfxWorld->numOccluders = 0; - gfxWorld->occluders = NULL; - - // remove Siege Skins - gfxWorld->numSiegeSkinInsts = 0; - gfxWorld->siegeSkinInsts = NULL; - - // remove outdoor bounds - gfxWorld->numOutdoorBounds = 0; - gfxWorld->outdoorBounds = NULL; - - // remove materials - gfxWorld->ropeMaterial = NULL; - gfxWorld->lutMaterial = NULL; - gfxWorld->waterMaterial = NULL; - gfxWorld->coronaMaterial = NULL; - - // remove shadow maps - gfxWorld->shadowMapVolumeCount = 0; - gfxWorld->shadowMapVolumes = NULL; - gfxWorld->shadowMapVolumePlaneCount = 0; - gfxWorld->shadowMapVolumePlanes = NULL; - - // remove stream info - gfxWorld->streamInfo.aabbTreeCount = 0; - gfxWorld->streamInfo.aabbTrees = NULL; - gfxWorld->streamInfo.leafRefCount = 0; - gfxWorld->streamInfo.leafRefs = NULL; - - // remove sun data - memset(&gfxWorld->sun, 0, sizeof(sunflare_t)); - gfxWorld->sun.hasValidData = false; - - // Remove Water - gfxWorld->waterDirection = 0.0f; - gfxWorld->waterBuffers[0].buffer = NULL; - gfxWorld->waterBuffers[0].bufferSize = NULL; - gfxWorld->waterBuffers[1].buffer = NULL; - gfxWorld->waterBuffers[1].bufferSize = NULL; - - // Remove Fog - gfxWorld->worldFogModifierVolumeCount = 0; - gfxWorld->worldFogModifierVolumes = NULL; - gfxWorld->worldFogModifierVolumePlaneCount = 0; - gfxWorld->worldFogModifierVolumePlanes = NULL; - gfxWorld->worldFogVolumeCount = 0; - gfxWorld->worldFogVolumes = NULL; - gfxWorld->worldFogVolumePlaneCount = 0; - gfxWorld->worldFogVolumePlanes = NULL; - - // materialMemory is unused - gfxWorld->materialMemoryCount = 0; - gfxWorld->materialMemory = NULL; - - // sunLight is overwritten by the game, just needs to be a valid pointer - gfxWorld->sunLight = new GfxLight; - memset(gfxWorld->sunLight, 0, sizeof(GfxLight)); - } - - void overwriteGfxLights(GfxWorld* gfxWorld) - { - // there must be 2 or more lights, first is the default light and second is the sun - gfxWorld->primaryLightCount = 2; - gfxWorld->sunPrimaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; - - gfxWorld->shadowGeom = new GfxShadowGeometry[gfxWorld->primaryLightCount]; - for (unsigned int i = 0; i < gfxWorld->primaryLightCount; i++) - { - gfxWorld->shadowGeom[i].smodelCount = 0; - gfxWorld->shadowGeom[i].surfaceCount = 0; - gfxWorld->shadowGeom[i].smodelIndex = NULL; - gfxWorld->shadowGeom[i].sortedSurfIndex = new uint16_t[gfxWorld->surfaceCount]; - memset(gfxWorld->shadowGeom[i].sortedSurfIndex, 0, sizeof(uint16_t) * gfxWorld->surfaceCount); - } - - gfxWorld->lightRegion = new GfxLightRegion[gfxWorld->primaryLightCount]; - for (unsigned int i = 0; i < gfxWorld->primaryLightCount; i++) - { - gfxWorld->lightRegion[i].hullCount = 0; - gfxWorld->lightRegion[i].hulls = NULL; - } - - int lightEntShadowVisSize = (gfxWorld->primaryLightCount - gfxWorld->sunPrimaryLightIndex - 1) * 8192; - if (lightEntShadowVisSize != 0) - { - gfxWorld->primaryLightEntityShadowVis = new unsigned int[lightEntShadowVisSize]; - memset(gfxWorld->primaryLightEntityShadowVis, 1, lightEntShadowVisSize * sizeof(unsigned int)); - } - else - { - gfxWorld->primaryLightEntityShadowVis = NULL; - } - } - - // the lightgrid is used to light models in a dynamic way and is precomputed - void overwriteLightGrid(GfxWorld* gfxWorld) - { - // there is almost no basis for the values in this code, i chose them based on what feels right and what i could see when RE. - // it works and that is all thats needed :) - - // mins and maxs define the range that the lightgrid will work in - // idk how these values are calculated, but the below values are larger - // than official map values - gfxWorld->lightGrid.mins[0] = 0; - gfxWorld->lightGrid.mins[1] = 0; - gfxWorld->lightGrid.mins[2] = 0; - gfxWorld->lightGrid.maxs[0] = 200; - gfxWorld->lightGrid.maxs[1] = 200; - gfxWorld->lightGrid.maxs[2] = 50; - - gfxWorld->lightGrid.rowAxis = 0; // default value - gfxWorld->lightGrid.colAxis = 1; // default value - gfxWorld->lightGrid.sunPrimaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; - gfxWorld->lightGrid.offset = 0.0f; // default value - - // this will make the lookup into rawRowData always return the first row - int rowDataStartSize = gfxWorld->lightGrid.maxs[gfxWorld->lightGrid.rowAxis] - gfxWorld->lightGrid.mins[gfxWorld->lightGrid.rowAxis] + 1; - gfxWorld->lightGrid.rowDataStart = new uint16_t[rowDataStartSize]; - memset(gfxWorld->lightGrid.rowDataStart, 0, rowDataStartSize * sizeof(uint16_t)); - - gfxWorld->lightGrid.rawRowDataSize = sizeof(GfxLightGridRow); - GfxLightGridRow* row = (GfxLightGridRow*)m_memory.AllocRaw(sizeof(GfxLightGridRow) + 0x10); - row->colStart = 0; - row->colCount = 0x1000; // 0x1000 as this is large enough for all checks done by the game - row->zStart = 0; - row->zCount = 0xFF; // 0xFF as this is large enough for all checks done by the game, but small enough not to mess with other checks - row->firstEntry = 0; - for (int i = 0; i < 0x11; i++) // set the lookup table to all 0 - { - row->lookupTable[i] = 0; - } - gfxWorld->lightGrid.rawRowData = (aligned_byte_pointer*)row; - - // entries are looked up based on the lightgrid sample pos and data within GfxLightGridRow - gfxWorld->lightGrid.entryCount = 60000; // 60000 as it should be enough entries to be indexed by all lightgrid data - GfxLightGridEntry* entryArray = new GfxLightGridEntry[gfxWorld->lightGrid.entryCount]; - for (unsigned int i = 0; i < gfxWorld->lightGrid.entryCount; i++) - { - entryArray[i].colorsIndex = 0; // always index first colour - entryArray[i].primaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; - entryArray[i].visibility = 0; - } - gfxWorld->lightGrid.entries = entryArray; - - // colours are looked up by an entries colourindex - gfxWorld->lightGrid.colorCount = 0x1000; //0x1000 as it should be enough to hold every index - gfxWorld->lightGrid.colors = new GfxCompressedLightGridColors[gfxWorld->lightGrid.colorCount]; - memset(gfxWorld->lightGrid.colors, BSPEditableConstants::LIGHTGRID_COLOUR, rowDataStartSize * sizeof(uint16_t)); - - // we use the colours array instead of coeffs array - gfxWorld->lightGrid.coeffCount = 0; - gfxWorld->lightGrid.coeffs = NULL; - gfxWorld->lightGrid.skyGridVolumeCount = 0; - gfxWorld->lightGrid.skyGridVolumes = NULL; - } - - void updateGfxCells(GfxWorld* gfxWorld) - { - // Cells are basically data used to determine what can be seen and what cant be seen - // Right now custom maps have no optimisation so there is only 1 cell - int cellCount = 1; - - gfxWorld->cellBitsCount = ((cellCount + 127) >> 3) & 0x1FFFFFF0; - - int cellCasterBitsCount = cellCount * ((cellCount + 31) / 32); - gfxWorld->cellCasterBits = new unsigned int[cellCasterBitsCount]; - memset(gfxWorld->cellCasterBits, 0x00, cellCasterBitsCount * sizeof(unsigned int)); - - gfxWorld->cells = new GfxCell[cellCount]; - gfxWorld->cells[0].portalCount = 0; - gfxWorld->cells[0].portals = NULL; - gfxWorld->cells[0].mins.x = gfxWorld->mins.x; - gfxWorld->cells[0].mins.y = gfxWorld->mins.y; - gfxWorld->cells[0].mins.z = gfxWorld->mins.z; - gfxWorld->cells[0].maxs.x = gfxWorld->maxs.x; - gfxWorld->cells[0].maxs.y = gfxWorld->maxs.y; - gfxWorld->cells[0].maxs.z = gfxWorld->maxs.z; - - // there is only 1 reflection probe - gfxWorld->cells[0].reflectionProbeCount = 1; - char* reflectionProbeIndexes = new char[gfxWorld->cells[0].reflectionProbeCount]; - reflectionProbeIndexes[0] = BSPEditableConstants::DEFAULT_SURFACE_REFLECTION_PROBE; - gfxWorld->cells[0].reflectionProbes = reflectionProbeIndexes; - - // AABB trees are used to detect what should be rendered and what shouldn't - // Just use the first AABB node to hold all models, no optimisation but all models/surfaces wil lbe drawn - gfxWorld->cells[0].aabbTreeCount = 1; - gfxWorld->cells[0].aabbTree = new GfxAabbTree[gfxWorld->cells[0].aabbTreeCount]; - gfxWorld->cells[0].aabbTree[0].childCount = 0; - gfxWorld->cells[0].aabbTree[0].childrenOffset = 0; - gfxWorld->cells[0].aabbTree[0].startSurfIndex = 0; - gfxWorld->cells[0].aabbTree[0].surfaceCount = gfxWorld->surfaceCount; - gfxWorld->cells[0].aabbTree[0].smodelIndexCount = gfxWorld->dpvs.smodelCount; - gfxWorld->cells[0].aabbTree[0].smodelIndexes = new unsigned short[gfxWorld->dpvs.smodelCount]; - for (unsigned short i = 0; i < gfxWorld->dpvs.smodelCount; i++) - { - gfxWorld->cells[0].aabbTree[0].smodelIndexes[i] = i; - } - gfxWorld->cells[0].aabbTree[0].mins.x = gfxWorld->mins.x; - gfxWorld->cells[0].aabbTree[0].mins.y = gfxWorld->mins.y; - gfxWorld->cells[0].aabbTree[0].mins.z = gfxWorld->mins.z; - gfxWorld->cells[0].aabbTree[0].maxs.x = gfxWorld->maxs.x; - gfxWorld->cells[0].aabbTree[0].maxs.y = gfxWorld->maxs.y; - gfxWorld->cells[0].aabbTree[0].maxs.z = gfxWorld->maxs.z; - - gfxWorld->dpvsPlanes.cellCount = cellCount; - - int sceneEntCellBitsCount = cellCount * 512; - gfxWorld->dpvsPlanes.sceneEntCellBits = new unsigned int[sceneEntCellBitsCount]; - memset(gfxWorld->dpvsPlanes.sceneEntCellBits, 0x00, sceneEntCellBitsCount * sizeof(unsigned int)); - - // nodes have the struct mnode_t, and there must be at least 1 node - // Nodes mnode_t.cellIndex indexes gfxWorld->cells - // and (mnode_t.cellIndex - (world->dpvsPlanes.cellCount + 1) indexes world->dpvsPlanes.planes - gfxWorld->nodeCount = 1; - gfxWorld->dpvsPlanes.nodes = new uint16_t[gfxWorld->nodeCount]; - gfxWorld->dpvsPlanes.nodes[0] = 1; // nodes reference cells by index + 1 - - // planes are overwritten by the clipmap loading code ingame - gfxWorld->planeCount = 0; - gfxWorld->dpvsPlanes.planes = NULL; - } - - void updateWorldBounds(GfxWorld* gfxWorld) - { - gfxWorld->mins.x = 0.0f; - gfxWorld->mins.y = 0.0f; - gfxWorld->mins.z = 0.0f; - gfxWorld->maxs.x = 0.0f; - gfxWorld->maxs.y = 0.0f; - gfxWorld->maxs.z = 0.0f; - - for (int i = 0; i < gfxWorld->surfaceCount; i++) - { - BSPUtil::calcNewBounds(&gfxWorld->dpvs.surfaces[i].bounds[0], &gfxWorld->dpvs.surfaces[i].bounds[1], &gfxWorld->mins, &gfxWorld->maxs); - } - } - - void overwriteModels(GfxWorld* gfxWorld) - { - // these models are the collision for the entities defined in the mapents asset - // used for triggers and stuff - - gfxWorld->modelCount = entityModelList.size() + 1; - gfxWorld->models = new GfxBrushModel[gfxWorld->modelCount]; - - // first model is always the world model - gfxWorld->models[0].startSurfIndex = 0; - gfxWorld->models[0].surfaceCount = gfxWorld->surfaceCount; - gfxWorld->models[0].bounds[0].x = gfxWorld->mins.x; - gfxWorld->models[0].bounds[0].y = gfxWorld->mins.y; - gfxWorld->models[0].bounds[0].z = gfxWorld->mins.z; - gfxWorld->models[0].bounds[1].x = gfxWorld->maxs.x; - gfxWorld->models[0].bounds[1].y = gfxWorld->maxs.y; - gfxWorld->models[0].bounds[1].z = gfxWorld->maxs.z; - memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); - - for (size_t i = 0; i < entityModelList.size(); i++) - { - auto currEntModel = &gfxWorld->models[i + 1]; - entModelBounds currEntModelBounds = entityModelList[i]; - - currEntModel->startSurfIndex = 0; - currEntModel->surfaceCount = -1; // -1 when it doesn't use map surfaces - currEntModel->bounds[0].x = currEntModelBounds.mins.x; - currEntModel->bounds[0].y = currEntModelBounds.mins.y; - currEntModel->bounds[0].z = currEntModelBounds.mins.z; - currEntModel->bounds[1].x = currEntModelBounds.maxs.x; - currEntModel->bounds[1].y = currEntModelBounds.maxs.y; - currEntModel->bounds[1].z = currEntModelBounds.maxs.z; - memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); - } - } - - void updateSunData(GfxWorld* gfxWorld) - { - // default values taken from mp_dig - gfxWorld->sunParse.fogTransitionTime = (float)0.001; - gfxWorld->sunParse.name[0] = 0x00; - - gfxWorld->sunParse.initWorldSun->control = 0; - gfxWorld->sunParse.initWorldSun->exposure = 2.5f; - gfxWorld->sunParse.initWorldSun->angles.x = -29.0f; - gfxWorld->sunParse.initWorldSun->angles.y = 254.0f; - gfxWorld->sunParse.initWorldSun->angles.z = 0.0f; - gfxWorld->sunParse.initWorldSun->sunCd.x = 1.0f; - gfxWorld->sunParse.initWorldSun->sunCd.y = 0.89f; - gfxWorld->sunParse.initWorldSun->sunCd.z = 0.69f; - gfxWorld->sunParse.initWorldSun->sunCd.w = 13.5f; - gfxWorld->sunParse.initWorldSun->ambientColor.x = 0.0f; - gfxWorld->sunParse.initWorldSun->ambientColor.y = 0.0f; - gfxWorld->sunParse.initWorldSun->ambientColor.z = 0.0f; - gfxWorld->sunParse.initWorldSun->ambientColor.w = 0.0f; - gfxWorld->sunParse.initWorldSun->skyColor.x = 0.0f; - gfxWorld->sunParse.initWorldSun->skyColor.y = 0.0f; - gfxWorld->sunParse.initWorldSun->skyColor.z = 0.0f; - gfxWorld->sunParse.initWorldSun->skyColor.w = 0.0f; - gfxWorld->sunParse.initWorldSun->sunCs.x = 0.0f; - gfxWorld->sunParse.initWorldSun->sunCs.y = 0.0f; - gfxWorld->sunParse.initWorldSun->sunCs.z = 0.0f; - gfxWorld->sunParse.initWorldSun->sunCs.w = 0.0f; - - gfxWorld->sunParse.initWorldFog->baseDist = 150.0f; - gfxWorld->sunParse.initWorldFog->baseHeight = -100.0f; - gfxWorld->sunParse.initWorldFog->fogColor.x = 2.35f; - gfxWorld->sunParse.initWorldFog->fogColor.y = 3.10f; - gfxWorld->sunParse.initWorldFog->fogColor.z = 3.84f; - gfxWorld->sunParse.initWorldFog->fogOpacity = 0.52f; - gfxWorld->sunParse.initWorldFog->halfDist = 4450.f; - gfxWorld->sunParse.initWorldFog->halfHeight = 2000.f; - gfxWorld->sunParse.initWorldFog->sunFogColor.x = 5.27f; - gfxWorld->sunParse.initWorldFog->sunFogColor.y = 4.73f; - gfxWorld->sunParse.initWorldFog->sunFogColor.z = 3.88f; - gfxWorld->sunParse.initWorldFog->sunFogInner = 0.0f; - gfxWorld->sunParse.initWorldFog->sunFogOpacity = 0.67f; - gfxWorld->sunParse.initWorldFog->sunFogOuter = 80.84f; - gfxWorld->sunParse.initWorldFog->sunFogPitch = -29.0f; - gfxWorld->sunParse.initWorldFog->sunFogYaw = 254.0f; - } - - void updateReflectionProbeData(GfxWorld* gfxWorld) - { - gfxWorld->draw.reflectionProbeCount = 1; - - gfxWorld->draw.reflectionProbeTextures = new GfxTexture[gfxWorld->draw.reflectionProbeCount]; - memset(gfxWorld->draw.reflectionProbeTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.reflectionProbeCount); - - gfxWorld->draw.reflectionProbes = new GfxReflectionProbe[gfxWorld->draw.reflectionProbeCount]; - - // default values taken from mp_dig - gfxWorld->draw.reflectionProbes[0].mipLodBias = -8.0; - - gfxWorld->draw.reflectionProbes[0].origin.x = 0.0f; - gfxWorld->draw.reflectionProbes[0].origin.y = 0.0f; - gfxWorld->draw.reflectionProbes[0].origin.z = 0.0f; - - gfxWorld->draw.reflectionProbes[0].lightingSH.V0.x = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V0.y = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V0.z = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V0.w = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V1.x = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V1.y = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V1.z = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V1.w = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V2.x = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V2.y = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V2.z = 0.0f; - gfxWorld->draw.reflectionProbes[0].lightingSH.V2.w = 0.0f; - - gfxWorld->draw.reflectionProbes[0].probeVolumeCount = 0; - gfxWorld->draw.reflectionProbes[0].probeVolumes = NULL; - - std::string probeImageName = "*reflection_probe0"; - auto probeImageAsset = m_context.LoadDependency(probeImageName); - if (probeImageAsset == NULL) - { - printf("ERROR! unable to find image %s!\n", probeImageName.c_str()); - hasLinkFailed = true; - return; - } - gfxWorld->draw.reflectionProbes[0].reflectionImage = probeImageAsset->Asset(); - } - - void overwriteLightmapData(GfxWorld* gfxWorld) - { - gfxWorld->draw.lightmapCount = 1; - - gfxWorld->draw.lightmapPrimaryTextures = new GfxTexture[gfxWorld->draw.lightmapCount]; - gfxWorld->draw.lightmapSecondaryTextures = new GfxTexture[gfxWorld->draw.lightmapCount]; - gfxWorld->draw.lightmaps = new GfxLightmapArray[gfxWorld->draw.lightmapCount]; - - // always set to 0 - memset(gfxWorld->draw.lightmapPrimaryTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.lightmapCount); - memset(gfxWorld->draw.lightmapSecondaryTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.lightmapCount); - - std::string secondaryTexture = "*lightmap0_secondary"; - auto secondaryTextureAsset = m_context.LoadDependency(secondaryTexture); - if (secondaryTextureAsset == NULL) - { - printf("ERROR! unable to find lightmap image %s!\n", secondaryTexture.c_str()); - hasLinkFailed = true; - return; - } - gfxWorld->draw.lightmaps[0].primary = NULL; // always NULL - gfxWorld->draw.lightmaps[0].secondary = secondaryTextureAsset->Asset(); - } - - void overwriteSkyBox(BSPData* projInfo, GfxWorld* gfxWorld) - { - std::string skyBoxName = "skybox_" + projInfo->name; - gfxWorld->skyBoxModel = _strdup(skyBoxName.c_str()); - - if (m_context.LoadDependency(skyBoxName) == NULL) - { - printf("WARN: Unable to load the skybox xmodel %s\n", skyBoxName.c_str()); - } - - // default skybox values from mp_dig - gfxWorld->skyDynIntensity.angle0 = 0.0f; - gfxWorld->skyDynIntensity.angle1 = 0.0f; - gfxWorld->skyDynIntensity.factor0 = 1.0f; - gfxWorld->skyDynIntensity.factor1 = 1.0f; - } - - void updateDynEntData(GfxWorld* gfxWorld) - { - int dynEntCount = 0; - gfxWorld->dpvsDyn.dynEntClientCount[0] = dynEntCount + 256; // the game allocs 256 empty dynents, as they may be used ingame - gfxWorld->dpvsDyn.dynEntClientCount[1] = 0; - - // +100: there is a crash that happens when regdolls are created, and dynEntClientWordCount[0] is the issue. - // Making the value much larger than required fixes it, but idk what the root cause is - gfxWorld->dpvsDyn.dynEntClientWordCount[0] = ((gfxWorld->dpvsDyn.dynEntClientCount[0] + 31) >> 5) + 100; - gfxWorld->dpvsDyn.dynEntClientWordCount[1] = 0; - gfxWorld->dpvsDyn.usageCount = 0; - - int dynEntCellBitsSize = gfxWorld->dpvsDyn.dynEntClientWordCount[0] * gfxWorld->dpvsPlanes.cellCount; - gfxWorld->dpvsDyn.dynEntCellBits[0] = new unsigned int[dynEntCellBitsSize]; - gfxWorld->dpvsDyn.dynEntCellBits[1] = NULL; - memset(gfxWorld->dpvsDyn.dynEntCellBits[0], 0, sizeof(unsigned int) * dynEntCellBitsSize); - - int dynEntVisData0Size = gfxWorld->dpvsDyn.dynEntClientWordCount[0] * 32; - gfxWorld->dpvsDyn.dynEntVisData[0][0] = new char[dynEntVisData0Size]; - gfxWorld->dpvsDyn.dynEntVisData[0][1] = new char[dynEntVisData0Size]; - gfxWorld->dpvsDyn.dynEntVisData[0][2] = new char[dynEntVisData0Size]; - gfxWorld->dpvsDyn.dynEntVisData[1][0] = NULL; - gfxWorld->dpvsDyn.dynEntVisData[1][1] = NULL; - gfxWorld->dpvsDyn.dynEntVisData[1][2] = NULL; - memset(gfxWorld->dpvsDyn.dynEntVisData[0][0], 0, dynEntVisData0Size); - memset(gfxWorld->dpvsDyn.dynEntVisData[0][1], 0, dynEntVisData0Size); - memset(gfxWorld->dpvsDyn.dynEntVisData[0][2], 0, dynEntVisData0Size); - - int dynEntShadowVisCount = gfxWorld->dpvsDyn.dynEntClientCount[0] * (gfxWorld->primaryLightCount - gfxWorld->sunPrimaryLightIndex - 1); - gfxWorld->primaryLightDynEntShadowVis[0] = new unsigned int[dynEntShadowVisCount]; - gfxWorld->primaryLightDynEntShadowVis[1] = NULL; - memset(gfxWorld->primaryLightDynEntShadowVis[0], 0, sizeof(unsigned int) * dynEntShadowVisCount); - - gfxWorld->sceneDynModel = new GfxSceneDynModel[gfxWorld->dpvsDyn.dynEntClientCount[0]]; - gfxWorld->sceneDynBrush = NULL; - memset(gfxWorld->sceneDynModel, 0, sizeof(GfxSceneDynModel) * gfxWorld->dpvsDyn.dynEntClientCount[0]); - } - - void updateOutdoors(GfxWorld* gfxWorld) - { - float xRecip = 1.0f / (gfxWorld->maxs.x - gfxWorld->mins.x); - float xScale = -(xRecip * gfxWorld->mins.x); - - float yRecip = 1.0f / (gfxWorld->maxs.y - gfxWorld->mins.y); - float yScale = -(yRecip * gfxWorld->mins.y); - - float zRecip = 1.0f / (gfxWorld->maxs.z - gfxWorld->mins.z); - float zScale = -(zRecip * gfxWorld->mins.z); - - memset(gfxWorld->outdoorLookupMatrix, 0, sizeof(gfxWorld->outdoorLookupMatrix)); - - gfxWorld->outdoorLookupMatrix[0].x = xRecip; - gfxWorld->outdoorLookupMatrix[1].y = yRecip; - gfxWorld->outdoorLookupMatrix[2].z = zRecip; - gfxWorld->outdoorLookupMatrix[3].x = xScale; - gfxWorld->outdoorLookupMatrix[3].y = yScale; - gfxWorld->outdoorLookupMatrix[3].z = zScale; - gfxWorld->outdoorLookupMatrix[3].w = 1.0f; - - std::string outdoorImageName = std::string("$outdoor"); - auto outdoorImageAsset = m_context.LoadDependency(outdoorImageName); - if (outdoorImageAsset == NULL) - { - printf("ERROR! unable to find image $outdoor!\n"); - hasLinkFailed = true; - return; - } - gfxWorld->outdoorImage = outdoorImageAsset->Asset(); - } - - void createGfxWorld(BSPData* projInfo) - { - GfxWorld* gfxWorld = new GfxWorld; - gfxWorld->baseName = _strdup(projInfo->name.c_str()); - gfxWorld->name = _strdup(projInfo->bspName.c_str()); - - // Default values taken from mp_dig - gfxWorld->lightingFlags = 0; - gfxWorld->lightingQuality = 4096; - - cleanGfxWorld(gfxWorld); - - overwriteMapSurfaces(projInfo, gfxWorld); - - overwriteMapSModels(projInfo, gfxWorld); - - overwriteLightmapData(gfxWorld); - - overwriteSkyBox(projInfo, gfxWorld); - - updateReflectionProbeData(gfxWorld); - - // world bounds are based on surface mins/maxs - // Other update functions depend on the bounds being set first - updateWorldBounds(gfxWorld); - - updateOutdoors(gfxWorld); - - // gfx cells depend on surface/smodel count - updateGfxCells(gfxWorld); - - overwriteLightGrid(gfxWorld); - - overwriteGfxLights(gfxWorld); - - overwriteModels(gfxWorld); - - updateSunData(gfxWorld); - - updateDynEntData(gfxWorld); - - m_context.AddAsset(gfxWorld->name, gfxWorld); - } - - void addXModelsToCollision(BSPData* projInfo, clipMap_t* clipMap) - { - auto gfxWorldAsset = m_context.LoadDependency(projInfo->bspName); - _ASSERT(gfxWorldAsset != NULL); - GfxWorld* gfxWorld = gfxWorldAsset->Asset(); - - clipMap->numStaticModels = gfxWorld->dpvs.smodelCount; - clipMap->staticModelList = new cStaticModel_s[clipMap->numStaticModels]; - - for (unsigned int i = 0; i < clipMap->numStaticModels; i++) - { - GfxStaticModelDrawInst* gfxModelDrawInst = &gfxWorld->dpvs.smodelDrawInsts[i]; - GfxStaticModelInst* gfxModelInst = &gfxWorld->dpvs.smodelInsts[i]; - cStaticModel_s* currModel = &clipMap->staticModelList[i]; - - memset(&currModel->writable, 0, sizeof(cStaticModelWritable)); - currModel->xmodel = gfxModelDrawInst->model; - currModel->contents = gfxModelDrawInst->model->contents; - currModel->origin.x = gfxModelDrawInst->placement.origin.x; - currModel->origin.y = gfxModelDrawInst->placement.origin.y; - currModel->origin.z = gfxModelDrawInst->placement.origin.z; - - // TODO: this does not account for model rotation or scale - currModel->absmin.x = gfxModelInst->mins.x; - currModel->absmin.y = gfxModelInst->mins.y; - currModel->absmin.z = gfxModelInst->mins.z; - currModel->absmax.x = gfxModelInst->maxs.x; - currModel->absmax.y = gfxModelInst->maxs.y; - currModel->absmax.z = gfxModelInst->maxs.z; - - BSPUtil::matrixTranspose3x3(gfxModelDrawInst->placement.axis, currModel->invScaledAxis); - currModel->invScaledAxis[0].x = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[0].x; - currModel->invScaledAxis[0].y = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[0].y; - currModel->invScaledAxis[0].z = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[0].z; - currModel->invScaledAxis[1].x = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[1].x; - currModel->invScaledAxis[1].y = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[1].y; - currModel->invScaledAxis[1].z = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[1].z; - currModel->invScaledAxis[2].x = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[2].x; - currModel->invScaledAxis[2].y = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[2].y; - currModel->invScaledAxis[2].z = (1.0f / gfxModelDrawInst->placement.scale) * currModel->invScaledAxis[2].z; - } - } - - void aabbCalcOriginAndHalfSize(vec3_t* mins, vec3_t* maxs, vec3_t* out_origin, vec3_t* out_halfSize) - { - // Origin is the midpoint: (min + max) / 2 - vec3_t temp; - temp.x = mins->x + maxs->x; - temp.y = mins->y + maxs->y; - temp.z = mins->z + maxs->z; - out_origin->x = temp.x * 0.5f; - out_origin->y = temp.y * 0.5f; - out_origin->z = temp.z * 0.5f; - - // Half-size is half the difference: (max - min) / 2 - temp.x = maxs->x - mins->x; - temp.y = maxs->y - mins->y; - temp.z = maxs->z - mins->z; - out_halfSize->x = temp.x * 0.5f; - out_halfSize->y = temp.y * 0.5f; - out_halfSize->z = temp.z * 0.5f; - } - - bool CM_IsEdgeWalkable(clipMap_t* clipMap, int triIndex, int edgeIndex) - { - - unsigned __int8 edgeBitMask = 1 << ((triIndex + edgeIndex + 2 * triIndex) & 7); - - return (edgeBitMask & clipMap->triEdgeIsWalkable[(triIndex + edgeIndex + 2 * triIndex) >> 3]) != 0; - } - - void traverseBSPTreeForCounts(BSPTree* node, size_t* numPlanes, size_t* numNodes, size_t* numLeafs, size_t* numAABBTrees, size_t* maxObjsPerLeaf) - { - if (node->isLeaf) - { - (*numLeafs)++; - // there won't be an AABB tree when objectList is empty - if (node->leaf->getObjectCount() > 0) - { - *numAABBTrees += node->leaf->getObjectCount() + 1; - - if (node->leaf->getObjectCount() > *maxObjsPerLeaf) - *maxObjsPerLeaf = node->leaf->getObjectCount(); - } - } - else - { - (*numPlanes)++; - (*numNodes)++; - traverseBSPTreeForCounts(node->node->front.get(), numPlanes, numNodes, numLeafs, numAABBTrees, maxObjsPerLeaf); - traverseBSPTreeForCounts(node->node->back.get(), numPlanes, numNodes, numLeafs, numAABBTrees, maxObjsPerLeaf); - } - } - - vec3_t normalX = { 1.0f, 0.0f, 0.0f }; - vec3_t normalY = { 0.0f, 1.0f, 0.0f }; - vec3_t normalZ = { 0.0f, 0.0f, 1.0f }; - - int currPlaneCount = 0; - int currNodeCount = 0; - int currLeafCount = 0; - int currAABBCount = 0; - - int addAABBTreeFromLeaf(BSPTree* node, clipMap_t* clipMap) - { - _ASSERT(node->isLeaf); - - int objectCount = node->leaf->getObjectCount(); - int firstAABBIndex = currAABBCount; - currAABBCount += objectCount + 1; - - // calculate root AABB node mins and maxs - // cannot convert mins and maxs coord to BO2 directly as this will result in incorrect mins and maxs - // so we have to recompute every min and max, not hard just tedious - int firstPartitionIndex = node->leaf->getObject(0)->partitionIndex; - auto firstPartition = &clipMap->partitions[firstPartitionIndex]; - uint16_t* firstTri = clipMap->triIndices[firstPartition->firstTri]; - vec3_t* firstVert = &clipMap->verts[firstTri[0]]; - vec3_t aabbMins; - vec3_t aabbMaxs; - aabbMins.x = firstVert->x; - aabbMins.y = firstVert->y; - aabbMins.z = firstVert->z; - aabbMaxs.x = firstVert->x; - aabbMaxs.y = firstVert->y; - aabbMaxs.z = firstVert->z; - for (int i = 0; i < objectCount; i++) - { - int currPartitionIndex = node->leaf->getObject(i)->partitionIndex; - auto currPartition = &clipMap->partitions[currPartitionIndex]; - - for (int k = 0; k < currPartition->triCount; k++) - { - uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; - for (int l = 0; l < 3; l++) - { - uint16_t vertIndex = tri[l]; - vec3_t vertCoord = clipMap->verts[vertIndex]; - BSPUtil::calcNewBoundsWithPoint(&vertCoord, &aabbMins, &aabbMaxs); - } - } - } - CollisionAabbTree* rootAABB = &clipMap->aabbTrees[firstAABBIndex]; - aabbCalcOriginAndHalfSize(&aabbMins, &aabbMaxs, &rootAABB->origin, &rootAABB->halfSize); - rootAABB->materialIndex = 0; - rootAABB->childCount = objectCount; - rootAABB->u.firstChildIndex = firstAABBIndex + 1; - - // populate child AABB nodes - for (int i = 0; i < objectCount; i++) - { - CollisionAabbTree* currAabbTree = &clipMap->aabbTrees[rootAABB->u.firstChildIndex + i]; - int currPartitionIndex = node->leaf->getObject(i)->partitionIndex; - - currAabbTree->materialIndex = 0; - currAabbTree->childCount = 0; - currAabbTree->u.partitionIndex = currPartitionIndex; - - // calculate partition origin and half size - CollisionPartition* aabbPartition = &clipMap->partitions[currPartitionIndex]; - uint16_t firstUind = clipMap->info.uinds[aabbPartition->fuind]; - vec3_t* firstVertex = &clipMap->verts[firstUind]; - vec3_t mins; - vec3_t maxs; - mins.x = firstVertex->x; - mins.y = firstVertex->y; - mins.z = firstVertex->z; - maxs.x = firstVertex->x; - maxs.y = firstVertex->y; - maxs.z = firstVertex->z; - for (int i = 1; i < aabbPartition->nuinds; i++) - { - uint16_t currUind = clipMap->info.uinds[aabbPartition->fuind + i]; - vec3_t* currVertex = &clipMap->verts[currUind]; - - BSPUtil::calcNewBoundsWithPoint(currVertex, &mins, &maxs); - } - - aabbCalcOriginAndHalfSize(&mins, &maxs, &currAabbTree->origin, &currAabbTree->halfSize); - } - - return firstAABBIndex; - } - - // returns the index corresponding to the BSPTree* node parsed - int16_t populateBSPTree_r(clipMap_t* clipMap, BSPTree* node) - { - new cplane_s; - new cNode_t; - new cLeaf_s; - new CollisionAabbTree; - - if (node->isLeaf) - { - int currLeafIndex = currLeafCount; - currLeafCount++; - cLeaf_s* currLeaf = &clipMap->leafs[currLeafIndex]; - - currLeaf->cluster = 0; - currLeaf->brushContents = 0; // no brushes used so contents is 0 - currLeaf->terrainContents = BSPEditableConstants::LEAF_TERRAIN_CONTENTS; // clipMap->cmodels[0].leaf.terrainContents takes prescedence - - // unused when leafBrushNode == 0 - currLeaf->mins.x = 0.0f; - currLeaf->mins.y = 0.0f; - currLeaf->mins.z = 0.0f; - currLeaf->maxs.x = 0.0f; - currLeaf->maxs.y = 0.0f; - currLeaf->maxs.z = 0.0f; - currLeaf->leafBrushNode = 0; - - if (node->leaf->getObjectCount() > 0) - { - currLeaf->firstCollAabbIndex = addAABBTreeFromLeaf(node, clipMap); - currLeaf->collAabbCount = 1; - } - else - { - currLeaf->firstCollAabbIndex = 0; - currLeaf->collAabbCount = 0; - } - - return -1 - currLeafIndex; - } - else - { - cplane_s* currPlane = &clipMap->info.planes[currPlaneCount]; - currPlaneCount++; - - if (node->node->axis == AXIS_X) - { - // X is unchanged when going from OGL x -> BO2 x - currPlane->normal = normalX; - - // converting OGL -> BO2 X coords doesn't change the x coords at all, so - // the dist stays the same - currPlane->dist = (float)node->node->distance; - } - else - { - // converting OGL -> BO2 Z coords negates the z coords and sets it to the y coord. - // convert the z normal to the y normal, but don't negate it. Negative normals don't do - // what is expected when the game uses them - _ASSERT(node->node->axis == AXIS_Z); - currPlane->normal = normalY; - - // converting OGL -> BO2 Z coords negates the z coords and sets it to the y coord. - // just negate it here as it is just the distance from the orgin along the axis - currPlane->dist = (float)(-node->node->distance); - } - - bool foundType = false; - if (currPlane->normal.x == 1.0f) - { - _ASSERT(!foundType); - foundType = true; - currPlane->type = 0; - } - else if (currPlane->normal.y == 1.0f) - { - _ASSERT(!foundType); - foundType = true; - currPlane->type = 1; - } - else if (currPlane->normal.z == 1.0f) - { - _ASSERT(!foundType); - foundType = true; - currPlane->type = 2; - } - else - _ASSERT(foundType); - - currPlane->signbits = 0; - if (currPlane->normal.x < 0.0f) - currPlane->signbits |= 1; - if (currPlane->normal.y < 0.0f) - currPlane->signbits |= 2; - if (currPlane->normal.z < 0.0f) - currPlane->signbits |= 4; - - currPlane->pad[0] = 0; - currPlane->pad[1] = 0; - - int currNodeIndex = currNodeCount; - currNodeCount++; - cNode_t* currNode = &clipMap->nodes[currNodeIndex]; - - currNode->plane = currPlane; - // Reason for the front and back flip (due to the hacky nature of making the mins and maxs work (see createClipMap)): - // after converting between OGL and BO2 coords and when and updating the normal from Z -> Y, - // the normal vector flips and objects behind the plane are now in front, and vise versa - // so the back node now represents the front, and the front node represents the back. - // Do the OGL -> Bo2 coord change on paper and it will make sense - if (currPlane->type == 1) - { - currNode->children[1] = populateBSPTree_r(clipMap, node->node->front.get()); - currNode->children[0] = populateBSPTree_r(clipMap, node->node->back.get()); - } - else - { - currNode->children[0] = populateBSPTree_r(clipMap, node->node->front.get()); - currNode->children[1] = populateBSPTree_r(clipMap, node->node->back.get()); - } - - return currNodeIndex; - } - } - - void populateBSPTree(clipMap_t* clipMap, BSPTree* tree) - { - size_t numPlanes = 0; - size_t numNodes = 0; - size_t numLeafs = 0; - size_t numAABBTrees = 0; - size_t maxObjsPerLeaf = 0; - - traverseBSPTreeForCounts(tree, &numPlanes, &numNodes, &numLeafs, &numAABBTrees, &maxObjsPerLeaf); - - printf("Max Objects per leaf: %i\n", maxObjsPerLeaf); - - clipMap->info.planeCount = numPlanes; - clipMap->info.planes = new cplane_s[clipMap->info.planeCount]; - clipMap->numNodes = numNodes; - clipMap->nodes = new cNode_t[clipMap->numNodes]; - // aabb trees: each leaf will have their own AABB tree of the objects within it, and the root aabb node will be the parent of every other aabb node. - // therefore, each aabb tree will be of size (numObjects + 1) as the tree needs a root aabb node to reference it's children. - clipMap->aabbTreeCount = numAABBTrees; - clipMap->aabbTrees = new CollisionAabbTree[clipMap->aabbTreeCount]; - - currPlaneCount = 0; - currNodeCount = 0; - currAABBCount = 0; - - // first leaf is always empty - clipMap->numLeafs = numLeafs + 1; - clipMap->leafs = new cLeaf_s[clipMap->numLeafs]; - memset(&clipMap->leafs[0], 0, sizeof(cLeaf_s)); - currLeafCount = 1; - - populateBSPTree_r(clipMap, tree); - - _ASSERT(clipMap->info.planeCount == currPlaneCount); - _ASSERT(clipMap->numNodes == currNodeCount); - _ASSERT(clipMap->numLeafs == currLeafCount); - _ASSERT(clipMap->aabbTreeCount == currAABBCount); - } - - void createPartitions(BSPData* projInfo, clipMap_t* clipMap) - { - int collisionVertexCount = projInfo->colWorld.vertices.size(); - std::vector collisionVertVec; - for (int i = 0; i < collisionVertexCount; i++) - { - collisionVertVec.push_back(BSPUtil::convertToBO2Coords(projInfo->colWorld.vertices[i].pos)); - //collisionVertVec.push_back(projInfo->colWorld.vertices[i].pos); - } - clipMap->vertCount = collisionVertexCount; - clipMap->verts = new vec3_t[collisionVertexCount]; - memcpy(clipMap->verts, &collisionVertVec[0], sizeof(vec3_t) * collisionVertexCount); - - // due to tris using uint16_t as the type for indexing the vert array, - // any vertex count over the uint16_t max means the vertices above it can't be indexed - if (collisionVertexCount > BSPGameConstants::MAX_COLLISION_VERTS) - { - printf("ERROR: collision vertex count %i exceeds the maximum number: %i!\n", collisionVertexCount, BSPGameConstants::MAX_COLLISION_VERTS); - hasLinkFailed = true; - return; - } - - std::vector triIndexVec; - for (size_t i = 0; i < projInfo->colWorld.surfaces.size(); i++) - { - BSPSurface* currSurface = &projInfo->colWorld.surfaces[i]; - int triCount = currSurface->triCount; - - for (int k = 0; k < triCount * 3; k += 3) - { - int firstIndex_Index = currSurface->indexOfFirstIndex; - int firstVertexIndex = currSurface->indexOfFirstVertex; - - // gfx index bufer starts at 0 for each new mesh, while the clipmap index buffer indexes the entire - // clipmap verts buffer, so this code updates the indexes to follow that. - int triIndex0 = projInfo->colWorld.indices[firstIndex_Index + (k + 0)] + firstVertexIndex; - int triIndex1 = projInfo->colWorld.indices[firstIndex_Index + (k + 1)] + firstVertexIndex; - int triIndex2 = projInfo->colWorld.indices[firstIndex_Index + (k + 2)] + firstVertexIndex; - - // triangle index ordering is opposite to blenders, so its converted here - triIndexVec.push_back(triIndex2); - triIndexVec.push_back(triIndex1); - triIndexVec.push_back(triIndex0); - } - } - _ASSERT(triIndexVec.size() % 3 == 0); - clipMap->triCount = triIndexVec.size() / 3; - clipMap->triIndices = (uint16_t(*)[3])(new uint16_t[triIndexVec.size()]); - memcpy(clipMap->triIndices, &triIndexVec[0], sizeof(uint16_t) * triIndexVec.size()); - - // partitions are made for each triangle, not one for each surface. - // one for each surface causes physics bugs, as the entire bounding box is considered solid instead of the surface itself (for some reason). - // so a partition is made for each triangle which removes the physics bugs but likely makes the game run slower - std::vector partitionVec; - for (size_t i = 0; i < projInfo->colWorld.surfaces.size(); i++) - { - int triCount = projInfo->colWorld.surfaces[i].triCount; - int firstTriIndex = projInfo->colWorld.surfaces[i].indexOfFirstIndex / 3; - for (int k = 0; k < triCount; k++) - { - CollisionPartition newPartition; - newPartition.nuinds = 0; // initialised later - newPartition.fuind = 0; // initialised later - newPartition.triCount = 1; - newPartition.firstTri = firstTriIndex; - firstTriIndex += 1; - - partitionVec.push_back(newPartition); - } - } - clipMap->partitionCount = partitionVec.size(); - clipMap->partitions = new CollisionPartition[clipMap->partitionCount]; - memcpy(clipMap->partitions, &partitionVec[0], sizeof(CollisionPartition) * clipMap->partitionCount); - - int totalUindCount = 0; - std::vector uindVec; - for (int i = 0; i < clipMap->partitionCount; i++) - { - CollisionPartition* currPartition = &clipMap->partitions[i]; - std::vector uniqueVertVec; - for (int k = 0; k < currPartition->triCount; k++) - { - uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; - for (int l = 0; l < 3; l++) - { - bool isVertexIndexUnique = true; - uint16_t vertIndex = tri[l]; - - for (size_t m = 0; m < uniqueVertVec.size(); m++) - { - if (uniqueVertVec[m] == vertIndex) - { - isVertexIndexUnique = false; - break; - } - } - - if (isVertexIndexUnique) - uniqueVertVec.push_back(vertIndex); - } - } - - currPartition->fuind = totalUindCount; - currPartition->nuinds = (int)uniqueVertVec.size(); - uindVec.insert(uindVec.end(), uniqueVertVec.begin(), uniqueVertVec.end()); - totalUindCount += currPartition->nuinds; - } - clipMap->info.nuinds = totalUindCount; - clipMap->info.uinds = new uint16_t[totalUindCount]; - memcpy(clipMap->info.uinds, &uindVec[0], sizeof(uint16_t) * totalUindCount); - } - - void createClipMap(BSPData* projInfo) - { - clipMap_t* clipMap = new clipMap_t; - - clipMap->name = _strdup(projInfo->bspName.c_str()); - - std::string mapEntsName = projInfo->bspName.c_str(); - auto mapEntsAsset = m_context.LoadDependency(mapEntsName); - _ASSERT(mapEntsAsset != NULL); - clipMap->mapEnts = mapEntsAsset->Asset(); - - clipMap->pInfo = NULL; - - clipMap->box_model.mins.x = 0.0f; - clipMap->box_model.mins.y = 0.0f; - clipMap->box_model.mins.z = 0.0f; - clipMap->box_model.maxs.x = 0.0f; - clipMap->box_model.maxs.y = 0.0f; - clipMap->box_model.maxs.z = 0.0f; - clipMap->box_model.radius = 0.0f; - clipMap->box_model.info = NULL; - - // for some reason the maxs are negative, and mins are positive - // float box_mins = 3.4028235e38; - // float box_maxs = -3.4028235e38; - // hack: the floats above can't be converted to 32 bit floats, and the game requires them to be exact - // so we use the hex representation and set it using int pointers - unsigned int box_mins = 0x7F7FFFFF; - unsigned int box_maxs = 0xFF7FFFFF; - *((unsigned int*)&clipMap->box_model.leaf.mins.x) = box_mins; - *((unsigned int*)&clipMap->box_model.leaf.mins.y) = box_mins; - *((unsigned int*)&clipMap->box_model.leaf.mins.z) = box_mins; - *((unsigned int*)&clipMap->box_model.leaf.maxs.x) = box_maxs; - *((unsigned int*)&clipMap->box_model.leaf.maxs.y) = box_maxs; - *((unsigned int*)&clipMap->box_model.leaf.maxs.z) = box_maxs; - - clipMap->box_model.leaf.brushContents = -1; - clipMap->box_model.leaf.terrainContents = 0; - clipMap->box_model.leaf.cluster = 0; - clipMap->box_model.leaf.collAabbCount = 0; - clipMap->box_model.leaf.firstCollAabbIndex = 0; - clipMap->box_model.leaf.leafBrushNode = 0; - - clipMap->box_brush = new cbrush_t; - clipMap->box_brush->axial_sflags[0][0] = -1; - clipMap->box_brush->axial_sflags[0][1] = -1; - clipMap->box_brush->axial_sflags[0][2] = -1; - clipMap->box_brush->axial_sflags[1][0] = -1; - clipMap->box_brush->axial_sflags[1][1] = -1; - clipMap->box_brush->axial_sflags[1][2] = -1; - clipMap->box_brush->axial_cflags[0][0] = -1; - clipMap->box_brush->axial_cflags[0][1] = -1; - clipMap->box_brush->axial_cflags[0][2] = -1; - clipMap->box_brush->axial_cflags[1][0] = -1; - clipMap->box_brush->axial_cflags[1][1] = -1; - clipMap->box_brush->axial_cflags[1][2] = -1; - clipMap->box_brush->contents = -1; - clipMap->box_brush->mins.x = 0.0f; - clipMap->box_brush->mins.y = 0.0f; - clipMap->box_brush->mins.z = 0.0f; - clipMap->box_brush->maxs.x = 0.0f; - clipMap->box_brush->maxs.y = 0.0f; - clipMap->box_brush->maxs.z = 0.0f; - clipMap->box_brush->numsides = 0; - clipMap->box_brush->numverts = 0; - clipMap->box_brush->sides = NULL; - clipMap->box_brush->verts = NULL; - - clipMap->numClusters = 1; - clipMap->vised = 0; - clipMap->clusterBytes = ((clipMap->numClusters + 63) >> 3) & 0xFFFFFFF8; - clipMap->visibility = new char[clipMap->clusterBytes]; - memset(clipMap->visibility, 0xFF, clipMap->clusterBytes); - - clipMap->isInUse = true; - clipMap->checksum = 0; - - clipMap->num_constraints = 0; - clipMap->constraints = NULL; - clipMap->max_ropes = 32; - clipMap->ropes = new rope_t[clipMap->max_ropes]; - memset(clipMap->ropes, 0, sizeof(rope_t) * clipMap->max_ropes); - - clipMap->info.numBrushSides = 0; - clipMap->info.brushsides = NULL; - clipMap->info.leafbrushNodesCount = 0; - clipMap->info.leafbrushNodes = NULL; - clipMap->info.numLeafBrushes = 0; - clipMap->info.leafbrushes = NULL; - clipMap->info.numBrushVerts = 0; - clipMap->info.brushVerts = NULL; - clipMap->info.numBrushes = NULL; - clipMap->info.brushes = NULL; - clipMap->info.brushBounds = NULL; - clipMap->info.brushContents = NULL; - - int dynEntCount = 0; - clipMap->originalDynEntCount = dynEntCount; - clipMap->dynEntCount[0] = clipMap->originalDynEntCount + 256; // the game allocs 256 empty dynents, as they may be used ingame - clipMap->dynEntCount[1] = 0; - clipMap->dynEntCount[2] = 0; - clipMap->dynEntCount[3] = 0; - - // assume that there are 0 dyn ents from here on - clipMap->dynEntClientList[0] = new DynEntityClient[clipMap->dynEntCount[0]]; - clipMap->dynEntClientList[1] = NULL; - memset(clipMap->dynEntClientList[0], 0, sizeof(DynEntityClient) * clipMap->dynEntCount[0]); - - clipMap->dynEntServerList[0] = NULL; - clipMap->dynEntServerList[1] = NULL; - - clipMap->dynEntCollList[0] = new DynEntityColl[clipMap->dynEntCount[0]]; - clipMap->dynEntCollList[1] = NULL; - clipMap->dynEntCollList[2] = NULL; - clipMap->dynEntCollList[3] = NULL; - memset(clipMap->dynEntCollList[0], 0, sizeof(DynEntityColl) * clipMap->dynEntCount[0]); - - clipMap->dynEntPoseList[0] = new DynEntityPose[clipMap->dynEntCount[0]]; - clipMap->dynEntPoseList[1] = NULL; - memset(clipMap->dynEntPoseList[0], 0, sizeof(DynEntityPose) * clipMap->dynEntCount[0]); - - clipMap->dynEntDefList[0] = new DynEntityDef[clipMap->dynEntCount[0]]; - clipMap->dynEntDefList[1] = NULL; - memset(clipMap->dynEntDefList[0], 0, sizeof(DynEntityDef) * clipMap->dynEntCount[0]); - - // cmodels is the collision for mapents - auto gfxWorldAsset = m_context.LoadDependency(projInfo->bspName); - _ASSERT(gfxWorldAsset != NULL); - GfxWorld* gfxWorld = gfxWorldAsset->Asset(); - clipMap->numSubModels = gfxWorld->modelCount; - clipMap->cmodels = new cmodel_t[clipMap->numSubModels]; - for (unsigned int i = 0; i < clipMap->numSubModels; i++) - { - // bomb triggers use leafs, not world terrain so that might be an issue - - GfxBrushModel* gfxModel = &gfxWorld->models[i]; - cmodel_t* cmModel = &clipMap->cmodels[i]; - - cmModel->leaf.firstCollAabbIndex = 0; - cmModel->leaf.collAabbCount = 0; - cmModel->leaf.brushContents = 0; - cmModel->leaf.terrainContents = BSPEditableConstants::WORLD_TERRAIN_CONTENTS; - cmModel->leaf.mins.x = 0.0f; - cmModel->leaf.mins.y = 0.0f; - cmModel->leaf.mins.z = 0.0f; - cmModel->leaf.maxs.x = 0.0f; - cmModel->leaf.maxs.y = 0.0f; - cmModel->leaf.maxs.z = 0.0f; - cmModel->leaf.leafBrushNode = 0; - cmModel->leaf.cluster = 0; - cmModel->info = NULL; - cmModel->mins.x = gfxModel->bounds[0].x; - cmModel->mins.y = gfxModel->bounds[0].y; - cmModel->mins.z = gfxModel->bounds[0].z; - cmModel->maxs.x = gfxModel->bounds[1].x; - cmModel->maxs.y = gfxModel->bounds[1].y; - cmModel->maxs.z = gfxModel->bounds[1].z; - cmModel->radius = BSPUtil::distBetweenPoints(cmModel->mins, cmModel->maxs) / 2; - } - - addXModelsToCollision(projInfo, clipMap); - - clipMap->info.numMaterials = 1; - clipMap->info.materials = new ClipMaterial[clipMap->info.numMaterials]; - clipMap->info.materials[0].name = _strdup(BSPLinkingConstants::MISSING_IMAGE_NAME); - clipMap->info.materials[0].contentFlags = BSPEditableConstants::MATERIAL_CONTENT_FLAGS; - clipMap->info.materials[0].surfaceFlags = BSPEditableConstants::MATERIAL_SURFACE_FLAGS; - - // set all edges to walkable (all walkable edge bits are set to 1, see isEdgeWalkable) until changing it is a possiblility - // might do weird stuff on walls, but from testing doesnt seem to do much - int walkableEdgeSize = (3 * clipMap->triCount + 31) / 32 * 4; - clipMap->triEdgeIsWalkable = new char[walkableEdgeSize]; - memset(clipMap->triEdgeIsWalkable, 1, walkableEdgeSize * sizeof(char)); - - // clipmap BSP creation must go last as it depends on unids, tris and verts already being populated - // HACK: - // the BSP tree creation does not work when BO2's coordinate system is used for mins and maxs. - // Workaround is to convert every BO2 coordinate to OGL's before it is added into the BSP tree, - // and then convert them back when it is being parsed into the clipmap. Requires some hacky - // logic, check populateBSPTree_r and addAABBTreeFromLeaf - - createPartitions(projInfo, clipMap); - - vec3_t* firstVert = &clipMap->verts[0]; - vec3_t clipMins; - vec3_t clipMaxs; - clipMins.x = firstVert->x; - clipMins.y = firstVert->y; - clipMins.z = firstVert->z; - clipMaxs.x = firstVert->x; - clipMaxs.y = firstVert->y; - clipMaxs.z = firstVert->z; - clipMins = BSPUtil::convertFromBO2Coords(clipMins); - clipMaxs = BSPUtil::convertFromBO2Coords(clipMaxs); - for (unsigned int i = 1; i < clipMap->vertCount; i++) - { - vec3_t vertCoord = BSPUtil::convertFromBO2Coords(clipMap->verts[i]); - BSPUtil::calcNewBoundsWithPoint(&vertCoord, &clipMins, &clipMaxs); - } - - BSPTree* tree = new BSPTree(clipMins.x, clipMins.y, clipMins.z, clipMaxs.x, clipMaxs.y, clipMaxs.z, 0); - - _ASSERT(!tree->isLeaf); - - for (int i = 0; i < clipMap->partitionCount; i++) - { - auto currPartition = &clipMap->partitions[i]; - - uint16_t* firstTri = clipMap->triIndices[currPartition->firstTri]; - vec3_t* firstVert = &clipMap->verts[firstTri[0]]; - vec3_t mins; - vec3_t maxs; - mins.x = firstVert->x; - mins.y = firstVert->y; - mins.z = firstVert->z; - maxs.x = firstVert->x; - maxs.y = firstVert->y; - maxs.z = firstVert->z; - mins = BSPUtil::convertFromBO2Coords(mins); - maxs = BSPUtil::convertFromBO2Coords(maxs); - for (int k = 0; k < currPartition->triCount; k++) - { - uint16_t* tri = clipMap->triIndices[currPartition->firstTri + k]; - for (int l = 0; l < 3; l++) - { - uint16_t vertIndex = tri[l]; - vec3_t vertCoord = BSPUtil::convertFromBO2Coords(clipMap->verts[vertIndex]); - BSPUtil::calcNewBoundsWithPoint(&vertCoord, &mins, &maxs); - } - } - - std::shared_ptr currObject = std::make_shared(mins.x, mins.y, mins.z, maxs.x, maxs.y, maxs.z, i); - - tree->addObjectToTree(std::move(currObject)); - } - - populateBSPTree(clipMap, tree); - - m_context.AddAsset(clipMap->name, clipMap); - } - - void createComWorld(BSPData* projInfo) - { - // all lights that aren't the sunlight or default light need their own GfxLightDef asset - ComWorld* comWorld = new ComWorld; - comWorld->name = _strdup(projInfo->bspName.c_str()); - comWorld->isInUse = 1; - comWorld->primaryLightCount = 2; - comWorld->primaryLights = new ComPrimaryLight[comWorld->primaryLightCount]; - - // default light is always empty - ComPrimaryLight* defaultLight = &comWorld->primaryLights[0]; - memset(defaultLight, 0, sizeof(ComPrimaryLight)); - - ComPrimaryLight* sunLight = &comWorld->primaryLights[1]; - memset(sunLight, 0, sizeof(ComPrimaryLight)); - sunLight->type = 1; - sunLight->diffuseColor.r = 0.75f; - sunLight->diffuseColor.g = 0.75f; - sunLight->diffuseColor.b = 0.75f; - sunLight->diffuseColor.a = 1.0f; - sunLight->dir.x = 0.0f; - sunLight->dir.y = 0.0f; - sunLight->dir.z = 0.0f; - - m_context.AddAsset(comWorld->name, comWorld); - } - - void parseMapEntsJSON(json& entArrayJs, std::string& entityString) - { - int entityCount = entArrayJs.size(); - for (int i = 0; i < entityCount; i++) - { - auto currEntity = entArrayJs[i]; - - if (i == 0) - { - std::string className = currEntity["classname"]; - if (className.compare("worldspawn") != 0) - { - printf("ERROR: first entity in the map entity string must be the worldspawn class!"); - hasLinkFailed = true; - return; - } - } - - entityString.append("{\n"); - - for (auto& element : currEntity.items()) - { - std::string key = element.key(); - std::string value = element.value(); - entityString.append(std::format("\"{}\" \"{}\"\n", key, value)); - } - - entityString.append("}\n"); - } - } - - void parseSpawnpointJSON(json& entArrayJs, std::string& entityString, const char* spawnpointNames[], int nameCount) - { - int entityCount = entArrayJs.size(); - for (int i = 0; i < entityCount; i++) - { - auto currEntity = entArrayJs[i]; - - std::string origin = currEntity["origin"]; - std::string angles = currEntity["angles"]; - - for (int k = 0; k < nameCount; k++) - { - entityString.append("{\n"); - entityString.append(std::format("\"origin\" \"{}\"\n", origin)); - entityString.append(std::format("\"angles\" \"{}\"\n", angles)); - entityString.append(std::format("\"classname\" \"{}\"\n", spawnpointNames[k])); - entityString.append("}\n"); - } - } - } - - void parseBombJSON(json& bombJs, std::string& entityString) - { - // add the bomb model - { - std::string bombOriginStr = bombJs["sd_bomb"]["origin"]; - entityString.append("{\n"); - entityString.append("\"classname\" \"script_model\"\n"); - entityString.append("\"model\" \"prop_suitcase_bomb\"\n"); - entityString.append("\"targetname\" \"sd_bomb\"\n"); - entityString.append("\"script_gameobjectname\" \"sd\"\n"); - entityString.append("\"spawnflags\" \"4\"\n"); - entityString.append(std::format("\"origin\" \"{}\"\n", bombOriginStr)); - entityString.append("}\n"); - } - if (m_context.LoadDependency("prop_suitcase_bomb") == NULL) - { - hasLinkFailed = true; - printf("ERROR: unable to find s&d bomb xmodel\n"); - return; - } - - // add the bomb pickup trigger - { - std::string bombOriginStr = bombJs["sd_bomb"]["origin"]; - vec3_t bomboriginV3 = BSPUtil::convertStringToVec3(bombOriginStr); - entModelBounds bounds; - bounds.mins.x = bomboriginV3.x - 32.0f; // bounds taken from mp_dig - bounds.mins.y = bomboriginV3.y - 32.0f; - bounds.mins.z = bomboriginV3.z - 8.0f; - bounds.maxs.x = bomboriginV3.x + 32.0f; - bounds.maxs.y = bomboriginV3.y + 32.0f; - bounds.maxs.z = bomboriginV3.z + 28.0f; - int entityModelIndex = entityModelList.size() + 1; // +1 as the first model is always the world model - entityModelList.push_back(bounds); - entityString.append("{\n"); - entityString.append("\"classname\" \"trigger_multiple\"\n"); - entityString.append("\"targetname\" \"sd_bomb_pickup_trig\"\n"); - entityString.append("\"script_gameobjectname\" \"sd\"\n"); - entityString.append(std::format("\"origin\" \"{}\"\n", bombOriginStr)); - entityString.append(std::format("\"model\" \"*{}\"\n", entityModelIndex)); - entityString.append("}\n"); - } - - // add A site bomb - { - std::string siteAPoint1Str = bombJs["sd_bombzone_a"]["point1"]; - std::string siteAPoint2Str = bombJs["sd_bombzone_a"]["point2"]; - vec3_t siteAPoint1V3 = BSPUtil::convertStringToVec3(siteAPoint1Str); - vec3_t siteAPoint2V3 = BSPUtil::convertStringToVec3(siteAPoint2Str); - entModelBounds bounds; - bounds.mins.x = siteAPoint1V3.x; - bounds.mins.y = siteAPoint1V3.y; - bounds.mins.z = siteAPoint1V3.z; - bounds.maxs.x = siteAPoint1V3.x; - bounds.maxs.y = siteAPoint1V3.y; - bounds.maxs.z = siteAPoint1V3.z; - BSPUtil::calcNewBoundsWithPoint(&siteAPoint2V3, &bounds.mins, &bounds.mins); - int entityModelIndex = entityModelList.size() + 1; // +1 as the first model is always the world model - entityModelList.push_back(bounds); - - vec3_t siteAOrigin = BSPUtil::calcMiddleOfBounds(&bounds.mins, &bounds.mins); - std::string siteAOriginStr = BSPUtil::convertVec3ToString(siteAOrigin); - - entityString.append("{\n"); - entityString.append("\"classname\" \"trigger_use_touch\"\n"); - entityString.append("\"targetname\" \"bombzone\"\n"); - entityString.append("\"script_gameobjectname\" \"bombzone\"\n"); - entityString.append("\"script_bombmode_original\" \"1\"\n"); - entityString.append("\"script_label\" \"_a\"\n"); - entityString.append(std::format("\"origin\" \"{}\"\n", siteAOriginStr)); - entityString.append(std::format("\"model\" \"*{}\"\n", entityModelIndex)); - entityString.append("}\n"); - } - - // add B site bomb - { - std::string siteBPoint1Str = bombJs["sd_bombzone_b"]["point1"]; - std::string siteBPoint2Str = bombJs["sd_bombzone_b"]["point2"]; - vec3_t siteBPoint1V3 = BSPUtil::convertStringToVec3(siteBPoint1Str); - vec3_t siteBPoint2V3 = BSPUtil::convertStringToVec3(siteBPoint2Str); - entModelBounds bounds; - bounds.mins.x = siteBPoint1V3.x; - bounds.mins.y = siteBPoint1V3.y; - bounds.mins.z = siteBPoint1V3.z; - bounds.maxs.x = siteBPoint1V3.x; - bounds.maxs.y = siteBPoint1V3.y; - bounds.maxs.z = siteBPoint1V3.z; - BSPUtil::calcNewBoundsWithPoint(&siteBPoint2V3, &bounds.mins, &bounds.mins); - int entityModelIndex = entityModelList.size() + 1; // +1 as the first model is always the world model - entityModelList.push_back(bounds); - - vec3_t siteAOrigin = BSPUtil::calcMiddleOfBounds(&bounds.mins, &bounds.mins); - std::string siteAOriginStr = BSPUtil::convertVec3ToString(siteAOrigin); - - entityString.append("{\n"); - entityString.append("\"classname\" \"trigger_use_touch\"\n"); - entityString.append("\"targetname\" \"bombzone\"\n"); - entityString.append("\"script_gameobjectname\" \"bombzone\"\n"); - entityString.append("\"script_bombmode_original\" \"1\"\n"); - entityString.append("\"script_label\" \"_b\"\n"); - entityString.append(std::format("\"origin\" \"{}\"\n", siteAOriginStr)); - entityString.append(std::format("\"model\" \"*{}\"\n", entityModelIndex)); - entityString.append("}\n"); - } - } - - void createMapEnts(BSPData* projInfo) - { - MapEnts* mapEnts = new MapEnts; - - mapEnts->name = _strdup(projInfo->bspName.c_str()); - - // don't need these - mapEnts->trigger.count = 0; - mapEnts->trigger.models = NULL; - mapEnts->trigger.hullCount = 0; - mapEnts->trigger.hulls = NULL; - mapEnts->trigger.slabCount = 0; - mapEnts->trigger.slabs = NULL; - - std::string entityString; - - const auto entFile = m_search_path.Open("BSP/entities.json"); - json entJs; - if (!entFile.IsOpen()) - { - printf("WARN: can't find entity json BSP/entities.json, using default entities\n"); - entJs = json::parse(BSPLinkingConstants::DEFAULT_MAP_ENTS_STRING); - } - else - { - entJs = json::parse(*entFile.m_stream); - } - parseMapEntsJSON(entJs["entities"], entityString); - - const auto spawnFile = m_search_path.Open("BSP/spawns.json"); - json spawnJs; - if (!spawnFile.IsOpen()) - { - printf("WARN: cant find BSP/spawns.json, setting spawns to 0 0 0\n"); - spawnJs = json::parse(BSPLinkingConstants::DEFAULT_SPAWN_POINT_STRING); - } - else - { - spawnJs = json::parse(*spawnFile.m_stream); - } - - int defenderNameCount = std::extent::value; - int attackerNameCount = std::extent::value; - int ffaNameCount = std::extent::value; - parseSpawnpointJSON(spawnJs["attackers"], entityString, BSPGameConstants::DEFENDER_SPAWN_POINT_NAMES, defenderNameCount); - parseSpawnpointJSON(spawnJs["defenders"], entityString, BSPGameConstants::ATTACKER_SPAWN_POINT_NAMES, attackerNameCount); - parseSpawnpointJSON(spawnJs["FFA"], entityString, BSPGameConstants::FFA_SPAWN_POINT_NAMES, ffaNameCount); - - //const auto objectiveFile = m_search_path.Open("objectives.json"); - //if (!spawnFile.IsOpen()) - //{ - // printf("WARN: no objectives given\n"); - //} - //else - //{ - // json objectiveJs = json::parse(*objectiveFile.m_stream); - // parseBombJSON(objectiveJs, entityString); - //} - - mapEnts->entityString = _strdup(entityString.c_str()); - mapEnts->numEntityChars = entityString.length() + 1; // numEntityChars includes the null character - - m_context.AddAsset(mapEnts->name, mapEnts); - } - - void createGameWorldMp(BSPData* projInfo) - { - GameWorldMp* gameWorldMp = new GameWorldMp; - - gameWorldMp->name = _strdup(projInfo->bspName.c_str()); - - gameWorldMp->path.nodeCount = 0; - gameWorldMp->path.originalNodeCount = 0; - gameWorldMp->path.visBytes = 0; - gameWorldMp->path.smoothBytes = 0; - gameWorldMp->path.nodeTreeCount = 0; - - int nodeCount = gameWorldMp->path.nodeCount + 128; - gameWorldMp->path.nodes = new pathnode_t[nodeCount]; - gameWorldMp->path.basenodes = new pathbasenode_t[nodeCount]; - memset(gameWorldMp->path.nodes, 0, nodeCount * sizeof(pathnode_t)); - memset(gameWorldMp->path.basenodes, 0, nodeCount * sizeof(pathbasenode_t)); - - gameWorldMp->path.pathVis = NULL; - gameWorldMp->path.smoothCache = NULL; - gameWorldMp->path.nodeTree = NULL; - - m_context.AddAsset(gameWorldMp->name, gameWorldMp); - } - - void createSkinnedVerts(BSPData* projInfo) - { - SkinnedVertsDef* skinnedVerts = new SkinnedVertsDef; - skinnedVerts->name = "skinnedverts"; - skinnedVerts->maxSkinnedVerts = projInfo->gfxWorld.vertices.size(); - // I'm pretty sure maxSkinnedVerts relates to the max amount of xmodel skinned verts a map will have - // But setting it to the world vertex count seems to work - - m_context.AddAsset("skinnedverts", skinnedVerts); - } - - FootstepTableDef* addEmptyFootstepTableAsset(std::string assetName) - { - if (assetName.length() == 0) - return NULL; - - FootstepTableDef* footstepTable = new FootstepTableDef; - footstepTable->name = _strdup(assetName.c_str()); - memset(footstepTable->sndAliasTable, 0, sizeof(footstepTable->sndAliasTable)); - - m_context.AddAsset(assetName, footstepTable); - - return footstepTable; - } - - void checkAndAddDefaultRequiredAssets(BSPData* projectInfo) - { - auto templateFile = m_search_path.Open("materials/material_template.json"); - if (!templateFile.IsOpen()) - { - printf("ERROR: failed to open materials/material_template.json\n"); - hasLinkFailed = true; - return; - } - materialTemplateJson = json::parse(*templateFile.m_stream); - - if (m_context.LoadDependency("maps/mp/" + projectInfo->name + ".gsc") == NULL) - { - hasLinkFailed = true; - return; - } - if (m_context.LoadDependency("maps/mp/" + projectInfo->name + "_amb.gsc") == NULL) - { - hasLinkFailed = true; - return; - } - if (m_context.LoadDependency("maps/mp/" + projectInfo->name + "_fx.gsc") == NULL) - { - hasLinkFailed = true; - return; - } - - if (m_context.LoadDependency("clientscripts/mp/" + projectInfo->name + ".csc") == NULL) - { - hasLinkFailed = true; - return; - } - if (m_context.LoadDependency("clientscripts/mp/" + projectInfo->name + "_amb.csc") == NULL) - { - hasLinkFailed = true; - return; - } - if (m_context.LoadDependency("clientscripts/mp/" + projectInfo->name + "_fx.csc") == NULL) - { - hasLinkFailed = true; - return; - } - - addEmptyFootstepTableAsset("default_1st_person"); - addEmptyFootstepTableAsset("default_3rd_person"); - addEmptyFootstepTableAsset("default_1st_person_quiet"); - addEmptyFootstepTableAsset("default_3rd_person_quiet"); - addEmptyFootstepTableAsset("default_3rd_person_loud"); - addEmptyFootstepTableAsset("default_ai"); - - if (m_context.LoadDependency("animtrees/fxanim_props.atr") == NULL) - { - hasLinkFailed = true; - return; - } - } - }; - -} \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.cpp index de6d6007..ccd42bd7 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/BSPLinker.cpp @@ -48,6 +48,8 @@ namespace BSP if (m_context.LoadDependency("animtrees/fxanim_props.atr") == nullptr) return false; + + return true; } BSPLinker::BSPLinker(MemoryManager& memory, ISearchPath& searchPath, AssetCreationContext& context) @@ -69,12 +71,27 @@ namespace BSP MapEntsLinker mapEntsLinker(m_memory, m_search_path, m_context); SkinnedVertsLinker skinnedVertsLinker(m_memory, m_search_path, m_context); - comWorldLinker.linkComWorld(bsp); - mapEntsLinker.linkMapEnts(bsp); + if (comWorldLinker.linkComWorld(bsp).HasFailed()) + return AssetCreationResult::Failure(); + + if (mapEntsLinker.linkMapEnts(bsp).HasFailed()) + return AssetCreationResult::Failure(); + gameWorldMpLinker.linkGameWorldMp(bsp); - skinnedVertsLinker.linkSkinnedVerts(bsp); - gfxWorldLinker.linkGfxWorld(bsp); // requires mapents asset - clipMapLinker.linkClipMap(bsp); // requires gfxworld and mapents asset + if (gameWorldMpLinker.linkGameWorldMp(bsp).HasFailed()) + return AssetCreationResult::Failure(); + + if (skinnedVertsLinker.linkSkinnedVerts(bsp).HasFailed()) + return AssetCreationResult::Failure(); + + auto result = gfxWorldLinker.linkGfxWorld(bsp); // requires mapents asset + if (result.HasFailed()) + return AssetCreationResult::Failure(); + + if (clipMapLinker.linkClipMap(bsp).HasFailed()) // requires gfxworld and mapents asset + return AssetCreationResult::Failure(); + + return result; } } diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp index 8d42b256..82fe31c5 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.cpp @@ -1,4 +1,6 @@ #include "GfxWorldLinker.h" +#include "../BSPUtil.h" +#include "Utils/Pack.h" namespace BSP { @@ -9,9 +11,832 @@ namespace BSP { } + // TODO vd1: + // used for UVs of sub-textures, when it is set to empty all of them turn a blank colour + // could fix by removing sub textures or figure out how they are created and redo that + // its not an important issue though + void GfxWorldLinker::overwriteDrawData(BSPData* projInfo, GfxWorld* gfxWorld) + { + int vertexCount = projInfo->gfxWorld.vertices.size(); + + gfxWorld->draw.vertexCount = vertexCount; + gfxWorld->draw.vertexDataSize0 = vertexCount * sizeof(GfxPackedWorldVertex); + GfxPackedWorldVertex* vertexBuffer = m_memory.Alloc(vertexCount); + for (int i = 0; i < vertexCount; i++) + { + BSPVertex* WorldVertex = &projInfo->gfxWorld.vertices[i]; + GfxPackedWorldVertex* GfxVertex = &vertexBuffer[i]; + + GfxVertex->xyz = BSPUtil::convertToBO2Coords(WorldVertex->pos); + //GfxVertex->xyz = WorldVertex->pos; + + GfxVertex->color.packed = pack32::Vec4PackGfxColor(WorldVertex->color.v); + + GfxVertex->texCoord.packed = pack32::Vec2PackTexCoordsUV(WorldVertex->texCoord.v); + + GfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(BSPUtil::convertToBO2Coords(WorldVertex->normal).v); + //GfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(WorldVertex->normal.v); + + GfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(BSPUtil::convertToBO2Coords(WorldVertex->tangent).v); + //GfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(WorldVertex->tangent.v); + + // unknown use variables + // binormalSign may be bitangent of the vertex + // lmapCoord may be the lightmap coordinate of the vertex + GfxVertex->binormalSign = 0.0f; + GfxVertex->lmapCoord.packed = 0; + } + gfxWorld->draw.vd0.data = (char*)vertexBuffer; + + // we don't use vd1 but still needs to be initialised + // the data type varies and 0x20 is enough for all types + gfxWorld->draw.vertexDataSize1 = 0x20; + gfxWorld->draw.vd1.data = m_memory.Alloc(gfxWorld->draw.vertexDataSize1); + memset(gfxWorld->draw.vd1.data, 0, gfxWorld->draw.vertexDataSize1); + + int indexCount = projInfo->gfxWorld.indices.size(); + assert(indexCount % 3 == 0); + gfxWorld->draw.indexCount = indexCount; + gfxWorld->draw.indices = m_memory.Alloc(indexCount); + for (int i = 0; i < indexCount; i += 3) + { + // the editor orders their vertices opposite to bo2, so its converted here + gfxWorld->draw.indices[i + 2] = projInfo->gfxWorld.indices[i + 0]; + gfxWorld->draw.indices[i + 1] = projInfo->gfxWorld.indices[i + 1]; + gfxWorld->draw.indices[i + 0] = projInfo->gfxWorld.indices[i + 2]; + } + } + + bool GfxWorldLinker::overwriteMapSurfaces(BSPData* projInfo, GfxWorld* gfxWorld) + { + overwriteDrawData(projInfo, gfxWorld); + + unsigned int surfaceCount = projInfo->gfxWorld.surfaces.size(); + gfxWorld->surfaceCount = surfaceCount; + gfxWorld->dpvs.staticSurfaceCount = surfaceCount; + gfxWorld->dpvs.surfaces = m_memory.Alloc(surfaceCount); + for (unsigned int i = 0; i < surfaceCount; i++) + { + auto currSurface = &gfxWorld->dpvs.surfaces[i]; + auto objSurface = &projInfo->gfxWorld.surfaces[i]; + + currSurface->primaryLightIndex = BSPEditableConstants::DEFAULT_SURFACE_LIGHT; + currSurface->lightmapIndex = BSPEditableConstants::DEFAULT_SURFACE_LIGHTMAP; + currSurface->reflectionProbeIndex = BSPEditableConstants::DEFAULT_SURFACE_REFLECTION_PROBE; + currSurface->flags = BSPEditableConstants::DEFAULT_SURFACE_FLAGS; + + currSurface->tris.triCount = objSurface->triCount; + currSurface->tris.baseIndex = objSurface->indexOfFirstIndex; + + currSurface->tris.vertexDataOffset0 = objSurface->indexOfFirstVertex * sizeof(GfxPackedWorldVertex); + currSurface->tris.vertexDataOffset1 = 0; + + std::string surfMaterialName; + switch (objSurface->material.materialType) + { + case MATERIAL_TYPE_TEXTURE: + surfMaterialName = objSurface->material.materialName; + break; + + case MATERIAL_TYPE_COLOUR: + case MATERIAL_TYPE_EMPTY: + surfMaterialName = BSPLinkingConstants::COLOR_ONLY_IMAGE_NAME; + break; + + default: + assert(false); + } + + auto surfMaterialAsset = m_context.LoadDependency(surfMaterialName); + if (surfMaterialAsset == NULL) + { + std::string missingImageName = BSPLinkingConstants::MISSING_IMAGE_NAME; + surfMaterialAsset = m_context.LoadDependency(missingImageName); + if (surfMaterialAsset == NULL) + { + con::error("unable to find the missing image texture!"); + return false; + } + } + currSurface->material = surfMaterialAsset->Asset(); + + GfxPackedWorldVertex* firstVert = (GfxPackedWorldVertex*)&gfxWorld->draw.vd0.data[currSurface->tris.vertexDataOffset0]; + currSurface->bounds[0].x = firstVert[0].xyz.x; + currSurface->bounds[0].y = firstVert[0].xyz.y; + currSurface->bounds[0].z = firstVert[0].xyz.z; + currSurface->bounds[1].x = firstVert[0].xyz.x; + currSurface->bounds[1].y = firstVert[0].xyz.y; + currSurface->bounds[1].z = firstVert[0].xyz.z; + for (int k = 0; k < currSurface->tris.triCount * 3; k++) + { + uint16_t vertIndex = gfxWorld->draw.indices[currSurface->tris.baseIndex + k]; + BSPUtil::calcNewBoundsWithPoint(&firstVert[vertIndex].xyz, &currSurface->bounds[0], &currSurface->bounds[1]); + } + + // unused values + currSurface->tris.mins.x = 0.0f; + currSurface->tris.mins.y = 0.0f; + currSurface->tris.mins.z = 0.0f; + currSurface->tris.maxs.x = 0.0f; + currSurface->tris.maxs.y = 0.0f; + currSurface->tris.maxs.z = 0.0f; + currSurface->tris.himipRadiusInvSq = 0.0f; + currSurface->tris.vertexCount = 0; + currSurface->tris.firstVertex = 0; + } + + // doesn't seem to matter what order the sorted surfs go in + gfxWorld->dpvs.sortedSurfIndex = m_memory.Alloc(surfaceCount); + for (unsigned int i = 0; i < surfaceCount; i++) + { + gfxWorld->dpvs.sortedSurfIndex[i] = i; + } + + // surface materials are written to by the game + gfxWorld->dpvs.surfaceMaterials = m_memory.Alloc(surfaceCount); + memset(gfxWorld->dpvs.surfaceMaterials, 0, sizeof(GfxDrawSurf_align4) * surfaceCount); + + // set all surface types to lit opaque + gfxWorld->dpvs.litSurfsBegin = 0; + gfxWorld->dpvs.litSurfsEnd = surfaceCount; + gfxWorld->dpvs.emissiveOpaqueSurfsBegin = surfaceCount; + gfxWorld->dpvs.emissiveOpaqueSurfsEnd = surfaceCount; + gfxWorld->dpvs.emissiveTransSurfsBegin = surfaceCount; + gfxWorld->dpvs.emissiveTransSurfsEnd = surfaceCount; + gfxWorld->dpvs.litTransSurfsBegin = surfaceCount; + gfxWorld->dpvs.litTransSurfsEnd = surfaceCount; + + // visdata is written to by the game + // all visdata is alligned by 128 + unsigned int allignedSurfaceCount = BSPUtil::allignBy128(surfaceCount); + gfxWorld->dpvs.surfaceVisDataCount = allignedSurfaceCount; + gfxWorld->dpvs.surfaceVisData[0] = m_memory.Alloc(allignedSurfaceCount); + gfxWorld->dpvs.surfaceVisData[1] = m_memory.Alloc(allignedSurfaceCount); + gfxWorld->dpvs.surfaceVisData[2] = m_memory.Alloc(allignedSurfaceCount); + gfxWorld->dpvs.surfaceVisDataCameraSaved = m_memory.Alloc(allignedSurfaceCount); + gfxWorld->dpvs.surfaceCastsShadow = m_memory.Alloc(allignedSurfaceCount); + gfxWorld->dpvs.surfaceCastsSunShadow = m_memory.Alloc(allignedSurfaceCount); + memset(gfxWorld->dpvs.surfaceVisData[0], 0, allignedSurfaceCount); + memset(gfxWorld->dpvs.surfaceVisData[1], 0, allignedSurfaceCount); + memset(gfxWorld->dpvs.surfaceVisData[2], 0, allignedSurfaceCount); + memset(gfxWorld->dpvs.surfaceVisDataCameraSaved, 0, allignedSurfaceCount); + memset(gfxWorld->dpvs.surfaceCastsShadow, 0, allignedSurfaceCount); + memset(gfxWorld->dpvs.surfaceCastsSunShadow, 0, allignedSurfaceCount); + + return true; + } + + void GfxWorldLinker::overwriteMapSModels(BSPData* projInfo, GfxWorld* gfxWorld) + { + /* + Models are unsupported right now + Code is left in in case it is supported later on + */ + //unsigned int modelCount = projInfo->modelCount; + //gfxWorld->dpvs.smodelCount = modelCount; + //gfxWorld->dpvs.smodelInsts = new GfxStaticModelInst[modelCount]; + //gfxWorld->dpvs.smodelDrawInsts = new GfxStaticModelDrawInst[modelCount]; + // + //for (unsigned int i = 0; i < modelCount; i++) + //{ + // auto currModel = &gfxWorld->dpvs.smodelDrawInsts[i]; + // auto currModelInst = &gfxWorld->dpvs.smodelInsts[i]; + // customMapModel* inModel = &projInfo->models[i]; + // + // auto xModelAsset = m_context.LoadDependency(inModel->name); + // if (xModelAsset == NULL) + // { + // printf("XModel %s not found!\n", inModel->name.c_str()); + // currModel->model = NULL; + // } + // else + // currModel->model = (XModel*)xModelAsset->Asset(); + // + // currModel->placement.origin.x = inModel->origin.x; + // currModel->placement.origin.y = inModel->origin.y; + // currModel->placement.origin.z = inModel->origin.z; + // currModel->placement.origin = BSPUtil::convertToBO2Coords(currModel->placement.origin); + // currModel->placement.scale = inModel->scale; + // + // BSPUtil::convertAnglesToAxis(&inModel->rotation, currModel->placement.axis); + // + // // mins and maxs are calculated in world space not local space + // // TODO: this does not account for model rotation or scale + // currModelInst->mins.x = currModel->model->mins.x + currModel->placement.origin.x; + // currModelInst->mins.y = currModel->model->mins.y + currModel->placement.origin.y; + // currModelInst->mins.z = currModel->model->mins.z + currModel->placement.origin.z; + // currModelInst->maxs.x = currModel->model->maxs.x + currModel->placement.origin.x; + // currModelInst->maxs.y = currModel->model->maxs.y + currModel->placement.origin.y; + // currModelInst->maxs.z = currModel->model->maxs.z + currModel->placement.origin.z; + // + // currModel->cullDist = DEFAULT_SMODEL_CULL_DIST; + // currModel->flags = DEFAULT_SMODEL_FLAGS; + // currModel->primaryLightIndex = DEFAULT_SMODEL_LIGHT; + // currModel->reflectionProbeIndex = DEFAULT_SMODEL_REFLECTION_PROBE; + // + // // unknown use / unused + // currModel->smid = i; + // memset(&currModel->lightingSH, 0, sizeof(GfxLightingSHQuantized)); + // currModel->invScaleSq = 0.0f; + // currModel->lightingHandle = 0; + // currModel->colorsIndex = 0; + // currModel->visibility = 0; + // + // // setting these to NULL makes any static/baked lighting go black when not rendered by real-time lighting or in a shadow + // // TODO: calculate lighting and store it here + // currModel->lmapVertexInfo[0].numLmapVertexColors = 0; + // currModel->lmapVertexInfo[0].lmapVertexColors = NULL; + // currModel->lmapVertexInfo[1].numLmapVertexColors = 0; + // currModel->lmapVertexInfo[1].lmapVertexColors = NULL; + // currModel->lmapVertexInfo[2].numLmapVertexColors = 0; + // currModel->lmapVertexInfo[2].lmapVertexColors = NULL; + // currModel->lmapVertexInfo[3].numLmapVertexColors = 0; + // currModel->lmapVertexInfo[3].lmapVertexColors = NULL; + //} + + unsigned int modelCount = 0; + gfxWorld->dpvs.smodelCount = modelCount; + gfxWorld->dpvs.smodelInsts = m_memory.Alloc(modelCount); + gfxWorld->dpvs.smodelDrawInsts = m_memory.Alloc(modelCount); + + // visdata is written to by the game + // all visdata is alligned by 128 + int allignedModelCount = BSPUtil::allignBy128(modelCount); + gfxWorld->dpvs.smodelVisDataCount = allignedModelCount; + gfxWorld->dpvs.smodelVisData[0] = m_memory.Alloc(allignedModelCount); + gfxWorld->dpvs.smodelVisData[1] = m_memory.Alloc(allignedModelCount); + gfxWorld->dpvs.smodelVisData[2] = m_memory.Alloc(allignedModelCount); + gfxWorld->dpvs.smodelVisDataCameraSaved = m_memory.Alloc(allignedModelCount); + gfxWorld->dpvs.smodelCastsShadow = m_memory.Alloc(allignedModelCount); + memset(gfxWorld->dpvs.smodelVisData[0], 0, allignedModelCount); + memset(gfxWorld->dpvs.smodelVisData[1], 0, allignedModelCount); + memset(gfxWorld->dpvs.smodelVisData[2], 0, allignedModelCount); + memset(gfxWorld->dpvs.smodelVisDataCameraSaved, 0, allignedModelCount); + memset(gfxWorld->dpvs.smodelCastsShadow, 0, allignedModelCount); + for (unsigned int i = 0; i < modelCount; i++) + { + if ((gfxWorld->dpvs.smodelDrawInsts[i].flags & STATIC_MODEL_FLAG_NO_SHADOW) == 0) + gfxWorld->dpvs.smodelCastsShadow[i] = 1; + else + gfxWorld->dpvs.smodelCastsShadow[i] = 0; + } + + // official maps set this to 0 + gfxWorld->dpvs.usageCount = 0; + } + + void GfxWorldLinker::cleanGfxWorld(GfxWorld* gfxWorld) + { + // checksum is generated by the game + gfxWorld->checksum = 0; + + // Remove Coronas + gfxWorld->coronaCount = 0; + gfxWorld->coronas = NULL; + + // Remove exposure volumes + gfxWorld->exposureVolumeCount = 0; + gfxWorld->exposureVolumes = NULL; + gfxWorld->exposureVolumePlaneCount = 0; + gfxWorld->exposureVolumePlanes = NULL; + + // Remove hero lights + gfxWorld->heroLightCount = 0; + gfxWorld->heroLights = NULL; + gfxWorld->heroLightTreeCount = 0; + gfxWorld->heroLightTree = NULL; + + // remove LUT data + gfxWorld->lutVolumeCount = 0; + gfxWorld->lutVolumes = NULL; + gfxWorld->lutVolumePlaneCount = 0; + gfxWorld->lutVolumePlanes = NULL; + + // remove occluders + gfxWorld->numOccluders = 0; + gfxWorld->occluders = NULL; + + // remove Siege Skins + gfxWorld->numSiegeSkinInsts = 0; + gfxWorld->siegeSkinInsts = NULL; + + // remove outdoor bounds + gfxWorld->numOutdoorBounds = 0; + gfxWorld->outdoorBounds = NULL; + + // remove materials + gfxWorld->ropeMaterial = NULL; + gfxWorld->lutMaterial = NULL; + gfxWorld->waterMaterial = NULL; + gfxWorld->coronaMaterial = NULL; + + // remove shadow maps + gfxWorld->shadowMapVolumeCount = 0; + gfxWorld->shadowMapVolumes = NULL; + gfxWorld->shadowMapVolumePlaneCount = 0; + gfxWorld->shadowMapVolumePlanes = NULL; + + // remove stream info + gfxWorld->streamInfo.aabbTreeCount = 0; + gfxWorld->streamInfo.aabbTrees = NULL; + gfxWorld->streamInfo.leafRefCount = 0; + gfxWorld->streamInfo.leafRefs = NULL; + + // remove sun data + memset(&gfxWorld->sun, 0, sizeof(sunflare_t)); + gfxWorld->sun.hasValidData = false; + + // Remove Water + gfxWorld->waterDirection = 0.0f; + gfxWorld->waterBuffers[0].buffer = NULL; + gfxWorld->waterBuffers[0].bufferSize = NULL; + gfxWorld->waterBuffers[1].buffer = NULL; + gfxWorld->waterBuffers[1].bufferSize = NULL; + + // Remove Fog + gfxWorld->worldFogModifierVolumeCount = 0; + gfxWorld->worldFogModifierVolumes = NULL; + gfxWorld->worldFogModifierVolumePlaneCount = 0; + gfxWorld->worldFogModifierVolumePlanes = NULL; + gfxWorld->worldFogVolumeCount = 0; + gfxWorld->worldFogVolumes = NULL; + gfxWorld->worldFogVolumePlaneCount = 0; + gfxWorld->worldFogVolumePlanes = NULL; + + // materialMemory is unused + gfxWorld->materialMemoryCount = 0; + gfxWorld->materialMemory = NULL; + + // sunLight is overwritten by the game, just needs to be a valid pointer + gfxWorld->sunLight = m_memory.Alloc(); + memset(gfxWorld->sunLight, 0, sizeof(GfxLight)); + } + + void GfxWorldLinker::overwriteGfxLights(GfxWorld* gfxWorld) + { + // there must be 2 or more lights, first is the default light and second is the sun + gfxWorld->primaryLightCount = 2; + gfxWorld->sunPrimaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; + + gfxWorld->shadowGeom = m_memory.Alloc(gfxWorld->primaryLightCount); + for (unsigned int i = 0; i < gfxWorld->primaryLightCount; i++) + { + gfxWorld->shadowGeom[i].smodelCount = 0; + gfxWorld->shadowGeom[i].surfaceCount = 0; + gfxWorld->shadowGeom[i].smodelIndex = NULL; + gfxWorld->shadowGeom[i].sortedSurfIndex = NULL; + } + + gfxWorld->lightRegion = m_memory.Alloc(gfxWorld->primaryLightCount); + for (unsigned int i = 0; i < gfxWorld->primaryLightCount; i++) + { + gfxWorld->lightRegion[i].hullCount = 0; + gfxWorld->lightRegion[i].hulls = NULL; + } + + int lightEntShadowVisSize = (gfxWorld->primaryLightCount - gfxWorld->sunPrimaryLightIndex - 1) * 8192; + if (lightEntShadowVisSize != 0) + { + gfxWorld->primaryLightEntityShadowVis = m_memory.Alloc(lightEntShadowVisSize); + memset(gfxWorld->primaryLightEntityShadowVis, 0, lightEntShadowVisSize * sizeof(unsigned int)); + } + else + { + gfxWorld->primaryLightEntityShadowVis = NULL; + } + } + + // the lightgrid is used to light models in a dynamic way and is precomputed + void GfxWorldLinker::overwriteLightGrid(GfxWorld* gfxWorld) + { + // there is almost no basis for the values in this code, i chose them based on what feels right and what i could see when RE. + // it works and that is all thats needed :) + + // mins and maxs define the range that the lightgrid will work in + // idk how these values are calculated, but the below values are larger + // than official map values + gfxWorld->lightGrid.mins[0] = 0; + gfxWorld->lightGrid.mins[1] = 0; + gfxWorld->lightGrid.mins[2] = 0; + gfxWorld->lightGrid.maxs[0] = 200; + gfxWorld->lightGrid.maxs[1] = 200; + gfxWorld->lightGrid.maxs[2] = 50; + + gfxWorld->lightGrid.rowAxis = 0; // default value + gfxWorld->lightGrid.colAxis = 1; // default value + gfxWorld->lightGrid.sunPrimaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; + gfxWorld->lightGrid.offset = 0.0f; // default value + + // this will make the lookup into rawRowData always return the first row + int rowDataStartSize = gfxWorld->lightGrid.maxs[gfxWorld->lightGrid.rowAxis] - gfxWorld->lightGrid.mins[gfxWorld->lightGrid.rowAxis] + 1; + gfxWorld->lightGrid.rowDataStart = m_memory.Alloc(rowDataStartSize); + memset(gfxWorld->lightGrid.rowDataStart, 0, rowDataStartSize * sizeof(uint16_t)); + + gfxWorld->lightGrid.rawRowDataSize = sizeof(GfxLightGridRow) + 0x10; + GfxLightGridRow* row = (GfxLightGridRow*)m_memory.AllocRaw(sizeof(GfxLightGridRow) + 0x10); + row->colStart = 0; + row->colCount = 0x1000; // 0x1000 as this is large enough for all checks done by the game + row->zStart = 0; + row->zCount = 0xFF; // 0xFF as this is large enough for all checks done by the game, but small enough not to mess with other checks + row->firstEntry = 0; + for (int i = 0; i < 0x11; i++) // set the lookup table to all 0 + { + row->lookupTable[i] = 0; + } + gfxWorld->lightGrid.rawRowData = (aligned_byte_pointer*)row; + + // entries are looked up based on the lightgrid sample pos (given ingame) and the lightgrid lookup table + gfxWorld->lightGrid.entryCount = 60000; // 60000 as it should be enough entries to be indexed by all lightgrid data + GfxLightGridEntry* entryArray = m_memory.Alloc(gfxWorld->lightGrid.entryCount); + for (unsigned int i = 0; i < gfxWorld->lightGrid.entryCount; i++) + { + entryArray[i].colorsIndex = 0; // always index first colour + entryArray[i].primaryLightIndex = BSPGameConstants::SUN_LIGHT_INDEX; + entryArray[i].visibility = 0; + } + gfxWorld->lightGrid.entries = entryArray; + + // colours are looked up by an entries colourindex + gfxWorld->lightGrid.colorCount = 0x1000; //0x1000 as it should be enough to hold every index + gfxWorld->lightGrid.colors = m_memory.Alloc(gfxWorld->lightGrid.colorCount); + memset(gfxWorld->lightGrid.colors, BSPEditableConstants::LIGHTGRID_COLOUR, rowDataStartSize * sizeof(uint16_t)); + + // we use the colours array instead of coeffs array + gfxWorld->lightGrid.coeffCount = 0; + gfxWorld->lightGrid.coeffs = NULL; + gfxWorld->lightGrid.skyGridVolumeCount = 0; + gfxWorld->lightGrid.skyGridVolumes = NULL; + } + + void GfxWorldLinker::updateGfxCells(GfxWorld* gfxWorld) + { + // Cells are basically data used to determine what can be seen and what cant be seen + // Right now custom maps have no optimisation so there is only 1 cell + int cellCount = 1; + + gfxWorld->cellBitsCount = ((cellCount + 127) >> 3) & 0x1FFFFFF0; + + int cellCasterBitsCount = cellCount * ((cellCount + 31) / 32); + gfxWorld->cellCasterBits = m_memory.Alloc(cellCasterBitsCount); + memset(gfxWorld->cellCasterBits, 0x00, cellCasterBitsCount * sizeof(unsigned int)); + + gfxWorld->cells = m_memory.Alloc(cellCount); + gfxWorld->cells[0].portalCount = 0; + gfxWorld->cells[0].portals = NULL; + gfxWorld->cells[0].mins.x = gfxWorld->mins.x; + gfxWorld->cells[0].mins.y = gfxWorld->mins.y; + gfxWorld->cells[0].mins.z = gfxWorld->mins.z; + gfxWorld->cells[0].maxs.x = gfxWorld->maxs.x; + gfxWorld->cells[0].maxs.y = gfxWorld->maxs.y; + gfxWorld->cells[0].maxs.z = gfxWorld->maxs.z; + + // there is only 1 reflection probe + gfxWorld->cells[0].reflectionProbeCount = 1; + char* reflectionProbeIndexes = m_memory.Alloc(gfxWorld->cells[0].reflectionProbeCount); + reflectionProbeIndexes[0] = BSPEditableConstants::DEFAULT_SURFACE_REFLECTION_PROBE; + gfxWorld->cells[0].reflectionProbes = reflectionProbeIndexes; + + // AABB trees are used to detect what should be rendered and what shouldn't + // Just use the first AABB node to hold all models, no optimisation but all models/surfaces wil lbe drawn + gfxWorld->cells[0].aabbTreeCount = 1; + gfxWorld->cells[0].aabbTree = m_memory.Alloc(gfxWorld->cells[0].aabbTreeCount); + gfxWorld->cells[0].aabbTree[0].childCount = 0; + gfxWorld->cells[0].aabbTree[0].childrenOffset = 0; + gfxWorld->cells[0].aabbTree[0].startSurfIndex = 0; + gfxWorld->cells[0].aabbTree[0].surfaceCount = gfxWorld->surfaceCount; + gfxWorld->cells[0].aabbTree[0].smodelIndexCount = gfxWorld->dpvs.smodelCount; + gfxWorld->cells[0].aabbTree[0].smodelIndexes = m_memory.Alloc(gfxWorld->dpvs.smodelCount); + for (unsigned short i = 0; i < gfxWorld->dpvs.smodelCount; i++) + { + gfxWorld->cells[0].aabbTree[0].smodelIndexes[i] = i; + } + gfxWorld->cells[0].aabbTree[0].mins.x = gfxWorld->mins.x; + gfxWorld->cells[0].aabbTree[0].mins.y = gfxWorld->mins.y; + gfxWorld->cells[0].aabbTree[0].mins.z = gfxWorld->mins.z; + gfxWorld->cells[0].aabbTree[0].maxs.x = gfxWorld->maxs.x; + gfxWorld->cells[0].aabbTree[0].maxs.y = gfxWorld->maxs.y; + gfxWorld->cells[0].aabbTree[0].maxs.z = gfxWorld->maxs.z; + + gfxWorld->dpvsPlanes.cellCount = cellCount; + + int sceneEntCellBitsCount = cellCount * 512; + gfxWorld->dpvsPlanes.sceneEntCellBits = m_memory.Alloc(sceneEntCellBitsCount); + memset(gfxWorld->dpvsPlanes.sceneEntCellBits, 0x00, sceneEntCellBitsCount * sizeof(unsigned int)); + + // nodes have the struct mnode_t, and there must be at least 1 node + // Nodes mnode_t.cellIndex indexes gfxWorld->cells + // and (mnode_t.cellIndex - (world->dpvsPlanes.cellCount + 1) indexes world->dpvsPlanes.planes + gfxWorld->nodeCount = 1; + gfxWorld->dpvsPlanes.nodes = m_memory.Alloc(gfxWorld->nodeCount); + gfxWorld->dpvsPlanes.nodes[0] = 1; // nodes reference cells by index + 1 + + // planes are overwritten by the clipmap loading code ingame + gfxWorld->planeCount = 0; + gfxWorld->dpvsPlanes.planes = NULL; + } + + void updateWorldBounds(GfxWorld* gfxWorld) + { + gfxWorld->mins.x = 0.0f; + gfxWorld->mins.y = 0.0f; + gfxWorld->mins.z = 0.0f; + gfxWorld->maxs.x = 0.0f; + gfxWorld->maxs.y = 0.0f; + gfxWorld->maxs.z = 0.0f; + + for (int i = 0; i < gfxWorld->surfaceCount; i++) + { + BSPUtil::calcNewBounds(&gfxWorld->dpvs.surfaces[i].bounds[0], &gfxWorld->dpvs.surfaces[i].bounds[1], &gfxWorld->mins, &gfxWorld->maxs); + } + } + + void GfxWorldLinker::overwriteModels(GfxWorld* gfxWorld) + { + // Models (Submodels in the clipmap code) are used for the world and map ent collision (triggers, bomb zones, etc) + // Right now there is only one submodel, the world sub model + gfxWorld->modelCount = 1; + gfxWorld->models = m_memory.Alloc(gfxWorld->modelCount); + + // first model is always the world model + gfxWorld->models[0].startSurfIndex = 0; + gfxWorld->models[0].surfaceCount = gfxWorld->surfaceCount; + gfxWorld->models[0].bounds[0].x = gfxWorld->mins.x; + gfxWorld->models[0].bounds[0].y = gfxWorld->mins.y; + gfxWorld->models[0].bounds[0].z = gfxWorld->mins.z; + gfxWorld->models[0].bounds[1].x = gfxWorld->maxs.x; + gfxWorld->models[0].bounds[1].y = gfxWorld->maxs.y; + gfxWorld->models[0].bounds[1].z = gfxWorld->maxs.z; + memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); + + // Other models aren't implemented yet + // Code kept for future use + //for (size_t i = 0; i < entityModelList.size(); i++) + //{ + // auto currEntModel = &gfxWorld->models[i + 1]; + // entModelBounds currEntModelBounds = entityModelList[i]; + // + // currEntModel->startSurfIndex = 0; + // currEntModel->surfaceCount = -1; // -1 when it doesn't use map surfaces + // currEntModel->bounds[0].x = currEntModelBounds.mins.x; + // currEntModel->bounds[0].y = currEntModelBounds.mins.y; + // currEntModel->bounds[0].z = currEntModelBounds.mins.z; + // currEntModel->bounds[1].x = currEntModelBounds.maxs.x; + // currEntModel->bounds[1].y = currEntModelBounds.maxs.y; + // currEntModel->bounds[1].z = currEntModelBounds.maxs.z; + // memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); + //} + } + + void updateSunData(GfxWorld* gfxWorld) + { + // default values taken from mp_dig + gfxWorld->sunParse.fogTransitionTime = (float)0.001; + gfxWorld->sunParse.name[0] = 0x00; + + gfxWorld->sunParse.initWorldSun->control = 0; + gfxWorld->sunParse.initWorldSun->exposure = 2.5f; + gfxWorld->sunParse.initWorldSun->angles.x = -29.0f; + gfxWorld->sunParse.initWorldSun->angles.y = 254.0f; + gfxWorld->sunParse.initWorldSun->angles.z = 0.0f; + gfxWorld->sunParse.initWorldSun->sunCd.x = 1.0f; + gfxWorld->sunParse.initWorldSun->sunCd.y = 0.89f; + gfxWorld->sunParse.initWorldSun->sunCd.z = 0.69f; + gfxWorld->sunParse.initWorldSun->sunCd.w = 13.5f; + gfxWorld->sunParse.initWorldSun->ambientColor.x = 0.0f; + gfxWorld->sunParse.initWorldSun->ambientColor.y = 0.0f; + gfxWorld->sunParse.initWorldSun->ambientColor.z = 0.0f; + gfxWorld->sunParse.initWorldSun->ambientColor.w = 0.0f; + gfxWorld->sunParse.initWorldSun->skyColor.x = 0.0f; + gfxWorld->sunParse.initWorldSun->skyColor.y = 0.0f; + gfxWorld->sunParse.initWorldSun->skyColor.z = 0.0f; + gfxWorld->sunParse.initWorldSun->skyColor.w = 0.0f; + gfxWorld->sunParse.initWorldSun->sunCs.x = 0.0f; + gfxWorld->sunParse.initWorldSun->sunCs.y = 0.0f; + gfxWorld->sunParse.initWorldSun->sunCs.z = 0.0f; + gfxWorld->sunParse.initWorldSun->sunCs.w = 0.0f; + + gfxWorld->sunParse.initWorldFog->baseDist = 150.0f; + gfxWorld->sunParse.initWorldFog->baseHeight = -100.0f; + gfxWorld->sunParse.initWorldFog->fogColor.x = 2.35f; + gfxWorld->sunParse.initWorldFog->fogColor.y = 3.10f; + gfxWorld->sunParse.initWorldFog->fogColor.z = 3.84f; + gfxWorld->sunParse.initWorldFog->fogOpacity = 0.52f; + gfxWorld->sunParse.initWorldFog->halfDist = 4450.f; + gfxWorld->sunParse.initWorldFog->halfHeight = 2000.f; + gfxWorld->sunParse.initWorldFog->sunFogColor.x = 5.27f; + gfxWorld->sunParse.initWorldFog->sunFogColor.y = 4.73f; + gfxWorld->sunParse.initWorldFog->sunFogColor.z = 3.88f; + gfxWorld->sunParse.initWorldFog->sunFogInner = 0.0f; + gfxWorld->sunParse.initWorldFog->sunFogOpacity = 0.67f; + gfxWorld->sunParse.initWorldFog->sunFogOuter = 80.84f; + gfxWorld->sunParse.initWorldFog->sunFogPitch = -29.0f; + gfxWorld->sunParse.initWorldFog->sunFogYaw = 254.0f; + } + + bool GfxWorldLinker::updateReflectionProbeData(GfxWorld* gfxWorld) + { + gfxWorld->draw.reflectionProbeCount = 1; + + gfxWorld->draw.reflectionProbeTextures = m_memory.Alloc(gfxWorld->draw.reflectionProbeCount); + memset(gfxWorld->draw.reflectionProbeTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.reflectionProbeCount); + + gfxWorld->draw.reflectionProbes = m_memory.Alloc(gfxWorld->draw.reflectionProbeCount); + + // default values taken from mp_dig + gfxWorld->draw.reflectionProbes[0].mipLodBias = -8.0; + + gfxWorld->draw.reflectionProbes[0].origin.x = 0.0f; + gfxWorld->draw.reflectionProbes[0].origin.y = 0.0f; + gfxWorld->draw.reflectionProbes[0].origin.z = 0.0f; + + gfxWorld->draw.reflectionProbes[0].lightingSH.V0.x = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V0.y = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V0.z = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V0.w = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V1.x = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V1.y = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V1.z = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V1.w = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V2.x = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V2.y = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V2.z = 0.0f; + gfxWorld->draw.reflectionProbes[0].lightingSH.V2.w = 0.0f; + + gfxWorld->draw.reflectionProbes[0].probeVolumeCount = 0; + gfxWorld->draw.reflectionProbes[0].probeVolumes = NULL; + + std::string probeImageName = "*reflection_probe0"; + auto probeImageAsset = m_context.LoadDependency(probeImageName); + if (probeImageAsset == NULL) + { + con::error("ERROR! unable to find reflection probe image {}!", probeImageName); + return false; + } + gfxWorld->draw.reflectionProbes[0].reflectionImage = probeImageAsset->Asset(); + + return true; + } + + bool GfxWorldLinker::overwriteLightmapData(GfxWorld* gfxWorld) + { + gfxWorld->draw.lightmapCount = 1; + + gfxWorld->draw.lightmapPrimaryTextures = m_memory.Alloc(gfxWorld->draw.lightmapCount); + gfxWorld->draw.lightmapSecondaryTextures = m_memory.Alloc(gfxWorld->draw.lightmapCount); + gfxWorld->draw.lightmaps = m_memory.Alloc(gfxWorld->draw.lightmapCount); + + // always set to 0 + memset(gfxWorld->draw.lightmapPrimaryTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.lightmapCount); + memset(gfxWorld->draw.lightmapSecondaryTextures, 0, sizeof(GfxTexture) * gfxWorld->draw.lightmapCount); + + std::string secondaryTexture = "*lightmap0_secondary"; + auto secondaryTextureAsset = m_context.LoadDependency(secondaryTexture); + if (secondaryTextureAsset == NULL) + { + con::error("ERROR! unable to find lightmap image {}!", secondaryTexture); + return false; + } + gfxWorld->draw.lightmaps[0].primary = NULL; // always NULL + gfxWorld->draw.lightmaps[0].secondary = secondaryTextureAsset->Asset(); + + return true; + } + + void GfxWorldLinker::overwriteSkyBox(BSPData* projInfo, GfxWorld* gfxWorld) + { + std::string skyBoxName = "skybox_" + projInfo->name; + gfxWorld->skyBoxModel = m_memory.Dup(skyBoxName.c_str()); + + if (m_context.LoadDependency(skyBoxName) == NULL) + { + con::warn("WARN: Unable to load the skybox xmodel {}!", skyBoxName); + } + + // default skybox values from mp_dig + gfxWorld->skyDynIntensity.angle0 = 0.0f; + gfxWorld->skyDynIntensity.angle1 = 0.0f; + gfxWorld->skyDynIntensity.factor0 = 1.0f; + gfxWorld->skyDynIntensity.factor1 = 1.0f; + } + + void GfxWorldLinker::updateDynEntData(GfxWorld* gfxWorld) + { + int dynEntCount = 0; + gfxWorld->dpvsDyn.dynEntClientCount[0] = dynEntCount + 256; // the game allocs 256 empty dynents, as they may be used ingame + gfxWorld->dpvsDyn.dynEntClientCount[1] = 0; + + // +100: there is a crash that happens when regdolls are created, and dynEntClientWordCount[0] is the issue. + // Making the value much larger than required fixes it, but idk what the root cause is + gfxWorld->dpvsDyn.dynEntClientWordCount[0] = ((gfxWorld->dpvsDyn.dynEntClientCount[0] + 31) >> 5) + 100; + gfxWorld->dpvsDyn.dynEntClientWordCount[1] = 0; + gfxWorld->dpvsDyn.usageCount = 0; + + int dynEntCellBitsSize = gfxWorld->dpvsDyn.dynEntClientWordCount[0] * gfxWorld->dpvsPlanes.cellCount; + gfxWorld->dpvsDyn.dynEntCellBits[0] = m_memory.Alloc(dynEntCellBitsSize); + gfxWorld->dpvsDyn.dynEntCellBits[1] = NULL; + memset(gfxWorld->dpvsDyn.dynEntCellBits[0], 0, sizeof(unsigned int) * dynEntCellBitsSize); + + int dynEntVisData0Size = gfxWorld->dpvsDyn.dynEntClientWordCount[0] * 32; + gfxWorld->dpvsDyn.dynEntVisData[0][0] = m_memory.Alloc(dynEntVisData0Size); + gfxWorld->dpvsDyn.dynEntVisData[0][1] = m_memory.Alloc(dynEntVisData0Size); + gfxWorld->dpvsDyn.dynEntVisData[0][2] = m_memory.Alloc(dynEntVisData0Size); + gfxWorld->dpvsDyn.dynEntVisData[1][0] = NULL; + gfxWorld->dpvsDyn.dynEntVisData[1][1] = NULL; + gfxWorld->dpvsDyn.dynEntVisData[1][2] = NULL; + memset(gfxWorld->dpvsDyn.dynEntVisData[0][0], 0, dynEntVisData0Size); + memset(gfxWorld->dpvsDyn.dynEntVisData[0][1], 0, dynEntVisData0Size); + memset(gfxWorld->dpvsDyn.dynEntVisData[0][2], 0, dynEntVisData0Size); + + int dynEntShadowVisCount = gfxWorld->dpvsDyn.dynEntClientCount[0] * (gfxWorld->primaryLightCount - gfxWorld->sunPrimaryLightIndex - 1); + gfxWorld->primaryLightDynEntShadowVis[0] = m_memory.Alloc(dynEntShadowVisCount); + gfxWorld->primaryLightDynEntShadowVis[1] = NULL; + memset(gfxWorld->primaryLightDynEntShadowVis[0], 0, sizeof(unsigned int) * dynEntShadowVisCount); + + gfxWorld->sceneDynModel = m_memory.Alloc(gfxWorld->dpvsDyn.dynEntClientCount[0]); + gfxWorld->sceneDynBrush = NULL; + memset(gfxWorld->sceneDynModel, 0, sizeof(GfxSceneDynModel) * gfxWorld->dpvsDyn.dynEntClientCount[0]); + } + + bool GfxWorldLinker::updateOutdoors(GfxWorld* gfxWorld) + { + float xRecip = 1.0f / (gfxWorld->maxs.x - gfxWorld->mins.x); + float xScale = -(xRecip * gfxWorld->mins.x); + + float yRecip = 1.0f / (gfxWorld->maxs.y - gfxWorld->mins.y); + float yScale = -(yRecip * gfxWorld->mins.y); + + float zRecip = 1.0f / (gfxWorld->maxs.z - gfxWorld->mins.z); + float zScale = -(zRecip * gfxWorld->mins.z); + + memset(gfxWorld->outdoorLookupMatrix, 0, sizeof(gfxWorld->outdoorLookupMatrix)); + + gfxWorld->outdoorLookupMatrix[0].x = xRecip; + gfxWorld->outdoorLookupMatrix[1].y = yRecip; + gfxWorld->outdoorLookupMatrix[2].z = zRecip; + gfxWorld->outdoorLookupMatrix[3].x = xScale; + gfxWorld->outdoorLookupMatrix[3].y = yScale; + gfxWorld->outdoorLookupMatrix[3].z = zScale; + gfxWorld->outdoorLookupMatrix[3].w = 1.0f; + + std::string outdoorImageName = std::string("$outdoor"); + auto outdoorImageAsset = m_context.LoadDependency(outdoorImageName); + if (outdoorImageAsset == NULL) + { + con::error("ERROR! unable to find outdoor image $outdoor!"); + return false; + } + gfxWorld->outdoorImage = outdoorImageAsset->Asset(); + + return true; + } + AssetCreationResult GfxWorldLinker::linkGfxWorld(BSPData* bsp) { - return AssetCreationResult::Failure(); + GfxWorld* gfxWorld = m_memory.Alloc(); + gfxWorld->baseName = m_memory.Dup(bsp->name.c_str()); + gfxWorld->name = m_memory.Dup(bsp->bspName.c_str()); + + // Default values taken from official maps + gfxWorld->lightingFlags = 0; + gfxWorld->lightingQuality = 4096; + + cleanGfxWorld(gfxWorld); + + if (!overwriteMapSurfaces(bsp, gfxWorld)) + return AssetCreationResult::Failure(); + + overwriteMapSModels(bsp, gfxWorld); + + if (!overwriteLightmapData(gfxWorld)) + return AssetCreationResult::Failure(); + + overwriteSkyBox(bsp, gfxWorld); + + if (!updateReflectionProbeData(gfxWorld)) + return AssetCreationResult::Failure(); + + // world bounds are based on surface mins/maxs + // Other update functions depend on the bounds being set first + updateWorldBounds(gfxWorld); + + if (!updateOutdoors(gfxWorld)) + return AssetCreationResult::Failure(); + + // gfx cells depend on surface/smodel count + updateGfxCells(gfxWorld); + + overwriteLightGrid(gfxWorld); + + overwriteGfxLights(gfxWorld); + + overwriteModels(gfxWorld); + + updateSunData(gfxWorld); + + updateDynEntData(gfxWorld); + + auto gfxWorldAsset = m_context.AddAsset(gfxWorld->name, gfxWorld); + return AssetCreationResult::Success(gfxWorldAsset); } } diff --git a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h index 7d219320..594b51ff 100644 --- a/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h +++ b/src/ObjLoading/Game/T6/BSP/Linker/GfxWorldLinker.h @@ -17,5 +17,19 @@ namespace BSP MemoryManager& m_memory; ISearchPath& m_search_path; AssetCreationContext& m_context; + + void overwriteDrawData(BSPData* projInfo, GfxWorld* gfxWorld); + bool overwriteMapSurfaces(BSPData* projInfo, GfxWorld* gfxWorld); + void overwriteMapSModels(BSPData* projInfo, GfxWorld* gfxWorld); + void cleanGfxWorld(GfxWorld* gfxWorld); + void overwriteGfxLights(GfxWorld* gfxWorld); + void overwriteLightGrid(GfxWorld* gfxWorld); + void updateGfxCells(GfxWorld* gfxWorld); + void overwriteModels(GfxWorld* gfxWorld); + bool updateReflectionProbeData(GfxWorld* gfxWorld); + bool overwriteLightmapData(GfxWorld* gfxWorld); + void overwriteSkyBox(BSPData* projInfo, GfxWorld* gfxWorld); + void updateDynEntData(GfxWorld* gfxWorld); + bool updateOutdoors(GfxWorld* gfxWorld); }; } \ No newline at end of file diff --git a/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp b/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp index b3a02399..74bac61c 100644 --- a/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp +++ b/src/ObjLoading/Game/T6/BSP/LoaderBSP_T6.cpp @@ -1,6 +1,8 @@ #include "LoaderBSP_T6.h" #include "BSPCreator.h" -#include "CustomMapLinker.h" +#include "BSPUtil.h" +#include "Linker/BSPLinker.h" +//#include "CustomMapLinker.h" namespace { @@ -27,17 +29,8 @@ namespace if (bsp == nullptr) return AssetCreationResult::Failure(); - CustomMapLinker linker(m_memory, m_search_path, m_zone, context); - bool result = linker.linkCustomMap(bsp.get()); - - if (result) - { - auto gfxWorldAsset = context.LoadDependency(bsp->bspName); - _ASSERT(gfxWorldAsset != nullptr); - return AssetCreationResult::Success(gfxWorldAsset); - } - else - return AssetCreationResult::Failure(); + BSPLinker linker(m_memory, m_search_path, context); + return linker.linkBSP(bsp.get()); } private: