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 12:50:29 +10:00
|
|
|
"log"
|
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
|
|
|
|
2021-09-16 11:18:24 +10:00
|
|
|
const commonDrawerComparisons = false
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d drawList) Less(i, j int) bool {
|
2021-09-15 17:41:23 +10:00
|
|
|
// Deal with tombstones first, in case anything else thinks it
|
|
|
|
// needs to go last.
|
2021-09-15 11:53:17 +10:00
|
|
|
if d.list[i] == (tombstone{}) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if d.list[j] == (tombstone{}) {
|
|
|
|
return true
|
|
|
|
}
|
2021-09-15 17:41:23 +10:00
|
|
|
|
2021-09-16 11:18:24 +10:00
|
|
|
if commonDrawerComparisons {
|
|
|
|
// Common logic for known interfaces (BoundingBoxer, ZPositioner), to
|
|
|
|
// simplify Draw{Before,After} implementations.
|
|
|
|
switch x := d.list[i].(type) {
|
2021-09-15 17:41:23 +10:00
|
|
|
case BoundingBoxer:
|
2021-09-16 11:18:24 +10:00
|
|
|
xb := x.BoundingBox()
|
|
|
|
switch y := d.list[j].(type) {
|
|
|
|
case BoundingBoxer:
|
|
|
|
yb := y.BoundingBox()
|
|
|
|
if xb.Min.Z >= yb.Max.Z { // x is in front of y
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if xb.Max.Z <= yb.Min.Z { // x is behind y
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if xb.Max.Y <= yb.Min.Y { // x is above y
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if xb.Min.Y >= yb.Max.Y { // x is below y
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
case ZPositioner:
|
|
|
|
return xb.Max.Z < y.ZPos() // x is before y
|
2021-09-15 17:41:23 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
case ZPositioner:
|
2021-09-16 11:18:24 +10:00
|
|
|
switch y := d.list[j].(type) {
|
|
|
|
case BoundingBoxer:
|
|
|
|
return x.ZPos() < y.BoundingBox().Min.Z
|
|
|
|
case ZPositioner:
|
|
|
|
return x.ZPos() < y.ZPos()
|
|
|
|
}
|
2021-09-15 17:41:23 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fallback case: ask the components themselves
|
2021-09-15 11:53:17 +10:00
|
|
|
return d.list[i].DrawBefore(d.list[j]) || d.list[j].DrawAfter(d.list[i])
|
|
|
|
}
|
|
|
|
|
|
|
|
func (d drawList) Len() int { return len(d.list) }
|
|
|
|
|
|
|
|
func (d drawList) Swap(i, j int) {
|
|
|
|
d.rev[d.list[i]], d.rev[d.list[j]] = j, i
|
|
|
|
d.list[i], d.list[j] = d.list[j], d.list[i]
|
|
|
|
}
|
2021-09-16 21:32:03 +10:00
|
|
|
|
2021-09-17 13:02:31 +10:00
|
|
|
// Slow topological sort
|
2021-09-17 12:50:29 +10:00
|
|
|
func (d *drawList) topsort() {
|
|
|
|
// Produce edge lists - O(|V|^2)
|
|
|
|
// Count indegrees - also O(|V|^2)
|
|
|
|
edges := make([][]int, len(d.list))
|
|
|
|
indegree := make([]int, len(d.list))
|
|
|
|
for i, u := range d.list {
|
|
|
|
if u == (tombstone{}) {
|
|
|
|
continue
|
|
|
|
}
|
2021-09-17 13:02:31 +10:00
|
|
|
var ub image.Rectangle
|
|
|
|
switch x := u.(type) {
|
|
|
|
case BoundingBoxer:
|
|
|
|
ub = x.BoundingBox().BoundingRect(geom.IntProjection{X: 0, Y: 1})
|
|
|
|
default:
|
|
|
|
ub = image.Rect(0, 0, 320, 240)
|
|
|
|
}
|
2021-09-17 12:50:29 +10:00
|
|
|
for j, v := range d.list {
|
|
|
|
if i == j {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if v == (tombstone{}) {
|
2021-09-16 21:32:03 +10:00
|
|
|
continue
|
|
|
|
}
|
2021-09-17 13:02:31 +10:00
|
|
|
var vb image.Rectangle
|
|
|
|
switch y := v.(type) {
|
|
|
|
case BoundingBoxer:
|
|
|
|
vb = y.BoundingBox().BoundingRect(geom.IntProjection{X: 0, Y: 1})
|
|
|
|
default:
|
|
|
|
vb = image.Rect(0, 0, 320, 240)
|
|
|
|
}
|
|
|
|
if !ub.Overlaps(vb) {
|
|
|
|
// No overlap, no need to emit an edge
|
|
|
|
continue
|
|
|
|
}
|
2021-09-16 21:32:03 +10:00
|
|
|
if u.DrawBefore(v) || v.DrawAfter(u) {
|
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
|
|
|
|
|
|
|
// Start queue with all zero-indegree vertices
|
|
|
|
var queue []int
|
|
|
|
for i, n := range indegree {
|
|
|
|
if d.list[i] == (tombstone{}) {
|
|
|
|
continue
|
2021-09-16 21:32:03 +10:00
|
|
|
}
|
2021-09-17 12:50:29 +10:00
|
|
|
if n == 0 {
|
|
|
|
queue = append(queue, i)
|
2021-09-16 21:32:03 +10:00
|
|
|
}
|
2021-09-17 12:50:29 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
// Process into new list
|
|
|
|
list := make([]Drawer, 0, len(d.list))
|
|
|
|
for len(queue) > 0 {
|
|
|
|
i := queue[0]
|
|
|
|
queue = queue[1:]
|
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])
|
|
|
|
for _, j := range edges[i] {
|
|
|
|
indegree[j]--
|
|
|
|
if indegree[j] <= 0 {
|
|
|
|
if indegree[j] < 0 {
|
|
|
|
log.Printf("indegree[%d] = %d (component %v)", j, indegree[j], d.list[j])
|
2021-09-16 21:32:03 +10:00
|
|
|
}
|
2021-09-17 12:50:29 +10:00
|
|
|
queue = append(queue, j)
|
2021-09-16 21:32:03 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-09-17 12:50:29 +10:00
|
|
|
|
2021-09-16 21:32:03 +10:00
|
|
|
// Replace list
|
|
|
|
d.list = list
|
2021-09-17 13:02:31 +10:00
|
|
|
if false {
|
|
|
|
// Update rev
|
|
|
|
d.rev = make(map[Drawer]int, len(list))
|
|
|
|
for i, v := range list {
|
|
|
|
d.rev[v] = i
|
|
|
|
}
|
2021-09-16 21:32:03 +10:00
|
|
|
}
|
|
|
|
}
|