From 5e698c63f0029dbda564c77e30162dcab7c153da Mon Sep 17 00:00:00 2001
From: faiface <faiface@ksp.sk>
Date: Tue, 21 Mar 2017 11:33:11 +0100
Subject: [PATCH] move IMDraw to separate package

---
 graphics.go      | 526 ----------------------------------------------
 imdraw/imdraw.go | 529 +++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 529 insertions(+), 526 deletions(-)
 create mode 100644 imdraw/imdraw.go

diff --git a/graphics.go b/graphics.go
index b186c8a..f90fa90 100644
--- a/graphics.go
+++ b/graphics.go
@@ -1,10 +1,5 @@
 package pixel
 
-import (
-	"image/color"
-	"math"
-)
-
 // Sprite is a drawable Picture. It's always anchored by the center of it's Picture.
 type Sprite struct {
 	tri    *TrianglesData
@@ -64,524 +59,3 @@ func (s *Sprite) Picture() Picture {
 func (s *Sprite) Draw(t Target) {
 	s.d.Draw(t)
 }
-
-// IMDraw is an immediate-like-mode shape drawer and BasicTarget. IMDraw supports TrianglesPosition,
-// TrianglesColor, TrianglesPicture and PictureColor.
-//
-// IMDraw, other than a regular BasicTarget, is used to draw shapes. To draw shapes, you first need
-// to Push some points to IMDraw:
-//
-//   imd := pixel.NewIMDraw(pic) // use nil pic if you only want to draw primitive shapes
-//   imd.Push(pixel.V(100, 100))
-//   imd.Push(pixel.V(500, 100))
-//
-// Once you have Pushed some points, you can use them to draw a shape, such as a line:
-//
-//   imd.Line(20) // draws a 20 units thick line
-//
-// Use various methods to change properties of Pushed points:
-//
-//   imd.Color(pixel.NRGBA{R: 1, G: 0, B: 0, A: 1})
-//   imd.Push(pixel.V(200, 200))
-//   imd.Circle(400, 0)
-type IMDraw struct {
-	points []point
-	opts   point
-	matrix Matrix
-	mask   NRGBA
-
-	tri   *TrianglesData
-	batch *Batch
-}
-
-var _ BasicTarget = (*IMDraw)(nil)
-
-type point struct {
-	pos       Vec
-	col       NRGBA
-	pic       Vec
-	in        float64
-	precision int
-	endshape  EndShape
-}
-
-// EndShape specifies the shape of an end of a line or a curve.
-type EndShape int
-
-const (
-	// NoEndShape leaves a line point with no special end shape.
-	NoEndShape EndShape = iota
-
-	// SharpEndShape is a sharp triangular end shape.
-	SharpEndShape
-
-	// RoundEndShape is a circular end shape.
-	RoundEndShape
-)
-
-// NewIMDraw creates a new empty IMDraw. An optional Picture can be used to draw with a Picture.
-//
-// If you just want to draw primitive shapes, pass nil as the Picture.
-func NewIMDraw(pic Picture) *IMDraw {
-	tri := &TrianglesData{}
-	im := &IMDraw{
-		tri:   tri,
-		batch: NewBatch(tri, pic),
-	}
-	im.SetMatrix(IM)
-	im.SetColorMask(NRGBA{1, 1, 1, 1})
-	im.Reset()
-	return im
-}
-
-// Clear removes all drawn shapes from the IM. This does not remove Pushed points.
-func (imd *IMDraw) Clear() {
-	imd.tri.SetLen(0)
-	imd.batch.Dirty()
-}
-
-// Reset restores all point properties to defaults and removes all Pushed points.
-//
-// This does not affect matrix and color mask set by SetMatrix and SetColorMask.
-func (imd *IMDraw) Reset() {
-	imd.points = nil
-	imd.opts = point{}
-	imd.Precision(64)
-}
-
-// Draw draws all currently drawn shapes inside the IM onto another Target.
-func (imd *IMDraw) Draw(t Target) {
-	imd.batch.Draw(t)
-}
-
-// Push adds some points to the IM queue. All Pushed points will have the same properties except for
-// the position.
-func (imd *IMDraw) Push(pts ...Vec) {
-	for _, pt := range pts {
-		imd.pushPt(pt, imd.opts)
-	}
-}
-
-func (imd *IMDraw) pushPt(pos Vec, pt point) {
-	pt.pos = pos
-	imd.points = append(imd.points, pt)
-}
-
-// Color sets the color of the next Pushed points.
-func (imd *IMDraw) Color(color color.Color) {
-	imd.opts.col = NRGBAModel.Convert(color).(NRGBA)
-}
-
-// Picture sets the Picture coordinates of the next Pushed points.
-func (imd *IMDraw) Picture(pic Vec) {
-	imd.opts.pic = pic
-}
-
-// Intensity sets the picture Intensity of the next Pushed points.
-func (imd *IMDraw) Intensity(in float64) {
-	imd.opts.in = in
-}
-
-// Precision sets the curve/circle drawing precision of the next Pushed points.
-//
-// It is the number of segments per 360 degrees.
-func (imd *IMDraw) Precision(p int) {
-	imd.opts.precision = p
-}
-
-// EndShape sets the endshape of the next Pushed points.
-func (imd *IMDraw) EndShape(es EndShape) {
-	imd.opts.endshape = es
-}
-
-// SetMatrix sets a Matrix that all further points will be transformed by.
-func (imd *IMDraw) SetMatrix(m Matrix) {
-	imd.matrix = m
-	imd.batch.SetMatrix(imd.matrix)
-}
-
-// SetColorMask sets a color that all further point's color will be multiplied by.
-func (imd *IMDraw) SetColorMask(color color.Color) {
-	imd.mask = NRGBAModel.Convert(color).(NRGBA)
-	imd.batch.SetColorMask(imd.mask)
-}
-
-// MakeTriangles returns a specialized copy of the provided Triangles that draws onto this IMDraw.
-func (imd *IMDraw) MakeTriangles(t Triangles) TargetTriangles {
-	return imd.batch.MakeTriangles(t)
-}
-
-// MakePicture returns a specialized copy of the provided Picture that draws onto this IMDraw.
-func (imd *IMDraw) MakePicture(p Picture) TargetPicture {
-	return imd.batch.MakePicture(p)
-}
-
-// Polygon draws a polygon from the Pushed points. If the thickness is 0, the convex polygon will be
-// filled. Otherwise, an outline of the specified thickness will be drawn. The outline does not have
-// to be convex.
-//
-// Note, that the filled polygon does not have to be strictly convex. The way it's drawn is that a
-// triangle is drawn between each two adjacent points and the first Pushed point. You can use this
-// property to draw certain kinds of concave polygons.
-func (imd *IMDraw) Polygon(thickness float64) {
-	if thickness == 0 {
-		imd.fillPolygon()
-	} else {
-		imd.polyline(thickness, true)
-	}
-}
-
-// Circle draws a circle of the specified radius around each Pushed point. If the thickness is 0,
-// the circle will be filled, otherwise a circle outline of the specified thickness will be drawn.
-func (imd *IMDraw) Circle(radius, thickness float64) {
-	if thickness == 0 {
-		imd.fillEllipseArc(V(radius, radius), 0, 2*math.Pi)
-	} else {
-		imd.outlineEllipseArc(V(radius, radius), 0, 2*math.Pi, thickness, false)
-	}
-}
-
-// CircleArc draws a circle arc of the specified radius around each Pushed point. If the thickness
-// is 0, the arc will be filled, otherwise will be outlined. The arc starts at the low angle and
-// continues to the high angle. If low<high, the arc will be drawn counterclockwise. Otherwise it
-// will be clockwise. The angles are not normalized by any means.
-//
-//   imd.CircleArc(40, 0, 8*math.Pi, 0)
-//
-// This line will fill the whole circle 4 times.
-func (imd *IMDraw) CircleArc(radius, low, high, thickness float64) {
-	if thickness == 0 {
-		imd.fillEllipseArc(V(radius, radius), low, high)
-	} else {
-		imd.outlineEllipseArc(V(radius, radius), low, high, thickness, true)
-	}
-}
-
-// Ellipse draws an ellipse of the specified radius in each axis around each Pushed points. If the
-// thickness is 0, the ellipse will be filled, otherwise an ellipse outline of the specified
-// thickness will be drawn.
-func (imd *IMDraw) Ellipse(radius Vec, thickness float64) {
-	if thickness == 0 {
-		imd.fillEllipseArc(radius, 0, 2*math.Pi)
-	} else {
-		imd.outlineEllipseArc(radius, 0, 2*math.Pi, thickness, false)
-	}
-}
-
-// EllipseArc draws an ellipse arc of the specified radius in each axis around each Pushed point. If
-// the thickness is 0, the arc will be filled, otherwise will be outlined. The arc starts at the low
-// angle and continues to the high angle. If low<high, the arc will be drawn counterclockwise.
-// Otherwise it will be clockwise. The angles are not normalized by any means.
-//
-//   imd.EllipseArc(pixel.V(100, 50), 0, 8*math.Pi, 0)
-//
-// This line will fill the whole ellipse 4 times.
-func (imd *IMDraw) EllipseArc(radius Vec, low, high, thickness float64) {
-	if thickness == 0 {
-		imd.fillEllipseArc(radius, low, high)
-	} else {
-		imd.outlineEllipseArc(radius, low, high, thickness, true)
-	}
-}
-
-// Line draws a polyline of the specified thickness between the Pushed points.
-func (imd *IMDraw) Line(thickness float64) {
-	imd.polyline(thickness, false)
-}
-
-func (imd *IMDraw) getAndClearPoints() []point {
-	points := imd.points
-	imd.points = nil
-	return points
-}
-
-func (imd *IMDraw) applyMatrixAndMask(off int) {
-	for i := range (*imd.tri)[off:] {
-		(*imd.tri)[off+i].Position = imd.matrix.Project((*imd.tri)[off+i].Position)
-		(*imd.tri)[off+i].Color = imd.mask.Mul((*imd.tri)[off+i].Color)
-	}
-}
-
-func (imd *IMDraw) fillPolygon() {
-	points := imd.getAndClearPoints()
-
-	if len(points) < 3 {
-		return
-	}
-
-	off := imd.tri.Len()
-	imd.tri.SetLen(imd.tri.Len() + 3*(len(points)-2))
-
-	for i, j := 1, off; i+1 < len(points); i, j = i+1, j+3 {
-		(*imd.tri)[j+0].Position = points[0].pos
-		(*imd.tri)[j+0].Color = points[0].col
-		(*imd.tri)[j+0].Picture = points[0].pic
-		(*imd.tri)[j+0].Intensity = points[0].in
-
-		(*imd.tri)[j+1].Position = points[i].pos
-		(*imd.tri)[j+1].Color = points[i].col
-		(*imd.tri)[j+1].Picture = points[i].pic
-		(*imd.tri)[j+1].Intensity = points[i].in
-
-		(*imd.tri)[j+2].Position = points[i+1].pos
-		(*imd.tri)[j+2].Color = points[i+1].col
-		(*imd.tri)[j+2].Picture = points[i+1].pic
-		(*imd.tri)[j+2].Intensity = points[i+1].in
-	}
-
-	imd.applyMatrixAndMask(off)
-	imd.batch.Dirty()
-}
-
-func (imd *IMDraw) fillEllipseArc(radius Vec, low, high float64) {
-	points := imd.getAndClearPoints()
-
-	for _, pt := range points {
-		num := math.Ceil(math.Abs(high-low) / (2 * math.Pi) * float64(pt.precision))
-		delta := (high - low) / num
-
-		off := imd.tri.Len()
-		imd.tri.SetLen(imd.tri.Len() + 3*int(num))
-
-		for i := range (*imd.tri)[off:] {
-			(*imd.tri)[off+i].Color = pt.col
-			(*imd.tri)[off+i].Picture = 0
-			(*imd.tri)[off+i].Intensity = 0
-		}
-
-		for i, j := 0.0, off; i < num; i, j = i+1, j+3 {
-			angle := low + i*delta
-			sin, cos := math.Sincos(angle)
-			a := pt.pos + V(
-				radius.X()*cos,
-				radius.Y()*sin,
-			)
-
-			angle = low + (i+1)*delta
-			sin, cos = math.Sincos(angle)
-			b := pt.pos + V(
-				radius.X()*cos,
-				radius.Y()*sin,
-			)
-
-			(*imd.tri)[j+0].Position = pt.pos
-			(*imd.tri)[j+1].Position = a
-			(*imd.tri)[j+2].Position = b
-		}
-
-		imd.applyMatrixAndMask(off)
-		imd.batch.Dirty()
-	}
-}
-
-func (imd *IMDraw) outlineEllipseArc(radius Vec, low, high, thickness float64, doEndShape bool) {
-	points := imd.getAndClearPoints()
-
-	for _, pt := range points {
-		num := math.Ceil(math.Abs(high-low) / (2 * math.Pi) * float64(pt.precision))
-		delta := (high - low) / num
-
-		off := imd.tri.Len()
-		imd.tri.SetLen(imd.tri.Len() + 6*int(num))
-
-		for i := range (*imd.tri)[off:] {
-			(*imd.tri)[off+i].Color = pt.col
-			(*imd.tri)[off+i].Picture = 0
-			(*imd.tri)[off+i].Intensity = 0
-		}
-
-		for i, j := 0.0, off; i < num; i, j = i+1, j+6 {
-			angle := low + i*delta
-			sin, cos := math.Sincos(angle)
-			normalSin, normalCos := V(sin, cos).ScaledXY(radius).Unit().XY()
-			a := pt.pos + V(
-				radius.X()*cos-thickness/2*normalCos,
-				radius.Y()*sin-thickness/2*normalSin,
-			)
-			b := pt.pos + V(
-				radius.X()*cos+thickness/2*normalCos,
-				radius.Y()*sin+thickness/2*normalSin,
-			)
-
-			angle = low + (i+1)*delta
-			sin, cos = math.Sincos(angle)
-			normalSin, normalCos = V(sin, cos).ScaledXY(radius).Unit().XY()
-			c := pt.pos + V(
-				radius.X()*cos-thickness/2*normalCos,
-				radius.Y()*sin-thickness/2*normalSin,
-			)
-			d := pt.pos + V(
-				radius.X()*cos+thickness/2*normalCos,
-				radius.Y()*sin+thickness/2*normalSin,
-			)
-
-			(*imd.tri)[j+0].Position = a
-			(*imd.tri)[j+1].Position = b
-			(*imd.tri)[j+2].Position = c
-			(*imd.tri)[j+3].Position = c
-			(*imd.tri)[j+4].Position = b
-			(*imd.tri)[j+5].Position = d
-		}
-
-		imd.applyMatrixAndMask(off)
-		imd.batch.Dirty()
-
-		if doEndShape {
-			lowSin, lowCos := math.Sincos(low)
-			lowCenter := pt.pos + V(
-				radius.X()*lowCos,
-				radius.Y()*lowSin,
-			)
-			normalLowSin, normalLowCos := V(lowSin, lowCos).ScaledXY(radius).Unit().XY()
-			normalLow := V(normalLowCos, normalLowSin).Angle()
-
-			highSin, highCos := math.Sincos(high)
-			highCenter := pt.pos + V(
-				radius.X()*highCos,
-				radius.Y()*highSin,
-			)
-			normalHighSin, normalHighCos := V(highSin, highCos).ScaledXY(radius).Unit().XY()
-			normalHigh := V(normalHighCos, normalHighSin).Angle()
-
-			orientation := 1.0
-			if low > high {
-				orientation = -1.0
-			}
-
-			switch pt.endshape {
-			case NoEndShape:
-				// nothing
-			case SharpEndShape:
-				thick := X(thickness / 2).Rotated(normalLow)
-				imd.pushPt(lowCenter+thick, pt)
-				imd.pushPt(lowCenter-thick, pt)
-				imd.pushPt(lowCenter-thick.Rotated(math.Pi/2*orientation), pt)
-				imd.fillPolygon()
-				thick = X(thickness / 2).Rotated(normalHigh)
-				imd.pushPt(highCenter+thick, pt)
-				imd.pushPt(highCenter-thick, pt)
-				imd.pushPt(highCenter+thick.Rotated(math.Pi/2*orientation), pt)
-				imd.fillPolygon()
-			case RoundEndShape:
-				imd.pushPt(lowCenter, pt)
-				imd.fillEllipseArc(V(thickness, thickness)/2, normalLow, normalLow-math.Pi*orientation)
-				imd.pushPt(highCenter, pt)
-				imd.fillEllipseArc(V(thickness, thickness)/2, normalHigh, normalHigh+math.Pi*orientation)
-			}
-		}
-	}
-}
-
-func (imd *IMDraw) polyline(thickness float64, closed bool) {
-	points := imd.getAndClearPoints()
-
-	// filter identical adjacent points
-	filtered := points[:0]
-	for i := 0; i < len(points); i++ {
-		if closed || i+1 < len(points) {
-			j := (i + 1) % len(points)
-			if points[i].pos != points[j].pos {
-				filtered = append(filtered, points[i])
-			}
-		}
-	}
-	points = filtered
-
-	if len(points) < 2 {
-		return
-	}
-
-	// first point
-	j, i := 0, 1
-	normal := (points[i].pos - points[j].pos).Rotated(math.Pi / 2).Unit().Scaled(thickness / 2)
-
-	if !closed {
-		switch points[j].endshape {
-		case NoEndShape:
-			// nothing
-		case SharpEndShape:
-			imd.pushPt(points[j].pos+normal, points[j])
-			imd.pushPt(points[j].pos-normal, points[j])
-			imd.pushPt(points[j].pos+normal.Rotated(math.Pi/2), points[j])
-			imd.fillPolygon()
-		case RoundEndShape:
-			imd.pushPt(points[j].pos, points[j])
-			imd.fillEllipseArc(V(thickness, thickness)/2, normal.Angle(), normal.Angle()+math.Pi)
-		}
-	}
-
-	imd.pushPt(points[j].pos+normal, points[j])
-	imd.pushPt(points[j].pos-normal, points[j])
-
-	// middle points
-	for i := 0; i < len(points); i++ {
-		j, k := i+1, i+2
-
-		closing := false
-		if j >= len(points) {
-			if !closed {
-				break
-			}
-			j %= len(points)
-			closing = true
-		}
-		if k >= len(points) {
-			k %= len(points)
-		}
-
-		ijNormal := (points[j].pos - points[i].pos).Rotated(math.Pi / 2).Unit().Scaled(thickness / 2)
-		jkNormal := (points[k].pos - points[j].pos).Rotated(math.Pi / 2).Unit().Scaled(thickness / 2)
-
-		orientation := 1.0
-		if ijNormal.Cross(jkNormal) > 0 {
-			orientation = -1.0
-		}
-
-		imd.pushPt(points[j].pos-ijNormal, points[j])
-		imd.pushPt(points[j].pos+ijNormal, points[j])
-		imd.fillPolygon()
-
-		switch points[j].endshape {
-		case NoEndShape:
-			// nothing
-		case SharpEndShape:
-			imd.pushPt(points[j].pos, points[j])
-			imd.pushPt(points[j].pos+ijNormal.Scaled(orientation), points[j])
-			imd.pushPt(points[j].pos+jkNormal.Scaled(orientation), points[j])
-			imd.fillPolygon()
-		case RoundEndShape:
-			imd.pushPt(points[j].pos, points[j])
-			imd.fillEllipseArc(V(thickness, thickness)/2, ijNormal.Angle(), ijNormal.Angle()-math.Pi)
-			imd.pushPt(points[j].pos, points[j])
-			imd.fillEllipseArc(V(thickness, thickness)/2, jkNormal.Angle(), jkNormal.Angle()+math.Pi)
-		}
-
-		if !closing {
-			imd.pushPt(points[j].pos+jkNormal, points[j])
-			imd.pushPt(points[j].pos-jkNormal, points[j])
-		}
-	}
-
-	// last point
-	i, j = len(points)-2, len(points)-1
-	normal = (points[j].pos - points[i].pos).Rotated(math.Pi / 2).Unit().Scaled(thickness / 2)
-
-	imd.pushPt(points[j].pos-normal, points[j])
-	imd.pushPt(points[j].pos+normal, points[j])
-	imd.fillPolygon()
-
-	if !closed {
-		switch points[j].endshape {
-		case NoEndShape:
-			// nothing
-		case SharpEndShape:
-			imd.pushPt(points[j].pos+normal, points[j])
-			imd.pushPt(points[j].pos-normal, points[j])
-			imd.pushPt(points[j].pos+normal.Rotated(-math.Pi/2), points[j])
-			imd.fillPolygon()
-		case RoundEndShape:
-			imd.pushPt(points[j].pos, points[j])
-			imd.fillEllipseArc(V(thickness, thickness)/2, normal.Angle(), normal.Angle()-math.Pi)
-		}
-	}
-}
diff --git a/imdraw/imdraw.go b/imdraw/imdraw.go
new file mode 100644
index 0000000..321c766
--- /dev/null
+++ b/imdraw/imdraw.go
@@ -0,0 +1,529 @@
+package imdraw
+
+import (
+	"image/color"
+	"math"
+
+	"github.com/faiface/pixel"
+)
+
+// IMDraw is an immediate-like-mode shape drawer and BasicTarget. IMDraw supports TrianglesPosition,
+// TrianglesColor, TrianglesPicture and PictureColor.
+//
+// IMDraw, other than a regular BasicTarget, is used to draw shapes. To draw shapes, you first need
+// to Push some points to IMDraw:
+//
+//   imd := pixel.NewIMDraw(pic) // use nil pic if you only want to draw primitive shapes
+//   imd.Push(pixel.V(100, 100))
+//   imd.Push(pixel.V(500, 100))
+//
+// Once you have Pushed some points, you can use them to draw a shape, such as a line:
+//
+//   imd.Line(20) // draws a 20 units thick line
+//
+// Use various methods to change properties of Pushed points:
+//
+//   imd.Color(pixel.NRGBA{R: 1, G: 0, B: 0, A: 1})
+//   imd.Push(pixel.V(200, 200))
+//   imd.Circle(400, 0)
+type IMDraw struct {
+	points []point
+	opts   point
+	matrix pixel.Matrix
+	mask   pixel.NRGBA
+
+	tri   *pixel.TrianglesData
+	batch *pixel.Batch
+}
+
+var _ pixel.BasicTarget = (*IMDraw)(nil)
+
+type point struct {
+	pos       pixel.Vec
+	col       pixel.NRGBA
+	pic       pixel.Vec
+	in        float64
+	precision int
+	endshape  EndShape
+}
+
+// EndShape specifies the shape of an end of a line or a curve.
+type EndShape int
+
+const (
+	// NoEndShape leaves a line point with no special end shape.
+	NoEndShape EndShape = iota
+
+	// SharpEndShape is a sharp triangular end shape.
+	SharpEndShape
+
+	// RoundEndShape is a circular end shape.
+	RoundEndShape
+)
+
+// New creates a new empty IMDraw. An optional Picture can be used to draw with a Picture.
+//
+// If you just want to draw primitive shapes, pass nil as the Picture.
+func New(pic pixel.Picture) *IMDraw {
+	tri := &pixel.TrianglesData{}
+	im := &IMDraw{
+		tri:   tri,
+		batch: pixel.NewBatch(tri, pic),
+	}
+	im.SetMatrix(pixel.IM)
+	im.SetColorMask(pixel.NRGBA{R: 1, G: 1, B: 1, A: 1})
+	im.Reset()
+	return im
+}
+
+// Clear removes all drawn shapes from the IM. This does not remove Pushed points.
+func (imd *IMDraw) Clear() {
+	imd.tri.SetLen(0)
+	imd.batch.Dirty()
+}
+
+// Reset restores all point properties to defaults and removes all Pushed points.
+//
+// This does not affect matrix and color mask set by SetMatrix and SetColorMask.
+func (imd *IMDraw) Reset() {
+	imd.points = nil
+	imd.opts = point{}
+	imd.Precision(64)
+}
+
+// Draw draws all currently drawn shapes inside the IM onto another Target.
+func (imd *IMDraw) Draw(t pixel.Target) {
+	imd.batch.Draw(t)
+}
+
+// Push adds some points to the IM queue. All Pushed points will have the same properties except for
+// the position.
+func (imd *IMDraw) Push(pts ...pixel.Vec) {
+	for _, pt := range pts {
+		imd.pushPt(pt, imd.opts)
+	}
+}
+
+func (imd *IMDraw) pushPt(pos pixel.Vec, pt point) {
+	pt.pos = pos
+	imd.points = append(imd.points, pt)
+}
+
+// Color sets the color of the next Pushed points.
+func (imd *IMDraw) Color(color color.Color) {
+	imd.opts.col = pixel.NRGBAModel.Convert(color).(pixel.NRGBA)
+}
+
+// Picture sets the Picture coordinates of the next Pushed points.
+func (imd *IMDraw) Picture(pic pixel.Vec) {
+	imd.opts.pic = pic
+}
+
+// Intensity sets the picture Intensity of the next Pushed points.
+func (imd *IMDraw) Intensity(in float64) {
+	imd.opts.in = in
+}
+
+// Precision sets the curve/circle drawing precision of the next Pushed points.
+//
+// It is the number of segments per 360 degrees.
+func (imd *IMDraw) Precision(p int) {
+	imd.opts.precision = p
+}
+
+// EndShape sets the endshape of the next Pushed points.
+func (imd *IMDraw) EndShape(es EndShape) {
+	imd.opts.endshape = es
+}
+
+// SetMatrix sets a Matrix that all further points will be transformed by.
+func (imd *IMDraw) SetMatrix(m pixel.Matrix) {
+	imd.matrix = m
+	imd.batch.SetMatrix(imd.matrix)
+}
+
+// SetColorMask sets a color that all further point's color will be multiplied by.
+func (imd *IMDraw) SetColorMask(color color.Color) {
+	imd.mask = pixel.NRGBAModel.Convert(color).(pixel.NRGBA)
+	imd.batch.SetColorMask(imd.mask)
+}
+
+// MakeTriangles returns a specialized copy of the provided Triangles that draws onto this IMDraw.
+func (imd *IMDraw) MakeTriangles(t pixel.Triangles) pixel.TargetTriangles {
+	return imd.batch.MakeTriangles(t)
+}
+
+// MakePicture returns a specialized copy of the provided Picture that draws onto this IMDraw.
+func (imd *IMDraw) MakePicture(p pixel.Picture) pixel.TargetPicture {
+	return imd.batch.MakePicture(p)
+}
+
+// Polygon draws a polygon from the Pushed points. If the thickness is 0, the convex polygon will be
+// filled. Otherwise, an outline of the specified thickness will be drawn. The outline does not have
+// to be convex.
+//
+// Note, that the filled polygon does not have to be strictly convex. The way it's drawn is that a
+// triangle is drawn between each two adjacent points and the first Pushed point. You can use this
+// property to draw certain kinds of concave polygons.
+func (imd *IMDraw) Polygon(thickness float64) {
+	if thickness == 0 {
+		imd.fillPolygon()
+	} else {
+		imd.polyline(thickness, true)
+	}
+}
+
+// Circle draws a circle of the specified radius around each Pushed point. If the thickness is 0,
+// the circle will be filled, otherwise a circle outline of the specified thickness will be drawn.
+func (imd *IMDraw) Circle(radius, thickness float64) {
+	if thickness == 0 {
+		imd.fillEllipseArc(pixel.V(radius, radius), 0, 2*math.Pi)
+	} else {
+		imd.outlineEllipseArc(pixel.V(radius, radius), 0, 2*math.Pi, thickness, false)
+	}
+}
+
+// CircleArc draws a circle arc of the specified radius around each Pushed point. If the thickness
+// is 0, the arc will be filled, otherwise will be outlined. The arc starts at the low angle and
+// continues to the high angle. If low<high, the arc will be drawn counterclockwise. Otherwise it
+// will be clockwise. The angles are not normalized by any means.
+//
+//   imd.CircleArc(40, 0, 8*math.Pi, 0)
+//
+// This line will fill the whole circle 4 times.
+func (imd *IMDraw) CircleArc(radius, low, high, thickness float64) {
+	if thickness == 0 {
+		imd.fillEllipseArc(pixel.V(radius, radius), low, high)
+	} else {
+		imd.outlineEllipseArc(pixel.V(radius, radius), low, high, thickness, true)
+	}
+}
+
+// Ellipse draws an ellipse of the specified radius in each axis around each Pushed points. If the
+// thickness is 0, the ellipse will be filled, otherwise an ellipse outline of the specified
+// thickness will be drawn.
+func (imd *IMDraw) Ellipse(radius pixel.Vec, thickness float64) {
+	if thickness == 0 {
+		imd.fillEllipseArc(radius, 0, 2*math.Pi)
+	} else {
+		imd.outlineEllipseArc(radius, 0, 2*math.Pi, thickness, false)
+	}
+}
+
+// EllipseArc draws an ellipse arc of the specified radius in each axis around each Pushed point. If
+// the thickness is 0, the arc will be filled, otherwise will be outlined. The arc starts at the low
+// angle and continues to the high angle. If low<high, the arc will be drawn counterclockwise.
+// Otherwise it will be clockwise. The angles are not normalized by any means.
+//
+//   imd.EllipseArc(pixel.V(100, 50), 0, 8*math.Pi, 0)
+//
+// This line will fill the whole ellipse 4 times.
+func (imd *IMDraw) EllipseArc(radius pixel.Vec, low, high, thickness float64) {
+	if thickness == 0 {
+		imd.fillEllipseArc(radius, low, high)
+	} else {
+		imd.outlineEllipseArc(radius, low, high, thickness, true)
+	}
+}
+
+// Line draws a polyline of the specified thickness between the Pushed points.
+func (imd *IMDraw) Line(thickness float64) {
+	imd.polyline(thickness, false)
+}
+
+func (imd *IMDraw) getAndClearPoints() []point {
+	points := imd.points
+	imd.points = nil
+	return points
+}
+
+func (imd *IMDraw) applyMatrixAndMask(off int) {
+	for i := range (*imd.tri)[off:] {
+		(*imd.tri)[off+i].Position = imd.matrix.Project((*imd.tri)[off+i].Position)
+		(*imd.tri)[off+i].Color = imd.mask.Mul((*imd.tri)[off+i].Color)
+	}
+}
+
+func (imd *IMDraw) fillPolygon() {
+	points := imd.getAndClearPoints()
+
+	if len(points) < 3 {
+		return
+	}
+
+	off := imd.tri.Len()
+	imd.tri.SetLen(imd.tri.Len() + 3*(len(points)-2))
+
+	for i, j := 1, off; i+1 < len(points); i, j = i+1, j+3 {
+		(*imd.tri)[j+0].Position = points[0].pos
+		(*imd.tri)[j+0].Color = points[0].col
+		(*imd.tri)[j+0].Picture = points[0].pic
+		(*imd.tri)[j+0].Intensity = points[0].in
+
+		(*imd.tri)[j+1].Position = points[i].pos
+		(*imd.tri)[j+1].Color = points[i].col
+		(*imd.tri)[j+1].Picture = points[i].pic
+		(*imd.tri)[j+1].Intensity = points[i].in
+
+		(*imd.tri)[j+2].Position = points[i+1].pos
+		(*imd.tri)[j+2].Color = points[i+1].col
+		(*imd.tri)[j+2].Picture = points[i+1].pic
+		(*imd.tri)[j+2].Intensity = points[i+1].in
+	}
+
+	imd.applyMatrixAndMask(off)
+	imd.batch.Dirty()
+}
+
+func (imd *IMDraw) fillEllipseArc(radius pixel.Vec, low, high float64) {
+	points := imd.getAndClearPoints()
+
+	for _, pt := range points {
+		num := math.Ceil(math.Abs(high-low) / (2 * math.Pi) * float64(pt.precision))
+		delta := (high - low) / num
+
+		off := imd.tri.Len()
+		imd.tri.SetLen(imd.tri.Len() + 3*int(num))
+
+		for i := range (*imd.tri)[off:] {
+			(*imd.tri)[off+i].Color = pt.col
+			(*imd.tri)[off+i].Picture = 0
+			(*imd.tri)[off+i].Intensity = 0
+		}
+
+		for i, j := 0.0, off; i < num; i, j = i+1, j+3 {
+			angle := low + i*delta
+			sin, cos := math.Sincos(angle)
+			a := pt.pos + pixel.V(
+				radius.X()*cos,
+				radius.Y()*sin,
+			)
+
+			angle = low + (i+1)*delta
+			sin, cos = math.Sincos(angle)
+			b := pt.pos + pixel.V(
+				radius.X()*cos,
+				radius.Y()*sin,
+			)
+
+			(*imd.tri)[j+0].Position = pt.pos
+			(*imd.tri)[j+1].Position = a
+			(*imd.tri)[j+2].Position = b
+		}
+
+		imd.applyMatrixAndMask(off)
+		imd.batch.Dirty()
+	}
+}
+
+func (imd *IMDraw) outlineEllipseArc(radius pixel.Vec, low, high, thickness float64, doEndShape bool) {
+	points := imd.getAndClearPoints()
+
+	for _, pt := range points {
+		num := math.Ceil(math.Abs(high-low) / (2 * math.Pi) * float64(pt.precision))
+		delta := (high - low) / num
+
+		off := imd.tri.Len()
+		imd.tri.SetLen(imd.tri.Len() + 6*int(num))
+
+		for i := range (*imd.tri)[off:] {
+			(*imd.tri)[off+i].Color = pt.col
+			(*imd.tri)[off+i].Picture = 0
+			(*imd.tri)[off+i].Intensity = 0
+		}
+
+		for i, j := 0.0, off; i < num; i, j = i+1, j+6 {
+			angle := low + i*delta
+			sin, cos := math.Sincos(angle)
+			normalSin, normalCos := pixel.V(sin, cos).ScaledXY(radius).Unit().XY()
+			a := pt.pos + pixel.V(
+				radius.X()*cos-thickness/2*normalCos,
+				radius.Y()*sin-thickness/2*normalSin,
+			)
+			b := pt.pos + pixel.V(
+				radius.X()*cos+thickness/2*normalCos,
+				radius.Y()*sin+thickness/2*normalSin,
+			)
+
+			angle = low + (i+1)*delta
+			sin, cos = math.Sincos(angle)
+			normalSin, normalCos = pixel.V(sin, cos).ScaledXY(radius).Unit().XY()
+			c := pt.pos + pixel.V(
+				radius.X()*cos-thickness/2*normalCos,
+				radius.Y()*sin-thickness/2*normalSin,
+			)
+			d := pt.pos + pixel.V(
+				radius.X()*cos+thickness/2*normalCos,
+				radius.Y()*sin+thickness/2*normalSin,
+			)
+
+			(*imd.tri)[j+0].Position = a
+			(*imd.tri)[j+1].Position = b
+			(*imd.tri)[j+2].Position = c
+			(*imd.tri)[j+3].Position = c
+			(*imd.tri)[j+4].Position = b
+			(*imd.tri)[j+5].Position = d
+		}
+
+		imd.applyMatrixAndMask(off)
+		imd.batch.Dirty()
+
+		if doEndShape {
+			lowSin, lowCos := math.Sincos(low)
+			lowCenter := pt.pos + pixel.V(
+				radius.X()*lowCos,
+				radius.Y()*lowSin,
+			)
+			normalLowSin, normalLowCos := pixel.V(lowSin, lowCos).ScaledXY(radius).Unit().XY()
+			normalLow := pixel.V(normalLowCos, normalLowSin).Angle()
+
+			highSin, highCos := math.Sincos(high)
+			highCenter := pt.pos + pixel.V(
+				radius.X()*highCos,
+				radius.Y()*highSin,
+			)
+			normalHighSin, normalHighCos := pixel.V(highSin, highCos).ScaledXY(radius).Unit().XY()
+			normalHigh := pixel.V(normalHighCos, normalHighSin).Angle()
+
+			orientation := 1.0
+			if low > high {
+				orientation = -1.0
+			}
+
+			switch pt.endshape {
+			case NoEndShape:
+				// nothing
+			case SharpEndShape:
+				thick := pixel.X(thickness / 2).Rotated(normalLow)
+				imd.pushPt(lowCenter+thick, pt)
+				imd.pushPt(lowCenter-thick, pt)
+				imd.pushPt(lowCenter-thick.Rotated(math.Pi/2*orientation), pt)
+				imd.fillPolygon()
+				thick = pixel.X(thickness / 2).Rotated(normalHigh)
+				imd.pushPt(highCenter+thick, pt)
+				imd.pushPt(highCenter-thick, pt)
+				imd.pushPt(highCenter+thick.Rotated(math.Pi/2*orientation), pt)
+				imd.fillPolygon()
+			case RoundEndShape:
+				imd.pushPt(lowCenter, pt)
+				imd.fillEllipseArc(pixel.V(thickness, thickness)/2, normalLow, normalLow-math.Pi*orientation)
+				imd.pushPt(highCenter, pt)
+				imd.fillEllipseArc(pixel.V(thickness, thickness)/2, normalHigh, normalHigh+math.Pi*orientation)
+			}
+		}
+	}
+}
+
+func (imd *IMDraw) polyline(thickness float64, closed bool) {
+	points := imd.getAndClearPoints()
+
+	// filter identical adjacent points
+	filtered := points[:0]
+	for i := 0; i < len(points); i++ {
+		if closed || i+1 < len(points) {
+			j := (i + 1) % len(points)
+			if points[i].pos != points[j].pos {
+				filtered = append(filtered, points[i])
+			}
+		}
+	}
+	points = filtered
+
+	if len(points) < 2 {
+		return
+	}
+
+	// first point
+	j, i := 0, 1
+	normal := (points[i].pos - points[j].pos).Rotated(math.Pi / 2).Unit().Scaled(thickness / 2)
+
+	if !closed {
+		switch points[j].endshape {
+		case NoEndShape:
+			// nothing
+		case SharpEndShape:
+			imd.pushPt(points[j].pos+normal, points[j])
+			imd.pushPt(points[j].pos-normal, points[j])
+			imd.pushPt(points[j].pos+normal.Rotated(math.Pi/2), points[j])
+			imd.fillPolygon()
+		case RoundEndShape:
+			imd.pushPt(points[j].pos, points[j])
+			imd.fillEllipseArc(pixel.V(thickness, thickness)/2, normal.Angle(), normal.Angle()+math.Pi)
+		}
+	}
+
+	imd.pushPt(points[j].pos+normal, points[j])
+	imd.pushPt(points[j].pos-normal, points[j])
+
+	// middle points
+	for i := 0; i < len(points); i++ {
+		j, k := i+1, i+2
+
+		closing := false
+		if j >= len(points) {
+			if !closed {
+				break
+			}
+			j %= len(points)
+			closing = true
+		}
+		if k >= len(points) {
+			k %= len(points)
+		}
+
+		ijNormal := (points[j].pos - points[i].pos).Rotated(math.Pi / 2).Unit().Scaled(thickness / 2)
+		jkNormal := (points[k].pos - points[j].pos).Rotated(math.Pi / 2).Unit().Scaled(thickness / 2)
+
+		orientation := 1.0
+		if ijNormal.Cross(jkNormal) > 0 {
+			orientation = -1.0
+		}
+
+		imd.pushPt(points[j].pos-ijNormal, points[j])
+		imd.pushPt(points[j].pos+ijNormal, points[j])
+		imd.fillPolygon()
+
+		switch points[j].endshape {
+		case NoEndShape:
+			// nothing
+		case SharpEndShape:
+			imd.pushPt(points[j].pos, points[j])
+			imd.pushPt(points[j].pos+ijNormal.Scaled(orientation), points[j])
+			imd.pushPt(points[j].pos+jkNormal.Scaled(orientation), points[j])
+			imd.fillPolygon()
+		case RoundEndShape:
+			imd.pushPt(points[j].pos, points[j])
+			imd.fillEllipseArc(pixel.V(thickness, thickness)/2, ijNormal.Angle(), ijNormal.Angle()-math.Pi)
+			imd.pushPt(points[j].pos, points[j])
+			imd.fillEllipseArc(pixel.V(thickness, thickness)/2, jkNormal.Angle(), jkNormal.Angle()+math.Pi)
+		}
+
+		if !closing {
+			imd.pushPt(points[j].pos+jkNormal, points[j])
+			imd.pushPt(points[j].pos-jkNormal, points[j])
+		}
+	}
+
+	// last point
+	i, j = len(points)-2, len(points)-1
+	normal = (points[j].pos - points[i].pos).Rotated(math.Pi / 2).Unit().Scaled(thickness / 2)
+
+	imd.pushPt(points[j].pos-normal, points[j])
+	imd.pushPt(points[j].pos+normal, points[j])
+	imd.fillPolygon()
+
+	if !closed {
+		switch points[j].endshape {
+		case NoEndShape:
+			// nothing
+		case SharpEndShape:
+			imd.pushPt(points[j].pos+normal, points[j])
+			imd.pushPt(points[j].pos-normal, points[j])
+			imd.pushPt(points[j].pos+normal.Rotated(-math.Pi/2), points[j])
+			imd.fillPolygon()
+		case RoundEndShape:
+			imd.pushPt(points[j].pos, points[j])
+			imd.fillEllipseArc(pixel.V(thickness, thickness)/2, normal.Angle(), normal.Angle()-math.Pi)
+		}
+	}
+}
-- 
GitLab