SleakEngine 0.1.0
C++23 multi-backend game engine
Loading...
Searching...
No Matches
SceneBase.cpp
Go to the documentation of this file.
2#include <Core/GameObject.hpp>
3#include <Core/Logger.hpp>
4#include <Camera/Camera.hpp>
6#include <Lighting/Light.hpp>
8#include <Runtime/Skybox.hpp>
13
14namespace Sleak {
15
16/// Calls fn on obj and the descendants it owns, skipping scene owned ones.
17template <typename Fn>
18static void ForEachOwned(GameObject* obj, Fn& fn) {
19 if (!obj) return;
20 fn(obj);
21 const auto& children = obj->GetChildren();
22 for (size_t i = 0; i < children.GetSize(); ++i) {
23 if (children[i] && !children[i]->IsOwnedByScene())
24 ForEachOwned(children[i], fn);
25 }
26}
27
28/// Depth-first search of obj and the descendants it owns.
29template <typename Pred>
30static GameObject* FindOwned(GameObject* obj, const Pred& pred) {
31 if (!obj) return nullptr;
32 if (pred(obj)) return obj;
33 const auto& children = obj->GetChildren();
34 for (size_t i = 0; i < children.GetSize(); ++i) {
35 if (!children[i] || children[i]->IsOwnedByScene()) continue;
36 if (GameObject* found = FindOwned(children[i], pred)) return found;
37 }
38 return nullptr;
39}
40
41/// Registers lights and colliders in obj's owned subtree, skipping null
42/// services.
43static void RegisterTree(GameObject* obj, LightManager* lights,
44 Physics::PhysicsWorld* world) {
45 auto reg = [&](GameObject* o) {
46 if (lights && o->IsLight())
47 lights->RegisterLight(static_cast<Light*>(o));
48 if (world) {
49 if (auto* collider = o->GetComponent<ColliderComponent>())
50 world->RegisterCollider(collider);
51 }
52 };
53 ForEachOwned(obj, reg);
54}
55
56/// Unregisters lights and colliders in obj's owned subtree.
57static void UnregisterTree(GameObject* obj, LightManager* lights,
58 Physics::PhysicsWorld* world) {
59 auto unreg = [&](GameObject* o) {
60 if (lights && o->IsLight())
61 lights->UnregisterLight(static_cast<Light*>(o));
62 if (world) {
63 if (auto* collider = o->GetComponent<ColliderComponent>())
64 world->UnregisterCollider(collider);
65 }
66 };
67 ForEachOwned(obj, unreg);
68}
69
73 "Scene '{0}' destroyed without Unload(); "
74 "OnDeactivate/OnUnload did not run.",
75 name);
76 }
77
79 delete m_lightManager;
80 m_lightManager = nullptr;
81 delete m_physicsWorld;
82 m_physicsWorld = nullptr;
83 delete m_skybox;
84 m_skybox = nullptr;
85}
86
93
96
97 Deactivate();
98
100
101 OnUnload();
102
104
106 q->ClearAll();
107
108 bInitialized = false;
109 bHasBegun = false;
111}
112
114 if (state == SceneState::Active) return;
115
116 if (!bInitialized) Initialize();
117
119 bActive = true;
120
121 OnActivate();
122
123 if (!bHasBegun) {
124 bHasBegun = true;
125 Begin();
126 }
127}
128
130 if (state != SceneState::Active) return;
131 OnDeactivate();
132 bActive = false;
134}
135
137 if (state != SceneState::Active) return;
139 bActive = false;
140}
141
143 if (state != SceneState::Paused) return;
145 bActive = true;
146}
147
149 if (bInitialized) return true;
150
151 LightManager* newLights = nullptr;
152 if (!m_lightManager) {
153 m_lightManager = newLights = new LightManager();
155 }
156
157 Physics::PhysicsWorld* newWorld = nullptr;
158 if (!m_physicsWorld) {
159 m_physicsWorld = newWorld = new Physics::PhysicsWorld();
160 }
161
162 if (newLights || newWorld) {
163 for (size_t i = 0; i < Objects.GetSize(); ++i) {
164 RegisterTree(Objects[i], newLights, newWorld);
165 }
166 }
167
168 if (m_skybox && !m_skybox->IsInitialized()) {
170 }
171
172 for (size_t i = 0; i < Objects.GetSize(); ++i) {
173 if (Objects[i] && !Objects[i]->IsInitialized())
174 Objects[i]->Initialize();
175 }
176
177 bInitialized = true;
178 return true;
179}
180
182 for (size_t i = 0; i < Objects.GetSize(); ++i) {
183 if (Objects[i]) Objects[i]->SetActive(true);
184 }
185}
186
187void SceneBase::Update(float deltaTime) {
188 if (!bActive) return;
189
190 // Update objects FIRST so Camera::View/Projection reflect this frame's
191 // rotation/position before any render-side UBO computes InvViewProj.
192 // Why: LightManager::UpdateDeferredCB reads the static Camera matrices
193 // to build InvViewProj for the deferred lighting pass. If it ran
194 // before the camera object updated, InvViewProj was one frame stale
195 // and the lighting pass reconstructed wrong world positions on any
196 // camera rotation, causing whole-terrain shadow flicker.
197 for (size_t i = 0; i < Objects.GetSize(); ++i) {
198 if (Objects[i] && Objects[i]->IsActive() && !Objects[i]->HasParent()) {
199 Objects[i]->Update(deltaTime);
200 }
201 }
202
203 if (m_lightManager)
205
206 if (m_skybox)
207 m_skybox->Render();
208
209 if (m_physicsWorld)
210 m_physicsWorld->Step(deltaTime);
211
213 auto drawColliderShape = [](ColliderComponent* collider, const Math::Vector3D& worldPos, const Math::Vector3D& worldScale) {
214 const auto& shape = collider->GetShape();
215
216 if (auto* aabb = std::get_if<Physics::AABB>(&shape)) {
217 Physics::AABB worldAABB(
218 aabb->min * worldScale + worldPos,
219 aabb->max * worldScale + worldPos);
220 DebugLineRenderer::DrawAABB(worldAABB, 0.0f, 1.0f, 0.0f);
221 } else if (auto* sphere = std::get_if<Physics::BoundingSphere>(&shape)) {
222 Math::Vector3D center = sphere->center * worldScale + worldPos;
223 float maxScale = std::max({worldScale.GetX(), worldScale.GetY(), worldScale.GetZ()});
224 DebugLineRenderer::DrawSphere(center, sphere->radius * maxScale, 0.0f, 1.0f, 0.0f);
225 } else if (auto* capsule = std::get_if<Physics::BoundingCapsule>(&shape)) {
226 Physics::BoundingCapsule worldCapsule = *capsule;
227 worldCapsule.center = capsule->center * worldScale + worldPos;
228 float maxScale = std::max({worldScale.GetX(), worldScale.GetY(), worldScale.GetZ()});
229 worldCapsule.radius = capsule->radius * maxScale;
230 worldCapsule.halfHeight = capsule->halfHeight * maxScale;
231 DebugLineRenderer::DrawCapsule(worldCapsule, 0.0f, 1.0f, 0.0f);
232 }
233 };
234
235 auto drawObject = [&](GameObject* obj) {
236 auto* collider = obj->GetComponent<ColliderComponent>();
237 if (!collider) return;
238
239 Math::Vector3D pos(0, 0, 0), scale(1, 1, 1);
240 auto* transform = obj->GetComponent<TransformComponent>();
241 if (transform) {
242 pos = transform->GetWorldPosition() + collider->GetOffset();
243 scale = transform->GetWorldScale();
244 } else if (auto* cam = dynamic_cast<Camera*>(obj)) {
245 pos = cam->GetPosition() + collider->GetOffset();
246 }
247 drawColliderShape(collider, pos, scale);
248 };
249
250 for (size_t i = 0; i < Objects.GetSize(); ++i) {
251 ForEachOwned(Objects[i], drawObject);
252 }
253 }
254
256
258}
259
260void SceneBase::FixedUpdate(float fixedDeltaTime) {
261 if (!bActive) return;
262
263 for (size_t i = 0; i < Objects.GetSize(); ++i) {
264 if (Objects[i] && Objects[i]->IsActive() && !Objects[i]->HasParent()) {
265 Objects[i]->FixedUpdate(fixedDeltaTime);
266 }
267 }
268}
269
270void SceneBase::LateUpdate(float deltaTime) {
271 if (!bActive) return;
272
273 for (size_t i = 0; i < Objects.GetSize(); ++i) {
274 if (Objects[i] && Objects[i]->IsActive() && !Objects[i]->HasParent()) {
275 Objects[i]->LateUpdate(deltaTime);
276 }
277 }
278}
279
281 if (!object) return;
282 if (Objects.indexOf(object) != -1) return; // already in scene
283
284 Objects.add(object);
285 object->m_ownedByScene = true;
286
288
289 if (bInitialized && !object->IsInitialized()) {
290 object->Initialize();
291 }
292}
293
295 if (!object) return;
296 if (Objects.indexOf(object) == -1) return;
297
298 List<GameObject*> children = object->GetChildren();
299 for (size_t i = 0; i < children.GetSize(); ++i) {
300 if (children[i] && children[i]->IsOwnedByScene())
301 RemoveObject(children[i]);
302 }
303
305
306 auto dropPending = [&](GameObject* o) {
307 int pending = m_pendingDestroy.indexOf(o);
308 if (pending != -1) m_pendingDestroy.erase(pending);
309 };
310 ForEachOwned(object, dropPending);
311
312 Objects.erase(Objects.indexOf(object));
313 delete object;
314}
315
317 if (!object) return;
318 if (object->IsPendingDestroy()) return;
319
320 object->MarkForDestroy();
321 m_pendingDestroy.add(object);
322
323 const auto& children = object->GetChildren();
324 for (size_t i = 0; i < children.GetSize(); ++i) {
325 if (children[i] && !children[i]->IsPendingDestroy()) {
326 DestroyObject(children[i]);
327 }
328 }
329}
330
331GameObject* SceneBase::FindObjectByName(const std::string& objectName) {
332 auto match = [&](GameObject* o) { return o->GetName() == objectName; };
333 for (size_t i = 0; i < Objects.GetSize(); ++i) {
334 if (GameObject* found = FindOwned(Objects[i], match)) return found;
335 }
336 return nullptr;
337}
338
340 auto match = [&](GameObject* o) { return o->GetUniqueID() == id; };
341 for (size_t i = 0; i < Objects.GetSize(); ++i) {
342 if (GameObject* found = FindOwned(Objects[i], match)) return found;
343 }
344 return nullptr;
345}
346
348 List<GameObject*> result;
349 auto collect = [&](GameObject* o) {
350 if (o->GetTag() == tag) result.add(o);
351 };
352 for (size_t i = 0; i < Objects.GetSize(); ++i) {
353 ForEachOwned(Objects[i], collect);
354 }
355 return result;
356}
357
359 if (m_pendingDestroy.empty()) return;
360
361 List<GameObject*> pending = std::move(m_pendingDestroy);
362
363 for (size_t i = 0; i < pending.GetSize(); ++i) {
364 GameObject* obj = pending[i];
366 int index = Objects.indexOf(obj);
367 if (index != -1) Objects.erase(index);
368 }
369
370 // Detach first so no pending object is deleted through its parent.
371 for (size_t i = 0; i < pending.GetSize(); ++i) {
372 if (pending[i]) pending[i]->SetParent(nullptr);
373 }
374 for (size_t i = 0; i < pending.GetSize(); ++i) {
375 delete pending[i];
376 }
377}
378
380 m_pendingDestroy.clear();
381
382 List<GameObject*> objects = std::move(Objects);
383
384 for (size_t i = 0; i < objects.GetSize(); ++i) {
386 }
387 for (size_t i = 0; i < objects.GetSize(); ++i) {
388 if (objects[i]) objects[i]->SetParent(nullptr);
389 }
390 for (size_t i = 0; i < objects.GetSize(); ++i) {
391 delete objects[i];
392 }
393}
394
396 if (m_skybox) {
397 delete m_skybox;
398 }
399 m_skybox = skybox;
402 }
403}
404
405} // namespace Sleak
#define SLEAK_ERROR(...)
Definition Logger.hpp:22
const Physics::ColliderShape & GetShape() const
Math::Vector3D GetOffset() const
static void Flush(Camera *camera)
Uploads all queued lines and draws them in one pass, then clears the queue.
static void DrawSphere(const Math::Vector3D &center, float radius, float r, float g, float b, float a=1.0f, int segments=16)
Queues a wireframe sphere approximated with segments latitude/longitude rings.
static void DrawAABB(const Physics::AABB &aabb, float r, float g, float b, float a=1.0f)
Queues a wireframe box outline.
static void DrawCapsule(const Physics::BoundingCapsule &capsule, float r, float g, float b, float a=1.0f, int segments=16)
Queues a wireframe capsule outline.
const List< GameObject * > & GetChildren() const
void MarkForDestroy()
Flags the object for deferred destruction on the next scene pass.
bool IsPendingDestroy() const
bool IsInitialized() const
void RegisterLight(Light *light)
void Initialize()
Allocates the GPU light buffer; call once before the first UpdateAndBind.
void UpdateAndBind()
Packs every registered light and the fog/ambient parameters into the light buffer and binds it.
void UnregisterLight(Light *light)
Implements a dynamic array-like list for storing and managing a collection of elements.
Definition List.hpp:20
void add(const T &value)
Definition List.hpp:113
size_t GetSize() const
Definition List.hpp:151
void RegisterCollider(ColliderComponent *collider)
void UnregisterCollider(ColliderComponent *collider)
static RenderCommandQueue * GetInstance()
Lazily creates and returns the process-wide singleton instance.
List< GameObject * > FindObjectsByTag(const std::string &tag)
Collects every object whose tag matches, children included.
void Unload()
Deactivates if active, calls OnUnload(), and destroys all owned objects.
Definition SceneBase.cpp:94
void Deactivate()
Marks the scene inactive and calls OnDeactivate().
List< GameObject * > Objects
virtual void Update(float deltaTime)=0
Advances all active, root-level objects by deltaTime, then steps lighting and physics.
virtual void FixedUpdate(float fixedDeltaTime)
Advances all active, root-level objects on the fixed timestep.
void Resume()
Reactivates a paused scene without re-running OnActivate().
void SetSkybox(Skybox *skybox)
Replaces the scene's skybox, deleting the previous one.
void ProcessPendingDestroy()
Actually deletes objects queued by DestroyObject().
GameObject * FindObjectByName(const std::string &name)
First object with a matching name, children included.
virtual void OnLoad()
Override to load scene-specific assets; called once from Load().
Definition SceneBase.hpp:82
void DestroyObject(GameObject *object)
Queues an object for destruction; actually freed on the next ProcessPendingDestroy().
virtual void RemoveObject(GameObject *object)
SceneState state
Camera * m_activeCamera
virtual void OnUnload()
Override to release scene-specific assets; called from Unload().
Definition SceneBase.hpp:84
void DestroyAllObjects()
Destroys every object still owned by the scene, e.g. during Unload().
List< GameObject * > m_pendingDestroy
void Activate()
Marks the scene active, calls OnActivate(), then Begin() once.
virtual void OnActivate()
Override for logic that should run when the scene becomes active.
Definition SceneBase.hpp:88
LightManager * m_lightManager
GameObject * FindObjectByID(uint64_t id)
Object with a matching unique ID, children included.
void Load()
Moves Unloaded -> Loading -> Active, calling OnLoad() and Initialize().
Definition SceneBase.cpp:87
virtual void OnDeactivate()
Override for logic that should run when the scene stops being active.
Definition SceneBase.hpp:90
void Pause()
Freezes the scene without tearing it down; leaves it in the Paused state.
virtual void AddObject(GameObject *object)
Takes ownership of object, registering it with lighting and physics as needed.
virtual bool Initialize()
Runs once before the scene's first Begin()/Update().
std::string name
Physics::PhysicsWorld * m_physicsWorld
bool IsActive() const
virtual void Begin()=0
Activates every owned object; runs once per Load().
virtual ~SceneBase()
Definition SceneBase.cpp:70
virtual void LateUpdate(float deltaTime)
Advances all active, root-level objects after the main Update pass.
void Render()
Submit render commands for this frame.
Definition Skybox.cpp:123
bool IsInitialized() const
Definition Skybox.hpp:44
void Initialize()
Create GPU resources (shader, buffers, cubemap texture).
Definition Skybox.cpp:40
Represents the position, rotation, and scale of an entity in 3D space.
Root namespace for everything the engine exposes.
Definition Camera.hpp:10
static GameObject * FindOwned(GameObject *obj, const Pred &pred)
Depth-first search of obj and the descendants it owns.
Definition SceneBase.cpp:30
static void UnregisterTree(GameObject *obj, LightManager *lights, Physics::PhysicsWorld *world)
Unregisters lights and colliders in obj's owned subtree.
Definition SceneBase.cpp:57
static void ForEachOwned(GameObject *obj, Fn &fn)
Calls fn on obj and the descendants it owns, skipping scene owned ones.
Definition SceneBase.cpp:18
static void RegisterTree(GameObject *obj, LightManager *lights, Physics::PhysicsWorld *world)
Definition SceneBase.cpp:43