Skip to content

Commit

Permalink
Merge pull request #28 from nulab/dev-11/spline-routing
Browse files Browse the repository at this point in the history
Dev 11/spline routing
  • Loading branch information
vibridi authored Sep 27, 2024
2 parents 94547d7 + 5e6cd50 commit 057dbd9
Show file tree
Hide file tree
Showing 17 changed files with 289 additions and 144 deletions.
4 changes: 4 additions & 0 deletions autolayout_options_algs.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ const (
// Dense graphs look tidier, but it's harder to understand where edges start and finish.
// Suitable when there's few sets of edges with the same target node.
EdgeRoutingOrtho = phase5.Ortho

// EdgeRoutingSplines outputs edges as piece-wise cubic Bézier curves. Edges that don't encounter obstacles
// are drawn as straight lines.
EdgeRoutingSplines = phase5.Splines
)

func WithCycleBreaking(alg phase1.Alg) Option {
Expand Down
8 changes: 7 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
module github.com/nulab/autog

go 1.22
go 1.22.4

toolchain go1.22.7

require github.com/stretchr/testify v1.8.4

replace github.com/vibridi/graphify => ../../vibridi/graphify

require (
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/vibridi/graphify v0.0.0-20240926092405-5791dfe773cb // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
31 changes: 31 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,10 +1,41 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY=
github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk=
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw=
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/vibridi/graphify v0.0.0-20240926092405-5791dfe773cb h1:vJaelmTCdGCWuUUmERFznS6843ewGv4MGKv8nOETyt4=
github.com/vibridi/graphify v0.0.0-20240926092405-5791dfe773cb/go.mod h1:ClQsJC5L+MO0eABQoEJBx8EXSxi++0VrUuIYXcYeViY=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
2 changes: 1 addition & 1 deletion internal/geom/point.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type P struct {
X, Y float64
}

func (p P) String() string {
func (p P) SVG() string {
return fmt.Sprintf(`<circle r="4" cx="%.02f" cy="%.02f" fill="black"/>`, p.X, p.Y)
}

Expand Down
27 changes: 27 additions & 0 deletions internal/geom/rect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package geom

import "fmt"

// Rect represents a rectangle
type Rect struct {
TL P // top-left vertex of the rectangle
BR P // bottom-right vertex of the rectangle
}

func (r Rect) String() string {
return fmt.Sprintf("{geom.P{%.02f,%.02f},geom.P{%.02f,%.02f}}", r.TL.X, r.TL.Y, r.BR.X, r.BR.Y)
}

func (r Rect) SVG() string {
width := r.BR.X - r.TL.X
height := r.BR.Y - r.TL.Y
return fmt.Sprintf(`<rect class="rect" x="%f" y="%f" width="%f" height="%f" style="fill: lightgrey; stroke: black;" />`, r.TL.X, r.TL.Y, width, height)
}

func (r Rect) Width() float64 {
return r.BR.X - r.TL.X
}

func (r Rect) Height() float64 {
return r.BR.Y - r.TL.Y
}
18 changes: 18 additions & 0 deletions internal/geom/segment.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package geom

import "fmt"

type Segment struct {
A, B P
}

func (seg Segment) SVG() string {
return fmt.Sprintf(`<path d="M %.2f,%.2f %.2f,%.2f" stroke="blue" />`, seg.A.X, seg.A.Y, seg.B.X, seg.B.Y)
}

func (seg Segment) Other(v P) P {
if seg.A == v {
return seg.B
}
return seg.A
}
79 changes: 0 additions & 79 deletions internal/geom/shapes.go

This file was deleted.

1 change: 0 additions & 1 deletion internal/geom/shortest.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ func Shortest(p1, p2 P, rects []Rect) []P {
// find start and end triangles
var start, end Tri
for _, t := range ts {
// fmt.Println(t.String())
if t.Contains(p1) {
start = t
}
Expand Down
48 changes: 0 additions & 48 deletions internal/geom/shortest_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package geom

import (
"fmt"
"slices"
"strconv"
"strings"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -83,20 +80,6 @@ func TestShortest(t *testing.T) {
})
}

func TestShortestEdgeCases(t *testing.T) {
rects := []Rect{
{P{112, 90}, P{200, 140}},
{P{80, 140}, P{150, 300}},
{P{140, 300}, P{270, 380}},
}
start := P{190, 140 - 1}
end := P{200, 300 + 1}

path := Shortest(start, end, rects)

printall(rects, start, end, path)
}

func assertPath(t *testing.T, want, got []P) {
require.Equal(t, len(want), len(got))
for i := 0; i < len(got); i++ {
Expand All @@ -106,34 +89,3 @@ func assertPath(t *testing.T, want, got []P) {
assert.Equal(t, want[i], got[i])
}
}

func printpath(path []P) {
for i := 1; i < len(path); i++ {
u, v := path[i-1], path[i]
fmt.Printf(`<path d="M %.2f,%.2f %.2f,%.2f" stroke="black" stroke-width="3" />`+"\n", u.X, u.Y, v.X, v.Y)
}
}

func printall(rects []Rect, start, end P, path []P) {
p := MergeRects(rects)

s := polyline(p.Points, "red")
fmt.Println(s)

fmt.Println(start.String())
fmt.Println(end.String())
printpath(path)
}

func polyline(points []P, color string) string {
b := strings.Builder{}
b.WriteString(`<polyline points="`)
for _, p := range points {
b.WriteString(strconv.FormatFloat(p.X, 'f', 2, 64))
b.WriteRune(',')
b.WriteString(strconv.FormatFloat(p.Y, 'f', 2, 64))
b.WriteRune(' ')
}
b.WriteString(`" fill="none" stroke="` + color + `" />`)
return b.String()
}
13 changes: 9 additions & 4 deletions internal/geom/spline_ctrlp.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ func (bz ctrlp) String() string {
bz.p0.X, bz.p0.Y, bz.p1.X, bz.p1.Y, bz.p2.X, bz.p2.Y, bz.p3.X, bz.p3.Y)
}

func (bz ctrlp) Float64Slice() [][2]float64 {
return [][2]float64{
{bz.p0.X, bz.p0.Y},
{bz.p1.X, bz.p1.Y},
{bz.p2.X, bz.p2.Y},
{bz.p3.X, bz.p3.Y},
}
}

// Returns a new set of control points with:
// - P0 and P3 unchanged
// - P1 and P2 moved along their tangents by thirds of the slide factor
Expand Down Expand Up @@ -82,7 +91,3 @@ func (bz ctrlp) maxerr(path []P, t []float64) int {
}
return maxi
}

func (bz ctrlp) slice() []P {
return []P{bz.p0, bz.p1, bz.p2, bz.p3}
}
52 changes: 52 additions & 0 deletions internal/geom/triangle.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package geom

import "fmt"

// Tri represents a triangle
type Tri struct {
ID int
A, B, C P
}

func (t Tri) SVG() string {
return fmt.Sprintf(`<path d="M %.2f,%.2f %.2f,%.2f %.2f,%.2f Z" stroke="blue" fill="none"/>`, t.A.X, t.A.Y, t.B.X, t.B.Y, t.C.X, t.C.Y)
}

func (t Tri) Barycenter() P {
bx := (t.A.X + t.B.X + t.C.X) / 3
by := (t.A.Y + t.B.Y + t.C.Y) / 3
return P{bx, by}
}

func (t Tri) Contains(p P) bool {
s := 0
e := []P{t.A, t.B, t.C}
for i := range 3 {
or := orientation(e[i%3], e[(i+1)%3], p)
if or == cln {
q, r := e[i%3], e[(i+1)%3]
return p.X >= min(q.X, r.X) && p.X <= max(q.X, r.X) &&
p.Y >= min(q.Y, r.Y) && p.Y <= max(q.Y, r.Y)
}

if or != cw {
s++
}
}
return s == 3 || s == 0
}

func (t Tri) OrderedSide(i int) Segment {
e := []P{t.A, t.B, t.C}
a, b := e[i%3], e[(i+1)%3]
if a.X < b.X {
return Segment{a, b}
}
if b.X < a.X {
return Segment{b, a}
}
if a.Y < b.Y {
return Segment{a, b}
}
return Segment{b, a}
}
25 changes: 25 additions & 0 deletions internal/geom/triangle_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package geom

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestTri(t *testing.T) {
t.Run("contains", func(t *testing.T) {
cases := []struct {
t Tri
p P
}{
{Tri{0, P{0, 0}, P{20, 0}, P{20, 50}}, P{10, 5}},
{Tri{1, P{19.2, 44.7}, P{142.6, 16.5}, P{228, 212}}, P{28.6, 46.6}},
{Tri{2, P{20, 20}, P{200, 20}, P{300, 40}}, P{197.7, 27}},
{Tri{3, P{50, 10}, P{80, 10}, P{100, 50}}, P{65, 10}}, // collinear parallel x axis
{Tri{4, P{255, 210}, P{0, 60}, P{255, 60}}, P{185.615, 169.185}}, // collinear neg slope
}
for _, c := range cases {
assert.Truef(t, c.t.Contains(c.p), "triangle %d does not contain point", c.t.ID)
}
})
}
Loading

0 comments on commit 057dbd9

Please sign in to comment.