changeset 1226:c8e3037aca66 compiletime-tests

add: contrib stuff
author sam <sam@basx.dev>
date Wed, 17 Jul 2024 23:41:51 +0700
parents 27cd1c21290e
children 4d97cfc4888b 66956794ce6d
files semiconginev2.nim semiconginev2/audio.nim semiconginev2/contrib/algorithms/collision.nim semiconginev2/contrib/algorithms/noise.nim semiconginev2/contrib/algorithms/texture_packing.nim semiconginev2/contrib/settings.nim semiconginev2/contrib/steam.nim semiconginev2/core/buildconfig.nim semiconginev2/old/algorithms.nim semiconginev2/old/collision.nim semiconginev2/old/noise.nim semiconginev2/old/settings.nim semiconginev2/old/steam.nim semiconginev2/rendering.nim
diffstat 14 files changed, 702 insertions(+), 700 deletions(-) [+]
line wrap: on
line diff
--- a/semiconginev2.nim	Wed Jul 17 22:20:59 2024 +0700
+++ b/semiconginev2.nim	Wed Jul 17 23:41:51 2024 +0700
@@ -13,6 +13,7 @@
 import std/os
 import std/options
 import std/parsecfg
+import std/parseutils
 import std/paths
 import std/random
 import std/sequtils
@@ -25,9 +26,12 @@
 import std/typetraits
 import std/unicode
 
+
 include ./semiconginev2/rendering/vulkan/api
 include ./semiconginev2/core
 
+setLogFilter(ENGINE_LOGLEVEL)
+
 include ./semiconginev2/resources
 
 include ./semiconginev2/events
@@ -38,4 +42,9 @@
 
 include ./semiconginev2/audio
 
-StartMixerThread()
+when not defined(NO_CONTRIB):
+  include ./semiconginev2/contrib/steam
+  include ./semiconginev2/contrib/settings
+  include ./semiconginev2/contrib/algorithms/collision
+  include ./semiconginev2/contrib/algorithms/noise
+  include ./semiconginev2/contrib/algorithms/texture_packing
--- a/semiconginev2/audio.nim	Wed Jul 17 22:20:59 2024 +0700
+++ b/semiconginev2/audio.nim	Wed Jul 17 23:41:51 2024 +0700
@@ -1,3 +1,5 @@
 include ./audio/mixer
 include ./audio/generators
 include ./audio/resources
+
+StartMixerThread()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/semiconginev2/contrib/algorithms/collision.nim	Wed Jul 17 23:41:51 2024 +0700
@@ -0,0 +1,384 @@
+const MAX_COLLISON_DETECTION_ITERATIONS = 20
+const MAX_COLLISON_POINT_CALCULATION_ITERATIONS = 20
+
+type
+  ColliderType* = enum
+    Box, Sphere, Points
+  Collider* = object
+    transform*: Mat4 = Unit4F32
+    case theType*: ColliderType
+      of Box: discard
+      of Sphere: radius*: float32
+      of Points: points*: seq[Vec3f]
+
+func between(value, b1, b2: float32): bool =
+  min(b1, b2) <= value and value <= max(b1, b2)
+
+func Contains*(collider: Collider, x: Vec3f): bool =
+  # from https://math.stackexchange.com/questions/1472049/check-if-a-point-is-inside-a-rectangular-shaped-area-3d
+  case collider.theType:
+  of Box:
+    let
+      P1 = collider.transform * NewVec3f(0, 0, 0) # origin
+      P2 = collider.transform * Z
+      P4 = collider.transform * X
+      P5 = collider.transform * Y
+      u = (P1 - P4).Cross(P1 - P5)
+      v = (P1 - P2).Cross(P1 - P5)
+      w = (P1 - P2).Cross(P1 - P4)
+      uP1 = u.Dot(P1)
+      uP2 = u.Dot(P2)
+      vP1 = v.Dot(P1)
+      vP4 = v.Dot(P4)
+      wP1 = w.Dot(P1)
+      wP5 = w.Dot(P5)
+      ux = u.Dot(x)
+      vx = v.Dot(x)
+      wx = w.Dot(x)
+    ux.between(uP1, uP2) and vx.between(vP1, vP4) and wx.between(wP1, wP5)
+  of Sphere:
+    (collider.transform * x).Length < (collider.transform * NewVec3f()).Length
+  of Points:
+    raise newException(Exception, "Points are not supported yet for 'contains'")
+
+# implementation of GJK, based on https://blog.winter.dev/2020/gjk-algorithm/
+
+# most generic implementation of findFurthestPoint
+# add other implementations of findFurthestPoint for other kind of geometry or optimization
+# (will be selected depening on type of the first parameter)
+func findFurthestPoint(points: openArray[Vec3f], direction: Vec3f): Vec3f =
+  var maxDist = low(float32)
+  for p in points:
+    let dist = direction.Dot(p)
+    if dist > maxDist:
+      maxDist = dist
+      result = p
+
+func findFurthestPoint(transform: Mat4, direction: Vec3f): Vec3f =
+  return findFurthestPoint(
+    [
+      transform * NewVec3f(0, 0, 0),
+      transform * X,
+      transform * Y,
+      transform * Z,
+      transform * (X + Y),
+      transform * (X + Z),
+      transform * (Y + Z),
+      transform * (X + Y + Z),
+    ],
+    direction
+  )
+func findFurthestPoint(collider: Collider, direction: Vec3f): Vec3f =
+  case collider.theType
+  of Sphere:
+    let directionNormalizedToSphere = ((direction / direction.Length) * collider.radius)
+    collider.transform * directionNormalizedToSphere
+  of Box:
+    findFurthestPoint(collider.transform, direction)
+  of Points:
+    findFurthestPoint(collider.points, direction)
+
+func supportPoint(a, b: Collider, direction: Vec3f): Vec3f =
+  a.findFurthestPoint(direction) - b.findFurthestPoint(-direction)
+
+func sameDirection(direction: Vec3f, ao: Vec3f): bool =
+  direction.Dot(ao) > 0
+
+func line(simplex: var seq[Vec3f], direction: var Vec3f): bool =
+  let
+    a = simplex[0]
+    b = simplex[1]
+    ab = b - a
+    ao = - a
+
+  if sameDirection(ab, ao):
+    direction = Cross(Cross(ab, ao), ab)
+  else:
+    simplex = @[a]
+    direction = ao
+
+  return false
+
+func triangle(simplex: var seq[Vec3f], direction: var Vec3f, twoDimensional = false): bool =
+  let
+    a = simplex[0]
+    b = simplex[1]
+    c = simplex[2]
+    ab = b - a
+    ac = c - a
+    ao = - a
+    abc = ab.Cross(ac)
+
+  if sameDirection(abc.Cross(ac), ao):
+    if sameDirection(ac, ao):
+      simplex = @[a, c]
+      direction = ac.Cross(ao).Cross(ac)
+    else:
+      simplex = @[a, b]
+      return line(simplex, direction)
+  else:
+    if (sameDirection(ab.Cross(abc), ao)):
+      simplex = @[a, b]
+      return line(simplex, direction)
+    else:
+      if twoDimensional:
+        return true
+      if (sameDirection(abc, ao)):
+        direction = abc
+      else:
+        simplex = @[a, c, b]
+        direction = -abc
+
+  return false
+
+func tetrahedron(simplex: var seq[Vec3f], direction: var Vec3f): bool =
+  let
+    a = simplex[0]
+    b = simplex[1]
+    c = simplex[2]
+    d = simplex[3]
+    ab = b - a
+    ac = c - a
+    ad = d - a
+    ao = - a
+    abc = ab.Cross(ac)
+    acd = ac.Cross(ad)
+    adb = ad.Cross(ab)
+
+  if sameDirection(abc, ao):
+    simplex = @[a, b, c]
+    return triangle(simplex, direction)
+  if sameDirection(acd, ao):
+    simplex = @[a, c, d]
+    return triangle(simplex, direction)
+  if sameDirection(adb, ao):
+    simplex = @[a, d, b]
+    return triangle(simplex, direction)
+
+  return true
+
+func getFaceNormals(polytope: seq[Vec3f], faces: seq[int]): (seq[Vec4f], int) =
+  var
+    normals: seq[Vec4f]
+    minTriangle = 0
+    minDistance = high(float32)
+
+  for i in countup(0, faces.len - 1, 3):
+    let
+      a = polytope[faces[i + 0]]
+      b = polytope[faces[i + 1]]
+      c = polytope[faces[i + 2]]
+
+    var normal = (b - a).Cross(c - a).Normalized()
+    var distance = normal.Dot(a)
+
+    if distance < 0:
+      normal = normal * -1'f32
+      distance = distance * -1'f32
+
+    normals.add normal.ToVec4(distance)
+
+    if distance < minDistance:
+      minTriangle = i div 3
+      minDistance = distance
+
+  return (normals, minTriangle)
+
+func addIfUniqueEdge(edges: var seq[(int, int)], faces: seq[int], a: int, b: int) =
+  let reverse = edges.find((faces[b], faces[a]))
+  if (reverse >= 0):
+    edges.delete(reverse)
+  else:
+    edges.add (faces[a], faces[b])
+
+func nextSimplex(simplex: var seq[Vec3f], direction: var Vec3f, twoDimensional = false): bool =
+  case simplex.len
+  of 2: simplex.line(direction)
+  of 3: simplex.triangle(direction, twoDimensional)
+  of 4: simplex.tetrahedron(direction)
+  else: raise newException(Exception, "Error in simplex")
+
+func collisionPoint3D(simplex: var seq[Vec3f], a, b: Collider): tuple[normal: Vec3f, penetrationDepth: float32] =
+  var
+    polytope = simplex
+    faces = @[
+      0, 1, 2,
+      0, 3, 1,
+      0, 2, 3,
+      1, 3, 2
+    ]
+    (normals, minFace) = getFaceNormals(polytope, faces)
+    minNormal: Vec3f
+    minDistance = high(float32)
+    iterCount = 0
+
+  while minDistance == high(float32) and iterCount < MAX_COLLISON_POINT_CALCULATION_ITERATIONS:
+    minNormal = normals[minFace].xyz
+    minDistance = normals[minFace].w
+    var
+      support = supportPoint(a, b, minNormal)
+      sDistance = minNormal.Dot(support)
+
+    if abs(sDistance - minDistance) > 0.001'f32:
+      minDistance = high(float32)
+      var uniqueEdges: seq[(int, int)]
+      var i = 0
+      while i < normals.len:
+        if sameDirection(normals[i], support):
+          var f = i * 3
+
+          addIfUniqueEdge(uniqueEdges, faces, f + 0, f + 1)
+          addIfUniqueEdge(uniqueEdges, faces, f + 1, f + 2)
+          addIfUniqueEdge(uniqueEdges, faces, f + 2, f + 0)
+
+          faces[f + 2] = faces.pop()
+          faces[f + 1] = faces.pop()
+          faces[f + 0] = faces.pop()
+
+          normals[i] = normals.pop()
+
+          dec i
+        inc i
+
+      var newFaces: seq[int]
+      for (edgeIndex1, edgeIndex2) in uniqueEdges:
+        newFaces.add edgeIndex1
+        newFaces.add edgeIndex2
+        newFaces.add polytope.len
+
+      polytope.add support
+
+      var (newNormals, newMinFace) = getFaceNormals(polytope, newFaces)
+      if newNormals.len == 0:
+        break
+
+      var oldMinDistance = high(float32)
+      for j in 0 ..< normals.len:
+        if normals[j].w < oldMinDistance:
+          oldMinDistance = normals[j].w
+          minFace = j
+
+      if (newNormals[newMinFace].w < oldMinDistance):
+        minFace = newMinFace + normals.len
+
+      for f in newFaces:
+        faces.add f
+      for n in newNormals:
+        normals.add n
+    inc iterCount
+
+  result = (normal: minNormal, penetrationDepth: minDistance + 0.001'f32)
+
+
+func collisionPoint2D(polytopeIn: seq[Vec3f], a, b: Collider): tuple[normal: Vec3f, penetrationDepth: float32] =
+  var
+    polytope = polytopeIn
+    minIndex = 0
+    minDistance = high(float32)
+    iterCount = 0
+    minNormal: Vec2f
+
+  while minDistance == high(float32) and iterCount < MAX_COLLISON_POINT_CALCULATION_ITERATIONS:
+    for i in 0 ..< polytope.len:
+      let
+        j = (i + 1) mod polytope.len
+        vertexI = polytope[i]
+        vertexJ = polytope[j]
+        ij = vertexJ - vertexI
+      var
+        normal = NewVec2f(ij.y, -ij.x).Normalized()
+        distance = normal.Dot(vertexI)
+
+      if (distance < 0):
+        distance *= -1'f32
+        normal = normal * -1'f32
+
+      if distance < minDistance:
+        minDistance = distance
+        minNormal = normal
+        minIndex = j
+
+    let
+      support = supportPoint(a, b, minNormal.ToVec3)
+      sDistance = minNormal.Dot(support)
+
+    if(abs(sDistance - minDistance) > 0.001):
+      minDistance = high(float32)
+      polytope.insert(support, minIndex)
+    inc iterCount
+
+  result = (normal: NewVec3f(minNormal.x, minNormal.y), penetrationDepth: minDistance + 0.001'f32)
+
+func Intersects*(a, b: Collider, as2D = false): bool =
+  var
+    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
+  simplex.insert(support, 0)
+  while n < MAX_COLLISON_DETECTION_ITERATIONS:
+    support = supportPoint(a, b, direction)
+    if support.Dot(direction) <= 0:
+      return false
+    simplex.insert(support, 0)
+    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, as2D = false): tuple[hasCollision: bool, normal: Vec3f, penetrationDepth: float32] =
+  var
+    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
+  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 = 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):
+      direction[0] = 0.0001
+    inc n
+
+func CalculateCollider*(points: openArray[Vec3f], theType: ColliderType): Collider =
+  var
+    minX = high(float32)
+    maxX = low(float32)
+    minY = high(float32)
+    maxY = low(float32)
+    minZ = high(float32)
+    maxZ = low(float32)
+    center: Vec3f
+
+  for p in points:
+    minX = min(minX, p.x)
+    maxX = max(maxX, p.x)
+    minY = min(minY, p.y)
+    maxY = max(maxY, p.y)
+    minZ = min(minZ, p.z)
+    maxZ = max(maxz, p.z)
+    center = center + p
+  center = center / float32(points.len)
+
+  let
+    scaleX = (maxX - minX)
+    scaleY = (maxY - minY)
+    scaleZ = (maxZ - minZ)
+
+  if theType == Points:
+    result = Collider(theType: Points, points: @points)
+  else:
+    result = Collider(theType: theType, transform: Translate(minX, minY, minZ) * Scale(scaleX, scaleY, scaleZ))
+
+    if theType == Sphere:
+      result.transform = Translate(center)
+      for p in points:
+        result.radius = max(result.radius, (p - center).Length)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/semiconginev2/contrib/algorithms/noise.nim	Wed Jul 17 23:41:51 2024 +0700
@@ -0,0 +1,28 @@
+proc randomGradient(pos: Vec2f, seed: int32 = 0): Vec2f =
+  let randomAngle: float32 = TAU * (float32(int(hash((pos.x, pos.y, seed)))) / float32(high(int)))
+  return NewVec2f(cos(randomAngle), sin(randomAngle))
+
+proc interpolate(a: float32, b: float32, weight: float32): float32 =
+  # with Smootherstep
+  (b - a) * ((weight * (weight * 6.0 - 15.0) + 10.0) * weight * weight * weight) + a;
+
+proc Perlin*(pos: Vec2f, seed: int32 = 0): float32 =
+  let
+    # grid coordinates around target point
+    topleft = NewVec2f(trunc(pos.x), trunc(pos.y))
+    topright = topleft + NewVec2f(1, 0)
+    bottomleft = topleft + NewVec2f(0, 1)
+    bottomright = topleft + NewVec2f(1, 1)
+    # products for weights
+    topleft_dot = topleft.randomGradient(seed).Dot((pos - topleft))
+    topright_dot = topright.randomGradient(seed).Dot((pos - topright))
+    bottomleft_dot = bottomleft.randomGradient(seed).Dot((pos - bottomleft))
+    bottomright_dot = bottomright.randomGradient(seed).Dot((pos - bottomright))
+    xinterpol = pos.x - topleft.x
+    yinterpol = pos.y - topleft.y
+
+  return interpolate(
+    interpolate(topleft_dot, bottomleft_dot, yinterpol),
+    interpolate(topright_dot, bottomright_dot, yinterpol),
+    xinterpol
+  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/semiconginev2/contrib/algorithms/texture_packing.nim	Wed Jul 17 23:41:51 2024 +0700
@@ -0,0 +1,77 @@
+type
+  Rect = tuple
+    i: int
+    x, y, w, h: uint32
+
+func between(a1, a2, b: uint32): bool =
+  a1 <= b and b <= a2
+
+func overlap(a1, a2, b1, b2: uint32): bool =
+  return between(a1, a2, b1) or
+         between(a1, a2, b2) or
+         between(b1, b2, a1) or
+         between(b1, b2, a2)
+
+# FYI: also serves as "overlaps"
+func advanceIfOverlap(fix, newRect: Rect): (bool, uint32) =
+  let overlapping = overlap(fix.x, fix.x + fix.w - 1, newRect.x, newRect.x + newRect.w - 1) and
+                    overlap(fix.y, fix.y + fix.h - 1, newRect.y, newRect.y + newRect.h - 1)
+  if overlapping: (true, fix.x + fix.w) # next free x coordinate to the right
+  else: (false, newRect.x) # current position is fine
+
+proc find_insertion_position(alreadyPlaced: seq[Rect], area: tuple[i: int, w, h: uint32], maxDim: uint32): (bool, Rect) =
+  var newRect = (i: area.i, x: 0'u32, y: 0'u32, w: area.w, h: area.h)
+
+  while newRect.y + newRect.h <= maxDim:
+    var hasOverlap = false
+    var advanceX: uint32
+
+    for placed in alreadyPlaced:
+      (hasOverlap, advanceX) = placed.advanceIfOverlap(newRect)
+      if hasOverlap: # rects were overlapping and newRect needs to be shifted to the right
+        newRect.x = advanceX
+        break
+
+    if not hasOverlap: # found a collision free position
+      return (true, newRect)
+
+    if newRect.x + newRect.w >= maxDim: # move to next scanline
+      newRect.x = 0
+      newRect.y += 1
+
+  return (false, newRect)
+
+
+proc Pack*[T](textures: seq[Texture[T]]): tuple[atlas: Texture[T], coords: seq[tuple[x: uint32, y: uint32]]] =
+  const MAX_ATLAS_SIZE = 4096'u32
+  var areas: seq[tuple[i: int, w, h: uint32]]
+
+  for i in 0 ..< textures.len:
+    areas.add (i, textures[i].width, textures[i].height)
+
+  let areasBySize = areas.sortedByIt(-(it[1] * it[2]).int64)
+  var assignedAreas: seq[Rect]
+  var maxDim = 128'u32
+
+  for area in areasBySize:
+    var pos = find_insertion_position(assignedAreas, area, maxDim)
+    while not pos[0]: # this should actually never loop more than once, but weird things happen ¯\_(ツ)_/¯
+      maxDim = maxDim * 2
+      assert maxDim <= MAX_ATLAS_SIZE, &"Atlas gets bigger than {MAX_ATLAS_SIZE}, cannot pack textures"
+      pos = find_insertion_position(assignedAreas, area, maxDim)
+
+    assignedAreas.add pos[1]
+
+  # check there are overlaps
+  for i in 0 ..< assignedAreas.len - 1:
+    for j in i + 1 ..< assignedAreas.len:
+      assert not assignedAreas[i].advanceIfOverlap(assignedAreas[j])[0], &"{assignedAreas[i]} and {assignedAreas[j]} overlap!"
+
+  result.atlas = Texture[T](width: maxDim, height: maxDim, data: newSeq[T](maxDim * maxDim))
+  result.coords.setLen(textures.len)
+  for rect in assignedAreas:
+    for y in 0 ..< rect.h:
+      for x in 0 ..< rect.w:
+        assert result.atlas[rect.x + x, rect.y + y] == 0, "Atlas texture packing encountered an overlap error"
+        result.atlas[rect.x + x, rect.y + y] = textures[rect.i][x, y]
+        result.coords[rect.i] = (x: rect.x, y: rect.y)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/semiconginev2/contrib/settings.nim	Wed Jul 17 23:41:51 2024 +0700
@@ -0,0 +1,100 @@
+const CONFIGROOT: string = "."
+const CONFIGEXTENSION: string = "ini"
+# by default enable hot-reload of runtime-configuration only in debug builds
+const CONFIGHOTRELOAD: bool = not defined(release)
+# milliseconds to wait between checks for settings hotreload
+const CONFIGHOTRELOADINTERVAL: int = 1000
+
+when CONFIGHOTRELOAD:
+  var
+    configUpdates: Channel[(string, string)]
+  configUpdates.open()
+
+# runtime configuration
+# =====================
+# namespace is the path from the CONFIGROOT to the according settings file without the file extension
+# a settings file must always have the extension CONFIGEXTENSION
+# a fully qualified settings identifier can be in the form {namespace}.{section}.{key}
+# {key} and {section} may not contain dots
+
+# a "namespace" is the path from the settings root to an *.CONFIGEXTENSION file, without the file extension
+# settings is a namespace <-> settings mapping
+var allsettings: Table[string, Config]
+
+proc configRoot(): string =
+  joinPath(absolutePath(getAppDir()), CONFIGROOT)
+
+proc getFile(namespace: string): string =
+  joinPath(configRoot(), namespace & "." & CONFIGEXTENSION)
+
+iterator walkConfigNamespaces(): string =
+  for file in walkDirRec(dir = configRoot(), relative = true, checkDir = true):
+    if file.endsWith("." & CONFIGEXTENSION):
+      yield file[0 ..< ^(CONFIGEXTENSION.len + 1)]
+
+proc loadAllConfig(): Table[string, Config] =
+  for ns in walkConfigNamespaces():
+    result[ns] = ns.getFile().loadConfig()
+
+proc ReloadSettings*() =
+  allsettings = loadAllConfig()
+
+proc configStr(key, section, namespace: string): string =
+  when CONFIGHOTRELOAD:
+    while configUpdates.peek() > 0:
+      let (updatedNamespace, updatedConfig) = configUpdates.recv()
+      allsettings[updatedNamespace] = loadConfig(newStringStream(updatedConfig))
+  if not allsettings.hasKey(namespace):
+    raise newException(Exception, &"Settings {namespace}.{section}.{key} was not found")
+  allsettings[namespace].getSectionValue(section, key)
+
+proc Setting*[T: int|float|string](key, section, namespace: string): T =
+  when T is int:
+    let value = configStr(key, section, namespace)
+    if parseInt(value, result) == 0:
+      raise newException(Exception, &"Unable to parse int from settings {namespace}.{section}.{key}: {value}")
+  elif T is float:
+    let value = configStr(key, section, namespace)
+    if parseFloat(value, result) == 0:
+      raise newException(Exception, &"Unable to parse float from settings {namespace}.{section}.{key}: {value}")
+  else:
+    result = configStr(key, section, namespace)
+
+proc Setting*[T: int|float|string](identifier: string): T =
+  # identifier can be in the form:
+  # {namespace}.{key}
+  # {namespace}.{section}.{key}
+  let parts = identifier.rsplit(".")
+  if parts.len == 1:
+    raise newException(Exception, &"Setting with name {identifier} has no namespace")
+  if parts.len == 2: result = Setting[T](parts[1], "", parts[0])
+  else: result = Setting[T](parts[^1], parts[^2], joinPath(parts[0 .. ^3]))
+
+proc HadConfigUpdate*(): bool =
+  when CONFIGHOTRELOAD == true:
+    result = configUpdates.peek() > 0
+
+allsettings = loadAllConfig()
+
+when CONFIGHOTRELOAD == true:
+  import std/times
+
+  proc configFileWatchdog() {.thread.} =
+    var configModTimes: Table[string, times.Time]
+    while true:
+      for namespace in walkConfigNamespaces():
+        if not (namespace in configModTimes):
+          configModTimes[namespace] = times.Time()
+        let lastMod = namespace.getFile().getLastModificationTime()
+        if lastMod > configModTimes[namespace]:
+          configModTimes[namespace] = lastMod
+          let configStr = newFileStream(namespace.getFile()).readAll()
+          configUpdates.send((namespace, configStr))
+      sleep CONFIGHOTRELOADINTERVAL
+  var thethread: Thread[void]
+  createThread(thethread, configFileWatchdog)
+
+if not defined(release):
+  setLogFilter(lvlAll)
+else:
+  setLogFilter(lvlWarn)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/semiconginev2/contrib/steam.nim	Wed Jul 17 23:41:51 2024 +0700
@@ -0,0 +1,70 @@
+var
+  steam_api: LibHandle
+  steam_is_loaded = false
+
+when defined(linux):
+  proc dlerror(): cstring {.stdcall, importc.}
+  steam_api = "libsteam_api.so".loadLib()
+elif defined(windows):
+  steam_api = "steam_api".loadLib()
+  # TODO: maybe should get some error reporting on windows too?
+
+
+# required to store reference, when calling certain APIs
+type
+  SteamUserStatsRef = ptr object
+var userStats: SteamUserStatsRef
+
+# load function pointers for steam API
+var
+  Shutdown*: proc() {.stdcall.}
+  Init: proc(msg: ptr array[1024, char]): cint {.stdcall.}
+  SteamUserStats: proc(): SteamUserStatsRef {.stdcall.}
+  RequestCurrentStats: proc(self: SteamUserStatsRef): bool {.stdcall.} # needs to be called before the achievment-stuff
+  ClearAchievement: proc(self: SteamUserStatsRef, pchName: cstring): bool {.stdcall.}
+  SetAchievement: proc(self: SteamUserStatsRef, pchName: cstring): bool {.stdcall.}
+  StoreStats: proc(self: SteamUserStatsRef): bool {.stdcall.}          # needs to be called in order for achievments to be saved
+                                                                       # dynlib-helper function
+proc loadFunc[T](nimFunc: var T, dllFuncName: string) =
+  nimFunc = cast[T](steam_api.checkedSymAddr(dllFuncName))
+if steam_api != nil:
+  loadFunc(Init, "SteamAPI_InitFlat")
+  loadFunc(Shutdown, "SteamAPI_Shutdown")
+  loadFunc(SteamUserStats, "SteamAPI_SteamUserStats_v012")
+  loadFunc(RequestCurrentStats, "SteamAPI_ISteamUserStats_RequestCurrentStats")
+  loadFunc(ClearAchievement, "SteamAPI_ISteamUserStats_ClearAchievement")
+  loadFunc(SetAchievement, "SteamAPI_ISteamUserStats_SetAchievement")
+  loadFunc(StoreStats, "SteamAPI_ISteamUserStats_StoreStats")
+
+
+# nice wrappers for steam API
+
+proc SteamRequestCurrentStats*(): bool =
+  RequestCurrentStats(userStats)
+
+proc SteamClearAchievement*(name: string): bool =
+  userStats.ClearAchievement(name.cstring)
+
+proc SteamSetAchievement*(name: string): bool =
+  userStats.SetAchievement(name.cstring)
+
+proc SteamStoreStats*(): bool =
+  userStats.StoreStats()
+
+proc SteamShutdown*() =
+  Shutdown()
+
+
+# helper funcs
+proc SteamAvailable*(): bool =
+  steam_api != nil and steam_is_loaded
+
+# first function that should be called
+proc TrySteamInit*() =
+  if steam_api != nil and not steam_is_loaded:
+    var msg: array[1024, char]
+    let success = Init(addr msg) == 0
+    warn join(@msg, "")
+    if success:
+      userStats = SteamUserStats()
+      steam_is_loaded = SteamRequestCurrentStats()
--- a/semiconginev2/core/buildconfig.nim	Wed Jul 17 22:20:59 2024 +0700
+++ b/semiconginev2/core/buildconfig.nim	Wed Jul 17 23:41:51 2024 +0700
@@ -12,24 +12,6 @@
 # build configuration
 # =====================
 
-# compile-time defines, usefull for build-dependent settings
-# can be overriden with compiler flags, e.g. -d:Foo=42 -d:Bar=false
-# pramas: {.intdefine.} {.strdefine.} {.booldefine.}
-
-# root of where settings files will be searched
-# must be relative (to the directory of the binary)
-const DEBUG* {.booldefine.} = not defined(release)
-const CONFIGROOT* {.strdefine.}: string = "."
-assert not isAbsolute(CONFIGROOT)
-
-const CONFIGEXTENSION* {.strdefine.}: string = "ini"
-
-# by default enable hot-reload of runtime-configuration only in debug builds
-const CONFIGHOTRELOAD* {.booldefine.}: bool = DEBUG
-
-# milliseconds to wait between checks for settings hotreload
-const CONFIGHOTRELOADINTERVAL* {.intdefine.}: int = 1000
-
 # log level
 const LOGLEVEL {.strdefine.}: string = "Warn"
 const ENGINE_LOGLEVEL* = parseEnum[Level]("lvl" & LOGLEVEL)
--- a/semiconginev2/old/algorithms.nim	Wed Jul 17 22:20:59 2024 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,81 +0,0 @@
-import std/algorithm
-
-import ./core
-
-type
-  Rect = tuple
-    i: int
-    x, y, w, h: uint32
-
-func between(a1, a2, b: uint32): bool =
-  a1 <= b and b <= a2
-
-func overlap(a1, a2, b1, b2: uint32): bool =
-  return between(a1, a2, b1) or
-         between(a1, a2, b2) or
-         between(b1, b2, a1) or
-         between(b1, b2, a2)
-
-# FYI: also serves as "overlaps"
-func advanceIfOverlap(fix, newRect: Rect): (bool, uint32) =
-  let overlapping = overlap(fix.x, fix.x + fix.w - 1, newRect.x, newRect.x + newRect.w - 1) and
-                    overlap(fix.y, fix.y + fix.h - 1, newRect.y, newRect.y + newRect.h - 1)
-  if overlapping: (true, fix.x + fix.w) # next free x coordinate to the right
-  else: (false, newRect.x) # current position is fine
-
-proc find_insertion_position(alreadyPlaced: seq[Rect], area: tuple[i: int, w, h: uint32], maxDim: uint32): (bool, Rect) =
-  var newRect = (i: area.i, x: 0'u32, y: 0'u32, w: area.w, h: area.h)
-
-  while newRect.y + newRect.h <= maxDim:
-    var hasOverlap = false
-    var advanceX: uint32
-
-    for placed in alreadyPlaced:
-      (hasOverlap, advanceX) = placed.advanceIfOverlap(newRect)
-      if hasOverlap: # rects were overlapping and newRect needs to be shifted to the right
-        newRect.x = advanceX
-        break
-
-    if not hasOverlap: # found a collision free position
-      return (true, newRect)
-
-    if newRect.x + newRect.w >= maxDim: # move to next scanline
-      newRect.x = 0
-      newRect.y += 1
-
-  return (false, newRect)
-
-
-proc Pack*[T: Pixel](images: seq[Image[T]]): tuple[atlas: Image[T], coords: seq[tuple[x: uint32, y: uint32]]] =
-  const MAX_ATLAS_SIZE = 4096'u32
-  var areas: seq[tuple[i: int, w, h: uint32]]
-
-  for i in 0 ..< images.len:
-    areas.add (i, images[i].width, images[i].height)
-
-  let areasBySize = areas.sortedByIt(-(it[1] * it[2]).int64)
-  var assignedAreas: seq[Rect]
-  var maxDim = 128'u32
-
-  for area in areasBySize:
-    var pos = find_insertion_position(assignedAreas, area, maxDim)
-    while not pos[0]: # this should actually never loop more than once, but weird things happen ¯\_(ツ)_/¯
-      maxDim = maxDim * 2
-      assert maxDim <= MAX_ATLAS_SIZE, &"Atlas gets bigger than {MAX_ATLAS_SIZE}, cannot pack images"
-      pos = find_insertion_position(assignedAreas, area, maxDim)
-
-    assignedAreas.add pos[1]
-
-  # check there are overlaps
-  for i in 0 ..< assignedAreas.len - 1:
-    for j in i + 1 ..< assignedAreas.len:
-      assert not assignedAreas[i].advanceIfOverlap(assignedAreas[j])[0], &"{assignedAreas[i]} and {assignedAreas[j]} overlap!"
-
-  result.atlas = NewImage[T](maxDim, maxDim)
-  result.coords.setLen(images.len)
-  for rect in assignedAreas:
-    for y in 0 ..< rect.h:
-      for x in 0 ..< rect.w:
-        assert result.atlas[rect.x + x, rect.y + y] == 0, "Atlas texture packing encountered an overlap error"
-        result.atlas[rect.x + x, rect.y + y] = images[rect.i][x, y]
-        result.coords[rect.i] = (x: rect.x, y: rect.y)
--- a/semiconginev2/old/collision.nim	Wed Jul 17 22:20:59 2024 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,386 +0,0 @@
-import ./core
-
-const MAX_COLLISON_DETECTION_ITERATIONS = 20
-const MAX_COLLISON_POINT_CALCULATION_ITERATIONS = 20
-
-type
-  ColliderType* = enum
-    Box, Sphere, Points
-  Collider* = object
-    transform*: Mat4 = Unit4F32
-    case theType*: ColliderType
-      of Box: discard
-      of Sphere: radius*: float32
-      of Points: points*: seq[Vec3f]
-
-func between(value, b1, b2: float32): bool =
-  min(b1, b2) <= value and value <= max(b1, b2)
-
-func Contains*(collider: Collider, x: Vec3f): bool =
-  # from https://math.stackexchange.com/questions/1472049/check-if-a-point-is-inside-a-rectangular-shaped-area-3d
-  case collider.theType:
-  of Box:
-    let
-      P1 = collider.transform * NewVec3f(0, 0, 0) # origin
-      P2 = collider.transform * Z
-      P4 = collider.transform * X
-      P5 = collider.transform * Y
-      u = (P1 - P4).Cross(P1 - P5)
-      v = (P1 - P2).Cross(P1 - P5)
-      w = (P1 - P2).Cross(P1 - P4)
-      uP1 = u.Dot(P1)
-      uP2 = u.Dot(P2)
-      vP1 = v.Dot(P1)
-      vP4 = v.Dot(P4)
-      wP1 = w.Dot(P1)
-      wP5 = w.Dot(P5)
-      ux = u.Dot(x)
-      vx = v.Dot(x)
-      wx = w.Dot(x)
-    ux.between(uP1, uP2) and vx.between(vP1, vP4) and wx.between(wP1, wP5)
-  of Sphere:
-    (collider.transform * x).Length < (collider.transform * NewVec3f()).Length
-  of Points:
-    raise newException(Exception, "Points are not supported yet for 'contains'")
-
-# implementation of GJK, based on https://blog.winter.dev/2020/gjk-algorithm/
-
-# most generic implementation of findFurthestPoint
-# add other implementations of findFurthestPoint for other kind of geometry or optimization
-# (will be selected depening on type of the first parameter)
-func findFurthestPoint(points: openArray[Vec3f], direction: Vec3f): Vec3f =
-  var maxDist = low(float32)
-  for p in points:
-    let dist = direction.Dot(p)
-    if dist > maxDist:
-      maxDist = dist
-      result = p
-
-func findFurthestPoint(transform: Mat4, direction: Vec3f): Vec3f =
-  return findFurthestPoint(
-    [
-      transform * NewVec3f(0, 0, 0),
-      transform * X,
-      transform * Y,
-      transform * Z,
-      transform * (X + Y),
-      transform * (X + Z),
-      transform * (Y + Z),
-      transform * (X + Y + Z),
-    ],
-    direction
-  )
-func findFurthestPoint(collider: Collider, direction: Vec3f): Vec3f =
-  case collider.theType
-  of Sphere:
-    let directionNormalizedToSphere = ((direction / direction.Length) * collider.radius)
-    collider.transform * directionNormalizedToSphere
-  of Box:
-    findFurthestPoint(collider.transform, direction)
-  of Points:
-    findFurthestPoint(collider.points, direction)
-
-func supportPoint(a, b: Collider, direction: Vec3f): Vec3f =
-  a.findFurthestPoint(direction) - b.findFurthestPoint(-direction)
-
-func sameDirection(direction: Vec3f, ao: Vec3f): bool =
-  direction.Dot(ao) > 0
-
-func line(simplex: var seq[Vec3f], direction: var Vec3f): bool =
-  let
-    a = simplex[0]
-    b = simplex[1]
-    ab = b - a
-    ao = - a
-
-  if sameDirection(ab, ao):
-    direction = Cross(Cross(ab, ao), ab)
-  else:
-    simplex = @[a]
-    direction = ao
-
-  return false
-
-func triangle(simplex: var seq[Vec3f], direction: var Vec3f, twoDimensional = false): bool =
-  let
-    a = simplex[0]
-    b = simplex[1]
-    c = simplex[2]
-    ab = b - a
-    ac = c - a
-    ao = - a
-    abc = ab.Cross(ac)
-
-  if sameDirection(abc.Cross(ac), ao):
-    if sameDirection(ac, ao):
-      simplex = @[a, c]
-      direction = ac.Cross(ao).Cross(ac)
-    else:
-      simplex = @[a, b]
-      return line(simplex, direction)
-  else:
-    if (sameDirection(ab.Cross(abc), ao)):
-      simplex = @[a, b]
-      return line(simplex, direction)
-    else:
-      if twoDimensional:
-        return true
-      if (sameDirection(abc, ao)):
-        direction = abc
-      else:
-        simplex = @[a, c, b]
-        direction = -abc
-
-  return false
-
-func tetrahedron(simplex: var seq[Vec3f], direction: var Vec3f): bool =
-  let
-    a = simplex[0]
-    b = simplex[1]
-    c = simplex[2]
-    d = simplex[3]
-    ab = b - a
-    ac = c - a
-    ad = d - a
-    ao = - a
-    abc = ab.Cross(ac)
-    acd = ac.Cross(ad)
-    adb = ad.Cross(ab)
-
-  if sameDirection(abc, ao):
-    simplex = @[a, b, c]
-    return triangle(simplex, direction)
-  if sameDirection(acd, ao):
-    simplex = @[a, c, d]
-    return triangle(simplex, direction)
-  if sameDirection(adb, ao):
-    simplex = @[a, d, b]
-    return triangle(simplex, direction)
-
-  return true
-
-func getFaceNormals(polytope: seq[Vec3f], faces: seq[int]): (seq[Vec4f], int) =
-  var
-    normals: seq[Vec4f]
-    minTriangle = 0
-    minDistance = high(float32)
-
-  for i in countup(0, faces.len - 1, 3):
-    let
-      a = polytope[faces[i + 0]]
-      b = polytope[faces[i + 1]]
-      c = polytope[faces[i + 2]]
-
-    var normal = (b - a).Cross(c - a).Normalized()
-    var distance = normal.Dot(a)
-
-    if distance < 0:
-      normal = normal * -1'f32
-      distance = distance * -1'f32
-
-    normals.add normal.ToVec4(distance)
-
-    if distance < minDistance:
-      minTriangle = i div 3
-      minDistance = distance
-
-  return (normals, minTriangle)
-
-func addIfUniqueEdge(edges: var seq[(int, int)], faces: seq[int], a: int, b: int) =
-  let reverse = edges.find((faces[b], faces[a]))
-  if (reverse >= 0):
-    edges.delete(reverse)
-  else:
-    edges.add (faces[a], faces[b])
-
-func nextSimplex(simplex: var seq[Vec3f], direction: var Vec3f, twoDimensional = false): bool =
-  case simplex.len
-  of 2: simplex.line(direction)
-  of 3: simplex.triangle(direction, twoDimensional)
-  of 4: simplex.tetrahedron(direction)
-  else: raise newException(Exception, "Error in simplex")
-
-func collisionPoint3D(simplex: var seq[Vec3f], a, b: Collider): tuple[normal: Vec3f, penetrationDepth: float32] =
-  var
-    polytope = simplex
-    faces = @[
-      0, 1, 2,
-      0, 3, 1,
-      0, 2, 3,
-      1, 3, 2
-    ]
-    (normals, minFace) = getFaceNormals(polytope, faces)
-    minNormal: Vec3f
-    minDistance = high(float32)
-    iterCount = 0
-
-  while minDistance == high(float32) and iterCount < MAX_COLLISON_POINT_CALCULATION_ITERATIONS:
-    minNormal = normals[minFace].xyz
-    minDistance = normals[minFace].w
-    var
-      support = supportPoint(a, b, minNormal)
-      sDistance = minNormal.Dot(support)
-
-    if abs(sDistance - minDistance) > 0.001'f32:
-      minDistance = high(float32)
-      var uniqueEdges: seq[(int, int)]
-      var i = 0
-      while i < normals.len:
-        if sameDirection(normals[i], support):
-          var f = i * 3
-
-          addIfUniqueEdge(uniqueEdges, faces, f + 0, f + 1)
-          addIfUniqueEdge(uniqueEdges, faces, f + 1, f + 2)
-          addIfUniqueEdge(uniqueEdges, faces, f + 2, f + 0)
-
-          faces[f + 2] = faces.pop()
-          faces[f + 1] = faces.pop()
-          faces[f + 0] = faces.pop()
-
-          normals[i] = normals.pop()
-
-          dec i
-        inc i
-
-      var newFaces: seq[int]
-      for (edgeIndex1, edgeIndex2) in uniqueEdges:
-        newFaces.add edgeIndex1
-        newFaces.add edgeIndex2
-        newFaces.add polytope.len
-
-      polytope.add support
-
-      var (newNormals, newMinFace) = getFaceNormals(polytope, newFaces)
-      if newNormals.len == 0:
-        break
-
-      var oldMinDistance = high(float32)
-      for j in 0 ..< normals.len:
-        if normals[j].w < oldMinDistance:
-          oldMinDistance = normals[j].w
-          minFace = j
-
-      if (newNormals[newMinFace].w < oldMinDistance):
-        minFace = newMinFace + normals.len
-
-      for f in newFaces:
-        faces.add f
-      for n in newNormals:
-        normals.add n
-    inc iterCount
-
-  result = (normal: minNormal, penetrationDepth: minDistance + 0.001'f32)
-
-
-func collisionPoint2D(polytopeIn: seq[Vec3f], a, b: Collider): tuple[normal: Vec3f, penetrationDepth: float32] =
-  var
-    polytope = polytopeIn
-    minIndex = 0
-    minDistance = high(float32)
-    iterCount = 0
-    minNormal: Vec2f
-
-  while minDistance == high(float32) and iterCount < MAX_COLLISON_POINT_CALCULATION_ITERATIONS:
-    for i in 0 ..< polytope.len:
-      let
-        j = (i + 1) mod polytope.len
-        vertexI = polytope[i]
-        vertexJ = polytope[j]
-        ij = vertexJ - vertexI
-      var
-        normal = NewVec2f(ij.y, -ij.x).Normalized()
-        distance = normal.Dot(vertexI)
-
-      if (distance < 0):
-        distance *= -1'f32
-        normal = normal * -1'f32
-
-      if distance < minDistance:
-        minDistance = distance
-        minNormal = normal
-        minIndex = j
-
-    let
-      support = supportPoint(a, b, minNormal.ToVec3)
-      sDistance = minNormal.Dot(support)
-
-    if(abs(sDistance - minDistance) > 0.001):
-      minDistance = high(float32)
-      polytope.insert(support, minIndex)
-    inc iterCount
-
-  result = (normal: NewVec3f(minNormal.x, minNormal.y), penetrationDepth: minDistance + 0.001'f32)
-
-func Intersects*(a, b: Collider, as2D = false): bool =
-  var
-    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
-  simplex.insert(support, 0)
-  while n < MAX_COLLISON_DETECTION_ITERATIONS:
-    support = supportPoint(a, b, direction)
-    if support.Dot(direction) <= 0:
-      return false
-    simplex.insert(support, 0)
-    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, as2D = false): tuple[hasCollision: bool, normal: Vec3f, penetrationDepth: float32] =
-  var
-    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
-  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 = 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):
-      direction[0] = 0.0001
-    inc n
-
-func CalculateCollider*(points: openArray[Vec3f], theType: ColliderType): Collider =
-  var
-    minX = high(float32)
-    maxX = low(float32)
-    minY = high(float32)
-    maxY = low(float32)
-    minZ = high(float32)
-    maxZ = low(float32)
-    center: Vec3f
-
-  for p in points:
-    minX = min(minX, p.x)
-    maxX = max(maxX, p.x)
-    minY = min(minY, p.y)
-    maxY = max(maxY, p.y)
-    minZ = min(minZ, p.z)
-    maxZ = max(maxz, p.z)
-    center = center + p
-  center = center / float32(points.len)
-
-  let
-    scaleX = (maxX - minX)
-    scaleY = (maxY - minY)
-    scaleZ = (maxZ - minZ)
-
-  if theType == Points:
-    result = Collider(theType: Points, points: @points)
-  else:
-    result = Collider(theType: theType, transform: Translate(minX, minY, minZ) * Scale(scaleX, scaleY, scaleZ))
-
-    if theType == Sphere:
-      result.transform = Translate(center)
-      for p in points:
-        result.radius = max(result.radius, (p - center).Length)
--- a/semiconginev2/old/noise.nim	Wed Jul 17 22:20:59 2024 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,34 +0,0 @@
-import hashes
-import math
-
-import ./core/vector
-
-
-proc randomGradient(pos: Vec2f, seed: int32 = 0): Vec2f =
-  let randomAngle: float32 = TAU * (float32(int(hash((pos.x, pos.y, seed)))) / float32(high(int)))
-  return NewVec2f(cos(randomAngle), sin(randomAngle))
-
-proc interpolate(a: float32, b: float32, weight: float32): float32 =
-  # with Smootherstep
-  (b - a) * ((weight * (weight * 6.0 - 15.0) + 10.0) * weight * weight * weight) + a;
-
-proc Perlin*(pos: Vec2f, seed: int32 = 0): float32 =
-  let
-    # grid coordinates around target point
-    topleft = NewVec2f(trunc(pos.x), trunc(pos.y))
-    topright = topleft + NewVec2f(1, 0)
-    bottomleft = topleft + NewVec2f(0, 1)
-    bottomright = topleft + NewVec2f(1, 1)
-    # products for weights
-    topleft_dot = topleft.randomGradient(seed).Dot((pos - topleft))
-    topright_dot = topright.randomGradient(seed).Dot((pos - topright))
-    bottomleft_dot = bottomleft.randomGradient(seed).Dot((pos - bottomleft))
-    bottomright_dot = bottomright.randomGradient(seed).Dot((pos - bottomright))
-    xinterpol = pos.x - topleft.x
-    yinterpol = pos.y - topleft.y
-
-  return interpolate(
-    interpolate(topleft_dot, bottomleft_dot, yinterpol),
-    interpolate(topright_dot, bottomright_dot, yinterpol),
-    xinterpol
-  )
--- a/semiconginev2/old/settings.nim	Wed Jul 17 22:20:59 2024 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,104 +0,0 @@
-import std/logging
-import std/streams
-import std/parsecfg
-import std/strutils
-import std/parseutils
-import std/strformat
-import std/tables
-import std/os
-
-import ./core
-
-when CONFIGHOTRELOAD:
-  var
-    configUpdates: Channel[(string, string)]
-  configUpdates.open()
-
-# runtime configuration
-# =====================
-# namespace is the path from the CONFIGROOT to the according settings file without the file extension
-# a settings file must always have the extension CONFIGEXTENSION
-# a fully qualified settings identifier can be in the form {namespace}.{section}.{key}
-# {key} and {section} may not contain dots
-
-# a "namespace" is the path from the settings root to an *.CONFIGEXTENSION file, without the file extension
-# settings is a namespace <-> settings mapping
-var allsettings: Table[string, Config]
-
-proc configRoot(): string =
-  joinPath(absolutePath(getAppDir()), CONFIGROOT)
-
-proc getFile(namespace: string): string =
-  joinPath(configRoot(), namespace & "." & CONFIGEXTENSION)
-
-iterator walkConfigNamespaces(): string =
-  for file in walkDirRec(dir = configRoot(), relative = true, checkDir = true):
-    if file.endsWith("." & CONFIGEXTENSION):
-      yield file[0 ..< ^(CONFIGEXTENSION.len + 1)]
-
-proc loadAllConfig(): Table[string, Config] =
-  for ns in walkConfigNamespaces():
-    result[ns] = ns.getFile().loadConfig()
-
-proc ReloadSettings*() =
-  allsettings = loadAllConfig()
-
-proc configStr(key, section, namespace: string): string =
-  when CONFIGHOTRELOAD:
-    while configUpdates.peek() > 0:
-      let (updatedNamespace, updatedConfig) = configUpdates.recv()
-      allsettings[updatedNamespace] = loadConfig(newStringStream(updatedConfig))
-  if not allsettings.hasKey(namespace):
-    raise newException(Exception, &"Settings {namespace}.{section}.{key} was not found")
-  allsettings[namespace].getSectionValue(section, key)
-
-proc Setting*[T: int|float|string](key, section, namespace: string): T =
-  when T is int:
-    let value = configStr(key, section, namespace)
-    if parseInt(value, result) == 0:
-      raise newException(Exception, &"Unable to parse int from settings {namespace}.{section}.{key}: {value}")
-  elif T is float:
-    let value = configStr(key, section, namespace)
-    if parseFloat(value, result) == 0:
-      raise newException(Exception, &"Unable to parse float from settings {namespace}.{section}.{key}: {value}")
-  else:
-    result = configStr(key, section, namespace)
-
-proc Setting*[T: int|float|string](identifier: string): T =
-  # identifier can be in the form:
-  # {namespace}.{key}
-  # {namespace}.{section}.{key}
-  let parts = identifier.rsplit(".")
-  if parts.len == 1:
-    raise newException(Exception, &"Setting with name {identifier} has no namespace")
-  if parts.len == 2: result = Setting[T](parts[1], "", parts[0])
-  else: result = Setting[T](parts[^1], parts[^2], joinPath(parts[0 .. ^3]))
-
-proc HadConfigUpdate*(): bool =
-  when CONFIGHOTRELOAD == true:
-    result = configUpdates.peek() > 0
-
-allsettings = loadAllConfig()
-
-when CONFIGHOTRELOAD == true:
-  import std/times
-
-  proc configFileWatchdog() {.thread.} =
-    var configModTimes: Table[string, Time]
-    while true:
-      for namespace in walkConfigNamespaces():
-        if not (namespace in configModTimes):
-          configModTimes[namespace] = Time()
-        let lastMod = namespace.getFile().getLastModificationTime()
-        if lastMod > configModTimes[namespace]:
-          configModTimes[namespace] = lastMod
-          let configStr = newFileStream(namespace.getFile()).readAll()
-          configUpdates.send((namespace, configStr))
-      sleep CONFIGHOTRELOADINTERVAL
-  var thethread: Thread[void]
-  createThread(thethread, configFileWatchdog)
-
-if DEBUG:
-  setLogFilter(lvlAll)
-else:
-  setLogFilter(lvlWarn)
--- a/semiconginev2/old/steam.nim	Wed Jul 17 22:20:59 2024 +0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,76 +0,0 @@
-import std/dynlib
-import std/strutils
-import std/logging
-
-var
-  steam_api: LibHandle
-  steam_is_loaded = false
-
-when defined(linux):
-  proc dlerror(): cstring {.stdcall, importc.}
-  steam_api = "libsteam_api.so".loadLib()
-  if steam_api == nil:
-    echo dlerror()
-elif defined(windows):
-  steam_api = "steam_api".loadLib()
-  # TODO: maybe should get some error reporting on windows too?
-
-
-# required to store reference, when calling certain APIs
-type
-  SteamUserStatsRef = ptr object
-var userStats: SteamUserStatsRef
-
-# load function pointers for steam API
-var
-  Shutdown*: proc() {.stdcall.}
-  Init: proc(msg: ptr array[1024, char]): cint {.stdcall.}
-  SteamUserStats: proc(): SteamUserStatsRef {.stdcall.}
-  RequestCurrentStats: proc(self: SteamUserStatsRef): bool {.stdcall.} # needs to be called before the achievment-stuff
-  ClearAchievement: proc(self: SteamUserStatsRef, pchName: cstring): bool {.stdcall.}
-  SetAchievement: proc(self: SteamUserStatsRef, pchName: cstring): bool {.stdcall.}
-  StoreStats: proc(self: SteamUserStatsRef): bool {.stdcall.}          # needs to be called in order for achievments to be saved
-                                                                       # dynlib-helper function
-proc loadFunc[T](nimFunc: var T, dllFuncName: string) =
-  nimFunc = cast[T](steam_api.checkedSymAddr(dllFuncName))
-if steam_api != nil:
-  loadFunc(Init, "SteamAPI_InitFlat")
-  loadFunc(Shutdown, "SteamAPI_Shutdown")
-  loadFunc(SteamUserStats, "SteamAPI_SteamUserStats_v012")
-  loadFunc(RequestCurrentStats, "SteamAPI_ISteamUserStats_RequestCurrentStats")
-  loadFunc(ClearAchievement, "SteamAPI_ISteamUserStats_ClearAchievement")
-  loadFunc(SetAchievement, "SteamAPI_ISteamUserStats_SetAchievement")
-  loadFunc(StoreStats, "SteamAPI_ISteamUserStats_StoreStats")
-
-
-# nice wrappers for steam API
-
-proc SteamRequestCurrentStats*(): bool =
-  RequestCurrentStats(userStats)
-
-proc SteamClearAchievement*(name: string): bool =
-  userStats.ClearAchievement(name.cstring)
-
-proc SteamSetAchievement*(name: string): bool =
-  userStats.SetAchievement(name.cstring)
-
-proc SteamStoreStats*(): bool =
-  userStats.StoreStats()
-
-proc SteamShutdown*() =
-  Shutdown()
-
-
-# helper funcs
-proc SteamAvailable*(): bool =
-  steam_api != nil and steam_is_loaded
-
-# first function that should be called
-proc TrySteamInit*() =
-  if steam_api != nil and not steam_is_loaded:
-    var msg: array[1024, char]
-    let success = Init(addr msg) == 0
-    warn join(@msg, "")
-    if success:
-      userStats = SteamUserStats()
-      steam_is_loaded = SteamRequestCurrentStats()
--- a/semiconginev2/rendering.nim	Wed Jul 17 22:20:59 2024 +0700
+++ b/semiconginev2/rendering.nim	Wed Jul 17 23:41:51 2024 +0700
@@ -68,6 +68,7 @@
     oldSwapchainCounter: int # swaps until old swapchain will be destroyed
 
 var vulkan*: VulkanGlobals
+var fullscreen: bool
 
 func currentFiF*(swapchain: Swapchain): int = swapchain.currentFiF
 
@@ -311,3 +312,33 @@
   vkDestroySurfaceKHR(vulkan.instance, vulkan.surface, nil)
   vkDestroyDebugUtilsMessengerEXT(vulkan.instance, vulkan.debugMessenger, nil)
   vkDestroyInstance(vulkan.instance, nil)
+
+proc ShowSystemCursor*() = vulkan.window.ShowSystemCursor()
+proc HideSystemCursor*() = vulkan.window.HideSystemCursor()
+proc Fullscreen*(): bool = fullscreen
+proc `Fullscreen=`*(enable: bool) =
+  if enable != fullscreen:
+    fullscreen = enable
+    vulkan.window.Fullscreen(fullscreen)
+
+func GetAspectRatio*(swapchain: Swapchain): float32 = swapchain.width.float32 / swapchain.height.float32
+
+proc MaxFramebufferSampleCount*(maxSamples = VK_SAMPLE_COUNT_8_BIT): VkSampleCountFlagBits =
+  let limits = svkGetPhysicalDeviceProperties().limits
+  let available = VkSampleCountFlags(
+    limits.framebufferColorSampleCounts.uint32 and limits.framebufferDepthSampleCounts.uint32
+  ).toEnums
+  return min(max(available), maxSamples)
+
+
+proc `[]`*(texture: Texture, x, y: uint32): auto =
+  assert x < texture.width, &"{x} < {texture.width} is not true"
+  assert y < texture.height, &"{y} < {texture.height} is not true"
+
+  texture[].imagedata[y * texture.width + x]
+
+proc `[]=`*[T](texture: var Texture[T], x, y: uint32, value: T) =
+  assert x < texture.width
+  assert y < texture.height
+
+  texture[].imagedata[y * texture.width + x] = value