Experimenting with different collision resolutions.
This commit is contained in:
127
game/game.go
127
game/game.go
@@ -17,7 +17,7 @@ import (
|
|||||||
const (
|
const (
|
||||||
GameWidth = 640
|
GameWidth = 640
|
||||||
GameHeight = 360
|
GameHeight = 360
|
||||||
GameParticleCount = 1000
|
GameParticleCount = 2000
|
||||||
GameGravity = 2
|
GameGravity = 2
|
||||||
GameParticleRadius = 5
|
GameParticleRadius = 5
|
||||||
GameDamping = .7
|
GameDamping = .7
|
||||||
@@ -36,6 +36,8 @@ type Game struct {
|
|||||||
paused bool
|
paused bool
|
||||||
renderquads bool
|
renderquads bool
|
||||||
resolvecollisions bool
|
resolvecollisions bool
|
||||||
|
resolvers []func(particle *elements.Particle)
|
||||||
|
resolveridx int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGame() *Game {
|
func NewGame() *Game {
|
||||||
@@ -44,6 +46,7 @@ func NewGame() *Game {
|
|||||||
paused: false,
|
paused: false,
|
||||||
renderquads: false,
|
renderquads: false,
|
||||||
resolvecollisions: false,
|
resolvecollisions: false,
|
||||||
|
resolveridx: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
g.particlebox = &gamedata.Vector{
|
g.particlebox = &gamedata.Vector{
|
||||||
@@ -70,6 +73,9 @@ func NewGame() *Game {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g.resolvers = append(g.resolvers, g.ResolveCollisionsA)
|
||||||
|
g.resolvers = append(g.resolvers, g.ResolveCollisionsB)
|
||||||
|
|
||||||
//g.InitializeColliders()
|
//g.InitializeColliders()
|
||||||
g.InitializeParticles()
|
g.InitializeParticles()
|
||||||
|
|
||||||
@@ -113,8 +119,12 @@ func (g *Game) RenderParticles(img *ebiten.Image) {
|
|||||||
x0 := particle.Position.X - GameParticleRadius
|
x0 := particle.Position.X - GameParticleRadius
|
||||||
y0 := particle.Position.Y - GameParticleRadius
|
y0 := particle.Position.Y - GameParticleRadius
|
||||||
|
|
||||||
|
//redness := float32(particle.Position.Y / g.particlebox.Y)
|
||||||
|
//blueness := 1 - redness
|
||||||
|
|
||||||
op := &ebiten.DrawImageOptions{}
|
op := &ebiten.DrawImageOptions{}
|
||||||
op.GeoM.Translate(x0, y0)
|
op.GeoM.Translate(x0, y0)
|
||||||
|
//op.ColorScale.Scale(redness, 0, blueness, 1)
|
||||||
img.DrawImage(g.particlebuff, op)
|
img.DrawImage(g.particlebuff, op)
|
||||||
|
|
||||||
//vector.FillCircle(img, float32(particle.Position.X), float32(particle.Position.Y), float32(particle.Radius), color.White, true)
|
//vector.FillCircle(img, float32(particle.Position.X), float32(particle.Position.Y), float32(particle.Radius), color.White, true)
|
||||||
@@ -178,50 +188,47 @@ func (g *Game) UpdateParticles() {
|
|||||||
|
|
||||||
dt := GameDeltaTimeStep
|
dt := GameDeltaTimeStep
|
||||||
|
|
||||||
/*
|
mx, my := ebiten.CursorPosition()
|
||||||
mx, my := ebiten.CursorPosition()
|
mpos := gamedata.Vector{X: float64(mx), Y: float64(my)}
|
||||||
mpos := gamedata.Vector{X: float64(mx), Y: float64(my)}
|
maxdeflect := 40 * GameDeltaTimeStep * GameGravity
|
||||||
maxdeflect := 40 * GameDeltaTimeStep * GameGravity
|
|
||||||
*/
|
|
||||||
|
|
||||||
for _, particle := range g.particles {
|
for _, particle := range g.particles {
|
||||||
particle.Velocity.Y += GameGravity * dt
|
particle.Velocity.Y += GameGravity * dt
|
||||||
|
|
||||||
//if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
|
//if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) {
|
||||||
/*
|
|
||||||
if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
|
|
||||||
delta := gamedata.Vector{
|
|
||||||
X: mpos.X - particle.Position.X,
|
|
||||||
Y: mpos.Y - particle.Position.Y,
|
|
||||||
}
|
|
||||||
|
|
||||||
dist := math.Sqrt(delta.X*delta.X + delta.Y*delta.Y)
|
|
||||||
theta := math.Atan2(delta.Y, delta.X)
|
|
||||||
|
|
||||||
if dist < GameInfluenceRadius {
|
|
||||||
|
|
||||||
dx := dist * math.Cos(theta)
|
|
||||||
dy := dist * math.Sin(theta)
|
|
||||||
|
|
||||||
if dx != 0 {
|
|
||||||
gainx := (-1./GameInfluenceRadius)*math.Abs(dx) + 1.
|
|
||||||
particle.Velocity.X += 20 * gainx * -1 * math.Copysign(1, dx)
|
|
||||||
}
|
|
||||||
|
|
||||||
if dy != 0 {
|
|
||||||
gainy := (-1./GameInfluenceRadius)*math.Abs(dy) + 1.
|
|
||||||
particle.Velocity.Y += maxdeflect * gainy * -1 * math.Copysign(1, dy)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
|
||||||
|
delta := gamedata.Vector{
|
||||||
|
X: mpos.X - particle.Position.X,
|
||||||
|
Y: mpos.Y - particle.Position.Y,
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
dist := math.Sqrt(delta.X*delta.X + delta.Y*delta.Y)
|
||||||
|
theta := math.Atan2(delta.Y, delta.X)
|
||||||
|
|
||||||
|
if dist < GameInfluenceRadius {
|
||||||
|
|
||||||
|
dx := dist * math.Cos(theta)
|
||||||
|
dy := dist * math.Sin(theta)
|
||||||
|
|
||||||
|
if dx != 0 {
|
||||||
|
gainx := (-1./GameInfluenceRadius)*math.Abs(dx) + 1.
|
||||||
|
particle.Velocity.X += 20 * gainx * -1 * math.Copysign(1, dx)
|
||||||
|
}
|
||||||
|
|
||||||
|
if dy != 0 {
|
||||||
|
gainy := (-1./GameInfluenceRadius)*math.Abs(dy) + 1.
|
||||||
|
particle.Velocity.Y += maxdeflect * gainy * -1 * math.Copysign(1, dy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
particle.Position.X += particle.Velocity.X * dt
|
particle.Position.X += particle.Velocity.X * dt
|
||||||
particle.Position.Y += particle.Velocity.Y * dt
|
particle.Position.Y += particle.Velocity.Y * dt
|
||||||
|
|
||||||
if g.resolvecollisions {
|
if g.resolvecollisions {
|
||||||
g.ResolveCollisions(particle)
|
g.resolvers[g.resolveridx](particle)
|
||||||
}
|
}
|
||||||
|
|
||||||
g.BoundParticle(particle)
|
g.BoundParticle(particle)
|
||||||
@@ -288,6 +295,17 @@ func (g *Game) ParseInputs() {
|
|||||||
g.resolvecollisions = !g.resolvecollisions
|
g.resolvecollisions = !g.resolvecollisions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if inpututil.IsKeyJustPressed(ebiten.KeyLeft) {
|
||||||
|
g.resolveridx = g.resolveridx - 1
|
||||||
|
if g.resolveridx < 0 {
|
||||||
|
g.resolveridx = len(g.resolvers) - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if inpututil.IsKeyJustPressed(ebiten.KeyRight) {
|
||||||
|
g.resolveridx = (g.resolveridx + 1) % len(g.resolvers)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) RebuildQuadtree() {
|
func (g *Game) RebuildQuadtree() {
|
||||||
@@ -300,7 +318,7 @@ func (g *Game) RebuildQuadtree() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Game) ResolveCollisions(particle *elements.Particle) {
|
func (g *Game) ResolveCollisionsA(particle *elements.Particle) {
|
||||||
//construct search quadrant from current particle
|
//construct search quadrant from current particle
|
||||||
quadrant := quadtree.Quadrant{
|
quadrant := quadtree.Quadrant{
|
||||||
Position: particle.Position,
|
Position: particle.Position,
|
||||||
@@ -349,3 +367,44 @@ func (g *Game) ResolveCollisions(particle *elements.Particle) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *Game) ResolveCollisionsB(particle *elements.Particle) {
|
||||||
|
//construct search quadrant from current particle
|
||||||
|
quadrant := quadtree.Quadrant{
|
||||||
|
Position: particle.Position,
|
||||||
|
Dimensions: particle.GetDimensions(),
|
||||||
|
}
|
||||||
|
|
||||||
|
//find list of possible maybe collisions, we inspect those in more detail
|
||||||
|
maybes := g.quadtree.FindAll(quadrant)
|
||||||
|
|
||||||
|
sqdist := float64(GameParticleRadius*GameParticleRadius) * 4
|
||||||
|
|
||||||
|
for _, p := range maybes {
|
||||||
|
if p == particle {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pos := p.GetPosition()
|
||||||
|
delta := gamedata.Vector{
|
||||||
|
X: pos.X - particle.Position.X,
|
||||||
|
Y: pos.Y - particle.Position.Y,
|
||||||
|
}
|
||||||
|
|
||||||
|
dist2 := delta.X*delta.X + delta.Y*delta.Y
|
||||||
|
|
||||||
|
if dist2 < sqdist {
|
||||||
|
d := math.Sqrt(dist2)
|
||||||
|
overlap := GameParticleRadius*2 - d
|
||||||
|
theta := math.Atan2(delta.Y, delta.X)
|
||||||
|
pos.X += overlap * math.Cos(theta)
|
||||||
|
pos.Y += overlap * math.Sin(theta)
|
||||||
|
p.SetPosition(pos)
|
||||||
|
|
||||||
|
m := p.(*elements.Particle)
|
||||||
|
m.Velocity.X *= -1 * GameDamping
|
||||||
|
m.Velocity.Y *= -1 * GameDamping
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
QuadtreeMaxColliders = 10
|
QuadtreeMaxColliders = 40
|
||||||
)
|
)
|
||||||
|
|
||||||
type Quadrant struct {
|
type Quadrant struct {
|
||||||
@@ -31,25 +31,27 @@ func (q *Quadtree) Insert(obj colliders.Collider) bool {
|
|||||||
|
|
||||||
var result bool = false
|
var result bool = false
|
||||||
//check that object meets containment criteria
|
//check that object meets containment criteria
|
||||||
if q.ContainsPoint(obj.GetPosition()) {
|
if !q.ContainsPoint(obj.GetPosition()) {
|
||||||
|
return result
|
||||||
//if we have children, we go deeper
|
|
||||||
if len(q.children) > 0 {
|
|
||||||
for _, node := range q.children {
|
|
||||||
if node.Insert(obj) {
|
|
||||||
result = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if len(q.colliders) < QuadtreeMaxColliders {
|
|
||||||
//otherwise, if we have space, add
|
|
||||||
q.colliders = append(q.colliders, obj)
|
|
||||||
result = true
|
|
||||||
} else {
|
|
||||||
//otherwise we have to subdivide, reorganize, and add
|
|
||||||
result = q.SubdivideAndInsert(obj)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//if we have children, we go deeper
|
||||||
|
if len(q.children) > 0 {
|
||||||
|
for _, node := range q.children {
|
||||||
|
if node.Insert(obj) {
|
||||||
|
result = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if len(q.colliders) < QuadtreeMaxColliders {
|
||||||
|
//otherwise, if we have space, add
|
||||||
|
q.colliders = append(q.colliders, obj)
|
||||||
|
result = true
|
||||||
|
} else {
|
||||||
|
//otherwise we have to subdivide, reorganize, and add
|
||||||
|
result = q.SubdivideAndInsert(obj)
|
||||||
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,26 +69,27 @@ func (q *Quadtree) ContainsPoint(p gamedata.Vector) bool {
|
|||||||
func (q *Quadtree) Remove(obj colliders.Collider) bool {
|
func (q *Quadtree) Remove(obj colliders.Collider) bool {
|
||||||
var result bool = false
|
var result bool = false
|
||||||
|
|
||||||
if q.ContainsPoint(obj.GetPosition()) {
|
if !q.ContainsPoint(obj.GetPosition()) {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
if len(q.colliders) > 0 {
|
if len(q.colliders) > 0 {
|
||||||
//examine existing colliders
|
//examine existing colliders
|
||||||
var collection []colliders.Collider
|
var collection []colliders.Collider
|
||||||
for _, node := range q.colliders {
|
for _, node := range q.colliders {
|
||||||
if node == obj {
|
if node == obj {
|
||||||
result = true
|
result = true
|
||||||
} else {
|
} else {
|
||||||
collection = append(collection, node)
|
collection = append(collection, node)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
q.colliders = collection
|
}
|
||||||
} else {
|
q.colliders = collection
|
||||||
//need to check children
|
} else {
|
||||||
for _, child := range q.children {
|
//need to check children
|
||||||
if child.Remove(obj) {
|
for _, child := range q.children {
|
||||||
result = true
|
if child.Remove(obj) {
|
||||||
break
|
result = true
|
||||||
}
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user