collider now collides with boxes
This commit is contained in:
parent
82559faa66
commit
5ac31e1e6d
10 changed files with 172 additions and 164 deletions
|
@ -24,7 +24,7 @@ type Actor struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Actor) CollidesAt(p Point3) bool {
|
func (a *Actor) CollidesAt(p Point3) bool {
|
||||||
bounds := Box{Min: p, Max: p.Add(a.Size)}.XY() // TODO: 3D collision
|
bounds := Box{Min: p, Max: p.Add(a.Size)}
|
||||||
for c := range a.game.Query(a.CollisionDomain, ColliderType) {
|
for c := range a.game.Query(a.CollisionDomain, ColliderType) {
|
||||||
if c.(Collider).CollidesWith(bounds) {
|
if c.(Collider).CollidesWith(bounds) {
|
||||||
return true
|
return true
|
||||||
|
|
65
engine/box.go
Normal file
65
engine/box.go
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
package engine
|
||||||
|
|
||||||
|
import "image"
|
||||||
|
|
||||||
|
// Box describes an axis-aligned rectangular prism.
|
||||||
|
type Box struct {
|
||||||
|
Min, Max Point3
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of b like "(3,4,5)-(6,5,8)".
|
||||||
|
func (b Box) String() string {
|
||||||
|
return b.Min.String() + "-" + b.Max.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty reports whether the box contains no points.
|
||||||
|
func (b Box) Empty() bool {
|
||||||
|
return b.Min.X >= b.Max.X || b.Min.Y >= b.Max.Y || b.Min.Z >= b.Max.Z
|
||||||
|
}
|
||||||
|
|
||||||
|
// Eq reports whether b and c contain the same set of points. All empty boxes
|
||||||
|
// are considered equal.
|
||||||
|
func (b Box) Eq(c Box) bool {
|
||||||
|
return b == c || b.Empty() && c.Empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overlaps reports whether b and c have non-empty intersection.
|
||||||
|
func (b Box) Overlaps(c Box) bool {
|
||||||
|
return !b.Empty() && !c.Empty() &&
|
||||||
|
b.Min.X < c.Max.X && c.Min.X < b.Max.X &&
|
||||||
|
b.Min.Y < c.Max.Y && c.Min.Y < b.Max.Y &&
|
||||||
|
b.Min.Z < c.Max.Z && c.Min.Z < b.Max.Z
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns b's width, height, and depth.
|
||||||
|
func (b Box) Size() Point3 {
|
||||||
|
return b.Max.Sub(b.Min)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Back returns an image.Rectangle representing the back of the box, using
|
||||||
|
// the given projection π.
|
||||||
|
func (b Box) Back(π image.Point) image.Rectangle {
|
||||||
|
b.Max.Z = b.Min.Z
|
||||||
|
return image.Rectangle{
|
||||||
|
Min: b.Min.IsoProject(π),
|
||||||
|
Max: b.Max.IsoProject(π),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Front returns an image.Rectangle representing the front of the box, using
|
||||||
|
// the given projection π.
|
||||||
|
func (b Box) Front(π image.Point) image.Rectangle {
|
||||||
|
b.Min.Z = b.Max.Z
|
||||||
|
return image.Rectangle{
|
||||||
|
Min: b.Min.IsoProject(π),
|
||||||
|
Max: b.Max.IsoProject(π),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// XY returns the image.Rectangle representing the box if we forgot about Z.
|
||||||
|
func (b Box) XY() image.Rectangle {
|
||||||
|
return image.Rectangle{
|
||||||
|
Min: b.Min.XY(),
|
||||||
|
Max: b.Max.XY(),
|
||||||
|
}
|
||||||
|
}
|
|
@ -49,7 +49,7 @@ type Bounder interface {
|
||||||
|
|
||||||
// Collider components have tangible form.
|
// Collider components have tangible form.
|
||||||
type Collider interface {
|
type Collider interface {
|
||||||
CollidesWith(image.Rectangle) bool
|
CollidesWith(Box) bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disabler components can be disabled.
|
// Disabler components can be disabled.
|
||||||
|
|
150
engine/iso.go
150
engine/iso.go
|
@ -3,7 +3,6 @@ package engine
|
||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"image"
|
"image"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/hajimehoshi/ebiten/v2"
|
"github.com/hajimehoshi/ebiten/v2"
|
||||||
)
|
)
|
||||||
|
@ -32,152 +31,6 @@ func init() {
|
||||||
gob.Register(&IsoVoxelSide{})
|
gob.Register(&IsoVoxelSide{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Point3 is a an element of int^3.
|
|
||||||
type Point3 struct {
|
|
||||||
X, Y, Z int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pt3(x, y, z) is shorthand for Point3{x, y, z}.
|
|
||||||
func Pt3(x, y, z int) Point3 {
|
|
||||||
return Point3{x, y, z}
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a string representation of p like "(3,4,5)".
|
|
||||||
func (p Point3) String() string {
|
|
||||||
return "(" + strconv.Itoa(p.X) + "," + strconv.Itoa(p.Y) + "," + strconv.Itoa(p.Z) + ")"
|
|
||||||
}
|
|
||||||
|
|
||||||
// XY applies the Z-forgetting projection. (It returns just X and Y.)
|
|
||||||
func (p Point3) XY() image.Point {
|
|
||||||
return image.Point{p.X, p.Y}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add performs vector addition.
|
|
||||||
func (p Point3) Add(q Point3) Point3 {
|
|
||||||
return Point3{p.X + q.X, p.Y + q.Y, p.Z + q.Z}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sub performs vector subtraction.
|
|
||||||
func (p Point3) Sub(q Point3) Point3 {
|
|
||||||
return p.Add(q.Neg())
|
|
||||||
}
|
|
||||||
|
|
||||||
// CMul performs componentwise multiplication.
|
|
||||||
func (p Point3) CMul(q Point3) Point3 {
|
|
||||||
return Point3{p.X * q.X, p.Y * q.Y, p.Z * q.Z}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mul performs scalar multiplication.
|
|
||||||
func (p Point3) Mul(k int) Point3 {
|
|
||||||
return Point3{p.X * k, p.Y * k, p.Z * k}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CDiv performs componentwise division.
|
|
||||||
func (p Point3) CDiv(q Point3) Point3 {
|
|
||||||
return Point3{p.X / q.X, p.Y / q.Y, p.Z / q.Z}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Div performs scalar division by k.
|
|
||||||
func (p Point3) Div(k int) Point3 {
|
|
||||||
return Point3{p.X / k, p.Y / k, p.Z / k}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Neg returns the vector pointing in the opposite direction.
|
|
||||||
func (p Point3) Neg() Point3 {
|
|
||||||
return Point3{-p.X, -p.Y, -p.Z}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Coord returns the components of the vector.
|
|
||||||
func (p Point3) Coord() (x, y, z int) {
|
|
||||||
return p.X, p.Y, p.Z
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsoProject performs isometric 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 (p Point3) IsoProject(π image.Point) image.Point {
|
|
||||||
/*
|
|
||||||
I'm using the π character because I'm a maths wanker.
|
|
||||||
|
|
||||||
Dividing is used because there's little reason for an isometric
|
|
||||||
projection in a game to exaggerate the Z position.
|
|
||||||
|
|
||||||
Integers are used to preserve that "pixel perfect" calculation in case
|
|
||||||
you are making the next Celeste.
|
|
||||||
*/
|
|
||||||
q := image.Point{p.X, p.Y}
|
|
||||||
if π.X != 0 {
|
|
||||||
q.X += p.Z / π.X
|
|
||||||
}
|
|
||||||
if π.Y != 0 {
|
|
||||||
q.Y += p.Z / π.Y
|
|
||||||
}
|
|
||||||
return q
|
|
||||||
}
|
|
||||||
|
|
||||||
// Box describes an axis-aligned rectangular prism.
|
|
||||||
type Box struct {
|
|
||||||
Min, Max Point3
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a string representation of b like "(3,4,5)-(6,5,8)".
|
|
||||||
func (b Box) String() string {
|
|
||||||
return b.Min.String() + "-" + b.Max.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty reports whether the box contains no points.
|
|
||||||
func (b Box) Empty() bool {
|
|
||||||
return b.Min.X >= b.Max.X || b.Min.Y >= b.Max.Y || b.Min.Z >= b.Max.Z
|
|
||||||
}
|
|
||||||
|
|
||||||
// Eq reports whether b and c contain the same set of points. All empty boxes
|
|
||||||
// are considered equal.
|
|
||||||
func (b Box) Eq(c Box) bool {
|
|
||||||
return b == c || b.Empty() && c.Empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Overlaps reports whether b and c have non-empty intersection.
|
|
||||||
func (b Box) Overlaps(c Box) bool {
|
|
||||||
return !b.Empty() && !c.Empty() &&
|
|
||||||
b.Min.X < c.Max.X && c.Min.X < b.Max.X &&
|
|
||||||
b.Min.Y < c.Max.Y && c.Min.Y < b.Max.Y &&
|
|
||||||
b.Min.Z < c.Max.Z && c.Min.Z < b.Max.Z
|
|
||||||
}
|
|
||||||
|
|
||||||
// Size returns b's width, height, and depth.
|
|
||||||
func (b Box) Size() Point3 {
|
|
||||||
return b.Max.Sub(b.Min)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Back returns an image.Rectangle representing the back of the box, using
|
|
||||||
// the given projection π.
|
|
||||||
func (b Box) Back(π image.Point) image.Rectangle {
|
|
||||||
b.Max.Z = b.Min.Z
|
|
||||||
return image.Rectangle{
|
|
||||||
Min: b.Min.IsoProject(π),
|
|
||||||
Max: b.Max.IsoProject(π),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Front returns an image.Rectangle representing the front of the box, using
|
|
||||||
// the given projection π.
|
|
||||||
func (b Box) Front(π image.Point) image.Rectangle {
|
|
||||||
b.Min.Z = b.Max.Z
|
|
||||||
return image.Rectangle{
|
|
||||||
Min: b.Min.IsoProject(π),
|
|
||||||
Max: b.Max.IsoProject(π),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// XY returns the image.Rectangle representing the box if we forgot about Z.
|
|
||||||
func (b Box) XY() image.Rectangle {
|
|
||||||
return image.Rectangle{
|
|
||||||
Min: b.Min.XY(),
|
|
||||||
Max: b.Max.XY(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsoVoxmap implements a voxel map, painted using flat images in 2D.
|
// IsoVoxmap implements a voxel map, painted using flat images in 2D.
|
||||||
type IsoVoxmap struct {
|
type IsoVoxmap struct {
|
||||||
ID
|
ID
|
||||||
|
@ -267,7 +120,8 @@ func (v *IsoVoxelSide) DrawOrder() (int, int) {
|
||||||
if v.front {
|
if v.front {
|
||||||
z += v.vox.ivm.VoxSize.Z - 1
|
z += v.vox.ivm.VoxSize.Z - 1
|
||||||
}
|
}
|
||||||
return z, dot(v.vox.pos.XY(), v.vox.ivm.DrawOrderBias)
|
bias := dot(v.vox.pos.XY(), v.vox.ivm.DrawOrderBias)
|
||||||
|
return z, bias
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transform offsets the image by either OffsetBack or OffsetFront.
|
// Transform offsets the image by either OffsetBack or OffsetFront.
|
||||||
|
|
90
engine/point.go
Normal file
90
engine/point.go
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
package engine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Point3 is a an element of int^3.
|
||||||
|
type Point3 struct {
|
||||||
|
X, Y, Z int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pt3(x, y, z) is shorthand for Point3{x, y, z}.
|
||||||
|
func Pt3(x, y, z int) Point3 {
|
||||||
|
return Point3{x, y, z}
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a string representation of p like "(3,4,5)".
|
||||||
|
func (p Point3) String() string {
|
||||||
|
return "(" + strconv.Itoa(p.X) + "," + strconv.Itoa(p.Y) + "," + strconv.Itoa(p.Z) + ")"
|
||||||
|
}
|
||||||
|
|
||||||
|
// XY applies the Z-forgetting projection. (It returns just X and Y.)
|
||||||
|
func (p Point3) XY() image.Point {
|
||||||
|
return image.Point{p.X, p.Y}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add performs vector addition.
|
||||||
|
func (p Point3) Add(q Point3) Point3 {
|
||||||
|
return Point3{p.X + q.X, p.Y + q.Y, p.Z + q.Z}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sub performs vector subtraction.
|
||||||
|
func (p Point3) Sub(q Point3) Point3 {
|
||||||
|
return p.Add(q.Neg())
|
||||||
|
}
|
||||||
|
|
||||||
|
// CMul performs componentwise multiplication.
|
||||||
|
func (p Point3) CMul(q Point3) Point3 {
|
||||||
|
return Point3{p.X * q.X, p.Y * q.Y, p.Z * q.Z}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mul performs scalar multiplication.
|
||||||
|
func (p Point3) Mul(k int) Point3 {
|
||||||
|
return Point3{p.X * k, p.Y * k, p.Z * k}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CDiv performs componentwise division.
|
||||||
|
func (p Point3) CDiv(q Point3) Point3 {
|
||||||
|
return Point3{p.X / q.X, p.Y / q.Y, p.Z / q.Z}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Div performs scalar division by k.
|
||||||
|
func (p Point3) Div(k int) Point3 {
|
||||||
|
return Point3{p.X / k, p.Y / k, p.Z / k}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Neg returns the vector pointing in the opposite direction.
|
||||||
|
func (p Point3) Neg() Point3 {
|
||||||
|
return Point3{-p.X, -p.Y, -p.Z}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Coord returns the components of the vector.
|
||||||
|
func (p Point3) Coord() (x, y, z int) {
|
||||||
|
return p.X, p.Y, p.Z
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsoProject performs isometric 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 (p Point3) IsoProject(π image.Point) image.Point {
|
||||||
|
/*
|
||||||
|
I'm using the π character because I'm a maths wanker.
|
||||||
|
|
||||||
|
Dividing is used because there's little reason for an isometric
|
||||||
|
projection in a game to exaggerate the Z position.
|
||||||
|
|
||||||
|
Integers are used to preserve that "pixel perfect" calculation in case
|
||||||
|
you are making the next Celeste.
|
||||||
|
*/
|
||||||
|
q := image.Point{p.X, p.Y}
|
||||||
|
if π.X != 0 {
|
||||||
|
q.X += p.Z / π.X
|
||||||
|
}
|
||||||
|
if π.Y != 0 {
|
||||||
|
q.Y += p.Z / π.Y
|
||||||
|
}
|
||||||
|
return q
|
||||||
|
}
|
|
@ -2,7 +2,6 @@ package engine
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"image"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ Collider = SolidRect{}
|
var _ Collider = SolidRect{}
|
||||||
|
@ -13,9 +12,9 @@ func init() {
|
||||||
|
|
||||||
type SolidRect struct {
|
type SolidRect struct {
|
||||||
ID
|
ID
|
||||||
Bounds
|
Box
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s SolidRect) CollidesWith(r image.Rectangle) bool {
|
func (s SolidRect) CollidesWith(r Box) bool {
|
||||||
return s.BoundingRect().Overlaps(r)
|
return s.Box.Overlaps(r)
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,13 +47,13 @@ type Tilemap struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CollidesWith implements Collider.
|
// CollidesWith implements Collider.
|
||||||
func (t *Tilemap) CollidesWith(r image.Rectangle) bool {
|
func (t *Tilemap) CollidesWith(b Box) bool {
|
||||||
if t.Ersatz {
|
if t.Ersatz {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Probe the map at all tilespace coordinates overlapping the rect.
|
// Probe the map at all tilespace coordinates overlapping the rect.
|
||||||
r = r.Sub(t.Offset)
|
r := b.XY().Sub(t.Offset) // TODO: pretend tilemap is a plane in 3D?
|
||||||
min := cdiv(r.Min, t.Sheet.CellSize)
|
min := cdiv(r.Min, t.Sheet.CellSize)
|
||||||
max := cdiv(r.Max.Sub(image.Pt(1, 1)), t.Sheet.CellSize) // NB: fencepost
|
max := cdiv(r.Max.Sub(image.Pt(1, 1)), t.Sheet.CellSize) // NB: fencepost
|
||||||
|
|
||||||
|
|
|
@ -39,13 +39,13 @@ type Wall struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CollidesWith implements a tilerange collosion check, similar to Tilemap.
|
// CollidesWith implements a tilerange collosion check, similar to Tilemap.
|
||||||
func (w *Wall) CollidesWith(r image.Rectangle) bool {
|
func (w *Wall) CollidesWith(b Box) bool {
|
||||||
if w.Ersatz {
|
if w.Ersatz {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Probe the map at all tilespace coordinates overlapping the rect.
|
// Probe the map at all tilespace coordinates overlapping the rect.
|
||||||
r = r.Sub(w.Offset)
|
r := b.XY().Sub(w.Offset)
|
||||||
min := cdiv(r.Min, w.UnitSize)
|
min := cdiv(r.Min, w.UnitSize)
|
||||||
max := cdiv(r.Max.Sub(image.Pt(1, 1)), w.UnitSize) // NB: fencepost
|
max := cdiv(r.Max.Sub(image.Pt(1, 1)), w.UnitSize) // NB: fencepost
|
||||||
|
|
||||||
|
|
Binary file not shown.
6
main.go
6
main.go
|
@ -171,15 +171,15 @@ func writeLevel1() {
|
||||||
},
|
},
|
||||||
&engine.SolidRect{
|
&engine.SolidRect{
|
||||||
ID: "ceiling",
|
ID: "ceiling",
|
||||||
Bounds: engine.Bounds(image.Rect(0, -1, 320, 0)),
|
Box: engine.Box{Min: engine.Pt3(0, -1, 0), Max: engine.Pt3(320, 0, 100)},
|
||||||
},
|
},
|
||||||
&engine.SolidRect{
|
&engine.SolidRect{
|
||||||
ID: "left_wall",
|
ID: "left_wall",
|
||||||
Bounds: engine.Bounds(image.Rect(-1, 0, 0, 240)),
|
Box: engine.Box{Min: engine.Pt3(-1, 0, 0), Max: engine.Pt3(0, 240, 100)},
|
||||||
},
|
},
|
||||||
&engine.SolidRect{
|
&engine.SolidRect{
|
||||||
ID: "right_wall",
|
ID: "right_wall",
|
||||||
Bounds: engine.Bounds(image.Rect(320, 0, 321, 240)),
|
Box: engine.Box{Min: engine.Pt3(320, 0, 0), Max: engine.Pt3(321, 240, 100)},
|
||||||
},
|
},
|
||||||
&game.Awakeman{
|
&game.Awakeman{
|
||||||
CameraID: "game_camera",
|
CameraID: "game_camera",
|
||||||
|
|
Loading…
Reference in a new issue