import Foundation import OrderedCollections extension ObjMaterial { func convert(content: UnsafeMutablePointer? = nil) -> Material { var m = Material() m.diffuse = self.diffuse.setAlpha(self.alpha) if ![ .colour, .lambert, .shadowOnly ].contains(self.model) { m.specular = self.specular m.specularExp = self.specularExp } if let content = content { if let albedo = self.diffuseMap { let filter = albedo.flags.contains(.blendHoriz) || albedo.flags.contains(.blendVert) m.texture = (try? content.pointee.load(albedo.path, params: Texture2DParameters( minFilter: filter ? .linear : .point, magFilter: filter ? .linear : .point, wrapMode: albedo.flags.contains(.clamp) ? .clampBorder : .repeating, mipMode: .linear)))?.id ?? RenderTexture2D.empty } } return m } } public struct ObjLoader: LoaderProtocol { typealias V = VertexPositionNormalColourTexcoord public typealias T = Mesh public init() {} public func load(url: URL) -> T? { return try? Self.read(model: try ObjReader.read(url: url), content: nil) } public func load(url: URL, content: inout ContentManager) -> T? { return try? Self.read(model: try ObjReader.read(url: url), content: &content) } public static func read(model: ObjModel, content: UnsafeMutablePointer?) throws -> T { var subMeshes = OrderedDictionary() var materials = OrderedDictionary() var vertices = [V]() var indices = [UInt16]() for (key, mtl) in model.materials { materials[key] = mtl.convert(content: content) } let readIndex = { (v: ObjModel.Index) -> UInt16 in let colour = model.colours.isEmpty ? .white : Colour( r: model.colours[v.p][0], g: model.colours[v.p][1], b: model.colours[v.p][2]).linear let vertex = V( position: model.positions[v.p], colour: colour, normal: model.normals[v.n], texCoord: model.texCoords[v.t]) if let index = vertices.firstIndex(of: vertex) { indices.append(UInt16(index)) return UInt16(index) } else { let index = UInt16(vertices.count) indices.append(index) vertices.append(vertex) return index } } for object in model.objects.filter({ $0.key != "Collision3D" }) { var id = 0 for mesh: ObjModel.Mesh in object.value.meshes { let start = indices.count for face: ObjModel.Face in mesh.faces { switch face { case .triangle(let v1, let v2, let v3): for v in [ v1, v2, v3 ] { _ = readIndex(v) } case .quad(let v1, let v2, let v3, let v4): let n1 = readIndex(v1) _ = readIndex(v2) indices.append(readIndex(v3)) _ = readIndex(v4) indices.append(n1) case .ngon(_): fallthrough default: break } } let length = indices.count - start if length > 0 { subMeshes["\(object.key)_\(id)"] = .init( start: start, length: length, material: materials.index(forKey: mesh.material) ?? -1) } id += 1 } } return Mesh(vertices: vertices, indices: indices, subMeshes: subMeshes, materials: materials.values.map { $0 }) } }