-
-
Notifications
You must be signed in to change notification settings - Fork 27
Expand file tree
/
Copy pathbody.go
More file actions
213 lines (186 loc) · 6.69 KB
/
Copy pathbody.go
File metadata and controls
213 lines (186 loc) · 6.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
// SPDX-FileCopyrightText : © 2016-2022 Galvanized Logic Inc.
// SPDX-License-Identifier: MIT
package vu
// body.go integrates physics into the engine.
import (
"log/slog"
"github.com/gazed/vu/physics"
)
// Body wraps physics.Body allowing the engine app to access common physics
// methods without including the physics package.
type Body *physics.Body
// Simulated bodies can either be kinematic-movable or static-immovable.
const (
KinematicSim bool = false // movable
StaticSim bool = true // immovable
)
// Sphere creates a ball shaped physics body located at the origin.
// The sphere size is defined by the radius.
func Sphere(radius float64, static bool) Body { return physics.NewSphere(radius, static) }
// Box creates a box shaped physics body located at the origin.
// The box size is given by the half-extents so that actual size
// is w=2*hx, h=2*hy, d=2*hz.
func Box(hx, hy, hz float64, static bool) Body { return physics.NewBox(hx, hy, hz, static) }
// AddToSimulation
// Bodies are generally set on top level pov transforms which always
// have valid world coordindates.
//
// b: a physics body eg: vu.Box, vu.Sphere.
func (e *Entity) AddToSimulation(b Body) *Entity {
if p := e.app.povs.get(e.eid); p == nil {
slog.Error("AddToSimulation requires existing pov")
return e
}
e.app.sim.create(e.eid, b)
return e
}
// Push adds to the body's linear velocity.
// It is a wrapper for physics.Body.Push
//
// Depends on AddToSimultation.
func (e *Entity) Push(x, y, z float64) {
if body := e.app.sim.get(e.eid); body != nil {
(*physics.Body)(body).Push(x, y, z)
return
}
slog.Error("Push needs AddToSimulation", "entity_id", e.eid)
}
// Velocity returns the physics body linear velocity.
// It is a wrapper for physics.Body.Velocity
//
// Depends on AddToSimultation.
func (e *Entity) Velocity() (x, y, z float64) {
if body := e.app.sim.get(e.eid); body != nil {
v3 := (*physics.Body)(body).Velocity()
return v3.X, v3.Y, v3.Z
}
slog.Error("Velocity needs AddToSimulation", "entity_id", e.eid)
return 0, 0, 0
}
// Stop all physics movement (set velocity and forces to zero).
// Stop is a wrapper for physics.Body.Stop
//
// Depends on AddToSimultation.
func (e *Entity) Stop() {
if body := e.app.sim.get(e.eid); body != nil {
(*physics.Body)(body).Stop()
return
}
slog.Error("Stop needs AddToSimulation", "entity_id", e.eid)
}
// SetActive activates or deactivates the body in the simulation.
func (e *Entity) SetActive(active bool) {
if body := e.app.sim.get(e.eid); body != nil {
(*physics.Body)(body).Activate(active)
return
}
slog.Error("SetActive needs AddToSimulation", "entity_id", e.eid)
}
// SetGravity sets the down force applied to this body.
// Gravity is 10.0m/s by default.
// SetGravity is a wrapper for physics.Body.SetGravity
//
// Depends on AddToSimultation.
func (e *Entity) SetGravity(downForce float64) *Entity {
if body := e.app.sim.get(e.eid); body != nil {
(*physics.Body)(body).SetGravity(downForce)
return e
}
slog.Error("SetGravity needs AddToSimulation", "entity_id", e.eid)
return e
}
// Body returns the underlying physics body for this entity,
// returning nil if no physics body exists.
func (e *Entity) Body() *physics.Body { return e.app.sim.get(e.eid) }
// DisposeBody removes the physics body from the given entity.
// Does nothing if there was no physics body.
func (e *Entity) DisposeBody() { e.app.sim.dispose(e.eid) }
// =============================================================================
// simulation is the physics component manager
// simulation manages all the active physics instances.
type simulation struct {
bids map[eID]uint32 // Sparse mapping of eid to bid.
bodies []physics.Body // Dense array of physics bodies, indexed by bid.
eids []eID // Dense array of eids indexed by bid.
}
// newSimulation creates a manager for a group of physics data. Expectation
// is for a single instance to be created by the engine on startup.
func newSimulation() *simulation {
sim := &simulation{}
sim.bodies = []physics.Body{} // Dense array of physics bodies...
sim.eids = []eID{} // ...and associated entity identifiers.
sim.bids = map[eID]uint32{} // map entity ids to body ids.
return sim
}
// create a new physics body. Guarantees that child pov's appear later in
// the dense data array since children must be created after their parents.
func (sim *simulation) create(id eID, b Body) Body {
if bid, ok := sim.bids[id]; ok {
return &sim.bodies[bid] // already exists.
}
bid := len(sim.bodies) // body id is the array index.
sim.bodies = append(sim.bodies, *b) // save body - indexed by bid
sim.eids = append(sim.eids, id) // "" - indexed by bid
sim.bids[id] = uint32(bid) // map eid to bid.
return b
}
// get the physics body for the given id, returning nil if
// it does not exist.
func (sim *simulation) get(id eID) Body {
if bid, ok := sim.bids[id]; ok {
return &sim.bodies[bid]
}
return nil
}
// dispose deletes the indicated physics body.
func (sim *simulation) dispose(eid eID) {
if index, ok := sim.bids[eid]; ok {
delete(sim.bids, eid) // delete index from sparse array.
// Save a mem copy by replacing deleted element with last element.
// No other indicies need to be updated.
lastIndex := len(sim.bodies) - 1
lastID := sim.eids[lastIndex] // eid of last index.
sim.eids[index] = sim.eids[lastIndex] // delete by replacing.
sim.bodies[index] = sim.bodies[lastIndex] // delete by replacing.
sim.eids = sim.eids[:lastIndex] // discard moved last element.
sim.bodies = sim.bodies[:lastIndex] // discard moved last element.
if eid != lastID {
// if the deleted element wasn't the last...
sim.bids[lastID] = index // ...update the moved element index.
}
}
}
// simulate runs physics on all the bodies; adjusting location and orientation.
// Expected to be called on regular timesteps from the main game loop.
func (sim *simulation) simulate(ps *povs, timestep float64) {
// update simulation body transforms with povs that may have
// been changed by the app.
for i := range sim.bodies {
bod := &sim.bodies[i]
eid := sim.eids[i]
p := ps.get(eid)
if p == nil {
slog.Error("physics body with no pov", "eid", eid)
continue
}
bod.SetPosition(*p.tn.Loc)
bod.SetRotation(*p.tn.Rot)
bod.SetScale(*p.sw)
}
// run the physics simulation.
physics.Simulate(sim.bodies, timestep)
// apply any physics transform changes to the povs
for i := range sim.bodies {
bod := &sim.bodies[i]
eid := sim.eids[i]
p := ps.get(eid)
if p == nil {
slog.Error("physics body with no pov", "eid", eid)
continue
}
p.tn.Loc.Set(bod.Position())
p.tn.Rot.Set(bod.Rotation())
// physics does not change scale.
ps.updateWorld(p, eid)
}
}