diff --git a/src/bsp.cpp b/src/bsp.cpp index 47cb376..f282bb1 100644 --- a/src/bsp.cpp +++ b/src/bsp.cpp @@ -36,6 +36,85 @@ BSP::BSP(BSPNode type, const std::vector& quadblockIndexes, BSP* parent, } } +void BSP::PopulateBranch(PSX::BSPBranch& branch, std::vector& bspArray, size_t global_id) +{ + g_id = global_id; + m_id = branch.id; + m_node = BSPNode::BRANCH; + if (branch.axis.x != 0) { m_axis = AxisSplit::X; } + else if (branch.axis.y != 0) { m_axis = AxisSplit::Y; } + else if (branch.axis.z != 0) { m_axis = AxisSplit::Z; } + else { m_axis = AxisSplit::NONE; } + m_flags = branch.flag; + if (branch.leftChild != BSPID::EMPTY) + { + uint16_t leftId = branch.leftChild & ~BSPID::LEAF; + if (leftId < bspArray.size()) + { + m_left = bspArray[leftId]; + m_left->SetParent(this); + } + } + if (branch.rightChild != BSPID::EMPTY) + { + uint16_t rightId = branch.rightChild & ~BSPID::LEAF; + if (rightId < bspArray.size()) + { + m_right = bspArray[rightId]; + m_right->SetParent(this); + } + } + m_bbox = BoundingBox(); + m_bbox.min = ConvertPSXVec3(branch.bbox.min, FP_ONE_GEO); + m_bbox.max = ConvertPSXVec3(branch.bbox.max, FP_ONE_GEO); + m_quadblockIndexes = std::vector(); // Need to be set later +} + +void BSP::PopulateLeaf(PSX::BSPLeaf& leaf, std::vector& bspArray, const std::vector& quadblocks, uint32_t offQuadblocks, size_t global_id) +{ + g_id = global_id; + m_id = leaf.id; + m_node = BSPNode::LEAF; + m_axis = AxisSplit::NONE; + m_flags = leaf.flag; + m_left = nullptr; + m_right = nullptr; + m_bbox = BoundingBox(); + m_bbox.min = ConvertPSXVec3(leaf.bbox.min, FP_ONE_GEO); + m_bbox.max = ConvertPSXVec3(leaf.bbox.max, FP_ONE_GEO); + m_quadblockIndexes = std::vector(); + for (size_t i = 0; i < leaf.numQuads; i++) + { + uint32_t relative_offset = leaf.offQuads - offQuadblocks; + m_quadblockIndexes.push_back(i + relative_offset/sizeof(PSX::Quadblock)); + } + for (size_t i : m_quadblockIndexes) + { + if (i < quadblocks.size()) { quadblocks[i].SetBSPID(m_id); } + } +} + + +void BSP::PopulateBranchQuadIndexes() +{ + if (m_node == BSPNode::BRANCH) + { + m_quadblockIndexes.clear(); + if (m_left != nullptr) + { + m_left->PopulateBranchQuadIndexes(); + const std::vector& leftIndexes = m_left->GetQuadblockIndexes(); + m_quadblockIndexes.insert(m_quadblockIndexes.end(), leftIndexes.begin(), leftIndexes.end()); + } + if (m_right != nullptr) + { + m_right->PopulateBranchQuadIndexes(); + const std::vector& rightIndexes = m_right->GetQuadblockIndexes(); + m_quadblockIndexes.insert(m_quadblockIndexes.end(), rightIndexes.begin(), rightIndexes.end()); + } + } +} + size_t BSP::GetId() const { return m_id; @@ -145,6 +224,11 @@ void BSP::SetQuadblockIndexes(const std::vector& quadblockIndexes) m_quadblockIndexes = quadblockIndexes; } +void BSP::SetParent(BSP* parent) +{ + m_parent = parent; +} + void BSP::Clear() { std::vector vBSP = {this}; diff --git a/src/bsp.h b/src/bsp.h index a9d0b83..254802d 100644 --- a/src/bsp.h +++ b/src/bsp.h @@ -38,6 +38,9 @@ class BSP public: BSP(); BSP(BSPNode type, const std::vector& quadblockIndexes, BSP* parent, const std::vector& quadblocks); + void PopulateLeaf(PSX::BSPLeaf& leaf, std::vector& bspArray, const std::vector& quadblocks, uint32_t offQuadblocks, size_t global_id); + void PopulateBranch(PSX::BSPBranch& branch, std::vector& bspArray, size_t global_id); + void PopulateBranchQuadIndexes(); size_t GetId() const; bool IsEmpty() const; bool IsValid() const; @@ -53,6 +56,7 @@ class BSP const std::vector GetTree() const; std::vector GetLeaves() const; void SetQuadblockIndexes(const std::vector& quadblockIndexes); + void SetParent(BSP* parent); void Clear(); void Generate(const std::vector& quadblocks, const size_t maxQuadsPerLeaf, const float maxAxisLength); std::vector Serialize(size_t offQuads) const; diff --git a/src/level.cpp b/src/level.cpp index a2326fe..8dca0c9 100644 --- a/src/level.cpp +++ b/src/level.cpp @@ -736,6 +736,30 @@ bool Level::LoadLEV(const std::filesystem::path& levFile) file.seekg(offLev + std::streampos(header.offMeshInfo)); Read(file, meshInfo); + + // Store raw texture groups (not CTE editable) + file.seekg(offLev + std::streampos(header.offMeshInfo + sizeof(PSX::MeshInfo))); + uint32_t texGroupsStart = header.offMeshInfo + sizeof(PSX::MeshInfo); + uint32_t texGroupsEnd = header.offAnimTex; + uint32_t numTexGroups = (texGroupsEnd - texGroupsStart) / sizeof(PSX::TextureGroup); + m_rawTexGroups.clear(); + m_rawTexGroups.resize(numTexGroups); + for (uint32_t i = 0; i < numTexGroups; i++) + { + Read(file, m_rawTexGroups[i]); + } + + // Store raw animated texture data (not CTE editable) + m_rawAnimData.clear(); + if (header.offAnimTex > 0 && meshInfo.offQuadblocks > header.offAnimTex) + { + file.seekg(offLev + std::streampos(header.offAnimTex)); + size_t animDataSize = meshInfo.offQuadblocks - header.offAnimTex; + m_rawAnimData.resize(animDataSize); + file.read(reinterpret_cast(m_rawAnimData.data()), animDataSize); + } + m_hasRawTextureData = !m_rawTexGroups.empty(); + std::vector vertices; vertices.reserve(meshInfo.numVertices); file.seekg(offLev + std::streampos(meshInfo.offVertices)); @@ -752,9 +776,52 @@ bool Level::LoadLEV(const std::filesystem::path& levFile) PSX::Quadblock quadblock = {}; Read(file, quadblock); m_quadblocks.emplace_back(quadblock, vertices, [this](const Quadblock& qb) { UpdateFilterRenderData(qb); }); + Quadblock& quad = m_quadblocks.back(); + quad.SetRawQuadblock(quadblock); m_materialToQuadblocks["default"].push_back(i); } + m_originalVertices = std::vector(vertices.begin(), vertices.end()); // Store the original PSX::Vertex array + m_hasOriginalVertices = true; + + m_bsp.Clear(); + file.seekg(offLev + std::streampos(meshInfo.offBSPNodes)); + std::vector bspArray; + for (uint32_t i = 0; i < meshInfo.numBSPNodes; i++) + { + bspArray.push_back(new BSP()); + } + + for (uint32_t i = 0; i < meshInfo.numBSPNodes; i++) + { + uint16_t flag; + std::streampos nodeStart = file.tellg(); + Read(file, flag); + file.seekg(nodeStart); + + if (flag & BSPFlags::LEAF) + { + PSX::BSPLeaf leaf = {}; + Read(file, leaf); + bspArray[leaf.id]->PopulateLeaf(leaf, bspArray, m_quadblocks, meshInfo.offQuadblocks, meshInfo.offBSPNodes); + } + else + { + PSX::BSPBranch branch = {}; + Read(file, branch); + bspArray[branch.id]->PopulateBranch(branch, bspArray, meshInfo.offBSPNodes); + } + } + + if (bspArray.size()) + { + m_bsp = *(bspArray[0]); + m_bsp.PopulateBranchQuadIndexes(); + if (m_bsp.IsValid()) { GenerateRenderBspData(); } + else { m_bsp.Clear(); } + } + else { m_bsp.Clear(); } + file.seekg(offLev + std::streampos(header.offCheckpointNodes)); for (uint32_t i = 0; i < header.numCheckpointNodes; i++) { @@ -764,6 +831,40 @@ bool Level::LoadLEV(const std::filesystem::path& levFile) } UpdateRenderCheckpointData(); + m_tropyGhost.clear(); + m_oxideGhost.clear(); + if (header.offExtra > 0) + { + file.seekg(offLev + std::streampos(header.offExtra)); + PSX::LevelExtraHeader extraHeader = {}; + Read(file, extraHeader); + // Read N. Tropy Ghost + if (extraHeader.count >= PSX::LevelExtra::N_TROPY_GHOST + 1 && + extraHeader.offsets[PSX::LevelExtra::N_TROPY_GHOST] > 0) + { + file.seekg(offLev + std::streampos(extraHeader.offsets[PSX::LevelExtra::N_TROPY_GHOST])); + size_t ghostSize = 0; + if (extraHeader.count > PSX::LevelExtra::N_OXIDE_GHOST && extraHeader.offsets[PSX::LevelExtra::N_OXIDE_GHOST] > 0) + { + ghostSize = extraHeader.offsets[PSX::LevelExtra::N_OXIDE_GHOST] - extraHeader.offsets[PSX::LevelExtra::N_TROPY_GHOST]; + } + else + { + ghostSize = header.offLevNavTable - extraHeader.offsets[PSX::LevelExtra::N_TROPY_GHOST]; + } + m_tropyGhost.resize(ghostSize); + file.read(reinterpret_cast(m_tropyGhost.data()), ghostSize); + } + // Read N. Oxide Ghost + if (extraHeader.count >= PSX::LevelExtra::N_OXIDE_GHOST + 1 && extraHeader.offsets[PSX::LevelExtra::N_OXIDE_GHOST] > 0) + { + file.seekg(offLev + std::streampos(extraHeader.offsets[PSX::LevelExtra::N_OXIDE_GHOST])); + size_t ghostSize = header.offLevNavTable - extraHeader.offsets[PSX::LevelExtra::N_OXIDE_GHOST]; + m_oxideGhost.resize(ghostSize); + file.read(reinterpret_cast(m_oxideGhost.data()), ghostSize); + } + } + m_loaded = true; file.close(); GenerateRenderLevData(); @@ -812,73 +913,40 @@ bool Level::SaveLEV(const std::filesystem::path& path) const size_t offTexture = currOffset; size_t offAnimData = 0; - PSX::TextureLayout defaultTex = {}; - defaultTex.clut.self = 32 | (20 << 6); - defaultTex.texPage.self = (512 >> 6) | ((0 >> 8) << 4) | (0 << 5) | (0 << 7); - defaultTex.u0 = 0; defaultTex.v0 = 0; - defaultTex.u1 = 15; defaultTex.v1 = 0; - defaultTex.u2 = 0; defaultTex.v2 = 15; - defaultTex.u3 = 15; defaultTex.v3 = 15; - - PSX::TextureGroup defaultTexGroup = {}; - defaultTexGroup.far = defaultTex; - defaultTexGroup.middle = defaultTex; - defaultTexGroup.near = defaultTex; - defaultTexGroup.mosaic = defaultTex; - std::vector animData; std::vector animPtrMapOffsets; std::vector texGroups; - std::unordered_map savedLayouts; - if (UpdateVRM()) + + if (!m_hasRawTextureData) { - for (auto& [material, texture] : m_materialToTexture) - { - std::vector& quadIndexes = m_materialToQuadblocks[material]; - for (size_t index : quadIndexes) - { - Quadblock& currQuad = m_quadblocks[index]; - if (currQuad.GetAnimated()) { continue; } - for (size_t i = 0; i < NUM_FACES_QUADBLOCK + 1; i++) - { - size_t textureID = 0; - const QuadUV& uvs = currQuad.GetQuadUV(i); - PSX::TextureLayout layout = texture.Serialize(uvs); - if (savedLayouts.contains(layout)) { textureID = savedLayouts[layout]; } - else - { - textureID = texGroups.size(); - savedLayouts[layout] = textureID; - - PSX::TextureGroup texGroup = {}; - texGroup.far = layout; - texGroup.middle = layout; - texGroup.near = layout; - texGroup.mosaic = layout; - texGroups.push_back(texGroup); - } - currQuad.SetTextureID(textureID, i); - } - } - } + PSX::TextureLayout defaultTex = {}; + defaultTex.clut.self = 32 | (20 << 6); + defaultTex.texPage.self = (512 >> 6) | ((0 >> 8) << 4) | (0 << 5) | (0 << 7); + defaultTex.u0 = 0; defaultTex.v0 = 0; + defaultTex.u1 = 15; defaultTex.v1 = 0; + defaultTex.u2 = 0; defaultTex.v2 = 15; + defaultTex.u3 = 15; defaultTex.v3 = 15; + + PSX::TextureGroup defaultTexGroup = {}; + defaultTexGroup.far = defaultTex; + defaultTexGroup.middle = defaultTex; + defaultTexGroup.near = defaultTex; + defaultTexGroup.mosaic = defaultTex; - if (!m_animTextures.empty()) + std::unordered_map savedLayouts; + if (UpdateVRM()) { - std::vector> animOffsetPerQuadblock; - for (AnimTexture& animTex : m_animTextures) + for (auto& [material, texture] : m_materialToTexture) { - const std::vector& animFrames = animTex.GetFrames(); - const std::vector& animTextures = animTex.GetTextures(); - std::vector> texgroupIndexesPerFrame(NUM_FACES_QUADBLOCK); - bool firstFrame = true; - for (const AnimTextureFrame& frame : animFrames) + std::vector& quadIndexes = m_materialToQuadblocks[material]; + for (size_t index : quadIndexes) { - Texture& texture = const_cast(animTextures[frame.textureIndex]); + Quadblock& currQuad = m_quadblocks[index]; + if (currQuad.GetAnimated()) { continue; } for (size_t i = 0; i < NUM_FACES_QUADBLOCK + 1; i++) { - if (i == NUM_FACES_QUADBLOCK && !firstFrame) { continue; } size_t textureID = 0; - const QuadUV& uvs = frame.uvs[i]; + const QuadUV& uvs = currQuad.GetQuadUV(i); PSX::TextureLayout layout = texture.Serialize(uvs); if (savedLayouts.contains(layout)) { textureID = savedLayouts[layout]; } else @@ -893,99 +961,173 @@ bool Level::SaveLEV(const std::filesystem::path& path) texGroup.mosaic = layout; texGroups.push_back(texGroup); } - if (firstFrame && i == NUM_FACES_QUADBLOCK) - { - const std::vector& quadblockIndexes = animTex.GetQuadblockIndexes(); - for (size_t index : quadblockIndexes) - { - m_quadblocks[index].SetTextureID(textureID, i); - } - } - else { texgroupIndexesPerFrame[i].push_back(textureID); } + currQuad.SetTextureID(textureID, i); } - firstFrame = false; } - std::array offsetPerQuadblock = {}; - for (size_t i = 0; i < NUM_FACES_QUADBLOCK; i++) + } + + if (!m_animTextures.empty()) + { + std::vector> animOffsetPerQuadblock; + for (AnimTexture& animTex : m_animTextures) { - bool foundEquivalent = false; - for (size_t j = 0; j < i; j++) + const std::vector& animFrames = animTex.GetFrames(); + const std::vector& animTextures = animTex.GetTextures(); + std::vector> texgroupIndexesPerFrame(NUM_FACES_QUADBLOCK); + bool firstFrame = true; + for (const AnimTextureFrame& frame : animFrames) { - if (texgroupIndexesPerFrame[i] == texgroupIndexesPerFrame[j]) + Texture& texture = const_cast(animTextures[frame.textureIndex]); + for (size_t i = 0; i < NUM_FACES_QUADBLOCK + 1; i++) { - offsetPerQuadblock[i] = offsetPerQuadblock[j]; - foundEquivalent = true; - break; + if (i == NUM_FACES_QUADBLOCK && !firstFrame) { continue; } + size_t textureID = 0; + const QuadUV& uvs = frame.uvs[i]; + PSX::TextureLayout layout = texture.Serialize(uvs); + if (savedLayouts.contains(layout)) { textureID = savedLayouts[layout]; } + else + { + textureID = texGroups.size(); + savedLayouts[layout] = textureID; + + PSX::TextureGroup texGroup = {}; + texGroup.far = layout; + texGroup.middle = layout; + texGroup.near = layout; + texGroup.mosaic = layout; + texGroups.push_back(texGroup); + } + if (firstFrame && i == NUM_FACES_QUADBLOCK) + { + const std::vector& quadblockIndexes = animTex.GetQuadblockIndexes(); + for (size_t index : quadblockIndexes) + { + m_quadblocks[index].SetTextureID(textureID, i); + } + } + else { texgroupIndexesPerFrame[i].push_back(textureID); } } + firstFrame = false; } - if (foundEquivalent) { continue; } - std::vector buffer = animTex.Serialize(texgroupIndexesPerFrame[i][0], offTexture); - size_t animTexOffset = animData.size(); - offsetPerQuadblock[i] = animTexOffset; - animPtrMapOffsets.push_back(animTexOffset); - for (uint8_t byte : buffer) { animData.push_back(byte); } - for (size_t j = 0; j < animFrames.size(); j++) + std::array offsetPerQuadblock = {}; + for (size_t i = 0; i < NUM_FACES_QUADBLOCK; i++) { - uint32_t offset = static_cast((texgroupIndexesPerFrame[i][j] * sizeof(PSX::TextureGroup)) + offTexture); - size_t offAnimTexArr = animData.size(); - animPtrMapOffsets.push_back(offAnimTexArr); - for (size_t k = 0; k < sizeof(uint32_t); k++) { animData.push_back(0); } - memcpy(&animData[offAnimTexArr], &offset, sizeof(uint32_t)); + bool foundEquivalent = false; + for (size_t j = 0; j < i; j++) + { + if (texgroupIndexesPerFrame[i] == texgroupIndexesPerFrame[j]) + { + offsetPerQuadblock[i] = offsetPerQuadblock[j]; + foundEquivalent = true; + break; + } + } + if (foundEquivalent) { continue; } + std::vector buffer = animTex.Serialize(texgroupIndexesPerFrame[i][0], offTexture); + size_t animTexOffset = animData.size(); + offsetPerQuadblock[i] = animTexOffset; + animPtrMapOffsets.push_back(animTexOffset); + for (uint8_t byte : buffer) { animData.push_back(byte); } + for (size_t j = 0; j < animFrames.size(); j++) + { + uint32_t offset = static_cast((texgroupIndexesPerFrame[i][j] * sizeof(PSX::TextureGroup)) + offTexture); + size_t offAnimTexArr = animData.size(); + animPtrMapOffsets.push_back(offAnimTexArr); + for (size_t k = 0; k < sizeof(uint32_t); k++) { animData.push_back(0); } + memcpy(&animData[offAnimTexArr], &offset, sizeof(uint32_t)); + } } + animOffsetPerQuadblock.push_back(offsetPerQuadblock); } - animOffsetPerQuadblock.push_back(offsetPerQuadblock); - } - offAnimData = currOffset + (sizeof(PSX::TextureGroup) * texGroups.size()); + offAnimData = currOffset + (sizeof(PSX::TextureGroup) * texGroups.size()); - animPtrMapOffsets.push_back(animData.size()); - size_t offEndAnimData = animData.size(); - for (size_t i = 0; i < sizeof(uint32_t); i++) { animData.push_back(0); } - memcpy(&animData[offEndAnimData], &offAnimData, sizeof(uint32_t)); + animPtrMapOffsets.push_back(animData.size()); + size_t offEndAnimData = animData.size(); + for (size_t i = 0; i < sizeof(uint32_t); i++) { animData.push_back(0); } + memcpy(&animData[offEndAnimData], &offAnimData, sizeof(uint32_t)); - for (size_t i = 0; i < m_animTextures.size(); i++) - { - const std::vector& quadblockIndexes = m_animTextures[i].GetQuadblockIndexes(); - for (size_t index : quadblockIndexes) + for (size_t i = 0; i < m_animTextures.size(); i++) { - Quadblock& quadblock = m_quadblocks[index]; - for (size_t j = 0; j < NUM_FACES_QUADBLOCK; j++) + const std::vector& quadblockIndexes = m_animTextures[i].GetQuadblockIndexes(); + for (size_t index : quadblockIndexes) { - quadblock.SetAnimTextureOffset(animOffsetPerQuadblock[i][j], offAnimData, j); + Quadblock& quadblock = m_quadblocks[index]; + for (size_t j = 0; j < NUM_FACES_QUADBLOCK; j++) + { + quadblock.SetAnimTextureOffset(animOffsetPerQuadblock[i][j], offAnimData, j); + } } } } + else + { + offAnimData = currOffset + (sizeof(PSX::TextureGroup) * texGroups.size()); + for (size_t i = 0; i < sizeof(uint32_t); i++) { animData.push_back(0); } + memcpy(&animData[0], &offAnimData, sizeof(uint32_t)); + animPtrMapOffsets.push_back(0); + } + + m_hotReloadVRMPath = path / (m_name + ".vrm"); + std::ofstream vrmFile(m_hotReloadVRMPath, std::ios::binary); + Write(vrmFile, m_vrm.data(), m_vrm.size()); + vrmFile.close(); } else { + texGroups.push_back(defaultTexGroup); offAnimData = currOffset + (sizeof(PSX::TextureGroup) * texGroups.size()); for (size_t i = 0; i < sizeof(uint32_t); i++) { animData.push_back(0); } memcpy(&animData[0], &offAnimData, sizeof(uint32_t)); animPtrMapOffsets.push_back(0); } - - m_hotReloadVRMPath = path / (m_name + ".vrm"); - std::ofstream vrmFile(m_hotReloadVRMPath, std::ios::binary); - Write(vrmFile, m_vrm.data(), m_vrm.size()); - vrmFile.close(); } else - { - texGroups.push_back(defaultTexGroup); + { // Restore raw texture data + texGroups = m_rawTexGroups; + animData = m_rawAnimData; offAnimData = currOffset + (sizeof(PSX::TextureGroup) * texGroups.size()); - for (size_t i = 0; i < sizeof(uint32_t); i++) { animData.push_back(0); } - memcpy(&animData[0], &offAnimData, sizeof(uint32_t)); - animPtrMapOffsets.push_back(0); + animPtrMapOffsets.clear(); + if (!animData.empty()) + { + size_t offset = 0; + while (offset + 4 <= animData.size()) + { + uint32_t value; + std::memcpy(&value, &animData[offset], sizeof(uint32_t)); + if (value >= offTexture && value < offTexture + (texGroups.size() * sizeof(PSX::TextureGroup)) + animData.size()) + { + animPtrMapOffsets.push_back(offset); + } + offset += 4; + } + } + else + { + for (size_t i = 0; i < sizeof(uint32_t); i++) { animData.push_back(0); } + std::memcpy(&animData[0], &offAnimData, sizeof(uint32_t)); + animPtrMapOffsets.push_back(0); + } } currOffset += (sizeof(PSX::TextureGroup) * texGroups.size()) + animData.size(); const size_t offQuadblocks = currOffset; + std::unordered_map vertexMap; + std::vector orderedVertices; + if (m_hasOriginalVertices && m_hasRawTextureData) + { + orderedVertices = m_originalVertices; + for (size_t i = 0; i < orderedVertices.size(); i++) + { + vertexMap[orderedVertices[i]] = i; + } + } + std::vector> serializedBSPs; std::vector> serializedQuads; std::vector orderedQuads; - std::unordered_map vertexMap; - std::vector orderedVertices; + size_t bspSize = 0; for (const BSP* bsp : orderedBSPNodes) { @@ -996,17 +1138,28 @@ bool Level::SaveLEV(const std::filesystem::path& path) for (const size_t index : quadIndexes) { const Quadblock& quadblock = m_quadblocks[index]; - std::vector quadVertices = quadblock.GetVertices(); std::vector verticesIndexes; - for (const Vertex& vertex : quadVertices) + if (quadblock.HasRawQuadblock() && m_hasOriginalVertices) { - if (!vertexMap.contains(vertex)) + const PSX::Quadblock& rawQuad = quadblock.GetRawQuadblock(); + for (size_t i = 0; i < NUM_VERTICES_QUADBLOCK; i++) { - size_t vertexIndex = orderedVertices.size(); - orderedVertices.push_back(vertex); - vertexMap[vertex] = vertexIndex; + verticesIndexes.push_back(rawQuad.index[i]); + } + } + else + { + std::vector quadVertices = quadblock.GetVertices(); + for (const Vertex& vertex : quadVertices) + { + if (!vertexMap.contains(vertex)) + { + size_t vertexIndex = orderedVertices.size(); + orderedVertices.push_back(vertex); + vertexMap[vertex] = vertexIndex; + } + verticesIndexes.push_back(vertexMap[vertex]); } - verticesIndexes.push_back(vertexMap[vertex]); } size_t quadIndex = serializedQuads.size(); serializedQuads.push_back(quadblock.Serialize(quadIndex, offTexture, verticesIndexes)); @@ -1121,6 +1274,7 @@ bool Level::SaveLEV(const std::filesystem::path& path) meshInfo.numVertices = static_cast(serializedVertices.size()); meshInfo.offQuadblocks = static_cast(offQuadblocks); meshInfo.offVertices = static_cast(offVertices); + meshInfo.unk1 = 0; meshInfo.unk2 = 0; meshInfo.offBSPNodes = static_cast(offBSP); meshInfo.numBSPNodes = static_cast(serializedBSPs.size()); @@ -1307,6 +1461,7 @@ bool Level::SaveLEV(const std::filesystem::path& path) bool Level::LoadOBJ(const std::filesystem::path& objFile) { + m_hasRawTextureData = false; std::string line; std::ifstream file(objFile); m_name = objFile.filename().replace_extension().string(); diff --git a/src/level.h b/src/level.h index 36998ad..395c634 100644 --- a/src/level.h +++ b/src/level.h @@ -145,4 +145,11 @@ class Level Vec3 m_rendererQueryPoint; std::vector m_rendererSelectedQuadblockIndexes; size_t m_lastAnimTextureCount; + + std::vector m_rawTexGroups; + std::vector m_rawAnimData; + bool m_hasRawTextureData; + + std::vector m_originalVertices; + bool m_hasOriginalVertices; }; diff --git a/src/psx_types.h b/src/psx_types.h index 64a1b69..e15beb7 100644 --- a/src/psx_types.h +++ b/src/psx_types.h @@ -373,8 +373,8 @@ static constexpr int16_t FP_ONE = 0x1000; static constexpr int16_t FP_ONE_GEO = 64; static constexpr int16_t FP_ONE_CP = 8; -static inline int16_t ConvertFloat(float x, int16_t one = FP_ONE) { return static_cast(x * static_cast(one)); }; -static inline int16_t ConvertAngle(float x) { return static_cast((x * static_cast(FP_ONE)) / 360.0f); } +static inline int16_t ConvertFloat(float x, int16_t one = FP_ONE) { return static_cast(std::round(x * static_cast(one))); }; +static inline int16_t ConvertAngle(float x) { return static_cast(std::round((x * static_cast(FP_ONE)) / 360.0f)); } static inline float ConvertFP(int16_t fp, int16_t one = FP_ONE) { return static_cast(fp) / static_cast(one); } static inline float ConvertFPAngle(int16_t fp) { return (static_cast(fp) * 360.0f) / static_cast(FP_ONE); } diff --git a/src/quadblock.cpp b/src/quadblock.cpp index 9e30b97..9da7be5 100644 --- a/src/quadblock.cpp +++ b/src/quadblock.cpp @@ -8,6 +8,7 @@ Quadblock::Quadblock(const std::string& name, Tri& t0, Tri& t1, Tri& t2, Tri& t3, const Vec3& normal, const std::string& material, bool hasUV, UpdateFilterCallback filterCallback) { + m_hasRawQuadblock = false; std::unordered_map vRefCount; for (size_t i = 0; i < 3; i++) { @@ -210,6 +211,7 @@ Quadblock::Quadblock(const std::string& name, Tri& t0, Tri& t1, Tri& t2, Tri& t3 Quadblock::Quadblock(const std::string& name, Quad& q0, Quad& q1, Quad& q2, Quad& q3, const Vec3& normal, const std::string& material, bool hasUV, UpdateFilterCallback filterCallback) { + m_hasRawQuadblock = false; std::unordered_map vRefCount; for (size_t i = 0; i < 4; i++) { @@ -390,6 +392,7 @@ Quadblock::Quadblock(const std::string& name, Quad& q0, Quad& q1, Quad& q2, Quad Quadblock::Quadblock(const PSX::Quadblock& quadblock, const std::vector& vertices, UpdateFilterCallback filterCallback) { + m_hasRawQuadblock = false; uint16_t reverseIndexMapping[NUM_VERTICES_QUADBLOCK] = { 0, 2, 6, 8, 1, 3, 4, 5, 7 }; for (size_t i = 0; i < NUM_VERTICES_QUADBLOCK; i++) { @@ -415,6 +418,8 @@ Quadblock::Quadblock(const PSX::Quadblock& quadblock, const std::vector Quadblock::Serialize(size_t id, size_t offTextures, const s { PSX::Quadblock quadblock = {}; std::vector buffer(sizeof(quadblock)); - for (size_t i = 0; i < NUM_VERTICES_QUADBLOCK; i++) - { - quadblock.index[i] = static_cast(vertexIndexes[i]); - } - quadblock.flags = m_flags; - quadblock.drawOrderLow = m_doubleSided ? (1 << 31) : 0; - for (size_t i = 0; i < NUM_FACES_QUADBLOCK; i++) - { - uint32_t packedFace = m_faceRotateFlip[i] | (m_faceDrawMode[i] << 3); - quadblock.drawOrderLow |= packedFace << (8 + i * 5); + + if (m_hasRawQuadblock) + { + quadblock = m_rawQuadblock; } - quadblock.drawOrderHigh = 0; - if (m_animated) + else { - quadblock.offMidTextures[0] = static_cast(m_animTexOffset[0] | 1); - quadblock.offMidTextures[1] = static_cast(m_animTexOffset[1] | 1); - quadblock.offMidTextures[2] = static_cast(m_animTexOffset[2] | 1); - quadblock.offMidTextures[3] = static_cast(m_animTexOffset[3] | 1); - quadblock.offLowTexture = static_cast(offTextures + (m_textureIDs[4] * sizeof(PSX::TextureGroup))); + quadblock.drawOrderLow = m_doubleSided ? (1 << 31) : 0; + for (size_t i = 0; i < NUM_FACES_QUADBLOCK; i++) + { + uint32_t packedFace = m_faceRotateFlip[i] | (m_faceDrawMode[i] << 3); + quadblock.drawOrderLow |= packedFace << (8 + i * 5); + } + quadblock.drawOrderHigh = 0; + if (m_animated) + { + quadblock.offMidTextures[0] = static_cast(m_animTexOffset[0] | 1); + quadblock.offMidTextures[1] = static_cast(m_animTexOffset[1] | 1); + quadblock.offMidTextures[2] = static_cast(m_animTexOffset[2] | 1); + quadblock.offMidTextures[3] = static_cast(m_animTexOffset[3] | 1); + quadblock.offLowTexture = static_cast(offTextures + (m_textureIDs[4] * sizeof(PSX::TextureGroup))); + } + else + { + quadblock.offMidTextures[0] = static_cast(offTextures + (m_textureIDs[0] * sizeof(PSX::TextureGroup))); + quadblock.offMidTextures[1] = static_cast(offTextures + (m_textureIDs[1] * sizeof(PSX::TextureGroup))); + quadblock.offMidTextures[2] = static_cast(offTextures + (m_textureIDs[2] * sizeof(PSX::TextureGroup))); + quadblock.offMidTextures[3] = static_cast(offTextures + (m_textureIDs[3] * sizeof(PSX::TextureGroup))); + quadblock.offLowTexture = static_cast(offTextures + (m_textureIDs[4] * sizeof(PSX::TextureGroup))); + } + quadblock.weatherIntensity = 0; + quadblock.weatherVanishRate = 0; + const size_t idVis = id / 32; + quadblock.id = static_cast((32 * idVis) + (31 - (id % 32))); + quadblock.triNormalVecBitshift = static_cast(std::round(std::log2(std::max(ComputeNormalVector(0, 2, 6).Length(), ComputeNormalVector(2, 8, 6).Length()) * 512.0f))); + auto CalculateNormalDividend = [this](size_t id0, size_t id1, size_t id2, float scaler) -> int16_t + { + return static_cast(std::round(scaler / ComputeNormalVector(id0, id1, id2).Length())); + }; + float scaler = static_cast(1 << quadblock.triNormalVecBitshift); + quadblock.triNormalVecDividend[0] = CalculateNormalDividend(0, 1, 3, scaler); + quadblock.triNormalVecDividend[1] = CalculateNormalDividend(1, 4, 3, scaler); + quadblock.triNormalVecDividend[2] = CalculateNormalDividend(4, 1, 2, scaler); + quadblock.triNormalVecDividend[3] = CalculateNormalDividend(3, 4, 6, scaler); + quadblock.triNormalVecDividend[4] = CalculateNormalDividend(7, 4, 5, scaler); + quadblock.triNormalVecDividend[5] = CalculateNormalDividend(5, 8, 7, scaler); + quadblock.triNormalVecDividend[6] = CalculateNormalDividend(2, 5, 4, scaler); + quadblock.triNormalVecDividend[7] = CalculateNormalDividend(6, 4, 7, scaler); + quadblock.triNormalVecDividend[9] = CalculateNormalDividend(2, 8, 6, scaler); + quadblock.triNormalVecDividend[8] = CalculateNormalDividend(0, 2, 6, scaler); } - else + + for (size_t i = 0; i < NUM_VERTICES_QUADBLOCK; i++) { - quadblock.offMidTextures[0] = static_cast(offTextures + (m_textureIDs[0] * sizeof(PSX::TextureGroup))); - quadblock.offMidTextures[1] = static_cast(offTextures + (m_textureIDs[1] * sizeof(PSX::TextureGroup))); - quadblock.offMidTextures[2] = static_cast(offTextures + (m_textureIDs[2] * sizeof(PSX::TextureGroup))); - quadblock.offMidTextures[3] = static_cast(offTextures + (m_textureIDs[3] * sizeof(PSX::TextureGroup))); - quadblock.offLowTexture = static_cast(offTextures + (m_textureIDs[4] * sizeof(PSX::TextureGroup))); + quadblock.index[i] = static_cast(vertexIndexes[i]); } - quadblock.bbox.min = ConvertVec3(m_bbox.min, FP_ONE_GEO); - quadblock.bbox.max = ConvertVec3(m_bbox.max, FP_ONE_GEO); - quadblock.terrain = m_terrain; - quadblock.weatherIntensity = 0; - quadblock.weatherVanishRate = 0; + quadblock.flags = m_flags; quadblock.speedImpact = static_cast(m_downforce); - const size_t idVis = id / 32; - quadblock.id = static_cast((32 * idVis) + (31 - (id % 32))); + quadblock.terrain = m_terrain; quadblock.checkpointIndex = static_cast(m_checkpointIndex); - quadblock.triNormalVecBitshift = static_cast(std::round(std::log2(std::max(ComputeNormalVector(0, 2, 6).Length(), ComputeNormalVector(2, 8, 6).Length()) * 512.0f))); - - auto CalculateNormalDividend = [this](size_t id0, size_t id1, size_t id2, float scaler) -> int16_t - { - return static_cast(std::round(scaler / ComputeNormalVector(id0, id1, id2).Length())); - }; - - float scaler = static_cast(1 << quadblock.triNormalVecBitshift); - quadblock.triNormalVecDividend[0] = CalculateNormalDividend(0, 1, 3, scaler); - quadblock.triNormalVecDividend[1] = CalculateNormalDividend(1, 4, 3, scaler); - quadblock.triNormalVecDividend[2] = CalculateNormalDividend(4, 1, 2, scaler); - quadblock.triNormalVecDividend[3] = CalculateNormalDividend(3, 4, 6, scaler); - quadblock.triNormalVecDividend[4] = CalculateNormalDividend(7, 4, 5, scaler); - quadblock.triNormalVecDividend[5] = CalculateNormalDividend(5, 8, 7, scaler); - quadblock.triNormalVecDividend[6] = CalculateNormalDividend(2, 5, 4, scaler); - quadblock.triNormalVecDividend[7] = CalculateNormalDividend(6, 4, 7, scaler); - quadblock.triNormalVecDividend[9] = CalculateNormalDividend(2, 8, 6, scaler); /* low LoD */ - quadblock.triNormalVecDividend[8] = CalculateNormalDividend(0, 2, 6, scaler); /* low LoD */ + quadblock.bbox.min = ConvertVec3(m_bbox.min, FP_ONE_GEO); + quadblock.bbox.max = ConvertVec3(m_bbox.max, FP_ONE_GEO); std::memcpy(buffer.data(), &quadblock, sizeof(quadblock)); return buffer; } @@ -937,3 +949,19 @@ void Quadblock::ComputeBoundingBox() m_bbox.min = min; m_bbox.max = max; } + +void Quadblock::SetRawQuadblock(const PSX::Quadblock& quadblock) +{ + m_rawQuadblock = quadblock; + m_hasRawQuadblock = true; +} + +bool Quadblock::HasRawQuadblock() const +{ + return m_hasRawQuadblock; +} + +const PSX::Quadblock& Quadblock::GetRawQuadblock() const +{ + return m_rawQuadblock; +} diff --git a/src/quadblock.h b/src/quadblock.h index 805938a..c3b4776 100644 --- a/src/quadblock.h +++ b/src/quadblock.h @@ -169,6 +169,9 @@ class Quadblock std::vector Serialize(size_t id, size_t offTextures, const std::vector& vertexIndexes) const; bool RenderUI(size_t checkpointCount, bool& resetBsp); Vec3 ComputeNormalVector(size_t id0, size_t id1, size_t id2) const; + void SetRawQuadblock(const PSX::Quadblock& quadblock); + bool HasRawQuadblock() const; + const PSX::Quadblock& GetRawQuadblock() const; private: void ResetUVs(); @@ -211,6 +214,9 @@ class Quadblock std::filesystem::path m_texPath; size_t m_renderPrimitiveIndex = RENDER_INDEX_NONE; UpdateFilterCallback m_filterCallback; + + PSX::Quadblock m_rawQuadblock; + bool m_hasRawQuadblock; }; class QuadException : public std::exception diff --git a/src/vertex.cpp b/src/vertex.cpp index fa21c43..224725d 100644 --- a/src/vertex.cpp +++ b/src/vertex.cpp @@ -35,7 +35,7 @@ std::vector Vertex::Serialize() const PSX::Vertex v = {}; std::vector buffer(sizeof(v)); v.pos = ConvertVec3(m_pos, FP_ONE_GEO); - v.flags = VertexFlags::NONE; + v.flags = m_flags; v.colorHi = ConvertColor(m_colorHigh); v.colorLo = ConvertColor(m_colorLow); std::memcpy(buffer.data(), &v, sizeof(v)); diff --git a/src/vertex.h b/src/vertex.h index 7894902..c93b3b3 100644 --- a/src/vertex.h +++ b/src/vertex.h @@ -20,7 +20,14 @@ class Vertex std::vector Serialize() const; Color GetColor(bool high) const; std::vector ToGeometry(bool highColor = true) const; - inline bool operator==(const Vertex& v) const { return (m_pos == v.m_pos) && (m_flags == v.m_flags) && (m_colorHigh == v.m_colorHigh) && (m_colorLow == v.m_colorLow); }; + inline bool operator==(const Vertex& v) const { + PSX::Vec3 pos1 = ConvertVec3(m_pos, FP_ONE_GEO); + PSX::Vec3 pos2 = ConvertVec3(v.m_pos, FP_ONE_GEO); + return (pos1.x == pos2.x && pos1.y == pos2.y && pos1.z == pos2.z) && + (m_flags == v.m_flags) && + (m_colorHigh == v.m_colorHigh) && + (m_colorLow == v.m_colorLow); + } public: Vec3 m_pos; @@ -39,8 +46,11 @@ struct std::hash { inline std::size_t operator()(const Vertex& key) const noexcept { + PSX::Vec3 pos = ConvertVec3(key.m_pos, FP_ONE_GEO); std::size_t seed = 0; - HashCombine(seed, key.m_pos); + HashCombine(seed, pos.x); + HashCombine(seed, pos.y); + HashCombine(seed, pos.z); HashCombine(seed, key.m_flags); HashCombine(seed, key.m_colorHigh); HashCombine(seed, key.m_colorLow);