gim: make incoming edges separate along the drawn rectangle edge, instead of all overlapping onto the same point

This commit is contained in:
Brian Picciano 2018-03-03 17:32:40 +00:00
parent ed8fa31104
commit 905b182467
5 changed files with 85 additions and 40 deletions

View File

@ -437,6 +437,9 @@ func Equal(g1, g2 *Graph) bool {
return true
}
// TODO Walk, but by edge
// TODO Walk, but without end. AKA FSM
// Walk will traverse the Graph, calling the callback on every Vertex in the
// Graph once. If startWith is non-nil then that Vertex will be the first one
// passed to the callback and used as the starting point of the traversal. If

View File

@ -11,11 +11,15 @@ type Rect struct {
Size XY
}
// Edge returns the coordinate of the edge indicated by the given direction (Up,
// Down, Left, or Right). The coordinate will be for the axis applicable to the
// direction, so for Left/Right it will be the x coordinate and for Up/Down the
// y.
func (r Rect) Edge(dir XY) int {
// Edge describes a straight edge starting at its first XY and ending at its
// second
type Edge [2]XY
// EdgeCoord returns the coordinate of the edge indicated by the given direction
// (Up, Down, Left, or Right). The coordinate will be for the axis applicable to
// the direction, so for Left/Right it will be the x coordinate and for Up/Down
// the y.
func (r Rect) EdgeCoord(dir XY) int {
switch dir {
case Up:
return r.TopLeft[1]
@ -49,25 +53,37 @@ func (r Rect) Corner(xDir, yDir XY) XY {
}
}
// EdgeMidpoint returns the point which is the midpoint of the edge dientified by the
// direction (Up/Down/Left/Right)
func (r Rect) EdgeMidpoint(dir XY, rounder Rounder) XY {
var a, b XY
// Edge returns an Edge instance for the edge of the Rect indicated by the given
// direction (Up, Down, Left, or Right). secDir indicates the direction the
// returned Edge should be pointing (i.e. the order of its XY's) and must be
// perpendicular to dir
func (r Rect) Edge(dir, secDir XY) Edge {
var e Edge
switch dir {
case Up:
a, b = r.Corner(Left, Up), r.Corner(Right, Up)
e[0], e[1] = r.Corner(Left, Up), r.Corner(Right, Up)
case Down:
a, b = r.Corner(Left, Down), r.Corner(Right, Down)
e[0], e[1] = r.Corner(Left, Down), r.Corner(Right, Down)
case Left:
a, b = r.Corner(Left, Up), r.Corner(Left, Down)
e[0], e[1] = r.Corner(Left, Up), r.Corner(Left, Down)
case Right:
a, b = r.Corner(Right, Up), r.Corner(Right, Down)
e[0], e[1] = r.Corner(Right, Up), r.Corner(Right, Down)
default:
panic(fmt.Sprintf("unsupported direction: %#v", dir))
}
mid := a.Midpoint(b, rounder)
return mid
switch secDir {
case Left, Up:
e[0], e[1] = e[1], e[0]
default:
// do nothing
}
return e
}
// Midpoint returns the point which is the midpoint of the Edge
func (e Edge) Midpoint(rounder Rounder) XY {
return e[0].Midpoint(e[1], rounder)
}
func (r Rect) halfSize(rounder Rounder) XY {

View File

@ -12,15 +12,29 @@ func TestRect(t *T) {
Size: XY{2, 2},
}
assert.Equal(t, 2, r.Edge(Up))
assert.Equal(t, 3, r.Edge(Down))
assert.Equal(t, 1, r.Edge(Left))
assert.Equal(t, 2, r.Edge(Right))
assert.Equal(t, 2, r.EdgeCoord(Up))
assert.Equal(t, 3, r.EdgeCoord(Down))
assert.Equal(t, 1, r.EdgeCoord(Left))
assert.Equal(t, 2, r.EdgeCoord(Right))
assert.Equal(t, XY{1, 2}, r.Corner(Left, Up))
assert.Equal(t, XY{1, 3}, r.Corner(Left, Down))
assert.Equal(t, XY{2, 2}, r.Corner(Right, Up))
assert.Equal(t, XY{2, 3}, r.Corner(Right, Down))
lu := XY{1, 2}
ld := XY{1, 3}
ru := XY{2, 2}
rd := XY{2, 3}
assert.Equal(t, lu, r.Corner(Left, Up))
assert.Equal(t, ld, r.Corner(Left, Down))
assert.Equal(t, ru, r.Corner(Right, Up))
assert.Equal(t, rd, r.Corner(Right, Down))
assert.Equal(t, Edge{lu, ld}, r.Edge(Left, Down))
assert.Equal(t, Edge{ru, rd}, r.Edge(Right, Down))
assert.Equal(t, Edge{lu, ru}, r.Edge(Up, Right))
assert.Equal(t, Edge{ld, rd}, r.Edge(Down, Right))
assert.Equal(t, Edge{ld, lu}, r.Edge(Left, Up))
assert.Equal(t, Edge{rd, ru}, r.Edge(Right, Up))
assert.Equal(t, Edge{ru, lu}, r.Edge(Up, Left))
assert.Equal(t, Edge{rd, ld}, r.Edge(Down, Left))
}
func TestRectCenter(t *T) {

View File

@ -39,16 +39,19 @@ var arrows = map[geo.XY]string{
geo.Right: ">",
}
type line [2]*box
type line struct {
from, to *box
toI int
}
// given the "primary" direction the line should be headed, picks a possible
// secondary one which may be used to detour along the path in order to reach
// the destination (in the case that the two boxes are diagonal from each other)
func (l line) secondaryDir(primary geo.XY) geo.XY {
fromRect, toRect := l[0].rect(), l[1].rect()
fromRect, toRect := l.from.rect(), l.to.rect()
rels := make([]int, len(geo.Units))
for i, dir := range geo.Units {
rels[i] = toRect.Edge(dir.Inv()) - fromRect.Edge(dir)
rels[i] = toRect.EdgeCoord(dir.Inv()) - fromRect.EdgeCoord(dir)
if dir == geo.Up || dir == geo.Left {
rels[i] *= -1
}
@ -74,13 +77,22 @@ func (l line) secondaryDir(primary geo.XY) geo.XY {
return secondary
}
func (l line) draw(term *terminal.Terminal, dir geo.XY) {
from, to := *l[0], *l[1]
dirSec := l.secondaryDir(dir)
//func (l line) startEnd(flowDir, secFlowDir geo.XY) (geo.XY, geo.XY) {
// from, to := *(l.from), *(l.to)
// start := from.rect().EdgeMidpoint(flowDir, rounder) // ezpz
//}
func (l line) draw(term *terminal.Terminal, flowDir, secFlowDir geo.XY) {
from, to := *(l.from), *(l.to)
dirSec := l.secondaryDir(flowDir)
flowDirInv := flowDir.Inv()
start := from.rect().Edge(flowDir, secFlowDir).Midpoint(rounder)
endSlot := l.toI*2 + 1
endSlotXY := geo.XY{endSlot, endSlot}
end := to.rect().Edge(flowDirInv, secFlowDir)[0].Add(secFlowDir.Mul(endSlotXY))
dirInv := dir.Inv()
start := from.rect().EdgeMidpoint(dir, rounder)
end := to.rect().EdgeMidpoint(dirInv, rounder)
mid := start.Midpoint(end, rounder)
along := func(xy, dir geo.XY) int {
@ -91,26 +103,26 @@ func (l line) draw(term *terminal.Terminal, dir geo.XY) {
}
var pts []geo.XY
midPrim := along(mid, dir)
midPrim := along(mid, flowDir)
endSec := along(end, dirSec)
for curr := start; curr != end; {
pts = append(pts, curr)
if prim := along(curr, dir); prim == midPrim {
if prim := along(curr, flowDir); prim == midPrim {
if sec := along(curr, dirSec); sec != endSec {
curr = curr.Add(dirSec)
continue
}
}
curr = curr.Add(dir)
curr = curr.Add(flowDir)
}
for i, pt := range pts {
var str string
switch {
case i == 0:
str = edgeSegments[dir]
str = edgeSegments[flowDir]
case i == len(pts)-1:
str = arrows[dir]
str = arrows[flowDir]
default:
prev, next := pts[i-1], pts[i+1]
seg := [2]geo.XY{

View File

@ -150,9 +150,9 @@ func (view *view) draw(term *terminal.Terminal) {
var lines []line
for _, b := range boxes {
v := boxesM[b]
for _, e := range v.In {
for i, e := range v.In {
bFrom := boxesMr[e.From]
lines = append(lines, line{bFrom, b})
lines = append(lines, line{from: bFrom, to: b, toI: i})
}
}
@ -164,6 +164,6 @@ func (view *view) draw(term *terminal.Terminal) {
b.draw(term)
}
for _, line := range lines {
line.draw(term, view.primFlowDir)
line.draw(term, view.primFlowDir, view.secFlowDir)
}
}