prism collism

This commit is contained in:
Josh Deprez 2021-09-08 17:08:21 +10:00
parent 67a8f03cbf
commit 94915072f0
5 changed files with 143 additions and 4 deletions

View file

@ -93,3 +93,11 @@ func (b Box) XY() image.Rectangle {
Max: b.Max.XY(),
}
}
// XZ returns the image.Rectangle representing the box if we forgot about Y.
func (b Box) XZ() image.Rectangle {
return image.Rectangle{
Min: b.Min.XZ(),
Max: b.Max.XZ(),
}
}

View file

@ -1,6 +1,8 @@
package engine
import "image"
import (
"image"
)
// ID implements Identifier directly (as a string value).
type ID string
@ -65,3 +67,97 @@ func cfloat(p image.Point) (x, y float64) {
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 {
// There's a good chance a vertex from one thing is inside the other...
// Check if any vertex of the polygon is inside the rect.
for _, p := range polygon {
if p.In(rect) {
return true
}
}
// Reduce Max to the inclusive bound.
rect.Max = rect.Max.Sub(image.Pt(1, 1))
// Check if any vertex of the rect is inside the polygon.
if polygonContains(polygon, rect.Min) {
return true
}
if polygonContains(polygon, rect.Max) {
return true
}
if polygonContains(polygon, image.Pt(rect.Min.X, rect.Max.Y)) {
return true
}
if polygonContains(polygon, image.Pt(rect.Max.X, rect.Min.Y)) {
return true
}
// Only remaining cases involve line intersection between the rect and poly.
// Since rect is an axis-aligned rectangle, we only need vertical and
// horizontal line intersection tests.
// Walk each edge of polygon. Only a point and the ∆x, ∆y are needed.
for i, p := range polygon {
q := polygon[(i+1)%len(polygon)]
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 (rect.Min.X >= p.X || rect.Min.X >= q.X) && (rect.Min.X <= p.X || rect.Min.X <= q.X) {
if t := (rect.Min.X - p.X) * d.Y; min <= t && t <= max {
return true
}
}
// Test right side of rect
if (rect.Max.X >= p.X || rect.Max.X >= q.X) && (rect.Max.X <= p.X || rect.Max.X <= q.X) {
if t := (rect.Max.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 (rect.Min.Y >= p.Y && rect.Min.Y >= q.Y) && (rect.Min.Y <= p.Y && rect.Min.Y <= q.Y) {
if t := (rect.Min.Y - p.Y) * d.X; min <= t && t <= max {
return true
}
}
// Test bottom side of rect
if (rect.Max.Y >= p.Y && rect.Max.Y >= q.Y) && (rect.Max.Y <= p.Y && rect.Max.Y <= q.Y) {
if t := (rect.Max.Y - p.Y) * d.X; min <= t && t <= max {
return true
}
}
}
}
return false
}

View file

@ -23,7 +23,12 @@ func (p Int3) String() string {
// XY applies the Z-forgetting projection. (It returns just X and Y.)
func (p Int3) XY() image.Point {
return image.Point{p.X, p.Y}
return image.Point{X: p.X, Y: p.Y}
}
// XZ applies the Y-forgetting projection. (It returns just X and Z (as Y).)
func (p Int3) XZ() image.Point {
return image.Point{X: p.X, Y: p.Z}
}
// Add performs vector addition.

View file

@ -40,6 +40,7 @@ type PrismMap struct {
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
Sheet Sheet
game *Game
@ -50,6 +51,7 @@ func (m *PrismMap) CollidesWith(b Box) bool {
if m.Ersatz {
return false
}
// To find the prisms need to test, we need to invert PosToWorld.
// Step 1: subtract whatever the translation component of PosToWorld is,
@ -80,11 +82,31 @@ func (m *PrismMap) CollidesWith(b Box) bool {
if !b.Overlaps(cb) {
continue
}
// TODO: take into account the prism shape
return true
// Take into account the prism shape
r := b.XZ().Sub(wp.XZ())
if polygonRectOverlap(m.PrismTop, r) {
return true
}
}
}
}
/*
// 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())
if polygonRectOverlap(m.PrismTop, r) {
return true
}
}
*/
return false
}

View file

@ -121,6 +121,14 @@ func level1() *engine.Scene {
2: [4]int{8, 0, 16, 0},
},
PrismSize: engine.Int3{X: 32, Y: 16, Z: 16},
PrismTop: []image.Point{
{X: 8, Y: 0},
{X: 0, Y: 8},
{X: 8, Y: 16},
{X: 23, Y: 16},
{X: 31, Y: 8},
{X: 23, Y: 0},
},
Sheet: engine.Sheet{
CellSize: image.Pt(32, 32),
Src: engine.ImageRef{Path: "assets/hexprism32.png"},