载入中...
搜索中...
未找到
SceneEditorScriptBindings.cpp
浏览该文件的文档.
4#include "scene/Scene.h"
8
9#include <simplesquirrel/simplesquirrel.hpp>
10
11namespace eve::scene_editor {
12namespace {
13const editing::Value* field(const editing::Value& value, const char* key) {
14 const auto* object = value.getIf<editing::Value::Object>();
15 if (!object) return nullptr;
16 const auto found = object->find(key);
17 return found == object->end() ? nullptr : &found->second;
18}
19
20bool number(const editing::Value& value, double& output) {
21 if (const auto* real = value.getIf<double>())
22 output = *real;
23 else if (const auto* integer = value.getIf<std::int64_t>())
24 output = static_cast<double>(*integer);
25 else
26 return false;
27 return true;
28}
29
30editing::Result<PhysicsPlacementCollider> placementCollider(const editing::Value& value) {
31 PhysicsPlacementCollider result;
32 const auto* shapeValue = field(value, "shape");
33 const auto* shape = shapeValue ? shapeValue->getIf<std::string>() : nullptr;
34 if (!shape || (*shape != "box" && *shape != "sphere" && *shape != "capsule" && *shape != "convex"))
35 return editing::failed<PhysicsPlacementCollider>(editing::Status::Rejected,
36 editing::RuleId("scene.physics-placement.script-part-shape"),
37 "Collider part shape is unsupported");
38 if (*shape == "sphere")
39 result.shape = PhysicsPlacementShape::Sphere;
40 else if (*shape == "capsule")
41 result.shape = PhysicsPlacementShape::Capsule;
42 else if (*shape == "convex")
44 if (const auto* sourceValue = field(value, "source")) {
45 const auto* source = sourceValue->getIf<std::string>();
46 if (!source || (*source != "existing" && *source != "generated"))
47 return editing::failed<PhysicsPlacementCollider>(
48 editing::Status::Rejected, editing::RuleId("scene.physics-placement.script-part-source"),
49 "Collider part source must be existing or generated");
50 result.source = *source == "existing" ? PhysicsPlacementColliderSource::Existing
52 }
53 const auto parseVector = [&](const char* key, double& x, double& y, double& z, bool required) {
54 const auto* candidate = field(value, key);
55 if (!candidate) return !required;
56 const auto* array = candidate->getIf<editing::Value::Array>();
57 return array && array->size() == 3 && number((*array)[0], x) && number((*array)[1], y) &&
58 number((*array)[2], z);
59 };
60 if (!parseVector("halfExtents", result.halfExtentX, result.halfExtentY, result.halfExtentZ, true) ||
61 !parseVector("localPosition", result.localX, result.localY, result.localZ, false) ||
62 !parseVector("localRotation", result.localRotationX, result.localRotationY, result.localRotationZ, false))
63 return editing::failed<PhysicsPlacementCollider>(editing::Status::Rejected,
64 editing::RuleId("scene.physics-placement.script-part-vector"),
65 "Collider part vectors require three numbers");
66 if (const auto* verticesValue = field(value, "vertices")) {
67 const auto* vertices = verticesValue->getIf<editing::Value::Array>();
68 if (!vertices)
69 return editing::failed<PhysicsPlacementCollider>(
70 editing::Status::Rejected, editing::RuleId("scene.physics-placement.script-part-vertices"),
71 "Collider part vertices must be an array");
72 for (const auto& component : *vertices) {
73 double numeric = 0.0;
74 if (!number(component, numeric))
75 return editing::failed<PhysicsPlacementCollider>(
76 editing::Status::Rejected, editing::RuleId("scene.physics-placement.script-part-vertex"),
77 "Collider part vertices must be numeric");
78 result.convexVertices.push_back(static_cast<float>(numeric));
79 }
80 }
81 if (const auto* maximumValue = field(value, "maxHullVertices")) {
82 const auto* maximum = maximumValue->getIf<std::int64_t>();
83 if (!maximum)
84 return editing::failed<PhysicsPlacementCollider>(
85 editing::Status::Rejected, editing::RuleId("scene.physics-placement.script-part-budget"),
86 "Collider part maxHullVertices must be an integer");
87 result.convexMaxVertices = static_cast<int>(*maximum);
88 }
89 return editing::applied<PhysicsPlacementCollider>(std::move(result));
90}
91
92editing::Result<PhysicsPlacementRequest> placementRequest(const editing::Value& payload) {
93 const auto* objectValues = field(payload, "objects");
94 const auto* objects = objectValues ? objectValues->getIf<editing::Value::Array>() : nullptr;
95 if (!objects)
96 return editing::failed<PhysicsPlacementRequest>(editing::Status::Rejected,
97 editing::RuleId("scene.physics-placement.script-payload"),
98 "Physics placement requires an objects array");
99 PhysicsPlacementRequest request;
100 if (const auto* primaryValue = field(payload, "primary")) {
101 const auto* primary = primaryValue->getIf<std::string>();
102 if (!primary)
103 return editing::failed<PhysicsPlacementRequest>(editing::Status::Rejected,
104 editing::RuleId("scene.physics-placement.script-primary"),
105 "Physics placement primary must be an object id");
106 request.primaryObject = editing::ObjectId(*primary);
107 }
108 if (const auto* modeValue = field(payload, "mode")) {
109 const auto* mode = modeValue->getIf<std::string>();
110 if (!mode || (*mode != "drag" && *mode != "place" && *mode != "drop" && *mode != "fall" && *mode != "rotate" &&
111 *mode != "align" && *mode != "point"))
112 return editing::failed<PhysicsPlacementRequest>(editing::Status::Rejected,
113 editing::RuleId("scene.physics-placement.script-mode"),
114 "Physics placement mode is unsupported");
115 if (*mode == "drop" || *mode == "fall")
116 request.settings.mode = PhysicsPlacementMode::Fall;
117 else if (*mode == "rotate")
119 else if (*mode == "align")
120 request.settings.mode = PhysicsPlacementMode::Align;
121 else if (*mode == "point")
122 request.settings.mode = PhysicsPlacementMode::Point;
123 else
124 request.settings.mode = PhysicsPlacementMode::Place;
125 }
126 if (const auto* settingsValue = field(payload, "settings")) {
127 if (!settingsValue->getIf<editing::Value::Object>())
128 return editing::failed<PhysicsPlacementRequest>(editing::Status::Rejected,
129 editing::RuleId("scene.physics-placement.script-settings"),
130 "Physics placement settings must be an object");
131 const auto assign = [&](const char* key, double& destination) {
132 const auto* value = field(*settingsValue, key);
133 return !value || number(*value, destination);
134 };
135 if (!assign("gravityX", request.settings.gravityX) || !assign("gravityY", request.settings.gravityY) ||
136 !assign("gravityZ", request.settings.gravityZ) ||
137 !assign("maximumLinearSpeed", request.settings.maximumLinearSpeed) ||
138 !assign("linearDamping", request.settings.linearDamping) ||
139 !assign("angularDamping", request.settings.angularDamping) ||
140 !assign("contactHertz", request.settings.contactHertz) ||
141 !assign("contactDampingRatio", request.settings.contactDampingRatio) ||
142 !assign("maximumPushOutSpeed", request.settings.maximumPushOutSpeed) ||
143 !assign("maximumAngularSpeed", request.settings.maximumAngularSpeed) ||
144 !assign("minimumBoundsSpeedFactor", request.settings.minimumBoundsSpeedFactor) ||
145 !assign("maximumBoundsSpeedFactor", request.settings.maximumBoundsSpeedFactor) ||
146 !assign("teleportDistance", request.settings.teleportDistance))
147 return editing::failed<PhysicsPlacementRequest>(
148 editing::Status::Rejected, editing::RuleId("scene.physics-placement.script-setting-number"),
149 "Physics placement numeric settings must contain numbers");
150 if (const auto* subStepsValue = field(*settingsValue, "subStepCount")) {
151 const auto* subSteps = subStepsValue->getIf<std::int64_t>();
152 if (!subSteps)
153 return editing::failed<PhysicsPlacementRequest>(
154 editing::Status::Rejected, editing::RuleId("scene.physics-placement.script-substeps"),
155 "Physics placement subStepCount must be an integer");
156 request.settings.subStepCount = static_cast<int>(*subSteps);
157 }
158 if (const auto* preserveValue = field(*settingsValue, "preserveSelectionLayout")) {
159 const auto* preserve = preserveValue->getIf<bool>();
160 if (!preserve)
161 return editing::failed<PhysicsPlacementRequest>(
162 editing::Status::Rejected, editing::RuleId("scene.physics-placement.script-layout"),
163 "Physics placement preserveSelectionLayout must be boolean");
164 request.settings.preserveSelectionLayout = *preserve;
165 }
166 const auto assignBool = [&](const char* key, bool& destination) {
167 const auto* value = field(*settingsValue, key);
168 if (!value) return true;
169 const auto* boolean = value->getIf<bool>();
170 if (!boolean) return false;
171 destination = *boolean;
172 return true;
173 };
174 if (!assignBool("freezePositionX", request.settings.freezePositionX) ||
175 !assignBool("freezePositionY", request.settings.freezePositionY) ||
176 !assignBool("freezePositionZ", request.settings.freezePositionZ) ||
177 !assignBool("freezeRotationX", request.settings.freezeRotationX) ||
178 !assignBool("freezeRotationY", request.settings.freezeRotationY) ||
179 !assignBool("freezeRotationZ", request.settings.freezeRotationZ) ||
180 !assignBool("scaleSpeedByBounds", request.settings.scaleSpeedByBounds) ||
181 !assignBool("softCollision", request.settings.softCollision))
182 return editing::failed<PhysicsPlacementRequest>(
183 editing::Status::Rejected, editing::RuleId("scene.physics-placement.script-setting-bool"),
184 "Physics placement boolean settings must be boolean");
185 if (const auto* policyValue = field(*settingsValue, "colliderPolicy")) {
186 const auto* policy = policyValue->getIf<std::string>();
187 if (!policy || (*policy != "existingOnly" && *policy != "generatedWhenMissing" &&
188 *policy != "generatedOnly" && *policy != "existingAndGenerated"))
189 return editing::failed<PhysicsPlacementRequest>(
190 editing::Status::Rejected, editing::RuleId("scene.physics-placement.script-collider-policy"),
191 "Physics placement colliderPolicy is unsupported");
192 if (*policy == "existingOnly")
194 else if (*policy == "generatedOnly")
196 else if (*policy == "existingAndGenerated")
198 else
200 }
201 }
202 if (const auto* admissionValue = field(payload, "admission")) {
203 if (!admissionValue->getIf<editing::Value::Object>())
204 return editing::failed<PhysicsPlacementRequest>(editing::Status::Rejected,
205 editing::RuleId("scene.physics-placement.script-admission"),
206 "Physics placement admission must be an object");
207 for (const auto& [key, destination] : std::initializer_list<std::pair<const char*, std::uint64_t*>>{
208 {"includedLayerBits", &request.admission.includedLayerBits},
209 {"excludedLayerBits", &request.admission.excludedLayerBits}}) {
210 if (const auto* candidate = field(*admissionValue, key)) {
211 const auto* integer = candidate->getIf<std::int64_t>();
212 if (!integer || *integer < 0)
213 return editing::failed<PhysicsPlacementRequest>(
214 editing::Status::Rejected, editing::RuleId("scene.physics-placement.script-admission-layer"),
215 "Admission layer masks must be non-negative integers");
216 *destination = static_cast<std::uint64_t>(*integer);
217 }
218 }
219 for (const auto& [key, destination] : std::initializer_list<std::pair<const char*, bool*>>{
220 {"excludeDisabled", &request.admission.excludeDisabled},
221 {"excludeLocked", &request.admission.excludeLocked}}) {
222 if (const auto* candidate = field(*admissionValue, key)) {
223 const auto* boolean = candidate->getIf<bool>();
224 if (!boolean)
225 return editing::failed<PhysicsPlacementRequest>(
226 editing::Status::Rejected, editing::RuleId("scene.physics-placement.script-admission-flag"),
227 "Admission flags must be boolean");
228 *destination = *boolean;
229 }
230 }
231 if (const auto* tagsValue = field(*admissionValue, "requiredTags")) {
232 const auto* tags = tagsValue->getIf<editing::Value::Array>();
233 if (!tags)
234 return editing::failed<PhysicsPlacementRequest>(
235 editing::Status::Rejected, editing::RuleId("scene.physics-placement.script-admission-tags"),
236 "Admission requiredTags must be an array");
237 for (const auto& candidate : *tags) {
238 const auto* tag = candidate.getIf<std::string>();
239 if (!tag)
240 return editing::failed<PhysicsPlacementRequest>(
241 editing::Status::Rejected, editing::RuleId("scene.physics-placement.script-admission-tag"),
242 "Admission requiredTags must contain strings");
243 request.admission.requiredTags.push_back(*tag);
244 }
245 }
246 const auto* minimumValue = field(*admissionValue, "minimum");
247 const auto* maximumValue = field(*admissionValue, "maximum");
248 if (minimumValue || maximumValue) {
249 const auto* minimum = minimumValue ? minimumValue->getIf<editing::Value::Array>() : nullptr;
250 const auto* maximum = maximumValue ? maximumValue->getIf<editing::Value::Array>() : nullptr;
251 if (!minimum || !maximum || minimum->size() != 3 || maximum->size() != 3 ||
252 !number((*minimum)[0], request.admission.minimumX) ||
253 !number((*minimum)[1], request.admission.minimumY) ||
254 !number((*minimum)[2], request.admission.minimumZ) ||
255 !number((*maximum)[0], request.admission.maximumX) ||
256 !number((*maximum)[1], request.admission.maximumY) ||
257 !number((*maximum)[2], request.admission.maximumZ))
258 return editing::failed<PhysicsPlacementRequest>(
259 editing::Status::Rejected, editing::RuleId("scene.physics-placement.script-admission-bounds"),
260 "Admission bounds require minimum and maximum numeric vectors");
261 request.admission.useBounds = true;
262 }
263 }
264 for (const auto& value : *objects) {
265 const auto* idValue = field(value, "object");
266 const auto* id = idValue ? idValue->getIf<std::string>() : nullptr;
267 const auto* selectedValue = field(value, "selected");
268 const auto* selected = selectedValue ? selectedValue->getIf<bool>() : nullptr;
269 const auto* boundsValue = field(value, "halfExtents");
270 const auto* bounds = boundsValue ? boundsValue->getIf<editing::Value::Array>() : nullptr;
271 PhysicsPlacementObject entry;
272 if (!id || !selected || !bounds || bounds->size() != 3 || !number((*bounds)[0], entry.halfExtentX) ||
273 !number((*bounds)[1], entry.halfExtentY) || !number((*bounds)[2], entry.halfExtentZ))
274 return editing::failed<PhysicsPlacementRequest>(
275 editing::Status::Rejected, editing::RuleId("scene.physics-placement.script-object"),
276 "Placement entries require object, selected and three half extents");
277 entry.object = editing::ObjectId(*id);
278 entry.selected = *selected;
279 if (const auto* shapeValue = field(value, "shape")) {
280 const auto* shape = shapeValue->getIf<std::string>();
281 if (!shape || (*shape != "box" && *shape != "sphere" && *shape != "capsule" && *shape != "convex" &&
282 *shape != "auto"))
283 return editing::failed<PhysicsPlacementRequest>(
284 editing::Status::Rejected, editing::RuleId("scene.physics-placement.script-shape"),
285 "Placement shape must be box, sphere, capsule, convex or auto");
286 if (*shape == "sphere") entry.shape = PhysicsPlacementShape::Sphere;
287 if (*shape == "capsule") entry.shape = PhysicsPlacementShape::Capsule;
288 if (*shape == "convex") entry.shape = PhysicsPlacementShape::ConvexHull;
289 if (*shape == "auto") entry.shape = PhysicsPlacementShape::Auto;
290 }
291 if (const auto* verticesValue = field(value, "vertices")) {
292 const auto* vertices = verticesValue->getIf<editing::Value::Array>();
293 if (!vertices)
294 return editing::failed<PhysicsPlacementRequest>(
295 editing::Status::Rejected, editing::RuleId("scene.physics-placement.script-vertices"),
296 "Convex placement vertices must be a numeric array");
297 entry.convexVertices.reserve(vertices->size());
298 for (const auto& vertex : *vertices) {
299 double component = 0.0;
300 if (!number(vertex, component))
301 return editing::failed<PhysicsPlacementRequest>(
302 editing::Status::Rejected, editing::RuleId("scene.physics-placement.script-vertex"),
303 "Convex placement vertices must be numeric");
304 entry.convexVertices.push_back(static_cast<float>(component));
305 }
306 }
307 if (const auto* maximumValue = field(value, "maxHullVertices")) {
308 const auto* maximum = maximumValue->getIf<std::int64_t>();
309 if (!maximum)
310 return editing::failed<PhysicsPlacementRequest>(
311 editing::Status::Rejected, editing::RuleId("scene.physics-placement.script-hull-budget"),
312 "Convex hull vertex budget must be an integer");
313 entry.convexMaxVertices = static_cast<int>(*maximum);
314 }
315 if (const auto* keyValue = field(value, "colliderResourceKey")) {
316 const auto* key = keyValue->getIf<std::string>();
317 if (!key)
318 return editing::failed<PhysicsPlacementRequest>(
319 editing::Status::Rejected, editing::RuleId("scene.physics-placement.script-resource-key"),
320 "colliderResourceKey must be a string");
321 entry.colliderResourceKey = *key;
322 }
323 if (const auto* layerValue = field(value, "layerBits")) {
324 const auto* layer = layerValue->getIf<std::int64_t>();
325 if (!layer || *layer < 0)
326 return editing::failed<PhysicsPlacementRequest>(editing::Status::Rejected,
327 editing::RuleId("scene.physics-placement.script-layer"),
328 "layerBits must be a non-negative integer");
329 entry.layerBits = static_cast<std::uint64_t>(*layer);
330 }
331 for (const auto& [key, destination] : std::initializer_list<std::pair<const char*, bool*>>{
332 {"enabled", &entry.enabled}, {"locked", &entry.locked}}) {
333 if (const auto* candidate = field(value, key)) {
334 const auto* boolean = candidate->getIf<bool>();
335 if (!boolean)
336 return editing::failed<PhysicsPlacementRequest>(
337 editing::Status::Rejected, editing::RuleId("scene.physics-placement.script-object-flag"),
338 "Placement object flags must be boolean");
339 *destination = *boolean;
340 }
341 }
342 if (const auto* tagsValue = field(value, "tags")) {
343 const auto* tags = tagsValue->getIf<editing::Value::Array>();
344 if (!tags)
345 return editing::failed<PhysicsPlacementRequest>(editing::Status::Rejected,
346 editing::RuleId("scene.physics-placement.script-tags"),
347 "Placement tags must be an array");
348 for (const auto& candidate : *tags) {
349 const auto* tag = candidate.getIf<std::string>();
350 if (!tag)
351 return editing::failed<PhysicsPlacementRequest>(
352 editing::Status::Rejected, editing::RuleId("scene.physics-placement.script-tag"),
353 "Placement tags must contain strings");
354 entry.tags.push_back(*tag);
355 }
356 }
357 if (const auto* collidersValue = field(value, "colliders")) {
358 const auto* colliders = collidersValue->getIf<editing::Value::Array>();
359 if (!colliders)
360 return editing::failed<PhysicsPlacementRequest>(
361 editing::Status::Rejected, editing::RuleId("scene.physics-placement.script-colliders"),
362 "Placement colliders must be an array");
363 for (const auto& colliderValue : *colliders) {
364 auto collider = placementCollider(colliderValue);
365 if (!collider.ok()) return editing::Result<PhysicsPlacementRequest>::failure(collider.status());
366 entry.colliders.push_back(std::move(collider.value()));
367 }
368 }
369 request.objects.push_back(std::move(entry));
370 }
371 return editing::applied<PhysicsPlacementRequest>(std::move(request));
372}
373
374editing::Value placementValue(const PhysicsPlacementFrame& frame) {
375 editing::Value::Array objects;
376 for (const auto& entry : frame.objects) {
377 const auto& value = entry.transform;
378 objects.emplace_back(editing::Value::Object{
379 {"object", entry.object.value()},
380 {"selected", entry.selected},
381 {"position", editing::Value::Array{value.x, value.y, value.z}},
382 {"rotation", editing::Value::Array{value.rotationX, value.rotationY, value.rotationZ}},
383 {"scale", editing::Value::Array{value.scaleX, value.scaleY, value.scaleZ}},
384 });
385 }
387 {"tick", static_cast<std::int64_t>(frame.tick)},
388 {"colliding", frame.colliding},
389 {"settled", frame.settled},
390 {"surfaceAligned", frame.surfaceAligned},
391 {"surfaceNormal", editing::Value::Array{frame.surfaceNormalX, frame.surfaceNormalY, frame.surfaceNormalZ}},
392 {"objects", std::move(objects)}};
393}
394} // namespace
395
396void exposeSceneEditorSessions(ssq::Table& table, ssq::Class& module) {
397 const auto vm = table.getHandle();
398 auto cls = table.addClass<SceneEditorSession>(
399 "SceneEditorSession", std::function<SceneEditorSession*()>([]() -> SceneEditorSession* { return nullptr; }),
400 true);
401 const auto project = [vm](editing::Result<editing::TransactionReceipt> result) {
402 return script::projectStatusResult(vm, result.status());
403 };
404 cls.addFunc("execute", [vm, project](SceneEditorSession* self, std::string command, ssq::Object payload) {
406 if (!value.ok()) return script::projectStatusResult(vm, value.status());
407 return project(self->execute(std::move(command), editing::toEditingValue(value.value())));
408 });
409 cls.addFunc("undo", [project](SceneEditorSession* self) { return project(self->undo()); });
410 cls.addFunc("restrictCommands", [vm](SceneEditorSession* self, ssq::Array commands) {
411 std::vector<std::string> ids;
412 for (std::size_t i = 0; i < commands.size(); ++i) ids.push_back(commands.get<std::string>(i));
413 auto result = self->restrictCommands(ids);
414 return script::projectStatusResult(vm, result.status());
415 });
416 cls.addFunc("redo", [project](SceneEditorSession* self) { return project(self->redo()); });
417 cls.addFunc("saveJson", &SceneEditorSession::saveJson);
418 cls.addFunc("restoreJson", [project](SceneEditorSession* self, const std::string& json) {
419 return project(self->restoreJson(json));
420 });
421 cls.addFunc("snapshot", [vm](SceneEditorSession* self) {
424 });
425 cls.addFunc("getRevision", &SceneEditorSession::revision);
426 cls.addFunc("cachePhysicsPlacementHull", [vm](SceneEditorSession* self, const std::string& object,
427 ssq::Array vertices, int maxVertices) {
428 std::vector<float> copied;
429 copied.reserve(vertices.size());
430 for (std::size_t index = 0; index < vertices.size(); ++index) copied.push_back(vertices.get<float>(index));
431 auto result = self->cachePhysicsPlacementHull(editing::ObjectId(object), std::move(copied), maxVertices);
432 return script::projectStatusResult(vm, result.status());
433 });
434 cls.addFunc("cachePhysicsPlacementCompound", [vm](SceneEditorSession* self, const std::string& object,
435 const std::string& resourceKey, ssq::Array parts) {
437 if (!value.ok()) return script::projectStatusResult(vm, value.status());
438 auto editingValue = editing::toEditingValue(value.value());
439 const auto* values = editingValue.getIf<editing::Value::Array>();
440 std::vector<PhysicsPlacementCollider> colliders;
441 if (values) {
442 colliders.reserve(values->size());
443 for (const auto& partValue : *values) {
444 auto part = placementCollider(partValue);
445 if (!part.ok()) return script::projectStatusResult(vm, part.status());
446 colliders.push_back(std::move(part.value()));
447 }
448 }
449 auto result = self->cachePhysicsPlacementCompound(editing::ObjectId(object), resourceKey, std::move(colliders));
450 return script::projectStatusResult(vm, result.status());
451 });
452 cls.addFunc("savePhysicsPlacementColliderCacheJson", &SceneEditorSession::savePhysicsPlacementColliderCacheJson);
453 cls.addFunc("restorePhysicsPlacementColliderCacheJson", [vm](SceneEditorSession* self, const std::string& json) {
454 auto result = self->restorePhysicsPlacementColliderCacheJson(json);
455 return script::projectStatusResult(vm, result.status());
456 });
457 cls.addFunc("removePhysicsPlacementHull", [vm](SceneEditorSession* self, const std::string& object) {
458 auto result = self->removePhysicsPlacementHull(editing::ObjectId(object));
459 return script::projectStatusResult(vm, result.status());
460 });
461 cls.addFunc("beginPhysicsPlacement", [vm](SceneEditorSession* self, ssq::Object payload) {
463 if (!value.ok()) return script::projectStatusResult(vm, value.status());
464 auto request = placementRequest(editing::toEditingValue(value.value()));
465 if (!request.ok()) return script::projectStatusResult(vm, request.status());
466 auto result = self->beginPhysicsPlacement(std::move(request.value()));
467 return script::projectStatusResult(vm, result.status());
468 });
469 cls.addFunc("updatePhysicsPlacement", [vm](SceneEditorSession* self, float x, float y, float z, float dt) {
470 auto result = self->updatePhysicsPlacement(x, y, z, dt);
471 if (!result.ok()) return script::projectStatusResult(vm, result.status());
472 return script::projectStatusResult(vm, result.status(),
473 editing::toPresentationValue(placementValue(result.value())));
474 });
475 cls.addFunc("updatePhysicsPlacementPose", [vm](SceneEditorSession* self, float x, float y, float z, float rotationX,
476 float rotationY, float rotationZ, float dt) {
477 auto result = self->updatePhysicsPlacementPose(x, y, z, rotationX, rotationY, rotationZ, dt);
478 if (!result.ok()) return script::projectStatusResult(vm, result.status());
479 return script::projectStatusResult(vm, result.status(),
480 editing::toPresentationValue(placementValue(result.value())));
481 });
482 cls.addFunc("updatePhysicsPlacementTransform",
483 [vm](SceneEditorSession* self, ssq::Array position, ssq::Array rotation, ssq::Array scale, float dt) {
484 if (position.size() != 3 || rotation.size() != 3 || scale.size() != 3)
487 "Placement transform arrays require three values")));
488 auto result = self->updatePhysicsPlacementTransform(
489 position.get<float>(0), position.get<float>(1), position.get<float>(2), rotation.get<float>(0),
490 rotation.get<float>(1), rotation.get<float>(2), scale.get<float>(0), scale.get<float>(1),
491 scale.get<float>(2), dt);
492 if (!result.ok()) return script::projectStatusResult(vm, result.status());
493 return script::projectStatusResult(vm, result.status(),
494 editing::toPresentationValue(placementValue(result.value())));
495 });
496 cls.addFunc("alignPhysicsPlacementToSurface",
497 [vm](SceneEditorSession* self, ssq::Array from, ssq::Array to, float offset, float dt) {
498 if (from.size() != 3 || to.size() != 3)
501 "Surface alignment endpoints require three values")));
502 auto result = self->alignPhysicsPlacementToSurface(from.get<float>(0), from.get<float>(1),
503 from.get<float>(2), to.get<float>(0),
504 to.get<float>(1), to.get<float>(2), offset, dt);
505 if (!result.ok()) return script::projectStatusResult(vm, result.status());
506 return script::projectStatusResult(vm, result.status(),
507 editing::toPresentationValue(placementValue(result.value())));
508 });
509 cls.addFunc("commitPhysicsPlacement",
510 [project](SceneEditorSession* self) { return project(self->commitPhysicsPlacement()); });
511 cls.addFunc("cancelPhysicsPlacement", [vm](SceneEditorSession* self) {
512 auto result = self->cancelPhysicsPlacement();
513 return script::projectStatusResult(vm, result.status());
514 });
515 cls.addFunc("isPhysicsPlacementActive", [](SceneEditorSession* self) { return self->physicsPlacementActive(); });
516 module.addFunc("createSession", [vm](SceneEditorModule*, const std::string& id) {
517 if (id.empty())
518 return script::projectStatusResult(
519 vm, Status::failure(
520 Diagnostic::error(DiagnosticCode::InvalidArgument, "Scene target id must not be empty")));
521 auto object =
522 script::makeOwnedSquirrelInstance<SceneEditorSession>(vm, std::make_unique<SceneEditorSession>(id));
523 if (!object.ok()) return script::projectStatusResult(vm, object.status());
525 result.set("value", std::move(object).takeValue());
526 result.set("ownership", std::string("owned"));
527 return result;
528 });
529 module.addFunc("createLiveSession", [vm](SceneEditorModule*, const std::string& id, const std::string& hostName) {
530 auto* scene = ModuleManager::getInstance<scene::Scene>("Scene");
531 if (!scene || id.empty())
535 "Live scene editing requires Scene module and a nonempty target id")));
536 auto host = scene->findHost(hostName);
537 if (!host.ok()) return script::projectStatusResult(vm, host.status());
538 auto object = script::makeOwnedSquirrelInstance<SceneEditorSession>(
539 vm, std::make_unique<SceneEditorSession>(
540 std::make_unique<scene_editing::SceneHostEditorTarget>(id, host.value())));
541 if (!object.ok()) return script::projectStatusResult(vm, object.status());
543 result.set("value", std::move(object).takeValue());
544 result.set("ownership", std::string("owned"));
545 return result;
546 });
547}
548} // namespace eve::scene_editor
double value
Value::Object payload
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
std::vector< eve::artifact::PartView > parts
std::string output
std::string from
HSQUIRRELVM vm
Definition ECS.cpp:20
HSQOBJECT cls
Definition ECS.cpp:21
std::map< std::string, Var > values
float maximum[3]
float minimum[3]
const GltfImportRequest & request
std::uint32_t key
ShaderImageInput shape
wgpu::PopErrorScopeStatus status
glm::uvec4 ids
HexCoordinates to
Cell the unit walks towards on this segment.
Definition HexUnits.cpp:64
size_t offset
std::array< float, 4 > rotation
std::array< float, 3 > position
std::array< float, 3 > scale
bool required
const std::string * tag
TileLayer * layer
std::vector< Point > vertices
std::weak_ptr< PrimitiveScene > scene
double number
bool found
bool boolean
The single Squirrel projection for common Result, Status and Value.
Explicit Value/Owned/Borrowed semantics at the Squirrel boundary.
UIHostHandle host
uint32_t index
const UnitySourceAsset & source
std::vector< VegetationPresetCommand > commands
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
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
static Status failure(StatusCode code, Diagnostic diagnostic)
Construct a failed status with one diagnostic.
Definition Status.h:84
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
UUID-backed identifier adapter for legacy textual boundaries.
Definition Identity.h:314
const T * getIf() const noexcept
Return a typed pointer, or nullptr when the kind differs.
Definition Value.h:180
std::vector< Value > Array
std::map< std::string, Value > Object
const T * getIf() const
Return an immediately borrowed typed value.
Composable scene command/history host with no UI or renderer dependency. Owns its target,...
editing::Result< editing::TransactionReceipt > redo()
Reapply the last undone scene transaction.
editing::Result< void > cachePhysicsPlacementCompound(const editing::ObjectId &object, std::string resourceKey, std::vector< PhysicsPlacementCollider > colliders)
Replace one object's generated compound collider cache entry.
editing::Result< PhysicsPlacementFrame > alignPhysicsPlacementToSurface(double fromX, double fromY, double fromZ, double toX, double toY, double toZ, double offset=0.0, double fixedDelta=1.0/60.0)
Align the active selection to a non-selected preview surface and advance one step.
editing::Result< void > removePhysicsPlacementHull(const editing::ObjectId &object)
Remove one cached automatic placement hull; missing entries return NoOp.
editing::Result< PhysicsPlacementFrame > updatePhysicsPlacementPose(double x, double y, double z, double rotationX, double rotationY, double rotationZ, double fixedDelta=1.0/60.0)
Move and rotate the preview handle, then advance one fixed simulation step.
editing::Result< void > restrictCommands(const std::vector< std::string > &commands)
Restrict future commands to a host allow-list; empty denies all. Existing history remains undoable.
editing::Result< editing::TransactionReceipt > undo()
Undo the last scene transaction; failures preserve history.
std::string savePhysicsPlacementColliderCacheJson() const
Encode schema-2 generated collider cache; host owns file I/O and the returned string.
editing::Result< editing::TransactionReceipt > commitPhysicsPlacement()
Atomically publish selected preview transforms as one undoable transaction.
editing::Result< editing::TransactionReceipt > execute(std::string command, editing::Value payload)
Execute a registered scene command through revision checks and one reversible transaction.
editing::Result< editing::TransactionReceipt > restoreJson(const std::string &json)
Parse and restore schema-1 JSON as an undoable transaction; invalid input changes nothing.
editing::Value snapshot() const
Copy the versioned hierarchy/TRS snapshot; caller owns the returned tree.
editing::Result< PhysicsPlacementFrame > updatePhysicsPlacement(double x, double y, double z, double fixedDelta=1.0/60.0)
Move the preview handle and advance one fixed simulation step.
editing::Revision revision() const
Return the current monotonic target revision.
std::string saveJson() const
Encode snapshot deterministically; file I/O and paths belong to the host.
editing::Result< void > beginPhysicsPlacement(PhysicsPlacementRequest request)
Begin an isolated Box3D placement preview using current scene transforms.
editing::Result< void > restorePhysicsPlacementColliderCacheJson(const std::string &json)
Atomically replace generated collider cache from schema-2 JSON.
bool physicsPlacementActive() const noexcept
Report whether this session owns an active placement preview.
editing::Result< void > cancelPhysicsPlacement()
Discard the isolated preview without changing scene state.
editing::Result< PhysicsPlacementFrame > updatePhysicsPlacementTransform(double x, double y, double z, double rotationX, double rotationY, double rotationZ, double scaleX, double scaleY, double scaleZ, double fixedDelta=1.0/60.0)
Move, rotate and scale the preview handle, then advance one fixed simulation step.
editing::Result< void > cachePhysicsPlacementHull(const editing::ObjectId &object, std::vector< float > vertices, int maxVertices=64)
Cache backend-neutral local-space mesh points for later automatic convex placement.
eve::Value toPresentationValue(const Value &value)
To presentation value.
StrongId< ObjectIdTag > ObjectId
Definition EditingIds.h:65
StrongId< RuleIdTag > RuleId
Definition EditingIds.h:60
Value toEditingValue(const eve::Value &value)
To editing value.
const EditorValue * field(const EditorValue &value, const char *name)
void exposeSceneEditorSessions(ssq::Table &table, ssq::Class &module)
Register owned, UI-neutral scene editing sessions in the supplied VM; owner-thread only.
eve::Value boundsValue(const eve::artifact::Bounds &bounds)
ssq::Table projectStatusResult(HSQUIRRELVM vm, const Status &status)
Project a checked native status that carries no payload.
Result< Value > valueFromSquirrel(HSQUIRRELVM vm, SQInteger index, const SquirrelValueOptions &options)
Convert one Squirrel value into the canonical owning Value tree.
int64_t integer(const RuntimeTensor &v, size_t i=0)
Integer.
glm::vec4 bounds