add Ahmad Parabolic Approximation implementation
This commit is contained in:
parent
cc37e5c658
commit
c20e243ba4
2 changed files with 756 additions and 65 deletions
|
@ -6,14 +6,21 @@ import (
|
||||||
"math"
|
"math"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
const (
|
||||||
flattening_threshold float64 = 0.25
|
CurveRecursionLimit = 32
|
||||||
|
CurveCollinearityEpsilon = 1e-30
|
||||||
|
CurveAngleToleranceEpsilon = 0.01
|
||||||
)
|
)
|
||||||
|
|
||||||
type CubicCurveFloat64 struct {
|
type CubicCurveFloat64 struct {
|
||||||
X1, Y1, X2, Y2, X3, Y3, X4, Y4 float64
|
X1, Y1, X2, Y2, X3, Y3, X4, Y4 float64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LineTracer interface {
|
||||||
|
LineTo(x, y float64)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//mu ranges from 0 to 1, start to end of curve
|
//mu ranges from 0 to 1, start to end of curve
|
||||||
func (c *CubicCurveFloat64) ArbitraryPoint(mu float64) (x, y float64) {
|
func (c *CubicCurveFloat64) ArbitraryPoint(mu float64) (x, y float64) {
|
||||||
|
|
||||||
|
@ -26,7 +33,7 @@ func (c *CubicCurveFloat64) ArbitraryPoint(mu float64) (x, y float64) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CubicCurveFloat64) SubdivideAt(c1, c2 *CubicCurveFloat64, t float64) {
|
func (c *CubicCurveFloat64) SubdivideAt(c1, c2 *CubicCurveFloat64, t float64) (x23, y23 float64) {
|
||||||
inv_t := (1 - t)
|
inv_t := (1 - t)
|
||||||
c1.X1, c1.Y1 = c.X1, c.Y1
|
c1.X1, c1.Y1 = c.X1, c.Y1
|
||||||
c2.X4, c2.Y4 = c.X4, c.Y4
|
c2.X4, c2.Y4 = c.X4, c.Y4
|
||||||
|
@ -34,8 +41,8 @@ func (c *CubicCurveFloat64) SubdivideAt(c1, c2 *CubicCurveFloat64, t float64) {
|
||||||
c1.X2 = inv_t*c.X1 + t*c.X2
|
c1.X2 = inv_t*c.X1 + t*c.X2
|
||||||
c1.Y2 = inv_t*c.Y1 + t*c.Y2
|
c1.Y2 = inv_t*c.Y1 + t*c.Y2
|
||||||
|
|
||||||
x23 := inv_t*c.X2 + t*c.X3
|
x23 = inv_t*c.X2 + t*c.X3
|
||||||
y23 := inv_t*c.Y2 + t*c.Y3
|
y23 = inv_t*c.Y2 + t*c.Y3
|
||||||
|
|
||||||
c2.X3 = inv_t*c.X3 + t*c.X4
|
c2.X3 = inv_t*c.X3 + t*c.X4
|
||||||
c2.Y3 = inv_t*c.Y3 + t*c.Y4
|
c2.Y3 = inv_t*c.Y3 + t*c.Y4
|
||||||
|
@ -50,17 +57,18 @@ func (c *CubicCurveFloat64) SubdivideAt(c1, c2 *CubicCurveFloat64, t float64) {
|
||||||
c1.Y4 = inv_t*c1.Y3 + t*c2.Y2
|
c1.Y4 = inv_t*c1.Y3 + t*c2.Y2
|
||||||
|
|
||||||
c2.X1, c2.Y1 = c1.X4, c1.Y4
|
c2.X1, c2.Y1 = c1.X4, c1.Y4
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CubicCurveFloat64) Subdivide(c1, c2 *CubicCurveFloat64) {
|
func (c *CubicCurveFloat64) Subdivide(c1, c2 *CubicCurveFloat64) (x23, y23 float64) {
|
||||||
// Calculate all the mid-points of the line segments
|
// Calculate all the mid-points of the line segments
|
||||||
//----------------------
|
//----------------------
|
||||||
c1.X1, c1.Y1 = c.X1, c.Y1
|
c1.X1, c1.Y1 = c.X1, c.Y1
|
||||||
c2.X4, c2.Y4 = c.X4, c.Y4
|
c2.X4, c2.Y4 = c.X4, c.Y4
|
||||||
c1.X2 = (c.X1 + c.X2) / 2
|
c1.X2 = (c.X1 + c.X2) / 2
|
||||||
c1.Y2 = (c.Y1 + c.Y2) / 2
|
c1.Y2 = (c.Y1 + c.Y2) / 2
|
||||||
x23 := (c.X2 + c.X3) / 2
|
x23 = (c.X2 + c.X3) / 2
|
||||||
y23 := (c.Y2 + c.Y3) / 2
|
y23 = (c.Y2 + c.Y3) / 2
|
||||||
c2.X3 = (c.X3 + c.X4) / 2
|
c2.X3 = (c.X3 + c.X4) / 2
|
||||||
c2.Y3 = (c.Y3 + c.Y4) / 2
|
c2.Y3 = (c.Y3 + c.Y4) / 2
|
||||||
c1.X3 = (c1.X2 + x23) / 2
|
c1.X3 = (c1.X2 + x23) / 2
|
||||||
|
@ -70,6 +78,7 @@ func (c *CubicCurveFloat64) Subdivide(c1, c2 *CubicCurveFloat64) {
|
||||||
c1.X4 = (c1.X3 + c2.X2) / 2
|
c1.X4 = (c1.X3 + c2.X2) / 2
|
||||||
c1.Y4 = (c1.Y3 + c2.Y2) / 2
|
c1.Y4 = (c1.Y3 + c2.Y2) / 2
|
||||||
c2.X1, c2.Y1 = c1.X4, c1.Y4
|
c2.X1, c2.Y1 = c1.X4, c1.Y4
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CubicCurveFloat64) EstimateDistance() float64 {
|
func (c *CubicCurveFloat64) EstimateDistance() float64 {
|
||||||
|
@ -83,19 +92,14 @@ func (c *CubicCurveFloat64) EstimateDistance() float64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// subdivide the curve in straight lines using line approximation and Casteljau recursive subdivision
|
// subdivide the curve in straight lines using line approximation and Casteljau recursive subdivision
|
||||||
func (c *CubicCurveFloat64) SegmentRec(segments []float64) []float64 {
|
func (c *CubicCurveFloat64) SegmentRec(t LineTracer, flattening_threshold float64) {
|
||||||
// reinit segments
|
// reinit segments
|
||||||
segments = segments[0 : len(segments)+2]
|
t.LineTo(c.X1, c.Y1)
|
||||||
segments[len(segments)-2] = c.X1
|
c.segmentRec(t, flattening_threshold)
|
||||||
segments[len(segments)-1] = c.Y1
|
t.LineTo(c.X4, c.Y4)
|
||||||
segments = c.segmentRec(segments)
|
|
||||||
segments = segments[0 : len(segments)+2]
|
|
||||||
segments[len(segments)-2] = c.X4
|
|
||||||
segments[len(segments)-1] = c.Y4
|
|
||||||
return segments
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *CubicCurveFloat64) segmentRec(segments []float64) []float64 {
|
func (c *CubicCurveFloat64) segmentRec(t LineTracer, flattening_threshold float64) {
|
||||||
var c1, c2 CubicCurveFloat64
|
var c1, c2 CubicCurveFloat64
|
||||||
c.Subdivide(&c1, &c2)
|
c.Subdivide(&c1, &c2)
|
||||||
|
|
||||||
|
@ -108,25 +112,20 @@ func (c *CubicCurveFloat64) segmentRec(segments []float64) []float64 {
|
||||||
d3 := math.Fabs(((c.X3-c.X4)*dy - (c.Y3-c.Y4)*dx))
|
d3 := math.Fabs(((c.X3-c.X4)*dy - (c.Y3-c.Y4)*dx))
|
||||||
|
|
||||||
if (d2+d3)*(d2+d3) < flattening_threshold*(dx*dx+dy*dy) {
|
if (d2+d3)*(d2+d3) < flattening_threshold*(dx*dx+dy*dy) {
|
||||||
segments = segments[0 : len(segments)+2]
|
t.LineTo(c.X4, c.Y4)
|
||||||
segments[len(segments)-2] = c2.X4
|
return
|
||||||
segments[len(segments)-1] = c2.Y4
|
|
||||||
return segments
|
|
||||||
}
|
}
|
||||||
// Continue subdivision
|
// Continue subdivision
|
||||||
//----------------------
|
//----------------------
|
||||||
segments = c1.segmentRec(segments)
|
c1.segmentRec(t, flattening_threshold)
|
||||||
segments = c2.segmentRec(segments)
|
c2.segmentRec(t, flattening_threshold)
|
||||||
return segments
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (curve *CubicCurveFloat64) Segment(segments []float64) []float64 {
|
func (curve *CubicCurveFloat64) Segment(t LineTracer, flattening_threshold float64) {
|
||||||
// Add the first point
|
// Add the first point
|
||||||
segments = segments[0 : len(segments)+2]
|
t.LineTo(curve.X1, curve.Y1)
|
||||||
segments[len(segments)-2] = curve.X1
|
|
||||||
segments[len(segments)-1] = curve.Y1
|
|
||||||
|
|
||||||
var curves [32]CubicCurveFloat64
|
var curves [CurveRecursionLimit]CubicCurveFloat64
|
||||||
curves[0] = *curve
|
curves[0] = *curve
|
||||||
i := 0
|
i := 0
|
||||||
// current curve
|
// current curve
|
||||||
|
@ -141,9 +140,7 @@ func (curve *CubicCurveFloat64) Segment(segments []float64) []float64 {
|
||||||
d3 = math.Fabs(((c.X3-c.X4)*dy - (c.Y3-c.Y4)*dx))
|
d3 = math.Fabs(((c.X3-c.X4)*dy - (c.Y3-c.Y4)*dx))
|
||||||
|
|
||||||
if (d2+d3)*(d2+d3) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 {
|
if (d2+d3)*(d2+d3) < flattening_threshold*(dx*dx+dy*dy) || i == len(curves)-1 {
|
||||||
segments = segments[0 : len(segments)+2]
|
t.LineTo(c.X4, c.Y4)
|
||||||
segments[len(segments)-2] = c.X4
|
|
||||||
segments[len(segments)-1] = c.Y4
|
|
||||||
i--
|
i--
|
||||||
} else {
|
} else {
|
||||||
// second half of bezier go lower onto the stack
|
// second half of bezier go lower onto the stack
|
||||||
|
@ -151,5 +148,618 @@ func (curve *CubicCurveFloat64) Segment(segments []float64) []float64 {
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return segments
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
The function has the following parameters:
|
||||||
|
approximationScale :
|
||||||
|
Eventually determines the approximation accuracy. In practice we need to transform points from the World coordinate system to the Screen one.
|
||||||
|
It always has some scaling coefficient.
|
||||||
|
The curves are usually processed in the World coordinates, while the approximation accuracy should be eventually in pixels.
|
||||||
|
Usually it looks as follows:
|
||||||
|
curved.approximationScale(transform.scale());
|
||||||
|
where transform is the affine matrix that includes all the transformations, including viewport and zoom.
|
||||||
|
angleTolerance :
|
||||||
|
You set it in radians.
|
||||||
|
The less this value is the more accurate will be the approximation at sharp turns.
|
||||||
|
But 0 means that we don't consider angle conditions at all.
|
||||||
|
cuspLimit :
|
||||||
|
An angle in radians.
|
||||||
|
If 0, only the real cusps will have bevel cuts.
|
||||||
|
If more than 0, it will restrict the sharpness.
|
||||||
|
The more this value is the less sharp turns will be cut.
|
||||||
|
Typically it should not exceed 10-15 degrees.
|
||||||
|
*/
|
||||||
|
func (c *CubicCurveFloat64) AdaptiveSegmentRec(t LineTracer, approximationScale, angleTolerance, cuspLimit float64) {
|
||||||
|
cuspLimit = computeCuspLimit(cuspLimit)
|
||||||
|
distanceToleranceSquare := 0.5 / approximationScale
|
||||||
|
distanceToleranceSquare = distanceToleranceSquare * distanceToleranceSquare
|
||||||
|
t.LineTo(c.X1, c.Y1)
|
||||||
|
c.adaptiveSegmentRec(t, 0, distanceToleranceSquare, angleTolerance, cuspLimit)
|
||||||
|
t.LineTo(c.X4, c.Y4)
|
||||||
|
}
|
||||||
|
|
||||||
|
func computeCuspLimit(v float64) (r float64) {
|
||||||
|
if v == 0.0 {
|
||||||
|
r = 0.0
|
||||||
|
} else {
|
||||||
|
r = math.Pi - v
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func squareDistance(x1, y1, x2, y2 float64) float64 {
|
||||||
|
dx := x2 - x1
|
||||||
|
dy := y2 - y1
|
||||||
|
return dx*dx + dy*dy
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* http://www.antigrain.com/research/adaptive_bezier/index.html
|
||||||
|
*/
|
||||||
|
func (c *CubicCurveFloat64) adaptiveSegmentRec(t LineTracer, level int, distanceToleranceSquare, angleTolerance, cuspLimit float64) {
|
||||||
|
if level > CurveRecursionLimit {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var c1, c2 CubicCurveFloat64
|
||||||
|
x23, y23 := c.Subdivide(&c1, &c2)
|
||||||
|
|
||||||
|
// Try to approximate the full cubic curve by a single straight line
|
||||||
|
//------------------
|
||||||
|
dx := c.X4 - c.X1
|
||||||
|
dy := c.Y4 - c.Y1
|
||||||
|
|
||||||
|
d2 := math.Fabs(((c.X2-c.X4)*dy - (c.Y2-c.Y4)*dx))
|
||||||
|
d3 := math.Fabs(((c.X3-c.X4)*dy - (c.Y3-c.Y4)*dx))
|
||||||
|
switch {
|
||||||
|
case d2 <= CurveCollinearityEpsilon && d3 <= CurveCollinearityEpsilon:
|
||||||
|
// All collinear OR p1==p4
|
||||||
|
//----------------------
|
||||||
|
k := dx*dx + dy*dy
|
||||||
|
if k == 0 {
|
||||||
|
d2 = squareDistance(c.X1, c.Y1, c.X2, c.Y2)
|
||||||
|
d3 = squareDistance(c.X4, c.Y4, c.X3, c.Y3)
|
||||||
|
} else {
|
||||||
|
k = 1 / k
|
||||||
|
da1 := c.X2 - c.X1
|
||||||
|
da2 := c.Y2 - c.Y1
|
||||||
|
d2 = k * (da1*dx + da2*dy)
|
||||||
|
da1 = c.X3 - c.X1
|
||||||
|
da2 = c.Y3 - c.Y1
|
||||||
|
d3 = k * (da1*dx + da2*dy)
|
||||||
|
if d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1 {
|
||||||
|
// Simple collinear case, 1---2---3---4
|
||||||
|
// We can leave just two endpoints
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if d2 <= 0 {
|
||||||
|
d2 = squareDistance(c.X2, c.Y2, c.X1, c.Y1)
|
||||||
|
} else if d2 >= 1 {
|
||||||
|
d2 = squareDistance(c.X2, c.Y2, c.X4, c.Y4)
|
||||||
|
} else {
|
||||||
|
d2 = squareDistance(c.X2, c.Y2, c.X1+d2*dx, c.Y1+d2*dy)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d3 <= 0 {
|
||||||
|
d3 = squareDistance(c.X3, c.Y3, c.X1, c.Y1)
|
||||||
|
} else if d3 >= 1 {
|
||||||
|
d3 = squareDistance(c.X3, c.Y3, c.X4, c.Y4)
|
||||||
|
} else {
|
||||||
|
d3 = squareDistance(c.X3, c.Y3, c.X1+d3*dx, c.Y1+d3*dy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if d2 > d3 {
|
||||||
|
if d2 < distanceToleranceSquare {
|
||||||
|
t.LineTo(c.X2, c.Y2)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if d3 < distanceToleranceSquare {
|
||||||
|
t.LineTo(c.X3, c.Y3)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case d2 <= CurveCollinearityEpsilon && d3 > CurveCollinearityEpsilon:
|
||||||
|
// p1,p2,p4 are collinear, p3 is significant
|
||||||
|
//----------------------
|
||||||
|
if d3*d3 <= distanceToleranceSquare*(dx*dx+dy*dy) {
|
||||||
|
if angleTolerance < CurveAngleToleranceEpsilon {
|
||||||
|
t.LineTo(x23, y23)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Angle Condition
|
||||||
|
//----------------------
|
||||||
|
da1 := math.Fabs(math.Atan2(c.Y4-c.Y3, c.X4-c.X3) - math.Atan2(c.Y3-c.Y2, c.X3-c.X2))
|
||||||
|
if da1 >= math.Pi {
|
||||||
|
da1 = 2*math.Pi - da1
|
||||||
|
}
|
||||||
|
|
||||||
|
if da1 < angleTolerance {
|
||||||
|
t.LineTo(c.X2, c.Y2)
|
||||||
|
t.LineTo(c.X3, c.Y3)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cuspLimit != 0.0 {
|
||||||
|
if da1 > cuspLimit {
|
||||||
|
t.LineTo(c.X3, c.Y3)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case d2 > CurveCollinearityEpsilon && d3 <= CurveCollinearityEpsilon:
|
||||||
|
// p1,p3,p4 are collinear, p2 is significant
|
||||||
|
//----------------------
|
||||||
|
if d2*d2 <= distanceToleranceSquare*(dx*dx+dy*dy) {
|
||||||
|
if angleTolerance < CurveAngleToleranceEpsilon {
|
||||||
|
t.LineTo(x23, y23)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Angle Condition
|
||||||
|
//----------------------
|
||||||
|
da1 := math.Fabs(math.Atan2(c.Y3-c.Y2, c.X3-c.X2) - math.Atan2(c.Y2-c.Y1, c.X2-c.X1))
|
||||||
|
if da1 >= math.Pi {
|
||||||
|
da1 = 2*math.Pi - da1
|
||||||
|
}
|
||||||
|
|
||||||
|
if da1 < angleTolerance {
|
||||||
|
t.LineTo(c.X2, c.Y2)
|
||||||
|
t.LineTo(c.X3, c.Y3)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cuspLimit != 0.0 {
|
||||||
|
if da1 > cuspLimit {
|
||||||
|
t.LineTo(c.X2, c.Y2)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case d2 > CurveCollinearityEpsilon && d3 > CurveCollinearityEpsilon:
|
||||||
|
// Regular case
|
||||||
|
//-----------------
|
||||||
|
if (d2+d3)*(d2+d3) <= distanceToleranceSquare*(dx*dx+dy*dy) {
|
||||||
|
// If the curvature doesn't exceed the distanceTolerance value
|
||||||
|
// we tend to finish subdivisions.
|
||||||
|
//----------------------
|
||||||
|
if angleTolerance < CurveAngleToleranceEpsilon {
|
||||||
|
t.LineTo(x23, y23)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Angle & Cusp Condition
|
||||||
|
//----------------------
|
||||||
|
k := math.Atan2(c.Y3-c.Y2, c.X3-c.X2)
|
||||||
|
da1 := math.Fabs(k - math.Atan2(c.Y2-c.Y1, c.X2-c.X1))
|
||||||
|
da2 := math.Fabs(math.Atan2(c.Y4-c.Y3, c.X4-c.X3) - k)
|
||||||
|
if da1 >= math.Pi {
|
||||||
|
da1 = 2*math.Pi - da1
|
||||||
|
}
|
||||||
|
if da2 >= math.Pi {
|
||||||
|
da2 = 2*math.Pi - da2
|
||||||
|
}
|
||||||
|
|
||||||
|
if da1+da2 < angleTolerance {
|
||||||
|
// Finally we can stop the recursion
|
||||||
|
//----------------------
|
||||||
|
t.LineTo(x23, y23)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if cuspLimit != 0.0 {
|
||||||
|
if da1 > cuspLimit {
|
||||||
|
t.LineTo(c.X2, c.Y2)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if da2 > cuspLimit {
|
||||||
|
t.LineTo(c.X3, c.Y3)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue subdivision
|
||||||
|
//----------------------
|
||||||
|
c1.adaptiveSegmentRec(t, level+1, distanceToleranceSquare, angleTolerance, cuspLimit)
|
||||||
|
c2.adaptiveSegmentRec(t, level+1, distanceToleranceSquare, angleTolerance, cuspLimit)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (curve *CubicCurveFloat64) AdaptiveSegment(t LineTracer, approximationScale, angleTolerance, cuspLimit float64) {
|
||||||
|
// Add the first point
|
||||||
|
t.LineTo(curve.X1, curve.Y1)
|
||||||
|
cuspLimit = computeCuspLimit(cuspLimit)
|
||||||
|
distanceToleranceSquare := 0.5 / approximationScale
|
||||||
|
distanceToleranceSquare = distanceToleranceSquare * distanceToleranceSquare
|
||||||
|
|
||||||
|
var curves [CurveRecursionLimit]CubicCurveFloat64
|
||||||
|
curves[0] = *curve
|
||||||
|
i := 0
|
||||||
|
// current curve
|
||||||
|
var c *CubicCurveFloat64
|
||||||
|
var c1, c2 CubicCurveFloat64
|
||||||
|
var dx, dy, d2, d3, k, x23, y23 float64
|
||||||
|
for i >= 0 {
|
||||||
|
c = &curves[i]
|
||||||
|
x23, y23 = c.Subdivide(&c1, &c2)
|
||||||
|
|
||||||
|
// Try to approximate the full cubic curve by a single straight line
|
||||||
|
//------------------
|
||||||
|
dx = c.X4 - c.X1
|
||||||
|
dy = c.Y4 - c.Y1
|
||||||
|
|
||||||
|
d2 = math.Fabs(((c.X2-c.X4)*dy - (c.Y2-c.Y4)*dx))
|
||||||
|
d3 = math.Fabs(((c.X3-c.X4)*dy - (c.Y3-c.Y4)*dx))
|
||||||
|
switch {
|
||||||
|
case i == len(curves)-1:
|
||||||
|
t.LineTo(c.X4, c.Y4)
|
||||||
|
i--
|
||||||
|
continue
|
||||||
|
case d2 <= CurveCollinearityEpsilon && d3 <= CurveCollinearityEpsilon:
|
||||||
|
// All collinear OR p1==p4
|
||||||
|
//----------------------
|
||||||
|
k = dx*dx + dy*dy
|
||||||
|
if k == 0 {
|
||||||
|
d2 = squareDistance(c.X1, c.Y1, c.X2, c.Y2)
|
||||||
|
d3 = squareDistance(c.X4, c.Y4, c.X3, c.Y3)
|
||||||
|
} else {
|
||||||
|
k = 1 / k
|
||||||
|
da1 := c.X2 - c.X1
|
||||||
|
da2 := c.Y2 - c.Y1
|
||||||
|
d2 = k * (da1*dx + da2*dy)
|
||||||
|
da1 = c.X3 - c.X1
|
||||||
|
da2 = c.Y3 - c.Y1
|
||||||
|
d3 = k * (da1*dx + da2*dy)
|
||||||
|
if d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1 {
|
||||||
|
// Simple collinear case, 1---2---3---4
|
||||||
|
// We can leave just two endpoints
|
||||||
|
i--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if d2 <= 0 {
|
||||||
|
d2 = squareDistance(c.X2, c.Y2, c.X1, c.Y1)
|
||||||
|
} else if d2 >= 1 {
|
||||||
|
d2 = squareDistance(c.X2, c.Y2, c.X4, c.Y4)
|
||||||
|
} else {
|
||||||
|
d2 = squareDistance(c.X2, c.Y2, c.X1+d2*dx, c.Y1+d2*dy)
|
||||||
|
}
|
||||||
|
|
||||||
|
if d3 <= 0 {
|
||||||
|
d3 = squareDistance(c.X3, c.Y3, c.X1, c.Y1)
|
||||||
|
} else if d3 >= 1 {
|
||||||
|
d3 = squareDistance(c.X3, c.Y3, c.X4, c.Y4)
|
||||||
|
} else {
|
||||||
|
d3 = squareDistance(c.X3, c.Y3, c.X1+d3*dx, c.Y1+d3*dy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if d2 > d3 {
|
||||||
|
if d2 < distanceToleranceSquare {
|
||||||
|
t.LineTo(c.X2, c.Y2)
|
||||||
|
i--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if d3 < distanceToleranceSquare {
|
||||||
|
t.LineTo(c.X3, c.Y3)
|
||||||
|
i--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case d2 <= CurveCollinearityEpsilon && d3 > CurveCollinearityEpsilon:
|
||||||
|
// p1,p2,p4 are collinear, p3 is significant
|
||||||
|
//----------------------
|
||||||
|
if d3*d3 <= distanceToleranceSquare*(dx*dx+dy*dy) {
|
||||||
|
if angleTolerance < CurveAngleToleranceEpsilon {
|
||||||
|
t.LineTo(x23, y23)
|
||||||
|
i--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Angle Condition
|
||||||
|
//----------------------
|
||||||
|
da1 := math.Fabs(math.Atan2(c.Y4-c.Y3, c.X4-c.X3) - math.Atan2(c.Y3-c.Y2, c.X3-c.X2))
|
||||||
|
if da1 >= math.Pi {
|
||||||
|
da1 = 2*math.Pi - da1
|
||||||
|
}
|
||||||
|
|
||||||
|
if da1 < angleTolerance {
|
||||||
|
t.LineTo(c.X2, c.Y2)
|
||||||
|
t.LineTo(c.X3, c.Y3)
|
||||||
|
i--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if cuspLimit != 0.0 {
|
||||||
|
if da1 > cuspLimit {
|
||||||
|
t.LineTo(c.X3, c.Y3)
|
||||||
|
i--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case d2 > CurveCollinearityEpsilon && d3 <= CurveCollinearityEpsilon:
|
||||||
|
// p1,p3,p4 are collinear, p2 is significant
|
||||||
|
//----------------------
|
||||||
|
if d2*d2 <= distanceToleranceSquare*(dx*dx+dy*dy) {
|
||||||
|
if angleTolerance < CurveAngleToleranceEpsilon {
|
||||||
|
t.LineTo(x23, y23)
|
||||||
|
i--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Angle Condition
|
||||||
|
//----------------------
|
||||||
|
da1 := math.Fabs(math.Atan2(c.Y3-c.Y2, c.X3-c.X2) - math.Atan2(c.Y2-c.Y1, c.X2-c.X1))
|
||||||
|
if da1 >= math.Pi {
|
||||||
|
da1 = 2*math.Pi - da1
|
||||||
|
}
|
||||||
|
|
||||||
|
if da1 < angleTolerance {
|
||||||
|
t.LineTo(c.X2, c.Y2)
|
||||||
|
t.LineTo(c.X3, c.Y3)
|
||||||
|
i--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if cuspLimit != 0.0 {
|
||||||
|
if da1 > cuspLimit {
|
||||||
|
t.LineTo(c.X2, c.Y2)
|
||||||
|
i--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case d2 > CurveCollinearityEpsilon && d3 > CurveCollinearityEpsilon:
|
||||||
|
// Regular case
|
||||||
|
//-----------------
|
||||||
|
if (d2+d3)*(d2+d3) <= distanceToleranceSquare*(dx*dx+dy*dy) {
|
||||||
|
// If the curvature doesn't exceed the distanceTolerance value
|
||||||
|
// we tend to finish subdivisions.
|
||||||
|
//----------------------
|
||||||
|
if angleTolerance < CurveAngleToleranceEpsilon {
|
||||||
|
t.LineTo(x23, y23)
|
||||||
|
i--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Angle & Cusp Condition
|
||||||
|
//----------------------
|
||||||
|
k := math.Atan2(c.Y3-c.Y2, c.X3-c.X2)
|
||||||
|
da1 := math.Fabs(k - math.Atan2(c.Y2-c.Y1, c.X2-c.X1))
|
||||||
|
da2 := math.Fabs(math.Atan2(c.Y4-c.Y3, c.X4-c.X3) - k)
|
||||||
|
if da1 >= math.Pi {
|
||||||
|
da1 = 2*math.Pi - da1
|
||||||
|
}
|
||||||
|
if da2 >= math.Pi {
|
||||||
|
da2 = 2*math.Pi - da2
|
||||||
|
}
|
||||||
|
|
||||||
|
if da1+da2 < angleTolerance {
|
||||||
|
// Finally we can stop the recursion
|
||||||
|
//----------------------
|
||||||
|
t.LineTo(x23, y23)
|
||||||
|
i--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if cuspLimit != 0.0 {
|
||||||
|
if da1 > cuspLimit {
|
||||||
|
t.LineTo(c.X2, c.Y2)
|
||||||
|
i--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if da2 > cuspLimit {
|
||||||
|
t.LineTo(c.X3, c.Y3)
|
||||||
|
i--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue subdivision
|
||||||
|
//----------------------
|
||||||
|
curves[i+1], curves[i] = c1, c2
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
t.LineTo(curve.X4, curve.Y4)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/********************** Ahmad thesis *******************/
|
||||||
|
|
||||||
|
/**************************************************************************************
|
||||||
|
* This code is the implementation of the Parabolic Approximation (PA). Although *
|
||||||
|
* it uses recursive subdivision as a safe net for the failing cases, this is an *
|
||||||
|
* iterative routine and reduces considerably the number of vertices (point) *
|
||||||
|
* generation. *
|
||||||
|
**************************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
func (c *CubicCurveFloat64) ParabolicSegment(t LineTracer, flattening_threshold float64) {
|
||||||
|
t.LineTo(c.X1, c.Y1)
|
||||||
|
estimatedIFP := c.numberOfInflectionPoints()
|
||||||
|
if estimatedIFP == 0 {
|
||||||
|
// If no inflection points then apply PA on the full Bezier segment.
|
||||||
|
c.doParabolicApproximation(t, flattening_threshold)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// If one or more inflection point then we will have to subdivide the curve
|
||||||
|
numOfIfP, t1, t2 := c.findInflectionPoints()
|
||||||
|
if numOfIfP == 2 {
|
||||||
|
// Case when 2 inflection points then divide at the smallest one first
|
||||||
|
var sub1, tmp1, sub2, sub3 CubicCurveFloat64
|
||||||
|
c.SubdivideAt(&sub1, &tmp1, t1)
|
||||||
|
// Now find the second inflection point in the second curve an subdivide
|
||||||
|
numOfIfP, t1, t2 = tmp1.findInflectionPoints()
|
||||||
|
if numOfIfP == 2 {
|
||||||
|
tmp1.SubdivideAt(&sub2, &sub3, t2)
|
||||||
|
} else if numOfIfP == 1 {
|
||||||
|
tmp1.SubdivideAt(&sub2, &sub3, t1)
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Use PA for first subsegment
|
||||||
|
sub1.doParabolicApproximation(t, flattening_threshold)
|
||||||
|
// Use RS for the second (middle) subsegment
|
||||||
|
sub2.Segment(t, flattening_threshold)
|
||||||
|
// Drop the last point in the array will be added by the PA in third subsegment
|
||||||
|
//noOfPoints--;
|
||||||
|
// Use PA for the third curve
|
||||||
|
sub3.doParabolicApproximation(t, flattening_threshold)
|
||||||
|
} else if numOfIfP == 1 {
|
||||||
|
// Case where there is one inflection point, subdivide once and use PA on
|
||||||
|
// both subsegments
|
||||||
|
var sub1, sub2 CubicCurveFloat64
|
||||||
|
c.SubdivideAt(&sub1, &sub2, t1)
|
||||||
|
sub1.doParabolicApproximation(t, flattening_threshold)
|
||||||
|
//noOfPoints--;
|
||||||
|
sub2.doParabolicApproximation(t, flattening_threshold)
|
||||||
|
} else {
|
||||||
|
// Case where there is no inflection USA PA directly
|
||||||
|
c.doParabolicApproximation(t, flattening_threshold)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the third control point deviation form the axis
|
||||||
|
func (c *CubicCurveFloat64) thirdControlPointDeviation() float64 {
|
||||||
|
dx := c.X2 - c.X1
|
||||||
|
dy := c.Y2 - c.Y1
|
||||||
|
l2 := dx*dx + dy*dy
|
||||||
|
if l2 == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
l := math.Sqrt(l2)
|
||||||
|
r := (c.Y2 - c.Y1) / l
|
||||||
|
s := (c.X1 - c.X2) / l
|
||||||
|
u := (c.X2*c.Y1 - c.X1*c.Y2) / l
|
||||||
|
return math.Fabs(r*c.X3 + s*c.Y3 + u)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the number of inflection point
|
||||||
|
func (c *CubicCurveFloat64) numberOfInflectionPoints() int {
|
||||||
|
dx21 := (c.X2 - c.X1)
|
||||||
|
dy21 := (c.Y2 - c.Y1)
|
||||||
|
dx32 := (c.X3 - c.X2)
|
||||||
|
dy32 := (c.Y3 - c.Y2)
|
||||||
|
dx43 := (c.X4 - c.X3)
|
||||||
|
dy43 := (c.Y4 - c.Y3)
|
||||||
|
if ((dx21*dy32 - dy21*dx32) * (dx32*dy43 - dy32*dx43)) < 0 {
|
||||||
|
return 1 // One inflection point
|
||||||
|
} else if ((dx21*dy32 - dy21*dx32) * (dx21*dy43 - dy21*dx43)) > 0 {
|
||||||
|
return 0 // No inflection point
|
||||||
|
} else {
|
||||||
|
// Most cases no inflection point
|
||||||
|
b1 := (dx21*dx32 + dy21*dy32) > 0
|
||||||
|
b2 := (dx32*dx43 + dy32*dy43) > 0
|
||||||
|
if b1 || b2 && !(b1 && b2) { // xor!!
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1 // cases where there in zero or two inflection points
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// This is the main function where all the work is done
|
||||||
|
func (curve *CubicCurveFloat64) doParabolicApproximation(tracer LineTracer, flattening_threshold float64) {
|
||||||
|
var c *CubicCurveFloat64
|
||||||
|
c = curve
|
||||||
|
var d, t, dx, dy, d2, d3 float64
|
||||||
|
for {
|
||||||
|
dx = c.X4 - c.X1
|
||||||
|
dy = c.Y4 - c.Y1
|
||||||
|
|
||||||
|
d2 = math.Fabs(((c.X2-c.X4)*dy - (c.Y2-c.Y4)*dx))
|
||||||
|
d3 = math.Fabs(((c.X3-c.X4)*dy - (c.Y3-c.Y4)*dx))
|
||||||
|
|
||||||
|
if (d2+d3)*(d2+d3) < flattening_threshold*(dx*dx+dy*dy) {
|
||||||
|
// If the subsegment deviation satisfy the flatness then store the last
|
||||||
|
// point and stop
|
||||||
|
tracer.LineTo(c.X4, c.Y4)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Find the third control point deviation and the t values for subdivision
|
||||||
|
d = c.thirdControlPointDeviation()
|
||||||
|
t = 2 * math.Sqrt(flattening_threshold/d/3)
|
||||||
|
if t > 1 {
|
||||||
|
// Case where the t value calculated is invalid so using RS
|
||||||
|
c.Segment(tracer, flattening_threshold)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// Valid t value to subdivide at that calculated value
|
||||||
|
var b1, b2 CubicCurveFloat64
|
||||||
|
c.SubdivideAt(&b1, &b2, t)
|
||||||
|
// First subsegment should have its deviation equal to flatness
|
||||||
|
dx = b1.X4 - b1.X1
|
||||||
|
dy = b1.Y4 - b1.Y1
|
||||||
|
|
||||||
|
d2 = math.Fabs(((b1.X2-b1.X4)*dy - (b1.Y2-b1.Y4)*dx))
|
||||||
|
d3 = math.Fabs(((b1.X3-b1.X4)*dy - (b1.Y3-b1.Y4)*dx))
|
||||||
|
|
||||||
|
if (d2+d3)*(d2+d3) > flattening_threshold*(dx*dx+dy*dy) {
|
||||||
|
// if not then use RS to handle any mathematical errors
|
||||||
|
b1.Segment(tracer, flattening_threshold)
|
||||||
|
} else {
|
||||||
|
tracer.LineTo(b1.X4, b1.Y4)
|
||||||
|
}
|
||||||
|
// repeat the process for the left over subsegment.
|
||||||
|
c = &b2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the actual inflection points and return the number of inflection points found
|
||||||
|
// if 2 inflection points found, the first one returned will be with smaller t value.
|
||||||
|
func (curve *CubicCurveFloat64) findInflectionPoints() (int, firstIfp, secondIfp float64) {
|
||||||
|
// For Cubic Bezier curve with equation P=a*t^3 + b*t^2 + c*t + d
|
||||||
|
// slope of the curve dP/dt = 3*a*t^2 + 2*b*t + c
|
||||||
|
// a = (float)(-bez.p1 + 3*bez.p2 - 3*bez.p3 + bez.p4);
|
||||||
|
// b = (float)(3*bez.p1 - 6*bez.p2 + 3*bez.p3);
|
||||||
|
// c = (float)(-3*bez.p1 + 3*bez.p2);
|
||||||
|
ax := (-curve.X1 + 3*curve.X2 - 3*curve.X3 + curve.X4)
|
||||||
|
bx := (3*curve.X1 - 6*curve.X2 + 3*curve.X3)
|
||||||
|
cx := (-3*curve.X1 + 3*curve.X2)
|
||||||
|
ay := (-curve.Y1 + 3*curve.Y2 - 3*curve.Y3 + curve.Y4)
|
||||||
|
by := (3*curve.Y1 - 6*curve.Y2 + 3*curve.Y3)
|
||||||
|
cy := (-3*curve.Y1 + 3*curve.Y2)
|
||||||
|
a := (3 * (ay*bx - ax*by))
|
||||||
|
b := (3 * (ay*cx - ax*cy))
|
||||||
|
c := (by*cx - bx*cy)
|
||||||
|
r2 := (b*b - 4*a*c)
|
||||||
|
firstIfp = 0.0
|
||||||
|
secondIfp = 0.0
|
||||||
|
if r2 >= 0.0 && a != 0.0 {
|
||||||
|
r := math.Sqrt(r2)
|
||||||
|
firstIfp = ((-b + r) / (2 * a))
|
||||||
|
secondIfp = ((-b - r) / (2 * a))
|
||||||
|
if (firstIfp > 0.0 && firstIfp < 1.0) && (secondIfp > 0.0 && secondIfp < 1.0) {
|
||||||
|
if firstIfp > secondIfp {
|
||||||
|
tmp := firstIfp
|
||||||
|
firstIfp = secondIfp
|
||||||
|
secondIfp = tmp
|
||||||
|
}
|
||||||
|
if secondIfp-firstIfp > 0.00001 {
|
||||||
|
return 2, firstIfp, secondIfp
|
||||||
|
} else {
|
||||||
|
return 1, firstIfp, secondIfp
|
||||||
|
}
|
||||||
|
} else if firstIfp > 0.0 && firstIfp < 1.0 {
|
||||||
|
return 1, firstIfp, secondIfp
|
||||||
|
} else if secondIfp > 0.0 && secondIfp < 1.0 {
|
||||||
|
firstIfp = secondIfp
|
||||||
|
return 1, firstIfp, secondIfp
|
||||||
|
}
|
||||||
|
return 0, firstIfp, secondIfp
|
||||||
|
}
|
||||||
|
return 0, firstIfp, secondIfp
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,13 +14,33 @@ import (
|
||||||
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
flattening_threshold float64 = 0.25
|
||||||
testsFloat64 = []CubicCurveFloat64{
|
testsFloat64 = []CubicCurveFloat64{
|
||||||
CubicCurveFloat64{100, 100, 200, 100, 100, 200, 200, 200},
|
CubicCurveFloat64{100, 100, 200, 100, 100, 200, 200, 200},
|
||||||
CubicCurveFloat64{100, 100, 300, 200, 200, 200, 300, 100},
|
CubicCurveFloat64{100, 100, 300, 200, 200, 200, 300, 100},
|
||||||
CubicCurveFloat64{100, 100, 0, 300, 200, 0, 300, 300},
|
CubicCurveFloat64{100, 100, 0, 300, 200, 0, 300, 300},
|
||||||
|
CubicCurveFloat64{150, 290, 10, 10, 290, 10, 150, 290},
|
||||||
|
CubicCurveFloat64{10, 290, 10, 10, 290, 10, 290, 290},
|
||||||
|
CubicCurveFloat64{100, 290, 290, 10, 10, 10, 200, 290},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Path struct {
|
||||||
|
points []float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Path) LineTo(x, y float64) {
|
||||||
|
if len(p.points)+2 > cap(p.points) {
|
||||||
|
points := make([]float64, len(p.points)+2, len(p.points)+32)
|
||||||
|
copy(points, p.points)
|
||||||
|
p.points = points
|
||||||
|
} else {
|
||||||
|
p.points = p.points[0 : len(p.points)+2]
|
||||||
|
}
|
||||||
|
p.points[len(p.points)-2] = x
|
||||||
|
p.points[len(p.points)-1] = y
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
f, err := os.Create("_test.html")
|
f, err := os.Create("_test.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -31,7 +51,7 @@ func init() {
|
||||||
log.Printf("Create html viewer")
|
log.Printf("Create html viewer")
|
||||||
f.Write([]byte("<html><body>"))
|
f.Write([]byte("<html><body>"))
|
||||||
for i := 0; i < len(testsFloat64); i++ {
|
for i := 0; i < len(testsFloat64); i++ {
|
||||||
f.Write([]byte(fmt.Sprintf("<div><img src='_testRec%d.png'/><img src='_test%d.png'/></div>", i, i)))
|
f.Write([]byte(fmt.Sprintf("<div><img src='_testRec%d.png'/>\n<img src='_test%d.png'/>\n<img src='_testAdaptiveRec%d.png'/>\n<img src='_testAdaptive%d.png'/>\n<img src='_testParabolic%d.png'/>\n</div>\n", i, i, i, i, i)))
|
||||||
}
|
}
|
||||||
f.Write([]byte("</body></html>"))
|
f.Write([]byte("</body></html>"))
|
||||||
|
|
||||||
|
@ -76,48 +96,84 @@ func drawPoints(img draw.Image, c image.Color, s ...float64) image.Image {
|
||||||
|
|
||||||
func TestCubicCurveRec(t *testing.T) {
|
func TestCubicCurveRec(t *testing.T) {
|
||||||
for i, curve := range testsFloat64 {
|
for i, curve := range testsFloat64 {
|
||||||
d := curve.EstimateDistance()
|
var p Path
|
||||||
log.Printf("Distance estimation: %f\n", d)
|
curve.SegmentRec(&p, flattening_threshold)
|
||||||
numSegments := int(d * 0.25)
|
|
||||||
log.Printf("Max segments estimation: %d\n", numSegments)
|
|
||||||
s := make([]float64, 0, numSegments)
|
|
||||||
s = curve.SegmentRec(s)
|
|
||||||
img := image.NewNRGBA(300, 300)
|
img := image.NewNRGBA(300, 300)
|
||||||
raster.PolylineBresenham(img, image.NRGBAColor{0xff, 0, 0, 0xff}, curve.X1, curve.Y1, curve.X2, curve.Y2, curve.X3, curve.Y3, curve.X4, curve.Y4)
|
raster.PolylineBresenham(img, image.NRGBAColor{0xff, 0, 0, 0xff}, curve.X1, curve.Y1, curve.X2, curve.Y2, curve.X3, curve.Y3, curve.X4, curve.Y4)
|
||||||
raster.PolylineBresenham(img, image.Black, s...)
|
raster.PolylineBresenham(img, image.Black, p.points...)
|
||||||
drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve.X1, curve.Y1, curve.X2, curve.Y2, curve.X3, curve.Y3, curve.X4, curve.Y4)
|
//drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve.X1, curve.Y1, curve.X2, curve.Y2, curve.X3, curve.Y3, curve.X4, curve.Y4)
|
||||||
drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, s...)
|
drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...)
|
||||||
savepng(fmt.Sprintf("_testRec%d.png", i), img)
|
savepng(fmt.Sprintf("_testRec%d.png", i), img)
|
||||||
log.Printf("Num of points: %d\n", len(s))
|
log.Printf("Num of points: %d\n", len(p.points))
|
||||||
}
|
}
|
||||||
|
fmt.Println()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCubicCurve(t *testing.T) {
|
func TestCubicCurve(t *testing.T) {
|
||||||
for i, curve := range testsFloat64 {
|
for i, curve := range testsFloat64 {
|
||||||
d := curve.EstimateDistance()
|
var p Path
|
||||||
log.Printf("Distance estimation: %f\n", d)
|
curve.Segment(&p, flattening_threshold)
|
||||||
numSegments := int(d * 0.25)
|
|
||||||
log.Printf("Max segments estimation: %d\n", numSegments)
|
|
||||||
s := make([]float64, 0, numSegments)
|
|
||||||
s = curve.Segment(s)
|
|
||||||
img := image.NewNRGBA(300, 300)
|
img := image.NewNRGBA(300, 300)
|
||||||
raster.PolylineBresenham(img, image.NRGBAColor{0xff, 0, 0, 0xff}, curve.X1, curve.Y1, curve.X2, curve.Y2, curve.X3, curve.Y3, curve.X4, curve.Y4)
|
raster.PolylineBresenham(img, image.NRGBAColor{0xff, 0, 0, 0xff}, curve.X1, curve.Y1, curve.X2, curve.Y2, curve.X3, curve.Y3, curve.X4, curve.Y4)
|
||||||
raster.PolylineBresenham(img, image.Black, s...)
|
raster.PolylineBresenham(img, image.Black, p.points...)
|
||||||
drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve.X1, curve.Y1, curve.X2, curve.Y2, curve.X3, curve.Y3, curve.X4, curve.Y4)
|
//drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve.X1, curve.Y1, curve.X2, curve.Y2, curve.X3, curve.Y3, curve.X4, curve.Y4)
|
||||||
drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, s...)
|
drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...)
|
||||||
savepng(fmt.Sprintf("_test%d.png", i), img)
|
savepng(fmt.Sprintf("_test%d.png", i), img)
|
||||||
log.Printf("Num of points: %d\n", len(s))
|
log.Printf("Num of points: %d\n", len(p.points))
|
||||||
}
|
}
|
||||||
|
fmt.Println()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCubicCurveAdaptiveRec(t *testing.T) {
|
||||||
|
for i, curve := range testsFloat64 {
|
||||||
|
var p Path
|
||||||
|
curve.AdaptiveSegmentRec(&p, 1, 0, 0)
|
||||||
|
img := image.NewNRGBA(300, 300)
|
||||||
|
raster.PolylineBresenham(img, image.NRGBAColor{0xff, 0, 0, 0xff}, curve.X1, curve.Y1, curve.X2, curve.Y2, curve.X3, curve.Y3, curve.X4, curve.Y4)
|
||||||
|
raster.PolylineBresenham(img, image.Black, p.points...)
|
||||||
|
//drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve.X1, curve.Y1, curve.X2, curve.Y2, curve.X3, curve.Y3, curve.X4, curve.Y4)
|
||||||
|
drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...)
|
||||||
|
savepng(fmt.Sprintf("_testAdaptiveRec%d.png", i), img)
|
||||||
|
log.Printf("Num of points: %d\n", len(p.points))
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCubicCurveAdaptive(t *testing.T) {
|
||||||
|
for i, curve := range testsFloat64 {
|
||||||
|
var p Path
|
||||||
|
curve.AdaptiveSegment(&p, 1, 0, 0)
|
||||||
|
img := image.NewNRGBA(300, 300)
|
||||||
|
raster.PolylineBresenham(img, image.NRGBAColor{0xff, 0, 0, 0xff}, curve.X1, curve.Y1, curve.X2, curve.Y2, curve.X3, curve.Y3, curve.X4, curve.Y4)
|
||||||
|
raster.PolylineBresenham(img, image.Black, p.points...)
|
||||||
|
//drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve.X1, curve.Y1, curve.X2, curve.Y2, curve.X3, curve.Y3, curve.X4, curve.Y4)
|
||||||
|
drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...)
|
||||||
|
savepng(fmt.Sprintf("_testAdaptive%d.png", i), img)
|
||||||
|
log.Printf("Num of points: %d\n", len(p.points))
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCubicCurveParabolic(t *testing.T) {
|
||||||
|
for i, curve := range testsFloat64 {
|
||||||
|
var p Path
|
||||||
|
curve.ParabolicSegment(&p, flattening_threshold)
|
||||||
|
img := image.NewNRGBA(300, 300)
|
||||||
|
raster.PolylineBresenham(img, image.NRGBAColor{0xff, 0, 0, 0xff}, curve.X1, curve.Y1, curve.X2, curve.Y2, curve.X3, curve.Y3, curve.X4, curve.Y4)
|
||||||
|
raster.PolylineBresenham(img, image.Black, p.points...)
|
||||||
|
//drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, curve.X1, curve.Y1, curve.X2, curve.Y2, curve.X3, curve.Y3, curve.X4, curve.Y4)
|
||||||
|
drawPoints(img, image.NRGBAColor{0, 0, 0, 0xff}, p.points...)
|
||||||
|
savepng(fmt.Sprintf("_testParabolic%d.png", i), img)
|
||||||
|
log.Printf("Num of points: %d\n", len(p.points))
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkCubicCurveRec(b *testing.B) {
|
func BenchmarkCubicCurveRec(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
for _, curve := range testsFloat64 {
|
for _, curve := range testsFloat64 {
|
||||||
d := curve.EstimateDistance()
|
p := Path{make([]float64, 0, 32)}
|
||||||
numSegments := int(d * 0.25)
|
curve.SegmentRec(&p, flattening_threshold)
|
||||||
s := make([]float64, 0, numSegments)
|
|
||||||
curve.SegmentRec(s)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -125,10 +181,35 @@ func BenchmarkCubicCurveRec(b *testing.B) {
|
||||||
func BenchmarkCubicCurve(b *testing.B) {
|
func BenchmarkCubicCurve(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
for _, curve := range testsFloat64 {
|
for _, curve := range testsFloat64 {
|
||||||
d := curve.EstimateDistance()
|
p := Path{make([]float64, 0, 32)}
|
||||||
numSegments := int(d * 0.25)
|
curve.Segment(&p, flattening_threshold)
|
||||||
s := make([]float64, 0, numSegments)
|
}
|
||||||
curve.Segment(s)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCubicCurveAdaptiveRec(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
for _, curve := range testsFloat64 {
|
||||||
|
p := Path{make([]float64, 0, 32)}
|
||||||
|
curve.AdaptiveSegmentRec(&p, 1, 0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCubicCurveAdaptive(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
for _, curve := range testsFloat64 {
|
||||||
|
p := Path{make([]float64, 0, 32)}
|
||||||
|
curve.AdaptiveSegment(&p, 1, 0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkCubicCurveParabolic(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
for _, curve := range testsFloat64 {
|
||||||
|
p := Path{make([]float64, 0, 32)}
|
||||||
|
curve.ParabolicSegment(&p, flattening_threshold)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue