载入中...
搜索中...
未找到
Scene.cpp
浏览该文件的文档.
1#include "scene/Scene.h"
2
3#include "scene/SceneBounds.h"
7
8#include "spatial/Octree.h"
9
10#ifdef EVENGINE_SCENE_JSON
11#include <Poco/JSON/Array.h>
12#include <Poco/JSON/Object.h>
13#include <Poco/JSON/Parser.h>
14#include <Poco/JSON/Stringifier.h>
15#endif
16
17#include <simplesquirrel/simplesquirrel.hpp>
18
19#include <glm/glm.hpp>
20#include <glm/gtc/matrix_transform.hpp>
21#include <glm/gtc/quaternion.hpp>
22
23#include <cstring>
24#include <limits>
25#include <sstream>
26#include <utility>
27#include <stdexcept>
28
29namespace eve::scene {
30
32
34
35namespace {
36
37SceneHost *findHostByName(const std::string &name) {
38 if (name.empty()) return nullptr;
39 if (ecs::current()->getManager<SceneHost>() == nullptr) return nullptr;
40 auto view = ecs::View<SceneHost, SceneHost::Meta>();
41 for (auto it = view.begin(); it != view.end(); ++it) {
42 auto [meta] = *it;
43 if (meta->entity && meta->name == name) return meta->entity;
44 }
45 return nullptr;
46}
47
48SceneHost *findHostByOwnerId(uint32_t ownerId) {
49 if (ownerId == 0) return nullptr;
50 if (ecs::current()->getManager<SceneHost>() == nullptr) return nullptr;
51 auto view = ecs::View<SceneHost, SceneHost::Meta>();
52 for (auto it = view.begin(); it != view.end(); ++it) {
53 auto [meta] = *it;
54 if (meta->entity && meta->ownerId == ownerId) return meta->entity;
55 }
56 return nullptr;
57}
58
60void setStringField(HSQUIRRELVM vm, HSQOBJECT inst, const char *name,
61 const std::string &value) {
62 if (!vm || inst._type != OT_INSTANCE) return;
63 const SQInteger top = sq_gettop(vm);
64 sq_pushobject(vm, inst);
65 sq_pushstring(vm, name, -1);
66 sq_pushstring(vm, value.c_str(), -1);
67 if (SQ_FAILED(sq_set(vm, -3))) {
68 sq_settop(vm, top);
69 sq_pushobject(vm, inst);
70 sq_pushstring(vm, name, -1);
71 sq_pushstring(vm, value.c_str(), -1);
72 sq_newslot(vm, -3, SQFalse);
73 }
74 sq_settop(vm, top);
75}
76
77bool sameScriptObject(const HSQOBJECT &a, const HSQOBJECT &b) {
78 return a._type == b._type &&
79 std::memcmp(&a._unVal, &b._unVal, sizeof(a._unVal)) == 0;
80}
81
82// --- JSON serialization helpers ---
83
84#ifdef EVENGINE_SCENE_JSON
85Poco::JSON::Object::Ptr nodeToJson(const SceneHost::Tree &tree, const SceneNode *n) {
86 Poco::JSON::Object::Ptr o = new Poco::JSON::Object;
87 o->set("id", n->id);
88 o->set("key", n->key);
89 o->set("name", n->name);
90 o->set("space", n->space);
91 o->set("visible", n->visible);
92 o->set("layer", n->layer);
93 o->set("x", n->x);
94 o->set("y", n->y);
95 o->set("z", n->z);
96 o->set("yaw", n->yaw);
97 o->set("pitch", n->pitch);
98 o->set("roll", n->roll);
99 o->set("sx", n->sx);
100 o->set("sy", n->sy);
101 o->set("sz", n->sz);
102 if (n->hasBounds) {
103 o->set("bminX", n->bminX);
104 o->set("bminY", n->bminY);
105 o->set("bminZ", n->bminZ);
106 o->set("bmaxX", n->bmaxX);
107 o->set("bmaxY", n->bmaxY);
108 o->set("bmaxZ", n->bmaxZ);
109 }
110 if (!n->tags.empty()) {
111 Poco::JSON::Array::Ptr arr = new Poco::JSON::Array;
112 for (const auto &t : n->tags) arr->add(t);
113 o->set("tags", arr);
114 }
115 if (n->firstChild >= 0) {
116 Poco::JSON::Array::Ptr kids = new Poco::JSON::Array;
117 for (int c = n->firstChild; c >= 0;
118 c = tree.nodes[size_t(c)].nextSibling) {
119 kids->add(nodeToJson(tree, &tree.nodes[size_t(c)]));
120 }
121 o->set("children", kids);
122 }
123 return o;
124}
125
126NodeDesc nodeFromJson(const Poco::JSON::Object::Ptr &o) {
127 NodeDesc d;
128 d.id = o->optValue<std::string>("id", "");
129 d.key = o->optValue<std::string>("key", d.id);
130 d.name = o->optValue<std::string>("name", d.id);
131 d.space = o->optValue<std::string>("space", "3d");
132 d.visible = o->optValue<bool>("visible", true);
133 d.layer = o->optValue<int>("layer", 0);
134 d.x = float(o->optValue<double>("x", 0.0));
135 d.y = float(o->optValue<double>("y", 0.0));
136 d.z = float(o->optValue<double>("z", 0.0));
137 d.yaw = float(o->optValue<double>("yaw", 0.0));
138 d.pitch = float(o->optValue<double>("pitch", 0.0));
139 d.roll = float(o->optValue<double>("roll", 0.0));
140 d.sx = float(o->optValue<double>("sx", 1.0));
141 d.sy = float(o->optValue<double>("sy", 1.0));
142 d.sz = float(o->optValue<double>("sz", 1.0));
143 if (o->has("bminX")) {
144 d.bminX = float(o->optValue<double>("bminX", 0.0));
145 d.bminY = float(o->optValue<double>("bminY", 0.0));
146 d.bminZ = float(o->optValue<double>("bminZ", 0.0));
147 d.bmaxX = float(o->optValue<double>("bmaxX", 0.0));
148 d.bmaxY = float(o->optValue<double>("bmaxY", 0.0));
149 d.bmaxZ = float(o->optValue<double>("bmaxZ", 0.0));
150 d.hasBounds = true;
151 }
152 if (o->has("tags")) {
153 Poco::JSON::Array::Ptr arr = o->getArray("tags");
154 for (size_t i = 0; i < arr->size(); ++i) {
155 d.tags.push_back(arr->getElement<std::string>(i));
156 }
157 }
158 if (o->has("children")) {
159 Poco::JSON::Array::Ptr kids = o->getArray("children");
160 for (size_t i = 0; i < kids->size(); ++i) {
161 d.children.push_back(nodeFromJson(kids->getObject(i)));
162 }
163 }
164 return d;
165}
166#endif
167
169const char *kSceneComponentScript = R"SQ(
170eve.SceneComponent <- class {
171 hostName = ""
172 dirty = true
173 forceFull = false
174 _scene = null
175 _mounted = false
176
177 constructor(sceneInstance = null) {
178 _scene = sceneInstance
179 hostName = ""
180 dirty = true
181 forceFull = false
182 _mounted = false
183 }
184
185 function setScene(sceneInstance) { _scene = sceneInstance }
186
187 function mountAs(name) {
188 hostName = name
189 dirty = true
190 forceFull = true
191 updateIfDirty()
192 }
193
194 function setState() { dirty = true }
195 function markDirty() { dirty = true }
196 function onMount() {}
197
198 // Override: call this.scene().beginNode / addNode / end ...
199 function build() {}
200
201 function scene() {
202 if (_scene != null) return _scene
203 try {
204 if (::scene != null) return ::scene
205 } catch (e) {}
206 _scene = ::eve.Scene()
207 return _scene
208 }
209
210 function updateIfDirty() {
211 if (!dirty) return false
212 local s = scene()
213 s.beginBuild()
214 build()
215 local name = hostName
216 if (name == null || name == "") name = "default"
217 if (forceFull) {
218 s.mountBuildAs(name)
219 forceFull = false
220 } else {
221 s.remountBuildAs(name)
222 }
223 if (!_mounted) {
224 _mounted = true
225 onMount()
226 }
227 dirty = false
228 return true
229 }
230
231 function rebuild(force = false) {
232 dirty = true
233 forceFull = force
234 return updateIfDirty()
235 }
236}
237)SQ";
238
239void injectSceneComponentClass(ssq::Table &eveTable) {
240 HSQUIRRELVM vm = eveTable.getHandle();
241 const SQInteger top = sq_gettop(vm);
242 if (SQ_FAILED(sq_compilebuffer(vm, kSceneComponentScript,
243 static_cast<SQInteger>(std::strlen(kSceneComponentScript)),
244 "SceneComponent.nut", SQTrue))) {
245 sq_settop(vm, top);
246 return;
247 }
248 sq_pushroottable(vm);
249 sq_call(vm, 1, SQFalse, SQTrue);
250 sq_settop(vm, top);
251}
252
258const char *kSceneEntityScript = R"SQ(
259// Per-node script entity base: nodes get gameplay logic through the script ECS.
260eve.SceneEntity <- class extends eve.Entity {
261 _scene = null
262 hostName = ""
263 nodeId = ""
264
265 function scene() { return _scene }
266 function node() {
267 if (_scene == null) return null
268 return _scene.getNodeRefAt(hostName, nodeId)
269 }
270 function onAttach() {}
271 function onDetach() {}
272 function update(dt) {}
273}
274
275// ---- eve.Scene: per-node entity API (script wrappers over native primitives) ----
276
277eve.Scene["getNodeRef"] <- function(nodeId, hostName = null) {
278 if (hostName == null) hostName = currentHostName()
279 return getNodeRefAt(hostName, nodeId) } eve.Scene["getNodeRefByPath"] <- function(path) {
280 return getNodeRefByPathAt(currentHostName(), path) } eve.Scene["attachEntity"] <- function(nodeId, cls) {
281 return attachEntityAt(currentHostName(), nodeId, cls) } eve.Scene["attachEntityAt"] <- function(hostName, nodeId, cls) {
282 if (typeof cls != "class") return null
283 if (!eve.isSubclass(cls, eve.Entity)) return null
284 local old = getEntityAt(hostName, nodeId, cls)
285 if (old != null) detachEntityAt(hostName, nodeId, old)
286 local inst = cls.create()
287 inst._scene = this
288 inst.hostName = hostName
289 inst.nodeId = nodeId
290 if (!rootEntity(hostName, nodeId, inst)) {
291 inst.destroy()
292 return null
293 }
294 inst.onAttach()
295 return inst
296}
297
298eve.Scene["detachEntity"] <- function(nodeId, inst) {
299 return detachEntityAt(currentHostName(), nodeId, inst) } eve.Scene["detachEntityAt"] <- function(hostName, nodeId, inst) {
300 if (inst == null) return false
301 local idx = -1
302 forEachEntity(hostName, nodeId, function(e, index) {
303 if (idx < 0 && e == inst) idx = index
304 })
305 if (idx < 0) return false
306 inst.onDetach()
307 inst.destroy()
308 return unrootEntityAt(hostName, nodeId, idx) } eve.Scene["getEntity"] <- function(nodeId, cls) {
309 return getEntityAt(currentHostName(), nodeId, cls) } eve.Scene["getEntityAt"] <- function(hostName, nodeId, cls) {
310 local out = null
311 forEachEntity(hostName, nodeId, function(e) {
312 if (out == null && e instanceof cls) out = e
313 })
314 return out
315}
316
317eve.Scene["hasEntity"] <- function(nodeId, cls) {
318 return getEntityAt(currentHostName(), nodeId, cls) != null
319}
320
321eve.Scene["hasEntityAt"] <- function(hostName, nodeId, cls) {
322 return getEntityAt(hostName, nodeId, cls) != null
323}
324
325eve.Scene["entitiesOf"] <- function(nodeId) {
326 return entitiesOfAt(currentHostName(), nodeId) } eve.Scene["entitiesOfAt"] <- function(hostName, nodeId) {
327 local out = []
328 forEachEntity(hostName, nodeId, function(e) { out.push(e) })
329 return out
330}
331
332eve.Scene["update"] <- function(dt) {
333 updateScripts(dt)
334}
335
336// ---- eve.Scene: generic link system (selected-host convenience) ----
337
338eve.Scene["linkPhysics2D"] <- function(nodeId, body, mode = "node") {
339 return linkPhysics2DAt(currentHostName(), nodeId, body, mode) } eve.Scene["linkPhysics3D"] <- function(nodeId, body, mode = "node") {
340 return linkPhysics3DAt(currentHostName(), nodeId, body, mode) } eve.Scene["linkCamera3D"] <- function(nodeId, cam) {
341 return linkCamera3DAt(currentHostName(), nodeId, cam) } eve.Scene["linkAudio3D"] <- function(nodeId, source) {
342 return linkAudio3DAt(currentHostName(), nodeId, source) } eve.Scene["unlinkNodeKind"] <- function(nodeId, kind) {
343 return unlinkNodeKindAt(currentHostName(), nodeId, kind) } eve.Scene["linkCount"] <- function(nodeId) {
344 return linkCountAt(currentHostName(), nodeId)
345}
346
347// ---- eve.SceneNodeRef: node-level entity forwarding ----
348
349eve.SceneNodeRef["attachEntity"] <- function(cls) {
350 return getScene().attachEntityAt(getHostName(), getNodeId(), cls)
351}
352eve.SceneNodeRef["detachEntity"] <- function(inst) {
353 return getScene().detachEntityAt(getHostName(), getNodeId(), inst)
354}
355eve.SceneNodeRef["getEntity"] <- function(cls) {
356 return getScene().getEntityAt(getHostName(), getNodeId(), cls)
357}
358eve.SceneNodeRef["hasEntity"] <- function(cls) {
359 return getScene().hasEntityAt(getHostName(), getNodeId(), cls)
360}
361eve.SceneNodeRef["entitiesOf"] <- function() {
362 return getScene().entitiesOfAt(getHostName(), getNodeId())
363}
364
365// ---- eve.SceneNodeRef: generic link system ----
366
367eve.SceneNodeRef["linkRenderable2D"] <- function(r) {
368 return getScene().linkRenderable2DAt(getHostName(), getNodeId(), r)
369}
370eve.SceneNodeRef["linkRenderable3D"] <- function(r) {
371 return getScene().linkRenderable3DAt(getHostName(), getNodeId(), r)
372}
373eve.SceneNodeRef["linkPhysics2D"] <- function(body, mode = "node") {
374 return getScene().linkPhysics2DAt(getHostName(), getNodeId(), body, mode)
375}
376eve.SceneNodeRef["linkPhysics3D"] <- function(body, mode = "node") {
377 return getScene().linkPhysics3DAt(getHostName(), getNodeId(), body, mode)
378}
379eve.SceneNodeRef["linkCamera3D"] <- function(cam) {
380 return getScene().linkCamera3DAt(getHostName(), getNodeId(), cam)
381}
382eve.SceneNodeRef["linkAudio3D"] <- function(source) {
383 return getScene().linkAudio3DAt(getHostName(), getNodeId(), source)
384}
385eve.SceneNodeRef["unlinkNode"] <- function() {
386 return getScene().unlinkNodeAt(getHostName(), getNodeId())
387}
388eve.SceneNodeRef["unlinkNodeKind"] <- function(kind) {
389 return getScene().unlinkNodeKindAt(getHostName(), getNodeId(), kind)
390}
391eve.SceneNodeRef["linkCount"] <- function() {
392 return getScene().linkCountAt(getHostName(), getNodeId())
393}
394
395// ---- eve.Scene: script-API completeness (selected-host convenience) ----
396
397eve.Scene["_requireNode"] <- function(nodeId) {
398 if (!hasNode(nodeId)) throw "scene: node '" + nodeId + "' not found"
399}
400eve.Scene["getNodePosition"] <- function(nodeId) {
401 _requireNode(nodeId)
402 return getNodePositionAt(currentHostName(), nodeId) } eve.Scene["getNodeRotation"] <- function(nodeId) {
403 _requireNode(nodeId)
404 return getNodeRotationAt(currentHostName(), nodeId) } eve.Scene["getNodeScale"] <- function(nodeId) {
405 _requireNode(nodeId)
406 return getNodeScaleAt(currentHostName(), nodeId) } eve.Scene["getNodeVisible"] <- function(nodeId) {
407 _requireNode(nodeId)
408 return getNodeVisibleAt(currentHostName(), nodeId) } eve.Scene["getNodeWorldPosition"] <- function(nodeId) {
409 _requireNode(nodeId)
410 return getNodeWorldPositionAt(currentHostName(), nodeId) } eve.Scene["getNodeWorldRotation"] <- function(nodeId) {
411 _requireNode(nodeId)
412 return getNodeWorldRotationAt(currentHostName(), nodeId) } eve.Scene["getNodeWorldScale"] <- function(nodeId) {
413 _requireNode(nodeId)
414 return getNodeWorldScaleAt(currentHostName(), nodeId) } eve.Scene["localToWorld"] <- function(nodeId, x, y, z) {
415 _requireNode(nodeId)
416 return localToWorldAt(currentHostName(), nodeId, x, y, z) } eve.Scene["worldToLocal"] <- function(nodeId, x, y, z) {
417 _requireNode(nodeId)
418 return worldToLocalAt(currentHostName(), nodeId, x, y, z) } eve.Scene["setNodeParent"] <- function(nodeId, parentId) { return setNodeParentAt(currentHostName(), nodeId, parentId) }
419eve.Scene["removeNode"] <- function(nodeId) { return removeNodeAt(currentHostName(), nodeId) }
420eve.Scene["addNodeChild"] <- function(parentId, childId) { return addChildAt(currentHostName(), parentId, childId) }
421eve.Scene["removeNodeChild"] <- function(parentId, childId) { return removeChildAt(currentHostName(), parentId, childId) }
422eve.Scene["setNodeQuaternion"] <- function(nodeId, qx, qy, qz, qw) { return setNodeQuaternionAt(currentHostName(), nodeId, qx, qy, qz, qw) }
423eve.Scene["getNodeQuaternion"] <- function(nodeId) {
424 _requireNode(nodeId)
425 return getNodeQuaternionAt(currentHostName(), nodeId) } eve.Scene["setNodeLookAt"] <- function(nodeId, tx, ty, tz) { return setNodeLookAtAt(currentHostName(), nodeId, tx, ty, tz) }
426eve.Scene["addNodeTag"] <- function(nodeId, tag) { return addNodeTagAt(currentHostName(), nodeId, tag) }
427eve.Scene["removeNodeTag"] <- function(nodeId, tag) { return removeNodeTagAt(currentHostName(), nodeId, tag) }
428eve.Scene["hasNodeTag"] <- function(nodeId, tag) { return hasNodeTagAt(currentHostName(), nodeId, tag) }
429eve.Scene["getNodeTags"] <- function(nodeId) {
430 _requireNode(nodeId)
431 return getNodeTagsAt(currentHostName(), nodeId) } eve.Scene["collectIdsByTag"] <- function(tag) { return collectIdsByTagAt(currentHostName(), tag) }
432eve.Scene["setNodeLayer"] <- function(nodeId, layer) { return setNodeLayerAt(currentHostName(), nodeId, layer) }
433eve.Scene["getNodeLayer"] <- function(nodeId) {
434 _requireNode(nodeId)
435 return getNodeLayerAt(currentHostName(), nodeId) } eve.Scene["setNodeEventHandler"] <- function(cb) {
436 return setNodeEventHandlerAt(currentHostName(), cb)
437}
438
439// ---- eve.SceneNodeRef: script-API completeness forwarding ----
440
441eve.SceneNodeRef["_requireValid"] <- function() {
442 if (!isValid()) throw "scene: node not found (" + getHostName() + "/" + getNodeId() + ")"
443}
444eve.SceneNodeRef["localToWorld"] <- function(x, y, z) {
445 _requireValid()
446 return getScene().localToWorldAt(getHostName(), getNodeId(), x, y, z)
447}
448eve.SceneNodeRef["worldToLocal"] <- function(x, y, z) {
449 _requireValid()
450 return getScene().worldToLocalAt(getHostName(), getNodeId(), x, y, z)
451}
452eve.SceneNodeRef["setParent"] <- function(parentId) { return getScene().setNodeParentAt(getHostName(), getNodeId(), parentId) }
453eve.SceneNodeRef["removeNode"] <- function() { return getScene().removeNodeAt(getHostName(), getNodeId()) }
454eve.SceneNodeRef["setQuaternion"] <- function(qx, qy, qz, qw) { return getScene().setNodeQuaternionAt(getHostName(), getNodeId(), qx, qy, qz, qw) }
455eve.SceneNodeRef["getQuaternion"] <- function() {
456 _requireValid()
457 return getScene().getNodeQuaternionAt(getHostName(), getNodeId())
458}
459eve.SceneNodeRef["lookAt"] <- function(tx, ty, tz) { return getScene().setNodeLookAtAt(getHostName(), getNodeId(), tx, ty, tz) }
460eve.SceneNodeRef["addTag"] <- function(tag) { return getScene().addNodeTagAt(getHostName(), getNodeId(), tag) }
461eve.SceneNodeRef["removeTag"] <- function(tag) { return getScene().removeNodeTagAt(getHostName(), getNodeId(), tag) }
462eve.SceneNodeRef["hasTag"] <- function(tag) { return getScene().hasNodeTagAt(getHostName(), getNodeId(), tag) }
463eve.SceneNodeRef["getTags"] <- function() {
464 _requireValid()
465 return getScene().getNodeTagsAt(getHostName(), getNodeId())
466}
467eve.SceneNodeRef["setLayer"] <- function(layer) { return getScene().setNodeLayerAt(getHostName(), getNodeId(), layer) }
468eve.SceneNodeRef["getLayer"] <- function() {
469 _requireValid()
470 return getScene().getNodeLayerAt(getHostName(), getNodeId())
471}
472
473// ---- bounds / serialization / picking / culling ----
474
475eve.Scene["setNodeBounds"] <- function(nodeId, minX, minY, minZ, maxX, maxY, maxZ) {
476 return setNodeBoundsAt(currentHostName(), nodeId, minX, minY, minZ, maxX, maxY, maxZ) } eve.Scene["hasNodeBounds"] <- function(nodeId) {
477 return hasNodeBoundsAt(currentHostName(), nodeId) } eve.Scene["getNodeBounds"] <- function(nodeId) {
478 _requireNode(nodeId)
479 return getNodeBoundsAt(currentHostName(), nodeId) } eve.Scene["serializeHost"] <- function() {
480 return serializeHostAt(currentHostName()) } eve.Scene["deserializeHost"] <- function(json) {
481 return deserializeHostAt(currentHostName(), json) } eve.Scene["pickRay"] <- function(ox, oy, oz, dx, dy, dz) {
482 return pickRayAt(currentHostName(), ox, oy, oz, dx, dy, dz) } eve.Scene["pickScreen"] <- function(cam, screenX, screenY, viewW, viewH) {
483 return pickScreenAt(currentHostName(), cam, screenX, screenY, viewW, viewH) } eve.Scene["collectFrustumIds"] <- function(cam, viewW, viewH) {
484 return collectFrustumIdsAt(currentHostName(), cam, viewW, viewH) } eve.Scene["syncSpatialIndex"] <- function(octree) {
485 return syncSpatialIndexAt(currentHostName(), octree) } eve.Scene["nodeIdFromSpatialId"] <- function(index) {
486 return nodeIdFromSpatialIdAt(currentHostName(), index)
487}
488
489eve.SceneNodeRef["setBounds"] <- function(minX, minY, minZ, maxX, maxY, maxZ) {
490 return getScene().setNodeBoundsAt(getHostName(), getNodeId(), minX, minY, minZ, maxX, maxY, maxZ)
491}
492eve.SceneNodeRef["hasBounds"] <- function() {
493 return getScene().hasNodeBoundsAt(getHostName(), getNodeId())
494}
495eve.SceneNodeRef["getBounds"] <- function() {
496 _requireValid()
497 return getScene().getNodeBoundsAt(getHostName(), getNodeId())
498}
499)SQ";
500
501void injectSceneEntityScript(ssq::Table &eveTable) {
502 HSQUIRRELVM vm = eveTable.getHandle();
503 const SQInteger top = sq_gettop(vm);
504 if (SQ_FAILED(sq_compilebuffer(vm, kSceneEntityScript,
505 static_cast<SQInteger>(std::strlen(kSceneEntityScript)),
506 "SceneEntity.nut", SQTrue))) {
507 sq_settop(vm, top);
508 return;
509 }
510 sq_pushroottable(vm);
511 sq_call(vm, 1, SQFalse, SQTrue);
512 sq_settop(vm, top);
513}
514
515bool g_sceneEntityHookRegistered = false;
516
517} // namespace
518
519SceneHost *Scene::ensureSelected(const std::string &preferredName) {
520 if (selected_) return selected_;
521 if (!preferredName.empty()) {
522 selected_ = findHostByName(preferredName);
523 if (selected_) return selected_;
524 selected_ = SceneHost::createHost(preferredName);
525 return selected_;
526 }
527 selected_ = SceneHost::createHost("default");
528 return selected_;
529}
530
531SceneHost *Scene::mountAs(const std::string &name, NodeDesc root) {
532 SceneHost *h = findHostByName(name);
533 if (!h) h = SceneHost::createHost(name);
534 h->setTree(std::move(root));
536 pruneOrphanObjects();
537 selected_ = h;
538 return h;
539}
540
541SceneHost *Scene::mount(NodeDesc root) { return mountAs("default", std::move(root)); }
542
543SceneHost *Scene::remount(NodeDesc root) {
544 SceneHost *h = ensureSelected("default");
545 h->setTree(std::move(root));
547 pruneOrphanObjects();
548 return h;
549}
550
551SceneHost *Scene::remountReconcile(NodeDesc root) {
552 SceneHost *h = ensureSelected("default");
553 h->setTreeReconcile(std::move(root));
555 pruneOrphanObjects();
556 return h;
557}
558
559SceneHost *Scene::remountAs(const std::string &name, NodeDesc root) {
560 return mountAs(name, std::move(root));
561}
562
563bool Scene::select(const std::string &name) {
564 SceneHost *h = findHostByName(name);
565 if (!h) return false;
566 selected_ = h;
567 return true;
568}
569
570SceneHost *Scene::findHost(const std::string &name) const { return findHostByName(name); }
571
572SceneHost *Scene::findHostByOwner(uint32_t ownerId) const { return findHostByOwnerId(ownerId); }
573
574void Scene::bindOwner(uint32_t ownerId) {
575 SceneHost *h = ensureSelected();
576 h->setOwnerId(ownerId);
577}
578
579void Scene::setHostVisible(bool visible) {
580 if (auto *h = ensureSelected()) h->setVisible(visible);
581}
582
583void Scene::setHostLayer(int layer) {
584 if (auto *h = ensureSelected()) h->setLayer(layer);
585}
586
588 if (selected_) TransformSystem::updateHost(selected_);
590}
591
593
594
595#ifdef EVENGINE_SCENE_JSON
596std::string Scene::serializeHostAt(const std::string &hostName) const {
597 SceneHost *h = resolveHost(hostName);
598 if (!h || !h->getRoot()) return "{}";
599 Poco::JSON::Object root;
600 root.set("host", h->getName());
601 root.set("root", nodeToJson(*h->tree(), h->getRoot()));
602 std::ostringstream oss;
603 Poco::JSON::Stringifier::stringify(root, oss);
604 return oss.str();
605}
606
607bool Scene::deserializeHostAt(const std::string &hostName, const std::string &json) {
608 try {
609 Poco::JSON::Parser parser;
610 Poco::Dynamic::Var result = parser.parse(json);
611 Poco::JSON::Object::Ptr root = result.extract<Poco::JSON::Object::Ptr>();
612 if (!root) return false;
613 Poco::JSON::Object::Ptr tree = root->getObject("root");
614 if (!tree) return false;
615 NodeDesc desc = nodeFromJson(tree);
616 mountAs(hostName.empty() ? "default" : hostName, std::move(desc));
617 return true;
618 } catch (...) {
619 return false;
620 }
621}
622#else
623std::string Scene::serializeHostAt(const std::string &hostName) const {
624 (void)hostName;
625 return "{}"; // Poco JSON is not available on this build (e.g. WASM trim)
626}
627
628bool Scene::deserializeHostAt(const std::string &hostName, const std::string &json) {
629 (void)hostName;
630 (void)json;
631 return false;
633#endif
634
635void Scene::beginBuild() {
636 openStack_.clear();
637 hasBuiltRoot_ = false;
638 builtRoot_ = NodeDesc{};
639}
640
641void Scene::pushOpen(NodeDesc d) { openStack_.push_back(std::move(d)); }
642
643NodeDesc &Scene::currentParent() {
644 if (openStack_.empty()) throw std::runtime_error("scene: node outside beginNode/beginGroup");
645 return openStack_.back();
646}
648void Scene::beginNode(const std::string &id, const std::string &name) {
649 if (openStack_.empty() && hasBuiltRoot_) beginBuild();
650 pushOpen(node(id, {}, name));
651}
653void Scene::beginGroup(const std::string &id) {
654 if (openStack_.empty() && hasBuiltRoot_) beginBuild();
655 pushOpen(group({}, id));
657
658void Scene::end() {
659 if (openStack_.empty()) throw std::runtime_error("scene: end() without begin");
660 NodeDesc finished = std::move(openStack_.back());
661 openStack_.pop_back();
662 if (openStack_.empty()) {
663 builtRoot_ = std::move(finished);
664 hasBuiltRoot_ = true;
665 } else {
666 openStack_.back().children.push_back(std::move(finished));
667 }
668}
669
670void Scene::addNode(const std::string &id, const std::string &name) {
671 currentParent().children.push_back(node(id, {}, name));
672}
673
674void Scene::setBuildPosition(float x, float y, float z) {
675 NodeDesc &d = currentParent();
676 d.x = x;
677 d.y = y;
678 d.z = z;
679}
680
681void Scene::setBuildRotation(float yaw, float pitch, float roll) {
682 NodeDesc &d = currentParent();
683 d.yaw = yaw;
684 d.pitch = pitch;
685 d.roll = roll;
686}
687
688void Scene::setBuildScale(float sx, float sy, float sz) {
689 NodeDesc &d = currentParent();
690 d.sx = sx;
691 d.sy = sy;
692 d.sz = sz;
693}
694
695void Scene::setBuildSpace(const std::string &space) { currentParent().space = space; }
697void Scene::setBuildVisible(bool visible) { currentParent().visible = visible; }
698
699bool Scene::buildComplete() const { return openStack_.empty() && hasBuiltRoot_; }
700
702 if (!buildComplete()) return false;
703 remount(std::move(builtRoot_));
704 hasBuiltRoot_ = false;
705 builtRoot_ = NodeDesc{};
706 return true;
707}
709bool Scene::mountBuildAs(const std::string &name) {
710 if (!buildComplete()) return false;
711 mountAs(name, std::move(builtRoot_));
712 hasBuiltRoot_ = false;
713 builtRoot_ = NodeDesc{};
714 return true;
715}
716
717bool Scene::remountBuildAs(const std::string &name) {
718 if (!buildComplete()) return false;
720 if (!h) h = SceneHost::createHost(name);
721 h->setTreeReconcile(std::move(builtRoot_));
723 pruneOrphanObjects();
724 selected_ = h;
725 hasBuiltRoot_ = false;
726 builtRoot_ = NodeDesc{};
727 return true;
728}
729
730// ---------------------------------------------------------------------------
731// Per-node script entities (native primitives; script wrappers see expose())
732// ---------------------------------------------------------------------------
733
734SceneHost *Scene::resolveHost(const std::string &hostName) const {
735 if (hostName.empty()) return selected_;
736 return findHostByName(hostName);
737}
738
739SceneObject *Scene::findSceneObjectById(uint32_t id) const {
740 if (id == 0) return nullptr;
741 if (ecs::current()->getManager<SceneObject>() == nullptr) return nullptr;
742 auto view = ecs::View<SceneObject, SceneObject::Meta>();
743 for (auto it = view.begin(); it != view.end(); ++it) {
744 auto [meta] = *it;
745 if (meta->entity && uint32_t(meta->entity->id) == id) return meta->entity;
746 }
747 return nullptr;
748}
749
750SceneObject *Scene::ensureSceneObject(SceneHost *host, SceneNode *node,
751 const std::string &hostName) {
752 if (node->objectId != 0) {
753 if (SceneObject *o = findSceneObjectById(node->objectId)) return o;
754 node->objectId = 0; // stale id (entity was released)
755 }
757 node->objectId = uint32_t(o->id);
758 return o;
759}
760
761bool Scene::rootEntity(const std::string &hostName, const std::string &nodeId,
762 ssq::Object instance) {
763 if (!vm_) return false;
764 SceneHost *h = resolveHost(hostName);
765 if (!h) return false;
766 SceneNode *n = h->findById(nodeId);
767 if (!n) return false;
768 HSQOBJECT raw = instance.getRaw();
769 if (raw._type != OT_INSTANCE) return false;
770 const std::string hName = hostName.empty() ? h->getName() : hostName;
771 SceneObject *obj = ensureSceneObject(h, n, hName);
772 if (!obj) return false;
773 for (const auto &b : obj->scriptBindings()->instances) {
774 if (sameScriptObject(b, raw)) return false; // already rooted
775 }
776 sq_addref(vm_, &raw);
777 obj->scriptBindings()->instances.push_back(raw);
778 return true;
779}
780
781bool Scene::unrootEntityAt(const std::string &hostName, const std::string &nodeId,
782 int index) {
783 if (!vm_) return false;
784 SceneHost *h = resolveHost(hostName);
785 if (!h) return false;
786 SceneNode *n = h->findById(nodeId);
787 if (!n) return false;
788 SceneObject *obj = n->objectId ? findSceneObjectById(n->objectId) : nullptr;
789 if (!obj) return false;
790 auto &vec = obj->scriptBindings()->instances;
791 if (index < 0 || index >= int(vec.size())) return false;
792 HSQOBJECT raw = vec[size_t(index)];
793 vec.erase(vec.begin() + index);
794 sq_release(vm_, &raw);
795 if (vec.empty()) {
796 n->objectId = 0;
797 obj->release();
798 }
799 return true;
800}
801
802int Scene::forEachEntity(const std::string &hostName, const std::string &nodeId,
803 ssq::Object cb) {
804 if (!vm_) return 0;
805 SceneHost *h = resolveHost(hostName);
806 if (!h) return 0;
807 SceneNode *n = h->findById(nodeId);
808 if (!n) return 0;
809 SceneObject *obj = n->objectId ? findSceneObjectById(n->objectId) : nullptr;
810 if (!obj) return 0;
811 // Copy + addref: the callback may attach/detach and mutate the live list.
812 auto vec = obj->scriptBindings()->instances;
813 for (auto &b : vec) sq_addref(vm_, &b);
814 HSQOBJECT fn = cb.getRaw();
815 sq_addref(vm_, &fn);
816 int count = 0;
817 for (size_t i = 0; i < vec.size(); ++i) {
818 if (callCallback(fn, vec[i], int(i))) ++count;
819 }
820 sq_release(vm_, &fn);
821 for (auto &b : vec) sq_release(vm_, &b);
822 return count;
823}
824
825void Scene::updateScripts(float dt) {
827 if (!vm_) return;
828 if (ecs::current()->getManager<SceneObject>() == nullptr) return;
829 // Snapshot + addref so script update(dt) may attach/detach safely.
830 std::vector<HSQOBJECT> snapshot;
831 {
832 auto view = ecs::View<SceneObject, SceneObject::Meta, SceneObject::ScriptBindings>();
833 for (auto it = view.begin(); it != view.end(); ++it) {
834 auto [meta, sb] = *it;
835 if (!meta->entity) continue;
836 for (auto &inst : sb->instances) snapshot.push_back(inst);
837 }
838 }
839 for (auto &h : snapshot) sq_addref(vm_, &h);
840 for (auto &h : snapshot) callMethod(h, "update", dt);
841 for (auto &h : snapshot) sq_release(vm_, &h);
842}
843
844std::string Scene::currentHostName() const {
845 return selected_ ? selected_->getName() : std::string{};
846}
847
848SceneNodeRef *Scene::getNodeRefAt(const std::string &hostName,
849 const std::string &nodeId) const {
850 std::string h = hostName.empty()
851 ? (selected_ ? selected_->getName() : std::string{})
852 : hostName;
853 return new SceneNodeRef(std::move(h), nodeId);
855
856SceneNodeRef *Scene::getNodeRefByPathAt(const std::string &hostName,
857 const std::string &path) const {
858 std::string h = hostName.empty()
859 ? (selected_ ? selected_->getName() : std::string{})
860 : hostName;
861 SceneHost *host = resolveHost(hostName);
862 if (!host) return new SceneNodeRef(std::move(h), "");
863 SceneNode *n = host->findByPath(path);
864 return new SceneNodeRef(std::move(h), n ? n->id : std::string{});
865}
866
867void Scene::pruneOrphanObjects() {
868 if (ecs::current()->getManager<SceneObject>() == nullptr) return;
869 std::vector<SceneObject *> orphans;
870 {
871 auto view = ecs::View<SceneObject, SceneObject::Meta, SceneObject::ScriptBindings>();
872 for (auto it = view.begin(); it != view.end(); ++it) {
873 auto [meta, sb] = *it;
874 if (!meta->entity) continue;
875 SceneHost *h = findHostByName(meta->hostName);
876 if (!h) {
877 orphans.push_back(meta->entity);
878 continue;
879 }
880 SceneNode *n = h->findById(meta->nodeId);
881 if (!n || n->objectId != uint32_t(meta->entity->id)) {
882 orphans.push_back(meta->entity);
883 continue;
884 }
885 (void)sb;
886 // Self-heal: node id / host renamed (reconcile patch), keep binding.
887 if (meta->hostName != h->getName() || meta->nodeId != n->id) {
888 meta->hostName = h->getName();
889 meta->nodeId = n->id;
890 syncBindingRefs(meta->entity);
891 }
892 }
893 }
894 for (SceneObject *o : orphans) teardownBindings(o);
895}
896
897void Scene::syncBindingRefs(SceneObject *obj) {
898 if (!vm_ || !obj) return;
899 for (auto &inst : obj->scriptBindings()->instances) {
900 setStringField(vm_, inst, "hostName", obj->meta()->hostName);
901 setStringField(vm_, inst, "nodeId", obj->meta()->nodeId);
902 }
903}
904
905void Scene::teardownBindings(SceneObject *obj) {
906 if (!obj) return;
907 auto &vec = obj->scriptBindings()->instances;
908 if (vm_) {
909 for (auto &inst : vec) {
910 callMethod0(inst, "onDetach");
911 callMethod0(inst, "destroy");
912 sq_release(vm_, &inst);
913 }
914 }
915 vec.clear();
916 obj->release();
918
919bool Scene::callMethod(HSQOBJECT inst, const char *name, float dt) {
920 if (!vm_ || inst._type != OT_INSTANCE) return false;
921 const SQInteger top = sq_gettop(vm_);
922 sq_pushobject(vm_, inst);
923 sq_pushstring(vm_, name, -1);
924 if (SQ_FAILED(sq_get(vm_, -2))) {
925 sq_settop(vm_, top);
926 return false;
927 }
928 const SQObjectType t = sq_gettype(vm_, -1);
929 if (t != OT_CLOSURE && t != OT_NATIVECLOSURE) {
930 sq_settop(vm_, top);
931 return false;
932 }
933 sq_pushobject(vm_, inst); // this
934 sq_pushfloat(vm_, SQFloat(dt));
935 if (SQ_FAILED(sq_call(vm_, 2, SQFalse, SQTrue))) {
936 sq_settop(vm_, top);
937 return false;
938 }
939 sq_settop(vm_, top);
940 return true;
941}
942
943bool Scene::callMethod0(HSQOBJECT inst, const char *name) {
944 if (!vm_ || inst._type != OT_INSTANCE) return false;
945 const SQInteger top = sq_gettop(vm_);
946 sq_pushobject(vm_, inst);
947 sq_pushstring(vm_, name, -1);
948 if (SQ_FAILED(sq_get(vm_, -2))) {
949 sq_settop(vm_, top);
950 return false;
951 }
952 const SQObjectType t = sq_gettype(vm_, -1);
953 if (t != OT_CLOSURE && t != OT_NATIVECLOSURE) {
954 sq_settop(vm_, top);
955 return false;
956 }
957 sq_pushobject(vm_, inst); // this
958 if (SQ_FAILED(sq_call(vm_, 1, SQFalse, SQTrue))) {
959 sq_settop(vm_, top);
960 return false;
961 }
962 sq_settop(vm_, top);
963 return true;
964}
965
966bool Scene::callCallback(HSQOBJECT fn, HSQOBJECT inst, int index) {
967 if (!vm_) return false;
968 const SQInteger top = sq_gettop(vm_);
969 sq_pushobject(vm_, fn);
970 sq_pushobject(vm_, inst);
971 sq_pushinteger(vm_, SQInteger(index));
972 if (SQ_FAILED(sq_call(vm_, 2, SQFalse, SQTrue))) {
973 sq_settop(vm_, top);
974 return false;
975 }
976 sq_settop(vm_, top);
977 return true;
978}
979
980void Scene::expose(ssq::Table &table) {
981 if (Scene *self = Scene::create()) self->vm_ = table.getHandle();
982 auto cls = table.addClass(name, Scene::create, false);
983 expose(cls);
984
985 // Lightweight node handle (owned by script; created via getNodeRef*).
986 auto refCls = table.addClass<SceneNodeRef>(
987 "SceneNodeRef",
988 std::function<SceneNodeRef *()>([]() -> SceneNodeRef * { return nullptr; }), true);
989 refCls.addFunc("getNodeId", &SceneNodeRef::getNodeId);
990 refCls.addFunc("getHostName", &SceneNodeRef::getHostName);
991 refCls.addFunc("isValid", &SceneNodeRef::isValid);
992 refCls.addFunc("getScene", &SceneNodeRef::getScene);
993 refCls.addFunc("setPosition", &SceneNodeRef::setPosition);
994 refCls.addFunc("getPositionX", &SceneNodeRef::getPositionX);
995 refCls.addFunc("getPositionY", &SceneNodeRef::getPositionY);
996 refCls.addFunc("getPositionZ", &SceneNodeRef::getPositionZ);
997 refCls.addFunc("getPosition", &SceneNodeRef::getPosition);
998 refCls.addFunc("setRotation", &SceneNodeRef::setRotation);
999 refCls.addFunc("getRotationYaw", &SceneNodeRef::getRotationYaw);
1000 refCls.addFunc("getRotationPitch", &SceneNodeRef::getRotationPitch);
1001 refCls.addFunc("getRotationRoll", &SceneNodeRef::getRotationRoll);
1002 refCls.addFunc("getRotation", &SceneNodeRef::getRotation);
1003 refCls.addFunc("setScale", &SceneNodeRef::setScale);
1004 refCls.addFunc("getScaleX", &SceneNodeRef::getScaleX);
1005 refCls.addFunc("getScaleY", &SceneNodeRef::getScaleY);
1006 refCls.addFunc("getScaleZ", &SceneNodeRef::getScaleZ);
1007 refCls.addFunc("getScale", &SceneNodeRef::getScale);
1008 refCls.addFunc("setVisible", &SceneNodeRef::setVisible);
1009 refCls.addFunc("isVisible", &SceneNodeRef::isVisible);
1010 refCls.addFunc("getWorldPositionX", &SceneNodeRef::getWorldPositionX);
1011 refCls.addFunc("getWorldPositionY", &SceneNodeRef::getWorldPositionY);
1012 refCls.addFunc("getWorldPositionZ", &SceneNodeRef::getWorldPositionZ);
1013 refCls.addFunc("getWorldPosition", &SceneNodeRef::getWorldPosition);
1014 refCls.addFunc("getWorldMatrix", &SceneNodeRef::getWorldMatrix);
1015 refCls.addFunc("getForward", &SceneNodeRef::getForward);
1016 refCls.addFunc("getRight", &SceneNodeRef::getRight);
1017 refCls.addFunc("getUp", &SceneNodeRef::getUp);
1018 refCls.addFunc("getParentId", &SceneNodeRef::getParentId);
1019 refCls.addFunc("getChildCount", &SceneNodeRef::getChildCount);
1020 refCls.addFunc("getChildIdAt", &SceneNodeRef::getChildIdAt);
1021 refCls.addFunc("getPath", &SceneNodeRef::getPath);
1022
1023 injectSceneComponentClass(table);
1024
1025 // Register a hook so eve.SceneEntity (extends eve.Entity) is injected only
1026 // after exposeECS() has defined the script ECS base classes.
1027 if (!g_sceneEntityHookRegistered) {
1028 g_sceneEntityHookRegistered = true;
1029 eve::registerPostEcsHook([](ssq::Table &t) { injectSceneEntityScript(t); });
1030 }
1031}
1032
1033void Scene::expose(ssq::Class &cls) {
1034 cls.addFunc("getName", &Scene::getName);
1035 cls.addFunc("select", &Scene::select);
1036 cls.addFunc("bindOwner", &Scene::bindOwner);
1037 cls.addFunc("setHostVisible", &Scene::setHostVisible);
1038 cls.addFunc("setHostLayer", &Scene::setHostLayer);
1039 cls.addFunc("currentHostName", &Scene::currentHostName);
1040 cls.addFunc("updateTransforms", &Scene::updateTransforms);
1041 cls.addFunc("updateTransformsAll", &Scene::updateTransformsAll);
1042 cls.addFunc("updateScripts", &Scene::updateScripts);
1043 cls.addFunc("setNodePosition", &Scene::setNodePosition);
1044 cls.addFunc("setNodeRotation", &Scene::setNodeRotation);
1045 cls.addFunc("setNodeScale", &Scene::setNodeScale);
1046 cls.addFunc("setNodeVisible", &Scene::setNodeVisible);
1047 cls.addFunc("linkRenderable2D", &Scene::linkRenderable2D);
1048 cls.addFunc("linkRenderable3D", &Scene::linkRenderable3D);
1049 cls.addFunc("unlinkNode", &Scene::unlinkNode);
1050
1051 // Generic link system primitives (host-scoped; script wrappers below)
1052 cls.addFunc("linkRenderable2DAt", &Scene::linkRenderable2DAt);
1053 cls.addFunc("linkRenderable3DAt", &Scene::linkRenderable3DAt);
1054 cls.addFunc("linkPhysics2DAt", &Scene::linkPhysics2DAt);
1055 cls.addFunc("linkPhysics3DAt", &Scene::linkPhysics3DAt);
1056 cls.addFunc("linkCamera3DAt", &Scene::linkCamera3DAt);
1057 cls.addFunc("linkAudio3DAt", &Scene::linkAudio3DAt);
1058 cls.addFunc("unlinkNodeAt", &Scene::unlinkNodeAt);
1059 cls.addFunc("unlinkNodeKindAt", &Scene::unlinkNodeKindAt);
1060 cls.addFunc("linkCountAt", &Scene::linkCountAt);
1061
1062 // Script-API completeness primitives (host-scoped)
1063 cls.addFunc("getNodePositionAt", &Scene::getNodePositionAt);
1064 cls.addFunc("getNodeRotationAt", &Scene::getNodeRotationAt);
1065 cls.addFunc("getNodeScaleAt", &Scene::getNodeScaleAt);
1066 cls.addFunc("getNodeVisibleAt", &Scene::getNodeVisibleAt);
1067 cls.addFunc("getNodeWorldPositionAt", &Scene::getNodeWorldPositionAt);
1068 cls.addFunc("getNodeWorldRotationAt", &Scene::getNodeWorldRotationAt);
1069 cls.addFunc("getNodeWorldScaleAt", &Scene::getNodeWorldScaleAt);
1070 cls.addFunc("localToWorldAt", &Scene::localToWorldAt);
1071 cls.addFunc("worldToLocalAt", &Scene::worldToLocalAt);
1072 cls.addFunc("setNodeParentAt", &Scene::setNodeParentAt);
1073 cls.addFunc("removeNodeAt", &Scene::removeNodeAt);
1074 cls.addFunc("addChildAt", &Scene::addChildAt);
1075 cls.addFunc("removeChildAt", &Scene::removeChildAt);
1076 cls.addFunc("setNodeQuaternionAt", &Scene::setNodeQuaternionAt);
1077 cls.addFunc("getNodeQuaternionAt", &Scene::getNodeQuaternionAt);
1078 cls.addFunc("setNodeLookAtAt", &Scene::setNodeLookAtAt);
1079 cls.addFunc("addNodeTagAt", &Scene::addNodeTagAt);
1080 cls.addFunc("removeNodeTagAt", &Scene::removeNodeTagAt);
1081 cls.addFunc("hasNodeTagAt", &Scene::hasNodeTagAt);
1082 cls.addFunc("getNodeTagsAt", &Scene::getNodeTagsAt);
1083 cls.addFunc("collectIdsByTagAt", &Scene::collectIdsByTagAt);
1084 cls.addFunc("setNodeLayerAt", &Scene::setNodeLayerAt);
1085 cls.addFunc("getNodeLayerAt", &Scene::getNodeLayerAt);
1086 cls.addFunc("setNodeEventHandlerAt",
1087 [](Scene *self, std::string hostName, ssq::Object cb) {
1088 return self->setNodeEventHandlerAt(hostName, cb);
1089 });
1090
1091 // Bounds / serialization / picking / culling
1092 cls.addFunc("setNodeBoundsAt", &Scene::setNodeBoundsAt);
1093 cls.addFunc("hasNodeBoundsAt", &Scene::hasNodeBoundsAt);
1094 cls.addFunc("getNodeBoundsAt", &Scene::getNodeBoundsAt);
1095 cls.addFunc("serializeHostAt", &Scene::serializeHostAt);
1096 cls.addFunc("deserializeHostAt", &Scene::deserializeHostAt);
1097 cls.addFunc("pickRayAt", &Scene::pickRayAt);
1098 cls.addFunc("pickScreenAt", &Scene::pickScreenAt);
1099 cls.addFunc("collectFrustumIdsAt", &Scene::collectFrustumIdsAt);
1100 cls.addFunc("syncSpatialIndexAt", &Scene::syncSpatialIndexAt);
1101 cls.addFunc("nodeIdFromSpatialIdAt", &Scene::nodeIdFromSpatialIdAt);
1102
1103 // Per-node script entity primitives (called from injected script wrappers).
1104 // ssq::Object params are bound via lambdas (member-pointer path may not
1105 // support raw script objects; lambda path is used by eve.component too).
1106 cls.addFunc("rootEntity",
1107 [](Scene *self, std::string hostName, std::string nodeId,
1108 ssq::Object instance) {
1109 return self->rootEntity(hostName, nodeId, instance);
1110 });
1111 cls.addFunc("unrootEntityAt", &Scene::unrootEntityAt);
1112 cls.addFunc("forEachEntity",
1113 [](Scene *self, std::string hostName, std::string nodeId,
1114 ssq::Object cb) {
1115 return self->forEachEntity(hostName, nodeId, cb);
1116 });
1117 cls.addFunc("getNodeRefAt", &Scene::getNodeRefAt);
1118 cls.addFunc("getNodeRefByPathAt", &Scene::getNodeRefByPathAt);
1119
1120 cls.addFunc("hasNode", &Scene::hasNode);
1121 cls.addFunc("getNodeCount", &Scene::getNodeCount);
1122 cls.addFunc("getRootId", &Scene::getRootId);
1123 cls.addFunc("getParentId", &Scene::getParentId);
1124 cls.addFunc("getChildCount", &Scene::getChildCount);
1125 cls.addFunc("getChildIdAt", &Scene::getChildIdAt);
1126 cls.addFunc("findIdByName", &Scene::findIdByName);
1127 cls.addFunc("findIdByPath", &Scene::findIdByPath);
1128 cls.addFunc("getNodePath", &Scene::getNodePath);
1129 cls.addFunc("isAncestor", &Scene::isAncestor);
1130 cls.addFunc("isDescendant", &Scene::isDescendant);
1131 cls.addFunc("collectIds", &Scene::collectIds);
1132 cls.addFunc("collectIdsFrom", &Scene::collectIdsFrom);
1133 cls.addFunc("collectIdsByName", &Scene::collectIdsByName);
1134 cls.addFunc("collectIdsVisible", &Scene::collectIdsVisible);
1135 cls.addFunc("collectChildIds", &Scene::collectChildIds);
1136 cls.addFunc("walkDepthFirstIds", &Scene::walkDepthFirstIds);
1137 cls.addFunc("walkBreadthFirstIds", &Scene::walkBreadthFirstIds);
1138
1139 cls.addFunc("beginBuild", &Scene::beginBuild);
1140 cls.addFunc("beginNode", &Scene::beginNode);
1141 cls.addFunc("beginGroup", &Scene::beginGroup);
1142 cls.addFunc("end", &Scene::end);
1143 cls.addFunc("addNode", &Scene::addNode);
1144 cls.addFunc("setBuildPosition", &Scene::setBuildPosition);
1145 cls.addFunc("setBuildRotation", &Scene::setBuildRotation);
1146 cls.addFunc("setBuildScale", &Scene::setBuildScale);
1147 cls.addFunc("setBuildSpace", &Scene::setBuildSpace);
1148 cls.addFunc("setBuildVisible", &Scene::setBuildVisible);
1149 cls.addFunc("mountBuild", &Scene::mountBuild);
1150 cls.addFunc("mountBuildAs", &Scene::mountBuildAs);
1151 cls.addFunc("remountBuildAs", &Scene::remountBuildAs);
1152}
1153
1154} // namespace eve::scene
1155
struct SQVM * HSQUIRRELVM
std::string value
HSQUIRRELVM vm
Definition ECS.cpp:20
HSQOBJECT cls
Definition ECS.cpp:21
std::string id
int y
Definition Grass.cpp:135
int z
Definition Grass.cpp:135
int x
Definition Grass.cpp:135
glm::vec3 n
Definition Grass.cpp:64
int h
const FusedGroup & group
uint32_t a
uint32_t b
uint32_t c
#define Module_IMPL(ModuleName, newExpr)
Definition Module.h:24
TileLayer * layer
glm::mat4 view
const char * name
Definition RockMesh.cpp:21
SettlementPipeline::Stage fn
int d
virtual std::string getName() const =0
ECS mount point for one scene graph (full scene or nested subtree root). Isomorphic to eve::ui::UIHos...
Definition SceneHost.h:80
static SceneHost * createHost(const std::string &name="")
const std::string & getName()
Script handle to a scene node (hostName + nodeId). Deliberately holds strings rather than arena point...
float getWorldPositionZ() const
std::vector< float > getForward() const
Normalized local axes in world space (forward = +Z).
bool isValid() const
True when host + node currently resolve.
std::string getHostName() const
float getWorldPositionX() const
float getWorldPositionY() const
float getRotationPitch() const
std::string getNodeId() const
Scene * getScene() const
The eve.Scene module instance (for entity binding forwarding).
std::string getPath() const
std::string getChildIdAt(int ordinal) const
std::vector< float > getPosition() const
std::vector< float > getWorldPosition() const
std::vector< float > getRotation() const
bool setPosition(float x, float y, float z)
bool setScale(float sx, float sy, float sz)
float getRotationRoll() const
std::vector< float > getScale() const
std::string getParentId() const
std::vector< float > getUp() const
bool setRotation(float yaw, float pitch, float roll)
std::vector< float > getRight() const
std::vector< float > getWorldMatrix() const
Column-major 4x4 world matrix, 16 floats.
Per-node ECS identity (created lazily when a node needs script bindings or engine components)....
Definition SceneObject.h:21
static SceneObject * createObject(const std::string &hostName, const std::string &nodeId)
void release() override
Definition SceneObject.h:25
Declarative scene module (eve.Scene).
Definition Scene.h:48
void beginNode(const std::string &id, const std::string &name="")
Definition Scene.cpp:721
SceneNodeRef * getNodeRefByPathAt(const std::string &hostName, const std::string &path) const
Definition Scene.cpp:929
bool linkPhysics3DAt(const std::string &hostName, const std::string &nodeId, physics::Body3D *b, const std::string &mode)
SceneHost * resolveHost(const std::string &hostName) const
Host-qualified resolution: empty hostName → currently selected host.
Definition Scene.cpp:807
void setBuildSpace(const std::string &space)
Definition Scene.cpp:768
bool mountBuild()
Definition Scene.cpp:774
std::vector< std::string > collectIdsFrom(const std::string &id)
void updateTransforms()
Propagate transforms (+ link sync) for all hosts (or current if selected).
Definition Scene.cpp:660
bool setNodeParentAt(const std::string &hostName, const std::string &childId, const std::string &parentId)
Reparent by id; empty parentId detaches. Cycle-safe.
std::vector< std::string > collectIds()
bool linkCamera3DAt(const std::string &hostName, const std::string &nodeId, graphics::Camera3D *c)
std::string serializeHostAt(const std::string &hostName) const
Definition Scene.cpp:696
std::vector< std::string > collectIdsByTagAt(const std::string &hostName, const std::string &tag) const
void setBuildVisible(bool visible)
Definition Scene.cpp:770
std::vector< float > getNodeRotationAt(const std::string &hostName, const std::string &nodeId) const
std::string findIdByPath(const std::string &path)
bool rootEntity(const std::string &hostName, const std::string &nodeId, ssq::Object instance)
Root one script instance on a node (creates SceneObject lazily).
Definition Scene.cpp:834
std::string pickScreenAt(const std::string &hostName, graphics::Camera3D *cam, float screenX, float screenY, float viewW, float viewH) const
Screen-space picking through a Camera3D (camera.screenToRay).
void beginBuild()
Definition Scene.cpp:708
bool setNodeQuaternionAt(const std::string &hostName, const std::string &nodeId, float qx, float qy, float qz, float qw)
SceneHost * remount(NodeDesc root)
Replaces the selected host's tree.
Definition Scene.cpp:616
bool getNodeVisibleAt(const std::string &hostName, const std::string &nodeId) const
std::vector< float > worldToLocalAt(const std::string &hostName, const std::string &nodeId, float x, float y, float z) const
SceneHost * remountAs(const std::string &name, NodeDesc root)
Creates/replaces a named host (does not select it).
Definition Scene.cpp:632
void setBuildRotation(float yaw, float pitch=0.f, float roll=0.f)
Definition Scene.cpp:754
void updateScripts(float dt)
updateTransformsAll() + call update(dt) on every rooted instance.
Definition Scene.cpp:898
std::vector< float > getNodePositionAt(const std::string &hostName, const std::string &nodeId) const
SceneHost * findHost(const std::string &name) const
Finds a host by name, or nullptr.
Definition Scene.cpp:643
std::vector< float > getNodeWorldPositionAt(const std::string &hostName, const std::string &nodeId) const
int getNodeLayerAt(const std::string &hostName, const std::string &nodeId) const
bool linkPhysics2DAt(const std::string &hostName, const std::string &nodeId, physics::Body *b, const std::string &mode)
bool removeNodeAt(const std::string &hostName, const std::string &nodeId)
Detach node from its parent (arena node stays; rebuild to delete).
SceneHost * mountAs(const std::string &name, NodeDesc root)
Creates/replaces a named host from a NodeDesc tree and selects it.
Definition Scene.cpp:604
SceneNodeRef * getNodeRefAt(const std::string &hostName, const std::string &nodeId) const
Definition Scene.cpp:921
std::vector< std::string > collectChildIds(const std::string &parentId)
void setHostVisible(bool visible)
Shows/hides every host (or the selected one when a host is selected).
Definition Scene.cpp:652
bool unlinkNodeAt(const std::string &hostName, const std::string &nodeId)
Remove every link on the node.
bool setNodePosition(const std::string &id, float x, float y, float z)
Script-friendly TRS setters on the current host; each marks the transform dirty.
std::vector< std::string > collectIdsVisible(bool visible)
bool linkAudio3DAt(const std::string &hostName, const std::string &nodeId, audio::Source *s)
bool setNodeLookAtAt(const std::string &hostName, const std::string &nodeId, float tx, float ty, float tz)
Orient node so its local +Z axis points at (tx,ty,tz).
std::string currentHostName() const
Definition Scene.cpp:917
std::vector< float > getNodeWorldRotationAt(const std::string &hostName, const std::string &nodeId) const
bool hasNode(const std::string &id)
void bindOwner(uint32_t ownerId)
Binds the selected host to a UI/scene owner id.
Definition Scene.cpp:647
std::string getNodePath(const std::string &id)
std::string findIdByName(const std::string &name)
int getChildCount(const std::string &id)
bool removeNodeTagAt(const std::string &hostName, const std::string &nodeId, const std::string &tag)
bool linkRenderable3DAt(const std::string &hostName, const std::string &nodeId, graphics::Renderable3D *r)
void setBuildPosition(float x, float y, float z=0.f)
Definition Scene.cpp:747
bool mountBuildAs(const std::string &name)
Definition Scene.cpp:782
bool isAncestor(const std::string &ancestorId, const std::string &nodeId)
void beginGroup(const std::string &id="")
Definition Scene.cpp:726
SceneHost * remountReconcile(NodeDesc root)
Remount with key reconcile (props-only when structure matches).
Definition Scene.cpp:624
bool setNodeScale(const std::string &id, float sx, float sy, float sz)
Sets the local scale of a node.
std::string getParentId(const std::string &id)
std::vector< std::string > collectIdsByName(const std::string &name)
void setHostLayer(int layer)
Sets the render layer of every host (or the selected one).
Definition Scene.cpp:656
bool unlinkNodeKindAt(const std::string &hostName, const std::string &nodeId, const std::string &kind)
Remove links of one kind ("renderable2d"|"renderable3d"|"physics2d"|...).
SceneHost * mount(NodeDesc root)
Mounts the tree as an auto-named host and selects it.
Definition Scene.cpp:614
std::vector< float > getNodeScaleAt(const std::string &hostName, const std::string &nodeId) const
std::string nodeIdFromSpatialIdAt(const std::string &hostName, int index) const
Map a spatial-index id (arena index) back to a node id.
std::vector< std::string > getNodeTagsAt(const std::string &hostName, const std::string &nodeId) const
void updateTransformsAll()
Propagate transforms for every host regardless of selection.
Definition Scene.cpp:665
int linkCountAt(const std::string &hostName, const std::string &nodeId)
bool hasNodeBoundsAt(const std::string &hostName, const std::string &nodeId) const
bool deserializeHostAt(const std::string &hostName, const std::string &json)
Definition Scene.cpp:701
std::vector< std::string > walkBreadthFirstIds()
bool unrootEntityAt(const std::string &hostName, const std::string &nodeId, int index)
Remove a rooted script instance by its index in the binding list.
Definition Scene.cpp:854
bool remountBuildAs(const std::string &name)
Definition Scene.cpp:790
bool linkRenderable3D(const std::string &nodeId, graphics::Renderable3D *r)
Links a 3D renderable to a node in the current host.
bool setNodeLayerAt(const std::string &hostName, const std::string &nodeId, int layer)
bool setNodeRotation(const std::string &id, float yaw, float pitch, float roll)
Sets the local rotation (yaw/pitch/roll in degrees) of a node.
bool unlinkNode(const std::string &nodeId)
Removes every link on a node in the current host.
bool syncSpatialIndexAt(const std::string &hostName, spatial::Octree *ot) const
Insert every bounded node's world AABB into an octree (id = arena index).
std::vector< float > getNodeQuaternionAt(const std::string &hostName, const std::string &nodeId) const
void addNode(const std::string &id, const std::string &name="")
Definition Scene.cpp:743
bool removeChildAt(const std::string &hostName, const std::string &parentId, const std::string &childId)
bool setNodeBoundsAt(const std::string &hostName, const std::string &nodeId, float minX, float minY, float minZ, float maxX, float maxY, float maxZ)
bool setNodeVisible(const std::string &id, bool visible)
Shows/hides a node.
std::string getRootId()
bool addChildAt(const std::string &hostName, const std::string &parentId, const std::string &childId)
std::string getChildIdAt(const std::string &parentId, int childOrdinal)
bool linkRenderable2DAt(const std::string &hostName, const std::string &nodeId, graphics::Renderable2D *r)
std::vector< float > localToWorldAt(const std::string &hostName, const std::string &nodeId, float x, float y, float z) const
std::vector< float > getNodeWorldScaleAt(const std::string &hostName, const std::string &nodeId) const
bool select(const std::string &name)
Selects a named host; false when it does not exist.
Definition Scene.cpp:636
bool isDescendant(const std::string &nodeId, const std::string &ancestorId)
bool hasNodeTagAt(const std::string &hostName, const std::string &nodeId, const std::string &tag) const
std::vector< std::string > walkDepthFirstIds()
DFS order of ids under root (same as collectIds). Kept for script naming clarity.
std::vector< std::string > collectFrustumIdsAt(const std::string &hostName, graphics::Camera3D *cam, float viewW, float viewH) const
Ids of nodes whose world AABB intersects the camera frustum.
void setBuildScale(float sx, float sy, float sz=1.f)
Definition Scene.cpp:761
std::vector< float > getNodeBoundsAt(const std::string &hostName, const std::string &nodeId) const
std::string pickRayAt(const std::string &hostName, float ox, float oy, float oz, float dx, float dy, float dz) const
Nearest node id hit by a world ray (nodes with bounds), or "".
bool addNodeTagAt(const std::string &hostName, const std::string &nodeId, const std::string &tag)
SceneHost * findHostByOwner(uint32_t ownerId) const
Finds the host bound to an owner id, or nullptr.
Definition Scene.cpp:645
bool linkRenderable2D(const std::string &nodeId, graphics::Renderable2D *r)
Links a 2D renderable to a node in the current host.
int forEachEntity(const std::string &hostName, const std::string &nodeId, ssq::Object cb)
Call cb(instance, index) for every rooted instance; returns count.
Definition Scene.cpp:875
static void updateHost(SceneHost *host)
void registerSceneCapabilities()
NodeDesc node(std::string id, std::vector< NodeDesc > children, std::string name)
Definition NodeDesc.cpp:214
void registerPostEcsHook(PostEcsHook fn)
Definition ECS.cpp:594
Declarative scene-node description (build once / on dirty → flatten into SceneHost::Tree)....
Definition NodeDesc.h:15
std::string id
Definition NodeDesc.h:16
std::vector< NodeDesc > children
Definition NodeDesc.h:33
std::string space
"2d" or "3d" (string enum per module convention).
Definition NodeDesc.h:21
Retained scene node (arena). Conceptual GameObject; isomorphic to eve::ui::UINode.
Definition SceneHost.h:39