Skip to content

Commit

Permalink
Rework spatial hash to support more than just rectangular shapes.
Browse files Browse the repository at this point in the history
  • Loading branch information
unitoftime committed Dec 2, 2024
1 parent a90faa6 commit 354d28c
Show file tree
Hide file tree
Showing 7 changed files with 520 additions and 75 deletions.
47 changes: 47 additions & 0 deletions glm/line.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package glm

type Line2 struct {
A, B Vec2
}

// Returns true if the two lines intersect
func (l1 Line2) Intersects(l2 Line2) bool {
// Adapted from: https://www.gorillasun.de/blog/an-algorithm-for-polygon-intersections/
x1 := l1.A.X
y1 := l1.A.Y
x2 := l1.B.X
y2 := l1.B.Y

x3 := l2.A.X
y3 := l2.A.Y
x4 := l2.B.X
y4 := l2.B.Y

// Ensure no line is 0 length
if ((x1 == x2 && y1 == y2) || (x3 == x4 && y3 == y4)) {
return false
}

den := ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1))

// Lines are parallel
if den == 0 {
return false
}

ua := ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / den
ub := ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / den

// is the intersection along the segments
if (ua < 0 || ua > 1 || ub < 0 || ub > 1) {
return false
}

return true

// // Return a object with the x and y coordinates of the intersection
// x := x1 + ua * (x2 - x1)
// y := y1 + ua * (y2 - y1)

// return {x, y}
}
24 changes: 19 additions & 5 deletions glm/math.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ var Mat4Ident Mat4 = Mat4{
0.0, 0.0, 0.0, 1.0,
}

func IM4() *Mat4 {
return &Mat4{
1.0, 0.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0,
}
}

func (m *Mat3) Translate(x, y float64) *Mat3 {
m[i3_2_0] = m[i3_2_0] + x
m[i3_2_1] = m[i3_2_1] + y
Expand Down Expand Up @@ -72,12 +81,17 @@ func (m *Mat4) GetTranslation() Vec3 {

// https://github.com/go-gl/mathgl/blob/v1.0.0/mgl32/transform.go#L159
func (m *Mat4) Rotate(angle float64, axis Vec3) *Mat4 {
// quat := mgl32.Mat4ToQuat(mgl32.Mat4(*m))
// return &retMat
// // quat := mgl32.Mat4ToQuat(mgl32.Mat4(*m))
// // return &retMat
// rotation := Mat4(mgl64.HomogRotate3D(angle, mgl64.Vec3{axis.X, axis.Y, axis.Z}))
// // retMat := Mat4(mgl32.Mat4(*m).)
// // return &retMat
// mNew := m.Mul(&rotation)
// *m = *mNew
// return m

rotation := Mat4(mgl64.HomogRotate3D(angle, mgl64.Vec3{axis.X, axis.Y, axis.Z}))
// retMat := Mat4(mgl32.Mat4(*m).)
// return &retMat
mNew := m.Mul(&rotation)
mNew := rotation.Mul(m)
*m = *mNew
return m
}
Expand Down
16 changes: 16 additions & 0 deletions glm/rect.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ func CR(radius float64) Rect {
}
}

// Returns the top left point
func (r Rect) TL() Vec2 {
return Vec2{r.Min.X, r.Max.Y}
}

// Returns the bottom right point
func (r Rect) BR() Vec2 {
return Vec2{r.Max.X, r.Min.Y}
}

// Returns a box that holds this rect. The Z axis is 0
func (r Rect) Box() Box {
return r.ToBox()
Expand Down Expand Up @@ -156,10 +166,16 @@ func (r Rect) Norm() Rect {
return R(x1, y1, x2, y2)
}

// Returns true if the point is inside (not on the edge of) the rect
func (r Rect) Contains(pos Vec2) bool {
return pos.X > r.Min.X && pos.X < r.Max.X && pos.Y > r.Min.Y && pos.Y < r.Max.Y
}

// Returns true if the point is inside or on the edge of the rect
func (r Rect) OverlapsPoint(pos Vec3) bool {
return pos.X >= r.Min.X && pos.X <= r.Max.X && pos.Y >= r.Min.Y && pos.Y <= r.Max.Y
}

// func (r Rect) Contains(x, y float64) bool {
// return x > r.Min.X && x < r.Max.X && y > r.Min.Y && y < r.Max.Y
// }
Expand Down
5 changes: 5 additions & 0 deletions glm/vec2.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,15 @@ func (v Vec2) Scaled(s float64) Vec2 {
return V2(s*v.X, s*v.Y)
}

// TODO: Same thing as mult
func (v Vec2) ScaledXY(s Vec2) Vec2 {
return Vec2{v.X * s.X, v.Y * s.Y}
}

func (v Vec2) Mult(s Vec2) Vec2 {
return Vec2{v.X * s.X, v.Y * s.Y}
}

func (v Vec2) Rotated(radians float64) Vec2 {
sin := math.Sin(radians)
cos := math.Cos(radians)
Expand Down
98 changes: 28 additions & 70 deletions spatial/hash.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,52 +7,6 @@ import (
"github.com/unitoftime/flow/glm"
)

// TODO: eventually use shapes
// type ShapeType uint8
// const (
// ShapeRect ShapeType = iota
// ShapeCircle
// )

// type Shape struct {
// Type ShapeType
// Bounds glm.Rect
// }
// func Rect(rect glm.Rect) Shape {
// return Shape{
// Type: ShapeRect,
// Bounds: rect,
// }
// }
// func Circle(rect glm.Rect) Shape {
// return Shape{
// Type: ShapeCircle,
// Bounds: rect,
// }
// }
// func (s Shape) Rect() glm.Rect {
// return s.Bounds
// }
// func (s Shape) Circle() glm.Circle {
// glm.NewCircle(s.Bounds.Center())
// }

// func (s Shape) Intersects(s2 Shape) bool {
// if s.Type == ShapeRect {
// if s2.Type == ShapeRect {
// return s.Rect().Intersects(s2.Bounds)
// } else if s2.Type == ShapeCircle {
// return s.Rect().IntersectCircle(s2.Circle())
// }
// } else if s.Type == ShapeCircle {
// if s2.Type == ShapeRect {
// return s.Bounds.Intersects(s2.Bounds)
// } else if s2.Type == ShapeCircle {
// return s.Circle().IntersectCircle(s2.Circle())
// }
// }
// }

type arrayMap[T any] struct {
topRight [][]*T
topLeft [][]*T
Expand Down Expand Up @@ -195,7 +149,7 @@ type Index struct {
}

type BucketItem[T comparable] struct {
shape glm.Rect
shape Shape
item T
}

Expand All @@ -208,7 +162,7 @@ func NewBucket[T comparable]() *Bucket[T] {
List: make([]BucketItem[T], 0),
}
}
func (b *Bucket[T]) Add(shape glm.Rect, val T) {
func (b *Bucket[T]) Add(shape Shape, val T) {
b.List = append(b.List, BucketItem[T]{
shape: shape,
item: val,
Expand All @@ -223,14 +177,14 @@ func (b *Bucket[T]) Remove(val T) {
func (b *Bucket[T]) Clear() {
b.List = b.List[:0]
}
func (b *Bucket[T]) Check(colSet *CollisionSet[T], shape glm.Rect) {
func (b *Bucket[T]) Check(colSet *CollisionSet[T], shape Shape) {
for i := range b.List {
if shape.Intersects(b.List[i].shape) {
colSet.Add(b.List[i].item)
}
}
}
func (b *Bucket[T]) Collides(shape glm.Rect) bool {
func (b *Bucket[T]) Collides(shape Shape) bool {
for i := range b.List {
if shape.Intersects(b.List[i].shape) {
return true
Expand All @@ -239,14 +193,14 @@ func (b *Bucket[T]) Collides(shape glm.Rect) bool {
return false
}

func (b *Bucket[T]) FindClosest(shape glm.Rect) (BucketItem[T], bool) {
center := shape.Center()
func (b *Bucket[T]) FindClosest(shape Shape) (BucketItem[T], bool) {
center := shape.Bounds.Center()
distSquared := math.MaxFloat64
ret := BucketItem[T]{}
set := false
for i := range b.List {
if shape.Intersects(b.List[i].shape) {
ds := center.DistSq(b.List[i].shape.Center())
ds := center.DistSq(b.List[i].shape.Bounds.Center())
if ds < distSquared {
distSquared = ds
ret = b.List[i]
Expand Down Expand Up @@ -329,9 +283,9 @@ func (h *Hashmap[T]) GetBucket(index Index) *Bucket[T] {
return bucket
}

func (h *Hashmap[T]) Add(shape glm.Rect, val T) {
min := h.PositionToIndex(shape.Min)
max := h.PositionToIndex(shape.Max)
func (h *Hashmap[T]) Add(shape Shape, val T) {
min := h.PositionToIndex(shape.Bounds.Min)
max := h.PositionToIndex(shape.Bounds.Max)

for x := min.X; x <= max.X; x++ {
for y := min.Y; y <= max.Y; y++ {
Expand All @@ -350,9 +304,10 @@ func (h *Hashmap[T]) Remove(val T) {
}

// Finds collisions and adds them directly into your collision set
func (h *Hashmap[T]) Check(colSet *CollisionSet[T], shape glm.Rect) {
min := h.PositionToIndex(shape.Min)
max := h.PositionToIndex(shape.Max)
func (h *Hashmap[T]) Check(colSet *CollisionSet[T], shape Shape) {
// shape := AABB(rect)
min := h.PositionToIndex(shape.Bounds.Min)
max := h.PositionToIndex(shape.Bounds.Max)

for x := min.X; x <= max.X; x++ {
for y := min.Y; y <= max.Y; y++ {
Expand All @@ -376,9 +331,10 @@ func (h *Hashmap[T]) Check(colSet *CollisionSet[T], shape glm.Rect) {
}
}

func (h *Hashmap[T]) Collides(shape glm.Rect) bool {
min := h.PositionToIndex(shape.Min)
max := h.PositionToIndex(shape.Max)
func (h *Hashmap[T]) Collides(rect glm.Rect) bool {
shape := AABB(rect)
min := h.PositionToIndex(shape.Bounds.Min)
max := h.PositionToIndex(shape.Bounds.Max)

for x := min.X; x <= max.X; x++ {
for y := min.Y; y <= max.Y; y++ {
Expand All @@ -405,11 +361,12 @@ func (h *Hashmap[T]) Collides(shape glm.Rect) bool {
return false
}

func (h *Hashmap[T]) FindClosest(shape glm.Rect) (T, bool) {
min := h.PositionToIndex(shape.Min)
max := h.PositionToIndex(shape.Max)
func (h *Hashmap[T]) FindClosest(rect glm.Rect) (T, bool) {
shape := AABB(rect)
min := h.PositionToIndex(shape.Bounds.Min)
max := h.PositionToIndex(shape.Bounds.Max)

center := shape.Center()
center := shape.Bounds.Center()
distSquared := math.MaxFloat64
var ret T
set := false
Expand All @@ -424,7 +381,7 @@ func (h *Hashmap[T]) FindClosest(shape glm.Rect) (T, bool) {
// For border chunks, we need to do narrow phase too
closest, ok := bucket.FindClosest(shape)
if ok {
ds := center.DistSq(closest.shape.Center())
ds := center.DistSq(closest.shape.Bounds.Center())
if ds < distSquared {
distSquared = ds
ret = closest.item
Expand All @@ -438,9 +395,10 @@ func (h *Hashmap[T]) FindClosest(shape glm.Rect) (T, bool) {
}

// Adds the collisions directly into your collision set. This one doesnt' do any narrow phase detection. It returns all objects that collide with the same chunk
func (h *Hashmap[T]) BroadCheck(colSet CollisionSet[T], shape glm.Rect) {
min := h.PositionToIndex(shape.Min)
max := h.PositionToIndex(shape.Max)
func (h *Hashmap[T]) BroadCheck(colSet CollisionSet[T], rect glm.Rect) {
shape := AABB(rect)
min := h.PositionToIndex(shape.Bounds.Min)
max := h.PositionToIndex(shape.Bounds.Max)

for x := min.X; x <= max.X; x++ {
for y := min.Y; y <= max.Y; y++ {
Expand Down
Loading

0 comments on commit 354d28c

Please sign in to comment.