2021-09-15 11:53:17 +10:00
|
|
|
package engine
|
|
|
|
|
2021-09-16 21:32:03 +10:00
|
|
|
import (
|
2021-09-17 13:02:31 +10:00
|
|
|
"image"
|
2021-09-17 13:51:24 +10:00
|
|
|
"math"
|
2021-09-16 21:32:03 +10:00
|
|
|
|
2021-09-17 13:02:31 +10:00
|
|
|
"drjosh.dev/gurgle/geom"
|
2021-09-16 21:32:03 +10:00
|
|
|
"github.com/hajimehoshi/ebiten/v2"
|
|
|
|
)
|
2021-09-15 11:53:17 +10:00
|
|
|
|
|
|
|
var _ Drawer = tombstone{}
|
|
|
|
|
|
|
|
type tombstone struct{}
|
|
|
|
|
|
|
|
func (tombstone) Draw(*ebiten.Image, *ebiten.DrawImageOptions) {}
|
|
|
|
|
|
|
|
func (tombstone) DrawAfter(x Drawer) bool { return x != tombstone{} }
|
|
|
|
func (tombstone) DrawBefore(Drawer) bool { return false }
|
|
|
|
|
2021-09-17 11:13:39 +10:00
|
|
|
func (tombstone) String() string { return "tombstone" }
|
|
|
|
|
2021-09-15 11:53:17 +10:00
|
|
|
type drawList struct {
|
|
|
|
list []Drawer
|
|
|
|
rev map[Drawer]int
|
|
|
|
}
|
|
|
|
|
2021-09-17 14:56:00 +10:00
|
|
|
// edge reports if there is a draw ordering constraint between u and v (where
|
|
|
|
// u draws before v).
|
2021-09-17 16:47:18 +10:00
|
|
|
func edge(u, v Drawer, πsign image.Point) bool {
|
2021-09-17 14:56:00 +10:00
|
|
|
// Common logic for known interfaces (BoundingBoxer, ZPositioner), to
|
|
|
|
// simplify DrawOrderer implementations.
|
2021-09-17 16:47:18 +10:00
|
|
|
switch u := u.(type) {
|
2021-09-17 14:56:00 +10:00
|
|
|
case BoundingBoxer:
|
2021-09-17 16:47:18 +10:00
|
|
|
ub := u.BoundingBox()
|
|
|
|
switch v := v.(type) {
|
2021-09-15 17:41:23 +10:00
|
|
|
case BoundingBoxer:
|
2021-09-17 16:47:18 +10:00
|
|
|
vb := v.BoundingBox()
|
|
|
|
if ub.Min.Z >= vb.Max.Z { // u is in front of v
|
2021-09-17 14:56:00 +10:00
|
|
|
return false
|
|
|
|
}
|
2021-09-17 16:47:18 +10:00
|
|
|
if ub.Max.Z <= vb.Min.Z { // u is behind v
|
2021-09-17 14:56:00 +10:00
|
|
|
return true
|
|
|
|
}
|
2021-09-17 16:47:18 +10:00
|
|
|
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
|
|
|
|
}
|
2021-09-15 17:41:23 +10:00
|
|
|
}
|
2021-09-17 16:47:18 +10:00
|
|
|
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
|
|
|
|
}
|
2021-09-17 14:56:00 +10:00
|
|
|
}
|
|
|
|
case ZPositioner:
|
2021-09-17 16:47:18 +10:00
|
|
|
return ub.Max.Z < v.ZPos() // u is before v
|
2021-09-17 14:56:00 +10:00
|
|
|
}
|
2021-09-15 17:41:23 +10:00
|
|
|
|
2021-09-17 14:56:00 +10:00
|
|
|
case ZPositioner:
|
|
|
|
switch y := v.(type) {
|
|
|
|
case BoundingBoxer:
|
2021-09-17 16:47:18 +10:00
|
|
|
return u.ZPos() < y.BoundingBox().Min.Z
|
2021-09-15 17:41:23 +10:00
|
|
|
case ZPositioner:
|
2021-09-17 16:47:18 +10:00
|
|
|
return u.ZPos() < y.ZPos()
|
2021-09-15 17:41:23 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-17 14:56:00 +10:00
|
|
|
// Fallback case: 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
|
|
|
|
}
|
2021-09-15 11:53:17 +10:00
|
|
|
|
2021-09-17 14:56:00 +10:00
|
|
|
// No relation
|
|
|
|
return false
|
2021-09-15 11:53:17 +10:00
|
|
|
}
|
2021-09-16 21:32:03 +10:00
|
|
|
|
2021-09-17 16:47:18 +10:00
|
|
|
var wholePlane = image.Rect(math.MinInt, math.MinInt, math.MaxInt, math.MaxInt)
|
|
|
|
|
2021-09-17 14:56:00 +10:00
|
|
|
// Topological sort. Uses a projection π to flatten bounding boxes for
|
2021-09-17 13:59:38 +10:00
|
|
|
// overlap tests, in order to reduce edge count.
|
2021-09-17 13:51:24 +10:00
|
|
|
func (d *drawList) topsort(π geom.Projector) {
|
|
|
|
// Produce edge lists and count indegrees - O(|V|^2)
|
|
|
|
// TODO: optimise this
|
2021-09-17 12:50:29 +10:00
|
|
|
edges := make([][]int, len(d.list))
|
|
|
|
indegree := make([]int, len(d.list))
|
|
|
|
for i, u := range d.list {
|
|
|
|
if u == (tombstone{}) {
|
2021-09-17 13:51:24 +10:00
|
|
|
// Prevents processing this vertex later on
|
|
|
|
indegree[i] = -1
|
2021-09-17 12:50:29 +10:00
|
|
|
continue
|
|
|
|
}
|
2021-09-17 13:51:24 +10:00
|
|
|
// If we can't get a more specific bounding rect, assume entire plane.
|
2021-09-17 16:47:18 +10:00
|
|
|
ub := wholePlane
|
2021-09-17 13:51:24 +10:00
|
|
|
if x, ok := u.(BoundingBoxer); ok {
|
|
|
|
ub = x.BoundingBox().BoundingRect(π)
|
2021-09-17 13:02:31 +10:00
|
|
|
}
|
2021-09-17 13:51:24 +10:00
|
|
|
// For each possible neighbor...
|
2021-09-17 12:50:29 +10:00
|
|
|
for j, v := range d.list {
|
2021-09-17 13:51:24 +10:00
|
|
|
if i == j || v == (tombstone{}) {
|
2021-09-16 21:32:03 +10:00
|
|
|
continue
|
|
|
|
}
|
2021-09-17 13:51:24 +10:00
|
|
|
// Does it have a bounding rect? Do overlap test.
|
|
|
|
if y, ok := v.(BoundingBoxer); ok {
|
|
|
|
if vb := y.BoundingBox().BoundingRect(π); !ub.Overlaps(vb) {
|
|
|
|
continue
|
|
|
|
}
|
2021-09-17 13:02:31 +10:00
|
|
|
}
|
2021-09-17 14:39:23 +10:00
|
|
|
|
2021-09-17 13:51:24 +10:00
|
|
|
// If the edge goes u->v, add it.
|
2021-09-17 16:47:18 +10:00
|
|
|
if edge(u, v, π.Sign()) {
|
2021-09-17 12:50:29 +10:00
|
|
|
edges[i] = append(edges[i], j)
|
|
|
|
indegree[j]++
|
2021-09-16 21:32:03 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-09-17 12:50:29 +10:00
|
|
|
|
2021-09-17 13:51:24 +10:00
|
|
|
// Initialise queue with all the zero-indegree vertices
|
2021-09-17 12:50:29 +10:00
|
|
|
var queue []int
|
|
|
|
for i, n := range indegree {
|
|
|
|
if n == 0 {
|
|
|
|
queue = append(queue, i)
|
2021-09-16 21:32:03 +10:00
|
|
|
}
|
2021-09-17 12:50:29 +10:00
|
|
|
}
|
|
|
|
|
2021-09-17 13:51:24 +10:00
|
|
|
// Process into new list. O(|V| + |E|)
|
2021-09-17 12:50:29 +10:00
|
|
|
list := make([]Drawer, 0, len(d.list))
|
|
|
|
for len(queue) > 0 {
|
2021-09-17 13:51:24 +10:00
|
|
|
// Get front of queue.
|
2021-09-17 12:50:29 +10:00
|
|
|
i := queue[0]
|
|
|
|
queue = queue[1:]
|
2021-09-17 13:51:24 +10:00
|
|
|
// Add to output list.
|
2021-09-17 13:02:31 +10:00
|
|
|
d.rev[d.list[i]] = len(list)
|
2021-09-17 12:50:29 +10:00
|
|
|
list = append(list, d.list[i])
|
2021-09-17 13:51:24 +10:00
|
|
|
// Reduce indegree for all outgoing edges, enqueue if indegree now 0.
|
2021-09-17 12:50:29 +10:00
|
|
|
for _, j := range edges[i] {
|
|
|
|
indegree[j]--
|
2021-09-17 13:51:24 +10:00
|
|
|
if indegree[j] == 0 {
|
2021-09-17 12:50:29 +10:00
|
|
|
queue = append(queue, j)
|
2021-09-16 21:32:03 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-09-17 13:51:24 +10:00
|
|
|
// Job done!
|
2021-09-16 21:32:03 +10:00
|
|
|
d.list = list
|
|
|
|
}
|