Bicameral Engine
GPU-resident engine for a magic-research open-world game
Loading...
Searching...
No Matches
physics_world.cpp
Go to the documentation of this file.
1// physics_world.cpp — 整数の AVBD の CPU の世界(physics_world.h)。物ごと・組ごとの手順は shaders/common/physics_step.hlsli(GPU と共通)、
2// 式は physics_*.hlsli。手順と分岐は試作(tools/physics_lab/avbd_solver.cpp)に合わせてある(違いは整数の単位と丸めだけ)。
3#include "sim/physics_world.h"
4
5#include <algorithm>
6#include <ranges>
7
8namespace bicameral::sim {
9
10 namespace {
11
12 using namespace bicameral::physics;
13 using namespace bicameral::fx;
14
15 uint32_t BitsOf(int64_t value) {
16 return value == 0 ? 0 : FxMsbU64(FxAbsU64(value)) + 1;
17 }
18
19 } // namespace
20
21 PhysicsBody MakePhysicsBody(const PhysicsSceneBody& source, int64_t rate) {
22 PhysicsBody body{};
23 body.halfExtent = PxMakeVec3(source.halfExtent[0], source.halfExtent[1], source.halfExtent[2]);
24 body.massMilligrams = source.massMilligrams;
25 body.massOverH2 = PxMassOverStepSquared(source.massMilligrams, rate);
26
27 const PxVec3 h = body.halfExtent;
28 body.inertiaOverH2 = PxMakeVec3(PxBoxInertiaOverStepSquared(body.massMilligrams, h.y, h.z, rate),
29 PxBoxInertiaOverStepSquared(body.massMilligrams, h.x, h.z, rate),
30 PxBoxInertiaOverStepSquared(body.massMilligrams, h.x, h.y, rate));
31 body.boundingRadius = (int64_t)PxLength(h);
32 body.spawnTick = source.spawnTick;
33 body.removeTick = source.removeTick;
34 body.position = PxMakeVec3(source.position[0], source.position[1], source.position[2]);
35 body.rotation = PxQuatNormalize(
36 PxMakeQuat(source.rotation[0], source.rotation[1], source.rotation[2], source.rotation[3]));
37 body.color = PX_NO_COLOR;
38
39 return body;
40 }
41
42 PhysicsWorld::PhysicsWorld(const PhysicsScene& scene, const PxParameters& parameters)
43 : m_scene(scene), m_parameters(parameters), m_rate(PxStepRate(parameters)) {
44 for (const PhysicsSceneBody& source : scene.bodies)
45 m_bodies.push_back(MakePhysicsBody(source, m_rate));
46
47 m_bodyManifolds.resize(m_bodies.size());
48 }
49
51 m_stats = {};
53 for (uint32_t i = 0; i < m_parameters.substeps; ++i)
54 Substep();
55
56 ++m_tick;
57 }
58
61 WarmStart();
63 Linearize();
65
66 // 本反復は α = 1(新しい誤差だけ防ぐ)、最後の 1 回は α = 0(残りの誤差を直す。速度には入れない)
67 const uint32_t iterations = m_parameters.iterations;
68 for (uint32_t iteration = 0; iteration <= iterations; ++iteration) {
69 if (iteration == m_parameters.recollideIteration)
71
72 const int64_t alphaQ16 = iteration < iterations ? PX_ALPHA_ONE_Q16 : 0;
73 for (int32_t color = 0; color < m_colorCount; ++color)
74 SolveColor(color, alphaQ16);
75
76 if (iteration < iterations)
77 UpdateDuals(alphaQ16);
78
79 if (iteration + 1 == iterations)
81 }
82
83 Finish();
84 }
85
87 for (PhysicsBody& body : m_bodies)
88 body.active = PxIsActiveAt(body, m_tick);
89 }
90
91 // 近い組ごとに接触を作り、前の刻みの同じ組から引き継ぐ
93 std::map<std::pair<uint32_t, uint32_t>, PhysicsManifold> next;
94 const auto count = (uint32_t)m_bodies.size();
95 for (uint32_t a = 0; a < count; ++a) {
96 for (uint32_t b = a + 1; b < count; ++b) {
97 const PhysicsBody& bodyA = m_bodies[a];
98 const PhysicsBody& bodyB = m_bodies[b];
99 const int64_t margin = PxPairMargin(bodyA, bodyB, m_parameters, m_rate);
100 if (margin < 0)
101 continue;
102
103 const PxContactGeometry geometry = PxCollideBoxes(PxShapeOf(bodyA), PxShapeOf(bodyB), margin);
104 if (geometry.count == 0)
105 continue;
106
107 const auto found = m_manifolds.find({a, b});
108 const PxPreviousManifold previous{found != m_manifolds.end() ? found->second : PhysicsManifold{}};
109 const PhysicsManifold manifold = PxBuildManifold(a, b, bodyA, bodyB, geometry, m_scene.frictionQ16,
110 previous, m_parameters, m_rate);
111 m_stats.maxPenetration = std::max(m_stats.maxPenetration, PxMaxPenetration(geometry));
112 m_stats.contactCount += manifold.count;
113 next.emplace(std::pair{a, b}, manifold);
114 }
115 }
116
117 m_manifolds = std::move(next);
118 for (auto& list : m_bodyManifolds)
119 list.clear();
120
121 for (const auto& [key, manifold] : m_manifolds) {
122 m_bodyManifolds[manifold.bodyA].emplace_back(&manifold, true);
123 m_bodyManifolds[manifold.bodyB].emplace_back(&manifold, false);
124 }
125
126 UpdateBetas();
127 }
128
129 // 硬さの増え方 β = βkg × 組の両方に触れている物の中で一番重い質量(physics_step.hlsli の PxBetaOf)
131 std::vector<uint64_t> touchingMass(m_bodies.size());
132 for (uint32_t i = 0; i < m_bodies.size(); ++i)
133 touchingMass[i] = PxTouchMass(m_bodies[i]);
134
135 for (const auto& [key, manifold] : m_manifolds) {
136 touchingMass[manifold.bodyA] = std::max(touchingMass[manifold.bodyA],
137 PxTouchMass(m_bodies[manifold.bodyB]));
138 touchingMass[manifold.bodyB] = std::max(touchingMass[manifold.bodyB],
139 PxTouchMass(m_bodies[manifold.bodyA]));
140 }
141
142 for (auto& [key, manifold] : m_manifolds) {
143 const uint64_t mass = std::max(touchingMass[manifold.bodyA], touchingMass[manifold.bodyB]);
144 manifold.beta = PxBetaOf(mass, m_parameters);
145 }
146 }
147
149 for (auto& [key, manifold] : m_manifolds)
150 manifold = PxWarmStartManifold(manifold, m_parameters.gammaQ16);
151 }
152
154 for (PhysicsBody& body : m_bodies)
155 body = PxInitializeBody(body, m_rate);
156 }
157
159 for (auto& [key, manifold] : m_manifolds)
160 manifold = PxLinearizeManifold(manifold, m_bodies[manifold.bodyA], m_bodies[manifold.bodyB]);
161 }
162
163 // 拘束でつながる物どうしが違う色になるように塗る(Jones-Plassmann: 番号のハッシュを優先度に。GPU でも同じ色になる)
165 const std::vector<std::vector<uint32_t>> neighbors = DynamicNeighbors();
166 m_colorCount = 0;
167 uint32_t rounds = 0;
168 for (;;) {
169 const std::vector<uint32_t> chosen = ChooseLocalMaxima(neighbors);
170 if (chosen.empty())
171 break;
172
173 for (uint32_t i : chosen)
174 AssignSmallestColor(i, neighbors[i]);
175
176 rounds += 1;
177 }
178
179 m_stats.colorCount = std::max(m_stats.colorCount, (uint32_t)m_colorCount);
180 m_stats.colorRounds = std::max(m_stats.colorRounds, rounds);
181 }
182
183 // 動く物どうしの接触でつながる隣
184 std::vector<std::vector<uint32_t>> PhysicsWorld::DynamicNeighbors() const {
185 std::vector<std::vector<uint32_t>> neighbors(m_bodies.size());
186 for (const auto& [key, manifold] : m_manifolds) {
187 if (!PxIsDynamic(m_bodies[manifold.bodyA]) || !PxIsDynamic(m_bodies[manifold.bodyB]))
188 continue;
189
190 neighbors[manifold.bodyA].push_back(manifold.bodyB);
191 neighbors[manifold.bodyB].push_back(manifold.bodyA);
192 }
193
194 return neighbors;
195 }
196
197 // まだ色の無い物のうち、色の無い隣のどれよりも優先度が高い物(同じ回で選ばれた物どうしは隣り合わない)
198 std::vector<uint32_t> PhysicsWorld::ChooseLocalMaxima(const std::vector<std::vector<uint32_t>>& neighbors) const {
199 std::vector<uint32_t> chosen;
200 for (uint32_t i = 0; i < m_bodies.size(); ++i) {
201 const PhysicsBody& body = m_bodies[i];
202 if (!PxIsDynamic(body) || body.active == 0 || body.color >= 0)
203 continue;
204
205 const auto isBelow = [&](uint32_t j) {
206 return m_bodies[j].color >= 0 || PxColorPriorityBelow(j, i);
207 };
208 if (std::ranges::all_of(neighbors[i], isBelow))
209 chosen.push_back(i);
210 }
211
212 return chosen;
213 }
214
215 // 隣が使っていない一番小さい色
216 void PhysicsWorld::AssignSmallestColor(uint32_t index, const std::vector<uint32_t>& neighbors) {
217 int32_t color = 0;
218 while (std::ranges::any_of(neighbors, [&](uint32_t j) { return m_bodies[j].color == color; }))
219 ++color;
220
221 m_bodies[index].color = color;
222 m_colorCount = std::max(m_colorCount, color + 1);
223 }
224
225 // 反復の途中で、今の推定の姿勢で接触を探し直す(physics_step.hlsli の PxRecollideManifold)
227 for (auto& [key, manifold] : m_manifolds) {
228 const uint32_t before = manifold.count;
229 manifold = PxRecollideManifold(manifold, m_bodies[manifold.bodyA], m_bodies[manifold.bodyB], m_parameters);
230 m_stats.contactCount += manifold.count - before;
231 }
232 }
233
234 // 同じ色の物は拘束を共有しないので、どの順に解いても結果は同じ(GPU では並列に解く)
235 void PhysicsWorld::SolveColor(int32_t color, int64_t alphaQ16) {
236 for (uint32_t i = 0; i < m_bodies.size(); ++i) {
237 if (m_bodies[i].color == color)
238 SolveBody(i, alphaQ16);
239 }
240 }
241
242 void PhysicsWorld::SolveBody(uint32_t index, int64_t alphaQ16) {
243 PhysicsBody& body = m_bodies[index];
244 PxBodySystem system = PxBeginSystemOf(body);
245 for (const auto& [manifold, isA] : m_bodyManifolds[index]) {
246 const PhysicsBody& a = m_bodies[manifold->bodyA];
247 const PhysicsBody& b = m_bodies[manifold->bodyB];
248 for (uint32_t k = 0; k < manifold->count; ++k) {
249 system = PxAddPointRows(system, manifold->points[k], manifold->frictionQ16, isA, alphaQ16,
250 m_parameters.gapSlop, a.deltaLinear, a.deltaAngular, b.deltaLinear,
251 b.deltaAngular);
252 }
253 }
254
255 RecordSystemBits(system);
256 const PxSolveResult solved = PxSolveSymmetric6(system.lhs, system.rhs, PX_SOLVE_GAIN_SHIFT);
257 m_stats.solveBits = std::max(m_stats.solveBits, solved.maxBits);
258 m_stats.solveFailures += solved.ok ? 0 : 1;
259
260 body = PxApplySolution(body, solved.x);
261 for (uint32_t i = 0; i < 3; ++i) {
262 m_stats.deltaBits = std::max(m_stats.deltaBits, BitsOf(PxGet(body.deltaLinear, i)));
263 m_stats.deltaBits = std::max(m_stats.deltaBits, BitsOf(PxGet(body.deltaAngular, i)));
264 }
265 }
266
267 void PhysicsWorld::RecordSystemBits(const PxBodySystem& system) {
268 for (int64_t value : system.lhs.m)
269 m_stats.lhsBits = std::max(m_stats.lhsBits, BitsOf(value));
270
271 for (int64_t value : system.rhs.v)
272 m_stats.rhsBits = std::max(m_stats.rhsBits, BitsOf(value));
273 }
274
275 void PhysicsWorld::UpdateDuals(int64_t alphaQ16) {
276 for (auto& [key, manifold] : m_manifolds) {
277 const PhysicsBody& a = m_bodies[manifold.bodyA];
278 const PhysicsBody& b = m_bodies[manifold.bodyB];
279 for (uint32_t k = 0; k < manifold.count; ++k) {
280 PhysicsContactPoint& contactPoint = manifold.points[k];
281 contactPoint = PxUpdatePointDuals(contactPoint, manifold.frictionQ16, manifold.beta,
282 manifold.maxPenalty, alphaQ16, m_parameters, a.deltaLinear,
283 a.deltaAngular, b.deltaLinear, b.deltaAngular);
284 RecordPointBits(contactPoint);
285 }
286 }
287 }
288
290 for (const PxRow& row : contactPoint.rows) {
291 m_stats.penaltyBits = std::max(m_stats.penaltyBits, BitsOf(row.penalty));
292 m_stats.lambdaBits = std::max(m_stats.lambdaBits, BitsOf(row.lambda));
293 }
294 }
295
297 for (PhysicsBody& body : m_bodies)
298 body = PxUpdateVelocity(body, m_rate);
299 }
300
302 for (PhysicsBody& body : m_bodies)
303 body = PxFinishBody(body);
304 }
305
306 // 光線が最初に入る物を番号の順に探し、動く物なら入った点に力積を加える(common/physics_push.hlsli。GPU の押すコマンドと同じ手順)
307 uint32_t PhysicsWorld::Push(const PxVec3& origin, const PxVec3& direction, uint32_t impulseMillinewtonSeconds) {
308 int64_t best = PX_RAY_MISS;
309 uint32_t hit = UINT32_MAX;
310 for (uint32_t index = 0; index < m_bodies.size(); ++index) {
311 const int64_t distance = PxRayEntryDistance(m_bodies[index], origin, direction);
312 if (PxRayHitCloser(distance, best)) {
313 best = distance;
314 hit = index;
315 }
316 }
317
318 if (hit == UINT32_MAX || !PxIsDynamic(m_bodies[hit]))
319 return UINT32_MAX;
320
321 PhysicsBody& body = m_bodies[hit];
322 const PxVec3 offset = PxSub(PxRayPoint(origin, direction, best), body.position);
323 body = PxApplyImpulse(body, offset, PxPushImpulse(direction, impulseMillinewtonSeconds), m_rate);
324
325 return hit;
326 }
327
328 uint64_t PhysicsWorld::StateHash() const {
329 uint64_t hash = FxMix64(m_tick);
330 for (const PhysicsBody& body : m_bodies)
331 hash = PxHashBody(hash, body);
332
333 return hash;
334 }
335
336} // namespace bicameral::sim
std::vector< uint32_t > ChooseLocalMaxima(const std::vector< std::vector< uint32_t > > &neighbors) const
void UpdateDuals(int64_t alphaQ16)
std::vector< std::vector< uint32_t > > DynamicNeighbors() const
void SolveBody(uint32_t index, int64_t alphaQ16)
void AssignSmallestColor(uint32_t index, const std::vector< uint32_t > &neighbors)
uint32_t Push(const physics::PxVec3 &origin, const physics::PxVec3 &direction, uint32_t impulseMillinewtonSeconds)
void SolveColor(int32_t color, int64_t alphaQ16)
PhysicsWorld(const PhysicsScene &scene, const physics::PxParameters &parameters)
void RecordPointBits(const PhysicsContactPoint &point)
physics::PxParameters m_parameters
std::map< std::pair< uint32_t, uint32_t >, PhysicsManifold > m_manifolds
void RecordSystemBits(const physics::PxBodySystem &system)
std::vector< std::vector< std::pair< const PhysicsManifold *, bool > > > m_bodyManifolds
std::vector< PhysicsBody > m_bodies
physics::PxContactPoint PhysicsContactPoint
PhysicsBody MakePhysicsBody(const PhysicsSceneBody &source, int64_t rate)
physics::PxManifold PhysicsManifold
physics::PxBody PhysicsBody
std::array< int64_t, 3 > halfExtent
std::array< int64_t, 3 > position
std::array< int32_t, 4 > rotation
std::vector< PhysicsSceneBody > bodies