annotate semiconginev2/contrib/algorithms/texture_packing.nim @ 1243:7e55fde39ca8

did: prepare for gltf importer and cleanup old engine code
author sam <sam@basx.dev>
date Mon, 22 Jul 2024 17:49:48 +0700
parents 841e12f33c47
children c15770761865
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
1226
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
1 type
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
2 Rect = tuple
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
3 i: int
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
4 x, y, w, h: uint32
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
5
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
6 func between(a1, a2, b: uint32): bool =
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
7 a1 <= b and b <= a2
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
8
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
9 func overlap(a1, a2, b1, b2: uint32): bool =
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
10 return between(a1, a2, b1) or
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
11 between(a1, a2, b2) or
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
12 between(b1, b2, a1) or
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
13 between(b1, b2, a2)
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
14
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
15 # FYI: also serves as "overlaps"
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
16 func advanceIfOverlap(fix, newRect: Rect): (bool, uint32) =
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
17 let overlapping = overlap(fix.x, fix.x + fix.w - 1, newRect.x, newRect.x + newRect.w - 1) and
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
18 overlap(fix.y, fix.y + fix.h - 1, newRect.y, newRect.y + newRect.h - 1)
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
19 if overlapping: (true, fix.x + fix.w) # next free x coordinate to the right
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
20 else: (false, newRect.x) # current position is fine
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
21
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
22 proc find_insertion_position(alreadyPlaced: seq[Rect], area: tuple[i: int, w, h: uint32], maxDim: uint32): (bool, Rect) =
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
23 var newRect = (i: area.i, x: 0'u32, y: 0'u32, w: area.w, h: area.h)
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
24
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
25 while newRect.y + newRect.h <= maxDim:
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
26 var hasOverlap = false
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
27 var advanceX: uint32
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
28
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
29 for placed in alreadyPlaced:
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
30 (hasOverlap, advanceX) = placed.advanceIfOverlap(newRect)
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
31 if hasOverlap: # rects were overlapping and newRect needs to be shifted to the right
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
32 newRect.x = advanceX
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
33 break
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
34
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
35 if not hasOverlap: # found a collision free position
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
36 return (true, newRect)
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
37
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
38 if newRect.x + newRect.w >= maxDim: # move to next scanline
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
39 newRect.x = 0
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
40 newRect.y += 1
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
41
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
42 return (false, newRect)
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
43
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
44
1228
4e465583ea32 did: rename texture to image
sam <sam@basx.dev>
parents: 1226
diff changeset
45 proc Pack*[T: PixelType](images: seq[Image[T]]): tuple[atlas: Image[T], coords: seq[tuple[x: uint32, y: uint32]]] =
1226
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
46 const MAX_ATLAS_SIZE = 4096'u32
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
47 var areas: seq[tuple[i: int, w, h: uint32]]
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
48
1228
4e465583ea32 did: rename texture to image
sam <sam@basx.dev>
parents: 1226
diff changeset
49 for i in 0 ..< images.len:
4e465583ea32 did: rename texture to image
sam <sam@basx.dev>
parents: 1226
diff changeset
50 areas.add (i, images[i].width, images[i].height)
1226
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
51
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
52 let areasBySize = areas.sortedByIt(-(it[1] * it[2]).int64)
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
53 var assignedAreas: seq[Rect]
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
54 var maxDim = 128'u32
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
55
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
56 for area in areasBySize:
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
57 var pos = find_insertion_position(assignedAreas, area, maxDim)
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
58 while not pos[0]: # this should actually never loop more than once, but weird things happen ¯\_(ツ)_/¯
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
59 maxDim = maxDim * 2
1228
4e465583ea32 did: rename texture to image
sam <sam@basx.dev>
parents: 1226
diff changeset
60 assert maxDim <= MAX_ATLAS_SIZE, &"Atlas gets bigger than {MAX_ATLAS_SIZE}, cannot pack images"
1226
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
61 pos = find_insertion_position(assignedAreas, area, maxDim)
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
62
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
63 assignedAreas.add pos[1]
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
64
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
65 # check there are overlaps
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
66 for i in 0 ..< assignedAreas.len - 1:
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
67 for j in i + 1 ..< assignedAreas.len:
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
68 assert not assignedAreas[i].advanceIfOverlap(assignedAreas[j])[0], &"{assignedAreas[i]} and {assignedAreas[j]} overlap!"
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
69
1228
4e465583ea32 did: rename texture to image
sam <sam@basx.dev>
parents: 1226
diff changeset
70 result.atlas = Image[T](width: maxDim, height: maxDim, data: newSeq[T](maxDim * maxDim))
4e465583ea32 did: rename texture to image
sam <sam@basx.dev>
parents: 1226
diff changeset
71 result.coords.setLen(images.len)
1226
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
72 for rect in assignedAreas:
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
73 for y in 0 ..< rect.h:
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
74 for x in 0 ..< rect.w:
1234
841e12f33c47 add: text & font rendering, not tested yet
sam <sam@basx.dev>
parents: 1228
diff changeset
75 when T is Gray:
841e12f33c47 add: text & font rendering, not tested yet
sam <sam@basx.dev>
parents: 1228
diff changeset
76 assert result.atlas[rect.x + x, rect.y + y] == [0'u8], "Atlas texture packing encountered an overlap error"
841e12f33c47 add: text & font rendering, not tested yet
sam <sam@basx.dev>
parents: 1228
diff changeset
77 elif T is RGBA:
841e12f33c47 add: text & font rendering, not tested yet
sam <sam@basx.dev>
parents: 1228
diff changeset
78 assert result.atlas[rect.x + x, rect.y + y] == [0'u8, 0'u8, 0'u8, 0'u8], "Atlas texture packing encountered an overlap error"
841e12f33c47 add: text & font rendering, not tested yet
sam <sam@basx.dev>
parents: 1228
diff changeset
79 else:
841e12f33c47 add: text & font rendering, not tested yet
sam <sam@basx.dev>
parents: 1228
diff changeset
80 {.error: "Unsupported type for texture packing".}
1228
4e465583ea32 did: rename texture to image
sam <sam@basx.dev>
parents: 1226
diff changeset
81 result.atlas[rect.x + x, rect.y + y] = images[rect.i][x, y]
1226
c8e3037aca66 add: contrib stuff
sam <sam@basx.dev>
parents:
diff changeset
82 result.coords[rect.i] = (x: rect.x, y: rect.y)