250 lines
5.5 KiB
Swift
250 lines
5.5 KiB
Swift
import Foundation
|
|
import OrderedCollections
|
|
|
|
|
|
class G3DbLoader: LoaderProtocol
|
|
{
|
|
typealias T = Mesh
|
|
|
|
func load(url: URL) -> T?
|
|
{
|
|
guard var reader = try? G3DbReader(url)
|
|
else { return Optional.none }
|
|
return try? reader.read()
|
|
}
|
|
}
|
|
|
|
|
|
fileprivate struct G3DbReader
|
|
{
|
|
private let version: [Int16] = [0, 1]
|
|
private var reader: UBJsonReader
|
|
|
|
init(_ url: URL) throws
|
|
{
|
|
guard let file = try? FileHandle(forReadingFrom: url)
|
|
else { throw G3DbReaderError.fileNotFound }
|
|
self.reader = UBJsonReader(file: file)
|
|
}
|
|
|
|
mutating func read() throws -> Mesh
|
|
{
|
|
let model = try readModel()
|
|
|
|
let mesh = model.meshes[0]
|
|
|
|
var vertices = [Mesh.Vertex]()
|
|
var indices = [UInt16]()
|
|
var subMeshes = OrderedDictionary<String, Mesh.SubMesh>()
|
|
|
|
let attributeSize =
|
|
{ (attrib: G3DAttribute) in
|
|
switch attrib
|
|
{
|
|
case .position, .normal, .colour, .tangent, .bitangent:
|
|
return 3
|
|
case .texCoord(_), .blendWeight(_):
|
|
return 2
|
|
case .colourPacked:
|
|
return 1
|
|
}
|
|
}
|
|
|
|
var stride = 0
|
|
for a in mesh.attributes
|
|
{
|
|
stride += attributeSize(a)
|
|
}
|
|
let numVerts = mesh.vertices.count / stride
|
|
vertices.reserveCapacity(numVerts)
|
|
for i in 0..<numVerts
|
|
{
|
|
var srcIdx = i * stride
|
|
var position: Vec3f = .zero, normal: Vec3f = .zero
|
|
var texCoord: Vec2f = .zero
|
|
for a in mesh.attributes
|
|
{
|
|
switch a
|
|
{
|
|
case .position:
|
|
position = .init(
|
|
mesh.vertices[srcIdx],
|
|
mesh.vertices[srcIdx + 2],
|
|
-mesh.vertices[srcIdx + 1])
|
|
case .normal:
|
|
normal = .init(
|
|
mesh.vertices[srcIdx],
|
|
mesh.vertices[srcIdx + 2],
|
|
-mesh.vertices[srcIdx + 1])
|
|
case .texCoord(id: 0):
|
|
texCoord = .init(
|
|
mesh.vertices[srcIdx],
|
|
1.0 - mesh.vertices[srcIdx + 1])
|
|
default: break
|
|
}
|
|
srcIdx += attributeSize(a)
|
|
}
|
|
vertices.append(.init(position: position, normal: normal, texCoord: texCoord))
|
|
}
|
|
for part in mesh.parts
|
|
{
|
|
subMeshes[part.key] = .init(start: indices.count, length: part.value.indices.count)
|
|
indices += part.value.indices.map { UInt16($0) }
|
|
}
|
|
return Mesh(vertices: vertices, indices: indices, subMeshes: subMeshes)
|
|
}
|
|
|
|
mutating func readModel() throws -> G3DModel
|
|
{
|
|
let root = try reader.read()
|
|
let version = try root.getArray(key: "version")
|
|
guard try version.count == 2 && (try version.map({ try $0.int16 }) == self.version)
|
|
else { throw G3DbReaderError.versionMismatch }
|
|
|
|
var model = G3DModel()
|
|
model.id = try root.getString(key: "id", default: "")
|
|
try readMeshes(root, &model)
|
|
try readMaterials(root, &model)
|
|
return model
|
|
}
|
|
|
|
mutating func readMeshes(_ root: UBJsonToken, _ model: inout G3DModel) throws
|
|
{
|
|
let meshes = try root.getArray(key: "meshes")
|
|
for obj in meshes
|
|
{
|
|
var mesh = G3DModelMesh()
|
|
|
|
mesh.id = try obj.getString(key: "id", default: "")
|
|
for attrib in try obj.getArray(key: "attributes")
|
|
{
|
|
mesh.attributes.append(try .resolve(try attrib.string))
|
|
}
|
|
mesh.vertices = try obj.getFloatArray(key: "vertices")
|
|
for partObj in try obj.getArray(key: "parts")
|
|
{
|
|
let id = try partObj.getString(key: "id")
|
|
if mesh.parts.keys.contains(id) { throw G3DbReaderError.duplicateIDs }
|
|
|
|
var part = G3dModelMeshPart()
|
|
part.mode = try .resolve(try partObj.getString(key: "type"))
|
|
part.indices = try partObj.getInt16Array(key: "indices")
|
|
|
|
mesh.parts[id] = part
|
|
}
|
|
model.meshes.append(mesh)
|
|
}
|
|
}
|
|
|
|
mutating func readMaterials(_ root: UBJsonToken, _ model: inout G3DModel) throws
|
|
{
|
|
|
|
}
|
|
}
|
|
|
|
fileprivate struct G3DModel
|
|
{
|
|
var id: String = .init()
|
|
var meshes: [G3DModelMesh] = .init()
|
|
}
|
|
|
|
fileprivate struct G3DModelMesh
|
|
{
|
|
var id: String = .init()
|
|
var attributes: [G3DAttribute] = .init()
|
|
var vertices: [Float] = .init()
|
|
var parts: Dictionary<String, G3dModelMeshPart> = .init()
|
|
}
|
|
|
|
fileprivate struct G3dModelMeshPart
|
|
{
|
|
var mode: G3DPrimativeType = .invalid
|
|
var indices: [Int16] = .init()
|
|
}
|
|
|
|
fileprivate enum G3DAttribute: Equatable
|
|
{
|
|
case position
|
|
case normal
|
|
case tangent
|
|
case bitangent
|
|
case colour
|
|
case colourPacked
|
|
case texCoord(id: UInt8)
|
|
case blendWeight(id: UInt8)
|
|
|
|
static let order = [ .position, .colour, .colourPacked, .normal,
|
|
texCoord(id: 0), .blendWeight(id: 0), .tangent, .bitangent ]
|
|
}
|
|
|
|
extension G3DAttribute
|
|
{
|
|
static func resolve(_ attrib: String) throws -> Self
|
|
{
|
|
let getAttributeId =
|
|
{ (attrib: String, offset: Int) throws -> UInt8 in
|
|
let idIdx = attrib.index(attrib.startIndex, offsetBy: offset)
|
|
let idStr = attrib.suffix(from: idIdx)
|
|
guard let id = UInt8(idStr)
|
|
else { throw G3DbReaderError.badAttribute }
|
|
return id
|
|
}
|
|
|
|
return switch attrib
|
|
{
|
|
case "POSITION": .position
|
|
case "NORMAL": .normal
|
|
case "COLOR": .colour
|
|
case "COLORPACKED": .colourPacked
|
|
case "TANGENT": .tangent
|
|
case "BINORMAL": .bitangent
|
|
default:
|
|
if attrib.starts(with: "TEXCOORD")
|
|
{
|
|
.texCoord(id: try getAttributeId(attrib, 8))
|
|
}
|
|
else if attrib.starts(with: "BLENDWEIGHT")
|
|
{
|
|
.blendWeight(id: try getAttributeId(attrib, 11))
|
|
}
|
|
else { throw G3DbReaderError.badAttribute }
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate enum G3DPrimativeType
|
|
{
|
|
case invalid
|
|
case triangles
|
|
case lines
|
|
case points
|
|
case triangleStrip
|
|
case lineStrip
|
|
}
|
|
|
|
extension G3DPrimativeType
|
|
{
|
|
static func resolve(_ key: String) throws -> Self
|
|
{
|
|
switch key
|
|
{
|
|
case "TRIANGLES": .triangles
|
|
case "LINES": .lines
|
|
case "POINTS": .points
|
|
case "TRIANGLE_STRIP": .triangleStrip
|
|
case "LINE_STRIP": .lineStrip
|
|
default: throw G3DbReaderError.invalidFormat
|
|
}
|
|
}
|
|
}
|
|
|
|
fileprivate enum G3DbReaderError: Error
|
|
{
|
|
case fileNotFound
|
|
case versionMismatch
|
|
case unsupportedFormat
|
|
case invalidFormat
|
|
case badAttribute
|
|
case duplicateIDs
|
|
}
|