refacteur
This commit is contained in:
parent
dccd13b623
commit
6686c24cd6
21 changed files with 216 additions and 166 deletions
|
@ -2,6 +2,8 @@ package engine
|
|||
|
||||
import (
|
||||
"encoding/gob"
|
||||
|
||||
"drjosh.dev/gurgle/geom"
|
||||
)
|
||||
|
||||
// Ensure Actor satisfies interfaces.
|
||||
|
@ -16,17 +18,17 @@ func init() {
|
|||
|
||||
// Actor handles basic movement.
|
||||
type Actor struct {
|
||||
CollisionDomain string // id of component to look for colliders inside of
|
||||
Pos, Size Int3 // in voxels; multiply by game.VoxelScale for regular Euclidean space
|
||||
CollisionDomain string // id of component to look for colliders inside of
|
||||
Pos, Size geom.Int3 // in voxels; multiply by game.VoxelScale for regular Euclidean space
|
||||
|
||||
rem Float3
|
||||
rem geom.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)}
|
||||
func (a *Actor) CollidesAt(p geom.Int3) bool {
|
||||
bounds := geom.Box{Min: p, Max: p.Add(a.Size)}
|
||||
for c := range a.game.Query(a.CollisionDomain, ColliderType) {
|
||||
if c.(Collider).CollidesWith(bounds) {
|
||||
return true
|
||||
|
@ -46,7 +48,7 @@ func (a *Actor) MoveX(x float64, onCollide func()) {
|
|||
return
|
||||
}
|
||||
a.rem.X -= float64(move)
|
||||
sign := sign(move)
|
||||
sign := geom.Sign(move)
|
||||
for move != 0 {
|
||||
a.Pos.X += sign
|
||||
move -= sign
|
||||
|
@ -70,7 +72,7 @@ func (a *Actor) MoveY(y float64, onCollide func()) {
|
|||
return
|
||||
}
|
||||
a.rem.Y -= float64(move)
|
||||
sign := sign(move)
|
||||
sign := geom.Sign(move)
|
||||
for move != 0 {
|
||||
a.Pos.Y += sign
|
||||
move -= sign
|
||||
|
@ -94,7 +96,7 @@ func (a *Actor) MoveZ(z float64, onCollide func()) {
|
|||
return
|
||||
}
|
||||
a.rem.Z -= float64(move)
|
||||
sign := sign(move)
|
||||
sign := geom.Sign(move)
|
||||
for move != 0 {
|
||||
a.Pos.Z += sign
|
||||
move -= sign
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/gob"
|
||||
"image"
|
||||
|
||||
"drjosh.dev/gurgle/geom"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
|
@ -37,6 +38,6 @@ func (b *Billboard) Draw(screen *ebiten.Image, opts *ebiten.DrawImageOptions) {
|
|||
func (b *Billboard) Scan() []interface{} { return []interface{}{&b.Src} }
|
||||
|
||||
func (b *Billboard) Transform() (opts ebiten.DrawImageOptions) {
|
||||
opts.GeoM.Translate(cfloat(b.Pos))
|
||||
opts.GeoM.Translate(geom.CFloat(b.Pos))
|
||||
return opts
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/gob"
|
||||
"image"
|
||||
|
||||
"drjosh.dev/gurgle/geom"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
|
@ -36,7 +37,7 @@ type Camera struct {
|
|||
|
||||
// PointAt points the camera at a particular centre point and zoom, but adjusts
|
||||
// for the bounds of the child component (if available).
|
||||
func (c *Camera) PointAt(centre Int3, zoom float64) {
|
||||
func (c *Camera) PointAt(centre geom.Int3, zoom float64) {
|
||||
// Special sauce: if Child has a BoundingRect, make some adjustments
|
||||
bnd, ok := c.Child.(Bounder)
|
||||
if !ok {
|
||||
|
@ -60,7 +61,7 @@ func (c *Camera) PointAt(centre Int3, zoom float64) {
|
|||
|
||||
// If the configured centre puts the camera out of bounds, move it.
|
||||
// Camera frame currently Rectangle{ centre ± (screen/(2*zoom)) }.
|
||||
sw2, sh2 := cfloat(c.game.ScreenSize.Div(2))
|
||||
sw2, sh2 := geom.CFloat(c.game.ScreenSize.Div(2))
|
||||
swz, shz := int(sw2/zoom), int(sh2/zoom)
|
||||
cent := c.game.Projection.Project(centre)
|
||||
if cent.X-swz < br.Min.X {
|
||||
|
@ -89,9 +90,9 @@ func (c *Camera) Scan() []interface{} { return []interface{}{c.Child} }
|
|||
|
||||
// Transform returns the camera transform.
|
||||
func (c *Camera) Transform() (opts ebiten.DrawImageOptions) {
|
||||
opts.GeoM.Translate(cfloat(c.Centre.Mul(-1)))
|
||||
opts.GeoM.Translate(geom.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)))
|
||||
opts.GeoM.Translate(geom.CFloat(c.game.ScreenSize.Div(2)))
|
||||
return opts
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"drjosh.dev/gurgle/geom"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
|
@ -41,8 +42,8 @@ type Game struct {
|
|||
Hidden
|
||||
ScreenSize image.Point
|
||||
Root interface{} // typically a *Scene or SceneRef though
|
||||
Projection IntProjection
|
||||
VoxelScale Float3
|
||||
Projection geom.IntProjection
|
||||
VoxelScale geom.Float3
|
||||
|
||||
dbmu sync.RWMutex
|
||||
byID map[string]Identifier // Named components by ID
|
||||
|
@ -266,8 +267,8 @@ 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}
|
||||
if g.VoxelScale == (geom.Float3{}) {
|
||||
g.VoxelScale = geom.Float3{1, 1, 1}
|
||||
}
|
||||
|
||||
// Load all the Loaders.
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"io/fs"
|
||||
"reflect"
|
||||
|
||||
"drjosh.dev/gurgle/geom"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
|
@ -49,7 +50,7 @@ type Bounder interface {
|
|||
|
||||
// Collider components have tangible form.
|
||||
type Collider interface {
|
||||
CollidesWith(Box) bool
|
||||
CollidesWith(geom.Box) bool
|
||||
}
|
||||
|
||||
// Disabler components can be disabled.
|
||||
|
|
|
@ -45,102 +45,3 @@ type ZOrder int
|
|||
|
||||
// DrawOrder returns z as a int with 0 bias.
|
||||
func (z ZOrder) DrawOrder() (int, int) { return int(z), 0 }
|
||||
|
||||
// ---------- Some helpers for image.Point ----------
|
||||
|
||||
// cmul performs componentwise multiplication of two image.Points.
|
||||
func cmul(p, q image.Point) image.Point {
|
||||
return image.Point{p.X * q.X, p.Y * q.Y}
|
||||
}
|
||||
|
||||
// cdiv performs componentwise division of two image.Points.
|
||||
func cdiv(p, q image.Point) image.Point {
|
||||
return image.Point{p.X / q.X, p.Y / q.Y}
|
||||
}
|
||||
|
||||
// cfloat returns the components of an image.Point as two floats.
|
||||
func cfloat(p image.Point) (x, y float64) {
|
||||
return float64(p.X), float64(p.Y)
|
||||
}
|
||||
|
||||
// dot returns the dot product of two image.Points.
|
||||
func dot(p, q image.Point) int {
|
||||
return p.X*q.X + p.Y*q.Y
|
||||
}
|
||||
|
||||
// polygonContains reports if a polygon contains a point
|
||||
func polygonContains(polygon []image.Point, p image.Point) bool {
|
||||
for i, p1 := range polygon {
|
||||
p2 := polygon[(i+1)%len(polygon)]
|
||||
// ∆(p p1 p2) should have positive signed area
|
||||
p1, p2 = p1.Sub(p), p2.Sub(p)
|
||||
if p2.X*p1.Y-p1.X*p2.Y < 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// polygonRectOverlap reports if a polygon overlaps a rectangle.
|
||||
func polygonRectOverlap(polygon []image.Point, rect image.Rectangle) bool {
|
||||
if polygon[0].In(rect) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if any vertex of the rect is inside the polygon.
|
||||
if polygonContains(polygon, rect.Min) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Reduced Max (to the inclusive bound).
|
||||
rmax := rect.Max.Sub(image.Pt(1, 1))
|
||||
|
||||
// Only remaining cases involve line intersection between the rect and poly
|
||||
// having eliminated the possibility that one is entirely within another.
|
||||
|
||||
// Since rect is an axis-aligned rectangle, we only need vertical and
|
||||
// horizontal line intersection tests.
|
||||
// Walk each edge of polygon.
|
||||
for i, p := range polygon {
|
||||
q := polygon[(i+1)%len(polygon)]
|
||||
// Pretend the edge is a rectangle. Exclude those that don't overlap.
|
||||
if !rect.Overlaps(image.Rectangle{p, q}.Canon()) {
|
||||
continue
|
||||
}
|
||||
|
||||
d := q.Sub(p)
|
||||
// If the polygon edge is not vertical, test left and right sides
|
||||
if d.X != 0 {
|
||||
if d.X < 0 {
|
||||
d = d.Mul(-1)
|
||||
}
|
||||
min := (rect.Min.Y - p.Y) * d.X
|
||||
max := (rect.Max.Y - p.Y) * d.X
|
||||
// Test left side of rect
|
||||
if t := (rect.Min.X - p.X) * d.Y; min <= t && t < max {
|
||||
return true
|
||||
}
|
||||
// Test right side of rect
|
||||
if t := (rmax.X - p.X) * d.Y; min <= t && t < max {
|
||||
return true
|
||||
}
|
||||
}
|
||||
// If the polygon edge is not horizontal, test the top and bottom sides
|
||||
if d.Y != 0 {
|
||||
if d.Y < 0 {
|
||||
d = d.Mul(-1)
|
||||
}
|
||||
min := (rect.Min.X - p.X) * d.Y
|
||||
max := (rect.Max.X - p.X) * d.Y
|
||||
// Test top side of rect
|
||||
if t := (rect.Min.Y - p.Y) * d.X; min <= t && t < max {
|
||||
return true
|
||||
}
|
||||
// Test bottom side of rect
|
||||
if t := (rmax.Y - p.Y) * d.X; min <= t && t < max {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/gob"
|
||||
"fmt"
|
||||
|
||||
"drjosh.dev/gurgle/geom"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
|
@ -42,7 +43,7 @@ func (p *Parallax) Scan() []interface{} { return []interface{}{p.Child} }
|
|||
|
||||
// Transform returns a GeoM translation of Factor * camera.Centre.
|
||||
func (p *Parallax) Transform() (opts ebiten.DrawImageOptions) {
|
||||
x, y := cfloat(p.camera.Centre)
|
||||
x, y := geom.CFloat(p.camera.Centre)
|
||||
opts.GeoM.Translate(x*p.Factor, y*p.Factor)
|
||||
return opts
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"image"
|
||||
|
||||
"drjosh.dev/gurgle/geom"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
|
@ -35,19 +36,19 @@ type PrismMap struct {
|
|||
Disabled
|
||||
Hidden
|
||||
Ersatz bool
|
||||
Map map[Int3]*Prism // pos -> prism
|
||||
DrawOrderBias image.Point // dot with pos.XY() = bias value
|
||||
DrawOffset image.Point // offset applies to whole map
|
||||
PosToWorld IntMatrix3x4 // p.pos -> world voxelspace
|
||||
PrismSize Int3 // in world voxelspace units
|
||||
PrismTop []image.Point // polygon vertices anticlockwise, Y means Z
|
||||
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
|
||||
Sheet Sheet
|
||||
|
||||
game *Game
|
||||
pwinverse RatMatrix3
|
||||
pwinverse geom.RatMatrix3
|
||||
}
|
||||
|
||||
func (m *PrismMap) CollidesWith(b Box) bool {
|
||||
func (m *PrismMap) CollidesWith(b geom.Box) bool {
|
||||
if m.Ersatz {
|
||||
return false
|
||||
}
|
||||
|
@ -65,10 +66,10 @@ func (m *PrismMap) CollidesWith(b Box) bool {
|
|||
rb = rb.Canon() // inverse might flip the corners around...
|
||||
|
||||
// Check neighboring prisms too because there's a fencepost somewhere here
|
||||
rb.Min = rb.Min.Sub(Int3{1, 1, 1})
|
||||
rb.Max = rb.Max.Add(Int3{1, 1, 1})
|
||||
rb.Min = rb.Min.Sub(geom.Int3{1, 1, 1})
|
||||
rb.Max = rb.Max.Add(geom.Int3{1, 1, 1})
|
||||
|
||||
var pp Int3
|
||||
var pp geom.Int3
|
||||
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++ {
|
||||
|
@ -78,13 +79,13 @@ func (m *PrismMap) CollidesWith(b Box) bool {
|
|||
}
|
||||
// Map it back to worldspace to get a bounding box for the prism
|
||||
wp := m.PosToWorld.Apply(pp)
|
||||
cb := Box{Min: wp, Max: wp.Add(m.PrismSize)}
|
||||
cb := geom.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())
|
||||
if polygonRectOverlap(m.PrismTop, r) {
|
||||
if geom.PolygonRectOverlap(m.PrismTop, r) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -102,7 +103,7 @@ func (m *PrismMap) CollidesWith(b Box) bool {
|
|||
}
|
||||
// Take into account the prism shape
|
||||
r := b.XZ().Sub(wp.XZ())
|
||||
if polygonRectOverlap(m.PrismTop, r) {
|
||||
if geom.PolygonRectOverlap(m.PrismTop, r) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
@ -134,14 +135,14 @@ func (m *PrismMap) Scan() []interface{} {
|
|||
}
|
||||
|
||||
func (m *PrismMap) Transform() (opts ebiten.DrawImageOptions) {
|
||||
opts.GeoM.Translate(cfloat(m.DrawOffset))
|
||||
opts.GeoM.Translate(geom.CFloat(m.DrawOffset))
|
||||
return opts
|
||||
}
|
||||
|
||||
type Prism struct {
|
||||
Cell int
|
||||
|
||||
pos Int3
|
||||
pos geom.Int3
|
||||
m *PrismMap
|
||||
}
|
||||
|
||||
|
@ -151,11 +152,11 @@ func (p *Prism) Draw(screen *ebiten.Image, opts *ebiten.DrawImageOptions) {
|
|||
|
||||
func (p *Prism) DrawOrder() (int, int) {
|
||||
return p.m.PosToWorld.Apply(p.pos).Z,
|
||||
dot(p.pos.XY(), p.m.DrawOrderBias)
|
||||
geom.Dot(p.pos.XY(), p.m.DrawOrderBias)
|
||||
}
|
||||
|
||||
func (p *Prism) Transform() (opts ebiten.DrawImageOptions) {
|
||||
opts.GeoM.Translate(cfloat(
|
||||
opts.GeoM.Translate(geom.CFloat(
|
||||
p.m.game.Projection.Project(p.m.PosToWorld.Apply(p.pos)),
|
||||
))
|
||||
return opts
|
||||
|
|
|
@ -3,6 +3,7 @@ package engine
|
|||
import (
|
||||
"image"
|
||||
|
||||
"drjosh.dev/gurgle/geom"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
|
@ -50,7 +51,7 @@ func (s *Sheet) Scan() []interface{} { return []interface{}{&s.Src} }
|
|||
|
||||
// SubImage returns an *ebiten.Image corresponding to the given cell index.
|
||||
func (s *Sheet) SubImage(i int) *ebiten.Image {
|
||||
p := cmul(image.Pt(i%s.w, i/s.w), s.CellSize)
|
||||
p := geom.CMul(image.Pt(i%s.w, i/s.w), s.CellSize)
|
||||
r := image.Rectangle{p, p.Add(s.CellSize)}
|
||||
return s.Src.Image().SubImage(r).(*ebiten.Image)
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@ package engine
|
|||
|
||||
import (
|
||||
"encoding/gob"
|
||||
|
||||
"drjosh.dev/gurgle/geom"
|
||||
)
|
||||
|
||||
var _ Collider = SolidRect{}
|
||||
|
@ -12,9 +14,9 @@ func init() {
|
|||
|
||||
type SolidRect struct {
|
||||
ID
|
||||
Box
|
||||
geom.Box
|
||||
}
|
||||
|
||||
func (s SolidRect) CollidesWith(r Box) bool {
|
||||
func (s SolidRect) CollidesWith(r geom.Box) bool {
|
||||
return s.Box.Overlaps(r)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/gob"
|
||||
"image"
|
||||
|
||||
"drjosh.dev/gurgle/geom"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
|
@ -58,7 +59,7 @@ func (s *Sprite) SetAnim(a *Anim) {
|
|||
|
||||
// Transform returns a translation by the DrawOffset and Actor.Pos projected
|
||||
func (s *Sprite) Transform() (opts ebiten.DrawImageOptions) {
|
||||
opts.GeoM.Translate(cfloat(
|
||||
opts.GeoM.Translate(geom.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...
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"image"
|
||||
"io/fs"
|
||||
|
||||
"drjosh.dev/gurgle/geom"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
|
@ -47,15 +48,15 @@ type Tilemap struct {
|
|||
}
|
||||
|
||||
// CollidesWith implements Collider.
|
||||
func (t *Tilemap) CollidesWith(b Box) bool {
|
||||
func (t *Tilemap) CollidesWith(b geom.Box) bool {
|
||||
if t.Ersatz {
|
||||
return false
|
||||
}
|
||||
|
||||
// Probe the map at all tilespace coordinates overlapping the rect.
|
||||
r := b.XY().Sub(t.Offset) // TODO: pretend tilemap is a plane in 3D?
|
||||
min := cdiv(r.Min, t.Sheet.CellSize)
|
||||
max := cdiv(r.Max.Sub(image.Pt(1, 1)), t.Sheet.CellSize) // NB: fencepost
|
||||
min := geom.CDiv(r.Min, t.Sheet.CellSize)
|
||||
max := geom.CDiv(r.Max.Sub(image.Pt(1, 1)), t.Sheet.CellSize) // NB: fencepost
|
||||
|
||||
for j := min.Y; j <= max.Y; j++ {
|
||||
for i := min.X; i <= max.X; i++ {
|
||||
|
@ -74,10 +75,10 @@ func (t *Tilemap) Draw(screen *ebiten.Image, opts *ebiten.DrawImageOptions) {
|
|||
if tile == nil {
|
||||
continue
|
||||
}
|
||||
var geom ebiten.GeoM
|
||||
geom.Translate(cfloat(cmul(p, t.Sheet.CellSize)))
|
||||
geom.Concat(og)
|
||||
opts.GeoM = geom
|
||||
var mat ebiten.GeoM
|
||||
mat.Translate(geom.CFloat(geom.CMul(p, t.Sheet.CellSize)))
|
||||
mat.Concat(og)
|
||||
opts.GeoM = mat
|
||||
|
||||
src := t.Sheet.SubImage(tile.Cell())
|
||||
screen.DrawImage(src, opts)
|
||||
|
@ -111,24 +112,24 @@ func (t *Tilemap) Scan() []interface{} {
|
|||
|
||||
// Transform returns a translation by t.Offset.
|
||||
func (t *Tilemap) Transform() (opts ebiten.DrawImageOptions) {
|
||||
opts.GeoM.Translate(cfloat(t.Offset))
|
||||
opts.GeoM.Translate(geom.CFloat(t.Offset))
|
||||
return opts
|
||||
}
|
||||
|
||||
// TileAt returns the tile present at the given world coordinate.
|
||||
func (t *Tilemap) TileAt(wc image.Point) Tile {
|
||||
return t.Map[cdiv(wc.Sub(t.Offset), t.Sheet.CellSize)]
|
||||
return t.Map[geom.CDiv(wc.Sub(t.Offset), t.Sheet.CellSize)]
|
||||
}
|
||||
|
||||
// SetTileAt sets the tile at the given world coordinate.
|
||||
func (t *Tilemap) SetTileAt(wc image.Point, tile Tile) {
|
||||
t.Map[cdiv(wc.Sub(t.Offset), t.Sheet.CellSize)] = tile
|
||||
t.Map[geom.CDiv(wc.Sub(t.Offset), t.Sheet.CellSize)] = tile
|
||||
}
|
||||
|
||||
// TileBounds returns a rectangle describing the tile boundary for the tile
|
||||
// at the given world coordinate.
|
||||
func (t *Tilemap) TileBounds(wc image.Point) image.Rectangle {
|
||||
p := cmul(cdiv(wc.Sub(t.Offset), t.Sheet.CellSize), t.Sheet.CellSize).Add(t.Offset)
|
||||
p := geom.CMul(geom.CDiv(wc.Sub(t.Offset), t.Sheet.CellSize), t.Sheet.CellSize).Add(t.Offset)
|
||||
return image.Rectangle{p, p.Add(t.Sheet.CellSize)}
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ package engine
|
|||
import (
|
||||
"image"
|
||||
|
||||
"drjosh.dev/gurgle/geom"
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
)
|
||||
|
||||
|
@ -39,15 +40,15 @@ type Wall struct {
|
|||
}
|
||||
|
||||
// CollidesWith implements a tilerange collosion check, similar to Tilemap.
|
||||
func (w *Wall) CollidesWith(b Box) bool {
|
||||
func (w *Wall) CollidesWith(b geom.Box) bool {
|
||||
if w.Ersatz {
|
||||
return false
|
||||
}
|
||||
|
||||
// Probe the map at all tilespace coordinates overlapping the rect.
|
||||
r := b.XY().Sub(w.Offset)
|
||||
min := cdiv(r.Min, w.UnitSize)
|
||||
max := cdiv(r.Max.Sub(image.Pt(1, 1)), w.UnitSize) // NB: fencepost
|
||||
min := geom.CDiv(r.Min, w.UnitSize)
|
||||
max := geom.CDiv(r.Max.Sub(image.Pt(1, 1)), w.UnitSize) // NB: fencepost
|
||||
|
||||
for j := min.Y; j <= max.Y; j++ {
|
||||
for i := min.X; i <= max.X; i++ {
|
||||
|
@ -81,7 +82,7 @@ func (w *Wall) Prepare(*Game) error {
|
|||
|
||||
// Transform returns a GeoM translation by Offset.
|
||||
func (w *Wall) Transform() (opts ebiten.DrawImageOptions) {
|
||||
opts.GeoM.Translate(cfloat(w.Offset))
|
||||
opts.GeoM.Translate(geom.CFloat(w.Offset))
|
||||
return opts
|
||||
}
|
||||
|
||||
|
@ -106,8 +107,8 @@ func (u *WallUnit) Draw(screen *ebiten.Image, opts *ebiten.DrawImageOptions) {
|
|||
func (u *WallUnit) Scan() []interface{} { return []interface{}{u.Tile} }
|
||||
|
||||
func (u *WallUnit) Transform() (opts ebiten.DrawImageOptions) {
|
||||
opts.GeoM.Translate(cfloat(
|
||||
cmul(u.pos, u.wall.UnitSize).Add(u.wall.UnitOffset),
|
||||
opts.GeoM.Translate(geom.CFloat(
|
||||
geom.CMul(u.pos, u.wall.UnitSize).Add(u.wall.UnitOffset),
|
||||
))
|
||||
return opts
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package engine
|
||||
package geom
|
||||
|
||||
import "image"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package engine
|
||||
package geom
|
||||
|
||||
import (
|
||||
"errors"
|
25
geom/misc.go
Normal file
25
geom/misc.go
Normal file
|
@ -0,0 +1,25 @@
|
|||
package geom
|
||||
|
||||
import "image"
|
||||
|
||||
// ---------- Some helpers for image.Point ----------
|
||||
|
||||
// CMul performs componentwise multiplication of two image.Points.
|
||||
func CMul(p, q image.Point) image.Point {
|
||||
return image.Point{p.X * q.X, p.Y * q.Y}
|
||||
}
|
||||
|
||||
// CDiv performs componentwise division of two image.Points.
|
||||
func CDiv(p, q image.Point) image.Point {
|
||||
return image.Point{p.X / q.X, p.Y / q.Y}
|
||||
}
|
||||
|
||||
// CFloat returns the components of an image.Point as two floats.
|
||||
func CFloat(p image.Point) (x, y float64) {
|
||||
return float64(p.X), float64(p.Y)
|
||||
}
|
||||
|
||||
// Dot returns the Dot product of two image.Points.
|
||||
func Dot(p, q image.Point) int {
|
||||
return p.X*q.X + p.Y*q.Y
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package engine
|
||||
package geom
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -73,7 +73,7 @@ func (p Int3) Coord() (x, y, z int) {
|
|||
|
||||
// Sign returns a sign vector.
|
||||
func (p Int3) Sign() Int3 {
|
||||
return Int3{sign(p.X), sign(p.Y), sign(p.Z)}
|
||||
return Int3{Sign(p.X), Sign(p.Y), Sign(p.Z)}
|
||||
}
|
||||
|
||||
// Dot returns the dot product of the two vectors.
|
||||
|
@ -81,7 +81,8 @@ func (p Int3) Dot(q Int3) int {
|
|||
return p.X*q.X + p.Y*q.Y + p.Z*q.Z
|
||||
}
|
||||
|
||||
func sign(m int) int {
|
||||
// Sign returns the sign of the int (-1, 0, or 1).
|
||||
func Sign(m int) int {
|
||||
if m == 0 {
|
||||
return 0
|
||||
}
|
||||
|
@ -91,7 +92,8 @@ func sign(m int) int {
|
|||
return 1
|
||||
}
|
||||
|
||||
func signf(m float64) float64 {
|
||||
// FSign returns the sign of the float64 (-1, 0, or 1).
|
||||
func FSign(m float64) float64 {
|
||||
if m == 0 {
|
||||
return 0
|
||||
}
|
||||
|
@ -153,7 +155,7 @@ func (p Float3) Coord() (x, y, z float64) {
|
|||
|
||||
// Sign returns a sign vector.
|
||||
func (p Float3) Sign() Float3 {
|
||||
return Float3{signf(p.X), signf(p.Y), signf(p.Z)}
|
||||
return Float3{FSign(p.X), FSign(p.Y), FSign(p.Z)}
|
||||
}
|
||||
|
||||
// Dot returns the dot product of the two vectors.
|
80
geom/polygon.go
Normal file
80
geom/polygon.go
Normal file
|
@ -0,0 +1,80 @@
|
|||
package geom
|
||||
|
||||
import "image"
|
||||
|
||||
// PolygonContains reports if a polygon contains a point
|
||||
func PolygonContains(polygon []image.Point, p image.Point) bool {
|
||||
for i, p1 := range polygon {
|
||||
p2 := polygon[(i+1)%len(polygon)]
|
||||
// ∆(p p1 p2) should have positive signed area
|
||||
p1, p2 = p1.Sub(p), p2.Sub(p)
|
||||
if p2.X*p1.Y-p1.X*p2.Y < 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// PolygonRectOverlap reports if a polygon overlaps a rectangle.
|
||||
func PolygonRectOverlap(polygon []image.Point, rect image.Rectangle) bool {
|
||||
if polygon[0].In(rect) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if any vertex of the rect is inside the polygon.
|
||||
if PolygonContains(polygon, rect.Min) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Reduced Max (to the inclusive bound).
|
||||
rmax := rect.Max.Sub(image.Pt(1, 1))
|
||||
|
||||
// Only remaining cases involve line intersection between the rect and poly
|
||||
// having eliminated the possibility that one is entirely within another.
|
||||
|
||||
// Since rect is an axis-aligned rectangle, we only need vertical and
|
||||
// horizontal line intersection tests.
|
||||
// Walk each edge of polygon.
|
||||
for i, p := range polygon {
|
||||
q := polygon[(i+1)%len(polygon)]
|
||||
// Pretend the edge is a rectangle. Exclude those that don't overlap.
|
||||
if !rect.Overlaps(image.Rectangle{p, q}.Canon()) {
|
||||
continue
|
||||
}
|
||||
|
||||
d := q.Sub(p)
|
||||
// If the polygon edge is not vertical, test left and right sides
|
||||
if d.X != 0 {
|
||||
if d.X < 0 {
|
||||
d = d.Mul(-1)
|
||||
}
|
||||
min := (rect.Min.Y - p.Y) * d.X
|
||||
max := (rect.Max.Y - p.Y) * d.X
|
||||
// Test left side of rect
|
||||
if t := (rect.Min.X - p.X) * d.Y; min <= t && t < max {
|
||||
return true
|
||||
}
|
||||
// Test right side of rect
|
||||
if t := (rmax.X - p.X) * d.Y; min <= t && t < max {
|
||||
return true
|
||||
}
|
||||
}
|
||||
// If the polygon edge is not horizontal, test the top and bottom sides
|
||||
if d.Y != 0 {
|
||||
if d.Y < 0 {
|
||||
d = d.Mul(-1)
|
||||
}
|
||||
min := (rect.Min.X - p.X) * d.Y
|
||||
max := (rect.Max.X - p.X) * d.Y
|
||||
// Test top side of rect
|
||||
if t := (rect.Min.Y - p.Y) * d.X; min <= t && t < max {
|
||||
return true
|
||||
}
|
||||
// Test bottom side of rect
|
||||
if t := (rmax.Y - p.Y) * d.X; min <= t && t < max {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
28
geom/polygon_test.go
Normal file
28
geom/polygon_test.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
package geom
|
||||
|
||||
import (
|
||||
"image"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPolygonContains(t *testing.T) {
|
||||
square5 := []image.Point{
|
||||
{X: 0, Y: 0},
|
||||
{X: 5, Y: 0},
|
||||
{X: 5, Y: 5},
|
||||
{X: 0, Y: 5},
|
||||
}
|
||||
tests := []struct {
|
||||
polygon []image.Point
|
||||
point image.Point
|
||||
want bool
|
||||
}{
|
||||
{square5, image.Pt(2, 3), true},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
if got, want := PolygonContains(test.polygon, test.point), test.want; got != want {
|
||||
t.Errorf("polygonContains(%v, %v) = %v, want %v", test.polygon, test.point, got, want)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package engine
|
||||
package geom
|
||||
|
||||
import "image"
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package engine
|
||||
package geom
|
||||
|
||||
import "strconv"
|
||||
|
Loading…
Reference in a new issue