Added collision model code.

This commit is contained in:
Justin Marshall
2026-08-09 01:29:43 -07:00
parent 1b718f17f6
commit a7fdbe32ad
31 changed files with 13317 additions and 224 deletions
@@ -0,0 +1,901 @@
#include "cm/collisionmodelbuilder.h"
#include "cm/collisionmodel.h"
#include "framework/resourcelist.h"
#include "idlib/geometry/drawvert.h"
#include "idlib/geometry/surface_patch.h"
#include "idlib/lib_print.h"
#include "idlib/sys/sys_alloc.h"
#include "mapfile/mapfile.h"
#include <algorithm>
#include <cfloat>
#include <cmath>
#include <cstring>
#include <malloc.h>
#include <vector>
namespace {
cm_materialBuildInfoExtractor_t materialInfoExtractor = nullptr;
cm_materialResolver_t materialResolver = nullptr;
cm_renderModelBuildExtractor_t renderModelExtractor = nullptr;
cm_staticModelBuildExtractor_t staticModelExtractor = nullptr;
cm_mapFileBuildCallback_t mapFileBuildCallback = nullptr;
idVec3 TransformPoint(const idVec3& point, const idVec3& origin,
const idMat3& axis, const idVec3& scale) {
const idVec3 scaled(point.x * scale.x, point.y * scale.y,
point.z * scale.z);
return origin + idVec3(
axis[0].x * scaled.x + axis[1].x * scaled.y
+ axis[2].x * scaled.z,
axis[0].y * scaled.x + axis[1].y * scaled.y
+ axis[2].y * scaled.z,
axis[0].z * scaled.x + axis[1].z * scaled.y
+ axis[2].z * scaled.z);
}
bool PlaneFromPoints(const idVec3& first, const idVec3& second,
const idVec3& third, idPlane& plane) {
idVec3 normal = (second - first).Cross(third - first);
if (normal.NormalizeFast() == 0.0f) {
return false;
}
plane = idPlane(normal.x, normal.y, normal.z, -normal.Dot(first));
return true;
}
idFixedWinding BasePlaneWinding(const idPlane& plane) {
idVec3 normal(plane.a, plane.b, plane.c);
normal.NormalizeFast();
idVec3 reference = std::fabs(normal.z) < 0.9f
? idVec3(0.0f, 0.0f, 1.0f)
: idVec3(0.0f, 1.0f, 0.0f);
idVec3 right = reference.Cross(normal);
right.NormalizeFast();
idVec3 up = normal.Cross(right);
up.NormalizeFast();
const idVec3 center = normal * -plane.d;
constexpr float radius = 131072.0f;
right = right * radius;
up = up * radius;
idFixedWinding winding;
winding.AddPoint(center - right - up);
winding.AddPoint(center + right - up);
winding.AddPoint(center + right + up);
winding.AddPoint(center - right + up);
return winding;
}
bool ClipWindingToBrush(const idMapBrush& brush, const int sideIndex,
idFixedWinding& winding) {
for (int otherIndex = 0; otherIndex < brush.sides.Num(); ++otherIndex) {
if (otherIndex == sideIndex || brush.sides[otherIndex] == nullptr) {
continue;
}
idFixedWinding back;
const int side = winding.SplitInPlace(
brush.sides[otherIndex]->plane, 0.1f, &back);
if (side == 0) {
winding.Clear();
return false;
}
if (side == 3) {
winding = back;
}
if (winding.GetNumPoints() < 3) {
return false;
}
}
return winding.GetNumPoints() >= 3;
}
void TransformWinding(idFixedWinding& winding, const idVec3& origin,
const idMat3& axis, const idVec3& scale) {
for (int index = 0; index < winding.GetNumPoints(); ++index) {
const idVec3 transformed = TransformPoint(idVec3(
winding[index].x, winding[index].y, winding[index].z),
origin, axis, scale);
winding[index].x = transformed.x;
winding[index].y = transformed.y;
winding[index].z = transformed.z;
}
}
void AddPointToBounds(idBounds& bounds, const idVec3& point) {
for (int axis = 0; axis < 3; ++axis) {
bounds[0][axis] = (std::min)(bounds[0][axis], point[axis]);
bounds[1][axis] = (std::max)(bounds[1][axis], point[axis]);
}
}
void AddBoundsToBounds(idBounds& destination, const idBounds& source) {
for (int axis = 0; axis < 3; ++axis) {
destination[0][axis] = (std::min)(destination[0][axis],
source[0][axis]);
destination[1][axis] = (std::max)(destination[1][axis],
source[1][axis]);
}
}
const idMaterial* ResolveMaterial(const idStr& name,
const idMaterial* const overrideMaterial) {
if (overrideMaterial != nullptr) {
return overrideMaterial;
}
return materialResolver != nullptr ? materialResolver(name.c_str())
: nullptr;
}
bool ExtractModel(const idCollisionModelBuilder::idStaticModelGeometry& g,
cm_modelBuildSource_t& source) {
std::memset(&source, 0, sizeof(source));
if (g.staticModel != nullptr && staticModelExtractor != nullptr) {
return staticModelExtractor(g.staticModel, source);
}
if (g.renderModel != nullptr && renderModelExtractor != nullptr) {
return renderModelExtractor(g.renderModel, source);
}
return false;
}
int PrimitiveGroup(
const idCollisionModelBuilder::idStaticModelGeometry& geometry,
const int primitiveIndex) {
return primitiveIndex >= 0
&& primitiveIndex < geometry.primitiveGroupNumbers.Num()
? geometry.primitiveGroupNumbers[primitiveIndex]
: 0;
}
const idMaterial* MapPrimitiveMaterial(const idMapPrimitive* primitive,
const idMaterial* overrideMaterial) {
if (overrideMaterial != nullptr) {
return overrideMaterial;
}
if (primitive == nullptr) {
return nullptr;
}
if (primitive->type == MAP_PRIMITIVE_PATCH) {
return ResolveMaterial(static_cast<const idMapPatch*>(primitive)
->material, nullptr);
}
const idMapBrush* const brush =
static_cast<const idMapBrush*>(primitive);
return brush->sides.Num() > 0 && brush->sides[0] != nullptr
? ResolveMaterial(brush->sides[0]->material, nullptr) : nullptr;
}
} // namespace
bool CM_GetMaterialBuildInfo(const idMaterial* const material,
cm_materialBuildInfo_t& info) {
info.contents = 1;
info.surfaceFlags = 0;
info.surfaceType = 0;
info.discrete = false;
return materialInfoExtractor != nullptr
? materialInfoExtractor(material, info) : material == nullptr;
}
void idCollisionModelBuilder::SetMaterialBuildInfoExtractor(
const cm_materialBuildInfoExtractor_t extractor) {
materialInfoExtractor = extractor;
}
void idCollisionModelBuilder::SetMaterialResolver(
const cm_materialResolver_t resolver) {
materialResolver = resolver;
}
void idCollisionModelBuilder::SetRenderModelBuildExtractor(
const cm_renderModelBuildExtractor_t extractor) {
renderModelExtractor = extractor;
}
void idCollisionModelBuilder::SetStaticModelBuildExtractor(
const cm_staticModelBuildExtractor_t extractor) {
staticModelExtractor = extractor;
}
void idCollisionModelBuilder::SetMapFileBuildCallback(
const cm_mapFileBuildCallback_t callback) {
mapFileBuildCallback = callback;
}
int idCollisionModelBuilder::SetupBuildGroups(
idCollisionModelGeometry& geometry) {
int nextGroup = 1;
for (int modelIndex = 0; modelIndex < geometry.models.Num();
++modelIndex) {
idStaticModelGeometry& model = geometry.models[modelIndex];
if (model.mapModel != nullptr) {
model.primitiveGroupNumbers.SetNum(
model.mapModel->primitives.Num());
for (int index = 0; index < model.primitiveGroupNumbers.Num();
++index) {
model.primitiveGroupNumbers[index] = 0;
}
continue;
}
cm_modelBuildSource_t source{};
if (!ExtractModel(model, source) || source.numSurfaces < 0) {
model.primitiveGroupNumbers.Clear();
continue;
}
model.primitiveGroupNumbers.SetNum(source.numSurfaces);
bool hasCollisionSurface = false;
for (int index = 0; index < source.numSurfaces; ++index) {
cm_materialBuildInfo_t info{};
CM_GetMaterialBuildInfo(source.surfaces[index].material, info);
hasCollisionSurface |= (info.surfaceFlags & 0x40) != 0;
}
for (int index = 0; index < source.numSurfaces; ++index) {
cm_materialBuildInfo_t info{};
CM_GetMaterialBuildInfo(source.surfaces[index].material, info);
const bool enabled = (info.contents & 0xEBFFFFFF) != 0
&& (!hasCollisionSurface
|| (info.surfaceFlags & 0x40) != 0);
model.primitiveGroupNumbers[index] = !enabled ? -1
: geometry.allowDiscrete && info.discrete
? nextGroup++ : 0;
}
}
return nextGroup;
}
void idCollisionModelBuilder::AddRenderModelEstimates(
const idStaticModelGeometry& geometry, const int groupNum,
const int, int& numVertices, int& numEdges, int& numPolygons,
idBounds& bounds) {
cm_modelBuildSource_t source{};
if (!ExtractModel(geometry, source)) {
return;
}
for (int surfaceIndex = 0; surfaceIndex < source.numSurfaces;
++surfaceIndex) {
if (PrimitiveGroup(geometry, surfaceIndex) != groupNum) {
continue;
}
const cm_modelSurfaceBuildSource_t& surface =
source.surfaces[surfaceIndex];
if (surface.vertices == nullptr || surface.indices == nullptr
|| surface.numIndices < 3) {
continue;
}
numVertices += surface.numVertices;
numEdges += surface.numIndices;
numPolygons += surface.numIndices / 3;
for (int vertex = 0; vertex < surface.numVertices; ++vertex) {
AddPointToBounds(bounds, TransformPoint(
surface.vertices[vertex].xyz, geometry.origin,
geometry.axis, geometry.scale));
}
}
}
void idCollisionModelBuilder::GetMapModelBrushBounds(
const idStaticModelGeometry& geometry, const int groupNum,
idBounds& bounds) {
if (geometry.mapModel == nullptr) {
return;
}
for (int primitiveIndex = 0;
primitiveIndex < geometry.mapModel->primitives.Num();
++primitiveIndex) {
const idMapPrimitive* const primitive =
geometry.mapModel->primitives[primitiveIndex];
if (primitive == nullptr || primitive->type != MAP_PRIMITIVE_BRUSH
|| PrimitiveGroup(geometry, primitiveIndex) != groupNum) {
continue;
}
const idMapBrush& brush = *static_cast<const idMapBrush*>(primitive);
for (int sideIndex = 0; sideIndex < brush.sides.Num(); ++sideIndex) {
if (brush.sides[sideIndex] == nullptr) {
continue;
}
idFixedWinding side = BasePlaneWinding(
brush.sides[sideIndex]->plane);
if (!ClipWindingToBrush(brush, sideIndex, side)) {
continue;
}
TransformWinding(side, geometry.origin, geometry.axis,
geometry.scale);
idBounds sideBounds;
side.GetBounds(sideBounds);
AddBoundsToBounds(bounds, sideBounds);
}
}
}
void idCollisionModelBuilder::AddMapModelEstimates(
const idStaticModelGeometry& geometry, const int groupNum,
const int, int& numVertices, int& numEdges, int& numPolygons,
idBounds& bounds) {
if (geometry.mapModel == nullptr) {
return;
}
GetMapModelBrushBounds(geometry, groupNum, bounds);
for (int primitiveIndex = 0;
primitiveIndex < geometry.mapModel->primitives.Num();
++primitiveIndex) {
const idMapPrimitive* const primitive =
geometry.mapModel->primitives[primitiveIndex];
if (primitive == nullptr
|| PrimitiveGroup(geometry, primitiveIndex) != groupNum) {
continue;
}
if (primitive->type == MAP_PRIMITIVE_BRUSH) {
const int sides = static_cast<const idMapBrush*>(primitive)
->sides.Num();
numVertices += sides * 8;
numEdges += sides * 8;
numPolygons += sides;
} else if (primitive->type == MAP_PRIMITIVE_PATCH) {
const idMapPatch* const patch =
static_cast<const idMapPatch*>(primitive);
numVertices += patch->verts.Num();
numEdges += patch->indexes.Num();
numPolygons += patch->indexes.Num() / 3;
for (int vertex = 0; vertex < patch->verts.Num(); ++vertex) {
AddPointToBounds(bounds, TransformPoint(
patch->verts[vertex].xyz, geometry.origin,
geometry.axis, geometry.scale));
}
}
}
}
void idCollisionModelBuilder::ConvertBrush(cm_buildModel_t* const model,
const idMapBrush* const brush, const idVec3& origin,
const idMat3& axis, const idVec3& scale,
const idMaterial* const overrideMaterial, const int primitiveNum) {
if (model == nullptr || brush == nullptr || brush->sides.Num() < 4) {
return;
}
cm_buildPolytope_t* const polytope = AllocPolytope(model,
brush->sides.Num());
if (polytope == nullptr) {
return;
}
polytope->bounds[0].Set(FLT_MAX, FLT_MAX, FLT_MAX);
polytope->bounds[1].Set(-FLT_MAX, -FLT_MAX, -FLT_MAX);
polytope->primitiveNum = primitiveNum;
const idMaterial* material = overrideMaterial;
for (int sideIndex = 0; sideIndex < brush->sides.Num(); ++sideIndex) {
const idMapBrushSide* const side = brush->sides[sideIndex];
if (side == nullptr) {
polytope->numPlanes = 0;
return;
}
if (material == nullptr) {
material = ResolveMaterial(side->material, nullptr);
}
idFixedWinding winding = BasePlaneWinding(side->plane);
if (!ClipWindingToBrush(*brush, sideIndex, winding)) {
polytope->numPlanes = 0;
return;
}
TransformWinding(winding, origin, axis, scale);
idBounds windingBounds;
winding.GetBounds(windingBounds);
AddBoundsToBounds(polytope->bounds, windingBounds);
idPlane transformedPlane;
if (!PlaneFromPoints(idVec3(winding[0].x, winding[0].y,
winding[0].z), idVec3(winding[1].x, winding[1].y,
winding[1].z), idVec3(winding[2].x, winding[2].y,
winding[2].z), transformedPlane)) {
polytope->numPlanes = 0;
return;
}
model->polytopePlanes[polytope->firstPlane + sideIndex] =
transformedPlane;
}
cm_materialBuildInfo_t info{};
CM_GetMaterialBuildInfo(material, info);
polytope->contents = info.contents;
polytope->material = FindMaterial(model, info.contents,
info.surfaceFlags, info.surfaceType);
if (model->node == nullptr) {
model->node = AllocNode(model, 8);
}
AddBoundsToBounds(model->node->bounds, polytope->bounds);
AddPolytopeToNode(model, model->node, polytope);
}
void idCollisionModelBuilder::ConvertBrushSides(
cm_buildModel_t* const model, const idMapBrush* const brush,
const idVec3& origin, const idMat3& axis, const idVec3& scale,
const idMaterial* const overrideMaterial, const int primitiveNum) {
if (model == nullptr || brush == nullptr) {
return;
}
for (int sideIndex = 0; sideIndex < brush->sides.Num(); ++sideIndex) {
const idMapBrushSide* const side = brush->sides[sideIndex];
if (side == nullptr) {
continue;
}
idFixedWinding winding = BasePlaneWinding(side->plane);
if (!ClipWindingToBrush(*brush, sideIndex, winding)) {
continue;
}
TransformWinding(winding, origin, axis, scale);
idPlane plane;
if (!PlaneFromPoints(idVec3(winding[0].x, winding[0].y,
winding[0].z), idVec3(winding[1].x, winding[1].y,
winding[1].z), idVec3(winding[2].x, winding[2].y,
winding[2].z), plane)) {
continue;
}
const idMaterial* const material = ResolveMaterial(side->material,
overrideMaterial);
cm_materialBuildInfo_t info{};
CM_GetMaterialBuildInfo(material, info);
idFixedWinding* const outside = WindingOutsidePolytopes(model,
&winding, plane, info.contents, primitiveNum);
if (outside != nullptr) {
PolygonFromWinding(model, outside, plane, material,
primitiveNum);
}
}
}
void idCollisionModelBuilder::CreatePatchPolygons(
cm_buildModel_t* const model, const idSurface_Patch* const mesh,
const idVec3& origin, const idMat3& axis, const idVec3& scale,
const idMaterial* const material, const int primitiveNum) {
if (model == nullptr || mesh == nullptr) {
return;
}
std::vector<int> generatedIndices;
const int* indices = mesh->indexes.Num() > 0
? mesh->indexes.Ptr() : nullptr;
int numIndices = mesh->indexes.Num();
if (indices == nullptr && mesh->width > 1 && mesh->height > 1) {
for (int row = 0; row < mesh->height - 1; ++row) {
for (int column = 0; column < mesh->width - 1; ++column) {
const int first = row * mesh->width + column;
generatedIndices.push_back(first);
generatedIndices.push_back(first + 1);
generatedIndices.push_back(first + mesh->width + 1);
generatedIndices.push_back(first);
generatedIndices.push_back(first + mesh->width + 1);
generatedIndices.push_back(first + mesh->width);
}
}
indices = generatedIndices.data();
numIndices = static_cast<int>(generatedIndices.size());
}
for (int index = 0; index + 2 < numIndices; index += 3) {
const int i0 = indices[index + 0];
const int i1 = indices[index + 1];
const int i2 = indices[index + 2];
if (i0 < 0 || i1 < 0 || i2 < 0 || i0 >= mesh->verts.Num()
|| i1 >= mesh->verts.Num() || i2 >= mesh->verts.Num()) {
continue;
}
const idVec3 p0 = TransformPoint(mesh->verts[i0].xyz, origin,
axis, scale);
const idVec3 p1 = TransformPoint(mesh->verts[i1].xyz, origin,
axis, scale);
const idVec3 p2 = TransformPoint(mesh->verts[i2].xyz, origin,
axis, scale);
idPlane plane;
if (!PlaneFromPoints(p0, p1, p2, plane)) {
continue;
}
idFixedWinding winding;
winding.AddPoint(p0);
winding.AddPoint(p1);
winding.AddPoint(p2);
cm_materialBuildInfo_t info{};
CM_GetMaterialBuildInfo(material, info);
idFixedWinding* const outside = WindingOutsidePolytopes(model,
&winding, plane, info.contents, primitiveNum);
if (outside != nullptr) {
PolygonFromWinding(model, outside, plane, material,
primitiveNum);
}
}
}
void idCollisionModelBuilder::ConvertPatch(cm_buildModel_t* const model,
const idMapPatch* const patch, const idVec3& origin,
const idMat3& axis, const idVec3& scale,
const idMaterial* const overrideMaterial, const int primitiveNum) {
if (patch == nullptr) {
return;
}
CreatePatchPolygons(model, patch, origin, axis, scale,
ResolveMaterial(patch->material, overrideMaterial), primitiveNum);
}
void idCollisionModelBuilder::ConvertMapModelPolytopes(
cm_buildModel_t* const model, const idStaticModelGeometry& geometry,
const int groupNum, int primitiveNum) {
if (model == nullptr || geometry.mapModel == nullptr) {
return;
}
for (int index = 0; index < geometry.mapModel->primitives.Num();
++index, ++primitiveNum) {
const idMapPrimitive* const primitive =
geometry.mapModel->primitives[index];
if (primitive != nullptr && primitive->type == MAP_PRIMITIVE_BRUSH
&& PrimitiveGroup(geometry, index) == groupNum) {
ConvertBrush(model, static_cast<const idMapBrush*>(primitive),
geometry.origin, geometry.axis, geometry.scale,
geometry.overrideClipMaterial, primitiveNum);
}
}
}
void idCollisionModelBuilder::ConvertMapModelPrimitives(
cm_buildModel_t* const model, const idStaticModelGeometry& geometry,
const int groupNum, int primitiveNum) {
if (model == nullptr || geometry.mapModel == nullptr) {
return;
}
for (int index = 0; index < geometry.mapModel->primitives.Num();
++index, ++primitiveNum) {
const idMapPrimitive* const primitive =
geometry.mapModel->primitives[index];
if (primitive == nullptr || PrimitiveGroup(geometry, index)
!= groupNum) {
continue;
}
if (primitive->type == MAP_PRIMITIVE_PATCH) {
ConvertPatch(model, static_cast<const idMapPatch*>(primitive),
geometry.origin, geometry.axis, geometry.scale,
geometry.overrideClipMaterial, primitiveNum);
} else if (primitive->type == MAP_PRIMITIVE_BRUSH) {
ConvertBrushSides(model,
static_cast<const idMapBrush*>(primitive), geometry.origin,
geometry.axis, geometry.scale,
geometry.overrideClipMaterial, primitiveNum);
}
}
}
void idCollisionModelBuilder::ConvertRenderModelSurfaces(
cm_buildModel_t* const model, const idStaticModelGeometry& geometry,
const int groupNum, int primitiveNum) {
cm_modelBuildSource_t source{};
if (model == nullptr || !ExtractModel(geometry, source)) {
return;
}
for (int surfaceIndex = 0; surfaceIndex < source.numSurfaces;
++surfaceIndex, ++primitiveNum) {
if (PrimitiveGroup(geometry, surfaceIndex) != groupNum) {
continue;
}
const cm_modelSurfaceBuildSource_t& surface =
source.surfaces[surfaceIndex];
if (surface.vertices == nullptr || surface.indices == nullptr) {
continue;
}
cm_materialBuildInfo_t info{};
CM_GetMaterialBuildInfo(surface.material, info);
for (int index = 0; index + 2 < surface.numIndices; index += 3) {
const int i0 = surface.indices[index + 0];
const int i1 = surface.indices[index + 1];
const int i2 = surface.indices[index + 2];
if (i0 < 0 || i1 < 0 || i2 < 0
|| i0 >= surface.numVertices || i1 >= surface.numVertices
|| i2 >= surface.numVertices) {
continue;
}
const idVec3 p0 = TransformPoint(surface.vertices[i0].xyz,
geometry.origin, geometry.axis, geometry.scale);
const idVec3 p1 = TransformPoint(surface.vertices[i1].xyz,
geometry.origin, geometry.axis, geometry.scale);
const idVec3 p2 = TransformPoint(surface.vertices[i2].xyz,
geometry.origin, geometry.axis, geometry.scale);
idPlane plane;
if (!PlaneFromPoints(p0, p1, p2, plane)) {
continue;
}
idFixedWinding winding;
winding.AddPoint(p0);
winding.AddPoint(p1);
winding.AddPoint(p2);
idFixedWinding* const outside = WindingOutsidePolytopes(model,
&winding, plane, info.contents, primitiveNum);
if (outside != nullptr) {
PolygonFromWinding(model, outside, plane, surface.material,
primitiveNum);
}
}
}
}
void idCollisionModelBuilder::AddCollisionModelGeometry(
idCollisionModelLocal* const collisionModel,
const idCollisionModelGeometry& geometry, const int groupNum) {
if (collisionModel == nullptr) {
return;
}
int numVertices = 0;
int numEdges = 0;
int numPolygons = 0;
int primitiveNum = 0;
idBounds bounds;
bounds[0].Set(FLT_MAX, FLT_MAX, FLT_MAX);
bounds[1].Set(-FLT_MAX, -FLT_MAX, -FLT_MAX);
for (int index = 0; index < geometry.models.Num(); ++index) {
const idStaticModelGeometry& modelGeometry = geometry.models[index];
if (modelGeometry.mapModel != nullptr) {
AddMapModelEstimates(modelGeometry, groupNum, primitiveNum,
numVertices, numEdges, numPolygons, bounds);
primitiveNum += modelGeometry.mapModel->primitives.Num();
} else {
AddRenderModelEstimates(modelGeometry, groupNum, primitiveNum,
numVertices, numEdges, numPolygons, bounds);
cm_modelBuildSource_t source{};
if (ExtractModel(modelGeometry, source)) {
primitiveNum += source.numSurfaces;
}
}
}
if (numVertices == 0 || numPolygons == 0) {
return;
}
cm_buildModel_t* const buildModel = AllocBuildModel();
buildModel->name = collisionModel->GetName();
buildModel->isWorldModel = geometry.isWorldEntity;
buildModel->node = AllocNode(buildModel, 8);
buildModel->node->bounds = bounds;
ClearHash(bounds);
primitiveNum = 0;
for (int index = 0; index < geometry.models.Num(); ++index) {
const idStaticModelGeometry& modelGeometry = geometry.models[index];
if (modelGeometry.mapModel != nullptr) {
ConvertMapModelPolytopes(buildModel, modelGeometry, groupNum,
primitiveNum);
primitiveNum += modelGeometry.mapModel->primitives.Num();
} else {
cm_modelBuildSource_t source{};
if (ExtractModel(modelGeometry, source)) {
primitiveNum += source.numSurfaces;
}
}
}
if (buildModel->numPolytopes > 1) {
CreateAxialBSPTree(buildModel);
}
ClearHash(bounds);
primitiveNum = 0;
for (int index = 0; index < geometry.models.Num(); ++index) {
const idStaticModelGeometry& modelGeometry = geometry.models[index];
if (modelGeometry.mapModel != nullptr) {
ConvertMapModelPrimitives(buildModel, modelGeometry, groupNum,
primitiveNum);
primitiveNum += modelGeometry.mapModel->primitives.Num();
} else {
ConvertRenderModelSurfaces(buildModel, modelGeometry, groupNum,
primitiveNum);
cm_modelBuildSource_t source{};
if (ExtractModel(modelGeometry, source)) {
primitiveNum += source.numSurfaces;
}
}
}
CreateAxialBSPTree(buildModel);
++buildModel->checkCount;
MergeTreePolygons(buildModel, buildModel->node, true);
SplitPolygons(buildModel);
++buildModel->checkCount;
FindInternalEdges(buildModel, buildModel->node);
++buildModel->checkCount;
GenerateEdgeNormals_r(buildModel, buildModel->node);
++buildModel->checkCount;
OffsetPolygonEdges_r(buildModel, buildModel->node);
CreateAxialBSPTree(buildModel);
TestBoundsRange(collisionModel->GetName(), bounds);
AddSubModelsToCollisionModel(collisionModel, buildModel);
FreeModelMemory(buildModel);
delete buildModel;
}
void idCollisionModelBuilder::BuildCollisionModelForGeometry(
idCollisionModelLocal* const model, idCollisionModelGeometry& geometry,
const int* const subModelIndices, const int numSubModelIndices,
const char* const optionalModelName) {
if (model == nullptr) {
return;
}
model->FreeData();
model->SetName(optionalModelName != nullptr ? optionalModelName
: geometry.modelName.c_str());
model->modelType = CM_POLYGONMODEL;
model->bounds[0].Set(FLT_MAX, FLT_MAX, FLT_MAX);
model->bounds[1].Set(-FLT_MAX, -FLT_MAX, -FLT_MAX);
model->contents = 0;
model->sourceFileTime = geometry.fileTime;
model->isWorldModel = geometry.isWorldEntity;
model->isTraceModel = false;
model->isConvex = false;
model->isStreamed = geometry.isStreamed;
const int numGroups = SetupBuildGroups(geometry);
if (subModelIndices != nullptr && numSubModelIndices > 0) {
for (int index = 0; index < numSubModelIndices; ++index) {
AddCollisionModelGeometry(model, geometry,
subModelIndices[index]);
}
} else {
for (int group = 0; group < numGroups; ++group) {
AddCollisionModelGeometry(model, geometry, group);
}
}
MergeModelTrees(model);
}
bool idCollisionModelBuilder::BuildForRenderModel(
idCollisionModelLocal* const model,
const idRenderModel* const renderModel) {
cm_modelBuildSource_t source{};
if (model == nullptr || renderModel == nullptr
|| renderModelExtractor == nullptr
|| !renderModelExtractor(renderModel, source)) {
return false;
}
idStaticModelGeometry modelGeometry;
modelGeometry.renderModel = renderModel;
modelGeometry.origin.Zero();
modelGeometry.axis = idMat3(1.0f);
modelGeometry.scale.Set(1.0f, 1.0f, 1.0f);
idCollisionModelGeometry geometry;
geometry.modelName = source.name != nullptr ? source.name
: "_renderModel";
geometry.fileTime = source.timeStamp;
geometry.allowDiscrete = true;
geometry.models.Append(modelGeometry);
SetupHash();
BuildCollisionModelForGeometry(model, geometry, nullptr, 0, nullptr);
ShutdownHash();
return model->polygonModel.numSubModels > 0;
}
bool idCollisionModelBuilder::BuildForStaticModel(
idCollisionModelLocal* const model,
const idStaticModel* const staticModel,
const int* const subModelIndices, const int numSubModelIndices,
const char* const optionalModelName) {
cm_modelBuildSource_t source{};
if (model == nullptr || staticModel == nullptr
|| staticModelExtractor == nullptr
|| !staticModelExtractor(staticModel, source)) {
return false;
}
idStaticModelGeometry modelGeometry;
modelGeometry.staticModel = staticModel;
modelGeometry.origin.Zero();
modelGeometry.axis = idMat3(1.0f);
modelGeometry.scale.Set(1.0f, 1.0f, 1.0f);
idCollisionModelGeometry geometry;
geometry.modelName = source.name != nullptr ? source.name
: "_staticModel";
geometry.fileTime = source.timeStamp;
geometry.allowDiscrete = true;
geometry.models.Append(modelGeometry);
SetupHash();
BuildCollisionModelForGeometry(model, geometry, subModelIndices,
numSubModelIndices, optionalModelName);
ShutdownHash();
return model->polygonModel.numSubModels > 0;
}
void idCollisionModelBuilder::BuildForMapFile(
const idMapFile* const mapFile, const bool inlineStatic,
const bool mapModelOnly) {
if (mapFileBuildCallback != nullptr) {
mapFileBuildCallback(mapFile, inlineStatic, mapModelOnly);
return;
}
if (mapFile == nullptr) {
return;
}
(void)inlineStatic;
(void)mapModelOnly;
SetupHash();
for (int entityIndex = 0; entityIndex < mapFile->entities.Num();
++entityIndex) {
const idMapEntity* const entity = mapFile->entities[entityIndex];
if (entity == nullptr || entity->model.primitives.Num() == 0) {
continue;
}
idStaticModelGeometry modelGeometry;
modelGeometry.mapModel = &entity->model;
modelGeometry.origin.Zero();
modelGeometry.axis = idMat3(1.0f);
modelGeometry.scale.Set(1.0f, 1.0f, 1.0f);
idCollisionModelGeometry geometry;
if (entityIndex == 0) {
geometry.modelName = mapFile->name;
geometry.isWorldEntity = true;
} else if (entity->model.name.Length() > 0) {
geometry.modelName = entity->model.name;
} else {
char generatedName[64];
_snprintf_s(generatedName, sizeof(generatedName), _TRUNCATE,
"%s_entity_%d", mapFile->name.c_str(), entityIndex);
geometry.modelName = generatedName;
}
geometry.fileTime = mapFile->fileTime;
geometry.models.Append(modelGeometry);
idCollisionModelLocal* const collisionModel =
new idCollisionModelLocal();
BuildCollisionModelForGeometry(collisionModel, geometry, nullptr,
0, nullptr);
if (collisionModel->polygonModel.numSubModels == 0) {
delete collisionModel;
continue;
}
idCollisionModelLocal::resourceList.Add(collisionModel);
collisionModel->Write_Binary();
}
ShutdownHash();
}
void idCollisionModelBuilder::CreateStreamAreas(
idCollisionModelLocal* const model,
const idList<idCollisionModelGeometry, TAG_IDLIB>& geometries) {
if (model == nullptr) {
return;
}
int numAreas = 0;
int nameBytes = 0;
for (int index = 0; index < geometries.Num(); ++index) {
if (geometries[index].isStreamArea) {
++numAreas;
nameBytes += geometries[index].modelName.Length() + 1;
}
}
if (numAreas == 0) {
return;
}
const int numAreaSubModels = numAreas
* model->polygonModel.numSubModels;
const int totalSize = sizeof(streamAreasHeader_t)
+ numAreas * sizeof(streamArea_t)
+ numAreaSubModels * sizeof(std::uint16_t) + nameBytes;
streamAreasHeader_t* const header =
static_cast<streamAreasHeader_t*>(_aligned_malloc(totalSize, 16));
if (header == nullptr) {
return;
}
std::memset(header, 0, totalSize);
header->totalSize = totalSize;
header->numStreamAreas = numAreas;
header->numStreamAreaSubModels = numAreaSubModels;
header->numStreamAreaNameBytes = nameBytes;
streamAreasPtrs_t pointers{};
SetupStreamAreaPtrs(header, pointers);
int areaIndex = 0;
int subModelOffset = 0;
int nameOffset = 0;
for (int index = 0; index < geometries.Num(); ++index) {
const idCollisionModelGeometry& geometry = geometries[index];
if (!geometry.isStreamArea) {
continue;
}
streamArea_t& area = pointers.streamAreas[areaIndex++];
area.volumeNameOffset = nameOffset;
area.volumeOrigin = geometry.streamVolumeOrigin;
area.volumeAxis = geometry.streamVolumeAxis;
area.numSubModels = model->polygonModel.numSubModels;
area.subModelsOffset = subModelOffset;
for (int subModel = 0;
subModel < model->polygonModel.numSubModels; ++subModel) {
pointers.streamAreaSubModels[subModelOffset++] =
static_cast<std::uint16_t>(subModel);
}
const int length = geometry.modelName.Length() + 1;
std::memcpy(pointers.streamAreaNameBytes + nameOffset,
geometry.modelName.c_str(), length);
nameOffset += length;
}
_aligned_free(model->streamAreas);
model->streamAreas = header;
}