Skip to content

Commit

Permalink
[p5-splines] Always draw splines with a slight curve except for verti…
Browse files Browse the repository at this point in the history
…cal segments
  • Loading branch information
vibridi committed Oct 2, 2024
1 parent 05fbbdc commit f8592f9
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 3 deletions.
8 changes: 8 additions & 0 deletions internal/geom/point.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,11 @@ func norm(p P) P {
}
return p
}

func rotate(p P, theta float64) P {
cs, sn := math.Cos(theta), math.Sin(theta)
return P{
X: p.X*cs - p.Y*sn,
Y: p.X*sn + p.Y*cs, // invert sin e cos to rotate in the SVG plane
}
}
43 changes: 43 additions & 0 deletions internal/geom/spline_make.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package geom

import "math"

// MakeSpline computes control points of a cubic bezier from a to b so that it gently curves in the
// direction of the slope.
func MakeSpline(a, b P) ctrlp {
// slide factor
const k = 0.2
// if the points are vertically aligned, let the cubic degenerate into a straight segment
if a.X == b.X {
return ctrlp{a, a, b, b}
}
// compute vector
v := P{b.X - a.X, b.Y - a.Y}

// rotation angle
// rotate clockwise when the x of the vector is positive, counterclockwise when is negative
// x is inverted to account for rotation in the SVG plane
theta1 := sign(-v.X) * math.Pi * (1.0 / 10.0)
theta2 := sign(-v.X) * math.Pi * (9.0 / 10.0)

// rotate the vector
r := rotate(v, theta1)
q := rotate(v, theta2)
// slide the control points along the rotated vector
p1 := P{a.X + k*r.X, a.Y + k*r.Y}
p2 := P{b.X + k*q.X, b.Y + k*q.Y}

return ctrlp{
p0: a,
p1: p1,
p2: p2,
p3: b,
}
}

func sign(x float64) float64 {
if x < 0 {
return -1
}
return 1
}
14 changes: 11 additions & 3 deletions internal/phase5/splines.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ import (

func execSplines(g *graph.DGraph, routes []routableEdge) {
for _, e := range routes {
if e.From.ID == "a" && e.To.ID == "m" {
imonitor.Log("spline", e)
}
imonitor.Log("spline", e)

rects := buildRects(g, e)

Expand All @@ -30,6 +28,16 @@ func execSplines(g *graph.DGraph, routes []routableEdge) {
}

path := geom.Shortest(start, end, rects)
// remember the order of the elements in the path slice is from end to start

// with only two points the path is a straight line
// in this case we can skip spline fitting because it would result in a straight segment
// instead apply a heuristic to slide the control points just a little
// to draw the edge as a gentle curve
if len(path) == 2 {
e.Points = geom.MakeSpline(start, end).Float64Slice()
continue
}

poly := geom.MergeRects(rects)
ctrls := geom.FitSpline(path, geom.P{}, geom.P{}, poly.Sides())
Expand Down

0 comments on commit f8592f9

Please sign in to comment.