-
-
Notifications
You must be signed in to change notification settings - Fork 10
/
WavefrontOBJ.swift
138 lines (128 loc) · 6.23 KB
/
WavefrontOBJ.swift
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
/*
* Copyright © 2023-2024 Dustin Collins (Strega's Gate)
* All Rights Reserved.
*
* http://stregasgate.com
*/
import Foundation
public final class WavefrontOBJImporter: GeometryImporter {
public required init() {}
public func loadData(path: String, options: GeometryImporterOptions) async throws -> RawGeometry {
let data = try await Game.shared.platform.loadResource(from: path)
guard let obj = String(data: data, encoding: .utf8) else {
throw GateEngineError.failedToDecode("File is not UTF8 or is corrupt.")
}
let lines = obj.components(separatedBy: "\n").map({
$0.trimmingCharacters(in: .whitespacesAndNewlines)
})
var prefix = "o "
if let name = options.subobjectName {
prefix += name
}
var index: Array<String>.Index = 0
if let objectIndex = lines.firstIndex(where: { $0.hasPrefix(prefix) }) {
index = objectIndex + 1 //Skip the object itself
} else if let objectIndex = lines.firstIndex(where: { $0.hasPrefix("o ") }) {
index = objectIndex + 1 //Skip the object itself
}
var positions: [Position3] = []
var uvs: [Position2] = []
var normals: [Direction3] = []
var triangles: [Triangle] = []
for line in lines[index...] {
// If we reach the next object, exit loop
guard line.hasPrefix("o ") == false else { break }
if line.hasPrefix("v ") {
func rawPositionConvert(_ string: String) throws -> Position3 {
let comps = string.components(separatedBy: " ")
let floats = comps.compactMap({ Float($0) })
guard floats.count == 3 else {
throw GateEngineError.failedToDecode(
"File malformed vertex position: \(line)"
)
}
return Position3(floats[0], floats[1], floats[2])
}
positions.append(try rawPositionConvert(line))
} else if line.hasPrefix("vt ") {
func rawUVConvert(_ string: String) throws -> Position2 {
let comps = string.components(separatedBy: " ")
let floats = comps.compactMap({ Float($0) })
guard floats.count == 2 else {
throw GateEngineError.failedToDecode(
"File malformed vertex texture coord: \(line)"
)
}
return Position2(floats[0], 1 - floats[1])
}
uvs.append(try rawUVConvert(line))
} else if line.hasPrefix("vn ") {
func rawNormalConvert(_ string: String) throws -> Direction3 {
let comps = string.components(separatedBy: " ")
let floats = comps.compactMap({ Float($0) })
guard floats.count == 3 else {
throw GateEngineError.failedToDecode(
"File malformed at vertex Normal: \(line)."
)
}
return Direction3(floats[0], floats[1], floats[2])
}
normals.append(try rawNormalConvert(line))
} else if line.hasPrefix("f ") {
func rawVertexConvert(_ string: String) throws -> Vertex {
let comps = string.components(separatedBy: "/")
let indices = comps.compactMap({ Int($0) }).map({ $0 - 1 }) //convert to base zero index
if indices.count >= 3 { //Has normals and possibly other stuff
return Vertex(positions[indices[0]], normals[indices[2]], uvs[indices[1]])
} else if indices.count == 2 { //Just position and texture or position and normal
let index1 = indices[1]
let hasUVs = uvs.indices.contains(index1)
let hasNormals = normals.indices.contains(index1)
return Vertex(positions[indices[0]],
hasNormals ? normals[indices[1]] : .zero,
hasUVs ? uvs[indices[1]] : .zero)
} else if indices.count == 1 { // Just position
return Vertex(positions[indices[0]], .zero, .zero)
} else {
throw GateEngineError.failedToDecode(
"File malformed at vertex from face: \(string)."
)
}
}
func rawTriangleConvert(_ string: String) throws -> [Triangle] {
let comps = string.components(separatedBy: " ")
var verts: [Vertex] = []
for raw in comps[1...] {
verts.append(try rawVertexConvert(raw))
}
if verts.count >= 3 { // N-Gon
var triangles = [
Triangle(v1: verts[0], v2: verts[1], v3: verts[2], repairIfNeeded: true)
]
for i in 1 ..< verts.count - 1 {
triangles.append(
Triangle(
v1: verts[0],
v2: verts[i],
v3: verts[i + 1],
repairIfNeeded: true
)
)
}
return triangles
} else {
throw GateEngineError.failedToDecode("File malformed at face: \(string)")
}
}
triangles.append(contentsOf: try rawTriangleConvert(line))
}
}
guard triangles.isEmpty == false else {
throw GateEngineError.failedToDecode("No triangles to create the geometry with.")
}
return RawGeometry(triangles: triangles)
}
public static func canProcessFile(_ file: URL) -> Bool {
return file.pathExtension.caseInsensitiveCompare("obj") == .orderedSame
}
}