diff --git a/src/Common/Game/T6/T6.h b/src/Common/Game/T6/T6.h index 72cd851b..252c39e9 100644 --- a/src/Common/Game/T6/T6.h +++ b/src/Common/Game/T6/T6.h @@ -159,6 +159,51 @@ namespace T6 AUFT_NUM_FIELD_TYPES, }; + + struct customMapVertex + { + vec3_t pos; + float binormalSign; + float color[4]; + float texCoord[2]; + vec3_t normal; + vec3_t tangent; + unsigned int packedLmapCoord; + }; + + struct worldSurface + { + char flags; + char lightmapIndex; + std::string materialName; + char primaryLightIndex; + char reflectionProbeIndex; + + int triCount; + int firstVertexIndex; + int firstIndex_Index; + }; + + struct customMapGfx + { + int vertexCount; + customMapVertex* vertices; + + int indexCount; + uint16_t* indices; + + int surfaceCount; + worldSurface* surfaces; + }; + + struct customMapInfo + { + std::string name; + std::string bspName; + + customMapGfx gfxInfo; + }; + using AssetPhysPreset = Asset; using AssetPhysConstraints = Asset; using AssetDestructibleDef = Asset; @@ -208,6 +253,7 @@ namespace T6 using AssetFootstepTable = Asset; using AssetFootstepFxTable = Asset; using AssetZBarrier = Asset; + using AssetCustomMap = Asset; } // namespace T6 DEFINE_ASSET_NAME_ACCESSOR(T6::AssetPhysPreset, name); diff --git a/src/Common/Game/T6/T6_Assets.h b/src/Common/Game/T6/T6_Assets.h index 62f3f300..7e695301 100644 --- a/src/Common/Game/T6/T6_Assets.h +++ b/src/Common/Game/T6/T6_Assets.h @@ -304,6 +304,8 @@ namespace T6 ASSET_TYPE_REPORT = 0x3E, ASSET_TYPE_DEPEND = 0x3F, ASSET_TYPE_FULL_COUNT = 0x40, + + ASSET_TYPE_CUSTOM_MAP = 0x41 }; enum XFileBlock @@ -1244,13 +1246,13 @@ namespace T6 struct GfxWorldVertexData0 { - byte128* data; + byte128* data; // GfxPackedWorldVertex void /*ID3D11Buffer*/* vb; }; struct GfxWorldVertexData1 { - byte128* data; + byte128* data; // GfxPackedWorldVertex void /*ID3D11Buffer*/* vb; }; @@ -5718,6 +5720,11 @@ namespace T6 unsigned int packed; }; + union PackedLmapCoords + { + unsigned int packed; + }; + struct type_align(16) GfxPackedVertex { vec3_t xyz; @@ -5728,6 +5735,17 @@ namespace T6 PackedUnitVec tangent; }; + struct GfxPackedWorldVertex + { + vec3_t xyz; + float binormalSign; + GfxColor color; + PackedTexCoords texCoord; + PackedUnitVec normal; + PackedUnitVec tangent; + PackedLmapCoords lmapCoord; + }; + struct XRigidVertList { uint16_t boneOffset; diff --git a/src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTreePreCalc.h b/src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTreePreCalc.h new file mode 100644 index 00000000..97c26022 --- /dev/null +++ b/src/ObjLoading/Game/T6/CustomMap/BinarySpacePartitionTreePreCalc.h @@ -0,0 +1,232 @@ +#pragma once + +/* +Heavily modified version of https://github.com/sudeshnapal12/Space-Partitioning-Algorithms BSP implementation +Credit to sudeshnapal12 + + +Precalculated, evenly sized BSPs are much more efficient and smaller compared to dynamically creating them +*/ + +#include +#include +#include +#include +#include +#include +#include + +#define MAX_AABB_SIZE 512 // maximum size an AABB tree can be before it becomes a node + +enum PlaneAxis +{ + AXIS_X, + AXIS_Y, + AXIS_Z +}; + +class Object +{ +public: + double low[3]; + double high[3]; + + // custom data + int partitionIndex; // index of the partition the object is based on (custom) + + Object(double min_x, double min_y, double min_z, double max_x, double max_y, double max_z, int partition_Index) + { + low[0] = min_x; + low[1] = min_y, low[2] = min_z; + high[0] = max_x; + high[1] = max_y; + high[2] = max_z; + partitionIndex = partition_Index; + } +}; + +union u_BSPNode; +class BSPTree; + +class BSPLeaf +{ +private: + std::vector objectList; + +public: + BSPLeaf() + { + objectList = std::vector(); + } + + ~BSPLeaf() + { + objectList.clear(); + } + + void addToList(Object* object) + { + objectList.push_back(object); + } + + int getObjectCount() + { + return objectList.size(); + } + + Object* getObject(int index) + { + return objectList.at(index); + } +}; + +enum objectPlaneSide +{ + SIDE_FRONT, + SIDE_BACK, + SIDE_INTERSECTS +}; + +class BSPNode +{ +public: + BSPTree* front; + BSPTree* back; + + PlaneAxis axis; // axis that the split plane is on + double distance; // distance from the origin (0, 0, 0) to the plane + + BSPNode(BSPTree* _front, BSPTree* _back, PlaneAxis _axis, double _distance) + { + front = _front; + back = _back; + axis = _axis; + distance = _distance; + } + + objectPlaneSide objectIsInfront(Object* object) + { + double minCoord, maxCoord; + + // Select the relevant coordinate based on the plane's axis + switch (axis) + { + case AXIS_X: + minCoord = object->low[0]; + maxCoord = object->high[0]; + break; + case AXIS_Y: + minCoord = object->low[1]; + maxCoord = object->high[1]; + break; + case AXIS_Z: + minCoord = object->low[2]; + maxCoord = object->high[2]; + break; + default: + _ASSERT(false); // this should never be executed + } + + // Compare with the plane's distance + if (maxCoord < distance) + { + return SIDE_BACK; // Object is entirely on the negative side + } + else if (minCoord > distance) + { + return SIDE_FRONT; // Object is entirely on the positive side + } + else + { + return SIDE_INTERSECTS; + } + } +}; + +union u_BSPNode +{ + BSPLeaf* leaf; + BSPNode* node; +}; + +class BSPTree +{ +public: + bool isLeaf; + u_BSPNode u; + + int level; // level in the BSP tree + double low[3]; // mins + double high[3]; // maxs + + BSPTree(double min_x, double min_y, double min_z, double max_x, double max_y, double max_z, int _level) + { + low[0] = min_x; + low[1] = min_y, low[2] = min_z; + high[0] = max_x; + high[1] = max_y; + high[2] = max_z; + level = _level; + splitTree(); + } + + // For simplicity, only split across the X and Z axis. + // It is unlikely that there are many layers to a map, and is instead mostly flat + void splitTree() + { + BSPTree* front; + BSPTree* back; + double halfLength; + + if (high[0] - low[0] > MAX_AABB_SIZE) + { + // split along the x axis + halfLength = (low[0] + high[0]) * 0.5f; + front = new BSPTree(halfLength, low[1], low[2], high[0], high[1], high[2], level + 1); + back = new BSPTree(low[0], low[1], low[2], halfLength, high[1], high[2], level + 1); + + isLeaf = false; + u.node = new BSPNode(front, back, AXIS_X, halfLength); + } + else if (high[2] - low[2] > MAX_AABB_SIZE) + { + // split along the z axis + halfLength = (low[2] + high[2]) * 0.5f; + front = new BSPTree(low[0], low[1], halfLength, high[0], high[1], high[2], level + 1); + back = new BSPTree(low[0], low[1], low[2], high[0], high[1], halfLength, level + 1); + + isLeaf = false; + u.node = new BSPNode(front, back, AXIS_Z, halfLength); + } + else + { + isLeaf = true; + u.leaf = new BSPLeaf(); + } + } + + void addObject(Object* object) + { + if (isLeaf) + { + u.leaf->addToList(object); + } + else + { + objectPlaneSide side = u.node->objectIsInfront(object); + if (side == SIDE_FRONT) + { + u.node->front->addObject(object); + } + else if (side == SIDE_BACK) + { + u.node->back->addObject(object); + } + else // intersects + { + u.node->front->addObject(object); + u.node->back->addObject(object); + } + } + } +}; diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h b/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h new file mode 100644 index 00000000..a8b1d3e0 --- /dev/null +++ b/src/ObjLoading/Game/T6/CustomMap/CustomMapConsts.h @@ -0,0 +1,20 @@ +#include + +#define DYN_ENT_COUNT 0 + +// the clipMap->cmodels[0].leaf.terrainContents takes precendence over leaf and material terrain contents + +// material flags determine the features of the surface +// unsure which flag type changes what right now +// -1 results in: no running, water splashes all the time, low friction, slanted angles make you slide very fast +// 1 results in: normal surface features, grenades work, +#define MATERIAL_SURFACE_FLAGS 1 +#define MATERIAL_CONTENT_FLAGS 1 + +// terrain flags: does not change the type of terrain or what features they have +// from testing, as long at it isn't 0 things will work correctly +#define LEAF_TERRAIN_CONTENTS 1 // match all flags +#define WORLD_TERRAIN_CONTENTS 1 // match all flags + +// const std::string defaultMaterialName = "wpc/intro_wall_plaster_light_blue_01"; +const std::string defaultMaterialName = "wpc/dig_plastic_blue"; diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.cpp b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.cpp new file mode 100644 index 00000000..798438e7 --- /dev/null +++ b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.cpp @@ -0,0 +1,1646 @@ +#include "CustomMapLinker.h" + +#include "Utils/Pack.h" +#include "Util.h" +#include "CustomMapConsts.h" +#include "BinarySpacePartitionTreePreCalc.h" + +#include +#include +using json = nlohmann::json; + +class CustomMapLinker +{ + MemoryManager& m_memory; + ISearchPath& m_search_path; + Zone& m_zone; + AssetCreationContext& m_context; + + bool hasLinkFailed; + + CustomMapLinker(MemoryManager& memory, ISearchPath& searchPath, Zone& zone, AssetCreationContext& context) + : m_memory(memory), + m_search_path(searchPath), + m_zone(zone), + m_context(context) + { + hasLinkFailed = false; + } + + // 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(customMapInfo* projInfo, GfxWorld* gfxWorld) + { + int vertexCount = projInfo->gfxInfo.vertexCount; + customMapVertex* worldVertices = projInfo->gfxInfo.vertices; + + gfxWorld->draw.vertexCount = vertexCount; + gfxWorld->draw.vertexDataSize0 = vertexCount * sizeof(GfxPackedWorldVertex); + GfxPackedWorldVertex* vertexBuffer = new GfxPackedWorldVertex[vertexCount]; + for (int i = 0; i < vertexCount; i++) + { + customMapVertex* WorldVertex = &worldVertices[i]; + GfxPackedWorldVertex* GfxVertex = &vertexBuffer[i]; + + GfxVertex->xyz = CMUtil::convertToBO2Coords(WorldVertex->pos); + + GfxVertex->binormalSign = WorldVertex->binormalSign; + + GfxVertex->color.packed = pack32::Vec4PackGfxColor(WorldVertex->color); + + GfxVertex->texCoord.packed = pack32::Vec2PackTexCoordsUV(WorldVertex->texCoord); + + GfxVertex->normal.packed = pack32::Vec3PackUnitVecThirdBased(CMUtil::convertToBO2Coords(WorldVertex->normal).v); + + GfxVertex->tangent.packed = pack32::Vec3PackUnitVecThirdBased(CMUtil::convertToBO2Coords(WorldVertex->tangent).v); + + GfxVertex->lmapCoord.packed = WorldVertex->packedLmapCoord; + } + 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->gfxInfo.indexCount; + _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->gfxInfo.indices[i + 0]; + gfxWorld->draw.indices[i + 1] = projInfo->gfxInfo.indices[i + 1]; + gfxWorld->draw.indices[i + 0] = projInfo->gfxInfo.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; + } + + void overwriteMapSurfaces(customMapInfo* projInfo, GfxWorld* gfxWorld) + { + bool overwriteResult = overwriteDrawData(projInfo, gfxWorld); + if (!overwriteResult) + return; + + unsigned int surfaceCount = projInfo->gfxInfo.surfaceCount; + gfxWorld->surfaceCount = surfaceCount; + gfxWorld->dpvs.staticSurfaceCount = surfaceCount; + gfxWorld->dpvs.surfaces = new GfxSurface[surfaceCount]; + for (int i = 0; i < surfaceCount; i++) + { + auto currSurface = &gfxWorld->dpvs.surfaces[i]; + auto objSurface = &projInfo->gfxInfo.surfaces[i]; + + currSurface->lightmapIndex = objSurface->lightmapIndex; + currSurface->primaryLightIndex = objSurface->primaryLightIndex; + currSurface->reflectionProbeIndex = objSurface->reflectionProbeIndex; + currSurface->flags = objSurface->flags; + + currSurface->tris.triCount = objSurface->triCount; + currSurface->tris.baseIndex = objSurface->firstIndex_Index; + + currSurface->tris.vertexDataOffset0 = objSurface->firstVertexIndex * sizeof(GfxPackedWorldVertex); + currSurface->tris.vertexDataOffset1 = 0; + + auto* assetInfo = m_context.LoadDependency(objSurface->materialName); + if (assetInfo == NULL) + { + printf("Warning: unable to load surface material %s, replacing with the default texture\n", objSurface->materialName.c_str()); + assetInfo = m_context.LoadDependency(defaultMaterialName); + _ASSERT(assetInfo != NULL); + } + currSurface->material = assetInfo->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++) + { + CMUtil::calcNewBoundsWithPoint(&firstVert[k].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; + } + + // sort all vertexes by vertex count + // unsure if this does anything + s_sortedSurf* sortedSurfs = new s_sortedSurf[surfaceCount]; + for (int i = 0; i < surfaceCount; i++) + { + sortedSurfs[i].surfaceIndex = i; + sortedSurfs[i].vertexCount = gfxWorld->dpvs.surfaces[i].tris.triCount * 3; + } + std::sort(sortedSurfs, &sortedSurfs[surfaceCount], compareSurfaces); + + gfxWorld->dpvs.sortedSurfIndex = new uint16_t[surfaceCount]; + for (int i = 0; i < surfaceCount; i++) + { + gfxWorld->dpvs.sortedSurfIndex[i] = sortedSurfs[i].surfaceIndex; + } + + 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 = CMUtil::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; + } + +#define SMODEL_FLAG_NO_SHADOW 1 +#define SMODEL_FLAG_IS_LIT 2 + + void overwriteMapSModels(GfxWorld* gfxWorld) + { + unsigned int modelCount = 0; + 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]; + json currModelJs = js["models"][i]; + + // TODO: load custom xmodels + std::string modelName = currModelJs["model"]; + auto xModelAsset = findAsset(assetPool, ASSET_TYPE_XMODEL, modelName); + if (xModelAsset == NULL) + { + printf("Custom model (%s) not supported!\n", modelName.c_str()); + hasLinkFailed = true; + return; + } + currModel->model = (XModel*)xModelAsset->m_ptr; + + currModel->placement.origin.x = currModelJs["origin"]["x"]; + currModel->placement.origin.y = currModelJs["origin"]["y"]; + currModel->placement.origin.z = currModelJs["origin"]["z"]; + currModel->placement.origin = CMUtil::convertToBO2Coords(currModel->placement.origin); + currModel->placement.axis[0].x = currModelJs["forward"]["x"]; + currModel->placement.axis[0].y = currModelJs["forward"]["y"]; + currModel->placement.axis[0].z = currModelJs["forward"]["z"]; + currModel->placement.axis[1].x = currModelJs["right"]["x"]; + currModel->placement.axis[1].y = currModelJs["right"]["y"]; + currModel->placement.axis[1].z = currModelJs["right"]["z"]; + currModel->placement.axis[2].x = currModelJs["up"]["x"]; + currModel->placement.axis[2].y = currModelJs["up"]["y"]; + currModel->placement.axis[2].z = currModelJs["up"]["z"]; + currModel->placement.scale = currModelJs["scale"]; + + // mins and maxs are calculated in world space not local space + // TODO: mins and maxs are slightly different to what bo2 uses, which results in some wierd culling ingame + 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 = currModelJs["cullDist"]; + currModel->flags = currModelJs["flags"]; + currModel->primaryLightIndex = (unsigned char)currModelJs["primaryLightIndex"]; + currModel->reflectionProbeIndex = (unsigned char)currModelJs["reflectionProbeIndex"]; + + // 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; + } + */ + + // all visdata is alligned by 128 + gfxWorld->dpvs.smodelVisDataCount = CMUtil::allignBy128(modelCount); + gfxWorld->dpvs.smodelVisData[0] = new char[modelCount]; + gfxWorld->dpvs.smodelVisData[1] = new char[modelCount]; + gfxWorld->dpvs.smodelVisData[2] = new char[modelCount]; + gfxWorld->dpvs.smodelVisDataCameraSaved = new char[modelCount]; + gfxWorld->dpvs.smodelCastsShadow = new char[modelCount]; + for (unsigned int i = 0; i < modelCount; i++) + { + if ((gfxWorld->dpvs.smodelDrawInsts[i].flags & SMODEL_FLAG_NO_SHADOW) == 0) + gfxWorld->dpvs.smodelCastsShadow[i] = 1; + else + gfxWorld->dpvs.smodelCastsShadow[i] = 0; + } + memset(gfxWorld->dpvs.smodelVisData[0], 0, modelCount); + memset(gfxWorld->dpvs.smodelVisData[1], 0, modelCount); + memset(gfxWorld->dpvs.smodelVisData[2], 0, modelCount); + memset(gfxWorld->dpvs.smodelVisDataCameraSaved, 0, modelCount); + + gfxWorld->dpvs.usageCount = 0; + } + + void cleanGfxWorld(GfxWorld* gfxWorld) + { + 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 = 1; // the sun is always index 1 + + gfxWorld->shadowGeom = new GfxShadowGeometry[gfxWorld->primaryLightCount]; + for (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 = new GfxLightRegion[gfxWorld->primaryLightCount]; + for (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, 0, lightEntShadowVisSize * sizeof(unsigned int)); + } + else + { + gfxWorld->primaryLightEntityShadowVis = NULL; + } + } + + void overwriteLightGrid(GfxWorld* gfxWorld) + { + gfxWorld->lightGrid.mins[0] = 0; + gfxWorld->lightGrid.mins[1] = 0; + gfxWorld->lightGrid.mins[2] = 0; + gfxWorld->lightGrid.maxs[0] = 0; + gfxWorld->lightGrid.maxs[1] = 0; + gfxWorld->lightGrid.maxs[2] = 0; + + gfxWorld->lightGrid.rowAxis = 0; // default value + gfxWorld->lightGrid.colAxis = 1; // default value + gfxWorld->lightGrid.sunPrimaryLightIndex = 1; // the sun is always index 1 + gfxWorld->lightGrid.offset = 0.0f; + + // if rowDataStart's first value is -1, it will not look up any lightmap data + gfxWorld->lightGrid.rowDataStart = new uint16_t[1]; + *gfxWorld->lightGrid.rowDataStart = (uint16_t)(-1); + + gfxWorld->lightGrid.rawRowDataSize = 0; + gfxWorld->lightGrid.rawRowData = NULL; + + gfxWorld->lightGrid.colorCount = 0; + gfxWorld->lightGrid.colors = NULL; + + gfxWorld->lightGrid.coeffCount = 0; + gfxWorld->lightGrid.coeffs = NULL; + + gfxWorld->lightGrid.entryCount = 0; + gfxWorld->lightGrid.entries = 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].reflectionProbeCount = 0; + gfxWorld->cells[0].reflectionProbes = 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; + + // 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->planeCount = 0; + gfxWorld->dpvsPlanes.nodes = new uint16_t[gfxWorld->nodeCount]; + gfxWorld->dpvsPlanes.nodes[0] = 1; // nodes reference cells by index + 1 + 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++) + { + CMUtil::calcNewBounds(&gfxWorld->dpvs.surfaces[i].bounds[0], &gfxWorld->dpvs.surfaces[i].bounds[1], &gfxWorld->mins, &gfxWorld->maxs); + } + } + + void overwriteModels(GfxWorld* gfxWorld) + { + gfxWorld->modelCount = 1; + gfxWorld->models = new GfxBrushModel[gfxWorld->modelCount]; + + // first model is the world model, the world json doesn't include it so its added here + 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 (int i = 1; i < gfxWorld->modelCount; i++) + //{ + // auto currEntModel = &gfxWorld->models[i]; + // json currEntModelJs = js["entityModels"][i - 1]; + // + // currEntModel->bounds[0].x = currEntModelJs["mins"]["x"]; + // currEntModel->bounds[0].y = currEntModelJs["mins"]["y"]; + // currEntModel->bounds[0].z = currEntModelJs["mins"]["z"]; + // currEntModel->bounds[1].x = currEntModelJs["maxs"]["x"]; + // currEntModel->bounds[1].y = currEntModelJs["maxs"]["y"]; + // currEntModel->bounds[1].z = currEntModelJs["maxs"]["z"]; + // + // currEntModel->surfaceCount = 0; + // currEntModel->startSurfIndex = (unsigned int)(-1); + // memset(&currEntModel->writable, 0, sizeof(GfxBrushModelWritable)); + // } + } + + void updateSunData(GfxWorld* gfxWorld) + { + // default values taken from mp_dig + gfxWorld->sunParse.fogTransitionTime = 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(GfxWorld* gfxWorld) + { + const char* skyBoxName = "skybox_mp_dig"; + gfxWorld->skyBoxModel = skyBoxName; + + if (m_context.LoadDependency(skyBoxName) == NULL) + { + printf("WARN: Unable to find the skybox model %s\n", 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 updateDynEntData(GfxWorld* gfxWorld) + { + gfxWorld->dpvsDyn.dynEntClientCount[0] = DYN_ENT_COUNT + 256; + gfxWorld->dpvsDyn.dynEntClientCount[1] = 0; + gfxWorld->dpvsDyn.dynEntClientWordCount[0] = 1; // needs to be at least 1 + 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, this will crash your game!\n"); + hasLinkFailed = true; + return; + } + gfxWorld->outdoorImage = outdoorImageAsset->Asset(); + } + + void createGfxWorld(customMapInfo* 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(gfxWorld); + + overwriteLightmapData(gfxWorld); + + overwriteSkyBox(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 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; + } + + float distBetweenPoints(vec3_t p1, vec3_t p2) + { + float x = p2.x - p1.x; + float y = p2.y - p1.y; + float z = p2.z - p1.z; + return sqrtf((x * x) + (y * y) + (z * z)); + } + + + + void traverseBSPTreeForCounts(BSPTree* node, int* numPlanes, int* numNodes, int* numLeafs, int* numAABBTrees, int* maxObjsPerLeaf) + { + if (node->isLeaf) + { + (*numLeafs)++; + // there won't be an AABB tree when objectList is empty + if (node->u.leaf->getObjectCount() > 0) + { + *numAABBTrees += node->u.leaf->getObjectCount() + 1; + + if (node->u.leaf->getObjectCount() > *maxObjsPerLeaf) + *maxObjsPerLeaf = node->u.leaf->getObjectCount(); + } + } + else + { + (*numPlanes)++; + (*numNodes)++; + traverseBSPTreeForCounts(node->u.node->front, numPlanes, numNodes, numLeafs, numAABBTrees, maxObjsPerLeaf); + traverseBSPTreeForCounts(node->u.node->back, 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->u.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->u.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->u.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]; + CMUtil::calcNewBoundsWithPoint(&vertCoord, &aabbMins, &aabbMaxs); + } + } + } + // create root AABB node + 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->u.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]; + + CMUtil::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 = 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->u.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->u.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 = node->u.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->u.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 = -node->u.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->u.node->front); + currNode->children[0] = populateBSPTree_r(clipMap, node->u.node->back); + } + else + { + currNode->children[0] = populateBSPTree_r(clipMap, node->u.node->front); + currNode->children[1] = populateBSPTree_r(clipMap, node->u.node->back); + } + + return currNodeIndex; + } + } + + void populateBSPTree(clipMap_t* clipMap, BSPTree* tree) + { + int numPlanes = 0; + int numNodes = 0; + int numLeafs = 0; + int numAABBTrees = 0; + int 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 createClipMap(customMapInfo* 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; + clipMap->box_model.leaf.mins.x = 3.4028235e38; + clipMap->box_model.leaf.mins.y = 3.4028235e38; + clipMap->box_model.leaf.mins.z = 3.4028235e38; + clipMap->box_model.leaf.maxs.x = -3.4028235e38; // for some reason the maxs are negative, and mins are positive + clipMap->box_model.leaf.maxs.y = -3.4028235e38; + clipMap->box_model.leaf.maxs.z = -3.4028235e38; + 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; + + clipMap->originalDynEntCount = DYN_ENT_COUNT; + clipMap->dynEntCount[0] = clipMap->originalDynEntCount + 256; + 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]); + for (int i = 0; i < clipMap->dynEntCount[0]; i++) + { + DynEntityDef* currDef = &clipMap->dynEntDefList[0][i]; + currDef->physConstraints[0] = 0x1FF; + currDef->physConstraints[1] = 0x1FF; + currDef->physConstraints[2] = 0x1FF; + currDef->physConstraints[3] = 0x1FF; + } + + clipMap->numSubModels = 1; + clipMap->cmodels = new cmodel_t[clipMap->numSubModels]; + clipMap->cmodels[0].leaf.firstCollAabbIndex = 0; + clipMap->cmodels[0].leaf.collAabbCount = 0; + clipMap->cmodels[0].leaf.brushContents = 0; + clipMap->cmodels[0].leaf.terrainContents = WORLD_TERRAIN_CONTENTS; + clipMap->cmodels[0].leaf.mins.x = 0.0f; + clipMap->cmodels[0].leaf.mins.y = 0.0f; + clipMap->cmodels[0].leaf.mins.z = 0.0f; + clipMap->cmodels[0].leaf.maxs.x = 0.0f; + clipMap->cmodels[0].leaf.maxs.y = 0.0f; + clipMap->cmodels[0].leaf.maxs.z = 0.0f; + clipMap->cmodels[0].leaf.leafBrushNode = 0; + clipMap->cmodels[0].leaf.cluster = 0; + clipMap->cmodels[0].info = NULL; + + auto gfxWorldAsset = m_context.LoadDependency(projInfo->bspName); + _ASSERT(gfxWorldAsset != NULL); + GfxWorld* gfxWorld = gfxWorldAsset->Asset(); + + clipMap->cmodels[0].mins.x = gfxWorld->models[0].bounds[0].x; + clipMap->cmodels[0].mins.y = gfxWorld->models[0].bounds[0].y; + clipMap->cmodels[0].mins.z = gfxWorld->models[0].bounds[0].z; + clipMap->cmodels[0].maxs.x = gfxWorld->models[0].bounds[1].x; + clipMap->cmodels[0].maxs.y = gfxWorld->models[0].bounds[1].y; + clipMap->cmodels[0].maxs.z = gfxWorld->models[0].bounds[1].z; + clipMap->cmodels[0].radius = distBetweenPoints(clipMap->cmodels[0].mins, clipMap->cmodels[0].maxs) / 2; + + clipMap->numStaticModels = 0; + clipMap->staticModelList = NULL; + + clipMap->info.numMaterials = 1; + clipMap->info.materials = new ClipMaterial[clipMap->info.numMaterials]; + clipMap->info.materials[0].name = _strdup(defaultMaterialName.c_str()); + clipMap->info.materials[0].contentFlags = MATERIAL_CONTENT_FLAGS; + clipMap->info.materials[0].surfaceFlags = MATERIAL_SURFACE_FLAGS; + + int collisionVertexCount = projInfo->gfxInfo.vertexCount; + vec3_t* convertedCollisionVerts = new vec3_t[collisionVertexCount]; + for (int i = 0; i < collisionVertexCount; i++) + { + convertedCollisionVerts[i] = CMUtil::convertToBO2Coords(projInfo->gfxInfo.vertices[i].pos); + } + clipMap->vertCount = collisionVertexCount; + clipMap->verts = convertedCollisionVerts; + + // 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 + const int maxVerts = UINT16_MAX - 1; + if (collisionVertexCount > maxVerts) + { + printf("ERROR: collision vertex count %i exceeds the maximum number: %i!\n", collisionVertexCount, maxVerts); + hasLinkFailed = true; + return; + } + + clipMap->triCount = projInfo->gfxInfo.indexCount / 3; + clipMap->triIndices = (uint16_t(*)[3])(new uint16_t[projInfo->gfxInfo.indexCount]); // 3 indexes per tri + int triIndex = 0; + for (int i = 0; i < projInfo->gfxInfo.indexCount; i += 3) + { + clipMap->triIndices[triIndex][2] = projInfo->gfxInfo.indices[i + 0]; + clipMap->triIndices[triIndex][1] = projInfo->gfxInfo.indices[i + 1]; + clipMap->triIndices[triIndex][0] = projInfo->gfxInfo.indices[i + 2]; + triIndex++; + } + + int partitionCount = 0; + for (int i = 0; i < projInfo->gfxInfo.surfaceCount; i++) + { + int triCount = projInfo->gfxInfo.surfaces[i].triCount; + int subdividedSize = triCount / 16; // integer division used + partitionCount += subdividedSize; + + // add another partition if the triangle count doesn't add up to a multiple of 16 + if (triCount % 16 != 0) + partitionCount++; + } + clipMap->partitionCount = partitionCount; + clipMap->partitions = new CollisionPartition[clipMap->partitionCount]; + + int partitionIndex = 0; + for (int i = 0; i < projInfo->gfxInfo.surfaceCount; i++) + { + int triCount = projInfo->gfxInfo.surfaces[i].triCount; + int firstIndex = projInfo->gfxInfo.surfaces[i].firstIndex_Index; + int firstVertex = projInfo->gfxInfo.surfaces[i].firstVertexIndex; + int firstTri = firstIndex / 3; + + //////////////////////////////////////////////////////////////////////////////////////////////////// + // TODO: fixup tri indices does not fully work correctly, and leaves some gaps or removes faces. + // NEED TO FIX + // fixup the tri indices. + // GfxWorld calculates what vertex to render through vertexBuffer[indexBuffer[index] + firstVertex] + // while clipmap caclulates it through vertexBuffer[indexBuffer[index]] + // this loop fixes the index buffer by adding the firstVertex to each index + for (int k = 0; k < triCount; k++) + { + clipMap->triIndices[k + firstTri][0] += firstVertex; + clipMap->triIndices[k + firstTri][1] += firstVertex; + clipMap->triIndices[k + firstTri][2] += firstVertex; + } + + while (triCount > 0) + { + if (triCount > 16) + clipMap->partitions[partitionIndex].triCount = 16; + else + clipMap->partitions[partitionIndex].triCount = triCount; + + clipMap->partitions[partitionIndex].firstTri = firstTri; + + triCount -= 16; + firstIndex += 16 * 3; + partitionIndex++; + } + } + + int uindCount = 0; + clipMap->info.uinds = new uint16_t[3 * 16 * clipMap->partitionCount]; // 3 vertexes per tri, and max 16 tris per partition + for (int i = 0; i < clipMap->partitionCount; i++) + { + auto currPartition = &clipMap->partitions[i]; + + int uniqueVertBufferCount = 0; + uint16_t uniqueVertBuffer[3 * 16]; // 3 vertexes per tri, and max 16 tris + + 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 (int m = 0; m < uniqueVertBufferCount; m++) + { + if (uniqueVertBuffer[m] == vertIndex) + { + isVertexIndexUnique = false; + break; + } + } + + if (isVertexIndexUnique) + { + uniqueVertBuffer[uniqueVertBufferCount] = vertIndex; + uniqueVertBufferCount++; + } + } + } + + _ASSERT(uniqueVertBufferCount <= 3 * 16); + + currPartition->fuind = uindCount; + currPartition->nuinds = uniqueVertBufferCount; + memcpy(&clipMap->info.uinds[uindCount], uniqueVertBuffer, uniqueVertBufferCount * sizeof(uint16_t)); + uindCount += uniqueVertBufferCount; + } + _ASSERT(uindCount < 3 * 16 * clipMap->partitionCount); + clipMap->info.nuinds = uindCount; + + // 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 + int walkableEdgeSize = (3 * clipMap->triCount + 31) / 32 * 4; + clipMap->triEdgeIsWalkable = new char[walkableEdgeSize]; + memset(clipMap->triEdgeIsWalkable, 0xFF, walkableEdgeSize * sizeof(char)); + + // 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 + + 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 = CMUtil::convertFromBO2Coords(clipMins); + clipMaxs = CMUtil::convertFromBO2Coords(clipMaxs); + for (int i = 1; i < clipMap->vertCount; i++) + { + vec3_t vertCoord = CMUtil::convertFromBO2Coords(clipMap->verts[i]); + CMUtil::calcNewBoundsWithPoint(&vertCoord, &clipMins, &clipMaxs); + } + + BSPTree* tree = new BSPTree(clipMins.x, clipMins.y, clipMins.z, clipMaxs.x, clipMaxs.y, clipMaxs.z, 0); + 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 = CMUtil::convertFromBO2Coords(mins); + maxs = CMUtil::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 = CMUtil::convertFromBO2Coords(clipMap->verts[vertIndex]); + CMUtil::calcNewBoundsWithPoint(&vertCoord, &mins, &maxs); + } + } + + Object* currObject = new Object(mins.x, mins.y, mins.z, maxs.x, maxs.y, maxs.z, i); + + tree->addObject(currObject); + } + + populateBSPTree(clipMap, tree); + + m_context.AddAsset(clipMap->name, clipMap); + } + + void createComWorld(customMapInfo* 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]; + + ComPrimaryLight* defaultLight = &comWorld->primaryLights[0]; + memset(defaultLight, 0, sizeof(ComPrimaryLight)); + + ComPrimaryLight* sunLight = &comWorld->primaryLights[1]; + memset(sunLight, 0, sizeof(ComPrimaryLight)); + sunLight->type = 1; + // Below are default values taken from mp_dig + sunLight->diffuseColor.r = 1.0f; + sunLight->diffuseColor.g = 1.0f; + sunLight->diffuseColor.b = 1.0f; + sunLight->diffuseColor.a = 0.0f; + sunLight->dir.x = -0.2410777360200882; + sunLight->dir.y = -0.8407384753227234; + sunLight->dir.z = 0.48480960726737976; + + m_context.AddAsset(comWorld->name, comWorld); + } + + void createMapEnts(customMapInfo* 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; + + const auto file = m_search_path.Open("custom_map/entities.json"); + if (!file.IsOpen()) + { + printf("WARN: can't find entity json!\n"); + return; + } + + json entJs = json::parse(*file.m_stream); + + std::string entityString; + int entityCount = entJs["entityCount"]; + for (int i = 0; i < entityCount; i++) + { + auto currEntity = entJs["entities"][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"); + } + entityString.pop_back(); // remove newline character from the end of the string + + mapEnts->entityString = _strdup(entityString.c_str()); + mapEnts->numEntityChars = entityString.length() + 1; // numEntityChars includes the null character + + m_context.AddAsset(mapEnts->name, mapEnts); + } + + void createGameWorldMp(customMapInfo* 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(customMapInfo* projInfo) + { + SkinnedVertsDef* skinnedVerts = new SkinnedVertsDef; + skinnedVerts->name = "skinnedverts"; + skinnedVerts->maxSkinnedVerts = projInfo->gfxInfo.vertexCount; + + 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(customMapInfo* projectInfo) + { + if (m_context.LoadDependency("maps/mp/mp_dig.gsc") == NULL) { hasLinkFailed = true; return; } + if (m_context.LoadDependency("maps/mp/mp_dig_amb.gsc") == NULL) { hasLinkFailed = true; return; } + if (m_context.LoadDependency("maps/mp/mp_dig_fx.gsc") == NULL) { hasLinkFailed = true; return; } + if (m_context.LoadDependency("maps/mp/createfx/mp_dig_fx.gsc") == NULL) { hasLinkFailed = true; return; } + if (m_context.LoadDependency("clientscripts/mp/mp_dig.csc") == NULL) { hasLinkFailed = true; return; } + if (m_context.LoadDependency("clientscripts/mp/mp_dig_amb.csc") == NULL) { hasLinkFailed = true; return; } + if (m_context.LoadDependency("clientscripts/mp/mp_dig_fx.csc") == NULL) { hasLinkFailed = true; return; } + if (m_context.LoadDependency("clientscripts/mp/createfx/mp_dig_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"); + } + + bool linkCustomMap(customMapInfo* projInfo) + { + _ASSERT(projInfo != NULL); + + createGfxWorld(projInfo); + createComWorld(projInfo); + createMapEnts(projInfo); + createGameWorldMp(projInfo); + createSkinnedVerts(projInfo); + createClipMap(projInfo); // must go last (requires gfx and mapents asset) + + checkAndAddDefaultRequiredAssets(projInfo); + + if (hasLinkFailed) + { + printf("Custom Map link has failed.\n"); + return false; + } + + return true; + } +}; diff --git a/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h new file mode 100644 index 00000000..6dcefb44 --- /dev/null +++ b/src/ObjLoading/Game/T6/CustomMap/CustomMapLinker.h @@ -0,0 +1,14 @@ +#include "Game/T6/T6.h" +#include "SearchPath/ISearchPath.h" +#include "Utils/MemoryManager.h" +#include "Asset/IAssetCreator.h" +using namespace T6; + +#include "OBJ_Loader.h" + +class CustomMapLinker +{ +public: + CustomMapLinker(MemoryManager& memory, ISearchPath& searchPath, Zone& zone); + bool linkCustomMap(customMapInfo* projInfo); +}; diff --git a/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp b/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp new file mode 100644 index 00000000..284cb1b3 --- /dev/null +++ b/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.cpp @@ -0,0 +1,57 @@ +#include "LoaderCustomMapT6.h" +#include "ProjectCreator.h" +#include "CustomMapLinker.h" + +#include "Game/T6/T6.h" + +#include + +using namespace T6; + +namespace +{ + class CustomMapLoader final : public AssetCreator + { + public: + CustomMapLoader(MemoryManager& memory, ISearchPath& searchPath, Zone& zone) + : m_memory(memory), + m_search_path(searchPath), + m_zone(zone) + { + } + + AssetCreationResult CreateAsset(const std::string& assetName, AssetCreationContext& context) override + { + auto mapFile = m_search_path.Open("custom_map/map.obj"); + if (!mapFile.IsOpen()) + return AssetCreationResult::NoAction(); + + // create map info from the obj file + customMapInfo* mapInfo = CustomMapInfo::createCustomMapInfo(m_zone.m_name, m_search_path); + if (mapInfo == NULL) + return AssetCreationResult::Failure(); + + // linker will add all the assets needed + CustomMapLinker* linker = new CustomMapLinker(m_memory, m_search_path, m_zone); + bool result = linker->linkCustomMap(mapInfo); + + if (result) + return AssetCreationResult::NoAction(); + else + return AssetCreationResult::Failure(); + } + + private: + MemoryManager& m_memory; + ISearchPath& m_search_path; + Zone& m_zone; + }; +} // namespace + +namespace T6 +{ + std::unique_ptr> CreateCustomMapLoader(MemoryManager& memory, ISearchPath& searchPath, Zone& zone) + { + return std::make_unique(memory, searchPath, zone); + } +} // namespace T6 diff --git a/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.h b/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.h new file mode 100644 index 00000000..13862ba6 --- /dev/null +++ b/src/ObjLoading/Game/T6/CustomMap/LoaderCustomMapT6.h @@ -0,0 +1,13 @@ +#pragma once + +#include "Asset/IAssetCreator.h" +#include "Game/T6/T6.h" +#include "SearchPath/ISearchPath.h" +#include "Utils/MemoryManager.h" + +#include + +namespace T6 +{ + std::unique_ptr> CreateCustomMapLoader(MemoryManager& memory, ISearchPath& searchPath); +} // namespace T6 diff --git a/src/ObjLoading/Game/T6/CustomMap/OBJ_Loader.h b/src/ObjLoading/Game/T6/CustomMap/OBJ_Loader.h new file mode 100644 index 00000000..d73226ee --- /dev/null +++ b/src/ObjLoading/Game/T6/CustomMap/OBJ_Loader.h @@ -0,0 +1,1160 @@ +// OBJ_Loader.h - A Single Header OBJ Model Loader + +#pragma once + +// Iostream - STD I/O Library +#include + +// Vector - STD Vector/Array Library +#include + +// String - STD String Library +#include + +// fStream - STD File I/O Library +#include + +// Math.h - STD math Library +#include + +// Print progress to console while loading (large models) +// #define OBJL_CONSOLE_OUTPUT + +// Namespace: OBJL +// +// Description: The namespace that holds eveyrthing that +// is needed and used for the OBJ Model Loader +namespace objl +{ + // Structure: Vector2 + // + // Description: A 2D Vector that Holds Positional Data + struct Vector2 + { + // Default Constructor + Vector2() + { + X = 0.0f; + Y = 0.0f; + } + + // Variable Set Constructor + Vector2(float X_, float Y_) + { + X = X_; + Y = Y_; + } + + // Bool Equals Operator Overload + bool operator==(const Vector2& other) const + { + return (this->X == other.X && this->Y == other.Y); + } + + // Bool Not Equals Operator Overload + bool operator!=(const Vector2& other) const + { + return !(this->X == other.X && this->Y == other.Y); + } + + // Addition Operator Overload + Vector2 operator+(const Vector2& right) const + { + return Vector2(this->X + right.X, this->Y + right.Y); + } + + // Subtraction Operator Overload + Vector2 operator-(const Vector2& right) const + { + return Vector2(this->X - right.X, this->Y - right.Y); + } + + // Float Multiplication Operator Overload + Vector2 operator*(const float& other) const + { + return Vector2(this->X * other, this->Y * other); + } + + // Positional Variables + float X; + float Y; + }; + + // Structure: Vector3 + // + // Description: A 3D Vector that Holds Positional Data + struct Vector3 + { + // Default Constructor + Vector3() + { + X = 0.0f; + Y = 0.0f; + Z = 0.0f; + } + + // Variable Set Constructor + Vector3(float X_, float Y_, float Z_) + { + X = X_; + Y = Y_; + Z = Z_; + } + + // Bool Equals Operator Overload + bool operator==(const Vector3& other) const + { + return (this->X == other.X && this->Y == other.Y && this->Z == other.Z); + } + + // Bool Not Equals Operator Overload + bool operator!=(const Vector3& other) const + { + return !(this->X == other.X && this->Y == other.Y && this->Z == other.Z); + } + + // Addition Operator Overload + Vector3 operator+(const Vector3& right) const + { + return Vector3(this->X + right.X, this->Y + right.Y, this->Z + right.Z); + } + + // Subtraction Operator Overload + Vector3 operator-(const Vector3& right) const + { + return Vector3(this->X - right.X, this->Y - right.Y, this->Z - right.Z); + } + + // Float Multiplication Operator Overload + Vector3 operator*(const float& other) const + { + return Vector3(this->X * other, this->Y * other, this->Z * other); + } + + // Float Division Operator Overload + Vector3 operator/(const float& other) const + { + return Vector3(this->X / other, this->Y / other, this->Z / other); + } + + // Positional Variables + float X; + float Y; + float Z; + }; + + // Structure: Vertex + // + // Description: Model Vertex object that holds + // a Position, Normal, and Texture Coordinate + struct Vertex + { + // Position Vector + Vector3 Position; + + // Normal Vector + Vector3 Normal; + + // Texture Coordinate Vector + Vector2 TextureCoordinate; + }; + + struct Material + { + Material() + { + name; + Ns = 0.0f; + Ni = 0.0f; + d = 0.0f; + illum = 0; + } + + // Material Name + std::string name; + // Ambient Color + Vector3 Ka; + // Diffuse Color + Vector3 Kd; + // Specular Color + Vector3 Ks; + // Specular Exponent + float Ns; + // Optical Density + float Ni; + // Dissolve + float d; + // Illumination + int illum; + // Ambient Texture Map + std::string map_Ka; + // Diffuse Texture Map + std::string map_Kd; + // Specular Texture Map + std::string map_Ks; + // Specular Hightlight Map + std::string map_Ns; + // Alpha Texture Map + std::string map_d; + // Bump Map + std::string map_bump; + }; + + // Structure: Mesh + // + // Description: A Simple Mesh Object that holds + // a name, a vertex list, and an index list + struct Mesh + { + // Default Constructor + Mesh() {} + + // Variable Set Constructor + Mesh(std::vector& _Vertices, std::vector& _Indices) + { + Vertices = _Vertices; + Indices = _Indices; + } + + // Mesh Name + std::string MeshName; + // Vertex List + std::vector Vertices; + // Index List + std::vector Indices; + + // Material + Material MeshMaterial; + }; + + // Namespace: Math + // + // Description: The namespace that holds all of the math + // functions need for OBJL + namespace math + { + // Vector3 Cross Product + Vector3 CrossV3(const Vector3 a, const Vector3 b) + { + return Vector3(a.Y * b.Z - a.Z * b.Y, a.Z * b.X - a.X * b.Z, a.X * b.Y - a.Y * b.X); + } + + // Vector3 Magnitude Calculation + float MagnitudeV3(const Vector3 in) + { + return (sqrtf(powf(in.X, 2) + powf(in.Y, 2) + powf(in.Z, 2))); + } + + // Vector3 DotProduct + float DotV3(const Vector3 a, const Vector3 b) + { + return (a.X * b.X) + (a.Y * b.Y) + (a.Z * b.Z); + } + + // Angle between 2 Vector3 Objects + float AngleBetweenV3(const Vector3 a, const Vector3 b) + { + float angle = DotV3(a, b); + angle /= (MagnitudeV3(a) * MagnitudeV3(b)); + return angle = acosf(angle); + } + + // Projection Calculation of a onto b + Vector3 ProjV3(const Vector3 a, const Vector3 b) + { + Vector3 bn = b / MagnitudeV3(b); + return bn * DotV3(a, bn); + } + } // namespace math + + // Namespace: Algorithm + // + // Description: The namespace that holds all of the + // Algorithms needed for OBJL + namespace algorithm + { + // Vector3 Multiplication Opertor Overload + Vector3 operator*(const float& left, const Vector3& right) + { + return Vector3(right.X * left, right.Y * left, right.Z * left); + } + + // A test to see if P1 is on the same side as P2 of a line segment ab + bool SameSide(Vector3 p1, Vector3 p2, Vector3 a, Vector3 b) + { + Vector3 cp1 = math::CrossV3(b - a, p1 - a); + Vector3 cp2 = math::CrossV3(b - a, p2 - a); + + if (math::DotV3(cp1, cp2) >= 0) + return true; + else + return false; + } + + // Generate a cross produect normal for a triangle + Vector3 GenTriNormal(Vector3 t1, Vector3 t2, Vector3 t3) + { + Vector3 u = t2 - t1; + Vector3 v = t3 - t1; + + Vector3 normal = math::CrossV3(u, v); + + return normal; + } + + // Check to see if a Vector3 Point is within a 3 Vector3 Triangle + bool inTriangle(Vector3 point, Vector3 tri1, Vector3 tri2, Vector3 tri3) + { + // Test to see if it is within an infinite prism that the triangle outlines. + bool within_tri_prisim = SameSide(point, tri1, tri2, tri3) && SameSide(point, tri2, tri1, tri3) && SameSide(point, tri3, tri1, tri2); + + // If it isn't it will never be on the triangle + if (!within_tri_prisim) + return false; + + // Calulate Triangle's Normal + Vector3 n = GenTriNormal(tri1, tri2, tri3); + + // Project the point onto this normal + Vector3 proj = math::ProjV3(point, n); + + // If the distance from the triangle to the point is 0 + // it lies on the triangle + if (math::MagnitudeV3(proj) == 0) + return true; + else + return false; + } + + // Split a String into a string array at a given token + inline void split(const std::string& in, std::vector& out, std::string token) + { + out.clear(); + + std::string temp; + + for (int i = 0; i < int(in.size()); i++) + { + std::string test = in.substr(i, token.size()); + + if (test == token) + { + if (!temp.empty()) + { + out.push_back(temp); + temp.clear(); + i += (int)token.size() - 1; + } + else + { + out.push_back(""); + } + } + else if (i + token.size() >= in.size()) + { + temp += in.substr(i, token.size()); + out.push_back(temp); + break; + } + else + { + temp += in[i]; + } + } + } + + // Get tail of string after first token and possibly following spaces + inline std::string tail(const std::string& in) + { + size_t token_start = in.find_first_not_of(" \t"); + size_t space_start = in.find_first_of(" \t", token_start); + size_t tail_start = in.find_first_not_of(" \t", space_start); + size_t tail_end = in.find_last_not_of(" \t"); + if (tail_start != std::string::npos && tail_end != std::string::npos) + { + return in.substr(tail_start, tail_end - tail_start + 1); + } + else if (tail_start != std::string::npos) + { + return in.substr(tail_start); + } + return ""; + } + + // Get first token of string + inline std::string firstToken(const std::string& in) + { + if (!in.empty()) + { + size_t token_start = in.find_first_not_of(" \t"); + size_t token_end = in.find_first_of(" \t", token_start); + if (token_start != std::string::npos && token_end != std::string::npos) + { + return in.substr(token_start, token_end - token_start); + } + else if (token_start != std::string::npos) + { + return in.substr(token_start); + } + } + return ""; + } + + // Get element at given index position + template inline const T& getElement(const std::vector& elements, std::string& index) + { + int idx = std::stoi(index); + if (idx < 0) + idx = int(elements.size()) + idx; + else + idx--; + return elements[idx]; + } + } // namespace algorithm + + // Class: Loader + // + // Description: The OBJ Model Loader + class Loader + { + public: + // Default Constructor + Loader() {} + + ~Loader() + { + LoadedMeshes.clear(); + } + + // Load a file into the loader + // + // If file is loaded return true + // + // If the file is unable to be found + // or unable to be loaded return false + bool LoadFile(std::string Path) + { + // If the file is not an .obj file return false + if (Path.substr(Path.size() - 4, 4) != ".obj") + return false; + + std::ifstream file(Path); + + if (!file.is_open()) + return false; + + LoadedMeshes.clear(); + LoadedVertices.clear(); + LoadedIndices.clear(); + + std::vector Positions; + std::vector TCoords; + std::vector Normals; + + std::vector Vertices; + std::vector Indices; + + std::vector MeshMatNames; + + bool listening = false; + std::string meshname; + + Mesh tempMesh; + +#ifdef OBJL_CONSOLE_OUTPUT + const unsigned int outputEveryNth = 1000; + unsigned int outputIndicator = outputEveryNth; +#endif + + std::string curline; + while (std::getline(file, curline)) + { +#ifdef OBJL_CONSOLE_OUTPUT + if ((outputIndicator = ((outputIndicator + 1) % outputEveryNth)) == 1) + { + if (!meshname.empty()) + { + std::cout << "\r- " << meshname << "\t| vertices > " << Positions.size() << "\t| texcoords > " << TCoords.size() << "\t| normals > " + << Normals.size() << "\t| triangles > " << (Vertices.size() / 3) + << (!MeshMatNames.empty() ? "\t| material: " + MeshMatNames.back() : ""); + } + } +#endif + + // Generate a Mesh Object or Prepare for an object to be created + if (algorithm::firstToken(curline) == "o" || algorithm::firstToken(curline) == "g" || curline[0] == 'g') + { + if (!listening) + { + listening = true; + + if (algorithm::firstToken(curline) == "o" || algorithm::firstToken(curline) == "g") + { + meshname = algorithm::tail(curline); + } + else + { + meshname = "unnamed"; + } + } + else + { + // Generate the mesh to put into the array + + if (!Indices.empty() && !Vertices.empty()) + { + // Create Mesh + tempMesh = Mesh(Vertices, Indices); + tempMesh.MeshName = meshname; + + // Insert Mesh + LoadedMeshes.push_back(tempMesh); + + // Cleanup + Vertices.clear(); + Indices.clear(); + meshname.clear(); + + meshname = algorithm::tail(curline); + } + else + { + if (algorithm::firstToken(curline) == "o" || algorithm::firstToken(curline) == "g") + { + meshname = algorithm::tail(curline); + } + else + { + meshname = "unnamed"; + } + } + } +#ifdef OBJL_CONSOLE_OUTPUT + std::cout << std::endl; + outputIndicator = 0; +#endif + } + // Generate a Vertex Position + if (algorithm::firstToken(curline) == "v") + { + std::vector spos; + Vector3 vpos; + algorithm::split(algorithm::tail(curline), spos, " "); + + vpos.X = std::stof(spos[0]); + vpos.Y = std::stof(spos[1]); + vpos.Z = std::stof(spos[2]); + + Positions.push_back(vpos); + } + // Generate a Vertex Texture Coordinate + if (algorithm::firstToken(curline) == "vt") + { + std::vector stex; + Vector2 vtex; + algorithm::split(algorithm::tail(curline), stex, " "); + + vtex.X = std::stof(stex[0]); + vtex.Y = std::stof(stex[1]); + + TCoords.push_back(vtex); + } + // Generate a Vertex Normal; + if (algorithm::firstToken(curline) == "vn") + { + std::vector snor; + Vector3 vnor; + algorithm::split(algorithm::tail(curline), snor, " "); + + vnor.X = std::stof(snor[0]); + vnor.Y = std::stof(snor[1]); + vnor.Z = std::stof(snor[2]); + + Normals.push_back(vnor); + } + // Generate a Face (vertices & indices) + if (algorithm::firstToken(curline) == "f") + { + // Generate the vertices + std::vector vVerts; + GenVerticesFromRawOBJ(vVerts, Positions, TCoords, Normals, curline); + + // Add Vertices + for (int i = 0; i < int(vVerts.size()); i++) + { + Vertices.push_back(vVerts[i]); + + LoadedVertices.push_back(vVerts[i]); + } + + std::vector iIndices; + + VertexTriangluation(iIndices, vVerts); + + // Add Indices + for (int i = 0; i < int(iIndices.size()); i++) + { + unsigned int indnum = (unsigned int)((Vertices.size()) - vVerts.size()) + iIndices[i]; + Indices.push_back(indnum); + + indnum = (unsigned int)((LoadedVertices.size()) - vVerts.size()) + iIndices[i]; + LoadedIndices.push_back(indnum); + } + } + // Get Mesh Material Name + if (algorithm::firstToken(curline) == "usemtl") + { + MeshMatNames.push_back(algorithm::tail(curline)); + + // Create new Mesh, if Material changes within a group + if (!Indices.empty() && !Vertices.empty()) + { + // Create Mesh + tempMesh = Mesh(Vertices, Indices); + tempMesh.MeshName = meshname; + int i = 2; + while (1) + { + tempMesh.MeshName = meshname + "_" + std::to_string(i); + + for (auto& m : LoadedMeshes) + if (m.MeshName == tempMesh.MeshName) + continue; + break; + } + + // Insert Mesh + LoadedMeshes.push_back(tempMesh); + + // Cleanup + Vertices.clear(); + Indices.clear(); + } + +#ifdef OBJL_CONSOLE_OUTPUT + outputIndicator = 0; +#endif + } + // Load Materials + if (algorithm::firstToken(curline) == "mtllib") + { + // Generate LoadedMaterial + + // Generate a path to the material file + std::vector temp; + algorithm::split(Path, temp, "\\"); // update: use windows file seperators + + std::string pathtomat = ""; + + if (temp.size() != 1) + { + for (int i = 0; i < temp.size() - 1; i++) + { + pathtomat += temp[i] + "/"; + } + } + + pathtomat += algorithm::tail(curline); + +#ifdef OBJL_CONSOLE_OUTPUT + std::cout << std::endl << "- find materials in: " << pathtomat << std::endl; +#endif + + // Load Materials + LoadMaterials(pathtomat); + } + } + +#ifdef OBJL_CONSOLE_OUTPUT + std::cout << std::endl; +#endif + + // Deal with last mesh + + if (!Indices.empty() && !Vertices.empty()) + { + // Create Mesh + tempMesh = Mesh(Vertices, Indices); + tempMesh.MeshName = meshname; + + // Insert Mesh + LoadedMeshes.push_back(tempMesh); + } + + file.close(); + + // Set Materials for each Mesh + for (int i = 0; i < MeshMatNames.size(); i++) + { + std::string matname = MeshMatNames[i]; + + // Find corresponding material name in loaded materials + // when found copy material variables into mesh material + for (int j = 0; j < LoadedMaterials.size(); j++) + { + if (LoadedMaterials[j].name == matname) + { + LoadedMeshes[i].MeshMaterial = LoadedMaterials[j]; + break; + } + } + } + + if (LoadedMeshes.empty() && LoadedVertices.empty() && LoadedIndices.empty()) + { + return false; + } + else + { + return true; + } + } + + // Loaded Mesh Objects + std::vector LoadedMeshes; + // Loaded Vertex Objects + std::vector LoadedVertices; + // Loaded Index Positions + std::vector LoadedIndices; + // Loaded Material Objects + std::vector LoadedMaterials; + + private: + // Generate vertices from a list of positions, + // tcoords, normals and a face line + void GenVerticesFromRawOBJ(std::vector& oVerts, + const std::vector& iPositions, + const std::vector& iTCoords, + const std::vector& iNormals, + std::string icurline) + { + std::vector sface, svert; + Vertex vVert; + algorithm::split(algorithm::tail(icurline), sface, " "); + + bool noNormal = false; + + // For every given vertex do this + for (int i = 0; i < int(sface.size()); i++) + { + // See What type the vertex is. + int vtype; + + algorithm::split(sface[i], svert, "/"); + + // Check for just position - v1 + if (svert.size() == 1) + { + // Only position + vtype = 1; + } + + // Check for position & texture - v1/vt1 + if (svert.size() == 2) + { + // Position & Texture + vtype = 2; + } + + // Check for Position, Texture and Normal - v1/vt1/vn1 + // or if Position and Normal - v1//vn1 + if (svert.size() == 3) + { + if (svert[1] != "") + { + // Position, Texture, and Normal + vtype = 4; + } + else + { + // Position & Normal + vtype = 3; + } + } + + // Calculate and store the vertex + switch (vtype) + { + case 1: // P + { + vVert.Position = algorithm::getElement(iPositions, svert[0]); + vVert.TextureCoordinate = Vector2(0, 0); + noNormal = true; + oVerts.push_back(vVert); + break; + } + case 2: // P/T + { + vVert.Position = algorithm::getElement(iPositions, svert[0]); + vVert.TextureCoordinate = algorithm::getElement(iTCoords, svert[1]); + noNormal = true; + oVerts.push_back(vVert); + break; + } + case 3: // P//N + { + vVert.Position = algorithm::getElement(iPositions, svert[0]); + vVert.TextureCoordinate = Vector2(0, 0); + vVert.Normal = algorithm::getElement(iNormals, svert[2]); + oVerts.push_back(vVert); + break; + } + case 4: // P/T/N + { + vVert.Position = algorithm::getElement(iPositions, svert[0]); + vVert.TextureCoordinate = algorithm::getElement(iTCoords, svert[1]); + vVert.Normal = algorithm::getElement(iNormals, svert[2]); + oVerts.push_back(vVert); + break; + } + default: + { + break; + } + } + } + + // take care of missing normals + // these may not be truly acurate but it is the + // best they get for not compiling a mesh with normals + if (noNormal) + { + Vector3 A = oVerts[0].Position - oVerts[1].Position; + Vector3 B = oVerts[2].Position - oVerts[1].Position; + + Vector3 normal = math::CrossV3(A, B); + + for (int i = 0; i < int(oVerts.size()); i++) + { + oVerts[i].Normal = normal; + } + } + } + + // Triangulate a list of vertices into a face by printing + // inducies corresponding with triangles within it + void VertexTriangluation(std::vector& oIndices, const std::vector& iVerts) + { + // If there are 2 or less verts, + // no triangle can be created, + // so exit + if (iVerts.size() < 3) + { + return; + } + // If it is a triangle no need to calculate it + if (iVerts.size() == 3) + { + oIndices.push_back(0); + oIndices.push_back(1); + oIndices.push_back(2); + return; + } + + // Create a list of vertices + std::vector tVerts = iVerts; + + while (true) + { + // For every vertex + for (int i = 0; i < int(tVerts.size()); i++) + { + // pPrev = the previous vertex in the list + Vertex pPrev; + if (i == 0) + { + pPrev = tVerts[tVerts.size() - 1]; + } + else + { + pPrev = tVerts[i - 1]; + } + + // pCur = the current vertex; + Vertex pCur = tVerts[i]; + + // pNext = the next vertex in the list + Vertex pNext; + if (i == tVerts.size() - 1) + { + pNext = tVerts[0]; + } + else + { + pNext = tVerts[i + 1]; + } + + // Check to see if there are only 3 verts left + // if so this is the last triangle + if (tVerts.size() == 3) + { + // Create a triangle from pCur, pPrev, pNext + for (int j = 0; j < int(tVerts.size()); j++) + { + if (iVerts[j].Position == pCur.Position) + oIndices.push_back(j); + if (iVerts[j].Position == pPrev.Position) + oIndices.push_back(j); + if (iVerts[j].Position == pNext.Position) + oIndices.push_back(j); + } + + tVerts.clear(); + break; + } + if (tVerts.size() == 4) + { + // Create a triangle from pCur, pPrev, pNext + for (int j = 0; j < int(iVerts.size()); j++) + { + if (iVerts[j].Position == pCur.Position) + oIndices.push_back(j); + if (iVerts[j].Position == pPrev.Position) + oIndices.push_back(j); + if (iVerts[j].Position == pNext.Position) + oIndices.push_back(j); + } + + Vector3 tempVec; + for (int j = 0; j < int(tVerts.size()); j++) + { + if (tVerts[j].Position != pCur.Position && tVerts[j].Position != pPrev.Position && tVerts[j].Position != pNext.Position) + { + tempVec = tVerts[j].Position; + break; + } + } + + // Create a triangle from pCur, pPrev, pNext + for (int j = 0; j < int(iVerts.size()); j++) + { + if (iVerts[j].Position == pPrev.Position) + oIndices.push_back(j); + if (iVerts[j].Position == pNext.Position) + oIndices.push_back(j); + if (iVerts[j].Position == tempVec) + oIndices.push_back(j); + } + + tVerts.clear(); + break; + } + + // If Vertex is not an interior vertex + float angle = math::AngleBetweenV3(pPrev.Position - pCur.Position, pNext.Position - pCur.Position) * (180 / 3.14159265359); + if (angle <= 0 && angle >= 180) + continue; + + // If any vertices are within this triangle + bool inTri = false; + for (int j = 0; j < int(iVerts.size()); j++) + { + if (algorithm::inTriangle(iVerts[j].Position, pPrev.Position, pCur.Position, pNext.Position) && iVerts[j].Position != pPrev.Position + && iVerts[j].Position != pCur.Position && iVerts[j].Position != pNext.Position) + { + inTri = true; + break; + } + } + if (inTri) + continue; + + // Create a triangle from pCur, pPrev, pNext + for (int j = 0; j < int(iVerts.size()); j++) + { + if (iVerts[j].Position == pCur.Position) + oIndices.push_back(j); + if (iVerts[j].Position == pPrev.Position) + oIndices.push_back(j); + if (iVerts[j].Position == pNext.Position) + oIndices.push_back(j); + } + + // Delete pCur from the list + for (int j = 0; j < int(tVerts.size()); j++) + { + if (tVerts[j].Position == pCur.Position) + { + tVerts.erase(tVerts.begin() + j); + break; + } + } + + // reset i to the start + // -1 since loop will add 1 to it + i = -1; + } + + // if no triangles were created + if (oIndices.size() == 0) + break; + + // if no more vertices + if (tVerts.size() == 0) + break; + } + } + + // Load Materials from .mtl file + bool LoadMaterials(std::string path) + { + // If the file is not a material file return false + if (path.substr(path.size() - 4, path.size()) != ".mtl") + return false; + + std::ifstream file(path); + + // If the file is not found return false + if (!file.is_open()) + return false; + + Material tempMaterial; + + bool listening = false; + + // Go through each line looking for material variables + std::string curline; + while (std::getline(file, curline)) + { + // new material and material name + if (algorithm::firstToken(curline) == "newmtl") + { + if (!listening) + { + listening = true; + + if (curline.size() > 7) + { + tempMaterial.name = algorithm::tail(curline); + } + else + { + tempMaterial.name = "none"; + } + } + else + { + // Generate the material + + // Push Back loaded Material + LoadedMaterials.push_back(tempMaterial); + + // Clear Loaded Material + tempMaterial = Material(); + + if (curline.size() > 7) + { + tempMaterial.name = algorithm::tail(curline); + } + else + { + tempMaterial.name = "none"; + } + } + } + // Ambient Color + if (algorithm::firstToken(curline) == "Ka") + { + std::vector temp; + algorithm::split(algorithm::tail(curline), temp, " "); + + if (temp.size() != 3) + continue; + + tempMaterial.Ka.X = std::stof(temp[0]); + tempMaterial.Ka.Y = std::stof(temp[1]); + tempMaterial.Ka.Z = std::stof(temp[2]); + } + // Diffuse Color + if (algorithm::firstToken(curline) == "Kd") + { + std::vector temp; + algorithm::split(algorithm::tail(curline), temp, " "); + + if (temp.size() != 3) + continue; + + tempMaterial.Kd.X = std::stof(temp[0]); + tempMaterial.Kd.Y = std::stof(temp[1]); + tempMaterial.Kd.Z = std::stof(temp[2]); + } + // Specular Color + if (algorithm::firstToken(curline) == "Ks") + { + std::vector temp; + algorithm::split(algorithm::tail(curline), temp, " "); + + if (temp.size() != 3) + continue; + + tempMaterial.Ks.X = std::stof(temp[0]); + tempMaterial.Ks.Y = std::stof(temp[1]); + tempMaterial.Ks.Z = std::stof(temp[2]); + } + // Specular Exponent + if (algorithm::firstToken(curline) == "Ns") + { + tempMaterial.Ns = std::stof(algorithm::tail(curline)); + } + // Optical Density + if (algorithm::firstToken(curline) == "Ni") + { + tempMaterial.Ni = std::stof(algorithm::tail(curline)); + } + // Dissolve + if (algorithm::firstToken(curline) == "d") + { + tempMaterial.d = std::stof(algorithm::tail(curline)); + } + // Illumination + if (algorithm::firstToken(curline) == "illum") + { + tempMaterial.illum = std::stoi(algorithm::tail(curline)); + } + // Ambient Texture Map + if (algorithm::firstToken(curline) == "map_Ka") + { + tempMaterial.map_Ka = algorithm::tail(curline); + } + // Diffuse Texture Map + if (algorithm::firstToken(curline) == "map_Kd") + { + tempMaterial.map_Kd = algorithm::tail(curline); + } + // Specular Texture Map + if (algorithm::firstToken(curline) == "map_Ks") + { + tempMaterial.map_Ks = algorithm::tail(curline); + } + // Specular Hightlight Map + if (algorithm::firstToken(curline) == "map_Ns") + { + tempMaterial.map_Ns = algorithm::tail(curline); + } + // Alpha Texture Map + if (algorithm::firstToken(curline) == "map_d") + { + tempMaterial.map_d = algorithm::tail(curline); + } + // Bump Map + if (algorithm::firstToken(curline) == "map_Bump" || algorithm::firstToken(curline) == "map_bump" || algorithm::firstToken(curline) == "bump") + { + tempMaterial.map_bump = algorithm::tail(curline); + } + } + + // Deal with last material + + // Push Back loaded Material + LoadedMaterials.push_back(tempMaterial); + + // Test to see if anything was loaded + // If not return false + if (LoadedMaterials.empty()) + return false; + // If so return true + else + return true; + } + }; +} // namespace objl diff --git a/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.cpp b/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.cpp new file mode 100644 index 00000000..77d5d53a --- /dev/null +++ b/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.cpp @@ -0,0 +1,105 @@ +#include "ProjectCreator.h" + +class CustomMapLoader +{ + void parseMesh(objl::Mesh* OBJMesh, worldSurface* meshSurface, customMapInfo* projInfo) + { + int meshVertexCount = OBJMesh->Vertices.size(); + int meshIndexCount = OBJMesh->Indices.size(); + int surfVertexStart = projInfo->gfxInfo.vertexCount; + int surfIndexStart = projInfo->gfxInfo.indexCount; + + projInfo->gfxInfo.vertexCount += meshVertexCount; + projInfo->gfxInfo.indexCount += meshIndexCount; + + projInfo->gfxInfo.vertices = (customMapVertex*)realloc(projInfo->gfxInfo.vertices, sizeof(customMapVertex) * projInfo->gfxInfo.vertexCount); + projInfo->gfxInfo.indices = (uint16_t*)realloc(projInfo->gfxInfo.indices, sizeof(uint16_t) * projInfo->gfxInfo.indexCount); + + customMapVertex* surfVertices = &projInfo->gfxInfo.vertices[surfVertexStart]; + uint16_t* surfIndices = &projInfo->gfxInfo.indices[surfIndexStart]; + + meshSurface->firstIndex_Index = surfIndexStart; + meshSurface->firstVertexIndex = surfVertexStart; + meshSurface->triCount = meshIndexCount / 3; + _ASSERT(meshIndexCount % 3 == 0); + + for (int i = 0; i < meshIndexCount; i++) + { + _ASSERT(OBJMesh->Indices[i] < UINT16_MAX); + surfIndices[i] = OBJMesh->Indices[i]; + } + + for (int i = 0; i < meshVertexCount; i++) + { + objl::Vertex* meshVertex = &OBJMesh->Vertices[i]; + customMapVertex* surfVertex = &surfVertices[i]; + + surfVertex->pos.x = meshVertex->Position.X; + surfVertex->pos.y = meshVertex->Position.Y; + surfVertex->pos.z = meshVertex->Position.Z; + + surfVertex->color[0] = OBJMesh->MeshMaterial.Kd.X; + surfVertex->color[1] = OBJMesh->MeshMaterial.Kd.Y; + surfVertex->color[2] = OBJMesh->MeshMaterial.Kd.Z; + surfVertex->color[3] = 1.0f; + + surfVertex->texCoord[0] = meshVertex->TextureCoordinate.X; + surfVertex->texCoord[1] = meshVertex->TextureCoordinate.Y; + + surfVertex->normal.x = meshVertex->Normal.X; + surfVertex->normal.y = meshVertex->Normal.Y; + surfVertex->normal.z = meshVertex->Normal.Z; + + // TODO: fix tangents, seems to work for now though + surfVertex->tangent.x = 1.0f; + surfVertex->tangent.y = 0.0f; + surfVertex->tangent.z = 0.0f; + + surfVertex->packedLmapCoord = 0; + + surfVertex->binormalSign = 0.0f; + } + } + + customMapInfo* createCustomMapInfo(std::string& projectName, ISearchPath& searchPath) + { + std::string objFilePath = searchPath.GetPath() + "custom_map/world.obj"; + + objl::Loader OBJloader; + bool isLoaded = OBJloader.LoadFile(objFilePath); + if (!isLoaded) + { + printf("OBJLoader: unable to load obj file %s\n", objFilePath.c_str()); + return NULL; + } + + customMapInfo* projInfo = new customMapInfo; + + projInfo->name = projectName; + projInfo->bspName = "maps/mp/" + projectName + ".d3dbsp"; + + projInfo->gfxInfo.surfaceCount = OBJloader.LoadedMeshes.size(); + projInfo->gfxInfo.surfaces = new worldSurface[projInfo->gfxInfo.surfaceCount]; + + projInfo->gfxInfo.vertexCount = 0; + projInfo->gfxInfo.indexCount = 0; + projInfo->gfxInfo.vertices = (customMapVertex*)malloc(1); + projInfo->gfxInfo.indices = (uint16_t*)malloc(1); + + for (int i = 0; i < projInfo->gfxInfo.surfaceCount; i++) + { + objl::Mesh* currMesh = &OBJloader.LoadedMeshes[i]; + worldSurface* currSurface = &projInfo->gfxInfo.surfaces[i]; + + currSurface->materialName = currMesh->MeshMaterial.name; + currSurface->reflectionProbeIndex = 0; + currSurface->primaryLightIndex = 0; + currSurface->lightmapIndex = 0; + currSurface->flags = 0; + + parseMesh(currMesh, currSurface, projInfo); + } + + return projInfo; + } +}; diff --git a/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.h b/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.h new file mode 100644 index 00000000..8c6a5408 --- /dev/null +++ b/src/ObjLoading/Game/T6/CustomMap/ProjectCreator.h @@ -0,0 +1,13 @@ +#include "SearchPath/ISearchPath.h" +#include "Utils/MemoryManager.h" +#include "Game/T6/T6.h" +using namespace T6; + +#include "OBJ_Loader.h" + +class CustomMapInfo +{ +public: + static void parseMesh(objl::Mesh* OBJMesh, worldSurface* meshSurface, customMapInfo* projInfo); + static customMapInfo* createCustomMapInfo(std::string& projectName, ISearchPath& searchPath); +}; diff --git a/src/ObjLoading/Game/T6/CustomMap/Util.h b/src/ObjLoading/Game/T6/CustomMap/Util.h new file mode 100644 index 00000000..30e71cbf --- /dev/null +++ b/src/ObjLoading/Game/T6/CustomMap/Util.h @@ -0,0 +1,73 @@ +#include "Game/T6/T6.h" +using namespace T6; + +class CMUtil +{ +public: + // BO2 uses a different coordinate system, so this converts it back from OpenGLs default + static vec3_t convertToBO2Coords(vec3_t OGL_coordinate) + { + vec3_t result; + result.x = OGL_coordinate.x; + result.y = -OGL_coordinate.z; + result.z = OGL_coordinate.y; + return result; + } + + // BO2 uses a weird coordinate system, so this converts it to OpenGLs default + static vec3_t convertFromBO2Coords(vec3_t bo2_coordinate) + { + vec3_t result; + result.x = bo2_coordinate.x; + result.y = bo2_coordinate.z; + result.z = -bo2_coordinate.y; + return result; + } + + static void calcNewBounds(vec3_t* newmins, vec3_t* newmaxs, vec3_t* currmins, vec3_t* currmaxs) + { + if (currmins->x > newmins->x) + currmins->x = newmins->x; + + if (newmaxs->x > currmaxs->x) + currmaxs->x = newmaxs->x; + + if (currmins->y > newmins->y) + currmins->y = newmins->y; + + if (newmaxs->y > currmaxs->y) + currmaxs->y = newmaxs->y; + + if (currmins->z > newmins->z) + currmins->z = newmins->z; + + if (newmaxs->z > currmaxs->z) + currmaxs->z = newmaxs->z; + } + + static void calcNewBoundsWithPoint(vec3_t* point, vec3_t* currmins, vec3_t* currmaxs) + { + if (currmins->x > point->x) + currmins->x = point->x; + + if (point->x > currmaxs->x) + currmaxs->x = point->x; + + if (currmins->y > point->y) + currmins->y = point->y; + + if (point->y > currmaxs->y) + currmaxs->y = point->y; + + if (currmins->z > point->z) + currmins->z = point->z; + + if (point->z > currmaxs->z) + currmaxs->z = point->z; + } + + static int allignBy128(int size) + { + return ((size + 127) & 0xFFFFFF80); + } +}; diff --git a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp index 37afdf89..5c16d719 100644 --- a/src/ObjLoading/Game/T6/ObjLoaderT6.cpp +++ b/src/ObjLoading/Game/T6/ObjLoaderT6.cpp @@ -40,6 +40,7 @@ #include "Weapon/RawLoaderWeaponT6.h" #include "ZBarrier/GdtLoaderZBarrierT6.h" #include "ZBarrier/RawLoaderZBarrierT6.h" +#include "CustomMap/LoaderCustomMapT6.h" #include #include @@ -334,6 +335,7 @@ namespace T6 collection.AddDefaultAssetCreator(std::make_unique>(memory)); collection.AddDefaultAssetCreator(std::make_unique>(memory)); collection.AddDefaultAssetCreator(std::make_unique>(memory)); + // custom maps have no default } void ConfigureGlobalAssetPoolsLoaders(AssetCreatorCollection& collection, Zone& zone) @@ -386,6 +388,7 @@ namespace T6 collection.AddAssetCreator(std::make_unique>(zone)); collection.AddAssetCreator(std::make_unique>(zone)); collection.AddAssetCreator(std::make_unique>(zone)); + collection.AddAssetCreator(std::make_unique>(zone)); } void ConfigureLoaders(AssetCreatorCollection& collection, Zone& zone, ISearchPath& searchPath, IGdtQueryable& gdt) @@ -446,6 +449,8 @@ namespace T6 // collection.AddAssetCreator(std::make_unique(memory)); collection.AddAssetCreator(CreateRawZBarrierLoader(memory, searchPath, zone)); collection.AddAssetCreator(CreateGdtZBarrierLoader(memory, searchPath, gdt, zone)); + + collection.AddAssetCreator(CreateCustomMapLoader(memory, searchPath)); } } // namespace diff --git a/src/ZoneCommon/Game/T6/AssetNameResolverT6.cpp b/src/ZoneCommon/Game/T6/AssetNameResolverT6.cpp index 765172d6..46c0a3fa 100644 --- a/src/ZoneCommon/Game/T6/AssetNameResolverT6.cpp +++ b/src/ZoneCommon/Game/T6/AssetNameResolverT6.cpp @@ -9,6 +9,8 @@ AssetNameResolver::AssetNameResolver() { for (auto assetType = 0; assetType < ASSET_TYPE_COUNT; assetType++) AddAssetTypeName(assetType, *GameAssetPoolT6::AssetTypeNameByType(assetType)); + + AddAssetTypeName(ASSET_TYPE_CUSTOM_MAP, "custom_map"); } GameId AssetNameResolver::GetGameId() const