# HG changeset patch # User Sam # Date 1696168415 -25200 # Node ID 995e273157cd6d223d942bf02d7d8911f12921a6 # Parent ad009d1c747f78678f0ff484d847af42e98e4236 improve 2D collision, add some vector functionality, allow shaders/pipelines to be ordered for deterministic rendering order diff -r ad009d1c747f -r 995e273157cd src/semicongine/collision.nim --- a/src/semicongine/collision.nim Fri Sep 29 19:30:07 2023 +0700 +++ b/src/semicongine/collision.nim Sun Oct 01 20:53:35 2023 +0700 @@ -272,7 +272,7 @@ result = (normal: minNormal, penetrationDepth: minDistance + 0.001'f32) -func collisionPoint2D(polytopeIn: seq[Vec3f], a, b: Collider): tuple[normal: Vec2f, penetrationDepth: float32] = +func collisionPoint2D(polytopeIn: seq[Vec3f], a, b: Collider): tuple[normal: Vec3f, penetrationDepth: float32] = var polytope = polytopeIn minIndex = 0 @@ -309,11 +309,11 @@ polytope.insert(support, minIndex) inc iterCount - result = (normal: minNormal, penetrationDepth: minDistance + 0.001'f32) + result = (normal: newVec3f(minNormal.x, minNormal.y), penetrationDepth: minDistance + 0.001'f32) -func intersects*(a, b: Collider): bool = +func intersects*(a, b: Collider, as2D=false): bool = var - support = supportPoint(a, b, newVec3f(0.8153, -0.4239, 0.5786)) # just random initial vector + support = supportPoint(a, b, newVec3f(0.8153, -0.4239, if as2D: 0.0 else: 0.5786)) # just random initial vector simplex = newSeq[Vec3f]() direction = -support n = 0 @@ -323,16 +323,16 @@ if support.dot(direction) <= 0: return false simplex.insert(support, 0) - if nextSimplex(simplex, direction): + if nextSimplex(simplex, direction, twoDimensional=as2D): return true # prevent numeric instability if direction == newVec3f(0, 0, 0): direction[0] = 0.0001 inc n -func collision*(a, b: Collider): tuple[hasCollision: bool, normal: Vec3f, penetrationDepth: float32] = +func collision*(a, b: Collider, as2D=false): tuple[hasCollision: bool, normal: Vec3f, penetrationDepth: float32] = var - support = supportPoint(a, b, newVec3f(0.8153, -0.4239, 0.5786)) # just random initial vector + support = supportPoint(a, b, newVec3f(0.8153, -0.4239, if as2D: 0.0 else: 0.5786)) # just random initial vector simplex = newSeq[Vec3f]() direction = -support n = 0 @@ -342,28 +342,8 @@ if support.dot(direction) <= 0: return result simplex.insert(support, 0) - if nextSimplex(simplex, direction): - let (normal, depth) = collisionPoint3D(simplex, a, b) - return (true, normal, depth) - # prevent numeric instability - if direction == newVec3f(0, 0, 0): - direction[0] = 0.0001 - inc n - -func collision2D*(a, b: Collider): tuple[hasCollision: bool, normal: Vec2f, penetrationDepth: float32] = - var - support = supportPoint(a, b, newVec3f(0.8153, -0.4239, 0)) # just random initial vector - simplex = newSeq[Vec3f]() - direction = -support - n = 0 - simplex.insert(support, 0) - while n < MAX_COLLISON_DETECTION_ITERATIONS: - support = supportPoint(a, b, direction) - if support.dot(direction) <= 0: - return result - simplex.insert(support, 0) - if nextSimplex(simplex, direction, twoDimensional=true): - let (normal, depth) = collisionPoint2D(simplex, a, b) + if nextSimplex(simplex, direction, twoDimensional=as2D): + let (normal, depth) = if as2D: collisionPoint2D(simplex, a, b) else: collisionPoint3D(simplex, a, b) return (true, normal, depth) # prevent numeric instability if direction == newVec3f(0, 0, 0): diff -r ad009d1c747f -r 995e273157cd src/semicongine/core/vector.nim --- a/src/semicongine/core/vector.nim Fri Sep 29 19:30:07 2023 +0700 +++ b/src/semicongine/core/vector.nim Sun Oct 01 20:53:35 2023 +0700 @@ -321,3 +321,6 @@ converter Vec2VkExtent*(vec: TVec2[uint32]): VkExtent2D = VkExtent2D(width: vec[0], height: vec[1]) converter Vec3VkExtent*(vec: TVec2[uint32]): VkExtent3D = VkExtent3D(width: vec[0], height: vec[1], depth: vec[2]) + +func angleBetween*(a, b: Vec3f): float32 = + arccos(a.dot(b) / (a.length * b.length)) diff -r ad009d1c747f -r 995e273157cd src/semicongine/engine.nim --- a/src/semicongine/engine.nim Fri Sep 29 19:30:07 2023 +0700 +++ b/src/semicongine/engine.nim Sun Oct 01 20:53:35 2023 +0700 @@ -103,12 +103,12 @@ ) startMixerThread() -proc initRenderer*(engine: var Engine, shaders: openArray[(string, ShaderConfiguration)], clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32])) = +proc initRenderer*(engine: var Engine, shaders: openArray[(string, ShaderConfiguration)], clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32]), backFaceCulling=true) = assert not engine.renderer.isSome var allShaders = @shaders allShaders.add (TEXT_MATERIAL, TEXT_SHADER) - engine.renderer = some(engine.device.initRenderer(shaders=allShaders.toTable, clearColor=clearColor)) + engine.renderer = some(engine.device.initRenderer(shaders=allShaders, clearColor=clearColor, backFaceCulling=backFaceCulling)) proc initRenderer*(engine: var Engine, clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32])) = engine.initRenderer(@[], clearColor) diff -r ad009d1c747f -r 995e273157cd src/semicongine/mesh.nim --- a/src/semicongine/mesh.nim Fri Sep 29 19:30:07 2023 +0700 +++ b/src/semicongine/mesh.nim Sun Oct 01 20:53:35 2023 +0700 @@ -10,6 +10,8 @@ import ./core import ./collision +var instanceCounter = 0 + type MeshIndexType* = enum None @@ -17,12 +19,13 @@ 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]] + of Tiny: tinyIndices*: seq[array[3, uint8]] + of Small: smallIndices*: seq[array[3, uint16]] + of Big: bigIndices*: seq[array[3, uint32]] materials*: seq[Material] transform*: Mat4 = Unit4 instanceTransforms*: seq[Mat4] @@ -57,9 +60,9 @@ func `$`*(mesh: MeshObject): string = if mesh.indexType == None: - &"Mesh(vertexCount: {mesh.vertexCount}, instanceCount: {mesh.instanceCount}, vertexData: {mesh.vertexData.keys().toSeq()}, instanceData: {mesh.instanceData.keys().toSeq()}, indexType: {mesh.indexType})" + &"Mesh('{mesh.name}', vertexCount: {mesh.vertexCount}, instanceCount: {mesh.instanceCount}, vertexData: {mesh.vertexData.keys().toSeq()}, instanceData: {mesh.instanceData.keys().toSeq()}, indexType: {mesh.indexType}, materials: {mesh.materials})" else: - &"Mesh(vertexCount: {mesh.vertexCount}, indexCount: {mesh.indicesCount}, instanceCount: {mesh.instanceCount}, vertexData: {mesh.vertexData.keys().toSeq()}, instanceData: {mesh.instanceData.keys().toSeq()}, indexType: {mesh.indexType})" + &"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}, materials: {mesh.materials})" func `$`*(mesh: Mesh): string = $mesh[] @@ -108,6 +111,7 @@ mesh.vertexData[attribute] = newDataList(thetype=datatype) mesh.vertexData[attribute].setLen(mesh.vertexCount) + 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] = newDataList(thetype=getDataType[T]()) @@ -131,9 +135,14 @@ instanceTransforms: openArray[Mat4]=[Unit4F32], material: Material=DEFAULT_MATERIAL, 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 @@ -145,6 +154,7 @@ indexType = Small result = Mesh( + name: theName, indexType: indexType, vertexCount: positions.len, instanceTransforms: @instanceTransforms, @@ -180,6 +190,7 @@ transform: Mat4=Unit4F32, instanceTransforms: openArray[Mat4]=[Unit4F32], material: Material=DEFAULT_MATERIAL, + name: string="", ): Mesh = newMesh( positions=positions, @@ -189,6 +200,7 @@ transform=transform, instanceTransforms=instanceTransforms, material=material, + name=name, ) func attributeSize*(mesh: MeshObject, attribute: string): int = @@ -319,6 +331,14 @@ if not mesh.dirtyAttributes.contains(attribute): mesh.dirtyAttributes.add attribute +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") @@ -366,6 +386,9 @@ for p in mesh[positionAttribute, Vec3f][]: result.add mesh.transform * p +func getCollider*(mesh: MeshObject, positionAttribute="position"): Collider = + return mesh.getCollisionPoints(positionAttribute).calculateCollider(Points) + proc asNonIndexedMesh*(mesh: MeshObject): MeshObject = if mesh.indexType == None: return mesh @@ -418,8 +441,10 @@ instanceTransforms: @[Unit4F32], indexType: Small, smallIndices: @[[0'u16, 1'u16, 2'u16], [2'u16, 3'u16, 0'u16]], - materials: @[DEFAULT_MATERIAL] + materials: @[DEFAULT_MATERIAL], + name: &"rect-{instanceCounter}", ) + inc instanceCounter let half_w = width / 2 @@ -432,7 +457,13 @@ 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], materials: @[DEFAULT_MATERIAL]) + result = Mesh( + vertexCount: 3, + instanceTransforms: @[Unit4F32], + materials: @[DEFAULT_MATERIAL], + name: &"tri-{instanceCounter}", + ) + inc instanceCounter let half_w = width / 2 half_h = height / 2 @@ -443,7 +474,14 @@ 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, materials: @[DEFAULT_MATERIAL]) + result = Mesh( + vertexCount: 3 + nSegments, + instanceTransforms: @[Unit4F32], + indexType: Small, + materials: @[DEFAULT_MATERIAL], + name: &"circle-{instanceCounter}", + ) + inc instanceCounter let half_w = width / 2 diff -r ad009d1c747f -r 995e273157cd src/semicongine/renderer.nim --- a/src/semicongine/renderer.nim Fri Sep 29 19:30:07 2023 +0700 +++ b/src/semicongine/renderer.nim Sun Oct 01 20:53:35 2023 +0700 @@ -43,11 +43,11 @@ scenedata: Table[Scene, SceneData] emptyTexture: VulkanTexture -proc initRenderer*(device: Device, shaders: Table[string, ShaderConfiguration], clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32])): Renderer = +proc initRenderer*(device: Device, shaders: openArray[(string, ShaderConfiguration)], clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32]), backFaceCulling=true): Renderer = assert device.vk.valid result.device = device - result.renderPass = device.simpleForwardRenderPass(shaders, clearColor=clearColor) + result.renderPass = device.simpleForwardRenderPass(shaders, clearColor=clearColor, backFaceCulling=backFaceCulling) result.surfaceFormat = device.physicalDevice.getSurfaceFormats().filterSurfaceFormat() # use last renderpass as output for swapchain let swapchain = device.createSwapchain(result.renderPass.vk, result.surfaceFormat, device.firstGraphicsQueue().get().family) @@ -60,7 +60,7 @@ func inputs(renderer: Renderer, scene: Scene): seq[ShaderAttribute] = var found: Table[string, ShaderAttribute] for i in 0 ..< renderer.renderPass.subpasses.len: - for materialName, pipeline in renderer.renderPass.subpasses[i].pipelines.pairs: + for (materialName, pipeline) in renderer.renderPass.subpasses[i].pipelines: if scene.usesMaterial(materialName): for input in pipeline.inputs: if found.contains(input.name): @@ -74,7 +74,7 @@ func samplers(renderer: Renderer, scene: Scene): seq[ShaderAttribute] = for i in 0 ..< renderer.renderPass.subpasses.len: - for materialName, pipeline in renderer.renderPass.subpasses[i].pipelines.pairs: + for (materialName, pipeline) in renderer.renderPass.subpasses[i].pipelines: if scene.usesMaterial(materialName): result.add pipeline.samplers @@ -131,7 +131,7 @@ var foundRenderableObject = false var shaderTypes: seq[string] for i in 0 ..< renderer.renderPass.subpasses.len: - for materialName, pipeline in renderer.renderPass.subpasses[i].pipelines.pairs: + for (materialName, pipeline) in renderer.renderPass.subpasses[i].pipelines: shaderTypes.add materialName for mesh in scene.meshes: if mesh.materials.anyIt(it.name == materialName): @@ -252,7 +252,7 @@ # fill offsets per pipeline (as sequence corresponds to shader input binding) var offsets: Table[VkPipeline, seq[(string, MemoryPerformanceHint, int)]] for subpass_i in 0 ..< renderer.renderPass.subpasses.len: - for materialName, pipeline in renderer.renderPass.subpasses[subpass_i].pipelines.pairs: + for (materialName, pipeline) in renderer.renderPass.subpasses[subpass_i].pipelines: if scene.usesMaterial(materialName): offsets[pipeline.vk] = newSeq[(string, MemoryPerformanceHint, int)]() for attribute in pipeline.inputs: @@ -284,7 +284,7 @@ # setup uniforms and samplers for subpass_i in 0 ..< renderer.renderPass.subpasses.len: - for materialName, pipeline in renderer.renderPass.subpasses[subpass_i].pipelines.pairs: + for (materialName, pipeline) in renderer.renderPass.subpasses[subpass_i].pipelines: if scene.usesMaterial(materialName): var uniformBufferSize = 0 for uniform in pipeline.uniforms: @@ -359,7 +359,7 @@ # loop over all used shaders/pipelines for i in 0 ..< renderer.renderPass.subpasses.len: - for materialName, pipeline in renderer.renderPass.subpasses[i].pipelines.pairs: + for (materialName, pipeline) in renderer.renderPass.subpasses[i].pipelines: if ( scene.usesMaterial(materialName) and renderer.scenedata[scene].uniformBuffers.hasKey(pipeline.vk) and @@ -425,7 +425,7 @@ debug " Index buffer: ", renderer.scenedata[scene].indexBuffer for i in 0 ..< renderer.renderPass.subpasses.len: - for materialName, pipeline in renderer.renderPass.subpasses[i].pipelines.pairs: + for (materialName, pipeline) in renderer.renderPass.subpasses[i].pipelines: if scene.usesMaterial(materialName): debug &"Start pipeline for '{materialName}'" commandBuffer.vkCmdBindPipeline(renderer.renderPass.subpasses[i].pipelineBindPoint, pipeline.vk) diff -r ad009d1c747f -r 995e273157cd src/semicongine/resources.nim --- a/src/semicongine/resources.nim Fri Sep 29 19:30:07 2023 +0700 +++ b/src/semicongine/resources.nim Sun Oct 01 20:53:35 2023 +0700 @@ -136,6 +136,9 @@ proc loadMeshes*(path: string): seq[MeshTree] = loadResource_intern(path).readglTF() +proc loadFirstMesh*(path: string): Mesh = + loadResource_intern(path).readglTF()[0].toSeq[0] + proc modList*(): seq[string] = modList_intern() diff -r ad009d1c747f -r 995e273157cd src/semicongine/resources/mesh.nim --- a/src/semicongine/resources/mesh.nim Fri Sep 29 19:30:07 2023 +0700 +++ b/src/semicongine/resources/mesh.nim Sun Oct 01 20:53:35 2023 +0700 @@ -135,6 +135,7 @@ proc loadTexture(root: JsonNode, textureIndex: int, mainBuffer: seq[uint8]): Texture = let textureNode = root["textures"][textureIndex] result.image = loadImage(root, textureNode["source"].getInt(), mainBuffer) + result.name = root["images"][textureNode["source"].getInt()]["name"].getStr() if textureNode.hasKey("sampler"): let sampler = root["samplers"][textureNode["sampler"].getInt()] @@ -271,7 +272,11 @@ else: indexType = Big - result = Mesh(instanceTransforms: @[Unit4F32], indexType: indexType) + result = Mesh( + instanceTransforms: @[Unit4F32], + indexType: indexType, + name: meshNode["name"].getStr() + ) # check we have the same attributes for all primitives let attributes = meshNode["primitives"][0]["attributes"].keys.toSeq @@ -286,6 +291,7 @@ # add all mesh data for primitive in meshNode["primitives"]: result.addPrimitive(root, primitive, mainBuffer) + transform[Vec3f](result[], "position", scale(1, -1, 1)) var maxMaterialIndex = 0 for material in result[].materials: @@ -341,7 +347,6 @@ result = MeshTree() for nodeId in scenenode["nodes"]: result.children.add loadNode(root, root["nodes"][nodeId.getInt()], mainBuffer) - result.transform = scale(1, -1, 1) result.updateTransforms() diff -r ad009d1c747f -r 995e273157cd src/semicongine/scene.nim --- a/src/semicongine/scene.nim Fri Sep 29 19:30:07 2023 +0700 +++ b/src/semicongine/scene.nim Sun Oct 01 20:53:35 2023 +0700 @@ -25,6 +25,12 @@ assert not mesh.isNil, "Cannot add a mesh that is 'nil'" scene.meshes.add mesh +proc add*(scene: var Scene, meshes: seq[Mesh]) = + assert not scene.loaded, &"Scene {scene.name} has already been loaded, cannot add meshes" + for mesh in meshes: + assert not mesh.isNil, "Cannot add a mesh that is 'nil'" + scene.meshes.add meshes + # generic way to add objects that have a mesh-attribute proc add*[T](scene: var Scene, obj: T) = assert not scene.loaded, &"Scene {scene.name} has already been loaded, cannot add meshes" diff -r ad009d1c747f -r 995e273157cd src/semicongine/vulkan/pipeline.nim --- a/src/semicongine/vulkan/pipeline.nim Fri Sep 29 19:30:07 2023 +0700 +++ b/src/semicongine/vulkan/pipeline.nim Sun Oct 01 20:53:35 2023 +0700 @@ -55,7 +55,7 @@ descriptor.imageviews.add emptyTexture.imageView descriptor.samplers.add emptyTexture.sampler -proc createPipeline*(device: Device, renderPass: VkRenderPass, shaderConfiguration: ShaderConfiguration, inFlightFrames: int, subpass = 0'u32): Pipeline = +proc createPipeline*(device: Device, renderPass: VkRenderPass, shaderConfiguration: ShaderConfiguration, inFlightFrames: int, subpass = 0'u32, backFaceCulling=true): Pipeline = assert renderPass.valid assert device.vk.valid @@ -118,7 +118,7 @@ rasterizerDiscardEnable: VK_FALSE, polygonMode: VK_POLYGON_MODE_FILL, lineWidth: 1.0, - cullMode: toBits [VK_CULL_MODE_BACK_BIT], + cullMode: if backFaceCulling: toBits [VK_CULL_MODE_BACK_BIT] else: VkCullModeFlags(0), frontFace: VK_FRONT_FACE_CLOCKWISE, depthBiasEnable: VK_FALSE, depthBiasConstantFactor: 0.0, diff -r ad009d1c747f -r 995e273157cd src/semicongine/vulkan/renderpass.nim --- a/src/semicongine/vulkan/renderpass.nim Fri Sep 29 19:30:07 2023 +0700 +++ b/src/semicongine/vulkan/renderpass.nim Sun Oct 01 20:53:35 2023 +0700 @@ -15,7 +15,7 @@ flags: VkSubpassDescriptionFlags outputs: seq[VkAttachmentReference] depthStencil: Option[VkAttachmentReference] - pipelines*: Table[string, Pipeline] + pipelines*: seq[(string, Pipeline)] RenderPass* = object vk*: VkRenderPass device*: Device @@ -62,15 +62,16 @@ proc simpleForwardRenderPass*( device: Device, - shaders: Table[string, ShaderConfiguration], + shaders: openArray[(string, ShaderConfiguration)], inFlightFrames=2, - clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32]) + clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32]), + backFaceCulling=true, ): RenderPass = # TODO: check wether materials are compatible with the assigned shaders {.warning: "Need to implement material -> shader compatability" .} assert device.vk.valid - for shaderconfig in shaders.values: + for (material, shaderconfig) in shaders: assert shaderconfig.outputs.len == 1 var attachments = @[VkAttachmentDescription( @@ -100,8 +101,8 @@ dstAccessMask: toBits [VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT], )] result = device.createRenderPass(attachments=attachments, subpasses=subpasses, dependencies=dependencies) - for material, shaderconfig in shaders.pairs: - result.subpasses[0].pipelines[material] = device.createPipeline(result.vk, shaderconfig, inFlightFrames, 0) + for (material, shaderconfig) in shaders: + result.subpasses[0].pipelines.add (material, device.createPipeline(result.vk, shaderconfig, inFlightFrames, 0, backFaceCulling=backFaceCulling)) proc beginRenderCommands*(commandBuffer: VkCommandBuffer, renderpass: RenderPass, framebuffer: Framebuffer) = @@ -160,6 +161,6 @@ renderPass.device.vk.vkDestroyRenderPass(renderPass.vk, nil) renderPass.vk.reset for i in 0 ..< renderPass.subpasses.len: - for pipeline in renderPass.subpasses[i].pipelines.mvalues: + for material, pipeline in renderPass.subpasses[i].pipelines.mitems: pipeline.destroy() renderPass.subpasses = @[]