载入中...
搜索中...
未找到
ScenePhysicsPlacement.cpp
浏览该文件的文档.
2
3#include "common/Exception.h"
4#include "common/Time.h"
5#include "physics/Body3D.h"
6#include "physics/Shape3D.h"
8#include "physics/World3D.h"
9
10#include <algorithm>
11#include <cmath>
12#include <map>
13#include <set>
14#include <string>
15#include <utility>
16
17#include <glm/gtc/quaternion.hpp>
18#include <glm/gtx/quaternion.hpp>
19#include <glm/vec3.hpp>
20
21namespace eve::scene_editor {
22namespace {
23
24bool finite(double value) { return std::isfinite(value); }
25
26bool finiteTransform(const scene_editing::SceneTransformValue& value) {
27 return finite(value.x) && finite(value.y) && finite(value.z) && finite(value.rotationX) &&
28 finite(value.rotationY) && finite(value.rotationZ) && finite(value.scaleX) && finite(value.scaleY) &&
29 finite(value.scaleZ);
30}
31
32glm::quat rotationOf(const scene_editing::SceneTransformValue& value) {
33 return glm::quat(glm::vec3(static_cast<float>(value.rotationX), static_cast<float>(value.rotationY),
34 static_cast<float>(value.rotationZ)));
35}
36
37} // namespace
38
42 std::unique_ptr<physics::Body3D> body;
43 std::vector<std::unique_ptr<physics::Shape3D>> shapes;
44 glm::dvec3 appliedScale{1.0};
45 };
46
47 static std::vector<PhysicsPlacementCollider> selectedColliders(const PhysicsPlacementObject& object,
49 std::vector<PhysicsPlacementCollider> values = object.colliders;
50 if (values.empty()) {
52 legacy.shape = object.shape;
53 legacy.halfExtentX = object.halfExtentX;
54 legacy.halfExtentY = object.halfExtentY;
55 legacy.halfExtentZ = object.halfExtentZ;
56 legacy.convexVertices = object.convexVertices;
57 legacy.convexMaxVertices = object.convexMaxVertices;
58 values.push_back(std::move(legacy));
59 }
60 const bool hasExisting = std::any_of(values.begin(), values.end(), [](const auto& value) {
61 return value.source == PhysicsPlacementColliderSource::Existing;
62 });
63 values.erase(std::remove_if(values.begin(), values.end(),
64 [&](const auto& value) {
65 switch (policy) {
66 case PhysicsPlacementColliderPolicy::ExistingOnly:
67 return value.source != PhysicsPlacementColliderSource::Existing;
68 case PhysicsPlacementColliderPolicy::GeneratedOnly:
69 return value.source != PhysicsPlacementColliderSource::Generated;
70 case PhysicsPlacementColliderPolicy::GeneratedWhenMissing:
71 return hasExisting
72 ? value.source != PhysicsPlacementColliderSource::Existing
73 : value.source != PhysicsPlacementColliderSource::Generated;
74 case PhysicsPlacementColliderPolicy::ExistingAndGenerated: return false;
75 }
76 return true;
77 }),
78 values.end());
79 return values;
80 }
81
82 static std::unique_ptr<physics::Shape3D> createShape(physics::Body3D& body,
83 const PhysicsPlacementCollider& collider,
84 const glm::dvec3& scale) {
85 const float width = static_cast<float>(2.0 * collider.halfExtentX * std::abs(scale.x));
86 const float height = static_cast<float>(2.0 * collider.halfExtentY * std::abs(scale.y));
87 const float depth = static_cast<float>(2.0 * collider.halfExtentZ * std::abs(scale.z));
88 physics::Shape3D* raw = nullptr;
90 raw = body.newSphereShape(std::max({width, height, depth}) * 0.5f, 1.0f, 0.5f, 0.0f);
91 else if (collider.shape == PhysicsPlacementShape::Capsule)
92 raw = body.newCapsuleShape(height, std::max(width, depth) * 0.5f, 1.0f, 0.5f, 0.0f);
93 else if (collider.shape == PhysicsPlacementShape::ConvexHull) {
94 auto vertices = collider.convexVertices;
95 for (std::size_t index = 0; index < vertices.size(); index += 3) {
96 vertices[index] *= static_cast<float>(scale.x);
97 vertices[index + 1] *= static_cast<float>(scale.y);
98 vertices[index + 2] *= static_cast<float>(scale.z);
99 }
100 raw = body.newConvexHullShape(vertices, collider.convexMaxVertices, 1.0f, 0.5f, 0.0f);
101 } else
102 raw = body.newBoxShape(width, height, depth, 1.0f, 0.5f, 0.0f);
103 std::unique_ptr<physics::Shape3D> shape(raw);
104 if (shape) {
105 const auto rotation = glm::quat(glm::vec3(static_cast<float>(collider.localRotationX),
106 static_cast<float>(collider.localRotationY),
107 static_cast<float>(collider.localRotationZ)));
108 shape->setLocalTransform(
109 static_cast<float>(collider.localX * scale.x), static_cast<float>(collider.localY * scale.y),
110 static_cast<float>(collider.localZ * scale.z), rotation.x, rotation.y, rotation.z, rotation.w);
111 }
112 return shape;
113 }
114
115 explicit Impl(PhysicsPlacementRequest value) : request(std::move(value)), world(0.0f, 0.0f, 0.0f, false) {}
116
118 try {
119 if (request.settings.mode == PhysicsPlacementMode::Fall)
120 world.setGravity(static_cast<float>(request.settings.gravityX),
121 static_cast<float>(request.settings.gravityY),
122 static_cast<float>(request.settings.gravityZ));
123 world.setContinuousCollisionEnabled(true);
124 world.setMaximumLinearSpeed(static_cast<float>(request.settings.maximumLinearSpeed));
125 world.setContactTuning(
126 static_cast<float>(request.settings.softCollision ? request.settings.contactHertz
127 : std::max(120.0, request.settings.contactHertz)),
128 static_cast<float>(request.settings.softCollision ? request.settings.contactDampingRatio : 1.0),
129 static_cast<float>(request.settings.softCollision ? request.settings.maximumPushOutSpeed
130 : std::max(request.settings.maximumLinearSpeed,
131 request.settings.maximumPushOutSpeed)));
132 glm::dvec3 centroid(0.0);
133 std::size_t selectedCount = 0;
134 for (const auto& object : request.objects) {
135 const auto& transform = object.transform;
136 auto* rawBody = world.newBody(object.selected ? "dynamic" : "static", static_cast<float>(transform.x),
137 static_cast<float>(transform.y), static_cast<float>(transform.z));
138 if (!rawBody)
139 return editing::failed<void>(editing::Status::Failed,
140 editing::RuleId("scene.physics-placement.body-create"),
141 "Box3D could not create a placement body");
142 RuntimeObject runtime;
143 runtime.admitted = object;
144 runtime.appliedScale = {transform.scaleX, transform.scaleY, transform.scaleZ};
145 runtime.body.reset(rawBody);
146 const glm::quat rotation = rotationOf(transform);
147 runtime.body->setRotation(rotation.x, rotation.y, rotation.z, rotation.w);
148 for (const auto& collider : selectedColliders(object, request.settings.colliderPolicy)) {
149 auto shape = createShape(*runtime.body, collider, runtime.appliedScale);
150 if (shape) runtime.shapes.push_back(std::move(shape));
151 }
152 if (runtime.shapes.empty())
153 return editing::failed<void>(editing::Status::Failed,
154 editing::RuleId("scene.physics-placement.shape-create"),
155 "Collider policy left the placement object without a usable collider");
156 if (object.selected) {
157 const bool dropping = request.settings.mode == PhysicsPlacementMode::Fall;
158 runtime.body->setGravityScale(dropping ? 1.0f : 0.0f);
159 runtime.body->setLinearDamping(static_cast<float>(dropping ? 0.6 : request.settings.linearDamping));
160 runtime.body->setAngularDamping(
161 static_cast<float>(dropping ? 0.8 : request.settings.angularDamping));
162 runtime.body->setMotionLocks(request.settings.freezePositionX, request.settings.freezePositionY,
163 request.settings.freezePositionZ, request.settings.freezeRotationX,
164 request.settings.freezeRotationY, request.settings.freezeRotationZ);
165 centroid += glm::dvec3(transform.x, transform.y, transform.z);
166 if ((request.primaryObject.empty() && selectedCount == 0) ||
167 request.primaryObject == object.object) {
168 initialHandleRotation = handleRotation = rotation;
169 initialHandleScale = handleScale = runtime.appliedScale;
170 }
171 ++selectedCount;
172 }
173 objects.push_back(std::move(runtime));
174 }
175 centroid /= static_cast<double>(selectedCount);
176 initialHandle = handle = centroid;
177 if (request.settings.preserveSelectionLayout) {
178 for (std::size_t left = 0; left < objects.size(); ++left) {
179 if (!objects[left].admitted.selected) continue;
180 for (std::size_t right = left + 1; right < objects.size(); ++right) {
181 if (objects[right].admitted.selected)
182 world.setBodyPairCollisionEnabled(objects[left].body.get(), objects[right].body.get(),
183 false);
184 }
185 }
186 }
187 return editing::applied<void>();
188 } catch (const std::exception& error) {
189 return editing::failed<void>(editing::Status::Failed, editing::RuleId("scene.physics-placement.build"),
190 std::string("Could not build placement preview: ") + error.what());
191 }
192 }
193
196 std::vector<RuntimeObject> objects;
197 glm::dvec3 initialHandle{0.0};
198 glm::dvec3 handle{0.0};
199 glm::quat initialHandleRotation{1.0f, 0.0f, 0.0f, 0.0f};
200 glm::quat handleRotation{1.0f, 0.0f, 0.0f, 0.0f};
201 glm::dvec3 initialHandleScale{1.0};
202 glm::dvec3 handleScale{1.0};
203 bool surfaceAligned = false;
204 glm::vec3 surfaceNormal{0.0f, 1.0f, 0.0f};
205 std::uint64_t tick = 0;
206};
207
208ScenePhysicsPlacementBackend::ScenePhysicsPlacementBackend(std::unique_ptr<Impl> impl) : impl_(std::move(impl)) {}
210
213 const auto admittedByPolicy = [&](const PhysicsPlacementObject& object) {
214 if (request.admission.excludeDisabled && !object.enabled) return false;
215 if (request.admission.excludeLocked && object.locked) return false;
216 if ((object.layerBits & request.admission.includedLayerBits) == 0 ||
217 (object.layerBits & request.admission.excludedLayerBits) != 0)
218 return false;
219 for (const auto& required : request.admission.requiredTags)
220 if (std::find(object.tags.begin(), object.tags.end(), required) == object.tags.end()) return false;
221 if (request.admission.useBounds) {
222 const auto& value = object.transform;
223 if (value.x < request.admission.minimumX || value.y < request.admission.minimumY ||
224 value.z < request.admission.minimumZ || value.x > request.admission.maximumX ||
225 value.y > request.admission.maximumY || value.z > request.admission.maximumZ)
226 return false;
227 }
228 return true;
229 };
230 for (const auto& object : request.objects)
231 if (object.selected && !admittedByPolicy(object))
232 return editing::failed<std::unique_ptr<ScenePhysicsPlacementBackend>>(
233 editing::Status::Rejected, editing::RuleId("scene.physics-placement.selection-filtered"),
234 "Admission policy must not exclude a selected placement object");
235 request.objects.erase(std::remove_if(request.objects.begin(), request.objects.end(),
236 [&](const auto& object) { return !admittedByPolicy(object); }),
237 request.objects.end());
238 if (request.objects.empty())
239 return editing::failed<std::unique_ptr<ScenePhysicsPlacementBackend>>(
240 editing::Status::Rejected, editing::RuleId("scene.physics-placement.empty"),
241 "Placement requires at least one object");
242 if (request.settings.subStepCount <= 0 || request.settings.subStepCount > 64 ||
243 !finite(request.settings.maximumLinearSpeed) || request.settings.maximumLinearSpeed <= 0.0 ||
244 !finite(request.settings.gravityX) || !finite(request.settings.gravityY) ||
245 !finite(request.settings.gravityZ) || !finite(request.settings.linearDamping) ||
246 request.settings.linearDamping < 0.0 || !finite(request.settings.angularDamping) ||
247 request.settings.angularDamping < 0.0 || !finite(request.settings.contactHertz) ||
248 request.settings.contactHertz <= 0.0 || !finite(request.settings.contactDampingRatio) ||
249 request.settings.contactDampingRatio < 0.0 || !finite(request.settings.maximumPushOutSpeed) ||
250 request.settings.maximumPushOutSpeed < 0.0 || !finite(request.settings.maximumAngularSpeed) ||
251 request.settings.maximumAngularSpeed <= 0.0 || !finite(request.settings.minimumBoundsSpeedFactor) ||
252 request.settings.minimumBoundsSpeedFactor <= 0.0 || !finite(request.settings.maximumBoundsSpeedFactor) ||
253 request.settings.maximumBoundsSpeedFactor < request.settings.minimumBoundsSpeedFactor ||
254 !finite(request.settings.teleportDistance) || request.settings.teleportDistance < 0.0)
255 return editing::failed<std::unique_ptr<ScenePhysicsPlacementBackend>>(
256 editing::Status::Rejected, editing::RuleId("scene.physics-placement.settings"),
257 "Placement settings must be finite and use a substep count in [1, 64]");
258 std::set<editing::ObjectId> ids;
259 std::size_t selectedCount = 0;
260 for (const auto& object : request.objects) {
261 if (object.shape == PhysicsPlacementShape::Auto && object.colliders.empty())
262 return editing::failed<std::unique_ptr<ScenePhysicsPlacementBackend>>(
263 editing::Status::Rejected, editing::RuleId("scene.physics-placement.unresolved-shape"),
264 "Automatic placement shapes must be resolved by a scene editor session");
265 const bool validHull = object.shape != PhysicsPlacementShape::ConvexHull || !object.colliders.empty() ||
266 (object.convexVertices.size() >= 12 && object.convexVertices.size() % 3 == 0 &&
267 object.convexMaxVertices >= 4 && object.convexMaxVertices <= 254 &&
268 std::all_of(object.convexVertices.begin(), object.convexVertices.end(),
269 [](float value) { return std::isfinite(value); }));
270 const auto colliders = Impl::selectedColliders(object, request.settings.colliderPolicy);
271 const bool validColliders =
272 !colliders.empty() && std::all_of(colliders.begin(), colliders.end(), [](const auto& collider) {
273 const bool validGeometry = finite(collider.halfExtentX) && finite(collider.halfExtentY) &&
274 finite(collider.halfExtentZ) && collider.halfExtentX > 0.0 &&
275 collider.halfExtentY > 0.0 && collider.halfExtentZ > 0.0;
276 const bool validPose = finite(collider.localX) && finite(collider.localY) && finite(collider.localZ) &&
277 finite(collider.localRotationX) && finite(collider.localRotationY) &&
278 finite(collider.localRotationZ);
279 const bool hull = collider.shape != PhysicsPlacementShape::ConvexHull ||
280 (collider.convexVertices.size() >= 12 && collider.convexVertices.size() % 3 == 0 &&
281 collider.convexMaxVertices >= 4 && collider.convexMaxVertices <= 254 &&
282 std::all_of(collider.convexVertices.begin(), collider.convexVertices.end(),
283 [](float value) { return std::isfinite(value); }));
284 return collider.shape != PhysicsPlacementShape::Auto && validGeometry && validPose && hull;
285 });
286 if (object.object.empty() || !ids.insert(object.object).second || !finiteTransform(object.transform) ||
287 !finite(object.halfExtentX) || !finite(object.halfExtentY) || !finite(object.halfExtentZ) ||
288 object.halfExtentX <= 0.0 || object.halfExtentY <= 0.0 || object.halfExtentZ <= 0.0 ||
289 object.transform.scaleX == 0.0 || object.transform.scaleY == 0.0 || object.transform.scaleZ == 0.0 ||
290 !validHull || !validColliders)
291 return editing::failed<std::unique_ptr<ScenePhysicsPlacementBackend>>(
292 editing::Status::Rejected, editing::RuleId("scene.physics-placement.object"),
293 "Placement objects require unique ids, finite transforms, and positive nonzero bounds");
294 if (object.selected) ++selectedCount;
295 }
296 if (selectedCount == 0)
297 return editing::failed<std::unique_ptr<ScenePhysicsPlacementBackend>>(
298 editing::Status::Rejected, editing::RuleId("scene.physics-placement.selection"),
299 "Placement requires at least one selected object");
300 if (!request.primaryObject.empty()) {
301 const auto primary = std::find_if(request.objects.begin(), request.objects.end(), [&](const auto& object) {
302 return object.object == request.primaryObject && object.selected;
303 });
304 if (primary == request.objects.end())
305 return editing::failed<std::unique_ptr<ScenePhysicsPlacementBackend>>(
306 editing::Status::Rejected, editing::RuleId("scene.physics-placement.primary"),
307 "Placement primary object must belong to the selected objects");
308 }
309 auto impl = std::make_unique<Impl>(std::move(request));
310 auto built = impl->build();
311 if (!built.ok()) return editing::Result<std::unique_ptr<ScenePhysicsPlacementBackend>>::failure(built.status());
312 return editing::applied<std::unique_ptr<ScenePhysicsPlacementBackend>>(
313 std::unique_ptr<ScenePhysicsPlacementBackend>(new ScenePhysicsPlacementBackend(std::move(impl))));
314}
315
316std::unique_ptr<editor::IEditorSimulationBackend> ScenePhysicsPlacementBackend::cloneForPreview() const {
317 auto cloned = create(impl_->request);
318 if (!cloned.ok()) return nullptr;
319 auto backend = std::move(cloned.value());
320 auto moved = backend->setHandlePosition(impl_->handle.x, impl_->handle.y, impl_->handle.z);
321 if (!moved.ok()) return nullptr;
322 backend->impl_->handleRotation = impl_->handleRotation;
323 backend->impl_->handleScale = impl_->handleScale;
324 backend->impl_->surfaceAligned = impl_->surfaceAligned;
325 backend->impl_->surfaceNormal = impl_->surfaceNormal;
326 return backend;
327}
328
330 if (!finite(x) || !finite(y) || !finite(z))
331 return editing::failed<void>(editing::Status::Rejected, editing::RuleId("scene.physics-placement.handle"),
332 "Placement handle coordinates must be finite");
333 impl_->handle = {x, y, z};
334 return editing::applied<void>();
335}
336
338 if (!finite(x) || !finite(y) || !finite(z))
339 return editing::failed<void>(editing::Status::Rejected,
340 editing::RuleId("scene.physics-placement.handle-rotation"),
341 "Placement handle rotation must be finite");
342 impl_->handleRotation = glm::quat(glm::vec3(static_cast<float>(x), static_cast<float>(y), static_cast<float>(z)));
343 return editing::applied<void>();
344}
345
347 if (!finite(x) || !finite(y) || !finite(z) || x == 0.0 || y == 0.0 || z == 0.0)
348 return editing::failed<void>(editing::Status::Rejected, editing::RuleId("scene.physics-placement.handle-scale"),
349 "Placement handle scale must be finite and nonzero");
350 impl_->handleScale = {x, y, z};
351 return editing::applied<void>();
352}
353
355 double toX, double toY, double toZ,
356 double offset) {
357 if (!finite(fromX) || !finite(fromY) || !finite(fromZ) || !finite(toX) || !finite(toY) || !finite(toZ) ||
358 !finite(offset) || (fromX == toX && fromY == toY && fromZ == toZ))
359 return editing::failed<void>(editing::Status::Rejected, editing::RuleId("scene.physics-placement.surface-ray"),
360 "Surface alignment requires a finite, nonzero ray and offset");
361 try {
362 impl_->world.rayCastAll(static_cast<float>(fromX), static_cast<float>(fromY), static_cast<float>(fromZ),
363 static_cast<float>(toX), static_cast<float>(toY), static_cast<float>(toZ), 256);
364 std::set<int> selectedBodies;
365 for (const auto& object : impl_->objects)
366 if (object.admitted.selected) selectedBodies.insert(object.body->getId());
367 int hit = -1;
368 for (int index = 0; index < impl_->world.getRayResultCount(); ++index) {
369 if (!selectedBodies.contains(impl_->world.getRayResultBodyId(index))) {
370 hit = index;
371 break;
372 }
373 }
374 if (hit < 0)
375 return editing::failed<void>(editing::Status::NotFound,
376 editing::RuleId("scene.physics-placement.surface-miss"),
377 "Surface alignment ray did not hit an admitted static object");
378 glm::vec3 normal(impl_->world.getRayResultNormalX(hit), impl_->world.getRayResultNormalY(hit),
379 impl_->world.getRayResultNormalZ(hit));
380 if (glm::dot(normal, normal) < 1.0e-8f)
381 return editing::failed<void>(editing::Status::Failed,
382 editing::RuleId("scene.physics-placement.surface-normal"),
383 "Surface alignment hit did not provide a usable normal");
384 normal = glm::normalize(normal);
385 const glm::vec3 initialUp = impl_->initialHandleRotation * glm::vec3(0.0f, 1.0f, 0.0f);
386 const glm::quat delta = glm::rotation(glm::normalize(initialUp), normal);
387 impl_->handleRotation = glm::normalize(delta * impl_->initialHandleRotation);
388 float minimumProjection = 0.0f;
389 bool hasProjection = false;
390 for (const auto& object : impl_->objects) {
391 if (!object.admitted.selected) continue;
392 const auto& transform = object.admitted.transform;
393 const glm::vec3 groupOffset = delta * glm::vec3(static_cast<float>(transform.x - impl_->initialHandle.x),
394 static_cast<float>(transform.y - impl_->initialHandle.y),
395 static_cast<float>(transform.z - impl_->initialHandle.z));
396 const glm::quat objectRotation = glm::normalize(delta * rotationOf(transform));
397 const glm::vec3 half(static_cast<float>(object.admitted.halfExtentX * std::abs(transform.scaleX)),
398 static_cast<float>(object.admitted.halfExtentY * std::abs(transform.scaleY)),
399 static_cast<float>(object.admitted.halfExtentZ * std::abs(transform.scaleZ)));
400 for (int mask = 0; mask < 8; ++mask) {
401 const glm::vec3 corner((mask & 1) ? half.x : -half.x, (mask & 2) ? half.y : -half.y,
402 (mask & 4) ? half.z : -half.z);
403 const float projection = glm::dot(groupOffset + objectRotation * corner, normal);
404 if (!hasProjection || projection < minimumProjection) minimumProjection = projection;
405 hasProjection = true;
406 }
407 }
408 const glm::vec3 hitPoint(impl_->world.getRayResultX(hit), impl_->world.getRayResultY(hit),
409 impl_->world.getRayResultZ(hit));
410 const glm::vec3 target = hitPoint + normal * (static_cast<float>(offset) - minimumProjection);
411 impl_->handle = glm::dvec3(target);
412 impl_->surfaceAligned = true;
413 impl_->surfaceNormal = normal;
414 return editing::applied<void>();
415 } catch (const std::exception& error) {
416 return editing::failed<void>(editing::Status::Failed, editing::RuleId("scene.physics-placement.surface-align"),
417 std::string("Surface alignment failed: ") + error.what());
418 }
419}
420
422 if (tick <= impl_->tick || !finite(fixedDelta) || fixedDelta <= 0.0 || fixedDelta > 1.0)
423 return editing::failed<void>(editing::Status::Rejected, editing::RuleId("scene.physics-placement.step"),
424 "Placement ticks must increase and delta must be within (0, 1]");
425 try {
426 if (impl_->request.settings.mode != PhysicsPlacementMode::Fall) {
427 const glm::quat delta = glm::normalize(impl_->handleRotation * glm::inverse(impl_->initialHandleRotation));
428 const glm::dvec3 scaleRatio = impl_->handleScale / impl_->initialHandleScale;
429 double requiredLinearSpeed = impl_->request.settings.maximumLinearSpeed;
430 for (auto& object : impl_->objects) {
431 if (!object.admitted.selected) continue;
432 const auto& transform = object.admitted.transform;
433 const glm::dvec3 desiredScale(transform.scaleX * scaleRatio.x, transform.scaleY * scaleRatio.y,
434 transform.scaleZ * scaleRatio.z);
435 if (desiredScale != object.appliedScale) {
436 const auto colliders =
437 Impl::selectedColliders(object.admitted, impl_->request.settings.colliderPolicy);
438 for (std::size_t index = 0; index < colliders.size(); ++index) {
439 const auto& collider = colliders[index];
440 const float width = static_cast<float>(2.0 * collider.halfExtentX * std::abs(desiredScale.x));
441 const float height = static_cast<float>(2.0 * collider.halfExtentY * std::abs(desiredScale.y));
442 const float depth = static_cast<float>(2.0 * collider.halfExtentZ * std::abs(desiredScale.z));
443 if (collider.shape == PhysicsPlacementShape::Sphere)
444 object.shapes[index]->setSphereRadius(std::max({width, height, depth}) * 0.5f);
445 else if (collider.shape == PhysicsPlacementShape::Capsule)
446 object.shapes[index]->setCapsuleSize(height, std::max(width, depth) * 0.5f);
447 else if (collider.shape == PhysicsPlacementShape::ConvexHull) {
448 auto vertices = collider.convexVertices;
449 for (std::size_t vertex = 0; vertex < vertices.size(); vertex += 3) {
450 vertices[vertex] *= static_cast<float>(desiredScale.x);
451 vertices[vertex + 1] *= static_cast<float>(desiredScale.y);
452 vertices[vertex + 2] *= static_cast<float>(desiredScale.z);
453 }
454 object.shapes[index]->setConvexHullVertices(vertices, collider.convexMaxVertices);
455 } else
456 object.shapes[index]->setBoxSize(width, height, depth);
457 const auto localRotation = glm::quat(glm::vec3(static_cast<float>(collider.localRotationX),
458 static_cast<float>(collider.localRotationY),
459 static_cast<float>(collider.localRotationZ)));
460 object.shapes[index]->setLocalTransform(static_cast<float>(collider.localX * desiredScale.x),
461 static_cast<float>(collider.localY * desiredScale.y),
462 static_cast<float>(collider.localZ * desiredScale.z),
463 localRotation.x, localRotation.y, localRotation.z,
464 localRotation.w);
465 }
466 object.appliedScale = desiredScale;
467 }
468 const glm::dvec3 unrotatedOffset((transform.x - impl_->initialHandle.x) * scaleRatio.x,
469 (transform.y - impl_->initialHandle.y) * scaleRatio.y,
470 (transform.z - impl_->initialHandle.z) * scaleRatio.z);
471 const glm::vec3 offset = delta * glm::vec3(unrotatedOffset);
472 const glm::quat rotation = glm::normalize(delta * rotationOf(transform));
473 glm::dvec3 target(static_cast<double>(offset.x) + impl_->handle.x,
474 static_cast<double>(offset.y) + impl_->handle.y,
475 static_cast<double>(offset.z) + impl_->handle.z);
476 glm::quat targetRotation = rotation;
477 if (impl_->request.settings.mode == PhysicsPlacementMode::Rotate)
478 target = glm::dvec3(offset) + impl_->initialHandle;
479 if (impl_->request.settings.mode == PhysicsPlacementMode::Point) {
480 target = {transform.x, transform.y, transform.z};
481 const glm::vec3 direction = glm::vec3(impl_->handle - target);
482 if (glm::dot(direction, direction) > 1.0e-8f) {
483 const glm::vec3 forward = glm::normalize(direction);
484 const glm::vec3 up = std::abs(glm::dot(forward, glm::vec3(0.0f, 1.0f, 0.0f))) > 0.99f
485 ? glm::vec3(0.0f, 0.0f, 1.0f)
486 : glm::vec3(0.0f, 1.0f, 0.0f);
487 targetRotation = glm::quatLookAt(forward, up);
488 }
489 }
490 const glm::dvec3 current(object.body->getX(), object.body->getY(), object.body->getZ());
491 const double bound = std::max({object.admitted.halfExtentX * 2.0, object.admitted.halfExtentY * 2.0,
492 object.admitted.halfExtentZ * 2.0});
493 const double boundFactor = impl_->request.settings.scaleSpeedByBounds
494 ? std::clamp(bound, impl_->request.settings.minimumBoundsSpeedFactor,
495 impl_->request.settings.maximumBoundsSpeedFactor)
496 : 1.0;
497 requiredLinearSpeed =
498 std::max(requiredLinearSpeed, glm::length(target - current) / fixedDelta * 1.1 * boundFactor);
499 if (impl_->request.settings.teleportDistance > 0.0 &&
500 glm::length(target - current) > impl_->request.settings.teleportDistance) {
501 object.body->setPosition(static_cast<float>(target.x), static_cast<float>(target.y),
502 static_cast<float>(target.z));
503 object.body->setRotation(targetRotation.x, targetRotation.y, targetRotation.z, targetRotation.w);
504 object.body->setLinearVelocity(0.0f, 0.0f, 0.0f);
505 object.body->setAngularVelocity(0.0f, 0.0f, 0.0f);
506 continue;
507 }
508 object.body->setTargetTransform(static_cast<float>(target.x), static_cast<float>(target.y),
509 static_cast<float>(target.z), targetRotation.x, targetRotation.y,
510 targetRotation.z, targetRotation.w, static_cast<float>(fixedDelta));
511 }
512 impl_->world.setMaximumLinearSpeed(static_cast<float>(requiredLinearSpeed));
513 }
514 auto duration = eve::Duration::fromSeconds(fixedDelta);
515 if (!duration.ok()) return editing::Result<void>::failure(duration.status());
517 settings.subStepCount = impl_->request.settings.subStepCount;
518 auto stepped = impl_->world.step({eve::SimulationTick{tick}, duration.value()}, settings);
519 if (!stepped.ok()) return editing::Result<void>::failure(stepped.status());
520 for (auto& object : impl_->objects) {
521 if (!object.admitted.selected) continue;
522 glm::vec3 angular(object.body->getAngularVelocityX(), object.body->getAngularVelocityY(),
523 object.body->getAngularVelocityZ());
524 const float length = glm::length(angular);
525 if (length > impl_->request.settings.maximumAngularSpeed) {
526 angular *= static_cast<float>(impl_->request.settings.maximumAngularSpeed) / length;
527 object.body->setAngularVelocity(angular.x, angular.y, angular.z);
528 }
529 }
530 impl_->tick = tick;
531 return editing::applied<void>();
532 } catch (const std::exception& error) {
533 return editing::failed<void>(editing::Status::Failed, editing::RuleId("scene.physics-placement.advance"),
534 std::string("Placement preview failed to advance: ") + error.what());
535 }
536}
537
539 std::vector<editor::SimulationObjectSample> samples;
540 samples.reserve(impl_->objects.size());
541 for (const auto& object : impl_->objects) {
542 if (!object.admitted.selected) continue;
544 sample.object = object.admitted.object.value();
545 sample.positionX = object.body->getX();
546 sample.positionY = object.body->getY();
547 sample.positionZ = object.body->getZ();
548 sample.rotationX = object.body->getRotX();
549 sample.rotationY = object.body->getRotY();
550 sample.rotationZ = object.body->getRotZ();
551 sample.rotationW = object.body->getRotW();
552 samples.push_back(std::move(sample));
553 }
554 return editing::applied<std::vector<editor::SimulationObjectSample>>(std::move(samples));
555}
556
559 frame.tick = impl_->tick;
560 frame.colliding = impl_->world.getContactCount() > 0;
561 frame.settled = frame.colliding && frame.tick > 2;
562 frame.surfaceAligned = impl_->surfaceAligned;
563 frame.surfaceNormalX = impl_->surfaceNormal.x;
564 frame.surfaceNormalY = impl_->surfaceNormal.y;
565 frame.surfaceNormalZ = impl_->surfaceNormal.z;
566 frame.objects.reserve(impl_->objects.size());
567 for (const auto& object : impl_->objects) {
568 PhysicsPlacementObject captured = object.admitted;
569 captured.transform.x = object.body->getX();
570 captured.transform.y = object.body->getY();
571 captured.transform.z = object.body->getZ();
572 const glm::quat rotation(object.body->getRotW(), object.body->getRotX(), object.body->getRotY(),
573 object.body->getRotZ());
574 const glm::vec3 euler = glm::eulerAngles(rotation);
575 captured.transform.rotationX = euler.x;
576 captured.transform.rotationY = euler.y;
577 captured.transform.rotationZ = euler.z;
578 captured.transform.scaleX = object.appliedScale.x;
579 captured.transform.scaleY = object.appliedScale.y;
580 captured.transform.scaleZ = object.appliedScale.z;
581 if (captured.selected && (std::abs(object.body->getLinearVelocityX()) > 0.05f ||
582 std::abs(object.body->getLinearVelocityY()) > 0.05f ||
583 std::abs(object.body->getLinearVelocityZ()) > 0.05f ||
584 std::abs(object.body->getAngularVelocityX()) > 0.05f ||
585 std::abs(object.body->getAngularVelocityY()) > 0.05f ||
586 std::abs(object.body->getAngularVelocityZ()) > 0.05f))
587 frame.settled = false;
588 frame.objects.push_back(std::move(captured));
589 }
590 return editing::applied<PhysicsPlacementFrame>(std::move(frame));
591}
592
594 return impl_->request.sourceRevision;
595}
596
597} // namespace eve::scene_editor
LogicalId target
double value
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
float duration
int mask
void * impl
float length
Definition CaveMesh.cpp:94
std::map< std::string, Var > values
const GltfImportRequest & request
ShaderImageInput shape
glm::uvec4 ids
HexVec3 up
HexVec3 left
HexVec3 right
std::uint32_t height
std::uint32_t width
size_t offset
std::array< float, 4 > rotation
std::array< float, 3 > scale
bool required
Vec3 centroid
Texture * normal
bool finite
std::string error
Definition Package.cpp:60
World3D * world
std::vector< Point > vertices
bool hit
PrimitiveHandle handle
RoadLaneDirection direction
double current
Backend-neutral, observable fixed-step contract for physics domains.
SimulationTick tick
TerrainThermalSettings settings
Json object
Common time values and an injectable deterministic simulation clock.
std::string body
uint32_t index
std::uint32_t depth
static Result< Duration > fromSeconds(double seconds)
Convert finite seconds to the nearest nanosecond.
Definition Time.cpp:16
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
UUID-backed identifier adapter for legacy textual boundaries.
Definition Identity.h:314
constexpr std::uint64_t value() const noexcept
Returns the underlying value at an explicit protocol boundary.
3D rigid body (Box3D) in meter-space coordinates (+Y up by convention). Owned by a World3D; create pr...
Definition Body3D.h:24
3D shape (box/sphere/capsule) attached to a Body3D with material settings. Created via Body3D::new*Sh...
Definition Shape3D.h:25
Box3D rigid-body world. Script coordinates are meters (Box3D native), unlike 2D World which uses pixe...
Definition World3D.h:63
editing::Result< PhysicsPlacementFrame > placementFrame() const
Capture all admitted objects, retaining bounds and selection metadata.
editing::Revision sourceRevision() const noexcept
Revision copied from the scene when this candidate was admitted.
editing::Result< std::vector< editor::SimulationObjectSample > > capture() const override
Capture stable selected-object transforms for generic preview tooling.
editing::Result< void > alignHandleToSurface(double fromX, double fromY, double fromZ, double toX, double toY, double toZ, double offset=0.0)
Align selection up to the nearest non-selected ray hit and place it outside the surface.
std::unique_ptr< editor::IEditorSimulationBackend > cloneForPreview() const override
Rebuild an independent preview with the same admitted scene and handle.
editing::Result< void > setHandlePosition(double x, double y, double z)
Move the shared world-space placement handle without advancing simulation.
editing::Result< void > setHandleScale(double x, double y, double z)
Scale the shared placement handle, resizing selected colliders without advancing simulation.
editing::Result< void > step(std::uint64_t tick, double fixedDelta) override
Advance exactly one increasing fixed tick.
~ScenePhysicsPlacementBackend() override
Scene physics placement backend.
static editing::Result< std::unique_ptr< ScenePhysicsPlacementBackend > > create(PhysicsPlacementRequest request)
Validate and build an isolated placement world.
editing::Result< void > setHandleRotation(double x, double y, double z)
Rotate the shared placement handle using XYZ Euler radians without advancing simulation.
std::uint64_t Revision
StrongId< RuleIdTag > RuleId
Definition EditingIds.h:60
Result< T > failed(Status status, eve::DiagnosticCode code, RuleId rule, std::string message)
Construct a failed editing result with explicit status and diagnostic categories.
PhysicsPlacementColliderPolicy
Policy deciding which admitted collider sources participate in preview.
Stable transform sample captured from one simulation object.
Validated solver policy for one simulation step.
int subStepCount
Positive specialized-solver substep count.
One local-space shape in a possibly compound placement collider.
Captured placement state suitable for drawing and final transaction planning.
std::vector< PhysicsPlacementObject > objects
Scene object and collision approximation admitted into an isolated placement preview.
scene_editing::SceneTransformValue transform
Owning request used to construct one isolated placement candidate.
static std::vector< PhysicsPlacementCollider > selectedColliders(const PhysicsPlacementObject &object, PhysicsPlacementColliderPolicy policy)
static std::unique_ptr< physics::Shape3D > createShape(physics::Body3D &body, const PhysicsPlacementCollider &collider, const glm::dvec3 &scale)