From f2ef87f1983a3797229d6625cb92f2da7b94d2a4 Mon Sep 17 00:00:00 2001
From: Seebs <seebs@seebs.net>
Date: Fri, 9 Jun 2017 21:42:20 -0500
Subject: [PATCH] Improve normal calculations

Soooo. It turns out that the bunch of smallish (~4-5% of runtime)
loads associated with Len(), Unit(), Rotated(), and so on... Were
actually more like 15% or more of computational effort. I first
figured this out by creating:

	func (u Vec) Normal(v Vec) Vec

which gives you a vector normal to u->v. That consumed a lot
of CPU time, and was followed by .Unit().Scaled(imd.thickness / 2),
which consumed a bit more CPU time.

After some poking, and in the interests of avoiding UI cruft,
the final selection is
	func (u Vec) Normal() Vec

This returns the vector rotated 90 degrees, which turns out to
be the most common problem.
---
 geometry.go      | 13 +++++++++++++
 imdraw/imdraw.go | 15 +++++++--------
 2 files changed, 20 insertions(+), 8 deletions(-)

diff --git a/geometry.go b/geometry.go
index 13a574d..02a8b51 100644
--- a/geometry.go
+++ b/geometry.go
@@ -49,6 +49,11 @@ func (u Vec) XY() (x, y float64) {
 	return u.X, u.Y
 }
 
+// Normal returns a vector normal to u (rotated by math.pi/2)
+func (u Vec) Normal() Vec {
+	return Vec{X: u.Y, Y: -u.X}
+}
+
 // Add returns the sum of vectors u and v.
 func (u Vec) Add(v Vec) Vec {
 	return Vec{
@@ -65,6 +70,14 @@ func (u Vec) Sub(v Vec) Vec {
 	}
 }
 
+// To returns the vector from vector u to vector v, equivalent to v.Sub(u).
+func (u Vec) To(v Vec) Vec {
+	return Vec{
+		v.X - u.X,
+		v.Y - u.Y,
+	}
+}
+
 // Scaled returns the vector u multiplied by c.
 func (u Vec) Scaled(c float64) Vec {
 	return Vec{u.X * c, u.Y * c}
diff --git a/imdraw/imdraw.go b/imdraw/imdraw.go
index ab72ea9..7f72a4d 100644
--- a/imdraw/imdraw.go
+++ b/imdraw/imdraw.go
@@ -495,12 +495,12 @@ func (imd *IMDraw) outlineEllipseArc(radius pixel.Vec, low, high, thickness floa
 				thick := pixel.V(thickness/2, 0).Rotated(normalLow)
 				imd.pushPt(lowCenter.Add(thick), pt)
 				imd.pushPt(lowCenter.Sub(thick), pt)
-				imd.pushPt(lowCenter.Sub(thick.Rotated(math.Pi/2*orientation)), pt)
+				imd.pushPt(lowCenter.Sub(thick.Normal().Scaled(orientation)), pt)
 				imd.fillPolygon()
 				thick = pixel.V(thickness/2, 0).Rotated(normalHigh)
 				imd.pushPt(highCenter.Add(thick), pt)
 				imd.pushPt(highCenter.Sub(thick), pt)
-				imd.pushPt(highCenter.Add(thick.Rotated(math.Pi/2*orientation)), pt)
+				imd.pushPt(highCenter.Add(thick.Normal().Scaled(orientation)), pt)
 				imd.fillPolygon()
 			case RoundEndShape:
 				imd.pushPt(lowCenter, pt)
@@ -528,7 +528,7 @@ func (imd *IMDraw) polyline(thickness float64, closed bool) {
 
 	// first point
 	j, i := 0, 1
-	ijNormal := points[1].pos.Sub(points[0].pos).Rotated(math.Pi / 2).Unit().Scaled(thickness / 2)
+	ijNormal := points[0].pos.To(points[1].pos).Normal().Unit().Scaled(thickness / 2)
 
 	if !closed {
 		switch points[j].endshape {
@@ -537,7 +537,7 @@ func (imd *IMDraw) polyline(thickness float64, closed bool) {
 		case SharpEndShape:
 			imd.pushPt(points[j].pos.Add(ijNormal), points[j])
 			imd.pushPt(points[j].pos.Sub(ijNormal), points[j])
-			imd.pushPt(points[j].pos.Add(ijNormal.Rotated(math.Pi/2)), points[j])
+			imd.pushPt(points[j].pos.Add(ijNormal.Normal()), points[j])
 			imd.fillPolygon()
 		case RoundEndShape:
 			imd.pushPt(points[j].pos, points[j])
@@ -549,7 +549,6 @@ func (imd *IMDraw) polyline(thickness float64, closed bool) {
 	imd.pushPt(points[j].pos.Sub(ijNormal), points[j])
 
 	// middle points
-	// compute "previous" normal:
 	for i := 0; i < len(points); i++ {
 		j, k := i+1, i+2
 
@@ -565,7 +564,7 @@ func (imd *IMDraw) polyline(thickness float64, closed bool) {
 			k %= len(points)
 		}
 
-		jkNormal := points[k].pos.Sub(points[j].pos).Rotated(math.Pi / 2).Unit().Scaled(thickness / 2)
+		jkNormal := points[j].pos.To(points[k].pos).Normal().Unit().Scaled(thickness / 2)
 
 		orientation := 1.0
 		if ijNormal.Cross(jkNormal) > 0 {
@@ -601,7 +600,7 @@ func (imd *IMDraw) polyline(thickness float64, closed bool) {
 
 	// last point
 	i, j = len(points)-2, len(points)-1
-	ijNormal = points[j].pos.Sub(points[i].pos).Rotated(math.Pi / 2).Unit().Scaled(thickness / 2)
+	ijNormal = points[i].pos.To(points[j].pos).Normal().Unit().Scaled(thickness / 2)
 
 	imd.pushPt(points[j].pos.Sub(ijNormal), points[j])
 	imd.pushPt(points[j].pos.Add(ijNormal), points[j])
@@ -614,7 +613,7 @@ func (imd *IMDraw) polyline(thickness float64, closed bool) {
 		case SharpEndShape:
 			imd.pushPt(points[j].pos.Add(ijNormal), points[j])
 			imd.pushPt(points[j].pos.Sub(ijNormal), points[j])
-			imd.pushPt(points[j].pos.Add(ijNormal.Rotated(-math.Pi/2)), points[j])
+			imd.pushPt(points[j].pos.Add(ijNormal.Normal().Scaled(-1)), points[j])
 			imd.fillPolygon()
 		case RoundEndShape:
 			imd.pushPt(points[j].pos, points[j])
-- 
GitLab