From f93f599d7f815d6ab81b72855a853eb104a34cc2 Mon Sep 17 00:00:00 2001
From: faiface <faiface@ksp.sk>
Date: Fri, 10 Mar 2017 14:51:55 +0100
Subject: [PATCH] add IM with several methods

---
 graphics.go | 195 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 195 insertions(+)

diff --git a/graphics.go b/graphics.go
index 2351944..15e7977 100644
--- a/graphics.go
+++ b/graphics.go
@@ -1,5 +1,10 @@
 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
@@ -59,3 +64,193 @@ func (s *Sprite) Picture() Picture {
 func (s *Sprite) Draw(t Target) {
 	s.d.Draw(t)
 }
+
+// IM is an immediate-like-mode shape drawer.
+//
+// TODO: mode doc
+type IM struct {
+	points []point
+	opts   point
+	matrix Matrix
+	mask   NRGBA
+	tri    *TrianglesData
+	d      Drawer
+	tmp    []Vec
+}
+
+type point struct {
+	position  Vec
+	color     NRGBA
+	picture   Vec
+	intensity float64
+	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
+)
+
+// NewIM creates a new empty IM. 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 NewIM(pic Picture) *IM {
+	tri := &TrianglesData{}
+	im := &IM{
+		tri: tri,
+		d:   Drawer{Triangles: tri, Picture: pic},
+	}
+	im.Precision(64)
+	im.SetMatrix(ZM)
+	im.SetColorMask(NRGBA{1, 1, 1, 1})
+	return im
+}
+
+// Clear removes all drawn shapes from the IM. This does not remove Pushed points.
+func (im *IM) Clear() {
+	im.tri.SetLen(0)
+	im.d.Dirty()
+}
+
+// Draw draws all currently drawn shapes inside the IM onto another Target.
+func (im *IM) Draw(t Target) {
+	im.d.Draw(t)
+}
+
+// Push adds some points to the IM queue. All Pushed points will have the same properties except for
+// the position.
+func (im *IM) Push(pts ...Vec) {
+	point := im.opts
+	for _, pt := range pts {
+		point.position = im.matrix.Project(pt)
+		point.color = im.mask.Mul(im.opts.color)
+		im.points = append(im.points, point)
+	}
+}
+
+// Color sets the color of the next Pushed points.
+func (im *IM) Color(color color.Color) {
+	im.opts.color = NRGBAModel.Convert(color).(NRGBA)
+}
+
+// Picture sets the Picture coordinates of the next Pushed points.
+func (im *IM) Picture(pic Vec) {
+	im.opts.picture = pic
+}
+
+// Intensity sets the picture Intensity of the next Pushed points.
+func (im *IM) Intensity(in float64) {
+	im.opts.intensity = in
+}
+
+// Width sets the with property of the next Pushed points.
+//
+// Note that this property does not apply to filled shapes.
+func (im *IM) Width(w float64) {
+	im.opts.width = w
+}
+
+// Precision sets the curve/circle drawing precision of the next Pushed points.
+//
+// It is the number of segments per 360 degrees.
+func (im *IM) Precision(p int) {
+	im.opts.precision = p
+	if p+1 > len(im.tmp) {
+		im.tmp = append(im.tmp, make([]Vec, p+1-len(im.tmp))...)
+	}
+	if p+1 < len(im.tmp) {
+		im.tmp = im.tmp[:p+1]
+	}
+}
+
+// EndShape sets the endshape of the next Pushed points.
+func (im *IM) EndShape(es EndShape) {
+	im.opts.endshape = es
+}
+
+// SetMatrix sets a Matrix that all further points will be transformed by.
+func (im *IM) SetMatrix(m Matrix) {
+	im.matrix = m
+}
+
+// SetColorMask sets a color that all futher point's color will be multiplied by.
+func (im *IM) SetColorMask(color color.Color) {
+	im.mask = NRGBAModel.Convert(color).(NRGBA)
+}
+
+// FillConvexPolygon takes all points Pushed into the IM's queue and fills the convex polygon formed
+// by them.
+//
+// It empties the queue after.
+func (im *IM) FillConvexPolygon() {
+	points := im.points
+	im.points = nil
+
+	if len(points) < 3 {
+		return
+	}
+
+	i := im.tri.Len()
+	im.tri.SetLen(im.tri.Len() + 3*(len(points)-2))
+
+	for j := 1; j+1 < len(points); j++ {
+		(*im.tri)[i].Position = points[0].position
+		(*im.tri)[i].Color = points[0].color
+		(*im.tri)[i].Picture = points[0].picture
+		(*im.tri)[i].Intensity = points[0].intensity
+
+		(*im.tri)[i+1].Position = points[j].position
+		(*im.tri)[i+1].Color = points[j].color
+		(*im.tri)[i+1].Picture = points[j].picture
+		(*im.tri)[i+1].Intensity = points[j].intensity
+
+		(*im.tri)[i+2].Position = points[j+1].position
+		(*im.tri)[i+2].Color = points[j+1].color
+		(*im.tri)[i+2].Picture = points[j+1].picture
+		(*im.tri)[i+2].Intensity = points[j+1].intensity
+
+		i += 3
+	}
+
+	im.d.Dirty()
+}
+
+// FillEllipseArc draws an ellipse arc around each point in the IM's queue. Low and high angles are
+// in radians.
+//
+// It empties the queue after.
+func (im *IM) FillEllipseArc(radius Vec, low, high float64) {
+	points := im.points
+	im.points = nil
+
+	// normalize high
+	if math.Abs(high-low) > 2*math.Pi {
+		high = low + math.Mod(high-low, 2*math.Pi)
+	}
+
+	for _, pt := range points {
+		im.Push(pt.position) // center
+
+		num := math.Ceil(math.Abs(high-low) / (2 * math.Pi) * float64(pt.precision))
+		delta := (high - low) / num
+		for i := range im.tmp[:int(num)+1] {
+			angle := low + float64(i)*delta
+			sin, cos := math.Sincos(angle)
+			im.tmp[i] = pt.position + V(
+				radius.X()*cos,
+				radius.Y()*sin,
+			)
+		}
+
+		im.Push(im.tmp[:int(num)+1]...)
+		im.FillConvexPolygon()
+	}
+}
-- 
GitLab