github.com/utopiagio/gio@v0.0.8/gpu/pack.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 package gpu 4 5 import ( 6 "image" 7 ) 8 9 // packer packs a set of many smaller rectangles into 10 // much fewer larger atlases. 11 type packer struct { 12 maxDims image.Point 13 spaces []image.Rectangle 14 15 sizes []image.Point 16 pos image.Point 17 } 18 19 type placement struct { 20 Idx int 21 Pos image.Point 22 } 23 24 // add adds the given rectangle to the atlases and 25 // return the allocated position. 26 func (p *packer) add(s image.Point) (placement, bool) { 27 if place, ok := p.tryAdd(s); ok { 28 return place, true 29 } 30 p.newPage() 31 return p.tryAdd(s) 32 } 33 34 func (p *packer) clear() { 35 p.sizes = p.sizes[:0] 36 p.spaces = p.spaces[:0] 37 } 38 39 func (p *packer) newPage() { 40 p.pos = image.Point{} 41 p.sizes = append(p.sizes, image.Point{}) 42 p.spaces = p.spaces[:0] 43 p.spaces = append(p.spaces, image.Rectangle{ 44 Max: image.Point{X: 1e6, Y: 1e6}, 45 }) 46 } 47 48 func (p *packer) tryAdd(s image.Point) (placement, bool) { 49 if len(p.spaces) == 0 || len(p.sizes) == 0 { 50 return placement{}, false 51 } 52 53 var ( 54 bestIdx *image.Rectangle 55 bestSize = p.maxDims 56 lastSize = p.sizes[len(p.sizes)-1] 57 ) 58 // Go backwards to prioritize smaller spaces. 59 for i := range p.spaces { 60 space := &p.spaces[i] 61 rightSpace := space.Dx() - s.X 62 bottomSpace := space.Dy() - s.Y 63 if rightSpace < 0 || bottomSpace < 0 { 64 continue 65 } 66 size := lastSize 67 if x := space.Min.X + s.X; x > size.X { 68 if x > p.maxDims.X { 69 continue 70 } 71 size.X = x 72 } 73 if y := space.Min.Y + s.Y; y > size.Y { 74 if y > p.maxDims.Y { 75 continue 76 } 77 size.Y = y 78 } 79 if size.X*size.Y < bestSize.X*bestSize.Y { 80 bestIdx = space 81 bestSize = size 82 } 83 } 84 if bestIdx == nil { 85 return placement{}, false 86 } 87 // Remove space. 88 bestSpace := *bestIdx 89 *bestIdx = p.spaces[len(p.spaces)-1] 90 p.spaces = p.spaces[:len(p.spaces)-1] 91 // Put s in the top left corner and add the (at most) 92 // two smaller spaces. 93 pos := bestSpace.Min 94 if rem := bestSpace.Dy() - s.Y; rem > 0 { 95 p.spaces = append(p.spaces, image.Rectangle{ 96 Min: image.Point{X: pos.X, Y: pos.Y + s.Y}, 97 Max: image.Point{X: bestSpace.Max.X, Y: bestSpace.Max.Y}, 98 }) 99 } 100 if rem := bestSpace.Dx() - s.X; rem > 0 { 101 p.spaces = append(p.spaces, image.Rectangle{ 102 Min: image.Point{X: pos.X + s.X, Y: pos.Y}, 103 Max: image.Point{X: bestSpace.Max.X, Y: pos.Y + s.Y}, 104 }) 105 } 106 idx := len(p.sizes) - 1 107 p.sizes[idx] = bestSize 108 return placement{Idx: idx, Pos: pos}, true 109 }