ichigo/engine/drawdag.go

431 lines
11 KiB
Go
Raw Normal View History

2021-09-20 10:13:43 +10:00
package engine
import (
2021-09-23 16:47:43 +10:00
"encoding/gob"
2021-09-20 14:24:32 +10:00
"fmt"
2021-09-20 10:13:43 +10:00
"image"
2021-09-23 12:45:05 +10:00
"log"
"math"
2021-09-20 14:24:32 +10:00
"strings"
2021-09-20 10:13:43 +10:00
"drjosh.dev/gurgle/geom"
"github.com/hajimehoshi/ebiten/v2"
)
var _ interface {
Drawer
DrawManager
Hider
Prepper
2021-09-21 14:42:18 +10:00
Registrar
Scanner
Updater
} = &DrawDAG{}
2021-09-23 16:47:43 +10:00
func init() {
gob.Register(&DrawDAG{})
}
2021-09-24 12:29:55 +10:00
// DrawDAG is a DrawManager that organises DrawBoxer descendants in a directed
2021-09-20 12:18:03 +10:00
// acyclic graph (DAG), in order to draw them according to ordering constraints.
2021-09-20 15:03:33 +10:00
// It combines a DAG with a spatial index used when updating vertices to reduce
// the number of tests between components.
2021-09-20 12:18:03 +10:00
type DrawDAG struct {
2021-09-21 14:42:18 +10:00
ChunkSize int
2021-09-22 14:37:00 +10:00
Child interface{}
2021-09-28 10:51:04 +10:00
Disables
2021-09-20 12:18:03 +10:00
Hides
2021-09-24 10:32:31 +10:00
dag
2021-09-24 17:01:17 +10:00
boxCache map[DrawBoxer]geom.Box // used to find components that moved
chunks map[image.Point]drawerSet // chunk coord -> drawers with bounding rects intersecting chunk
game *Game
2021-09-20 10:13:43 +10:00
}
2021-09-20 12:18:03 +10:00
// Draw draws everything in the DAG in topological order.
func (d *DrawDAG) Draw(screen *ebiten.Image, opts *ebiten.DrawImageOptions) {
2021-09-20 12:18:03 +10:00
if d.Hidden() {
return
2021-09-20 10:13:43 +10:00
}
// 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
}
2021-09-20 12:18:03 +10:00
cache := map[interface{}]state{
d: {
hidden: false,
opts: *opts,
},
}
// Draw everything in d.dag, where not hidden (itself or any parent)
2021-09-21 15:17:59 +10:00
d.dag.topWalk(func(x Drawer) {
2021-09-20 10:13:43 +10:00
// Is d hidden itself?
if h, ok := x.(Hider); ok && h.Hidden() {
cache[x] = state{hidden: true}
return // skip drawing
}
2021-09-20 12:18:03 +10:00
// Walk up game tree to find the nearest state in cache.
2021-09-20 10:13:43 +10:00
var st state
stack := []interface{}{x}
2021-09-21 16:53:04 +10:00
for p := d.game.Parent(x); p != nil; p = d.game.Parent(p) {
2021-09-20 10:13:43 +10:00
if s, found := cache[p]; found {
st = s
break
}
stack = append(stack, p)
}
// Unwind the stack, accumulating state along the way.
2021-09-21 17:23:59 +10:00
for i := len(stack) - 1; i >= 0; i-- {
p := stack[i]
2021-09-20 10:13:43 +10:00
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)
})
}
2021-09-27 16:57:29 +10:00
// ManagesDrawingSubcomponents is present so DrawDAG is recognised as a
// DrawManager.
func (DrawDAG) ManagesDrawingSubcomponents() {}
2021-09-20 16:02:01 +10:00
// Prepare adds all subcomponents to the DAG.
2021-09-20 12:18:03 +10:00
func (d *DrawDAG) Prepare(game *Game) error {
2021-09-24 10:32:31 +10:00
d.dag = make(dag)
2021-09-20 14:24:32 +10:00
d.boxCache = make(map[DrawBoxer]geom.Box)
2021-09-20 12:18:03 +10:00
d.chunks = make(map[image.Point]drawerSet)
2021-09-20 15:03:33 +10:00
d.game = game
2021-09-20 12:18:03 +10:00
2021-09-20 15:52:44 +10:00
// Because Game.LoadAndPrepare calls Prepare in a post-order walk, all the
2021-09-21 14:42:18 +10:00
// descendants should be prepared, meaning BoundingBox (hence Register) is
// likely to be a safe call.
2021-09-24 15:38:16 +10:00
return d.Register(d.Child, nil)
2021-09-20 10:13:43 +10:00
}
2021-09-22 15:55:38 +10:00
// Scan visits d.Child.
2021-09-22 15:48:02 +10:00
func (d *DrawDAG) Scan(visit func(interface{}) error) error {
return visit(d.Child)
}
2021-09-22 14:37:00 +10:00
2021-09-23 14:17:18 +10:00
func (d *DrawDAG) String() string { return "DrawDAG" }
2021-09-21 13:00:05 +10:00
// Update checks for any changes to descendants, and updates its internal
// data structures accordingly.
2021-09-20 14:24:32 +10:00
func (d *DrawDAG) Update() error {
// Re-evaluate bounding boxes for all descendants. If a box has changed,
// fix up the edges by removing and re-adding the vertex.
2021-09-24 17:01:17 +10:00
// Thanks once again to postorder traversal in Game.Update, this happens
// after all descendant updates.
// TODO: more flexible update ordering system...
2021-09-20 14:24:32 +10:00
var readd []DrawBoxer
for db, bb := range d.boxCache {
nbb := db.BoundingBox()
if bb != nbb {
2021-09-24 17:01:17 +10:00
d.unregisterOne(db)
2021-09-20 14:24:32 +10:00
readd = append(readd, db)
}
}
for _, db := range readd {
2021-09-24 17:01:17 +10:00
d.registerOne(db)
2021-09-20 14:24:32 +10:00
}
return nil
}
2021-09-21 14:42:18 +10:00
// Register recursively registers compponent and all descendants that are
// DrawBoxers into internal data structures (the DAG, etc) unless they are
// descendants of a different DrawManager.
func (d *DrawDAG) Register(component, _ interface{}) error {
2021-09-22 10:14:44 +10:00
// *Don't* register the component if it is inside a descendant DrawManager.
// These queries work because component should be registered in game before
// this call.
for dm := range d.game.Query(d, DrawManagerType) {
if dm == d {
continue
}
dbs := d.game.Query(dm, DrawBoxerType)
if _, found := dbs[component]; found {
return nil
}
}
2021-09-24 15:38:16 +10:00
// Register db, and then subcomponents recursively.
2021-09-21 14:42:18 +10:00
if db, ok := component.(DrawBoxer); ok {
d.registerOne(db)
}
2021-09-24 15:38:16 +10:00
if _, ok := component.(DrawManager); ok {
2021-09-21 14:42:18 +10:00
return nil
}
if sc, ok := component.(Scanner); ok {
2021-09-24 15:38:16 +10:00
return sc.Scan(func(x interface{}) error {
2021-09-22 15:48:02 +10:00
return d.Register(x, nil)
2021-09-24 15:38:16 +10:00
})
2021-09-21 14:42:18 +10:00
}
return nil
}
// registerOne adds component and any needed edges to the DAG and chunk map.
func (d *DrawDAG) registerOne(x DrawBoxer) {
2021-09-20 14:36:00 +10:00
// Ensure vertex is present
d.dag.addVertex(x)
2021-09-20 14:24:32 +10:00
// Update the box cache
2021-09-20 14:36:00 +10:00
xb := x.BoundingBox()
d.boxCache[x] = xb
2021-09-20 14:24:32 +10:00
2021-09-20 10:13:43 +10:00
// Update the reverse chunk map
2021-09-20 15:03:33 +10:00
xbr := xb.BoundingRect(d.game.Projection)
2021-09-24 17:01:17 +10:00
min := xbr.Min.Div(d.ChunkSize)
max := xbr.Max.Sub(image.Pt(1, 1)).Div(d.ChunkSize)
2021-09-20 14:24:32 +10:00
2021-09-24 17:01:17 +10:00
// Find possible edges between x and items in the overlapping chunks.
// First, a set of all the items in those chunks.
2021-09-20 10:13:43 +10:00
cand := make(drawerSet)
2021-09-20 12:18:03 +10:00
var p image.Point
2021-09-24 17:01:17 +10:00
for p.Y = min.Y; p.Y <= max.Y; p.Y++ {
for p.X = min.X; p.X <= max.X; p.X++ {
chunk := d.chunks[p]
if chunk == nil {
d.chunks[p] = drawerSet{x: {}}
continue
2021-09-20 12:18:03 +10:00
}
2021-09-24 17:01:17 +10:00
// Merge chunk contents into cand
for c := range chunk {
2021-09-20 10:13:43 +10:00
cand[c] = struct{}{}
}
2021-09-24 17:01:17 +10:00
// Add x to chunk
chunk[x] = struct{}{}
2021-09-20 10:13:43 +10:00
}
}
// Add edges between x and elements of cand
2021-09-20 15:03:33 +10:00
πsign := d.game.Projection.Sign()
2021-09-20 10:13:43 +10:00
for c := range cand {
2021-09-24 17:01:17 +10:00
if x == c {
continue
}
2021-09-20 10:13:43 +10:00
y := c.(DrawBoxer)
2021-09-20 12:18:03 +10:00
// Bounding rectangle overlap test
// No overlap, no edge.
2021-09-20 15:03:33 +10:00
if ybr := y.BoundingBox().BoundingRect(d.game.Projection); !xbr.Overlaps(ybr) {
2021-09-20 10:13:43 +10:00
continue
}
switch {
2021-09-20 12:18:03 +10:00
case drawOrderConstraint(x, y, πsign):
2021-09-20 10:13:43 +10:00
d.dag.addEdge(x, y)
2021-09-20 14:36:00 +10:00
case drawOrderConstraint(y, x, πsign):
d.dag.addEdge(y, x)
2021-09-20 10:13:43 +10:00
}
}
}
2021-09-21 14:42:18 +10:00
// Unregister unregisters the component and all subcomponents.
func (d *DrawDAG) Unregister(component interface{}) {
if db, ok := component.(DrawBoxer); ok {
d.unregisterOne(db)
2021-09-21 13:00:05 +10:00
}
2021-09-21 14:42:18 +10:00
if _, ok := component.(DrawManager); ok && component != d {
2021-09-21 13:00:05 +10:00
return
}
2021-09-21 14:42:18 +10:00
if sc, ok := component.(Scanner); ok {
2021-09-24 15:38:16 +10:00
sc.Scan(func(x interface{}) error {
2021-09-21 14:42:18 +10:00
d.Unregister(x)
2021-09-22 15:48:02 +10:00
return nil
2021-09-24 15:38:16 +10:00
})
2021-09-21 13:00:05 +10:00
}
}
2021-09-21 14:42:18 +10:00
func (d *DrawDAG) unregisterOne(x DrawBoxer) {
2021-09-20 10:13:43 +10:00
// Remove from chunk map
2021-09-24 17:01:17 +10:00
xbr := d.boxCache[x].BoundingRect(d.game.Projection)
min := xbr.Min.Div(d.ChunkSize)
max := xbr.Max.Sub(image.Pt(1, 1)).Div(d.ChunkSize)
for j := min.Y; j <= max.Y; j++ {
for i := min.X; i <= max.X; i++ {
2021-09-20 10:13:43 +10:00
delete(d.chunks[image.Pt(i, j)], x)
}
}
2021-09-21 16:53:04 +10:00
// Remove from box cache
delete(d.boxCache, x)
2021-09-20 10:13:43 +10:00
// Remove from DAG
d.dag.removeVertex(x)
}
2021-09-20 12:18:03 +10:00
// drawOrderConstraint reports if there is a draw ordering constraint between u
// and v (where u must draw before v).
func drawOrderConstraint(u, v DrawBoxer, πsign image.Point) bool {
// Common logic for known interfaces (BoundingBoxer, ZPositioner), to
// simplify DrawOrderer implementations.
ub, vb := u.BoundingBox(), v.BoundingBox()
if ub.Min.Z >= vb.Max.Z { // u is in front of v
return false
}
if ub.Max.Z <= vb.Min.Z { // u is behind v
return true
}
if πsign.X != 0 {
if ub.Max.X*πsign.X <= vb.Min.X*πsign.X { // u is to the left of v
return false
}
if ub.Min.X*πsign.X >= vb.Max.X*πsign.X { // u is to the right of v
return true
}
}
if πsign.Y != 0 {
if ub.Max.Y*πsign.Y <= vb.Min.Y*πsign.Y { // u is above v
return false
}
if ub.Min.Y*πsign.Y >= vb.Max.Y*πsign.Y { // u is below v
return true
}
}
// Ask the components themselves if they have an opinion
if do, ok := u.(DrawOrderer); ok && do.DrawBefore(v) {
return true
}
if do, ok := v.(DrawOrderer); ok && do.DrawAfter(u) {
return true
}
// No relation
return false
}
2021-09-20 10:13:43 +10:00
type drawerSet map[Drawer]struct{}
2021-09-20 14:24:32 +10:00
func (s drawerSet) String() string {
var sb strings.Builder
sb.WriteString("{ ")
for x := range s {
fmt.Fprintf(&sb, "%v ", x)
}
sb.WriteString("}")
return sb.String()
}
2021-09-24 10:32:31 +10:00
type edges struct {
in, out drawerSet
2021-09-20 10:13:43 +10:00
}
2021-09-24 10:32:31 +10:00
type dag map[Drawer]edges
2021-09-20 10:13:43 +10:00
2021-09-23 14:17:18 +10:00
// Dot returns a dot-syntax-like description of the graph.
2021-09-24 10:32:31 +10:00
func (d dag) Dot() string {
2021-09-20 14:24:32 +10:00
var sb strings.Builder
sb.WriteString("digraph {\n")
2021-09-24 10:32:31 +10:00
for v, e := range d {
fmt.Fprintf(&sb, "%v -> %v\n", v, e.out)
2021-09-20 14:24:32 +10:00
}
sb.WriteString(" }\n")
return sb.String()
}
2021-09-20 14:36:00 +10:00
// addVertex ensures the vertex is present, even if there are no edges.
2021-09-24 10:32:31 +10:00
func (d dag) addVertex(v Drawer) {
if _, found := d[v]; found {
return
}
d[v] = edges{
in: make(drawerSet),
out: make(drawerSet),
}
2021-09-20 14:36:00 +10:00
}
2021-09-24 12:28:11 +10:00
// removeVertex removes v, and all edges associated with v, in O(degree(v)).
2021-09-24 10:32:31 +10:00
func (d dag) removeVertex(v Drawer) {
for u := range d[v].in {
2021-09-24 12:27:34 +10:00
delete(d[u].out, v)
2021-09-20 10:13:43 +10:00
}
2021-09-24 10:32:31 +10:00
for w := range d[v].out {
2021-09-24 12:27:34 +10:00
delete(d[w].in, v)
2021-09-20 10:13:43 +10:00
}
2021-09-24 10:32:31 +10:00
delete(d, v)
2021-09-20 10:13:43 +10:00
}
2021-09-24 11:29:08 +10:00
// addEdge adds the edge (u -> v) in O(1).
2021-09-24 10:35:23 +10:00
func (d dag) addEdge(u, v Drawer) {
d.addVertex(u)
d.addVertex(v)
d[v].in[u] = struct{}{}
d[u].out[v] = struct{}{}
}
2021-09-24 11:29:08 +10:00
// removeEdge removes the edge (u -> v) in O(1).
2021-09-24 12:27:34 +10:00
//lint:ignore U1000 this exists for symmetry with addEdge
2021-09-24 10:35:23 +10:00
func (d dag) removeEdge(u, v Drawer) {
delete(d[v].in, u)
delete(d[u].out, v)
}
2021-09-21 15:17:59 +10:00
// topWalk visits each vertex in topological order, in time O(|V| + |E|) and
2021-09-23 12:45:05 +10:00
// O(|V|) temporary memory (for acyclic graphs) and a bit longer if it has to
// break cycles.
2021-09-24 10:32:31 +10:00
func (d dag) topWalk(visit func(Drawer)) {
2021-09-24 11:29:08 +10:00
// Count indegrees - indegree(v) = len(d[v].in) for each vertex v.
2021-09-20 10:13:43 +10:00
// If indegree(v) = 0, enqueue. Total: O(|V|).
2021-09-24 10:32:31 +10:00
queue := make([]Drawer, 0, len(d))
2021-09-24 11:41:45 +10:00
indegree := make(map[Drawer]int, len(d))
2021-09-24 11:15:52 +10:00
for v, e := range d {
if len(e.in) == 0 {
queue = append(queue, v)
2021-09-20 10:13:43 +10:00
} else {
2021-09-24 11:15:52 +10:00
indegree[v] = len(e.in)
2021-09-20 10:13:43 +10:00
}
}
2021-09-23 12:45:05 +10:00
for len(indegree) > 0 || len(queue) > 0 {
if len(queue) == 0 {
// Getting here means there are unprocessed vertices, because none
// have zero indegree, otherwise they would be in the queue.
// Arbitrarily break a cycle by enqueueing the least-indegree vertex
mind, minv := math.MaxInt, Drawer(nil)
for v, d := range indegree {
if d < mind {
mind, minv = d, v
}
}
log.Printf("breaking cycle in 'DAG' by enqueueing %v with indegree %d", minv, mind)
queue = append(queue, minv)
delete(indegree, minv)
}
// 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:]
2021-09-24 11:15:52 +10:00
// Decrement indegree for all out-edges, and enqueue target if its
2021-09-23 12:45:05 +10:00
// indegree is now 0.
2021-09-24 10:32:31 +10:00
for v := range d[u].out {
2021-09-23 12:45:05 +10:00
if _, ready := indegree[v]; !ready {
// Vertex already drawn. This happens if there was a cycle.
continue
}
indegree[v]--
if indegree[v] == 0 {
queue = append(queue, v)
delete(indegree, v)
}
2021-09-20 10:13:43 +10:00
}
}
}
}