Skip to content
batch.go 4.17 KiB
Newer Older
faiface's avatar
faiface committed
package pixel

import (
faiface's avatar
faiface committed
	"fmt"
faiface's avatar
faiface committed
	"image/color"
)

// Batch is a Target that allows for efficient drawing of many objects with the same Picture (but
// different slices of the same Picture are allowed).
//
// To put an object into a Batch, just draw it onto it:
//   object.Draw(batch)
type Batch struct {
faiface's avatar
faiface committed
	cont Drawer
faiface's avatar
faiface committed

	mat Matrix
faiface's avatar
faiface committed
	col NRGBA
}

faiface's avatar
faiface committed
var _ BasicTarget = (*Batch)(nil)

faiface's avatar
faiface committed
// NewBatch creates an empty Batch with the specified Picture and container.
//
faiface's avatar
faiface committed
// The container is where objects get accumulated. Batch will support precisely those Triangles
// properties, that the supplied container supports. If you retain access to the container and
// change it, call Dirty to notify Batch about the change.
faiface's avatar
faiface committed
//
// Note, that if the container does not support TrianglesColor, color masking will not work.
faiface's avatar
faiface committed
func NewBatch(container Triangles, pic Picture) *Batch {
	b := &Batch{cont: Drawer{Triangles: container, Picture: pic}}
	b.SetMatrix(IM)
	b.SetColorMask(NRGBA{1, 1, 1, 1})
	return b
faiface's avatar
faiface committed
}

faiface's avatar
faiface committed
// Dirty notifies Batch about an external modification of it's container. If you retain access to
// the Batch's container and change it, call Dirty to notify Batch about the change.
//
//   container := &pixel.TrianglesData{}
//   batch := pixel.NewBatch(container, nil)
faiface's avatar
faiface committed
//   container.SetLen(10) // container changed from outside of Batch
faiface's avatar
faiface committed
//   batch.Dirty()        // notify Batch about the change
func (b *Batch) Dirty() {
	b.cont.Dirty()
}

faiface's avatar
faiface committed
// Clear removes all objects from the Batch.
func (b *Batch) Clear() {
faiface's avatar
faiface committed
	b.cont.Triangles.SetLen(0)
	b.cont.Dirty()
faiface's avatar
faiface committed
}

// Draw draws all objects that are currently in the Batch onto another Target.
func (b *Batch) Draw(t Target) {
	b.cont.Draw(t)
}

// SetMatrix sets a Matrix that every point will be projected by.
func (b *Batch) SetMatrix(m Matrix) {
	b.mat = m
faiface's avatar
faiface committed
}

faiface's avatar
faiface committed
// SetColorMask sets a mask color used in the following draws onto the Batch.
func (b *Batch) SetColorMask(c color.Color) {
faiface's avatar
faiface committed
	if c == nil {
		b.col = NRGBA{1, 1, 1, 1}
		return
	}
faiface's avatar
faiface committed
	b.col = ToNRGBA(c)
faiface's avatar
faiface committed
}

faiface's avatar
faiface committed
// MakeTriangles returns a specialized copy of the provided Triangles that draws onto this Batch.
func (b *Batch) MakeTriangles(t Triangles) TargetTriangles {
	bt := &batchTriangles{
		tri: t.Copy(),
		tmp: MakeTrianglesData(t.Len()),
		dst: b,
faiface's avatar
faiface committed
	}
	return bt
}

// MakePicture returns a specialized copy of the provided Picture that draws onto this Batch.
func (b *Batch) MakePicture(p Picture) TargetPicture {
	if p.Original() != b.cont.Picture.Original() {
		panic(fmt.Errorf("(%T).MakePicture: Picture is not a slice of Batch's Picture", b))
	}
	bp := &batchPicture{
		pic: p,
		dst: b,
faiface's avatar
faiface committed
	}
faiface's avatar
faiface committed
	bp.orig = bp
	return bp
faiface's avatar
faiface committed
}

faiface's avatar
faiface committed
type batchTriangles struct {
	tri Triangles
	tmp *TrianglesData
faiface's avatar
faiface committed

faiface's avatar
faiface committed
	dst *Batch
faiface's avatar
faiface committed
}

func (bt *batchTriangles) Len() int {
	return bt.tri.Len()
}

func (bt *batchTriangles) SetLen(len int) {
	bt.tri.SetLen(len)
	bt.tmp.SetLen(len)
}

func (bt *batchTriangles) Slice(i, j int) Triangles {
	return &batchTriangles{
		tri: bt.tri.Slice(i, j),
		tmp: bt.tmp.Slice(i, j).(*TrianglesData),
		dst: bt.dst,
faiface's avatar
faiface committed
	}
func (bt *batchTriangles) Update(t Triangles) {
	bt.tri.Update(t)
}

func (bt *batchTriangles) Copy() Triangles {
	return &batchTriangles{
		tri: bt.tri.Copy(),
		tmp: bt.tmp.Copy().(*TrianglesData),
		dst: bt.dst,
	}
}

func (bt *batchTriangles) draw(bp *batchPicture) {
	bt.tmp.Update(bt.tri)

	for i := range *bt.tmp {
		(*bt.tmp)[i].Position = bt.dst.mat.Project((*bt.tmp)[i].Position)
		(*bt.tmp)[i].Color = bt.dst.col.Mul((*bt.tmp)[i].Color)
	}
faiface's avatar
faiface committed

faiface's avatar
faiface committed
	cont := bt.dst.cont.Triangles
	cont.SetLen(cont.Len() + bt.tri.Len())
	added := cont.Slice(cont.Len()-bt.tri.Len(), cont.Len())
	added.Update(bt.tri)
	added.Update(bt.tmp)
faiface's avatar
faiface committed
	bt.dst.cont.Dirty()
faiface's avatar
faiface committed
}

func (bt *batchTriangles) Draw() {
	bt.draw(nil)
}

type batchPicture struct {
	pic  Picture
faiface's avatar
faiface committed
	orig *batchPicture
	dst  *Batch
func (bp *batchPicture) Bounds() Rect {
	return bp.pic.Bounds()
}

faiface's avatar
faiface committed
func (bp *batchPicture) Slice(r Rect) Picture {
	return &batchPicture{
		pic:  bp.pic.Slice(r),
		orig: bp.orig,
		dst:  bp.dst,
func (bp *batchPicture) Original() Picture {
faiface's avatar
faiface committed
	return bp.orig
faiface's avatar
faiface committed
func (bp *batchPicture) Draw(t TargetTriangles) {
faiface's avatar
faiface committed
	bt := t.(*batchTriangles)
faiface's avatar
faiface committed
	if bp.dst != bt.dst {
		panic(fmt.Errorf("(%T).Draw: TargetTriangles generated by different Batch", bp))
faiface's avatar
faiface committed
	}
	bt.draw(bp)
faiface's avatar
faiface committed
}