#pragma once #include #include #include #include #include #include #include #include #include #include #include "scene/vk_loader.h" class EngineContext; class PlanetSystem; struct RenderObject { // Geometry and material binding uint32_t indexCount; uint32_t firstIndex; VkBuffer indexBuffer; VkBuffer vertexBuffer; // for RG buffer tracking (device-address path still used in shader) MaterialInstance *material; Bounds bounds; glm::mat4 transform; VkDeviceAddress vertexBufferAddress; // Optional debug/source information (may be null/unused for some objects). MeshAsset *sourceMesh = nullptr; uint32_t surfaceIndex = 0; // Unique per-draw identifier for ID-buffer picking (0 = none). uint32_t objectID = 0; // Optional logical owner for editor/picking (instance name etc.). enum class OwnerType : uint8_t { None = 0, StaticGLTF, // loaded scene GLTFInstance, // runtime glTF instance with transform MeshInstance // dynamic primitive/mesh instance }; OwnerType ownerType = OwnerType::None; std::string ownerName; // Optional owning glTF scene and node for this draw (null for procedural/dynamic meshes). LoadedGLTF *sourceScene = nullptr; Node *sourceNode = nullptr; }; struct DrawContext { std::vector OpaqueSurfaces; std::vector TransparentSurfaces; // Monotonic counter used to assign stable per-frame object IDs. uint32_t nextID = 1; // Optional per-instance glTF node local overrides (additive layer in local space). // When non-null, MeshNode::Draw will rebuild world transforms using these offsets. const std::unordered_map *gltfNodeLocalOverrides = nullptr; }; class SceneManager { public: SceneManager(); ~SceneManager(); void init(EngineContext *context); void cleanup(); void update_scene(); Camera &getMainCamera() { return mainCamera; } const Camera &getMainCamera() const { return mainCamera; } CameraRig &getCameraRig() { return cameraRig; } const CameraRig &getCameraRig() const { return cameraRig; } WorldVec3 get_world_origin() const { return _origin_world; } glm::vec3 get_camera_local_position() const { return _camera_position_local; } // Ray-pick against current DrawContext using per-surface Bounds. // mousePosPixels is in window coordinates (SDL), origin at top-left. // Returns true if any object was hit, filling outObject and outWorldPos. bool pick(const glm::vec2 &mousePosPixels, RenderObject &outObject, WorldVec3 &outWorldPos); // Resolve an object ID (from ID buffer) back to the RenderObject for // the most recently built DrawContext. Returns false if not found or id==0. bool resolveObjectID(uint32_t id, RenderObject &outObject) const; // Select all objects whose projected bounds intersect the given screen-space // rectangle (window coordinates, origin top-left). Results are appended to outObjects. void selectRect(const glm::vec2 &p0, const glm::vec2 &p1, std::vector &outObjects) const; const GPUSceneData &getSceneData() const { return sceneData; } // Sunlight (directional light) access void setSunlightDirection(const glm::vec3& dir); glm::vec3 getSunlightDirection() const; void setSunlightColor(const glm::vec3& color, float intensity); glm::vec3 getSunlightColor() const; float getSunlightIntensity() const; // Delta time (seconds) for the current frame float getDeltaTime() const { return _deltaTime; } DrawContext &getMainDrawContext() { return mainDrawContext; } void loadScene(const std::string &name, std::shared_ptr scene); std::shared_ptr getScene(const std::string &name); // Dynamic renderables API struct MeshInstance { std::shared_ptr mesh; WorldVec3 translation_world{0.0, 0.0, 0.0}; glm::quat rotation{1.0f, 0.0f, 0.0f, 0.0f}; glm::vec3 scale{1.0f, 1.0f, 1.0f}; std::optional boundsTypeOverride; }; void addMeshInstance(const std::string &name, std::shared_ptr mesh, const glm::mat4 &transform = glm::mat4(1.f), std::optional boundsType = {}); bool getMeshInstanceTransform(const std::string &name, glm::mat4 &outTransform); bool setMeshInstanceTransform(const std::string &name, const glm::mat4 &transform); bool getMeshInstanceTransformLocal(const std::string &name, glm::mat4 &outTransformLocal) const; bool setMeshInstanceTransformLocal(const std::string &name, const glm::mat4 &transformLocal); bool getMeshInstanceTRSWorld(const std::string &name, WorldVec3 &outTranslationWorld, glm::quat &outRotation, glm::vec3 &outScale) const; bool setMeshInstanceTRSWorld(const std::string &name, const WorldVec3 &translationWorld, const glm::quat &rotation, const glm::vec3 &scale); bool removeMeshInstance(const std::string &name); void clearMeshInstances(); // GLTF instances (runtime-spawned scenes with transforms) struct GLTFInstance { std::shared_ptr scene; WorldVec3 translation_world{0.0, 0.0, 0.0}; glm::quat rotation{1.0f, 0.0f, 0.0f, 0.0f}; glm::vec3 scale{1.0f, 1.0f, 1.0f}; LoadedGLTF::AnimationState animation; // Per-instance local-space pose offsets for nodes in this glTF scene. // The offset matrix is post-multiplied onto the node's localTransform. std::unordered_map nodeLocalOverrides; }; void addGLTFInstance(const std::string &name, std::shared_ptr scene, const glm::mat4 &transform = glm::mat4(1.f)); bool removeGLTFInstance(const std::string &name); bool getGLTFInstanceTransform(const std::string &name, glm::mat4 &outTransform); bool setGLTFInstanceTransform(const std::string &name, const glm::mat4 &transform); bool getGLTFInstanceTransformLocal(const std::string &name, glm::mat4 &outTransformLocal) const; bool setGLTFInstanceTransformLocal(const std::string &name, const glm::mat4 &transformLocal); bool getGLTFInstanceTRSWorld(const std::string &name, WorldVec3 &outTranslationWorld, glm::quat &outRotation, glm::vec3 &outScale) const; bool setGLTFInstanceTRSWorld(const std::string &name, const WorldVec3 &translationWorld, const glm::quat &rotation, const glm::vec3 &scale); void clearGLTFInstances(); // Per-instance glTF node pose overrides (local-space, layered on top of animation/base TRS). // 'offset' is post-multiplied onto the node's localTransform for this instance only. bool setGLTFInstanceNodeOffset(const std::string &instanceName, const std::string &nodeName, const glm::mat4 &offset); bool clearGLTFInstanceNodeOffset(const std::string &instanceName, const std::string &nodeName); void clearGLTFInstanceNodeOffsets(const std::string &instanceName); // Animation control helpers (glTF) // Note: a LoadedGLTF may be shared by multiple instances; changing // the active animation on a scene or instance affects all users // of that shared LoadedGLTF. bool setSceneAnimation(const std::string &sceneName, int animationIndex, bool resetTime = true); bool setSceneAnimation(const std::string &sceneName, const std::string &animationName, bool resetTime = true); bool setSceneAnimationLoop(const std::string &sceneName, bool loop); bool setGLTFInstanceAnimation(const std::string &instanceName, int animationIndex, bool resetTime = true); bool setGLTFInstanceAnimation(const std::string &instanceName, const std::string &animationName, bool resetTime = true); bool setGLTFInstanceAnimationLoop(const std::string &instanceName, bool loop); struct PointLight { WorldVec3 position_world; float radius; glm::vec3 color; float intensity; }; void addPointLight(const PointLight &light); void clearPointLights(); size_t getPointLightCount() const { return pointLights.size(); } bool getPointLight(size_t index, PointLight &outLight) const; bool setPointLight(size_t index, const PointLight &light); bool removePointLight(size_t index); const std::vector &getPointLights() const { return pointLights; } struct SpotLight { WorldVec3 position_world; glm::vec3 direction{0.0f, -1.0f, 0.0f}; // world-space unit vector float radius = 10.0f; glm::vec3 color{1.0f, 1.0f, 1.0f}; float intensity = 1.0f; // Cone half-angles in degrees (inner <= outer). float inner_angle_deg = 15.0f; float outer_angle_deg = 25.0f; }; void addSpotLight(const SpotLight &light); void clearSpotLights(); size_t getSpotLightCount() const { return spotLights.size(); } bool getSpotLight(size_t index, SpotLight &outLight) const; bool setSpotLight(size_t index, const SpotLight &light); bool removeSpotLight(size_t index); const std::vector &getSpotLights() const { return spotLights; } struct SceneStats { float scene_update_time = 0.f; } stats; struct PickingDebug { bool usedMeshBVH = false; bool meshBVHHit = false; bool meshBVHFallbackBox = false; uint32_t meshBVHPrimCount = 0; uint32_t meshBVHNodeCount = 0; }; const PickingDebug &getPickingDebug() const { return pickingDebug; } PlanetSystem *get_planet_system() const { return _planetSystem.get(); } // Returns the LoadedGLTF scene for a named GLTF instance, or nullptr if not found. std::shared_ptr getGLTFInstanceScene(const std::string &instanceName) const; private: EngineContext *_context = nullptr; Camera mainCamera = {}; CameraRig cameraRig{}; GPUSceneData sceneData = {}; DrawContext mainDrawContext; std::vector pointLights; std::vector spotLights; WorldVec3 _origin_world{0.0, 0.0, 0.0}; glm::vec3 _camera_position_local{0.0f, 0.0f, 0.0f}; double _floating_origin_recenter_threshold = 1000.0; double _floating_origin_snap_size = 100.0; float _deltaTime = 0.0f; std::chrono::steady_clock::time_point _lastFrameTime{}; std::unordered_map > loadedScenes; // Per-named static glTF scene animation state (independent of instances). std::unordered_map sceneAnimations; std::unordered_map > loadedNodes; std::unordered_map dynamicMeshInstances; std::unordered_map dynamicGLTFInstances; // Keep GLTF assets alive until after the next frame fence to avoid destroying // GPU resources that might still be in-flight. std::vector> pendingGLTFRelease; std::unique_ptr _planetSystem; PickingDebug pickingDebug{}; };