view src/semicongine/mesh.nim @ 792:d65b62812d34

did: undid using meshes as values, ref is much better, fix a few things, fix a few huge performance issues
author Sam <sam@basx.dev>
date Sun, 03 Sep 2023 17:34:29 +0700
parents 43432dad4797
children 59c54c4486c4
line wrap: on
line source

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

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
    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*: Material
    transform*: Mat4 = Unit4F32
    instanceTransforms*: seq[Mat4]
    transformCache: seq[Mat4]
    vertexData: Table[string, DataList]
    instanceData: Table[string, DataList]
    dirtyAttributes: seq[string]
  Mesh* = ref MeshObject
  Material* = object
    name*: string
    constants*: Table[string, DataList]
    textures*: Table[string, Texture]

let EMPTY_MATERIAL = Material(
  name: "empty material"
)

func `$`*(mesh: MeshObject): string =
  &"Mesh(vertexCount: {mesh.vertexCount}, vertexData: {mesh.vertexData.keys().toSeq()}, instanceData: {mesh.instanceData.keys().toSeq()}, indexType: {mesh.indexType})"
func `$`*(mesh: Mesh): string =
  $mesh[]

proc `$`*(material: Material): string =
  var constants: seq[string]
  for key, value in material.constants.pairs:
    constants.add &"{key}: {value}"
  var textures: seq[string]
  for key in material.textures.keys:
    textures.add &"{key}"
  return &"""{material.name} | Values: {constants.join(", ")} | Textures: {textures.join(", ")}"""

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*(material: Material): Hash =
  hash(material.name)

func hash*(mesh: Mesh): Hash =
  hash(cast[ptr MeshObject](mesh))

func instanceCount*(mesh: MeshObject): int =
  mesh.instanceTransforms.len

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

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 initVertexAttribute*[T](mesh: var MeshObject, attribute: string, value: seq[T]) =
  assert not mesh.vertexData.contains(attribute)
  mesh.vertexData[attribute] = newDataList(thetype=getDataType[T]())
  mesh.vertexData[attribute].initData(mesh.vertexCount)
  mesh.vertexData[attribute].setValues(value)
func initVertexAttribute*[T](mesh: var MeshObject, attribute: string, value: T) =
  initVertexAttribute(mesh, attribute, newSeqWith(mesh.vertexCount, value))
func initVertexAttribute*[T](mesh: var MeshObject, attribute: string) =
  initVertexAttribute(mesh=mesh, attribute=attribute, value=default(T))
func initVertexAttribute*(mesh: var MeshObject, attribute: string, datatype: DataType) =
  assert not mesh.vertexData.contains(attribute)
  mesh.vertexData[attribute] = newDataList(thetype=datatype)
  mesh.vertexData[attribute].initData(mesh.vertexCount)

func initInstanceAttribute*[T](mesh: var MeshObject, attribute: string, value: seq[T]) =
  assert not mesh.instanceData.contains(attribute)
  mesh.instanceData[attribute] = newDataList(thetype=getDataType[T]())
  mesh.instanceData[attribute].initData(mesh.instanceCount)
  mesh.instanceData[attribute].setValues(value)
func initInstanceAttribute*[T](mesh: var MeshObject, attribute: string, value: T) =
  initInstanceAttribute(mesh, attribute, newSeqWith(mesh.instanceCount, value))
func initInstanceAttribute*[T](mesh: var MeshObject, attribute: string) =
  initInstanceAttribute(mesh=mesh, attribute=attribute, value=default(T))
func initInstanceAttribute*(mesh: var MeshObject, attribute: string, datatype: DataType) =
  assert not mesh.instanceData.contains(attribute)
  mesh.instanceData[attribute] = newDataList(thetype=datatype)
  mesh.instanceData[attribute].initData(mesh.instanceCount)

proc newMesh*(
  positions: openArray[Vec3f],
  indices: openArray[array[3, uint32|uint16|uint8]],
  colors: openArray[Vec4f]=[],
  uvs: openArray[Vec2f]=[],
  transform: Mat4=Unit4F32,
  instanceTransforms: openArray[Mat4]=[Unit4F32],
  material: Material=EMPTY_MATERIAL,
  autoResize=true,
): Mesh =
  assert colors.len == 0 or colors.len == positions.len
  assert uvs.len == 0 or uvs.len == positions.len

  # 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(
    indexType: indexType,
    vertexCount: positions.len,
    instanceTransforms: @instanceTransforms,
    transform: transform,
    material: material,
  )

  result[].initVertexAttribute("position", 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])]

proc newMesh*(
  positions: openArray[Vec3f],
  colors: openArray[Vec4f]=[],
  uvs: openArray[Vec2f]=[],
  transform: Mat4=Unit4F32,
  instanceTransforms: openArray[Mat4]=[Unit4F32],
  material: Material=EMPTY_MATERIAL,
): Mesh =
  newMesh(
    positions=positions,
    indices=newSeq[array[3, uint16]](),
    colors=colors,
    uvs=uvs,
    transform=transform,
    instanceTransforms=instanceTransforms,
    material=material,
  )

func attributeSize*(mesh: MeshObject, attribute: string): int =
  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): int =
  case mesh.indexType
    of None: 0
    of Tiny: mesh.tinyIndices.len * sizeof(get(genericParams(typeof(mesh.tinyIndices)), 0))
    of Small: mesh.smallIndices.len * sizeof(get(genericParams(typeof(mesh.smallIndices)), 0))
    of Big: mesh.bigIndices.len * sizeof(get(genericParams(typeof(mesh.bigIndices)), 0))

func rawData[T: seq](value: T): (pointer, int) =
  (pointer(addr(value[0])), sizeof(get(genericParams(typeof(value)), 0)) * value.len)

func getRawIndexData*(mesh: MeshObject): (pointer, int) =
  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 getRawData*(mesh: var MeshObject, attribute: string): (pointer, int) =
  if mesh.vertexData.contains(attribute):
    mesh.vertexData[attribute].getRawData()
  elif mesh.instanceData.contains(attribute):
    mesh.instanceData[attribute].getRawData()
  else:
    raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}")

proc getAttribute*[T: GPUType|int|uint|float](mesh: MeshObject, attribute: string): seq[T] =
  if mesh.vertexData.contains(attribute):
    getValues[T](mesh.vertexData[attribute])
  elif mesh.instanceData.contains(attribute):
    getValues[T](mesh.instanceData[attribute])
  else:
    raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}")

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
    setValues(mesh.vertexData[attribute], data)
  elif mesh.instanceData.contains(attribute):
    assert data.len == mesh.instanceCount
    setValues(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
    setValue(mesh.vertexData[attribute], i, value)
  elif mesh.instanceData.contains(attribute):
    assert i < mesh.instanceData[attribute].len
    setValue(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 appendAttributeData*[T: GPUType|int|uint|float](mesh: var MeshObject, attribute: string, data: seq[T]) =
  if mesh.vertexData.contains(attribute):
    appendValues(mesh.vertexData[attribute], data)
  elif mesh.instanceData.contains(attribute):
    appendValues(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

# currently only used for loading from files, shouls
proc appendAttributeData*(mesh: var MeshObject, attribute: string, data: DataList) =
  if mesh.vertexData.contains(attribute):
    assert data.thetype == mesh.vertexData[attribute].thetype
    appendValues(mesh.vertexData[attribute], data)
  elif mesh.instanceData.contains(attribute):
    assert data.thetype == mesh.instanceData[attribute].thetype
    appendValues(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 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) =
  let currentTransforms = mesh.instanceTransforms.mapIt(mesh.transform * it)
  if currentTransforms != mesh.transformCache:
    mesh.updateAttributeData(attribute, currentTransforms)
    mesh.transformCache = currentTransforms

func dirtyAttributes*(mesh: MeshObject): seq[string] =
  mesh.dirtyAttributes

proc clearDirtyAttributes*(mesh: var MeshObject) =
  mesh.dirtyAttributes.reset

proc transform*[T: GPUType](mesh: MeshObject, attribute: string, transform: Mat4) =
  if mesh.vertexData.contains(attribute):
    for i in 0 ..< mesh.vertexData[attribute].len:
      setValue(mesh.vertexData[attribute], i, transform * getValue[T](mesh.vertexData[attribute], i))
  elif mesh.instanceData.contains(attribute):
    for i in 0 ..< mesh.instanceData[attribute].len:
      setValue(mesh.instanceData[attribute], i, transform * getValue[T](mesh.vertexData[attribute], i))
  else:
    raise newException(Exception, &"Attribute {attribute} is not defined for mesh {mesh}")

proc rect*(width=1'f32, height=1'f32, color="ffffffff"): Mesh =
  result = Mesh(
    vertexCount: 4,
    instanceTransforms: @[Unit4F32],
    indexType: Small,
    smallIndices: @[[0'u16, 1'u16, 2'u16], [2'u16, 3'u16, 0'u16]],
  )

  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 = hexToColorAlpha(color)

  result[].initVertexAttribute("position", pos)
  result[].initVertexAttribute("color", @[c, c, c, c])
  result[].initVertexAttribute("uv", @[newVec2f(0, 0), newVec2f(1, 0), newVec2f(1, 1), newVec2f(0, 1)])

proc tri*(width=1'f32, height=1'f32, color="ffffffff"): Mesh =
  result = Mesh(vertexCount: 3, instanceTransforms: @[Unit4F32])
  let
    half_w = width / 2
    half_h = height / 2
    colorVec = hexToColorAlpha(color)

  result[].initVertexAttribute("position", @[newVec3f(0, -half_h), newVec3f( half_w, half_h), newVec3f(-half_w,  half_h)])
  result[].initVertexAttribute("color", @[colorVec, colorVec, colorVec])

proc circle*(width=1'f32, height=1'f32, nSegments=12, color="ffffffff"): Mesh =
  assert nSegments >= 3
  result = Mesh(vertexCount: 3 + nSegments, instanceTransforms: @[Unit4F32], indexType: Small)

  let
    half_w = width / 2
    half_h = height / 2
    c = hexToColorAlpha(color)
    step = (2'f32 * PI) / float32(nSegments)
  var
    pos = @[newVec3f(0, 0), newVec3f(0, half_h)]
    col = @[c, c]
  for i in 0 .. nSegments:
    pos.add newVec3f(cos(float32(i) * step) * half_w, sin(float32(i) * step) * half_h)
    col.add c
    result[].smallIndices.add [uint16(0), uint16(i + 1), uint16(i + 2)]

  result[].initVertexAttribute("position", pos)
  result[].initVertexAttribute("color", col)

func getCollisionPoints*(mesh: MeshObject, positionAttribute="position"): seq[Vec3f] =
  for p in getAttribute[Vec3f](mesh, positionAttribute):
    result.add mesh.transform * p