init dump
This commit is contained in:
249
Sources/JolkEngine/Loaders/G3DbLoader.swift
Normal file
249
Sources/JolkEngine/Loaders/G3DbLoader.swift
Normal file
@ -0,0 +1,249 @@
|
||||
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
|
||||
}
|
Reference in New Issue
Block a user