changeset 894:2aa26c23cc60

add: initial implementation of "panels"
author Sam <sam@basx.dev>
date Sat, 10 Feb 2024 15:55:05 +0700
parents a0826956dc5c
children 10267da04fba
files semicongine/engine.nim semicongine/panel.nim tests/test_panel.nim
diffstat 3 files changed, 130 insertions(+), 19 deletions(-) [+]
line wrap: on
line diff
--- a/semicongine/engine.nim	Sat Feb 10 15:54:25 2024 +0700
+++ b/semicongine/engine.nim	Sat Feb 10 15:55:05 2024 +0700
@@ -16,6 +16,7 @@
 import ./events
 import ./audio
 import ./text
+import ./panel
 
 type
   EngineState* = enum
@@ -61,7 +62,7 @@
 
 proc initEngine*(
   applicationName: string,
-  debug=DEBUG,
+  debug = DEBUG,
   exitHandler: proc(engine: var Engine) = nil,
   resizeHandler: proc(engine: var Engine) = nil,
   eventHandler: proc(engine: var Engine, event: Event) = nil
@@ -88,9 +89,9 @@
   if defined(linux) and DEBUG:
     enabledLayers.add "VK_LAYER_MESA_overlay"
   result.instance = result.window.createInstance(
-    vulkanVersion=VK_MAKE_API_VERSION(0, 1, 3, 0),
-    instanceExtensions=instanceExtensions,
-    layers=enabledLayers,
+    vulkanVersion = VK_MAKE_API_VERSION(0, 1, 3, 0),
+    instanceExtensions = instanceExtensions,
+    layers = enabledLayers,
   )
   if debug:
     result.debugger = result.instance.createDebugMessenger()
@@ -104,22 +105,23 @@
   )
   startMixerThread()
 
-proc initRenderer*(engine: var Engine, shaders: openArray[(MaterialType, ShaderConfiguration)], clearColor=Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32]), backFaceCulling=true) =
+proc initRenderer*(engine: var Engine, shaders: openArray[(MaterialType, 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_TYPE, TEXT_SHADER)
-  engine.renderer = some(engine.device.initRenderer(shaders=allShaders, clearColor=clearColor, backFaceCulling=backFaceCulling))
+  allShaders.add (PANEL_MATERIAL_TYPE, PANEL_SHADER)
+  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])) =
+proc initRenderer*(engine: var Engine, clearColor = Vec4f([0.8'f32, 0.8'f32, 0.8'f32, 1'f32])) =
   engine.initRenderer(@[], clearColor)
 
 proc loadScene*(engine: var Engine, scene: var Scene) =
   assert engine.renderer.isSome
   assert not scene.loaded
   engine.renderer.get.setupDrawableBuffers(scene)
-  engine.renderer.get.updateMeshData(scene, forceAll=true)
-  engine.renderer.get.updateUniformData(scene, forceAll=true)
+  engine.renderer.get.updateMeshData(scene, forceAll = true)
+  engine.renderer.get.updateUniformData(scene, forceAll = true)
 
 proc unloadScene*(engine: var Engine, scene: Scene) =
   engine.renderer.get.destroy(scene)
--- a/semicongine/panel.nim	Sat Feb 10 15:54:25 2024 +0700
+++ b/semicongine/panel.nim	Sat Feb 10 15:55:05 2024 +0700
@@ -1,11 +1,43 @@
 import std/strformat
+import std/tables
 
 import ./core
 import ./mesh
+import ./material
+import ./vulkan/shader
 
 const
+  # font shader
   SHADER_ATTRIB_PREFIX = "semicon_panel_"
   MAX_PANEL_MATERIALS = 10
+  POSITION_ATTRIB = SHADER_ATTRIB_PREFIX & "position"
+  UV_ATTRIB = SHADER_ATTRIB_PREFIX & "uv"
+  PANEL_MATERIAL_TYPE* = MaterialType(
+    name: "default-panel-material-type",
+    vertexAttributes: {TRANSFORM_ATTRIB: Mat4F32, POSITION_ATTRIB: Vec3F32, UV_ATTRIB: Vec2F32}.toTable,
+    attributes: {"panelTexture": TextureType, "color": Vec4F32}.toTable,
+  )
+  PANEL_SHADER* = createShaderConfiguration(
+    inputs = [
+      attr[Mat4](TRANSFORM_ATTRIB, memoryPerformanceHint = PreferFastWrite, perInstance = true),
+      attr[Vec3f](POSITION_ATTRIB, memoryPerformanceHint = PreferFastWrite),
+      attr[Vec2f](UV_ATTRIB, memoryPerformanceHint = PreferFastWrite),
+      attr[uint16](MATERIALINDEX_ATTRIBUTE, memoryPerformanceHint = PreferFastRead, perInstance = true),
+    ],
+    intermediates = [
+      attr[Vec2f]("uvFrag"),
+      attr[uint16]("materialIndexOut", noInterpolation = true)
+    ],
+    outputs = [attr[Vec4f]("color")],
+    uniforms = [attr[Vec4f]("color", arrayCount = MAX_PANEL_MATERIALS)],
+    samplers = [attr[Texture]("panelTexture", arrayCount = MAX_PANEL_MATERIALS)],
+    vertexCode = &"""
+  gl_Position = vec4({POSITION_ATTRIB}, 1.0) * {TRANSFORM_ATTRIB};
+  uvFrag = {UV_ATTRIB};
+  materialIndexOut = {MATERIALINDEX_ATTRIBUTE};
+  """,
+    fragmentCode = &"""color = Uniforms.color[materialIndexOut] * texture(panelTexture[materialIndexOut], uvFrag);"""
+  )
 
 var instanceCounter = 0
 
@@ -13,7 +45,6 @@
   Panel* = object
     position: Vec2f
     size: Vec2f
-    color*: Vec4f
 
     texture: Texture
     horizontalAlignment: HorizontalAlignment = Center
@@ -22,19 +53,63 @@
     dirty: bool
     mesh: Mesh
 
+proc `$`*(panel: Panel): string =
+  &"Panel {panel.position} (size {panel.size})"
+
+proc refresh*(panel: var Panel) =
+  if not panel.dirty:
+    return
+
+  var
+    offsetX = case panel.horizontalAlignment
+      of Left: panel.size.x / 2
+      of Center: 0
+      of Right: -panel.size.x / 2
+    offsetY = case panel.verticalAlignment
+      of Top: panel.size.y / 2
+      of Center: 0
+      of Bottom: -panel.size.y / 2
+
+  panel.mesh[POSITION_ATTRIB, 0] = newVec3f(
+    panel.position.x - panel.size.x / 2 + offsetX,
+    (panel.position.y - panel.size.y / 2 + offsetY) * panel.aspect_ratio
+  )
+  panel.mesh[POSITION_ATTRIB, 1] = newVec3f(
+    panel.position.x + panel.size.x / 2 + offsetX,
+    (panel.position.y - panel.size.y / 2 + offsetY) * panel.aspect_ratio
+  )
+  panel.mesh[POSITION_ATTRIB, 2] = newVec3f(
+    panel.position.x + panel.size.x / 2 + offsetX,
+    (panel.position.y + panel.size.y / 2 + offsetY) * panel.aspect_ratio
+  )
+  panel.mesh[POSITION_ATTRIB, 3] = newVec3f(
+    panel.position.x - panel.size.x / 2 + offsetX,
+    (panel.position.y + panel.size.y / 2 + offsetY) * panel.aspect_ratio
+  )
+
+  panel.dirty = false
+
 proc initPanel*(position = newVec2f(), size = newVec2f(), color = newVec4f(1, 1, 1, 1), texture = EMPTY_TEXTURE, horizontalAlignment = HorizontalAlignment.Center, verticalAlignment = VerticalAlignment.Center): Panel =
 
-  result = Panel(position: position, size: size, color: color, texture: texture, horizontalAlignment: horizontalAlignment, verticalAlignment: verticalAlignment, aspect_ratio: 1)
+  result = Panel(position: position, size: size, texture: texture, horizontalAlignment: horizontalAlignment, verticalAlignment: verticalAlignment, aspect_ratio: 1)
 
-  inc instanceCounter
-  var
-    positions = newSeq[Vec3f](4)
+  result.mesh = newMesh(
+    positions = newSeq[Vec3f](4),
     indices = @[
       [uint16(0), uint16(1), uint16(2)],
       [uint16(2), uint16(3), uint16(0)],
-    ]
-    uvs = newSeq[Vec2f](4)
-  result.mesh = newMesh(positions = positions, indices = indices, uvs = uvs, name = &"panel-{instanceCounter}")
+    ],
+    uvs = @[newVec2f(0, 1), newVec2f(1, 1), newVec2f(1, 0), newVec2f(0, 0)], name = &"panel-{instanceCounter}"
+  )
+  result.mesh[].renameAttribute("position", POSITION_ATTRIB)
+  result.mesh[].renameAttribute("uv", UV_ATTRIB)
+  result.mesh.material = initMaterialData(
+    theType = PANEL_MATERIAL_TYPE,
+    name = "Panel material",
+    attributes = {"panelTexture": initDataList(@[texture]), "color": initDataList(@[color])},
+  )
+  inc instanceCounter
+  result.refresh()
 
 proc position*(panel: Panel): Vec2f =
   panel.position
@@ -43,6 +118,12 @@
     panel.position = value
     panel.dirty = true
 
+proc color*(panel: Panel): Vec4f =
+  panel.mesh.material["color", 0, Vec4f]
+proc `color=`*(panel: var Panel, value: Vec4f) =
+  if value != panel.color:
+    panel.mesh.material["color", 0] = value
+
 proc size*(panel: Panel): Vec2f =
   panel.size
 proc `size=`*(panel: var Panel, value: Vec2f) =
--- a/tests/test_panel.nim	Sat Feb 10 15:54:25 2024 +0700
+++ b/tests/test_panel.nim	Sat Feb 10 15:55:05 2024 +0700
@@ -9,16 +9,44 @@
   engine.initRenderer([])
 
   # build scene
-  var scene = Scene(name: "main")
-  var panel = Panel(position: newVec2f(0, 0), size: newVec2f(0.1, 0.1))
+  var
+    font = loadFont("DejaVuSans.ttf", lineHeightPixels = 210'f32)
+    scene = Scene(name: "main")
+    origin = initPanel(size = newVec2f(0.01, 0.01), color = newVec4f(0, 0, 0, 1))
+    panel = initPanel(size = newVec2f(0.2, 0.2), color = newVec4f(1, 0, 0, 1))
+    help_text = font.initText("""Controls
+
+Horizontal alignment:
+  F1: Left
+  F2: Center
+  F3: Right
+Vertical alignment:
+  F4: Top
+  F5: Center
+  F6: Bottom""", scale = 0.0002, position = newVec2f(-0.9, -0.9), horizontalAlignment = Left, verticalAlignment = Top)
 
   scene.add panel
+  scene.add help_text
+  scene.add origin
   engine.loadScene(scene)
 
   while engine.updateInputs() == Running and not engine.keyIsDown(Escape):
     if engine.windowWasResized():
       var winSize = engine.getWindow().size
       panel.aspect_ratio = winSize[0] / winSize[1]
+      origin.aspect_ratio = winSize[0] / winSize[1]
+      help_text.aspect_ratio = winSize[0] / winSize[1]
+
+    if engine.keyWasPressed(F1): panel.horizontalAlignment = Left
+    elif engine.keyWasPressed(F2): panel.horizontalAlignment = Center
+    elif engine.keyWasPressed(F3): panel.horizontalAlignment = Right
+    elif engine.keyWasPressed(F4): panel.verticalAlignment = Top
+    elif engine.keyWasPressed(F5): panel.verticalAlignment = Center
+    elif engine.keyWasPressed(F6): panel.verticalAlignment = Bottom
+
+    panel.refresh()
+    origin.refresh()
+    help_text.refresh()
 
     engine.renderScene(scene)
   engine.destroy()