Files
CavesOfSwift/Sources/JolkEngine/Loaders/G3DbLoader.swift

252 lines
5.7 KiB
Swift
Raw Normal View History

2024-05-05 17:01:56 +10:00
import Foundation
import OrderedCollections
class G3DbLoader: LoaderProtocol
{
2024-05-09 20:52:01 +10:00
typealias T = Mesh<VertexPositionNormalTexcoord>
2024-05-05 17:01:56 +10:00
func load(url: URL) -> T?
{
guard var reader = try? G3DbReader(url)
else { return Optional.none }
return try? reader.read()
}
2024-05-09 20:52:01 +10:00
func load(url: URL, content: inout ContentManager) -> T? { return load(url: url) }
2024-05-05 17:01:56 +10:00
}
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)
}
2024-05-09 20:52:01 +10:00
mutating func read() throws -> Mesh<VertexPositionNormalTexcoord>
2024-05-05 17:01:56 +10:00
{
let model = try readModel()
let mesh = model.meshes[0]
2024-05-09 20:52:01 +10:00
var vertices = [VertexPositionNormalTexcoord]()
2024-05-05 17:01:56 +10:00
var indices = [UInt16]()
2024-05-09 20:52:01 +10:00
var subMeshes = OrderedDictionary<String, Mesh<VertexPositionNormalTexcoord>.SubMesh>()
2024-05-05 17:01:56 +10:00
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
}