Skip to content

Commit 870abc7

Browse files
committed
Improve RawGeometry optimization
1 parent fd68bc9 commit 870abc7

File tree

1 file changed

+92
-25
lines changed

1 file changed

+92
-25
lines changed

Sources/GateEngine/Resources/Geometry/Raw/RawGeometry.swift

Lines changed: 92 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,7 @@ public struct RawGeometry: Codable, Sendable, Equatable, Hashable {
107107
}
108108

109109
public func flipped() -> RawGeometry {
110-
return RawGeometry(
111-
triangles: self.generateTriangles().map({ $0.flipped() }),
112-
optimizeDistance: nil
113-
)
110+
return RawGeometry(triangles: self.generateTriangles().map({ $0.flipped() }))
114111
}
115112

116113
/// Creates a new `Geometry` from element array values.
@@ -143,10 +140,22 @@ public struct RawGeometry: Codable, Sendable, Equatable, Hashable {
143140
self.colors = colors
144141
self.indices = indices
145142
}
143+
144+
public enum Optimization {
145+
/// Keeps every vertex as is, including duplicates.
146+
/// This option is required for skins as the indices are pre computed
147+
case dontOptimize
148+
/// Compares each vertex using equality. If equal, they are considered the same and will be folded into a single vertex.
149+
case byEquality
150+
/// Compares the vertex components. If the difference between components is within `threshold` they are considered the same and will be folded into a single vertex.
151+
case byThreshold(_ threshold: Float)
152+
/// Checks the result of the provided comparator. If true, the vertices will be folded into a single vertex. The vertex kept is always lhs.
153+
case usingComparator(_ comparator: (_ lhs: Vertex, _ rhs: Vertex) -> Bool)
154+
}
146155

147156
/// Create `Geometry` from counter-clockwise wound `Triangles` and optionanly attempts to optimize the arrays by distance.
148157
/// Optimization is extremely slow and may result in loss of data. It should be used to pre-optimize assets and should not be used at runtime.
149-
public init(triangles: [Triangle], optimizeDistance: Float? = nil) {
158+
public init(triangles: [Triangle], optimization: Optimization = .dontOptimize) {
150159
assert(triangles.isEmpty == false)
151160

152161
self.positions = []
@@ -166,32 +175,90 @@ public struct RawGeometry: Codable, Sendable, Equatable, Hashable {
166175

167176
let inVertices: [Vertex] = triangles.vertices
168177

169-
var similars: [UInt16?]? = nil
170-
if let threshold = optimizeDistance {
171-
similars = Array(repeating: nil, count: inVertices.count)
178+
var optimizedIndicies: [UInt16]
179+
switch optimization {
180+
case .dontOptimize:
181+
assert(inVertices.count < UInt16.max, "Exceeded the maximum number of indices (\(UInt16.max)) for a single geometry. This geometry needs to be spilt up.")
182+
optimizedIndicies = Array(0 ..< UInt16(inVertices.count))
183+
case .byEquality:
184+
optimizedIndicies = Array(repeating: 0, count: inVertices.count)
185+
for index in 0 ..< inVertices.count {
186+
assert(index < UInt16.max, "Exceeded the maximum number of indices (\(UInt16.max)) for a single geometry. This geometry needs to be spilt up.")
187+
let vertex = inVertices[index]
188+
if let similarIndex = inVertices.firstIndex(where: {$0 == vertex}) {
189+
optimizedIndicies[index] = UInt16(similarIndex)
190+
}else{
191+
optimizedIndicies[index] = UInt16(index)
192+
}
193+
}
194+
case .byThreshold(let threshold):
195+
optimizedIndicies = Array(repeating: 0, count: inVertices.count)
196+
for index in 0 ..< inVertices.count {
197+
assert(index < UInt16.max, "Exceeded the maximum number of indices (\(UInt16.max)) for a single geometry. This geometry needs to be spilt up.")
198+
let vertex = inVertices[index]
199+
if let similarIndex = inVertices.firstIndex(where: {$0.isSimilar(to: vertex, threshold: threshold)}) {
200+
optimizedIndicies[index] = UInt16(similarIndex)
201+
}else{
202+
optimizedIndicies[index] = UInt16(index)
203+
}
204+
}
205+
case .usingComparator(let comparator):
206+
optimizedIndicies = Array(repeating: 0, count: inVertices.count)
172207
for index in 0 ..< inVertices.count {
208+
assert(index < UInt16.max, "Exceeded the maximum number of indices (\(UInt16.max)) for a single geometry. This geometry needs to be spilt up.")
173209
let vertex = inVertices[index]
174-
if let similarIndex = Array(inVertices[..<index]).firstIndex(where: {
175-
$0.isSimilar(to: vertex, threshold: threshold)
176-
}) {
177-
similars?[index] = UInt16(similarIndex)
210+
if let similarIndex = inVertices.firstIndex(where: { comparator($0, vertex)}) {
211+
optimizedIndicies[index] = UInt16(similarIndex)
212+
}else{
213+
optimizedIndicies[index] = UInt16(index)
178214
}
179215
}
180216
}
181-
217+
218+
// The next real indices index
182219
var nextIndex = 0
183-
for vertexIndex in inVertices.indices {
184-
if let similarIndex = similars?[vertexIndex] {
185-
indices.append(similarIndex)
186-
} else {
187-
let vertex = inVertices[vertexIndex]
188-
positions.append(contentsOf: vertex.storage[0 ..< 3])
189-
normals.append(contentsOf: vertex.storage[3 ..< 6])
220+
if case .dontOptimize = optimization {
221+
for vertex in inVertices {
222+
self.positions.append(contentsOf: vertex.storage[0 ..< 3])
223+
self.normals.append(contentsOf: vertex.storage[3 ..< 6])
224+
uvSet1.append(contentsOf: vertex.storage[6 ..< 8])
225+
uvSet2.append(contentsOf: vertex.storage[8 ..< 10])
226+
self.tangents.append(contentsOf: vertex.storage[10 ..< 13])
227+
self.colors.append(contentsOf: vertex.storage[13 ..< 17])
228+
229+
self.indices.append(UInt16(nextIndex))
230+
// Increment the next real indicies index
231+
nextIndex += 1
232+
}
233+
}else{
234+
// Store the optimized vertex index using the actual indicies index
235+
// so we can look up the real index for repeated verticies
236+
var indicesMap: [UInt16:UInt16] = [:]
237+
indicesMap.reserveCapacity(inVertices.count)
238+
for vertexIndexInt in inVertices.indices {
239+
// Obtain the optimized vertexIndex for this vertex
240+
let vertexIndex: UInt16 = optimizedIndicies[vertexIndexInt]
241+
242+
// Check our map to see if this vertex was already added
243+
if let index = indicesMap[vertexIndex] {
244+
// Add the repeated index to the indices and continue to the next
245+
self.indices.append(index)
246+
continue
247+
}
248+
249+
let vertex = inVertices[vertexIndexInt]
250+
self.positions.append(contentsOf: vertex.storage[0 ..< 3])
251+
self.normals.append(contentsOf: vertex.storage[3 ..< 6])
190252
uvSet1.append(contentsOf: vertex.storage[6 ..< 8])
191253
uvSet2.append(contentsOf: vertex.storage[8 ..< 10])
192-
tangents.append(contentsOf: vertex.storage[10 ..< 13])
193-
colors.append(contentsOf: vertex.storage[13 ..< 17])
194-
indices.append(UInt16(nextIndex))
254+
self.tangents.append(contentsOf: vertex.storage[10 ..< 13])
255+
self.colors.append(contentsOf: vertex.storage[13 ..< 17])
256+
257+
let index = UInt16(nextIndex)
258+
self.indices.append(index)
259+
// Update the map
260+
indicesMap[vertexIndex] = index
261+
// Increment the next real indicies index
195262
nextIndex += 1
196263
}
197264
}
@@ -203,7 +270,7 @@ public struct RawGeometry: Codable, Sendable, Equatable, Hashable {
203270
for geom in geometries {
204271
triangles.append(contentsOf: geom.generateTriangles())
205272
}
206-
self.init(triangles: triangles, optimizeDistance: nil)
273+
self.init(triangles: triangles)
207274
}
208275

209276
/// Creates a new `Geometry` by merging multiple geometry. This is usful for loading files that store geometry speretly base don material if you intend to only use a single material for them all.
@@ -212,7 +279,7 @@ public struct RawGeometry: Codable, Sendable, Equatable, Hashable {
212279
for geometry in geometries {
213280
triangles.append(contentsOf: geometry.generateTriangles())
214281
}
215-
self.init(triangles: triangles, optimizeDistance: nil)
282+
self.init(triangles: triangles)
216283
}
217284

218285
public func hash(into hasher: inout Hasher) {

0 commit comments

Comments
 (0)