diff semiconginev2/gltf.nim @ 1247:c15770761865

add: gltf loading test, gltf loading for materials
author sam <sam@basx.dev>
date Wed, 24 Jul 2024 23:26:34 +0700
parents d594b1d07d49
children 317bb5a73606
line wrap: on
line diff
--- a/semiconginev2/gltf.nim	Wed Jul 24 20:12:19 2024 +0700
+++ b/semiconginev2/gltf.nim	Wed Jul 24 23:26:34 2024 +0700
@@ -1,9 +1,10 @@
 type
-  GLTFMesh[TMesh, TMaterial] = object
-    scenes: seq[int]
-    nodes: seq[int]
-    meshes: seq[TMesh]
-    materials: seq[TMaterial]
+  GLTFMesh*[TMesh, TMaterial] = object
+    scenes*: seq[seq[int]] # each scene has a seq of node indices
+    nodes*: seq[seq[int]]  # each node has a seq of mesh indices
+    meshes*: seq[TMesh]
+    materials*: seq[TMaterial]
+    textures*: seq[Image[BGRA]]
   glTFHeader = object
     magic: uint32
     version: uint32
@@ -13,20 +14,41 @@
     binaryBufferData: seq[uint8]
 
   MaterialAttributeNames = object
+    # pbr
+    baseColorTexture: string
+    baseColorTextureUv: string
     baseColorFactor: string
-    emissiveFactor: string
+    metallicRoughnessTexture: string
+    metallicRoughnessTextureUv: string
     metallicFactor: string
     roughnessFactor: string
-    baseColorTexture: string
-    metallicRoughnessTexture: string
+
+    # other
     normalTexture: string
+    normalTextureUv: string
     occlusionTexture: string
+    occlusionTextureUv: string
     emissiveTexture: string
+    emissiveTextureUv: string
+    emissiveFactor: string
+
+#[
+static:
+  let TypeIds = {
+    int8: 5120,
+    uint8: 5121,
+    int16: 5122,
+    uint16: 5123,
+    uint32: 5125,
+    float32: 5126,
+  }.toTable
+]#
 
 const
   HEADER_MAGIC = 0x46546C67
   JSON_CHUNK = 0x4E4F534A
   BINARY_CHUNK = 0x004E4942
+  #[
   ACCESSOR_TYPE_MAP = {
     5120: Int8,
     5121: UInt8,
@@ -35,6 +57,7 @@
     5125: UInt32,
     5126: Float32,
   }.toTable
+  ]#
   SAMPLER_FILTER_MODE_MAP = {
     9728: VK_FILTER_NEAREST,
     9729: VK_FILTER_LINEAR,
@@ -48,18 +71,8 @@
     33648: VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT,
     10497: VK_SAMPLER_ADDRESS_MODE_REPEAT
   }.toTable
-  GLTF_MATERIAL_MAPPING = {
-    "color": "baseColorFactor",
-    "emissiveColor": "emissiveFactor",
-    "metallic": "metallicFactor",
-    "roughness", "roughnessFactor",
-    "baseTexture": "baseColorTexture",
-    "metallicRoughnessTexture": "metallicRoughnessTexture",
-    "normalTexture": "normalTexture",
-    "occlusionTexture": "occlusionTexture",
-    "emissiveTexture": "emissiveTexture",
-  }.toTable
 
+#[
 proc getGPUType(accessor: JsonNode, attribute: string): DataType =
   # TODO: no full support for all datatypes that glTF may provide
   # semicongine/core/gpu_data should maybe generated with macros to allow for all combinations
@@ -95,6 +108,7 @@
     case componentType
     of Float32: return Vec4F32
     else: raise newException(Exception, &"Unsupported data type for attribute '{attribute}': {componentType} {theType}")
+]#
 
 proc getBufferViewData(bufferView: JsonNode, mainBuffer: seq[uint8], baseBufferOffset = 0): seq[uint8] =
   assert bufferView["buffer"].getInt() == 0, "Currently no external buffers supported"
@@ -107,6 +121,7 @@
     raise newException(Exception, "Unsupported feature: byteStride in buffer view")
   copyMem(dstPointer, addr mainBuffer[bufferOffset], result.len)
 
+#[
 proc getAccessorData(root: JsonNode, accessor: JsonNode, mainBuffer: seq[uint8]): DataList =
   result = InitDataList(thetype = accessor.getGPUType("??"))
   result.SetLen(accessor["count"].getInt())
@@ -130,113 +145,60 @@
       dstPointer = cast[pointer](cast[uint](dstPointer) + result.thetype.Size)
   else:
     copyMem(dstPointer, addr mainBuffer[bufferOffset], length)
+]#
 
-proc loadImage(root: JsonNode, imageIndex: int, mainBuffer: seq[uint8]): Image[RGBAPixel] =
+proc loadTexture(root: JsonNode, textureNode: JsonNode, mainBuffer: seq[uint8]): Image[BGRA] =
+
+  let imageIndex = textureNode["source"].getInt()
+
   if root["images"][imageIndex].hasKey("uri"):
-    raise newException(Exception, "Unsupported feature: Load images from external files")
+    raise newException(Exception, "Unsupported feature: Cannot load images from external files")
+  let imageType = root["images"][imageIndex]["mimeType"].getStr()
+  assert imageType == "image/png", "glTF loader currently only supports PNG"
 
   let bufferView = root["bufferViews"][root["images"][imageIndex]["bufferView"].getInt()]
-  let imgData = newStringStream(cast[string](getBufferViewData(bufferView, mainBuffer)))
-
-  let imageType = root["images"][imageIndex]["mimeType"].getStr()
-  case imageType
-  of "image/bmp":
-    result = ReadBMP(imgData)
-  of "image/png":
-    result = ReadPNG(imgData)
-  else:
-    raise newException(Exception, "Unsupported feature: Load image of type " & imageType)
-
-proc loadTexture(root: JsonNode, textureIndex: int, mainBuffer: seq[uint8]): Texture =
-  let textureNode = root["textures"][textureIndex]
-  result = Texture(isGrayscale: false)
-  result.colorImage = loadImage(root, textureNode["source"].getInt(), mainBuffer)
-  result.name = root["images"][textureNode["source"].getInt()]["name"].getStr()
-  if result.name == "":
-    result.name = &"Texture{textureIndex}"
+  result = LoadImage[BGRA](getBufferViewData(bufferView, mainBuffer))
 
   if textureNode.hasKey("sampler"):
     let sampler = root["samplers"][textureNode["sampler"].getInt()]
     if sampler.hasKey("magFilter"):
-      result.sampler.magnification = SAMPLER_FILTER_MODE_MAP[sampler["magFilter"].getInt()]
+      result.magInterpolation = SAMPLER_FILTER_MODE_MAP[sampler["magFilter"].getInt()]
     if sampler.hasKey("minFilter"):
-      result.sampler.minification = SAMPLER_FILTER_MODE_MAP[sampler["minFilter"].getInt()]
+      result.minInterpolation = SAMPLER_FILTER_MODE_MAP[sampler["minFilter"].getInt()]
     if sampler.hasKey("wrapS"):
-      result.sampler.wrapModeS = SAMPLER_WRAP_MODE_MAP[sampler["wrapS"].getInt()]
+      result.wrapU = SAMPLER_WRAP_MODE_MAP[sampler["wrapS"].getInt()]
     if sampler.hasKey("wrapT"):
-      result.sampler.wrapModeT = SAMPLER_WRAP_MODE_MAP[sampler["wrapS"].getInt()]
+      result.wrapV = SAMPLER_WRAP_MODE_MAP[sampler["wrapT"].getInt()]
 
+proc getVec4f(node: JsonNode): Vec4f =
+  NewVec4f(node[0].getFloat(), node[1].getFloat(), node[2].getFloat(), node[3].getFloat())
 
 proc loadMaterial[TMaterial](
   root: JsonNode,
   materialNode: JsonNode,
   mainBuffer: seq[uint8],
-  mapping: MaterialAttributeNames
+  mapping: static MaterialAttributeNames
 ): TMaterial =
-  let pbr = materialNode["pbrMetallicRoughness"]
-  for glName, glValue in fieldPairs(mapping):
-    if glValue != "":
-      for name, value in fieldPairs(result):
-        when name == glName:
-          value = 
-
-  #[
-
-  # color
-  if defaultMaterial.attributes.contains("color"):
-    attributes["color"] = InitDataList(thetype = Vec4F32)
-    if pbr.hasKey(GLTF_MATERIAL_MAPPING["color"]):
-      attributes["color"] = @[NewVec4f(
-        pbr[GLTF_MATERIAL_MAPPING["color"]][0].getFloat(),
-        pbr[GLTF_MATERIAL_MAPPING["color"]][1].getFloat(),
-        pbr[GLTF_MATERIAL_MAPPING["color"]][2].getFloat(),
-        pbr[GLTF_MATERIAL_MAPPING["color"]][3].getFloat(),
-      )]
-    else:
-      attributes["color"] = @[NewVec4f(1, 1, 1, 1)]
-
-    # pbr material values
-    for factor in ["metallic", "roughness"]:
-      if defaultMaterial.attributes.contains(factor):
-        attributes[factor] = InitDataList(thetype = Float32)
-        if pbr.hasKey(GLTF_MATERIAL_MAPPING[factor]):
-          attributes[factor] = @[float32(pbr[GLTF_MATERIAL_MAPPING[factor]].getFloat())]
-        else:
-          attributes[factor] = @[0.5'f32]
+  result = TMaterial()
 
-  # pbr material textures
-  for texture in ["baseTexture", "metallicRoughnessTexture"]:
-    if defaultMaterial.attributes.contains(texture):
-      attributes[texture] = InitDataList(thetype = TextureType)
-      # attributes[texture & "Index"] = InitDataList(thetype=UInt8)
-      if pbr.hasKey(GLTF_MATERIAL_MAPPING[texture]):
-        attributes[texture] = @[loadTexture(root, pbr[GLTF_MATERIAL_MAPPING[texture]]["index"].getInt(), mainBuffer)]
-      else:
-        attributes[texture] = @[EMPTY_TEXTURE]
+  let pbr = materialNode["pbrMetallicRoughness"]
+  for name, value in fieldPairs(result):
+    for gltfAttribute, mappedName in fieldPairs(mapping):
+      when gltfAttribute != "" and name == mappedName:
+        if pbr.hasKey(gltfAttribute):
+          when gltfAttribute.endsWith("Texture"):
+            value = typeof(value)(pbr[gltfAttribute]["index"].getInt())
+          elif gltfAttribute.endsWith("TextureUv"):
+            value = typeof(pbr[gltfAttribute[0 ..< ^2]]["index"].getInt())
+          elif gltfAttribute in ["baseColorFactor", "emissiveFactor"]:
+            value = pbr[gltfAttribute].getVec4f()
+          elif gltfAttribute in ["metallicFactor", "roughnessFactor"]:
+            value = pbr[gltfAttribute].getFloat()
+          else:
+            {.error: "Unsupported gltf material attribute".}
 
-  # generic material textures
-  for texture in ["normalTexture", "occlusionTexture", "emissiveTexture"]:
-    if defaultMaterial.attributes.contains(texture):
-      attributes[texture] = InitDataList(thetype = TextureType)
-      # attributes[texture & "Index"] = InitDataList(thetype=UInt8)
-      if materialNode.hasKey(GLTF_MATERIAL_MAPPING[texture]):
-        attributes[texture] = @[loadTexture(root, materialNode[texture]["index"].getInt(), mainBuffer)]
-      else:
-        attributes[texture] = @[EMPTY_TEXTURE]
 
-  # emissiv color
-  if defaultMaterial.attributes.contains("emissiveColor"):
-    attributes["emissiveColor"] = InitDataList(thetype = Vec3F32)
-    if materialNode.hasKey(GLTF_MATERIAL_MAPPING["emissiveColor"]):
-      attributes["emissiveColor"] = @[NewVec3f(
-        materialNode[GLTF_MATERIAL_MAPPING["emissiveColor"]][0].getFloat(),
-        materialNode[GLTF_MATERIAL_MAPPING["emissiveColor"]][1].getFloat(),
-        materialNode[GLTF_MATERIAL_MAPPING["emissiveColor"]][2].getFloat(),
-      )]
-    else:
-      attributes["emissiveColor"] = @[NewVec3f(1'f32, 1'f32, 1'f32)]
-  ]#
-
+#[
 
 proc loadMesh(meshname: string, root: JsonNode, primitiveNode: JsonNode, materials: seq[MaterialData], mainBuffer: seq[uint8]): Mesh =
   if primitiveNode.hasKey("mode") and primitiveNode["mode"].getInt() != 4:
@@ -296,6 +258,7 @@
   # TODO: getting from gltf to vulkan system is still messed up somehow, see other TODO
   Transform[Vec3f](result[], "position", Scale(1, -1, 1))
 
+
 proc loadNode(root: JsonNode, node: JsonNode, materials: seq[MaterialData], mainBuffer: var seq[uint8]): MeshTree =
   result = MeshTree()
   # mesh
@@ -349,30 +312,30 @@
   # result.transform = Scale(1, -1, 1)
   result.updateTransforms()
 
+  ]#
 
-proc ReadglTF*[TMaterial, TMesh](
+proc ReadglTF*[TMesh, TMaterial](
   stream: Stream,
-  attributeNames: MaterialAttributeNames,
-  baseColorFactor = "",
-  emissiveFactor = "",
-  metallicFactor = "",
-  roughnessFactor = "",
-  baseColorTexture = "",
-  metallicRoughnessTexture = "",
-  normalTexture = "",
-  occlusionTexture = "",
-  emissiveTexture = "",
+  baseColorFactor: static string = "",
+  emissiveFactor: static string = "",
+  metallicFactor: static string = "",
+  roughnessFactor: static string = "",
+  baseColorTexture: static string = "",
+  metallicRoughnessTexture: static string = "",
+  normalTexture: static string = "",
+  occlusionTexture: static string = "",
+  emissiveTexture: static string = "",
 ): GLTFMesh[TMesh, TMaterial] =
-  let mapping = MaterialAttributeNames(
-    baseColorFactor: baseColorFactor
-    emissiveFactor: emissiveFactor
-    metallicFactor: metallicFactor
-    roughnessFactor: roughnessFactor
-    baseColorTexture: baseColorTexture
-    metallicRoughnessTexture: metallicRoughnessTexture
-    normalTexture: normalTexture
-    occlusionTexture: occlusionTexture
-    emissiveTexture: emissiveTexture
+  const mapping = MaterialAttributeNames(
+    baseColorFactor: baseColorFactor,
+    emissiveFactor: emissiveFactor,
+    metallicFactor: metallicFactor,
+    roughnessFactor: roughnessFactor,
+    baseColorTexture: baseColorTexture,
+    metallicRoughnessTexture: metallicRoughnessTexture,
+    normalTexture: normalTexture,
+    occlusionTexture: occlusionTexture,
+    emissiveTexture: emissiveTexture,
   )
   var
     header: glTFHeader
@@ -402,9 +365,40 @@
 
   debug "Loading mesh: ", data.structuredContent.pretty
 
-  var materials: seq[MaterialData]
-  for materialnode in data.structuredContent["materials"]:
-    result.materials.add loadMaterial[TMaterial](data.structuredContent, materialnode, data.binaryBufferData, mapping)
+  if "materials" in data.structuredContent:
+    for materialnode in items(data.structuredContent["materials"]):
+      result.materials.add loadMaterial[TMaterial](data.structuredContent, materialnode, data.binaryBufferData, mapping)
+
+  if "textures" in data.structuredContent:
+    for texturenode in items(data.structuredContent["textures"]):
+      result.textures.add loadTexture(data.structuredContent, texturenode, data.binaryBufferData)
 
-  for scenedata in data.structuredContent["scenes"]:
-    result.add data.structuredContent.loadScene(scenedata, materials, data.binaryBufferData)
+  echo result
+  # for scenedata in data.structuredContent["scenes"]:
+    # result.add data.structuredContent.loadScene(scenedata, materials, data.binaryBufferData)
+    #
+proc LoadMeshes*[TMesh, TMaterial](
+  path: string,
+  baseColorFactor: static string = "",
+  emissiveFactor: static string = "",
+  metallicFactor: static string = "",
+  roughnessFactor: static string = "",
+  baseColorTexture: static string = "",
+  metallicRoughnessTexture: static string = "",
+  normalTexture: static string = "",
+  occlusionTexture: static string = "",
+  emissiveTexture: static string = "",
+  package = DEFAULT_PACKAGE
+): GLTFMesh[TMesh, TMaterial] =
+  ReadglTF[TMesh, TMaterial](
+    stream = loadResource_intern(path, package = package),
+    baseColorFactor = baseColorFactor,
+    emissiveFactor = emissiveFactor,
+    metallicFactor = metallicFactor,
+    roughnessFactor = roughnessFactor,
+    baseColorTexture = baseColorTexture,
+    metallicRoughnessTexture = metallicRoughnessTexture,
+    normalTexture = normalTexture,
+    occlusionTexture = occlusionTexture,
+    emissiveTexture = emissiveTexture,
+  )