2017-04-13 10:41:38 -05:00
|
|
|
// Package imdraw implements a basic primitive geometry shape and pictured polygon drawing for Pixel
|
2017-04-13 10:44:28 -05:00
|
|
|
// with a nice immediate-mode-like API.
|
2017-03-21 05:33:11 -05:00
|
|
|
package imdraw
|
|
|
|
|
|
|
|
import (
|
|
|
|
"image/color"
|
|
|
|
"math"
|
|
|
|
|
|
|
|
"github.com/faiface/pixel"
|
|
|
|
)
|
|
|
|
|
2017-04-13 10:44:28 -05:00
|
|
|
// IMDraw is an immediate-mode-like shape drawer and BasicTarget. IMDraw supports TrianglesPosition,
|
2017-03-21 05:33:11 -05:00
|
|
|
// 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
|
|
|
|
//
|
2017-05-18 16:50:45 -05:00
|
|
|
// Set exported fields to change properties of Pushed points:
|
2017-03-21 05:33:11 -05:00
|
|
|
//
|
2017-05-18 16:50:45 -05:00
|
|
|
// imd.Color = pixel.RGB(1, 0, 0)
|
2017-03-21 05:33:11 -05:00
|
|
|
// imd.Push(pixel.V(200, 200))
|
|
|
|
// imd.Circle(400, 0)
|
2017-03-22 12:53:25 -05:00
|
|
|
//
|
|
|
|
// Here is the list of all available point properties (need to be set before Pushing a point):
|
|
|
|
// - Color - applies to all
|
|
|
|
// - Picture - coordinates, only applies to filled polygons
|
|
|
|
// - Intensity - picture intensity, only applies to filled polygons
|
|
|
|
// - Precision - curve drawing precision, only applies to circles and ellipses
|
|
|
|
// - EndShape - shape of the end of a line, only applies to lines and outlines
|
|
|
|
//
|
|
|
|
// And here's the list of all shapes that can be drawn (all, except for line, can be filled or
|
|
|
|
// outlined):
|
|
|
|
// - Line
|
|
|
|
// - Polygon
|
|
|
|
// - Circle
|
|
|
|
// - Circle arc
|
|
|
|
// - Ellipse
|
|
|
|
// - Ellipse arc
|
2017-03-21 05:33:11 -05:00
|
|
|
type IMDraw struct {
|
2017-05-18 16:50:45 -05:00
|
|
|
Color color.Color
|
|
|
|
Picture pixel.Vec
|
|
|
|
Intensity float64
|
|
|
|
Precision int
|
|
|
|
EndShape EndShape
|
|
|
|
|
2017-03-21 05:33:11 -05:00
|
|
|
points []point
|
2017-06-05 19:37:53 -05:00
|
|
|
pool [][]point
|
2017-03-21 05:33:11 -05:00
|
|
|
matrix pixel.Matrix
|
2017-04-09 15:00:26 -05:00
|
|
|
mask pixel.RGBA
|
2017-03-21 05:33:11 -05:00
|
|
|
|
|
|
|
tri *pixel.TrianglesData
|
|
|
|
batch *pixel.Batch
|
|
|
|
}
|
|
|
|
|
|
|
|
var _ pixel.BasicTarget = (*IMDraw)(nil)
|
|
|
|
|
|
|
|
type point struct {
|
|
|
|
pos pixel.Vec
|
2017-04-09 15:00:26 -05:00
|
|
|
col pixel.RGBA
|
2017-03-21 05:33:11 -05:00
|
|
|
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)
|
2017-04-10 10:25:56 -05:00
|
|
|
im.SetColorMask(pixel.Alpha(1))
|
2017-03-21 05:33:11 -05:00
|
|
|
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() {
|
2017-06-05 19:37:53 -05:00
|
|
|
imd.points = imd.points[:0]
|
2017-05-18 16:50:45 -05:00
|
|
|
imd.Color = pixel.Alpha(1)
|
2017-05-21 12:25:06 -05:00
|
|
|
imd.Picture = pixel.ZV
|
2017-05-18 16:50:45 -05:00
|
|
|
imd.Intensity = 0
|
|
|
|
imd.Precision = 64
|
|
|
|
imd.EndShape = NoEndShape
|
2017-03-21 05:33:11 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Draw draws all currently drawn shapes inside the IM onto another Target.
|
2017-04-12 09:03:36 -05:00
|
|
|
//
|
|
|
|
// Note, that IMDraw's matrix and color mask have no effect here.
|
2017-03-21 05:33:11 -05:00
|
|
|
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) {
|
2017-06-09 22:22:27 -05:00
|
|
|
if _, ok := imd.Color.(pixel.RGBA); !ok {
|
|
|
|
imd.Color = pixel.ToRGBA(imd.Color)
|
|
|
|
}
|
2017-05-18 16:50:45 -05:00
|
|
|
opts := point{
|
|
|
|
col: imd.Color.(pixel.RGBA),
|
|
|
|
pic: imd.Picture,
|
|
|
|
in: imd.Intensity,
|
|
|
|
precision: imd.Precision,
|
|
|
|
endshape: imd.EndShape,
|
|
|
|
}
|
2017-03-21 05:33:11 -05:00
|
|
|
for _, pt := range pts {
|
2017-05-18 16:50:45 -05:00
|
|
|
imd.pushPt(pt, opts)
|
2017-03-21 05:33:11 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (imd *IMDraw) pushPt(pos pixel.Vec, pt point) {
|
|
|
|
pt.pos = pos
|
|
|
|
imd.points = append(imd.points, pt)
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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) {
|
2017-04-09 15:00:26 -05:00
|
|
|
imd.mask = pixel.ToRGBA(color)
|
2017-03-21 05:33:11 -05:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2017-03-22 12:53:25 -05:00
|
|
|
// Line draws a polyline of the specified thickness between the Pushed points.
|
|
|
|
func (imd *IMDraw) Line(thickness float64) {
|
|
|
|
imd.polyline(thickness, false)
|
|
|
|
}
|
|
|
|
|
2017-04-15 17:01:43 -05:00
|
|
|
// Rectangle draws a rectangle between each two subsequent Pushed points. Drawing a rectangle
|
|
|
|
// between two points means drawing a rectangle with sides parallel to the axes of the coordinate
|
|
|
|
// system, where the two points specify it's two opposite corners.
|
|
|
|
//
|
|
|
|
// If the thickness is 0, rectangles will be filled, otherwise will be outlined with the given
|
|
|
|
// thickness.
|
|
|
|
func (imd *IMDraw) Rectangle(thickness float64) {
|
|
|
|
if thickness == 0 {
|
|
|
|
imd.fillRectangle()
|
|
|
|
} else {
|
|
|
|
imd.outlineRectangle(thickness)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-03-21 05:33:11 -05:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (imd *IMDraw) getAndClearPoints() []point {
|
|
|
|
points := imd.points
|
2017-06-05 19:37:53 -05:00
|
|
|
// use one of the existing pools so we don't reallocate as often
|
|
|
|
if len(imd.pool) > 0 {
|
|
|
|
pos := len(imd.pool) - 1
|
2017-06-09 11:13:05 -05:00
|
|
|
imd.points = imd.pool[pos][:0]
|
|
|
|
imd.pool = imd.pool[:pos]
|
2017-06-05 19:37:53 -05:00
|
|
|
} else {
|
|
|
|
imd.points = nil
|
|
|
|
}
|
2017-03-21 05:33:11 -05:00
|
|
|
return points
|
|
|
|
}
|
|
|
|
|
2017-06-05 19:37:53 -05:00
|
|
|
func (imd *IMDraw) restorePoints(points []point) {
|
|
|
|
imd.pool = append(imd.pool, imd.points)
|
|
|
|
imd.points = points[:0]
|
|
|
|
}
|
|
|
|
|
2017-03-21 05:33:11 -05:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-15 17:01:43 -05:00
|
|
|
func (imd *IMDraw) fillRectangle() {
|
|
|
|
points := imd.getAndClearPoints()
|
|
|
|
|
|
|
|
if len(points) < 2 {
|
2017-06-05 19:37:53 -05:00
|
|
|
imd.restorePoints(points)
|
2017-04-15 17:01:43 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
off := imd.tri.Len()
|
|
|
|
imd.tri.SetLen(imd.tri.Len() + 6*(len(points)-1))
|
|
|
|
|
|
|
|
for i, j := 0, off; i+1 < len(points); i, j = i+1, j+6 {
|
|
|
|
a, b := points[i], points[i+1]
|
|
|
|
c := point{
|
2017-05-21 12:25:06 -05:00
|
|
|
pos: pixel.V(a.pos.X, b.pos.Y),
|
2017-04-15 17:01:43 -05:00
|
|
|
col: a.col.Add(b.col).Mul(pixel.Alpha(0.5)),
|
2017-05-21 12:25:06 -05:00
|
|
|
pic: pixel.V(a.pic.X, b.pic.Y),
|
2017-04-15 17:01:43 -05:00
|
|
|
in: (a.in + b.in) / 2,
|
|
|
|
}
|
|
|
|
d := point{
|
2017-05-21 12:25:06 -05:00
|
|
|
pos: pixel.V(b.pos.X, a.pos.Y),
|
2017-04-15 17:01:43 -05:00
|
|
|
col: a.col.Add(b.col).Mul(pixel.Alpha(0.5)),
|
2017-05-21 12:25:06 -05:00
|
|
|
pic: pixel.V(b.pic.X, a.pic.Y),
|
2017-04-15 17:01:43 -05:00
|
|
|
in: (a.in + b.in) / 2,
|
|
|
|
}
|
|
|
|
|
2017-06-09 11:13:05 -05:00
|
|
|
for k, p := range [...]point{a, b, c, a, b, d} {
|
2017-04-15 17:01:43 -05:00
|
|
|
(*imd.tri)[j+k].Position = p.pos
|
|
|
|
(*imd.tri)[j+k].Color = p.col
|
|
|
|
(*imd.tri)[j+k].Picture = p.pic
|
|
|
|
(*imd.tri)[j+k].Intensity = p.in
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
imd.applyMatrixAndMask(off)
|
|
|
|
imd.batch.Dirty()
|
2017-06-09 11:13:05 -05:00
|
|
|
|
2017-06-05 19:37:53 -05:00
|
|
|
imd.restorePoints(points)
|
2017-04-15 17:01:43 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func (imd *IMDraw) outlineRectangle(thickness float64) {
|
|
|
|
points := imd.getAndClearPoints()
|
|
|
|
|
|
|
|
if len(points) < 2 {
|
2017-06-05 19:37:53 -05:00
|
|
|
imd.restorePoints(points)
|
2017-04-15 17:01:43 -05:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i+1 < len(points); i++ {
|
|
|
|
a, b := points[i], points[i+1]
|
|
|
|
mid := a
|
|
|
|
mid.col = a.col.Add(b.col).Mul(pixel.Alpha(0.5))
|
|
|
|
mid.in = (a.in + b.in) / 2
|
|
|
|
|
|
|
|
imd.pushPt(a.pos, a)
|
2017-05-21 12:25:06 -05:00
|
|
|
imd.pushPt(pixel.V(a.pos.X, b.pos.Y), mid)
|
2017-04-15 17:01:43 -05:00
|
|
|
imd.pushPt(b.pos, b)
|
2017-05-21 12:25:06 -05:00
|
|
|
imd.pushPt(pixel.V(b.pos.X, a.pos.Y), mid)
|
2017-04-15 17:01:43 -05:00
|
|
|
imd.polyline(thickness, true)
|
|
|
|
}
|
2017-06-09 11:13:05 -05:00
|
|
|
|
2017-06-05 19:37:53 -05:00
|
|
|
imd.restorePoints(points)
|
2017-04-15 17:01:43 -05:00
|
|
|
}
|
|
|
|
|
2017-03-21 05:33:11 -05:00
|
|
|
func (imd *IMDraw) fillPolygon() {
|
|
|
|
points := imd.getAndClearPoints()
|
|
|
|
|
|
|
|
if len(points) < 3 {
|
2017-06-05 19:37:53 -05:00
|
|
|
imd.restorePoints(points)
|
2017-03-21 05:33:11 -05:00
|
|
|
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 {
|
2017-06-09 11:13:05 -05:00
|
|
|
for k, p := range [...]int{0, i, i + 1} {
|
2017-06-05 20:12:35 -05:00
|
|
|
tri := &(*imd.tri)[j+k]
|
|
|
|
tri.Position = points[p].pos
|
|
|
|
tri.Color = points[p].col
|
|
|
|
tri.Picture = points[p].pic
|
|
|
|
tri.Intensity = points[p].in
|
2017-04-15 17:59:07 -05:00
|
|
|
}
|
2017-03-21 05:33:11 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
imd.applyMatrixAndMask(off)
|
|
|
|
imd.batch.Dirty()
|
2017-06-09 11:13:05 -05:00
|
|
|
|
2017-06-05 19:37:53 -05:00
|
|
|
imd.restorePoints(points)
|
2017-03-21 05:33:11 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2017-05-21 12:25:06 -05:00
|
|
|
(*imd.tri)[off+i].Picture = pixel.ZV
|
2017-03-21 05:33:11 -05:00
|
|
|
(*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)
|
2017-05-21 12:25:06 -05:00
|
|
|
a := pt.pos.Add(pixel.V(
|
|
|
|
radius.X*cos,
|
|
|
|
radius.Y*sin,
|
|
|
|
))
|
2017-03-21 05:33:11 -05:00
|
|
|
|
|
|
|
angle = low + (i+1)*delta
|
|
|
|
sin, cos = math.Sincos(angle)
|
2017-05-21 12:25:06 -05:00
|
|
|
b := pt.pos.Add(pixel.V(
|
|
|
|
radius.X*cos,
|
|
|
|
radius.Y*sin,
|
|
|
|
))
|
2017-03-21 05:33:11 -05:00
|
|
|
|
|
|
|
(*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()
|
|
|
|
}
|
2017-06-09 11:13:05 -05:00
|
|
|
|
2017-06-05 19:37:53 -05:00
|
|
|
imd.restorePoints(points)
|
2017-03-21 05:33:11 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2017-05-21 12:25:06 -05:00
|
|
|
(*imd.tri)[off+i].Picture = pixel.ZV
|
2017-03-21 05:33:11 -05:00
|
|
|
(*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()
|
2017-05-21 12:25:06 -05:00
|
|
|
a := pt.pos.Add(pixel.V(
|
|
|
|
radius.X*cos-thickness/2*normalCos,
|
|
|
|
radius.Y*sin-thickness/2*normalSin,
|
|
|
|
))
|
|
|
|
b := pt.pos.Add(pixel.V(
|
|
|
|
radius.X*cos+thickness/2*normalCos,
|
|
|
|
radius.Y*sin+thickness/2*normalSin,
|
|
|
|
))
|
2017-03-21 05:33:11 -05:00
|
|
|
|
|
|
|
angle = low + (i+1)*delta
|
|
|
|
sin, cos = math.Sincos(angle)
|
|
|
|
normalSin, normalCos = pixel.V(sin, cos).ScaledXY(radius).Unit().XY()
|
2017-05-21 12:25:06 -05:00
|
|
|
c := pt.pos.Add(pixel.V(
|
|
|
|
radius.X*cos-thickness/2*normalCos,
|
|
|
|
radius.Y*sin-thickness/2*normalSin,
|
|
|
|
))
|
|
|
|
d := pt.pos.Add(pixel.V(
|
|
|
|
radius.X*cos+thickness/2*normalCos,
|
|
|
|
radius.Y*sin+thickness/2*normalSin,
|
|
|
|
))
|
2017-03-21 05:33:11 -05:00
|
|
|
|
|
|
|
(*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)
|
2017-05-21 12:25:06 -05:00
|
|
|
lowCenter := pt.pos.Add(pixel.V(
|
|
|
|
radius.X*lowCos,
|
|
|
|
radius.Y*lowSin,
|
|
|
|
))
|
2017-03-21 05:33:11 -05:00
|
|
|
normalLowSin, normalLowCos := pixel.V(lowSin, lowCos).ScaledXY(radius).Unit().XY()
|
|
|
|
normalLow := pixel.V(normalLowCos, normalLowSin).Angle()
|
|
|
|
|
|
|
|
highSin, highCos := math.Sincos(high)
|
2017-05-21 12:25:06 -05:00
|
|
|
highCenter := pt.pos.Add(pixel.V(
|
|
|
|
radius.X*highCos,
|
|
|
|
radius.Y*highSin,
|
|
|
|
))
|
2017-03-21 05:33:11 -05:00
|
|
|
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:
|
2017-05-21 12:25:06 -05:00
|
|
|
thick := pixel.V(thickness/2, 0).Rotated(normalLow)
|
|
|
|
imd.pushPt(lowCenter.Add(thick), pt)
|
|
|
|
imd.pushPt(lowCenter.Sub(thick), pt)
|
2017-06-09 21:42:20 -05:00
|
|
|
imd.pushPt(lowCenter.Sub(thick.Normal().Scaled(orientation)), pt)
|
2017-03-21 05:33:11 -05:00
|
|
|
imd.fillPolygon()
|
2017-05-21 12:25:06 -05:00
|
|
|
thick = pixel.V(thickness/2, 0).Rotated(normalHigh)
|
|
|
|
imd.pushPt(highCenter.Add(thick), pt)
|
|
|
|
imd.pushPt(highCenter.Sub(thick), pt)
|
2017-06-09 21:42:20 -05:00
|
|
|
imd.pushPt(highCenter.Add(thick.Normal().Scaled(orientation)), pt)
|
2017-03-21 05:33:11 -05:00
|
|
|
imd.fillPolygon()
|
|
|
|
case RoundEndShape:
|
|
|
|
imd.pushPt(lowCenter, pt)
|
2017-05-21 12:25:06 -05:00
|
|
|
imd.fillEllipseArc(pixel.V(thickness/2, thickness/2), normalLow, normalLow-math.Pi*orientation)
|
2017-03-21 05:33:11 -05:00
|
|
|
imd.pushPt(highCenter, pt)
|
2017-05-21 12:25:06 -05:00
|
|
|
imd.fillEllipseArc(pixel.V(thickness/2, thickness/2), normalHigh, normalHigh+math.Pi*orientation)
|
2017-03-21 05:33:11 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-06-09 11:13:05 -05:00
|
|
|
|
2017-06-05 19:37:53 -05:00
|
|
|
imd.restorePoints(points)
|
2017-03-21 05:33:11 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func (imd *IMDraw) polyline(thickness float64, closed bool) {
|
|
|
|
points := imd.getAndClearPoints()
|
|
|
|
|
2017-03-23 13:38:53 -05:00
|
|
|
if len(points) == 0 {
|
2017-06-05 19:37:53 -05:00
|
|
|
imd.restorePoints(points)
|
2017-03-21 05:33:11 -05:00
|
|
|
return
|
|
|
|
}
|
2017-03-23 13:38:53 -05:00
|
|
|
if len(points) == 1 {
|
|
|
|
// one point special case
|
|
|
|
points = append(points, points[0])
|
|
|
|
}
|
2017-03-21 05:33:11 -05:00
|
|
|
|
|
|
|
// first point
|
|
|
|
j, i := 0, 1
|
2017-06-09 21:42:20 -05:00
|
|
|
ijNormal := points[0].pos.To(points[1].pos).Normal().Unit().Scaled(thickness / 2)
|
2017-03-21 05:33:11 -05:00
|
|
|
|
|
|
|
if !closed {
|
|
|
|
switch points[j].endshape {
|
|
|
|
case NoEndShape:
|
|
|
|
// nothing
|
|
|
|
case SharpEndShape:
|
2017-06-09 20:19:17 -05:00
|
|
|
imd.pushPt(points[j].pos.Add(ijNormal), points[j])
|
|
|
|
imd.pushPt(points[j].pos.Sub(ijNormal), points[j])
|
2017-06-09 21:42:20 -05:00
|
|
|
imd.pushPt(points[j].pos.Add(ijNormal.Normal()), points[j])
|
2017-03-21 05:33:11 -05:00
|
|
|
imd.fillPolygon()
|
|
|
|
case RoundEndShape:
|
|
|
|
imd.pushPt(points[j].pos, points[j])
|
2017-06-09 20:19:17 -05:00
|
|
|
imd.fillEllipseArc(pixel.V(thickness/2, thickness/2), ijNormal.Angle(), ijNormal.Angle()+math.Pi)
|
2017-03-21 05:33:11 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-09 20:19:17 -05:00
|
|
|
imd.pushPt(points[j].pos.Add(ijNormal), points[j])
|
|
|
|
imd.pushPt(points[j].pos.Sub(ijNormal), points[j])
|
2017-03-21 05:33:11 -05:00
|
|
|
|
|
|
|
// middle points
|
|
|
|
for i := 0; i < len(points); i++ {
|
|
|
|
j, k := i+1, i+2
|
|
|
|
|
|
|
|
closing := false
|
|
|
|
if j >= len(points) {
|
|
|
|
j %= len(points)
|
|
|
|
closing = true
|
|
|
|
}
|
|
|
|
if k >= len(points) {
|
2017-04-20 18:17:29 -05:00
|
|
|
if !closed {
|
|
|
|
break
|
|
|
|
}
|
2017-03-21 05:33:11 -05:00
|
|
|
k %= len(points)
|
|
|
|
}
|
|
|
|
|
2017-06-09 21:42:20 -05:00
|
|
|
jkNormal := points[j].pos.To(points[k].pos).Normal().Unit().Scaled(thickness / 2)
|
2017-03-21 05:33:11 -05:00
|
|
|
|
|
|
|
orientation := 1.0
|
|
|
|
if ijNormal.Cross(jkNormal) > 0 {
|
|
|
|
orientation = -1.0
|
|
|
|
}
|
|
|
|
|
2017-05-21 12:25:06 -05:00
|
|
|
imd.pushPt(points[j].pos.Sub(ijNormal), points[j])
|
|
|
|
imd.pushPt(points[j].pos.Add(ijNormal), points[j])
|
2017-03-21 05:33:11 -05:00
|
|
|
imd.fillPolygon()
|
|
|
|
|
|
|
|
switch points[j].endshape {
|
|
|
|
case NoEndShape:
|
|
|
|
// nothing
|
|
|
|
case SharpEndShape:
|
|
|
|
imd.pushPt(points[j].pos, points[j])
|
2017-05-21 12:25:06 -05:00
|
|
|
imd.pushPt(points[j].pos.Add(ijNormal.Scaled(orientation)), points[j])
|
|
|
|
imd.pushPt(points[j].pos.Add(jkNormal.Scaled(orientation)), points[j])
|
2017-03-21 05:33:11 -05:00
|
|
|
imd.fillPolygon()
|
|
|
|
case RoundEndShape:
|
|
|
|
imd.pushPt(points[j].pos, points[j])
|
2017-05-21 12:25:06 -05:00
|
|
|
imd.fillEllipseArc(pixel.V(thickness/2, thickness/2), ijNormal.Angle(), ijNormal.Angle()-math.Pi)
|
2017-03-21 05:33:11 -05:00
|
|
|
imd.pushPt(points[j].pos, points[j])
|
2017-05-21 12:25:06 -05:00
|
|
|
imd.fillEllipseArc(pixel.V(thickness/2, thickness/2), jkNormal.Angle(), jkNormal.Angle()+math.Pi)
|
2017-03-21 05:33:11 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if !closing {
|
2017-05-21 12:25:06 -05:00
|
|
|
imd.pushPt(points[j].pos.Add(jkNormal), points[j])
|
|
|
|
imd.pushPt(points[j].pos.Sub(jkNormal), points[j])
|
2017-03-21 05:33:11 -05:00
|
|
|
}
|
2017-06-05 19:46:16 -05:00
|
|
|
// "next" normal becomes previous normal
|
|
|
|
ijNormal = jkNormal
|
2017-03-21 05:33:11 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// last point
|
|
|
|
i, j = len(points)-2, len(points)-1
|
2017-06-09 21:42:20 -05:00
|
|
|
ijNormal = points[i].pos.To(points[j].pos).Normal().Unit().Scaled(thickness / 2)
|
2017-03-21 05:33:11 -05:00
|
|
|
|
2017-06-09 20:19:17 -05:00
|
|
|
imd.pushPt(points[j].pos.Sub(ijNormal), points[j])
|
|
|
|
imd.pushPt(points[j].pos.Add(ijNormal), points[j])
|
2017-03-21 05:33:11 -05:00
|
|
|
imd.fillPolygon()
|
|
|
|
|
|
|
|
if !closed {
|
|
|
|
switch points[j].endshape {
|
|
|
|
case NoEndShape:
|
|
|
|
// nothing
|
|
|
|
case SharpEndShape:
|
2017-06-09 20:19:17 -05:00
|
|
|
imd.pushPt(points[j].pos.Add(ijNormal), points[j])
|
|
|
|
imd.pushPt(points[j].pos.Sub(ijNormal), points[j])
|
2017-06-09 21:42:20 -05:00
|
|
|
imd.pushPt(points[j].pos.Add(ijNormal.Normal().Scaled(-1)), points[j])
|
2017-03-21 05:33:11 -05:00
|
|
|
imd.fillPolygon()
|
|
|
|
case RoundEndShape:
|
|
|
|
imd.pushPt(points[j].pos, points[j])
|
2017-06-09 20:19:17 -05:00
|
|
|
imd.fillEllipseArc(pixel.V(thickness/2, thickness/2), ijNormal.Angle(), ijNormal.Angle()-math.Pi)
|
2017-03-21 05:33:11 -05:00
|
|
|
}
|
|
|
|
}
|
2017-06-09 11:13:05 -05:00
|
|
|
|
2017-06-05 19:37:53 -05:00
|
|
|
imd.restorePoints(points)
|
2017-03-21 05:33:11 -05:00
|
|
|
}
|