abolish transform
This commit is contained in:
parent
02d91f9d23
commit
024cf2cafe
13 changed files with 102 additions and 96 deletions
|
@ -17,12 +17,14 @@ func init() {
|
|||
// Actor handles basic movement.
|
||||
type Actor struct {
|
||||
CollisionDomain string // id of component to look for colliders inside of
|
||||
Pos, Size Int3
|
||||
xRem, yRem, zRem float64
|
||||
Pos, Size Int3 // in voxels; multiply by game.VoxelScale for regular Euclidean space
|
||||
|
||||
rem Float3
|
||||
game *Game
|
||||
}
|
||||
|
||||
// CollidesAt runs a collision test of the actor, supposing the actor is at a
|
||||
// given position (not necessarily a.Pos).
|
||||
func (a *Actor) CollidesAt(p Int3) bool {
|
||||
bounds := Box{Min: p, Max: p.Add(a.Size)}
|
||||
for c := range a.game.Query(a.CollisionDomain, ColliderType) {
|
||||
|
@ -33,13 +35,17 @@ func (a *Actor) CollidesAt(p Int3) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// MoveX moves the actor x units in world space. It takes Game.VoxelScale into
|
||||
// account (so MoveX(x) moves the actor x/VoxelScale.X voxel units). onCollide
|
||||
// is called if a collision occurs, and the actor wil be in the colliding
|
||||
// position during the call.
|
||||
func (a *Actor) MoveX(x float64, onCollide func()) {
|
||||
a.xRem += x
|
||||
move := int(a.xRem + 0.5) // Note: math.Round can lead to vibration
|
||||
a.rem.X += x / a.game.VoxelScale.X
|
||||
move := int(a.rem.X + 0.5) // Note: math.Round can lead to vibration
|
||||
if move == 0 {
|
||||
return
|
||||
}
|
||||
a.xRem -= float64(move)
|
||||
a.rem.X -= float64(move)
|
||||
sign := sign(move)
|
||||
for move != 0 {
|
||||
a.Pos.X += sign
|
||||
|
@ -51,18 +57,19 @@ func (a *Actor) MoveX(x float64, onCollide func()) {
|
|||
onCollide()
|
||||
}
|
||||
a.Pos.X -= sign
|
||||
a.xRem = 0
|
||||
a.rem.X = 0
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// MoveY is like MoveX but in the Y dimension. See MoveX for more information.
|
||||
func (a *Actor) MoveY(y float64, onCollide func()) {
|
||||
a.yRem += y
|
||||
move := int(a.yRem + 0.5)
|
||||
a.rem.Y += y / a.game.VoxelScale.Y
|
||||
move := int(a.rem.Y + 0.5)
|
||||
if move == 0 {
|
||||
return
|
||||
}
|
||||
a.yRem -= float64(move)
|
||||
a.rem.Y -= float64(move)
|
||||
sign := sign(move)
|
||||
for move != 0 {
|
||||
a.Pos.Y += sign
|
||||
|
@ -74,18 +81,19 @@ func (a *Actor) MoveY(y float64, onCollide func()) {
|
|||
onCollide()
|
||||
}
|
||||
a.Pos.Y -= sign
|
||||
a.yRem = 0
|
||||
a.rem.Y = 0
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// MoveZ is like MoveX but in the Y dimension. See MoveX for more information.
|
||||
func (a *Actor) MoveZ(z float64, onCollide func()) {
|
||||
a.zRem += z
|
||||
move := int(a.zRem + 0.5)
|
||||
a.rem.Z += z / a.game.VoxelScale.Z
|
||||
move := int(a.rem.Z + 0.5)
|
||||
if move == 0 {
|
||||
return
|
||||
}
|
||||
a.zRem -= float64(move)
|
||||
a.rem.Z -= float64(move)
|
||||
sign := sign(move)
|
||||
for move != 0 {
|
||||
a.Pos.Z += sign
|
||||
|
@ -97,11 +105,12 @@ func (a *Actor) MoveZ(z float64, onCollide func()) {
|
|||
onCollide()
|
||||
}
|
||||
a.Pos.Z -= sign
|
||||
a.zRem = 0
|
||||
a.rem.Z = 0
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare stores a reference to the game.
|
||||
func (a *Actor) Prepare(g *Game) error {
|
||||
a.game = g
|
||||
return nil
|
||||
|
|
|
@ -36,7 +36,7 @@ func (b *Billboard) Draw(screen *ebiten.Image, opts *ebiten.DrawImageOptions) {
|
|||
// Scan returns a slice containing Src.
|
||||
func (b *Billboard) Scan() []interface{} { return []interface{}{&b.Src} }
|
||||
|
||||
func (b *Billboard) Transform(pt Transform) (tf Transform) {
|
||||
tf.Opts.GeoM.Translate(cfloat(b.Pos))
|
||||
return tf.Concat(pt)
|
||||
func (b *Billboard) Transform() (opts ebiten.DrawImageOptions) {
|
||||
opts.GeoM.Translate(cfloat(b.Pos))
|
||||
return opts
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ package engine
|
|||
import (
|
||||
"encoding/gob"
|
||||
"image"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
// Ensure Camera satisfies interfaces.
|
||||
|
@ -87,11 +89,10 @@ func (c *Camera) Prepare(game *Game) error {
|
|||
func (c *Camera) Scan() []interface{} { return []interface{}{c.Child} }
|
||||
|
||||
// Transform returns the camera transform.
|
||||
func (c *Camera) Transform(pt Transform) (tf Transform) {
|
||||
tf.Projection = c.Projection
|
||||
tf.Opts.GeoM.Translate(cfloat(c.Centre.Mul(-1)))
|
||||
tf.Opts.GeoM.Scale(c.Zoom, c.Zoom)
|
||||
tf.Opts.GeoM.Rotate(c.Rotation)
|
||||
tf.Opts.GeoM.Translate(cfloat(c.game.ScreenSize.Div(2)))
|
||||
return tf.Concat(pt)
|
||||
func (c *Camera) Transform() (opts ebiten.DrawImageOptions) {
|
||||
opts.GeoM.Translate(cfloat(c.Centre.Mul(-1)))
|
||||
opts.GeoM.Scale(c.Zoom, c.Zoom)
|
||||
opts.GeoM.Rotate(c.Rotation)
|
||||
opts.GeoM.Translate(cfloat(c.game.ScreenSize.Div(2)))
|
||||
return opts
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ type Game struct {
|
|||
Hidden
|
||||
ScreenSize image.Point
|
||||
Root interface{} // typically a *Scene or SceneRef though
|
||||
Projection IntProjection
|
||||
VoxelScale Float3
|
||||
|
||||
dbmu sync.RWMutex
|
||||
|
@ -62,7 +63,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
|
|||
// accum memoises the results for each component.
|
||||
type state struct {
|
||||
hidden bool
|
||||
transform Transform
|
||||
opts ebiten.DrawImageOptions
|
||||
}
|
||||
accum := map[interface{}]state{
|
||||
g: {hidden: false},
|
||||
|
@ -96,9 +97,9 @@ func (g *Game) Draw(screen *ebiten.Image) {
|
|||
accum[p] = state{hidden: true}
|
||||
continue
|
||||
}
|
||||
// p is not hidden, so compute its cumulative transform.
|
||||
// p is not hidden, so compute its cumulative opts.
|
||||
if tf, ok := p.(Transformer); ok {
|
||||
st.transform = tf.Transform(st.transform)
|
||||
st.opts = ConcatOpts(tf.Transform(), st.opts)
|
||||
}
|
||||
accum[p] = st
|
||||
}
|
||||
|
@ -107,7 +108,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
|
|||
if st.hidden {
|
||||
continue
|
||||
}
|
||||
d.Draw(screen, &st.transform.Opts)
|
||||
d.Draw(screen, &st.opts)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -265,6 +266,10 @@ func postorderWalk(component, parent interface{}, visit func(component, parent i
|
|||
// builds the component databases and then calls Prepare on every Preparer.
|
||||
// LoadAndPrepare must be called before any calls to Component or Query.
|
||||
func (g *Game) LoadAndPrepare(assets fs.FS) error {
|
||||
if g.VoxelScale == (Float3{}) {
|
||||
g.VoxelScale = Float3{1, 1, 1}
|
||||
}
|
||||
|
||||
// Load all the Loaders.
|
||||
startLoad := time.Now()
|
||||
if err := PreorderWalk(g, func(c, _ interface{}) error {
|
||||
|
@ -440,3 +445,17 @@ func (d drawList) Less(i, j int) bool {
|
|||
|
||||
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).
|
||||
func ConcatOpts(a, b ebiten.DrawImageOptions) ebiten.DrawImageOptions {
|
||||
a.ColorM.Concat(b.ColorM)
|
||||
a.GeoM.Concat(b.GeoM)
|
||||
if b.CompositeMode != 0 {
|
||||
a.CompositeMode = b.CompositeMode
|
||||
}
|
||||
if b.Filter != 0 {
|
||||
a.Filter = b.Filter
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
|
|
@ -105,17 +105,11 @@ type Saver interface {
|
|||
Save() error
|
||||
}
|
||||
|
||||
// Transformer components can provide a transform to apply to themselves and any
|
||||
// child components, based on the cumulative parent transform. An
|
||||
// example implementation:
|
||||
//
|
||||
// func (f Foo) Transform(pt Transform) Transform {
|
||||
// var tf Transform
|
||||
// tf.Opts.GeoM.Translate(-2, 3) // or your own transform
|
||||
// return tf.Concat(pt)
|
||||
// }
|
||||
// Transformer components can provide draw options to apply to themselves and
|
||||
// any child components. The opts passed to Draw of a component c will be the
|
||||
// cumulative opts of all parents of c plus the value returned from c.Transform.
|
||||
type Transformer interface {
|
||||
Transform(Transform) Transform
|
||||
Transform() ebiten.DrawImageOptions
|
||||
}
|
||||
|
||||
// Updater components can update themselves. Update is called repeatedly. Each
|
||||
|
|
|
@ -3,6 +3,8 @@ package engine
|
|||
import (
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
var _ interface {
|
||||
|
@ -39,8 +41,8 @@ func (p *Parallax) Prepare(game *Game) error {
|
|||
func (p *Parallax) Scan() []interface{} { return []interface{}{p.Child} }
|
||||
|
||||
// Transform returns a GeoM translation of Factor * camera.Centre.
|
||||
func (p *Parallax) Transform(pt Transform) (tf Transform) {
|
||||
func (p *Parallax) Transform() (opts ebiten.DrawImageOptions) {
|
||||
x, y := cfloat(p.camera.Centre)
|
||||
tf.Opts.GeoM.Translate(x*p.Factor, y*p.Factor)
|
||||
return tf.Concat(pt)
|
||||
opts.GeoM.Translate(x*p.Factor, y*p.Factor)
|
||||
return opts
|
||||
}
|
||||
|
|
|
@ -57,9 +57,9 @@ func (m *PrismMap) Prepare(*Game) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *PrismMap) Transform(pt Transform) (tf Transform) {
|
||||
tf.Opts.GeoM.Translate(cfloat(m.DrawOffset))
|
||||
return tf.Concat(pt)
|
||||
func (m *PrismMap) Transform() (opts ebiten.DrawImageOptions) {
|
||||
opts.GeoM.Translate(cfloat(m.DrawOffset))
|
||||
return opts
|
||||
}
|
||||
|
||||
type Prism struct {
|
||||
|
@ -78,9 +78,9 @@ func (p *Prism) DrawOrder() (int, int) {
|
|||
dot(p.pos.XY(), p.pm.DrawOrderBias)
|
||||
}
|
||||
|
||||
func (p *Prism) Transform(pt Transform) (tf Transform) {
|
||||
tf.Opts.GeoM.Translate(cfloat(
|
||||
func (p *Prism) Transform() (opts ebiten.DrawImageOptions) {
|
||||
opts.GeoM.Translate(cfloat(
|
||||
p.pm.PosToDraw.Apply(p.pos),
|
||||
))
|
||||
return tf.Concat(pt)
|
||||
return opts
|
||||
}
|
||||
|
|
|
@ -2,10 +2,12 @@ package engine
|
|||
|
||||
import "image"
|
||||
|
||||
// IntProjection holds an integer projection definition.
|
||||
// It is designed for projecting Z onto X and Y with integer fractions as would
|
||||
// be used in e.g. a diametric projection (IntProjection{X:0, Y:-2}).
|
||||
type IntProjection image.Point
|
||||
|
||||
// Project performs an integer parallel projection of a 3D coordinate into 2D.
|
||||
//
|
||||
// If π.X = 0, the x returned is p.X; similarly for π.Y and y.
|
||||
// Otherwise, x projects to x + z/π.X and y projects to y + z/π.Y.
|
||||
func (π IntProjection) Project(p Int3) image.Point {
|
||||
|
|
|
@ -56,12 +56,15 @@ func (s *Sprite) SetAnim(a *Anim) {
|
|||
s.anim = a
|
||||
}
|
||||
|
||||
// Transform returns a translation by the DrawOffset and the iso-projected Pos.
|
||||
func (s *Sprite) Transform(pt Transform) (tf Transform) {
|
||||
tf.Opts.GeoM.Translate(cfloat(
|
||||
pt.Projection.Project(s.Actor.Pos).Add(s.DrawOffset),
|
||||
// Transform returns a translation by the DrawOffset and Actor.Pos projected
|
||||
func (s *Sprite) Transform() (opts ebiten.DrawImageOptions) {
|
||||
opts.GeoM.Translate(cfloat(
|
||||
// Reaching into Actor for a reference to Game so I don't have to
|
||||
// implement Prepare in this file, but writing this long comment
|
||||
// providing exposition...
|
||||
s.Actor.game.Projection.Project(s.Actor.Pos).Add(s.DrawOffset),
|
||||
))
|
||||
return tf.Concat(pt)
|
||||
return opts
|
||||
}
|
||||
|
||||
// Update updates the Sprite's anim. anim can change a bit so we don't tell Game
|
||||
|
|
|
@ -110,9 +110,9 @@ func (t *Tilemap) Scan() []interface{} {
|
|||
}
|
||||
|
||||
// Transform returns a translation by t.Offset.
|
||||
func (t *Tilemap) Transform(pt Transform) (tf Transform) {
|
||||
tf.Opts.GeoM.Translate(cfloat(t.Offset))
|
||||
return tf.Concat(pt)
|
||||
func (t *Tilemap) Transform() (opts ebiten.DrawImageOptions) {
|
||||
opts.GeoM.Translate(cfloat(t.Offset))
|
||||
return opts
|
||||
}
|
||||
|
||||
// TileAt returns the tile present at the given world coordinate.
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
package engine
|
||||
|
||||
import "github.com/hajimehoshi/ebiten/v2"
|
||||
|
||||
// Transform is a bucket of things that affect drawing.
|
||||
type Transform struct {
|
||||
// Projection is used by isometric 3D components to project their
|
||||
// coordinates into 2D. There's usually only one component in the tree that
|
||||
// sets this field, but it would apply to all descendants.
|
||||
Projection IntProjection
|
||||
|
||||
// Opts contains the 2D geometry matrix, the colour matrix, filter mode, and
|
||||
// composition mode.
|
||||
Opts ebiten.DrawImageOptions
|
||||
}
|
||||
|
||||
// Concat returns the combined transform (a transform equivalent to applying t
|
||||
// and then u).
|
||||
func (t Transform) Concat(u Transform) Transform {
|
||||
if u.Projection != (IntProjection{}) {
|
||||
t.Projection = u.Projection
|
||||
}
|
||||
t.Opts.ColorM.Concat(u.Opts.ColorM)
|
||||
t.Opts.GeoM.Concat(u.Opts.GeoM)
|
||||
if u.Opts.CompositeMode != 0 {
|
||||
t.Opts.CompositeMode = u.Opts.CompositeMode
|
||||
}
|
||||
if u.Opts.Filter != 0 {
|
||||
t.Opts.Filter = u.Opts.Filter
|
||||
}
|
||||
return t
|
||||
}
|
|
@ -80,9 +80,9 @@ func (w *Wall) Prepare(*Game) error {
|
|||
}
|
||||
|
||||
// Transform returns a GeoM translation by Offset.
|
||||
func (w *Wall) Transform(pt Transform) (tf Transform) {
|
||||
tf.Opts.GeoM.Translate(cfloat(w.Offset))
|
||||
return tf.Concat(pt)
|
||||
func (w *Wall) Transform() (opts ebiten.DrawImageOptions) {
|
||||
opts.GeoM.Translate(cfloat(w.Offset))
|
||||
return opts
|
||||
}
|
||||
|
||||
// WallUnit is a unit in a wall. Unlike a tile in a tilemap, WallUnit is
|
||||
|
@ -105,9 +105,9 @@ func (u *WallUnit) Draw(screen *ebiten.Image, opts *ebiten.DrawImageOptions) {
|
|||
// Scan returns the Tile.
|
||||
func (u *WallUnit) Scan() []interface{} { return []interface{}{u.Tile} }
|
||||
|
||||
func (u *WallUnit) Transform(pt Transform) (tf Transform) {
|
||||
tf.Opts.GeoM.Translate(cfloat(
|
||||
func (u *WallUnit) Transform() (opts ebiten.DrawImageOptions) {
|
||||
opts.GeoM.Translate(cfloat(
|
||||
cmul(u.pos, u.wall.UnitSize).Add(u.wall.UnitOffset),
|
||||
))
|
||||
return tf.Concat(pt)
|
||||
return opts
|
||||
}
|
||||
|
|
8
main.go
8
main.go
|
@ -5,6 +5,7 @@ import (
|
|||
"image/color"
|
||||
_ "image/png"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
|
@ -51,6 +52,13 @@ func main() {
|
|||
|
||||
g := &engine.Game{
|
||||
ScreenSize: image.Pt(320, 240),
|
||||
Projection: engine.IntProjection{X: 0, Y: 1},
|
||||
VoxelScale: engine.Float3{
|
||||
// Each voxel counts for this much Eucliden space:
|
||||
X: 1,
|
||||
Y: 1,
|
||||
Z: math.Sqrt(3),
|
||||
},
|
||||
Root: &engine.Scene{
|
||||
ID: "root",
|
||||
Components: []interface{}{
|
||||
|
|
Loading…
Reference in a new issue