WIP: opts -> transform
This commit is contained in:
parent
9717ab565f
commit
e065bad1a7
11 changed files with 86 additions and 61 deletions
|
@ -36,7 +36,7 @@ func (b *Billboard) Draw(screen *ebiten.Image, opts *ebiten.DrawImageOptions) {
|
||||||
// Scan returns a slice containing Src.
|
// Scan returns a slice containing Src.
|
||||||
func (b *Billboard) Scan() []interface{} { return []interface{}{&b.Src} }
|
func (b *Billboard) Scan() []interface{} { return []interface{}{&b.Src} }
|
||||||
|
|
||||||
func (b *Billboard) Transform() (opts ebiten.DrawImageOptions) {
|
func (b *Billboard) Transform() (tf Transform) {
|
||||||
opts.GeoM.Translate(cfloat(b.Pos))
|
tf.Opts.GeoM.Translate(cfloat(b.Pos))
|
||||||
return opts
|
return tf
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,6 @@ package engine
|
||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"image"
|
"image"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Ensure Camera satisfies interfaces.
|
// Ensure Camera satisfies interfaces.
|
||||||
|
@ -30,6 +28,7 @@ type Camera struct {
|
||||||
Centre image.Point // world coordinates
|
Centre image.Point // world coordinates
|
||||||
Rotation float64 // radians
|
Rotation float64 // radians
|
||||||
Zoom float64 // unitless
|
Zoom float64 // unitless
|
||||||
|
IsoProjection image.Point
|
||||||
|
|
||||||
game *Game
|
game *Game
|
||||||
}
|
}
|
||||||
|
@ -86,10 +85,11 @@ func (c *Camera) Prepare(game *Game) error {
|
||||||
func (c *Camera) Scan() []interface{} { return []interface{}{c.Child} }
|
func (c *Camera) Scan() []interface{} { return []interface{}{c.Child} }
|
||||||
|
|
||||||
// Transform returns the camera transform.
|
// Transform returns the camera transform.
|
||||||
func (c *Camera) Transform() (opts ebiten.DrawImageOptions) {
|
func (c *Camera) Transform() (tf Transform) {
|
||||||
opts.GeoM.Translate(cfloat(c.Centre.Mul(-1)))
|
tf.IsoProjection = c.IsoProjection
|
||||||
opts.GeoM.Scale(c.Zoom, c.Zoom)
|
tf.Opts.GeoM.Translate(cfloat(c.Centre.Mul(-1)))
|
||||||
opts.GeoM.Rotate(c.Rotation)
|
tf.Opts.GeoM.Scale(c.Zoom, c.Zoom)
|
||||||
opts.GeoM.Translate(cfloat(c.game.ScreenSize.Div(2)))
|
tf.Opts.GeoM.Rotate(c.Rotation)
|
||||||
return opts
|
tf.Opts.GeoM.Translate(cfloat(c.game.ScreenSize.Div(2)))
|
||||||
|
return tf
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
|
||||||
// accum memoises the results for each component.
|
// accum memoises the results for each component.
|
||||||
type state struct {
|
type state struct {
|
||||||
hidden bool
|
hidden bool
|
||||||
opts ebiten.DrawImageOptions
|
transform Transform
|
||||||
}
|
}
|
||||||
accum := map[interface{}]state{
|
accum := map[interface{}]state{
|
||||||
g: {hidden: false},
|
g: {hidden: false},
|
||||||
|
@ -97,7 +97,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
|
||||||
}
|
}
|
||||||
// p is not hidden, so compute its cumulative transform.
|
// p is not hidden, so compute its cumulative transform.
|
||||||
if t, ok := p.(Transformer); ok {
|
if t, ok := p.(Transformer); ok {
|
||||||
st.opts = concatOpts(t.Transform(), st.opts)
|
st.transform = t.Transform().Concat(st.transform)
|
||||||
}
|
}
|
||||||
accum[p] = st
|
accum[p] = st
|
||||||
}
|
}
|
||||||
|
@ -106,7 +106,7 @@ func (g *Game) Draw(screen *ebiten.Image) {
|
||||||
if st.hidden {
|
if st.hidden {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
d.Draw(screen, &st.opts)
|
d.Draw(screen, &st.transform.Opts)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -439,15 +439,3 @@ func (d drawList) Less(i, j int) bool {
|
||||||
|
|
||||||
func (d drawList) Len() int { return len(d) }
|
func (d drawList) Len() int { return len(d) }
|
||||||
func (d drawList) Swap(i, j int) { d[i], d[j] = d[j], d[i] }
|
func (d drawList) Swap(i, j int) { d[i], d[j] = d[j], d[i] }
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
|
@ -107,7 +107,7 @@ type Saver interface {
|
||||||
|
|
||||||
// Transformer components can transform their child components.
|
// Transformer components can transform their child components.
|
||||||
type Transformer interface {
|
type Transformer interface {
|
||||||
Transform() ebiten.DrawImageOptions
|
Transform() Transform
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updater components can update themselves. Update is called repeatedly. Each
|
// Updater components can update themselves. Update is called repeatedly. Each
|
||||||
|
|
|
@ -11,12 +11,12 @@ var (
|
||||||
_ interface {
|
_ interface {
|
||||||
Prepper
|
Prepper
|
||||||
Scanner
|
Scanner
|
||||||
|
Transformer
|
||||||
} = &IsoVoxmap{}
|
} = &IsoVoxmap{}
|
||||||
|
|
||||||
_ interface {
|
_ interface {
|
||||||
Prepper
|
Prepper
|
||||||
Scanner
|
Scanner
|
||||||
Transformer
|
|
||||||
} = &IsoVoxel{}
|
} = &IsoVoxel{}
|
||||||
|
|
||||||
_ Drawer = &IsoVoxelSide{}
|
_ Drawer = &IsoVoxelSide{}
|
||||||
|
@ -36,7 +36,6 @@ type IsoVoxmap struct {
|
||||||
Map map[Point3]*IsoVoxel
|
Map map[Point3]*IsoVoxel
|
||||||
DrawOrderBias image.Point // so boxes overdraw correctly
|
DrawOrderBias image.Point // so boxes overdraw correctly
|
||||||
DrawOffset image.Point
|
DrawOffset image.Point
|
||||||
Projection image.Point // IsoProjection parameter
|
|
||||||
Sheet Sheet
|
Sheet Sheet
|
||||||
VoxSize Point3 // size of each voxel
|
VoxSize Point3 // size of each voxel
|
||||||
}
|
}
|
||||||
|
@ -61,6 +60,12 @@ func (m *IsoVoxmap) Scan() []interface{} {
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Transform returns a translation by DrawOffset.
|
||||||
|
func (m *IsoVoxmap) Transform() (tf Transform) {
|
||||||
|
tf.Opts.GeoM.Translate(cfloat(m.DrawOffset))
|
||||||
|
return tf
|
||||||
|
}
|
||||||
|
|
||||||
// IsoVoxel is a voxel in an IsoVoxmap.
|
// IsoVoxel is a voxel in an IsoVoxmap.
|
||||||
type IsoVoxel struct {
|
type IsoVoxel struct {
|
||||||
CellBack int // cell to draw for back side
|
CellBack int // cell to draw for back side
|
||||||
|
@ -85,16 +90,6 @@ func (v *IsoVoxel) Scan() []interface{} {
|
||||||
return []interface{}{&v.back, &v.front}
|
return []interface{}{&v.back, &v.front}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform returns a translation of first by DrawOffset, then by
|
|
||||||
// pos.CMul(VoxSize) iso-projected (the top-left of the back of the voxel).
|
|
||||||
func (v *IsoVoxel) Transform() (opts ebiten.DrawImageOptions) {
|
|
||||||
opts.GeoM.Translate(cfloat(v.ivm.DrawOffset))
|
|
||||||
p3 := v.pos.CMul(v.ivm.VoxSize)
|
|
||||||
p2 := p3.IsoProject(v.ivm.Projection)
|
|
||||||
opts.GeoM.Translate(cfloat(p2))
|
|
||||||
return opts
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsoVoxelSide is a side of a voxel.
|
// IsoVoxelSide is a side of a voxel.
|
||||||
type IsoVoxelSide struct {
|
type IsoVoxelSide struct {
|
||||||
front bool
|
front bool
|
||||||
|
@ -103,6 +98,12 @@ type IsoVoxelSide struct {
|
||||||
|
|
||||||
// Draw draws this side.
|
// Draw draws this side.
|
||||||
func (v *IsoVoxelSide) Draw(screen *ebiten.Image, opts *ebiten.DrawImageOptions) {
|
func (v *IsoVoxelSide) Draw(screen *ebiten.Image, opts *ebiten.DrawImageOptions) {
|
||||||
|
|
||||||
|
// TODO: apply IsoProjection to opts.GeoM
|
||||||
|
// p3 := v.pos.CMul(v.ivm.VoxSize)
|
||||||
|
// p2 := p3.IsoProject(v.ivm.Projection)
|
||||||
|
// tf.Opts.GeoM.Translate(cfloat(p2))
|
||||||
|
|
||||||
cell := v.vox.CellBack
|
cell := v.vox.CellBack
|
||||||
if v.front {
|
if v.front {
|
||||||
cell = v.vox.CellFront
|
cell = v.vox.CellFront
|
||||||
|
|
|
@ -3,8 +3,6 @@ package engine
|
||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ interface {
|
var _ interface {
|
||||||
|
@ -41,8 +39,8 @@ func (p *Parallax) Prepare(game *Game) error {
|
||||||
func (p *Parallax) Scan() []interface{} { return []interface{}{p.Child} }
|
func (p *Parallax) Scan() []interface{} { return []interface{}{p.Child} }
|
||||||
|
|
||||||
// Transform returns a GeoM translation of Factor * camera.Centre.
|
// Transform returns a GeoM translation of Factor * camera.Centre.
|
||||||
func (p *Parallax) Transform() (opts ebiten.DrawImageOptions) {
|
func (p *Parallax) Transform() (tf Transform) {
|
||||||
x, y := cfloat(p.camera.Centre)
|
x, y := cfloat(p.camera.Centre)
|
||||||
opts.GeoM.Translate(x*p.Factor, y*p.Factor)
|
tf.Opts.GeoM.Translate(x*p.Factor, y*p.Factor)
|
||||||
return opts
|
return tf
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,9 +57,9 @@ func (s *Sprite) SetAnim(a *Anim) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform returns a translation by the FrameOffset.
|
// Transform returns a translation by the FrameOffset.
|
||||||
func (s *Sprite) Transform() (opts ebiten.DrawImageOptions) {
|
func (s *Sprite) Transform() (tf Transform) {
|
||||||
opts.GeoM.Translate(cfloat(s.Actor.Pos.XY().Add(s.FrameOffset)))
|
tf.Opts.GeoM.Translate(cfloat(s.Actor.Pos.XY().Add(s.FrameOffset)))
|
||||||
return opts
|
return tf
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update updates the Sprite's anim. anim can change a bit so we don't tell Game
|
// Update updates the Sprite's anim. anim can change a bit so we don't tell Game
|
||||||
|
|
|
@ -109,9 +109,10 @@ func (t *Tilemap) Scan() []interface{} {
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Tilemap) Transform() (opts ebiten.DrawImageOptions) {
|
// Transform returns a translation by t.Offset.
|
||||||
opts.GeoM.Translate(cfloat(t.Offset))
|
func (t *Tilemap) Transform() (tf Transform) {
|
||||||
return opts
|
tf.Opts.GeoM.Translate(cfloat(t.Offset))
|
||||||
|
return tf
|
||||||
}
|
}
|
||||||
|
|
||||||
// TileAt returns the tile present at the given world coordinate.
|
// TileAt returns the tile present at the given world coordinate.
|
||||||
|
|
36
engine/transform.go
Normal file
36
engine/transform.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package engine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
|
||||||
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Transform is a bucket of things that affect drawing.
|
||||||
|
type Transform struct {
|
||||||
|
// IsoProjection 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.
|
||||||
|
IsoProjection image.Point
|
||||||
|
|
||||||
|
// 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.IsoProjection != (image.Point{}) {
|
||||||
|
t.IsoProjection = u.IsoProjection
|
||||||
|
}
|
||||||
|
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.
|
// Transform returns a GeoM translation by Offset.
|
||||||
func (w *Wall) Transform() (opts ebiten.DrawImageOptions) {
|
func (w *Wall) Transform() (tf Transform) {
|
||||||
opts.GeoM.Translate(cfloat(w.Offset))
|
tf.Opts.GeoM.Translate(cfloat(w.Offset))
|
||||||
return opts
|
return tf
|
||||||
}
|
}
|
||||||
|
|
||||||
// WallUnit is a unit in a wall. Unlike a tile in a tilemap, WallUnit is
|
// WallUnit is a unit in a wall. Unlike a tile in a tilemap, WallUnit is
|
||||||
|
@ -105,7 +105,7 @@ func (u *WallUnit) Draw(screen *ebiten.Image, opts *ebiten.DrawImageOptions) {
|
||||||
// Scan returns the Tile.
|
// Scan returns the Tile.
|
||||||
func (u *WallUnit) Scan() []interface{} { return []interface{}{u.Tile} }
|
func (u *WallUnit) Scan() []interface{} { return []interface{}{u.Tile} }
|
||||||
|
|
||||||
func (u *WallUnit) Transform() (opts ebiten.DrawImageOptions) {
|
func (u *WallUnit) Transform() (tf Transform) {
|
||||||
opts.GeoM.Translate(cfloat(cmul(u.pos, u.wall.UnitSize).Add(u.wall.UnitOffset)))
|
tf.Opts.GeoM.Translate(cfloat(cmul(u.pos, u.wall.UnitSize).Add(u.wall.UnitOffset)))
|
||||||
return opts
|
return tf
|
||||||
}
|
}
|
||||||
|
|
3
main.go
3
main.go
|
@ -57,6 +57,8 @@ func main() {
|
||||||
&engine.Camera{
|
&engine.Camera{
|
||||||
ID: "game_camera",
|
ID: "game_camera",
|
||||||
Child: lev1,
|
Child: lev1,
|
||||||
|
// Each step in Z becomes -½ step in X plus ½ step in Y:
|
||||||
|
IsoProjection: image.Pt(-2, 2),
|
||||||
},
|
},
|
||||||
&engine.DebugToast{ID: "toast", Pos: image.Pt(0, 15)},
|
&engine.DebugToast{ID: "toast", Pos: image.Pt(0, 15)},
|
||||||
engine.PerfDisplay{},
|
engine.PerfDisplay{},
|
||||||
|
@ -129,7 +131,6 @@ func level1() *engine.Scene {
|
||||||
ID: "voxmap",
|
ID: "voxmap",
|
||||||
DrawOrderBias: image.Pt(1, -1), // left before right, bottom before top
|
DrawOrderBias: image.Pt(1, -1), // left before right, bottom before top
|
||||||
DrawOffset: image.Pt(-8, 0),
|
DrawOffset: image.Pt(-8, 0),
|
||||||
Projection: image.Pt(-2, 2), // each step in Z becomes -1/2 step in X plus 1/2 step in Y.
|
|
||||||
VoxSize: engine.Pt3(16, 16, 16),
|
VoxSize: engine.Pt3(16, 16, 16),
|
||||||
Sheet: engine.Sheet{
|
Sheet: engine.Sheet{
|
||||||
CellSize: image.Pt(24, 24),
|
CellSize: image.Pt(24, 24),
|
||||||
|
|
Loading…
Reference in a new issue