WIP: draw dag

This commit is contained in:
Josh Deprez 2021-09-20 10:13:43 +10:00
parent aafa6204ed
commit 169b7f343c
2 changed files with 222 additions and 125 deletions

222
engine/drawdag.go Normal file
View file

@ -0,0 +1,222 @@
package engine
import (
"image"
"drjosh.dev/gurgle/geom"
"github.com/hajimehoshi/ebiten/v2"
)
// drawDAG combines a DAG with a spatial index used when adding new vertices
// in order to reduce the number of tests between components.
type drawDAG struct {
*dag
chunks map[image.Point]drawerSet
chunksRev map[DrawBoxer]image.Rectangle
chunkSize int
parent func(x interface{}) interface{}
proj geom.Projector
}
func newDrawDAG(game *Game, chunkSize int, π geom.Projector) *drawDAG {
return &drawDAG{
dag: newDAG(),
chunks: make(map[image.Point]drawerSet), // chunk coord -> drawers with bounding rects intersecting chunk
chunksRev: make(map[DrawBoxer]image.Rectangle), // comopnent -> rectangle of chunk coords
chunkSize: chunkSize,
parent: game.Parent,
proj: π,
}
}
func (d *drawDAG) Draw(screen *ebiten.Image) {
// Hiding a parent component should hide the child objects, and the
// transform applied to a child should be the cumulative transform of all
// parents as well.
// cache memoises the results for each component.
type state struct {
hidden bool
opts ebiten.DrawImageOptions
}
cache := make(map[interface{}]state)
// Draw everything in g.drawList, where not hidden (itself or any parent)
d.dag.topIterate(func(x Drawer) {
// Is d hidden itself?
if h, ok := x.(Hider); ok && h.Hidden() {
cache[x] = state{hidden: true}
return // skip drawing
}
// Walk up g.par to find the nearest state in accum.
var st state
stack := []interface{}{x}
for p := d.parent(x); p != nil; p = d.parent(p) {
if s, found := cache[p]; found {
st = s
break
}
stack = append(stack, p)
}
// Unwind the stack, accumulating state along the way.
for len(stack) > 0 {
l1 := len(stack) - 1
p := stack[l1]
stack = stack[:l1]
if h, ok := p.(Hider); ok {
st.hidden = st.hidden || h.Hidden()
}
if st.hidden {
cache[p] = state{hidden: true}
continue
}
// p is not hidden, so compute its cumulative opts.
if tf, ok := p.(Transformer); ok {
st.opts = concatOpts(tf.Transform(), st.opts)
}
cache[p] = st
}
// Skip drawing if hidden.
if st.hidden {
return
}
x.Draw(screen, &st.opts)
})
}
type DrawBoxer interface {
Drawer
BoundingBoxer
}
// add adds a Drawer and any needed edges to the DAG and chunk map.
func (d *drawDAG) add(x DrawBoxer) {
πsign := d.proj.Sign()
br := x.BoundingBox().BoundingRect(d.proj)
// Update the reverse chunk map
revr := image.Rectangle{
Min: br.Min.Div(d.chunkSize),
Max: br.Max.Sub(image.Pt(1, 1)).Div(d.chunkSize),
}
d.chunksRev[x] = revr
// Find possible edges between x and items in the overlapping cells.
// First, a set of all the items in those cells.
cand := make(drawerSet)
for j := revr.Min.Y; j <= revr.Max.Y; j++ {
for i := revr.Min.X; i <= revr.Max.X; i++ {
cell := d.chunks[image.Pt(i, j)]
// Merge cell contents into cand
for c := range cell {
cand[c] = struct{}{}
}
// Add x to cell
cell[x] = struct{}{}
}
}
// Add edges between x and elements of cand
for c := range cand {
y := c.(DrawBoxer)
// Bounding rectangle test
if ybr := y.BoundingBox().BoundingRect(d.proj); !br.Overlaps(ybr) {
continue
}
switch {
case edge(y, x, πsign):
d.dag.addEdge(y, x)
case edge(x, y, πsign):
d.dag.addEdge(x, y)
}
}
}
// remove removes a Drawer and all associated edges and metadata.
func (d *drawDAG) remove(x DrawBoxer) {
// Remove from chunk map
revr := d.chunksRev[x]
for j := revr.Min.Y; j <= revr.Max.Y; j++ {
for i := revr.Min.X; i <= revr.Max.X; i++ {
delete(d.chunks[image.Pt(i, j)], x)
}
}
// Remove from reverse chunk map
delete(d.chunksRev, x)
// Remove from DAG
d.dag.removeVertex(x)
}
type drawerSet map[Drawer]struct{}
type dag struct {
in, out map[Drawer]drawerSet
}
func newDAG() *dag {
return &dag{
in: make(map[Drawer]drawerSet),
out: make(map[Drawer]drawerSet),
}
}
// addEdge adds the edge u-v in O(1).
func (d *dag) addEdge(u, v Drawer) {
if d.in[v] == nil {
d.in[v] = make(drawerSet)
}
if d.out[u] == nil {
d.out[u] = make(drawerSet)
}
d.in[v][u] = struct{}{}
d.out[u][v] = struct{}{}
}
// removeEdge removes the edge u-v in O(1).
func (d *dag) removeEdge(u, v Drawer) {
delete(d.in[v], u)
delete(d.out[u], v)
}
// removeVertex removes all in and out edges associated with v in O(degree(v)).
func (d *dag) removeVertex(v Drawer) {
for u := range d.in[v] {
// u-v is no longer an edge
delete(d.out[u], v)
}
for w := range d.out[v] {
// v-w is no longer an edge
delete(d.in[w], v)
}
delete(d.in, v)
delete(d.out, v)
}
// topIterate visits each vertex in topological order, in time O(|V| + |E|) and
// O(|V|) temporary memory.
func (d *dag) topIterate(visit func(Drawer)) {
// Count indegrees - indegree(v) = len(d.in[v]) for each v.
// If indegree(v) = 0, enqueue. Total: O(|V|).
queue := make([]Drawer, 0, len(d.in))
indegree := make(map[Drawer]int)
for u, e := range d.in {
if len(e) == 0 {
queue = append(queue, u)
} else {
indegree[u] = len(e)
}
}
// Visit every vertex (O(|V|)) and decrement indegrees for every out edge
// of each vertex visited (O(|E|)). Total: O(|V|+|E|).
for len(queue) > 0 {
u := queue[0]
visit(u)
queue = queue[1:]
// Decrement indegree for all out edges, and enqueue target if its
// indegree is now 0.
for v := range d.out[u] {
indegree[v]--
if indegree[v] == 0 {
queue = append(queue, v)
}
}
}
}

View file

@ -150,128 +150,3 @@ func (d *drawList) topsort(π geom.Projector) {
// Job done!
d.list = list
}
type drawDAG struct {
*dag
chunks map[image.Point]set
chunksRev map[Drawer]image.Rectangle
chunkSize int
proj geom.Projector
}
func newDrawDAG(chunkSize int) *drawDAG {
return &drawDAG{
dag: newDAG(),
chunks: make(map[image.Point]set), // chunk coord -> drawers with bounding rects intersecting chunk
chunksRev: make(map[Drawer]image.Rectangle), // drawer -> rectangle of chunk coords
chunkSize: chunkSize,
}
}
// add adds a Drawer and any needed edges to the DAG and chunk map.
func (d *drawDAG) add(x Drawer) {
bb := x.(BoundingBoxer)
br := bb.BoundingBox().BoundingRect(d.proj)
min := br.Min.Div(d.chunkSize)
max := br.Max.Sub(image.Pt(1, 1)).Div(d.chunkSize)
cand := make(set)
for j := min.Y; j <= max.Y; j++ {
for i := min.X; i <= max.X; i++ {
cell := d.chunks[image.Pt(i, j)]
// Merge cell into cand
for c := range cell {
cand[c] = struct{}{}
}
// Add x to cell
cell[x] = struct{}{}
}
}
// Add edges between x and elements of cand
for c := range cand {
y := c.(Drawer)
switch {
case edge(y, x, d.proj.Sign()):
d.dag.addEdge(y, x)
case edge(x, y, d.proj.Sign()):
d.dag.addEdge(x, y)
}
}
}
type set map[interface{}]struct{}
type dag struct {
in, out map[interface{}]set
}
func newDAG() *dag {
return &dag{
in: make(map[interface{}]set),
out: make(map[interface{}]set),
}
}
// addEdge adds the edge u-v in O(1).
func (d *dag) addEdge(u, v interface{}) {
if d.in[v] == nil {
d.in[v] = make(set)
}
if d.out[u] == nil {
d.out[u] = make(set)
}
d.in[v][u] = struct{}{}
d.out[u][v] = struct{}{}
}
// removeEdge removes the edge u-v in O(1).
func (d *dag) removeEdge(u, v interface{}) {
delete(d.in[v], u)
delete(d.out[u], v)
}
// removeVertex removes all in and out edges associated with v in O(degree(v)).
func (d *dag) removeVertex(v interface{}) {
for u := range d.in[v] {
// u-v is no longer an edge
delete(d.out[u], v)
}
for w := range d.out[v] {
// v-w is no longer an edge
delete(d.in[w], v)
}
delete(d.in, v)
delete(d.out, v)
}
// topIterate visits each vertex in topological order, in time O(|V| + |E|) and
// O(|V|) temporary memory.
func (d *dag) topIterate(visit func(interface{})) {
// Count indegrees - indegree(v) = len(d.in[v]) for each v.
// If indegree(v) = 0, enqueue. Total: O(|V|).
queue := make([]interface{}, 0, len(d.in))
indegree := make(map[interface{}]int)
for u, e := range d.in {
if len(e) == 0 {
queue = append(queue, u)
} else {
indegree[u] = len(e)
}
}
// Visit every vertex (O(|V|)) and decrement indegrees for every out edge
// of each vertex visited (O(|E|)). Total: O(|V|+|E|).
for len(queue) > 0 {
u := queue[0]
visit(u)
queue = queue[1:]
// Decrement indegree for all out edges, and enqueue target if its
// indegree is now 0.
for v := range d.out[u] {
indegree[v]--
if indegree[v] == 0 {
queue = append(queue, v)
}
}
}
}