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  }