diff --git a/.gitignore b/.gitignore index 5def43c..7bceffc 100644 --- a/.gitignore +++ b/.gitignore @@ -414,3 +414,4 @@ FodyWeavers.xsd # JetBrains CLion .idea +imgui.ini diff --git a/AGENTS.md b/AGENTS.md index 4b6e9cb..d15fa6e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,12 +9,6 @@ cmake --build build # Run the main example ./build/examples/sample-scenes/WeirdSamples - -# muOS cross-compile (aarch64, uses podman) -./scripts/anbernic/build-muos.sh -./scripts/anbernic/deploy-muos.sh # build + deploy via MTP -./scripts/anbernic/deploy-muos.sh --no-build # deploy only -./scripts/anbernic/fetch-logs.sh # pull log.txt + screenshots ``` There are no tests. CI runs `ctest` but no test targets are defined. @@ -27,6 +21,7 @@ There are no tests. CI runs `ctest` but no test targets are defined. | `WEIRD_DISABLE_IMGUI` | `OFF` | Strip ImGui (used for muOS build) | | `WEIRD_TEST_HOOKS` | `OFF` | Enables `WEIRD_AUTO_QUIT_SECONDS` / `WEIRD_SCREENSHOT_FRAME` env vars | | `WEIRD_ENGINE_ENABLE_ASAN` | `OFF` | AddressSanitizer | +| `WEIRD_ENGINE_ENABLE_ASSERTS` | Debug/RelWithDebInfo | Enables `WEIRD_ASSERT` runtime assertions (abort on violation); disabled in Release | | `WEIRD_USE_FBDEV_EGL` | `OFF` | fbdev EGL backend for Mali devices (no GBM/KMS) | | `WEIRD_ENGINE_USE_RUNTIME_ASSETS` | `OFF` | Load shaders/fonts from `./shaders/` `./fonts/` instead of source tree | @@ -41,6 +36,7 @@ There are no tests. CI runs `ctest` but no test targets are defined. - `sample-scenes` → `WeirdSamples` (main demo) - `3d-experiments`, `opengl-experiments` — other demos - `empty-project` — starter template +- **ServiceProvider pattern**: `Scene` state is highly encapsulated. Game systems should use `ServiceProvider` (passed into update/render loops) to interact with rendering, audio, or physics systems instead of accessing `Scene` internals. - Entry point for games: `WeirdEngine::start(sceneManager, ...)` in `include/weird-engine.h`. ## Dependencies @@ -59,3 +55,9 @@ There are no tests. CI runs `ctest` but no test targets are defined. - The `build/` and `build-muos/` directories are separate CMake trees; do not mix them. - `compile_flags.txt` exists for clangd; it does not drive the actual build. - The `.vscode/settings.json` enables `WEIRD_ENGINE_BUILD_EXAMPLES=ON` by default. + +## Commit Guidelines + +- **Naming Convention**: Prefix all commit messages with the affected module or system name, followed by a colon and a space. Keep the prefix lowercase. + - Prefix Examples: `scene:`, `core:`, `physics:`, `renderer:`, `examples:`, `tools:`, `core/assert:` + - Full Example: `physics: add BodyUserData for attaching custom data to rigidbodies` diff --git a/CMakeLists.txt b/CMakeLists.txt index fdd20ec..a59fc5c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,20 @@ if(WEIRD_TEST_HOOKS) add_compile_definitions(WEIRD_TEST_HOOKS) endif() +# Runtime assertions: enabled by default in debug-like configurations +# (Debug, RelWithDebInfo), disabled in Release/MinSizeRel. Games that build +# the engine via add_subdirectory inherit this through the PUBLIC definition. +set(WEIRD_ENGINE_ENABLE_ASSERTS_DEFAULT OFF) +if(CMAKE_BUILD_TYPE MATCHES "^(Debug|RelWithDebInfo)$" + OR CMAKE_CONFIGURATION_TYPES MATCHES "(^|;)Debug(;|$)" + OR CMAKE_CONFIGURATION_TYPES MATCHES "(^|;)RelWithDebInfo(;|$)") + set(WEIRD_ENGINE_ENABLE_ASSERTS_DEFAULT ON) +endif() +option(WEIRD_ENGINE_ENABLE_ASSERTS "Enable runtime assertions (WEIRD_ASSERT)" ${WEIRD_ENGINE_ENABLE_ASSERTS_DEFAULT}) +if(WEIRD_ENGINE_ENABLE_ASSERTS) + target_compile_definitions(${PROJECT_NAME} PUBLIC WEIRD_ENABLE_ASSERTS=1) +endif() + if(NOT WEIRD_DISABLE_IMGUI) # ImGui setup set(IMGUI_DIR ${CMAKE_CURRENT_SOURCE_DIR}/third-party/imgui) @@ -222,6 +236,7 @@ if(WEIRD_ENGINE_BUILD_EXAMPLES) add_subdirectory(examples/3d-experiments) add_subdirectory(examples/opengl-experiments) add_subdirectory(examples/sample-scenes) + add_subdirectory(examples/empty-project) endif() # Tools diff --git a/README.md b/README.md index a703e38..741510f 100644 --- a/README.md +++ b/README.md @@ -1,66 +1,275 @@ +# Weird Engine -# Weird Engine +Weird Engine is a C++20 game engine designed for 2D and 3D Signed Distance Field (SDF) rendering. -## Overview +## Features -Weird Engine is a simple yet unique game engine featuring: +- **Ray Marching Renderer**: Renders 2D and 3D Signed Distance Fields using custom OpenGL ES shaders. +- **Physics Engine**: Calculates 2D Position-Based Dynamics (PBD) with SDF collision detection. +- **Entity Component System (ECS)**: Manages entities, component storage, and system dispatching. +- **Service Architecture**: Provides decoupled engine services through a single provider interface. -- **Custom OpenGL Renderer**: Uses ray marching to render 2D Signed Distance Fields (SDFs). -- **Physics Engine**: Implements Position-Based Dynamics (PBD) with custom SDF-based collision detection. -- **ECS Architecture**: A fully custom-built Entity Component System (ECS). +--- -## Getting Started +## Getting Started -### Creating Your First Project +### Prerequisites + +Install the SDL3 development library for your operating system before building. +Refer to the [SDL3 Linux README](https://wiki.libsdl.org/SDL3/README-linux) for Linux package names. + +### Building Your Project with the Template Preset -1. Navigate to `/CMake/create-project`. -2. Place your header files (`.h`) in the `/include` directory. -3. Place your source files (`.cpp`) in the `/src` directory. - - You can create subfolders for better organization. -4. Build witch CMake and run your project. +Weird Engine provides a project template in `examples/empty-project`. +Use this template to start building a new game. -### Linux -You'll need to install SDL3 dependencies: -[SDL3 Linux README](https://wiki.libsdl.org/SDL3/README-linux) +1. Copy the `examples/empty-project` directory to your project location. +2. Open `CMakeLists.txt` in your new project directory. +3. Configure the engine source location: + - **Local Engine (Default)**: Set `USE_LOCAL_WEIRD_ENGINE` to `ON`. Set `WEIRD_ENGINE_LOCAL_PATH` to your local engine directory. + - **Automatic Download**: Set `USE_LOCAL_WEIRD_ENGINE` to `OFF`. CMake automatically downloads Weird Engine from GitHub. +4. Place your header files in `include/` and source files in `src/`. +5. Place your game assets in `assets/`. +6. Configure and build the project using CMake: -### Issues downloading SDL submodule +```bash +cmake -B build -S . +cmake --build build ``` -git rm --cached third-party/SDL -rm -rf .git/modules/third-party/SDL -rm -rf third-party/SDL -git submodule add https://github.com/libsdl-org/SDL.git third-party/SDL +7. Run the compiled executable from the build directory. + +--- + +## Engine Architecture + +For detailed guides, refer to: +- [Scene and ECS Architecture Guide](docs/SCENE_AND_ECS.md) +- [Defining Shapes with SDFs Guide](docs/SDF_SHAPES.md) + +### Creating a Scene + +Inherit from one of the scene base classes in `include/weird-engine/Scene.h`: + +- `Scene2D`: Uses 2D ray marching and 2D physics. +- `Scene3D`: Uses 3D ray marching. +- `SceneBoth`: Combines 2D and 3D ray marching paths. + +Register your scene in `main()` with the `SceneManager` instance: + +```cpp +#include + +using namespace WeirdEngine; + +class MyScene : public Scene2D +{ +public: + MyScene() + { + addStartSystem(onStartSystem); + } +}; + +int main(int argc, char* argv[]) +{ + SceneManager& sceneManager = SceneManager::getInstance(); + sceneManager.registerScene("my-scene"); + start(sceneManager, {}, {}, {}, argc, argv); +} ``` -## Anbernic muOS Deployment +### Entities and Components + +Entities are unique numerical identifiers. +The `Registry` class manages entities and stores components. + +#### Creating an Entity + +Call `registry.createEntity()` to make a new entity: + +```cpp +Entity entity = registry.createEntity(); +``` + +#### Adding Components + +Call `registry.addComponent(entity)` to attach a component to an entity: + +```cpp +auto& transform = registry.addComponent(entity); +transform.position = vec3(0.0f, 10.0f, 0.0f); + +auto& dot = registry.addComponent(entity); +dot.materialId = DisplaySettings::LightGray; +``` + +If you modify a component after creation, mark it dirty if required: + +```cpp +registry.setComponentDirty(transform); +``` + +#### Creating and Registering Custom Components + +Define custom components as C++ structures: + +```cpp +struct Health +{ + int current = 100; + int max = 100; +}; +``` + +The `Registry` automatically registers new component types when first accessed. +You can also register component types explicitly: + +```cpp +registry.registerComponent(); +``` -Weird Engine includes generic scripts for building and deploying games directly to Anbernic handheld consoles running muOS over MTP. These scripts are located in `scripts/anbernic/`. +#### Scene State Component Pattern -You can use these generic scripts to deploy *any* game built with Weird Engine without needing to copy the scripts to your game's folder. The scripts automatically detect your project's name, cross-compile it via Podman, package assets, generate launcher scripts, and push the files to the device. +Store scene variables in an ECS component instead of global variables. +Create a `State` component and attach it to a dedicated entity: + +```cpp +struct State +{ + int score = 0; + float timer = 0.0f; +}; + +void onCreateSystem(Registry& registry, ServiceProvider& services) +{ + Entity stateEntity = registry.createEntity(); + registry.addComponent(stateEntity); + services.tags().tag(stateEntity, "state"); + services.serialization().blacklistEntity(stateEntity); +} +``` + +### Systems and Logic + +Add game logic using the System Dispatcher or legacy callbacks. + +#### System Dispatcher (Recommended) + +Systems are plain free functions or lambdas with this signature: + +```cpp +void system(Registry& registry, ServiceProvider& services); +``` + +Register systems inside your scene constructor: + +```cpp +MyScene() +{ + addCreateSystem(onCreateSystem); + addStartSystem(onStartSystem); + addUpdateSystem(movementSystem); + addUpdateSystem(combatSystem); + addImGuiRenderSystem(uiSystem); + addEntityCollisionSystem(onCollisionSystem); + addEntityShapeCollisionSystem(onShapeCollisionSystem); + addDestroySystem(onDestroySystem); +} +``` + +Systems registered to the same stage run sequentially in registration order. + +#### Service Provider Interface + +Systems access engine subsystems through the `ServiceProvider` facade: + +- `services.input()`: Read keyboard, mouse, and gamepad inputs. +- `services.physics()`: Change gravity, damping, pause state, or run raycasts. +- `services.render()`: Control camera, lights, and force shader updates. +- `services.shapes()`: Register custom SDFs and add geometric shapes. +- `services.materials()`: Create and query 3D materials. +- `services.audio()`: Play sounds and check friction audio levels. +- `services.tags()`: Assign unique string tags to entities and look up entities by tag. +- `services.serialization()`: Save or load `.weird` scene files and blacklist entities. +- `services.time()`: Read frame delta time and total simulation time. +- `services.resources()`: Resolve asset paths and file input/output. +- `services.sceneControl()`: Trigger scene transitions. + +#### Legacy Scene Callbacks + +Override virtual methods in `Scene` to use legacy callbacks: + +```cpp +class MyScene : public Scene2D +{ +protected: + void onStart(Registry& registry, ServiceProvider& services) override {} + void onUpdate(Registry& registry, ServiceProvider& services) override {} + void onRender(Registry& registry, ServiceProvider& services, WeirdRenderer::RenderTarget& target) override {} +}; +``` + +Note: Use `onRender` specifically when you need custom 3D render pipeline operations. + +#### Physics Thread Callbacks + +Physics simulation steps run on a dedicated thread. +Override these virtual methods to execute logic mid-step: + +- `onPhysicsStep(Simulation2D& simulation)` +- `onPhysicsRigidBodyCollision(Simulation2D& simulation, PhysicsCollisionEvent& event)` +- `onPhysicsShapeCollision(Simulation2D& simulation, PhysicsShapeCollisionEvent& event)` + +Physics callbacks receive `Simulation2D&` only. +Physics callbacks cannot access `Registry` or `ServiceProvider` because the main thread owns the ECS. + +To associate custom data with physics bodies, derive from `BodyUserData`: + +```cpp +struct CharacterData : BodyUserData +{ + static constexpr int TYPE = 1; + CharacterData() { type = TYPE; } + float jumpStrength = 10.0f; +}; + +// Hand off ownership to the simulation: +services.physics().setUserData(rb.simulationId, std::make_unique()); + +// Query data back in physics callbacks: +if (auto* data = simulation.getUserDataAs(bodyId)) +{ + simulation.addImpulseForce(bodyId, vec2(0.0f, data->jumpStrength)); +} +``` + +--- + +## Anbernic muOS Deployment + +Weird Engine includes scripts for building and deploying games to Anbernic handhelds running muOS. +Find these scripts in `scripts/anbernic/`. ### Prerequisites -- [Podman](https://podman.io/) installed on your machine (used to safely isolate the cross-compiler toolchain). -- The device must be connected to your PC via USB and mounted via MTP (e.g., `mtp:/RG35XX-H/SD2`). +- Install [Podman](https://podman.io/) on your PC. +- Mount the console SD card over USB using MTP (for example `mtp:/RG35XX-H/SD2`). ### Deploying a Game -To build and deploy a game to the console: +Run `deploy-muos.sh` with your project path and MTP destination: ```bash -# General Usage -/path/to/weird-engine/scripts/anbernic/deploy-muos.sh - -# Example: Deploying a game from its own directory -cd my-awesome-game -../weird-engine/scripts/anbernic/deploy-muos.sh . mtp:/RG35XX-H/SD2 +/path/to/weird-engine/scripts/anbernic/deploy-muos.sh . mtp:/RG35XX-H/SD2 ``` ### Fetching Device Logs -If you need to retrieve `log.txt` or screenshots from the device after running your game: +Pull log files and screenshots from the device: ```bash -../weird-engine/scripts/anbernic/fetch-logs.sh . mtp:/RG35XX-H/SD2 +/path/to/weird-engine/scripts/anbernic/fetch-logs.sh . mtp:/RG35XX-H/SD2 ``` -Logs will be saved to a timestamped folder inside your project's `device-logs/` directory. + +Logs are saved to `device-logs/` inside your project directory. diff --git a/docs/SCENE_AND_ECS.md b/docs/SCENE_AND_ECS.md new file mode 100644 index 0000000..d8ce7c1 --- /dev/null +++ b/docs/SCENE_AND_ECS.md @@ -0,0 +1,329 @@ +# Scene and ECS Architecture Guide + +This document explains how to build scenes, manage entities and components, and write system logic in Weird Engine. +All instructions follow the ASD-STE100 Simplified Technical English standard. + +--- + +## 1. Project Setup and Building + +Weird Engine includes a pre-configured template project in `examples/empty-project`. +Use this template to create new games. + +### Build Configuration + +Open `CMakeLists.txt` in your game directory to configure engine linking options. + +#### Option A: Using a Local Engine Copy + +Set `USE_LOCAL_WEIRD_ENGINE` to `ON` (default). +Set `WEIRD_ENGINE_LOCAL_PATH` to your engine directory relative to `CMakeLists.txt`: + +```cmake +set(USE_LOCAL_WEIRD_ENGINE ON) +set(WEIRD_ENGINE_LOCAL_PATH "../weird-engine/") +``` + +#### Option B: Automatic GitHub Download + +Set `USE_LOCAL_WEIRD_ENGINE` to `OFF`. +CMake uses `FetchContent` to download Weird Engine from GitHub: + +```cmake +set(USE_LOCAL_WEIRD_ENGINE OFF) +``` + +### Compiling Your Project + +Run these CMake commands from your project root: + +```bash +cmake -B build -S . +cmake --build build +``` + +--- + +## 2. Building a Scene + +Scenes inherit from one of three base classes defined in `include/weird-engine/Scene.h`: + +- `Scene2D`: Standard 2D Signed Distance Field (SDF) ray marching and PBD physics. +- `Scene3D`: 3D SDF ray marching path. +- `SceneBoth`: Combined 2D and 3D ray marching paths. + +### Registering Scenes + +Register your scene class in `main()` with the singleton `SceneManager`: + +```cpp +#include + +using namespace WeirdEngine; + +class MainGameScene : public Scene2D +{ +public: + MainGameScene() + { + addCreateSystem(onCreateSystem); + addStartSystem(onStartSystem); + addUpdateSystem(onUpdateSystem); + } +}; + +int main(int argc, char* argv[]) +{ + SceneManager& sceneManager = SceneManager::getInstance(); + sceneManager.registerScene("main-game"); + + DisplaySettings displaySettings{}; + PhysicsSettings physicsSettings{}; + AudioSettings audioSettings{}; + + start(sceneManager, displaySettings, physicsSettings, audioSettings, argc, argv); +} +``` + +--- + +## 3. Entity Component System (ECS) + +The `Registry` class manages entity IDs and component storage. + +### Creating and Destroying Entities + +Call `registry.createEntity()` to generate an entity ID: + +```cpp +Entity entity = registry.createEntity(); +``` + +Call `registry.destroyEntity(entity)` to remove an entity and all attached components: + +```cpp +registry.destroyEntity(entity); +``` + +### Adding and Accessing Components + +Add components to an entity using `registry.addComponent(entity)`: + +```cpp +auto& transform = registry.addComponent(entity); +transform.position = vec3(10.0f, 5.0f, 0.0f); + +auto& dot = registry.addComponent(entity); +dot.materialId = DisplaySettings::LightGray; + +auto& rb = registry.addComponent(entity); +rb.velocity = vec2(0.0f, 5.0f); +``` + +Retrieve components using `registry.getComponent(entity)`: + +```cpp +auto& transform = registry.getComponent(entity); +``` + +Check component existence using `registry.hasComponent(entity)`: + +```cpp +if (registry.hasComponent(entity)) +{ + // Perform action +} +``` + +Mark modified components dirty when required by rendering or physics systems: + +```cpp +registry.setComponentDirty(rb); +``` + +### Creating Custom Component Types + +Define custom components as plain C++ structures: + +```cpp +struct Health +{ + int current = 100; + int max = 100; +}; +``` + +The `Registry` registers component types automatically during first access. +You can also register component types explicitly: + +```cpp +registry.registerComponent(); +``` + +### ECS Native Scene State Pattern + +Systems do not keep local state variables inside the scene class. +Store scene state inside an ECS component attached to a dedicated state entity: + +```cpp +struct SceneState +{ + int score = 0; + float spawnTimer = 0.0f; + Entity playerEntity = INVALID_ENTITY; +}; + +void onCreateSystem(Registry& registry, ServiceProvider& services) +{ + Entity stateEntity = registry.createEntity(); + registry.addComponent(stateEntity); + services.tags().tag(stateEntity, "state"); + services.serialization().blacklistEntity(stateEntity); +} + +inline SceneState& getState(Registry& registry, ServiceProvider& services) +{ + return registry.getComponentArray()->getDataAtIdx(0); +} +``` + +--- + +## 4. System Dispatcher Architecture + +Add scene logic by registering systems to stage dispatchers. + +### System Function Signature + +Systems are free functions or lambdas matching the `CoreSystem` signature: + +```cpp +void systemName(Registry& registry, ServiceProvider& services); +``` + +### Registering Stage Systems + +Register systems inside your Scene constructor: + +- `addCreateSystem`: Runs after ECS initialization before scene loading. +- `addStartSystem`: Runs once after initial scene setup. +- `addUpdateSystem`: Runs every frame for game logic. +- `addImGuiRenderSystem`: Runs during ImGui interface rendering. +- `addEntityCollisionSystem`: Dispatches main-thread entity collision events. +- `addEntityShapeCollisionSystem`: Dispatches main-thread entity shape collision events. +- `addDestroySystem`: Runs when replacing or exiting the scene. + +```cpp +class ShowcaseScene : public Scene2D +{ +public: + ShowcaseScene() + { + addCreateSystem(onCreateSystem); + addStartSystem(onStartSystem); + addUpdateSystem(spawnSystem); + addUpdateSystem(inputSystem); + addUpdateSystem(uiSystem); + addDestroySystem(onDestroySystem); + } +}; +``` + +Multiple systems registered to the same stage execute sequentially in registration order. + +--- + +## 5. ServiceProvider Subsystems + +The `ServiceProvider` parameter provides controlled access to engine subsystems. + +| Subsystem | Service Call | Purpose | +|---|---|---| +| Input | `services.input()` | Query keys, mouse coordinates, and gamepads | +| Physics | `services.physics()` | Control gravity, damping, pause state, and raycasts | +| Render | `services.render()` | Access camera, lights, and trigger shader updates | +| Shapes | `services.shapes()` | Register custom SDFs and spawn shapes (see [SDF Shapes Guide](SDF_SHAPES.md)) | +| Materials | `services.materials()` | Create and access 3D material definitions | +| Audio | `services.audio()` | Queue audio requests and read friction audio levels | +| Tags | `services.tags()` | Assign and query unique string tags on entities | +| Serialization | `services.serialization()` | Save and load `.weird` files and blacklist entities | +| Time | `services.time()` | Read simulation time and frame delta time | +| Resources | `services.resources()` | Resolve asset paths and file storage operations | +| Scene Control | `services.sceneControl()` | Request scene transitions | +| Debug | `services.debug()` | Toggle fly camera and input debugging options | + +--- + +## 6. Legacy Callbacks and Physics Thread Interaction + +### Legacy Virtual Callbacks + +You can override virtual callbacks in `Scene`: + +- `onStart(Registry& registry, ServiceProvider& services)` +- `onUpdate(Registry& registry, ServiceProvider& services)` +- `onRender(Registry& registry, ServiceProvider& services, WeirdRenderer::RenderTarget& target)` + +Use `onRender` when issuing direct 3D draw commands to the render target. + +### Physics Thread Callbacks + +Physics simulation steps run on a dedicated physics thread. +Override these virtual methods for mid-step physics logic: + +```cpp +void onPhysicsStep(Simulation2D& simulation) override; +void onPhysicsRigidBodyCollision(Simulation2D& simulation, PhysicsCollisionEvent& event) override; +void onPhysicsShapeCollision(Simulation2D& simulation, PhysicsShapeCollisionEvent& event) override; +``` + +Physics thread callbacks receive `Simulation2D&` only. +Physics thread callbacks must NOT access `Registry` or `ServiceProvider` because the main thread owns the ECS. + +### Physics Body User Data + +Attach custom C++ structs to physics bodies by deriving from `BodyUserData`: + +```cpp +struct CharacterData : BodyUserData +{ + static constexpr int TYPE = 1; + + CharacterData() + { + type = TYPE; + } + + float jumpStrength = 10.0f; + float restitution = 1.5f; +}; +``` + +Pass ownership of the user data to the physics simulation: + +```cpp +auto data = std::make_unique(); +data->jumpStrength = 12.0f; +services.physics().setUserData(rb.simulationId, std::move(data)); +``` + +Query user data in physics thread callbacks safely using `getUserDataAs`: + +```cpp +void onPhysicsShapeCollision(Simulation2D& simulation, PhysicsShapeCollisionEvent& event) override +{ + if (auto* data = simulation.getUserDataAs(event.body)) + { + event.absortion *= 1.0f / data->restitution; + event.friction *= 0.5f; + } +} +``` + +--- + +## 7. Sample Scene Reference + +For a complete working example demonstrating systems, `ServiceProvider`, custom SDFs, UI text, and physics callbacks, consult: + +`examples/sample-scenes/include/ServiceShowcaseScene.h` diff --git a/docs/SDF_SHAPES.md b/docs/SDF_SHAPES.md new file mode 100644 index 0000000..f61c0f6 --- /dev/null +++ b/docs/SDF_SHAPES.md @@ -0,0 +1,180 @@ +# Defining Shapes with Signed Distance Fields (SDFs) + +This document explains how to construct, register, and instantiate Signed Distance Field (SDF) shapes in Weird Engine. +All instructions follow the ASD-STE100 Simplified Technical English standard. + +--- + +## 1. Overview of SDF Shapes + +A Signed Distance Field (SDF) evaluates the shortest distance from a world position to the surface of a shape. + +- **Negative values**: Inside the shape. +- **Zero**: Exactly on the boundary. +- **Positive values**: Outside the shape. + +In Weird Engine, shape definitions inherit from `IMathExpression` (defined in `include/weird-engine/math/MathExpressions.h`). + +Each `IMathExpression` provides two functions: + +- `getValue(const float* parameters)`: Evaluates distance on the CPU for physics collisions and CPU raymarching. +- `print()`: Returns GLSL code string to generate OpenGL raymarching shaders. + +--- + +## 2. Using Default Primitive Shapes + +Weird Engine includes default shape primitives in `WeirdEngine::DefaultShapes` (defined in `include/weird-engine/math/Default2DSDFs.h`). + +### Available Default Shapes + +- `DefaultShapes::CIRCLE` +- `DefaultShapes::BOX` +- `DefaultShapes::TRIANGLE` +- `DefaultShapes::LINE` +- `DefaultShapes::RAMP` +- `DefaultShapes::SINE` +- `DefaultShapes::STAR` +- `DefaultShapes::CIRCLE_LINE` +- `DefaultShapes::BOX_LINE` + +### Adding a Default Shape to a Scene + +Use `services.shapes().addShape(...)` with a `ShapeConfig` struct to instantiate a shape entity: + +```cpp +Entity sphere = services.shapes().addShape({ + .shapeId = DefaultShapes::CIRCLE, + .variables = {{Primitives::Circle::POS_X, 15.0f}, {Primitives::Circle::POS_Y, 10.0f}, {Primitives::Circle::RADIUS, 5.0f}}, + .material = materialId, + .combination = CombinationType::Addition, + .hasCollision = true // Enable collisions +}); +``` + +### Adding a UI Shape + +For rendering shapes on the 2D user interface layer (which is screen-space and does not use physical collisions), use `addUIShape` with a `UIShapeConfig`: + +```cpp +Entity uiBox = services.shapes().addUIShape({ + .shapeId = DefaultShapes::BOX, + .variables = {Display::width * 0.5f, 90.0f, 40.0f, 14.0f}, // Inline positional array + .material = 2, + .combination = CombinationType::Addition +}); +``` + +--- + +## 3. Creating Custom SDF Shapes + +You can build custom geometric shapes by combining mathematical expressions. + +### Expression Nodes + +Build expression trees using shared pointers to `IMathExpression` nodes: + +- `FloatVariable(offset)`: Reads a float value from the parameter array at the specified index. +- `FloatConstant(value)`: Represents a fixed float constant. +- **Math Operations**: `Addition`, `Subtraction`, `Multiplication`, `Division`, `Sine`, `Abs`, `Min`, `Max`. +- **CSG Operations**: `SDFAddition`, `SDFSubtraction`, `SDFIntersection`, `SDFSmoothAddition`, `SDFSmoothSubtraction`, `SDFOnion`. +- **Primitives**: `Primitives::Circle`, `Primitives::Box`, `Primitives::Triangle`, `Primitives::Line`, `Primitives::Ramp`, `Primitives::SineWave`. + +### Defining a Custom Ring SDF + +The following example builds a ring by subtracting an inner circle from an outer circle: + +```cpp +// Define variable index bindings +auto x = std::make_shared(0); +auto y = std::make_shared(1); +auto outerRadius = std::make_shared(2); +auto innerRadius = std::make_shared(3); + +// Construct primitive circles +auto outer = std::make_shared(x, y, outerRadius); +auto inner = std::make_shared(x, y, innerRadius); + +// Subtract inner circle from outer circle using Max(outer, -inner) +auto negatedInner = std::make_shared(-1.0f, inner); +auto ringExpression = std::make_shared(outer, negatedInner); +``` + +--- + +## 4. Registering and Instantiating Custom SDFs + +### Registering the SDF + +Register your expression tree with `services.shapes().registerSDF(...)` to obtain a `ShapeId`: + +```cpp +ShapeId ringShapeId = services.shapes().registerSDF(ringExpression); +``` + +You can also register global default SDFs before scene start using `Scene::registerDefaultSDF(expression)`. + +### Adding the Custom Shape Entity + +Pass a `ShapeConfig` struct to `addShape`. You can pass variables positionally or by index offset (`{{INDEX, value}, ...}`): + +```cpp +// 1. Positional syntax +Entity ringEntity = services.shapes().addShape({ + .shapeId = ringShapeId, + .variables = { 15.0f, 20.0f, 5.0f, 4.0f }, // POS_X, POS_Y, outerRadius, innerRadius + .material = ringMaterial, + .combination = CombinationType::Addition, + .hasCollision = true, // Enable collision + .group = 0 // Group index +}); + +// 2. Indexed offset syntax using constants (e.g. Primitives::Box or custom constants) +static constexpr uint8_t POS_X = 0; +static constexpr uint8_t POS_Y = 1; +static constexpr uint8_t OUTER_R = 2; +static constexpr uint8_t INNER_R = 3; + +Entity ringEntity2 = services.shapes().addShape({ + .shapeId = ringShapeId, + .variables = {{POS_X, 15.0f}, {POS_Y, 20.0f}, {OUTER_R, 5.0f}, {INNER_R, 4.0f}}, + .material = ringMaterial +}); + +// Adjust smooth factor on the CustomShape component +registry.getComponent(ringEntity).smoothFactor = 2.0f; +``` + +--- + +## 5. Shape Combination Types + +When adding shapes to a scene, specify how the shape blends with existing scene geometry: + +- `CombinationType::Addition`: Standard CSG union (`min`). +- `CombinationType::Subtraction`: CSG subtraction (`max(a, -b)`). +- `CombinationType::SmoothAddition`: Smooth blending union. +- `CombinationType::SmoothSubtraction`: Smooth blending subtraction. + +Example of creating a subtractive pit in the floor: + +```cpp +services.shapes().addShape({ + .shapeId = DefaultShapes::CIRCLE, + .variables = {{Primitives::Circle::POS_X, 30.0f}, {Primitives::Circle::POS_Y, 5.0f}, {Primitives::Circle::RADIUS, 4.0f}}, + .material = 0, // No material required for subtraction + .combination = CombinationType::Subtraction, + .hasCollision = true, + .group = CustomShape::GLOBAL_GROUP +}); +``` + +--- + +## 6. Code Reference + +For full implementation examples of custom SDF registration, subtraction shapes, and parameter passing, see: + +`examples/sample-scenes/include/ServiceShowcaseScene.h` + diff --git a/examples/3d-experiments/include/Classic.h b/examples/3d-experiments/include/Classic.h index 27eb98a..3580ecc 100644 --- a/examples/3d-experiments/include/Classic.h +++ b/examples/3d-experiments/include/Classic.h @@ -12,17 +12,17 @@ class ClassicScene : public Scene3D Entity m_ball; // Inherited via Scene - void onStart(ECSManager& ecs) override + void onStart(Registry& registry, ServiceProvider& services) override { - m_debugFly = true; + services.debug().setDebugFly(true); - auto& redMat = createMaterial(); + auto& redMat = services.materials().createMaterial(); redMat.color = vec4(.8f, 0.2f, 0.2f, 1.0f); - auto& orangeMat = createMaterial(); + auto& orangeMat = services.materials().createMaterial(); orangeMat.color = vec4(.95f, 0.4f, 0.1f, 1.0f); - auto& floorMaterial = createMaterial(); + auto& floorMaterial = services.materials().createMaterial(); floorMaterial.color = vec4(1.0f, 1.0f, 1.0f, 1.0f); floorMaterial.secondaryColor = vec4(0.4f, 0.4f, 0.6f, 1.0f); floorMaterial.metallic = 0.7f; @@ -30,13 +30,13 @@ class ClassicScene : public Scene3D floorMaterial.pattern = MaterialPattern::Checkers; { - Entity entity = ecs.createEntity(); - Transform& t = ecs.addComponent(entity); + Entity entity = registry.createEntity(); + Transform& t = registry.addComponent(entity); t.position = vec3(0, 1, 0); - MeshRenderer& mr = ecs.addComponent(entity); + MeshRenderer& mr = registry.addComponent(entity); - auto id = m_resourceManager.getMeshId(ASSETS_PATH "monkey/demo.gltf", entity, true); + auto id = services.resources().getMeshId("monkey/demo.gltf", entity, true); mr.mesh = id; // mr.materialIndex = floorMaterial.id; @@ -44,52 +44,63 @@ class ClassicScene : public Scene3D } { - Entity entity = ecs.createEntity(); - Transform& t = ecs.addComponent(entity); + Entity entity = registry.createEntity(); + Transform& t = registry.addComponent(entity); t.position = vec3(2, 3, 2); - auto& sdf = ecs.addComponent(entity); + auto& sdf = registry.addComponent(entity); sdf.materialId = redMat.id; m_ball = entity; } - { - float vars1[8] = {25.0f, 10.0f, 5.0f, 0.5f, 13.0f, 0.0f}; // Custom shape - Entity start = addShape(DefaultShapes::STAR, vars1, orangeMat, CombinationType::Addition, true, 0); - } + services.shapes().addShape({.shapeId = DefaultShapes::STAR, + .variables = {25.0f, 10.0f, 5.0f, 0.5f, 13.0f, 0.0f}, + .material = orangeMat, + .combination = CombinationType::Addition, + .hasCollision = true, + .group = 0}); + + services.shapes().addShape({.shapeId = DefaultShapes3D::PLANE, + .variables = {}, + .material = floorMaterial, + .combination = CombinationType::Addition, + .hasCollision = false}); { - float vars1[8] = {}; // Custom shape - Entity start = addShape(DefaultShapes3D::PLANE, vars1, floorMaterial, CombinationType::Addition, false); + Entity entity = registry.createEntity(); + Transform& t = registry.addComponent(entity); + t.position = glm::vec3(0.0f, 3.0f, 0.0f); + t.rotation = glm::vec3(0.35f, 0.45f, 0.5f); + + LightComponent& lc = registry.addComponent(entity); + lc.type = LightType::Directional; + lc.color = glm::vec4(1.0f, 0.95f, 0.9f, 2.0f); } - getLigths().push_back(Light{0, glm::vec3(0.0f, 3.0f, 0.0f), 0, glm::vec3(0.35f, 0.45f, 0.5f), - glm::vec4(1.0f, 0.95f, 0.9f, 2.0f)}); - - ecs.getComponent(m_mainCamera).position = vec3(0, 2, 10); + registry.getComponent(services.render().getCameraEntity()).position = vec3(0, 2, 10); } - void onUpdate(float delta, ECSManager& ecs) override + void onUpdate(Registry& registry, ServiceProvider& services) override { - if (Input::GetKeyDown(Input::Q)) + if (services.input().getKeyDown(Input::Q)) { - setSceneComplete(); + services.sceneControl().goToNextScene(); } - Transform& cameraTransform = ecs.getComponent(m_mainCamera); + Transform& cameraTransform = registry.getComponent(services.render().getCameraEntity()); return; { - Transform& t = ecs.getComponent(m_monkey); + Transform& t = registry.getComponent(m_monkey); } { - Transform& t = ecs.getComponent(m_ball); - // t.position.z = 10 * sinf(getTime()); - t.position.x = 2.0f * sinf(-getTime()); - t.position.z = 2.0f * cosf(-getTime()); + Transform& t = registry.getComponent(m_ball); + // t.position.z = 10 * sinf(services.time().time()); + t.position.x = 2.0f * sinf(-services.time().time()); + t.position.z = 2.0f * cosf(-services.time().time()); } // t.position = cameraTransform.position + vec3(-10, -6, -20); diff --git a/examples/3d-experiments/include/CornellBox.h b/examples/3d-experiments/include/CornellBox.h index 064fde3..f6412f5 100644 --- a/examples/3d-experiments/include/CornellBox.h +++ b/examples/3d-experiments/include/CornellBox.h @@ -14,122 +14,172 @@ class CornellBox : public Scene3D CornellBox() {}; private: + Entity m_sunLight; // Inherited via Scene - void onStart(ECSManager& ecs) override + void onStart(Registry& registry, ServiceProvider& services) override { - m_debugFly = true; + services.debug().setDebugFly(true); - auto& ballMat = createMaterial(); + auto& ballMat = services.materials().createMaterial(); ballMat.color = vec4(1.0f); ballMat.metallic = 1.0f; ballMat.roughness = 0.005f; - auto& redMat = createMaterial(); + auto& redMat = services.materials().createMaterial(); redMat.color = vec4(.8f, 0.2f, 0.2f, 1.0f); - auto& greenMat = createMaterial(); + auto& greenMat = services.materials().createMaterial(); greenMat.color = vec4(0.1f, .95f, 0.1f, 1.0f); greenMat.metallic = 0.5f; greenMat.roughness = 0.1f; - auto& whiteMat = createMaterial(); + auto& whiteMat = services.materials().createMaterial(); whiteMat.color = vec4(1.0f, 1.0f, 1.0f, 1.0f); { - Entity entity = ecs.createEntity(); - Transform& t = ecs.addComponent(entity); + Entity entity = registry.createEntity(); + Transform& t = registry.addComponent(entity); t.position = vec3(0.0f, 0.75f, 0.0f); - auto& sdf = ecs.addComponent(entity); + auto& sdf = registry.addComponent(entity); sdf.materialId = ballMat.id; } { - Entity entity = ecs.createEntity(); - Transform& t = ecs.addComponent(entity); + Entity entity = registry.createEntity(); + Transform& t = registry.addComponent(entity); t.position = vec3(20.0f, 20.0f, 0.0f); - auto& text = ecs.addComponent(entity); + auto& text = registry.addComponent(entity); text.text = "Cornell Box"; } { std::shared_ptr box = std::make_shared(); - auto boxId = registerSDF(box); + auto boxId = services.shapes().registerSDF(box); // Left - { - float vars1[8] = {-2.0f * 2.6f, 2.6f, 0.0f, 2.6f, 2.6f, 2.6f}; // Custom shape - Entity start = addShape(boxId, vars1, redMat, CombinationType::Addition, false); - } + services.shapes().addShape({.shapeId = boxId, + .variables = {{Primitives3D::Box::POS_X, -2.0f * 2.6f}, + {Primitives3D::Box::POS_Y, 2.6f}, + {Primitives3D::Box::POS_Z, 0.0f}, + {Primitives3D::Box::SIZE_X, 2.6f}, + {Primitives3D::Box::SIZE_Y, 2.6f}, + {Primitives3D::Box::SIZE_Z, 2.6f}}, + .material = redMat, + .combination = CombinationType::Addition, + .hasCollision = false}); // Right - { - float vars1[8] = {2.0f * 2.6f, 2.6f, 0.0f, 2.6f, 2.6f, 2.6f}; // Custom shape - Entity start = addShape(boxId, vars1, greenMat, CombinationType::Addition, false); - } + services.shapes().addShape({.shapeId = boxId, + .variables = {{Primitives3D::Box::POS_X, 2.0f * 2.6f}, + {Primitives3D::Box::POS_Y, 2.6f}, + {Primitives3D::Box::POS_Z, 0.0f}, + {Primitives3D::Box::SIZE_X, 2.6f}, + {Primitives3D::Box::SIZE_Y, 2.6f}, + {Primitives3D::Box::SIZE_Z, 2.6f}}, + .material = greenMat, + .combination = CombinationType::Addition, + .hasCollision = false}); // Back - { - float vars1[8] = {0.0f, 2.6f, -2.0f * 2.6f, 3.0f * 2.6f, 2.6f, 2.6f}; // Custom shape - Entity start = addShape(boxId, vars1, whiteMat, CombinationType::Addition, false); - } + services.shapes().addShape({.shapeId = boxId, + .variables = {{Primitives3D::Box::POS_X, 0.0f}, + {Primitives3D::Box::POS_Y, 2.6f}, + {Primitives3D::Box::POS_Z, -2.0f * 2.6f}, + {Primitives3D::Box::SIZE_X, 3.0f * 2.6f}, + {Primitives3D::Box::SIZE_Y, 2.6f}, + {Primitives3D::Box::SIZE_Z, 2.6f}}, + .material = whiteMat, + .combination = CombinationType::Addition, + .hasCollision = false}); // Top - { - float vars1[8] = {0.0f, 3.0f * 2.6f, -2.6f, 3.0f * 2.6f, 2.6f, 2.0f * 2.6f}; // Custom shape - Entity start = addShape(boxId, vars1, whiteMat, CombinationType::Addition, false); - } + services.shapes().addShape({.shapeId = boxId, + .variables = {{Primitives3D::Box::POS_X, 0.0f}, + {Primitives3D::Box::POS_Y, 3.0f * 2.6f}, + {Primitives3D::Box::POS_Z, -2.6f}, + {Primitives3D::Box::SIZE_X, 3.0f * 2.6f}, + {Primitives3D::Box::SIZE_Y, 2.6f}, + {Primitives3D::Box::SIZE_Z, 2.0f * 2.6f}}, + .material = whiteMat, + .combination = CombinationType::Addition, + .hasCollision = false}); // Light hole - { - float vars1[8] = {0.0f, 2.0f * 2.6f, 0.0f, 0.5f, 1.0f, 0.5f}; // Custom shape - Entity start = addShape(boxId, vars1, whiteMat, CombinationType::Subtraction, false); - } + services.shapes().addShape({.shapeId = boxId, + .variables = {{Primitives3D::Box::POS_X, 0.0f}, + {Primitives3D::Box::POS_Y, 2.0f * 2.6f}, + {Primitives3D::Box::POS_Z, 0.0f}, + {Primitives3D::Box::SIZE_X, 0.5f}, + {Primitives3D::Box::SIZE_Y, 1.0f}, + {Primitives3D::Box::SIZE_Z, 0.5f}}, + .material = whiteMat, + .combination = CombinationType::Subtraction, + .hasCollision = false}); // Floor - { - float vars1[8] = {0.0f, -1.0f * 2.6f, -2.6f, 3.0f * 2.6f, 2.6f, 2.0f * 2.6f}; // Custom shape - Entity start = addShape(boxId, vars1, whiteMat, CombinationType::Addition, false); - } + services.shapes().addShape({.shapeId = boxId, + .variables = {{Primitives3D::Box::POS_X, 0.0f}, + {Primitives3D::Box::POS_Y, -1.0f * 2.6f}, + {Primitives3D::Box::POS_Z, -2.6f}, + {Primitives3D::Box::SIZE_X, 3.0f * 2.6f}, + {Primitives3D::Box::SIZE_Y, 2.6f}, + {Primitives3D::Box::SIZE_Z, 2.0f * 2.6f}}, + .material = whiteMat, + .combination = CombinationType::Addition, + .hasCollision = false}); // { // float vars1[8] = {0.0f, 2.6f, 0.0f, 2.7f, 2.7f, 2.7f}; // Custom shape - // Entity start = addShape(boxId, vars1, DisplaySettings::White, CombinationType::Intersection, false); + // Entity start = services.shapes().addShape(boxId, vars1, DisplaySettings::White, + // CombinationType::Intersection, false); // } } // Sun - getLigths().push_back(Light{0, glm::vec3(0.0f, 0.0f, 0.0f), 0, normalize(glm::vec3(0.0f, 0.0f, 0.0f)), - glm::vec4(1.0f, 1.0f, 1.0f, 0.0f)}); + { + m_sunLight = registry.createEntity(); + Transform& t = registry.addComponent(m_sunLight); + t.position = glm::vec3(0.0f, 0.0f, 0.0f); + t.rotation = normalize(glm::vec3(0.0f, 0.0f, 0.0f)); + + LightComponent& lc = registry.addComponent(m_sunLight); + lc.type = LightType::Directional; + lc.color = glm::vec4(1.0f, 1.0f, 1.0f, 0.0f); + } - getLigths().push_back(Light{1, glm::vec3(0.0f, (2.0f * 2.6f) + 0.25f, 0.0f), 0, glm::vec3(0.0f, 0.0f, 0.0f), - glm::vec4(1.0f, 1.0f, 1.0f, 3.0f)}); + { + Entity entity = registry.createEntity(); + Transform& t = registry.addComponent(entity); + t.position = glm::vec3(0.0f, (2.0f * 2.6f) + 0.25f, 0.0f); + t.rotation = glm::vec3(0.0f, 0.0f, 0.0f); + + LightComponent& lc = registry.addComponent(entity); + lc.type = LightType::Point; + lc.color = glm::vec4(1.0f, 1.0f, 1.0f, 3.0f); + } - // getLigths().push_back( + // getLights().push_back( // Light{1, glm::vec3(0.0f, 0.0f, 0.0f), 0, glm::vec3(0.35f, 0.45f, 0.5f), glm::vec4(0.0f, 1.0f, 0.0f, 1.0f)}); - // getLigths().push_back( + // getLights().push_back( // Light{2, glm::vec3(0.0f, 0.0f, 0.0f), 0, glm::vec3(0.0f, 1.0f, 0.0f), glm::vec4(0.0f, 0.0f, 2.0f, 10.0f)}); - ecs.getComponent(m_mainCamera).position = vec3(0, 2.6f, 12.0f); + registry.getComponent(services.render().getCameraEntity()).position = vec3(0, 2.6f, 12.0f); } - void onUpdate(float delta, ECSManager& ecs) override + void onUpdate(Registry& registry, ServiceProvider& services) override { - if (Input::GetKeyDown(Input::Q)) + if (services.input().getKeyDown(Input::Q)) { - setSceneComplete(); + services.sceneControl().goToNextScene(); } - auto& cameraTransform = ecs.getComponent(m_mainCamera); - - // getLigths()[0].position.x = cameraTransform.position.x; - // getLigths()[0].position.y = cameraTransform.position.y; - // getLigths()[0].position.z = cameraTransform.position.z; + auto& cameraTransform = registry.getComponent(services.render().getCameraEntity()); - // getLigths()[0].rotation.x = -cameraTransform.rotation.x; - // getLigths()[0].rotation.y = -cameraTransform.rotation.y; - // getLigths()[0].rotation.z = -cameraTransform.rotation.z; + auto& lightTransform = registry.getComponent(m_sunLight); + lightTransform.position = cameraTransform.position; + lightTransform.rotation = -cameraTransform.rotation; } }; diff --git a/examples/3d-experiments/include/MaterialShowcase.h b/examples/3d-experiments/include/MaterialShowcase.h index fbc7d88..ac36ed1 100644 --- a/examples/3d-experiments/include/MaterialShowcase.h +++ b/examples/3d-experiments/include/MaterialShowcase.h @@ -14,22 +14,23 @@ class MaterialShowcaseScene : public Scene3D MaterialShowcaseScene() {}; private: + Entity m_sunLight; // Inherited via Scene - void onStart(ECSManager& ecs) override + void onStart(Registry& registry, ServiceProvider& services) override { - m_debugFly = true; + services.debug().setDebugFly(true); { - Entity entity = ecs.createEntity(); - Transform& t = ecs.addComponent(entity); + Entity entity = registry.createEntity(); + Transform& t = registry.addComponent(entity); t.position = vec3(-0.5f, -2.0f, 0); - auto& mat = createMaterial(); + auto& mat = services.materials().createMaterial(); mat.color = vec4(1.0f); mat.metallic = 1.0f; mat.roughness = 0.0f; - auto& sdf = ecs.addComponent(entity); + auto& sdf = registry.addComponent(entity); sdf.materialId = mat.id; } @@ -46,7 +47,7 @@ class MaterialShowcaseScene : public Scene3D }; { - auto& mat = createMaterial(); + auto& mat = services.materials().createMaterial(); mat.color = vec4(.95f, 0.4f, 0.1f, 1.0f); mat.metallic = 0.5f; mat.roughness = 0.1f; @@ -57,7 +58,7 @@ class MaterialShowcaseScene : public Scene3D } { - auto& mat = createMaterial(); + auto& mat = services.materials().createMaterial(); mat.color = vec4(0.5f, 1.0f, 0.5f, 1.0f); mat.metallic = 0.05f; mat.roughness = 0.99f; @@ -68,7 +69,7 @@ class MaterialShowcaseScene : public Scene3D } { - auto& mat = createMaterial(); + auto& mat = services.materials().createMaterial(); mat.color = vec4(1.0f, 0.3f, .6f, 1.0f); mat.secondaryColor = vec4(1.0f, 0.2f, 0.05f, 1.0f); @@ -80,7 +81,7 @@ class MaterialShowcaseScene : public Scene3D } { - auto& mat = createMaterial(); + auto& mat = services.materials().createMaterial(); mat.color = vec4(0.0f, 10.9f, 10.9f, 1.0f); mat.metallic = 0.05f; mat.roughness = 0.99f; @@ -91,7 +92,7 @@ class MaterialShowcaseScene : public Scene3D } { - auto& mat = createMaterial(); + auto& mat = services.materials().createMaterial(); mat.color = vec4(0.5f, 0.5f, 0.8f, 1.0f); mat.secondaryColor = vec4(1.0f, 1.0f, 1.0f, 1.0f); mat.metallic = 0.3f; @@ -103,7 +104,7 @@ class MaterialShowcaseScene : public Scene3D } { - auto& mat = createMaterial(); + auto& mat = services.materials().createMaterial(); mat.color = vec4(0.85f, 0.7f, 0.1f, 0.5f); mat.metallic = 0.5f; mat.roughness = 0.0f; @@ -113,77 +114,91 @@ class MaterialShowcaseScene : public Scene3D for (size_t i = 0; i < randomMats.size(); i++) { - Entity entity = ecs.createEntity(); - Transform& t = ecs.addComponent(entity); + Entity entity = registry.createEntity(); + Transform& t = registry.addComponent(entity); t.position = vec3(1.0f + (i), -2.0f, 0); - auto& sdf = ecs.addComponent(entity); + auto& sdf = registry.addComponent(entity); sdf.materialId = randomMats[i]; } - { - auto& floorMaterial = createMaterial(); - floorMaterial.color = vec4(1.0f, 1.0f, 1.0f, 1.0f); - floorMaterial.metallic = 0.0f; - floorMaterial.roughness = 0.3f; - floorMaterial.pattern = MaterialPattern::Checkers; - floorMaterial.secondaryColor = floorMaterial.color * 0.8f; - - float vars[8] = {3}; - Entity floor = addShape(DefaultShapes3D::PLANE, vars, floorMaterial, CombinationType::Addition, false); - } + auto& floorMaterial = services.materials().createMaterial(); + floorMaterial.color = vec4(1.0f, 1.0f, 1.0f, 1.0f); + floorMaterial.metallic = 0.1f; + floorMaterial.roughness = 0.3f; + floorMaterial.pattern = MaterialPattern::Checkers; + floorMaterial.secondaryColor = floorMaterial.color * 0.8f; + + services.shapes().addShape({.shapeId = DefaultShapes3D::PLANE, + .variables = {3}, + .material = floorMaterial, + .combination = CombinationType::Addition, + .hasCollision = false}); - auto& mirrorMaterial = createMaterial(); + auto& mirrorMaterial = services.materials().createMaterial(); mirrorMaterial.color = vec4(1.0f); mirrorMaterial.metallic = 1.0f; mirrorMaterial.roughness = 0.0f; { - std::shared_ptr box = std::make_shared(); - auto boxId = registerSDF(box); - - float vars1[8] = {-5.0f, -2.0f, 0.0f, 0.1f, 1.0f, 3.0f}; // Custom shape - Entity start = addShape(boxId, vars1, mirrorMaterial, CombinationType::Addition, false); + auto boxId = services.shapes().registerSDF(box); + + services.shapes().addShape({.shapeId = boxId, + .variables = {{Primitives3D::Box::POS_X, -5.0f}, + {Primitives3D::Box::POS_Y, -2.0f}, + {Primitives3D::Box::POS_Z, 0.0f}, + {Primitives3D::Box::SIZE_X, 0.1f}, + {Primitives3D::Box::SIZE_Y, 1.0f}, + {Primitives3D::Box::SIZE_Z, 3.0f}}, + .material = mirrorMaterial, + .combination = CombinationType::Addition, + .hasCollision = false}); } { std::shared_ptr box = std::make_shared(); - auto boxId = registerSDF(box); - - float vars1[8] = {20.0f, -2.0f, 0.0f, 0.1f, 1.0f, 3.0f}; // Custom shape - Entity start = addShape(boxId, vars1, mirrorMaterial, CombinationType::Addition, false); + auto boxId = services.shapes().registerSDF(box); + + services.shapes().addShape({.shapeId = boxId, + .variables = {{Primitives3D::Box::POS_X, 20.0f}, + {Primitives3D::Box::POS_Y, -2.0f}, + {Primitives3D::Box::POS_Z, 0.0f}, + {Primitives3D::Box::SIZE_X, 0.1f}, + {Primitives3D::Box::SIZE_Y, 1.0f}, + {Primitives3D::Box::SIZE_Z, 3.0f}}, + .material = mirrorMaterial, + .combination = CombinationType::Addition, + .hasCollision = false}); } - getLigths().push_back(Light{0, glm::vec3(0.0f, 0.0f, 0.0f), 0, normalize(glm::vec3(0.0f, 0.4f, 1.0f)), - glm::vec4(1.0f, 1.0f, 1.0f, 0.5f)}); + { + m_sunLight = registry.createEntity(); + Transform& t = registry.addComponent(m_sunLight); + t.position = glm::vec3(0.0f, 0.0f, 0.0f); + t.rotation = normalize(glm::vec3(0.0f, 0.4f, 1.0f)); + + LightComponent& lc = registry.addComponent(m_sunLight); + lc.type = LightType::Directional; + lc.color = glm::vec4(1.0f, 1.0f, 1.0f, 0.75f); + } - // getLigths().push_back( + // getLights().push_back( // Light{1, glm::vec3(0.0f, 0.0f, 0.0f), 0, glm::vec3(0.35f, 0.45f, 0.5f), glm::vec4(0.0f, 1.0f, 0.0f, 1.0f)}); - // getLigths().push_back( + // getLights().push_back( // Light{2, glm::vec3(0.0f, 0.0f, 0.0f), 0, glm::vec3(0.0f, 1.0f, 0.0f), glm::vec4(0.0f, 0.0f, 2.0f, 10.0f)}); - auto& cameraTransform = ecs.getComponent(m_mainCamera); + auto& cameraTransform = registry.getComponent(services.render().getCameraEntity()); cameraTransform.position = vec3(12, -1, 12); cameraTransform.rotation.x = -0.95f; } - void onUpdate(float delta, ECSManager& ecs) override + void onUpdate(Registry& registry, ServiceProvider& services) override { - if (Input::GetKeyDown(Input::Q)) + if (services.input().getKeyDown(Input::Q)) { - setSceneComplete(); + services.sceneControl().goToNextScene(); } - - auto& cameraTransform = ecs.getComponent(m_mainCamera); - - // getLigths()[0].position.x = cameraTransform.position.x; - // getLigths()[0].position.y = cameraTransform.position.y; - // getLigths()[0].position.z = cameraTransform.position.z; - - // getLigths()[0].rotation.x = -cameraTransform.rotation.x; - // getLigths()[0].rotation.y = -cameraTransform.rotation.y; - // getLigths()[0].rotation.z = -cameraTransform.rotation.z; } }; diff --git a/examples/empty-project/src/main.cpp b/examples/empty-project/src/main.cpp index e76cab1..11dded3 100644 --- a/examples/empty-project/src/main.cpp +++ b/examples/empty-project/src/main.cpp @@ -1,26 +1,23 @@ -#include // Only needed for debug output, can remove if unused #include // Main engine include using namespace WeirdEngine; -// Example scene demonstrating how to create a rope of connected circles using springs. +// Starter template for a new game. Register scenes in main() and the engine +// takes care of the rest. Override the callbacks you need: +// onCreate - once, before the physics thread starts +// onStart(registry[, tags])- after the ECS is ready and the physics thread runs +// onUpdate(dt, registry) - game logic, once per frame (pure virtual) +// onRender(target) - extra 3D rendering +// onImGuiRender - debug UI +// onCollision / onShapeCollision - physics thread, no ECS access +// onEntityCollision / onEntityShapeCollision - main thread, ECS safe +// onDestroy - before the scene is replaced during a transition class EmptyScene : public Scene2D { -public: - EmptyScene() - : Scene() - { - } - private: - void onStart() override {} + void onStart(Registry& registry, ServiceProvider& services) override {} - void onUpdate(float delta) override {} - void onCreate() override {} - void onRender(WeirdRenderer::RenderTarget& renderTarget) override {} - void onCollision(WeirdEngine::CollisionEvent& event) override {} - void onShapeCollision(WeirdEngine::ShapeCollisionEvent& event) override {} - void onDestroy() override {} + void onUpdate(Registry& registry, ServiceProvider& services) override {} }; int main(int argc, char* argv[]) diff --git a/examples/opengl-experiments/include/Fire.h b/examples/opengl-experiments/include/Fire.h index 96dc280..e6767cf 100644 --- a/examples/opengl-experiments/include/Fire.h +++ b/examples/opengl-experiments/include/Fire.h @@ -9,6 +9,8 @@ class FireScene : public Scene3D FireScene() {}; private: + Entity m_light0; + Entity m_light1; Shader m_flameShader; Shader m_particlesShader; Shader m_smokeShader; @@ -37,30 +39,48 @@ class FireScene : public Scene3D RenderPlane m_renderPlane; - void onCreate() override + void onCreate(Registry& registry, ServiceProvider& services) override { // Base shaders m_backgroundShader = Shader(SHADERS_PATH "common/screen_plane.vert", SHADERS_PATH "misc/background_spherical_grid.frag"); - m_litShader = Shader(SHADERS_PATH "3d/geometry.vert", ASSETS_PATH "fire/shaders/lit.frag"); + m_litShader = Shader(SHADERS_PATH "3d/geometry.vert", services.resources().assetPath("fire/shaders/lit.frag")); m_bloomShader = Shader(SHADERS_PATH "common/screen_plane.vert", SHADERS_PATH "postprocess/bloom.frag"); m_blurShader = Shader(SHADERS_PATH "common/screen_plane.vert", SHADERS_PATH "postprocess/blur.frag"); m_brightFilterShader = Shader(SHADERS_PATH "common/screen_plane.vert", SHADERS_PATH "postprocess/bright_filter.frag"); // Custom shaders - m_flameShader = Shader(SHADERS_PATH "3d/geometry.vert", ASSETS_PATH "fire/shaders/flame.frag"); - m_particlesShader = - Shader(ASSETS_PATH "fire/shaders/fireParticles.vert", ASSETS_PATH "fire/shaders/fireParticles.frag"); - m_smokeShader = - Shader(ASSETS_PATH "fire/shaders/smokeParticles.vert", ASSETS_PATH "fire/shaders/smokeParticles.frag"); + m_flameShader = + Shader(SHADERS_PATH "3d/geometry.vert", services.resources().assetPath("fire/shaders/flame.frag")); + m_particlesShader = Shader(services.resources().assetPath("fire/shaders/fireParticles.vert"), + services.resources().assetPath("fire/shaders/fireParticles.frag")); + m_smokeShader = Shader(services.resources().assetPath("fire/shaders/smokeParticles.vert"), + services.resources().assetPath("fire/shaders/smokeParticles.frag")); m_heatDistortionShader = - Shader(SHADERS_PATH "3d/geometry.vert", ASSETS_PATH "fire/shaders/heatDistortion.frag"); + Shader(SHADERS_PATH "3d/geometry.vert", services.resources().assetPath("fire/shaders/heatDistortion.frag")); - getLigths().push_back(Light{0, glm::vec3(0.0f), 0, glm::vec3(0.0f), glm::vec4(0.0f)}); - getLigths().push_back( - Light{1, glm::vec3(0.0f, 1.0f, 0.0f), 0, glm::vec3(0.0f), glm::vec4(1.0f, 0.95f, 0.9f, 2.0f)}); + m_light0 = registry.createEntity(); + { + Transform& t = registry.addComponent(m_light0); + t.position = glm::vec3(0.0f); + t.rotation = glm::vec3(0.0f); + + LightComponent& lc = registry.addComponent(m_light0); + lc.type = LightType::Directional; + lc.color = glm::vec4(0.0f); + } + m_light1 = registry.createEntity(); + { + Transform& t = registry.addComponent(m_light1); + t.position = glm::vec3(0.0f, 1.0f, 0.0f); + t.rotation = glm::vec3(0.0f); + + LightComponent& lc = registry.addComponent(m_light1); + lc.type = LightType::Point; + lc.color = glm::vec4(1.0f, 0.95f, 0.9f, 2.0f); + } // Load meshes // Quad geom @@ -144,8 +164,8 @@ class FireScene : public Scene3D } // Fire textures - m_noiseTexture = new Texture(ASSETS_PATH "fire/fire.jpg"); - m_flameShape = new Texture(ASSETS_PATH "fire/flame.png"); + m_noiseTexture = new Texture(services.resources().assetPath("fire/fire.jpg")); + m_flameShape = new Texture(services.resources().assetPath("fire/flame.png")); m_sceneTextureBeforeFire = new Texture(Display::rWidth, Display::rHeight, Texture::TextureType::Data); m_postProcessTextureFront = new Texture(Display::rWidth, Display::rHeight, Texture::TextureType::Data); @@ -166,7 +186,7 @@ class FireScene : public Scene3D m_bloomRenderTarget->bindColorTextureToFrameBuffer(*m_brightPassTexture); } - void onDestroy() override + void onDestroy(Registry& registry, ServiceProvider& services) override { m_flameShader.free(); m_particlesShader.free(); @@ -207,28 +227,29 @@ class FireScene : public Scene3D } // Inherited via Scene - void onStart(ECSManager& ecs) override + void onStart(Registry& registry, ServiceProvider& services) override { - m_debugFly = false; + services.debug().setDebugFly(false); } float m_time = 3.1416f; - void onUpdate(float delta, ECSManager& ecs) override + void onUpdate(Registry& registry, ServiceProvider& services) override { - if (Input::GetKeyDown(Input::Q)) + float delta = services.time().deltaTime(); + if (services.input().getKeyDown(Input::Q)) { - setSceneComplete(); + services.sceneControl().goToNextScene(); } - if (m_debugFly) + if (services.debug().debugFly()) { return; } - if (!Input::GetKey(Input::Space)) + if (!services.input().getKey(Input::Space)) { static float speed = 0.15f; - if (Input::GetKey(Input::R)) + if (services.input().getKey(Input::R)) { m_time -= delta * speed; } @@ -238,7 +259,7 @@ class FireScene : public Scene3D } } - Transform& cameraTransform = ecs.getComponent(m_mainCamera); + Transform& cameraTransform = registry.getComponent(services.render().getCameraEntity()); static float amplitude = 10.0f; @@ -291,10 +312,11 @@ class FireScene : public Scene3D glDisable(GL_BLEND); } - void onRender(WeirdRenderer::RenderTarget& renderTarget) override + void onRender(Registry& registry, ServiceProvider& services, WeirdRenderer::RenderTarget& renderTarget) override { - WeirdRenderer::Camera& sceneCamera = getCamera(); - float time = getTime(); + WeirdRenderer::Camera& sceneCamera = + registry.getComponent(services.render().getCameraEntity()).camera; + float time = services.time().time(); glDepthMask(GL_FALSE); glDisable(GL_DEPTH_TEST); @@ -321,12 +343,15 @@ class FireScene : public Scene3D m_litShader.setUniform("u_far", sceneCamera.farPlane); // Pass light rotation - auto& lights = getLigths(); - glm::vec3 position = lights[1].position; + auto& light0_t = registry.getComponent(m_light0); + auto& light0_lc = registry.getComponent(m_light0); + auto& light1_t = registry.getComponent(m_light1); + auto& light1_lc = registry.getComponent(m_light1); + glm::vec3 position = light1_t.position; m_litShader.setUniform("u_lightPos", position); - glm::vec3 direction = lights[1].rotation; + glm::vec3 direction = light1_t.rotation; m_litShader.setUniform("u_directionalLightDir", direction); - glm::vec4 color = lights[1].color; + glm::vec4 color = light1_lc.color; m_litShader.setUniform("u_lightColor", color); // bind current FBO @@ -399,7 +424,7 @@ class FireScene : public Scene3D // Fire renderFire(sceneCamera, time); - if (Input::GetKey(Input::P)) + if (services.input().getKey(Input::P)) { return; } @@ -451,7 +476,7 @@ class FireScene : public Scene3D RenderTarget* finalTarget = m_postProcessDoubleBuffer[!horizontal]; finalTarget->getColorAttachment()->bind(1); - if (Input::GetKey(Input::B)) + if (services.input().getKey(Input::B)) { finalTarget->getColorAttachment()->bind(0); } diff --git a/examples/opengl-experiments/include/Lines.h b/examples/opengl-experiments/include/Lines.h index b4ce3e7..32b26a9 100644 --- a/examples/opengl-experiments/include/Lines.h +++ b/examples/opengl-experiments/include/Lines.h @@ -24,72 +24,78 @@ class LinesScene : public Scene3D Entity m_monkey; - void onCreate() override + void onCreate(Registry& registry, ServiceProvider& services) override { { - auto& whiteMat = createMaterial(); + auto& whiteMat = services.materials().createMaterial(); m_whiteMatId = whiteMat.id; whiteMat.pattern = MaterialPattern::Checkers; std::shared_ptr plane = std::make_shared(0.0f); - auto planeId = registerSDF(plane); + auto planeId = services.shapes().registerSDF(plane); - float vars1[8] = {}; // Custom shape - Entity start = addShape(planeId, vars1, whiteMat); + Entity start = services.shapes().addShape({.shapeId = planeId, .variables = {}, .material = whiteMat}); } m_renderPlane = new RenderPlane(); m_colorTextureCopy = new Texture(Display::rWidth, Display::rHeight, Texture::TextureType::Data); - m_lineShader = new Shader(SHADERS_PATH "common/screen_plane.vert", ASSETS_PATH "lines/lines.frag"); + m_lineShader = + new Shader(SHADERS_PATH "common/screen_plane.vert", services.resources().assetPath("lines/lines.frag")); m_lineRender = new RenderTarget(false); m_lineTexture = new Texture(Display::rWidth, Display::rHeight, Texture::TextureType::Data); m_lineRender->bindColorTextureToFrameBuffer(*m_lineTexture); - m_combinationShader = new Shader(SHADERS_PATH "common/screen_plane.vert", ASSETS_PATH "lines/combination.frag"); + m_combinationShader = new Shader(SHADERS_PATH "common/screen_plane.vert", + services.resources().assetPath("lines/combination.frag")); } // Inherited via Scene - void onStart(ECSManager& ecs) override + void onStart(Registry& registry, ServiceProvider& services) override { - m_debugFly = false; - getLigths().push_back(Light{}); + services.debug().setDebugFly(false); + { + Entity entity = registry.createEntity(); + registry.addComponent(entity); + registry.addComponent(entity); + } { - Entity entity = ecs.createEntity(); - Transform& t = ecs.addComponent(entity); + Entity entity = registry.createEntity(); + Transform& t = registry.addComponent(entity); t.position = vec3(0, 0, 0); - // MeshRenderer &mr = ecs.addComponent(entity); + // MeshRenderer &mr = registry.addComponent(entity); - // auto id = m_resourceManager.getMeshId(ASSETS_PATH "monkey/demo.gltf", entity, true); - // mr.mesh = id; + // auto id = services.resources().getMeshId(services.resources().assetPath("monkey/demo.gltf"), entity, + // true); mr.mesh = id; - auto& sdf = ecs.addComponent(entity); + auto& sdf = registry.addComponent(entity); sdf.materialId = m_whiteMatId; m_monkey = entity; } } - void onUpdate(float delta, ECSManager& ecs) override + void onUpdate(Registry& registry, ServiceProvider& services) override { - if (Input::GetKeyDown(Input::Q)) + float delta = services.time().deltaTime(); + if (services.input().getKeyDown(Input::Q)) { - setSceneComplete(); + services.sceneControl().goToNextScene(); } - Transform& cameraTransform = ecs.getComponent(m_mainCamera); + Transform& cameraTransform = registry.getComponent(services.render().getCameraEntity()); cameraTransform.position.y = 5.0f; cameraTransform.position.z -= 10.0f * delta; - Transform& monkeyTransform = ecs.getComponent(m_monkey); + Transform& monkeyTransform = registry.getComponent(m_monkey); monkeyTransform.position = cameraTransform.position; monkeyTransform.position.z -= 5.0f; } - void onRender(WeirdRenderer::RenderTarget& renderTarget) override + void onRender(Registry& registry, ServiceProvider& services, WeirdRenderer::RenderTarget& renderTarget) override { m_lineRender->bind(); glClearColor(0, 0, 0, 0); // Set clear color diff --git a/examples/opengl-experiments/include/Water.h b/examples/opengl-experiments/include/Water.h index 781cfdd..412cc9c 100644 --- a/examples/opengl-experiments/include/Water.h +++ b/examples/opengl-experiments/include/Water.h @@ -11,6 +11,7 @@ class WaterScene : public Scene3D WaterScene() {}; private: + Entity m_light0; Shader m_waterShader; RenderPlane m_renderPlane; @@ -44,18 +45,27 @@ class WaterScene : public Scene3D // ------------------------------------------------------------------------- - void onCreate() override + void onCreate(Registry& registry, ServiceProvider& services) override { - m_waterShader = Shader(ASSETS_PATH "water/shaders/water.vert", ASSETS_PATH "water/shaders/water.frag"); + m_waterShader = Shader(services.resources().assetPath("water/shaders/water.vert"), + services.resources().assetPath("water/shaders/water.frag")); - getLigths().push_back(Light{0, glm::vec3(0.0f, 0.0f, 0.0f), 0, normalize(glm::vec3(0.0f, 0.4f, 1.0f)), - glm::vec4(1.0f, 1.0f, 1.0f, 0.5f)}); + m_light0 = registry.createEntity(); + { + Transform& t = registry.addComponent(m_light0); + t.position = glm::vec3(0.0f, 0.0f, 0.0f); + t.rotation = normalize(glm::vec3(0.0f, 0.4f, 1.0f)); + + LightComponent& lc = registry.addComponent(m_light0); + lc.type = LightType::Directional; + lc.color = glm::vec4(1.0f, 1.0f, 1.0f, 0.5f); + } m_waterPlane.build(); } - void onDestroy() override + void onDestroy(Registry& registry, ServiceProvider& services) override { m_waterShader.free(); @@ -76,56 +86,57 @@ class WaterScene : public Scene3D float buoyancy = 10.0f; // how strongly this entity is affected by the water surface }; - void onStart(ECSManager& ecs) override + void onStart(Registry& registry, ServiceProvider& services) override { - m_debugFly = true; + services.debug().setDebugFly(true); - auto& redMat = createMaterial(); + auto& redMat = services.materials().createMaterial(); redMat.color = vec4(.8f, 0.2f, 0.2f, 1.0f); { - m_dot = ecs.createEntity(); - Transform& t = ecs.addComponent(m_dot); + m_dot = registry.createEntity(); + Transform& t = registry.addComponent(m_dot); t.position = vec3(0, 0, 0); - auto& renderer = ecs.addComponent(m_dot); + auto& renderer = registry.addComponent(m_dot); renderer.materialId = redMat.id; - ecs.addComponent(m_dot); + registry.addComponent(m_dot); } { - Entity entity = ecs.createEntity(); - Transform& t = ecs.addComponent(entity); + Entity entity = registry.createEntity(); + Transform& t = registry.addComponent(entity); t.position = vec3(3, 0, 0); - MeshRenderer& mr = ecs.addComponent(entity); - auto id = m_resourceManager.getMeshId(ASSETS_PATH "monkey/demo.gltf", entity, true); + MeshRenderer& mr = registry.addComponent(entity); + auto id = services.resources().getMeshId("monkey/demo.gltf", entity, true); mr.mesh = id; - ecs.addComponent(entity); + registry.addComponent(entity); } - ecs.getComponent(m_mainCamera).position = vec3(0, 3, 20); + registry.getComponent(services.render().getCameraEntity()).position = vec3(0, 3, 20); } float m_time = 0.0f; - void onUpdate(float delta, ECSManager& ecs) override + void onUpdate(Registry& registry, ServiceProvider& services) override { - if (Input::GetKeyDown(Input::Q)) + float delta = services.time().deltaTime(); + if (services.input().getKeyDown(Input::Q)) { - setSceneComplete(); + services.sceneControl().goToNextScene(); } m_time += delta; - const auto& floatables = ecs.getComponentArray(); + const auto& floatables = registry.getComponentArray(); for (int i = 0; i < floatables->getSize(); i++) { auto& floatable = floatables->getDataAtIdx(i); // Keep the dot riding the water surface - Transform& transform = ecs.getComponent(floatables->getEntityAtIdx(i)); + Transform& transform = registry.getComponent(floatables->getEntityAtIdx(i)); glm::vec2 flatPos = {transform.position.x, transform.position.z}; float centerHeight = m_waterPlane.waterHeightAt(flatPos, m_time); transform.position.y = centerHeight; @@ -142,12 +153,14 @@ class WaterScene : public Scene3D } } - void onRender(WeirdRenderer::RenderTarget& renderTarget) override + void onRender(Registry& registry, ServiceProvider& services, WeirdRenderer::RenderTarget& renderTarget) override { - WeirdRenderer::Camera& sceneCamera = getCamera(); - float time = getTime(); + WeirdRenderer::Camera& sceneCamera = + registry.getComponent(services.render().getCameraEntity()).camera; + float time = services.time().time(); - auto& lights = getLigths(); + auto& light0_t = registry.getComponent(m_light0); + auto& light0_lc = registry.getComponent(m_light0); // ── Snapshot the current scene colour + depth ──────────────────────── // We need to read from these textures while drawing the water plane, @@ -187,15 +200,15 @@ class WaterScene : public Scene3D m_snapshotDepth.bind(1); m_waterShader.setUniform("u_screenSize", glm::vec2((float)w, (float)h)); - int numLights = (std::min)((int)lights.size(), 8); + int numLights = 1; m_waterShader.setUniform("u_numLights", numLights); for (int i = 0; i < numLights; i++) { std::string prefix = "u_lights[" + std::to_string(i) + "]."; - m_waterShader.setUniform(prefix + "position", lights[i].position); - m_waterShader.setUniform(prefix + "direction", lights[i].rotation); - m_waterShader.setUniform(prefix + "color", lights[i].color); - m_waterShader.setUniform(prefix + "type", (int)lights[i].type); + m_waterShader.setUniform(prefix + "position", light0_t.position); + m_waterShader.setUniform(prefix + "direction", light0_t.rotation); + m_waterShader.setUniform(prefix + "color", light0_lc.color); + m_waterShader.setUniform(prefix + "type", (int)light0_lc.type); } glm::mat4 waterModel = glm::mat4(1.0f); diff --git a/examples/sample-scenes/include/AquariumScene.h b/examples/sample-scenes/include/AquariumScene.h index aaf604e..2e2eb7a 100644 --- a/examples/sample-scenes/include/AquariumScene.h +++ b/examples/sample-scenes/include/AquariumScene.h @@ -72,72 +72,85 @@ class AquariumScene : public Scene2D static constexpr float TANK_W = TANK_RIGHT - TANK_LEFT; static constexpr float TANK_H = TANK_TOP - TANK_BOTTOM; - void onStart(ECSManager& ecs) override + void onStart(Registry& registry, ServiceProvider& services) override { - m_debugInput = true; - m_debugFly = true; + services.debug().setDebugInput(true); + services.debug().setDebugFly(true); - m_background.type = BackgroundType::Sky; - m_background.primaryColor = vec4(98, 129, 240, 255) / 255.0f; - m_background.secondaryColor = vec4(86, 208, 197, 255) / 255.0f; - m_background.scale = 0.15f; + auto& background = services.render().getBackground(); + background.type = BackgroundType::Sky; + background.primaryColor = vec4(98, 129, 240, 255) / 255.0f; + background.secondaryColor = vec4(86, 208, 197, 255) / 255.0f; + background.scale = 0.15f; - Entity globalSettingsEnt = ecs.createEntity(); - auto& settings = ecs.addComponent(globalSettingsEnt); + Entity globalSettingsEnt = registry.createEntity(); + auto& settings = registry.addComponent(globalSettingsEnt); settings.gravity = -10.0f; settings.damping = 0.025f; - ecs.setComponentDirty(settings); + registry.setComponentDirty(settings); - createJellyfish(ecs, 0.0f, 25.0f, 4 + 0, 1.3f, 0.0f); - createJellyfish(ecs, 15.0f, 20.0f, 4 + 3, 1.6f, 1.5f); - createJellyfish(ecs, 30.0f, 28.0f, 4 + 6, 1.1f, 3.0f); - createJellyfish(ecs, 40.0f, 15.0f, 4 + 9, 1.4f, 4.5f); + createJellyfish(registry, services, 0.0f, 25.0f, 4 + 0, 1.3f, 0.0f); + createJellyfish(registry, services, 15.0f, 20.0f, 4 + 3, 1.6f, 1.5f); + createJellyfish(registry, services, 30.0f, 28.0f, 4 + 6, 1.1f, 3.0f); + createJellyfish(registry, services, 40.0f, 15.0f, 4 + 9, 1.4f, 4.5f); - createEel(ecs, -10.0f, 50.0f, 20, 1.0f, 4); - createEel(ecs, 40.0f, 40.0f, 18, 1.0f, 8); - createEel(ecs, 10.0f, 30.0f, 22, 0.9f, 6); + createEel(registry, -10.0f, 50.0f, 20, 1.0f, 4); + createEel(registry, 40.0f, 40.0f, 18, 1.0f, 8); + createEel(registry, 10.0f, 30.0f, 22, 0.9f, 6); { - float seaweedVars[8] = {3.0f, 1.2f, 2.5f}; - Entity seaweed = addShape(DefaultShapes::SINE, seaweedVars, DisplaySettings::Green); - auto& sw = ecs.addComponent(seaweed); + Entity seaweed = services.shapes().addShape({.shapeId = DefaultShapes::SINE, + .variables = {{Primitives::SineWave::AMPLITUDE, 3.0f}, + {Primitives::SineWave::PERIOD, 1.2f}, + {Primitives::SineWave::SPEED, 2.5f}}, + .material = static_cast(DisplaySettings::Green)}); + auto& sw = registry.addComponent(seaweed); sw.animationOffset = 0.0f; } { - float seaweedVars[8] = {2.0f, 2.0f, 1.8f}; - Entity seaweed = addShape(DefaultShapes::SINE, seaweedVars, DisplaySettings::LightGreen); - auto& sw = ecs.addComponent(seaweed); + Entity seaweed = + services.shapes().addShape({.shapeId = DefaultShapes::SINE, + .variables = {{Primitives::SineWave::AMPLITUDE, 2.0f}, + {Primitives::SineWave::PERIOD, 2.0f}, + {Primitives::SineWave::SPEED, 1.8f}}, + .material = static_cast(DisplaySettings::LightGreen)}); + auto& sw = registry.addComponent(seaweed); sw.animationOffset = 1.5f; } - { - float boxVars[8] = {TANK_CX, TANK_CY, TANK_W, TANK_H}; - Entity box = - addShape(DefaultShapes::BOX, boxVars, DisplaySettings::LightBlue, CombinationType::Intersection); - } - - { - float boxVars[8] = {TANK_CX, TANK_CY, TANK_W, TANK_H, 1.0f}; - Entity box = addShape(DefaultShapes::BOX_LINE, boxVars, DisplaySettings::LightBlue); - } + services.shapes().addShape({.shapeId = DefaultShapes::BOX, + .variables = {{Primitives::Box::POS_X, TANK_CX}, + {Primitives::Box::POS_Y, TANK_CY}, + {Primitives::Box::SIZE_X, TANK_W}, + {Primitives::Box::SIZE_Y, TANK_H}}, + .material = static_cast(DisplaySettings::LightBlue), + .combination = CombinationType::Intersection}); + + services.shapes().addShape({.shapeId = DefaultShapes::BOX_LINE, + .variables = {{Primitives::Box::POS_X, TANK_CX}, + {Primitives::Box::POS_Y, TANK_CY}, + {Primitives::Box::SIZE_X, TANK_W}, + {Primitives::Box::SIZE_Y, TANK_H}, + {4, 1.0f}}, + .material = static_cast(DisplaySettings::LightBlue)}); for (int i = 0; i < 40; i++) { - Entity fish = ecs.createEntity(); - auto& t = ecs.addComponent(fish); + Entity fish = registry.createEntity(); + auto& t = registry.addComponent(fish); float fx = TANK_LEFT + 5.0f + static_cast(std::rand() % 60); float fy = TANK_BOTTOM + 5.0f + static_cast(std::rand() % 35); t.position = vec3(fx, fy, 0.0f); - auto& dot = ecs.addComponent(fish); + auto& dot = registry.addComponent(fish); dot.materialId = 4 + (i % 12); - auto& rb = ecs.addComponent(fish); + auto& rb = registry.addComponent(fish); rb.pendingImpulseForce += vec2(static_cast((std::rand() % 100) - 50) * 0.05f, static_cast((std::rand() % 100) - 50) * 0.05f); - auto& fishComp = ecs.addComponent(fish); + auto& fishComp = registry.addComponent(fish); float angle = static_cast(std::rand() % 628) * 0.01f; fishComp.velocity = vec2(std::cos(angle), std::sin(angle)) * 3.0f; fishComp.maxSpeed = 5.0f; @@ -147,22 +160,26 @@ class AquariumScene : public Scene2D fishComp.perceptionRadius = 5.0f; } - ecs.getComponent(m_mainCamera).position = vec3(TANK_CX, TANK_CY, 45.0f); + registry.getComponent(services.render().getCameraEntity()).position = vec3(TANK_CX, TANK_CY, 45.0f); } - void createJellyfish(ECSManager& ecs, float x, float y, int material, float scale, float phase) + void createJellyfish(Registry& registry, ServiceProvider& services, float x, float y, int material, float scale, + float phase) { - Entity bellEntity = ecs.createEntity(); - auto& t = ecs.addComponent(bellEntity); + Entity bellEntity = registry.createEntity(); + auto& t = registry.addComponent(bellEntity); t.position = vec3(x, y, 0.0f); - auto& dot = ecs.addComponent(bellEntity); + auto& dot = registry.addComponent(bellEntity); dot.materialId = material; - auto& rb = ecs.addComponent(bellEntity); + auto& rb = registry.addComponent(bellEntity); - float bellVars[8] = {x, y, 2.5f * scale, 0.8f, 6.0f, 2.0f}; - Entity bellShape = addShape(DefaultShapes::STAR, bellVars, material, CombinationType::Addition, false); + Entity bellShape = services.shapes().addShape({.shapeId = DefaultShapes::STAR, + .variables = {x, y, 2.5f * scale, 0.8f, 6.0f, 2.0f}, + .material = static_cast(material), + .combination = CombinationType::Addition, + .hasCollision = false}); - auto& jf = ecs.addComponent(bellEntity); + auto& jf = registry.addComponent(bellEntity); jf.bellShape = bellShape; jf.pulsePhase = phase; jf.pulseSpeed = 0.5f + static_cast(std::rand() % 100) * 0.01f; @@ -181,19 +198,19 @@ class AquariumScene : public Scene2D for (int s = 0; s < segmentsPerTentacle; s++) { - Entity segment = ecs.createEntity(); - auto& st = ecs.addComponent(segment); + Entity segment = registry.createEntity(); + auto& st = registry.addComponent(segment); st.position = vec3(x + offsetX, y - 2.5f * scale - s * 0.8f, 0.0f); - auto& sd = ecs.addComponent(segment); + auto& sd = registry.addComponent(segment); sd.materialId = material; - auto& srb = ecs.addComponent(segment); + auto& srb = registry.addComponent(segment); jf.tentacleSegments.push_back(segment); if (s > 0) { - Entity springEnt = ecs.createEntity(); - auto& spring = ecs.addComponent(springEnt); + Entity springEnt = registry.createEntity(); + auto& spring = registry.addComponent(springEnt); spring.entityA = jf.tentacleSegments[jf.tentacleSegments.size() - 2]; spring.entityB = segment; spring.stiffness = 0.8f; @@ -201,8 +218,8 @@ class AquariumScene : public Scene2D } } - Entity springEnt = ecs.createEntity(); - auto& spring = ecs.addComponent(springEnt); + Entity springEnt = registry.createEntity(); + auto& spring = registry.addComponent(springEnt); spring.entityA = bellEntity; spring.entityB = jf.tentacleSegments[t_idx * segmentsPerTentacle]; spring.stiffness = 0.5f; @@ -210,13 +227,13 @@ class AquariumScene : public Scene2D } } - void createEel(ECSManager& ecs, float x, float y, int numSegments, float spacing, int baseMaterial) + void createEel(Registry& registry, float x, float y, int numSegments, float spacing, int baseMaterial) { float angle = static_cast(std::rand() % 628) * 0.01f; vec2 dir(std::cos(angle), std::sin(angle)); - Entity eelEnt = ecs.createEntity(); - auto& eel = ecs.addComponent(eelEnt); + Entity eelEnt = registry.createEntity(); + auto& eel = registry.addComponent(eelEnt); eel.phaseOffset = static_cast(std::rand() % 100) * 0.0628f; eel.speed = 2.0f + static_cast(std::rand() % 100) * 0.02f; eel.direction = dir; @@ -225,21 +242,21 @@ class AquariumScene : public Scene2D for (int i = 0; i < numSegments; i++) { - Entity seg = ecs.createEntity(); - auto& t = ecs.addComponent(seg); + Entity seg = registry.createEntity(); + auto& t = registry.addComponent(seg); t.position = vec3(x + dir.x * i * spacing, y + dir.y * i * spacing, 0.0f); - auto& dot = ecs.addComponent(seg); + auto& dot = registry.addComponent(seg); dot.materialId = static_cast(baseMaterial + (i % 4)); - ecs.addComponent(seg); + registry.addComponent(seg); eel.segments.push_back(seg); if (i > 0) { - Entity springEnt = ecs.createEntity(); - auto& spring = ecs.addComponent(springEnt); + Entity springEnt = registry.createEntity(); + auto& spring = registry.addComponent(springEnt); spring.entityA = eel.segments[i - 1]; spring.entityB = seg; spring.stiffness = 0.25f; @@ -248,50 +265,51 @@ class AquariumScene : public Scene2D } } - void onUpdate(float delta, ECSManager& ecs) override + void onUpdate(Registry& registry, ServiceProvider& services) override { - g_cameraPositon = ecs.getComponent(m_mainCamera).position; + float delta = services.time().deltaTime(); + g_cameraPositon = registry.getComponent(services.render().getCameraEntity()).position; - if (Input::GetKeyDown(Input::Q) || Input::GetGamepadButtonDown(Input::GamepadButton::North)) + if (services.input().getKeyDown(Input::Q) || services.input().getGamepadButtonDown(Input::GamepadButton::North)) { - setSceneComplete(); + services.sceneControl().goToNextScene(); } m_time += delta; - auto& cameraTransform = ecs.getComponent(m_mainCamera); - vec2 mouseWorld = - ECS::Camera::screenPositionToWorldPosition2D(cameraTransform, vec2(Input::GetMouseX(), Input::GetMouseY())); + auto& cameraTransform = registry.getComponent(services.render().getCameraEntity()); + vec2 mouseWorld = ECS::Camera::screenPositionToWorldPosition2D( + cameraTransform, vec2(services.input().getMouseX(), services.input().getMouseY())); - if (Input::GetKeyDown(Input::E)) + if (services.input().getKeyDown(Input::E)) { constexpr int FOOD_COUNT = 30; for (int i = 0; i < FOOD_COUNT; i++) { - Entity food = ecs.createEntity(); - auto& ft = ecs.addComponent(food); + Entity food = registry.createEntity(); + auto& ft = registry.addComponent(food); float ox = static_cast(std::rand() % 200 - 100) * 0.06f; float oy = static_cast(std::rand() % 200 - 100) * 0.06f; ft.position = vec3(mouseWorld.x + ox, mouseWorld.y + oy, 0.0f); - ecs.setComponentDirty(ft); - auto& fd = ecs.addComponent(food); + registry.setComponentDirty(ft); + auto& fd = registry.addComponent(food); fd.materialId = 8; - auto& frb = ecs.addComponent(food); + auto& frb = registry.addComponent(food); - ecs.addComponent(food); + registry.addComponent(food); } } - ecs.forEach( + registry.forEach( [&](Entity bellEntity, JellyfishComponent& jf, Transform& bellT, RigidBody2D& rb) { - auto& cs = ecs.getComponent(jf.bellShape); + auto& cs = registry.getComponent(jf.bellShape); cs.parameters[0] = bellT.position.x; cs.parameters[1] = bellT.position.y; float pulse = std::sin(m_time * jf.pulseSpeed + jf.pulsePhase); cs.parameters[3] = 1.0f + pulse * 0.8f; cs.parameters[2] = 2.5f + pulse * 0.3f; - ecs.setComponentDirty(cs); + registry.setComponentDirty(cs); float pulseUp = (pulse > 0.0f) ? pulse * pulse * 20.5f : 0.0f; rb.pendingImpulseForce += jf.direction * pulseUp; @@ -332,14 +350,14 @@ class AquariumScene : public Scene2D } }); - ecs.forEach( + registry.forEach( [&](Entity, Seaweed& sw, CustomShape& cs) { cs.parameters[3] = std::sin(m_time * 1.5f + sw.animationOffset) * 4.0f; - ecs.setComponentDirty(cs); + registry.setComponentDirty(cs); }); - ecs.forEach( + registry.forEach( [&](Entity, EelComponent& eel) { if (std::rand() % 120 == 0) @@ -353,15 +371,15 @@ class AquariumScene : public Scene2D } Entity head = eel.segments[0]; - auto& headT = ecs.getComponent(head); + auto& headT = registry.getComponent(head); vec2 headPos(headT.position); - auto& headRb = ecs.getComponent(head); + auto& headRb = registry.getComponent(head); headRb.pendingContinuousForce += eel.direction * eel.speed * 120.0f; { - auto foodArray = ecs.getComponentArray(); - auto transformArray = ecs.getComponentArray(); + auto foodArray = registry.getComponentArray(); + auto transformArray = registry.getComponentArray(); constexpr float eatRadius = 2.0f; for (size_t fi = 0; fi < foodArray->getSize(); fi++) { @@ -376,7 +394,7 @@ class AquariumScene : public Scene2D if (toFood.x * toFood.x + toFood.y * toFood.y < eatRadius * eatRadius) { ff.eaten = true; - ecs.destroyEntity(foodEntity); + registry.destroyEntity(foodEntity); Entity lastSeg = eel.segments.back(); Entity prevSeg = @@ -391,19 +409,19 @@ class AquariumScene : public Scene2D else tailDir /= tailLen; - Entity newSeg = ecs.createEntity(); - auto& nt = ecs.addComponent(newSeg); + Entity newSeg = registry.createEntity(); + auto& nt = registry.addComponent(newSeg); nt.position = vec3(lastT.position.x + tailDir.x * eel.segmentSpacing, lastT.position.y + tailDir.y * eel.segmentSpacing, 0.0f); - auto& nd = ecs.addComponent(newSeg); + auto& nd = registry.addComponent(newSeg); nd.materialId = static_cast(eel.baseMaterial + static_cast(eel.segments.size() % 4)); - ecs.addComponent(newSeg); + registry.addComponent(newSeg); - Entity springEnt = ecs.createEntity(); - auto& spring = ecs.addComponent(springEnt); + Entity springEnt = registry.createEntity(); + auto& spring = registry.addComponent(springEnt); spring.entityA = lastSeg; spring.entityB = newSeg; spring.stiffness = 0.25f; @@ -417,8 +435,8 @@ class AquariumScene : public Scene2D for (size_t i = 1; i < eel.segments.size(); i++) { - auto& segT = ecs.getComponent(eel.segments[i]); - auto& prevT = ecs.getComponent(eel.segments[i - 1]); + auto& segT = registry.getComponent(eel.segments[i]); + auto& prevT = registry.getComponent(eel.segments[i - 1]); vec2 bodyDir = vec2(segT.position) - vec2(prevT.position); float bodyLen = length(bodyDir); @@ -427,15 +445,15 @@ class AquariumScene : public Scene2D vec2 normal(bodyDir.y, -bodyDir.x); normal /= bodyLen; float wave = std::sin(m_time * 5.0f + eel.phaseOffset + static_cast(i) * 0.6f); - auto& segRb = ecs.getComponent(eel.segments[i]); + auto& segRb = registry.getComponent(eel.segments[i]); segRb.pendingContinuousForce += normal * wave * 80.0f; } } for (Entity seg : eel.segments) { - auto& segT = ecs.getComponent(seg); - auto& segRb = ecs.getComponent(seg); + auto& segT = registry.getComponent(seg); + auto& segRb = registry.getComponent(seg); segRb.pendingContinuousForce += vec2(0.0f, 5.0f); if (segT.position.x < TANK_LEFT + 5.0f) segRb.pendingImpulseForce += vec2(2.0f, 0.0f); @@ -449,17 +467,17 @@ class AquariumScene : public Scene2D }); // Boids - updateFishBoids(delta, ecs); + updateFishBoids(delta, registry); } - void updateFishBoids(float delta, ECSManager& ecs) + void updateFishBoids(float delta, Registry& registry) { PROFILE_SCOPE("Boids"); - auto fishArray = ecs.getComponentArray(); - auto foodArray = ecs.getComponentArray(); - auto transformArray = ecs.getComponentArray(); - auto rbArray = ecs.getComponentArray(); + auto fishArray = registry.getComponentArray(); + auto foodArray = registry.getComponentArray(); + auto transformArray = registry.getComponentArray(); + auto rbArray = registry.getComponentArray(); const size_t fishCount = fishArray->getSize(); if (fishCount == 0) @@ -651,7 +669,7 @@ class AquariumScene : public Scene2D { fd.energy += 1.0f; foodArray->getDataFromEntity(fc.entity).eaten = true; - ecs.destroyEntity(fc.entity); + registry.destroyEntity(fc.entity); foodCache[fi] = foodCache.back(); foodCache.pop_back(); fi--; @@ -671,16 +689,15 @@ class AquariumScene : public Scene2D } } - void onEntityShapeCollision(ECSManager& ecs, WeirdEngine::EntityShapeCollisionEvent& event) override + void onEntityShapeCollision(Registry& registry, ServiceProvider& services, + WeirdEngine::EntityShapeCollisionEvent& event) override { - event.raw.friction *= 50.0f; - if (std::rand() % 8 == 0) { - playSound({0.015f, 150.0f + (std::rand() % 150), true, vec3(event.raw.position, 0.0f), 1}); + services.audio().playSound({0.015f, 150.0f + (std::rand() % 150), true, vec3(event.raw.position, 0.0f), 1}); } - auto eelArray = ecs.getComponentArray(); + auto eelArray = registry.getComponentArray(); for (size_t i = 0; i < eelArray->getSize(); i++) { auto& eel = eelArray->getDataAtIdx(i); @@ -691,14 +708,15 @@ class AquariumScene : public Scene2D } } - if (ecs.hasComponent(event.entity)) + if (registry.hasComponent(event.entity)) { - auto& jf = ecs.getComponent(event.entity); + auto& jf = registry.getComponent(event.entity); jf.direction = -jf.direction; } } - void onEntityCollision(ECSManager& ecs, WeirdEngine::EntityCollisionEvent& event) override + void onEntityCollision(Registry& registry, ServiceProvider& services, + WeirdEngine::EntityCollisionEvent& event) override { Entity a = event.entityA; Entity b = event.entityB; @@ -708,13 +726,13 @@ class AquariumScene : public Scene2D if (std::rand() % 10 == 0) { - playSound({0.01f, 300.0f + (std::rand() % 200), true, vec3(0.0f), 1}); + services.audio().playSound({0.01f, 300.0f + (std::rand() % 200), true, vec3(0.0f), 1}); } - if (ecs.hasComponent(a) && ecs.hasComponent(b)) + if (registry.hasComponent(a) && registry.hasComponent(b)) { - auto& fishA = ecs.getComponent(a); - auto& fishB = ecs.getComponent(b); + auto& fishA = registry.getComponent(a); + auto& fishB = registry.getComponent(b); if (fishA.energy >= 3.0f && fishB.energy >= 3.0f && fishA.mateCooldown <= 0.0f && fishB.mateCooldown <= 0.0f) @@ -724,26 +742,26 @@ class AquariumScene : public Scene2D fishA.mateCooldown = 5.0f; fishB.mateCooldown = 5.0f; - auto& tA = ecs.getComponent(a); - auto& tB = ecs.getComponent(b); - auto& dotA = ecs.getComponent(a); + auto& tA = registry.getComponent(a); + auto& tB = registry.getComponent(b); + auto& dotA = registry.getComponent(a); int numOffspring = 1 + (std::rand() % 4); for (int i = 0; i < numOffspring; i++) { - Entity baby = ecs.createEntity(); - auto& bt = ecs.addComponent(baby); + Entity baby = registry.createEntity(); + auto& bt = registry.addComponent(baby); float ox = static_cast(std::rand() % 100 - 50) * 0.03f; float oy = static_cast(std::rand() % 100 - 50) * 0.03f; bt.position = (tA.position + tB.position) * 0.5f + vec3(ox, oy, 0.0f); - ecs.setComponentDirty(bt); + registry.setComponentDirty(bt); - auto& bd = ecs.addComponent(baby); + auto& bd = registry.addComponent(baby); bd.materialId = static_cast(dotA.materialId); - auto& brb = ecs.addComponent(baby); + auto& brb = registry.addComponent(baby); - auto& bFish = ecs.addComponent(baby); + auto& bFish = registry.addComponent(baby); float angle = static_cast(std::rand() % 628) * 0.01f; bFish.velocity = vec2(std::cos(angle), std::sin(angle)) * 3.0f; bFish.maxSpeed = fishA.maxSpeed; diff --git a/examples/sample-scenes/include/CollisionHandling.h b/examples/sample-scenes/include/CollisionHandling.h index 489949e..5e63452 100644 --- a/examples/sample-scenes/include/CollisionHandling.h +++ b/examples/sample-scenes/include/CollisionHandling.h @@ -12,10 +12,10 @@ class CollisionHandlingScene : public Scene2D private: // Inherited via Scene - void onStart(ECSManager& ecs) override + void onStart(Registry& registry, ServiceProvider& services) override { - m_debugInput = true; - m_debugFly = true; + services.debug().setDebugInput(true); + services.debug().setDebugFly(true); // Create a random number generator engine @@ -28,48 +28,56 @@ class CollisionHandlingScene : public Scene2D float z = 0; - Entity entity = ecs.createEntity(); - Transform& t = ecs.addComponent(entity); + Entity entity = registry.createEntity(); + Transform& t = registry.addComponent(entity); t.position = vec3(x + 0.5f, y + 0.5f, z); - Dot& dot = ecs.addComponent(entity); + Dot& dot = registry.addComponent(entity); dot.materialId = material; - RigidBody2D& rb = ecs.addComponent(entity); + RigidBody2D& rb = registry.addComponent(entity); } // Floor - { - float variables[8]{15.0f, 5.0f, 25.0f}; - addShape(DefaultShapes::CIRCLE, variables, 3); - } - - { - float variables[8]{15.0f, -50.0f, 250.0f, 50.0f}; - auto floor = addShape(DefaultShapes::BOX, variables, 3, CombinationType::SmoothAddition); - ecs.getComponent(floor).smoothFactor = 3.0f; - } - - { - float variables[8]{15.0f, 5.0f, 20.0f}; - addShape(DefaultShapes::CIRCLE, variables, 3, CombinationType::Subtraction); - } - - ecs.getComponent(m_mainCamera).position = g_cameraPositon; + services.shapes().addShape({.shapeId = DefaultShapes::CIRCLE, + .variables = {{Primitives::Circle::POS_X, 15.0f}, + {Primitives::Circle::POS_Y, 5.0f}, + {Primitives::Circle::RADIUS, 25.0f}}, + .material = 3}); + + auto floor = services.shapes().addShape({.shapeId = DefaultShapes::BOX, + .variables = {{Primitives::Box::POS_X, 15.0f}, + {Primitives::Box::POS_Y, -50.0f}, + {Primitives::Box::SIZE_X, 250.0f}, + {Primitives::Box::SIZE_Y, 50.0f}}, + .material = 3, + .combination = CombinationType::SmoothAddition}); + registry.getComponent(floor).smoothFactor = 3.0f; + + services.shapes().addShape({.shapeId = DefaultShapes::CIRCLE, + .variables = {{Primitives::Circle::POS_X, 15.0f}, + {Primitives::Circle::POS_Y, 5.0f}, + {Primitives::Circle::RADIUS, 20.0f}}, + .material = 3, + .combination = CombinationType::Subtraction}); + + registry.getComponent(services.render().getCameraEntity()).position = g_cameraPositon; } - void onUpdate(float delta, ECSManager& ecs) override + float m_currentTime = 0.0f; + void onUpdate(Registry& registry, ServiceProvider& services) override { - if (Input::GetKeyDown(Input::Q) || Input::GetGamepadButtonDown(Input::GamepadButton::North)) + m_currentTime = services.time().time(); + if (services.input().getKeyDown(Input::Q) || services.input().getGamepadButtonDown(Input::GamepadButton::North)) { - setSceneComplete(); + services.sceneControl().goToNextScene(); } } float m_lastTime = 0.0f; - void onCollision(Simulation2D& simulation, WeirdEngine::CollisionEvent& event) override + void onPhysicsRigidBodyCollision(Simulation2D& simulation, WeirdEngine::PhysicsCollisionEvent& event) override { - float t = getTime(); + float t = m_currentTime; if (t - m_lastTime < 0.1f) return; // Avoid multiple collisions in a short time @@ -79,8 +87,8 @@ class CollisionHandlingScene : public Scene2D simulation.setPosition(event.bodyB, vec2(15.0f, 24.45f)); simulation.addImpulseForce(event.bodyB, vec2(20.0f * sinf(10.0f * t), -30.0f)); - // Entity a = ecs.getComponentArray()->getDataAtIdx(event.bodyA).Owner; - // Transform &at = ecs.getComponent(a); + // Entity a = registry.getComponentArray()->getDataAtIdx(event.bodyA).Owner; + // Transform &at = registry.getComponent(a); // at.position.x = 15.0f + sinf(event.bodyB * 123.4565f + t); // at.position.y = 5.0f + (event.bodyA % 10) * 2.5f; // at.isDirty = true; diff --git a/examples/sample-scenes/include/DestroyScene.h b/examples/sample-scenes/include/DestroyScene.h index 731b274..a4fd11b 100644 --- a/examples/sample-scenes/include/DestroyScene.h +++ b/examples/sample-scenes/include/DestroyScene.h @@ -24,21 +24,22 @@ class DestroyScene : public Scene2D float m_timer = 0.0f; // Inherited via Scene - void onStart(ECSManager& ecs) override + void onStart(Registry& registry, ServiceProvider& services) override { - m_debugInput = true; - m_debugFly = true; + services.debug().setDebugInput(true); + services.debug().setDebugFly(true); - ecs.getComponent(m_mainCamera).position = g_cameraPositon; + registry.getComponent(services.render().getCameraEntity()).position = g_cameraPositon; } - void onUpdate(float delta, ECSManager& ecs) override + void onUpdate(Registry& registry, ServiceProvider& services) override { - g_cameraPositon = ecs.getComponent(m_mainCamera).position; + float delta = services.time().deltaTime(); + g_cameraPositon = registry.getComponent(services.render().getCameraEntity()).position; - if (Input::GetKeyDown(Input::Q) || Input::GetGamepadButtonDown(Input::GamepadButton::North)) + if (services.input().getKeyDown(Input::Q) || services.input().getGamepadButtonDown(Input::GamepadButton::North)) { - setSceneComplete(); + services.sceneControl().goToNextScene(); } m_timer += delta; @@ -58,13 +59,13 @@ class DestroyScene : public Scene2D { for (int j = 0; j < 10; ++j) { - Entity e = ecs.createEntity(); - auto& t = ecs.addComponent(e); + Entity e = registry.createEntity(); + auto& t = registry.addComponent(e); t.position = vec3((std::rand() % 200) - 100.0f, (std::rand() % 100) - 50.0f, 0.0f); - ecs.setComponentDirty(t); - auto& ui = ecs.addComponent(e); + registry.setComponentDirty(t); + auto& ui = registry.addComponent(e); ui.materialId = 4 + (e % 12); - auto& rb = ecs.addComponent(e); + auto& rb = registry.addComponent(e); m_testBalls.push_back(e); } } @@ -78,9 +79,14 @@ class DestroyScene : public Scene2D float y = (std::rand() % 100) - 50.0f; float w = (float)(std::rand() % 4 + 1); float h = (float)(std::rand() % 4 + 1); - float variables[8]{w, y, x, h, 0.0f, 0.0f, 0.0f, 0.0f}; uint16_t material = std::rand() % 16; - Entity shape = addShape(DefaultShapes::BOX, variables, material, CombinationType::Addition); + Entity shape = services.shapes().addShape({.shapeId = DefaultShapes::BOX, + .variables = {{Primitives::Box::POS_X, w}, + {Primitives::Box::POS_Y, y}, + {Primitives::Box::SIZE_X, x}, + {Primitives::Box::SIZE_Y, h}}, + .material = material, + .combination = CombinationType::Addition}); m_testShapes.push_back(shape); } break; @@ -93,17 +99,18 @@ class DestroyScene : public Scene2D int idx2 = std::rand() % m_testBalls.size(); if (idx1 != idx2) { - Entity constraintEnt = ecs.createEntity(); + Entity constraintEnt = registry.createEntity(); if (std::rand() % 2 == 0) { - auto& constraint = ecs.addComponent(constraintEnt); + auto& constraint = + registry.addComponent(constraintEnt); constraint.entityA = m_testBalls[idx1]; constraint.entityB = m_testBalls[idx2]; constraint.distance = 3.0f + (std::rand() % 5); } else { - auto& spring = ecs.addComponent(constraintEnt); + auto& spring = registry.addComponent(constraintEnt); spring.entityA = m_testBalls[idx1]; spring.entityB = m_testBalls[idx2]; spring.restDistance = 3.0f + (std::rand() % 5); @@ -119,7 +126,7 @@ class DestroyScene : public Scene2D if (!m_testShapes.empty()) { int idx = std::rand() % m_testShapes.size(); - ecs.destroyEntity(m_testShapes[idx]); + registry.destroyEntity(m_testShapes[idx]); m_testShapes[idx] = m_testShapes.back(); m_testShapes.pop_back(); } @@ -130,7 +137,7 @@ class DestroyScene : public Scene2D if (!m_testBalls.empty()) { int idx = std::rand() % m_testBalls.size(); - ecs.destroyEntity(m_testBalls[idx]); + registry.destroyEntity(m_testBalls[idx]); m_testBalls[idx] = m_testBalls.back(); m_testBalls.pop_back(); } @@ -141,7 +148,7 @@ class DestroyScene : public Scene2D if (!m_testConstraints.empty()) { int idx = std::rand() % m_testConstraints.size(); - ecs.destroyEntity(m_testConstraints[idx]); + registry.destroyEntity(m_testConstraints[idx]); m_testConstraints[idx] = m_testConstraints.back(); m_testConstraints.pop_back(); } @@ -152,7 +159,8 @@ class DestroyScene : public Scene2D } } - void onEntityCollision(ECSManager& ecs, WeirdEngine::EntityCollisionEvent& event) override + void onEntityCollision(Registry& registry, ServiceProvider& services, + WeirdEngine::EntityCollisionEvent& event) override { if (std::rand() % 5 != 0) return; @@ -161,26 +169,25 @@ class DestroyScene : public Scene2D if (a != INVALID_ENTITY) { - if (!ecs.hasComponent(a)) - ecs.addComponent(a); - ecs.getComponent(a).collisionCount++; + if (!registry.hasComponent(a)) + registry.addComponent(a); + registry.getComponent(a).collisionCount++; } - playSound({0.02f, 400.0f + (std::rand() % 200), false, vec3(0.0f), 1}); + services.audio().playSound({0.02f, 400.0f + (std::rand() % 200), false, vec3(0.0f), 1}); } - void onEntityShapeCollision(ECSManager& ecs, WeirdEngine::EntityShapeCollisionEvent& event) override + void onEntityShapeCollision(Registry& registry, ServiceProvider& services, + WeirdEngine::EntityShapeCollisionEvent& event) override { - event.raw.friction *= 100.0f; - if (std::rand() % 20 == 0) { Entity e = event.entity; - if (e != INVALID_ENTITY && ecs.hasComponent(e)) + if (e != INVALID_ENTITY && registry.hasComponent(e)) { - auto& rb = ecs.getComponent(e); + auto& rb = registry.getComponent(e); rb.isFixed = true; - ecs.setComponentDirty(rb); + registry.setComponentDirty(rb); } } @@ -188,16 +195,16 @@ class DestroyScene : public Scene2D { int idx = std::rand() % m_testConstraints.size(); Entity constraint = m_testConstraints[idx]; - if (ecs.hasComponent(constraint)) + if (registry.hasComponent(constraint)) { - auto& spring = ecs.getComponent(constraint); + auto& spring = registry.getComponent(constraint); spring.restDistance = 1.0f + (std::rand() % 10); } } if (std::rand() % 5 == 0) { - playSound({0.02f, 200.0f + (std::rand() % 100), false, vec3(event.raw.position, 0.0f), 1}); + services.audio().playSound({0.02f, 200.0f + (std::rand() % 100), false, vec3(event.raw.position, 0.0f), 1}); } } }; diff --git a/examples/sample-scenes/include/ImageScene.h b/examples/sample-scenes/include/ImageScene.h index 8fddbb9..8e11922 100644 --- a/examples/sample-scenes/include/ImageScene.h +++ b/examples/sample-scenes/include/ImageScene.h @@ -15,13 +15,14 @@ class ImageScene : public Scene2D private: std::string binaryString; std::string filePath = "cache/image.txt"; - std::string imagePath = ASSETS_PATH "jimmy.jpg"; + std::string imagePath; // Inherited via Scene - void onStart(ECSManager& ecs) override + void onStart(Registry& registry, ServiceProvider& services) override { - m_debugInput = true; - m_debugFly = true; + services.debug().setDebugInput(true); + services.debug().setDebugFly(true); + imagePath = services.resources().assetPath("jimmy.jpg"); // Check if the folder exists if (!std::filesystem::exists("cache/")) @@ -60,35 +61,41 @@ class ImageScene : public Scene2D material = (materialId.size() > 0 && materialId.size() <= 2) ? std::stoi(materialId) : 0; - Entity entity = ecs.createEntity(); - Transform& t = ecs.addComponent(entity); + Entity entity = registry.createEntity(); + Transform& t = registry.addComponent(entity); t.position = vec3(x + 0.5f, y + 0.5f, 0); - Dot& dot = ecs.addComponent(entity); + Dot& dot = registry.addComponent(entity); dot.materialId = material; - RigidBody2D& rb = ecs.addComponent(entity); + RigidBody2D& rb = registry.addComponent(entity); } // Floor - { - float variables[8]{15, -5, 25.0f, 5.0f, 0.0f}; - addShape(DefaultShapes::BOX, variables, 3); - } + services.shapes().addShape({.shapeId = DefaultShapes::BOX, + .variables = {{Primitives::Box::POS_X, 15.0f}, + {Primitives::Box::POS_Y, -5.0f}, + {Primitives::Box::SIZE_X, 25.0f}, + {Primitives::Box::SIZE_Y, 5.0f}}, + .material = 3}); // Wall right - { - float variables[8]{30 + 5, 20, 5.0f, 30.0f, 0.0f}; - addShape(DefaultShapes::BOX, variables, 3); - } + services.shapes().addShape({.shapeId = DefaultShapes::BOX, + .variables = {{Primitives::Box::POS_X, 35.0f}, + {Primitives::Box::POS_Y, 20.0f}, + {Primitives::Box::SIZE_X, 5.0f}, + {Primitives::Box::SIZE_Y, 30.0f}}, + .material = 3}); // Wall left - { - float variables[8]{0 - 5, 20, 5.0f, 30.0f, 0.0f}; - addShape(DefaultShapes::BOX, variables, 3); - } - - ecs.getComponent(m_mainCamera).position = g_cameraPositon; + services.shapes().addShape({.shapeId = DefaultShapes::BOX, + .variables = {{Primitives::Box::POS_X, -5.0f}, + {Primitives::Box::POS_Y, 20.0f}, + {Primitives::Box::SIZE_X, 5.0f}, + {Primitives::Box::SIZE_Y, 30.0f}}, + .material = 3}); + + registry.getComponent(services.render().getCameraEntity()).position = g_cameraPositon; } vec3 getColor(const char* path, float x, float y) @@ -167,17 +174,17 @@ class ImageScene : public Scene2D return closestIndex; } - void onUpdate(float delta, ECSManager& ecs) override + void onUpdate(Registry& registry, ServiceProvider& services) override { - if (Input::GetKeyDown(Input::Q) || Input::GetGamepadButtonDown(Input::GamepadButton::North)) + if (services.input().getKeyDown(Input::Q) || services.input().getGamepadButtonDown(Input::GamepadButton::North)) { - setSceneComplete(); + services.sceneControl().goToNextScene(); } // Get colors - if (Input::GetKeyDown(Input::P)) + if (services.input().getKeyDown(Input::P)) { - auto components = ecs.getComponentArray(); + auto components = registry.getComponentArray(); // Result string std::string result; @@ -186,7 +193,7 @@ class ImageScene : public Scene2D { RigidBody2D& rb = components->getDataAtIdx(i); Entity rbOwner = components->getEntityAtIdx(i); - Transform& t = ecs.getComponent(rbOwner); + Transform& t = registry.getComponent(rbOwner); int x = static_cast(floor(t.position.x)); int y = static_cast(floor(30.0f - t.position.y)); diff --git a/examples/sample-scenes/include/LifeScene.h b/examples/sample-scenes/include/LifeScene.h index 8f7c2b4..bc0c5fc 100644 --- a/examples/sample-scenes/include/LifeScene.h +++ b/examples/sample-scenes/include/LifeScene.h @@ -27,18 +27,18 @@ class LifeScene : public Scene2D private: // Inherited via Scene - void onStart(ECSManager& ecs) override + void onStart(Registry& registry, ServiceProvider& services) override { - m_debugInput = true; - m_debugFly = true; + services.debug().setDebugInput(true); + services.debug().setDebugFly(true); - Entity globalSettingsEnt = ecs.createEntity(); - auto& settings = ecs.addComponent(globalSettingsEnt); + Entity globalSettingsEnt = registry.createEntity(); + auto& settings = registry.addComponent(globalSettingsEnt); settings.gravity = 0.0f; settings.damping = 0.1f; - ecs.setComponentDirty(settings); + registry.setComponentDirty(settings); - const std::filesystem::path organismsDir(ASSETS_PATH "Organisms"); + const std::filesystem::path organismsDir(services.resources().assetPath("Organisms")); { int i = 0; @@ -51,32 +51,32 @@ class LifeScene : public Scene2D for (size_t j = 0; j < 3; j++) { - Entity firstCreated = static_cast(ecs.getEntityCount()); + Entity firstCreated = static_cast(registry.getEntityCount()); - auto tags = loadWeirdFile(entry.path().string()); + auto tags = services.serialization().loadWeirdFile(entry.path().string()); - Entity lastCreated = static_cast(ecs.getEntityCount()); + Entity lastCreated = static_cast(registry.getEntityCount()); for (Entity e = 0; e < (lastCreated - firstCreated); e++) { - if (!ecs.hasComponent(firstCreated + e)) + if (!registry.hasComponent(firstCreated + e)) { continue; } - auto& t = ecs.getComponent(firstCreated + e); + auto& t = registry.getComponent(firstCreated + e); t.position += vec3(-10.0f + (float)(i * 10), -10.0f + (float)(j * 10), 0.0f); } if (tags.contains("head")) { Entity headEntity = tags["head"]; - ecs.addComponent(headEntity); + registry.addComponent(headEntity); } else { - auto& a = ecs.getComponent(firstCreated); - ecs.addComponent(firstCreated); + auto& a = registry.getComponent(firstCreated); + registry.addComponent(firstCreated); } // break; @@ -86,33 +86,34 @@ class LifeScene : public Scene2D } } - ecs.getComponent(m_mainCamera).position = g_cameraPositon; + registry.getComponent(services.render().getCameraEntity()).position = g_cameraPositon; } - void onUpdate(float delta, ECSManager& ecs) override + void onUpdate(Registry& registry, ServiceProvider& services) override { - g_cameraPositon = ecs.getComponent(m_mainCamera).position; + float delta = services.time().deltaTime(); + g_cameraPositon = registry.getComponent(services.render().getCameraEntity()).position; - if (Input::GetKeyDown(Input::Q) || Input::GetGamepadButtonDown(Input::GamepadButton::North)) + if (services.input().getKeyDown(Input::Q) || services.input().getGamepadButtonDown(Input::GamepadButton::North)) { - setSceneComplete(); + services.sceneControl().goToNextScene(); } - updateHeads(delta, ecs); + updateHeads(delta, registry, services); } - void updateHeads(float delta, ECSManager& ecs) + void updateHeads(float delta, Registry& registry, ServiceProvider& services) { - float animationT = std::sin(getTime() * 10.0f) * 0.5f + 0.25f; + float animationT = std::sin(services.time().time() * 10.0f) * 0.5f + 0.25f; - auto headArray = ecs.getComponentArray(); + auto headArray = registry.getComponentArray(); for (size_t i = 0; i < headArray->getSize(); i++) { auto& head = headArray->getDataAtIdx(i); Entity headEntity = headArray->getEntityAtIdx(i); - auto& rb = ecs.getComponent(headEntity); + auto& rb = registry.getComponent(headEntity); rb.pendingContinuousForce += head.forceMagnitude * head.direction * animationT; if (animationT < 0.0f && !head.directionChanged) @@ -132,7 +133,7 @@ class LifeScene : public Scene2D head.directionChanged = false; } - vec2 positon = vec2(ecs.getComponent(headEntity).position); + vec2 positon = vec2(registry.getComponent(headEntity).position); if (length(positon) > 50.0f) { head.direction = -normalize(positon); diff --git a/examples/sample-scenes/include/MouseCollisionScene.h b/examples/sample-scenes/include/MouseCollisionScene.h index 3d05d5f..22b7cc9 100644 --- a/examples/sample-scenes/include/MouseCollisionScene.h +++ b/examples/sample-scenes/include/MouseCollisionScene.h @@ -20,10 +20,10 @@ class MouseCollisionScene : public Scene2D Entity m_cursorShape; // Inherited via Scene - void onStart(ECSManager& ecs) override + void onStart(Registry& registry, ServiceProvider& services) override { - m_debugInput = true; - m_debugFly = true; + services.debug().setDebugInput(true); + services.debug().setDebugFly(true); for (size_t i = 0; i < 9900; i++) { @@ -35,85 +35,90 @@ class MouseCollisionScene : public Scene2D float z = 0; - Entity entity = ecs.createEntity(); - Transform& t = ecs.addComponent(entity); + Entity entity = registry.createEntity(); + Transform& t = registry.addComponent(entity); t.position = vec3(x + 0.5f, y + 0.5f, z); if (i < 100) { } - Dot& dot = ecs.addComponent(entity); + Dot& dot = registry.addComponent(entity); dot.materialId = 0; - RigidBody2D& rb = ecs.addComponent(entity); - CollisionCounter& counter = ecs.addComponent(entity); + RigidBody2D& rb = registry.addComponent(entity); + CollisionCounter& counter = registry.addComponent(entity); } // Floor - { - float variables[8]{0.0f, 1.5f, 1.0f}; - addShape(DefaultShapes::SINE, variables, 3); - } + services.shapes().addShape({.shapeId = DefaultShapes::SINE, + .variables = {{Primitives::SineWave::AMPLITUDE, 0.0f}, + {Primitives::SineWave::PERIOD, 1.5f}, + {Primitives::SineWave::SPEED, 1.0f}}, + .material = 3}); // Wall right - { - float variables[8]{30 + 5, 0, 5.0f, 30.0f, 0.0f}; - addShape(DefaultShapes::BOX, variables, 3); - } + services.shapes().addShape({.shapeId = DefaultShapes::BOX, + .variables = {{Primitives::Box::POS_X, 35.0f}, + {Primitives::Box::POS_Y, 0.0f}, + {Primitives::Box::SIZE_X, 5.0f}, + {Primitives::Box::SIZE_Y, 30.0f}}, + .material = 3}); // Wall left - { - float variables[8]{-5, 0, 5.0f, 30.0f, 0.0f}; - addShape(DefaultShapes::BOX, variables, 3); - } - - { - float variables[8]{-15.0f, 50.0f, 5.0f, 4.5f, 2.0f, 10.0f}; - Entity star = addShape(DefaultShapes::CIRCLE, variables, 7); - - m_cursorShape = star; - } - - ecs.getComponent(m_mainCamera).position = g_cameraPositon; + services.shapes().addShape({.shapeId = DefaultShapes::BOX, + .variables = {{Primitives::Box::POS_X, -5.0f}, + {Primitives::Box::POS_Y, 0.0f}, + {Primitives::Box::SIZE_X, 5.0f}, + {Primitives::Box::SIZE_Y, 30.0f}}, + .material = 3}); + + m_cursorShape = services.shapes().addShape({.shapeId = DefaultShapes::CIRCLE, + .variables = {{Primitives::Circle::POS_X, -15.0f}, + {Primitives::Circle::POS_Y, 50.0f}, + {Primitives::Circle::RADIUS, 5.0f}}, + .material = 7}); + + registry.getComponent(services.render().getCameraEntity()).position = g_cameraPositon; } - void onUpdate(float delta, ECSManager& ecs) override + void onUpdate(Registry& registry, ServiceProvider& services) override { - g_cameraPositon = ecs.getComponent(m_mainCamera).position; + g_cameraPositon = registry.getComponent(services.render().getCameraEntity()).position; - if (Input::GetKeyDown(Input::Q) || Input::GetGamepadButtonDown(Input::GamepadButton::North)) + if (services.input().getKeyDown(Input::Q) || services.input().getGamepadButtonDown(Input::GamepadButton::North)) { - setSceneComplete(); + services.sceneControl().goToNextScene(); } // Move wall to mouse { - CustomShape& cs = ecs.getComponent(m_cursorShape); - auto& cameraTransform = ecs.getComponent(m_mainCamera); - float x = Input::GetMouseX(); - float y = Input::GetMouseY(); + CustomShape& cs = registry.getComponent(m_cursorShape); + auto& cameraTransform = registry.getComponent(services.render().getCameraEntity()); + float x = services.input().getMouseX(); + float y = services.input().getMouseY(); // Transform mouse coordinates to world space vec2 mousePositionInWorld = ECS::Camera::screenPositionToWorldPosition2D(cameraTransform, vec2(x, y)); cs.parameters[0] = mousePositionInWorld.x; cs.parameters[1] = mousePositionInWorld.y; - ecs.setComponentDirty(cs); + registry.setComponentDirty(cs); } } - void onEntityCollision(ECSManager& ecs, WeirdEngine::EntityCollisionEvent& event) override + void onEntityCollision(Registry& registry, ServiceProvider& services, + WeirdEngine::EntityCollisionEvent& event) override { - if (ecs.hasComponent(event.entityA)) + if (registry.hasComponent(event.entityA)) { - auto& counter = ecs.getComponent(event.entityA); + auto& counter = registry.getComponent(event.entityA); counter.count++; constexpr int COLLISIONS_PER_MATERIAL = 50; if (counter.count <= 10 * COLLISIONS_PER_MATERIAL && counter.count % COLLISIONS_PER_MATERIAL == 0) { - auto& dot = ecs.getComponent(event.entityA); + auto& dot = registry.getComponent(event.entityA); dot.materialId++; if (counter.count == 10 * COLLISIONS_PER_MATERIAL) @@ -121,15 +126,15 @@ class MouseCollisionScene : public Scene2D } } - if (ecs.hasComponent(event.entityB)) + if (registry.hasComponent(event.entityB)) { - auto& counter = ecs.getComponent(event.entityB); + auto& counter = registry.getComponent(event.entityB); counter.count++; constexpr int COLLISIONS_PER_MATERIAL = 50; if (counter.count <= 10 * COLLISIONS_PER_MATERIAL && counter.count % COLLISIONS_PER_MATERIAL == 0) { - auto& dot = ecs.getComponent(event.entityB); + auto& dot = registry.getComponent(event.entityB); dot.materialId++; if (counter.count == 10 * COLLISIONS_PER_MATERIAL) @@ -138,13 +143,14 @@ class MouseCollisionScene : public Scene2D } } - void onEntityShapeCollision(ECSManager& ecs, WeirdEngine::EntityShapeCollisionEvent& event) override + void onEntityShapeCollision(Registry& registry, ServiceProvider& services, + WeirdEngine::EntityShapeCollisionEvent& event) override { - if (ecs.hasComponent(event.entity)) + if (registry.hasComponent(event.entity)) { - auto& counter = ecs.getComponent(event.entity); + auto& counter = registry.getComponent(event.entity); counter.count = 0; - auto& dot = ecs.getComponent(event.entity); + auto& dot = registry.getComponent(event.entity); dot.materialId = 0; } } diff --git a/examples/sample-scenes/include/RopeScene.h b/examples/sample-scenes/include/RopeScene.h index 9247159..92195d4 100644 --- a/examples/sample-scenes/include/RopeScene.h +++ b/examples/sample-scenes/include/RopeScene.h @@ -18,10 +18,10 @@ class RopeScene : public Scene2D std::vector m_balls; - void onStart(ECSManager& ecs) override + void onStart(Registry& registry, ServiceProvider& services) override { - m_debugInput = true; - m_debugFly = true; + services.debug().setDebugInput(true); + services.debug().setDebugFly(true); constexpr int rowWidth = 30; constexpr int numBalls = rowWidth * 2; @@ -35,15 +35,15 @@ class RopeScene : public Scene2D float y = startY - static_cast(i / rowWidth); int material = 4 + (i % 12); - Entity entity = ecs.createEntity(); + Entity entity = registry.createEntity(); - auto& t = ecs.addComponent(entity); + auto& t = registry.addComponent(entity); t.position = vec3(x + 0.5f, y + 0.5f, 0.0f); - auto& sdf = ecs.addComponent(entity); + auto& sdf = registry.addComponent(entity); sdf.materialId = material; - auto& rb = ecs.addComponent(entity); + auto& rb = registry.addComponent(entity); m_balls.push_back(entity); } @@ -57,8 +57,8 @@ class RopeScene : public Scene2D // Structural Springs (Down and Right) if (hasRowBelow) // Down { - Entity springEnt = ecs.createEntity(); - auto& spring = ecs.addComponent(springEnt); + Entity springEnt = registry.createEntity(); + auto& spring = registry.addComponent(springEnt); spring.entityA = m_balls[i]; spring.entityB = m_balls[i + rowWidth]; spring.stiffness = stiffness; @@ -67,8 +67,8 @@ class RopeScene : public Scene2D if (notRightEdge) // Right { - Entity springEnt = ecs.createEntity(); - auto& spring = ecs.addComponent(springEnt); + Entity springEnt = registry.createEntity(); + auto& spring = registry.addComponent(springEnt); spring.entityA = m_balls[i]; spring.entityB = m_balls[i + 1]; spring.stiffness = stiffness; @@ -78,8 +78,8 @@ class RopeScene : public Scene2D // Shear Springs (Diagonal) if (hasRowBelow && notRightEdge) // Bottom-Right { - Entity springEnt = ecs.createEntity(); - auto& spring = ecs.addComponent(springEnt); + Entity springEnt = registry.createEntity(); + auto& spring = registry.addComponent(springEnt); spring.entityA = m_balls[i]; spring.entityB = m_balls[i + rowWidth + 1]; spring.stiffness = stiffness; @@ -88,8 +88,8 @@ class RopeScene : public Scene2D if (hasRowBelow && notLeftEdge) // Bottom-Left { - Entity springEnt = ecs.createEntity(); - auto& spring = ecs.addComponent(springEnt); + Entity springEnt = registry.createEntity(); + auto& spring = registry.addComponent(springEnt); spring.entityA = m_balls[i]; spring.entityB = m_balls[i + rowWidth - 1]; spring.stiffness = stiffness; @@ -98,34 +98,42 @@ class RopeScene : public Scene2D } // Fix corners - ecs.getComponent(m_balls[0]).isFixed = true; - ecs.setEntityDirty(m_balls[0], true); + registry.getComponent(m_balls[0]).isFixed = true; + registry.setEntityDirty(m_balls[0], true); if (numBalls > rowWidth) { - ecs.getComponent(m_balls[rowWidth - 1]).isFixed = true; - ecs.setEntityDirty(m_balls[rowWidth - 1], true); - ecs.getComponent(m_balls[rowWidth]).isFixed = true; - ecs.setEntityDirty(m_balls[rowWidth], true); - ecs.getComponent(m_balls[(2 * rowWidth) - 1]).isFixed = true; - ecs.setEntityDirty(m_balls[(2 * rowWidth) - 1], true); + registry.getComponent(m_balls[rowWidth - 1]).isFixed = true; + registry.setEntityDirty(m_balls[rowWidth - 1], true); + registry.getComponent(m_balls[rowWidth]).isFixed = true; + registry.setEntityDirty(m_balls[rowWidth], true); + registry.getComponent(m_balls[(2 * rowWidth) - 1]).isFixed = true; + registry.setEntityDirty(m_balls[(2 * rowWidth) - 1], true); } // Add base shapes (walls, ground, custom) - float vars0[8] = {1.0f, 0.5f, 1.0f}; // Floor shape - addShape(DefaultShapes::SINE, vars0, 3); - - float vars1[8] = {25.0f, 10.0f, 5.0f, 0.5f, 13.0f, 5.0f}; // Custom shape - m_star = addShape(DefaultShapes::STAR, vars1, 3); - - float vars3[8] = {15.0f, -98.0f, 15.0f, 100.0f}; - addShape(DefaultShapes::BOX, vars3, 3, CombinationType::Addition); - - ecs.getComponent(m_mainCamera).position = g_cameraPositon; + services.shapes().addShape({.shapeId = DefaultShapes::SINE, + .variables = {{Primitives::SineWave::AMPLITUDE, 1.0f}, + {Primitives::SineWave::PERIOD, 0.5f}, + {Primitives::SineWave::SPEED, 1.0f}}, + .material = 3}); + + m_star = services.shapes().addShape( + {.shapeId = DefaultShapes::STAR, .variables = {25.0f, 10.0f, 5.0f, 0.5f, 13.0f, 5.0f}, .material = 3}); + + services.shapes().addShape({.shapeId = DefaultShapes::BOX, + .variables = {{Primitives::Box::POS_X, 15.0f}, + {Primitives::Box::POS_Y, -98.0f}, + {Primitives::Box::SIZE_X, 15.0f}, + {Primitives::Box::SIZE_Y, 100.0f}}, + .material = 3, + .combination = CombinationType::Addition}); + + registry.getComponent(services.render().getCameraEntity()).position = g_cameraPositon; } - void throwBalls(ECSManager& ecs) + void throwBalls(Registry& registry, ServiceProvider& services) { - if (getTime() <= m_lastSpawnTime + 0.1) + if (services.time().time() <= m_lastSpawnTime + 0.1) { return; } @@ -135,54 +143,56 @@ class RopeScene : public Scene2D { float y = 60.0f + (1.2f * i); - Entity entity = ecs.createEntity(); + Entity entity = registry.createEntity(); - auto& t = ecs.addComponent(entity); + auto& t = registry.addComponent(entity); t.position = vec3(0.5f, y + 0.5f, 0.0f); - auto& sdf = ecs.addComponent(entity); - sdf.materialId = 4 + ecs.getComponentArray()->getSize() % 12; + auto& sdf = registry.addComponent(entity); + sdf.materialId = 4 + registry.getComponentArray()->getSize() % 12; - auto& rb = ecs.addComponent(entity); + auto& rb = registry.addComponent(entity); rb.pendingImpulseForce += vec2(20.0f, 0.0f); } - m_lastSpawnTime = getTime(); + m_lastSpawnTime = services.time().time(); } - void onUpdate(float delta, ECSManager& ecs) override + void onUpdate(Registry& registry, ServiceProvider& services) override { - g_cameraPositon = ecs.getComponent(m_mainCamera).position; - if (Input::GetKeyDown(Input::Q) || Input::GetGamepadButtonDown(Input::GamepadButton::North)) + float delta = services.time().deltaTime(); + g_cameraPositon = registry.getComponent(services.render().getCameraEntity()).position; + + if (services.input().getKeyDown(Input::Q) || services.input().getGamepadButtonDown(Input::GamepadButton::North)) { - setSceneComplete(); + services.sceneControl().goToNextScene(); } // Animate custom shape over time if (m_star != INVALID_ENTITY) { - // Instead of getSimulation().getSimulationTime(), we can just use getTime() if Scene provides it, or track - // delta. + // Instead of getSimulation().getSimulationTime(), we can just use services.time().time() if Scene provides + // it, or track delta. static float animTime = 0.0f; animTime += delta; - auto& cs = ecs.getComponent(m_star); + auto& cs = registry.getComponent(m_star); cs.parameters[4] = static_cast((static_cast(std::floor(animTime)) % 5) + 2); cs.parameters[3] = std::sin(3.1416f * animTime); - ecs.setComponentDirty(cs); + registry.setComponentDirty(cs); } - if (Input::GetKey(Input::E) || Input::GetGamepadButton(Input::GamepadButton::West)) + if (services.input().getKey(Input::E) || services.input().getGamepadButton(Input::GamepadButton::West)) { - throwBalls(ecs); + throwBalls(registry, services); } static vec2 boxStart; static bool createBoxInUI = true; - if (Input::GetKeyDown(Input::M)) + if (services.input().getKeyDown(Input::M)) { - auto& cam = ecs.getComponent(m_mainCamera); - vec2 screen = {Input::GetMouseX(), Input::GetMouseY()}; + auto& cam = registry.getComponent(services.render().getCameraEntity()); + vec2 screen = {services.input().getMouseX(), services.input().getMouseY()}; if (createBoxInUI) { @@ -194,10 +204,10 @@ class RopeScene : public Scene2D boxStart = world; } } - else if (Input::GetKeyUp(Input::M)) + else if (services.input().getKeyUp(Input::M)) { - auto& cam = ecs.getComponent(m_mainCamera); - vec2 screen = {Input::GetMouseX(), Input::GetMouseY()}; + auto& cam = registry.getComponent(services.render().getCameraEntity()); + vec2 screen = {services.input().getMouseX(), services.input().getMouseY()}; vec2 world = ECS::Camera::screenPositionToWorldPosition2D(cam, screen); vec2 boxEnd; @@ -214,28 +224,43 @@ class RopeScene : public Scene2D float y = (boxStart.y + boxEnd.y) / 2.0f; float w = 0.5f * std::abs(boxStart.x - boxEnd.x); float h = 0.5f * std::abs(boxStart.y - boxEnd.y); - float vars[8] = {x, y, w, h, 1.2f}; if (createBoxInUI) - addUIShape(DefaultShapes::BOX, vars, 7, CombinationType::SmoothAddition); + services.shapes().addUIShape({.shapeId = DefaultShapes::BOX, + .variables = {{Primitives::Box::POS_X, x}, + {Primitives::Box::POS_Y, y}, + {Primitives::Box::SIZE_X, w}, + {Primitives::Box::SIZE_Y, h}, + {4, 1.2f}}, + .material = 7, + .combination = CombinationType::SmoothAddition}); else - addShape(DefaultShapes::BOX, vars, 4 + ecs.getComponentArray()->getSize() % 12, - CombinationType::SmoothAddition, true, ecs.getComponentArray()->getSize()); + services.shapes().addShape( + {.shapeId = DefaultShapes::BOX, + .variables = {{Primitives::Box::POS_X, x}, + {Primitives::Box::POS_Y, y}, + {Primitives::Box::SIZE_X, w}, + {Primitives::Box::SIZE_Y, h}, + {4, 1.2f}}, + .material = static_cast(4 + registry.getComponentArray()->getSize() % 12), + .combination = CombinationType::SmoothAddition, + .hasCollision = true, + .group = static_cast(registry.getComponentArray()->getSize())}); } - if (Input::GetKeyDown(Input::N)) + if (services.input().getKeyDown(Input::N)) { - auto& cam = ecs.getComponent(m_mainCamera); - vec2 screen = {Input::GetMouseX(), Input::GetMouseY()}; + auto& cam = registry.getComponent(services.render().getCameraEntity()); + vec2 screen = {services.input().getMouseX(), services.input().getMouseY()}; vec2 world = ECS::Camera::screenPositionToWorldPosition2D(cam, screen); - float vars[8] = {world.x, world.y, 5.0f, 7.5f, 1.0f}; - addShape(DefaultShapes::STAR, vars, 3); + services.shapes().addShape( + {.shapeId = DefaultShapes::STAR, .variables = {world.x, world.y, 5.0f, 7.5f, 1.0f}, .material = 3}); } - if (Input::GetKey(Input::R) || Input::GetGamepadButton(Input::GamepadButton::South)) + if (services.input().getKey(Input::R) || services.input().getGamepadButton(Input::GamepadButton::South)) { - ecs.forEach( + registry.forEach( [&](Entity e, RigidBody2D& rb, Transform& t) { vec2 force(0, -0.001f * (t.position.y * t.position.y)); diff --git a/examples/sample-scenes/include/ServiceShowcaseScene.h b/examples/sample-scenes/include/ServiceShowcaseScene.h new file mode 100644 index 0000000..3912434 --- /dev/null +++ b/examples/sample-scenes/include/ServiceShowcaseScene.h @@ -0,0 +1,583 @@ +#pragma once + +#include +#include +#include +#include + +#include + +#include "globals.h" +#include "weird-engine/math/Default2DSDFs.h" +#include "weird-renderer/core/Display.h" + +using namespace WeirdEngine; + +// ============================================================================ +// Service showcase scene. +// +// Demonstrates the new system + ServiceProvider API: most Scene callbacks are +// thin wrappers that delegate to a plain free function (a "system") of the +// form: +// +// void system(Registry& registry, ServiceProvider& services, ...); +// +// (onRender and the physics-thread callbacks are inlined in the scene +// instead, see below: the dispatcher is deliberately not involved there.) +// +// Systems never touch Scene internals: everything they need is either on the +// Registry& or on the ServiceProvider& passed to the callback. Even the +// scene's own state lives in the ECS (see State below): a single "state" +// entity owns it, and systems reach it through the component array. +// +// Registered as systems: onCreate, onStart, onUpdate (4 systems), +// onImGuiRender, onEntityCollision, onEntityShapeCollision, onDestroy. +// Inlined overrides: onRender and the physics-thread callbacks +// (onPhysicsStep, onPhysicsRigidBodyCollision, onPhysicsShapeCollision). +// +// Controls: +// Left click spawn a ball at the cursor +// Space pause / resume the physics simulation +// Up / Down gravity up / down (live) +// Left / Right damping down / up (live) +// Ctrl+S save the scene to assets/scenes/service_showcase.weird +// Ctrl+L load the saved scene into the current one +// Q go to the next scene +// ============================================================================ + +namespace ServiceShowcase +{ + // Game-defined per-body user data: derives from BodyUserData and sets a + // type discriminator so the physics callbacks can cast safely (see + // getUserDataAs). + struct CharacterData : BodyUserData + { + static constexpr int TYPE = 1; + + // The inherited `type` member must match TYPE or every + // getUserDataAs() / forEachUserData() check will fail. + CharacterData() + { + type = TYPE; + } + + float restitution = 1.2f; + float jumpStrength = 5.0f; + }; + + // Scene state as an ECS component: attached to a single "state" entity + // created by onCreateSystem. This is the ECS-native way for systems to + // share state instead of passing a struct around. + struct State + { + Entity timeText = INVALID_ENTITY; + Entity entitiesText = INVALID_ENTITY; + Entity collisionsText = INVALID_ENTITY; + Entity hintsText = INVALID_ENTITY; + + float spawnTimer = 0.0f; + float leaderAngle = 0.0f; + int ballsSpawned = 0; + float gravity = -9.8f; + float damping = 0.001f; + float initialTime = 0.0f; + + // Each counter is only touched from the main thread (collision + // callbacks); plain ints are fine. Atomicity would require a custom + // component manager since components must be copyable for the ECS + // storage. + int entityCollisions = 0; + int shapeCollisions = 0; + }; + + // State entity lookup: there is exactly one State component in the scene + // (created by onCreateSystem), so it always lives at index 0 of the State + // component array. + inline State& getState(Registry& registry, ServiceProvider& services) + { + return registry.getComponentArray()->getDataAtIdx(0); + } + + inline Entity spawnBall(Registry& registry, vec2 position) + { + Entity entity = registry.createEntity(); + auto& t = registry.addComponent(entity); + t.position = vec3(position, 0.0f); + + auto& dot = registry.addComponent(entity); + dot.materialId = DisplaySettings::LightGray; + + auto& rb = registry.addComponent(entity); + rb.velocity = vec2((std::rand() % 200 - 100) / 40.0f, 0.0f); + registry.setComponentDirty(rb); + + return entity; + } + + // ---------------------------------------------------------------- onCreate + // Runs after the ECS, materials and camera exist, before any scene file is + // loaded and before onStart. Creates the "state" entity that owns the + // scene's State component. + inline void onCreateSystem(Registry& registry, ServiceProvider& services) + { + Entity stateEntity = registry.createEntity(); + registry.addComponent(stateEntity); + services.tags().tag(stateEntity, "state"); + services.serialization().blacklistEntity(stateEntity); + + State& state = getState(registry, services); + state.initialTime = services.time().time(); + std::cout << "[ServiceShowcase] onCreate at simulation time " << state.initialTime << "s" << std::endl; + } + + // ----------------------------------------------------------------- onStart + inline void onStartSystem(Registry& registry, ServiceProvider& services) + { + State& state = getState(registry, services); + + // Debug flags through the provider + services.debug().setDebugFly(true); + services.debug().setDebugInput(true); + + // Materials through the provider + Material3D& floorMaterial = services.materials().createMaterial(); + floorMaterial.color = vec4(0.8f, 0.8f, 0.8f, 1.0f); + floorMaterial.roughness = 1.0f; + + Material3D& ringMaterial = services.materials().createMaterial(); + ringMaterial.color = vec4(0.9f, 0.3f, 0.2f, 1.0f); + + // Register a custom SDF: a ring (outer circle minus inner circle) + ShapeId ringShape; + { + auto x = std::make_shared(0); + auto y = std::make_shared(1); + auto outerRadius = std::make_shared(2); + auto innerRadius = std::make_shared(3); + + auto outer = std::make_shared(x, y, outerRadius); + auto inner = + std::make_shared(-1.0f, std::make_shared(x, y, innerRadius)); + auto ring = std::make_shared(outer, inner); + + ringShape = services.shapes().registerSDF(ring); + + Entity ringEntity = services.shapes().addShape({.shapeId = ringShape, + .variables = {15.0f, 20.0f, 5.0f, 4.0f}, + .material = ringMaterial, + .combination = CombinationType::Addition, + .hasCollision = true, + .group = 0}); + registry.getComponent(ringEntity).smoothFactor = 2.0f; + } + + // Floor + Entity floor = services.shapes().addShape({.shapeId = DefaultShapes::BOX, + .variables = {{Primitives::Box::POS_X, 15.0f}, + {Primitives::Box::POS_Y, -50.0f}, + {Primitives::Box::SIZE_X, 250.0f}, + {Primitives::Box::SIZE_Y, 50.0f}}, + .material = floorMaterial, + .combination = CombinationType::SmoothAddition}); + services.tags().tag(floor, "floor"); + registry.getComponent(floor).smoothFactor = 3.0f; + + // Pit: a subtraction shape; balls that roll into it fall through + services.shapes().addShape({.shapeId = DefaultShapes::CIRCLE, + .variables = {{Primitives::Circle::POS_X, 30.0f}, + {Primitives::Circle::POS_Y, 5.0f}, + {Primitives::Circle::RADIUS, 4.0f}}, + .material = 0, + .combination = CombinationType::Subtraction, + .hasCollision = true, + .group = CustomShape::GLOBAL_GROUP}); + + // Camera + registry.getComponent(services.render().getCameraEntity()).position = g_cameraPositon; + + // Leader ball: orbits a point (moved by FollowSystem through the + // physics simulation) + { + Entity leader = registry.createEntity(); + auto& t = registry.addComponent(leader); + t.position = vec3(15.0f, 12.0f, 0.0f); + + auto& dot = registry.addComponent(leader); + dot.materialId = DisplaySettings::Yellow; + + registry.addComponent(leader); + services.tags().tag(leader, "leader"); + } + + // Character ball: carries per-body user data so the physics callbacks + // can identify and tune it without touching the ECS (the physics + // thread must not access the ECS). Ownership of the data is handed off + // to the simulation; the callbacks reach it through the simulation id + // stored in the RigidBody2D component. + { + Entity character = registry.createEntity(); + auto& t = registry.addComponent(character); + t.position = vec3(15.0f, 15.0f, 0.0f); + + auto& dot = registry.addComponent(character); + dot.materialId = DisplaySettings::Blue; + + auto& rb = registry.addComponent(character); + services.tags().tag(character, "character"); + + // Configure the data before handing ownership to the simulation; + // std::move() empties the local unique_ptr, so the only way to + // reach the data afterwards is getUserDataAs(). + auto characterData = std::make_unique(); + characterData->jumpStrength = 10.0f; + services.physics().setUserData(rb.simulationId, std::move(characterData)); + + // Reading and modifying the data after the handoff: no cached + // pointer is kept, everything goes through the physics service. + services.physics().getUserDataAs(rb.simulationId)->restitution = 2.0f; + } + + // Initial ball pile + for (int i = 0; i < 12; ++i) + { + float x = 8.0f + (i % 4) * 3.0f; + float y = 28.0f + (i / 4) * 4.0f; + spawnBall(registry, vec2(x, y)); + } + + // UI text (screen space; blacklisted so it is never serialized) + { + auto makeText = [&](const char* initial, vec2 screenPosition, Entity& outEntity, int material) + { + outEntity = registry.createEntity(); + services.serialization().blacklistEntity(outEntity); + + auto& t = registry.addComponent(outEntity); + t.position = vec3(screenPosition, 0.0f); + + auto& text = registry.addComponent(outEntity); + text.text = initial; + text.material = material; + text.horizontalAlignment = TextRenderer::HorizontalAlignment::Left; + text.verticalAlignment = TextRenderer::VerticalAlignment::Bottom; + }; + + makeText("time 0.0s", vec2(10.0f, static_cast(Display::height) - 10.0f), state.timeText, + DisplaySettings::LightGreen); + makeText("entities 0", vec2(10.0f, static_cast(Display::height) - 22.0f), state.entitiesText, + DisplaySettings::Cyan); + makeText("collisions 0 / 0", vec2(10.0f, static_cast(Display::height) - 34.0f), state.collisionsText, + DisplaySettings::Magenta); + makeText("click: spawn | space: pause | arrows: gravity/damping | ctrl+s: save | ctrl+l: load | q: next", + vec2(10.0f, 10.0f), state.hintsText, DisplaySettings::Orange); + } + } + + // ----------------------------------------------------- update: spawn system + // Periodically drops a new ball from the top of the world. + inline void spawnSystem(Registry& registry, ServiceProvider& services) + { + State& state = getState(registry, services); + + state.spawnTimer += services.time().deltaTime(); + if (state.spawnTimer > 0.35f && registry.getEntityCount() < 160) + { + state.spawnTimer = 0.0f; + float x = 3.0f + static_cast(std::rand() % 240) / 10.0f; + spawnBall(registry, vec2(x, 35.0f)); + state.ballsSpawned++; + } + } + + // ----------------------------------------------------- update: input system + inline void inputSystem(Registry& registry, ServiceProvider& services) + { + State& state = getState(registry, services); + + // Scene transition through the provider + if (services.input().getKeyDown(Input::Q) || services.input().getGamepadButtonDown(Input::GamepadButton::North)) + { + services.sceneControl().goToNextScene(); + } + + // Pause / resume through the provider + if (services.input().getKeyDown(Input::Space)) + { + if (services.physics().isPaused()) + services.physics().resume(); + else + services.physics().pause(); + } + + // Real-time physics settings through the provider + if (services.input().getKeyDown(Input::Up)) + { + state.gravity = std::clamp(state.gravity + 1.0f, -30.0f, 0.0f); + services.physics().setGravity(state.gravity); + } + if (services.input().getKeyDown(Input::Down)) + { + state.gravity = std::clamp(state.gravity - 1.0f, -30.0f, 0.0f); + services.physics().setGravity(state.gravity); + } + if (services.input().getKeyDown(Input::Left)) + { + state.damping = std::max(0.0f, state.damping - 0.05f); + services.physics().setDamping(state.damping); + } + if (services.input().getKeyDown(Input::Right)) + { + state.damping += 0.05f; + services.physics().setDamping(state.damping); + } + + // Spawn a ball where the mouse points + if (services.input().getMouseButtonDown(Input::LeftClick) && !services.input().isUIClick()) + { + auto& cameraTransform = registry.getComponent(services.render().getCameraEntity()); + vec2 mouseWorld = ECS::Camera::screenPositionToWorldPosition2D( + cameraTransform, vec2(services.input().getMouseX(), services.input().getMouseY())); + spawnBall(registry, mouseWorld); + state.ballsSpawned++; + } + + // Serialization through the provider + if (services.input().getKeyDown(Input::S) && services.input().getKey(Input::LeftCtrl)) + { + services.serialization().saveScene(services.resources().assetPath("scenes/service_showcase.weird")); + std::cout << "[ServiceShowcase] scene saved" << std::endl; + } + if (services.input().getKeyDown(Input::L) && services.input().getKey(Input::LeftCtrl)) + { + // blacklistEntities = true: entities loaded from disk are excluded + // from future saves, so saving again does not duplicate them. + TagMap loaded = services.serialization().loadWeirdFile( + services.resources().assetPath("scenes/service_showcase.weird"), true); + for (const auto& [name, entity] : loaded) + std::cout << "[ServiceShowcase] loaded tag '" << name << "' -> entity " << entity << std::endl; + } + } + + // ----------------------------------------------------- update: follow system + // Orbits the "leader" ball around a point by writing its velocity straight + // into the physics simulation through the provider. + inline void followSystem(Registry& registry, ServiceProvider& services) + { + State& state = getState(registry, services); + + Entity leader = services.tags().getEntityByTag("leader"); + if (leader == INVALID_ENTITY) + return; + + state.leaderAngle += services.time().deltaTime() * 1.5f; + + glm::vec2 center(15.0f, 18.0f); + glm::vec2 target = center + 6.0f * glm::vec2(std::cos(state.leaderAngle), std::sin(state.leaderAngle)); + + auto& rb = registry.getComponent(leader); + glm::vec2 current = glm::vec2(registry.getComponent(leader).position); + rb.velocity = (target - current) * 2.0f; + registry.setComponentDirty(rb); + } + + // -------------------------------------------------------- update: ui system + inline void uiSystem(Registry& registry, ServiceProvider& services) + { + State& state = getState(registry, services); + + char buffer[64]; + + auto& timeText = registry.getComponent(state.timeText); + std::snprintf(buffer, sizeof(buffer), "time %.1fs", services.time().time()); + timeText.text = buffer; + registry.setComponentDirty(timeText); + + auto& entitiesText = registry.getComponent(state.entitiesText); + std::snprintf(buffer, sizeof(buffer), "entities %d (balls spawned: %d)", services.registry().getEntityCount(), + state.ballsSpawned); + entitiesText.text = buffer; + registry.setComponentDirty(entitiesText); + + auto& collisionsText = registry.getComponent(state.collisionsText); + std::snprintf(buffer, sizeof(buffer), "collisions %d body / %d shape", state.entityCollisions, + state.shapeCollisions); + collisionsText.text = buffer; + registry.setComponentDirty(collisionsText); + } + + // ------------------------------------------------------- onEntityCollision + // Main thread. Body-body collisions mapped to entities: count them, flash + // the colliding ball and play a sound through the provider. + inline void onEntityCollisionSystem(Registry& registry, ServiceProvider& services, EntityCollisionEvent& event) + { + State& state = getState(registry, services); + state.entityCollisions++; + + // Flash the colliding ball orange, but keep the character's identity + // color: it is identified through its per-body user data. + if (event.entityA != INVALID_ENTITY && registry.hasComponent(event.entityA) && + registry.hasComponent(event.entityA)) + { + RigidBody2D& rb = registry.getComponent(event.entityA); + if (services.physics().getUserDataAs(rb.simulationId) == nullptr) + { + auto& dot = registry.getComponent(event.entityA); + dot.materialId = DisplaySettings::Orange; + registry.setComponentDirty(dot); + } + } + + services.audio().playSound({0.05f, 300.0f, false, vec3(0.0f), 1}); + } + + // --------------------------------------------------- onEntityShapeCollision + // Main thread. Shape collisions mapped to entities: count them and play a + // spatial sound at the contact point. + inline void onEntityShapeCollisionSystem(Registry& registry, ServiceProvider& services, + EntityShapeCollisionEvent& event) + { + State& state = getState(registry, services); + state.shapeCollisions++; + + if (event.entity != INVALID_ENTITY) + { + float frequency = 200.0f + static_cast(state.shapeCollisions % 40) * 5.0f; + services.audio().playSound({0.04f, frequency, true, vec3(event.raw.position, 0.0f), 1}); + } + } + + // ---------------------------------------------------------------- onDestroy + inline void onDestroySystem(Registry& registry, ServiceProvider& services) + { + std::cout << "[ServiceShowcase] scene destroyed at " << services.time().time() << "s" << std::endl; + + State& state = getState(registry, services); + state.ballsSpawned = 0; + state.entityCollisions = 0; + state.shapeCollisions = 0; + } +} // namespace ServiceShowcase + +class ServiceShowcaseScene : public Scene2D +{ +public: + ServiceShowcaseScene() + { + addCreateSystem(ServiceShowcase::onCreateSystem); + addStartSystem(ServiceShowcase::onStartSystem); + + // Multiple systems for the same stage run sequentially! + addUpdateSystem(ServiceShowcase::spawnSystem); + addUpdateSystem(ServiceShowcase::inputSystem); + addUpdateSystem(ServiceShowcase::followSystem); + addUpdateSystem(ServiceShowcase::uiSystem); + + addEntityCollisionSystem(ServiceShowcase::onEntityCollisionSystem); + addEntityShapeCollisionSystem(ServiceShowcase::onEntityShapeCollisionSystem); + + addDestroySystem(ServiceShowcase::onDestroySystem); + + addImGuiRenderSystem( + [](Registry& registry, ServiceProvider& services) + { + auto& state = ServiceShowcase::getState(registry, services); + + ImGui::Text("Time: %.2fs", services.time().time()); + ImGui::Text("Entities: %d", services.registry().getEntityCount()); + ImGui::Text("Gravity: %.1f | Damping: %.2f | Physics %s", state.gravity, state.damping, + services.physics().isPaused() ? "paused" : "running"); + ImGui::Text("Collisions: %d body / %d shape", state.entityCollisions, state.shapeCollisions); + ImGui::Text("Balls spawned: %d", state.ballsSpawned); + + ImGui::Separator(); + ImGui::Text("Left click: spawn ball | Space: pause/resume"); + ImGui::Text("Up/Down: gravity | Left/Right: damping"); + ImGui::Text("Ctrl+S: save scene | Ctrl+L: load scene | Q: next scene"); + }); + } + +private: + void onRender(Registry& registry, ServiceProvider& services, WeirdRenderer::RenderTarget& renderTarget) override + { + static bool logged = false; + if (!logged) + { + logged = true; + std::cout << "[ServiceShowcase] onRender (3D render path)" << std::endl; + } + } + + // Physics thread callbacks. Fire on the physics thread mid-step; no ECS + // access here. They delegate to Simulation2D-only systems. + void onPhysicsStep(Simulation2D& simulation) override + { + // ------------------------------------------------------------ onPhysicsStep + // Physics thread. Simulation-coupled logic only: no ECS access here (the + // engine does not allow touching the ECS from the physics thread), so the + // step counter is a local static rather than a component. + // Every 120 steps, applies a wind gust that alternates direction. + // Physics-thread systems only receive the Simulation2D& (no ECS, no + // ServiceProvider). + static int stepCounter = 0; + if (++stepCounter % 120 != 0) + return; + + static float direction = 1.0f; + direction *= -1.0f; + + for (SimulationID id = 0; id < simulation.getSize(); ++id) + simulation.addImpulseForce(id, vec2(2.5f * direction, 0.0f)); + + // Per-body user data: iterate only the bodies that opted in, and + // branch on the type discriminator. No ECS access needed here. + simulation.forEachUserData( + [&](SimulationID id, BodyUserData& data) + { + if (data.type == ServiceShowcase::CharacterData::TYPE) + { + auto characterData = simulation.getUserDataAs(id); + + // Kick the character into a visible hop each gust. + // massIndependent = true: jumpStrength is the delta-v in + // m/s, regardless of the body's mass. + simulation.addImpulseForce(id, vec2(0.0f, characterData->jumpStrength), true); + } + }); + } + + void onPhysicsRigidBodyCollision(Simulation2D& simulation, PhysicsCollisionEvent& event) override + { + // -------------------------------------------------------------- onPhysicsRigidBodyCollision + // Physics thread. Body-body collisions: push the pair apart based on their + // relative velocity. + vec2 va = simulation.getPhysicsVelocity(event.bodyA); + vec2 vb = simulation.getPhysicsVelocity(event.bodyB); + + vec2 separation = (va - vb) * 0.05f; + simulation.addImpulseForce(event.bodyA, -separation); + simulation.addImpulseForce(event.bodyB, separation); + } + + void onPhysicsShapeCollision(Simulation2D& simulation, PhysicsShapeCollisionEvent& event) override + { + // --------------------------------------------------------- onPhysicsShapeCollision + // Physics thread. Shape collisions: bounce the body off the shape normal + // when the penetration is deep enough. The character (identified via its + // per-body user data) gets a bouncier, more slippery response by tuning + // the event before the solver reads it. + if (event.state == CollisionState::START && event.penetration > 0.1f) + { + simulation.addImpulseForce(event.body, event.normal * (2.0f + event.penetration * 10.0f)); + } + + // Type-checked cast: nullptr unless this body carries CharacterData + if (auto* data = simulation.getUserDataAs(event.body)) + { + // absortion damps the normal axis (more damping = less bounce), so + // higher restitution = bouncier. At restitution = 2.0 (set in + // onStartSystem) this equals the previous hard-coded 0.5x. + event.absortion *= 1.0f / data->restitution; + event.friction *= 0.5f; // slippery character + } + } +}; diff --git a/examples/sample-scenes/include/ShapesCombinations.h b/examples/sample-scenes/include/ShapesCombinations.h index 958aefe..a2b39ab 100644 --- a/examples/sample-scenes/include/ShapesCombinations.h +++ b/examples/sample-scenes/include/ShapesCombinations.h @@ -20,16 +20,20 @@ class ShapeCombinatiosScene : public Scene2D std::vector m_uiPoints; - void onStart(ECSManager& ecs) override + void onStart(Registry& registry, ServiceProvider& services) override { - m_debugInput = true; - m_debugFly = true; + services.debug().setDebugInput(true); + services.debug().setDebugFly(true); // Floor shape - { - float vars0[8] = {0.5f, 2.5f, 1.0f}; - addShape(DefaultShapes::SINE, vars0, 2, CombinationType::Addition, true, 0); - } + services.shapes().addShape({.shapeId = DefaultShapes::SINE, + .variables = {{Primitives::SineWave::AMPLITUDE, 0.5f}, + {Primitives::SineWave::PERIOD, 2.5f}, + {Primitives::SineWave::SPEED, 1.0f}}, + .material = 2, + .combination = CombinationType::Addition, + .hasCollision = true, + .group = 0}); std::random_device rd; std::mt19937 gen(rd()); @@ -45,71 +49,91 @@ class ShapeCombinatiosScene : public Scene2D float x = distrib(gen) + 15.0f; float y = -2.0f + distribY(gen); - float vars2[8] = {x, y, 3.0f, 5.0f, 1.0f, 0.0f}; // Custom shape - addShape(DefaultShapes::BOX, vars2, 4 + i, CombinationType::Addition, true, 1); + services.shapes().addShape({.shapeId = DefaultShapes::BOX, + .variables = {{Primitives::Box::POS_X, x}, + {Primitives::Box::POS_Y, y}, + {Primitives::Box::SIZE_X, 3.0f}, + {Primitives::Box::SIZE_Y, 5.0f}}, + .material = static_cast(4 + i), + .combination = CombinationType::Addition, + .hasCollision = true, + .group = 1}); } } // Circle - { - float vars[8] = {15.0f, 7.5f, 5.0f}; - addShape(DefaultShapes::CIRCLE, vars, 7, CombinationType::Addition, true, 2); - } + services.shapes().addShape({.shapeId = DefaultShapes::CIRCLE, + .variables = {{Primitives::Circle::POS_X, 15.0f}, + {Primitives::Circle::POS_Y, 7.5f}, + {Primitives::Circle::RADIUS, 5.0f}}, + .material = 7, + .combination = CombinationType::Addition, + .hasCollision = true, + .group = 2}); // Subtract star - { - float vars[8] = {-2.5f + 15.0f, 12.5f, 5.0f, 0.5f, 13.0f, 5.0f}; - addShape(DefaultShapes::STAR, vars, 0, CombinationType::SmoothSubtraction, true, 2); - } + services.shapes().addShape({.shapeId = DefaultShapes::STAR, + .variables = {-2.5f + 15.0f, 12.5f, 5.0f, 0.5f, 13.0f, 5.0f}, + .material = 0, + .combination = CombinationType::SmoothSubtraction, + .hasCollision = true, + .group = 2}); // Cursor circle - { - float vars2[8] = {250.0f, 10.0f, 0.0f, 0.0f, 0.0f, 0.0f}; // Custom shape - m_circle = addShape(DefaultShapes::CIRCLE, vars2, 0, CombinationType::Subtraction, true, - CustomShape::GLOBAL_GROUP); - } - - { - float vars2[8] = {15.0f, 0.0f, 30.0f, 0.0f, 0.0f, 0.0f}; // Custom shape - addShape(DefaultShapes::CIRCLE, vars2, 0, CombinationType::Intersection, true, CustomShape::GLOBAL_GROUP); - } + m_circle = services.shapes().addShape( + {.shapeId = DefaultShapes::CIRCLE, + .variables = {{Primitives::Circle::POS_X, 250.0f}, {Primitives::Circle::POS_Y, 10.0f}}, + .material = 0, + .combination = CombinationType::Subtraction, + .hasCollision = true, + .group = CustomShape::GLOBAL_GROUP}); + + services.shapes().addShape({.shapeId = DefaultShapes::CIRCLE, + .variables = {{Primitives::Circle::POS_X, 15.0f}, + {Primitives::Circle::POS_Y, 0.0f}, + {Primitives::Circle::RADIUS, 30.0f}}, + .material = 0, + .combination = CombinationType::Intersection, + .hasCollision = true, + .group = CustomShape::GLOBAL_GROUP}); for (int i = 0; i < 10; ++i) { - auto ee = ecs.createEntity(); - auto& t = ecs.addComponent(ee); + auto ee = registry.createEntity(); + auto& t = registry.addComponent(ee); t.position = vec3(15.0f, 15.0f, 10.0f); - auto& ui = ecs.addComponent(ee); + auto& ui = registry.addComponent(ee); ui.materialId = 4 + (i % 12); m_uiPoints.push_back(ee); } - ecs.getComponent(m_mainCamera).position = g_cameraPositon; + registry.getComponent(services.render().getCameraEntity()).position = g_cameraPositon; } - void onUpdate(float delta, ECSManager& ecs) override + void onUpdate(Registry& registry, ServiceProvider& services) override { - g_cameraPositon = ecs.getComponent(m_mainCamera).position; + float delta = services.time().deltaTime(); + g_cameraPositon = registry.getComponent(services.render().getCameraEntity()).position; - if (Input::GetKeyDown(Input::Q) || Input::GetGamepadButtonDown(Input::GamepadButton::North)) + if (services.input().getKeyDown(Input::Q) || services.input().getGamepadButtonDown(Input::GamepadButton::North)) { - setSceneComplete(); + services.sceneControl().goToNextScene(); } - auto& cameraTransform = ecs.getComponent(m_mainCamera); - float x = Input::GetMouseX(); - float y = Input::GetMouseY(); + auto& cameraTransform = registry.getComponent(services.render().getCameraEntity()); + float x = services.input().getMouseX(); + float y = services.input().getMouseY(); // Transform mouse coordinates to world space vec2 mousePositionInWorld = ECS::Camera::screenPositionToWorldPosition2D(cameraTransform, vec2(x, y)); - if (Input::GetMouseButtonDown(Input::RightClick)) + if (services.input().getMouseButtonDown(Input::RightClick)) { m_initialMousePositionInWorld = mousePositionInWorld; } - else if (Input::GetGamepadButtonDown(Input::GamepadButton::LeftShoulder)) + else if (services.input().getGamepadButtonDown(Input::GamepadButton::LeftShoulder)) { float halfWidth = Display::width / 2.0f; float halfHeight = Display::height / 2.0f; @@ -118,12 +142,12 @@ class ShapeCombinatiosScene : public Scene2D ECS::Camera::screenPositionToWorldPosition2D(cameraTransform, vec2(halfWidth, halfHeight)); } - if (Input::GetMouseButton(Input::RightClick)) + if (services.input().getMouseButton(Input::RightClick)) { vec2 v = mousePositionInWorld - m_initialMousePositionInWorld; m_circleRadious = (std::min)(10.0f, length(v)); } - else if (Input::GetGamepadButton(Input::GamepadButton::LeftShoulder)) + else if (services.input().getGamepadButton(Input::GamepadButton::LeftShoulder)) { m_circleRadious = (std::min)(10.0f, m_circleRadious + (10.0f * delta)); } @@ -134,12 +158,12 @@ class ShapeCombinatiosScene : public Scene2D } { - CustomShape& cs = ecs.getComponent(m_circle); + CustomShape& cs = registry.getComponent(m_circle); cs.parameters[0] = m_initialMousePositionInWorld.x; cs.parameters[1] = m_circleRadious <= 0.0f ? -1000.0f : m_initialMousePositionInWorld.y; cs.parameters[2] = m_circleRadious; - ecs.setComponentDirty(cs); + registry.setComponentDirty(cs); } float volume = AudioEngine::getInstance().getAudioData().currentVolume; @@ -152,12 +176,12 @@ class ShapeCombinatiosScene : public Scene2D for (int i = 0; i < m_uiPoints.size(); i++) { // Calculate angle: Time moves them, 'i' spreads them out - float angle = (getTime() * speed) + (i * spacing); + float angle = (services.time().time() * speed) + (i * spacing); float x = center.x + std::cos(angle) * radius; float y = center.y + std::sin(angle) * radius; - auto& t = ecs.getComponent(m_uiPoints[i]); + auto& t = registry.getComponent(m_uiPoints[i]); t.position = vec3(x, y, 0.0f); } } diff --git a/examples/sample-scenes/include/TextScene.h b/examples/sample-scenes/include/TextScene.h index 7774a40..7583f81 100644 --- a/examples/sample-scenes/include/TextScene.h +++ b/examples/sample-scenes/include/TextScene.h @@ -24,25 +24,28 @@ class TextScene : public Scene2D int m_counter = 0; int m_lastResolutionHash = 0; - void onStart(ECSManager& ecs) override + void onStart(Registry& registry, ServiceProvider& services) override { - m_debugInput = true; - m_debugFly = true; - - { - float vars[8] = {15.0f, -50.0f, 250.0f, 50.0f}; - addShape(DefaultShapes::BOX, vars, DisplaySettings::LightGray, CombinationType::SmoothAddition); - } - - ecs.getComponent(m_mainCamera).position = g_cameraPositon; + services.debug().setDebugInput(true); + services.debug().setDebugFly(true); + + services.shapes().addShape({.shapeId = DefaultShapes::BOX, + .variables = {{Primitives::Box::POS_X, 15.0f}, + {Primitives::Box::POS_Y, -50.0f}, + {Primitives::Box::SIZE_X, 250.0f}, + {Primitives::Box::SIZE_Y, 50.0f}}, + .material = static_cast(DisplaySettings::LightGray), + .combination = CombinationType::SmoothAddition}); + + registry.getComponent(services.render().getCameraEntity()).position = g_cameraPositon; m_lastResolutionHash = Display::width + Display::height; { - m_worldText = ecs.createEntity(); - auto& t = ecs.addComponent(m_worldText); + m_worldText = registry.createEntity(); + auto& t = registry.addComponent(m_worldText); t.position = vec3(15.0f, 12.0f, 0.0f); - auto& text = ecs.addComponent(m_worldText); + auto& text = registry.addComponent(m_worldText); text.text = "WORLD TEXT"; text.material = DisplaySettings::Cyan; text.horizontalAlignment = TextRenderer::HorizontalAlignment::Center; @@ -50,11 +53,11 @@ class TextScene : public Scene2D } { - m_worldMouseText = ecs.createEntity(); - auto& t = ecs.addComponent(m_worldMouseText); + m_worldMouseText = registry.createEntity(); + auto& t = registry.addComponent(m_worldMouseText); t.position = vec3(0.0f, 0.0f, 0.0f); - auto& text = ecs.addComponent(m_worldMouseText); + auto& text = registry.addComponent(m_worldMouseText); text.text = "WORLD MOUSE"; text.material = DisplaySettings::LightBlue; text.horizontalAlignment = TextRenderer::HorizontalAlignment::Left; @@ -62,11 +65,11 @@ class TextScene : public Scene2D } { - m_counterText = ecs.createEntity(); - auto& t = ecs.addComponent(m_counterText); + m_counterText = registry.createEntity(); + auto& t = registry.addComponent(m_counterText); t.position = vec3(static_cast(Display::width) * 0.5f, 50.0f, 0.0f); - auto& text = ecs.addComponent(m_counterText); + auto& text = registry.addComponent(m_counterText); text.text = "0"; text.material = DisplaySettings::LightGreen; text.horizontalAlignment = TextRenderer::HorizontalAlignment::Left; @@ -74,11 +77,11 @@ class TextScene : public Scene2D } { - m_centerText = ecs.createEntity(); - auto& t = ecs.addComponent(m_centerText); + m_centerText = registry.createEntity(); + auto& t = registry.addComponent(m_centerText); t.position = vec3(static_cast(Display::width) * 0.5f, 20.0f, 0.0f); - auto& text = ecs.addComponent(m_centerText); + auto& text = registry.addComponent(m_centerText); text.text = "CENTERED"; text.material = DisplaySettings::Yellow; text.horizontalAlignment = TextRenderer::HorizontalAlignment::Center; @@ -86,11 +89,11 @@ class TextScene : public Scene2D } { - m_leftText = ecs.createEntity(); - auto& t = ecs.addComponent(m_leftText); + m_leftText = registry.createEntity(); + auto& t = registry.addComponent(m_leftText); t.position = vec3(10.0f, 20.0f, 0.0f); - auto& text = ecs.addComponent(m_leftText); + auto& text = registry.addComponent(m_leftText); text.text = "LEFT"; text.material = DisplaySettings::Orange; text.horizontalAlignment = TextRenderer::HorizontalAlignment::Left; @@ -98,11 +101,11 @@ class TextScene : public Scene2D } { - m_rightText = ecs.createEntity(); - auto& t = ecs.addComponent(m_rightText); + m_rightText = registry.createEntity(); + auto& t = registry.addComponent(m_rightText); t.position = vec3(static_cast(Display::width) - 10.0f, 20.0f, 0.0f); - auto& text = ecs.addComponent(m_rightText); + auto& text = registry.addComponent(m_rightText); text.text = "RIGHT"; text.material = DisplaySettings::Magenta; text.horizontalAlignment = TextRenderer::HorizontalAlignment::Right; @@ -110,12 +113,12 @@ class TextScene : public Scene2D } { - m_nonResponsiveText = ecs.createEntity(); - auto& t = ecs.addComponent(m_nonResponsiveText); + m_nonResponsiveText = registry.createEntity(); + auto& t = registry.addComponent(m_nonResponsiveText); t.position = vec3(static_cast(Display::width) - 10.0f, static_cast(Display::height) - 10.0f, 0.0f); - auto& text = ecs.addComponent(m_nonResponsiveText); + auto& text = registry.addComponent(m_nonResponsiveText); text.text = "STUCK"; text.material = DisplaySettings::Red; text.horizontalAlignment = TextRenderer::HorizontalAlignment::Right; @@ -123,33 +126,33 @@ class TextScene : public Scene2D } } - void onUpdate(float delta, ECSManager& ecs) override + void onUpdate(Registry& registry, ServiceProvider& services) override { - if (Input::GetKeyDown(Input::Q) || Input::GetGamepadButtonDown(Input::GamepadButton::North)) + if (services.input().getKeyDown(Input::Q) || services.input().getGamepadButtonDown(Input::GamepadButton::North)) { - setSceneComplete(); + services.sceneControl().goToNextScene(); } m_counter++; { - auto& text = ecs.getComponent(m_counterText); + auto& text = registry.getComponent(m_counterText); text.text = std::to_string(m_counter); - ecs.setComponentDirty(text); + registry.setComponentDirty(text); - auto& t = ecs.getComponent(m_counterText); - t.position.x = Input::GetMouseX() + 20.0f; - t.position.y = Input::GetMouseY() + 10.0f; + auto& t = registry.getComponent(m_counterText); + t.position.x = services.input().getMouseX() + 20.0f; + t.position.y = services.input().getMouseY() + 10.0f; } { - auto& cameraTransform = ecs.getComponent(m_mainCamera); - vec2 mouseScreen = vec2(Input::GetMouseX() + 20.0f, Input::GetMouseY() - 10.0f); + auto& cameraTransform = registry.getComponent(services.render().getCameraEntity()); + vec2 mouseScreen = vec2(services.input().getMouseX() + 20.0f, services.input().getMouseY() - 10.0f); vec2 mouseWorld = ECS::Camera::screenPositionToWorldPosition2D(cameraTransform, mouseScreen); - auto& t = ecs.getComponent(m_worldMouseText); + auto& t = registry.getComponent(m_worldMouseText); t.position.x = mouseWorld.x; t.position.y = mouseWorld.y; - ecs.setComponentDirty(t); + registry.setComponentDirty(t); } int hash = Display::width + Display::height; @@ -159,9 +162,9 @@ class TextScene : public Scene2D float halfW = static_cast(Display::width) * 0.5f; - ecs.getComponent(m_counterText).position = vec3(halfW, 20.0f, 0.0f); - ecs.getComponent(m_centerText).position = vec3(halfW, 40.0f, 0.0f); - ecs.getComponent(m_rightText).position = + registry.getComponent(m_counterText).position = vec3(halfW, 20.0f, 0.0f); + registry.getComponent(m_centerText).position = vec3(halfW, 40.0f, 0.0f); + registry.getComponent(m_rightText).position = vec3(static_cast(Display::width) - 10.0f, 20.0f, 0.0f); } } diff --git a/examples/sample-scenes/include/WalkScene.h b/examples/sample-scenes/include/WalkScene.h index d0c76fa..dfdefe5 100644 --- a/examples/sample-scenes/include/WalkScene.h +++ b/examples/sample-scenes/include/WalkScene.h @@ -30,66 +30,73 @@ class WalkScene : public Scene2D Entity m_head; // Inherited via Scene - void onStart(ECSManager& ecs) override + void onStart(Registry& registry, ServiceProvider& services) override { - m_debugInput = true; - m_debugFly = true; + services.debug().setDebugInput(true); + services.debug().setDebugFly(true); - m_background.type = BackgroundType::Sky; - m_background.primaryColor = vec4(0.2f, 0.55f, 0.9f, 1.0f); - m_background.secondaryColor = vec4(0.4f, 0.75f, 0.85f, 1.0f); - m_background.scale = 0.2f; + auto& background = services.render().getBackground(); + background.type = BackgroundType::Sky; + background.primaryColor = vec4(0.2f, 0.55f, 0.9f, 1.0f); + background.secondaryColor = vec4(0.4f, 0.75f, 0.85f, 1.0f); + background.scale = 0.2f; - auto tags = loadWeirdFile(ASSETS_PATH "man.weird"); + auto tags = services.serialization().loadWeirdFile(services.resources().assetPath("man.weird")); - Entity firstCreated = static_cast(ecs.getEntityCount()); + Entity firstCreated = static_cast(registry.getEntityCount()); - Entity lastCreated = static_cast(ecs.getEntityCount()); + Entity lastCreated = static_cast(registry.getEntityCount()); for (Entity e = 0; e < (lastCreated - firstCreated); e++) { - auto& t = ecs.getComponent(firstCreated + e); + auto& t = registry.getComponent(firstCreated + e); t.position += vec3(-10.0f, 0.0f, 0.0f); } Entity leftFootEntity = tags["foot_left"]; - ecs.addComponent(leftFootEntity); + registry.addComponent(leftFootEntity); Entity rightFootEntity = tags["foot_right"]; - ecs.addComponent(rightFootEntity); + registry.addComponent(rightFootEntity); m_head = tags["head"]; - float boundsVars2[8]{0.0f, -24.0f, 200.0f, 20.0f}; - Entity inside = - addShape(DefaultShapes::BOX, boundsVars2, DisplaySettings::LightGreen, CombinationType::Addition); + services.shapes().addShape({.shapeId = DefaultShapes::BOX, + .variables = {{Primitives::Box::POS_X, 0.0f}, + {Primitives::Box::POS_Y, -24.0f}, + {Primitives::Box::SIZE_X, 200.0f}, + {Primitives::Box::SIZE_Y, 20.0f}}, + .material = static_cast(DisplaySettings::LightGreen), + .combination = CombinationType::Addition}); - ecs.getComponent(m_mainCamera).position = g_cameraPositon; + registry.getComponent(services.render().getCameraEntity()).position = g_cameraPositon; - Entity globalSettingsEnt = ecs.createEntity(); - auto& settings = ecs.addComponent(globalSettingsEnt); + Entity globalSettingsEnt = registry.createEntity(); + auto& settings = registry.addComponent(globalSettingsEnt); settings.gravity = -10.0f; - ecs.setComponentDirty(settings); + registry.setComponentDirty(settings); } - void onUpdate(float delta, ECSManager& ecs) override + void onUpdate(Registry& registry, ServiceProvider& services) override { - g_cameraPositon = ecs.getComponent(m_mainCamera).position; + float delta = services.time().deltaTime(); - if (Input::GetKeyDown(Input::Q) || Input::GetGamepadButtonDown(Input::GamepadButton::North)) + g_cameraPositon = registry.getComponent(services.render().getCameraEntity()).position; + + if (services.input().getKeyDown(Input::Q) || services.input().getGamepadButtonDown(Input::GamepadButton::North)) { - setSceneComplete(); + services.sceneControl().goToNextScene(); } - updatePhysics(delta, ecs); + updatePhysics(delta, registry); } int m_currentFoot = 0; bool m_feetTouching = false; - void updatePhysics(float delta, ECSManager& ecs) + void updatePhysics(float delta, Registry& registry) { - auto componentArray = ecs.getComponentArray(); - auto rigidBodies = ecs.getComponentArray(); + auto componentArray = registry.getComponentArray(); + auto rigidBodies = registry.getComponentArray(); for (size_t i = 0; i < componentArray->getSize(); i++) { @@ -101,28 +108,29 @@ class WalkScene : public Scene2D if (foot.onFloor) { rb.isFixed = true; - ecs.setComponentDirty(rb); + registry.setComponentDirty(rb); } continue; } - auto& headRB = ecs.getComponent(m_head); + auto& headRB = registry.getComponent(m_head); headRB.pendingImpulseForce += vec2(0.0f, 1.0f); rb.isFixed = false; - ecs.setComponentDirty(rb); + registry.setComponentDirty(rb); // Start step if (!foot.stepStarted) { if (foot.onFloor) { - foot.initialPos = vec2(ecs.getComponent(componentArray->getEntityAtIdx(i)).position); + foot.initialPos = + vec2(registry.getComponent(componentArray->getEntityAtIdx(i)).position); foot.stepStarted = true; foot.t = 0.0f; // rb.position = foot.initialPos + vec2(0.0f, 0.1f); rb.isFixed = false; - ecs.setComponentDirty(rb); + registry.setComponentDirty(rb); } } else @@ -167,23 +175,25 @@ class WalkScene : public Scene2D m_feetTouching = false; } - void onEntityCollision(ECSManager& ecs, WeirdEngine::EntityCollisionEvent& event) override + void onEntityCollision(Registry& registry, ServiceProvider& services, + WeirdEngine::EntityCollisionEvent& event) override { Entity entityA = event.entityA; Entity entityB = event.entityB; - if (ecs.hasComponent(entityA) && ecs.hasComponent(entityB)) + if (registry.hasComponent(entityA) && registry.hasComponent(entityB)) { m_feetTouching = true; } } - void onEntityShapeCollision(ECSManager& ecs, WeirdEngine::EntityShapeCollisionEvent& event) override + void onEntityShapeCollision(Registry& registry, ServiceProvider& services, + WeirdEngine::EntityShapeCollisionEvent& event) override { Entity entity = event.entity; - if (ecs.hasComponent(entity)) + if (registry.hasComponent(entity)) { - auto& foot = ecs.getComponent(entity); + auto& foot = registry.getComponent(entity); if (event.raw.state == CollisionState::START) { foot.onFloor = true; diff --git a/examples/sample-scenes/src/main.cpp b/examples/sample-scenes/src/main.cpp index 19f07eb..3d7b4ba 100644 --- a/examples/sample-scenes/src/main.cpp +++ b/examples/sample-scenes/src/main.cpp @@ -8,6 +8,7 @@ #include "LifeScene.h" #include "MouseCollisionScene.h" #include "RopeScene.h" +#include "ServiceShowcaseScene.h" #include "ShapesCombinations.h" #include "TextScene.h" #include "WalkScene.h" @@ -27,6 +28,7 @@ int main(int argc, char* argv[]) sceneManager.registerScene("life"); sceneManager.registerScene("cursor-collision"); sceneManager.registerScene("destroy-test"); + // sceneManager.registerScene("service-showcase"); // sceneManager.registerScene("collision-handling"); // sceneManager.registerScene("image"); diff --git a/include/weird-engine.h b/include/weird-engine.h index 0bcc52c..0c34245 100644 --- a/include/weird-engine.h +++ b/include/weird-engine.h @@ -165,6 +165,7 @@ namespace WeirdEngine else { Input::handleEvent(event); + ctx.renderer.handleEvent(event); } } } @@ -248,9 +249,11 @@ namespace WeirdEngine #endif } // namespace Detail - void start(SceneManager& sceneManager, DisplaySettings displaySettings = {}, PhysicsSettings physicsSettings = {}, - AudioSettings audioSettings = {}, int argc = 0, char** argv = nullptr) + inline void start(SceneManager& sceneManager, DisplaySettings displaySettings = {}, + PhysicsSettings physicsSettings = {}, AudioSettings audioSettings = {}, int argc = 0, + char** argv = nullptr, const std::string& assetsPath = ASSETS_PATH) { + sceneManager.setAssetsPath(assetsPath); WeirdEngine::Logger::log("Starting Weird Engine..."); std::string startupScene; diff --git a/include/weird-engine/Assert.h b/include/weird-engine/Assert.h new file mode 100644 index 0000000..d60ed1b --- /dev/null +++ b/include/weird-engine/Assert.h @@ -0,0 +1,31 @@ +#pragma once + +// Weird Engine runtime assertions. +// +// Enabled when the WEIRD_ENABLE_ASSERTS compile definition is set (done by the +// engine's CMake for Debug and RelWithDebInfo builds by default). These remain +// active in RelWithDebInfo even though CMake defines NDEBUG there, which is +// why this is not a wrapper around the standard assert(). +// +// When disabled, the macro compiles away entirely and imposes zero overhead. +#if defined(WEIRD_ENABLE_ASSERTS) + +#include +#include + +#define WEIRD_ASSERT(condition, message) \ + do \ + { \ + if (!(condition)) \ + { \ + std::fprintf(stderr, "WEIRD ASSERT FAILED: %s\n %s\n at %s:%d\n", #condition, message, __FILE__, \ + __LINE__); \ + std::abort(); \ + } \ + } while (false) + +#else + +#define WEIRD_ASSERT(condition, message) ((void)0) + +#endif diff --git a/include/weird-engine/Scene.h b/include/weird-engine/Scene.h index 49d28af..ed16acf 100644 --- a/include/weird-engine/Scene.h +++ b/include/weird-engine/Scene.h @@ -1,6 +1,6 @@ #pragma once -#include "ecs/ECS.h" +#include "ecs/Registry.h" #include "ResourceManager.h" #include "weird-engine/systems/SDFRenderSystem.h" @@ -12,9 +12,11 @@ #include "weird-engine/Background.h" #include "weird-engine/Material3D.h" +#include "weird-engine/services/ServiceProvider.h" #include "weird-physics/PhysicsSettings.h" #include "weird-physics/Simulation2D.h" +#include #include #include #include @@ -25,56 +27,182 @@ namespace WeirdEngine struct EntityCollisionEvent { - CollisionEvent& raw; + // Raw event data from the physics thread. Read-only: the physics + // response has already been applied by the time this is dispatched. + const PhysicsCollisionEvent& raw; Entity entityA; Entity entityB; }; struct EntityShapeCollisionEvent { - ShapeCollisionEvent& raw; + // Raw event data from the physics thread. Read-only: the physics + // response has already been applied by the time this is dispatched. + const PhysicsShapeCollisionEvent& raw; Entity entity; }; - constexpr int SOUND_QUEUE_SIZE = 16; - // Forward declaration – full definition in SceneSerializer.h class SceneSerializer; + class SceneManager; + + // ---- System Signatures ---- + using CoreSystem = std::function; + using EntityCollisionSystem = std::function; + using EntityShapeCollisionSystem = std::function; + + namespace WeirdRenderer + { + class AudioEngine; + class Renderer; + class MeshRenderPipeline; + } // namespace WeirdRenderer + + namespace Detail + { + struct RuntimeContext; + void runFrame(RuntimeContext& ctx); + } // namespace Detail class Scene { + // Serialization and the service provider reach into the scene's + // private state (storage lives here; the provider is a facade). + friend class SceneManager; friend class SceneSerializer; + friend class ServiceProvider; + friend struct SerializationService; + + friend class WeirdRenderer::AudioEngine; + friend class WeirdRenderer::Renderer; + friend void Detail::runFrame(Detail::RuntimeContext& ctx); public: + // ---- Types /// Map from tag name (std::string) to the entity that owns it. - using TagMap = std::unordered_map; + using TagMap = ::WeirdEngine::TagMap; + + using RenderMode = ::WeirdEngine::RenderMode; + + using RaymarchResult = ::WeirdEngine::RaymarchResult; - Scene(); virtual ~Scene(); + + // ---- Global SDF registry (engine-level, shared across scenes) + static ShapeId registerDefaultSDF(std::shared_ptr sdf); + static const std::vector>& getGlobalSDFs(); + + // ---- System Dispatcher (Register systems to be called automatically) + void addCreateSystem(CoreSystem system) + { + m_createSystems.push_back(std::move(system)); + } + void addStartSystem(CoreSystem system) + { + m_startSystems.push_back(std::move(system)); + } + void addUpdateSystem(CoreSystem system) + { + m_updateSystems.push_back(std::move(system)); + } + void addDestroySystem(CoreSystem system) + { + m_destroySystems.push_back(std::move(system)); + } + void addImGuiRenderSystem(CoreSystem system) + { + m_imguiSystems.push_back(std::move(system)); + } + void addEntityCollisionSystem(EntityCollisionSystem system) + { + m_entityCollisionSystems.push_back(std::move(system)); + } + void addEntityShapeCollisionSystem(EntityShapeCollisionSystem system) + { + m_entityShapeCollisionSystems.push_back(std::move(system)); + } + + protected: + // Internal constructors: sets the render mode for Scene2D/Scene3D/SceneBoth. + Scene(); + Scene(RenderMode mode); + + // ---- Lifecycle callbacks + virtual void onCreate(Registry& registry, ServiceProvider& services) {}; + virtual void onStart(Registry& registry, ServiceProvider& services) {} + virtual void onUpdate(Registry& registry, ServiceProvider& services) {}; + virtual void onDestroy(Registry& registry, ServiceProvider& services) {}; + virtual void onImGuiRender(Registry& registry, ServiceProvider& services) {}; + virtual void onRender(Registry& registry, ServiceProvider& services, + WeirdRenderer::RenderTarget& renderTarget) {}; + + // ---- Main thread collision callbacks (onEntity* family). Fire after + // the physics response has been applied; the events are read-only. + // m_registry is safe to use here (the physics thread only ever touches + // Simulation2D internals). See the onPhysics* callbacks below for the + // pre-response, mutable equivalents. + virtual void onEntityCollision(Registry& registry, ServiceProvider& services, + WeirdEngine::EntityCollisionEvent& event) {}; + virtual void onEntityShapeCollision(Registry& registry, ServiceProvider& services, + WeirdEngine::EntityShapeCollisionEvent& event) {}; + + // ---- Physics thread callbacks (onPhysics* family). Fire on the + // physics thread mid-step; no ECS access here. The collision events + // are pre-response and mutable: tune the event fields (friction, + // absorption), queue impulses, or call fix() to alter the solver's + // behavior. Everything else belongs in the onEntity* main-thread + // callbacks above (post-response, read-only). + virtual void onPhysicsStep(Simulation2D& simulation) {}; + virtual void onPhysicsRigidBodyCollision(Simulation2D& simulation, WeirdEngine::PhysicsCollisionEvent& event) { + }; + virtual void onPhysicsShapeCollision(Simulation2D& simulation, WeirdEngine::PhysicsShapeCollisionEvent& event) { + }; + + private: + // ---- Lifecycle (engine-driven) void start(); + void update(double delta, double time); + void destroy() + { + onDestroy(m_registry, m_services); + for (auto& sys : m_destroySystems) + { + sys(m_registry, m_services); + } + } + // ---- Rendering pipeline (engine-driven) void renderExtra(WeirdRenderer::RenderTarget& renderTarget); - void update2DWorldShader(WeirdRenderer::Shader& shader); void update3DWorldShader(WeirdRenderer::Shader& shader); void updateUIShader(WeirdRenderer::Shader& shader); void forceShaderRefresh(); - void update(double delta, double time); - void get2DShapesData(vec4*& data, uint32_t& size, uint32_t& customShapeCount); void get3DShapesData(vec4*& data, uint32_t& size, uint32_t& customShapeCount); void getUIData(vec4*& uiData, uint32_t& size, uint32_t& customShapeCount); + void renderImGui(); + void renderPhysicsStatsUI(); + // ---- Scene state access (engine-driven) WeirdRenderer::Camera& getCamera(); - std::vector& getLigths(); + std::vector& getLights(); + const std::vector& getDrawQueue() const; + AudioRingBuffer& getAudioQueue(); + float getFrictionSound(); - Simulation2D& getSimulation2D() + BackgroundParams& getBackground() { - return m_simulation2D; + return m_background; } - Material3D& createMaterial(); + const BackgroundParams& getBackground() const + { + return m_background; + } + RenderMode getRenderMode() const; + float getTime(); + Material3D& createMaterial(); Material3D& getMaterial(int index) { return m_materials[index]; @@ -84,30 +212,7 @@ namespace WeirdEngine return m_materials; } - BackgroundParams& getBackground() - { - return m_background; - } - const BackgroundParams& getBackground() const - { - return m_background; - } - - float getTime(); - - enum class RenderMode - { - RayMarching3D, - RayMarching2D, - RayMarchingBoth - }; - - RenderMode getRenderMode() const; - - float getFrictionSound(); - const std::vector& getDrawQueue() const; - AudioRingBuffer& getAudioQueue(); - + // ---- Scene control bool isSceneComplete() const { return m_isSceneComplete; @@ -117,171 +222,86 @@ namespace WeirdEngine return m_nextScene; }; - static ShapeId registerDefaultSDF(std::shared_ptr sdf); - static const std::vector>& getGlobalSDFs(); - - ShapeId registerSDF(std::shared_ptr sdf); - // Set the path to a .weird file to load when the scene starts void setSceneFilePath(const std::string& path) { m_sceneFilePath = path; } - struct RaymarchResult - { - float distance; - Entity entity; - }; - - // Physics queries - RaymarchResult raymarch(glm::vec2 origin, glm::vec2 direction, float epsilon = 0.001f, - float maxDistance = 150.0f); - - void renderImGui(); - void renderPhysicsStatsUI(); - - protected: - virtual void onCreate() {}; - virtual void onStart(ECSManager& ecs, const TagMap& tags) - { - onStart(ecs); - } - virtual void onStart(ECSManager& ecs) {} - virtual void onUpdate(float delta, ECSManager& ecs) = 0; - virtual void onRender(WeirdRenderer::RenderTarget& renderTarget) {}; - virtual void onImGuiRender() {}; - - // Physics thread callbacks (No m_ecs access recommended!) - virtual void onPhysicsStep(Simulation2D& simulation) {}; - virtual void onCollision(Simulation2D& simulation, WeirdEngine::CollisionEvent& event) {}; - virtual void onShapeCollision(Simulation2D& simulation, WeirdEngine::ShapeCollisionEvent& event) {}; - - // Main thread callbacks (m_ecs is safe to use here) // ARE YOU SURE??? - virtual void onEntityCollision(ECSManager& ecs, WeirdEngine::EntityCollisionEvent& event) {}; - virtual void onEntityShapeCollision(ECSManager& ecs, WeirdEngine::EntityShapeCollisionEvent& event) {}; - virtual void onDestroy() {}; + // ---- Internal helpers + static void handlePhysicsStep(void* userData); + static void handleCollision(PhysicsCollisionEvent& event, void* userData); + static void handleShapeCollision(PhysicsShapeCollisionEvent& event, void* userData); + // Load scene state from a .weird JSON file + void loadFromWeirdFile(const std::string& path); + void playSound(const WeirdRenderer::SimpleAudioRequest& audio); + // Resolve a physics SimulationID to the owning entity. + Entity getEntityForSimulationId(SimulationID simulationId, + std::shared_ptr> rigidBodies); - void setSceneComplete(std::string nextScene = "") - { - m_isSceneComplete = true; - m_nextScene = nextScene; - }; + ServiceProvider m_services; + // ---- Shared state (managed via ServiceProvider) Entity m_mainCamera; ResourceManager m_resourceManager; - - Material3D m_materials[16]; - uint16_t m_materialCount = 0; - std::vector> m_sdfs; - - Entity addShape(ShapeId shapeId, float* variables, uint16_t material, - CombinationType combination = CombinationType::Addition, bool hasCollision = true, - int group = 0); - Entity addShape(ShapeId shapeId, float* variables, const Material3D& material, - CombinationType combination = CombinationType::Addition, bool hasCollision = true, - int group = 0) - { - return addShape(shapeId, variables, material.id, combination, hasCollision, group); - } - - Entity addUIShape(ShapeId shapeId, float* variables, uint16_t material, - CombinationType combination = CombinationType::Addition, int group = 0); - Entity addUIShape(ShapeId shapeId, float* variables, const Material3D& material, - CombinationType combination = CombinationType::Addition, int group = 0) - { - return addUIShape(shapeId, variables, material.id, combination, group); - } - UIShape& addUIShape(ShapeId shapeId, float* variables, Entity& entity, int group = 0); - - void lookAt(Entity entity); - - // Entities in this set will be skipped during scene serialization - std::unordered_set m_serializationBlacklist; - void blacklistEntity(Entity e) - { - m_serializationBlacklist.insert(e); - } - - // Tag management - // Assign a unique tag to an entity. If the tag is already owned by - // another entity, it is moved to this one. An empty name is treated - // as a removal request (equivalent to calling removeTag). - void tag(Entity entity, const std::string& name); - // Remove any tag currently assigned to an entity. - void removeTag(Entity entity); - // Return the tag of an entity, or "" if none. - std::string getEntityTag(Entity entity) const; - // Return the entity that owns a tag, or MAX_ENTITIES if none. - Entity getEntityByTag(const std::string& name) const; - - SDFRenderSystemContext m_2DWorldRenderContext; - SDFRenderSystemContext m_3DWorldRenderContext; - SDFRenderSystemContext m_UIRenderContext; - // Resolve a physics SimulationID to the owning entity. - Entity getEntityForSimulationId(SimulationID simulationId, - std::shared_ptr> rigidBodies); - bool m_debugFly = false; bool m_debugInput = false; - RenderMode m_renderMode = RenderMode::RayMarching2D; - - void playSound(const WeirdRenderer::SimpleAudioRequest& audio); - - // Save the current scene state to a .weird JSON file - void saveScene(const std::string& filename); - - // Dynamically load a .weird file and add its contents to the scene. - // If blacklistEntities is true, all entities created by the load will be - // excluded from future scene serialization. - // Returns a map of tag names to their corresponding entities. - TagMap loadWeirdFile(const std::string& path, bool blacklistEntities = false); - - // Path to a .weird file to load when the scene starts (set via setSceneFilePath or registerScene) - std::string m_sceneFilePath; - - BackgroundParams m_background; - - private: - // Load scene state from a .weird JSON file - void loadFromWeirdFile(const std::string& path); - + // ---- Simulation + Registry m_registry; + Simulation2D m_simulation2D; bool m_runSimulationInThread; + bool m_simulationIsPaused = false; + // ---- Collision queues (physics thread pushes, main thread drains) + std::mutex m_collisionQueueMutex; + std::vector m_queuedCollisions; + std::vector m_queuedShapeCollisions; + + // ---- Audio, draw queue, lights AudioRingBuffer m_audioQueue; float m_frictionSoundLevel{0.0f}; std::atomic m_frictionSoundLevelRead{0.0f}; - std::vector m_drawQueue; std::vector m_lights; - static void handlePhysicsStep(void* userData); - static void handleCollision(CollisionEvent& event, void* userData); - static void handleShapeCollision(ShapeCollisionEvent& event, void* userData); + // ---- Serialization & visuals + std::unordered_set m_serializationBlacklist; + std::string m_sceneFilePath; + BackgroundParams m_background; + Material3D m_materials[16]; + uint16_t m_materialCount = 0; + SDFRenderSystemContext m_2DWorldRenderContext; + SDFRenderSystemContext m_3DWorldRenderContext; + SDFRenderSystemContext m_UIRenderContext; + RenderMode m_renderMode = RenderMode::RayMarching2D; + // ---- Scene control state std::string m_nextScene; bool m_isSceneComplete = false; - ECSManager m_ecs; - Simulation2D m_simulation2D; - bool m_simulationIsPaused = false; - - std::mutex m_collisionQueueMutex; - std::vector m_queuedCollisions; - std::vector m_queuedShapeCollisions; - - // Entity tag storage (bidirectional maps kept in sync) + // ---- Entity tag storage (bidirectional maps kept in sync) TagMap m_tagToEntity; std::unordered_map m_entityToTag; + + // ---- Registered Systems + std::vector m_createSystems; + std::vector m_startSystems; + std::vector m_updateSystems; + std::vector m_destroySystems; + std::vector m_imguiSystems; + std::vector m_entityCollisionSystems; + std::vector m_entityShapeCollisionSystems; + + float m_lastDelta = 0.0f; }; class Scene2D : public Scene { public: Scene2D() + : Scene(RenderMode::RayMarching2D) { - m_renderMode = RenderMode::RayMarching2D; } }; @@ -289,8 +309,8 @@ namespace WeirdEngine { public: Scene3D() + : Scene(RenderMode::RayMarching3D) { - m_renderMode = RenderMode::RayMarching3D; } }; @@ -298,8 +318,8 @@ namespace WeirdEngine { public: SceneBoth() + : Scene(RenderMode::RayMarchingBoth) { - m_renderMode = RenderMode::RayMarchingBoth; } }; } // namespace WeirdEngine diff --git a/include/weird-engine/SceneManager.h b/include/weird-engine/SceneManager.h index 6c555ec..e9bb73e 100644 --- a/include/weird-engine/SceneManager.h +++ b/include/weird-engine/SceneManager.h @@ -16,8 +16,6 @@ namespace WeirdEngine public: ~SceneManager(); - void loadProject(std::string projectDir); - Scene* getCurrentScene(); void setPhysicsSettings(const PhysicsSettings& settings) @@ -29,6 +27,11 @@ namespace WeirdEngine return m_physicsSettings; } + void setAssetsPath(const std::string& path) + { + m_assetsPath = path; + } + static SceneManager& getInstance() { static SceneManager _instance; @@ -50,18 +53,14 @@ namespace WeirdEngine int currentSceneIdx = 0; int targetSceneIdx = 0; PhysicsSettings m_physicsSettings; + std::string m_assetsPath; }; - // ChatGPT: Template method declarations and definitions are usually placed in header files. - // The header files are then included in the source files that use the templates. - // If the template is only in the static library and the client code doesn’t see its full definition, it won't be - // able to use it. That's why this is here... template void SceneManager::registerScene(const std::string& sceneName, const std::string& sceneFilePath) { static_assert(std::is_base_of::value, "T must derive from Scene"); - // TODO: check ECS for a similar names.push_back(sceneName); sceneFactories[sceneName] = [this, sceneFilePath]() { diff --git a/include/weird-engine/ecs/ECS.h b/include/weird-engine/ecs/Registry.h similarity index 97% rename from include/weird-engine/ecs/ECS.h rename to include/weird-engine/ecs/Registry.h index 72964bd..9854399 100644 --- a/include/weird-engine/ecs/ECS.h +++ b/include/weird-engine/ecs/Registry.h @@ -37,8 +37,10 @@ namespace WeirdEngine } } // namespace internal - // ECSManager Manager - class ECSManager + // Entity + component storage. Systems are not managed here: scheduling + // lives on Scene, which dispatches them with a Registry& and + // ServiceProvider&. + class Registry { public: Entity createEntity() @@ -311,6 +313,7 @@ namespace WeirdEngine #include "weird-renderer/components/Camera.h" #include "weird-renderer/components/CustomShape.h" #include "weird-renderer/components/InstancedMeshRenderer.h" +#include "weird-renderer/components/LightComponent.h" #include "weird-renderer/components/MeshRenderer.h" #include "weird-renderer/components/SDFRenderer.h" #include "weird-renderer/components/TextRenderer.h" diff --git a/include/weird-engine/math/MathExpressions.h b/include/weird-engine/math/MathExpressions.h index eb7befb..6277ba9 100644 --- a/include/weird-engine/math/MathExpressions.h +++ b/include/weird-engine/math/MathExpressions.h @@ -215,8 +215,8 @@ namespace WeirdEngine } }; - // Substract - struct Substraction : TwoFloatOperation + // Subtraction + struct Subtraction : TwoFloatOperation { using TwoFloatOperation::TwoFloatOperation; @@ -233,6 +233,9 @@ namespace WeirdEngine } }; + // Deprecated alias of Subtraction + using Substraction = Subtraction; + // Multiplication struct Multiplication : TwoFloatOperation { diff --git a/include/weird-engine/services/ServiceProvider.h b/include/weird-engine/services/ServiceProvider.h new file mode 100644 index 0000000..ffc1721 --- /dev/null +++ b/include/weird-engine/services/ServiceProvider.h @@ -0,0 +1,810 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "weird-engine/Assert.h" +#include "weird-engine/Background.h" +#include "weird-engine/ecs/Registry.h" +#include "weird-engine/Input.h" +#include "weird-engine/Material3D.h" +#include "weird-engine/ResourceManager.h" +#include "weird-engine/systems/SDFRenderSystem.h" +#include "weird-engine/Utils.h" +#include "weird-engine/vec.h" +#include "weird-physics/components/RigidBody.h" +#include "weird-physics/Simulation2D.h" +#include "weird-renderer/audio/AudioRingBuffer.h" +#include "weird-renderer/audio/SimpleAudioRequest.h" +#include "weird-renderer/components/Camera.h" +#include "weird-renderer/components/CustomShape.h" +#include "weird-renderer/scene/Light.h" + +namespace WeirdEngine +{ + class Scene; + + constexpr int SOUND_QUEUE_SIZE = 16; + + /// Map from tag name (std::string) to the entity that owns it. + using TagMap = std::unordered_map; + + struct RaymarchResult + { + float distance; + Entity entity; + }; + + enum class RenderMode + { + RayMarching3D, + RayMarching2D, + RayMarchingBoth + }; + + // Shared raymarch implementation used by both Scene::raymarch and + // PhysicsService::raymarch. Defined in Scene.cpp. + RaymarchResult raymarchScene(Registry& registry, std::vector>& sdfs, + Simulation2D& simulation, float time, glm::vec2 origin, glm::vec2 direction, + float epsilon, float maxDistance); + + struct TimeService + { + Simulation2D& simulation; + const float& delta; + + float time() const + { + return static_cast(simulation.getSimulationTime()); + } + + float deltaTime() const + { + return delta; + } + + double fixedDeltaTime() const + { + return simulation.getDeltaTime(); + } + }; + + struct PhysicsService + { + Registry& registry; + Simulation2D& simulation; + std::vector>& sdfs; + + // Simulation2D& sim() + // { + // return simulation; + // } + + void setGravity(float gravity) + { + simulation.setGravity(gravity); + } + + void setDamping(float damping) + { + simulation.setDamping(damping); + } + + void pause() + { + simulation.pause(); + } + + void resume() + { + simulation.resume(); + } + + bool isPaused() const + { + return simulation.isPaused(); + } + + Entity entityForSimulationId(SimulationID simulationId) const + { + auto rigidBodies = registry.getComponentArray(); + if (simulationId >= static_cast(rigidBodies->getSize())) + return INVALID_ENTITY; + + return rigidBodies->getEntityAtIdx(static_cast(simulationId)); + } + + // Per-body user data. Set the data right after adding the RigidBody2D + // component (read rb.simulationId from it). Ownership is transferred to + // the simulation (e.g. std::make_unique()): it deletes + // the data when the body is removed or when the simulation is + // destroyed. Do not retain the pointer after the call; query it back + // through getUserData()/getUserDataAs(). + void setUserData(SimulationID id, std::unique_ptr data) + { + simulation.setUserData(id, std::move(data)); + } + + BodyUserData* getUserData(SimulationID id) + { + return simulation.getUserData(id); + } + + template T* getUserDataAs(SimulationID id) + { + return simulation.getUserDataAs(id); + } + + template void forEachUserData(Fn&& fn) + { + simulation.forEachUserData(std::forward(fn)); + } + + RaymarchResult raymarch(glm::vec2 origin, glm::vec2 direction, float epsilon = 0.001f, + float maxDistance = 150.0f) + { + return raymarchScene(registry, sdfs, simulation, static_cast(simulation.getSimulationTime()), origin, + direction, epsilon, maxDistance); + } + }; + + struct ShapeParamValue + { + size_t index = 0; + float value = 0.0f; + }; + + struct ShapeVariables + { + float data[8]{}; + + constexpr ShapeVariables() = default; + + constexpr ShapeVariables(std::initializer_list list) + { + size_t i = 0; + for (float v : list) + { + if (i >= 8) + break; + data[i++] = v; + } + } + + constexpr ShapeVariables(std::initializer_list indexedList) + { + for (const auto& pv : indexedList) + { + if (pv.index < 8) + { + data[pv.index] = pv.value; + } + } + } + + template constexpr ShapeVariables(const float (&arr)[N]) + { + const size_t count = std::min(N, 8); + for (size_t i = 0; i < count; ++i) + data[i] = arr[i]; + } + + constexpr ShapeVariables(std::span s) + { + const size_t count = std::min(s.size(), 8); + for (size_t i = 0; i < count; ++i) + data[i] = s[i]; + } + + constexpr ShapeVariables(const float* ptr, size_t n = 8) + { + const size_t count = std::min(n, 8); + for (size_t i = 0; i < count; ++i) + data[i] = ptr[i]; + } + }; + + struct ShapeMaterial + { + uint16_t id = 0; + + constexpr ShapeMaterial() = default; + constexpr ShapeMaterial(uint16_t matId) + : id(matId) + { + } + constexpr ShapeMaterial(int matId) + : id(static_cast(matId)) + { + } + ShapeMaterial(const Material3D& mat) + : id(mat.id) + { + } + constexpr operator uint16_t() const + { + return id; + } + }; + + struct ShapeConfig + { + ShapeId shapeId = 0; + ShapeVariables variables{}; + ShapeMaterial material = 0; + CombinationType combination = CombinationType::Addition; + bool hasCollision = true; + int group = 0; + }; + + struct UIShapeConfig + { + ShapeId shapeId = 0; + ShapeVariables variables{}; + ShapeMaterial material = 0; + CombinationType combination = CombinationType::Addition; + int group = 0; + }; + + struct ShapeService + { + Registry& registry; + Simulation2D& simulation; + std::vector>& sdfs; + + static ShapeId registerDefaultSDF(std::shared_ptr sdf); + + ShapeId registerSDF(std::shared_ptr sdf) + { + sdfs.push_back(std::move(sdf)); + simulation.setSDFs(sdfs); + + return static_cast(sdfs.size() - 1); + } + + const std::vector>& getSDFs() const + { + return sdfs; + } + + Entity addShape(const ShapeConfig& config) + { + Entity entity = registry.createEntity(); + CustomShape& shape = registry.addComponent(entity); + shape.distanceFieldId = config.shapeId; + shape.combination = config.combination; + shape.hasCollisions = config.hasCollision; + shape.groupIdx = config.group; + shape.material = config.material.id; + + std::copy_n(config.variables.data, 8, shape.parameters); + + return entity; + } + + Entity addUIShape(const UIShapeConfig& config) + { + Entity entity = registry.createEntity(); + UIShape& shape = registry.addComponent(entity); + shape.distanceFieldId = config.shapeId; + shape.combination = config.combination; + shape.groupIdx = config.group; + shape.material = config.material.id; + + std::copy_n(config.variables.data, 8, shape.parameters); + + return entity; + } + }; + + struct RenderService + { + Registry& registry; + Entity& cameraEntity; + SDFRenderSystemContext& context2D; + SDFRenderSystemContext& context3D; + SDFRenderSystemContext& contextUI; + std::vector& lights; + BackgroundParams& background; + RenderMode& renderMode; + + WeirdRenderer::Camera& camera() + { + return registry.getComponent(cameraEntity).camera; + } + + Entity getCameraEntity() const + { + return cameraEntity; + } + + std::vector& getLights() + { + return lights; + } + + BackgroundParams& getBackground() + { + return background; + } + + const BackgroundParams& getBackground() const + { + return background; + } + + RenderMode getRenderMode() const + { + return renderMode; + } + + SDFRenderSystemContext& getContext2D() + { + return context2D; + } + + SDFRenderSystemContext& getContext3D() + { + return context3D; + } + + SDFRenderSystemContext& getContextUI() + { + return contextUI; + } + + // Force the shader to regenerate the next frame. Materials are baked + // into the shader, so changing a shape's material (or any parameter + // that affects shader generation) requires a refresh. Use the + // granular variants to refresh only the affected render target. + void forceShaderRefresh2D() + { + context2D.shapesNeedUpdate = true; + } + + void forceShaderRefresh3D() + { + context3D.shapesNeedUpdate = true; + } + + void forceShaderRefreshUI() + { + contextUI.shapesNeedUpdate = true; + } + + void forceShaderRefresh() + { + forceShaderRefresh2D(); + forceShaderRefresh3D(); + forceShaderRefreshUI(); + } + }; + + struct MaterialService + { + Material3D (&materials)[16]; + uint16_t& count; + + Material3D& createMaterial() + { + if (count >= 16) + { + // Max materials reached, return the last one + return materials[15]; + } + + Material3D& mat = materials[count]; + mat.id = count; + count++; + + return mat; + } + + Material3D& getMaterial(int index) + { + return materials[index]; + } + + const Material3D* getMaterials() const + { + return materials; + } + + uint16_t getMaterialCount() const + { + return count; + } + }; + + struct AudioService + { + AudioRingBuffer& queue; + const std::atomic& frictionSoundLevel; + + void playSound(const WeirdRenderer::SimpleAudioRequest& audio) + { + queue.push(audio); + } + + float getFrictionSound() const + { + return frictionSoundLevel.load(std::memory_order_acquire); + } + + AudioRingBuffer& audioQueue() + { + return queue; + } + }; + + struct TagService + { + TagMap& tagToEntity; + std::unordered_map& entityToTag; + + // Assign a unique tag to an entity. If the tag is already owned by + // another entity, it is moved to this one. An empty name is treated + // as a removal request (equivalent to calling removeTag). + void tag(Entity entity, const std::string& name) + { + if (name.empty()) + { + removeTag(entity); + return; + } + + // If the tag is already owned by another entity, remove it from that entity + auto existingOwner = tagToEntity.find(name); + if (existingOwner != tagToEntity.end() && existingOwner->second != entity) + { + entityToTag.erase(existingOwner->second); + } + + // Remove any previous tag this entity had + auto existingTag = entityToTag.find(entity); + if (existingTag != entityToTag.end() && existingTag->second != name) + { + tagToEntity.erase(existingTag->second); + } + + tagToEntity[name] = entity; + entityToTag[entity] = name; + } + + void removeTag(Entity entity) + { + auto it = entityToTag.find(entity); + if (it == entityToTag.end()) + return; + tagToEntity.erase(it->second); + entityToTag.erase(it); + } + + std::string getEntityTag(Entity entity) const + { + auto it = entityToTag.find(entity); + if (it == entityToTag.end()) + return ""; + return it->second; + } + + Entity getEntityByTag(const std::string& name) const + { + auto it = tagToEntity.find(name); + if (it == tagToEntity.end()) + return MAX_ENTITIES; + return it->second; + } + }; + + struct SerializationService + { + Scene& scene; + std::unordered_set& blacklist; + std::string& sceneFilePath; + + // Save the current scene state to a .weird JSON file + void saveScene(const std::string& filename); + + // Dynamically load a .weird file and add its contents to the scene. + // If blacklistEntities is true, all entities created by the load will be + // excluded from future scene serialization. + // Returns a map of tag names to their corresponding entities. + TagMap loadWeirdFile(const std::string& path, bool blacklistEntities = false); + + void blacklistEntity(Entity entity) + { + blacklist.insert(entity); + } + + // Set the path to a .weird file to load when the scene starts + void setSceneFilePath(const std::string& path) + { + sceneFilePath = path; + } + }; + + struct SceneControlService + { + bool& isComplete; + std::string& nextScene; + + void goToNextScene(std::string next = "") + { + isComplete = true; + nextScene = std::move(next); + } + }; + + struct InputService + { + bool getKey(Input::KeyCode key) const + { + return Input::GetKey(key); + } + bool getKeyDown(Input::KeyCode key) const + { + return Input::GetKeyDown(key); + } + bool getKeyUp(Input::KeyCode key) const + { + return Input::GetKeyUp(key); + } + + float getMouseX() const + { + return Input::GetMouseX(); + } + float getMouseY() const + { + return Input::GetMouseY(); + } + float getMouseDeltaX() const + { + return Input::GetMouseDeltaX(); + } + float getMouseDeltaY() const + { + return Input::GetMouseDeltaY(); + } + float getMouseDeltaXRaw() const + { + return Input::GetMouseDeltaXRaw(); + } + float getMouseDeltaYRaw() const + { + return Input::GetMouseDeltaYRaw(); + } + bool getMouseButton(Input::MouseButton button) const + { + return Input::GetMouseButton(button); + } + bool getMouseButtonDown(Input::MouseButton button) const + { + return Input::GetMouseButtonDown(button); + } + bool getMouseButtonUp(Input::MouseButton button) const + { + return Input::GetMouseButtonUp(button); + } + void setMousePosition(float x, float y) + { + Input::SetMousePosition(x, y); + } + void showMouse() + { + Input::ShowMouse(); + } + void hideMouse() + { + Input::HideMouse(); + } + bool isUIClick() const + { + return Input::isUIClick(); + } + void flagUIClick() + { + Input::flagUIClick(); + } + + bool getGamepadButton(Input::GamepadButton button) const + { + return Input::GetGamepadButton(button); + } + bool getGamepadButtonDown(Input::GamepadButton button) const + { + return Input::GetGamepadButtonDown(button); + } + bool getGamepadButtonUp(Input::GamepadButton button) const + { + return Input::GetGamepadButtonUp(button); + } + float getGamepadAxis(Input::GamepadAxis axis) const + { + return Input::GetGamepadAxis(axis); + } + + void suppressMouseInput() + { + Input::suppressMouseInput(); + } + void suppressKeyboardInput() + { + Input::suppressKeyboardInput(); + } + }; + + struct ResourceService + { + ResourceManager& resourceManager; + std::string assetsBasePath; + + ResourceManager& resources() + { + return resourceManager; + } + + void setAssetsBasePath(const std::string& path) + { + assetsBasePath = path; + } + + std::string assetPath(const std::string& relative) const + { + return assetsBasePath + relative; + } + + MeshID getMeshId(const std::string& path, Entity entity, bool instancing = false) + { + return resourceManager.getMeshId(assetPath(path).c_str(), entity, instancing); + } + + std::string readTextFile(const std::string& path) const + { + return get_file_contents(path.c_str()); + } + + void writeTextFile(const std::string& path, const std::string& content) const + { + saveToFile(path.c_str(), content); + } + + bool fileExists(const std::string& path) const + { + return checkIfFileExists(path.c_str()); + } + + void ensureDirectory(const std::string& path) const + { + if (!std::filesystem::exists(path)) + { + std::filesystem::create_directory(path); + } + } + }; + + struct DebugService + { + bool& fly; + bool& input; + + bool debugFly() const + { + return fly; + } + + void setDebugFly(bool value) + { + fly = value; + } + + bool debugInput() const + { + return input; + } + + void setDebugInput(bool value) + { + input = value; + } + }; + + // Central access point for all non-ECS scene functionality. Systems take + // a ServiceProvider& (plus the Registry&) and use it instead of reaching + // into Scene internals. Owned by Scene, which binds it to its storage. + class ServiceProvider + { + public: + explicit ServiceProvider(Scene& scene); + + Registry& registry() + { + return m_registry; + } + + TimeService& time() + { + return m_time; + } + + PhysicsService& physics() + { + return m_physics; + } + + ShapeService& shapes() + { + return m_shapes; + } + + RenderService& render() + { + return m_render; + } + + MaterialService& materials() + { + return m_materials; + } + + AudioService& audio() + { + return m_audio; + } + + TagService& tags() + { + return m_tags; + } + + SerializationService& serialization() + { + return m_serialization; + } + + SceneControlService& sceneControl() + { + return m_sceneControl; + } + + ResourceService& resources() + { + return m_resources; + } + + DebugService& debug() + { + return m_debug; + } + + InputService& input() + { + return m_input; + } + + private: + Registry& m_registry; + TimeService m_time; + PhysicsService m_physics; + ShapeService m_shapes; + RenderService m_render; + MaterialService m_materials; + AudioService m_audio; + TagService m_tags; + SerializationService m_serialization; + SceneControlService m_sceneControl; + ResourceService m_resources; + DebugService m_debug; + InputService m_input; + }; +} // namespace WeirdEngine diff --git a/include/weird-engine/systems/ButtonSystem.h b/include/weird-engine/systems/ButtonSystem.h index e28117b..b9f0abd 100644 --- a/include/weird-engine/systems/ButtonSystem.h +++ b/include/weird-engine/systems/ButtonSystem.h @@ -1,5 +1,5 @@ #pragma once -#include "weird-engine/ecs/ECS.h" +#include "weird-engine/ecs/Registry.h" #include "weird-engine/Input.h" #include "weird-engine/math/MathExpressions.h" @@ -12,20 +12,20 @@ namespace WeirdEngine namespace ButtonSystem { - inline void updateButtons(ECSManager& ecs, std::vector>& sdfs, float time); - inline void updateToggles(ECSManager& ecs, std::vector>& sdfs, float time); + inline void updateButtons(Registry& registry, std::vector>& sdfs, float time); + inline void updateToggles(Registry& registry, std::vector>& sdfs, float time); - inline void update(ECSManager& ecs, std::vector>& sdfs, float time) + inline void update(Registry& registry, std::vector>& sdfs, float time) { - updateButtons(ecs, sdfs, time); - updateToggles(ecs, sdfs, time); + updateButtons(registry, sdfs, time); + updateToggles(registry, sdfs, time); } - inline void updateButtons(ECSManager& ecs, std::vector>& sdfs, float time) + inline void updateButtons(Registry& registry, std::vector>& sdfs, float time) { bool mouseIsClicking = Input::GetMouseButton(Input::LeftClick); - ecs.forEach( + registry.forEach( [&](Entity buttonOwner, ShapeButton& buttonComponent, UIShape& shape) { { @@ -95,11 +95,11 @@ namespace WeirdEngine }); } - inline void updateToggles(ECSManager& ecs, std::vector>& sdfs, float time) + inline void updateToggles(Registry& registry, std::vector>& sdfs, float time) { bool mouseIsClicking = Input::GetMouseButtonDown(Input::LeftClick); - ecs.forEach( + registry.forEach( [&](Entity toggleOwner, ShapeToggle& toggleComponent, UIShape& shape) { { diff --git a/include/weird-engine/systems/CameraSystem.h b/include/weird-engine/systems/CameraSystem.h index f46a634..fd1dc80 100644 --- a/include/weird-engine/systems/CameraSystem.h +++ b/include/weird-engine/systems/CameraSystem.h @@ -1,5 +1,5 @@ #pragma once -#include "weird-engine/ecs/ECS.h" +#include "weird-engine/ecs/Registry.h" #include "weird-engine/Input.h" namespace WeirdEngine @@ -8,9 +8,9 @@ namespace WeirdEngine { namespace CameraSystem { - inline void update(ECSManager& ecs) + inline void update(Registry& registry) { - ecs.forEach( + registry.forEach( [](Entity camOwner, Camera& c, Transform& t) { c.camera.position = t.position; diff --git a/include/weird-engine/systems/PhysicsInteractionSystem.h b/include/weird-engine/systems/PhysicsInteractionSystem.h index a31adab..5c1749a 100644 --- a/include/weird-engine/systems/PhysicsInteractionSystem.h +++ b/include/weird-engine/systems/PhysicsInteractionSystem.h @@ -1,5 +1,5 @@ #pragma once -#include "weird-engine/ecs/ECS.h" +#include "weird-engine/ecs/Registry.h" #include "weird-engine/Input.h" namespace WeirdEngine @@ -80,12 +80,12 @@ namespace WeirdEngine return false; } - inline vec2 getMousePositionInWorld(ECSManager& ecs); - inline void drag(ECSManager& ecs); - inline void impulse(ECSManager& ecs); - inline void fix(ECSManager& ecs); - inline void spring(ECSManager& ecs); - inline void positionConstraint(ECSManager& ecs); + inline vec2 getMousePositionInWorld(Registry& registry); + inline void drag(Registry& registry); + inline void impulse(Registry& registry); + inline void fix(Registry& registry); + inline void spring(Registry& registry); + inline void positionConstraint(Registry& registry); inline void reset() { @@ -102,23 +102,23 @@ namespace WeirdEngine m_selectedId = INVALID_ENTITY; } - inline void update(ECSManager& ecs) + inline void update(Registry& registry) { // Spawn ball if (getLeftClickDown()) { // Get mouse coordinates world space - vec2 mousePositionInWorld = getMousePositionInWorld(ecs); + vec2 mousePositionInWorld = getMousePositionInWorld(registry); // t.position = vec3(mousePositionInWorld.x + sin(time), mousePositionInWorld.y + cos(time), 0.0); - Entity entity = ecs.createEntity(); - Transform& t = ecs.addComponent(entity); + Entity entity = registry.createEntity(); + Transform& t = registry.addComponent(entity); t.position = vec3(mousePositionInWorld.x, mousePositionInWorld.y, 0.0); - auto& dot = ecs.addComponent(entity); + auto& dot = registry.addComponent(entity); dot.materialId = m_currentMaterial + 4; - RigidBody2D& rb = ecs.addComponent(entity); + RigidBody2D& rb = registry.addComponent(entity); if (!m_usingController) { rb.pendingImpulseForce += 1000.0f * vec2(Input::GetMouseDeltaX(), -Input::GetMouseDeltaY()); @@ -140,19 +140,19 @@ namespace WeirdEngine switch (m_currentInteractionMode) { case PhysicsInteractionSystem::InteractionMode::Drag: - drag(ecs); + drag(registry); break; case PhysicsInteractionSystem::InteractionMode::Impulse: - impulse(ecs); + impulse(registry); break; case PhysicsInteractionSystem::InteractionMode::Fix: - fix(ecs); + fix(registry); break; case PhysicsInteractionSystem::InteractionMode::Spring: - spring(ecs); + spring(registry); break; case PhysicsInteractionSystem::InteractionMode::DistanceConstraint: - positionConstraint(ecs); + positionConstraint(registry); break; default: break; @@ -203,12 +203,12 @@ namespace WeirdEngine // Add force to last ball if (Input::GetKey(Input::T)) { - auto rbs = ecs.getComponentArray(); + auto rbs = registry.getComponentArray(); RigidBody2D& target = rbs->getLastData(); Entity targetOwner = rbs->getEntityAtIdx(rbs->getSize() - 1); - vec3 position = ecs.getComponent(targetOwner).position; + vec3 position = registry.getComponent(targetOwner).position; vec2 v = vec2(15, 30) - vec2(position.x, position.y); target.pendingContinuousForce += 50.0f * normalize(v); @@ -230,10 +230,10 @@ namespace WeirdEngine // } } - inline vec2 getMousePositionInWorld(ECSManager& ecs) + inline vec2 getMousePositionInWorld(Registry& registry) { // Get mouse coordinates - auto& cameraTransform = ecs.getComponent(0); // m_mainCamera + auto& cameraTransform = registry.getComponent(0); // m_mainCamera float x = m_usingController ? (WeirdRenderer::Display::width / 2.0f) : Input::GetMouseX(); float y = m_usingController ? (WeirdRenderer::Display::height / 2.0f) : Input::GetMouseY(); @@ -243,21 +243,21 @@ namespace WeirdEngine return mousePositionInWorld; } - inline void drag(ECSManager& ecs) + inline void drag(Registry& registry) { if (getRightClickDown()) { - auto rbs = ecs.getComponentArray(); + auto rbs = registry.getComponentArray(); float minD2 = 1.0f; - vec2 mouseInWorld = getMousePositionInWorld(ecs); + vec2 mouseInWorld = getMousePositionInWorld(registry); for (int i = 0; i < rbs->getSize(); i++) { auto& rb = rbs->getDataAtIdx(i); Entity rbOwner = rbs->getEntityAtIdx(i); - auto& t = ecs.getComponent(rbOwner); + auto& t = registry.getComponent(rbOwner); float d2 = glm::length2(mouseInWorld - vec2(t.position.x, t.position.y)); if (d2 < minD2) @@ -269,7 +269,7 @@ namespace WeirdEngine if (m_dragId != INVALID_ENTITY) { - auto& rb = ecs.getComponent(m_dragId); + auto& rb = registry.getComponent(m_dragId); rb.isFixed = true; rbs->setEntityDirty(m_dragId, true); } @@ -279,7 +279,7 @@ namespace WeirdEngine { if (m_dragId != INVALID_ENTITY) { - auto& rb = ecs.getComponent(m_dragId); + auto& rb = registry.getComponent(m_dragId); rb.isFixed = false; if (!m_usingController) { @@ -290,7 +290,7 @@ namespace WeirdEngine rb.pendingImpulseForce += 1000.0f * vec2(Input::GetGamepadAxis(Input::GamepadAxis::RightX), -Input::GetGamepadAxis(Input::GamepadAxis::RightY)); } - ecs.getComponentArray()->setEntityDirty(m_dragId, true); + registry.getComponentArray()->setEntityDirty(m_dragId, true); m_dragId = INVALID_ENTITY; } @@ -298,17 +298,17 @@ namespace WeirdEngine if (m_dragId != INVALID_ENTITY) { - vec2 mousePositionInWorld = getMousePositionInWorld(ecs); - auto& t = ecs.getComponent(m_dragId); + vec2 mousePositionInWorld = getMousePositionInWorld(registry); + auto& t = registry.getComponent(m_dragId); t.position = vec3(mousePositionInWorld, 0.0f); - ecs.getComponentArray()->setEntityDirty(m_dragId, true); + registry.getComponentArray()->setEntityDirty(m_dragId, true); } } - inline Entity getEntityAtPosition(ECSManager& ecs, vec2 position, float radius = 0.5f) + inline Entity getEntityAtPosition(Registry& registry, vec2 position, float radius = 0.5f) { - auto rbs = ecs.getComponentArray(); + auto rbs = registry.getComponentArray(); float minD2 = radius * radius; Entity found = INVALID_ENTITY; @@ -316,7 +316,7 @@ namespace WeirdEngine { auto& rb = rbs->getDataAtIdx(i); Entity rbOwner = rbs->getEntityAtIdx(i); - auto& t = ecs.getComponent(rbOwner); + auto& t = registry.getComponent(rbOwner); float d2 = glm::length2(position - vec2(t.position.x, t.position.y)); if (d2 < minD2) @@ -328,7 +328,7 @@ namespace WeirdEngine return found; } - inline void impulse(ECSManager& ecs) + inline void impulse(Registry& registry) { if (getRightClickDown()) { @@ -336,7 +336,7 @@ namespace WeirdEngine return; m_loadingImpulse = true; - m_loadStartPosition = getMousePositionInWorld(ecs); + m_loadStartPosition = getMousePositionInWorld(registry); } if (getRightClickUp()) @@ -346,12 +346,12 @@ namespace WeirdEngine m_loadingImpulse = false; - auto m_rbManager = ecs.getComponentManager(); + auto m_rbManager = registry.getComponentManager(); auto componentArray = m_rbManager->getComponentArray(); - auto transforms = ecs.getComponentArray(); + auto transforms = registry.getComponentArray(); - vec2 mousePositionInWorld = getMousePositionInWorld(ecs); + vec2 mousePositionInWorld = getMousePositionInWorld(registry); vec2 direction = (mousePositionInWorld - m_loadStartPosition); float dragDistance = length(direction); @@ -377,26 +377,26 @@ namespace WeirdEngine } } - inline void fix(ECSManager& ecs) + inline void fix(Registry& registry) { if (getRightClickDown()) { - Entity id = getEntityAtPosition(ecs, getMousePositionInWorld(ecs)); + Entity id = getEntityAtPosition(registry, getMousePositionInWorld(registry)); if (id != INVALID_ENTITY) { - auto& rb = ecs.getComponent(id); + auto& rb = registry.getComponent(id); rb.isFixed = !rb.isFixed; - ecs.getComponentArray()->setEntityDirty(id, true); + registry.getComponentArray()->setEntityDirty(id, true); } } } - inline void spring(ECSManager& ecs) + inline void spring(Registry& registry) { if (getRightClickDown()) { - Entity id = getEntityAtPosition(ecs, getMousePositionInWorld(ecs)); + Entity id = getEntityAtPosition(registry, getMousePositionInWorld(registry)); if (id != INVALID_ENTITY) { m_firstIdInSpring = id; @@ -412,16 +412,16 @@ namespace WeirdEngine if (m_firstIdInSpring != INVALID_ENTITY) { // Check - Entity id = getEntityAtPosition(ecs, getMousePositionInWorld(ecs)); + Entity id = getEntityAtPosition(registry, getMousePositionInWorld(registry)); if (id != INVALID_ENTITY) { - Entity entity = ecs.createEntity(); - auto& spring = ecs.addComponent(entity); + Entity entity = registry.createEntity(); + auto& spring = registry.addComponent(entity); spring.entityA = id; spring.entityB = m_firstIdInSpring; spring.stiffness = 0.1f; spring.restDistance = 1.4142f; - ecs.getComponentArray()->setEntityDirty(entity, true); + registry.getComponentArray()->setEntityDirty(entity, true); } } @@ -429,12 +429,12 @@ namespace WeirdEngine } } - inline void positionConstraint(ECSManager& ecs) + inline void positionConstraint(Registry& registry) { if (getRightClickDown()) { - Entity id = getEntityAtPosition(ecs, getMousePositionInWorld(ecs)); + Entity id = getEntityAtPosition(registry, getMousePositionInWorld(registry)); if (id != INVALID_ENTITY) { m_firstIdInSpring = id; @@ -450,15 +450,15 @@ namespace WeirdEngine if (m_firstIdInSpring != INVALID_ENTITY) { // Check - Entity id = getEntityAtPosition(ecs, getMousePositionInWorld(ecs)); + Entity id = getEntityAtPosition(registry, getMousePositionInWorld(registry)); if (id != INVALID_ENTITY) { - Entity entity = ecs.createEntity(); - auto& constraint = ecs.addComponent(entity); + Entity entity = registry.createEntity(); + auto& constraint = registry.addComponent(entity); constraint.entityA = id; constraint.entityB = m_firstIdInSpring; constraint.distance = 1.0f; // Was default in simulation.addPositionConstraint - ecs.getComponentArray()->setEntityDirty(entity, true); + registry.getComponentArray()->setEntityDirty(entity, true); } } diff --git a/include/weird-engine/systems/PhysicsSystem2D.h b/include/weird-engine/systems/PhysicsSystem2D.h index 7f373bd..8c036ad 100644 --- a/include/weird-engine/systems/PhysicsSystem2D.h +++ b/include/weird-engine/systems/PhysicsSystem2D.h @@ -1,6 +1,6 @@ #pragma once #pragma once -#include "weird-engine/ecs/ECS.h" +#include "weird-engine/ecs/Registry.h" #include "weird-physics/components/DistanceConstraint.h" #include "weird-physics/components/GlobalPhysicsSettings.h" #include "weird-physics/components/Spring.h" @@ -14,26 +14,28 @@ namespace WeirdEngine namespace PhysicsSystem2D { - inline void init(ECSManager& ecs, Simulation2D& simulation) + inline void init(Registry& registry, Simulation2D& simulation) { - ecs.registerComponent(); - ecs.registerComponent(); - ecs.registerComponent(); + registry.registerComponent(); + registry.registerComponent(); + registry.registerComponent(); } - inline void update(ECSManager& ecs, Simulation2D& simulation) + inline void update(Registry& registry, Simulation2D& simulation) { - ecs.forEach( + // Pass 1: ECS -> physics. Writes are queued as commands and the + // physics thread applies them on its next step. + registry.forEach( [&](Entity entity, RigidBody2D& rb, Transform& transform) { - if (ecs.isComponentDirty(transform)) + if (registry.isComponentDirty(transform)) { // Override simulation transform simulation.setPosition(rb.simulationId, glm::vec2(transform.position)); - ecs.setComponentDirty(transform, false); // TODO: move somewhere else + registry.setComponentDirty(transform, false); // TODO: move somewhere else } - if (ecs.isComponentDirty(rb)) + if (registry.isComponentDirty(rb)) { simulation.setVelocity(rb.simulationId, rb.velocity); @@ -42,7 +44,7 @@ namespace WeirdEngine else simulation.unFix(rb.simulationId); - ecs.setComponentDirty(rb, false); + registry.setComponentDirty(rb, false); } if (glm::length2(rb.pendingImpulseForce) > 0.0001f) @@ -56,61 +58,59 @@ namespace WeirdEngine simulation.setContinuousForce(rb.simulationId, rb.pendingContinuousForce); rb.pendingContinuousForce = glm::vec2(0.0f); } - - simulation.updateTransform(transform, rb.simulationId); }); - ecs.forEach( + registry.forEach( [&](Entity entity, CustomShape& shape) { - if (ecs.isComponentDirty(shape)) + if (registry.isComponentDirty(shape)) { simulation.updateShape(entity, shape); - ecs.setComponentDirty(shape, false); + registry.setComponentDirty(shape, false); } }); - ecs.forEach( + registry.forEach( [&](Entity entity, GlobalPhysicsSettings& settings) { - if (ecs.isComponentDirty(settings)) + if (registry.isComponentDirty(settings)) { simulation.setGravity(settings.gravity); simulation.setDamping(settings.damping); - ecs.setComponentDirty(settings, false); + registry.setComponentDirty(settings, false); } }); - ecs.forEach( + registry.forEach( [&](Entity entity, Spring& spring) { - if (ecs.isComponentDirty(spring) && spring.entityA != INVALID_ENTITY && + if (registry.isComponentDirty(spring) && spring.entityA != INVALID_ENTITY && spring.entityB != INVALID_ENTITY) { - if (ecs.hasComponent(spring.entityA) && - ecs.hasComponent(spring.entityB)) + if (registry.hasComponent(spring.entityA) && + registry.hasComponent(spring.entityB)) { - auto simIdA = ecs.getComponent(spring.entityA).simulationId; - auto simIdB = ecs.getComponent(spring.entityB).simulationId; + auto simIdA = registry.getComponent(spring.entityA).simulationId; + auto simIdB = registry.getComponent(spring.entityB).simulationId; simulation.addSpring(simIdA, simIdB, spring.stiffness, spring.restDistance); - ecs.setComponentDirty(spring, false); + registry.setComponentDirty(spring, false); } } }); - ecs.forEach( + registry.forEach( [&](Entity entity, DistanceConstraint& constraint) { - if (ecs.isComponentDirty(constraint) && constraint.entityA != INVALID_ENTITY && + if (registry.isComponentDirty(constraint) && constraint.entityA != INVALID_ENTITY && constraint.entityB != INVALID_ENTITY) { - if (ecs.hasComponent(constraint.entityA) && - ecs.hasComponent(constraint.entityB)) + if (registry.hasComponent(constraint.entityA) && + registry.hasComponent(constraint.entityB)) { - auto simIdA = ecs.getComponent(constraint.entityA).simulationId; - auto simIdB = ecs.getComponent(constraint.entityB).simulationId; + auto simIdA = registry.getComponent(constraint.entityA).simulationId; + auto simIdB = registry.getComponent(constraint.entityB).simulationId; simulation.addPositionConstraint(simIdA, simIdB, constraint.distance); - ecs.setComponentDirty(constraint, false); + registry.setComponentDirty(constraint, false); } } }); @@ -122,6 +122,24 @@ namespace WeirdEngine // initialization commands (position, velocity, mass, etc.) // have been queued ahead of this in m_pendingCommands. simulation.activatePendingBodies(); + + // Pass 2: physics -> ECS readback. The published buffers are + // copied into a reusable snapshot under a short lock; the ECS + // iteration then runs without holding the simulation mutex, so + // the physics thread can keep stepping meanwhile. + static Simulation2D::ReadBufferSnapshot readSnapshot; + + simulation.copyReadBuffers(readSnapshot); + + registry.forEach( + [&](Entity entity, RigidBody2D& rb, Transform& transform) + { + vec2 position = readSnapshot.positions[rb.simulationId]; + transform.position.x = position.x; + transform.position.y = position.y; + + rb.velocity = readSnapshot.velocities[rb.simulationId]; + }); } } // namespace PhysicsSystem2D } // namespace ECS diff --git a/include/weird-engine/systems/PlayerMovementSystem.h b/include/weird-engine/systems/PlayerMovementSystem.h index c395a51..684b250 100644 --- a/include/weird-engine/systems/PlayerMovementSystem.h +++ b/include/weird-engine/systems/PlayerMovementSystem.h @@ -1,5 +1,5 @@ #pragma once -#include "weird-engine/ecs/ECS.h" +#include "weird-engine/ecs/Registry.h" #include "weird-engine/Input.h" #include @@ -23,20 +23,20 @@ namespace WeirdEngine return std::clamp(delta, 0.0f, MAX_DEBUG_CAMERA_DELTA); } - inline void updateMovement2D(ECSManager& ecs, float delta); - inline void updateFly(ECSManager& ecs, float delta); + inline void updateMovement2D(Registry& registry, float delta); + inline void updateFly(Registry& registry, float delta); - inline void update(ECSManager& ecs, float delta) + inline void update(Registry& registry, float delta) { - updateMovement2D(ecs, delta); - updateFly(ecs, delta); + updateMovement2D(registry, delta); + updateFly(registry, delta); } - inline void updateMovement2D(ECSManager& ecs, float delta) + inline void updateMovement2D(Registry& registry, float delta) { float safeDelta = clampMovementDelta(delta); - ecs.forEach( + registry.forEach( [&](Entity target, FlyMovement2D& flyComponent, Transform& t, Camera& c) { vec3 targetPosition = flyComponent.targetPosition; @@ -174,11 +174,11 @@ namespace WeirdEngine }); } - inline void updateFly(ECSManager& ecs, float delta) + inline void updateFly(Registry& registry, float delta) { float safeDelta = clampMovementDelta(delta); - ecs.forEach( + registry.forEach( [&](Entity target, FlyMovement& flyComponent, Transform& t, Camera& c) { glm::vec3 forward = glm::normalize(t.rotation); diff --git a/include/weird-engine/systems/RenderSystem.h b/include/weird-engine/systems/RenderSystem.h index c5da1b3..10a3689 100644 --- a/include/weird-engine/systems/RenderSystem.h +++ b/include/weird-engine/systems/RenderSystem.h @@ -1,8 +1,9 @@ #pragma once -#include "weird-engine/ecs/ECS.h" +#include "weird-engine/ecs/Registry.h" #include "weird-engine/ResourceManager.h" #include "weird-renderer/resources/DrawCommand.h" +#include "weird-renderer/scene/Light.h" #include namespace WeirdEngine @@ -11,12 +12,14 @@ namespace WeirdEngine namespace RenderSystem { - inline void update(ECSManager& ecs, ResourceManager& resourceManager, - std::vector& drawQueue) + inline void update(Registry& registry, ResourceManager& resourceManager, + std::vector& drawQueue, + std::vector& lights) { drawQueue.clear(); + lights.clear(); - ecs.forEach( + registry.forEach( [&](Entity mOwner, MeshRenderer& mr, Transform& t) { WeirdRenderer::DrawCommand cmd; @@ -28,6 +31,18 @@ namespace WeirdEngine drawQueue.push_back(cmd); }); + + registry.forEach( + [&](Entity mOwner, LightComponent& lc, Transform& t) + { + WeirdRenderer::Light light; + light.type = static_cast(lc.type); + light.color = lc.color; + light.position = t.position; + light.rotation = t.rotation; + + lights.push_back(light); + }); } } // namespace RenderSystem -} // namespace WeirdEngine \ No newline at end of file +} // namespace WeirdEngine diff --git a/include/weird-engine/systems/SDFRenderSystem.h b/include/weird-engine/systems/SDFRenderSystem.h index 6779417..a47026d 100644 --- a/include/weird-engine/systems/SDFRenderSystem.h +++ b/include/weird-engine/systems/SDFRenderSystem.h @@ -1,5 +1,5 @@ #pragma once -#include "weird-engine/ecs/ECS.h" +#include "weird-engine/ecs/Registry.h" #include "weird-engine/vec.h" #include "weird-renderer/resources/Font.h" #include @@ -27,19 +27,19 @@ namespace WeirdEngine { template - inline void update(ECSManager& ecs, SDFRenderSystemContext& ctx, vec4*& data, uint32_t& size) + inline void update(Registry& registry, SDFRenderSystemContext& ctx, vec4*& data, uint32_t& size) { uint32_t normalDots = 0; - if (auto dotArray = ecs.getComponentArray()) + if (auto dotArray = registry.getComponentArray()) { normalDots = dotArray->getSize(); } uint32_t textDots = 0; - ecs.forEach( + registry.forEach( [&](Entity entity, TextClass& text) { - if (ecs.isComponentDirty(text)) + if (registry.isComponentDirty(text)) { // Update dot count text.bufferedDotCount = 0; @@ -54,7 +54,7 @@ namespace WeirdEngine ((charCount - 1) * ctx.charSpacing); text.height = ctx.font.getCharHeight() * 2 * ctx.dotRadious; - ecs.setComponentDirty(text, false); + registry.setComponentDirty(text, false); } textDots += text.bufferedDotCount; @@ -63,7 +63,7 @@ namespace WeirdEngine uint32_t dotCount = normalDots + textDots; uint32_t shapeCount = 0; - if (auto shapeArray = ecs.getComponentArray()) + if (auto shapeArray = registry.getComponentArray()) { shapeCount = shapeArray->getSize(); } @@ -89,7 +89,7 @@ namespace WeirdEngine // Process DotClass instances int dotIdx = 0; - ecs.forEach( + registry.forEach( [&](Entity entity, DotClass& dotComp, Transform& t) { data[dotIdx].x = t.position.x; @@ -103,7 +103,7 @@ namespace WeirdEngine // Text int dotIndex = 0; - ecs.forEach( + registry.forEach( [&](Entity entity, TextClass& text, Transform& t) { int charCount = static_cast(text.text.length()); @@ -161,7 +161,7 @@ namespace WeirdEngine // Process ShapeClass instances int shapeIdx = 0; - ecs.forEach( + registry.forEach( [&](Entity entity, ShapeClass& shapeComp) { // Assuming ShapeClass has m_parameters[0] through m_parameters[7] diff --git a/include/weird-engine/systems/SDFShaderGenerationSystem.h b/include/weird-engine/systems/SDFShaderGenerationSystem.h index 60c86a2..c51b6c8 100644 --- a/include/weird-engine/systems/SDFShaderGenerationSystem.h +++ b/include/weird-engine/systems/SDFShaderGenerationSystem.h @@ -1,6 +1,6 @@ #pragma once -#include "weird-engine/ecs/ECS.h" +#include "weird-engine/ecs/Registry.h" #include "weird-engine/Input.h" #include "weird-renderer/components/CustomShape.h" #include "weird-renderer/resources/Shader.h" @@ -19,10 +19,10 @@ namespace WeirdEngine::SDFShaderGenerationSystem { template - inline void update(ECSManager& ecs, RenderContext& ctx, WeirdRenderer::Shader& shader, + inline void update(Registry& registry, RenderContext& ctx, WeirdRenderer::Shader& shader, const std::vector>& sdfs) { - const auto componentArray = ecs.getComponentManager()->getComponentArray(); + const auto componentArray = registry.getComponentManager()->getComponentArray(); if (!ctx.shapesNeedUpdate) { diff --git a/include/weird-physics/BodyUserData.h b/include/weird-physics/BodyUserData.h new file mode 100644 index 0000000..03f684f --- /dev/null +++ b/include/weird-physics/BodyUserData.h @@ -0,0 +1,24 @@ +#pragma once + +namespace WeirdEngine +{ + // Base class for per-body user data attached to rigid bodies via + // Simulation2D::setUserData(). The simulation only stores the pointer and + // never interprets it: derive from this, set `type` to a game-specific + // discriminator, and use Simulation2D::getUserDataAs() (which checks + // `T::TYPE` against `type` before casting) or check `type` manually. + // + // Set `type` in the derived class's constructor (e.g. + // `MyData() { type = TYPE; }`) so the discriminator can never drift out + // of sync with T::TYPE. + // + // Ownership is transferred to the simulation with std::unique_ptr (e.g. + // std::make_unique()): it deletes the data when the body is + // removed and deletes all remaining data when the simulation is destroyed + // (scene teardown). Do not retain the pointer after setUserData(); query + // it back via getUserData()/getUserDataAs() instead. + struct BodyUserData + { + int type = 0; + }; +} // namespace WeirdEngine diff --git a/include/weird-physics/PhysicsSettings.h b/include/weird-physics/PhysicsSettings.h index 7a0ff2f..4f1a47d 100644 --- a/include/weird-physics/PhysicsSettings.h +++ b/include/weird-physics/PhysicsSettings.h @@ -8,5 +8,6 @@ namespace WeirdEngine float damping = 0.001f; float simulationFrequency = 100.0f; int relaxationSteps = 10; + bool runSimulationInThread = true; }; } // namespace WeirdEngine diff --git a/include/weird-physics/Simulation2D.h b/include/weird-physics/Simulation2D.h index 22ba11c..a721127 100644 --- a/include/weird-physics/Simulation2D.h +++ b/include/weird-physics/Simulation2D.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -20,6 +21,7 @@ #include "weird-engine/vec.h" #include "PhysicsSettings.h" +#include "weird-physics/BodyUserData.h" namespace WeirdEngine { @@ -54,14 +56,14 @@ namespace WeirdEngine END }; - struct CollisionEvent + struct PhysicsCollisionEvent { // CollisionState state; SimulationID bodyA; SimulationID bodyB; }; - struct ShapeCollisionEvent + struct PhysicsShapeCollisionEvent { CollisionState state; SimulationID body; @@ -78,8 +80,8 @@ namespace WeirdEngine using StepCallbackFn = void (*)(void*); // Define the function pointer type and include a user data pointer - using CollisionCallbackFn = void (*)(CollisionEvent&, void*); - using ShapeCollisionCallbackFn = void (*)(ShapeCollisionEvent&, void*); + using CollisionCallbackFn = void (*)(PhysicsCollisionEvent&, void*); + using ShapeCollisionCallbackFn = void (*)(PhysicsShapeCollisionEvent&, void*); struct SpatialGridSnapshot { @@ -120,6 +122,10 @@ namespace WeirdEngine size_t getSize(); // Interaction + // One-shot kick applied to a body. With massIndependent = false the + // impulse is a force scaled by the simulation frequency; with + // massIndependent = true it is applied as a direct velocity change + // (the parameter is the desired delta-v in m/s, regardless of mass). void addImpulseForce(SimulationID id, const vec2& impulse, bool massIndependent = false); void setContinuousForce(SimulationID id, const vec2& force, bool massIndependent = false); void swapContinuousForces(); @@ -151,14 +157,41 @@ namespace WeirdEngine return m_stats; } - // Retrieve results + // Retrieve published results. Safe from any thread, including physics + // callbacks (they just pay a per-call lock). For reading many bodies + // at once on the main thread, copy them into a ReadBufferSnapshot via + // copyReadBuffers() instead. vec2 getPosition(SimulationID id); void setPosition(SimulationID id, vec2 pos); vec2 getVelocity(SimulationID id); void setVelocity(SimulationID id, vec2 vel); - void updateTransform(Transform& transform, SimulationID id); void setMass(SimulationID id, float mass); + // Published physics state copied out under a brief lock. Fill it once + // per frame with copyReadBuffers(), then iterate the ECS without + // holding the simulation mutex. + struct ReadBufferSnapshot + { + std::vector positions; + std::vector velocities; + }; + + // Copies the published positions/velocities into the snapshot under a + // short lock. The snapshot's buffers grow as needed but keep their + // capacity across calls. Main thread only; must NOT be called from + // physics execution (WEIRD_ASSERT enforces this in debug builds). + void copyReadBuffers(ReadBufferSnapshot& snapshot); + + // Current working physics state. PHYSICS EXECUTION ONLY: call these + // from onPhysicsStep/onCollision/onShapeCollision callbacks, never + // from the main thread (WEIRD_ASSERT enforces this in debug builds). + vec2 getPhysicsPosition(SimulationID id) const; + vec2 getPhysicsVelocity(SimulationID id) const; + + // True while inside a physics step (physics thread in threaded mode, + // main thread in single-threaded mode). + static bool isPhysicsExecutionContext(); + void setSDFs(std::vector>& sdfs); std::shared_ptr getSpatialGridSnapshot() @@ -183,6 +216,50 @@ namespace WeirdEngine m_damping = damping; } + // Per-body user data, keyed by SimulationID (entity-free: the ECS maps + // simulation IDs back to entities via the RigidBody2D component array). + // Takes ownership via unique_ptr: the simulation deletes the data when + // the body is removed (removeObject) and when the simulation is + // destroyed. Do not retain the pointer after the call; read or modify + // it through getUserData()/getUserDataAs() instead. setUserData() is + // main-thread only; getUserData()/getUserDataAs()/forEachUserData() + // are safe from the physics callbacks without locks. + void setUserData(SimulationID id, std::unique_ptr data); + BodyUserData* getUserData(SimulationID id); + + // Type-checked cast: returns nullptr unless the attached data exists + // and its `type` matches T::TYPE. + template T* getUserDataAs(SimulationID id) + { + BodyUserData* data = getUserData(id); + if (!data || data->type != T::TYPE) + return nullptr; + return static_cast(data); + } + + // Calls fn(SimulationID, BodyUserData&) for every active body that has + // user data attached. Lock-free from physics callbacks (the step + // already holds the structural mutex); serialized on the main thread. + template void forEachUserData(Fn&& fn) + { + if (isPhysicsExecutionContext()) + { + for (SimulationID id = 0; id < m_size; ++id) + { + if (m_userData[id]) + fn(id, *m_userData[id]); + } + return; + } + + std::lock_guard lock(m_structuralMutex); + for (SimulationID id = 0; id < m_size; ++id) + { + if (m_userData[id]) + fn(id, *m_userData[id]); + } + } + // Constraint structs (public for serialization) struct DistanceConstraint { @@ -335,9 +412,9 @@ namespace WeirdEngine float m_fixedDeltaTimeF; int m_relaxationSteps; - bool m_isPaused; - bool m_simulating; - double m_simulationDelay; + std::atomic m_isPaused{false}; + std::atomic m_simulating{false}; + std::atomic m_simulationDelay{0.0}; std::atomic m_simulationTime{0.0}; bool m_useSimdOperations; @@ -366,6 +443,10 @@ namespace WeirdEngine float* m_mass; float* m_invMass; + // Per-body user data, parallel to the body arrays. Swapped in + // removeObject() so the data follows the body through renumbering. + BodyUserData** m_userData; + const float m_diameter; const float m_diameterSquared; const float m_radious; @@ -381,7 +462,7 @@ namespace WeirdEngine std::vector m_objects; std::vector m_collisionMap; - std::vector m_collisionQueue; + std::vector m_collisionQueue; float map(vec2 p); float map(vec2 p, int& closestShape); diff --git a/include/weird-physics/components/DistanceConstraint.h b/include/weird-physics/components/DistanceConstraint.h index 101bed0..b22be96 100644 --- a/include/weird-physics/components/DistanceConstraint.h +++ b/include/weird-physics/components/DistanceConstraint.h @@ -1,6 +1,6 @@ #pragma once -#include "weird-engine/ecs/ECS.h" +#include "weird-engine/ecs/Registry.h" namespace WeirdEngine { diff --git a/include/weird-physics/components/DistanceConstraintManager.h b/include/weird-physics/components/DistanceConstraintManager.h index 7a7d352..d262082 100644 --- a/include/weird-physics/components/DistanceConstraintManager.h +++ b/include/weird-physics/components/DistanceConstraintManager.h @@ -11,12 +11,12 @@ namespace WeirdEngine { private: Simulation2D* m_simulation; - ECSManager* m_ecs; + Registry* m_registry; public: - DistanceConstraintManager(Simulation2D& simulation, ECSManager& ecs) + DistanceConstraintManager(Simulation2D& simulation, Registry& registry) : m_simulation(&simulation) - , m_ecs(&ecs) + , m_registry(®istry) { } @@ -25,11 +25,11 @@ namespace WeirdEngine auto componentArray = std::static_pointer_cast>(m_componentArray); DistanceConstraint& removedConstraint = componentArray->getDataFromEntity(entity); - if (m_ecs->hasComponent(removedConstraint.entityA) && - m_ecs->hasComponent(removedConstraint.entityB)) + if (m_registry->hasComponent(removedConstraint.entityA) && + m_registry->hasComponent(removedConstraint.entityB)) { - auto simIdA = m_ecs->getComponent(removedConstraint.entityA).simulationId; - auto simIdB = m_ecs->getComponent(removedConstraint.entityB).simulationId; + auto simIdA = m_registry->getComponent(removedConstraint.entityA).simulationId; + auto simIdB = m_registry->getComponent(removedConstraint.entityB).simulationId; m_simulation->removeDistanceConstraint(simIdA, simIdB); } } diff --git a/include/weird-physics/components/Spring.h b/include/weird-physics/components/Spring.h index 673c0b9..2c45c97 100644 --- a/include/weird-physics/components/Spring.h +++ b/include/weird-physics/components/Spring.h @@ -1,6 +1,6 @@ #pragma once -#include "weird-engine/ecs/ECS.h" +#include "weird-engine/ecs/Registry.h" namespace WeirdEngine { diff --git a/include/weird-physics/components/SpringManager.h b/include/weird-physics/components/SpringManager.h index ea60ffc..e2f6078 100644 --- a/include/weird-physics/components/SpringManager.h +++ b/include/weird-physics/components/SpringManager.h @@ -11,12 +11,12 @@ namespace WeirdEngine { private: Simulation2D* m_simulation; - ECSManager* m_ecs; + Registry* m_registry; public: - SpringManager(Simulation2D& simulation, ECSManager& ecs) + SpringManager(Simulation2D& simulation, Registry& registry) : m_simulation(&simulation) - , m_ecs(&ecs) + , m_registry(®istry) { } @@ -25,11 +25,11 @@ namespace WeirdEngine auto componentArray = std::static_pointer_cast>(m_componentArray); Spring& removedSpring = componentArray->getDataFromEntity(entity); - if (m_ecs->hasComponent(removedSpring.entityA) && - m_ecs->hasComponent(removedSpring.entityB)) + if (m_registry->hasComponent(removedSpring.entityA) && + m_registry->hasComponent(removedSpring.entityB)) { - auto simIdA = m_ecs->getComponent(removedSpring.entityA).simulationId; - auto simIdB = m_ecs->getComponent(removedSpring.entityB).simulationId; + auto simIdA = m_registry->getComponent(removedSpring.entityA).simulationId; + auto simIdB = m_registry->getComponent(removedSpring.entityB).simulationId; // DistanceConstraints and Springs are treated identically for removal in Simulation2D m_simulation->removeDistanceConstraint(simIdA, simIdB); } diff --git a/include/weird-renderer/components/LightComponent.h b/include/weird-renderer/components/LightComponent.h new file mode 100644 index 0000000..8034413 --- /dev/null +++ b/include/weird-renderer/components/LightComponent.h @@ -0,0 +1,18 @@ +#pragma once +#include + +namespace WeirdEngine +{ + enum class LightType : uint32_t + { + Directional = 0, + Point = 1, + Spot = 2 + }; + + struct LightComponent + { + LightType type = LightType::Directional; + glm::vec4 color = glm::vec4(1.0f); + }; +} // namespace WeirdEngine diff --git a/include/weird-renderer/core/MeshRenderPipeline.h b/include/weird-renderer/core/MeshRenderPipeline.h index 064e5b2..7ac7bf9 100644 --- a/include/weird-renderer/core/MeshRenderPipeline.h +++ b/include/weird-renderer/core/MeshRenderPipeline.h @@ -1,18 +1,13 @@ #pragma once #include "weird-renderer/core/RenderTarget.h" +#include "weird-renderer/resources/DrawCommand.h" #include "weird-renderer/resources/Shader.h" #include "weird-renderer/resources/Texture.h" #include "weird-renderer/scene/Camera.h" #include "weird-renderer/scene/Light.h" #include -// Forward declaration to avoid pulling in the full Scene header -namespace WeirdEngine -{ - class Scene; -} - namespace WeirdEngine { namespace WeirdRenderer @@ -42,8 +37,8 @@ namespace WeirdEngine // Renders 3D models into the internal GBuffer. // outputTarget is forwarded to Scene::onRender for custom per-scene rendering. - void render(Scene& scene, RenderTarget& outputTarget, const Camera& camera, - const std::vector& lights); + void render(RenderTarget& outputTarget, const std::vector& drawQueue, + const Camera& camera, const std::vector& lights); // Recreates internal textures for a new resolution. void resize(unsigned int newWidth, unsigned int newHeight); diff --git a/include/weird-renderer/core/Renderer.h b/include/weird-renderer/core/Renderer.h index 430ee39..b18a398 100644 --- a/include/weird-renderer/core/Renderer.h +++ b/include/weird-renderer/core/Renderer.h @@ -31,6 +31,7 @@ namespace WeirdEngine void render(Scene& scene, const double time, const double delta); void setWindowTitle(const char* name); void setWindowSize(unsigned int width, unsigned int height); + void handleEvent(const SDL_Event& event); SDL_Window* getWindow(); @@ -78,6 +79,7 @@ namespace WeirdEngine std::string m_lastScreenshotPath; // Stats UI (F4) + bool m_showDebugUI = false; bool m_showStatsUI = false; static constexpr int STATS_HISTORY_SIZE = 128; float m_frametimeHistory[STATS_HISTORY_SIZE] = {}; diff --git a/include/weird-renderer/core/SDF3DRenderPipeline.h b/include/weird-renderer/core/SDF3DRenderPipeline.h index 9c02834..6499a62 100644 --- a/include/weird-renderer/core/SDF3DRenderPipeline.h +++ b/include/weird-renderer/core/SDF3DRenderPipeline.h @@ -38,13 +38,21 @@ namespace WeirdEngine Shader& getShader(); + struct GBuffer + { + Texture& albedo; + Texture& worldPos; + Texture& normal; + Texture& material; + Texture& depth; + Texture& backDepth; + }; + // Renders the SDF 3D scene using ray marching with path-traced accumulation. // GBuffer textures come from MeshRenderPipeline and allow the shader to composite // mesh surfaces with SDF lighting (SDFs cast light on meshes; meshes don't affect SDFs). void render(vec4* shapeData, uint32_t dataSize, uint32_t shapeCount, const std::vector& lights, - const Camera& camera, double time, Texture& gbufferAlbedo, Texture& gbufferWorldPos, - Texture& gbufferNormal, Texture& gbufferMaterial, Texture& gbufferDepth, - Texture& gbufferBackDepth, const Material3D* materials); + const Camera& camera, double time, const GBuffer& gbuffer, const Material3D* materials); RenderTarget& getRenderTarget(); Texture& getOutputTexture(); diff --git a/include/weird-renderer/resources/Shader.h b/include/weird-renderer/resources/Shader.h index a6f21f1..5cba4c8 100644 --- a/include/weird-renderer/resources/Shader.h +++ b/include/weird-renderer/resources/Shader.h @@ -22,6 +22,10 @@ namespace WeirdEngine GLuint ID = -1; // Constructor that build the Shader Program from 2 different shaders Shader(const char* vertexFile, const char* fragmentFile); + Shader(const std::string& vertexFile, const std::string& fragmentFile) + : Shader(vertexFile.c_str(), fragmentFile.c_str()) + { + } Shader() {}; // Activates the Shader Program diff --git a/include/weird-renderer/resources/Texture.h b/include/weird-renderer/resources/Texture.h index 841e0e1..e828e35 100644 --- a/include/weird-renderer/resources/Texture.h +++ b/include/weird-renderer/resources/Texture.h @@ -35,6 +35,10 @@ namespace WeirdEngine , height(0) {}; Texture(const char* image); + Texture(const std::string& image) + : Texture(image.c_str()) + { + } Texture(glm::vec4 color); diff --git a/src/weird-engine/Scene.cpp b/src/weird-engine/Scene.cpp index 05331fe..70fc237 100644 --- a/src/weird-engine/Scene.cpp +++ b/src/weird-engine/Scene.cpp @@ -45,10 +45,17 @@ namespace WeirdEngine Scene::Scene() : m_simulation2D(MAX_ENTITIES, SceneManager::getInstance().getPhysicsSettings()) - , m_runSimulationInThread(true) + , m_runSimulationInThread(SceneManager::getInstance().getPhysicsSettings().runSimulationInThread) + , m_services(*this) { } + Scene::Scene(RenderMode mode) + : Scene() + { + m_renderMode = mode; + } + Scene::~Scene() { m_simulation2D.stopSimulationThread(); @@ -59,40 +66,46 @@ namespace WeirdEngine void Scene::start() { + onCreate(m_registry, m_services); + for (auto& sys : m_createSystems) + { + sys(m_registry, m_services); + } + // Custom component managers std::shared_ptr rbManager = std::make_shared(m_simulation2D); - m_ecs.registerComponent(rbManager); + m_registry.registerComponent(rbManager); if (m_renderMode == RenderMode::RayMarching2D) { std::shared_ptr shapeManager = std::make_shared(m_simulation2D, m_2DWorldRenderContext); - m_ecs.registerComponent(shapeManager); + m_registry.registerComponent(shapeManager); } else { std::shared_ptr shapeManager = std::make_shared(m_simulation2D, m_3DWorldRenderContext); - m_ecs.registerComponent(shapeManager); + m_registry.registerComponent(shapeManager); } std::shared_ptr distManager = - std::make_shared(m_simulation2D, m_ecs); - m_ecs.registerComponent(distManager); + std::make_shared(m_simulation2D, m_registry); + m_registry.registerComponent(distManager); - std::shared_ptr springManager = std::make_shared(m_simulation2D, m_ecs); - m_ecs.registerComponent(springManager); + std::shared_ptr springManager = std::make_shared(m_simulation2D, m_registry); + m_registry.registerComponent(springManager); std::shared_ptr uiShapeManager = std::make_shared(m_UIRenderContext); - m_ecs.registerComponent(uiShapeManager); + m_registry.registerComponent(uiShapeManager); // Shapes m_sdfs = Scene::getGlobalSDFs(); m_simulation2D.setSDFs(m_sdfs); // Initialize simulation - PhysicsSystem2D::init(m_ecs, m_simulation2D); + PhysicsSystem2D::init(m_registry, m_simulation2D); // Start simulation if different thread if (m_runSimulationInThread) @@ -114,50 +127,52 @@ namespace WeirdEngine defaultMaterial.roughness = 0.1f; // Create camera - m_mainCamera = m_ecs.createEntity(); - tag(m_mainCamera, "mainCamera"); - Transform& t = m_ecs.addComponent(m_mainCamera); + m_mainCamera = m_registry.createEntity(); + m_services.tags().tag(m_mainCamera, "mainCamera"); + Transform& t = m_registry.addComponent(m_mainCamera); t.rotation = vec3(0, 0, -1.0f); - ECS::Camera& c = m_ecs.addComponent(m_mainCamera); - - onCreate(); + ECS::Camera& c = m_registry.addComponent(m_mainCamera); // If a .weird file path was provided (via setSceneFilePath / registerScene), // restore saved scene state before the derived class's onStart() runs. - TagMap loadedTags; if (!m_sceneFilePath.empty()) { SceneSerializer::load(*this, m_sceneFilePath); - loadedTags = m_tagToEntity; } - onStart(m_ecs, loadedTags); + onStart(m_registry, m_services); + for (auto& sys : m_startSystems) + { + sys(m_registry, m_services); + } switch (m_renderMode) { case WeirdEngine::Scene::RenderMode::RayMarching3D: { - FlyMovement& fly = m_ecs.addComponent(m_mainCamera); + FlyMovement& fly = m_registry.addComponent(m_mainCamera); break; } case WeirdEngine::Scene::RenderMode::RayMarching2D: case WeirdEngine::Scene::RenderMode::RayMarchingBoth: { - FlyMovement2D& fly = m_ecs.addComponent(m_mainCamera); - fly.targetPosition = m_ecs.getComponent(m_mainCamera).position; + FlyMovement2D& fly = m_registry.addComponent(m_mainCamera); + fly.targetPosition = m_registry.getComponent(m_mainCamera).position; break; } default: break; } - PhysicsSystem2D::update(m_ecs, m_simulation2D); + PhysicsSystem2D::update(m_registry, m_simulation2D); } void Scene::update(double delta, double time) { PROFILE_SCOPE("Scene Update"); + m_lastDelta = static_cast(delta); + if (Input::GetKey(Input::LeftCtrl) && Input::GetKeyDown((Input::R))) { forceShaderRefresh(); @@ -167,21 +182,21 @@ namespace WeirdEngine { if (m_debugFly) { - PlayerMovementSystem::update(m_ecs, static_cast(delta)); + PlayerMovementSystem::update(m_registry, static_cast(delta)); } - CameraSystem::update(m_ecs); + CameraSystem::update(m_registry); } - ButtonSystem::update(m_ecs, m_sdfs, getTime()); + ButtonSystem::update(m_registry, m_sdfs, getTime()); { PROFILE_SCOPE("Physics synchronization"); - PhysicsSystem2D::update(m_ecs, m_simulation2D); + PhysicsSystem2D::update(m_registry, m_simulation2D); if (m_debugInput) { - PhysicsInteractionSystem::update(m_ecs); + PhysicsInteractionSystem::update(m_registry); } m_simulation2D.update(delta); @@ -193,8 +208,8 @@ namespace WeirdEngine // Process queued collisions // Static vectors retain heap capacity across frames, avoiding // repeated allocations when thousands of collisions are generated. - static std::vector collisions; - static std::vector shapeCollisions; + static std::vector collisions; + static std::vector shapeCollisions; collisions.clear(); shapeCollisions.clear(); { @@ -203,19 +218,27 @@ namespace WeirdEngine std::swap(shapeCollisions, m_queuedShapeCollisions); } - auto rigidBodies = m_ecs.getComponentArray(); + auto rigidBodies = m_registry.getComponentArray(); for (auto& ev : collisions) { EntityCollisionEvent entityEvent{ev, getEntityForSimulationId(ev.bodyA, rigidBodies), getEntityForSimulationId(ev.bodyB, rigidBodies)}; - onEntityCollision(m_ecs, entityEvent); + onEntityCollision(m_registry, m_services, entityEvent); + for (auto& sys : m_entityCollisionSystems) + { + sys(m_registry, m_services, entityEvent); + } } for (auto& ev : shapeCollisions) { EntityShapeCollisionEvent entityEvent{ev, getEntityForSimulationId(ev.body, rigidBodies)}; - onEntityShapeCollision(m_ecs, entityEvent); + onEntityShapeCollision(m_registry, m_services, entityEvent); + for (auto& sys : m_entityShapeCollisionSystems) + { + sys(m_registry, m_services, entityEvent); + } const float m_soundFalloff = 0.1f; bool spatialAudio = false; @@ -246,15 +269,19 @@ namespace WeirdEngine { PROFILE_SCOPE("OnUpdate"); - onUpdate(static_cast(delta), m_ecs); + onUpdate(m_registry, m_services); + for (auto& sys : m_updateSystems) + { + sys(m_registry, m_services); + } } { PROFILE_SCOPE("Render Queue update"); - RenderSystem::update(m_ecs, m_resourceManager, m_drawQueue); + RenderSystem::update(m_registry, m_resourceManager, m_drawQueue, m_lights); } - m_ecs.freeRemovedComponents(); + m_registry.freeRemovedComponents(); } float Scene::getTime() @@ -268,19 +295,19 @@ namespace WeirdEngine self->onPhysicsStep(self->m_simulation2D); } - void Scene::handleCollision(CollisionEvent& event, void* userData) + void Scene::handleCollision(PhysicsCollisionEvent& event, void* userData) { Scene* self = static_cast(userData); - self->onCollision(self->m_simulation2D, event); + self->onPhysicsRigidBodyCollision(self->m_simulation2D, event); std::lock_guard lock(self->m_collisionQueueMutex); self->m_queuedCollisions.push_back(event); } - void Scene::handleShapeCollision(ShapeCollisionEvent& event, void* userData) + void Scene::handleShapeCollision(PhysicsShapeCollisionEvent& event, void* userData) { Scene* self = static_cast(userData); - self->onShapeCollision(self->m_simulation2D, event); + self->onPhysicsShapeCollision(self->m_simulation2D, event); { std::lock_guard lock(self->m_collisionQueueMutex); @@ -297,43 +324,43 @@ namespace WeirdEngine WeirdRenderer::Camera& Scene::getCamera() { - return m_ecs.getComponent(m_mainCamera).camera; + return m_registry.getComponent(m_mainCamera).camera; } void Scene::get2DShapesData(vec4*& data, uint32_t& size, uint32_t& customShapeCount) { // PROFILE_SCOPE("Fetch World Data"); - customShapeCount = m_ecs.getComponentArray()->getSize(); - SDFRenderSystem::update(m_ecs, m_2DWorldRenderContext, data, size); + customShapeCount = m_registry.getComponentArray()->getSize(); + SDFRenderSystem::update(m_registry, m_2DWorldRenderContext, data, size); } void Scene::get3DShapesData(vec4*& data, uint32_t& size, uint32_t& customShapeCount) { // PROFILE_SCOPE("Fetch 3D World Data"); - customShapeCount = m_ecs.getComponentArray()->getSize(); - SDFRenderSystem::update(m_ecs, m_3DWorldRenderContext, data, size); + customShapeCount = m_registry.getComponentArray()->getSize(); + SDFRenderSystem::update(m_registry, m_3DWorldRenderContext, data, size); } void Scene::getUIData(vec4*& uiData, uint32_t& size, uint32_t& customShapeCount) { // PROFILE_SCOPE("Fetch UI Data"); - customShapeCount = m_ecs.getComponentArray()->getSize(); - SDFRenderSystem::update(m_ecs, m_UIRenderContext, uiData, size); + customShapeCount = m_registry.getComponentArray()->getSize(); + SDFRenderSystem::update(m_registry, m_UIRenderContext, uiData, size); } void Scene::update2DWorldShader(WeirdRenderer::Shader& shader) { - SDFShaderGenerationSystem::update(m_ecs, m_2DWorldRenderContext, shader, m_sdfs); + SDFShaderGenerationSystem::update(m_registry, m_2DWorldRenderContext, shader, m_sdfs); } void Scene::update3DWorldShader(WeirdRenderer::Shader& shader) { - SDFShaderGenerationSystem::update(m_ecs, m_3DWorldRenderContext, shader, m_sdfs); + SDFShaderGenerationSystem::update(m_registry, m_3DWorldRenderContext, shader, m_sdfs); } void Scene::updateUIShader(WeirdRenderer::Shader& shader) { - SDFShaderGenerationSystem::update(m_ecs, m_UIRenderContext, shader, m_sdfs); + SDFShaderGenerationSystem::update(m_registry, m_UIRenderContext, shader, m_sdfs); } void Scene::forceShaderRefresh() @@ -348,7 +375,7 @@ namespace WeirdEngine return m_drawQueue; } - std::vector& Scene::getLigths() + std::vector& Scene::getLights() { return m_lights; } @@ -357,20 +384,12 @@ namespace WeirdEngine { if (m_renderMode == RenderMode::RayMarching3D || m_renderMode == RenderMode::RayMarchingBoth) { - onRender(renderTarget); + onRender(m_registry, m_services, renderTarget); } } // SDFs - ShapeId Scene::registerSDF(std::shared_ptr sdf) - { - m_sdfs.push_back(sdf); - m_simulation2D.setSDFs(m_sdfs); - - return static_cast(m_sdfs.size() - 1); - } - // AUDIO AudioRingBuffer& Scene::getAudioQueue() @@ -390,95 +409,70 @@ namespace WeirdEngine // Serialization - void Scene::tag(Entity entity, const std::string& name) + Entity Scene::getEntityForSimulationId(SimulationID simulationId, + std::shared_ptr> rigidBodies) { - if (name.empty()) - { - removeTag(entity); - return; - } - - // If the tag is already owned by another entity, remove it from that entity - auto existingOwner = m_tagToEntity.find(name); - if (existingOwner != m_tagToEntity.end() && existingOwner->second != entity) - { - m_entityToTag.erase(existingOwner->second); - } - - // Remove any previous tag this entity had - auto existingTag = m_entityToTag.find(entity); - if (existingTag != m_entityToTag.end() && existingTag->second != name) - { - m_tagToEntity.erase(existingTag->second); - } + if (simulationId >= static_cast(rigidBodies->getSize())) + return INVALID_ENTITY; - m_tagToEntity[name] = entity; - m_entityToTag[entity] = name; + return rigidBodies->getEntityAtIdx(static_cast(simulationId)); } - void Scene::removeTag(Entity entity) + void Scene::loadFromWeirdFile(const std::string& path) { - auto it = m_entityToTag.find(entity); - if (it == m_entityToTag.end()) - return; - m_tagToEntity.erase(it->second); - m_entityToTag.erase(it); + SceneSerializer::load(*this, path); } - std::string Scene::getEntityTag(Entity entity) const - { - auto it = m_entityToTag.find(entity); - if (it == m_entityToTag.end()) - return ""; - return it->second; - } + // ServiceProvider - Entity Scene::getEntityByTag(const std::string& name) const + ServiceProvider::ServiceProvider(Scene& scene) + : m_registry(scene.m_registry) + , m_time(scene.m_simulation2D, scene.m_lastDelta) + , m_physics(scene.m_registry, scene.m_simulation2D, scene.m_sdfs) + , m_shapes(scene.m_registry, scene.m_simulation2D, scene.m_sdfs) + , m_render(scene.m_registry, scene.m_mainCamera, scene.m_2DWorldRenderContext, scene.m_3DWorldRenderContext, + scene.m_UIRenderContext, scene.m_lights, scene.m_background, scene.m_renderMode) + , m_materials(scene.m_materials, scene.m_materialCount) + , m_audio(scene.m_audioQueue, scene.m_frictionSoundLevelRead) + , m_tags(scene.m_tagToEntity, scene.m_entityToTag) + , m_serialization(scene, scene.m_serializationBlacklist, scene.m_sceneFilePath) + , m_sceneControl(scene.m_isSceneComplete, scene.m_nextScene) + , m_resources{scene.m_resourceManager, ""} + , m_debug(scene.m_debugFly, scene.m_debugInput) + , m_input() { - auto it = m_tagToEntity.find(name); - if (it == m_tagToEntity.end()) - return MAX_ENTITIES; - return it->second; } - Entity Scene::getEntityForSimulationId(SimulationID simulationId, - std::shared_ptr> rigidBodies) + ShapeId ShapeService::registerDefaultSDF(std::shared_ptr sdf) { - if (simulationId >= static_cast(rigidBodies->getSize())) - return INVALID_ENTITY; - - return rigidBodies->getEntityAtIdx(static_cast(simulationId)); + return Scene::registerDefaultSDF(std::move(sdf)); } - void Scene::saveScene(const std::string& filename) + void SerializationService::saveScene(const std::string& filename) { - SceneSerializer::save(*this, filename); + SceneSerializer::save(scene, filename); } - Scene::TagMap Scene::loadWeirdFile(const std::string& path, bool blacklistEntities) + TagMap SerializationService::loadWeirdFile(const std::string& path, bool blacklistEntities) { TagMap loadedTags; - Entity firstNewEntity = m_ecs.getEntityCount(); - SceneSerializer::load(*this, path, &loadedTags); + Entity firstNewEntity = scene.m_registry.getEntityCount(); + SceneSerializer::load(scene, path, &loadedTags); if (blacklistEntities) { - Entity lastNewEntity = m_ecs.getEntityCount(); + Entity lastNewEntity = scene.m_registry.getEntityCount(); for (Entity entity = firstNewEntity; entity < lastNewEntity; ++entity) - m_serializationBlacklist.insert(entity); + scene.m_serializationBlacklist.insert(entity); } return loadedTags; } - void Scene::loadFromWeirdFile(const std::string& path) - { - SceneSerializer::load(*this, path); - } - - Scene::RaymarchResult Scene::raymarch(glm::vec2 origin, glm::vec2 direction, float epsilon, float maxDistance) + RaymarchResult raymarchScene(Registry& registry, std::vector>& sdfs, + Simulation2D& simulation, float time, glm::vec2 origin, glm::vec2 direction, + float epsilon, float maxDistance) { float traveled = 0.0f; - float time = getTime(); - auto gridSnapshot = m_simulation2D.getSpatialGridSnapshot(); + auto gridSnapshot = simulation.getSpatialGridSnapshot(); if (epsilon <= 0.0f) { @@ -503,9 +497,9 @@ namespace WeirdEngine groups.reserve(16); // Cache the rigid bodies component array to avoid repeated lookups in the ECS during the raymarching loop - auto rigidBodies = m_ecs.getComponentArray(); + auto rigidBodies = registry.getComponentArray(); - auto shapeArray = m_ecs.getComponentArray(); + auto shapeArray = registry.getComponentArray(); for (size_t j = 0; j < shapeArray->getSize(); j++) { auto& shape = shapeArray->getDataAtIdx(j); @@ -513,7 +507,7 @@ namespace WeirdEngine if (!shape.hasCollisions) continue; - if (shape.distanceFieldId >= m_sdfs.size()) + if (shape.distanceFieldId >= sdfs.size()) continue; float parameters[11]; @@ -522,7 +516,7 @@ namespace WeirdEngine parameters[9] = p.x; parameters[10] = p.y; - float dist = m_sdfs[shape.distanceFieldId]->getValue(parameters); + float dist = sdfs[shape.distanceFieldId]->getValue(parameters); float currentMinDistance = d; Entity currentEntity = INVALID_ENTITY; @@ -613,6 +607,14 @@ namespace WeirdEngine float minRigidbodyDist = 1000.0f; Entity closestRbEntity = INVALID_ENTITY; + auto entityForSimulationId = [&](SimulationID simulationId) -> Entity + { + if (simulationId >= static_cast(rigidBodies->getSize())) + return INVALID_ENTITY; + + return rigidBodies->getEntityAtIdx(static_cast(simulationId)); + }; + int gx = static_cast(std::floor(p.x * gridSnapshot->invCellSize)); int gy = static_cast(std::floor(p.y * gridSnapshot->invCellSize)); const int TABLE_SIZE = 8191; // Must match Simulation2D.cpp @@ -643,7 +645,7 @@ namespace WeirdEngine if (dist < minRigidbodyDist) { minRigidbodyDist = dist; - closestRbEntity = getEntityForSimulationId(rbIndex, rigidBodies); + closestRbEntity = entityForSimulationId(rbIndex); } rbIndex = gridSnapshot->next[rbIndex]; @@ -712,7 +714,11 @@ namespace WeirdEngine ImGui::Separator(); - onImGuiRender(); + onImGuiRender(m_registry, m_services); + for (auto& sys : m_imguiSystems) + { + sys(m_registry, m_services); + } ImGui::PopID(); } @@ -723,14 +729,14 @@ namespace WeirdEngine ImGui::PushID(label2); - for (Entity e = 0; e < m_ecs.getEntityCount(); ++e) + for (Entity e = 0; e < m_registry.getEntityCount(); ++e) { - std::vector componentIDs = m_ecs.getComponentTypes(e); + std::vector componentIDs = m_registry.getComponentTypes(e); if (componentIDs.empty()) continue; - std::string tag = getEntityTag(e); + std::string tag = m_services.tags().getEntityTag(e); std::string label = tag.empty() ? ("Entity " + std::to_string(e)) : (tag + " (ID: " + std::to_string(e) + ")"); @@ -740,7 +746,7 @@ namespace WeirdEngine for (size_t compID : componentIDs) { - std::string compName = m_ecs.getComponentName(compID); + std::string compName = m_registry.getComponentName(compID); ImGui::BulletText("%s", compName.c_str()); } @@ -767,47 +773,6 @@ namespace WeirdEngine #endif } - Entity Scene::addShape(ShapeId shapeId, float* variables, uint16_t material, CombinationType combination, - bool hasCollision, int group) - { - Entity entity = m_ecs.createEntity(); - CustomShape& shape = m_ecs.addComponent(entity); - shape.distanceFieldId = shapeId; - shape.combination = combination; - shape.hasCollisions = hasCollision; - shape.groupIdx = group; - shape.material = material; - std::copy(variables, variables + 8, shape.parameters); - - return entity; - } - - Entity Scene::addUIShape(ShapeId shapeId, float* variables, uint16_t material, CombinationType combination, - int group) - { - Entity entity = m_ecs.createEntity(); - UIShape& shape = m_ecs.addComponent(entity); - shape.distanceFieldId = shapeId; - shape.combination = combination; - shape.groupIdx = group; - shape.material = material; - std::copy(variables, variables + 8, shape.parameters); - - return entity; - } - - UIShape& Scene::addUIShape(ShapeId shapeId, float* variables, Entity& entity, int group) - { - entity = m_ecs.createEntity(); - UIShape& component = m_ecs.addComponent(entity); - component.distanceFieldId = shapeId; - component.groupIdx = group; - component.smoothFactor = 100.0f; - std::copy(variables, variables + 8, component.parameters); - - return component; - } - Material3D& Scene::createMaterial() { if (m_materialCount >= 16) diff --git a/src/weird-engine/SceneManager.cpp b/src/weird-engine/SceneManager.cpp index ec4e05c..14c0779 100644 --- a/src/weird-engine/SceneManager.cpp +++ b/src/weird-engine/SceneManager.cpp @@ -1,5 +1,7 @@ #include "weird-engine/SceneManager.h" +#include + namespace WeirdEngine { SceneManager::SceneManager() {} @@ -23,15 +25,32 @@ namespace WeirdEngine void SceneManager::loadScene(const std::string& sceneName) { - if (sceneFactories.find(sceneName) != sceneFactories.end()) + if (sceneFactories.find(sceneName) == sceneFactories.end()) + return; + + // Keep the current/target indices in sync so a later loadNextScene() + // cycles from the scene that is actually loaded. + auto it = std::find(names.begin(), names.end(), sceneName); + if (it != names.end()) + { + currentSceneIdx = static_cast(it - names.begin()); + targetSceneIdx = currentSceneIdx; + } + + if (currentScene) { - currentScene = nullptr; - currentScene = sceneFactories[sceneName](); // Instantiate the scene - currentScene->start(); + // Main-thread cleanup hook. The physics thread may still be + // stepping, so only touch ECS/sim state from the main thread here. + currentScene->destroy(); + } + + currentScene = nullptr; + currentScene = sceneFactories[sceneName](); // Instantiate the scene + currentScene->m_services.resources().setAssetsBasePath(m_assetsPath); + currentScene->start(); #ifndef NDEBUG - WeirdEngine::Logger::log("Changed to " + sceneName + " scene"); + WeirdEngine::Logger::log("Changed to " + sceneName + " scene"); #endif - } } void SceneManager::loadScene(int idx) diff --git a/src/weird-engine/SceneSerializer.cpp b/src/weird-engine/SceneSerializer.cpp index c2773e3..7494673 100644 --- a/src/weird-engine/SceneSerializer.cpp +++ b/src/weird-engine/SceneSerializer.cpp @@ -4,6 +4,7 @@ #include "weird-physics/components/DistanceConstraint.h" #include "weird-physics/components/GlobalPhysicsSettings.h" #include "weird-physics/components/Spring.h" +#include #include #include #include @@ -20,7 +21,7 @@ namespace WeirdEngine // Save camera state { - auto& camTransform = scene.m_ecs.getComponent(scene.m_mainCamera); + auto& camTransform = scene.m_registry.getComponent(scene.m_mainCamera); j["camera"]["position"] = {camTransform.position.x, camTransform.position.y, camTransform.position.z}; j["camera"]["rotation"] = {camTransform.rotation.x, camTransform.rotation.y, camTransform.rotation.z}; j["camera"]["scale"] = {camTransform.scale.x, camTransform.scale.y, camTransform.scale.z}; @@ -33,12 +34,12 @@ namespace WeirdEngine auto isBlacklisted = [&](Entity e) { return e == scene.m_mainCamera || blacklist.count(e); }; { - auto transformArray = scene.m_ecs.getComponentArray(); - auto customShapeArray = scene.m_ecs.getComponentArray(); - auto uiShapeArray = scene.m_ecs.getComponentArray(); - auto dotArray = scene.m_ecs.getComponentArray(); - auto rigidBodyArray = scene.m_ecs.getComponentArray(); - auto textArray = scene.m_ecs.getComponentArray(); + auto transformArray = scene.m_registry.getComponentArray(); + auto customShapeArray = scene.m_registry.getComponentArray(); + auto uiShapeArray = scene.m_registry.getComponentArray(); + auto dotArray = scene.m_registry.getComponentArray(); + auto rigidBodyArray = scene.m_registry.getComponentArray(); + auto textArray = scene.m_registry.getComponentArray(); std::unordered_map entityMap; @@ -139,7 +140,7 @@ namespace WeirdEngine } // GlobalPhysicsSettings - auto globalSettingsArray = scene.m_ecs.getComponentArray(); + auto globalSettingsArray = scene.m_registry.getComponentArray(); if (globalSettingsArray) { for (size_t i = 0; i < globalSettingsArray->getSize(); i++) @@ -157,7 +158,7 @@ namespace WeirdEngine // Entity order is very important for correct SDF operations // BIG TODO: when entities are added/removed, ensure this order is maintained // (deleted entitiy ids can be reused) - for (Entity e = 0; e < scene.m_ecs.getEntityCount(); ++e) + for (Entity e = 0; e < scene.m_registry.getEntityCount(); ++e) { if (isBlacklisted(e)) continue; @@ -187,7 +188,7 @@ namespace WeirdEngine // Save physics constraints directly from ECS components { json distanceConstraintsJson = json::array(); - auto distConstraintArray = scene.m_ecs.getComponentArray(); + auto distConstraintArray = scene.m_registry.getComponentArray(); if (distConstraintArray) { for (size_t i = 0; i < distConstraintArray->getSize(); i++) @@ -202,7 +203,7 @@ namespace WeirdEngine } json springsJson = json::array(); - auto springArray = scene.m_ecs.getComponentArray(); + auto springArray = scene.m_registry.getComponentArray(); if (springArray) { for (size_t i = 0; i < springArray->getSize(); i++) @@ -221,6 +222,12 @@ namespace WeirdEngine j["physics"] = {{"distanceConstraints", distanceConstraintsJson}, {"springs", springsJson}}; } + std::filesystem::path filePath(filename); + if (filePath.has_parent_path()) + { + std::filesystem::create_directories(filePath.parent_path()); + } + std::ofstream outFile(filename); if (!outFile.is_open()) { @@ -256,12 +263,12 @@ namespace WeirdEngine // Restore camera state if (j.contains("camera")) { - auto& camTransform = scene.m_ecs.getComponent(scene.m_mainCamera); + auto& camTransform = scene.m_registry.getComponent(scene.m_mainCamera); const auto& cam = j["camera"]; if (cam.contains("position")) { camTransform.position = vec3(cam["position"][0], cam["position"][1], cam["position"][2]); - scene.m_ecs.getComponentArray()->setEntityDirty(scene.m_mainCamera, true); + scene.m_registry.getComponentArray()->setEntityDirty(scene.m_mainCamera, true); } if (cam.contains("rotation")) camTransform.rotation = vec3(cam["rotation"][0], cam["rotation"][1], cam["rotation"][2]); @@ -281,7 +288,7 @@ namespace WeirdEngine { for (const auto& ej : j["entities"]) { - Entity entity = scene.m_ecs.createEntity(); + Entity entity = scene.m_registry.createEntity(); // Track saved-id → new-entity mapping for tag remapping if (ej.contains("id")) @@ -289,7 +296,7 @@ namespace WeirdEngine if (ej.contains("transform")) { - auto& t = scene.m_ecs.addComponent(entity); + auto& t = scene.m_registry.addComponent(entity); const auto& tj = ej["transform"]; if (tj.contains("position")) t.position = vec3(tj["position"][0], tj["position"][1], tj["position"][2]); @@ -297,12 +304,12 @@ namespace WeirdEngine t.rotation = vec3(tj["rotation"][0], tj["rotation"][1], tj["rotation"][2]); if (tj.contains("scale")) t.scale = vec3(tj["scale"][0], tj["scale"][1], tj["scale"][2]); - scene.m_ecs.getComponentArray()->setEntityDirty(entity, true); + scene.m_registry.getComponentArray()->setEntityDirty(entity, true); } if (ej.contains("customShape")) { - auto& s = scene.m_ecs.addComponent(entity); + auto& s = scene.m_registry.addComponent(entity); const auto& sj = ej["customShape"]; s.distanceFieldId = static_cast(sj.value("distanceFieldId", 0)); s.combination = static_cast(sj.value("combination", 0)); @@ -315,13 +322,13 @@ namespace WeirdEngine for (int pi = 0; pi < (int)std::size(s.parameters) && pi < (int)sj["parameters"].size(); pi++) s.parameters[pi] = sj["parameters"][pi].get(); } - scene.m_ecs.getComponentArray()->setEntityDirty(entity, true); + scene.m_registry.getComponentArray()->setEntityDirty(entity, true); scene.m_2DWorldRenderContext.shapesNeedUpdate = true; } if (ej.contains("uiShape")) { - auto& s = scene.m_ecs.addComponent(entity); + auto& s = scene.m_registry.addComponent(entity); const auto& sj = ej["uiShape"]; s.distanceFieldId = static_cast(sj.value("distanceFieldId", 0)); s.combination = static_cast(sj.value("combination", 0)); @@ -338,7 +345,7 @@ namespace WeirdEngine if (ej.contains("dot")) { - auto& r = scene.m_ecs.addComponent(entity); + auto& r = scene.m_registry.addComponent(entity); const auto& rj = ej["dot"]; r.isStatic = rj.value("isStatic", false); r.materialId = static_cast(rj.value("materialId", 0)); @@ -346,27 +353,27 @@ namespace WeirdEngine if (ej.contains("textRenderer")) { - auto& tr = scene.m_ecs.addComponent(entity); + auto& tr = scene.m_registry.addComponent(entity); const auto& trj = ej["textRenderer"]; tr.text = trj.value("text", std::string{}); tr.material = static_cast(trj.value("material", 0)); tr.width = trj.value("width", 0.0f); tr.height = trj.value("height", 0.0f); - scene.m_ecs.getComponentArray()->setEntityDirty(entity, true); + scene.m_registry.getComponentArray()->setEntityDirty(entity, true); } if (ej.contains("globalPhysicsSettings")) { - auto& gs = scene.m_ecs.addComponent(entity); + auto& gs = scene.m_registry.addComponent(entity); const auto& gsj = ej["globalPhysicsSettings"]; gs.gravity = gsj.value("gravity", 0.0f); gs.damping = gsj.value("damping", 0.05f); - scene.m_ecs.getComponentArray()->setEntityDirty(entity, true); + scene.m_registry.getComponentArray()->setEntityDirty(entity, true); } if (ej.contains("rigidBody2D")) { - auto& rb = scene.m_ecs.addComponent(entity); + auto& rb = scene.m_registry.addComponent(entity); const auto& rbj = ej["rigidBody2D"]; int savedSimId = rbj.value("simulationId", -1); @@ -378,7 +385,7 @@ namespace WeirdEngine { rb.isFixed = rbj.value("isFixed", false); } - scene.m_ecs.getComponentArray()->setEntityDirty( + scene.m_registry.getComponentArray()->setEntityDirty( entity, true); // Sync velocity and fixed state to simulation if (rbj.contains("physicsPosition")) @@ -418,8 +425,8 @@ namespace WeirdEngine } else { - // Use scene's tag() method to keep both maps in sync - scene.tag(newEntity, name); + // Use service provider to keep both maps in sync + scene.m_services.tags().tag(newEntity, name); } } } @@ -465,16 +472,17 @@ namespace WeirdEngine { if (k >= 1.0f) { - Entity constraintEnt = scene.m_ecs.createEntity(); - auto& constraint = scene.m_ecs.addComponent(constraintEnt); + Entity constraintEnt = scene.m_registry.createEntity(); + auto& constraint = + scene.m_registry.addComponent(constraintEnt); constraint.entityA = entityA; constraint.entityB = entityB; constraint.distance = dist; } else { - Entity springEnt = scene.m_ecs.createEntity(); - auto& spring = scene.m_ecs.addComponent(springEnt); + Entity springEnt = scene.m_registry.createEntity(); + auto& spring = scene.m_registry.addComponent(springEnt); spring.entityA = entityA; spring.entityB = entityB; spring.stiffness = k; @@ -493,8 +501,8 @@ namespace WeirdEngine if (entityIdMap.find(savedA) != entityIdMap.end() && entityIdMap.find(savedB) != entityIdMap.end()) { - Entity springEnt = scene.m_ecs.createEntity(); - auto& spring = scene.m_ecs.addComponent(springEnt); + Entity springEnt = scene.m_registry.createEntity(); + auto& spring = scene.m_registry.addComponent(springEnt); spring.entityA = entityIdMap[savedA]; spring.entityB = entityIdMap[savedB]; spring.stiffness = spj.value("k", 1.0f); @@ -529,11 +537,11 @@ namespace WeirdEngine if (it != simIdMap.end()) { Entity e = simIdToEntityMap[savedId]; - if (scene.m_ecs.hasComponent(e)) + if (scene.m_registry.hasComponent(e)) { - auto& rb = scene.m_ecs.getComponent(e); + auto& rb = scene.m_registry.getComponent(e); rb.isFixed = true; - scene.m_ecs.getComponentArray()->setEntityDirty(e, true); + scene.m_registry.getComponentArray()->setEntityDirty(e, true); } // The actual fix will happen in PhysicsSystem2D::update thanks to isDirty=true } diff --git a/src/weird-physics/Simulation2D.cpp b/src/weird-physics/Simulation2D.cpp index 52619a2..1440c74 100644 --- a/src/weird-physics/Simulation2D.cpp +++ b/src/weird-physics/Simulation2D.cpp @@ -3,11 +3,38 @@ #include #include "glm/gtx/norm.hpp" +#include "weird-engine/Assert.h" #include "weird-engine/Logger.h" namespace WeirdEngine { + namespace + { + // True on whatever thread currently runs physics steps. Lets + // Simulation2D::isPhysicsExecutionContext() and the getPhysics*() + // assertions work in both threaded and single-threaded simulation. + thread_local bool g_inPhysicsExecution = false; + + class PhysicsExecutionScope + { + public: + PhysicsExecutionScope() + : m_previous(g_inPhysicsExecution) + { + g_inPhysicsExecution = true; + } + + ~PhysicsExecutionScope() + { + g_inPhysicsExecution = m_previous; + } + + private: + bool m_previous; + }; + } // namespace + #define MEASURE_PERFORMANCE false #define INTEGRATION_METHOD 1 @@ -17,8 +44,7 @@ namespace WeirdEngine const float EPSILON = 0.0001f; Simulation2D::Simulation2D(size_t size, const PhysicsSettings& settings) - : m_isPaused(false) - , m_positions(new vec2[size]) + : m_positions(new vec2[size]) , m_positionsRead(new vec2[size]) , m_positionsAux(new vec2[size]) , m_previousPositions(new vec2[size]) @@ -32,11 +58,10 @@ namespace WeirdEngine , m_continuousForcesWrite(new vec2[size]) , m_mass(new float[size]) , m_invMass(new float[size]) + , m_userData(new BodyUserData*[size]) , m_maxSize(size) , m_size(0) , m_allocated(0) - , m_simulationDelay(0) - , m_simulationTime(0) , m_substeps(1) , m_simulationFrequency(settings.simulationFrequency) , m_fixedDeltaTime(1.0 / static_cast(settings.simulationFrequency)) @@ -45,7 +70,6 @@ namespace WeirdEngine , m_gravity(settings.gravity) , m_push(10.0f * settings.simulationFrequency) , m_damping(settings.damping) - , m_simulating(false) , m_collisionDetectionMethod(MethodNaive) , m_useSimdOperations(false) , m_diameter(1.0f) @@ -69,6 +93,7 @@ namespace WeirdEngine m_mass[i] = 1000.0f; m_invMass[i] = 0.001f; + m_userData[i] = nullptr; } m_sdfs = std::make_shared>>(); @@ -76,6 +101,14 @@ namespace WeirdEngine Simulation2D::~Simulation2D() { + // Free any user data still attached to live bodies (the simulation + // owns these pointers; removed bodies free theirs in removeObject). + for (size_t i = 0; i < m_allocated; ++i) + { + delete m_userData[i]; + m_userData[i] = nullptr; + } + delete[] m_positions; delete[] m_positionsRead; delete[] m_positionsAux; @@ -89,6 +122,7 @@ namespace WeirdEngine delete[] m_continuousForcesWrite; delete[] m_mass; delete[] m_invMass; + delete[] m_userData; } void Simulation2D::pause() @@ -122,6 +156,8 @@ namespace WeirdEngine void Simulation2D::process() { + PhysicsExecutionScope physicsExecution; + int steps = 0; while (m_simulationDelay >= m_fixedDeltaTime && steps < MAX_STEPS) @@ -292,6 +328,39 @@ namespace WeirdEngine // std::lock_guard lock(g_simulationTimeMutex); return m_simulationTime; } + void Simulation2D::setUserData(SimulationID id, std::unique_ptr data) + { + WEIRD_ASSERT(!isPhysicsExecutionContext(), "setUserData() may not be called from physics execution context"); + + std::lock_guard lock(m_structuralMutex); + + // Bounds-check against m_allocated, not m_size: bodies can carry user + // data before they are activated (ActivatePending) later in the frame. + WEIRD_ASSERT(id < m_allocated, "setUserData() called with invalid simulation id"); + + // Free any prior data attached to this body before overwriting. + delete m_userData[id]; + m_userData[id] = data.release(); + } + + BodyUserData* Simulation2D::getUserData(SimulationID id) + { + // Inside a physics step the structural mutex is already held, so the + // read is lock-free; on the main thread it is serialized against + // structural changes (removeObject renumbering). + if (isPhysicsExecutionContext()) + { + if (id >= m_allocated) + return nullptr; + return m_userData[id]; + } + + std::lock_guard lock(m_structuralMutex); + + if (id >= m_allocated) + return nullptr; + return m_userData[id]; + } void Simulation2D::startSimulationThread() { @@ -433,7 +502,7 @@ namespace WeirdEngine // Check bool currentCollision = false; - ShapeCollisionEvent collisionEvent; + PhysicsShapeCollisionEvent collisionEvent; collisionEvent.body = static_cast(i); // Static shapes @@ -769,8 +838,8 @@ namespace WeirdEngine // Notify collision callback if (m_collisionCallback) { - CollisionEvent event{col.A, col.B}; - m_collisionCallback(event, m_callbackUserData); + PhysicsCollisionEvent event{col.A, col.B}; + m_collisionCallback(event, m_callbackUserData); // Why am I creating a new event and not saving it?????? } } @@ -954,6 +1023,7 @@ namespace WeirdEngine SimulationID Simulation2D::generateSimulationID() { std::lock_guard lock(m_structuralMutex); + std::lock_guard readLock(m_readMutex); SimulationID id = static_cast(m_allocated); @@ -973,6 +1043,7 @@ namespace WeirdEngine m_mass[id] = 1.0f; m_invMass[id] = 1.0f; m_collisionMap[id] = false; + m_userData[id] = nullptr; m_allocated++; return id; @@ -989,7 +1060,7 @@ namespace WeirdEngine void Simulation2D::removeObject(SimulationID id) { - std::scoped_lock lock(m_structuralMutex, m_externalForcesMutex, m_fixMutex); + std::scoped_lock lock(m_structuralMutex, m_externalForcesMutex, m_fixMutex, m_readMutex); if (m_size == 0 || id >= m_size) { @@ -1001,6 +1072,12 @@ namespace WeirdEngine if (toId != fromId) { + // The simulation owns user data: free the removed body's data and + // move the swapped body's data along with it. + delete m_userData[toId]; + m_userData[toId] = m_userData[fromId]; + m_userData[fromId] = nullptr; + m_positions[toId] = m_positions[fromId]; m_positionsRead[toId] = m_positionsRead[fromId]; m_positionsAux[toId] = m_positionsAux[fromId]; @@ -1020,6 +1097,12 @@ namespace WeirdEngine m_collisionMap[toId] = m_collisionMap[fromId]; } } + else + { + // Removing the last body: just free its user data. + delete m_userData[toId]; + m_userData[toId] = nullptr; + } // Fix constraints (potentially slow...) @@ -1221,6 +1304,9 @@ namespace WeirdEngine return std::find(m_fixedObjects.begin(), m_fixedObjects.end(), id) != m_fixedObjects.end(); } + // Safe from any thread (main thread, or physics callbacks while the + // physics thread runs). Prefer copyReadBuffers() when reading many + // bodies at once on the main thread. vec2 Simulation2D::getPosition(SimulationID entity) { std::lock_guard lock(m_readMutex); @@ -1243,8 +1329,12 @@ namespace WeirdEngine } } + // Safe from any thread (main thread, or physics callbacks while the + // physics thread runs). Prefer copyReadBuffers() when reading many + // bodies at once on the main thread. vec2 Simulation2D::getVelocity(SimulationID id) { + std::lock_guard lock(m_readMutex); return m_velocitiesRead[id]; } @@ -1264,11 +1354,51 @@ namespace WeirdEngine } } - void Simulation2D::updateTransform(Transform& transform, SimulationID id) + void Simulation2D::copyReadBuffers(ReadBufferSnapshot& snapshot) { + // Reading the published buffers while physics is executing would race + // against the read-buffer swap that happens at the end of every step. + WEIRD_ASSERT(!isPhysicsExecutionContext(), "copyReadBuffers() may not be called from physics execution " + "(onPhysicsStep/onCollision/onShapeCollision). " + "Use simulation.getPhysicsPosition()/getPhysicsVelocity() instead."); + std::lock_guard lock(m_readMutex); - transform.position.x = m_positionsRead[id].x; - transform.position.y = m_positionsRead[id].y; + + // Copy every allocated slot, not just the active bodies: bodies + // created this frame have ids in [m_size, m_allocated) until their + // ActivatePending command is processed, and the readback must be able + // to look up those ids. + size_t count = m_allocated; + if (snapshot.positions.size() < count) + snapshot.positions.resize(count); + if (snapshot.velocities.size() < count) + snapshot.velocities.resize(count); + + std::copy(m_positionsRead, m_positionsRead + count, snapshot.positions.begin()); + std::copy(m_velocitiesRead, m_velocitiesRead + count, snapshot.velocities.begin()); + } + + bool Simulation2D::isPhysicsExecutionContext() + { + return g_inPhysicsExecution; + } + + vec2 Simulation2D::getPhysicsPosition(SimulationID id) const + { + WEIRD_ASSERT(isPhysicsExecutionContext(), "getPhysicsPosition() may only be called from physics execution " + "(onPhysicsStep/onCollision/onShapeCollision callbacks). " + "Use simulation.getPosition() from the main thread instead."); + + return m_positions[id]; + } + + vec2 Simulation2D::getPhysicsVelocity(SimulationID id) const + { + WEIRD_ASSERT(isPhysicsExecutionContext(), "getPhysicsVelocity() may only be called from physics execution " + "(onPhysicsStep/onCollision/onShapeCollision callbacks). " + "Use simulation.getVelocity() from the main thread instead."); + + return m_velocities[id]; } void Simulation2D::setMass(SimulationID id, float mass) diff --git a/src/weird-renderer/audio/AudioEngine.cpp b/src/weird-renderer/audio/AudioEngine.cpp index de7439c..587deae 100644 --- a/src/weird-renderer/audio/AudioEngine.cpp +++ b/src/weird-renderer/audio/AudioEngine.cpp @@ -175,9 +175,6 @@ namespace WeirdEngine if (m_mute) return; - if (Input::GetKeyDown(Input::C)) - playSineSound(getPleasantFrequency(200.0f), 1.0f, 0.1f); - float frictionValue = scene.getFrictionSound(); setFrictionLevel(frictionValue); diff --git a/src/weird-renderer/core/MeshRenderPipeline.cpp b/src/weird-renderer/core/MeshRenderPipeline.cpp index 7e93487..064ca95 100644 --- a/src/weird-renderer/core/MeshRenderPipeline.cpp +++ b/src/weird-renderer/core/MeshRenderPipeline.cpp @@ -5,8 +5,6 @@ #include #endif -#include "weird-engine/Scene.h" - namespace WeirdEngine { namespace WeirdRenderer @@ -67,7 +65,8 @@ namespace WeirdEngine return m_backDepthTexture; } - void MeshRenderPipeline::render(Scene& scene, RenderTarget& outputTarget, const Camera& camera, + void MeshRenderPipeline::render(RenderTarget& outputTarget, + const std::vector& drawQueue, const Camera& camera, const std::vector& lights) { // Set GBuffer uniforms for both shaders @@ -87,7 +86,6 @@ namespace WeirdEngine glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Render scene meshes into the GBuffer. - const auto& drawQueue = scene.getDrawQueue(); for (const auto& cmd : drawQueue) { cmd.mesh->draw(m_gbufferShader, camera, cmd.translation, cmd.rotation, cmd.scale, cmd.materialIndex); diff --git a/src/weird-renderer/core/Renderer.cpp b/src/weird-renderer/core/Renderer.cpp index ec5669a..0d207f1 100644 --- a/src/weird-renderer/core/Renderer.cpp +++ b/src/weird-renderer/core/Renderer.cpp @@ -171,34 +171,12 @@ namespace WeirdEngine #ifndef WEIRD_DISABLE_IMGUI { PROFILE_SCOPE("ImGui"); - static bool showDebugUI = false; - static bool showStatsUI = false; - - if (Input::GetKeyDown(Input::F3)) - { - showDebugUI = !showDebugUI; - } - - if (Input::GetKeyDown(Input::F4)) - { - showStatsUI = !showStatsUI; - if (showStatsUI) - Profiler::get().enableRealtime(); - else - Profiler::get().disableRealtime(); - } - - if (Input::GetKeyDown(Input::F11)) - { - bool isFullscreen = (SDL_GetWindowFlags(m_window) & SDL_WINDOW_FULLSCREEN) != 0; - SDL_SetWindowFullscreen(m_window, !isFullscreen); - } ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplSDL3_NewFrame(); ImGui::NewFrame(); - if (showDebugUI) + if (m_showDebugUI) { ImGui::Begin("Engine Settings"); @@ -309,7 +287,7 @@ namespace WeirdEngine ImGui::End(); } - if (showStatsUI) + if (m_showStatsUI) { drawStatsUI(scene, delta); } @@ -768,7 +746,7 @@ namespace WeirdEngine { PROFILE_SCOPE("3D Render", enable2D); - auto& lights = scene.getLigths(); + auto& lights = scene.getLights(); // --- 1. GBuffer pass: render mesh geometry first --- // Depth testing and culling must be enabled for correct GBuffer writes. @@ -782,7 +760,8 @@ namespace WeirdEngine glFrontFace(GL_CCW); // outputTarget (SDF render target) is forwarded to Scene::onRender callbacks - m_meshPipeline->render(scene, m_3DWorldPipeline->getRenderTarget(), sceneCamera, lights); + m_meshPipeline->render(m_3DWorldPipeline->getRenderTarget(), scene.getDrawQueue(), sceneCamera, + lights); Profiler::get().gpuSync(); } @@ -803,12 +782,13 @@ namespace WeirdEngine static vec4* data3D = nullptr; scene.get3DShapesData(data3D, dataSize3D, shapeCount3D); - m_3DWorldPipeline->render(data3D, dataSize3D, shapeCount3D, lights, sceneCamera, scene.getTime(), - m_meshPipeline->getGBufferAlbedo(), m_meshPipeline->getGBufferWorldPos(), - m_meshPipeline->getGBufferNormal(), m_meshPipeline->getGBufferMaterial(), - m_meshPipeline->getDepthTexture(), m_meshPipeline->getBackDepthTexture(), - scene.getMaterials()); + SDF3DRenderPipeline::GBuffer gbuffer = { + m_meshPipeline->getGBufferAlbedo(), m_meshPipeline->getGBufferWorldPos(), + m_meshPipeline->getGBufferNormal(), m_meshPipeline->getGBufferMaterial(), + m_meshPipeline->getDepthTexture(), m_meshPipeline->getBackDepthTexture()}; + m_3DWorldPipeline->render(data3D, dataSize3D, shapeCount3D, lights, sceneCamera, scene.getTime(), + gbuffer, scene.getMaterials()); glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LEQUAL); @@ -852,4 +832,35 @@ namespace WeirdEngine } } // namespace WeirdRenderer +} // namespace WeirdEngine +namespace WeirdEngine +{ + namespace WeirdRenderer + { + void Renderer::handleEvent(const SDL_Event& event) + { + if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat) + { + switch (event.key.key) + { + case SDLK_F3: + m_showDebugUI = !m_showDebugUI; + break; + case SDLK_F4: + m_showStatsUI = !m_showStatsUI; + if (m_showStatsUI) + Profiler::get().enableRealtime(); + else + Profiler::get().disableRealtime(); + break; + case SDLK_F11: + { + bool isFullscreen = (SDL_GetWindowFlags(m_window) & SDL_WINDOW_FULLSCREEN) != 0; + SDL_SetWindowFullscreen(m_window, !isFullscreen); + break; + } + } + } + } + } // namespace WeirdRenderer } // namespace WeirdEngine \ No newline at end of file diff --git a/src/weird-renderer/core/SDF3DRenderPipeline.cpp b/src/weird-renderer/core/SDF3DRenderPipeline.cpp index 779bf73..86325ca 100644 --- a/src/weird-renderer/core/SDF3DRenderPipeline.cpp +++ b/src/weird-renderer/core/SDF3DRenderPipeline.cpp @@ -56,9 +56,7 @@ namespace WeirdEngine void SDF3DRenderPipeline::render(vec4* shapeData, uint32_t dataSize, uint32_t shapeCount, const std::vector& lights, const Camera& camera, double time, - Texture& gbufferAlbedo, Texture& gbufferWorldPos, Texture& gbufferNormal, - Texture& gbufferMaterial, Texture& gbufferDepth, Texture& gbufferBackDepth, - const Material3D* materials) + const GBuffer& gbuffer, const Material3D* materials) { // Reset frame counter when path tracer is disabled (no accumulation) if (!m_config.enablePathTracer) @@ -117,7 +115,7 @@ namespace WeirdEngine m_accumTexture[previousAccumIdx].bind(0); m_sdfShader.setUniform("t_depthTexture", 1); - gbufferDepth.bind(1); + gbuffer.depth.bind(1); // Shape data buffer m_sdfShader.setUniform("t_shapeBuffer", 2); @@ -126,16 +124,16 @@ namespace WeirdEngine // GBuffer colour attachments m_sdfShader.setUniform("t_gbufferAlbedo", 3); - gbufferAlbedo.bind(3); + gbuffer.albedo.bind(3); m_sdfShader.setUniform("t_gbufferWorldPos", 4); - gbufferWorldPos.bind(4); + gbuffer.worldPos.bind(4); m_sdfShader.setUniform("t_gbufferNormal", 5); - gbufferNormal.bind(5); + gbuffer.normal.bind(5); m_sdfShader.setUniform("t_gbufferMaterial", 6); - gbufferMaterial.bind(6); + gbuffer.material.bind(6); m_sdfShader.setUniform("t_gbufferBackDepth", 7); - gbufferBackDepth.bind(7); + gbuffer.backDepth.bind(7); m_sdfShader.setUniform("u_loadedObjects", (int)dataSize); m_sdfShader.setUniform("u_customShapeCount", (int)shapeCount); diff --git a/tools/molecule-editor/include/MoleculeEditor.h b/tools/molecule-editor/include/MoleculeEditor.h index c170cc9..2eaa4fe 100644 --- a/tools/molecule-editor/include/MoleculeEditor.h +++ b/tools/molecule-editor/include/MoleculeEditor.h @@ -28,7 +28,8 @@ class MoleculeEditor : public Scene2D public: MoleculeEditor() {} - ECSManager* m_tempEcs = nullptr; + Registry* m_tempRegistry = nullptr; + ServiceProvider* m_tempSvc = nullptr; private: enum class RightMouseMode @@ -125,51 +126,57 @@ class MoleculeEditor : public Scene2D static constexpr float TAG_INNER_RADIUS = 25.0f; static constexpr int TAG_RING_GROUP = 8; - void onStart(ECSManager& ecs) override + void onStart(Registry& registry, ServiceProvider& services) override { - m_tempEcs = &ecs; - m_debugFly = true; + m_tempRegistry = ®istry; + m_tempSvc = &services; + m_tempSvc->debug().setDebugFly(true); g_cameraPositon.x = 0.0f; g_cameraPositon.y = 0.0f; - m_tempEcs->getComponent(m_mainCamera).position = g_cameraPositon; + m_tempRegistry->getComponent(m_tempSvc->render().getCameraEntity()).position = g_cameraPositon; // Request neutral simulation behavior for this editor scene. - Entity globalSettingsEnt = m_tempEcs->createEntity(); - auto& settings = m_tempEcs->addComponent(globalSettingsEnt); + Entity globalSettingsEnt = m_tempRegistry->createEntity(); + auto& settings = m_tempRegistry->addComponent(globalSettingsEnt); settings.gravity = 0.0f; settings.damping = 1.0f; - m_tempEcs->setComponentDirty(settings); + m_tempRegistry->setComponentDirty(settings); buildMaterialPalette(); buildToolbar(); buildTagEditorUI(); { - float boundsVars[8]{0.0f, 0.0f, 3000.0f}; - Entity outside = addShape(DefaultShapes::CIRCLE, boundsVars, 17, CombinationType::Addition); - - float boundsVars2[8]{0.0f, 0.0f, 20.0f, 20.0f}; - Entity inside = - addShape(DefaultShapes::BOX, boundsVars2, DisplaySettings::Black, CombinationType::Subtraction); - - blacklistEntity(outside); - blacklistEntity(inside); + Entity outside = m_tempSvc->shapes().addShape({.shapeId = DefaultShapes::CIRCLE, + .variables = {0.0f, 0.0f, 3000.0f}, + .material = 17, + .combination = CombinationType::Addition}); + + Entity inside = m_tempSvc->shapes().addShape({.shapeId = DefaultShapes::BOX, + .variables = {0.0f, 0.0f, 20.0f, 20.0f}, + .material = static_cast(DisplaySettings::Black), + .combination = CombinationType::Subtraction}); + + m_tempSvc->serialization().blacklistEntity(outside); + m_tempSvc->serialization().blacklistEntity(inside); } } - void onUpdate(float delta, ECSManager& ecs) override + void onUpdate(Registry& registry, ServiceProvider& services) override { - m_tempEcs = &ecs; - g_cameraPositon = m_tempEcs->getComponent(m_mainCamera).position; + m_tempRegistry = ®istry; + m_tempSvc = &services; + g_cameraPositon = m_tempRegistry->getComponent(m_tempSvc->render().getCameraEntity()).position; - if (Input::GetKeyDown(Input::Q) || Input::GetGamepadButtonDown(Input::GamepadButton::North)) + if (m_tempSvc->input().getKeyDown(Input::Q) || + m_tempSvc->input().getGamepadButtonDown(Input::GamepadButton::North)) { - setSceneComplete(); + m_tempSvc->sceneControl().goToNextScene(); return; } - if (Input::GetKey(Input::LeftCtrl) && Input::GetKeyDown(Input::S)) + if (m_tempSvc->input().getKey(Input::LeftCtrl) && m_tempSvc->input().getKeyDown(Input::S)) { WeirdEngine::Logger::log("Save scene name: "); @@ -185,11 +192,11 @@ class MoleculeEditor : public Scene2D { fileName += ".weird"; } - saveScene(ASSETS_PATH "Organisms/" + fileName); + m_tempSvc->serialization().saveScene(m_tempSvc->resources().assetPath("Organisms/") + fileName); } } - if (Input::GetKey(Input::LeftCtrl) && Input::GetKeyDown(Input::L)) + if (m_tempSvc->input().getKey(Input::LeftCtrl) && m_tempSvc->input().getKeyDown(Input::L)) { WeirdEngine::Logger::log("Load scene name: "); @@ -205,11 +212,11 @@ class MoleculeEditor : public Scene2D { fileName += ".weird"; } - loadMolecule(ASSETS_PATH "Organisms/" + fileName); + loadMolecule(m_tempSvc->resources().assetPath("Organisms/") + fileName); } } - if (Input::GetMouseButtonDown(Input::LeftClick) && !Input::isUIClick()) + if (m_tempSvc->input().getMouseButtonDown(Input::LeftClick) && !m_tempSvc->input().isUIClick()) { spawnBallAtMouse(); } @@ -247,7 +254,7 @@ class MoleculeEditor : public Scene2D continue; } - const auto& t = m_tempEcs->getComponent(b.entity); + const auto& t = m_tempRegistry->getComponent(b.entity); if (t.position.y < -1000.0f) { toDelete.push_back(b.entity); @@ -262,7 +269,7 @@ class MoleculeEditor : public Scene2D for (Entity e : toDelete) { - m_tempEcs->destroyEntity(e); + m_tempRegistry->destroyEntity(e); } m_links.erase(std::remove_if(m_links.begin(), m_links.end(), @@ -273,7 +280,7 @@ class MoleculeEditor : public Scene2D { if (m_draggedLink == &link) m_draggedLink = nullptr; - m_tempEcs->destroyEntity(link.lineEntity); + m_tempRegistry->destroyEntity(link.lineEntity); } return remove; }), @@ -309,20 +316,19 @@ class MoleculeEditor : public Scene2D { float px = START_X + i * MAT_SPACING; float p[8]{px, MAT_Y, BTN_SIZE - 4.0f}; - Entity e; - UIShape& sh = addUIShape(DefaultShapes::CIRCLE, p, e); - sh.material = static_cast(i); + Entity e = m_tempSvc->shapes().addUIShape( + {.shapeId = DefaultShapes::CIRCLE, .variables = p, .material = static_cast(i)}); - auto& tog = m_tempEcs->addComponent(e); + auto& tog = m_tempRegistry->addComponent(e); tog.clickPadding = BTN_SIZE + 3.0f; tog.parameterModifierMask.set(2); tog.modifierAmount = 5.0f; m_materialToggles[i] = e; - blacklistEntity(e); + m_tempSvc->serialization().blacklistEntity(e); } - m_tempEcs->getComponent(m_materialToggles[m_selectedMaterial]).active = true; + m_tempRegistry->getComponent(m_materialToggles[m_selectedMaterial]).active = true; } void syncMaterialPalette() @@ -330,7 +336,7 @@ class MoleculeEditor : public Scene2D int activated = -1; for (int i = 0; i < 16; i++) { - auto& t = m_tempEcs->getComponent(m_materialToggles[i]); + auto& t = m_tempRegistry->getComponent(m_materialToggles[i]); if (t.active && t.state == ButtonState::Down) { activated = i; @@ -345,7 +351,7 @@ class MoleculeEditor : public Scene2D { if (i != activated) { - m_tempEcs->getComponent(m_materialToggles[i]).active = false; + m_tempRegistry->getComponent(m_materialToggles[i]).active = false; } } } @@ -353,7 +359,7 @@ class MoleculeEditor : public Scene2D { for (int i = 0; i < 16; i++) { - if (m_tempEcs->getComponent(m_materialToggles[i]).active) + if (m_tempRegistry->getComponent(m_materialToggles[i]).active) { m_selectedMaterial = i; break; @@ -369,44 +375,48 @@ class MoleculeEditor : public Scene2D { float y = (Display::height - TOOL_Y_START) - (i * TOOL_SPACING); float p[8]{TOOL_X, y, TOOL_BTN_HALF, TOOL_BTN_HALF}; - Entity e = addUIShape(DefaultShapes::BOX, p, static_cast(2)); - auto& tog = m_tempEcs->addComponent(e); + Entity e = m_tempSvc->shapes().addUIShape({.shapeId = DefaultShapes::BOX, .variables = p, .material = 2}); + auto& tog = m_tempRegistry->addComponent(e); tog.clickPadding = TOOL_BTN_HALF + 8.0f; tog.parameterModifierMask.set(2); tog.parameterModifierMask.set(3); tog.modifierAmount = 3.0f; m_toolToggles[i] = e; - blacklistEntity(e); + m_tempSvc->serialization().blacklistEntity(e); - Entity lbl = m_tempEcs->createEntity(); - auto& lt = m_tempEcs->addComponent(lbl); + Entity lbl = m_tempRegistry->createEntity(); + auto& lt = m_tempRegistry->addComponent(lbl); lt.position = vec3(TOOL_X + TOOL_BTN_HALF + 20.0f, y, 0.0f); - auto& tx = m_tempEcs->addComponent(lbl); + auto& tx = m_tempRegistry->addComponent(lbl); tx.text = labels[i]; tx.material = 1; tx.horizontalAlignment = TextRenderer::HorizontalAlignment::Left; tx.verticalAlignment = TextRenderer::VerticalAlignment::Center; - blacklistEntity(lbl); + m_tempSvc->serialization().blacklistEntity(lbl); } - m_tempEcs->getComponent(m_toolToggles[0]).active = true; + m_tempRegistry->getComponent(m_toolToggles[0]).active = true; - float starP[8]{Display::width - GRAV_Y, Display::height - GRAV_Y, GRAV_Y * 0.5f, 5.0f, 10.0f, 0.0f}; - m_gravityToggleEntity = addUIShape(DefaultShapes::STAR, starP, static_cast(2)); - auto& gravTog = m_tempEcs->addComponent(m_gravityToggleEntity); + m_gravityToggleEntity = m_tempSvc->shapes().addUIShape( + {.shapeId = DefaultShapes::STAR, + .variables = {Display::width - GRAV_Y, Display::height - GRAV_Y, GRAV_Y * 0.5f, 5.0f, 10.0f, 0.0f}, + .material = 2}); + auto& gravTog = m_tempRegistry->addComponent(m_gravityToggleEntity); gravTog.clickPadding = 18.0f; // gravTog.parameterModifierMask.set(2); gravTog.parameterModifierMask.set(5); gravTog.modifierAmount = 10.0f; - blacklistEntity(m_gravityToggleEntity); + m_tempSvc->serialization().blacklistEntity(m_gravityToggleEntity); - float gridP[8]{Display::width - GRAV_Y, Display::height - GRID_Y, 12.0f, 12.0f}; - m_gridToggleEntity = addUIShape(DefaultShapes::BOX, gridP, static_cast(2)); - auto& gridTog = m_tempEcs->addComponent(m_gridToggleEntity); + m_gridToggleEntity = m_tempSvc->shapes().addUIShape( + {.shapeId = DefaultShapes::BOX, + .variables = {Display::width - GRAV_Y, Display::height - GRID_Y, 12.0f, 12.0f}, + .material = 2}); + auto& gridTog = m_tempRegistry->addComponent(m_gridToggleEntity); gridTog.clickPadding = 18.0f; gridTog.parameterModifierMask.set(2); gridTog.parameterModifierMask.set(3); gridTog.modifierAmount = 3.0f; - blacklistEntity(m_gridToggleEntity); + m_tempSvc->serialization().blacklistEntity(m_gridToggleEntity); } void syncToolbar() @@ -414,7 +424,7 @@ class MoleculeEditor : public Scene2D int activated = -1; for (int i = 0; i < 6; i++) { - auto& t = m_tempEcs->getComponent(m_toolToggles[i]); + auto& t = m_tempRegistry->getComponent(m_toolToggles[i]); if (t.active && t.state == ButtonState::Down) { activated = i; @@ -427,17 +437,17 @@ class MoleculeEditor : public Scene2D for (int i = 0; i < 6; i++) { if (i != activated) - m_tempEcs->getComponent(m_toolToggles[i]).active = false; + m_tempRegistry->getComponent(m_toolToggles[i]).active = false; } } - auto& gravTog = m_tempEcs->getComponent(m_gravityToggleEntity); + auto& gravTog = m_tempRegistry->getComponent(m_gravityToggleEntity); bool wantsGravity = gravTog.active; if (wantsGravity != m_gravityEnabled) { m_gravityEnabled = wantsGravity; - auto& starShape = m_tempEcs->getComponent(m_gravityToggleEntity); - auto globalSettingsArray = m_tempEcs->getComponentArray(); + auto& starShape = m_tempRegistry->getComponent(m_gravityToggleEntity); + auto globalSettingsArray = m_tempRegistry->getComponentArray(); auto& settings = globalSettingsArray->getDataAtIdx(0); if (m_gravityEnabled) { @@ -449,38 +459,40 @@ class MoleculeEditor : public Scene2D settings.gravity = 0.0f; settings.damping = 1.0f; } - m_tempEcs->setComponentDirty(settings); + m_tempRegistry->setComponentDirty(settings); } - m_gridMode = m_tempEcs->getComponent(m_gridToggleEntity).active; + m_gridMode = m_tempRegistry->getComponent(m_gridToggleEntity).active; } void spawnBallAtMouse() { - auto& cam = m_tempEcs->getComponent(m_mainCamera); - vec2 world = ECS::Camera::screenPositionToWorldPosition2D(cam, vec2(Input::GetMouseX(), Input::GetMouseY())); + auto& cam = m_tempRegistry->getComponent(m_tempSvc->render().getCameraEntity()); + vec2 world = ECS::Camera::screenPositionToWorldPosition2D( + cam, vec2(m_tempSvc->input().getMouseX(), m_tempSvc->input().getMouseY())); if (m_gridMode) world = snapToGrid(world); - Entity e = m_tempEcs->createEntity(); + Entity e = m_tempRegistry->createEntity(); - auto& t = m_tempEcs->addComponent(e); + auto& t = m_tempRegistry->addComponent(e); t.position = vec3(world.x, world.y, 0.0f); - m_tempEcs->setComponentDirty(t); + m_tempRegistry->setComponentDirty(t); - auto& sdf = m_tempEcs->addComponent(e); + auto& sdf = m_tempRegistry->addComponent(e); sdf.materialId = static_cast(m_selectedMaterial); - auto& rb = m_tempEcs->addComponent(e); + auto& rb = m_tempRegistry->addComponent(e); m_balls.push_back({e, static_cast(rb.simulationId)}); } vec2 getMouseWorldPosition() { - auto& cam = m_tempEcs->getComponent(m_mainCamera); - return ECS::Camera::screenPositionToWorldPosition2D(cam, vec2(Input::GetMouseX(), Input::GetMouseY())); + auto& cam = m_tempRegistry->getComponent(m_tempSvc->render().getCameraEntity()); + return ECS::Camera::screenPositionToWorldPosition2D( + cam, vec2(m_tempSvc->input().getMouseX(), m_tempSvc->input().getMouseY())); } vec2 snapToGrid(vec2 pos, Entity exclude = static_cast(-1)) @@ -533,9 +545,9 @@ class MoleculeEditor : public Scene2D { if (b.entity == exclude) continue; - if (!m_tempEcs->hasComponent(b.entity)) + if (!m_tempRegistry->hasComponent(b.entity)) continue; - const auto& t = m_tempEcs->getComponent(b.entity); + const auto& t = m_tempRegistry->getComponent(b.entity); vec2 p(t.position.x, t.position.y); if (std::abs(p.x - cell.x) < threshold && std::abs(p.y - cell.y) < threshold) return true; @@ -545,7 +557,7 @@ class MoleculeEditor : public Scene2D void handleRightMouseDragInput() { - bool rightDown = Input::GetMouseButton(Input::RightClick); + bool rightDown = m_tempSvc->input().getMouseButton(Input::RightClick); if (rightDown && !m_rightWasDown) { @@ -610,16 +622,16 @@ class MoleculeEditor : public Scene2D m_draggedSimulationId = id; m_keepFixedAfterDrag = false; - auto& rb = m_tempEcs->getComponent(m_draggedBall); + auto& rb = m_tempRegistry->getComponent(m_draggedBall); rb.isFixed = true; - m_tempEcs->setComponentDirty(rb); + m_tempRegistry->setComponentDirty(rb); vec2 startPos = getMouseWorldPosition(); if (m_gridMode) startPos = snapToGrid(startPos, m_draggedBall); - auto& t = m_tempEcs->getComponent(m_draggedBall); + auto& t = m_tempRegistry->getComponent(m_draggedBall); t.position = vec3(startPos.x, startPos.y, 0.0f); - m_tempEcs->setComponentDirty(t); + m_tempRegistry->setComponentDirty(t); } void onRightDragUpdate() @@ -627,7 +639,7 @@ class MoleculeEditor : public Scene2D if (m_draggedBall == static_cast(-1) || m_draggedSimulationId < 0) return; - if (Input::GetKeyDown(Input::F)) + if (m_tempSvc->input().getKeyDown(Input::F)) { m_keepFixedAfterDrag = true; } @@ -635,9 +647,9 @@ class MoleculeEditor : public Scene2D vec2 dragPos = getMouseWorldPosition(); if (m_gridMode) dragPos = snapToGrid(dragPos, m_draggedBall); - auto& t = m_tempEcs->getComponent(m_draggedBall); + auto& t = m_tempRegistry->getComponent(m_draggedBall); t.position = vec3(dragPos.x, dragPos.y, 0.0f); - m_tempEcs->setComponentDirty(t); + m_tempRegistry->setComponentDirty(t); } void onRightDragEnd() @@ -647,9 +659,9 @@ class MoleculeEditor : public Scene2D if (!m_keepFixedAfterDrag) { - auto& rb = m_tempEcs->getComponent(m_draggedBall); + auto& rb = m_tempRegistry->getComponent(m_draggedBall); rb.isFixed = false; - m_tempEcs->setComponentDirty(rb); + m_tempRegistry->setComponentDirty(rb); } m_draggedBall = static_cast(-1); @@ -691,8 +703,9 @@ class MoleculeEditor : public Scene2D Entity pickBallAtMouse() { - auto& cam = m_tempEcs->getComponent(m_mainCamera); - vec2 world = ECS::Camera::screenPositionToWorldPosition2D(cam, vec2(Input::GetMouseX(), Input::GetMouseY())); + auto& cam = m_tempRegistry->getComponent(m_tempSvc->render().getCameraEntity()); + vec2 world = ECS::Camera::screenPositionToWorldPosition2D( + cam, vec2(m_tempSvc->input().getMouseX(), m_tempSvc->input().getMouseY())); float best = BALL_HIT_RADIUS; Entity bestEntity = static_cast(-1); @@ -702,7 +715,7 @@ class MoleculeEditor : public Scene2D if (!hasTransform(b.entity)) continue; - const auto& t = m_tempEcs->getComponent(b.entity); + const auto& t = m_tempRegistry->getComponent(b.entity); vec2 p(t.position.x, t.position.y); float d = length(world - p); if (d < best) @@ -740,24 +753,24 @@ class MoleculeEditor : public Scene2D if (idA < 0 || idB < 0) return; - auto& ta = m_tempEcs->getComponent(a); - auto& tb = m_tempEcs->getComponent(b); + auto& ta = m_tempRegistry->getComponent(a); + auto& tb = m_tempRegistry->getComponent(b); vec2 pa(ta.position.x, ta.position.y); vec2 pb(tb.position.x, tb.position.y); float restDistance = length(pb - pa); restDistance = (std::max)(restDistance, 1.0f); - Entity constraintEnt = m_tempEcs->createEntity(); + Entity constraintEnt = m_tempRegistry->createEntity(); if (type == LinkType::Distance) { - auto& constraint = m_tempEcs->addComponent(constraintEnt); + auto& constraint = m_tempRegistry->addComponent(constraintEnt); constraint.entityA = a; constraint.entityB = b; constraint.distance = restDistance; } else { - auto& spring = m_tempEcs->addComponent(constraintEnt); + auto& spring = m_tempRegistry->addComponent(constraintEnt); spring.entityA = a; spring.entityB = b; spring.stiffness = SPRING_STIFFNESS; @@ -768,13 +781,14 @@ class MoleculeEditor : public Scene2D float lineVars[8]{}; computeScreenLineParams(pa, pb, lineVars); - Entity line = addUIShape(DefaultShapes::LINE, lineVars, lineColor); + Entity line = m_tempSvc->shapes().addUIShape( + {.shapeId = DefaultShapes::LINE, .variables = lineVars, .material = static_cast(lineColor)}); - auto& btn = m_tempEcs->addComponent(line); + auto& btn = m_tempRegistry->addComponent(line); btn.clickPadding = 8.0f; btn.modifierAmount = 0.0f; - blacklistEntity(line); + m_tempSvc->serialization().blacklistEntity(line); m_links.push_back({a, b, idA, idB, restDistance, line, type, constraintEnt}); } @@ -792,12 +806,12 @@ class MoleculeEditor : public Scene2D continue; } - m_tempEcs->destroyEntity(link.constraintEntity); + m_tempRegistry->destroyEntity(link.constraintEntity); if (m_draggedLink == &link) m_draggedLink = nullptr; - m_tempEcs->destroyEntity(link.lineEntity); + m_tempRegistry->destroyEntity(link.lineEntity); m_links.erase(m_links.begin() + i); } } @@ -805,17 +819,17 @@ class MoleculeEditor : public Scene2D void handleConstraintLineClicks() { // Detect click-down via ShapeButton state. - // ButtonSystem already calls Input::flagUIClick() when a line is clicked, + // ButtonSystem already calls m_tempSvc->input().flagUIClick() when a line is clicked, // so ball spawning is suppressed automatically. for (auto& link : m_links) { if (!hasShapeButton(link.lineEntity)) continue; - auto& btn = m_tempEcs->getComponent(link.lineEntity); + auto& btn = m_tempRegistry->getComponent(link.lineEntity); if (btn.state == ButtonState::Down) { m_draggedLink = &link; - m_linkDragStartX = Input::GetMouseX(); + m_linkDragStartX = m_tempSvc->input().getMouseX(); m_linkDragStartDist = link.restDistance; m_dragLinkSimIdA.store(link.simulationIdA, std::memory_order_relaxed); m_dragLinkSimIdB.store(link.simulationIdB, std::memory_order_relaxed); @@ -823,7 +837,7 @@ class MoleculeEditor : public Scene2D } } - if (!Input::GetMouseButton(Input::LeftClick)) + if (!m_tempSvc->input().getMouseButton(Input::LeftClick)) { m_draggedLink = nullptr; return; @@ -832,7 +846,7 @@ class MoleculeEditor : public Scene2D if (m_draggedLink == nullptr) return; - float dx = (Input::GetMouseX() - m_linkDragStartX) * 0.3f; + float dx = (m_tempSvc->input().getMouseX() - m_linkDragStartX) * 0.3f; float newDist = std::round((m_linkDragStartDist + dx) * 10.0f) / 10.0f; newDist = (std::clamp)(newDist, 1.0f, 10.0f); m_draggedLink->restDistance = newDist; @@ -843,22 +857,22 @@ class MoleculeEditor : public Scene2D void updateConstraintLines() { - auto& cam = m_tempEcs->getComponent(m_mainCamera); + auto& cam = m_tempRegistry->getComponent(m_tempSvc->render().getCameraEntity()); for (auto& link : m_links) { if (!hasTransform(link.a) || !hasTransform(link.b) || !hasUIShape(link.lineEntity)) continue; - const auto& ta = m_tempEcs->getComponent(link.a); - const auto& tb = m_tempEcs->getComponent(link.b); + const auto& ta = m_tempRegistry->getComponent(link.a); + const auto& tb = m_tempRegistry->getComponent(link.b); vec2 aWorld(ta.position.x, ta.position.y); vec2 bWorld(tb.position.x, tb.position.y); vec2 aScreen = ECS::Camera::worldPosition2DToScreenPosition(cam, aWorld); vec2 bScreen = ECS::Camera::worldPosition2DToScreenPosition(cam, bWorld); - auto& ui = m_tempEcs->getComponent(link.lineEntity); + auto& ui = m_tempRegistry->getComponent(link.lineEntity); ui.parameters[0] = aScreen.x; ui.parameters[1] = aScreen.y; ui.parameters[2] = bScreen.x; @@ -869,7 +883,7 @@ class MoleculeEditor : public Scene2D void computeScreenLineParams(const vec2& aWorld, const vec2& bWorld, float outParams[8]) { - auto& cam = m_tempEcs->getComponent(m_mainCamera); + auto& cam = m_tempRegistry->getComponent(m_tempSvc->render().getCameraEntity()); vec2 aScreen = ECS::Camera::worldPosition2DToScreenPosition(cam, aWorld); vec2 bScreen = ECS::Camera::worldPosition2DToScreenPosition(cam, bWorld); @@ -896,30 +910,41 @@ class MoleculeEditor : public Scene2D void buildTagEditorUI() { - // Tag label – shows "tag: " or "tag: (none)" + // Create label text entity for the selection tag + m_tagLabelEntity = m_tempRegistry->createEntity(); + auto& lt = m_tempRegistry->addComponent(m_tagLabelEntity); + lt.position = vec3(Display::width * 0.5f, 115.0f, 0.0f); + + auto& tx = m_tempRegistry->addComponent(m_tagLabelEntity); + tx.text = ""; + tx.material = static_cast(DisplaySettings::Yellow); + tx.horizontalAlignment = TextRenderer::HorizontalAlignment::Center; + tx.verticalAlignment = TextRenderer::VerticalAlignment::Center; + m_tempSvc->serialization().blacklistEntity(m_tagLabelEntity); + + // Label above the edit button { - Entity lbl = m_tempEcs->createEntity(); - auto& lt = m_tempEcs->addComponent(lbl); - lt.position = vec3(Display::width * 0.5f, 150.0f, 0.0f); - auto& tx = m_tempEcs->addComponent(lbl); - tx.text = ""; - tx.material = 1; - tx.horizontalAlignment = TextRenderer::HorizontalAlignment::Center; - tx.verticalAlignment = TextRenderer::VerticalAlignment::Center; - m_tagLabelEntity = lbl; - blacklistEntity(lbl); + Entity btnLbl = m_tempRegistry->createEntity(); + auto& blt = m_tempRegistry->addComponent(btnLbl); + blt.position = vec3(Display::width * 0.5f, 90.0f, 0.0f); + auto& btx = m_tempRegistry->addComponent(btnLbl); + btx.text = "edit tag"; + btx.material = 1; + btx.horizontalAlignment = TextRenderer::HorizontalAlignment::Center; + btx.verticalAlignment = TextRenderer::VerticalAlignment::Center; + m_tempSvc->serialization().blacklistEntity(btnLbl); } // "edit tag" button (a small box) { static constexpr float BW = 40.0f; static constexpr float BH = 14.0f; - float p[8]{Display::width * 0.5f, 90.0f, BW, BH}; - m_tagEditButton = addUIShape(DefaultShapes::BOX, p, static_cast(2)); - auto& btn = m_tempEcs->addComponent(m_tagEditButton); + m_tagEditButton = m_tempSvc->shapes().addUIShape( + {.shapeId = DefaultShapes::BOX, .variables = {Display::width * 0.5f, 90.0f, BW, BH}, .material = 2}); + auto& btn = m_tempRegistry->addComponent(m_tagEditButton); btn.clickPadding = 6.0f; btn.modifierAmount = 0.0f; - blacklistEntity(m_tagEditButton); + m_tempSvc->serialization().blacklistEntity(m_tagEditButton); } } @@ -930,12 +955,12 @@ class MoleculeEditor : public Scene2D { if (m_tagCircleOuter != static_cast(-1)) { - m_tempEcs->getComponent(m_tagCircleOuter).parameters[2] = 0.0f; - m_tempEcs->getComponent(m_tagCircleInner).parameters[2] = 0.0f; + m_tempRegistry->getComponent(m_tagCircleOuter).parameters[2] = 0.0f; + m_tempRegistry->getComponent(m_tagCircleInner).parameters[2] = 0.0f; } if (m_tagLabelEntity != static_cast(-1)) { - m_tempEcs->getComponent(m_tagLabelEntity).text = ""; + m_tempRegistry->getComponent(m_tagLabelEntity).text = ""; } return; } @@ -950,26 +975,34 @@ class MoleculeEditor : public Scene2D if (m_tagCircleOuter == static_cast(-1)) { float p[8]{}; - m_tagCircleOuter = addUIShape(DefaultShapes::CIRCLE, p, static_cast(DisplaySettings::Yellow), - CombinationType::Addition, TAG_RING_GROUP); - blacklistEntity(m_tagCircleOuter); - - m_tagCircleInner = addUIShape(DefaultShapes::CIRCLE, p, static_cast(DisplaySettings::Yellow), - CombinationType::Subtraction, TAG_RING_GROUP); - blacklistEntity(m_tagCircleInner); + m_tagCircleOuter = + m_tempSvc->shapes().addUIShape({.shapeId = DefaultShapes::CIRCLE, + .variables = p, + .material = static_cast(DisplaySettings::Yellow), + .combination = CombinationType::Addition, + .group = TAG_RING_GROUP}); + m_tempSvc->serialization().blacklistEntity(m_tagCircleOuter); + + m_tagCircleInner = + m_tempSvc->shapes().addUIShape({.shapeId = DefaultShapes::CIRCLE, + .variables = p, + .material = static_cast(DisplaySettings::Yellow), + .combination = CombinationType::Subtraction, + .group = TAG_RING_GROUP}); + m_tempSvc->serialization().blacklistEntity(m_tagCircleInner); } - auto& cam = m_tempEcs->getComponent(m_mainCamera); - const auto& ht = m_tempEcs->getComponent(m_tagSelectedEntity); + auto& cam = m_tempRegistry->getComponent(m_tempSvc->render().getCameraEntity()); + const auto& ht = m_tempRegistry->getComponent(m_tagSelectedEntity); vec2 world(ht.position.x, ht.position.y); vec2 screen = ECS::Camera::worldPosition2DToScreenPosition(cam, world); - auto& outer = m_tempEcs->getComponent(m_tagCircleOuter); + auto& outer = m_tempRegistry->getComponent(m_tagCircleOuter); outer.parameters[0] = screen.x; outer.parameters[1] = screen.y; outer.parameters[2] = TAG_OUTER_RADIUS; - auto& inner = m_tempEcs->getComponent(m_tagCircleInner); + auto& inner = m_tempRegistry->getComponent(m_tagCircleInner); inner.parameters[0] = screen.x; inner.parameters[1] = screen.y; inner.parameters[2] = TAG_INNER_RADIUS; @@ -977,13 +1010,13 @@ class MoleculeEditor : public Scene2D // Update the tag label text if (m_tagLabelEntity != static_cast(-1)) { - std::string currentTag = getEntityTag(m_tagSelectedEntity); - auto& tx = m_tempEcs->getComponent(m_tagLabelEntity); + std::string currentTag = m_tempSvc->tags().getEntityTag(m_tagSelectedEntity); + auto& tx = m_tempRegistry->getComponent(m_tagLabelEntity); std::string newText = currentTag.empty() ? "tag: (none)" : ("tag: " + currentTag); if (tx.text != newText) { tx.text = newText; - m_tempEcs->setComponentDirty(tx); + m_tempRegistry->setComponentDirty(tx); } } @@ -992,7 +1025,7 @@ class MoleculeEditor : public Scene2D // as required by the design of this editor scene. if (m_tagEditButton != static_cast(-1)) { - auto& btn = m_tempEcs->getComponent(m_tagEditButton); + auto& btn = m_tempRegistry->getComponent(m_tagEditButton); if (btn.state == ButtonState::Down) { WeirdEngine::Logger::log("Enter new tag (empty to remove): "); @@ -1000,11 +1033,11 @@ class MoleculeEditor : public Scene2D std::getline(std::cin, newTag); if (newTag.empty()) { - removeTag(m_tagSelectedEntity); + m_tempSvc->tags().removeTag(m_tagSelectedEntity); } else { - tag(m_tagSelectedEntity, newTag); + m_tempSvc->tags().tag(m_tagSelectedEntity, newTag); } } } @@ -1021,47 +1054,47 @@ class MoleculeEditor : public Scene2D if (hit == static_cast(-1)) return; - auto& dot = m_tempEcs->getComponent(hit); + auto& dot = m_tempRegistry->getComponent(hit); dot.materialId = static_cast(m_selectedMaterial); } bool hasTransform(Entity e) { - auto arr = m_tempEcs->getComponentArray(); + auto arr = m_tempRegistry->getComponentArray(); return arr->hasData(e); } bool hasUIShape(Entity e) { - auto arr = m_tempEcs->getComponentArray(); + auto arr = m_tempRegistry->getComponentArray(); return arr->hasData(e); } bool hasShapeButton(Entity e) { - auto arr = m_tempEcs->getComponentArray(); + auto arr = m_tempRegistry->getComponentArray(); return arr->hasData(e); } void loadMolecule(const std::string& path) { // Remember constraint count before loading so we can find new ones - size_t prevSpringCount = m_tempEcs->getComponentArray()->getSize(); - size_t prevDistCount = m_tempEcs->getComponentArray()->getSize(); + size_t prevSpringCount = m_tempRegistry->getComponentArray()->getSize(); + size_t prevDistCount = m_tempRegistry->getComponentArray()->getSize(); // Load the file — creates new entities / rigid bodies / constraints - TagMap loadedTags = loadWeirdFile(path); + TagMap loadedTags = m_tempSvc->serialization().loadWeirdFile(path); // Apply loaded tags to the scene for (const auto& [name, entity] : loadedTags) { - tag(entity, name); + m_tempSvc->tags().tag(entity, name); } // Collect new balls: find entities with both Dot and RigidBody2D // that are not already tracked - auto dotArray = m_tempEcs->getComponentArray(); - auto rbArray = m_tempEcs->getComponentArray(); + auto dotArray = m_tempRegistry->getComponentArray(); + auto rbArray = m_tempRegistry->getComponentArray(); // Build a set of already-tracked entities for fast lookup std::unordered_set existingBalls; @@ -1086,7 +1119,7 @@ class MoleculeEditor : public Scene2D } // Collect new constraints and create visual links - auto springArray = m_tempEcs->getComponentArray(); + auto springArray = m_tempRegistry->getComponentArray(); size_t newSprings = 0; for (size_t i = prevSpringCount; i < springArray->getSize(); i++) { @@ -1098,29 +1131,30 @@ class MoleculeEditor : public Scene2D if (!hasTransform(a) || !hasTransform(b)) continue; - if (!m_tempEcs->hasComponent(a) || !m_tempEcs->hasComponent(b)) + if (!m_tempRegistry->hasComponent(a) || !m_tempRegistry->hasComponent(b)) continue; auto lineColor = DisplaySettings::Orange; - vec2 pa(m_tempEcs->getComponent(a).position); - vec2 pb(m_tempEcs->getComponent(b).position); + vec2 pa(m_tempRegistry->getComponent(a).position); + vec2 pb(m_tempRegistry->getComponent(b).position); float lineVars[8]{}; computeScreenLineParams(pa, pb, lineVars); - Entity line = addUIShape(DefaultShapes::LINE, lineVars, lineColor); + Entity line = m_tempSvc->shapes().addUIShape( + {.shapeId = DefaultShapes::LINE, .variables = lineVars, .material = static_cast(lineColor)}); - auto& btn = m_tempEcs->addComponent(line); + auto& btn = m_tempRegistry->addComponent(line); btn.clickPadding = 8.0f; btn.modifierAmount = 0.0f; - blacklistEntity(line); + m_tempSvc->serialization().blacklistEntity(line); - int idA = m_tempEcs->getComponent(a).simulationId; - int idB = m_tempEcs->getComponent(b).simulationId; + int idA = m_tempRegistry->getComponent(a).simulationId; + int idB = m_tempRegistry->getComponent(b).simulationId; m_links.push_back({a, b, idA, idB, spring.restDistance, line, LinkType::Spring, springEnt}); newSprings++; } - auto distArray = m_tempEcs->getComponentArray(); + auto distArray = m_tempRegistry->getComponentArray(); size_t newDists = 0; for (size_t i = prevDistCount; i < distArray->getSize(); i++) { @@ -1132,24 +1166,25 @@ class MoleculeEditor : public Scene2D if (!hasTransform(a) || !hasTransform(b)) continue; - if (!m_tempEcs->hasComponent(a) || !m_tempEcs->hasComponent(b)) + if (!m_tempRegistry->hasComponent(a) || !m_tempRegistry->hasComponent(b)) continue; auto lineColor = DisplaySettings::Cyan; - vec2 pa(m_tempEcs->getComponent(a).position); - vec2 pb(m_tempEcs->getComponent(b).position); + vec2 pa(m_tempRegistry->getComponent(a).position); + vec2 pb(m_tempRegistry->getComponent(b).position); float lineVars[8]{}; computeScreenLineParams(pa, pb, lineVars); - Entity line = addUIShape(DefaultShapes::LINE, lineVars, lineColor); + Entity line = m_tempSvc->shapes().addUIShape( + {.shapeId = DefaultShapes::LINE, .variables = lineVars, .material = static_cast(lineColor)}); - auto& btn = m_tempEcs->addComponent(line); + auto& btn = m_tempRegistry->addComponent(line); btn.clickPadding = 8.0f; btn.modifierAmount = 0.0f; - blacklistEntity(line); + m_tempSvc->serialization().blacklistEntity(line); - int idA = m_tempEcs->getComponent(a).simulationId; - int idB = m_tempEcs->getComponent(b).simulationId; + int idA = m_tempRegistry->getComponent(a).simulationId; + int idB = m_tempRegistry->getComponent(b).simulationId; m_links.push_back({a, b, idA, idB, dist.distance, line, LinkType::Distance, distEnt}); newDists++; } @@ -1159,9 +1194,10 @@ class MoleculeEditor : public Scene2D WeirdEngine::Logger::log(loadMsg); } - void onEntityShapeCollision(ECSManager& ecs, WeirdEngine::EntityShapeCollisionEvent& event) override + void onEntityShapeCollision(Registry& registry, ServiceProvider& services, + WeirdEngine::EntityShapeCollisionEvent& event) override { - m_tempEcs = &ecs; - event.raw.friction *= 100.0f; + m_tempRegistry = ®istry; + m_tempSvc = &services; } }; diff --git a/tools/scene-editor/include/SceneEditor.h b/tools/scene-editor/include/SceneEditor.h index 5bf99ac..805cef7 100644 --- a/tools/scene-editor/include/SceneEditor.h +++ b/tools/scene-editor/include/SceneEditor.h @@ -22,7 +22,8 @@ class SceneEditor : public Scene2D { } - ECSManager* m_tempEcs = nullptr; + Registry* m_tempRegistry = nullptr; + ServiceProvider* m_tempSvc = nullptr; private: // ===================================================================== @@ -86,12 +87,13 @@ class SceneEditor : public Scene2D // ===================================================================== // Lifecycle // ===================================================================== - void onStart(ECSManager& ecs, const TagMap& tags) override + void onStart(Registry& registry, ServiceProvider& services) override { - m_tempEcs = &ecs; - m_debugInput = true; - m_debugFly = true; - m_tempEcs->getComponent(m_mainCamera).position = g_cameraPositon; + m_tempRegistry = ®istry; + m_tempSvc = &services; + m_tempSvc->debug().setDebugInput(true); + m_tempSvc->debug().setDebugFly(true); + m_tempRegistry->getComponent(m_tempSvc->render().getCameraEntity()).position = g_cameraPositon; buildShapeButtons(); buildCombToggles(); @@ -99,30 +101,33 @@ class SceneEditor : public Scene2D buildParamPanel(); } - void onUpdate(float delta, ECSManager& ecs) override + void onUpdate(Registry& registry, ServiceProvider& services) override { - m_tempEcs = &ecs; - g_cameraPositon = m_tempEcs->getComponent(m_mainCamera).position; + m_tempRegistry = ®istry; + m_tempSvc = &services; + g_cameraPositon = m_tempRegistry->getComponent(m_tempSvc->render().getCameraEntity()).position; - if (Input::GetKeyDown(Input::Q) || Input::GetGamepadButtonDown(Input::GamepadButton::North)) - setSceneComplete(); - if (Input::GetKey(Input::LeftCtrl) && Input::GetKeyDown(Input::S)) - saveScene(ASSETS_PATH "example.weird"); + if (m_tempSvc->input().getKeyDown(Input::Q) || + m_tempSvc->input().getGamepadButtonDown(Input::GamepadButton::North)) + m_tempSvc->sceneControl().goToNextScene(); + if (m_tempSvc->input().getKey(Input::LeftCtrl) && m_tempSvc->input().getKeyDown(Input::S)) + m_tempSvc->serialization().saveScene(m_tempSvc->resources().assetPath("example.weird")); syncMaterialToggles(); syncCombToggles(); - if (Input::GetMouseButtonDown(Input::LeftClick)) + if (m_tempSvc->input().getMouseButtonDown(Input::LeftClick)) onLeftClick(); - if (Input::GetMouseButton(Input::LeftClick)) + if (m_tempSvc->input().getMouseButton(Input::LeftClick)) { - auto& cam = m_tempEcs->getComponent(m_mainCamera); - vec2 wp = ECS::Camera::screenPositionToWorldPosition2D(cam, vec2(Input::GetMouseX(), Input::GetMouseY())); + auto& cam = m_tempRegistry->getComponent(m_tempSvc->render().getCameraEntity()); + vec2 wp = ECS::Camera::screenPositionToWorldPosition2D( + cam, vec2(m_tempSvc->input().getMouseX(), m_tempSvc->input().getMouseY())); spawnPhysicsEntity(wp); } - if (Input::GetMouseButtonDown(Input::RightClick)) + if (m_tempSvc->input().getMouseButtonDown(Input::RightClick)) onRightClick(); - if (Input::GetKeyDown(Input::X) && m_hasSelection) + if (m_tempSvc->input().getKeyDown(Input::X) && m_hasSelection) deleteSelected(); refreshPanel(); @@ -131,7 +136,7 @@ class SceneEditor : public Scene2D void destroyOffscreenEntities() { - auto transformArray = m_tempEcs->getComponentArray(); + auto transformArray = m_tempRegistry->getComponentArray(); if (!transformArray) return; @@ -139,12 +144,12 @@ class SceneEditor : public Scene2D { auto& transform = transformArray->getDataAtIdx(i); Entity entity = transformArray->getEntityAtIdx(i); - if (entity == m_mainCamera) + if (entity == m_tempSvc->render().getCameraEntity()) continue; if (transform.position.y < -10.0f) { - m_tempEcs->destroyEntity(entity); + m_tempRegistry->destroyEntity(entity); } } } @@ -164,13 +169,13 @@ class SceneEditor : public Scene2D float p[8]{}; previewParams(types[i], cx, cy, p); - Entity e = addUIShape(types[i], p, 2); - auto& b = m_tempEcs->addComponent(e); + Entity e = m_tempSvc->shapes().addUIShape({.shapeId = types[i], .variables = p, .material = 2}); + auto& b = m_tempRegistry->addComponent(e); b.modifierAmount = 1.0f; b.clickPadding = 8.0f; m_shapeButtons.push_back({e, types[i]}); - blacklistEntity(e); + m_tempSvc->serialization().blacklistEntity(e); } } @@ -244,32 +249,37 @@ class SceneEditor : public Scene2D int g = COMB_GRP_BASE + i; float p1[8]{cx - off * 0.5f, cy, r}; - Entity e1 = addUIShape(DefaultShapes::CIRCLE, p1, static_cast(1), CombinationType::Addition, g); + Entity e1 = m_tempSvc->shapes().addUIShape({.shapeId = DefaultShapes::CIRCLE, + .variables = p1, + .material = 1, + .combination = CombinationType::Addition, + .group = g}); float p2[8]{cx + off * 0.5f, cy, r}; - Entity e2 = addUIShape(DefaultShapes::CIRCLE, p2, static_cast(1), ct[i], g); + Entity e2 = m_tempSvc->shapes().addUIShape( + {.shapeId = DefaultShapes::CIRCLE, .variables = p2, .material = 1, .combination = ct[i], .group = g}); if (ct[i] == CombinationType::SmoothAddition || ct[i] == CombinationType::SmoothSubtraction) - m_tempEcs->getComponent(e2).smoothFactor = 5.0f; + m_tempRegistry->getComponent(e2).smoothFactor = 5.0f; - auto& tog = m_tempEcs->addComponent(e1); + auto& tog = m_tempRegistry->addComponent(e1); tog.clickPadding = r + 10.0f; tog.parameterModifierMask.set(2); tog.modifierAmount = 3.0f; - Entity lbl = m_tempEcs->createEntity(); - m_tempEcs->addComponent(lbl).position = vec3(cx, cy - 2600.0f, 0.0f); - auto& tx = m_tempEcs->addComponent(lbl); + Entity lbl = m_tempRegistry->createEntity(); + m_tempRegistry->addComponent(lbl).position = vec3(cx, cy - 2600.0f, 0.0f); + auto& tx = m_tempRegistry->addComponent(lbl); tx.text = label[i]; tx.material = 1; tx.horizontalAlignment = TextRenderer::HorizontalAlignment::Center; m_combButtons.push_back({e1, ct[i]}); - blacklistEntity(e1); - blacklistEntity(e2); - blacklistEntity(lbl); + m_tempSvc->serialization().blacklistEntity(e1); + m_tempSvc->serialization().blacklistEntity(e2); + m_tempSvc->serialization().blacklistEntity(lbl); } - m_tempEcs->getComponent(m_combButtons[0].toggleEntity).active = true; + m_tempRegistry->getComponent(m_combButtons[0].toggleEntity).active = true; } // ===================================================================== @@ -281,19 +291,18 @@ class SceneEditor : public Scene2D { float px = START_X + i * MAT_SPACING; float p[8]{px, MAT_Y, BTN_SIZE - 4.0f}; - Entity e; - UIShape& sh = addUIShape(DefaultShapes::CIRCLE, p, e); - sh.material = static_cast(i); + Entity e = m_tempSvc->shapes().addUIShape( + {.shapeId = DefaultShapes::CIRCLE, .variables = p, .material = static_cast(i)}); - auto& tog = m_tempEcs->addComponent(e); + auto& tog = m_tempRegistry->addComponent(e); tog.clickPadding = BTN_SIZE + 3.0f; tog.parameterModifierMask.set(2); tog.modifierAmount = 5.0f; m_materialToggles[i] = e; - blacklistEntity(e); + m_tempSvc->serialization().blacklistEntity(e); } - m_tempEcs->getComponent(m_materialToggles[m_selectedMaterial]).active = true; + m_tempRegistry->getComponent(m_materialToggles[m_selectedMaterial]).active = true; } // ===================================================================== @@ -301,38 +310,38 @@ class SceneEditor : public Scene2D // ===================================================================== void buildParamPanel() { - m_selInfoText = m_tempEcs->createEntity(); - auto& selInfoTf = m_tempEcs->addComponent(m_selInfoText); + m_selInfoText = m_tempRegistry->createEntity(); + auto& selInfoTf = m_tempRegistry->addComponent(m_selInfoText); selInfoTf.position = vec3(HIDDEN, PANEL_TOP_Y + 35.0f, 0.0f); - m_tempEcs->setComponentDirty(selInfoTf); + m_tempRegistry->setComponentDirty(selInfoTf); - auto& hdr = m_tempEcs->addComponent(m_selInfoText); + auto& hdr = m_tempRegistry->addComponent(m_selInfoText); hdr.material = 1; hdr.horizontalAlignment = TextRenderer::HorizontalAlignment::Right; - blacklistEntity(m_selInfoText); + m_tempSvc->serialization().blacklistEntity(m_selInfoText); for (int i = 0; i < 8; i++) { float py = PANEL_TOP_Y - i * PARAM_GAP; - float bp[8]{HIDDEN, py, P_BTN_W, P_BTN_H}; - Entity be = addUIShape(DefaultShapes::BOX, bp, static_cast(3)); - auto& btn = m_tempEcs->addComponent(be); + Entity be = m_tempSvc->shapes().addUIShape( + {.shapeId = DefaultShapes::BOX, .variables = {HIDDEN, py, P_BTN_W, P_BTN_H}, .material = 3}); + auto& btn = m_tempRegistry->addComponent(be); btn.modifierAmount = 1.0f; btn.clickPadding = 3.0f; - Entity te = m_tempEcs->createEntity(); - auto& ttf = m_tempEcs->addComponent(te); + Entity te = m_tempRegistry->createEntity(); + auto& ttf = m_tempRegistry->addComponent(te); ttf.position = vec3(HIDDEN, py, 0.0f); - m_tempEcs->setComponentDirty(ttf); - auto& tx = m_tempEcs->addComponent(te); + m_tempRegistry->setComponentDirty(ttf); + auto& tx = m_tempRegistry->addComponent(te); tx.material = 0; tx.horizontalAlignment = TextRenderer::HorizontalAlignment::Right; tx.verticalAlignment = TextRenderer::VerticalAlignment::Center; m_paramBtns[i] = {be, te}; - blacklistEntity(be); - blacklistEntity(te); + m_tempSvc->serialization().blacklistEntity(be); + m_tempSvc->serialization().blacklistEntity(te); } } @@ -343,7 +352,7 @@ class SceneEditor : public Scene2D { for (auto& sb : m_shapeButtons) { - if (m_tempEcs->getComponent(sb.entity).state == ButtonState::Down) + if (m_tempRegistry->getComponent(sb.entity).state == ButtonState::Down) { spawnShape(sb.shapeType); return; @@ -354,7 +363,7 @@ class SceneEditor : public Scene2D { for (int i = 0; i < 8; i++) { - if (m_tempEcs->getComponent(m_paramBtns[i].shapeEntity).state == ButtonState::Down) + if (m_tempRegistry->getComponent(m_paramBtns[i].shapeEntity).state == ButtonState::Down) { promptParam(i); return; @@ -365,8 +374,9 @@ class SceneEditor : public Scene2D void onRightClick() { - auto& cam = m_tempEcs->getComponent(m_mainCamera); - vec2 wp = ECS::Camera::screenPositionToWorldPosition2D(cam, vec2(Input::GetMouseX(), Input::GetMouseY())); + auto& cam = m_tempRegistry->getComponent(m_tempSvc->render().getCameraEntity()); + vec2 wp = ECS::Camera::screenPositionToWorldPosition2D( + cam, vec2(m_tempSvc->input().getMouseX(), m_tempSvc->input().getMouseY())); selectNearest(wp); } @@ -375,8 +385,8 @@ class SceneEditor : public Scene2D // ===================================================================== void selectNearest(vec2 pos) { - auto cs = m_tempEcs->getComponentArray(); - auto ui = m_tempEcs->getComponentArray(); + auto cs = m_tempRegistry->getComponentArray(); + auto ui = m_tempRegistry->getComponentArray(); float best = SEL_THRESH; Entity hit = static_cast(-1); @@ -392,7 +402,7 @@ class SceneEditor : public Scene2D std::copy(std::begin(s.parameters), std::end(s.parameters), p); p[9] = pos.x; p[10] = pos.y; - float d = m_sdfs[s.distanceFieldId]->getValue(p); + float d = m_tempSvc->shapes().getSDFs()[s.distanceFieldId]->getValue(p); if (d < best) { best = d; @@ -419,7 +429,7 @@ class SceneEditor : public Scene2D void deleteSelected() { - m_tempEcs->destroyEntity(m_selectedEntity); + m_tempRegistry->destroyEntity(m_selectedEntity); doDeselect(); } @@ -431,17 +441,17 @@ class SceneEditor : public Scene2D for (int i = 0; i < 8; i++) { float py = PANEL_TOP_Y - i * PARAM_GAP; - auto& u = m_tempEcs->getComponent(m_paramBtns[i].shapeEntity); + auto& u = m_tempRegistry->getComponent(m_paramBtns[i].shapeEntity); u.parameters[0] = PANEL_X + (P_BTN_W) * 0.5f; u.parameters[1] = py; - auto& t = m_tempEcs->getComponent(m_paramBtns[i].textEntity); + auto& t = m_tempRegistry->getComponent(m_paramBtns[i].textEntity); t.position = vec3(PANEL_X - P_BTN_W - 10.0f, py, 0.0f); - m_tempEcs->setComponentDirty(t); + m_tempRegistry->setComponentDirty(t); } - auto& ht = m_tempEcs->getComponent(m_selInfoText); + auto& ht = m_tempRegistry->getComponent(m_selInfoText); ht.position = vec3(PANEL_X, PANEL_TOP_Y + 35.0f, 0.0f); - m_tempEcs->setComponentDirty(ht); + m_tempRegistry->setComponentDirty(ht); // m_UIRenderSystem.shaderNeedsUpdate() = true; } @@ -449,15 +459,15 @@ class SceneEditor : public Scene2D { for (int i = 0; i < 8; i++) { - auto& u = m_tempEcs->getComponent(m_paramBtns[i].shapeEntity); + auto& u = m_tempRegistry->getComponent(m_paramBtns[i].shapeEntity); u.parameters[0] = HIDDEN; - auto& t = m_tempEcs->getComponent(m_paramBtns[i].textEntity); + auto& t = m_tempRegistry->getComponent(m_paramBtns[i].textEntity); t.position.x = HIDDEN; - m_tempEcs->setComponentDirty(t); + m_tempRegistry->setComponentDirty(t); } - auto& ht = m_tempEcs->getComponent(m_selInfoText); + auto& ht = m_tempRegistry->getComponent(m_selInfoText); ht.position.x = HIDDEN; - m_tempEcs->setComponentDirty(ht); + m_tempRegistry->setComponentDirty(ht); // m_UIRenderSystem.shaderNeedsUpdate() = true; } @@ -471,20 +481,20 @@ class SceneEditor : public Scene2D return; } - auto& cs = m_tempEcs->getComponent(m_selectedEntity); + auto& cs = m_tempRegistry->getComponent(m_selectedEntity); - auto& hdr = m_tempEcs->getComponent(m_selInfoText); + auto& hdr = m_tempRegistry->getComponent(m_selInfoText); const char* name = shapeName(cs.distanceFieldId); if (hdr.text != name) { hdr.text = name; - m_tempEcs->setComponentDirty(hdr); + m_tempRegistry->setComponentDirty(hdr); } int pc = paramCount(cs.distanceFieldId); for (int i = 0; i < 8; i++) { - auto& tx = m_tempEcs->getComponent(m_paramBtns[i].textEntity); + auto& tx = m_tempRegistry->getComponent(m_paramBtns[i].textEntity); if (i < pc) { char buf[48]; @@ -492,23 +502,23 @@ class SceneEditor : public Scene2D if (tx.text != buf) { tx.text = buf; - m_tempEcs->setComponentDirty(tx); + m_tempRegistry->setComponentDirty(tx); } - auto& u = m_tempEcs->getComponent(m_paramBtns[i].shapeEntity); + auto& u = m_tempRegistry->getComponent(m_paramBtns[i].shapeEntity); if (u.parameters[0] < 0.0f) { u.parameters[0] = PANEL_X + (P_BTN_W) * 0.5f; u.parameters[1] = PANEL_TOP_Y - i * PARAM_GAP; } - auto& t = m_tempEcs->getComponent(m_paramBtns[i].textEntity); + auto& t = m_tempRegistry->getComponent(m_paramBtns[i].textEntity); float py = PANEL_TOP_Y - i * PARAM_GAP; float txX = PANEL_X - P_BTN_W - 10.0f; if (t.position.x < 0.0f || std::abs(t.position.y - py) > 0.001f) { t.position = vec3(txX, py, 0.0f); - m_tempEcs->setComponentDirty(t); + m_tempRegistry->setComponentDirty(t); } } else @@ -516,15 +526,15 @@ class SceneEditor : public Scene2D if (!tx.text.empty()) { tx.text.clear(); - m_tempEcs->setComponentDirty(tx); + m_tempRegistry->setComponentDirty(tx); } - auto& u = m_tempEcs->getComponent(m_paramBtns[i].shapeEntity); + auto& u = m_tempRegistry->getComponent(m_paramBtns[i].shapeEntity); if (u.parameters[0] > 0.0f) { u.parameters[0] = HIDDEN; - auto& t2 = m_tempEcs->getComponent(m_paramBtns[i].textEntity); + auto& t2 = m_tempRegistry->getComponent(m_paramBtns[i].textEntity); t2.position.x = HIDDEN; - m_tempEcs->setComponentDirty(t2); + m_tempRegistry->setComponentDirty(t2); } } } @@ -532,7 +542,7 @@ class SceneEditor : public Scene2D bool entityHasShape(Entity e) { - auto arr = m_tempEcs->getComponentArray(); + auto arr = m_tempRegistry->getComponentArray(); for (size_t i = 0; i < arr->getSize(); i++) if (arr->getEntityAtIdx(i) == e) return true; @@ -547,7 +557,7 @@ class SceneEditor : public Scene2D if (!m_hasSelection || !entityHasShape(m_selectedEntity)) return; - auto& cs = m_tempEcs->getComponent(m_selectedEntity); + auto& cs = m_tempRegistry->getComponent(m_selectedEntity); int pc = paramCount(cs.distanceFieldId); if (idx >= pc) return; @@ -563,7 +573,7 @@ class SceneEditor : public Scene2D if (cs.distanceFieldId == DefaultShapes::STAR && idx == 4) v = std::round(v); cs.parameters[idx] = v; - m_tempEcs->setComponentDirty(cs); + m_tempRegistry->setComponentDirty(cs); } else { @@ -580,7 +590,7 @@ class SceneEditor : public Scene2D int activated = -1; for (int i = 0; i < 16; i++) { - auto& t = m_tempEcs->getComponent(m_materialToggles[i]); + auto& t = m_tempRegistry->getComponent(m_materialToggles[i]); if (t.active && t.state == ButtonState::Down) { activated = i; @@ -592,12 +602,12 @@ class SceneEditor : public Scene2D m_selectedMaterial = activated; for (int i = 0; i < 16; i++) if (i != activated) - m_tempEcs->getComponent(m_materialToggles[i]).active = false; + m_tempRegistry->getComponent(m_materialToggles[i]).active = false; } else { for (int i = 0; i < 16; i++) - if (m_tempEcs->getComponent(m_materialToggles[i]).active) + if (m_tempRegistry->getComponent(m_materialToggles[i]).active) { m_selectedMaterial = i; break; @@ -610,7 +620,7 @@ class SceneEditor : public Scene2D int activated = -1; for (int i = 0; i < (int)m_combButtons.size(); i++) { - auto& t = m_tempEcs->getComponent(m_combButtons[i].toggleEntity); + auto& t = m_tempRegistry->getComponent(m_combButtons[i].toggleEntity); if (t.active && t.state == ButtonState::Down) { activated = i; @@ -623,12 +633,12 @@ class SceneEditor : public Scene2D m_selectedCombination = m_combButtons[activated].combType; for (int i = 0; i < (int)m_combButtons.size(); i++) if (i != activated) - m_tempEcs->getComponent(m_combButtons[i].toggleEntity).active = false; + m_tempRegistry->getComponent(m_combButtons[i].toggleEntity).active = false; } else { for (int i = 0; i < (int)m_combButtons.size(); i++) - if (m_tempEcs->getComponent(m_combButtons[i].toggleEntity).active) + if (m_tempRegistry->getComponent(m_combButtons[i].toggleEntity).active) { m_selectedCombIdx = i; m_selectedCombination = m_combButtons[i].combType; @@ -644,23 +654,26 @@ class SceneEditor : public Scene2D { float p[8]{}; fillRandomParams(type, p); - Entity e = addShape(type, p, static_cast(m_selectedMaterial), m_selectedCombination); + Entity e = m_tempSvc->shapes().addShape({.shapeId = type, + .variables = p, + .material = static_cast(m_selectedMaterial), + .combination = m_selectedCombination}); if (m_selectedCombination == CombinationType::SmoothAddition || m_selectedCombination == CombinationType::SmoothSubtraction) - m_tempEcs->getComponent(e).smoothFactor = 1.5f; + m_tempRegistry->getComponent(e).smoothFactor = 1.5f; doSelect(e); } void spawnPhysicsEntity(vec2 wp) { - Entity e = m_tempEcs->createEntity(); - auto& t = m_tempEcs->addComponent(e); + Entity e = m_tempRegistry->createEntity(); + auto& t = m_tempRegistry->addComponent(e); t.position = vec3(wp.x, wp.y, 0.0f); - m_tempEcs->setComponentDirty(t); - auto& sdf = m_tempEcs->addComponent(e); + m_tempRegistry->setComponentDirty(t); + auto& sdf = m_tempRegistry->addComponent(e); sdf.materialId = static_cast(m_selectedMaterial); - m_tempEcs->addComponent(e); - blacklistEntity(e); + m_tempRegistry->addComponent(e); + m_tempSvc->serialization().blacklistEntity(e); } // ===================================================================== @@ -755,7 +768,7 @@ class SceneEditor : public Scene2D vec2 camCentre() { - auto& t = m_tempEcs->getComponent(m_mainCamera); + auto& t = m_tempRegistry->getComponent(m_tempSvc->render().getCameraEntity()); return vec2(t.position.x, t.position.y); }