summaryrefslogtreecommitdiff
path: root/src/factories/oot/OoTCollisionFactory.cpp
blob: bd7119965682b67957288bf393642a2d560f6023 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
#include "OoTCollisionFactory.h"
#include "spdlog/spdlog.h"
#include "Companion.h"
#include "utils/Decompressor.h"

namespace OoT {

static void parseCameraData(std::vector<uint8_t>& buffer, YAML::Node& node,
                            uint32_t camDataAddr, uint32_t polyTypeDefAddr,
                            uint32_t polyAddr, uint32_t vtxAddr, uint32_t waterBoxAddr,
                            OoTCollisionData& col) {
    uint32_t camDataSegOff = SEGMENT_OFFSET(camDataAddr);
    uint8_t sceneSeg = SEGMENT_NUMBER(camDataAddr);

    // Determine upper boundary for camera data entries (in segment offsets)
    uint32_t upperBoundary = 0;
    if (polyTypeDefAddr != 0) {
        upperBoundary = SEGMENT_OFFSET(polyTypeDefAddr);
    } else if (polyAddr != 0) {
        upperBoundary = SEGMENT_OFFSET(polyAddr);
    } else if (vtxAddr != 0) {
        upperBoundary = SEGMENT_OFFSET(vtxAddr);
    } else if (waterBoxAddr != 0) {
        upperBoundary = SEGMENT_OFFSET(waterBoxAddr);
    } else {
        upperBoundary = SEGMENT_OFFSET(GetSafeNode<uint32_t>(node, "offset"));
    }

    // Initial Sharp Ocarina check: cam data entries come before the boundary
    // in standard layout. If boundary < camDataSegOff, layout is reversed.
    bool isSharpOcarina = false;
    if (upperBoundary < camDataSegOff) {
        uint32_t scanSize = 0x2000;
        YAML::Node scanNode;
        scanNode["offset"] = camDataAddr;
        auto scanRaw = Decompressor::AutoDecode(scanNode, buffer, scanSize);

        uint32_t offset = 0;
        while (offset + 8 <= scanSize &&
               scanRaw.segment.data[offset] == 0x00 &&
               scanRaw.segment.data[offset + 4] == 0x02) {
            offset += 8;
        }
        upperBoundary = camDataSegOff + offset;
        isSharpOcarina = true;
    }

    uint32_t numEntries = (upperBoundary - camDataSegOff) / 8;
    if (numEntries == 0 || numEntries >= 10000) {
        return;
    }

    YAML::Node cdNode;
    cdNode["offset"] = camDataAddr;
    auto cdRaw = Decompressor::AutoDecode(cdNode, buffer, numEntries * 8);
    LUS::BinaryReader cdReader(cdRaw.segment.data, cdRaw.segment.size);
    cdReader.SetEndianness(Torch::Endianness::Big);

    // Match OTRExporter's per-entry Sharp Ocarina detection:
    // For each entry, if the position pointer's segment offset >= camDataSegOff,
    // it's Sharp Ocarina layout. This also triggers when cameraPosDataSeg == 0
    // and camDataSegOff == 0, since 0 >= 0 (the case for object collisions
    // where cam data is at the start of the file).
    uint32_t lowestCamPosOffset = camDataSegOff;
    uint32_t highestCamPosEnd = camDataSegOff;

    for (uint32_t i = 0; i < numEntries; i++) {
        CamDataEntry entry;
        entry.cameraSType = cdReader.ReadUInt16();
        entry.numData = cdReader.ReadInt16();
        uint32_t cameraPosDataSeg = cdReader.ReadUInt32();
        entry.cameraPosIndex = 0;

        uint32_t posSegOffset = SEGMENT_OFFSET(cameraPosDataSeg);

        if (camDataSegOff > posSegOffset) {
            // Standard layout: positions are before cam data entries
            if (cameraPosDataSeg != 0 && posSegOffset < lowestCamPosOffset) {
                lowestCamPosOffset = posSegOffset;
            }
        } else {
            // Sharp Ocarina layout: positions are after cam data entries
            isSharpOcarina = true;
            if (highestCamPosEnd < posSegOffset) {
                highestCamPosEnd = posSegOffset;
            }
        }

        col.camDataEntries.push_back(entry);
    }

    // Calculate camera position data count and offset (in segment offsets)
    uint32_t camPosDataSegOff;
    uint32_t numPosData;
    if (!isSharpOcarina) {
        camPosDataSegOff = lowestCamPosOffset;
        numPosData = (camDataSegOff - camPosDataSegOff) / 6;
    } else {
        camPosDataSegOff = camDataSegOff + numEntries * 8;
        numPosData = (highestCamPosEnd - camPosDataSegOff + 18) / 6;
    }

    // Read camera position data
    if (numPosData > 0 && numPosData < 100000) {
        uint32_t camPosSeg = (sceneSeg << 24) | camPosDataSegOff;
        YAML::Node cpNode;
        cpNode["offset"] = camPosSeg;
        auto cpRaw = Decompressor::AutoDecode(cpNode, buffer, numPosData * 6);
        LUS::BinaryReader cpReader(cpRaw.segment.data, cpRaw.segment.size);
        cpReader.SetEndianness(Torch::Endianness::Big);

        for (uint32_t i = 0; i < numPosData; i++) {
            CamPosData pos;
            pos.x = cpReader.ReadInt16();
            pos.y = cpReader.ReadInt16();
            pos.z = cpReader.ReadInt16();
            col.camPositions.push_back(pos);
        }
    }

    // Re-read entries to set camera position indices
    cdReader.Seek(0, LUS::SeekOffsetType::Start);
    for (uint32_t i = 0; i < numEntries; i++) {
        cdReader.ReadUInt16(); // skip cameraSType
        cdReader.ReadInt16();  // skip numData
        uint32_t cameraPosDataSeg = cdReader.ReadUInt32();

        if (cameraPosDataSeg != 0) {
            uint32_t posSegOffset = SEGMENT_OFFSET(cameraPosDataSeg);
            col.camDataEntries[i].cameraPosIndex = (posSegOffset - camPosDataSegOff) / 6;
        }
    }
}

std::optional<std::shared_ptr<IParsedData>> OoTCollisionFactory::parse(std::vector<uint8_t>& buffer, YAML::Node& node) {
    // CollisionHeader: 44 bytes (0x2C)
    auto [_, segment] = Decompressor::AutoDecode(node, buffer, 0x2C);
    LUS::BinaryReader reader(segment.data, segment.size);
    reader.SetEndianness(Torch::Endianness::Big);

    auto col = std::make_shared<OoTCollisionData>();

    // Bounding box
    col->absMinX = reader.ReadInt16();
    col->absMinY = reader.ReadInt16();
    col->absMinZ = reader.ReadInt16();
    col->absMaxX = reader.ReadInt16();
    col->absMaxY = reader.ReadInt16();
    col->absMaxZ = reader.ReadInt16();

    uint16_t numVerts = reader.ReadUInt16();
    reader.ReadInt16(); // padding
    uint32_t vtxAddr = Companion::Instance->PatchVirtualAddr(reader.ReadUInt32());

    uint16_t numPolygons = reader.ReadUInt16();
    reader.ReadInt16(); // padding
    uint32_t polyAddr = Companion::Instance->PatchVirtualAddr(reader.ReadUInt32());
    uint32_t polyTypeDefAddr = Companion::Instance->PatchVirtualAddr(reader.ReadUInt32());
    uint32_t camDataAddr = Companion::Instance->PatchVirtualAddr(reader.ReadUInt32());

    uint16_t numWaterBoxes = reader.ReadUInt16();
    reader.ReadInt16(); // padding
    uint32_t waterBoxAddr = Companion::Instance->PatchVirtualAddr(reader.ReadUInt32());

    // Read vertices
    if (numVerts > 0 && vtxAddr != 0) {
        YAML::Node vNode;
        vNode["offset"] = vtxAddr;
        auto vRaw = Decompressor::AutoDecode(vNode, buffer, numVerts * 6);
        LUS::BinaryReader vReader(vRaw.segment.data, vRaw.segment.size);
        vReader.SetEndianness(Torch::Endianness::Big);

        for (uint16_t i = 0; i < numVerts; i++) {
            CollisionVertex v;
            v.x = vReader.ReadInt16();
            v.y = vReader.ReadInt16();
            v.z = vReader.ReadInt16();
            col->vertices.push_back(v);
        }
    }

    // Read polygons
    if (numPolygons > 0 && polyAddr != 0) {
        YAML::Node pNode;
        pNode["offset"] = polyAddr;
        auto pRaw = Decompressor::AutoDecode(pNode, buffer, numPolygons * 16);
        LUS::BinaryReader pReader(pRaw.segment.data, pRaw.segment.size);
        pReader.SetEndianness(Torch::Endianness::Big);

        for (uint16_t i = 0; i < numPolygons; i++) {
            CollisionPoly p;
            p.type = pReader.ReadUInt16();
            p.vtxA = pReader.ReadUInt16();
            p.vtxB = pReader.ReadUInt16();
            p.vtxC = pReader.ReadUInt16();
            p.normX = pReader.ReadUInt16();
            p.normY = pReader.ReadUInt16();
            p.normZ = pReader.ReadUInt16();
            p.dist = pReader.ReadUInt16();
            col->polygons.push_back(p);
        }
    }

    // Read surface types: count = highest polygon type + 1
    if (polyTypeDefAddr != 0 && !col->polygons.empty()) {
        uint16_t highestType = 0;
        for (const auto& p : col->polygons) {
            if (p.type > highestType) highestType = p.type;
        }
        uint32_t numSurfaceTypes = highestType + 1;

        YAML::Node stNode;
        stNode["offset"] = polyTypeDefAddr;
        auto stRaw = Decompressor::AutoDecode(stNode, buffer, numSurfaceTypes * 8);
        LUS::BinaryReader stReader(stRaw.segment.data, stRaw.segment.size);
        stReader.SetEndianness(Torch::Endianness::Big);

        for (uint32_t i = 0; i < numSurfaceTypes; i++) {
            SurfaceType st;
            st.data0 = stReader.ReadUInt32();
            st.data1 = stReader.ReadUInt32();
            col->surfaceTypes.push_back(st);
        }
    }

    // Read camera data
    // Uses segment offsets throughout to match OTRExporter's logic.
    if (camDataAddr != 0) {
        parseCameraData(buffer, node, camDataAddr, polyTypeDefAddr, polyAddr, vtxAddr, waterBoxAddr, *col);
    }

    // Read water boxes
    if (numWaterBoxes > 0 && waterBoxAddr != 0) {
        YAML::Node wbNode;
        wbNode["offset"] = waterBoxAddr;
        auto wbRaw = Decompressor::AutoDecode(wbNode, buffer, numWaterBoxes * 16);
        LUS::BinaryReader wbReader(wbRaw.segment.data, wbRaw.segment.size);
        wbReader.SetEndianness(Torch::Endianness::Big);

        for (uint16_t i = 0; i < numWaterBoxes; i++) {
            WaterBox wb;
            wb.xMin = wbReader.ReadInt16();
            wb.ySurface = wbReader.ReadInt16();
            wb.zMin = wbReader.ReadInt16();
            wb.xLength = wbReader.ReadInt16();
            wb.zLength = wbReader.ReadInt16();
            wbReader.ReadInt16(); // padding
            wb.properties = wbReader.ReadUInt32();
            col->waterBoxes.push_back(wb);
        }
    }

    return col;
}

ExportResult OoTCollisionBinaryExporter::Export(std::ostream& write, std::shared_ptr<IParsedData> raw,
                                                 std::string& entryName, YAML::Node& node,
                                                 std::string* replacement) {
    auto writer = LUS::BinaryWriter();
    auto col = std::static_pointer_cast<OoTCollisionData>(raw);

    WriteHeader(writer, Torch::ResourceType::OoTCollisionHeader, 0);

    // Bounding box
    writer.Write(col->absMinX);
    writer.Write(col->absMinY);
    writer.Write(col->absMinZ);
    writer.Write(col->absMaxX);
    writer.Write(col->absMaxY);
    writer.Write(col->absMaxZ);

    // Vertices
    writer.Write(static_cast<uint32_t>(col->vertices.size()));
    for (auto& v : col->vertices) {
        writer.Write(v.x);
        writer.Write(v.y);
        writer.Write(v.z);
    }

    // Polygons
    writer.Write(static_cast<uint32_t>(col->polygons.size()));
    for (auto& p : col->polygons) {
        writer.Write(p.type);
        writer.Write(p.vtxA);
        writer.Write(p.vtxB);
        writer.Write(p.vtxC);
        writer.Write(p.normX);
        writer.Write(p.normY);
        writer.Write(p.normZ);
        writer.Write(p.dist);
    }

    // Surface types (written in reversed data order to match OTRExporter)
    writer.Write(static_cast<uint32_t>(col->surfaceTypes.size()));
    for (auto& st : col->surfaceTypes) {
        writer.Write(st.data1);
        writer.Write(st.data0);
    }

    // Camera data entries
    writer.Write(static_cast<uint32_t>(col->camDataEntries.size()));
    for (auto& entry : col->camDataEntries) {
        writer.Write(entry.cameraSType);
        writer.Write(entry.numData);
        writer.Write(entry.cameraPosIndex);
    }

    // Camera positions
    writer.Write(static_cast<uint32_t>(col->camPositions.size()));
    for (auto& pos : col->camPositions) {
        writer.Write(pos.x);
        writer.Write(pos.y);
        writer.Write(pos.z);
    }

    // Water boxes
    writer.Write(static_cast<uint32_t>(col->waterBoxes.size()));
    for (auto& wb : col->waterBoxes) {
        writer.Write(wb.xMin);
        writer.Write(wb.ySurface);
        writer.Write(wb.zMin);
        writer.Write(wb.xLength);
        writer.Write(wb.zLength);
        writer.Write(wb.properties);
    }

    writer.Finish(write);
    return std::nullopt;
}

} // namespace OoT