update to use curveMonotoneX alg for smothing curves
This commit is contained in:
@@ -170,26 +170,66 @@ func linePathD(pts [][2]float64) string {
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// smoothPathD: Catmull-Rom → cubic bezier through every point.
|
||||
func monoSign(x float64) float64 {
|
||||
if x < 0 {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
// monoTangent is the interior tangent for monotone-cubic interpolation (d3's
|
||||
// curveMonotoneX): the lesser of the two neighbouring secant slopes, and zero at a local
|
||||
// extremum. Capping the tangent is what stops a segment bulging past its endpoints, so an
|
||||
// area fill can't dip below a value-0 point into negative space the way a Catmull-Rom
|
||||
// overshoot does.
|
||||
func monoTangent(x0, y0, x1, y1, x2, y2 float64) float64 {
|
||||
h0, h1 := x1-x0, x2-x1
|
||||
var s0, s1 float64
|
||||
if h0 != 0 {
|
||||
s0 = (y1 - y0) / h0
|
||||
}
|
||||
if h1 != 0 {
|
||||
s1 = (y2 - y1) / h1
|
||||
}
|
||||
p := (s0*h1 + s1*h0) / (h0 + h1)
|
||||
m := (monoSign(s0) + monoSign(s1)) * math.Min(math.Min(math.Abs(s0), math.Abs(s1)), 0.5*math.Abs(p))
|
||||
if math.IsNaN(m) || math.IsInf(m, 0) {
|
||||
return 0
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// monoEndTangent is the endpoint tangent (d3 slope2): a parabola-end estimate constrained
|
||||
// by the adjacent interior tangent t, so the boundary segments don't overshoot either.
|
||||
func monoEndTangent(x0, y0, x1, y1, t float64) float64 {
|
||||
h := x1 - x0
|
||||
if h == 0 {
|
||||
return t
|
||||
}
|
||||
return (3*(y1-y0)/h - t) / 2
|
||||
}
|
||||
|
||||
// smoothPathD: monotone cubic through every point, emitted as cubic beziers. A monotone
|
||||
// interpolant never overshoots its data, so the smoothed curve stays within the value
|
||||
// range of each pair of adjacent points.
|
||||
func smoothPathD(pts [][2]float64) string {
|
||||
if len(pts) < 3 {
|
||||
n := len(pts)
|
||||
if n < 3 {
|
||||
return linePathD(pts)
|
||||
}
|
||||
m := make([]float64, n)
|
||||
for i := 1; i < n-1; i++ {
|
||||
m[i] = monoTangent(pts[i-1][0], pts[i-1][1], pts[i][0], pts[i][1], pts[i+1][0], pts[i+1][1])
|
||||
}
|
||||
m[0] = monoEndTangent(pts[0][0], pts[0][1], pts[1][0], pts[1][1], m[1])
|
||||
m[n-1] = monoEndTangent(pts[n-2][0], pts[n-2][1], pts[n-1][0], pts[n-1][1], m[n-2])
|
||||
var b strings.Builder
|
||||
b.WriteString("M" + num(pts[0][0]) + "," + num(pts[0][1]))
|
||||
for i := 0; i < len(pts)-1; i++ {
|
||||
p0 := pts[i]
|
||||
if i > 0 {
|
||||
p0 = pts[i-1]
|
||||
}
|
||||
p1, p2 := pts[i], pts[i+1]
|
||||
p3 := p2
|
||||
if i+2 < len(pts) {
|
||||
p3 = pts[i+2]
|
||||
}
|
||||
c1x, c1y := p1[0]+(p2[0]-p0[0])/6, p1[1]+(p2[1]-p0[1])/6
|
||||
c2x, c2y := p2[0]-(p3[0]-p1[0])/6, p2[1]-(p3[1]-p1[1])/6
|
||||
b.WriteString(" C" + num(c1x) + "," + num(c1y) + " " + num(c2x) + "," + num(c2y) + " " + num(p2[0]) + "," + num(p2[1]))
|
||||
for i := 0; i < n-1; i++ {
|
||||
dx := (pts[i+1][0] - pts[i][0]) / 3
|
||||
c1x, c1y := pts[i][0]+dx, pts[i][1]+dx*m[i]
|
||||
c2x, c2y := pts[i+1][0]-dx, pts[i+1][1]-dx*m[i+1]
|
||||
b.WriteString(" C" + num(c1x) + "," + num(c1y) + " " + num(c2x) + "," + num(c2y) + " " + num(pts[i+1][0]) + "," + num(pts[i+1][1]))
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user