Mercurial > games > semicongine
comparison semiconginev2/old/algorithms.nim @ 1218:56781cc0fc7c compiletime-tests
did: renamge main package
| author | sam <sam@basx.dev> |
|---|---|
| date | Wed, 17 Jul 2024 21:01:37 +0700 |
| parents | semicongine/old/algorithms.nim@a3eb305bcac2 |
| children |
comparison
equal
deleted
inserted
replaced
| 1217:f819a874058f | 1218:56781cc0fc7c |
|---|---|
| 1 import std/algorithm | |
| 2 | |
| 3 import ./core | |
| 4 | |
| 5 type | |
| 6 Rect = tuple | |
| 7 i: int | |
| 8 x, y, w, h: uint32 | |
| 9 | |
| 10 func between(a1, a2, b: uint32): bool = | |
| 11 a1 <= b and b <= a2 | |
| 12 | |
| 13 func overlap(a1, a2, b1, b2: uint32): bool = | |
| 14 return between(a1, a2, b1) or | |
| 15 between(a1, a2, b2) or | |
| 16 between(b1, b2, a1) or | |
| 17 between(b1, b2, a2) | |
| 18 | |
| 19 # FYI: also serves as "overlaps" | |
| 20 func advanceIfOverlap(fix, newRect: Rect): (bool, uint32) = | |
| 21 let overlapping = overlap(fix.x, fix.x + fix.w - 1, newRect.x, newRect.x + newRect.w - 1) and | |
| 22 overlap(fix.y, fix.y + fix.h - 1, newRect.y, newRect.y + newRect.h - 1) | |
| 23 if overlapping: (true, fix.x + fix.w) # next free x coordinate to the right | |
| 24 else: (false, newRect.x) # current position is fine | |
| 25 | |
| 26 proc find_insertion_position(alreadyPlaced: seq[Rect], area: tuple[i: int, w, h: uint32], maxDim: uint32): (bool, Rect) = | |
| 27 var newRect = (i: area.i, x: 0'u32, y: 0'u32, w: area.w, h: area.h) | |
| 28 | |
| 29 while newRect.y + newRect.h <= maxDim: | |
| 30 var hasOverlap = false | |
| 31 var advanceX: uint32 | |
| 32 | |
| 33 for placed in alreadyPlaced: | |
| 34 (hasOverlap, advanceX) = placed.advanceIfOverlap(newRect) | |
| 35 if hasOverlap: # rects were overlapping and newRect needs to be shifted to the right | |
| 36 newRect.x = advanceX | |
| 37 break | |
| 38 | |
| 39 if not hasOverlap: # found a collision free position | |
| 40 return (true, newRect) | |
| 41 | |
| 42 if newRect.x + newRect.w >= maxDim: # move to next scanline | |
| 43 newRect.x = 0 | |
| 44 newRect.y += 1 | |
| 45 | |
| 46 return (false, newRect) | |
| 47 | |
| 48 | |
| 49 proc Pack*[T: Pixel](images: seq[Image[T]]): tuple[atlas: Image[T], coords: seq[tuple[x: uint32, y: uint32]]] = | |
| 50 const MAX_ATLAS_SIZE = 4096'u32 | |
| 51 var areas: seq[tuple[i: int, w, h: uint32]] | |
| 52 | |
| 53 for i in 0 ..< images.len: | |
| 54 areas.add (i, images[i].width, images[i].height) | |
| 55 | |
| 56 let areasBySize = areas.sortedByIt(-(it[1] * it[2]).int64) | |
| 57 var assignedAreas: seq[Rect] | |
| 58 var maxDim = 128'u32 | |
| 59 | |
| 60 for area in areasBySize: | |
| 61 var pos = find_insertion_position(assignedAreas, area, maxDim) | |
| 62 while not pos[0]: # this should actually never loop more than once, but weird things happen ¯\_(ツ)_/¯ | |
| 63 maxDim = maxDim * 2 | |
| 64 assert maxDim <= MAX_ATLAS_SIZE, &"Atlas gets bigger than {MAX_ATLAS_SIZE}, cannot pack images" | |
| 65 pos = find_insertion_position(assignedAreas, area, maxDim) | |
| 66 | |
| 67 assignedAreas.add pos[1] | |
| 68 | |
| 69 # check there are overlaps | |
| 70 for i in 0 ..< assignedAreas.len - 1: | |
| 71 for j in i + 1 ..< assignedAreas.len: | |
| 72 assert not assignedAreas[i].advanceIfOverlap(assignedAreas[j])[0], &"{assignedAreas[i]} and {assignedAreas[j]} overlap!" | |
| 73 | |
| 74 result.atlas = NewImage[T](maxDim, maxDim) | |
| 75 result.coords.setLen(images.len) | |
| 76 for rect in assignedAreas: | |
| 77 for y in 0 ..< rect.h: | |
| 78 for x in 0 ..< rect.w: | |
| 79 assert result.atlas[rect.x + x, rect.y + y] == 0, "Atlas texture packing encountered an overlap error" | |
| 80 result.atlas[rect.x + x, rect.y + y] = images[rect.i][x, y] | |
| 81 result.coords[rect.i] = (x: rect.x, y: rect.y) |
