Bicameral Engine
GPU-resident engine for a magic-research open-world game
Loading...
Searching...
No Matches
physics_scene.cpp
Go to the documentation of this file.
1// physics_scene.cpp — 物理の原理の確認の場面(physics_scene.h)。乱数は FxHash64(R6)なので、同じ種からは同じ場面になる。
2#include "sim/physics_scene.h"
3
4#include "common/fixed.hlsli"
5#include "common/units.hlsli"
6
7namespace bicameral::sim {
8
9 namespace {
10
11 using namespace bicameral::fx;
12
13 constexpr int64_t METER = (int64_t)1 << POSITION_FRACTION_BITS;
14 constexpr uint64_t MILLIGRAMS_PER_KG = 1000000;
15
16 // --- 乱数の用途(FxHash64)---
17 constexpr uint32_t PURPOSE_SIZE = 0x50480001u;
18 constexpr uint32_t PURPOSE_DENSITY = 0x50480002u;
19 constexpr uint32_t PURPOSE_ANGLE = 0x50480003u;
20 constexpr uint32_t PURPOSE_JITTER = 0x50480004u;
21
22 // 辺の長さ(mm)から半分の辺(2^-20 m)
23 int64_t HalfExtentFromMillimeters(int64_t sideMillimeters) {
24 return (sideMillimeters * METER) / 2000;
25 }
26
27 // 地面: 100 m 四方・厚さ 1 m、上の面が y = 0
29 return {
30 .halfExtent = {50 * METER, METER / 2, 50 * METER},
31 .position = {0, -METER / 2, 0},
32 };
33 }
34
35 // 立方体(辺 1 m)を、底が y = bottom(2^-20 m)になる高さに置く
36 PhysicsSceneBody MakeMeterCube(uint64_t massKg, int64_t bottom) {
37 return {
38 .halfExtent = {METER / 2, METER / 2, METER / 2},
39 .massMilligrams = massKg * MILLIGRAMS_PER_KG,
40 .position = {0, bottom + METER / 2, 0},
41 };
42 }
43
44 // Q1.30 の四元数の積 a × b
45 std::array<int32_t, 4> MultiplyQuaternion(const std::array<int32_t, 4>& a, const std::array<int32_t, 4>& b) {
46 const auto product = [](int32_t x, int32_t y) {
47 return (int64_t)x * y;
48 };
49 const int64_t x = product(a[3], b[0]) + product(a[0], b[3]) + product(a[1], b[2]) - product(a[2], b[1]);
50 const int64_t y = product(a[3], b[1]) - product(a[0], b[2]) + product(a[1], b[3]) + product(a[2], b[0]);
51 const int64_t z = product(a[3], b[2]) + product(a[0], b[1]) - product(a[1], b[0]) + product(a[2], b[3]);
52 const int64_t w = product(a[3], b[3]) - product(a[0], b[0]) - product(a[1], b[1]) - product(a[2], b[2]);
53
54 return {(int32_t)(x >> 30), (int32_t)(y >> 30), (int32_t)(z >> 30), (int32_t)(w >> 30)};
55 }
56
57 // 軸(0 = x, 1 = y, 2 = z)まわりに angle(1 周 = 2^32)回す四元数
58 std::array<int32_t, 4> AxisRotation(uint32_t axis, uint32_t angle) {
59 const FxSinCos halfAngle = FxSinCosTurn32(angle / 2);
60 std::array<int32_t, 4> rotation{0, 0, 0, halfAngle.cosine};
61 rotation[axis] = halfAngle.sine;
62
63 return rotation;
64 }
65
66 // 乱数の向き(y → x → z の順に回す)
67 std::array<int32_t, 4> RandomRotation(uint64_t seed, uint64_t id) {
68 const auto angle = [&](uint32_t axis) {
69 return (uint32_t)FxHash64(seed, axis, id, PURPOSE_ANGLE);
70 };
71 const auto yaw = AxisRotation(1, angle(1));
72 const auto pitch = AxisRotation(0, angle(0));
73 const auto roll = AxisRotation(2, angle(2));
74
75 return MultiplyQuaternion(roll, MultiplyQuaternion(pitch, yaw));
76 }
77
78 // 囲い: 内側 3 m 四方、壁は厚さ 0.2 m・高さ 3 m。番号 0 の壁(+x)を後で消す
79 void AddPen(PhysicsScene& scene, uint64_t removeTick) {
80 constexpr int64_t INNER_HALF = 3 * METER / 2;
81 constexpr int64_t THICK_HALF = METER / 10;
82 constexpr int64_t HEIGHT_HALF = 3 * METER / 2;
83 constexpr int64_t OFFSET = INNER_HALF + THICK_HALF;
84 constexpr int64_t LONG_HALF = INNER_HALF + 2 * THICK_HALF;
85
86 const std::array<PhysicsSceneBody, 4> walls{{
87 {.halfExtent = {THICK_HALF, HEIGHT_HALF, LONG_HALF}, .position = {OFFSET, HEIGHT_HALF, 0}},
88 {.halfExtent = {THICK_HALF, HEIGHT_HALF, LONG_HALF}, .position = {-OFFSET, HEIGHT_HALF, 0}},
89 {.halfExtent = {LONG_HALF, HEIGHT_HALF, THICK_HALF}, .position = {0, HEIGHT_HALF, OFFSET}},
90 {.halfExtent = {LONG_HALF, HEIGHT_HALF, THICK_HALF}, .position = {0, HEIGHT_HALF, -OFFSET}},
91 }};
92
93 for (const PhysicsSceneBody& wall : walls)
94 scene.bodies.push_back(wall);
95
96 scene.bodies[scene.bodies.size() - 4].removeTick = removeTick;
97 }
98
99 // 落とす箱 1 つ: 辺 250〜1000 mm、密度は木(300 kg/m³)か岩(2700 kg/m³)
100 PhysicsSceneBody MakeFallingBox(uint64_t seed, uint64_t index) {
101 std::array<int64_t, 3> sideMillimeters{};
102 for (uint32_t axis = 0; axis < 3; ++axis)
103 sideMillimeters[axis] = 250 + FxRandomBelow(FxHash64(seed, axis, index, PURPOSE_SIZE), 751);
104
105 const bool isRock = FxRandomBelow(FxHash64(seed, 0, index, PURPOSE_DENSITY), 2) == 1;
106 const uint64_t densityKgPerCubicMeter = isRock ? 2700 : 300;
107 const auto volumeCubicMillimeters = (uint64_t)(sideMillimeters[0] * sideMillimeters[1] *
108 sideMillimeters[2]);
109
110 // 落とす場所: 4 か所を対角に跳びながら回り、少しずらす(±5 cm)。
111 // 続けて落とす 2 つは対角(1.98 m 離れる)、2 つ前は隣(1.4 m)だが 0.5 s 先に落ちているので、外接球(半径 0.87 m)が重ならない
112 constexpr std::array<std::array<int64_t, 2>, 4> COLUMNS{{{1, 1}, {-1, -1}, {-1, 1}, {1, -1}}};
113 const auto& column = COLUMNS[index % 4];
114 const auto jitter = [&](uint32_t axis) {
115 return (int64_t)FxRandomBelow(FxHash64(seed, axis, index, PURPOSE_JITTER), (uint32_t)(METER / 10)) -
116 METER / 20;
117 };
118
119 return {
120 .halfExtent = {HalfExtentFromMillimeters(sideMillimeters[0]),
121 HalfExtentFromMillimeters(sideMillimeters[1]),
122 HalfExtentFromMillimeters(sideMillimeters[2])},
123 .massMilligrams = densityKgPerCubicMeter * volumeCubicMillimeters / 1000,
124 .position = {column[0] * 7 * METER / 10 + jitter(0), 7 * METER, column[1] * 7 * METER / 10 + jitter(2)},
125 .rotation = RandomRotation(seed, index),
126 };
127 }
128
129 } // namespace
130
132 PhysicsScene scene{.name = "stack", .tickCount = (uint64_t)60 * TICKS_PER_SECOND};
133 scene.bodies.push_back(MakeGround());
134
135 for (int64_t level = 0; level < 10; ++level)
136 scene.bodies.push_back(MakeMeterCube(500, level * METER));
137
138 return scene;
139 }
140
142 // 格子の座標で x = 20・z = 32.5(既定の断面 z = 32 のセルの真ん中)の柱。地面は格子の床を覆う
143 constexpr int64_t TOWER_X = 10 * METER;
144 constexpr int64_t TOWER_Z = 16 * METER + METER / 4;
145
146 PhysicsScene scene{.name = "probe_stack", .tickCount = (uint64_t)60 * TICKS_PER_SECOND};
147 PhysicsSceneBody ground = MakeGround();
148 ground.position = {16 * METER, -METER / 2, 16 * METER};
149 scene.bodies.push_back(ground);
150
151 for (int64_t level = 0; level < 10; ++level) {
152 PhysicsSceneBody cube = MakeMeterCube(500, level * METER);
153 cube.position[0] = TOWER_X;
154 cube.position[2] = TOWER_Z;
155 scene.bodies.push_back(cube);
156 }
157
158 return scene;
159 }
160
162 PhysicsScene scene{.name = "mass_ratio", .tickCount = (uint64_t)10 * TICKS_PER_SECOND};
163 scene.bodies.push_back(MakeGround());
164 scene.bodies.push_back(MakeMeterCube(1, 0));
165 scene.bodies.push_back(MakeMeterCube(100, METER));
166
167 return scene;
168 }
169
171 // 0.25 s ごとに 1 つ落とす(50 個で 12.5 s)→ 16 s で壁を消す → 30 s まで
172 constexpr uint64_t BOX_COUNT = 50;
173 constexpr uint64_t SPAWN_INTERVAL_TICKS = 15;
174 constexpr uint64_t REMOVE_TICK = (uint64_t)16 * TICKS_PER_SECOND;
175
176 PhysicsScene scene{.name = "pile", .tickCount = (uint64_t)30 * TICKS_PER_SECOND};
177 scene.bodies.push_back(MakeGround());
178 AddPen(scene, REMOVE_TICK);
179
180 for (uint64_t index = 0; index < BOX_COUNT; ++index) {
181 PhysicsSceneBody box = MakeFallingBox(seed, index);
182 box.spawnTick = index * SPAWN_INTERVAL_TICKS;
183 scene.bodies.push_back(box);
184 }
185
186 return scene;
187 }
188
190 // れんが 1 個: 幅 1 m・高さ 0.5 m・奥行き 1 m・500 kg。段ごとに横に半分ずらし、同じ段のれんがの間は 10 cm 空ける
191 // (横どうしは触れず、上下の 2 個ずつと触れて全部が 1 つの島になる)
192 constexpr int64_t COLUMNS = 20;
193 constexpr int64_t ROWS = 15;
194 constexpr int64_t HALF_WIDTH = METER / 2;
195 constexpr int64_t HALF_HEIGHT = METER / 4;
196 constexpr int64_t PITCH = METER + METER / 10;
197
198 PhysicsScene scene{.name = "wall", .tickCount = (uint64_t)5 * TICKS_PER_SECOND};
199 scene.bodies.push_back(MakeGround());
200 for (int64_t row = 0; row < ROWS; ++row) {
201 const int64_t shift = (row % 2) * PITCH / 2;
202 for (int64_t column = 0; column < COLUMNS; ++column) {
203 scene.bodies.push_back({
204 .halfExtent = {HALF_WIDTH, HALF_HEIGHT, HALF_WIDTH},
205 .massMilligrams = 500 * MILLIGRAMS_PER_KG,
206 .position = {(column - COLUMNS / 2) * PITCH + shift, row * 2 * HALF_HEIGHT + HALF_HEIGHT, 0},
207 });
208 }
209 }
210
211 return scene;
212 }
213
214} // namespace bicameral::sim
std::array< int32_t, 4 > RandomRotation(uint64_t seed, uint64_t id)
PhysicsSceneBody MakeFallingBox(uint64_t seed, uint64_t index)
int64_t HalfExtentFromMillimeters(int64_t sideMillimeters)
void AddPen(PhysicsScene &scene, uint64_t removeTick)
std::array< int32_t, 4 > AxisRotation(uint32_t axis, uint32_t angle)
std::array< int32_t, 4 > MultiplyQuaternion(const std::array< int32_t, 4 > &a, const std::array< int32_t, 4 > &b)
PhysicsSceneBody MakeMeterCube(uint64_t massKg, int64_t bottom)
PhysicsScene MakeWallScene()
PhysicsScene MakeProbeStackScene()
PhysicsScene MakeStackScene()
PhysicsScene MakePileScene(uint64_t seed)
PhysicsScene MakeMassRatioScene()
std::array< int64_t, 3 > position
std::vector< PhysicsSceneBody > bodies