Skip to content
graphics.go 6.66 KiB
Newer Older
faiface's avatar
faiface committed
package pixel

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

faiface's avatar
faiface committed
// Sprite is a drawable Picture. It's always anchored by the center of it's Picture.
type Sprite struct {
	tri    *TrianglesData
	bounds Rect
	d      Drawer
faiface's avatar
faiface committed
}

// NewSprite creates a Sprite from the supplied Picture.
func NewSprite(pic Picture) *Sprite {
	tri := MakeTrianglesData(6)
	s := &Sprite{
		tri: tri,
		d:   Drawer{Triangles: tri},
	}
	s.SetPicture(pic)
	return s
}

// SetPicture changes the Sprite's Picture. The new Picture may have a different size, everything
// works.
func (s *Sprite) SetPicture(pic Picture) {
	s.d.Picture = pic

	if s.bounds == pic.Bounds() {
	s.bounds = pic.Bounds()
faiface's avatar
faiface committed
	var (
		center     = s.bounds.Center()
		horizontal = V(s.bounds.W()/2, 0)
		vertical   = V(0, s.bounds.H()/2)
faiface's avatar
faiface committed
	)
faiface's avatar
faiface committed

faiface's avatar
faiface committed
	(*s.tri)[0].Position = -horizontal - vertical
	(*s.tri)[1].Position = +horizontal - vertical
	(*s.tri)[2].Position = +horizontal + vertical
	(*s.tri)[3].Position = -horizontal - vertical
	(*s.tri)[4].Position = +horizontal + vertical
	(*s.tri)[5].Position = -horizontal + vertical
faiface's avatar
faiface committed

faiface's avatar
faiface committed
	for i := range *s.tri {
		(*s.tri)[i].Color = NRGBA{1, 1, 1, 1}
		(*s.tri)[i].Picture = center + (*s.tri)[i].Position
		(*s.tri)[i].Intensity = 1
	}

	s.d.Dirty()
}

// Picture returns the current Sprite's Picture.
func (s *Sprite) Picture() Picture {
	return s.d.Picture
}

// Draw draws the Sprite onto the provided Target.
func (s *Sprite) Draw(t Target) {
	s.d.Draw(t)
}
faiface's avatar
faiface committed
// IMDraw is an immediate-like-mode shape drawer.
faiface's avatar
faiface committed
//
// TODO: mode doc
faiface's avatar
faiface committed
type IMDraw struct {
faiface's avatar
faiface committed
	points []point
	opts   point
	matrix Matrix
	mask   NRGBA
	tri    *TrianglesData
	d      Drawer
	tmp    []Vec
}

type point struct {
	pos       Vec
	col       NRGBA
	pic       Vec
	in        float64
faiface's avatar
faiface committed
	width     float64
	precision int
	endshape  EndShape
}

// EndShape specifies the shape of an end of a line or a curve.
type EndShape int

const (
	// RoundEndShape is a circular end shape.
	RoundEndShape EndShape = iota

	// SharpEndShape is a square end shape.
	SharpEndShape
)

faiface's avatar
faiface committed
// NewIMDraw creates a new empty IMDraw. An optional Picture can be used to draw with a Picture.
faiface's avatar
faiface committed
//
// If you just want to draw primitive shapes, pass nil as the Picture.
faiface's avatar
faiface committed
func NewIMDraw(pic Picture) *IMDraw {
faiface's avatar
faiface committed
	tri := &TrianglesData{}
faiface's avatar
faiface committed
	im := &IMDraw{
faiface's avatar
faiface committed
		tri: tri,
		d:   Drawer{Triangles: tri, Picture: pic},
	}
	im.Precision(64)
faiface's avatar
faiface committed
	im.SetMatrix(IM)
faiface's avatar
faiface committed
	im.SetColorMask(NRGBA{1, 1, 1, 1})
	return im
}

// Clear removes all drawn shapes from the IM. This does not remove Pushed points.
faiface's avatar
faiface committed
func (imd *IMDraw) Clear() {
	imd.tri.SetLen(0)
	imd.d.Dirty()
faiface's avatar
faiface committed
}

// Draw draws all currently drawn shapes inside the IM onto another Target.
faiface's avatar
faiface committed
func (imd *IMDraw) Draw(t Target) {
	imd.d.Draw(t)
faiface's avatar
faiface committed
}

// Push adds some points to the IM queue. All Pushed points will have the same properties except for
// the position.
faiface's avatar
faiface committed
func (imd *IMDraw) Push(pts ...Vec) {
	point := imd.opts
faiface's avatar
faiface committed
	for _, pt := range pts {
faiface's avatar
faiface committed
		point.pos = imd.matrix.Project(pt)
		point.col = imd.mask.Mul(imd.opts.col)
		imd.points = append(imd.points, point)
faiface's avatar
faiface committed
	}
}

// Color sets the color of the next Pushed points.
faiface's avatar
faiface committed
func (imd *IMDraw) Color(color color.Color) {
	imd.opts.col = NRGBAModel.Convert(color).(NRGBA)
faiface's avatar
faiface committed
}

// Picture sets the Picture coordinates of the next Pushed points.
faiface's avatar
faiface committed
func (imd *IMDraw) Picture(pic Vec) {
	imd.opts.pic = pic
faiface's avatar
faiface committed
}

// Intensity sets the picture Intensity of the next Pushed points.
faiface's avatar
faiface committed
func (imd *IMDraw) Intensity(in float64) {
	imd.opts.in = in
faiface's avatar
faiface committed
}

// Width sets the with property of the next Pushed points.
//
// Note that this property does not apply to filled shapes.
faiface's avatar
faiface committed
func (imd *IMDraw) Width(w float64) {
	imd.opts.width = w
faiface's avatar
faiface committed
}

// Precision sets the curve/circle drawing precision of the next Pushed points.
//
// It is the number of segments per 360 degrees.
faiface's avatar
faiface committed
func (imd *IMDraw) Precision(p int) {
	imd.opts.precision = p
	if p+1 > len(imd.tmp) {
		imd.tmp = append(imd.tmp, make([]Vec, p+1-len(imd.tmp))...)
faiface's avatar
faiface committed
	}
faiface's avatar
faiface committed
	if p+1 < len(imd.tmp) {
		imd.tmp = imd.tmp[:p+1]
faiface's avatar
faiface committed
	}
}

// EndShape sets the endshape of the next Pushed points.
faiface's avatar
faiface committed
func (imd *IMDraw) EndShape(es EndShape) {
	imd.opts.endshape = es
faiface's avatar
faiface committed
}

// SetMatrix sets a Matrix that all further points will be transformed by.
faiface's avatar
faiface committed
func (imd *IMDraw) SetMatrix(m Matrix) {
	imd.matrix = m
faiface's avatar
faiface committed
}

// SetColorMask sets a color that all futher point's color will be multiplied by.
faiface's avatar
faiface committed
func (imd *IMDraw) SetColorMask(color color.Color) {
	imd.mask = NRGBAModel.Convert(color).(NRGBA)
faiface's avatar
faiface committed
}

// FillConvexPolygon takes all points Pushed into the IM's queue and fills the convex polygon formed
// by them.
faiface's avatar
faiface committed
//
// The polygon does not need to be exactly convex. The way it's drawn is that for each two adjacent
// points, a triangle is constructed from those two points and the first Pushed point. You can use
faiface's avatar
faiface committed
// this property to draw specific concave polygons.
faiface's avatar
faiface committed
func (imd *IMDraw) FillConvexPolygon() {
	points := imd.points
	imd.points = nil
faiface's avatar
faiface committed

	if len(points) < 3 {
		return
	}

faiface's avatar
faiface committed
	i := imd.tri.Len()
	imd.tri.SetLen(imd.tri.Len() + 3*(len(points)-2))
faiface's avatar
faiface committed

	for j := 1; j+1 < len(points); j++ {
faiface's avatar
faiface committed
		(*imd.tri)[i].Position = points[0].pos
		(*imd.tri)[i].Color = points[0].col
		(*imd.tri)[i].Picture = points[0].pic
		(*imd.tri)[i].Intensity = points[0].in
faiface's avatar
faiface committed
		(*imd.tri)[i+1].Position = points[j].pos
		(*imd.tri)[i+1].Color = points[j].col
		(*imd.tri)[i+1].Picture = points[j].pic
		(*imd.tri)[i+1].Intensity = points[j].in
faiface's avatar
faiface committed
		(*imd.tri)[i+2].Position = points[j+1].pos
		(*imd.tri)[i+2].Color = points[j+1].col
		(*imd.tri)[i+2].Picture = points[j+1].pic
		(*imd.tri)[i+2].Intensity = points[j+1].in
faiface's avatar
faiface committed
	imd.d.Dirty()
// FillCircle draws a filled circle around each point in the IM's queue.
faiface's avatar
faiface committed
func (imd *IMDraw) FillCircle(radius float64) {
	imd.FillEllipseArc(V(radius, radius), 0, 2*math.Pi)
faiface's avatar
faiface committed
}

// FillCircleArc draws a filled circle arc around each point in the IM's queue.
faiface's avatar
faiface committed
func (imd *IMDraw) FillCircleArc(radius, low, high float64) {
	imd.FillEllipseArc(V(radius, radius), low, high)
}

// FillEllipse draws a filled ellipse around each point in the IM's queue.
faiface's avatar
faiface committed
func (imd *IMDraw) FillEllipse(radius Vec) {
	imd.FillEllipseArc(radius, 0, 2*math.Pi)
}

// FillEllipseArc draws a filled ellipse arc around each point in the IM's queue. Low and high
// angles are in radians.
faiface's avatar
faiface committed
func (imd *IMDraw) FillEllipseArc(radius Vec, low, high float64) {
	points := imd.points
	imd.points = nil
faiface's avatar
faiface committed

	// normalize high
	if math.Abs(high-low) > 2*math.Pi {
		high = low + math.Mod(high-low, 2*math.Pi)
	}

	for _, pt := range points {
faiface's avatar
faiface committed
		imd.Push(pt.pos) // center
faiface's avatar
faiface committed

		num := math.Ceil(math.Abs(high-low) / (2 * math.Pi) * float64(pt.precision))
		delta := (high - low) / num
faiface's avatar
faiface committed
		for i := range imd.tmp[:int(num)+1] {
faiface's avatar
faiface committed
			angle := low + float64(i)*delta
			sin, cos := math.Sincos(angle)
faiface's avatar
faiface committed
			imd.tmp[i] = pt.pos + V(
faiface's avatar
faiface committed
				radius.X()*cos,
				radius.Y()*sin,
			)
		}

faiface's avatar
faiface committed
		imd.Push(imd.tmp[:int(num)+1]...)
		imd.FillConvexPolygon()