载入中...
搜索中...
未找到
PhysicsRuntime.cpp
浏览该文件的文档.
2
3#include "physics/Body.h"
4#include "physics/Body3D.h"
5#include "physics/Fixture.h"
6#include "physics/Shape3D.h"
7
8#include <utility>
9
10namespace eve::physics_editing {
11namespace {
12
13template <class T>
14const T* propertyValue(const EditorValue::Object& values, const char* path) {
15 const auto found = values.find(path);
16 return found == values.end() ? nullptr : found->second.getIf<T>();
17}
18
19} // namespace
20
21Result<physics::Fixture*> PhysicsColliderRuntimeBuilder::build2D(const PhysicsColliderTarget& target,
22 physics::Body* body) const {
23 if (!body || target.describe().type != "physics-collider-2d")
24 return eve::editing::failed<physics::Fixture*>(EditorStatus::Rejected,
25 RuleId("editor.physics.runtime-2d-input"),
26 "A 2D collider target and body are required");
27 const EditorValue snapshot = target.snapshotValue();
28 const auto* root = snapshot.getIf<EditorValue::Object>();
29 const auto* values = root ? root->at("properties").getIf<EditorValue::Object>() : nullptr;
30 if (!values)
31 return eve::editing::failed<physics::Fixture*>(
32 EditorStatus::Failed, RuleId("editor.physics.runtime-properties"), "Collider properties are unavailable");
33 const std::string kind = *propertyValue<std::string>(*values, "shape.kind");
34 const auto* size = propertyValue<EditorValue::Array>(*values, "shape.size");
35 const auto* offset = propertyValue<EditorValue::Array>(*values, "shape.offset");
36 const float radius = static_cast<float>(*propertyValue<double>(*values, "shape.radius"));
37 const float density = static_cast<float>(*propertyValue<double>(*values, "material.density"));
38 const float friction = static_cast<float>(*propertyValue<double>(*values, "material.friction"));
39 const float restitution = static_cast<float>(*propertyValue<double>(*values, "material.restitution"));
40 physics::Fixture* fixture = nullptr;
41 if (kind == "box")
42 fixture = body->newRectangleFixtureAt(
43 static_cast<float>(*(*size)[0].getIf<double>()), static_cast<float>(*(*size)[1].getIf<double>()),
44 static_cast<float>(*(*offset)[0].getIf<double>()), static_cast<float>(*(*offset)[1].getIf<double>()),
45 density, friction, restitution);
46 else if (kind == "circle")
47 fixture = body->newCircleFixture(radius, density, friction, restitution);
48 else
49 return eve::editing::failed<physics::Fixture*>(EditorStatus::Unsupported,
50 RuleId("editor.physics.runtime-2d-asset-shape"),
51 "Polygon and chain colliders require an asset resolver");
52 if (!fixture)
53 return eve::editing::failed<physics::Fixture*>(EditorStatus::Failed, RuleId("editor.physics.runtime-create"),
54 "Physics backend failed to create the fixture");
55 fixture->setSensor(*propertyValue<bool>(*values, "shape.sensor"));
56 fixture->setCategoryBits(static_cast<int>(*propertyValue<int64_t>(*values, "collision.category")));
57 fixture->setMaskBits(static_cast<int>(*propertyValue<int64_t>(*values, "collision.mask")));
58 return eve::editing::applied<physics::Fixture*>(fixture);
59}
60
61Result<physics::Fixture*> PhysicsColliderRuntimeBuilder::build2D(
62 const PhysicsColliderTarget& target, physics::Body* body, const IPhysicsColliderAssetResolver& assets) const {
63 if (!body || target.describe().type != "physics-collider-2d")
64 return eve::editing::failed<physics::Fixture*>(EditorStatus::Rejected,
65 RuleId("editor.physics.runtime-2d-input"),
66 "A 2D collider target and body are required");
67 const EditorValue snapshot = target.snapshotValue();
68 const auto* root = snapshot.getIf<EditorValue::Object>();
69 const auto* values = root ? root->at("properties").getIf<EditorValue::Object>() : nullptr;
70 if (!values)
71 return eve::editing::failed<physics::Fixture*>(
72 EditorStatus::Failed, RuleId("editor.physics.runtime-properties"), "Collider properties are unavailable");
73 const std::string kind = *propertyValue<std::string>(*values, "shape.kind");
74 if (kind == "box" || kind == "circle") return build2D(target, body);
75 const std::string asset = *propertyValue<std::string>(*values, "shape.asset");
76 auto geometry = assets.resolve(asset, kind);
77 if (!geometry.ok()) return Result<physics::Fixture*>::failure(geometry.status());
78 std::vector<float> vertices = geometry.value().vertices;
79 const auto* offset = propertyValue<EditorValue::Array>(*values, "shape.offset");
80 const float offsetX = static_cast<float>(*(*offset)[0].getIf<double>());
81 const float offsetY = static_cast<float>(*(*offset)[1].getIf<double>());
82 for (size_t index = 0; index < vertices.size(); index += 2) {
84 vertices[index + 1] += offsetY;
85 }
86 physics::Fixture* fixture = nullptr;
87 try {
88 const float density = static_cast<float>(*propertyValue<double>(*values, "material.density"));
89 const float friction = static_cast<float>(*propertyValue<double>(*values, "material.friction"));
90 const float restitution = static_cast<float>(*propertyValue<double>(*values, "material.restitution"));
91 if (kind == "polygon")
92 fixture = body->newPolygonFixture(vertices, density, friction, restitution);
93 else if (kind == "chain")
94 fixture = body->newChainFixture(vertices, geometry.value().loop, friction, restitution);
95 } catch (const std::exception& exception) {
96 return eve::editing::failed<physics::Fixture*>(
97 EditorStatus::Rejected, RuleId("editor.physics.runtime-2d-asset-shape"), exception.what());
98 }
99 if (!fixture)
100 return eve::editing::failed<physics::Fixture*>(EditorStatus::Failed, RuleId("editor.physics.runtime-create"),
101 "Physics backend failed to create the 2D asset fixture");
102 fixture->setSensor(*propertyValue<bool>(*values, "shape.sensor"));
103 fixture->setCategoryBits(static_cast<int>(*propertyValue<int64_t>(*values, "collision.category")));
104 fixture->setMaskBits(static_cast<int>(*propertyValue<int64_t>(*values, "collision.mask")));
105 return eve::editing::applied<physics::Fixture*>(fixture);
106}
107
108Result<physics::Shape3D*> PhysicsColliderRuntimeBuilder::build3D(const PhysicsColliderTarget& target,
109 physics::Body3D* body) const {
110 if (!body || target.describe().type != "physics-collider-3d")
111 return eve::editing::failed<physics::Shape3D*>(EditorStatus::Rejected,
112 RuleId("editor.physics.runtime-3d-input"),
113 "A 3D collider target and body are required");
114 const EditorValue snapshot = target.snapshotValue();
115 const auto* root = snapshot.getIf<EditorValue::Object>();
116 const auto* values = root ? root->at("properties").getIf<EditorValue::Object>() : nullptr;
117 if (!values)
118 return eve::editing::failed<physics::Shape3D*>(
119 EditorStatus::Failed, RuleId("editor.physics.runtime-properties"), "Collider properties are unavailable");
120 const std::string kind = *propertyValue<std::string>(*values, "shape.kind");
121 const auto* size = propertyValue<EditorValue::Array>(*values, "shape.size");
122 const auto* offset = propertyValue<EditorValue::Array>(*values, "shape.offset");
123 const float radius = static_cast<float>(*propertyValue<double>(*values, "shape.radius"));
124 const float capsuleHeight = static_cast<float>(*propertyValue<double>(*values, "shape.capsule-height"));
125 const float density = static_cast<float>(*propertyValue<double>(*values, "material.density"));
126 const float friction = static_cast<float>(*propertyValue<double>(*values, "material.friction"));
127 const float restitution = static_cast<float>(*propertyValue<double>(*values, "material.restitution"));
128 physics::Shape3D* shape = nullptr;
129 if (kind == "box")
130 shape = body->newBoxShape(static_cast<float>(*(*size)[0].getIf<double>()),
131 static_cast<float>(*(*size)[1].getIf<double>()),
132 static_cast<float>(*(*size)[2].getIf<double>()), density, friction, restitution);
133 else if (kind == "sphere")
134 shape = body->newSphereShape(radius, density, friction, restitution);
135 else if (kind == "capsule")
136 shape = body->newCapsuleShape(capsuleHeight, radius, density, friction, restitution);
137 else
138 return eve::editing::failed<physics::Shape3D*>(
139 EditorStatus::Unsupported, RuleId("editor.physics.runtime-3d-asset-shape"),
140 "Mesh, hull and height-field colliders require an asset resolver");
141 if (!shape)
142 return eve::editing::failed<physics::Shape3D*>(EditorStatus::Failed, RuleId("editor.physics.runtime-create"),
143 "Physics backend failed to create the shape");
144 shape->setLocalPosition(static_cast<float>(*(*offset)[0].getIf<double>()),
145 static_cast<float>(*(*offset)[1].getIf<double>()),
146 static_cast<float>(*(*offset)[2].getIf<double>()));
147 shape->setSensor(*propertyValue<bool>(*values, "shape.sensor"));
148 shape->setFilterBits(static_cast<uint64_t>(*propertyValue<int64_t>(*values, "collision.category")),
149 static_cast<uint64_t>(*propertyValue<int64_t>(*values, "collision.mask")));
150 return eve::editing::applied<physics::Shape3D*>(shape);
151}
152
153Result<physics::Shape3D*> PhysicsColliderRuntimeBuilder::build3D(
154 const PhysicsColliderTarget& target, physics::Body3D* body, const IPhysicsColliderAssetResolver& assets) const {
155 if (!body || target.describe().type != "physics-collider-3d")
156 return eve::editing::failed<physics::Shape3D*>(EditorStatus::Rejected,
157 RuleId("editor.physics.runtime-3d-input"),
158 "A 3D collider target and body are required");
159 const EditorValue snapshot = target.snapshotValue();
160 const auto* root = snapshot.getIf<EditorValue::Object>();
161 const auto* values = root ? root->at("properties").getIf<EditorValue::Object>() : nullptr;
162 if (!values)
163 return eve::editing::failed<physics::Shape3D*>(
164 EditorStatus::Failed, RuleId("editor.physics.runtime-properties"), "Collider properties are unavailable");
165 const std::string kind = *propertyValue<std::string>(*values, "shape.kind");
166 if (kind == "box" || kind == "sphere" || kind == "capsule") return build3D(target, body);
167 const std::string asset = *propertyValue<std::string>(*values, "shape.asset");
168 auto geometry = assets.resolve(asset, kind);
169 if (!geometry.ok()) return Result<physics::Shape3D*>::failure(geometry.status());
170 physics::Shape3D* shape = nullptr;
171 try {
172 const float density = static_cast<float>(*propertyValue<double>(*values, "material.density"));
173 const float friction = static_cast<float>(*propertyValue<double>(*values, "material.friction"));
174 const float restitution = static_cast<float>(*propertyValue<double>(*values, "material.restitution"));
175 if (kind == "convex-hull")
176 shape = body->newConvexHullShape(geometry.value().vertices, 64, density, friction, restitution);
177 else if (kind == "triangle-mesh")
178 shape = body->newTriangleMeshShape(geometry.value().vertices, geometry.value().indices);
179 else if (kind == "height-field")
180 shape = body->newHeightFieldShape(geometry.value().countX, geometry.value().countZ,
181 geometry.value().cellSizeX, geometry.value().cellSizeZ,
182 geometry.value().heights, geometry.value().minimumHeight,
183 geometry.value().maximumHeight);
184 } catch (const std::exception& exception) {
185 return eve::editing::failed<physics::Shape3D*>(
186 EditorStatus::Rejected, RuleId("editor.physics.runtime-complex-shape"), exception.what());
187 }
188 if (!shape)
189 return eve::editing::failed<physics::Shape3D*>(EditorStatus::Failed, RuleId("editor.physics.runtime-create"),
190 "Physics backend failed to create the resolved shape");
191 shape->setDensity(static_cast<float>(*propertyValue<double>(*values, "material.density")));
192 shape->setFriction(static_cast<float>(*propertyValue<double>(*values, "material.friction")));
193 shape->setRestitution(static_cast<float>(*propertyValue<double>(*values, "material.restitution")));
194 const auto* offset = propertyValue<EditorValue::Array>(*values, "shape.offset");
195 shape->setLocalPosition(static_cast<float>(*(*offset)[0].getIf<double>()),
196 static_cast<float>(*(*offset)[1].getIf<double>()),
197 static_cast<float>(*(*offset)[2].getIf<double>()));
198 shape->setSensor(*propertyValue<bool>(*values, "shape.sensor"));
199 shape->setFilterBits(static_cast<uint64_t>(*propertyValue<int64_t>(*values, "collision.category")),
200 static_cast<uint64_t>(*propertyValue<int64_t>(*values, "collision.mask")));
201 return eve::editing::applied<physics::Shape3D*>(shape);
202}
203
204Result<void> PhysicsCollider3DRuntimeSink::publish(const PhysicsColliderTarget& candidate) {
205 if (!body_ || !body_->isValid() || candidate.describe().type != "physics-collider-3d")
206 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.physics.live-3d-input"),
207 "A live Body3D and 3D collider candidate are required");
208 if (current_ && current_->isValid() && current_->getBody() != body_)
209 return eve::editing::failed<void>(EditorStatus::Conflict, RuleId("editor.physics.live-shape-owner"),
210 "Current collider shape belongs to another body");
211 const auto diagnostics = candidate.validate();
212 for (const EditorDiagnostic& diagnostic : diagnostics) {
213 if (diagnostic.severity() == DiagnosticSeverity::Error)
214 return Result<void>::failure(eve::Status(EditorStatus::Rejected, diagnostics));
215 }
216 const EditorValue snapshot = candidate.snapshotValue();
217 const auto* root = snapshot.getIf<EditorValue::Object>();
218 const auto* values = root ? root->at("properties").getIf<EditorValue::Object>() : nullptr;
219 if (!values)
220 return eve::editing::failed<void>(EditorStatus::Failed, RuleId("editor.physics.live-properties"),
221 "Collider properties are unavailable");
222 const std::string kind = *propertyValue<std::string>(*values, "shape.kind");
223 const std::string desiredBodyType = *propertyValue<std::string>(*values, "body.type");
224 const std::string previousBodyType = body_->getType();
225 const bool currentRequiresStatic = current_ && current_->isValid() &&
226 (current_->getKind() == "triangleMesh" || current_->getKind() == "heightField");
227 if (currentRequiresStatic && desiredBodyType != "static" && desiredBodyType != previousBodyType)
228 return eve::editing::failed<void>(EditorStatus::Unsupported,
229 RuleId("editor.physics.live-complex-type-transition"),
230 "Swap the complex collider to a primitive before changing its body type");
231 try {
232 if (previousBodyType != desiredBodyType) body_->setType(desiredBodyType);
233 } catch (const std::exception& exception) {
234 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.physics.live-body-type"),
235 exception.what());
236 }
237
238 Result<physics::Shape3D*> built = assets_
239 ? PhysicsColliderRuntimeBuilder().build3D(candidate, body_, *assets_)
240 : PhysicsColliderRuntimeBuilder().build3D(candidate, body_);
241 if (!built.ok()) {
242 if (previousBodyType != desiredBodyType) {
243 try {
244 body_->setType(previousBodyType);
245 } catch (...) {
246 auto diagnostics = built.diagnostics();
247 diagnostics.push_back(eve::editing::ruleDiagnostic(
248 eve::DiagnosticCode::Failed, RuleId("editor.physics.live-rollback-body-type"),
249 DiagnosticSeverity::Error,
250 "Collider build failed and the previous body type could not be restored"));
251 return Result<void>::failure(eve::Status(EditorStatus::Failed, std::move(diagnostics)));
252 }
253 }
254 return Result<void>::failure(built.status());
255 }
256
257 physics::Shape3D* replacement = built.value();
258 if (current_ && current_->isValid()) current_->destroy();
259 current_ = replacement;
260 return eve::editing::applied<void>();
261}
262
263Result<void> PhysicsCollider2DRuntimeSink::publish(const PhysicsColliderTarget& candidate) {
264 if (!body_ || !body_->raw() || candidate.describe().type != "physics-collider-2d")
265 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.physics.live-2d-input"),
266 "A live Body and 2D collider candidate are required");
267 if (current_ && current_->raw() && current_->getBody() != body_)
268 return eve::editing::failed<void>(EditorStatus::Conflict, RuleId("editor.physics.live-fixture-owner"),
269 "Current fixture belongs to another body");
270 const auto diagnostics = candidate.validate();
271 for (const EditorDiagnostic& diagnostic : diagnostics) {
272 if (diagnostic.severity() == DiagnosticSeverity::Error)
273 return Result<void>::failure(eve::Status(EditorStatus::Rejected, diagnostics));
274 }
275 const EditorValue snapshot = candidate.snapshotValue();
276 const auto* root = snapshot.getIf<EditorValue::Object>();
277 const auto* values = root ? root->at("properties").getIf<EditorValue::Object>() : nullptr;
278 if (!values)
279 return eve::editing::failed<void>(EditorStatus::Failed, RuleId("editor.physics.live-properties"),
280 "Collider properties are unavailable");
281 const std::string desiredType = *propertyValue<std::string>(*values, "body.type");
282 const std::string previousType = body_->getType();
283 try {
284 if (desiredType != previousType) body_->setType(desiredType);
285 } catch (const std::exception& exception) {
286 return eve::editing::failed<void>(EditorStatus::Rejected, RuleId("editor.physics.live-body-type"),
287 exception.what());
288 }
289 Result<physics::Fixture*> built = assets_
290 ? PhysicsColliderRuntimeBuilder().build2D(candidate, body_, *assets_)
291 : PhysicsColliderRuntimeBuilder().build2D(candidate, body_);
292 if (!built.ok()) {
293 if (desiredType != previousType) body_->setType(previousType);
294 return Result<void>::failure(built.status());
295 }
296 if (current_ && current_->raw()) current_->destroy();
297 current_ = built.value();
298 return eve::editing::applied<void>();
299}
300
301} // namespace eve::physics_editing
LogicalId target
int root
Definition AnimSmr.cpp:119
std::map< std::string, Var > values
ShaderImageInput shape
TokenKind kind
size_t offset
std::vector< Point > vertices
float radius
std::string path
Definition PlayHost.cpp:110
bool found
double restitution
float offsetX
float offsetY
float size
Definition TreeMesh.cpp:156
std::string body
uint32_t index
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
Structured status and zero or more diagnostics for an operation.
Definition Status.h:68
std::map< std::string, Value > Object
const T * getIf() const
Return an immediately borrowed typed value.
Diagnostic ruleDiagnostic(eve::DiagnosticCode code, RuleId rule, DiagnosticSeverity severity, std::string message)
Build a common diagnostic carrying an open editing rule identity.
eve::physics_editing::PhysicsColliderRuntimeBuilder PhysicsColliderRuntimeBuilder
editing::Diagnostic EditorDiagnostic
editing::Value EditorValue