Mercurial > games > semicongine
comparison semiconginev2/gltf.nim @ 1250:9ceb509af5ea
add: loading of most kinds of data from gltf
| author | sam <sam@basx.dev> |
|---|---|
| date | Thu, 25 Jul 2024 23:15:05 +0700 |
| parents | d83726af7abb |
| children | 3f98ad20a9d3 |
comparison
equal
deleted
inserted
replaced
| 1249:d83726af7abb | 1250:9ceb509af5ea |
|---|---|
| 1 type | 1 type |
| 2 GLTFMesh*[TMesh, TMaterial] = object | 2 GltfNode* = object |
| 3 children: seq[int] | |
| 4 mesh: int | |
| 5 transform: Mat4 | |
| 6 GltfMesh*[TMesh, TMaterial] = object | |
| 3 scenes*: seq[seq[int]] # each scene has a seq of node indices | 7 scenes*: seq[seq[int]] # each scene has a seq of node indices |
| 4 nodes*: seq[seq[int]] # each node has a seq of mesh indices | 8 nodes*: seq[GltfNode] # each node has a seq of mesh indices |
| 5 meshes*: seq[seq[(TMesh, VkPrimitiveTopology)]] | 9 meshes*: seq[seq[(TMesh, VkPrimitiveTopology)]] |
| 6 materials*: seq[TMaterial] | 10 materials*: seq[TMaterial] |
| 7 textures*: seq[Image[BGRA]] | 11 textures*: seq[Image[BGRA]] |
| 8 glTFHeader = object | 12 glTFHeader = object |
| 9 magic: uint32 | 13 magic: uint32 |
| 41 JOINTS*: seq[string] | 45 JOINTS*: seq[string] |
| 42 WEIGHTS*: seq[string] | 46 WEIGHTS*: seq[string] |
| 43 indices*: string | 47 indices*: string |
| 44 material*: string | 48 material*: string |
| 45 | 49 |
| 46 #[ | |
| 47 static: | |
| 48 let TypeIds = { | |
| 49 int8: 5120, | |
| 50 uint8: 5121, | |
| 51 int16: 5122, | |
| 52 uint16: 5123, | |
| 53 uint32: 5125, | |
| 54 float32: 5126, | |
| 55 }.toTable | |
| 56 ]# | |
| 57 | |
| 58 const | 50 const |
| 59 HEADER_MAGIC = 0x46546C67 | 51 HEADER_MAGIC = 0x46546C67 |
| 60 JSON_CHUNK = 0x4E4F534A | 52 JSON_CHUNK = 0x4E4F534A |
| 61 BINARY_CHUNK = 0x004E4942 | 53 BINARY_CHUNK = 0x004E4942 |
| 62 #[ | |
| 63 ACCESSOR_TYPE_MAP = { | |
| 64 5120: Int8, | |
| 65 5121: UInt8, | |
| 66 5122: Int16, | |
| 67 5123: UInt16, | |
| 68 5125: UInt32, | |
| 69 5126: Float32, | |
| 70 }.toTable | |
| 71 ]# | |
| 72 SAMPLER_FILTER_MODE_MAP = { | 54 SAMPLER_FILTER_MODE_MAP = { |
| 73 9728: VK_FILTER_NEAREST, | 55 9728: VK_FILTER_NEAREST, |
| 74 9729: VK_FILTER_LINEAR, | 56 9729: VK_FILTER_LINEAR, |
| 75 9984: VK_FILTER_NEAREST, | 57 9984: VK_FILTER_NEAREST, |
| 76 9985: VK_FILTER_LINEAR, | 58 9985: VK_FILTER_LINEAR, |
| 89 3: VK_PRIMITIVE_TOPOLOGY_LINE_STRIP, | 71 3: VK_PRIMITIVE_TOPOLOGY_LINE_STRIP, |
| 90 4: VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, | 72 4: VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, |
| 91 5: VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, | 73 5: VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, |
| 92 6: VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN, | 74 6: VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN, |
| 93 ] | 75 ] |
| 94 | |
| 95 #[ | |
| 96 proc getGPUType(accessor: JsonNode, attribute: string): DataType = | |
| 97 # TODO: no full support for all datatypes that glTF may provide | |
| 98 # semicongine/core/gpu_data should maybe generated with macros to allow for all combinations | |
| 99 let componentType = ACCESSOR_TYPE_MAP[accessor["componentType"].getInt()] | |
| 100 let theType = accessor["type"].getStr() | |
| 101 case theType | |
| 102 of "SCALAR": | |
| 103 return componentType | |
| 104 of "VEC2": | |
| 105 case componentType | |
| 106 of UInt32: return Vec2U32 | |
| 107 of Float32: return Vec2F32 | |
| 108 else: raise newException(Exception, &"Unsupported data type for attribute '{attribute}': {componentType} {theType}") | |
| 109 of "VEC3": | |
| 110 case componentType | |
| 111 of UInt32: return Vec3U32 | |
| 112 of Float32: return Vec3F32 | |
| 113 else: raise newException(Exception, &"Unsupported data type for attribute '{attribute}': {componentType} {theType}") | |
| 114 of "VEC4": | |
| 115 case componentType | |
| 116 of UInt32: return Vec4U32 | |
| 117 of Float32: return Vec4F32 | |
| 118 else: raise newException(Exception, &"Unsupported data type for attribute '{attribute}': {componentType} {theType}") | |
| 119 of "MAT2": | |
| 120 case componentType | |
| 121 of Float32: return Vec4F32 | |
| 122 else: raise newException(Exception, &"Unsupported data type for attribute '{attribute}': {componentType} {theType}") | |
| 123 of "MAT3": | |
| 124 case componentType | |
| 125 of Float32: return Vec4F32 | |
| 126 else: raise newException(Exception, &"Unsupported data type for attribute '{attribute}': {componentType} {theType}") | |
| 127 of "MAT4": | |
| 128 case componentType | |
| 129 of Float32: return Vec4F32 | |
| 130 else: raise newException(Exception, &"Unsupported data type for attribute '{attribute}': {componentType} {theType}") | |
| 131 ]# | |
| 132 | 76 |
| 133 proc getBufferViewData(bufferView: JsonNode, mainBuffer: seq[uint8], baseBufferOffset = 0): seq[uint8] = | 77 proc getBufferViewData(bufferView: JsonNode, mainBuffer: seq[uint8], baseBufferOffset = 0): seq[uint8] = |
| 134 assert bufferView["buffer"].getInt() == 0, "Currently no external buffers supported" | 78 assert bufferView["buffer"].getInt() == 0, "Currently no external buffers supported" |
| 135 | 79 |
| 136 result = newSeq[uint8](bufferView["byteLength"].getInt()) | 80 result = newSeq[uint8](bufferView["byteLength"].getInt()) |
| 276 let gltfAttributeIndexed = gltfAttribute & "_" & $i | 220 let gltfAttributeIndexed = gltfAttribute & "_" & $i |
| 277 if primitive["attributes"].hasKey(gltfAttributeIndexed): | 221 if primitive["attributes"].hasKey(gltfAttributeIndexed): |
| 278 let accessor = primitive["attributes"][gltfAttributeIndexed].getInt() | 222 let accessor = primitive["attributes"][gltfAttributeIndexed].getInt() |
| 279 resultValue.data = getAccessorData[elementType(resultValue.data)](root, root["accessors"][accessor], mainBuffer) | 223 resultValue.data = getAccessorData[elementType(resultValue.data)](root, root["accessors"][accessor], mainBuffer) |
| 280 inc i | 224 inc i |
| 281 #[ | 225 |
| 282 when gltfAttribute == "indices": | 226 proc loadNode(node: JsonNode): GltfNode = |
| 283 if primitive.hasKey(gltfAttribute): | 227 result.transform = Unit4 |
| 284 let accessor = primitive[gltfAttribute].getInt() | 228 if "mesh" in node: |
| 285 value.data = getAccessorData[elementType(value.data)](root, root["accessors"][accessor], mainBuffer) | 229 result.mesh = node["mesh"].getInt() |
| 286 elif gltfAttribute == "material": | 230 if "children" in node: |
| 287 if primitive.hasKey(gltfAttribute): | 231 for child in items(node["children"]): |
| 288 value.data = typeof(value.data)(primitive[gltfAttribute].getInt()) | 232 result.children.add child.getInt() |
| 289 else: | 233 if "matrix" in node: |
| 290 if primitive["attributes"].hasKey(gltfAttribute): | |
| 291 let accessor = primitive["attributes"][gltfAttribute].getInt() | |
| 292 value.data = getAccessorData[elementType(value.data)](root, root["accessors"][accessor], mainBuffer) | |
| 293 ]# | |
| 294 | |
| 295 #[ | |
| 296 var indexType = None | |
| 297 let indexed = primitive.hasKey("indices") | |
| 298 if indexed: | |
| 299 var indexCount = root["accessors"][primitive["indices"].getInt()]["count"].getInt() | |
| 300 if indexCount < int(high(uint16)): | |
| 301 indexType = Small | |
| 302 else: | |
| 303 indexType = Big | |
| 304 | |
| 305 for attribute, accessor in primitive["attributes"].pairs: | |
| 306 let data = root.getAccessorData(root["accessors"][accessor.getInt()], mainBuffer) | |
| 307 if result.vertexCount == 0: | |
| 308 result.vertexCount = data.len | |
| 309 assert data.len == result.vertexCount | |
| 310 result[].InitVertexAttribute(attribute.toLowerAscii, data) | |
| 311 | |
| 312 if primitive.hasKey("material"): | |
| 313 let materialId = primitive["material"].getInt() | |
| 314 result[].material = materials[materialId] | |
| 315 else: | |
| 316 result[].material = EMPTY_MATERIAL.InitMaterialData() | |
| 317 | |
| 318 if primitive.hasKey("indices"): | |
| 319 assert result[].indexType != None | |
| 320 let data = root.getAccessorData(root["accessors"][primitive["indices"].getInt()], mainBuffer) | |
| 321 var tri: seq[int] | |
| 322 case data.thetype | |
| 323 of UInt16: | |
| 324 for entry in data[uint16][]: | |
| 325 tri.add int(entry) | |
| 326 if tri.len == 3: | |
| 327 # FYI gltf uses counter-clockwise indexing | |
| 328 result[].AppendIndicesData(tri[0], tri[1], tri[2]) | |
| 329 tri.setLen(0) | |
| 330 of UInt32: | |
| 331 for entry in data[uint32][]: | |
| 332 tri.add int(entry) | |
| 333 if tri.len == 3: | |
| 334 # FYI gltf uses counter-clockwise indexing | |
| 335 result[].AppendIndicesData(tri[0], tri[1], tri[2]) | |
| 336 tri.setLen(0) | |
| 337 else: | |
| 338 raise newException(Exception, &"Unsupported index data type: {data.thetype}") | |
| 339 ]# | |
| 340 | |
| 341 | |
| 342 #[ | |
| 343 | |
| 344 proc loadPrimitive(meshname: string, root: JsonNode, primitiveNode: JsonNode, materials: seq[MaterialData], mainBuffer: seq[uint8]): Mesh = | |
| 345 if primitiveNode.hasKey("mode") and primitiveNode["mode"].getInt() != 4: | |
| 346 raise newException(Exception, "Currently only TRIANGLE mode is supported for geometry mode") | |
| 347 | |
| 348 var indexType = None | |
| 349 let indexed = primitiveNode.hasKey("indices") | |
| 350 if indexed: | |
| 351 # TODO: Tiny indices | |
| 352 var indexCount = root["accessors"][primitiveNode["indices"].getInt()]["count"].getInt() | |
| 353 if indexCount < int(high(uint16)): | |
| 354 indexType = Small | |
| 355 else: | |
| 356 indexType = Big | |
| 357 | |
| 358 result = Mesh( | |
| 359 instanceTransforms: @[Unit4F32], | |
| 360 indexType: indexType, | |
| 361 name: meshname, | |
| 362 vertexCount: 0, | |
| 363 ) | |
| 364 | |
| 365 for attribute, accessor in primitiveNode["attributes"].pairs: | |
| 366 let data = root.getAccessorData(root["accessors"][accessor.getInt()], mainBuffer) | |
| 367 if result.vertexCount == 0: | |
| 368 result.vertexCount = data.len | |
| 369 assert data.len == result.vertexCount | |
| 370 result[].InitVertexAttribute(attribute.toLowerAscii, data) | |
| 371 | |
| 372 if primitiveNode.hasKey("material"): | |
| 373 let materialId = primitiveNode["material"].getInt() | |
| 374 result[].material = materials[materialId] | |
| 375 else: | |
| 376 result[].material = EMPTY_MATERIAL.InitMaterialData() | |
| 377 | |
| 378 if primitiveNode.hasKey("indices"): | |
| 379 assert result[].indexType != None | |
| 380 let data = root.getAccessorData(root["accessors"][primitiveNode["indices"].getInt()], mainBuffer) | |
| 381 var tri: seq[int] | |
| 382 case data.thetype | |
| 383 of UInt16: | |
| 384 for entry in data[uint16][]: | |
| 385 tri.add int(entry) | |
| 386 if tri.len == 3: | |
| 387 # FYI gltf uses counter-clockwise indexing | |
| 388 result[].AppendIndicesData(tri[0], tri[1], tri[2]) | |
| 389 tri.setLen(0) | |
| 390 of UInt32: | |
| 391 for entry in data[uint32][]: | |
| 392 tri.add int(entry) | |
| 393 if tri.len == 3: | |
| 394 # FYI gltf uses counter-clockwise indexing | |
| 395 result[].AppendIndicesData(tri[0], tri[1], tri[2]) | |
| 396 tri.setLen(0) | |
| 397 else: | |
| 398 raise newException(Exception, &"Unsupported index data type: {data.thetype}") | |
| 399 # TODO: getting from gltf to vulkan system is still messed up somehow, see other TODO | |
| 400 Transform[Vec3f](result[], "position", Scale(1, -1, 1)) | |
| 401 | |
| 402 | |
| 403 proc loadNode(root: JsonNode, node: JsonNode, materials: seq[MaterialData], mainBuffer: var seq[uint8]): MeshTree = | |
| 404 result = MeshTree() | |
| 405 # mesh | |
| 406 if node.hasKey("mesh"): | |
| 407 let mesh = root["meshes"][node["mesh"].getInt()] | |
| 408 for primitive in mesh["primitives"]: | |
| 409 result.children.add MeshTree(mesh: loadPrimitive(mesh["name"].getStr(), root, primitive, materials, mainBuffer)) | |
| 410 | |
| 411 # transformation | |
| 412 if node.hasKey("matrix"): | |
| 413 var mat: Mat4 | |
| 414 for i in 0 ..< node["matrix"].len: | 234 for i in 0 ..< node["matrix"].len: |
| 415 mat[i] = node["matrix"][i].getFloat() | 235 result.transform[i] = node["matrix"][i].getFloat() |
| 416 result.transform = mat | 236 |
| 417 else: | 237 var (t, r, s) = (Unit4, Unit4, Unit4) |
| 418 var (t, r, s) = (Unit4F32, Unit4F32, Unit4F32) | 238 if "translation" in node: |
| 419 if node.hasKey("translation"): | 239 t = Translate( |
| 420 t = Translate( | 240 float32(node["translation"][0].getFloat()), |
| 421 float32(node["translation"][0].getFloat()), | 241 float32(node["translation"][1].getFloat()), |
| 422 float32(node["translation"][1].getFloat()), | 242 float32(node["translation"][2].getFloat()) |
| 423 float32(node["translation"][2].getFloat()) | 243 ) |
| 244 if "rotation" in node: | |
| 245 t = Rotate( | |
| 246 float32(node["rotation"][3].getFloat()), | |
| 247 NewVec3f( | |
| 248 float32(node["rotation"][0].getFloat()), | |
| 249 float32(node["rotation"][1].getFloat()), | |
| 250 float32(node["rotation"][2].getFloat()) | |
| 424 ) | 251 ) |
| 425 if node.hasKey("rotation"): | 252 ) |
| 426 t = Rotate( | 253 if "scale" in node: |
| 427 float32(node["rotation"][3].getFloat()), | 254 t = Scale( |
| 428 NewVec3f( | 255 float32(node["scale"][0].getFloat()), |
| 429 float32(node["rotation"][0].getFloat()), | 256 float32(node["scale"][1].getFloat()), |
| 430 float32(node["rotation"][1].getFloat()), | 257 float32(node["scale"][2].getFloat()) |
| 431 float32(node["rotation"][2].getFloat()) | 258 ) |
| 432 ) | 259 |
| 433 ) | 260 result.transform = t * r * s * result.transform |
| 434 if node.hasKey("scale"): | |
| 435 t = Scale( | |
| 436 float32(node["scale"][0].getFloat()), | |
| 437 float32(node["scale"][1].getFloat()), | |
| 438 float32(node["scale"][2].getFloat()) | |
| 439 ) | |
| 440 result.transform = t * r * s | |
| 441 result.transform = Scale(1, -1, 1) * result.transform | |
| 442 | |
| 443 # children | |
| 444 if node.hasKey("children"): | |
| 445 for childNode in node["children"]: | |
| 446 result.children.add loadNode(root, root["nodes"][childNode.getInt()], materials, mainBuffer) | |
| 447 | |
| 448 proc loadScene(root: JsonNode, scenenode: JsonNode, materials: seq[MaterialData], mainBuffer: var seq[uint8]): MeshTree = | |
| 449 result = MeshTree() | |
| 450 for nodeId in scenenode["nodes"]: | |
| 451 result.children.add loadNode(root, root["nodes"][nodeId.getInt()], materials, mainBuffer) | |
| 452 # TODO: getting from gltf to vulkan system is still messed up somehow (i.e. not consistent for different files), see other TODO | |
| 453 # result.transform = Scale(1, -1, 1) | |
| 454 result.updateTransforms() | |
| 455 | |
| 456 ]# | |
| 457 | 261 |
| 458 proc ReadglTF*[TMesh, TMaterial]( | 262 proc ReadglTF*[TMesh, TMaterial]( |
| 459 stream: Stream, | 263 stream: Stream, |
| 460 meshAttributesMapping: static MeshAttributeNames, | 264 meshAttributesMapping: static MeshAttributeNames, |
| 461 materialAttributesMapping: static MaterialAttributeNames, | 265 materialAttributesMapping: static MaterialAttributeNames, |
| 462 ): GLTFMesh[TMesh, TMaterial] = | 266 ): GltfMesh[TMesh, TMaterial] = |
| 463 var | 267 var |
| 464 header: glTFHeader | 268 header: glTFHeader |
| 465 data: glTFData | 269 data: glTFData |
| 466 | 270 |
| 467 for name, value in fieldPairs(header): | 271 for name, value in fieldPairs(header): |
| 501 var primitives: seq[(TMesh, VkPrimitiveTopology)] | 305 var primitives: seq[(TMesh, VkPrimitiveTopology)] |
| 502 for primitive in items(mesh["primitives"]): | 306 for primitive in items(mesh["primitives"]): |
| 503 primitives.add loadPrimitive[TMesh](data.structuredContent, primitive, meshAttributesMapping, data.binaryBufferData) | 307 primitives.add loadPrimitive[TMesh](data.structuredContent, primitive, meshAttributesMapping, data.binaryBufferData) |
| 504 result.meshes.add primitives | 308 result.meshes.add primitives |
| 505 | 309 |
| 506 echo "Textures:" | 310 if "nodes" in data.structuredContent: |
| 507 for t in result.textures: | 311 for node in items(data.structuredContent["nodes"]): |
| 508 echo " ", t | 312 result.nodes.add loadNode(node) |
| 509 | 313 |
| 510 echo "Materials:" | 314 if "scenes" in data.structuredContent: |
| 511 for m in result.materials: | 315 for scene in items(data.structuredContent["scenes"]): |
| 512 echo " ", m | 316 if "nodes" in scene: |
| 513 | 317 var nodes: seq[int] |
| 514 echo "Meshes:" | 318 for nodeId in items(scene["nodes"]): |
| 515 for m in result.meshes: | 319 nodes.add nodeId.getInt() |
| 516 echo " Primitives:" | 320 result.scenes.add nodes |
| 517 for p in m: | |
| 518 for field, value in fieldPairs(p[0]): | |
| 519 if typeof(value) is GPUData: | |
| 520 echo " ", field, ": ", value.data.len | |
| 521 | 321 |
| 522 proc LoadMeshes*[TMesh, TMaterial]( | 322 proc LoadMeshes*[TMesh, TMaterial]( |
| 523 path: string, | 323 path: string, |
| 524 meshAttributesMapping: static MeshAttributeNames, | 324 meshAttributesMapping: static MeshAttributeNames, |
| 525 materialAttributesMapping: static MaterialAttributeNames, | 325 materialAttributesMapping: static MaterialAttributeNames, |
| 526 package = DEFAULT_PACKAGE | 326 package = DEFAULT_PACKAGE |
| 527 ): GLTFMesh[TMesh, TMaterial] = | 327 ): GltfMesh[TMesh, TMaterial] = |
| 528 ReadglTF[TMesh, TMaterial]( | 328 ReadglTF[TMesh, TMaterial]( |
| 529 stream = loadResource_intern(path, package = package), | 329 stream = loadResource_intern(path, package = package), |
| 530 meshAttributesMapping = meshAttributesMapping, | 330 meshAttributesMapping = meshAttributesMapping, |
| 531 materialAttributesMapping = materialAttributesMapping, | 331 materialAttributesMapping = materialAttributesMapping, |
| 532 ) | 332 ) |
