progress on draw ordering
This commit is contained in:
parent
550cb5ef73
commit
bdb8e6775e
17 changed files with 182 additions and 76 deletions
|
@ -7,7 +7,10 @@ import (
|
|||
)
|
||||
|
||||
// Ensure Actor satisfies interfaces.
|
||||
var _ Prepper = &Actor{}
|
||||
var _ interface {
|
||||
BoundingBoxer
|
||||
Prepper
|
||||
} = &Actor{}
|
||||
|
||||
func init() {
|
||||
gob.Register(&Actor{})
|
||||
|
@ -26,6 +29,7 @@ type Actor struct {
|
|||
game *Game
|
||||
}
|
||||
|
||||
// BoundingBox returns the box Bounds.Add(Pos).
|
||||
func (a *Actor) BoundingBox() geom.Box {
|
||||
return a.Bounds.Add(a.Pos)
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ type Billboard struct {
|
|||
Hidden
|
||||
Pos image.Point
|
||||
Src ImageRef
|
||||
ZOrder
|
||||
ZPosition
|
||||
}
|
||||
|
||||
// Draw draws the image.
|
||||
|
|
|
@ -39,7 +39,7 @@ type Camera struct {
|
|||
// for the bounds of the child component (if available).
|
||||
func (c *Camera) PointAt(centre geom.Int3, zoom float64) {
|
||||
// Special sauce: if Child has a BoundingRect, make some adjustments
|
||||
bnd, ok := c.Child.(Bounder)
|
||||
bnd, ok := c.Child.(BoundingRecter)
|
||||
if !ok {
|
||||
c.Centre = c.game.Projection.Project(centre)
|
||||
c.Zoom = zoom
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"encoding/gob"
|
||||
"fmt"
|
||||
"image"
|
||||
"math"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
|
||||
|
@ -41,9 +40,9 @@ func (d *DebugToast) Draw(screen *ebiten.Image, _ *ebiten.DrawImageOptions) {
|
|||
ebitenutil.DebugPrintAt(screen, d.Text, d.Pos.X, d.Pos.Y)
|
||||
}
|
||||
|
||||
func (d *DebugToast) DrawOrder() float64 {
|
||||
func (DebugToast) DrawAfter(x Drawer) bool {
|
||||
// Always draw on top
|
||||
return math.MaxFloat64
|
||||
return x != Tombstone{}
|
||||
}
|
||||
|
||||
func (d *DebugToast) Toast(text string) {
|
||||
|
@ -68,7 +67,7 @@ func (p PerfDisplay) Draw(screen *ebiten.Image, _ *ebiten.DrawImageOptions) {
|
|||
ebitenutil.DebugPrint(screen, fmt.Sprintf("TPS: %0.2f FPS: %0.2f", ebiten.CurrentTPS(), ebiten.CurrentFPS()))
|
||||
}
|
||||
|
||||
func (PerfDisplay) DrawOrder() float64 {
|
||||
func (PerfDisplay) DrawAfter(x Drawer) bool {
|
||||
// Always draw on top
|
||||
return math.MaxFloat64
|
||||
return x != Tombstone{}
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ type Fill struct {
|
|||
ID
|
||||
Color color.Color
|
||||
Hidden
|
||||
ZOrder
|
||||
ZPosition
|
||||
}
|
||||
|
||||
func (f *Fill) Draw(screen *ebiten.Image, opts *ebiten.DrawImageOptions) {
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"image"
|
||||
"io/fs"
|
||||
"log"
|
||||
"math"
|
||||
"reflect"
|
||||
"sort"
|
||||
"sync"
|
||||
|
@ -180,7 +179,7 @@ func (g *Game) Update() error {
|
|||
sort.Stable(g.drawList)
|
||||
// Truncate tombstones from the end.
|
||||
for i := len(g.drawList) - 1; i >= 0; i-- {
|
||||
if g.drawList[i] == (tombstone{}) {
|
||||
if g.drawList[i] == (Tombstone{}) {
|
||||
g.drawList = g.drawList[:i]
|
||||
}
|
||||
}
|
||||
|
@ -408,7 +407,7 @@ func (g *Game) unregister(component interface{}) {
|
|||
// unregister from g.drawList
|
||||
for i, d := range g.drawList {
|
||||
if d == component {
|
||||
g.drawList[i] = tombstone{}
|
||||
g.drawList[i] = Tombstone{}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -425,25 +424,23 @@ type abKey struct {
|
|||
behaviour reflect.Type
|
||||
}
|
||||
|
||||
var _ Drawer = tombstone{}
|
||||
var _ Drawer = Tombstone{}
|
||||
|
||||
type tombstone struct{}
|
||||
type Tombstone struct{}
|
||||
|
||||
func (tombstone) Draw(*ebiten.Image, *ebiten.DrawImageOptions) {}
|
||||
func (Tombstone) Draw(*ebiten.Image, *ebiten.DrawImageOptions) {}
|
||||
|
||||
func (tombstone) DrawOrder() float64 { return math.Inf(1) }
|
||||
func (Tombstone) DrawAfter(x Drawer) bool {
|
||||
return x != Tombstone{}
|
||||
}
|
||||
|
||||
type drawList []Drawer
|
||||
|
||||
func (d drawList) Less(i, j int) bool {
|
||||
return d[i].DrawOrder() < d[j].DrawOrder()
|
||||
}
|
||||
|
||||
func (d drawList) Less(i, j int) bool { return d[j].DrawAfter(d[i]) }
|
||||
func (d drawList) Len() int { return len(d) }
|
||||
func (d drawList) Swap(i, j int) { d[i], d[j] = d[j], d[i] }
|
||||
|
||||
// ConcatOpts returns the combined options (as though a was applied and then
|
||||
// b).
|
||||
// ConcatOpts returns the combined options (as though a was applied and then b).
|
||||
func ConcatOpts(a, b ebiten.DrawImageOptions) ebiten.DrawImageOptions {
|
||||
a.ColorM.Concat(b.ColorM)
|
||||
a.GeoM.Concat(b.GeoM)
|
||||
|
|
|
@ -13,7 +13,8 @@ import (
|
|||
var (
|
||||
// TypeOf(pointer to interface).Elem() is "idiomatic" -
|
||||
// see https://pkg.go.dev/reflect#example-TypeOf
|
||||
BounderType = reflect.TypeOf((*Bounder)(nil)).Elem()
|
||||
BoundingRecterType = reflect.TypeOf((*BoundingRecter)(nil)).Elem()
|
||||
BoundingBoxerType = reflect.TypeOf((*BoundingBoxer)(nil)).Elem()
|
||||
ColliderType = reflect.TypeOf((*Collider)(nil)).Elem()
|
||||
DisablerType = reflect.TypeOf((*Disabler)(nil)).Elem()
|
||||
DrawerType = reflect.TypeOf((*Drawer)(nil)).Elem()
|
||||
|
@ -28,7 +29,8 @@ var (
|
|||
|
||||
// Behaviours lists all the behaviours that can be queried with Game.Query.
|
||||
Behaviours = []reflect.Type{
|
||||
BounderType,
|
||||
BoundingRecterType,
|
||||
BoundingBoxerType,
|
||||
ColliderType,
|
||||
DisablerType,
|
||||
DrawerType,
|
||||
|
@ -43,11 +45,16 @@ var (
|
|||
}
|
||||
)
|
||||
|
||||
// Bounder components have a bounding rectangle.
|
||||
type Bounder interface {
|
||||
// BoundingRecter components have a bounding rectangle.
|
||||
type BoundingRecter interface {
|
||||
BoundingRect() image.Rectangle
|
||||
}
|
||||
|
||||
// BoundingBoxer components have a bounding box.
|
||||
type BoundingBoxer interface {
|
||||
BoundingBox() geom.Box
|
||||
}
|
||||
|
||||
// Collider components have tangible form.
|
||||
type Collider interface {
|
||||
CollidesWith(geom.Box) bool
|
||||
|
@ -65,7 +72,7 @@ type Disabler interface {
|
|||
// passed to Game.Register or returned from Scan).
|
||||
type Drawer interface {
|
||||
Draw(screen *ebiten.Image, opts *ebiten.DrawImageOptions)
|
||||
DrawOrder() float64
|
||||
DrawAfter(x Drawer) bool
|
||||
}
|
||||
|
||||
// Hider components can be hidden.
|
||||
|
|
|
@ -40,8 +40,22 @@ func (h *Hidden) Hide() { *h = true }
|
|||
// Show sets h to false.
|
||||
func (h *Hidden) Show() { *h = false }
|
||||
|
||||
// ZOrder implements DrawOrder (in Drawer) directly (as a numeric value).
|
||||
type ZOrder float64
|
||||
// ZPosition implements DrawAfter and DrawPosition as a simple Z coordinate.
|
||||
type ZPosition int
|
||||
|
||||
// DrawOrder returns z.
|
||||
func (z ZOrder) DrawOrder() float64 { return float64(z) }
|
||||
// DrawAfter reports if z > x.Z.
|
||||
func (z ZPosition) DrawAfter(x Drawer) bool {
|
||||
switch d := x.(type) {
|
||||
case BoundingBoxer:
|
||||
return int(z) > d.BoundingBox().Max.Z
|
||||
case zpositioner:
|
||||
return z.zposition() > d.zposition()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (z ZPosition) zposition() int { return int(z) }
|
||||
|
||||
type zpositioner interface {
|
||||
zposition() int
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/gob"
|
||||
"fmt"
|
||||
"image"
|
||||
"log"
|
||||
|
||||
"drjosh.dev/gurgle/geom"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
|
@ -20,6 +21,7 @@ var (
|
|||
} = &PrismMap{}
|
||||
|
||||
_ interface {
|
||||
BoundingBoxer
|
||||
Drawer
|
||||
Transformer
|
||||
} = &Prism{}
|
||||
|
@ -142,9 +144,64 @@ func (p *Prism) Draw(screen *ebiten.Image, opts *ebiten.DrawImageOptions) {
|
|||
screen.DrawImage(p.m.Sheet.SubImage(p.Cell), opts)
|
||||
}
|
||||
|
||||
// DrawOrder returns the projected draw distance.
|
||||
func (p *Prism) DrawOrder() float64 {
|
||||
return p.m.game.Projection.DrawOrder(p.pos)
|
||||
func (p *Prism) logue(c Drawer, s string) {
|
||||
if p.pos.Y != -16 {
|
||||
return
|
||||
}
|
||||
if _, ok := c.(*Sprite); ok {
|
||||
log.Print(s)
|
||||
}
|
||||
}
|
||||
|
||||
// DrawAfter reports if the prism should be drawn after x.
|
||||
func (p *Prism) DrawAfter(x Drawer) bool {
|
||||
pb := p.BoundingBox()
|
||||
switch d := x.(type) {
|
||||
case *Prism:
|
||||
if p.pos.Z == d.pos.Z {
|
||||
return p.pos.Y < d.pos.Y
|
||||
}
|
||||
return p.pos.Z > d.pos.Z
|
||||
case BoundingBoxer:
|
||||
xb := d.BoundingBox()
|
||||
/*// No X overlap - no comparison needed
|
||||
if pb.Max.X <= xb.Min.X || pb.Min.X >= xb.Max.X {
|
||||
p.logue(x, "no x overlap")
|
||||
return false
|
||||
}*/
|
||||
// Z ?
|
||||
if pb.Min.Z >= xb.Max.Z { // p is unambiguously in front
|
||||
p.logue(x, "prism unambiguously in front")
|
||||
return true
|
||||
}
|
||||
if pb.Max.Z <= xb.Min.Z { // p is unambiguously behind
|
||||
p.logue(x, "prism unambiguously behind")
|
||||
return false
|
||||
}
|
||||
// Y ? (NB: up is negative)
|
||||
if pb.Max.Y <= xb.Min.Y { // p is above
|
||||
p.logue(x, "prism unambiguously above")
|
||||
return true
|
||||
}
|
||||
if pb.Min.Y >= xb.Max.Y { // p is below
|
||||
p.logue(x, "prism unambiguously below")
|
||||
return false
|
||||
}
|
||||
// Try Z again
|
||||
if pb.Min.Z+8 >= xb.Max.Z {
|
||||
p.logue(x, "prism midsection after max Z")
|
||||
return true
|
||||
}
|
||||
if pb.Min.Z+8 <= xb.Min.Z {
|
||||
p.logue(x, "prism midsection before min Z")
|
||||
return false
|
||||
}
|
||||
case zpositioner:
|
||||
p.logue(x, "zpositioner test??")
|
||||
return pb.Min.Z > int(d.zposition())
|
||||
}
|
||||
p.logue(x, "no tests at all???")
|
||||
return false
|
||||
}
|
||||
|
||||
// Transform returns a translation by the projected position.
|
||||
|
|
|
@ -40,6 +40,8 @@ func (g *Game) REPL(src io.Reader, dst io.Writer, assets fs.FS) error {
|
|||
g.cmdHide(dst, argv)
|
||||
case "show":
|
||||
g.cmdShow(dst, argv)
|
||||
case "print":
|
||||
g.cmdPrint(dst, argv)
|
||||
}
|
||||
fmt.Fprint(dst, prompt)
|
||||
}
|
||||
|
@ -184,3 +186,11 @@ func (g *Game) cmdShow(dst io.Writer, argv []string) {
|
|||
}
|
||||
h.Show()
|
||||
}
|
||||
|
||||
func (g *Game) cmdPrint(dst io.Writer, argv []string) {
|
||||
c := g.cmdutilComponentArg1(dst, argv)
|
||||
if c == nil {
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(dst, "%#v\n", c)
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ var (
|
|||
)
|
||||
|
||||
type scener interface {
|
||||
Bounder
|
||||
BoundingRecter
|
||||
Disabler
|
||||
Hider
|
||||
Identifier
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
// Ensure Sprite satisfies interfaces.
|
||||
var _ interface {
|
||||
BoundingBoxer
|
||||
Drawer
|
||||
Scanner
|
||||
Transformer
|
||||
|
@ -30,14 +31,42 @@ type Sprite struct {
|
|||
anim *Anim
|
||||
}
|
||||
|
||||
// BoundingBox forwards the call to Actor.
|
||||
func (s *Sprite) BoundingBox() geom.Box { return s.Actor.BoundingBox() }
|
||||
|
||||
// Draw draws the current cell to the screen.
|
||||
func (s *Sprite) Draw(screen *ebiten.Image, opts *ebiten.DrawImageOptions) {
|
||||
screen.DrawImage(s.Sheet.SubImage(s.anim.Cell()), opts)
|
||||
}
|
||||
|
||||
// DrawOrder returns the projected draw order.
|
||||
func (s *Sprite) DrawOrder() float64 {
|
||||
return s.Actor.game.Projection.DrawOrder(s.Actor.Pos)
|
||||
// DrawAfter reports if the sprite must be drawn after x.
|
||||
func (s *Sprite) DrawAfter(x Drawer) bool {
|
||||
sb := s.BoundingBox()
|
||||
switch d := x.(type) {
|
||||
case BoundingBoxer:
|
||||
xb := d.BoundingBox()
|
||||
/*// No X overlap - no comparison needed
|
||||
if sb.Max.X <= xb.Min.X || sb.Min.X >= xb.Max.X {
|
||||
return false
|
||||
}*/
|
||||
// Z ?
|
||||
if sb.Min.Z >= xb.Max.Z { // s is unambiguously in front
|
||||
return true
|
||||
}
|
||||
if sb.Max.Z <= xb.Min.Z { // s is unambiguously behind
|
||||
return false
|
||||
}
|
||||
// Y ? (NB: up is negative)
|
||||
if sb.Max.Y <= xb.Min.Y { // s is unambiguously above
|
||||
return true
|
||||
}
|
||||
if sb.Min.Y >= xb.Max.Y { // s is unambiguously below
|
||||
return false
|
||||
}
|
||||
case zpositioner:
|
||||
return sb.Min.Z > int(d.zposition())
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Scan returns the Actor and the Sheet.
|
||||
|
|
|
@ -44,7 +44,7 @@ type Tilemap struct {
|
|||
Ersatz bool // disables collisions ("fake wall")
|
||||
Offset image.Point // world coordinates
|
||||
Sheet Sheet
|
||||
ZOrder
|
||||
ZPosition
|
||||
}
|
||||
|
||||
// CollidesWith implements Collider.
|
||||
|
|
|
@ -92,7 +92,7 @@ type WallUnit struct {
|
|||
Disabled
|
||||
Hidden
|
||||
Tile Tile // chooses which cell in wall.Sheet to draw
|
||||
ZOrder
|
||||
ZPosition
|
||||
|
||||
pos image.Point // tilespace coordinates
|
||||
wall *Wall
|
||||
|
|
|
@ -17,13 +17,13 @@ func Level1() *engine.Scene {
|
|||
&engine.Fill{
|
||||
ID: "bg_fill",
|
||||
Color: color.Gray{100},
|
||||
ZOrder: -1000,
|
||||
ZPosition: -1000,
|
||||
},
|
||||
&engine.Parallax{
|
||||
CameraID: "game_camera",
|
||||
Child: &engine.Billboard{
|
||||
ID: "bg_image",
|
||||
ZOrder: -900,
|
||||
ZPosition: -900,
|
||||
Pos: image.Pt(-160, -120),
|
||||
Src: engine.ImageRef{Path: "assets/space.png"},
|
||||
},
|
||||
|
@ -31,7 +31,6 @@ func Level1() *engine.Scene {
|
|||
},
|
||||
&engine.PrismMap{
|
||||
ID: "hexagons",
|
||||
//DrawOrderBias: image.Pt(0, -1), // draw higher Y after lower Y
|
||||
PosToWorld: geom.IntMatrix3x4{
|
||||
// For each tile in the X direction, go right by 24 and
|
||||
// forward by 8, etc
|
||||
|
|
|
@ -54,9 +54,13 @@ func PolygonRectOverlap(convex []image.Point, rect image.Rectangle) bool {
|
|||
if PolygonContains(convex, rect.Min) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Reduced Max (to the inclusive bound).
|
||||
rmax := rect.Max.Sub(image.Pt(1, 1))
|
||||
// Since we went to the trouble of computing another point...
|
||||
// TODO: this shouldn't be necessary
|
||||
if PolygonContains(convex, rmax) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Only remaining cases involve line intersection between the rect and poly
|
||||
// having eliminated the possibility that one is entirely within another.
|
||||
|
|
|
@ -29,17 +29,3 @@ func (π IntProjection) Project(p Int3) image.Point {
|
|||
}
|
||||
return q
|
||||
}
|
||||
|
||||
// DrawOrder computes a draw-order value for a point under this projection.
|
||||
// Each projection has an implied camera angle - Z alone is insufficient to
|
||||
// order things properly.
|
||||
func (π IntProjection) DrawOrder(p Int3) float64 {
|
||||
z := float64(p.Z)
|
||||
if π.X != 0 {
|
||||
z -= float64(p.X) / float64(π.X)
|
||||
}
|
||||
if π.Y != 0 {
|
||||
z -= float64(p.Y) / float64(π.Y)
|
||||
}
|
||||
return z
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue