123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109 |
- // SPDX-License-Identifier: Unlicense OR MIT
- package gpu
- import (
- "image"
- )
- // packer packs a set of many smaller rectangles into
- // much fewer larger atlases.
- type packer struct {
- maxDims image.Point
- spaces []image.Rectangle
- sizes []image.Point
- pos image.Point
- }
- type placement struct {
- Idx int
- Pos image.Point
- }
- // add adds the given rectangle to the atlases and
- // return the allocated position.
- func (p *packer) add(s image.Point) (placement, bool) {
- if place, ok := p.tryAdd(s); ok {
- return place, true
- }
- p.newPage()
- return p.tryAdd(s)
- }
- func (p *packer) clear() {
- p.sizes = p.sizes[:0]
- p.spaces = p.spaces[:0]
- }
- func (p *packer) newPage() {
- p.pos = image.Point{}
- p.sizes = append(p.sizes, image.Point{})
- p.spaces = p.spaces[:0]
- p.spaces = append(p.spaces, image.Rectangle{
- Max: image.Point{X: 1e6, Y: 1e6},
- })
- }
- func (p *packer) tryAdd(s image.Point) (placement, bool) {
- if len(p.spaces) == 0 || len(p.sizes) == 0 {
- return placement{}, false
- }
- var (
- bestIdx *image.Rectangle
- bestSize = p.maxDims
- lastSize = p.sizes[len(p.sizes)-1]
- )
- // Go backwards to prioritize smaller spaces.
- for i := range p.spaces {
- space := &p.spaces[i]
- rightSpace := space.Dx() - s.X
- bottomSpace := space.Dy() - s.Y
- if rightSpace < 0 || bottomSpace < 0 {
- continue
- }
- size := lastSize
- if x := space.Min.X + s.X; x > size.X {
- if x > p.maxDims.X {
- continue
- }
- size.X = x
- }
- if y := space.Min.Y + s.Y; y > size.Y {
- if y > p.maxDims.Y {
- continue
- }
- size.Y = y
- }
- if size.X*size.Y < bestSize.X*bestSize.Y {
- bestIdx = space
- bestSize = size
- }
- }
- if bestIdx == nil {
- return placement{}, false
- }
- // Remove space.
- bestSpace := *bestIdx
- *bestIdx = p.spaces[len(p.spaces)-1]
- p.spaces = p.spaces[:len(p.spaces)-1]
- // Put s in the top left corner and add the (at most)
- // two smaller spaces.
- pos := bestSpace.Min
- if rem := bestSpace.Dy() - s.Y; rem > 0 {
- p.spaces = append(p.spaces, image.Rectangle{
- Min: image.Point{X: pos.X, Y: pos.Y + s.Y},
- Max: image.Point{X: bestSpace.Max.X, Y: bestSpace.Max.Y},
- })
- }
- if rem := bestSpace.Dx() - s.X; rem > 0 {
- p.spaces = append(p.spaces, image.Rectangle{
- Min: image.Point{X: pos.X + s.X, Y: pos.Y},
- Max: image.Point{X: bestSpace.Max.X, Y: pos.Y + s.Y},
- })
- }
- idx := len(p.sizes) - 1
- p.sizes[idx] = bestSize
- return placement{Idx: idx, Pos: pos}, true
- }
|