comparison semiconginev2/old/resources/mesh.nim @ 1218:56781cc0fc7c compiletime-tests

did: renamge main package
author sam <sam@basx.dev>
date Wed, 17 Jul 2024 21:01:37 +0700
parents semicongine/old/resources/mesh.nim@a3eb305bcac2
children
comparison
equal deleted inserted replaced
1217:f819a874058f 1218:56781cc0fc7c
1 import std/strutils
2 import std/json
3 import std/logging
4 import std/tables
5 import std/strformat
6 import std/streams
7
8 import ../mesh
9 import ../material
10 import ../core
11
12 import ./image
13
14 type
15 glTFHeader = object
16 magic: uint32
17 version: uint32
18 length: uint32
19 glTFData = object
20 structuredContent: JsonNode
21 binaryBufferData: seq[uint8]
22
23 const
24 JSON_CHUNK = 0x4E4F534A
25 BINARY_CHUNK = 0x004E4942
26 ACCESSOR_TYPE_MAP = {
27 5120: Int8,
28 5121: UInt8,
29 5122: Int16,
30 5123: UInt16,
31 5125: UInt32,
32 5126: Float32,
33 }.toTable
34 SAMPLER_FILTER_MODE_MAP = {
35 9728: VK_FILTER_NEAREST,
36 9729: VK_FILTER_LINEAR,
37 9984: VK_FILTER_NEAREST,
38 9985: VK_FILTER_LINEAR,
39 9986: VK_FILTER_NEAREST,
40 9987: VK_FILTER_LINEAR,
41 }.toTable
42 SAMPLER_WRAP_MODE_MAP = {
43 33071: VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE,
44 33648: VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT,
45 10497: VK_SAMPLER_ADDRESS_MODE_REPEAT
46 }.toTable
47 GLTF_MATERIAL_MAPPING = {
48 "color": "baseColorFactor",
49 "emissiveColor": "emissiveFactor",
50 "metallic": "metallicFactor",
51 "roughness", "roughnessFactor",
52 "baseTexture": "baseColorTexture",
53 "metallicRoughnessTexture": "metallicRoughnessTexture",
54 "normalTexture": "normalTexture",
55 "occlusionTexture": "occlusionTexture",
56 "emissiveTexture": "emissiveTexture",
57 }.toTable
58
59 proc getGPUType(accessor: JsonNode, attribute: string): DataType =
60 # TODO: no full support for all datatypes that glTF may provide
61 # semicongine/core/gpu_data should maybe generated with macros to allow for all combinations
62 let componentType = ACCESSOR_TYPE_MAP[accessor["componentType"].getInt()]
63 let theType = accessor["type"].getStr()
64 case theType
65 of "SCALAR":
66 return componentType
67 of "VEC2":
68 case componentType
69 of UInt32: return Vec2U32
70 of Float32: return Vec2F32
71 else: raise newException(Exception, &"Unsupported data type for attribute '{attribute}': {componentType} {theType}")
72 of "VEC3":
73 case componentType
74 of UInt32: return Vec3U32
75 of Float32: return Vec3F32
76 else: raise newException(Exception, &"Unsupported data type for attribute '{attribute}': {componentType} {theType}")
77 of "VEC4":
78 case componentType
79 of UInt32: return Vec4U32
80 of Float32: return Vec4F32
81 else: raise newException(Exception, &"Unsupported data type for attribute '{attribute}': {componentType} {theType}")
82 of "MAT2":
83 case componentType
84 of Float32: return Vec4F32
85 else: raise newException(Exception, &"Unsupported data type for attribute '{attribute}': {componentType} {theType}")
86 of "MAT3":
87 case componentType
88 of Float32: return Vec4F32
89 else: raise newException(Exception, &"Unsupported data type for attribute '{attribute}': {componentType} {theType}")
90 of "MAT4":
91 case componentType
92 of Float32: return Vec4F32
93 else: raise newException(Exception, &"Unsupported data type for attribute '{attribute}': {componentType} {theType}")
94
95 proc getBufferViewData(bufferView: JsonNode, mainBuffer: seq[uint8], baseBufferOffset = 0): seq[uint8] =
96 assert bufferView["buffer"].getInt() == 0, "Currently no external buffers supported"
97
98 result = newSeq[uint8](bufferView["byteLength"].getInt())
99 let bufferOffset = bufferView["byteOffset"].getInt() + baseBufferOffset
100 var dstPointer = addr result[0]
101
102 if bufferView.hasKey("byteStride"):
103 raise newException(Exception, "Unsupported feature: byteStride in buffer view")
104 copyMem(dstPointer, addr mainBuffer[bufferOffset], result.len)
105
106 proc getAccessorData(root: JsonNode, accessor: JsonNode, mainBuffer: seq[uint8]): DataList =
107 result = InitDataList(thetype = accessor.getGPUType("??"))
108 result.SetLen(accessor["count"].getInt())
109
110 let bufferView = root["bufferViews"][accessor["bufferView"].getInt()]
111 assert bufferView["buffer"].getInt() == 0, "Currently no external buffers supported"
112
113 if accessor.hasKey("sparse"):
114 raise newException(Exception, "Sparce accessors are currently not implemented")
115
116 let accessorOffset = if accessor.hasKey("byteOffset"): accessor["byteOffset"].getInt() else: 0
117 let length = bufferView["byteLength"].getInt()
118 let bufferOffset = bufferView["byteOffset"].getInt() + accessorOffset
119 var dstPointer = result.GetPointer()
120
121 if bufferView.hasKey("byteStride"):
122 warn "Congratulations, you try to test a feature (loading buffer data with stride attributes) that we have no idea where it is used and how it can be tested (need a coresponding *.glb file)."
123 # we don't support stride, have to convert stuff here... does this even work?
124 for i in 0 ..< int(result.len):
125 copyMem(dstPointer, addr mainBuffer[bufferOffset + i * bufferView["byteStride"].getInt()], int(result.thetype.Size))
126 dstPointer = cast[pointer](cast[uint](dstPointer) + result.thetype.Size)
127 else:
128 copyMem(dstPointer, addr mainBuffer[bufferOffset], length)
129
130 proc loadImage(root: JsonNode, imageIndex: int, mainBuffer: seq[uint8]): Image[RGBAPixel] =
131 if root["images"][imageIndex].hasKey("uri"):
132 raise newException(Exception, "Unsupported feature: Load images from external files")
133
134 let bufferView = root["bufferViews"][root["images"][imageIndex]["bufferView"].getInt()]
135 let imgData = newStringStream(cast[string](getBufferViewData(bufferView, mainBuffer)))
136
137 let imageType = root["images"][imageIndex]["mimeType"].getStr()
138 case imageType
139 of "image/bmp":
140 result = ReadBMP(imgData)
141 of "image/png":
142 result = ReadPNG(imgData)
143 else:
144 raise newException(Exception, "Unsupported feature: Load image of type " & imageType)
145
146 proc loadTexture(root: JsonNode, textureIndex: int, mainBuffer: seq[uint8]): Texture =
147 let textureNode = root["textures"][textureIndex]
148 result = Texture(isGrayscale: false)
149 result.colorImage = loadImage(root, textureNode["source"].getInt(), mainBuffer)
150 result.name = root["images"][textureNode["source"].getInt()]["name"].getStr()
151 if result.name == "":
152 result.name = &"Texture{textureIndex}"
153
154 if textureNode.hasKey("sampler"):
155 let sampler = root["samplers"][textureNode["sampler"].getInt()]
156 if sampler.hasKey("magFilter"):
157 result.sampler.magnification = SAMPLER_FILTER_MODE_MAP[sampler["magFilter"].getInt()]
158 if sampler.hasKey("minFilter"):
159 result.sampler.minification = SAMPLER_FILTER_MODE_MAP[sampler["minFilter"].getInt()]
160 if sampler.hasKey("wrapS"):
161 result.sampler.wrapModeS = SAMPLER_WRAP_MODE_MAP[sampler["wrapS"].getInt()]
162 if sampler.hasKey("wrapT"):
163 result.sampler.wrapModeT = SAMPLER_WRAP_MODE_MAP[sampler["wrapS"].getInt()]
164
165
166 proc loadMaterial(root: JsonNode, materialNode: JsonNode, defaultMaterial: MaterialType, mainBuffer: seq[uint8]): MaterialData =
167 let pbr = materialNode["pbrMetallicRoughness"]
168 var attributes: Table[string, DataList]
169
170 # color
171 if defaultMaterial.attributes.contains("color"):
172 attributes["color"] = InitDataList(thetype = Vec4F32)
173 if pbr.hasKey(GLTF_MATERIAL_MAPPING["color"]):
174 attributes["color"] = @[NewVec4f(
175 pbr[GLTF_MATERIAL_MAPPING["color"]][0].getFloat(),
176 pbr[GLTF_MATERIAL_MAPPING["color"]][1].getFloat(),
177 pbr[GLTF_MATERIAL_MAPPING["color"]][2].getFloat(),
178 pbr[GLTF_MATERIAL_MAPPING["color"]][3].getFloat(),
179 )]
180 else:
181 attributes["color"] = @[NewVec4f(1, 1, 1, 1)]
182
183 # pbr material values
184 for factor in ["metallic", "roughness"]:
185 if defaultMaterial.attributes.contains(factor):
186 attributes[factor] = InitDataList(thetype = Float32)
187 if pbr.hasKey(GLTF_MATERIAL_MAPPING[factor]):
188 attributes[factor] = @[float32(pbr[GLTF_MATERIAL_MAPPING[factor]].getFloat())]
189 else:
190 attributes[factor] = @[0.5'f32]
191
192 # pbr material textures
193 for texture in ["baseTexture", "metallicRoughnessTexture"]:
194 if defaultMaterial.attributes.contains(texture):
195 attributes[texture] = InitDataList(thetype = TextureType)
196 # attributes[texture & "Index"] = InitDataList(thetype=UInt8)
197 if pbr.hasKey(GLTF_MATERIAL_MAPPING[texture]):
198 attributes[texture] = @[loadTexture(root, pbr[GLTF_MATERIAL_MAPPING[texture]]["index"].getInt(), mainBuffer)]
199 else:
200 attributes[texture] = @[EMPTY_TEXTURE]
201
202 # generic material textures
203 for texture in ["normalTexture", "occlusionTexture", "emissiveTexture"]:
204 if defaultMaterial.attributes.contains(texture):
205 attributes[texture] = InitDataList(thetype = TextureType)
206 # attributes[texture & "Index"] = InitDataList(thetype=UInt8)
207 if materialNode.hasKey(GLTF_MATERIAL_MAPPING[texture]):
208 attributes[texture] = @[loadTexture(root, materialNode[texture]["index"].getInt(), mainBuffer)]
209 else:
210 attributes[texture] = @[EMPTY_TEXTURE]
211
212 # emissiv color
213 if defaultMaterial.attributes.contains("emissiveColor"):
214 attributes["emissiveColor"] = InitDataList(thetype = Vec3F32)
215 if materialNode.hasKey(GLTF_MATERIAL_MAPPING["emissiveColor"]):
216 attributes["emissiveColor"] = @[NewVec3f(
217 materialNode[GLTF_MATERIAL_MAPPING["emissiveColor"]][0].getFloat(),
218 materialNode[GLTF_MATERIAL_MAPPING["emissiveColor"]][1].getFloat(),
219 materialNode[GLTF_MATERIAL_MAPPING["emissiveColor"]][2].getFloat(),
220 )]
221 else:
222 attributes["emissiveColor"] = @[NewVec3f(1'f32, 1'f32, 1'f32)]
223
224 result = InitMaterialData(theType = defaultMaterial, name = materialNode["name"].getStr(), attributes = attributes)
225
226 proc loadMesh(meshname: string, root: JsonNode, primitiveNode: JsonNode, materials: seq[MaterialData], mainBuffer: seq[uint8]): Mesh =
227 if primitiveNode.hasKey("mode") and primitiveNode["mode"].getInt() != 4:
228 raise newException(Exception, "Currently only TRIANGLE mode is supported for geometry mode")
229
230 var indexType = None
231 let indexed = primitiveNode.hasKey("indices")
232 if indexed:
233 # TODO: Tiny indices
234 var indexCount = root["accessors"][primitiveNode["indices"].getInt()]["count"].getInt()
235 if indexCount < int(high(uint16)):
236 indexType = Small
237 else:
238 indexType = Big
239
240 result = Mesh(
241 instanceTransforms: @[Unit4F32],
242 indexType: indexType,
243 name: meshname,
244 vertexCount: 0,
245 )
246
247 for attribute, accessor in primitiveNode["attributes"].pairs:
248 let data = root.getAccessorData(root["accessors"][accessor.getInt()], mainBuffer)
249 if result.vertexCount == 0:
250 result.vertexCount = data.len
251 assert data.len == result.vertexCount
252 result[].InitVertexAttribute(attribute.toLowerAscii, data)
253
254 if primitiveNode.hasKey("material"):
255 let materialId = primitiveNode["material"].getInt()
256 result[].material = materials[materialId]
257 else:
258 result[].material = EMPTY_MATERIAL.InitMaterialData()
259
260 if primitiveNode.hasKey("indices"):
261 assert result[].indexType != None
262 let data = root.getAccessorData(root["accessors"][primitiveNode["indices"].getInt()], mainBuffer)
263 var tri: seq[int]
264 case data.thetype
265 of UInt16:
266 for entry in data[uint16][]:
267 tri.add int(entry)
268 if tri.len == 3:
269 # FYI gltf uses counter-clockwise indexing
270 result[].AppendIndicesData(tri[0], tri[1], tri[2])
271 tri.setLen(0)
272 of UInt32:
273 for entry in data[uint32][]:
274 tri.add int(entry)
275 if tri.len == 3:
276 # FYI gltf uses counter-clockwise indexing
277 result[].AppendIndicesData(tri[0], tri[1], tri[2])
278 tri.setLen(0)
279 else:
280 raise newException(Exception, &"Unsupported index data type: {data.thetype}")
281 # TODO: getting from gltf to vulkan system is still messed up somehow, see other TODO
282 Transform[Vec3f](result[], "position", Scale(1, -1, 1))
283
284 proc loadNode(root: JsonNode, node: JsonNode, materials: seq[MaterialData], mainBuffer: var seq[uint8]): MeshTree =
285 result = MeshTree()
286 # mesh
287 if node.hasKey("mesh"):
288 let mesh = root["meshes"][node["mesh"].getInt()]
289 for primitive in mesh["primitives"]:
290 result.children.add MeshTree(mesh: loadMesh(mesh["name"].getStr(), root, primitive, materials, mainBuffer))
291
292 # transformation
293 if node.hasKey("matrix"):
294 var mat: Mat4
295 for i in 0 ..< node["matrix"].len:
296 mat[i] = node["matrix"][i].getFloat()
297 result.transform = mat
298 else:
299 var (t, r, s) = (Unit4F32, Unit4F32, Unit4F32)
300 if node.hasKey("translation"):
301 t = Translate(
302 float32(node["translation"][0].getFloat()),
303 float32(node["translation"][1].getFloat()),
304 float32(node["translation"][2].getFloat())
305 )
306 if node.hasKey("rotation"):
307 t = Rotate(
308 float32(node["rotation"][3].getFloat()),
309 NewVec3f(
310 float32(node["rotation"][0].getFloat()),
311 float32(node["rotation"][1].getFloat()),
312 float32(node["rotation"][2].getFloat())
313 )
314 )
315 if node.hasKey("scale"):
316 t = Scale(
317 float32(node["scale"][0].getFloat()),
318 float32(node["scale"][1].getFloat()),
319 float32(node["scale"][2].getFloat())
320 )
321 result.transform = t * r * s
322 result.transform = Scale(1, -1, 1) * result.transform
323
324 # children
325 if node.hasKey("children"):
326 for childNode in node["children"]:
327 result.children.add loadNode(root, root["nodes"][childNode.getInt()], materials, mainBuffer)
328
329 proc loadMeshTree(root: JsonNode, scenenode: JsonNode, materials: seq[MaterialData], mainBuffer: var seq[uint8]): MeshTree =
330 result = MeshTree()
331 for nodeId in scenenode["nodes"]:
332 result.children.add loadNode(root, root["nodes"][nodeId.getInt()], materials, mainBuffer)
333 # TODO: getting from gltf to vulkan system is still messed up somehow (i.e. not consistent for different files), see other TODO
334 # result.transform = Scale(1, -1, 1)
335 result.updateTransforms()
336
337
338 proc ReadglTF*(stream: Stream, defaultMaterial: MaterialType): seq[MeshTree] =
339 var
340 header: glTFHeader
341 data: glTFData
342
343 for name, value in fieldPairs(header):
344 stream.read(value)
345
346 assert header.magic == 0x46546C67
347 assert header.version == 2
348
349 var chunkLength = stream.readUint32()
350 assert stream.readUint32() == JSON_CHUNK
351 data.structuredContent = parseJson(stream.readStr(int(chunkLength)))
352
353 chunkLength = stream.readUint32()
354 assert stream.readUint32() == BINARY_CHUNK
355 data.binaryBufferData.setLen(chunkLength)
356 assert stream.readData(addr data.binaryBufferData[0], int(chunkLength)) == int(chunkLength)
357
358 # check that the refered buffer is the same as the binary chunk
359 # external binary buffers are not supported
360 assert data.structuredContent["buffers"].len == 1
361 assert not data.structuredContent["buffers"][0].hasKey("uri")
362 let bufferLenDiff = int(chunkLength) - data.structuredContent["buffers"][0]["byteLength"].getInt()
363 assert 0 <= bufferLenDiff and bufferLenDiff <= 3 # binary buffer may be aligned to 4 bytes
364
365 debug "Loading mesh: ", data.structuredContent.pretty
366
367 var materials: seq[MaterialData]
368 for materialnode in data.structuredContent["materials"]:
369 materials.add data.structuredContent.loadMaterial(materialnode, defaultMaterial, data.binaryBufferData)
370
371 for scenedata in data.structuredContent["scenes"]:
372 result.add data.structuredContent.loadMeshTree(scenedata, materials, data.binaryBufferData)