Mercurial > games > semicongine
view src/semicongine/mesh.nim @ 744:d1c6e3132f6a
did: improve matrix transformation API, no need to manually convert Vec3 anymore
author | Sam <sam@basx.dev> |
---|---|
date | Sun, 04 Jun 2023 00:30:43 +0700 |
parents | c0f8ef9594cc |
children | 37a9abfd0008 |
line wrap: on
line source
import std/math as nimmath import std/typetraits import std/tables import std/enumerate import std/strformat import std/sequtils import ./core import ./scene 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 Mesh* = ref object of Component instanceCount*: uint32 instanceTransforms: seq[Mat4] # this should not reside in data["transform"], as we will use data["transform"] to store the final transformation matrix (as derived from the scene-tree) dirtyInstanceTransforms: bool data: Table[string, DataList] changedAttributes: seq[string] 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]] 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 vertexCount*(mesh: Mesh): uint32 = result = 0'u32 for list in mesh.data.values: result = max(list.len, result) func indicesCount*(mesh: Mesh): uint32 = ( case mesh.indexType of None: 0'u32 of Tiny: uint32(mesh.tinyIndices.len) of Small: uint32(mesh.smallIndices.len) of Big: uint32(mesh.bigIndices.len) ) * 3 method `$`*(mesh: Mesh): string = &"Mesh, vertexCount: {mesh.vertexCount}, vertexData: {mesh.data.keys().toSeq()}, indexType: {mesh.indexType}" func prettyData*(mesh: Mesh): string = for attr, data in mesh.data.pairs: result &= &"{attr}: {data}\n" result &= (case mesh.indexType of None: "" of Tiny: &"indices: {mesh.tinyIndices}" of Small: &"indices: {mesh.smallIndices}" of Big: &"indices: {mesh.bigIndices}") proc setMeshData*[T: GPUType|int|uint|float](mesh: var Mesh, attribute: string, data: seq[T]) = assert not (attribute in mesh.data) mesh.data[attribute] = newDataList(data) proc setMeshData*(mesh: var Mesh, attribute: string, data: DataList) = assert not (attribute in mesh.data) mesh.data[attribute] = data proc setInstanceData*[T: GPUType|int|uint|float](mesh: var Mesh, attribute: string, data: seq[T]) = assert uint32(data.len) == mesh.instanceCount assert not (attribute in mesh.data) mesh.data[attribute] = newDataList(data) func newMesh*( positions: openArray[Vec3f], indices: openArray[array[3, uint32|int32|uint16|int16|int]], colors: openArray[Vec4f]=[], uvs: openArray[Vec2f]=[], instanceCount=1'u32, autoResize=true ): auto = assert colors.len == 0 or colors.len == positions.len assert uvs.len == 0 or uvs.len == positions.len result = Mesh(instanceCount: instanceCount, instanceTransforms: newSeqWith(int(instanceCount), Unit4F32)) setMeshData(result, "position", positions.toSeq) if colors.len > 0: setMeshData(result, "color", colors.toSeq) if uvs.len > 0: setMeshData(result, "uv", uvs.toSeq) for i in indices: assert uint32(i[0]) < result.vertexCount assert uint32(i[1]) < result.vertexCount assert uint32(i[2]) < result.vertexCount if indices.len == 0: result.indexType = None else: if autoResize and uint32(positions.len) < uint32(high(uint8)) and false: # TODO: check feature support result.indexType = Tiny for i, tri in enumerate(indices): result.tinyIndices.add [uint8(tri[0]), uint8(tri[1]), uint8(tri[2])] elif autoResize and uint32(positions.len) < uint32(high(uint16)): result.indexType = Small for i, tri in enumerate(indices): result.smallIndices.add [uint16(tri[0]), uint16(tri[1]), uint16(tri[2])] else: result.indexType = Big for i, tri in enumerate(indices): result.bigIndices.add [uint32(tri[0]), uint32(tri[1]), uint32(tri[2])] setInstanceData(result, "transform", newSeqWith(int(instanceCount), Unit4F32)) func newMesh*( positions: openArray[Vec3f], colors: openArray[Vec4f]=[], uvs: openArray[Vec2f]=[], instanceCount=1'u32, ): auto = newMesh(positions, newSeq[array[3, int]](), colors, uvs, instanceCount) func availableAttributes*(mesh: Mesh): seq[string] = mesh.data.keys.toSeq func dataSize*(mesh: Mesh, attribute: string): uint32 = mesh.data[attribute].size func dataType*(mesh: Mesh, attribute: string): DataType = mesh.data[attribute].theType func indexDataSize*(mesh: Mesh): uint32 = 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: var T): (pointer, uint32) = (pointer(addr(value[0])), uint32(sizeof(get(genericParams(typeof(value)), 0)) * value.len)) func getRawIndexData*(mesh: Mesh): (pointer, uint32) = 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 hasDataFor*(mesh: Mesh, attribute: string): bool = attribute in mesh.data func getRawData*(mesh: Mesh, attribute: string): (pointer, uint32) = mesh.data[attribute].getRawData() proc getMeshData*[T: GPUType|int|uint|float](mesh: Mesh, attribute: string): ref seq[T] = assert attribute in mesh.data getValues[T](mesh.data[attribute]) proc initData*(mesh: var Mesh, attribute: ShaderAttribute) = assert not (attribute.name in mesh.data) mesh.data[attribute.name] = newDataList(thetype=attribute.thetype) if attribute.perInstance: mesh.data[attribute.name].initData(mesh.instanceCount) else: mesh.data[attribute.name].initData(mesh.vertexCount) proc updateMeshData*[T: GPUType|int|uint|float](mesh: var Mesh, attribute: string, data: seq[T]) = assert attribute in mesh.data mesh.changedAttributes.add attribute setValues(mesh.data[attribute], data) proc updateMeshData*[T: GPUType|int|uint|float](mesh: var Mesh, attribute: string, i: uint32, value: T) = assert attribute in mesh.data mesh.changedAttributes.add attribute setValue(mesh.data[attribute], i, value) proc appendMeshData*[T: GPUType|int|uint|float](mesh: var Mesh, attribute: string, data: seq[T]) = assert attribute in mesh.data mesh.changedAttributes.add attribute appendValues(mesh.data[attribute], data) # currently only used for loading from files, shouls proc appendMeshData*(mesh: var Mesh, attribute: string, data: DataList) = assert attribute in mesh.data assert data.thetype == mesh.data[attribute].thetype mesh.changedAttributes.add attribute appendValues(mesh.data[attribute], data) proc updateInstanceData*[T: GPUType|int|uint|float](mesh: var Mesh, attribute: string, data: seq[T]) = assert uint32(data.len) == mesh.instanceCount assert attribute in mesh.data mesh.changedAttributes.add attribute setValues(mesh.data[attribute], data) proc appendInstanceData*[T: GPUType|int|uint|float](mesh: var Mesh, attribute: string, data: seq[T]) = assert uint32(data.len) == mesh.instanceCount assert attribute in mesh.data mesh.changedAttributes.add attribute appendValues(mesh.data[attribute], data) proc appendIndicesData*(mesh: var Mesh, v1, v2, v3: uint32) = 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([v1, v2, v3]) func hasDataChanged*(mesh: Mesh, attribute: string): bool = attribute in mesh.changedAttributes proc clearDataChanged*(mesh: var Mesh) = mesh.changedAttributes = @[] proc transform*[T: GPUType](mesh: var Mesh, attribute: string, transform: Mat4) = assert attribute in mesh.data for v in getValues[T](mesh.data[attribute])[].mitems: v = transform * v func rect*(width=1'f32, height=1'f32, color="ffffffff"): Mesh = result = Mesh( instanceCount: 1, indexType: Small, smallIndices: @[[0'u16, 1'u16, 2'u16], [2'u16, 3'u16, 0'u16]], instanceTransforms: @[Unit4F32] ) 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) setMeshData(result, "position", pos) setMeshData(result, "color", @[c, c, c, c]) setMeshData(result, "uv", @[newVec2f(0, 0), newVec2f(1, 0), newVec2f(1, 1), newVec2f(0, 1)]) setInstanceData(result, "transform", @[Unit4F32]) func tri*(width=1'f32, height=1'f32, color="ffffffff"): Mesh = result = Mesh(instanceCount: 1, instanceTransforms: @[Unit4F32]) let half_w = width / 2 half_h = height / 2 colorVec = hexToColorAlpha(color) setMeshData(result, "position", @[newVec3f(0, -half_h), newVec3f( half_w, half_h), newVec3f(-half_w, half_h)]) setMeshData(result, "color", @[colorVec, colorVec, colorVec]) setInstanceData(result, "transform", @[Unit4F32]) func circle*(width=1'f32, height=1'f32, nSegments=12'u16, color="ffffffff"): Mesh = assert nSegments >= 3 result = Mesh(instanceCount: 1, indexType: Small, instanceTransforms: @[Unit4F32]) 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'u16 .. nSegments: pos.add newVec3f(cos(float32(i) * step) * half_w, sin(float32(i) * step) * half_h) col.add c result.smallIndices.add [0'u16, i + 1, i + 2] setMeshData(result, "position", pos) setMeshData(result, "color", col) setInstanceData(result, "transform", @[Unit4F32]) proc areInstanceTransformsDirty*(mesh: var Mesh): bool = result = mesh.dirtyInstanceTransforms mesh.dirtyInstanceTransforms = false proc setInstanceTransform*(mesh: var Mesh, i: uint32, mat: Mat4) = assert 0 <= i and i < mesh.instanceCount mesh.instanceTransforms[i] = mat mesh.dirtyInstanceTransforms = true proc setInstanceTransforms*(mesh: var Mesh, mat: seq[Mat4]) = mesh.instanceTransforms = mat mesh.dirtyInstanceTransforms = true func getInstanceTransform*(mesh: Mesh, i: uint32): Mat4 = assert 0 <= i and i < mesh.instanceCount mesh.instanceTransforms[i] func getInstanceTransforms*(mesh: Mesh): seq[Mat4] = mesh.instanceTransforms func getCollisionPoints*(mesh: Mesh, positionAttribute="position"): seq[Vec3f] = for p in getMeshData[Vec3f](mesh, positionAttribute)[]: result.add mesh.entity.getModelTransform() * p