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 ) |