ichigo/engine/prisms.go

164 lines
3.7 KiB
Go
Raw Normal View History

2021-09-05 20:54:43 +10:00
package engine
import (
2021-09-05 20:58:55 +10:00
"encoding/gob"
2021-09-07 17:10:26 +10:00
"fmt"
2021-09-05 20:54:43 +10:00
"image"
2021-09-08 20:08:57 +10:00
"drjosh.dev/gurgle/geom"
2021-09-05 20:54:43 +10:00
"github.com/hajimehoshi/ebiten/v2"
)
2021-09-05 20:58:55 +10:00
var (
_ interface {
2021-09-06 10:45:51 +10:00
Identifier
Collider
Disabler
Hider
2021-09-05 20:58:55 +10:00
Prepper
Transformer
} = &PrismMap{}
_ interface {
Drawer
Transformer
} = &Prism{}
)
func init() {
gob.Register(&PrismMap{})
gob.Register(&Prism{})
}
2021-09-08 12:24:34 +10:00
// PrismMap is a generalised 3D tilemap/wallmap/etc.
2021-09-05 20:54:43 +10:00
type PrismMap struct {
2021-09-06 10:45:51 +10:00
ID
Disabled
Hidden
2021-09-07 17:10:26 +10:00
Ersatz bool
2021-09-08 20:08:57 +10:00
Map map[geom.Int3]*Prism // pos -> prism
DrawOrderBias image.Point // dot with pos.XY() = bias value
DrawOffset image.Point // offset applies to whole map
PosToWorld geom.IntMatrix3x4 // p.pos -> world voxelspace
PrismSize geom.Int3 // in world voxelspace units
PrismTop []image.Point // polygon vertices anticlockwise, Y means Z
2021-09-05 20:54:43 +10:00
Sheet Sheet
2021-09-07 14:46:05 +10:00
2021-09-07 17:10:26 +10:00
game *Game
2021-09-08 20:08:57 +10:00
pwinverse geom.RatMatrix3
2021-09-05 20:54:43 +10:00
}
2021-09-08 20:08:57 +10:00
func (m *PrismMap) CollidesWith(b geom.Box) bool {
2021-09-07 17:10:26 +10:00
if m.Ersatz {
return false
}
2021-09-08 17:08:21 +10:00
2021-09-07 17:10:26 +10:00
// To find the prisms need to test, we need to invert PosToWorld.
// Step 1: subtract whatever the translation component of PosToWorld is,
// reducing the rest of the problem to the 3x3 submatrix.
2021-09-07 21:55:05 +10:00
rb := b.Sub(m.PosToWorld.Translation())
2021-09-07 17:10:26 +10:00
// Step 2: invert the rest of the fucking matrix.
// (Spoilers: I did this already in Prepare)
2021-09-07 21:55:05 +10:00
rb.Min = m.pwinverse.IntApply(rb.Min)
rb.Max = m.pwinverse.IntApply(rb.Max) //.Sub(Int3{1, 1, 1}))
2021-09-07 20:45:04 +10:00
2021-09-07 21:55:05 +10:00
rb = rb.Canon() // inverse might flip the corners around...
2021-09-07 20:45:04 +10:00
2021-09-07 21:55:05 +10:00
// Check neighboring prisms too because there's a fencepost somewhere here
2021-09-08 20:09:52 +10:00
rb.Min = rb.Min.Sub(geom.Int3{X: 1, Y: 1, Z: 1})
rb.Max = rb.Max.Add(geom.Int3{X: 1, Y: 1, Z: 1})
2021-09-07 21:55:05 +10:00
2021-09-08 20:08:57 +10:00
var pp geom.Int3
2021-09-07 21:55:05 +10:00
for pp.Z = rb.Min.Z; pp.Z <= rb.Max.Z; pp.Z++ {
for pp.Y = rb.Min.Y; pp.Y <= rb.Max.Y; pp.Y++ {
for pp.X = rb.Min.X; pp.X <= rb.Max.X; pp.X++ {
2021-09-08 10:00:37 +10:00
// Is there a prism here?
2021-09-07 21:55:05 +10:00
if _, found := m.Map[pp]; !found {
continue
}
// Map it back to worldspace to get a bounding box for the prism
wp := m.PosToWorld.Apply(pp)
2021-09-08 20:08:57 +10:00
cb := geom.Box{Min: wp, Max: wp.Add(m.PrismSize)}
2021-09-08 10:00:37 +10:00
if !b.Overlaps(cb) {
continue
2021-09-07 17:10:26 +10:00
}
2021-09-08 17:08:21 +10:00
// Take into account the prism shape
r := b.XZ().Sub(wp.XZ())
2021-09-08 20:08:57 +10:00
if geom.PolygonRectOverlap(m.PrismTop, r) {
2021-09-08 17:08:21 +10:00
return true
}
2021-09-07 17:10:26 +10:00
}
}
}
2021-09-08 17:08:21 +10:00
/*
// Here's the test-every-prism approach
for pos := range m.Map {
// Map it back to worldspace to get a bounding box for the prism
wp := m.PosToWorld.Apply(pos)
cb := Box{Min: wp, Max: wp.Add(m.PrismSize)}
if !b.Overlaps(cb) {
continue
}
// Take into account the prism shape
r := b.XZ().Sub(wp.XZ())
2021-09-08 20:08:57 +10:00
if geom.PolygonRectOverlap(m.PrismTop, r) {
2021-09-08 17:08:21 +10:00
return true
}
}
*/
2021-09-06 10:45:51 +10:00
return false
}
2021-09-07 14:46:05 +10:00
func (m *PrismMap) Prepare(g *Game) error {
m.game = g
2021-09-07 17:10:26 +10:00
pwi, err := m.PosToWorld.ToRatMatrix3().Inverse()
if err != nil {
return fmt.Errorf("inverting PosToWorld: %w", err)
}
m.pwinverse = pwi
2021-09-05 20:54:43 +10:00
for v, p := range m.Map {
p.pos = v
2021-09-07 14:46:05 +10:00
p.m = m
2021-09-05 20:54:43 +10:00
}
return nil
}
2021-09-07 14:46:05 +10:00
func (m *PrismMap) Scan() []interface{} {
c := make([]interface{}, 1, len(m.Map)+1)
c[0] = &m.Sheet
for _, prism := range m.Map {
c = append(c, prism)
}
return c
}
2021-09-07 14:00:50 +10:00
func (m *PrismMap) Transform() (opts ebiten.DrawImageOptions) {
2021-09-08 20:08:57 +10:00
opts.GeoM.Translate(geom.CFloat(m.DrawOffset))
2021-09-07 14:00:50 +10:00
return opts
2021-09-05 20:54:43 +10:00
}
type Prism struct {
Cell int
2021-09-08 20:08:57 +10:00
pos geom.Int3
2021-09-07 14:46:05 +10:00
m *PrismMap
2021-09-05 20:54:43 +10:00
}
2021-09-05 20:58:55 +10:00
func (p *Prism) Draw(screen *ebiten.Image, opts *ebiten.DrawImageOptions) {
2021-09-07 14:46:05 +10:00
screen.DrawImage(p.m.Sheet.SubImage(p.Cell), opts)
2021-09-05 20:54:43 +10:00
}
func (p *Prism) DrawOrder() (int, int) {
2021-09-07 14:46:05 +10:00
return p.m.PosToWorld.Apply(p.pos).Z,
2021-09-08 20:08:57 +10:00
geom.Dot(p.pos.XY(), p.m.DrawOrderBias)
2021-09-05 20:54:43 +10:00
}
2021-09-07 14:00:50 +10:00
func (p *Prism) Transform() (opts ebiten.DrawImageOptions) {
2021-09-08 20:08:57 +10:00
opts.GeoM.Translate(geom.CFloat(
2021-09-07 14:46:05 +10:00
p.m.game.Projection.Project(p.m.PosToWorld.Apply(p.pos)),
2021-09-05 20:54:43 +10:00
))
2021-09-07 14:00:50 +10:00
return opts
2021-09-05 20:54:43 +10:00
}