diff semiconginev2/old/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/mesh.nim@a3eb305bcac2
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/semiconginev2/old/mesh.nim	Wed Jul 17 21:01:37 2024 +0700
@@ -0,0 +1,635 @@
+import std/hashes
+import std/options
+import std/typetraits
+import std/tables
+import std/strformat
+import std/enumerate
+import std/strutils
+import std/sequtils
+
+import ./core
+import ./collision
+import ./material
+
+const DEFAULT_POSITION_ATTRIBUTE = "position"
+
+var instanceCounter* = 0
+
+type
+  MeshIndexType* = enum
+    None
+    Tiny  # up to 2^8 vertices # TODO: need to check and enable support for this
+    Small # up to 2^16 vertices
+    Big   # up to 2^32 vertices
+  MeshObject* = object
+    name*: string
+    vertexCount*: int
+    case indexType*: MeshIndexType
+      of None: discard
+      of Tiny: tinyIndices*: seq[array[3, uint8]]
+      of Small: smallIndices*: seq[array[3, uint16]]
+      of Big: bigIndices*: seq[array[3, uint32]]
+    material*: MaterialData
+    transform*: Mat4 = Unit4
+    instanceTransforms*: seq[Mat4]
+    applyMeshTransformToInstances*: bool = true # if true, the transform attribute for the shader will apply the instance transform AND the mesh transform, to each instance
+    visible*: bool = true
+    transformCache: seq[Mat4]
+    vertexData: Table[string, DataList]
+    instanceData: Table[string, DataList]
+    dirtyAttributes: seq[string]
+  Mesh* = ref MeshObject
+
+func Material*(mesh: MeshObject): MaterialData =
+  mesh.material
+
+func `material=`*(mesh: var MeshObject, material: MaterialData) =
+  for name, theType in material.theType.vertexAttributes:
+    if mesh.vertexData.contains(name):
+      assert mesh.vertexData[name].theType == theType, &"{material.theType} expected vertex attribute '{name}' to be '{theType}' but it is {mesh.vertexData[name].theType}"
+    else:
+      assert false, &"Mesh '{mesh.name}' is missing required vertex attribute '{name}: {theType}' for {material.theType}"
+  for name, theType in material.theType.instanceAttributes:
+    if name in [TRANSFORM_ATTRIB, MATERIALINDEX_ATTRIBUTE]:
+      continue
+    if mesh.instanceData.contains(name):
+      assert mesh.instanceData[name].theType == theType, &"{material.theType} expected instance attribute '{name}' to be '{theType}' but it is {mesh.instanceData[name].theType}"
+    else:
+      assert false, &"Mesh '{mesh.name}' is missing required instance attribute '{name}: {theType}' for {material.theType}"
+  mesh.material = material
+
+func InstanceCount*(mesh: MeshObject): int =
+  mesh.instanceTransforms.len
+
+func IndicesCount*(mesh: MeshObject): int =
+  (
+    case mesh.indexType
+    of None: 0
+    of Tiny: mesh.tinyIndices.len
+    of Small: mesh.smallIndices.len
+    of Big: mesh.bigIndices.len
+  ) * 3
+
+func `$`*(mesh: MeshObject): string =
+  if mesh.indexType == None:
+    &"Mesh('{mesh.name}', vertexCount: {mesh.vertexCount}, instanceCount: {mesh.InstanceCount}, vertexData: {mesh.vertexData.keys().toSeq()}, instanceData: {mesh.instanceData.keys().toSeq()}, indexType: {mesh.indexType}, material: {mesh.material})"
+  else:
+    &"Mesh('{mesh.name}', vertexCount: {mesh.vertexCount}, indexCount: {mesh.IndicesCount}, instanceCount: {mesh.InstanceCount}, vertexData: {mesh.vertexData.keys().toSeq()}, instanceData: {mesh.instanceData.keys().toSeq()}, indexType: {mesh.indexType}, material: {mesh.material})"
+func `$`*(mesh: Mesh): string =
+  $mesh[]
+
+func VertexAttributes*(mesh: MeshObject): seq[string] =
+  mesh.vertexData.keys.toSeq
+
+func InstanceAttributes*(mesh: MeshObject): seq[string] =
+  mesh.instanceData.keys.toSeq
+
+func Attributes*(mesh: MeshObject): seq[string] =
+  mesh.VertexAttributes & mesh.InstanceAttributes
+
+func hash*(mesh: Mesh): Hash =
+  hash(cast[ptr MeshObject](mesh))
+
+converter ToVulkan*(indexType: MeshIndexType): VkIndexType =
+  case indexType:
+    of None: VK_INDEX_TYPE_NONE_KHR
+    of Tiny: VK_INDEX_TYPE_UINT8_EXT
+    of Small: VK_INDEX_TYPE_UINT16
+    of Big: VK_INDEX_TYPE_UINT32
+
+proc InitVertexAttribute*[T](mesh: var MeshObject, attribute: string, value: seq[T]) =
+  assert not mesh.vertexData.contains(attribute) and not mesh.instanceData.contains(attribute)
+  mesh.vertexData[attribute] = InitDataList(thetype = GetDataType[T]())
+  mesh.vertexData[attribute].SetLen(mesh.vertexCount)
+  mesh.vertexData[attribute] = value
+proc InitVertexAttribute*[T](mesh: var MeshObject, attribute: string, value: T) =
+  InitVertexAttribute(mesh, attribute, newSeqWith(mesh.vertexCount, value))
+proc InitVertexAttribute*[T](mesh: var MeshObject, attribute: string) =
+  InitVertexAttribute(mesh = mesh, attribute = attribute, value = default(T))
+proc InitVertexAttribute*(mesh: var MeshObject, attribute: string, datatype: DataType) =
+  assert not mesh.vertexData.contains(attribute) and not mesh.instanceData.contains(attribute)
+  mesh.vertexData[attribute] = InitDataList(thetype = datatype)
+  mesh.vertexData[attribute].SetLen(mesh.vertexCount)
+proc InitVertexAttribute*(mesh: var MeshObject, attribute: string, data: DataList) =
+  assert not mesh.vertexData.contains(attribute) and not mesh.instanceData.contains(attribute)
+  mesh.vertexData[attribute] = data
+
+
+proc InitInstanceAttribute*[T](mesh: var MeshObject, attribute: string, value: seq[T]) =
+  assert not mesh.vertexData.contains(attribute) and not mesh.instanceData.contains(attribute)
+  mesh.instanceData[attribute] = InitDataList(thetype = GetDataType[T]())
+  mesh.instanceData[attribute].SetLen(mesh.InstanceCount)
+  mesh.instanceData[attribute] = value
+proc InitInstanceAttribute*[T](mesh: var MeshObject, attribute: string, value: T) =
+  InitInstanceAttribute(mesh, attribute, newSeqWith(mesh.InstanceCount, value))
+proc InitInstanceAttribute*[T](mesh: var MeshObject, attribute: string) =
+  InitInstanceAttribute(mesh = mesh, attribute = attribute, value = default(T))
+proc InitInstanceAttribute*(mesh: var MeshObject, attribute: string, datatype: DataType) =
+  assert not mesh.vertexData.contains(attribute) and not mesh.instanceData.contains(attribute)
+  mesh.instanceData[attribute] = InitDataList(thetype = datatype)
+  mesh.instanceData[attribute].SetLen(mesh.InstanceCount)
+proc InitInstanceAttribute*(mesh: var MeshObject, attribute: string, data: DataList) =
+  assert not mesh.vertexData.contains(attribute) and not mesh.instanceData.contains(attribute)
+  mesh.instanceData[attribute] = data
+
+proc NewMesh*(
+  positions: openArray[Vec3f],
+  indices: openArray[array[3, uint32|uint16|uint8]],
+  colors: openArray[Vec4f] = [],
+  uvs: openArray[Vec2f] = [],
+  transform: Mat4 = Unit4,
+  instanceTransforms: openArray[Mat4] = [Unit4],
+  material = EMPTY_MATERIAL.InitMaterialData(),
+  autoResize = true,
+  name: string = ""
+): Mesh =
+  assert colors.len == 0 or colors.len == positions.len
+  assert uvs.len == 0 or uvs.len == positions.len
+  var theName = name
+  if theName == "":
+    theName = &"mesh-{instanceCounter}"
+    inc instanceCounter
+
+  # determine index type (uint8, uint16, uint32)
+  var indexType = None
+  if indices.len > 0:
+    indexType = Big
+    if autoResize and uint32(positions.len) < uint32(high(uint8)) and false: # TODO: check feature support
+      indexType = Tiny
+    elif autoResize and uint32(positions.len) < uint32(high(uint16)):
+      indexType = Small
+
+  result = Mesh(
+    name: theName,
+    indexType: indexType,
+    vertexCount: positions.len,
+    instanceTransforms: @instanceTransforms,
+    transform: transform,
+  )
+
+  result[].InitVertexAttribute(DEFAULT_POSITION_ATTRIBUTE, positions.toSeq)
+  if colors.len > 0: result[].InitVertexAttribute("color", colors.toSeq)
+  if uvs.len > 0: result[].InitVertexAttribute("uv", uvs.toSeq)
+
+  # assert all indices are valid
+  for i in indices:
+    assert int(i[0]) < result[].vertexCount
+    assert int(i[1]) < result[].vertexCount
+    assert int(i[2]) < result[].vertexCount
+
+  # cast index values to appropiate type
+  if result[].indexType == Tiny and uint32(positions.len) < uint32(high(uint8)) and false: # TODO: check feature support
+    for i, tri in enumerate(indices):
+      result[].tinyIndices.add [uint8(tri[0]), uint8(tri[1]), uint8(tri[2])]
+  elif result[].indexType == Small and uint32(positions.len) < uint32(high(uint16)):
+    for i, tri in enumerate(indices):
+      result[].smallIndices.add [uint16(tri[0]), uint16(tri[1]), uint16(tri[2])]
+  elif result[].indexType == Big:
+    for i, tri in enumerate(indices):
+      result[].bigIndices.add [uint32(tri[0]), uint32(tri[1]), uint32(tri[2])]
+  `material=`(result[], material)
+
+proc NewMesh*(
+  positions: openArray[Vec3f],
+  colors: openArray[Vec4f] = [],
+  uvs: openArray[Vec2f] = [],
+  transform: Mat4 = Unit4,
+  instanceTransforms: openArray[Mat4] = [Unit4],
+  material = EMPTY_MATERIAL.InitMaterialData(),
+  name: string = "",
+): Mesh =
+  NewMesh(
+    positions = positions,
+    indices = newSeq[array[3, uint16]](),
+    colors = colors,
+    uvs = uvs,
+    transform = transform,
+    instanceTransforms = instanceTransforms,
+    material = material,
+    name = name,
+  )
+
+func AttributeSize*(mesh: MeshObject, attribute: string): uint64 =
+  if mesh.vertexData.contains(attribute):
+    mesh.vertexData[attribute].Size
+  elif mesh.instanceData.contains(attribute):
+    mesh.instanceData[attribute].Size
+  else:
+    0
+
+func AttributeType*(mesh: MeshObject, attribute: string): DataType =
+  if mesh.vertexData.contains(attribute):
+    mesh.vertexData[attribute].theType
+  elif mesh.instanceData.contains(attribute):
+    mesh.instanceData[attribute].theType
+  else:
+    raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}")
+
+func IndexSize*(mesh: MeshObject): uint64 =
+  case mesh.indexType
+    of None: 0'u64
+    of Tiny: uint64(mesh.tinyIndices.len * sizeof(get(genericParams(typeof(mesh.tinyIndices)), 0)))
+    of Small: uint64(mesh.smallIndices.len * sizeof(get(genericParams(typeof(mesh.smallIndices)), 0)))
+    of Big: uint64(mesh.bigIndices.len * sizeof(get(genericParams(typeof(mesh.bigIndices)), 0)))
+
+func rawData[T: seq](value: T): (pointer, uint64) =
+  (
+    pointer(addr(value[0])),
+    uint64(sizeof(get(genericParams(typeof(value)), 0)) * value.len)
+  )
+
+func GetRawIndexData*(mesh: MeshObject): (pointer, uint64) =
+  case mesh.indexType:
+    of None: raise newException(Exception, "Trying to get index data for non-indexed mesh")
+    of Tiny: rawData(mesh.tinyIndices)
+    of Small: rawData(mesh.smallIndices)
+    of Big: rawData(mesh.bigIndices)
+
+func GetPointer*(mesh: var MeshObject, attribute: string): pointer =
+  if mesh.vertexData.contains(attribute):
+    mesh.vertexData[attribute].GetPointer()
+  elif mesh.instanceData.contains(attribute):
+    mesh.instanceData[attribute].GetPointer()
+  else:
+    raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}")
+
+proc GetAttribute[T: GPUType|int|uint|float](mesh: MeshObject, attribute: string): ref seq[T] =
+  if mesh.vertexData.contains(attribute):
+    mesh.vertexData[attribute][T]
+  elif mesh.instanceData.contains(attribute):
+    mesh.instanceData[attribute][T]
+  else:
+    raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}")
+
+proc GetAttribute[T: GPUType|int|uint|float](mesh: MeshObject, attribute: string, i: int): T =
+  if mesh.vertexData.contains(attribute):
+    mesh.vertexData[attribute][i, T]
+  elif mesh.instanceData.contains(attribute):
+    mesh.instanceData[attribute][i, T]
+  else:
+    raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}")
+
+template `[]`*(mesh: MeshObject, attribute: string, t: typedesc): ref seq[t] =
+  GetAttribute[t](mesh, attribute)
+template `[]`*(mesh: MeshObject, attribute: string, i: int, t: typedesc): untyped =
+  GetAttribute[t](mesh, attribute, i)
+template `[]=`*[T](mesh: MeshObject, attribute: string, value: seq[T]) =
+  GetAttribute[t](mesh, attribute)
+template `[]=`*[T](mesh: MeshObject, attribute: string, i: int, value: T) =
+  GetAttribute[t](mesh, attribute, i)
+
+template `[]`*(mesh: Mesh, attribute: string, t: typedesc): ref seq[t] =
+  mesh[][attribute, t]
+template `[]`*(mesh: Mesh, attribute: string, i: int, t: typedesc): untyped =
+  mesh[][attribute, i, t]
+
+proc UpdateAttributeData[T: GPUType|int|uint|float](mesh: var MeshObject, attribute: string, data: DataList) =
+  if mesh.vertexData.contains(attribute):
+    assert data.len == mesh.vertexCount
+    assert data.theType == mesh.vertexData[attribute].theType
+    mesh.vertexData[attribute] = data
+  elif mesh.instanceData.contains(attribute):
+    assert data.len == mesh.InstanceCount
+    assert data.theType == mesh.instanceData[attribute].theType
+    mesh.instanceData[attribute] = data
+  else:
+    raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}")
+  if not mesh.dirtyAttributes.contains(attribute):
+    mesh.dirtyAttributes.add attribute
+
+proc UpdateAttributeData[T: GPUType|int|uint|float](mesh: var MeshObject, attribute: string, data: seq[T]) =
+  if mesh.vertexData.contains(attribute):
+    assert data.len == mesh.vertexCount
+    mesh.vertexData[attribute] = data
+  elif mesh.instanceData.contains(attribute):
+    assert data.len == mesh.InstanceCount
+    mesh.instanceData[attribute] = data
+  else:
+    raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}")
+  if not mesh.dirtyAttributes.contains(attribute):
+    mesh.dirtyAttributes.add attribute
+
+proc UpdateAttributeData[T: GPUType|int|uint|float](mesh: var MeshObject, attribute: string, i: int, value: T) =
+  if mesh.vertexData.contains(attribute):
+    assert i < mesh.vertexData[attribute].len
+    mesh.vertexData[attribute][i] = value
+  elif mesh.instanceData.contains(attribute):
+    assert i < mesh.instanceData[attribute].len
+    mesh.instanceData[attribute][i] = value
+  else:
+    raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}")
+  if not mesh.dirtyAttributes.contains(attribute):
+    mesh.dirtyAttributes.add attribute
+
+proc `[]=`*[T: GPUType|int|uint|float](mesh: var MeshObject, attribute: string, data: DataList) =
+  UpdateAttributeData[T](mesh, attribute, data)
+proc `[]=`*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string, data: DataList) =
+  UpdateAttributeData[t](mesh[], attribute, data)
+
+proc `[]=`*[T: GPUType|int|uint|float](mesh: var MeshObject, attribute: string, data: seq[T]) =
+  UpdateAttributeData[T](mesh, attribute, data)
+proc `[]=`*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string, data: seq[T]) =
+  UpdateAttributeData[T](mesh[], attribute, data)
+
+proc `[]=`*[T: GPUType|int|uint|float](mesh: var MeshObject, attribute: string, value: T) =
+  UpdateAttributeData[T](mesh, attribute, newSeqWith(mesh.vertexCount, value))
+proc `[]=`*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string, value: T) =
+  UpdateAttributeData[T](mesh[], attribute, newSeqWith(mesh.vertexCount, value))
+
+proc `[]=`*[T: GPUType|int|uint|float](mesh: var MeshObject, attribute: string, i: int, value: T) =
+  UpdateAttributeData[T](mesh, attribute, i, value)
+proc `[]=`*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string, i: int, value: T) =
+  UpdateAttributeData[T](mesh[], attribute, i, value)
+
+proc RemoveAttribute*(mesh: var MeshObject, attribute: string) =
+  if mesh.vertexData.contains(attribute):
+    mesh.vertexData.del(attribute)
+  elif mesh.instanceData.contains(attribute):
+    mesh.instanceData.del(attribute)
+  else:
+    raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}")
+
+proc AppendIndicesData*(mesh: var MeshObject, v1, v2, v3: int) =
+  case mesh.indexType
+  of None: raise newException(Exception, "Mesh does not support indexed data")
+  of Tiny: mesh.tinyIndices.add([uint8(v1), uint8(v2), uint8(v3)])
+  of Small: mesh.smallIndices.add([uint16(v1), uint16(v2), uint16(v3)])
+  of Big: mesh.bigIndices.add([uint32(v1), uint32(v2), uint32(v3)])
+
+proc UpdateInstanceTransforms*(mesh: var MeshObject, attribute: string) =
+  var currentTransforms: seq[Mat4]
+  if mesh.applyMeshTransformToInstances:
+    currentTransforms = mesh.instanceTransforms.mapIt(mesh.transform * it)
+  else:
+    currentTransforms = mesh.instanceTransforms
+  if currentTransforms != mesh.transformCache:
+    mesh[attribute] = currentTransforms
+    mesh.transformCache = currentTransforms
+
+proc RenameAttribute*(mesh: var MeshObject, oldname, newname: string) =
+  if mesh.vertexData.contains(oldname):
+    mesh.vertexData[newname] = mesh.vertexData[oldname]
+    mesh.vertexData.del oldname
+  elif mesh.instanceData.contains(oldname):
+    mesh.instanceData[newname] = mesh.vertexData[oldname]
+    mesh.instanceData.del oldname
+  else:
+    raise newException(Exception, &"Attribute {oldname} is not defined for mesh {mesh}")
+
+func DirtyAttributes*(mesh: MeshObject): seq[string] =
+  mesh.dirtyAttributes
+
+proc ClearDirtyAttributes*(mesh: var MeshObject) =
+  mesh.dirtyAttributes.reset
+
+proc SetShaderMaterialIndices*(mesh: var MeshObject, shadername: string, values: seq[uint16], attributeName = MATERIALINDEX_ATTRIBUTE) =
+  let indexAttribute = shadername & "_" & attributeName
+  assert values.len == mesh.InstanceCount, &"Mesh {mesh}: While trying to set shader material indices for shader '{shadername}': indices have len {values.len}, but instance count is {mesh.InstanceCount}"
+  mesh[indexAttribute] = values
+
+# MESH-TOOLS
+
+proc Transform*[T: GPUType](mesh: var MeshObject, attribute: string, transform: Mat4) =
+  if mesh.vertexData.contains(attribute):
+    for i in 0 ..< mesh.vertexData[attribute].len:
+      mesh.vertexData[attribute][i] = transform * mesh.vertexData[attribute][i, T]
+  elif mesh.instanceData.contains(attribute):
+    for i in 0 ..< mesh.instanceData[attribute].len:
+      mesh.instanceData[attribute][i] = transform * mesh.vertexData[attribute][i, T]
+  else:
+    raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}")
+  mesh.dirtyAttributes.add attribute
+
+func GetMeshPoints*(mesh: MeshObject, positionAttribute = DEFAULT_POSITION_ATTRIBUTE): seq[Vec3f] =
+  for p in mesh[positionAttribute, Vec3f][]:
+    result.add mesh.transform * p
+
+func GetCollider*(mesh: MeshObject, positionAttribute = DEFAULT_POSITION_ATTRIBUTE): Collider =
+  return mesh.GetMeshPoints(positionAttribute).CalculateCollider(Points)
+
+proc AsNonIndexedMesh*(mesh: MeshObject): MeshObject =
+  if mesh.indexType == None:
+    return mesh
+
+  result = MeshObject(
+    vertexCount: mesh.IndicesCount,
+    indexType: None,
+    transform: mesh.transform,
+    instanceTransforms: mesh.instanceTransforms,
+    visible: mesh.visible,
+  )
+  for attribute, datalist in mesh.vertexData.pairs:
+    result.InitVertexAttribute(attribute, datalist.theType)
+  for attribute, datalist in mesh.instanceData.pairs:
+    result.instanceData[attribute] = datalist.Copy()
+  var i = 0
+  case mesh.indexType
+  of Tiny:
+    for indices in mesh.tinyIndices:
+      for attribute, value in mesh.vertexData.pairs:
+        result.vertexData[attribute].AppendFrom(i, mesh.vertexData[attribute], int(indices[0]))
+        result.vertexData[attribute].AppendFrom(i + 1, mesh.vertexData[attribute], int(indices[1]))
+        result.vertexData[attribute].AppendFrom(i + 2, mesh.vertexData[attribute], int(indices[2]))
+      i += 3
+  of Small:
+    for indices in mesh.smallIndices:
+      for attribute, value in mesh.vertexData.pairs:
+        result.vertexData[attribute].AppendFrom(i, value, int(indices[0]))
+        result.vertexData[attribute].AppendFrom(i + 1, value, int(indices[1]))
+        result.vertexData[attribute].AppendFrom(i + 2, value, int(indices[2]))
+      i += 3
+  of Big:
+    for indices in mesh.bigIndices:
+      for attribute, value in mesh.vertexData.pairs:
+        result.vertexData[attribute].AppendFrom(i, mesh.vertexData[attribute], int(indices[0]))
+        result.vertexData[attribute].AppendFrom(i + 1, mesh.vertexData[attribute], int(indices[1]))
+        result.vertexData[attribute].AppendFrom(i + 2, mesh.vertexData[attribute], int(indices[2]))
+      i += 3
+  else:
+    discard
+  `material=`(result, mesh.material)
+
+
+# GENERATORS ============================================================================
+
+proc Rect*(width = 1'f32, height = 1'f32, color = "ffffffff", material = EMPTY_MATERIAL.InitMaterialData()): Mesh =
+  result = Mesh(
+    vertexCount: 4,
+    instanceTransforms: @[Unit4],
+    indexType: Small,
+    smallIndices: @[[0'u16, 1'u16, 2'u16], [2'u16, 3'u16, 0'u16]],
+    name: &"rect-{instanceCounter}",
+  )
+  inc instanceCounter
+
+  let
+    half_w = width / 2
+    half_h = height / 2
+    pos = @[NewVec3f(-half_w, -half_h), NewVec3f(half_w, -half_h), NewVec3f(half_w, half_h), NewVec3f(-half_w, half_h)]
+    c = ToRGBA(color)
+
+  result[].InitVertexAttribute(DEFAULT_POSITION_ATTRIBUTE, pos)
+  result[].InitVertexAttribute("color", @[c, c, c, c])
+  result[].InitVertexAttribute("uv", @[NewVec2f(0, 0), NewVec2f(1, 0), NewVec2f(1, 1), NewVec2f(0, 1)])
+  `material=`(result[], material)
+
+proc Tri*(width = 1'f32, height = 1'f32, color = "ffffffff", material = EMPTY_MATERIAL.InitMaterialData()): Mesh =
+  result = Mesh(
+    vertexCount: 3,
+    instanceTransforms: @[Unit4],
+    name: &"tri-{instanceCounter}",
+  )
+  inc instanceCounter
+  let
+    half_w = width / 2
+    half_h = height / 2
+    colorVec = ToRGBA(color)
+
+  result[].InitVertexAttribute(DEFAULT_POSITION_ATTRIBUTE, @[NewVec3f(0, -half_h), NewVec3f(half_w, half_h), NewVec3f(-half_w, half_h)])
+  result[].InitVertexAttribute("color", @[colorVec, colorVec, colorVec])
+  `material=`(result[], material)
+
+proc CircleMesh*(nSegments: int): (seq[Vec3f], seq[array[3, uint16]]) =
+  let
+    rX = 0.5
+    rY = 0.5
+    step = (2'f32 * PI) / float32(nSegments)
+  result[0] = @[NewVec3f(0, 0), NewVec3f(rX, 0)]
+  for i in 1 .. nSegments:
+    result[0].add NewVec3f(cos(float32(i) * step) * rX, sin(float32(i) * step) * rY)
+    result[1].add [uint16(0), uint16(i), uint16(i + 1)]
+
+proc Circle*(width = 1'f32, height = 1'f32, nSegments = 12, color = "ffffffff", material = EMPTY_MATERIAL.InitMaterialData()): Mesh =
+  assert nSegments >= 3
+  result = Mesh(
+    vertexCount: 3 + nSegments,
+    instanceTransforms: @[Unit4],
+    indexType: Small,
+    name: &"circle-{instanceCounter}",
+  )
+  inc instanceCounter
+
+  let
+    rX = width / 2
+    rY = height / 2
+    c = ToRGBA(color)
+    step = (2'f32 * PI) / float32(nSegments)
+  var
+    pos = @[NewVec3f(0, 0), NewVec3f(rX, 0)]
+    col = @[c, c]
+    uv = @[NewVec2f(0.5, 0.5), NewVec2f(rX, height / 2)]
+  for i in 1 .. nSegments:
+    pos.add NewVec3f(cos(float32(i) * step) * rX, sin(float32(i) * step) * rY)
+    col.add c
+    uv.add NewVec2f(cos(float32(i) * step) * 0.5 + 0.5, sin(float32(i) * step) * 0.5 + 0.5)
+    result[].smallIndices.add [uint16(0), uint16(i), uint16(i + 1)]
+
+  result[].InitVertexAttribute(DEFAULT_POSITION_ATTRIBUTE, pos)
+  result[].InitVertexAttribute("color", col)
+  result[].InitVertexAttribute("uv", uv)
+  `material=`(result[], material)
+
+proc CircleMesh*(width = 1'f32, height = 1'f32, nSegments = 12): (seq[Vec3f], seq[array[3, uint16]]) =
+  assert nSegments >= 3
+  result[0] = newSeq[Vec3f](3 + nSegments)
+
+  let
+    rX = width / 2
+    rY = height / 2
+    step = (2'f32 * PI) / float32(nSegments)
+  result[0][0] = NewVec3f(0, 0)
+  result[0][1] = NewVec3f(rX, 0)
+  for i in 1 .. nSegments:
+    result[0][i + 1] = NewVec3f(cos(float32(i) * step) * rX, sin(float32(i) * step) * rY)
+    result[1].add [uint16(0), uint16(i), uint16(i + 1)]
+
+proc Grid*(columns, rows: uint16, cellSize = 1.0'f32, color = "ffffffff", material = EMPTY_MATERIAL.InitMaterialData()): Mesh =
+
+  result = Mesh(
+    vertexCount: int((rows + 1) * (columns + 1)),
+    instanceTransforms: @[Unit4],
+    indexType: Small,
+    name: &"grid-{instanceCounter}",
+  )
+  inc instanceCounter
+
+  let
+    color = ToRGBA(color)
+    center_offset_x = -(float32(columns) * cellSize) / 2'f32
+    center_offset_y = -(float32(rows) * cellSize) / 2'f32
+  var
+    pos: seq[Vec3f]
+    col: seq[Vec4f]
+    i = 0'u16
+  for h in 0'u16 .. rows:
+    for w in 0'u16 .. columns:
+      pos.add NewVec3f(center_offset_x + float32(w) * cellSize, center_offset_y + float32(h) * cellSize)
+      col.add color
+      if w > 0 and h > 0:
+        result[].smallIndices.add [i, i - 1, i - rows - 2]
+        result[].smallIndices.add [i, i - rows - 2, i - rows - 1]
+      i.inc
+
+  result[].InitVertexAttribute(DEFAULT_POSITION_ATTRIBUTE, pos)
+  result[].InitVertexAttribute("color", col)
+  `material=`(result[], material)
+
+proc MergeMeshData*(a: var Mesh, b: Mesh) =
+  let originalOffset = a.vertexCount
+  a.vertexCount = a.vertexCount + b.vertexCount
+  assert a.indexType == b.indexType
+  for key in a.vertexData.keys:
+    assert key in b.vertexData, &"Mesh {b} is missing vertex data for '{key}'"
+  for (key, value) in b.vertexData.pairs:
+    a.vertexData[key].AppendValues(value)
+
+  case a.indexType:
+    of None:
+      discard
+    of Tiny:
+      let offset = uint8(originalOffset)
+      for i in b.tinyIndices:
+        a.tinyIndices.add [i[0] + offset, i[1] + offset, i[2] + offset]
+    of Small:
+      let offset = uint16(originalOffset)
+      for i in b.smallIndices:
+        a.smallIndices.add [i[0] + offset, i[1] + offset, i[2] + offset]
+    of Big:
+      let offset = uint32(originalOffset)
+      for i in b.bigIndices:
+        a.bigIndices.add [i[0] + offset, i[1] + offset, i[2] + offset]
+
+# MESH TREES =============================================================================
+
+type
+  MeshTree* = ref object
+    mesh*: Mesh
+    transform*: Mat4 = Unit4
+    children*: seq[MeshTree]
+
+func toStringRec*(tree: MeshTree, theindent = 0): seq[string] =
+  if tree.mesh.isNil:
+    result.add "*"
+  else:
+    result.add indent($tree.mesh, theindent)
+  for child in tree.children:
+    result.add child.toStringRec(theindent + 4)
+
+func `$`*(tree: MeshTree): string =
+  toStringRec(tree).join("\n")
+
+
+proc toSeq*(tree: MeshTree): seq[Mesh] =
+  var queue = @[tree]
+  while queue.len > 0:
+    var current = queue.pop
+    if not current.mesh.isNil:
+      result.add current.mesh
+    queue.add current.children
+
+proc updateTransforms*(tree: MeshTree, parentTransform = Unit4) =
+  let currentTransform = parentTransform * tree.transform
+  if not tree.mesh.isNil:
+    tree.mesh.transform = currentTransform
+  for child in tree.children:
+    child.updateTransforms(currentTransform)