Skip to content

Commit 08e5595

Browse files
committed
Add binary coder & decoder for collision
1 parent c639e19 commit 08e5595

File tree

1 file changed

+197
-0
lines changed

1 file changed

+197
-0
lines changed
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
//
2+
// CollisionTrianglesCoder.swift
3+
// GateEngine
4+
//
5+
// Created by Dustin Collins on 4/8/25.
6+
//
7+
8+
import GameMath
9+
10+
public final class CollisionMeshEncoder {
11+
public func encode(_ collisionMesh: CollisionMesh) throws(EncodingError) -> Data {
12+
return try CollisionMeshEncodableRepresenation_v1.encode(collisionMesh)
13+
}
14+
15+
public enum EncodingError: Error {
16+
/// The encoding failed due to an unknown reason
17+
case encodingFailed
18+
}
19+
20+
public init() {
21+
22+
}
23+
}
24+
25+
public final class CollisionMeshDecoder {
26+
public func decode(_ data: Data) throws(DecodingError) -> CollisionMesh {
27+
guard data.isEmpty == false else { throw .decodingFailed }
28+
let header = data.withUnsafeBytes { data in
29+
data.load(as: CollisionMeshCodableHeader.self)
30+
}
31+
guard header.isValid else { throw .invalidFormat }
32+
guard let version = header.version else { throw .unsupportedVersion }
33+
guard header.documentLength == data.count else { throw .decodingFailed }
34+
switch version {
35+
case .v1:
36+
return try CollisionMeshEncodableRepresenation_v1.decode(data)
37+
}
38+
}
39+
40+
public enum DecodingError: Error {
41+
/// The data is empty or corrupted.
42+
case decodingFailed
43+
/// The data does not appear to be intended for this decoder.
44+
case invalidFormat
45+
/// This version of GateEngine doesn't support loading this file.
46+
/// - note: It's best to encode and decode with the same GateEngine version.
47+
case unsupportedVersion
48+
}
49+
50+
public init() {
51+
52+
}
53+
}
54+
55+
fileprivate struct CollisionMeshCodableHeader {
56+
static let magic1: UInt32 = 0x45544147 // "GATE"
57+
static let magic2: UInt32 = 0x434D5348 // "CMSH"
58+
let magic1: UInt32
59+
let magic2: UInt32
60+
let _version: UInt8
61+
private let _reserved1: UInt8 = 0
62+
private let _reserved2: UInt8 = 0
63+
private let _reserved3: UInt8 = 0
64+
var documentLength: UInt32 = 0
65+
66+
enum Version: UInt8 {
67+
case v1 = 1
68+
}
69+
70+
var version: Version? {
71+
return Version(rawValue: _version)
72+
}
73+
74+
init(version: Version) {
75+
self.magic1 = Self.magic1
76+
self.magic2 = Self.magic2
77+
self._version = version.rawValue
78+
}
79+
80+
var isValid: Bool {
81+
return self.magic1 == Self.magic1 && self.magic2 == Self.magic2
82+
}
83+
}
84+
85+
fileprivate struct CollisionMeshEncodableRepresenation_v1 {
86+
struct Header {
87+
var version: CollisionMeshCodableHeader
88+
var dataOffsets: DataOffsets
89+
struct DataOffsets {
90+
var indices: UInt32 = 0
91+
var positions: UInt32 = 0
92+
var normals: UInt32 = 0
93+
var attributes: UInt32 = 0
94+
}
95+
96+
init(collisionMesh: CollisionMesh) {
97+
self.version = CollisionMeshCodableHeader(version: .v1)
98+
self.dataOffsets = DataOffsets()
99+
}
100+
}
101+
struct CompactTriangleIndices {
102+
let p1: UInt16
103+
let p2: UInt16
104+
let p3: UInt16
105+
let center: UInt16
106+
let normal: UInt16
107+
let faceNormal: UInt16
108+
let attributes: UInt16
109+
110+
var native: CollisionMesh.TriangleIndices {
111+
return CollisionMesh.TriangleIndices(
112+
p1: Int(p1),
113+
p2: Int(p2),
114+
p3: Int(p3),
115+
center: Int(center),
116+
normal: Int(normal),
117+
faceNormal: Int(faceNormal),
118+
attributes: Int(attributes)
119+
)
120+
}
121+
122+
init(_ triangleIndices: CollisionMesh.TriangleIndices) {
123+
self.p1 = UInt16(triangleIndices.p1)
124+
self.p2 = UInt16(triangleIndices.p2)
125+
self.p3 = UInt16(triangleIndices.p3)
126+
self.center = UInt16(triangleIndices.center)
127+
self.normal = UInt16(triangleIndices.normal)
128+
self.faceNormal = UInt16(triangleIndices.faceNormal)
129+
self.attributes = UInt16(triangleIndices.attributes)
130+
}
131+
}
132+
133+
static func encode(_ collisionMesh: CollisionMesh) throws(CollisionMeshEncoder.EncodingError) -> Data {
134+
var header = Header(collisionMesh: collisionMesh)
135+
var data = Data(repeating: 0, count: MemoryLayout<Header>.size)
136+
137+
header.dataOffsets.indices = UInt32(data.count)
138+
collisionMesh.indices.map({CompactTriangleIndices($0)}).withUnsafeBytes { bytes in
139+
data.append(contentsOf: bytes)
140+
}
141+
142+
header.dataOffsets.positions = UInt32(data.count)
143+
collisionMesh.components.positions.withUnsafeBytes { bytes in
144+
data.append(contentsOf: bytes)
145+
}
146+
147+
header.dataOffsets.normals = UInt32(data.count)
148+
collisionMesh.components.normals.withUnsafeBytes { bytes in
149+
data.append(contentsOf: bytes)
150+
}
151+
152+
header.dataOffsets.attributes = UInt32(data.count)
153+
collisionMesh.components.attributes.withUnsafeBytes { bytes in
154+
data.append(contentsOf: bytes)
155+
}
156+
157+
// Update header documentLength
158+
header.version.documentLength = UInt32(data.count)
159+
160+
// Replace header bytes
161+
data.withUnsafeMutableBytes { data in
162+
withUnsafeBytes(of: header) { headerBytes in
163+
data.copyMemory(from: headerBytes)
164+
}
165+
}
166+
167+
return data
168+
}
169+
170+
static func decode(_ data: Data) throws(CollisionMeshDecoder.DecodingError) -> CollisionMesh {
171+
return data.withUnsafeBytes({ (data: UnsafeRawBufferPointer) -> CollisionMesh in
172+
let header = data.load(as: Header.self)
173+
174+
let indicesOffset = Int(header.dataOffsets.indices)
175+
let positionsOffset = Int(header.dataOffsets.positions)
176+
let normalsOffset = Int(header.dataOffsets.normals)
177+
let attributesOffset = Int(header.dataOffsets.attributes)
178+
179+
let indicesCount = (positionsOffset - indicesOffset) / MemoryLayout<CompactTriangleIndices>.size
180+
let positionsCount = (normalsOffset - positionsOffset) / MemoryLayout<Float>.size
181+
let normalsCount = (attributesOffset - normalsOffset) / MemoryLayout<Float>.size
182+
let attributesCount = (Int(header.version.documentLength) - attributesOffset) / MemoryLayout<UInt64>.size
183+
184+
let indices = UnsafeBufferPointer(start: data.baseAddress!.advanced(by: indicesOffset).assumingMemoryBound(to: CompactTriangleIndices.self), count: indicesCount)
185+
let positions = UnsafeBufferPointer(start: data.baseAddress!.advanced(by: positionsOffset).assumingMemoryBound(to: Float.self), count: positionsCount)
186+
let normals = UnsafeBufferPointer(start: data.baseAddress!.advanced(by: normalsOffset).assumingMemoryBound(to: Float.self), count: normalsCount)
187+
let attributes = UnsafeBufferPointer(start: data.baseAddress!.advanced(by: attributesOffset).assumingMemoryBound(to: UInt64.self), count: attributesCount)
188+
189+
return CollisionMesh(
190+
indices: indices.map({$0.native}),
191+
positions: Array(positions),
192+
normals: Array(normals),
193+
attributes: Array(attributes)
194+
)
195+
})
196+
}
197+
}

0 commit comments

Comments
 (0)