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 )