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");
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;
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>()),
46 else if (
kind ==
"circle")
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");
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);
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");
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");
75 const std::string asset = *propertyValue<std::string>(*
values,
"shape.asset");
76 auto geometry = assets.resolve(asset,
kind);
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>());
86 physics::Fixture* fixture =
nullptr;
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")
93 else if (
kind ==
"chain")
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());
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);
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");
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;
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")
135 else if (
kind ==
"capsule")
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");
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);
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");
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");
167 const std::string asset = *propertyValue<std::string>(*
values,
"shape.asset");
168 auto geometry = assets.resolve(asset,
kind);
170 physics::Shape3D*
shape =
nullptr;
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());
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);
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();
213 if (diagnostic.severity() == DiagnosticSeverity::Error)
216 const EditorValue snapshot = candidate.snapshotValue();
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");
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"),
238 Result<physics::Shape3D*> built = assets_
242 if (previousBodyType != desiredBodyType) {
244 body_->setType(previousBodyType);
246 auto diagnostics = built.diagnostics();
249 DiagnosticSeverity::Error,
250 "Collider build failed and the previous body type could not be restored"));
257 physics::Shape3D* replacement = built.value();
258 if (current_ && current_->isValid()) current_->destroy();
259 current_ = replacement;
260 return eve::editing::applied<void>();
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();
272 if (diagnostic.severity() == DiagnosticSeverity::Error)
275 const EditorValue snapshot = candidate.snapshotValue();
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();
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"),
289 Result<physics::Fixture*> built = assets_
293 if (desiredType != previousType) body_->setType(previousType);
296 if (current_ && current_->raw()) current_->destroy();
297 current_ = built.value();
298 return eve::editing::applied<void>();
std::map< std::string, Var > values
std::vector< Point > vertices
static Result failure(Status status)
Construct a failed result from a structured status.
Structured status and zero or more diagnostics for an operation.
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