2
0
mirror of https://github.com/Laupetin/OpenAssetTools.git synced 2026-06-06 08:42:35 +00:00

chore: added many bound checks to prevent integer overflows and game imposed limits, beautified code, added comments where logic wasn't very obvious.

This commit is contained in:
LJW-Dev
2026-04-30 16:38:16 +08:00
committed by Jan Laupetin
parent 22820f0443
commit d7602ea710
9 changed files with 282 additions and 233 deletions
+27 -8
View File
@@ -272,6 +272,8 @@ namespace
const auto faceCount = indexCount / 3u; const auto faceCount = indexCount / 3u;
if (faceCount > UINT16_MAX) if (faceCount > UINT16_MAX)
throw GltfLoadException(std::format("Face count ({}) on node {} exceeded the UINT16_MAX", faceCount, node.name.value_or("unnamed node"))); throw GltfLoadException(std::format("Face count ({}) on node {} exceeded the UINT16_MAX", faceCount, node.name.value_or("unnamed node")));
if (vertexCount > UINT16_MAX)
throw GltfLoadException(std::format("Vertex count ({}) on node {} exceeded the UINT16_MAX", vertexCount, node.name.value_or("unnamed node")));
out_surface.vertexCount = static_cast<uint16_t>(vertexCount); out_surface.vertexCount = static_cast<uint16_t>(vertexCount);
out_surface.triCount = static_cast<uint16_t>(faceCount); out_surface.triCount = static_cast<uint16_t>(faceCount);
@@ -1013,17 +1015,17 @@ namespace
return addXModelNode(jRoot, node, nodeMatrix); return addXModelNode(jRoot, node, nodeMatrix);
if (hasSpawnpoint) if (hasSpawnpoint)
return addSpawnPointNode(node, nodeMatrix); return addSpawnPointNode(node, nodeMatrix);
if (m_bsp->isZombiesMap) if (m_bsp->isZombiesMap)
{ {
if (hasZone) if (hasZone)
return addZoneNode(jRoot, node, nodeMatrix); return addZoneNode(jRoot, node, nodeMatrix);
if (hasSpawner) if (hasSpawner)
return addZSpawnerNode(node, nodeMatrix); return addZSpawnerNode(node, nodeMatrix);
}
} }
}
if (node.mesh) if (node.mesh)
{ {
@@ -1036,7 +1038,7 @@ namespace
if (m_is_world_gfx && isNoDraw) if (m_is_world_gfx && isNoDraw)
return true; return true;
return addMeshNode(jRoot, node, nodeMatrix); return addMeshNode(jRoot, node, nodeMatrix);
} }
return false; return false;
@@ -1354,6 +1356,7 @@ namespace BSP
bsp->containsWorldspawn = false; bsp->containsWorldspawn = false;
con::warn("XModels don't support scale currently, keep it at 1 in your editor"); con::warn("XModels don't support scale currently, keep it at 1 in your editor");
con::warn("All brushmodels, zones, triggers, and info_volumes must be an axis aligned box with 6 sides to work correctly.");
BSPLoader loader(bsp.get()); BSPLoader loader(bsp.get());
if (isGfxFileGltf) if (isGfxFileGltf)
@@ -1411,7 +1414,6 @@ namespace BSP
bsp->sunlight.innerConeAngle = 0.0f; bsp->sunlight.innerConeAngle = 0.0f;
bsp->sunlight.outerConeAngle = 0.0f; bsp->sunlight.outerConeAngle = 0.0f;
} }
if (!bsp->containsIntermssion) if (!bsp->containsIntermssion)
{ {
con::error("Map does not contain a mp_global_intermission class"); con::error("Map does not contain a mp_global_intermission class");
@@ -1422,6 +1424,23 @@ namespace BSP
con::error("Map does not contain a worldspawn class"); con::error("Map does not contain a worldspawn class");
return nullptr; return nullptr;
} }
if (bsp->spawnpoints.size() == 0)
{
con::error("Map must have spawn points!");
return nullptr;
}
if (bsp->gfxWorld.staticSurfaces.size() + bsp->gfxWorld.scriptSurfaces.size() == 0 || bsp->gfxWorld.vertices.size() == 0
|| bsp->gfxWorld.indices.size() == 0)
{
con::error("GFX world has no surfaces, indicies or vertices!");
return nullptr;
}
if (bsp->colWorld.staticSurfaces.size() + bsp->colWorld.scriptSurfaces.size() == 0 || bsp->colWorld.vertices.size() == 0
|| bsp->colWorld.indices.size() == 0)
{
con::error("Collision world has no surfaces, indicies or vertices!");
return nullptr;
}
return bsp; return bsp;
} }
+18 -13
View File
@@ -26,18 +26,23 @@ namespace BSP
bool BSPLinker::addDefaultRequiredAssets(BSPData* bsp) bool BSPLinker::addDefaultRequiredAssets(BSPData* bsp)
{ {
if (m_context.LoadDependency<AssetScript>("maps/mp/" + bsp->name + ".gsc") == nullptr) if (m_context.LoadDependency<AssetScript>("maps/mp/" + bsp->name + ".gsc") == nullptr)
return false; {
if (m_context.LoadDependency<AssetScript>("maps/mp/" + bsp->name + "_amb.gsc") == nullptr) con::error("maps/mp/" + bsp->name + ".gsc not found, make sure GSC file is in another fastfile.");
return false; }
if (m_context.LoadDependency<AssetScript>("maps/mp/" + bsp->name + "_fx.gsc") == nullptr) else
return false; {
if (m_context.LoadDependency<AssetScript>("maps/mp/" + bsp->name + "_amb.gsc") == nullptr)
return false;
if (m_context.LoadDependency<AssetScript>("maps/mp/" + bsp->name + "_fx.gsc") == nullptr)
return false;
if (m_context.LoadDependency<AssetScript>("clientscripts/mp/" + bsp->name + ".csc") == nullptr) if (m_context.LoadDependency<AssetScript>("clientscripts/mp/" + bsp->name + ".csc") == nullptr)
return false; return false;
if (m_context.LoadDependency<AssetScript>("clientscripts/mp/" + bsp->name + "_amb.csc") == nullptr) if (m_context.LoadDependency<AssetScript>("clientscripts/mp/" + bsp->name + "_amb.csc") == nullptr)
return false; return false;
if (m_context.LoadDependency<AssetScript>("clientscripts/mp/" + bsp->name + "_fx.csc") == nullptr) if (m_context.LoadDependency<AssetScript>("clientscripts/mp/" + bsp->name + "_fx.csc") == nullptr)
return false; return false;
}
addEmptyFootstepTableAsset("default_1st_person"); addEmptyFootstepTableAsset("default_1st_person");
addEmptyFootstepTableAsset("default_3rd_person"); addEmptyFootstepTableAsset("default_3rd_person");
@@ -91,12 +96,12 @@ namespace BSP
return false; return false;
m_context.AddAsset<AssetSkinnedVerts>(skinnedVerts->name, skinnedVerts); m_context.AddAsset<AssetSkinnedVerts>(skinnedVerts->name, skinnedVerts);
GfxWorld* gfxWorld = gfxWorldLinker.linkGfxWorld(bsp); // requires mapents asset GfxWorld* gfxWorld = gfxWorldLinker.linkGfxWorld(bsp);
if (gfxWorld == nullptr) if (gfxWorld == nullptr)
return false; return false;
m_context.AddAsset<AssetGfxWorld>(gfxWorld->name, gfxWorld); m_context.AddAsset<AssetGfxWorld>(gfxWorld->name, gfxWorld);
clipMap_t* clipMap = clipMapLinker.linkClipMap(bsp); // requires gfxworld and mapents asset clipMap_t* clipMap = clipMapLinker.linkClipMap(bsp); // requires mapents asset
if (clipMap == nullptr) if (clipMap == nullptr)
return false; return false;
m_context.AddAsset<AssetClipMap>(clipMap->name, clipMap); m_context.AddAsset<AssetClipMap>(clipMap->name, clipMap);
@@ -134,12 +134,20 @@ namespace BSP
bool ClipMapLinker::loadXModelCollision(clipMap_t* clipMap, BSPData* bsp) bool ClipMapLinker::loadXModelCollision(clipMap_t* clipMap, BSPData* bsp)
{ {
// it seems like for players to be able to collide with xmodels, it requires xmodel->collmaps to be valid. // it seems like for players to be able to collide with xmodels, it requires xmodel->collmaps to be valid.
// A lot of XModels don't implement collmaps (OAT also doesn't generate these collmaps), and // A lot of XModels don't implement collmaps (OAT also doesn't generate these collmaps atm), and
// even official maps instead use terrain or brushes to cover where the collision should be. // even official maps instead use terrain or brushes to cover where the collision should be.
clipMap->numStaticModels = bsp->colWorld.xmodels.size(); clipMap->numStaticModels = bsp->colWorld.xmodels.size();
clipMap->staticModelList = m_memory.Alloc<cStaticModel_s>(clipMap->numStaticModels); clipMap->staticModelList = m_memory.Alloc<cStaticModel_s>(clipMap->numStaticModels);
if (clipMap->numStaticModels == 0)
return true;
if (clipMap->numStaticModels > 0xFFFF)
{
con::error("COLWorld exceeded the maximum number of static xmodels (65535 max, map count: {})", clipMap->numStaticModels);
return false;
}
for (unsigned int i = 0; i < clipMap->numStaticModels; i++) for (unsigned int i = 0; i < clipMap->numStaticModels; i++)
{ {
cStaticModel_s* currModel = &clipMap->staticModelList[i]; cStaticModel_s* currModel = &clipMap->staticModelList[i];
@@ -386,12 +394,8 @@ namespace BSP
// no brushes used so data is set to 0 // no brushes used so data is set to 0
leaf.brushContents = 0; leaf.brushContents = 0;
leaf.mins.x = 0.0f; leaf.mins = {};
leaf.mins.y = 0.0f; leaf.maxs = {};
leaf.mins.z = 0.0f;
leaf.maxs.x = 0.0f;
leaf.maxs.y = 0.0f;
leaf.maxs.z = 0.0f;
leaf.leafBrushNode = 0; leaf.leafBrushNode = 0;
if (tree->leaf->getObjectCount() > 0) if (tree->leaf->getObjectCount() > 0)
@@ -527,8 +531,6 @@ namespace BSP
size_t scriptSurfaceCount = bsp->colWorld.scriptSurfaces.size(); size_t scriptSurfaceCount = bsp->colWorld.scriptSurfaces.size();
size_t totalSurfaceCount = staticSurfaceCount + scriptSurfaceCount; size_t totalSurfaceCount = staticSurfaceCount + scriptSurfaceCount;
// TODO: figure out SV_EntityContact and see how it interacts with partitions
std::vector<uint16_t> triIndexVec; std::vector<uint16_t> triIndexVec;
std::vector<CollisionPartition> partitionVec; std::vector<CollisionPartition> partitionVec;
std::vector<uint16_t> uniqueIndicesVec; std::vector<uint16_t> uniqueIndicesVec;
@@ -598,71 +600,17 @@ namespace BSP
return true; return true;
} }
bool ClipMapLinker::loadWorldCollision(clipMap_t* clipMap, BSPData* bsp) bool ClipMapLinker::loadSubModelCollision(clipMap_t* clipMap, BSPData* bsp)
{
// No support for brushes, only tris right now
clipMap->info.numBrushSides = 0;
clipMap->info.brushsides = nullptr;
clipMap->info.numLeafBrushes = 0;
clipMap->info.leafbrushes = nullptr;
clipMap->info.brushBounds = nullptr;
clipMap->info.brushContents = nullptr;
// first brush node is always empty
cLeafBrushNode_s tempNode;
memset(&tempNode, 0, sizeof(cLeafBrushNode_s));
brushNodeVec.emplace_back(tempNode);
clipMap->info.numBrushVerts = static_cast<uint16_t>(bsp->colWorld.boxBrushVerts.size());
clipMap->info.brushVerts = m_memory.Alloc<vec3_t>(bsp->colWorld.boxBrushVerts.size());
memcpy(clipMap->info.brushVerts, bsp->colWorld.boxBrushVerts.data(), sizeof(vec3_t) * bsp->colWorld.boxBrushVerts.size());
// load verts, tris, uinds and partitions
if (!loadPartitions(clipMap, bsp))
return false;
if (!loadBSPTree(clipMap, bsp))
return false;
loadSubModelCollision(clipMap, bsp); // requires tri verts
clipMap->info.planeCount = static_cast<int>(planeVec.size());
clipMap->info.planes = m_memory.Alloc<cplane_s>(planeVec.size());
memcpy(clipMap->info.planes, planeVec.data(), sizeof(cplane_s) * planeVec.size());
clipMap->numNodes = static_cast<unsigned int>(nodeVec.size());
clipMap->nodes = m_memory.Alloc<cNode_t>(nodeVec.size());
memcpy(clipMap->nodes, nodeVec.data(), sizeof(cNode_t) * nodeVec.size());
clipMap->numLeafs = static_cast<unsigned int>(leafVec.size());
clipMap->leafs = m_memory.Alloc<cLeaf_s>(leafVec.size());
memcpy(clipMap->leafs, leafVec.data(), sizeof(cLeaf_s) * leafVec.size());
clipMap->aabbTreeCount = static_cast<unsigned int>(AABBTreeVec.size());
clipMap->aabbTrees = m_memory.Alloc<CollisionAabbTree>(AABBTreeVec.size());
memcpy(clipMap->aabbTrees, AABBTreeVec.data(), sizeof(CollisionAabbTree) * AABBTreeVec.size());
clipMap->info.leafbrushNodesCount = static_cast<unsigned int>(brushNodeVec.size());
clipMap->info.leafbrushNodes = m_memory.Alloc<cLeafBrushNode_s>(brushNodeVec.size());
memcpy(clipMap->info.leafbrushNodes, brushNodeVec.data(), sizeof(cLeafBrushNode_s) * brushNodeVec.size());
clipMap->info.numBrushes = static_cast<uint16_t>(brushVec.size());
clipMap->info.brushes = m_memory.Alloc<cbrush_array_t>(brushVec.size());
memcpy(clipMap->info.brushes, brushVec.data(), sizeof(cbrush_array_t) * brushVec.size());
// The plane of each node have the same index
for (size_t nodeIdx = 0; nodeIdx < nodeVec.size(); nodeIdx++)
clipMap->nodes[nodeIdx].plane = &clipMap->info.planes[nodeIdx];
return true;
}
void ClipMapLinker::loadSubModelCollision(clipMap_t* clipMap, BSPData* bsp)
{ {
// Submodels are used for the world and map ent collision (triggers, bomb zones, etc) // Submodels are used for the world and map ent collision (triggers, bomb zones, etc)
clipMap->numSubModels = static_cast<unsigned int>(bsp->models.size() + 1); clipMap->numSubModels = static_cast<unsigned int>(bsp->models.size() + 1);
clipMap->cmodels = m_memory.Alloc<cmodel_t>(clipMap->numSubModels); clipMap->cmodels = m_memory.Alloc<cmodel_t>(clipMap->numSubModels);
if (clipMap->numSubModels > 0x3FFF)
{
con::error("ERROR: There are more than 0x3FFF entity brushmodels.");
return false;
}
// first model is always the world model // first model is always the world model
for (unsigned int vertIdx = 0; vertIdx < clipMap->vertCount; vertIdx++) for (unsigned int vertIdx = 0; vertIdx < clipMap->vertCount; vertIdx++)
{ {
@@ -742,10 +690,9 @@ namespace BSP
if (bspModel.hasBrush) if (bspModel.hasBrush)
{ {
BSPBoxBrush& bspBrush = bsp->colWorld.scriptBoxBrushes.at(bspModel.brushIndex); BSPBoxBrush& bspBrush = bsp->colWorld.scriptBoxBrushes.at(bspModel.brushIndex);
vec3_t mins; vec3_t mins = {};
vec3_t maxs; vec3_t maxs = {};
for (size_t vertIdx = 0; vertIdx < bspBrush.vertexCount; vertIdx++) for (size_t vertIdx = 0; vertIdx < bspBrush.vertexCount; vertIdx++)
{ {
vec3_t* vertex = &clipMap->info.brushVerts[bspBrush.vertexIndex + vertIdx]; vec3_t* vertex = &clipMap->info.brushVerts[bspBrush.vertexIndex + vertIdx];
@@ -824,6 +771,89 @@ namespace BSP
clipModel->leaf.cluster = 0; clipModel->leaf.cluster = 0;
clipModel->info = nullptr; clipModel->info = nullptr;
} }
return true;
}
bool ClipMapLinker::loadWorldCollision(clipMap_t* clipMap, BSPData* bsp)
{
// unused brush data
clipMap->info.numBrushSides = 0;
clipMap->info.brushsides = nullptr;
clipMap->info.numLeafBrushes = 0;
clipMap->info.leafbrushes = nullptr;
clipMap->info.brushBounds = nullptr;
clipMap->info.brushContents = nullptr;
// first brush node is always empty
cLeafBrushNode_s tempNode;
memset(&tempNode, 0, sizeof(cLeafBrushNode_s));
brushNodeVec.emplace_back(tempNode);
clipMap->info.numBrushVerts = static_cast<uint16_t>(bsp->colWorld.boxBrushVerts.size());
clipMap->info.brushVerts = m_memory.Alloc<vec3_t>(bsp->colWorld.boxBrushVerts.size());
memcpy(clipMap->info.brushVerts, bsp->colWorld.boxBrushVerts.data(), sizeof(vec3_t) * bsp->colWorld.boxBrushVerts.size());
// load verts, tris, uinds and partitions
if (!loadPartitions(clipMap, bsp))
return false;
if (!loadBSPTree(clipMap, bsp))
return false;
if (!loadSubModelCollision(clipMap, bsp)) // requires tri verts
return false;
if (nodeVec.size() > 0xFFFF)
{
con::error("exceeded 0xFFFF nodes in clipmap");
return false;
}
if (leafVec.size() > 0xFFFF)
{
con::error("exceeded 0xFFFF leafs in clipmap");
return false;
}
if (AABBTreeVec.size() > 0xFFFF)
{
con::error("exceeded 0xFFFF AABBTrees in clipmap");
return false;
}
if (brushVec.size() > 0xFFFF)
{
con::error("exceeded 0xFFFF brushes in clipmap");
return false;
}
clipMap->info.planeCount = static_cast<int>(planeVec.size());
clipMap->info.planes = m_memory.Alloc<cplane_s>(planeVec.size());
memcpy(clipMap->info.planes, planeVec.data(), sizeof(cplane_s) * planeVec.size());
clipMap->numNodes = static_cast<unsigned int>(nodeVec.size());
clipMap->nodes = m_memory.Alloc<cNode_t>(nodeVec.size());
memcpy(clipMap->nodes, nodeVec.data(), sizeof(cNode_t) * nodeVec.size());
clipMap->numLeafs = static_cast<unsigned int>(leafVec.size());
clipMap->leafs = m_memory.Alloc<cLeaf_s>(leafVec.size());
memcpy(clipMap->leafs, leafVec.data(), sizeof(cLeaf_s) * leafVec.size());
clipMap->aabbTreeCount = static_cast<unsigned int>(AABBTreeVec.size());
clipMap->aabbTrees = m_memory.Alloc<CollisionAabbTree>(AABBTreeVec.size());
memcpy(clipMap->aabbTrees, AABBTreeVec.data(), sizeof(CollisionAabbTree) * AABBTreeVec.size());
clipMap->info.leafbrushNodesCount = static_cast<unsigned int>(brushNodeVec.size());
clipMap->info.leafbrushNodes = m_memory.Alloc<cLeafBrushNode_s>(brushNodeVec.size());
memcpy(clipMap->info.leafbrushNodes, brushNodeVec.data(), sizeof(cLeafBrushNode_s) * brushNodeVec.size());
clipMap->info.numBrushes = static_cast<uint16_t>(brushVec.size());
clipMap->info.brushes = m_memory.Alloc<cbrush_array_t>(brushVec.size());
memcpy(clipMap->info.brushes, brushVec.data(), sizeof(cbrush_array_t) * brushVec.size());
// The plane of each node have the same index
for (size_t nodeIdx = 0; nodeIdx < nodeVec.size(); nodeIdx++)
clipMap->nodes[nodeIdx].plane = &clipMap->info.planes[nodeIdx];
return true;
} }
bool ClipMapLinker::loadMaterials(clipMap_t* clipMap, BSPData* bsp) bool ClipMapLinker::loadMaterials(clipMap_t* clipMap, BSPData* bsp)
@@ -30,7 +30,7 @@ namespace BSP
void loadVisibility(clipMap_t* clipMap); void loadVisibility(clipMap_t* clipMap);
void loadDynEnts(clipMap_t* clipMap); void loadDynEnts(clipMap_t* clipMap);
void loadRopesAndConstraints(clipMap_t* clipMap); void loadRopesAndConstraints(clipMap_t* clipMap);
void loadSubModelCollision(clipMap_t* clipMap, BSPData* bsp); bool loadSubModelCollision(clipMap_t* clipMap, BSPData* bsp);
bool loadXModelCollision(clipMap_t* clipMap, BSPData* bsp); bool loadXModelCollision(clipMap_t* clipMap, BSPData* bsp);
std::vector<cplane_s> planeVec; std::vector<cplane_s> planeVec;
@@ -1,7 +1,5 @@
#include "ComWorldLinker.h" #include "ComWorldLinker.h"
#include "../BSPUtil.h"
#include <numbers> #include <numbers>
namespace namespace
@@ -36,7 +34,6 @@ namespace BSP
ComWorld* ComWorldLinker::linkComWorld(BSPData* bsp) ComWorld* ComWorldLinker::linkComWorld(BSPData* bsp)
{ {
// all lights that aren't the sunlight or default light need their own GfxLightDef asset
ComWorld* comWorld = m_memory.Alloc<ComWorld>(); ComWorld* comWorld = m_memory.Alloc<ComWorld>();
comWorld->name = m_memory.Dup(bsp->bspName.c_str()); comWorld->name = m_memory.Dup(bsp->bspName.c_str());
comWorld->isInUse = 1; comWorld->isInUse = 1;
@@ -110,11 +107,11 @@ namespace BSP
light->aAbB.w = 1.0f; light->aAbB.w = 1.0f;
light->cullDist = LIGHT_CULLDIST; light->cullDist = LIGHT_CULLDIST;
light->defName = DEFAULT_LIGHTDEF_NAME; light->defName = DEFAULT_LIGHTDEF_NAME; // all lights that aren't the sunlight or default light need their own GfxLightDef asset
light->rotationLimit = 1.0f; // 1.0f - doesn't rotate, -1.0f - unclamped rotation light->rotationLimit = 1.0f; // 1.0f - doesn't rotate, -1.0f - unclamped rotation
light->translationLimit = 0.0f; // 0.0f - doesn't translate, above 0.0f - distance per game update translated light->translationLimit = 0.0f; // 0.0f - doesn't translate, above 0.0f - distance per game update translated
light->roundness = 1.0f; // 0.0f - light is a square. 1.0f - light is a circle light->roundness = 1.0f; // 0.0f - light is a square. 1.0f - light is a circle
light->canUseShadowMap = 1; // light does not show up with this set to 0 light->canUseShadowMap = 1; // light does not show up with this set to 0
} }
} }
@@ -21,8 +21,9 @@ namespace
} }
constexpr const char* DEFAULT_IMAGE_NAME = ",mc/lambert1"; constexpr const char* DEFAULT_IMAGE_NAME = ",mc/lambert1";
constexpr char EMPTY_LIGHTMAP_INDEX = 0; constexpr char DEFAULT_PRIMARYLIGHT_INDEX = 1; // max 254
constexpr char EMPTY_RPROBE_INDEX = 0; constexpr char DEFAULT_LIGHTMAP_INDEX = 0; // max 30
constexpr char DEFAULT_RPROBE_INDEX = 0; // max 254
constexpr float XMODEL_CULL_DIST = 10000.0f; constexpr float XMODEL_CULL_DIST = 10000.0f;
constexpr char DEFAULT_LIGHTGRID_COLOUR = 32; constexpr char DEFAULT_LIGHTGRID_COLOUR = 32;
} // namespace } // namespace
@@ -65,7 +66,7 @@ namespace BSP
size_t indexCount = bsp->gfxWorld.indices.size(); size_t indexCount = bsp->gfxWorld.indices.size();
assert(indexCount % 3 == 0); assert(indexCount % 3 == 0);
gfxWorld->draw.indexCount = static_cast<int>(indexCount); gfxWorld->draw.indexCount = static_cast<int>(indexCount);
gfxWorld->draw.indices = m_memory.Alloc<uint16_t>(indexCount); gfxWorld->draw.indices = m_memory.Alloc<uint16_t>(indexCount); // overflow checked in bspcreator
static_assert(sizeof(bsp->gfxWorld.indices.data()[0]) == sizeof(uint16_t)); static_assert(sizeof(bsp->gfxWorld.indices.data()[0]) == sizeof(uint16_t));
memcpy(gfxWorld->draw.indices, bsp->gfxWorld.indices.data(), sizeof(uint16_t) * indexCount); memcpy(gfxWorld->draw.indices, bsp->gfxWorld.indices.data(), sizeof(uint16_t) * indexCount);
} }
@@ -78,6 +79,12 @@ namespace BSP
size_t scriptSurfaceCount = bsp->gfxWorld.scriptSurfaces.size(); size_t scriptSurfaceCount = bsp->gfxWorld.scriptSurfaces.size();
size_t totalSurfaceCount = staticSurfaceCount + scriptSurfaceCount; size_t totalSurfaceCount = staticSurfaceCount + scriptSurfaceCount;
if (staticSurfaceCount > 0xffff)
{
con::error("There are more than 65535 Static surfaces. count: {}", staticSurfaceCount);
return false;
}
// Static surfaces go first in the array then script surfaces after // Static surfaces go first in the array then script surfaces after
gfxWorld->surfaceCount = static_cast<int>(totalSurfaceCount); gfxWorld->surfaceCount = static_cast<int>(totalSurfaceCount);
gfxWorld->dpvs.staticSurfaceCount = static_cast<unsigned int>(staticSurfaceCount); gfxWorld->dpvs.staticSurfaceCount = static_cast<unsigned int>(staticSurfaceCount);
@@ -91,9 +98,9 @@ namespace BSP
bspSurface = bsp->gfxWorld.scriptSurfaces.at(surfIdx - staticSurfaceCount); bspSurface = bsp->gfxWorld.scriptSurfaces.at(surfIdx - staticSurfaceCount);
GfxSurface* gfxSurface = &gfxWorld->dpvs.surfaces[surfIdx]; GfxSurface* gfxSurface = &gfxWorld->dpvs.surfaces[surfIdx];
gfxSurface->tris.triCount = bspSurface.triCount; gfxSurface->tris.triCount = bspSurface.triCount; // overflow checked in bspcreator
gfxSurface->tris.baseIndex = bspSurface.indexOfFirstIndex; gfxSurface->tris.baseIndex = bspSurface.indexOfFirstIndex;
gfxSurface->tris.vertexCount = bspSurface.vertexCount; gfxSurface->tris.vertexCount = bspSurface.vertexCount; // overflow checked in bspcreator
gfxSurface->tris.firstVertex = bspSurface.indexOfFirstVertex; gfxSurface->tris.firstVertex = bspSurface.indexOfFirstVertex;
gfxSurface->tris.vertexDataOffset0 = bspSurface.indexOfFirstVertex * sizeof(GfxPackedWorldVertex); gfxSurface->tris.vertexDataOffset0 = bspSurface.indexOfFirstVertex * sizeof(GfxPackedWorldVertex);
@@ -116,23 +123,15 @@ namespace BSP
gfxSurface->material = surfMaterialAsset->Asset(); gfxSurface->material = surfMaterialAsset->Asset();
GfxPackedWorldVertex* firstVert = reinterpret_cast<GfxPackedWorldVertex*>(&gfxWorld->draw.vd0.data[gfxSurface->tris.vertexDataOffset0]); GfxPackedWorldVertex* firstVert = reinterpret_cast<GfxPackedWorldVertex*>(&gfxWorld->draw.vd0.data[gfxSurface->tris.vertexDataOffset0]);
gfxSurface->bounds[0].x = firstVert[0].xyz.x; gfxSurface->bounds[0] = firstVert[0].xyz;
gfxSurface->bounds[0].y = firstVert[0].xyz.y; gfxSurface->bounds[1] = firstVert[0].xyz;
gfxSurface->bounds[0].z = firstVert[0].xyz.z;
gfxSurface->bounds[1].x = firstVert[0].xyz.x;
gfxSurface->bounds[1].y = firstVert[0].xyz.y;
gfxSurface->bounds[1].z = firstVert[0].xyz.z;
for (size_t indexIdx = 0; indexIdx < static_cast<size_t>(gfxSurface->tris.triCount * 3); indexIdx++) for (size_t indexIdx = 0; indexIdx < static_cast<size_t>(gfxSurface->tris.triCount * 3); indexIdx++)
{ {
uint16_t vertIndex = gfxWorld->draw.indices[gfxSurface->tris.baseIndex + indexIdx]; uint16_t vertIndex = gfxWorld->draw.indices[gfxSurface->tris.baseIndex + indexIdx];
BSPUtil::updateAABBWithPoint(firstVert[vertIndex].xyz, gfxSurface->bounds[0], gfxSurface->bounds[1]); BSPUtil::updateAABBWithPoint(firstVert[vertIndex].xyz, gfxSurface->bounds[0], gfxSurface->bounds[1]);
} }
gfxSurface->tris.mins.x = gfxSurface->bounds[0].x; gfxSurface->tris.mins = gfxSurface->bounds[0];
gfxSurface->tris.mins.y = gfxSurface->bounds[0].y; gfxSurface->tris.maxs = gfxSurface->bounds[1];
gfxSurface->tris.mins.z = gfxSurface->bounds[0].z;
gfxSurface->tris.maxs.x = gfxSurface->bounds[1].x;
gfxSurface->tris.maxs.y = gfxSurface->bounds[1].y;
gfxSurface->tris.maxs.z = gfxSurface->bounds[1].z;
gfxSurface->flags = 0; gfxSurface->flags = 0;
if ((bspMaterial.contentFlags & BSPFlags::surfaceTypeToFlagMap[BSPFlags::SURF_TYPE_SKY].contentFlags) != 0) if ((bspMaterial.contentFlags & BSPFlags::surfaceTypeToFlagMap[BSPFlags::SURF_TYPE_SKY].contentFlags) != 0)
@@ -144,12 +143,9 @@ namespace BSP
if ((bspMaterial.surfaceFlags & BSPFlags::surfaceTypeToFlagMap[BSPFlags::SURF_TYPE_NOCASTSHADOW].surfaceFlags) == 0) if ((bspMaterial.surfaceFlags & BSPFlags::surfaceTypeToFlagMap[BSPFlags::SURF_TYPE_NOCASTSHADOW].surfaceFlags) == 0)
gfxSurface->flags |= GFX_SURFACE_CASTS_SHADOW; gfxSurface->flags |= GFX_SURFACE_CASTS_SHADOW;
gfxSurface->primaryLightIndex = SUN_LIGHT_INDEX; gfxSurface->primaryLightIndex = DEFAULT_PRIMARYLIGHT_INDEX;
gfxSurface->lightmapIndex = EMPTY_LIGHTMAP_INDEX; gfxSurface->lightmapIndex = DEFAULT_LIGHTMAP_INDEX;
gfxSurface->reflectionProbeIndex = EMPTY_RPROBE_INDEX; gfxSurface->reflectionProbeIndex = DEFAULT_RPROBE_INDEX;
// unknown value
gfxSurface->tris.himipRadiusInvSq = 0.0f;
} }
// Some code uses Sorted surfs to index surfaces, so for simplicity keep the indexes sequential and from 0 // Some code uses Sorted surfs to index surfaces, so for simplicity keep the indexes sequential and from 0
@@ -191,6 +187,29 @@ namespace BSP
gfxWorld->dpvs.smodelInsts = m_memory.Alloc<GfxStaticModelInst>(modelCount); gfxWorld->dpvs.smodelInsts = m_memory.Alloc<GfxStaticModelInst>(modelCount);
gfxWorld->dpvs.smodelDrawInsts = m_memory.Alloc<GfxStaticModelDrawInst>(modelCount); gfxWorld->dpvs.smodelDrawInsts = m_memory.Alloc<GfxStaticModelDrawInst>(modelCount);
if (modelCount == 0)
return true;
if (modelCount > 0xFFFF)
{
con::error("GFXWorld exceeded the maximum number of static xmodels (65535 max, map count: {})", modelCount);
return false;
}
char color = DEFAULT_LIGHTGRID_COLOUR;
float sunIntensity = bsp->sunlight.intensity;
if (sunIntensity < 0.0f || sunIntensity > 20000.0f)
{
con::error("Sun intensity ({}) needs to be between 0 and 20000", sunIntensity);
return false;
}
else
{
float normalised = sunIntensity / 20000.0f;
normalised *= 255.0f;
float clean = roundf(normalised);
color = static_cast<char>(clean);
}
for (size_t modelIdx = 0; modelIdx < modelCount; modelIdx++) for (size_t modelIdx = 0; modelIdx < modelCount; modelIdx++)
{ {
auto currModel = &gfxWorld->dpvs.smodelDrawInsts[modelIdx]; auto currModel = &gfxWorld->dpvs.smodelDrawInsts[modelIdx];
@@ -206,9 +225,7 @@ namespace BSP
else else
currModel->model = (XModel*)xModelAsset->Asset(); currModel->model = (XModel*)xModelAsset->Asset();
currModel->placement.origin.x = bspModel.origin.x; currModel->placement.origin = bspModel.origin;
currModel->placement.origin.y = bspModel.origin.y;
currModel->placement.origin.z = bspModel.origin.z;
BSPUtil::convertQuaternionToAxis(&bspModel.rotationQuaternion, currModel->placement.axis); BSPUtil::convertQuaternionToAxis(&bspModel.rotationQuaternion, currModel->placement.axis);
currModel->placement.scale = bspModel.scale; currModel->placement.scale = bspModel.scale;
@@ -217,16 +234,11 @@ namespace BSP
currModel->flags |= STATIC_MODEL_FLAG_NO_SHADOW; currModel->flags |= STATIC_MODEL_FLAG_NO_SHADOW;
currModel->cullDist = XMODEL_CULL_DIST; currModel->cullDist = XMODEL_CULL_DIST;
currModel->primaryLightIndex = SUN_LIGHT_INDEX; currModel->primaryLightIndex = DEFAULT_PRIMARYLIGHT_INDEX;
currModel->reflectionProbeIndex = EMPTY_LIGHTMAP_INDEX; currModel->reflectionProbeIndex = DEFAULT_RPROBE_INDEX;
currModel->smid = modelIdx; currModel->smid = modelIdx;
// unknown use / unused currModelInst->lightingOrigin = bspModel.origin;
memset(&currModel->lightingSH, 0, sizeof(GfxLightingSHQuantized));
currModel->invScaleSq = 0.0f;
currModel->lightingHandle = 0; // overwritten to by the game
currModel->colorsIndex = 0;
currModel->visibility = 0;
if (!xModelAsset->IsReference()) if (!xModelAsset->IsReference())
{ {
@@ -249,8 +261,7 @@ namespace BSP
currModel->lmapVertexInfo[lodIdx].numLmapVertexColors = vertCount; currModel->lmapVertexInfo[lodIdx].numLmapVertexColors = vertCount;
currModel->lmapVertexInfo[lodIdx].lmapVertexColors = m_memory.Alloc<unsigned int>(vertCount); currModel->lmapVertexInfo[lodIdx].lmapVertexColors = m_memory.Alloc<unsigned int>(vertCount);
// a value of 1 makes shadowed surfaces slightly easier to see memset(currModel->lmapVertexInfo[lodIdx].lmapVertexColors, color, sizeof(unsigned int) * vertCount);
memset(currModel->lmapVertexInfo[lodIdx].lmapVertexColors, 1, sizeof(unsigned int) * vertCount);
} }
} }
else else
@@ -270,20 +281,7 @@ namespace BSP
currModelInst->maxs.y = bspModel.origin.y + 1.0f; currModelInst->maxs.y = bspModel.origin.y + 1.0f;
currModelInst->maxs.z = bspModel.origin.z + 1.0f; currModelInst->maxs.z = bspModel.origin.z + 1.0f;
} }
// setting these to nullptr makes any static/baked lighting go black when not rendered by real-time lighting or in a shadow
currModel->lmapVertexInfo[0].numLmapVertexColors = 0;
currModel->lmapVertexInfo[0].lmapVertexColors = nullptr;
currModel->lmapVertexInfo[1].numLmapVertexColors = 0;
currModel->lmapVertexInfo[1].lmapVertexColors = nullptr;
currModel->lmapVertexInfo[2].numLmapVertexColors = 0;
currModel->lmapVertexInfo[2].lmapVertexColors = nullptr;
currModel->lmapVertexInfo[3].numLmapVertexColors = 0;
currModel->lmapVertexInfo[3].lmapVertexColors = nullptr;
} }
currModelInst->lightingOrigin.x = 0.0f;
currModelInst->lightingOrigin.y = 0.0f;
currModelInst->lightingOrigin.z = 0.0f;
} }
// visdata is written to by the game // visdata is written to by the game
@@ -299,8 +297,6 @@ namespace BSP
{ {
if ((gfxWorld->dpvs.smodelDrawInsts[i].flags & STATIC_MODEL_FLAG_NO_SHADOW) == 0) if ((gfxWorld->dpvs.smodelDrawInsts[i].flags & STATIC_MODEL_FLAG_NO_SHADOW) == 0)
gfxWorld->dpvs.smodelCastsShadow[i] = 1; gfxWorld->dpvs.smodelCastsShadow[i] = 1;
else
gfxWorld->dpvs.smodelCastsShadow[i] = 0;
} }
return true; return true;
@@ -395,12 +391,18 @@ namespace BSP
gfxWorld->sunLight = m_memory.Alloc<GfxLight>(); gfxWorld->sunLight = m_memory.Alloc<GfxLight>();
} }
void GfxWorldLinker::loadGfxLights(BSPData* bsp, GfxWorld* gfxWorld) bool GfxWorldLinker::loadGfxLights(BSPData* bsp, GfxWorld* gfxWorld)
{ {
// there must be 2 or more lights, first is the static light and second is the sun light // there must be 2 or more lights, first is the static light and second is the sun light
gfxWorld->primaryLightCount = BSP_DEFAULT_LIGHT_COUNT + static_cast<unsigned int>(bsp->lights.size()); gfxWorld->primaryLightCount = BSP_DEFAULT_LIGHT_COUNT + static_cast<unsigned int>(bsp->lights.size());
gfxWorld->sunPrimaryLightIndex = SUN_LIGHT_INDEX; gfxWorld->sunPrimaryLightIndex = SUN_LIGHT_INDEX;
if (gfxWorld->primaryLightCount > 254)
{
con::error("Exceeded 254 lights in BSP, count: {}", gfxWorld->primaryLightCount);
return false;
}
gfxWorld->shadowGeom = m_memory.Alloc<GfxShadowGeometry>(gfxWorld->primaryLightCount); gfxWorld->shadowGeom = m_memory.Alloc<GfxShadowGeometry>(gfxWorld->primaryLightCount);
for (unsigned int lightIdx = 0; lightIdx < gfxWorld->primaryLightCount; lightIdx++) for (unsigned int lightIdx = 0; lightIdx < gfxWorld->primaryLightCount; lightIdx++)
{ {
@@ -416,7 +418,7 @@ namespace BSP
{ {
if ((gfxWorld->dpvs.smodelDrawInsts[modelIdx].flags & STATIC_MODEL_FLAG_NO_SHADOW) != 0) if ((gfxWorld->dpvs.smodelDrawInsts[modelIdx].flags & STATIC_MODEL_FLAG_NO_SHADOW) != 0)
continue; continue;
char lightIndex = gfxWorld->dpvs.smodelDrawInsts[modelIdx].primaryLightIndex; unsigned char lightIndex = gfxWorld->dpvs.smodelDrawInsts[modelIdx].primaryLightIndex;
gfxWorld->shadowGeom[lightIndex].smodelIndex[gfxWorld->shadowGeom[lightIndex].smodelCount] = modelIdx; gfxWorld->shadowGeom[lightIndex].smodelIndex[gfxWorld->shadowGeom[lightIndex].smodelCount] = modelIdx;
gfxWorld->shadowGeom[lightIndex].smodelCount++; gfxWorld->shadowGeom[lightIndex].smodelCount++;
} }
@@ -433,22 +435,41 @@ namespace BSP
gfxWorld->primaryLightEntityShadowVis = m_memory.Alloc<unsigned int>(lightEntShadowVisSize); gfxWorld->primaryLightEntityShadowVis = m_memory.Alloc<unsigned int>(lightEntShadowVisSize);
else else
gfxWorld->primaryLightEntityShadowVis = nullptr; gfxWorld->primaryLightEntityShadowVis = nullptr;
return true;
} }
void GfxWorldLinker::loadLightGrid(BSPData* bsp, GfxWorld* gfxWorld) bool GfxWorldLinker::loadLightGrid(BSPData* bsp, GfxWorld* gfxWorld)
{ {
// gfxWorld->lightGrid.mins[0] = static_cast<uint16_t>((static_cast<int>(gfxWorld->lightGrid.mins[0]) + 0x20000) >> 5); // world to lightgrid coords conversion:
// gfxWorld->lightGrid.mins[1] = static_cast<uint16_t>((static_cast<int>(gfxWorld->lightGrid.mins[1]) + 0x20000) >> 5); // l_x = (w_x + 131072.0f) / 32.0f;
// gfxWorld->lightGrid.mins[2] = static_cast<uint16_t>((static_cast<int>(gfxWorld->lightGrid.mins[2]) + 0x20000) >> 6); // l_y = (w_y + 131072.0f) / 32.0f;
// gfxWorld->lightGrid.maxs[0] = static_cast<uint16_t>((static_cast<int>(gfxWorld->lightGrid.maxs[0]) + 0x20000) >> 5); // l_z = (w_z + 131072.0f) / 64.0f;
// gfxWorld->lightGrid.maxs[1] = static_cast<uint16_t>((static_cast<int>(gfxWorld->lightGrid.maxs[1]) + 0x20000) >> 5); //
// gfxWorld->lightGrid.maxs[2] = static_cast<uint16_t>((static_cast<int>(gfxWorld->lightGrid.maxs[2]) + 0x20000) >> 6); // lightgrid to world coords conversion:
gfxWorld->lightGrid.mins[0] = 100; // w_x = (double)l_x * 32.0f - 131072.0f;
gfxWorld->lightGrid.mins[1] = 100; // w_y = (double)l_y * 32.0f - 131072.0f;
gfxWorld->lightGrid.mins[2] = 100; // w_z = (double)l_z * 64.0f - 131072.0f;
gfxWorld->lightGrid.maxs[0] = 65435; //
gfxWorld->lightGrid.maxs[1] = 65435; // Lightrid coords are bounded by the uint16 limits:
gfxWorld->lightGrid.maxs[2] = 65435; // w_minX = -131072, w_maxX = 1966048
// w_minY = -131072, w_maxY = 1966048
// w_minZ = -131072, w_maxZ = 4063168
if (gfxWorld->mins.x < -131072.0f || gfxWorld->mins.y < -131072.0f || gfxWorld->mins.z < -131072.0f || gfxWorld->maxs.x > 1966048.0f
|| gfxWorld->maxs.y > 1966048.0f || gfxWorld->maxs.z > 4063168.0f)
{
con::error("Lightrid mins/maxs exceeded");
return false;
}
// integer-only method used by the game
gfxWorld->lightGrid.mins[0] = static_cast<uint16_t>((static_cast<int>(gfxWorld->mins.x) + 0x20000) >> 5);
gfxWorld->lightGrid.mins[1] = static_cast<uint16_t>((static_cast<int>(gfxWorld->mins.y) + 0x20000) >> 5);
gfxWorld->lightGrid.mins[2] = static_cast<uint16_t>((static_cast<int>(gfxWorld->mins.z) + 0x20000) >> 6);
gfxWorld->lightGrid.maxs[0] = static_cast<uint16_t>((static_cast<int>(gfxWorld->maxs.x) + 0x20000) >> 5);
gfxWorld->lightGrid.maxs[1] = static_cast<uint16_t>((static_cast<int>(gfxWorld->maxs.y) + 0x20000) >> 5);
gfxWorld->lightGrid.maxs[2] = static_cast<uint16_t>((static_cast<int>(gfxWorld->maxs.z) + 0x20000) >> 6);
gfxWorld->lightGrid.rowAxis = 0; // default value gfxWorld->lightGrid.rowAxis = 0; // default value
gfxWorld->lightGrid.colAxis = 1; // default value gfxWorld->lightGrid.colAxis = 1; // default value
@@ -467,8 +488,8 @@ namespace BSP
row->zStart = 0; row->zStart = 0;
row->zCount = 0xFF; // 0xFF as this seems to be the sweet spot of values row->zCount = 0xFF; // 0xFF as this seems to be the sweet spot of values
row->firstEntry = 0; row->firstEntry = 0;
for (int i = 0; i < 0x200; i++) // set the lookup table to all 0xFF for (int i = 0; i < 0x200; i++)
row->lookupTable[i] = -1; row->lookupTable[i] = -1; // values in the lookup table are subtracted from the sample pos until it reaches 0, so use 0xFF
gfxWorld->lightGrid.rawRowData = reinterpret_cast<aligned_byte_pointer*>(row); gfxWorld->lightGrid.rawRowData = reinterpret_cast<aligned_byte_pointer*>(row);
// entries are looked up based on the lightgrid sample pos (given ingame) and the lightgrid lookup table // entries are looked up based on the lightgrid sample pos (given ingame) and the lightgrid lookup table
@@ -477,7 +498,7 @@ namespace BSP
for (unsigned int i = 0; i < gfxWorld->lightGrid.entryCount; i++) for (unsigned int i = 0; i < gfxWorld->lightGrid.entryCount; i++)
{ {
entryArray[i].colorsIndex = 0; // always index first colour entryArray[i].colorsIndex = 0; // always index first colour
entryArray[i].primaryLightIndex = SUN_LIGHT_INDEX; entryArray[i].primaryLightIndex = DEFAULT_PRIMARYLIGHT_INDEX;
entryArray[i].visibility = 0; entryArray[i].visibility = 0;
} }
gfxWorld->lightGrid.entries = entryArray; gfxWorld->lightGrid.entries = entryArray;
@@ -485,7 +506,10 @@ namespace BSP
char color = DEFAULT_LIGHTGRID_COLOUR; char color = DEFAULT_LIGHTGRID_COLOUR;
float sunIntensity = bsp->sunlight.intensity; float sunIntensity = bsp->sunlight.intensity;
if (sunIntensity < 0.0f || sunIntensity > 20000.0f) if (sunIntensity < 0.0f || sunIntensity > 20000.0f)
con::warn("Sun intensity ({}) needs to be between 0 and 20000", sunIntensity); {
con::error("Sun intensity ({}) needs to be between 0 and 20000", sunIntensity);
return false;
}
else else
{ {
float normalised = sunIntensity / 20000.0f; float normalised = sunIntensity / 20000.0f;
@@ -504,6 +528,8 @@ namespace BSP
gfxWorld->lightGrid.coeffs = nullptr; gfxWorld->lightGrid.coeffs = nullptr;
gfxWorld->lightGrid.skyGridVolumeCount = 0; gfxWorld->lightGrid.skyGridVolumeCount = 0;
gfxWorld->lightGrid.skyGridVolumes = nullptr; gfxWorld->lightGrid.skyGridVolumes = nullptr;
return true;
} }
struct mnode_t struct mnode_t
@@ -530,17 +556,13 @@ namespace BSP
gfxWorld->cells = m_memory.Alloc<GfxCell>(cellCount); gfxWorld->cells = m_memory.Alloc<GfxCell>(cellCount);
gfxWorld->cells[0].portalCount = 0; gfxWorld->cells[0].portalCount = 0;
gfxWorld->cells[0].portals = nullptr; gfxWorld->cells[0].portals = nullptr;
gfxWorld->cells[0].mins.x = gfxWorld->mins.x; gfxWorld->cells[0].mins = gfxWorld->mins;
gfxWorld->cells[0].mins.y = gfxWorld->mins.y; gfxWorld->cells[0].maxs = gfxWorld->maxs;
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 // there is only 1 reflection probe
gfxWorld->cells[0].reflectionProbeCount = 1; gfxWorld->cells[0].reflectionProbeCount = 1;
gfxWorld->cells[0].reflectionProbes = m_memory.Alloc<char>(gfxWorld->cells[0].reflectionProbeCount); gfxWorld->cells[0].reflectionProbes = m_memory.Alloc<char>(gfxWorld->cells[0].reflectionProbeCount);
gfxWorld->cells[0].reflectionProbes[0] = EMPTY_RPROBE_INDEX; gfxWorld->cells[0].reflectionProbes[0] = DEFAULT_RPROBE_INDEX;
// AABB trees are used to detect what should be rendered and what shouldn't // 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 // Just use the first AABB node to hold all models, no optimisation but all models/surfaces wil lbe drawn
@@ -556,12 +578,8 @@ namespace BSP
{ {
gfxWorld->cells[0].aabbTree[0].smodelIndexes[smodelIdx] = smodelIdx; gfxWorld->cells[0].aabbTree[0].smodelIndexes[smodelIdx] = smodelIdx;
} }
gfxWorld->cells[0].aabbTree[0].mins.x = gfxWorld->mins.x; gfxWorld->cells[0].aabbTree[0].mins = gfxWorld->mins;
gfxWorld->cells[0].aabbTree[0].mins.y = gfxWorld->mins.y; gfxWorld->cells[0].aabbTree[0].maxs = gfxWorld->maxs;
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;
// nodes have the struct mnode_t, and there must be at least 1 node (similar to BSP nodes) // nodes have the struct mnode_t, and there must be at least 1 node (similar to BSP nodes)
// Nodes mnode_t.cellIndex indexes gfxWorld->cells // Nodes mnode_t.cellIndex indexes gfxWorld->cells
@@ -595,6 +613,7 @@ namespace BSP
void GfxWorldLinker::loadModels(BSPData* bsp, GfxWorld* gfxWorld) void GfxWorldLinker::loadModels(BSPData* bsp, GfxWorld* gfxWorld)
{ {
// Models (Submodels in the clipmap code) are used for the world and map ent collision (triggers, bomb zones, etc) // Models (Submodels in the clipmap code) are used for the world and map ent collision (triggers, bomb zones, etc)
// bounds are checked in clipmap linker
gfxWorld->modelCount = static_cast<int>(bsp->models.size() + 1); gfxWorld->modelCount = static_cast<int>(bsp->models.size() + 1);
gfxWorld->models = m_memory.Alloc<GfxBrushModel>(gfxWorld->modelCount); gfxWorld->models = m_memory.Alloc<GfxBrushModel>(gfxWorld->modelCount);
@@ -614,6 +633,11 @@ namespace BSP
gfxWorld->models[0].bounds[0], gfxWorld->models[0].bounds[0],
gfxWorld->models[0].bounds[1]); gfxWorld->models[0].bounds[1]);
} }
if (gfxWorld->dpvs.staticSurfaceCount == 0)
{
gfxWorld->models[0].bounds[0] = {};
gfxWorld->models[0].bounds[1] = {};
}
memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable));
for (size_t modelIdx = 0; modelIdx < bsp->models.size(); modelIdx++) for (size_t modelIdx = 0; modelIdx < bsp->models.size(); modelIdx++)
@@ -641,12 +665,8 @@ namespace BSP
{ {
currEntModel->startSurfIndex = -1; // -1 when it doesn't use map surfaces currEntModel->startSurfIndex = -1; // -1 when it doesn't use map surfaces
currEntModel->surfaceCount = 0; currEntModel->surfaceCount = 0;
currEntModel->bounds[0].x = 0.0f; currEntModel->bounds[0] = {};
currEntModel->bounds[0].y = 0.0f; currEntModel->bounds[1] = {};
currEntModel->bounds[0].z = 0.0f;
currEntModel->bounds[1].x = 0.0f;
currEntModel->bounds[1].y = 0.0f;
currEntModel->bounds[1].z = 0.0f;
} }
memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable)); memset(&gfxWorld->models[0].writable, 0, sizeof(GfxBrushModelWritable));
} }
@@ -656,14 +676,13 @@ namespace BSP
{ {
BSPLight& sunlight = bsp->sunlight; BSPLight& sunlight = bsp->sunlight;
vec3_t viewAngles = BSPUtil::convertForwardVectorToViewAngles(sunlight.direction); vec3_t viewAngles = BSPUtil::convertForwardVectorToViewAngles(sunlight.direction);
gfxWorld->sunParse.initWorldSun->angles.x = viewAngles.x; gfxWorld->sunParse.initWorldSun->angles = viewAngles;
gfxWorld->sunParse.initWorldSun->angles.y = viewAngles.y;
gfxWorld->sunParse.initWorldSun->angles.z = viewAngles.z;
gfxWorld->sunParse.initWorldSun->sunCd.x = sunlight.colour.x; gfxWorld->sunParse.initWorldSun->sunCd.x = sunlight.colour.x;
gfxWorld->sunParse.initWorldSun->sunCd.y = sunlight.colour.y; gfxWorld->sunParse.initWorldSun->sunCd.y = sunlight.colour.y;
gfxWorld->sunParse.initWorldSun->sunCd.z = sunlight.colour.z; gfxWorld->sunParse.initWorldSun->sunCd.z = sunlight.colour.z;
gfxWorld->sunParse.initWorldSun->sunCd.w = 1.0f; gfxWorld->sunParse.initWorldSun->sunCd.w = 1.0f;
// fog is not implemented yet, values taken from mp_dig
gfxWorld->sunParse.initWorldFog->baseDist = 150.0f; gfxWorld->sunParse.initWorldFog->baseDist = 150.0f;
gfxWorld->sunParse.initWorldFog->baseHeight = -100.0f; gfxWorld->sunParse.initWorldFog->baseHeight = -100.0f;
gfxWorld->sunParse.initWorldFog->fogColor.x = 2.35f; gfxWorld->sunParse.initWorldFog->fogColor.x = 2.35f;
@@ -690,22 +709,11 @@ namespace BSP
// default values taken from mp_dig // default values taken from mp_dig
gfxWorld->draw.reflectionProbes = m_memory.Alloc<GfxReflectionProbe>(gfxWorld->draw.reflectionProbeCount); gfxWorld->draw.reflectionProbes = m_memory.Alloc<GfxReflectionProbe>(gfxWorld->draw.reflectionProbeCount);
gfxWorld->draw.reflectionProbes[0].mipLodBias = -8.0; gfxWorld->draw.reflectionProbes[0].mipLodBias = -8.0; // always -8.0f
gfxWorld->draw.reflectionProbes[0].origin.x = 0.0f; gfxWorld->draw.reflectionProbes[0].origin = {};
gfxWorld->draw.reflectionProbes[0].origin.y = 0.0f; gfxWorld->draw.reflectionProbes[0].lightingSH.V0 = {};
gfxWorld->draw.reflectionProbes[0].origin.z = 0.0f; gfxWorld->draw.reflectionProbes[0].lightingSH.V1 = {};
gfxWorld->draw.reflectionProbes[0].lightingSH.V0.x = 0.0f; gfxWorld->draw.reflectionProbes[0].lightingSH.V2 = {};
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].probeVolumeCount = 0;
gfxWorld->draw.reflectionProbes[0].probeVolumes = nullptr; gfxWorld->draw.reflectionProbes[0].probeVolumes = nullptr;
@@ -753,7 +761,7 @@ namespace BSP
con::warn("WARN: Unable to load the skybox xmodel {}!", skyBoxName); con::warn("WARN: Unable to load the skybox xmodel {}!", skyBoxName);
} }
// default skybox values from mp_dig // always 0 and 1
gfxWorld->skyDynIntensity.angle0 = 0.0f; gfxWorld->skyDynIntensity.angle0 = 0.0f;
gfxWorld->skyDynIntensity.angle1 = 0.0f; gfxWorld->skyDynIntensity.angle1 = 0.0f;
gfxWorld->skyDynIntensity.factor0 = 1.0f; gfxWorld->skyDynIntensity.factor0 = 1.0f;
@@ -831,9 +839,9 @@ namespace BSP
gfxWorld->baseName = m_memory.Dup(bsp->name.c_str()); gfxWorld->baseName = m_memory.Dup(bsp->name.c_str());
gfxWorld->name = m_memory.Dup(bsp->bspName.c_str()); gfxWorld->name = m_memory.Dup(bsp->bspName.c_str());
// Default values taken from official maps // Default values taken from origins
gfxWorld->lightingFlags = 0; gfxWorld->lightingFlags = 4;
gfxWorld->lightingQuality = 4096; gfxWorld->lightingQuality = 10000;
cleanGfxWorld(gfxWorld); cleanGfxWorld(gfxWorld);
@@ -854,21 +862,22 @@ namespace BSP
// world bounds are based on loaded surface mins/maxs and xmodels // world bounds are based on loaded surface mins/maxs and xmodels
loadWorldBounds(gfxWorld); loadWorldBounds(gfxWorld);
if (!loadOutdoors(gfxWorld)) if (!loadOutdoors(gfxWorld)) // requires world mins/maxs
return nullptr; return nullptr;
// gfx cells depend on surface/smodel count // gfx cells depend on surface/smodel count
loadGfxCells(gfxWorld); loadGfxCells(gfxWorld);
loadLightGrid(bsp, gfxWorld); loadLightGrid(bsp, gfxWorld); // requires world mins/maxs
loadGfxLights(bsp, gfxWorld); // requires xmodels and surfaces if (!loadGfxLights(bsp, gfxWorld)) // requires xmodels and surfaces
return nullptr;
loadModels(bsp, gfxWorld); // requires surfaces loadModels(bsp, gfxWorld); // requires surfaces
loadSunData(bsp, gfxWorld); loadSunData(bsp, gfxWorld);
loadDynEntData(gfxWorld); loadDynEntData(gfxWorld); // requires cells and lights
return gfxWorld; return gfxWorld;
} }
@@ -22,8 +22,8 @@ namespace BSP
bool loadMapSurfaces(BSPData* projInfo, GfxWorld* gfxWorld); bool loadMapSurfaces(BSPData* projInfo, GfxWorld* gfxWorld);
bool loadXModels(BSPData* projInfo, GfxWorld* gfxWorld); bool loadXModels(BSPData* projInfo, GfxWorld* gfxWorld);
void cleanGfxWorld(GfxWorld* gfxWorld); void cleanGfxWorld(GfxWorld* gfxWorld);
void loadGfxLights(BSPData* bsp, GfxWorld* gfxWorld); bool loadGfxLights(BSPData* bsp, GfxWorld* gfxWorld);
void loadLightGrid(BSPData* bsp, GfxWorld* gfxWorld); bool loadLightGrid(BSPData* bsp, GfxWorld* gfxWorld);
void loadGfxCells(GfxWorld* gfxWorld); void loadGfxCells(GfxWorld* gfxWorld);
void loadModels(BSPData* bsp, GfxWorld* gfxWorld); void loadModels(BSPData* bsp, GfxWorld* gfxWorld);
bool loadReflectionProbeData(GfxWorld* gfxWorld); bool loadReflectionProbeData(GfxWorld* gfxWorld);
@@ -2,9 +2,6 @@
#include "../BSPUtil.h" #include "../BSPUtil.h"
#include <nlohmann/json.hpp>
using namespace nlohmann;
namespace namespace
{ {
inline const std::vector<const char*> DEFENDER_SPAWN_POINT_NAMES = {"mp_ctf_spawn_allies", inline const std::vector<const char*> DEFENDER_SPAWN_POINT_NAMES = {"mp_ctf_spawn_allies",
@@ -172,14 +169,6 @@ namespace BSP
mapEnts->entityString = m_memory.Dup(entityString.c_str()); mapEnts->entityString = m_memory.Dup(entityString.c_str());
mapEnts->numEntityChars = static_cast<int>(entityString.length() + 1); // numEntityChars includes the null character mapEnts->numEntityChars = static_cast<int>(entityString.length() + 1); // numEntityChars includes the null character
// don't need these, unused by the game
mapEnts->trigger.count = 0;
mapEnts->trigger.models = nullptr;
mapEnts->trigger.hullCount = 0;
mapEnts->trigger.hulls = nullptr;
mapEnts->trigger.slabCount = 0;
mapEnts->trigger.slabs = nullptr;
return mapEnts; return mapEnts;
} }
} // namespace BSP } // namespace BSP
@@ -12,10 +12,10 @@ namespace BSP
SkinnedVertsDef* SkinnedVertsLinker::linkSkinnedVerts(BSPData* bsp) SkinnedVertsDef* SkinnedVertsLinker::linkSkinnedVerts(BSPData* bsp)
{ {
// maxSkinnedVerts defines how many model verts can be drawn at once // maxSkinnedVerts defines how many model verts can be drawn at once
// Low values cause models not to be drawn, so double origin's maxSkinnedVerts is used as a safe bet // Low values cause models not to be drawn, so double origin's maxSkinnedVerts (163840) is used as a safe bet
SkinnedVertsDef* skinnedVerts = m_memory.Alloc<SkinnedVertsDef>(); SkinnedVertsDef* skinnedVerts = m_memory.Alloc<SkinnedVertsDef>();
skinnedVerts->name = m_memory.Dup("skinnedverts"); skinnedVerts->name = m_memory.Dup("skinnedverts");
skinnedVerts->maxSkinnedVerts = static_cast<unsigned int>(300000); skinnedVerts->maxSkinnedVerts = 300000;
return skinnedVerts; return skinnedVerts;
} }