pack.go 2.3 KB

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