Mercurial > games > semicongine
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) |