# HG changeset patch # User sam # Date 1721234511 -25200 # Node ID c8e3037aca66f30af6d09a7f234868d99488f218 # Parent 27cd1c21290ede72ab009080e18dbae7628fe502 add: contrib stuff diff -r 27cd1c21290e -r c8e3037aca66 semiconginev2.nim --- 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 diff -r 27cd1c21290e -r c8e3037aca66 semiconginev2/audio.nim --- 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() diff -r 27cd1c21290e -r c8e3037aca66 semiconginev2/contrib/algorithms/collision.nim --- /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) diff -r 27cd1c21290e -r c8e3037aca66 semiconginev2/contrib/algorithms/noise.nim --- /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 + ) diff -r 27cd1c21290e -r c8e3037aca66 semiconginev2/contrib/algorithms/texture_packing.nim --- /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) diff -r 27cd1c21290e -r c8e3037aca66 semiconginev2/contrib/settings.nim --- /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) diff -r 27cd1c21290e -r c8e3037aca66 semiconginev2/contrib/steam.nim --- /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() diff -r 27cd1c21290e -r c8e3037aca66 semiconginev2/core/buildconfig.nim --- 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) diff -r 27cd1c21290e -r c8e3037aca66 semiconginev2/old/algorithms.nim --- 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) diff -r 27cd1c21290e -r c8e3037aca66 semiconginev2/old/collision.nim --- 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) diff -r 27cd1c21290e -r c8e3037aca66 semiconginev2/old/noise.nim --- 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 - ) diff -r 27cd1c21290e -r c8e3037aca66 semiconginev2/old/settings.nim --- 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) diff -r 27cd1c21290e -r c8e3037aca66 semiconginev2/old/steam.nim --- 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() diff -r 27cd1c21290e -r c8e3037aca66 semiconginev2/rendering.nim --- 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