Compare commits
25 Commits
feat/entt
...
feat/editor
| Author | SHA1 | Date | |
|---|---|---|---|
| 3e23f7da8e | |||
| 8c2ce637a2 | |||
| 5f031b80c2 | |||
| eb155da5d8 | |||
| 36fd4047dc | |||
| 756b818f46 | |||
| d7add3964d | |||
| 8041286a9d | |||
| b3871716a1 | |||
| 2a74648ffd | |||
| cd754fda28 | |||
| 9ebf77daec | |||
| d6f2cc1ea3 | |||
| 7a3810805e | |||
| ae21d4a5d2 | |||
| 0baabd689f | |||
| 28328cbc0d | |||
| 18ca7105c5 | |||
| 9a4dba0a50 | |||
| b95365f6a6 | |||
| 271e2ea7b7 | |||
| d7f9019e61 | |||
| 5367b31f4e | |||
| e6a53baa1a | |||
| 15e5d580da |
@@ -18,19 +18,37 @@ jobs:
|
||||
with:
|
||||
ssh-private-key: ${{ secrets.WIKI_SSH_KEY }}
|
||||
|
||||
- name: Add Gitea host key
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
ssh-keyscan -t rsa gitea.appstack.me >> ~/.ssh/known_hosts
|
||||
|
||||
- name: Sync docs to wiki
|
||||
env:
|
||||
WIKI_REPO: git@gitea.appstack.me:nick/simian.wiki.git
|
||||
run: |
|
||||
git config --global user.name "Simian CI"
|
||||
git config --global user.email "ci@simian.local"
|
||||
git clone "$WIKI_REPO" wiki
|
||||
rsync -av --delete docs/ wiki/
|
||||
cd wiki
|
||||
|
||||
# Workspace is always $GITHUB_WORKSPACE in Actions
|
||||
WORKSPACE=${GITHUB_WORKSPACE:-.}
|
||||
|
||||
TEMP_WIKI=$(mktemp -d)
|
||||
git clone "$WIKI_REPO" "$TEMP_WIKI"
|
||||
|
||||
cd "$TEMP_WIKI"
|
||||
|
||||
# Safe clean (keep .git)
|
||||
find . -mindepth 1 -maxdepth 1 ! -name '.git' -exec rm -rf {} +
|
||||
|
||||
# Copy from workspace/docs
|
||||
cp -r "$WORKSPACE/docs"/* . 2>/dev/null || true
|
||||
|
||||
git add -A
|
||||
if git diff --staged --quiet; then
|
||||
echo "No wiki changes"
|
||||
else
|
||||
git commit -m "Update wiki from docs (CI)"
|
||||
git push origin HEAD
|
||||
git commit -m "Update wiki from docs (CI) [skip ci]"
|
||||
git push origin main
|
||||
echo "✅ Synced to simian.wiki main"
|
||||
fi
|
||||
|
||||
@@ -10,3 +10,6 @@
|
||||
[submodule "external/rlImGui"]
|
||||
path = external/rlImGui
|
||||
url = https://github.com/raylib-extras/rlImGui.git
|
||||
[submodule "external/physfs"]
|
||||
path = external/physfs
|
||||
url = https://github.com/icculus/physfs
|
||||
|
||||
@@ -14,6 +14,11 @@ add_subdirectory(external/raylib)
|
||||
# -------------------------
|
||||
add_subdirectory(external/angelscript/sdk/angelscript/projects/cmake)
|
||||
|
||||
# -------------------------
|
||||
# PhysFS (submodule)
|
||||
# -------------------------
|
||||
add_subdirectory(external/physfs)
|
||||
|
||||
# -------------------------
|
||||
# Dear ImGui (docking branch)
|
||||
# -------------------------
|
||||
@@ -79,6 +84,9 @@ target_include_directories(scriptarray
|
||||
# -------------------------
|
||||
add_library(simian_core STATIC
|
||||
src/Application.cpp
|
||||
src/PhysFSManager.cpp
|
||||
src/SceneGraph.cpp
|
||||
src/SceneLoader.cpp
|
||||
src/scripting/ScriptEngine.cpp
|
||||
src/scripting/ScriptBindings.cpp
|
||||
src/scripting/ToastBindings.cpp
|
||||
@@ -89,13 +97,18 @@ add_library(simian_core STATIC
|
||||
src/scripting/ECSBindings.cpp
|
||||
src/scripting/ResourceBindings.cpp
|
||||
src/scripting/InputBindings.cpp
|
||||
src/scripting/SceneBindings.cpp
|
||||
src/scripting/PhysFSBindings.cpp
|
||||
src/HotReload.cpp
|
||||
src/gui/GuiManager.cpp
|
||||
src/gui/ImViewGuizmoIntegration.cpp
|
||||
src/gui/LogWindow.cpp
|
||||
src/log/log.c
|
||||
src/ShaderManager.cpp
|
||||
src/ModelManager.cpp
|
||||
src/MaterialManager.cpp
|
||||
src/AssetManager.cpp
|
||||
src/TextureManager.cpp
|
||||
src/InputManager.cpp
|
||||
)
|
||||
|
||||
@@ -109,12 +122,15 @@ target_include_directories(simian_core PUBLIC
|
||||
external/angelscript/sdk/add_on/scriptstdstring
|
||||
external/angelscript/sdk/add_on/scriptbuilder
|
||||
external/angelscript/sdk/add_on/scriptarray
|
||||
external/physfs/src
|
||||
external/imgui
|
||||
external/ImViewGuizmo
|
||||
)
|
||||
|
||||
target_link_libraries(simian_core PUBLIC
|
||||
raylib
|
||||
angelscript
|
||||
PhysFS::PhysFS-static
|
||||
scriptstdstring
|
||||
scriptbuilder
|
||||
scriptarray
|
||||
@@ -154,6 +170,9 @@ if(MSVC)
|
||||
target_compile_options(simian_core PRIVATE /W4 /EHsc)
|
||||
else()
|
||||
target_compile_options(simian_core PRIVATE -Wall -Wextra -pedantic)
|
||||
if(TARGET raylib)
|
||||
target_compile_options(raylib PRIVATE -Wno-missing-field-initializers)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# -------------------------
|
||||
|
||||
@@ -73,6 +73,16 @@ Simian/
|
||||
- If compilation succeeds, the temp module replaces the live `main` module and new functions are used. If compilation fails, the running module is left intact and a single-line error is shown on-screen.
|
||||
- This avoids crashes that happen when a script with compilation errors would otherwise replace/break function pointers while the engine is executing.
|
||||
|
||||
## Asset registry
|
||||
|
||||
Asset keys are persisted in a project-wide registry at `assets/registry.toml`. This file is loaded on startup and written on shutdown so scene references stay valid across sessions.
|
||||
|
||||
## Scene scripts (class-based)
|
||||
|
||||
Scene scripts now use class instances (similar to entity scripts). Implement a class with `Init()`, `Update(float)`, and `Shutdown()` methods. The engine picks the first class that exposes any of those methods and instantiates it.
|
||||
|
||||
See `scripts/game.as` and `scripts/scene_example.as` for examples.
|
||||
|
||||
## ImGui / rlImGui (optional)
|
||||
|
||||
The repository prefers to keep upstream code as submodules. Some upstream projects (like certain ImGui tags or rlImGui) don't provide a CMakeLists.txt. To support those without editing submodules, the project includes small wrappers in `cmake/`:
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
# Simian asset registry
|
||||
# Auto-generated - edits may be overwritten
|
||||
|
||||
[[model]]
|
||||
key = "Torus"
|
||||
type = "primitive"
|
||||
primitive = "torus"
|
||||
params = [0.5, 1, 16, 8]
|
||||
|
||||
[[model]]
|
||||
key = "demo_cube"
|
||||
type = "primitive"
|
||||
primitive = "cube"
|
||||
params = [1, 1, 1]
|
||||
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
# Editor Overview
|
||||
|
||||
This document outlines the in-app editor UI, its panels, and common workflows.
|
||||
|
||||
## Launching the Editor
|
||||
|
||||
- Start the app with the editor flag enabled.
|
||||
- The editor UI appears as dockable panels around the Game Window.
|
||||
|
||||
## Layout
|
||||
|
||||
- DockSpace host with a top menu bar.
|
||||
- You can drag and drop to move panels around.
|
||||
- Panels: Game Window, Scene, Assets, Inspector, Log Viewer.
|
||||
|
||||
## Menu Bar
|
||||
|
||||
### Game
|
||||
|
||||
- Quit: request a clean shutdown.
|
||||
|
||||
### Scene
|
||||
|
||||
- New: clear the current scene.
|
||||
- Load: load a scene from a TOML-like file path.
|
||||
- Save As: save the current scene to a file path.
|
||||
|
||||
### View
|
||||
|
||||
- Toggle Log Viewer, Scene, and Inspector panels.
|
||||
- Toggle Assets panel.
|
||||
|
||||
## Game Window
|
||||
|
||||
- Renders the current scene view.
|
||||
- Resizes with the available docked panel space.
|
||||
|
||||
## Scene Panel
|
||||
|
||||
- Displays the entity hierarchy as a tree.
|
||||
- Drag and drop entities to re-parent.
|
||||
- Drop on the bottom target to clear parent.
|
||||
- Create Entity and Delete Selected controls.
|
||||
|
||||
## Inspector Panel
|
||||
|
||||
- Shows components for the selected entity.
|
||||
- Add Component dropdown lists only components not yet on the entity.
|
||||
- Remove buttons appear on removable components.
|
||||
|
||||
### Components
|
||||
|
||||
- Tag: rename the entity.
|
||||
- Transform: position, scale, rotation (degrees in UI).
|
||||
- Velocity: linear and angular velocity.
|
||||
- Model Renderer: model id, shader id, color, outline.
|
||||
- Texture: texture id.
|
||||
- UV Transform: scale, offset, rotation (radians).
|
||||
- Script: per-entity script path and enabled toggle.
|
||||
- Camera: position, target, up, FOV, projection, helper actions.
|
||||
- Light: direction, color, intensity, helper presets.
|
||||
- Material: material id.
|
||||
- Render Pass: order, depth flags, shader id.
|
||||
- World Transform: read-only computed transform.
|
||||
|
||||
## Assets Panel
|
||||
|
||||
- Browse loaded textures, shaders, models, and materials.
|
||||
- Load assets by key and path, or create primitive models.
|
||||
- Unload assets by key.
|
||||
|
||||
## Log Viewer
|
||||
|
||||
- Shows engine log output in real time.
|
||||
|
||||
## Shortcuts
|
||||
|
||||
- Ctrl+Q: Quit
|
||||
- Ctrl+L: Toggle Log Viewer
|
||||
- Ctrl+N: New Scene
|
||||
- Ctrl+O: Load Scene
|
||||
- Ctrl+S: Save Scene
|
||||
|
||||
## Notes
|
||||
|
||||
- Scene Save and Load use file path text fields.
|
||||
- Components can be assigned via asset keys in the inspector; numeric IDs remain as a fallback.
|
||||
- World Transform is computed from hierarchy and local transforms and is read-only.
|
||||
- UV Transform values are passed as shader uniforms `uvScale`, `uvOffset`, and `uvRotation` when present.
|
||||
|
||||
## Scene Scripts
|
||||
|
||||
- Scene scripts are loaded by the scene system and run once per scene.
|
||||
- The editor lets you set a scene-level script path in the Scene panel.
|
||||
- Preferred scene callbacks: `SceneInit()`, `SceneUpdate(float dt)`, `SceneShutdown()`.
|
||||
- Legacy callbacks still work: `Init()`, `Update(float dt)`, `Shutdown()`.
|
||||
|
||||
## ScriptComponent
|
||||
|
||||
- ScriptComponent runs a per-entity script module.
|
||||
- Set the script path in the Inspector and toggle enabled.
|
||||
- Preferred callbacks: `EntityInit(uint entity)`, `EntityUpdate(uint entity, float dt)`, `EntityShutdown(uint entity)`.
|
||||
- Legacy callbacks still work: `Init(uint entity)`, `Update(uint entity, float dt)`, `Shutdown(uint entity)`.
|
||||
- Subject to change
|
||||
+2
-3
@@ -6,7 +6,6 @@ Edit markdown files here and push to the repository; CI will mirror them to the
|
||||
## Pages
|
||||
- [Scripting](Scripting.md) — scripting API and examples
|
||||
- [Usage](Usage.md) — getting started and build instructions
|
||||
- [Scenegraph](Scenegraph.md) — hierarchy components and scene format
|
||||
- [Editor](Editor.md) - details on how to use the editor
|
||||
|
||||
## Notes
|
||||
The CI workflow (.github/workflows/sync-docs-to-wiki.yml) clones the wiki repository and copies these files into it.
|
||||
Make sure `WIKI_SSH_KEY` is configured in repository secrets (SSH private key with push access to the wiki).
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
# Scenegraph
|
||||
|
||||
This project uses a lightweight hierarchy component to model parent/child relationships in ECS.
|
||||
The scenegraph is EnTT-friendly and uses a linked-list structure for children.
|
||||
|
||||
## Components
|
||||
|
||||
### `Hierarchy`
|
||||
Each entity can have a `Hierarchy` component with these fields:
|
||||
|
||||
- `parent` — parent entity or `null`
|
||||
- `first` — first child entity
|
||||
- `prev` / `next` — sibling links
|
||||
|
||||
This mirrors the common EnTT scenegraph pattern and avoids storing vectors of children inside a component.
|
||||
|
||||
### `ECSTransform` (local)
|
||||
Stores local position/scale and a Y-axis rotation (radians).
|
||||
|
||||
### `WorldTransform` (computed)
|
||||
Computed each frame from the hierarchy and local transform. Rendering uses `WorldTransform` so children move with parents.
|
||||
|
||||
## Update Order
|
||||
|
||||
1. Systems update local transforms (e.g., velocity system).
|
||||
2. `UpdateWorldTransforms()` traverses the hierarchy and computes `WorldTransform`.
|
||||
3. Rendering draws from `WorldTransform` + `ModelRenderer`.
|
||||
|
||||
## Script API
|
||||
|
||||
```angelscript
|
||||
ECS::SetParent(child, parent);
|
||||
ECS::ClearParent(child);
|
||||
uint p = ECS::GetParent(child);
|
||||
bool has = ECS::HasParent(child);
|
||||
```
|
||||
|
||||
## Scene File Format (TOML)
|
||||
|
||||
Scenes are stored in `scenes/*.toml` using a minimal TOML-like structure. Example:
|
||||
|
||||
```toml
|
||||
scene_script = "scripts/scene.as"
|
||||
|
||||
[[entity]]
|
||||
id = "root"
|
||||
tag = "Root"
|
||||
position = [0.0, 0.0, 0.0]
|
||||
rotation_deg = 0.0
|
||||
scale = [1.0, 1.0, 1.0]
|
||||
|
||||
[[entity]]
|
||||
id = "parent"
|
||||
parent = "root"
|
||||
tag = "Parent"
|
||||
position = [2.0, 0.0, 0.0]
|
||||
model = "cube"
|
||||
model_asset = "unit_cube"
|
||||
model_size = [1.0, 1.0, 1.0]
|
||||
color = 0x00FF00FF
|
||||
shader_vs = "shaders/toon.vs"
|
||||
shader_fs = "shaders/toon.fs"
|
||||
shader_asset = "toon"
|
||||
texture = "assets/textures/grid.png"
|
||||
texture_asset = "grid"
|
||||
uv_scale = [2.0, 2.0]
|
||||
uv_offset = [0.0, 0.0]
|
||||
material_asset = "toon_mat"
|
||||
|
||||
[[entity]]
|
||||
id = "child"
|
||||
parent = "parent"
|
||||
tag = "Child"
|
||||
position = [0.0, 1.25, 0.0]
|
||||
model = "sphere"
|
||||
radius = 0.5
|
||||
color = 0xFF0000FF
|
||||
shader_vs = "shaders/toon.vs"
|
||||
shader_fs = "shaders/toon.fs"
|
||||
```
|
||||
|
||||
### Supported Keys
|
||||
|
||||
- `scene_script` (string) — scene-level script path (outside entity blocks)
|
||||
- `id` (string) — unique identifier for parent links
|
||||
- `parent` (string) — parent entity id
|
||||
- `tag` (string) — `Tag` component value
|
||||
- `position` (vec3)
|
||||
- `rotation` (radians) or `rotation_deg` (degrees)
|
||||
- `scale` (vec3)
|
||||
- `model` — `cube`, `sphere`, `plane`, or file path
|
||||
- `model_asset` (string) — asset key for models
|
||||
- `model_id` (int) — numeric model id
|
||||
- `model_size` (vec3) for cubes/planes
|
||||
- `radius`, `rings`, `slices` for spheres
|
||||
- `color` — hex `0xRRGGBBAA` (matches script colors)
|
||||
- `outline_size` — optional model outline
|
||||
- `shader_vs`, `shader_fs` — optional shader paths
|
||||
- `shader_asset` (string) — asset key for shaders
|
||||
- `shader_id` (int) — numeric shader id
|
||||
- `texture` (string) — texture path
|
||||
- `texture_asset` (string) — asset key for textures
|
||||
- `texture_id` (int) — numeric texture id
|
||||
- `material_asset` (string) — asset key for materials
|
||||
- `material_id` (int) — numeric material id
|
||||
- `uv_scale` (vec2)
|
||||
- `uv_offset` (vec2)
|
||||
- `uv_rotation` (radians)
|
||||
- `script` (string) — per-entity script path
|
||||
- `script_enabled` (bool)
|
||||
- `script_class` (string)
|
||||
- `script_var_*` (string) — per-field overrides for script-editable values
|
||||
|
||||
## Loading a Scene
|
||||
|
||||
```angelscript
|
||||
uint root = Scene::Load("scenes/demo.toml");
|
||||
```
|
||||
|
||||
`Scene::Load` returns a root entity that parents all top-level entities. Use `ECS::IsValid(root)` to check success on the scene load.
|
||||
|
||||
## Editor
|
||||
|
||||
Run the app with `--editor` to open the ImGui editor. The **Scene** window shows the hierarchy tree, supports drag-and-drop reparenting, and lets you create/delete entities. The **Inspector** window shows components for the selected entity and allows live editing.
|
||||
+50
-1
@@ -6,8 +6,57 @@ This page is a starting point for scripting documentation.
|
||||
- Scripts live in `scripts/` and are executed by AngelScript.
|
||||
- Predefined API and engine bindings are declared in `scripts/as.predefined`.
|
||||
|
||||
## Input Actions
|
||||
- Query input with `IsActionPressed`, `IsActionDown`, `IsActionReleased`, and `GetActionStrength`.
|
||||
- Register and bind at runtime with `RegisterAction`, `BindKeyboard`, `BindMouseButton`, `BindGamepadButton`, and `BindGamepadAxis`.
|
||||
- Enums: `Key`, `MouseButton`, `GamepadButton`, `GamepadAxis`.
|
||||
|
||||
Example:
|
||||
|
||||
```angelscript
|
||||
RegisterAction("boost");
|
||||
BindKeyboard("boost", Key::LEFT_CONTROL);
|
||||
BindGamepadButton("boost", 0, GamepadButton::RIGHT_TRIGGER_1);
|
||||
|
||||
RegisterAction("zoom");
|
||||
BindGamepadAxis("zoom", 0, GamepadAxis::RIGHT_TRIGGER, 0.2f, false);
|
||||
```
|
||||
|
||||
## ECS Texture + UV
|
||||
- Texture and UV helpers live under the `ECS::` namespace.
|
||||
- Texture: `AddTexture`, `SetTextureId`, `GetTextureId`, `HasTexture`, `RemoveTexture`.
|
||||
- UV Transform: `AddUVTransform`, `SetUVScale`, `SetUVOffset`, `SetUVRotation`, `HasUVTransform`, `RemoveUVTransform`.
|
||||
|
||||
Example:
|
||||
|
||||
```angelscript
|
||||
uint e = ECS::CreateEntity();
|
||||
ECS::AddTexture(e, texId);
|
||||
ECS::AddUVTransform(e, 2.0f, 2.0f, 0.0f, 0.0f, 0.0f);
|
||||
ECS::SetUVOffset(e, 0.25f, 0.0f);
|
||||
```
|
||||
|
||||
## Asset Manager
|
||||
- Asset loading can be keyed for editor and scene usage.
|
||||
- Use `Asset::Load*` helpers to create assets and reference them by key.
|
||||
|
||||
Example:
|
||||
|
||||
```angelscript
|
||||
Asset::LoadTexture("grid", "assets/textures/grid.png");
|
||||
Asset::LoadShader("toon", "shaders/toon.vs", "shaders/toon.fs");
|
||||
Asset::LoadCube("unit_cube", 1.0f, 1.0f, 1.0f);
|
||||
Asset::CreateMaterial("toon_mat", Asset::GetShaderId("toon"), 255, 255, 255, 255);
|
||||
|
||||
uint e = ECS::CreateEntity();
|
||||
ECS::AddModelRenderer(e, int(Asset::GetModelId("unit_cube")), 0xFFFFFFFF, 0.0f);
|
||||
ECS::SetModelRendererShader(e, Asset::GetShaderId("toon"));
|
||||
ECS::AddTexture(e, Asset::GetTextureId("grid"));
|
||||
ECS::AddMaterial(e, Asset::GetMaterialId("toon_mat"));
|
||||
```
|
||||
|
||||
## Examples
|
||||
- `scripts/game.as` — demo showing ECS usage, rendering and input actions.
|
||||
- `scripts/game.as` — demo showing ECS usage, rendering, scenegraph and input actions.
|
||||
|
||||
## How to document APIs
|
||||
Add Markdown pages in the `docs/` folder. Use relative links and include code fences for examples.
|
||||
|
||||
+8
-3
@@ -64,8 +64,8 @@ bash scripts/sync_wiki.sh
|
||||
|
||||
CI sync (automated):
|
||||
|
||||
1. The workflow `.github/workflows/sync-docs-to-wiki.yml` runs on pushes that affect `docs/**`.
|
||||
2. The workflow requires an SSH private key in repository secrets named `GITEA_SSH_KEY`.
|
||||
1. The workflow `.gitea/workflows/sync-docs-to-wiki.yml` runs on pushes that affect `docs/**`.
|
||||
2. The workflow requires an SSH private key in repository secrets named `WIKI_SSH_KEY`.
|
||||
|
||||
To create the deploy key and CI secret:
|
||||
|
||||
@@ -91,5 +91,10 @@ Notes:
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- If the CI job fails with authentication errors, confirm the deploy key was added to the Gitea repo and the private key is stored in the CI secrets as `GITEA_SSH_KEY`.
|
||||
- If the CI job fails with authentication errors, confirm the deploy key was added to the Gitea repo and the private key is stored in the CI secrets as `WIKI_SSH_KEY`.
|
||||
- To debug locally, run the `scripts/sync_wiki.sh` script and watch for `git push` errors.
|
||||
|
||||
## Scenes
|
||||
|
||||
Scenes are stored as TOML files in `scenes/`. Use `Scene::Load("scenes/demo.toml")` from scripts to create entities with hierarchy relationships.
|
||||
See [Scenegraph](Scenegraph.md) for the format and examples.
|
||||
|
||||
Vendored
+603
File diff suppressed because it is too large
Load Diff
+1
Submodule external/physfs added at 49cfd5fdd4
@@ -9,40 +9,40 @@ Size=400,400
|
||||
Collapsed=0
|
||||
|
||||
[Window][Game Window]
|
||||
Pos=8,28
|
||||
Size=1264,580
|
||||
Pos=232,28
|
||||
Size=663,507
|
||||
Collapsed=0
|
||||
DockId=0x00000001,0
|
||||
DockId=0x00000004,0
|
||||
|
||||
[Window][Log Viewer]
|
||||
Pos=8,610
|
||||
Size=1264,102
|
||||
Pos=8,537
|
||||
Size=1264,175
|
||||
Collapsed=0
|
||||
DockId=0x00000002,0
|
||||
|
||||
[Window][##TOAST0]
|
||||
Pos=1060,637
|
||||
Size=200,63
|
||||
Pos=1035,637
|
||||
Size=225,63
|
||||
Collapsed=0
|
||||
|
||||
[Window][##TOAST1]
|
||||
Pos=1060,564
|
||||
Size=200,63
|
||||
Pos=1035,564
|
||||
Size=225,63
|
||||
Collapsed=0
|
||||
|
||||
[Window][##TOAST2]
|
||||
Pos=1064,491
|
||||
Size=196,63
|
||||
Pos=1035,491
|
||||
Size=225,63
|
||||
Collapsed=0
|
||||
|
||||
[Window][##TOAST3]
|
||||
Pos=1064,418
|
||||
Size=196,63
|
||||
Pos=1675,741
|
||||
Size=225,63
|
||||
Collapsed=0
|
||||
|
||||
[Window][##TOAST4]
|
||||
Pos=1064,345
|
||||
Size=196,63
|
||||
Pos=1068,345
|
||||
Size=192,63
|
||||
Collapsed=0
|
||||
|
||||
[Window][Scene (0 entities)]
|
||||
@@ -71,12 +71,49 @@ Size=71,108
|
||||
Collapsed=0
|
||||
|
||||
[Window][##TOAST5]
|
||||
Pos=1064,272
|
||||
Size=196,63
|
||||
Pos=1071,272
|
||||
Size=189,63
|
||||
Collapsed=0
|
||||
|
||||
[Docking][Data]
|
||||
DockSpace ID=0x9076BACA Window=0x34F970D7 Pos=8,28 Size=1264,684 Split=Y Selected=0x27A02DAA
|
||||
DockNode ID=0x00000001 Parent=0x9076BACA SizeRef=1264,580 CentralNode=1 Selected=0x27A02DAA
|
||||
DockNode ID=0x00000002 Parent=0x9076BACA SizeRef=1264,102 Selected=0xBEDDA0C1
|
||||
[Window][Inspector]
|
||||
Pos=897,28
|
||||
Size=375,507
|
||||
Collapsed=0
|
||||
DockId=0x00000006,0
|
||||
|
||||
[Window][Scene]
|
||||
Pos=8,28
|
||||
Size=222,507
|
||||
Collapsed=0
|
||||
DockId=0x00000003,0
|
||||
|
||||
[Window][New Scene]
|
||||
Pos=512,318
|
||||
Size=255,84
|
||||
Collapsed=0
|
||||
|
||||
[Window][Save Scene]
|
||||
Pos=807,454
|
||||
Size=306,82
|
||||
Collapsed=0
|
||||
|
||||
[Window][Load Scene]
|
||||
Pos=807,454
|
||||
Size=306,82
|
||||
Collapsed=0
|
||||
|
||||
[Window][Assets]
|
||||
Pos=8,761
|
||||
Size=1904,274
|
||||
Collapsed=0
|
||||
DockId=0x00000002,1
|
||||
|
||||
[Docking][Data]
|
||||
DockSpace ID=0x9076BACA Window=0x34F970D7 Pos=8,28 Size=1264,684 Split=Y Selected=0x27A02DAA
|
||||
DockNode ID=0x00000001 Parent=0x9076BACA SizeRef=1264,507 Split=X
|
||||
DockNode ID=0x00000005 Parent=0x00000001 SizeRef=887,684 Split=X
|
||||
DockNode ID=0x00000003 Parent=0x00000005 SizeRef=222,684 Selected=0xE601B12F
|
||||
DockNode ID=0x00000004 Parent=0x00000005 SizeRef=663,684 CentralNode=1 Selected=0x27A02DAA
|
||||
DockNode ID=0x00000006 Parent=0x00000001 SizeRef=375,684 Selected=0x36DC96AB
|
||||
DockNode ID=0x00000002 Parent=0x9076BACA SizeRef=1264,175 Selected=0xBEDDA0C1
|
||||
|
||||
|
||||
+53
-2
@@ -6,8 +6,12 @@
|
||||
#include "ShaderManager.h"
|
||||
#include "ModelManager.h"
|
||||
#include "MaterialManager.h"
|
||||
#include "AssetManager.h"
|
||||
#include "TextureManager.h"
|
||||
#include "InputManager.h"
|
||||
#include <entt.hpp>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
class Application {
|
||||
public:
|
||||
@@ -21,12 +25,42 @@ public:
|
||||
|
||||
bool IsEditorEnabled() const { return enableEditor; }
|
||||
bool HasScriptCompilationError() const { return scriptCompilationError; }
|
||||
entt::registry& GetRegistry() { return registry; }
|
||||
AssetManager& GetAssetManager() { return assetManager; }
|
||||
InputManager& GetInputManager() { return inputManager; }
|
||||
Camera3D& GetEditorCamera() { return editorCamera; }
|
||||
bool IsUsingEditorCamera() const { return useEditorCamera; }
|
||||
void SetUseEditorCamera(bool enabled) { useEditorCamera = enabled; }
|
||||
|
||||
private:
|
||||
struct EntityScriptModule {
|
||||
std::string path;
|
||||
std::string moduleName;
|
||||
asIScriptModule* module = nullptr;
|
||||
// For object-instance based entity scripts we cache the object type
|
||||
// (stored opaquely here to avoid requiring the AngelScript headers
|
||||
// in all translation units) and the object-methods (Init/Update/Shutdown)
|
||||
// to avoid repeated lookups.
|
||||
asITypeInfo* objectType = nullptr;
|
||||
std::string objectTypeName;
|
||||
asIScriptFunction* initFunc = nullptr; // object method: Init(uint)
|
||||
asIScriptFunction* updateFunc = nullptr; // object method: Update(uint,float)
|
||||
asIScriptFunction* shutdownFunc = nullptr;// object method: Shutdown(uint)
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
struct SceneScriptInstance {
|
||||
asIScriptObject* instance = nullptr;
|
||||
std::string className;
|
||||
asIScriptFunction* initFunc = nullptr;
|
||||
asIScriptFunction* updateFunc = nullptr;
|
||||
asIScriptFunction* shutdownFunc = nullptr;
|
||||
};
|
||||
|
||||
ScriptEngine scriptEngine;
|
||||
HotReload* hotReload;
|
||||
bool scriptCompilationError;
|
||||
FILE* logFile;
|
||||
struct PHYSFS_File* logFile;
|
||||
GuiManager guiManager;
|
||||
bool enableEditor;
|
||||
bool queueShutdown = false;
|
||||
@@ -39,16 +73,33 @@ private:
|
||||
ShaderManager shaderManager;
|
||||
ModelManager modelManager;
|
||||
MaterialManager materialManager;
|
||||
AssetManager assetManager;
|
||||
TextureManager textureManager;
|
||||
InputManager inputManager;
|
||||
std::unordered_map<std::string, EntityScriptModule> entityScriptModules;
|
||||
std::unordered_map<entt::entity, std::string> entityScriptBindings;
|
||||
unsigned int entityScriptModuleCounter = 0;
|
||||
std::string currentSceneScriptPath;
|
||||
SceneScriptInstance sceneScriptInstance;
|
||||
Camera3D editorCamera;
|
||||
bool useEditorCamera = false;
|
||||
|
||||
static const int WINDOW_WIDTH = 1280;
|
||||
static const int WINDOW_HEIGHT = 720;
|
||||
static const int TARGET_FPS = 60;
|
||||
static const char* WINDOW_TITLE;
|
||||
static const char* SCRIPT_FILE;
|
||||
|
||||
void Update(float deltaTime);
|
||||
void UpdateSystems(float deltaTime);
|
||||
void UpdateEntityScripts(float deltaTime);
|
||||
void ShutdownEntityScripts();
|
||||
bool ProcessSceneScriptChanges();
|
||||
void ReloadEntityScriptModules();
|
||||
EntityScriptModule* GetEntityScriptModule(const std::string& path);
|
||||
void InitializeSceneScriptInstance();
|
||||
void ShutdownSceneScriptInstance();
|
||||
void CallSceneUpdate(float deltaTime);
|
||||
void RenderScene();
|
||||
void Draw();
|
||||
bool RunAutoloadScript(bool abortOnFailure);
|
||||
};
|
||||
@@ -0,0 +1,144 @@
|
||||
#pragma once
|
||||
|
||||
#include "raylib.h"
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
class ModelManager;
|
||||
class ShaderManager;
|
||||
class MaterialManager;
|
||||
class TextureManager;
|
||||
|
||||
struct AssetEntry {
|
||||
unsigned int id = 0;
|
||||
std::string source;
|
||||
};
|
||||
|
||||
struct TextureDefinition {
|
||||
std::string path;
|
||||
};
|
||||
|
||||
struct ShaderDefinition {
|
||||
std::string vsPath;
|
||||
std::string fsPath;
|
||||
};
|
||||
|
||||
struct ModelDefinition {
|
||||
enum class Type {
|
||||
Path,
|
||||
Primitive,
|
||||
Heightmap,
|
||||
Cubicmap,
|
||||
Mesh
|
||||
};
|
||||
|
||||
Type type = Type::Path;
|
||||
std::string path;
|
||||
std::string primitive;
|
||||
std::vector<float> params;
|
||||
};
|
||||
|
||||
struct MaterialDefinition {
|
||||
std::string shaderKey;
|
||||
unsigned int shaderId = 0;
|
||||
Color albedo = {255, 255, 255, 255};
|
||||
bool hasShaderKey = false;
|
||||
bool hasShaderId = false;
|
||||
};
|
||||
|
||||
class AssetManager
|
||||
{
|
||||
public:
|
||||
AssetManager();
|
||||
~AssetManager();
|
||||
|
||||
void SetManagers(ModelManager* modelMgr, ShaderManager* shaderMgr,
|
||||
MaterialManager* materialMgr, TextureManager* textureMgr);
|
||||
|
||||
// Register an existing asset ID under a key (no loading performed).
|
||||
bool RegisterTexture(const std::string& key, unsigned int id, const std::string& source = std::string());
|
||||
bool RegisterShader(const std::string& key, unsigned int id, const std::string& source = std::string());
|
||||
bool RegisterModel(const std::string& key, unsigned int id, const std::string& source = std::string());
|
||||
bool RegisterMaterial(const std::string& key, unsigned int id, const std::string& source = std::string());
|
||||
|
||||
// Load helpers that also register by key.
|
||||
unsigned int LoadTexture(const std::string& key, const std::string& path);
|
||||
unsigned int LoadShader(const std::string& key, const std::string& vsPath, const std::string& fsPath);
|
||||
unsigned int LoadModel(const std::string& key, const std::string& path);
|
||||
unsigned int LoadCube(const std::string& key, float width, float height, float length);
|
||||
unsigned int LoadPoly(const std::string& key, int sides, float radius);
|
||||
unsigned int LoadSphere(const std::string& key, float radius, int rings, int slices);
|
||||
unsigned int LoadHemiSphere(const std::string& key, float radius, int rings, int slices);
|
||||
unsigned int LoadPlane(const std::string& key, float width, float length, int resX, int resZ);
|
||||
unsigned int LoadCylinder(const std::string& key, float radius, float height, int slices);
|
||||
unsigned int LoadCone(const std::string& key, float radius, float height, int slices);
|
||||
unsigned int LoadTorus(const std::string& key, float radius, float size, int radSeg, int sides);
|
||||
unsigned int LoadKnot(const std::string& key, float radius, float size, int radSeg, int sides);
|
||||
unsigned int LoadHeightmap(const std::string& key, const std::string& heightmapPath, float sizeX, float sizeY, float sizeZ);
|
||||
unsigned int LoadCubicmap(const std::string& key, const std::string& cubicmapPath, float sizeX, float sizeY, float sizeZ);
|
||||
unsigned int LoadFromMesh(const std::string& key, Mesh mesh, const std::string& source = std::string());
|
||||
unsigned int CreateMaterial(const std::string& key, unsigned int shaderId, Color albedo);
|
||||
bool ApplyMeshTangents(const std::string& key, int meshIndex = 0);
|
||||
|
||||
// Unload by key (removes registry entries).
|
||||
bool UnloadTexture(const std::string& key);
|
||||
bool UnloadShader(const std::string& key);
|
||||
bool UnloadModel(const std::string& key);
|
||||
bool UnloadMaterial(const std::string& key);
|
||||
|
||||
// Query by key.
|
||||
unsigned int GetTextureId(const std::string& key) const;
|
||||
unsigned int GetShaderId(const std::string& key) const;
|
||||
unsigned int GetModelId(const std::string& key) const;
|
||||
unsigned int GetMaterialId(const std::string& key) const;
|
||||
|
||||
// Query by id.
|
||||
const std::string& GetTextureKey(unsigned int id) const;
|
||||
const std::string& GetShaderKey(unsigned int id) const;
|
||||
const std::string& GetModelKey(unsigned int id) const;
|
||||
const std::string& GetMaterialKey(unsigned int id) const;
|
||||
|
||||
// Asset listing for editor UI.
|
||||
std::vector<std::string> ListTextureKeys() const;
|
||||
std::vector<std::string> ListShaderKeys() const;
|
||||
std::vector<std::string> ListModelKeys() const;
|
||||
std::vector<std::string> ListMaterialKeys() const;
|
||||
|
||||
// Source info (path or descriptive string).
|
||||
std::string GetTextureSource(const std::string& key) const;
|
||||
std::string GetShaderSource(const std::string& key) const;
|
||||
std::string GetModelSource(const std::string& key) const;
|
||||
std::string GetMaterialSource(const std::string& key) const;
|
||||
|
||||
// Asset registry persistence.
|
||||
bool SaveRegistry(const std::string& path) const;
|
||||
bool LoadRegistry(const std::string& path);
|
||||
|
||||
// Clear registry entries (does not unload underlying resources).
|
||||
void Clear();
|
||||
|
||||
private:
|
||||
ModelManager* modelManager = nullptr;
|
||||
ShaderManager* shaderManager = nullptr;
|
||||
MaterialManager* materialManager = nullptr;
|
||||
TextureManager* textureManager = nullptr;
|
||||
|
||||
std::unordered_map<std::string, AssetEntry> textureAssets;
|
||||
std::unordered_map<std::string, AssetEntry> shaderAssets;
|
||||
std::unordered_map<std::string, AssetEntry> modelAssets;
|
||||
std::unordered_map<std::string, AssetEntry> materialAssets;
|
||||
|
||||
std::unordered_map<std::string, TextureDefinition> textureDefs;
|
||||
std::unordered_map<std::string, ShaderDefinition> shaderDefs;
|
||||
std::unordered_map<std::string, ModelDefinition> modelDefs;
|
||||
std::unordered_map<std::string, MaterialDefinition> materialDefs;
|
||||
|
||||
std::unordered_map<unsigned int, std::string> textureIds;
|
||||
std::unordered_map<unsigned int, std::string> shaderIds;
|
||||
std::unordered_map<unsigned int, std::string> modelIds;
|
||||
std::unordered_map<unsigned int, std::string> materialIds;
|
||||
|
||||
const std::string& GetKeyForId(const std::unordered_map<unsigned int, std::string>& map, unsigned int id) const;
|
||||
std::vector<std::string> ListKeys(const std::unordered_map<std::string, AssetEntry>& map) const;
|
||||
};
|
||||
+85
-9
@@ -1,20 +1,31 @@
|
||||
#pragma once
|
||||
#include "raylib.h"
|
||||
#include "raymath.h"
|
||||
#include "entt.hpp"
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
// ECSTransform component - position, rotation, and scale
|
||||
// ECSTransform component - position, rotation, scale, and cached local matrix
|
||||
struct ECSTransform {
|
||||
Vector3 position;
|
||||
Vector3 rotation; // Euler rotation (radians)
|
||||
Vector3 scale;
|
||||
float rotation; // Rotation around Y axis in radians
|
||||
Matrix localMatrix;
|
||||
|
||||
ECSTransform()
|
||||
: position{0.0f, 0.0f, 0.0f}
|
||||
, rotation{0.0f, 0.0f, 0.0f}
|
||||
, scale{1.0f, 1.0f, 1.0f}
|
||||
, rotation(0.0f) {}
|
||||
, localMatrix(MatrixIdentity()) {}
|
||||
|
||||
ECSTransform(Vector3 pos, Vector3 scl = {1.0f, 1.0f, 1.0f}, float rot = 0.0f)
|
||||
: position(pos), scale(scl), rotation(rot) {}
|
||||
ECSTransform(Vector3 pos, Vector3 scl, Vector3 rot)
|
||||
: position(pos), rotation(rot), scale(scl), localMatrix(MatrixIdentity()) {}
|
||||
|
||||
ECSTransform(Vector3 pos, Vector3 scl = {1.0f, 1.0f, 1.0f}, float rotY = 0.0f)
|
||||
: position(pos)
|
||||
, rotation{0.0f, rotY, 0.0f}
|
||||
, scale(scl)
|
||||
, localMatrix(MatrixIdentity()) {}
|
||||
};
|
||||
|
||||
// Velocity component - for movement
|
||||
@@ -30,23 +41,46 @@ struct Velocity {
|
||||
: linear(lin), angular(ang) {}
|
||||
};
|
||||
|
||||
// Sprite component - visual representation
|
||||
struct Sprite {
|
||||
// ModelRenderer component - 3D model rendering data
|
||||
struct ModelRenderer {
|
||||
size_t modelId; // Index into models array
|
||||
Color color; // Tint color
|
||||
float outlineSize; // Outline thickness
|
||||
unsigned int shaderId; // Shader to use (0 = no shader)
|
||||
|
||||
Sprite()
|
||||
ModelRenderer()
|
||||
: modelId(0)
|
||||
, color(WHITE)
|
||||
, outlineSize(0.0f)
|
||||
, shaderId(0) {}
|
||||
|
||||
Sprite(int model, Color col = WHITE, float outline = 0.0f, unsigned int shader = 0)
|
||||
ModelRenderer(int model, Color col = WHITE, float outline = 0.0f, unsigned int shader = 0)
|
||||
: modelId(model), color(col), outlineSize(outline), shaderId(shader) {}
|
||||
};
|
||||
|
||||
// Texture component - material texture reference
|
||||
struct TextureComponent {
|
||||
unsigned int textureId;
|
||||
|
||||
TextureComponent() : textureId(0) {}
|
||||
TextureComponent(unsigned int id) : textureId(id) {}
|
||||
};
|
||||
|
||||
// UV transform component - tiling and offset for texture sampling
|
||||
struct UVTransform {
|
||||
Vector2 scale;
|
||||
Vector2 offset;
|
||||
float rotation;
|
||||
|
||||
UVTransform()
|
||||
: scale{1.0f, 1.0f}
|
||||
, offset{0.0f, 0.0f}
|
||||
, rotation(0.0f) {}
|
||||
|
||||
UVTransform(Vector2 s, Vector2 o = {0.0f, 0.0f}, float r = 0.0f)
|
||||
: scale(s), offset(o), rotation(r) {}
|
||||
};
|
||||
|
||||
// Tag component - simple string identifier
|
||||
struct Tag {
|
||||
std::string name;
|
||||
@@ -55,6 +89,48 @@ struct Tag {
|
||||
Tag(const std::string& n) : name(n) {}
|
||||
};
|
||||
|
||||
// ScriptComponent - per-entity script reference
|
||||
struct ScriptComponent {
|
||||
std::string scriptPath;
|
||||
bool enabled = true;
|
||||
bool started = false;
|
||||
// Optional: name of the script class to instantiate. If empty, engine
|
||||
// will attempt to pick a reasonable object type from the module.
|
||||
std::string className;
|
||||
// Opaque pointer to the script object instance (asIScriptObject*).
|
||||
void* scriptInstance = nullptr;
|
||||
// Persisted overrides for editable script fields (name -> raw value string).
|
||||
std::unordered_map<std::string, std::string> scriptOverrides;
|
||||
};
|
||||
|
||||
// SceneEntity component - marks entities owned by the scene
|
||||
struct SceneEntity {
|
||||
bool persistent = false;
|
||||
std::string id;
|
||||
};
|
||||
|
||||
// Hierarchy component - parent/child links using EnTT-friendly linked list
|
||||
struct Hierarchy {
|
||||
entt::entity parent{entt::null};
|
||||
entt::entity first{entt::null};
|
||||
entt::entity prev{entt::null};
|
||||
entt::entity next{entt::null};
|
||||
};
|
||||
|
||||
// WorldTransform component - computed from hierarchy + local transform
|
||||
struct WorldTransform {
|
||||
Vector3 position;
|
||||
Vector3 rotation; // Euler rotation (radians)
|
||||
Vector3 scale;
|
||||
Matrix worldMatrix;
|
||||
|
||||
WorldTransform()
|
||||
: position{0.0f, 0.0f, 0.0f}
|
||||
, rotation{0.0f, 0.0f, 0.0f}
|
||||
, scale{1.0f, 1.0f, 1.0f}
|
||||
, worldMatrix(MatrixIdentity()) {}
|
||||
};
|
||||
|
||||
// Camera3D component - camera settings
|
||||
struct Camera3DComponent {
|
||||
Vector3 position;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
|
||||
class HotReload {
|
||||
public:
|
||||
|
||||
@@ -0,0 +1,155 @@
|
||||
#pragma once
|
||||
|
||||
#include "raylib.h"
|
||||
#include "raymath.h"
|
||||
#include "imgui.h"
|
||||
|
||||
#define IMVIEWGUIZMO_VEC3 Vector3
|
||||
#define IMVIEWGUIZMO_VEC4 Vector4
|
||||
#define IMVIEWGUIZMO_QUAT Quaternion
|
||||
#define IMVIEWGUIZMO_MAT4 Matrix
|
||||
|
||||
namespace ImViewGuizmo {
|
||||
namespace GizmoMath {
|
||||
inline float get_vec_comp(const Vector3& v, int index) {
|
||||
switch (index) {
|
||||
case 0: return v.x;
|
||||
case 1: return v.y;
|
||||
default: return v.z;
|
||||
}
|
||||
}
|
||||
|
||||
inline float get_vec_comp(const Vector4& v, int index) {
|
||||
switch (index) {
|
||||
case 0: return v.x;
|
||||
case 1: return v.y;
|
||||
case 2: return v.z;
|
||||
default: return v.w;
|
||||
}
|
||||
}
|
||||
|
||||
inline Vector3 make_vec3(float x, float y, float z) { return {x, y, z}; }
|
||||
inline Vector4 make_vec4(float x, float y, float z, float w) { return {x, y, z, w}; }
|
||||
|
||||
inline Quaternion angleAxis(float angle, const Vector3& axis) {
|
||||
return QuaternionFromAxisAngle(axis, angle);
|
||||
}
|
||||
|
||||
inline Quaternion quatLookAt(const Vector3& dir, const Vector3& up) {
|
||||
Vector3 forward = Vector3Normalize(dir);
|
||||
Vector3 right = Vector3Normalize(Vector3CrossProduct(up, forward));
|
||||
Vector3 correctedUp = Vector3CrossProduct(forward, right);
|
||||
|
||||
Matrix mat = MatrixIdentity();
|
||||
mat.m0 = right.x;
|
||||
mat.m1 = right.y;
|
||||
mat.m2 = right.z;
|
||||
mat.m4 = correctedUp.x;
|
||||
mat.m5 = correctedUp.y;
|
||||
mat.m6 = correctedUp.z;
|
||||
mat.m8 = forward.x;
|
||||
mat.m9 = forward.y;
|
||||
mat.m10 = forward.z;
|
||||
return QuaternionFromMatrix(mat);
|
||||
}
|
||||
|
||||
inline Matrix mat4_identity() { return MatrixIdentity(); }
|
||||
|
||||
inline Vector3 cross(const Vector3& a, const Vector3& b) {
|
||||
return Vector3CrossProduct(a, b);
|
||||
}
|
||||
|
||||
inline float dot(const Vector3& a, const Vector3& b) {
|
||||
return Vector3DotProduct(a, b);
|
||||
}
|
||||
|
||||
inline float dot(const Quaternion& a, const Quaternion& b) {
|
||||
return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
|
||||
}
|
||||
|
||||
inline float length(const Vector3& v) {
|
||||
return Vector3Length(v);
|
||||
}
|
||||
|
||||
inline float length2(const Vector3& v) {
|
||||
return v.x * v.x + v.y * v.y + v.z * v.z;
|
||||
}
|
||||
|
||||
inline Vector3 normalize(const Vector3& v) {
|
||||
return Vector3Normalize(v);
|
||||
}
|
||||
|
||||
inline Vector3 mix(const Vector3& a, const Vector3& b, float t) {
|
||||
return Vector3Lerp(a, b, t);
|
||||
}
|
||||
|
||||
inline Vector3 add_vv(const Vector3& a, const Vector3& b) {
|
||||
return Vector3Add(a, b);
|
||||
}
|
||||
|
||||
inline Vector3 subtract_vv(const Vector3& a, const Vector3& b) {
|
||||
return Vector3Subtract(a, b);
|
||||
}
|
||||
|
||||
inline Vector3 multiply_vf(const Vector3& v, float f) {
|
||||
return Vector3Scale(v, f);
|
||||
}
|
||||
|
||||
inline Matrix mat4_cast(const Quaternion& q) {
|
||||
return QuaternionToMatrix(q);
|
||||
}
|
||||
|
||||
inline Matrix transpose(const Matrix& m) {
|
||||
return MatrixTranspose(m);
|
||||
}
|
||||
|
||||
inline Vector3 get_matrix_col(const Matrix& m, int col) {
|
||||
switch (col) {
|
||||
case 0: return {m.m0, m.m1, m.m2};
|
||||
case 1: return {m.m4, m.m5, m.m6};
|
||||
case 2: return {m.m8, m.m9, m.m10};
|
||||
default: return {m.m12, m.m13, m.m14};
|
||||
}
|
||||
}
|
||||
|
||||
inline void set_matrix_col(Matrix& m, int col, const Vector4& v) {
|
||||
switch (col) {
|
||||
case 0:
|
||||
m.m0 = v.x; m.m1 = v.y; m.m2 = v.z; m.m3 = v.w;
|
||||
break;
|
||||
case 1:
|
||||
m.m4 = v.x; m.m5 = v.y; m.m6 = v.z; m.m7 = v.w;
|
||||
break;
|
||||
case 2:
|
||||
m.m8 = v.x; m.m9 = v.y; m.m10 = v.z; m.m11 = v.w;
|
||||
break;
|
||||
default:
|
||||
m.m12 = v.x; m.m13 = v.y; m.m14 = v.z; m.m15 = v.w;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
inline Quaternion multiply_qq(const Quaternion& a, const Quaternion& b) {
|
||||
return QuaternionMultiply(a, b);
|
||||
}
|
||||
|
||||
inline Vector3 multiply_qv(const Quaternion& q, const Vector3& v) {
|
||||
return Vector3RotateByQuaternion(v, q);
|
||||
}
|
||||
|
||||
inline Matrix multiply_mm(const Matrix& a, const Matrix& b) {
|
||||
return MatrixMultiply(a, b);
|
||||
}
|
||||
|
||||
inline Vector4 multiply_mv4(const Matrix& m, const Vector4& v) {
|
||||
Vector4 result;
|
||||
result.x = m.m0 * v.x + m.m4 * v.y + m.m8 * v.z + m.m12 * v.w;
|
||||
result.y = m.m1 * v.x + m.m5 * v.y + m.m9 * v.z + m.m13 * v.w;
|
||||
result.z = m.m2 * v.x + m.m6 * v.y + m.m10 * v.z + m.m14 * v.w;
|
||||
result.w = m.m3 * v.x + m.m7 * v.y + m.m11 * v.z + m.m15 * v.w;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
} // namespace ImViewGuizmo
|
||||
|
||||
#include "ImViewGuizmo.h"
|
||||
@@ -52,6 +52,7 @@ public:
|
||||
void BindGamepadButton(const std::string& actionName, int gamepadId, int button);
|
||||
void BindGamepadAxis(const std::string& actionName, int gamepadId, int axis, float threshold, bool negative = false);
|
||||
void ClearBindings(const std::string& actionName);
|
||||
void ClearAllActions();
|
||||
|
||||
// Input queries (called per frame)
|
||||
bool IsActionPressed(const std::string& actionName) const;
|
||||
|
||||
@@ -4,6 +4,23 @@
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
|
||||
enum class ModelSourceType {
|
||||
None,
|
||||
Path,
|
||||
Cube,
|
||||
Sphere,
|
||||
Plane
|
||||
};
|
||||
|
||||
struct ModelSource {
|
||||
ModelSourceType type = ModelSourceType::None;
|
||||
std::string path;
|
||||
Vector3 size{1.0f, 1.0f, 1.0f};
|
||||
float radius = 0.5f;
|
||||
int rings = 16;
|
||||
int slices = 16;
|
||||
};
|
||||
|
||||
class ModelManager
|
||||
{
|
||||
public:
|
||||
@@ -18,17 +35,30 @@ public:
|
||||
// Returns a unique ID for the model, or 0 on failure
|
||||
unsigned int LoadFromMesh(Mesh mesh);
|
||||
|
||||
// Load a model from a generated cube mesh
|
||||
unsigned int LoadCube(float width, float height, float length);
|
||||
|
||||
// Load a model from a generated sphere mesh
|
||||
unsigned int LoadSphere(float radius, int rings, int slices);
|
||||
|
||||
// Load a model from a generated plane mesh
|
||||
unsigned int LoadPlane(float width, float length, int resX, int resZ);
|
||||
|
||||
// Unload a model by ID
|
||||
void Unload(unsigned int id);
|
||||
|
||||
// Get a model by ID (returns nullptr if not found)
|
||||
Model* Get(unsigned int id);
|
||||
|
||||
// Get source metadata for a model if available
|
||||
const ModelSource* GetSource(unsigned int id) const;
|
||||
|
||||
// Unload all models
|
||||
void UnloadAll();
|
||||
|
||||
private:
|
||||
std::unordered_map<unsigned int, Model> models;
|
||||
std::unordered_map<unsigned int, ModelSource> modelSources;
|
||||
unsigned int nextId;
|
||||
bool unloaded;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
struct PHYSFS_File;
|
||||
|
||||
namespace PhysFSManager {
|
||||
|
||||
bool Initialize(const char* argv0);
|
||||
bool IsInitialized();
|
||||
void Shutdown();
|
||||
|
||||
bool MountPath(const std::string& path, const std::string& mountPoint, bool append = true);
|
||||
bool UnmountPath(const std::string& path);
|
||||
void ClearSearchPath();
|
||||
|
||||
bool SetWriteDir(const std::string& path);
|
||||
bool EnsureDir(const std::string& path);
|
||||
|
||||
bool Exists(const std::string& path);
|
||||
bool IsDirectory(const std::string& path);
|
||||
std::string GetRealDir(const std::string& path);
|
||||
|
||||
std::int64_t GetModTime(const std::string& path);
|
||||
std::vector<std::string> ListFiles(const std::string& path);
|
||||
|
||||
bool ReadTextFile(const std::string& path, std::string& out);
|
||||
bool WriteTextFile(const std::string& path, const std::string& contents);
|
||||
|
||||
PHYSFS_File* OpenRead(const std::string& path);
|
||||
PHYSFS_File* OpenWrite(const std::string& path);
|
||||
void Close(PHYSFS_File* file);
|
||||
|
||||
} // namespace PhysFSManager
|
||||
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "entt.hpp"
|
||||
|
||||
|
||||
// Attach child to parent (updates linked list)
|
||||
void AttachChild(entt::registry& registry, entt::entity child, entt::entity parent);
|
||||
|
||||
// Detach child from its parent (if any)
|
||||
void DetachFromParent(entt::registry& registry, entt::entity child);
|
||||
|
||||
// Detach all children from a parent (orphans them)
|
||||
void DetachChildren(entt::registry& registry, entt::entity parent);
|
||||
|
||||
// Update WorldTransform for all entities using hierarchy traversal
|
||||
void UpdateWorldTransforms(entt::registry& registry);
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "entt.hpp"
|
||||
#include <string>
|
||||
|
||||
class ModelManager;
|
||||
class ShaderManager;
|
||||
class TextureManager;
|
||||
class AssetManager;
|
||||
|
||||
|
||||
// Set global context used by scene loading
|
||||
void SetSceneContext(entt::registry* registry, ModelManager* modelManager, ShaderManager* shaderManager, TextureManager* textureManager);
|
||||
void SetSceneAssetManager(AssetManager* assetManager);
|
||||
|
||||
// Load a scene from a TOML-like file. Returns the root entity (or entt::null on failure).
|
||||
entt::entity LoadSceneFromFile(const std::string& path, bool clearExisting);
|
||||
|
||||
// Save the current scene to a TOML-like file. Returns true on success.
|
||||
bool SaveSceneToFile(const std::string& path);
|
||||
|
||||
// Clear all entities from the registry
|
||||
void ClearScene();
|
||||
|
||||
// Scene script path helpers
|
||||
void SetSceneScriptPath(const std::string& path);
|
||||
const std::string& GetSceneScriptPath();
|
||||
bool ConsumeSceneScriptPath(std::string& outPath);
|
||||
@@ -4,6 +4,11 @@
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
|
||||
struct ShaderSource {
|
||||
std::string vsPath;
|
||||
std::string fsPath;
|
||||
};
|
||||
|
||||
class ShaderManager
|
||||
{
|
||||
public:
|
||||
@@ -20,11 +25,15 @@ public:
|
||||
// Get a shader by ID (returns nullptr if not found)
|
||||
Shader* Get(unsigned int id);
|
||||
|
||||
// Get source metadata for a shader if available
|
||||
const ShaderSource* GetSource(unsigned int id) const;
|
||||
|
||||
// Unload all shaders
|
||||
void UnloadAll();
|
||||
|
||||
private:
|
||||
std::unordered_map<unsigned int, Shader> shaders;
|
||||
std::unordered_map<unsigned int, ShaderSource> shaderSources;
|
||||
unsigned int nextId;
|
||||
bool unloaded;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "raylib.h"
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
struct TextureSource {
|
||||
std::string path;
|
||||
};
|
||||
|
||||
class TextureManager
|
||||
{
|
||||
public:
|
||||
TextureManager();
|
||||
~TextureManager();
|
||||
|
||||
// Load a texture from a file
|
||||
// Returns a unique ID for the texture, or 0 on failure
|
||||
unsigned int Load(const std::string& path);
|
||||
|
||||
// Unload a texture by ID
|
||||
void Unload(unsigned int id);
|
||||
|
||||
// Get a texture by ID (returns nullptr if not found)
|
||||
Texture2D* Get(unsigned int id);
|
||||
|
||||
// Get source metadata for a texture if available
|
||||
const TextureSource* GetSource(unsigned int id) const;
|
||||
|
||||
// Unload all textures
|
||||
void UnloadAll();
|
||||
|
||||
private:
|
||||
std::unordered_map<unsigned int, Texture2D> textures;
|
||||
std::unordered_map<unsigned int, TextureSource> textureSources;
|
||||
unsigned int nextId;
|
||||
bool unloaded;
|
||||
};
|
||||
@@ -5,6 +5,12 @@
|
||||
#include "gui/fa-solid-900.h"
|
||||
#include "gui/ImGuiNotify.hpp"
|
||||
#include "LogWindow.h" // Include the new LogWindow class
|
||||
#include "raylib.h"
|
||||
#include <entt.hpp>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
|
||||
class Application;
|
||||
|
||||
@@ -15,15 +21,135 @@ public:
|
||||
|
||||
void Initialize(Application* application);
|
||||
void Render(RenderTexture2D& renderTexture);
|
||||
void ApplyRenderTextureResize(RenderTexture2D& renderTexture);
|
||||
void Shutdown();
|
||||
|
||||
private:
|
||||
enum class AssetUnloadType {
|
||||
None,
|
||||
Texture,
|
||||
Shader,
|
||||
Model,
|
||||
Material
|
||||
};
|
||||
|
||||
void SetupDockspace(RenderTexture2D& renderTexture);
|
||||
void RenderNotifications();
|
||||
void RenderErrorBanner();
|
||||
void SetTheme();
|
||||
void RenderSceneWindow();
|
||||
void RenderAssetWindow();
|
||||
void RenderInspectorWindow();
|
||||
void RenderEntityNode(entt::entity entity);
|
||||
bool IsEntityAncestor(entt::entity ancestor, entt::entity entity) const;
|
||||
const char* GetEntityLabel(entt::entity entity);
|
||||
struct ScriptEditableFieldMeta {
|
||||
std::string name;
|
||||
std::string type;
|
||||
std::string group;
|
||||
std::string description;
|
||||
bool hasRange = false;
|
||||
float rangeMin = 0.0f;
|
||||
float rangeMax = 1.0f;
|
||||
std::vector<std::string> options;
|
||||
};
|
||||
|
||||
struct ScriptEditableCache {
|
||||
std::string className;
|
||||
std::vector<ScriptEditableFieldMeta> fields;
|
||||
std::int64_t lastWriteTime = 0;
|
||||
bool hasWriteTime = false;
|
||||
};
|
||||
|
||||
enum class ScriptValueKind { Float, Int, Bool, String };
|
||||
|
||||
struct ScriptEditableValue {
|
||||
ScriptValueKind kind = ScriptValueKind::Float;
|
||||
bool hasValue = false;
|
||||
bool hasBaseline = false;
|
||||
float f = 0.0f;
|
||||
float baselineF = 0.0f;
|
||||
int i = 0;
|
||||
int baselineI = 0;
|
||||
bool b = false;
|
||||
bool baselineB = false;
|
||||
std::string s;
|
||||
std::string baselineS;
|
||||
};
|
||||
|
||||
void SyncTagBuffer(entt::entity entity);
|
||||
void SyncScriptPathBuffer(entt::entity entity);
|
||||
void RenderScriptEditableControls(entt::entity entity);
|
||||
ScriptEditableCache &GetOrParseEditableCache(const std::string &path, const std::string &className);
|
||||
|
||||
bool showLogWindow = true;
|
||||
bool showSceneWindow = true;
|
||||
bool showInspectorWindow = true;
|
||||
bool showAssetWindow = false;
|
||||
Application *app;
|
||||
LogWindow logWindow; // Add LogWindow as a member
|
||||
entt::entity selectedEntity = entt::null;
|
||||
entt::entity tagBufferEntity = entt::null;
|
||||
char tagBuffer[128] = {0};
|
||||
entt::entity scriptPathEntity = entt::null;
|
||||
char scriptPathBuffer[260] = {0};
|
||||
char sceneScriptPathBuffer[260] = {0};
|
||||
std::string lastSceneScriptPath;
|
||||
ImVec2 desiredRenderSize = ImVec2(0.0f, 0.0f);
|
||||
ImGuiID mainDockspaceId = 0;
|
||||
bool requestSceneNew = false;
|
||||
bool requestSceneLoad = false;
|
||||
bool requestSceneSave = false;
|
||||
char sceneLoadPath[260] = {0};
|
||||
char sceneSavePath[260] = {0};
|
||||
int addComponentIndex = 0;
|
||||
|
||||
char textureKeyBuffer[128] = {0};
|
||||
char texturePathBuffer[260] = {0};
|
||||
int textureUnloadIndex = 0;
|
||||
|
||||
char shaderKeyBuffer[128] = {0};
|
||||
char shaderVsPathBuffer[260] = {0};
|
||||
char shaderFsPathBuffer[260] = {0};
|
||||
int shaderUnloadIndex = 0;
|
||||
|
||||
char modelKeyBuffer[128] = {0};
|
||||
char modelPathBuffer[260] = {0};
|
||||
char modelHeightmapPathBuffer[260] = {0};
|
||||
char modelCubicmapPathBuffer[260] = {0};
|
||||
int modelUnloadIndex = 0;
|
||||
int modelPrimitiveIndex = 0;
|
||||
float modelWidth = 1.0f;
|
||||
float modelHeight = 1.0f;
|
||||
float modelLength = 1.0f;
|
||||
float modelRadius = 0.5f;
|
||||
int modelRings = 16;
|
||||
int modelSlices = 16;
|
||||
int modelResX = 1;
|
||||
int modelResZ = 1;
|
||||
int modelRadSeg = 16;
|
||||
int modelSides = 8;
|
||||
int modelPolySides = 6;
|
||||
float modelSizeX = 1.0f;
|
||||
float modelSizeY = 1.0f;
|
||||
float modelSizeZ = 1.0f;
|
||||
int modelAutoKeyCounter = 0;
|
||||
|
||||
char materialKeyBuffer[128] = {0};
|
||||
int materialUnloadIndex = 0;
|
||||
int materialShaderIndex = 0;
|
||||
int materialShaderId = 0;
|
||||
float materialAlbedo[4] = {1.0f, 1.0f, 1.0f, 1.0f};
|
||||
|
||||
AssetUnloadType pendingAssetUnloadType = AssetUnloadType::None;
|
||||
std::string pendingAssetUnloadKey;
|
||||
|
||||
Vector3 editorCameraPivot = {0.0f, 0.0f, 0.0f};
|
||||
Vector3 editorPivotOffset = {0.0f, 0.0f, 0.0f};
|
||||
entt::entity editorPivotEntity = entt::null;
|
||||
bool editorPivotInitialized = false;
|
||||
|
||||
std::unordered_map<std::string, ScriptEditableCache> scriptEditableCache;
|
||||
std::unordered_map<entt::entity, std::unordered_map<std::string, ScriptEditableValue>> scriptEditableStaged;
|
||||
std::unordered_map<entt::entity, void*> scriptEditableInstancePtr;
|
||||
};
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
// Warning: Requires ImGui docking with multi-viewport enabled
|
||||
#ifndef NOTIFY_RENDER_OUTSIDE_MAIN_WINDOW
|
||||
#define NOTIFY_RENDER_OUTSIDE_MAIN_WINDOW true // If true, the notifications will be rendered in the corner of the monitor, otherwise in the corner of the main window
|
||||
#define NOTIFY_RENDER_OUTSIDE_MAIN_WINDOW false // If true, the notifications will be rendered in the corner of the monitor, otherwise in the corner of the main window
|
||||
#endif
|
||||
|
||||
/**
|
||||
@@ -502,8 +502,9 @@ namespace ImGui
|
||||
}
|
||||
|
||||
#if NOTIFY_RENDER_LIMIT > 0
|
||||
if (i > NOTIFY_RENDER_LIMIT)
|
||||
if (i >= NOTIFY_RENDER_LIMIT)
|
||||
{
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
class asIScriptEngine;
|
||||
|
||||
void RegisterPhysFSBindings(asIScriptEngine* engine);
|
||||
@@ -4,6 +4,9 @@ class asIScriptEngine;
|
||||
class ShaderManager;
|
||||
class ModelManager;
|
||||
class MaterialManager;
|
||||
class TextureManager;
|
||||
class AssetManager;
|
||||
|
||||
void SetResourceManagers(ShaderManager* shaderMgr, ModelManager* modelMgr, MaterialManager* materialMgr);
|
||||
void SetResourceManagers(ShaderManager* shaderMgr, ModelManager* modelMgr, MaterialManager* materialMgr, TextureManager* textureMgr);
|
||||
void SetAssetManager(AssetManager* assetMgr);
|
||||
void RegisterResourceBindings(asIScriptEngine *engine);
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
class asIScriptEngine;
|
||||
|
||||
void RegisterSceneBindings(asIScriptEngine* engine);
|
||||
@@ -12,7 +12,10 @@ public:
|
||||
bool Initialize();
|
||||
void Shutdown();
|
||||
bool CompileScript(const std::string& filename);
|
||||
bool CompileModule(const std::string& moduleName, const std::string& filename, asIScriptModule** outModule);
|
||||
bool CallModuleFunction(asIScriptModule* module, const std::string& decl);
|
||||
void CallScriptFunction(asIScriptFunction* func, float dt = 0.0f);
|
||||
void CallScriptFunction(asIScriptFunction* func, unsigned int entityId, float dt);
|
||||
void GarbageCollect();
|
||||
|
||||
// Add a path used to resolve #include directives in scripts
|
||||
@@ -24,10 +27,21 @@ public:
|
||||
void RestoreScriptState(const std::string &state);
|
||||
|
||||
asIScriptEngine* GetEngine() const { return engine; }
|
||||
asIScriptModule* GetCurrentModule() const { return currentModule; }
|
||||
asIScriptFunction* GetUpdateFunction() const { return updateFunc; }
|
||||
asIScriptFunction* GetInitFunction() const { return initFunc; }
|
||||
asIScriptFunction* GetShutdownFunction() const { return shutdownFunc; }
|
||||
|
||||
// Create and release script object instances for entity scripts
|
||||
asIScriptObject* CreateObjectInstance(asIScriptModule* module, const std::string& className);
|
||||
void ReleaseObjectInstance(asIScriptObject* obj);
|
||||
// Retrieve an object's method by declaration (e.g. "void Update(uint, float)")
|
||||
asIScriptFunction* GetObjectMethod(asIScriptModule* module, const std::string& className, const std::string& methodDecl);
|
||||
// Call an object method; method must be an object method (use GetObjectMethod)
|
||||
void CallObjectMethod(asIScriptFunction* method, asIScriptObject* object, unsigned int entityId, float dt);
|
||||
// Call a scene object method (Init/Update/Shutdown) without entity id
|
||||
void CallSceneObjectMethod(asIScriptFunction* method, asIScriptObject* object, float dt = 0.0f);
|
||||
|
||||
private:
|
||||
asIScriptEngine* engine;
|
||||
asIScriptFunction* updateFunc;
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
scene_script = "scripts/demo_scene.as"
|
||||
|
||||
[[entity]]
|
||||
id = "entity_1"
|
||||
tag = "Root"
|
||||
position = [0.000, 0.000, 0.000]
|
||||
scale = [1.000, 1.000, 1.000]
|
||||
rotation_deg = [0.000, 0.000, 0.000]
|
||||
|
||||
[[entity]]
|
||||
id = "entity_2"
|
||||
parent = "entity_1"
|
||||
tag = "Parent"
|
||||
position = [2.000, -1.555, 0.000]
|
||||
scale = [1.000, 1.000, 1.000]
|
||||
rotation_deg = [0.000, 0.000, 0.000]
|
||||
model_asset = "Torus"
|
||||
model_id = 2
|
||||
color = 0x00FF00FF
|
||||
outline_size = 0.000
|
||||
shader_vs = "shaders/toon.vs"
|
||||
shader_fs = "shaders/toon.fs"
|
||||
script = "scripts/ball.as"
|
||||
script_var_phase = "0.000000"
|
||||
script_var_speed = "0.699000"
|
||||
script_var_amplitude = "1.748000"
|
||||
|
||||
[[entity]]
|
||||
id = "entity_3"
|
||||
parent = "entity_2"
|
||||
tag = "Child"
|
||||
position = [0.500, -3.100, 1.600]
|
||||
scale = [0.600, 0.600, 0.600]
|
||||
rotation_deg = [0.000, 0.000, 0.000]
|
||||
model = "sphere"
|
||||
radius = 0.500
|
||||
rings = 16
|
||||
slices = 16
|
||||
color = 0xFF0000FF
|
||||
outline_size = 0.000
|
||||
shader_vs = "shaders/toon.vs"
|
||||
shader_fs = "shaders/toon.fs"
|
||||
script = "scripts/ball.as"
|
||||
script_var_amplitude = "1.452000"
|
||||
|
||||
[[entity]]
|
||||
id = "entity_6"
|
||||
parent = "entity_2"
|
||||
tag = "Child 2"
|
||||
position = [0.000, 0.351, 1.900]
|
||||
scale = [1.000, 1.000, 1.000]
|
||||
rotation_deg = [0.000, 0.000, 0.000]
|
||||
model = "sphere"
|
||||
radius = 0.500
|
||||
rings = 16
|
||||
slices = 16
|
||||
color = 0x4523BAFF
|
||||
outline_size = 0.000
|
||||
shader_vs = "shaders/toon.vs"
|
||||
shader_fs = "shaders/toon.fs"
|
||||
script = "scripts/ball.as"
|
||||
script_var_amplitude = "0.568000"
|
||||
script_var_phase = "0.472000"
|
||||
script_var_speed = "10.000000"
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
scene_script = "scripts/demo_scene.as"
|
||||
|
||||
[[entity]]
|
||||
id = "entity_1048581"
|
||||
tag = "Light"
|
||||
position = [0.000, 0.000, 0.000]
|
||||
scale = [1.000, 1.000, 1.000]
|
||||
rotation_deg = [0.000, 0.000, 0.000]
|
||||
|
||||
[[entity]]
|
||||
id = "entity_1048576"
|
||||
tag = "Camera"
|
||||
position = [0.000, 0.000, 0.000]
|
||||
scale = [1.000, 1.000, 1.000]
|
||||
rotation_deg = [0.000, 0.000, 0.000]
|
||||
|
||||
[[entity]]
|
||||
id = "entity_1048578"
|
||||
tag = "Root"
|
||||
position = [0.000, 0.000, 0.000]
|
||||
scale = [1.000, 1.000, 1.000]
|
||||
rotation_deg = [0.000, 0.000, 0.000]
|
||||
|
||||
[[entity]]
|
||||
id = "entity_1048579"
|
||||
tag = "Parent"
|
||||
position = [0.000, 0.000, 0.000]
|
||||
scale = [1.000, 1.000, 1.000]
|
||||
rotation_deg = [0.000, 0.000, 0.000]
|
||||
model_asset = "Torus"
|
||||
model_id = 1
|
||||
color = 0xFFFFFFFF
|
||||
outline_size = 0.000
|
||||
|
||||
[[entity]]
|
||||
id = "entity_1048580"
|
||||
parent = "entity_1048579"
|
||||
tag = "Child"
|
||||
position = [0.000, 1.400, 0.000]
|
||||
scale = [1.000, 1.000, 1.000]
|
||||
rotation_deg = [0.000, 0.000, 0.000]
|
||||
model_asset = "demo_cube"
|
||||
model = "cube"
|
||||
model_size = [1.000, 1.000, 1.000]
|
||||
color = 0xC11E1EFF
|
||||
outline_size = 0.000
|
||||
shader_vs = "shaders/toon.vs"
|
||||
shader_fs = "shaders/toon.fs"
|
||||
|
||||
+97
-8
@@ -44,7 +44,9 @@ namespace ECS {
|
||||
void GetPosition(uint entity, float &out x, float &out y, float &out z);
|
||||
void SetScale(uint entity, float x, float y, float z);
|
||||
void SetRotation(uint entity, float rotation);
|
||||
void SetRotationEuler(uint entity, float x, float y, float z);
|
||||
float GetRotation(uint entity);
|
||||
void GetRotationEuler(uint entity, float &out x, float &out y, float &out z);
|
||||
bool HasTransform(uint entity);
|
||||
void RemoveTransform(uint entity);
|
||||
|
||||
@@ -55,14 +57,29 @@ namespace ECS {
|
||||
bool HasVelocity(uint entity);
|
||||
void RemoveVelocity(uint entity);
|
||||
|
||||
// Sprite component
|
||||
void AddSprite(uint entity, int modelId, uint color, float outlineSize = 0.0f);
|
||||
void SetSpriteColor(uint entity, uint color);
|
||||
void SetSpriteModel(uint entity, int modelId);
|
||||
void SetSpriteOutline(uint entity, float outlineSize);
|
||||
void SetSpriteShader(uint entity, uint shaderId);
|
||||
bool HasSprite(uint entity);
|
||||
void RemoveSprite(uint entity);
|
||||
// ModelRenderer component
|
||||
void AddModelRenderer(uint entity, int modelId, uint color, float outlineSize = 0.0f);
|
||||
void SetModelRendererColor(uint entity, uint color);
|
||||
void SetModelRendererModel(uint entity, int modelId);
|
||||
void SetModelRendererOutline(uint entity, float outlineSize);
|
||||
void SetModelRendererShader(uint entity, uint shaderId);
|
||||
bool HasModelRenderer(uint entity);
|
||||
void RemoveModelRenderer(uint entity);
|
||||
|
||||
// Texture component
|
||||
void AddTexture(uint entity, uint textureId);
|
||||
void SetTextureId(uint entity, uint textureId);
|
||||
uint GetTextureId(uint entity);
|
||||
bool HasTexture(uint entity);
|
||||
void RemoveTexture(uint entity);
|
||||
|
||||
// UV transform component
|
||||
void AddUVTransform(uint entity, float scaleX, float scaleY, float offsetX = 0.0f, float offsetY = 0.0f, float rotation = 0.0f);
|
||||
void SetUVScale(uint entity, float scaleX, float scaleY);
|
||||
void SetUVOffset(uint entity, float offsetX, float offsetY);
|
||||
void SetUVRotation(uint entity, float rotation);
|
||||
bool HasUVTransform(uint entity);
|
||||
void RemoveUVTransform(uint entity);
|
||||
|
||||
// Tag component
|
||||
void AddTag(uint entity, const string &in name);
|
||||
@@ -70,6 +87,12 @@ namespace ECS {
|
||||
void SetTag(uint entity, const string &in name);
|
||||
bool HasTag(uint entity);
|
||||
void RemoveTag(uint entity);
|
||||
|
||||
// Hierarchy component
|
||||
void SetParent(uint child, uint parent);
|
||||
void ClearParent(uint child);
|
||||
uint GetParent(uint child);
|
||||
bool HasParent(uint child);
|
||||
|
||||
// Camera3D component
|
||||
void AddCamera3D(uint entity, float px, float py, float pz, float tx, float ty, float tz, float fovy);
|
||||
@@ -125,6 +148,39 @@ namespace Material {
|
||||
void SetAlbedo(uint id, uint8 r, uint8 g, uint8 b, uint8 a);
|
||||
}
|
||||
|
||||
// Asset manager (keyed assets)
|
||||
namespace Asset {
|
||||
uint LoadTexture(const string &in key, const string &in path);
|
||||
uint LoadShader(const string &in key, const string &in vsPath, const string &in fsPath);
|
||||
uint LoadModel(const string &in key, const string &in path);
|
||||
uint LoadCube(const string &in key, float width, float height, float length);
|
||||
uint LoadPoly(const string &in key, int sides, float radius);
|
||||
uint LoadSphere(const string &in key, float radius, int rings, int slices);
|
||||
uint LoadHemiSphere(const string &in key, float radius, int rings, int slices);
|
||||
uint LoadPlane(const string &in key, float width, float length, int resX, int resZ);
|
||||
uint LoadCylinder(const string &in key, float radius, float height, int slices);
|
||||
uint LoadCone(const string &in key, float radius, float height, int slices);
|
||||
uint LoadTorus(const string &in key, float radius, float size, int radSeg, int sides);
|
||||
uint LoadKnot(const string &in key, float radius, float size, int radSeg, int sides);
|
||||
uint LoadHeightmap(const string &in key, const string &in heightmapPath, float sizeX, float sizeY, float sizeZ);
|
||||
uint LoadCubicmap(const string &in key, const string &in cubicmapPath, float sizeX, float sizeY, float sizeZ);
|
||||
uint CreateMaterial(const string &in key, uint shaderId, uint8 r, uint8 g, uint8 b, uint8 a);
|
||||
bool UnloadTexture(const string &in key);
|
||||
bool UnloadShader(const string &in key);
|
||||
bool UnloadModel(const string &in key);
|
||||
bool UnloadMaterial(const string &in key);
|
||||
uint GetTextureId(const string &in key);
|
||||
uint GetShaderId(const string &in key);
|
||||
uint GetModelId(const string &in key);
|
||||
uint GetMaterialId(const string &in key);
|
||||
bool ApplyMeshTangents(const string &in key, int meshIndex = 0);
|
||||
}
|
||||
|
||||
namespace Scene {
|
||||
uint Load(const string &in path, bool clearExisting = true);
|
||||
void Clear();
|
||||
}
|
||||
|
||||
namespace Texture {
|
||||
class Image {
|
||||
int width;
|
||||
@@ -178,7 +234,10 @@ float GetActionStrength(const string &in actionName);
|
||||
void RegisterAction(const string &in actionName);
|
||||
void BindKeyboard(const string &in actionName, int keyCode);
|
||||
void BindMouseButton(const string &in actionName, int mouseButton);
|
||||
void BindGamepadButton(const string &in actionName, int gamepadId, int button);
|
||||
void BindGamepadAxis(const string &in actionName, int gamepadId, int axis, float threshold = 0.5f, bool negative = false);
|
||||
void ClearBindings(const string &in actionName);
|
||||
void ClearAllActions();
|
||||
|
||||
namespace Key {
|
||||
const int SPACE = 32;
|
||||
@@ -204,3 +263,33 @@ namespace MouseButton {
|
||||
const int MIDDLE = 2;
|
||||
}
|
||||
|
||||
namespace GamepadButton {
|
||||
const int UNKNOWN = 0;
|
||||
const int LEFT_FACE_UP = 1;
|
||||
const int LEFT_FACE_RIGHT = 2;
|
||||
const int LEFT_FACE_DOWN = 3;
|
||||
const int LEFT_FACE_LEFT = 4;
|
||||
const int RIGHT_FACE_UP = 5;
|
||||
const int RIGHT_FACE_RIGHT = 6;
|
||||
const int RIGHT_FACE_DOWN = 7;
|
||||
const int RIGHT_FACE_LEFT = 8;
|
||||
const int LEFT_TRIGGER_1 = 9;
|
||||
const int LEFT_TRIGGER_2 = 10;
|
||||
const int RIGHT_TRIGGER_1 = 11;
|
||||
const int RIGHT_TRIGGER_2 = 12;
|
||||
const int MIDDLE_LEFT = 13;
|
||||
const int MIDDLE = 14;
|
||||
const int MIDDLE_RIGHT = 15;
|
||||
const int LEFT_THUMB = 16;
|
||||
const int RIGHT_THUMB = 17;
|
||||
}
|
||||
|
||||
namespace GamepadAxis {
|
||||
const int LEFT_X = 0;
|
||||
const int LEFT_Y = 1;
|
||||
const int RIGHT_X = 2;
|
||||
const int RIGHT_Y = 3;
|
||||
const int LEFT_TRIGGER = 4;
|
||||
const int RIGHT_TRIGGER = 5;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
// Autoload configuration script.
|
||||
// Runs before any scene/game script to configure input and preload resources.
|
||||
|
||||
void Autoload() {
|
||||
// Pick the initial scene to load.
|
||||
Scene::Load("scenes/demo.toml");
|
||||
|
||||
ClearAllActions();
|
||||
|
||||
// Movement
|
||||
RegisterAction("move_up");
|
||||
RegisterAction("move_down");
|
||||
RegisterAction("move_left");
|
||||
RegisterAction("move_right");
|
||||
BindKeyboard("move_up", Key::W);
|
||||
BindKeyboard("move_up", Key::UP);
|
||||
BindKeyboard("move_down", Key::S);
|
||||
BindKeyboard("move_down", Key::DOWN);
|
||||
BindKeyboard("move_left", Key::A);
|
||||
BindKeyboard("move_left", Key::LEFT);
|
||||
BindKeyboard("move_right", Key::D);
|
||||
BindKeyboard("move_right", Key::RIGHT);
|
||||
|
||||
// Actions
|
||||
RegisterAction("jump");
|
||||
RegisterAction("interact");
|
||||
RegisterAction("attack");
|
||||
BindKeyboard("jump", Key::SPACE);
|
||||
BindKeyboard("interact", Key::E);
|
||||
BindKeyboard("attack", Key::LEFT_CONTROL);
|
||||
BindMouseButton("attack", MouseButton::LEFT);
|
||||
|
||||
// Gamepad (optional)
|
||||
BindGamepadButton("jump", 0, GamepadButton::RIGHT_FACE_DOWN);
|
||||
BindGamepadButton("interact", 0, GamepadButton::RIGHT_FACE_RIGHT);
|
||||
BindGamepadAxis("move_left", 0, GamepadAxis::LEFT_X, 0.3f, true);
|
||||
BindGamepadAxis("move_right", 0, GamepadAxis::LEFT_X, 0.3f, false);
|
||||
|
||||
// TODO: Preload assets and other config here.
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// Class-based entity script: one `EntityScript` instance per entity.
|
||||
// Implements Init/Update/Shutdown so the engine will instantiate it per-entity.
|
||||
|
||||
class EntityScript
|
||||
{
|
||||
float baseX;
|
||||
float baseY;
|
||||
float baseZ;
|
||||
[group["Movement"]][desc["Phase accumulator for the sine wave"]][editable][range[0,1]]float phase;
|
||||
|
||||
// per-instance movement parameters (can be tuned per-entity)
|
||||
[group["Movement"]][desc["Bounce height"]][editable][range[0,10]]float amplitude = 2.5f;
|
||||
[group["Movement"]][desc["Cycles per second"]][editable][range[0,10]]float speed = 2.5f;
|
||||
|
||||
[group["State"]][desc["Debug animation state"]][editable][options[Idle,Walk,Run]] int state = 0;
|
||||
|
||||
EntityScript()
|
||||
{
|
||||
baseX = 0.0f;
|
||||
baseY = 0.0f;
|
||||
baseZ = 0.0f;
|
||||
phase = 0.0f;
|
||||
}
|
||||
|
||||
void Init(uint entity)
|
||||
{
|
||||
if (!ECS::IsValid(entity)) return;
|
||||
if (!ECS::HasTransform(entity))
|
||||
ECS::AddTransform(entity, 0.0f, 0.0f, 0.0f);
|
||||
|
||||
ECS::GetPosition(entity, baseX, baseY, baseZ);
|
||||
phase = 0.0f;
|
||||
}
|
||||
|
||||
void Update(uint entity, float dt)
|
||||
{
|
||||
if (!ECS::IsValid(entity)) return;
|
||||
if (!ECS::HasTransform(entity)) return;
|
||||
|
||||
phase += dt;
|
||||
float offset = Math::Sin(phase * speed + float(entity) * 0.1f) * amplitude;
|
||||
ECS::SetPosition(entity, baseX, baseY + offset, baseZ);
|
||||
}
|
||||
|
||||
void Shutdown(uint entity)
|
||||
{
|
||||
// nothing to clean up per-instance for this simple example
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Demo scene script with input-driven behavior.
|
||||
class SceneScript {
|
||||
uint sceneRoot = 0;
|
||||
uint camera = 0;
|
||||
uint light = 0;
|
||||
uint cube = 0;
|
||||
float time = 0.0f;
|
||||
bool highlight = false;
|
||||
|
||||
void Init() {
|
||||
Log(LOG_INFO, "Demo scene script init");
|
||||
|
||||
cube = ECS::CreateEntity();
|
||||
ECS::AddTransform(cube, 0.0f, 1.5f, 0.0f);
|
||||
uint modelId = Asset::GetModelId("demo_cube");
|
||||
if (modelId == 0) {
|
||||
modelId = Asset::LoadCube("demo_cube", 1.0f, 1.0f, 1.0f);
|
||||
}
|
||||
ECS::AddModelRenderer(cube, int(modelId), 0xFFAA00FF, 0.0f);
|
||||
ECS::AddTag(cube, "InputCube");
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
Log(LOG_INFO, "Demo scene script shutdown");
|
||||
}
|
||||
|
||||
void Update(float dt) {
|
||||
time += dt;
|
||||
|
||||
if (ECS::IsValid(cube) && ECS::HasTransform(cube)) {
|
||||
ECS::SetRotationEuler(cube, 0.0f, time, time * 0.5f);
|
||||
}
|
||||
|
||||
if (IsActionPressed("jump")) {
|
||||
highlight = !highlight;
|
||||
if (highlight) {
|
||||
ECS::SetModelRendererColor(cube, 0x00FF88FF);
|
||||
ECS::SetModelRendererOutline(cube, 2.0f);
|
||||
Toast::Info("Highlight on");
|
||||
} else {
|
||||
ECS::SetModelRendererColor(cube, 0xFFAA00FF);
|
||||
ECS::SetModelRendererOutline(cube, 0.0f);
|
||||
Toast::Info("Highlight off");
|
||||
}
|
||||
}
|
||||
|
||||
if (IsActionPressed("attack")) {
|
||||
ECS::SetLightDirection(light, -0.5f, 0.7f, -0.3f);
|
||||
Toast::Success("Light toggled");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ void Init() {
|
||||
// === Create Cube with Outline ===
|
||||
cube = ECS::CreateEntity();
|
||||
ECS::AddTransform(cube, -1.5, 0.5, 0.0);
|
||||
ECS::AddSprite(cube, cubeModel, 0xFFFFFFFF, 0.0);
|
||||
ECS::AddModelRenderer(cube, cubeModel, 0xFFFFFFFF, 0.0);
|
||||
ECS::AddMaterial(cube, redMaterial);
|
||||
ECS::AddTag(cube, "Cube");
|
||||
ECS::AddVelocity(cube, 0.0, 0.0, 0.0, 1.0); // rotating
|
||||
@@ -64,7 +64,7 @@ void Init() {
|
||||
// === Create Sphere ===
|
||||
sphere = ECS::CreateEntity();
|
||||
ECS::AddTransform(sphere, 1.5, 0.5, 0.0);
|
||||
ECS::AddSprite(sphere, sphereModel, 0xFFFFFFFF, 0.0);
|
||||
ECS::AddModelRenderer(sphere, sphereModel, 0xFFFFFFFF, 0.0);
|
||||
ECS::AddMaterial(sphere, blueMaterial);
|
||||
ECS::AddTag(sphere, "Sphere");
|
||||
ECS::AddVelocity(sphere, 0.5, 0.0, 0.2, 0.5); // moving and rotating
|
||||
|
||||
+5
-195
@@ -1,197 +1,7 @@
|
||||
// ECS-based game script demo with script-driven rendering
|
||||
// This demonstrates creating entities with various components and managing rendering
|
||||
|
||||
// Resource IDs
|
||||
uint toonShader = 0;
|
||||
uint outlineShader = 0;
|
||||
uint cubeModel = 0;
|
||||
uint sphereModel = 0;
|
||||
uint defaultMaterial = 0;
|
||||
|
||||
// Scene entities
|
||||
uint camera = 0;
|
||||
uint light = 0;
|
||||
|
||||
// Store entity IDs for manipulation
|
||||
array<uint> entities;
|
||||
uint player;
|
||||
uint spinningCube;
|
||||
float time = 0.0f;
|
||||
|
||||
void Init() {
|
||||
Log(LOG_TRACE, "Initialization complete - ECS Demo with Script-Driven Rendering!");
|
||||
Toast::Success("ECS Script initialized successfully!");
|
||||
|
||||
// === Load shaders ===
|
||||
toonShader = Shader::Load("shaders/toon.vs", "shaders/toon.fs");
|
||||
outlineShader = Shader::Load("shaders/outline.vs", "shaders/outline.fs");
|
||||
|
||||
if (toonShader == 0 || outlineShader == 0) {
|
||||
Toast::Error("Failed to load shaders!");
|
||||
return;
|
||||
}
|
||||
|
||||
// === Load models ===
|
||||
cubeModel = Model::LoadCube(1.0, 1.0, 1.0);
|
||||
sphereModel = Model::LoadSphere(0.5, 16, 16);
|
||||
|
||||
// === Create default material ===
|
||||
defaultMaterial = Material::Create(toonShader, 255, 255, 255, 255);
|
||||
|
||||
// === Set up camera ===
|
||||
camera = ECS::CreateEntity();
|
||||
ECS::AddCamera3D(camera,
|
||||
5.0, 5.0, 5.0, // position
|
||||
0.0, 0.0, 0.0, // target
|
||||
45.0); // fovy
|
||||
ECS::AddTag(camera, "MainCamera");
|
||||
|
||||
// === Set up light ===
|
||||
light = ECS::CreateEntity();
|
||||
ECS::AddLight(light, 0.5, 0.7, 0.3, 1.0); // direction + intensity
|
||||
ECS::AddTag(light, "MainLight");
|
||||
|
||||
// === Create player entity at origin with outline ===
|
||||
player = ECS::CreateEntity();
|
||||
ECS::AddTransform(player, 0.0f, 0.0f, 0.0f);
|
||||
ECS::AddSprite(player, sphereModel, 0xFF0000FF, 0.0f); // Red sphere
|
||||
ECS::SetSpriteShader(player, toonShader); // Apply toon shader
|
||||
|
||||
ECS::AddTag(player, "Player");
|
||||
Log(LOG_INFO, "Created player entity: " + player);
|
||||
|
||||
// === Create a spinning cube ===
|
||||
spinningCube = ECS::CreateEntity();
|
||||
ECS::AddTransform(spinningCube, 2.0f, 0.0f, 0.0f);
|
||||
ECS::AddSprite(spinningCube, cubeModel, 0x00FF00FF, 0.0f); // Green cube
|
||||
ECS::SetSpriteShader(spinningCube, toonShader); // Apply toon shader
|
||||
ECS::AddVelocity(spinningCube, 0.0f, 0.0f, 0.0f, 2.0f); // Angular velocity
|
||||
|
||||
ECS::AddTag(spinningCube, "Spinner");
|
||||
Log(LOG_INFO, "Created spinning cube: " + spinningCube);
|
||||
|
||||
// === Create orbiting entities ===
|
||||
for (int i = 0; i < 5; i++) {
|
||||
uint entity = ECS::CreateEntity();
|
||||
|
||||
float angle = (float(i) / 5.0f) * 6.28318f; // 2*PI
|
||||
float radius = 3.0f;
|
||||
float x = Math::Cos(angle) * radius;
|
||||
float z = Math::Sin(angle) * radius;
|
||||
|
||||
ECS::AddTransform(entity, x, 0.0f, z);
|
||||
|
||||
// Different colors for each
|
||||
uint color = 0;
|
||||
uint8 r = 255, g = 255, b = 255;
|
||||
if (i == 0) { color = 0xFFFF00FF; r = 255; g = 255; b = 0; } // Yellow else if (i == 1) { color = 0xFF00FFFF; r = 255; g = 0; b = 255; } // Magenta else if (i == 2) { color = 0x00FFFFFF; r = 0; g = 255; b = 255; } // Cyan else if (i == 3) { color = 0xFFA500FF; r = 255; g = 165; b = 0; } // Orange else { color = 0xFF69B4FF; r = 255; g = 105; b = 180; } // Pink
|
||||
|
||||
ECS::AddSprite(entity, sphereModel, color, 0.0f);
|
||||
ECS::SetSpriteShader(entity, toonShader); // Apply toon shader
|
||||
|
||||
ECS::AddTag(entity, "Orbiter" + i);
|
||||
entities.insertLast(entity);
|
||||
}
|
||||
|
||||
// === Create a moving entity ===
|
||||
uint mover = ECS::CreateEntity();
|
||||
ECS::AddTransform(mover, -3.0f, 0.5f, 0.0f);
|
||||
ECS::AddSprite(mover, cubeModel, 0xFFFFFFFF, 0.0f); // White cube
|
||||
ECS::SetSpriteShader(mover, toonShader); // Apply toon shader
|
||||
ECS::AddVelocity(mover, 1.0f, 0.0f, 0.5f, 0.0f); // Moving velocity
|
||||
|
||||
ECS::AddTag(mover, "Mover");
|
||||
entities.insertLast(mover);
|
||||
|
||||
Toast::Success("Created " + (entities.length() + 2) + " entities with script-driven rendering!");
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
Log(LOG_TRACE, "Shutdown complete!");
|
||||
}
|
||||
bool lightToggle = false;
|
||||
void Update(float dt) {
|
||||
time += dt;
|
||||
|
||||
// Update camera to orbit around the scene
|
||||
if (ECS::HasCamera3D(camera)) {
|
||||
float angle = time * 0.3;
|
||||
float radius = 7.0;
|
||||
float x = Math::Cos(angle) * radius;
|
||||
float z = Math::Sin(angle) * radius;
|
||||
ECS::SetCameraPosition(camera, x, 5.0, z);
|
||||
}
|
||||
|
||||
// Update player position - simple back and forth motion
|
||||
float playerX = Math::Sin(time) * 2.0f;
|
||||
ECS::SetPosition(player, playerX, 0.0f, 0.0f);
|
||||
|
||||
// Update orbiting entities in a circle
|
||||
for (uint i = 0; i < entities.length() - 1; i++) {
|
||||
uint entity = entities[i];
|
||||
if (!ECS::IsValid(entity)) continue;
|
||||
|
||||
float angle = (float(i) / 5.0f) * 6.28318f + time * 0.5f;
|
||||
float radius = 3.0f;
|
||||
float x = Math::Cos(angle) * radius;
|
||||
float z = Math::Sin(angle) * radius;
|
||||
float y = Math::Sin(time * 2.0f + float(i)) * 0.5f; // Bobbing motion
|
||||
|
||||
ECS::SetPosition(entity, x, y, z);
|
||||
ECS::SetRotation(entity, time + float(i));
|
||||
}
|
||||
|
||||
// The mover entity wraps around using the Velocity component
|
||||
// which is handled by the C++ movement system
|
||||
if (entities.length() > 0) {
|
||||
uint mover = entities[entities.length() - 1];
|
||||
if (ECS::IsValid(mover) && ECS::HasTransform(mover)) {
|
||||
float x, y, z;
|
||||
ECS::GetPosition(mover, x, y, z);
|
||||
|
||||
// Wrap around
|
||||
if (x > 5.0f) {
|
||||
ECS::SetPosition(mover, -5.0f, y, z);
|
||||
}
|
||||
if (z > 5.0f) {
|
||||
ECS::SetPosition(mover, x, y, -5.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === Input Action System Demo ===
|
||||
// Toggle light with jump action
|
||||
if (IsActionPressed("jump")) {
|
||||
lightToggle = !lightToggle;
|
||||
|
||||
if (lightToggle) {
|
||||
ECS::SetLightDirection(light, -0.5, 0.7, -0.3);
|
||||
Toast::Info("Light direction changed!");
|
||||
} else {
|
||||
ECS::SetLightDirection(light, 0.5, 0.7, 0.3);
|
||||
Toast::Info("Light direction reset!");
|
||||
}
|
||||
}
|
||||
|
||||
// Move player entity with input actions
|
||||
if (ECS::IsValid(player) && ECS::HasTransform(player)) {
|
||||
float x, y, z;
|
||||
ECS::GetPosition(player, x, y, z);
|
||||
float moveSpeed = 3.0f * dt;
|
||||
|
||||
if (IsActionDown("move_left")) {
|
||||
x -= moveSpeed;
|
||||
}
|
||||
if (IsActionDown("move_right")) {
|
||||
x += moveSpeed;
|
||||
}
|
||||
if (IsActionDown("move_up")) {
|
||||
z -= moveSpeed;
|
||||
}
|
||||
if (IsActionDown("move_down")) {
|
||||
z += moveSpeed;
|
||||
}
|
||||
|
||||
ECS::SetPosition(player, x, y, z);
|
||||
// Empty scene script to avoid auto-loading demo content.
|
||||
class SceneScript {
|
||||
void Init() {}
|
||||
void Shutdown() {}
|
||||
void Update(float dt) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// Example scene script using a class instance.
|
||||
class SceneScript {
|
||||
float time = 0.0f;
|
||||
|
||||
void Init() {
|
||||
Log(LOG_INFO, "SceneScript.Init (example)");
|
||||
}
|
||||
|
||||
void Update(float dt) {
|
||||
time += dt;
|
||||
if (time > 1.0f) {
|
||||
time = 0.0f;
|
||||
Log(LOG_DEBUG, "SceneScript.Update tick");
|
||||
}
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
Log(LOG_INFO, "SceneScript.Shutdown (example)");
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
float x = 50;
|
||||
float y = 100;
|
||||
float r = 40.0f;
|
||||
float theta = 0.0f;
|
||||
float ix = 0.0f;
|
||||
float iy = 0.0f;
|
||||
Texture::Image img;
|
||||
Texture::Texture2D tex;
|
||||
|
||||
void Init() {
|
||||
Log(LOG_TRACE, "Initialization complete!");
|
||||
Toast::Success("Script initialized successfully!");
|
||||
img = Texture::LoadImage("assets/raylib_96x96.png");
|
||||
Toast::Info("Image loaded successfully.");
|
||||
tex = Texture::LoadTexture("assets/raylib_96x96.png");
|
||||
Toast::Info("Texture loaded successfully.");
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
Texture::Unload(img);
|
||||
Texture::Unload(tex);
|
||||
Log(LOG_TRACE, "Shutdown complete!");
|
||||
}
|
||||
|
||||
void Update(float dt) {
|
||||
x += 64 * dt;
|
||||
theta += 2.0f * dt;
|
||||
ix = 100+ r * Math::Cos(theta);
|
||||
iy = 100+r * Math::Sin(theta);
|
||||
if (x > 800) {
|
||||
Log(LOG_TRACE, format("Reset happened at position {}", x));
|
||||
x = 0;
|
||||
Toast::Info(format("X position has been reset to {}", x));
|
||||
}
|
||||
}
|
||||
+724
-94
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+24
-29
@@ -1,8 +1,7 @@
|
||||
#include "HotReload.h"
|
||||
#include "PhysFSManager.h"
|
||||
#include <iostream>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
HotReload::HotReload(const std::string& pathToWatch) : path(pathToWatch), lastWriteTime(0) {
|
||||
UpdateLastWriteTime();
|
||||
}
|
||||
@@ -21,36 +20,32 @@ void HotReload::UpdateLastWriteTime() {
|
||||
}
|
||||
|
||||
std::time_t HotReload::GetFileWriteTime(const std::string& filename) {
|
||||
try {
|
||||
auto ftime = fs::last_write_time(filename);
|
||||
auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
|
||||
ftime - fs::file_time_type::clock::now() + std::chrono::system_clock::now());
|
||||
return std::chrono::system_clock::to_time_t(sctp);
|
||||
} catch (const std::exception&) {
|
||||
return 0;
|
||||
}
|
||||
return static_cast<std::time_t>(PhysFSManager::GetModTime(filename));
|
||||
}
|
||||
|
||||
std::time_t HotReload::GetLatestWriteTimeInPath() {
|
||||
try {
|
||||
fs::path p(path);
|
||||
if (fs::is_regular_file(p)) {
|
||||
return GetFileWriteTime(path);
|
||||
}
|
||||
|
||||
// If it's a directory, scan files (non-recursively) and return the
|
||||
// newest write time found.
|
||||
if (fs::is_directory(p)) {
|
||||
std::time_t newest = 0;
|
||||
for (auto &entry : fs::directory_iterator(p)) {
|
||||
if (!fs::is_regular_file(entry.path())) continue;
|
||||
std::time_t t = GetFileWriteTime(entry.path().string());
|
||||
if (t > newest) newest = t;
|
||||
}
|
||||
return newest;
|
||||
}
|
||||
} catch (const std::exception&) {
|
||||
// fall through
|
||||
if (PhysFSManager::Exists(path) && !PhysFSManager::IsDirectory(path)) {
|
||||
return GetFileWriteTime(path);
|
||||
}
|
||||
|
||||
if (PhysFSManager::IsDirectory(path)) {
|
||||
std::time_t newest = 0;
|
||||
auto entries = PhysFSManager::ListFiles(path);
|
||||
for (const auto& entry : entries) {
|
||||
if (entry == "." || entry == "..") continue;
|
||||
std::string child = path;
|
||||
if (!child.empty() && child.back() != '/') {
|
||||
child.push_back('/');
|
||||
}
|
||||
child += entry;
|
||||
if (PhysFSManager::IsDirectory(child)) {
|
||||
continue;
|
||||
}
|
||||
std::time_t t = GetFileWriteTime(child);
|
||||
if (t > newest) newest = t;
|
||||
}
|
||||
return newest;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -55,6 +55,10 @@ void InputManager::ClearBindings(const std::string& actionName) {
|
||||
}
|
||||
}
|
||||
|
||||
void InputManager::ClearAllActions() {
|
||||
actions.clear();
|
||||
}
|
||||
|
||||
bool InputManager::IsActionPressed(const std::string& actionName) const {
|
||||
auto it = actions.find(actionName);
|
||||
if (it == actions.end()) return false;
|
||||
|
||||
@@ -13,6 +13,7 @@ ModelManager::~ModelManager()
|
||||
UnloadModel(pair.second);
|
||||
}
|
||||
models.clear();
|
||||
modelSources.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +29,10 @@ unsigned int ModelManager::Load(const std::string& path)
|
||||
|
||||
unsigned int id = nextId++;
|
||||
models[id] = model;
|
||||
ModelSource source;
|
||||
source.type = ModelSourceType::Path;
|
||||
source.path = path;
|
||||
modelSources[id] = source;
|
||||
log_info("Loaded model id=%u from %s", id, path.c_str());
|
||||
|
||||
return id;
|
||||
@@ -44,6 +49,49 @@ unsigned int ModelManager::LoadFromMesh(Mesh mesh)
|
||||
return id;
|
||||
}
|
||||
|
||||
unsigned int ModelManager::LoadCube(float width, float height, float length)
|
||||
{
|
||||
Mesh mesh = GenMeshCube(width, height, length);
|
||||
unsigned int id = LoadFromMesh(mesh);
|
||||
if (id != 0) {
|
||||
ModelSource source;
|
||||
source.type = ModelSourceType::Cube;
|
||||
source.size = {width, height, length};
|
||||
modelSources[id] = source;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
unsigned int ModelManager::LoadSphere(float radius, int rings, int slices)
|
||||
{
|
||||
Mesh mesh = GenMeshSphere(radius, rings, slices);
|
||||
unsigned int id = LoadFromMesh(mesh);
|
||||
if (id != 0) {
|
||||
ModelSource source;
|
||||
source.type = ModelSourceType::Sphere;
|
||||
source.radius = radius;
|
||||
source.rings = rings;
|
||||
source.slices = slices;
|
||||
modelSources[id] = source;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
unsigned int ModelManager::LoadPlane(float width, float length, int resX, int resZ)
|
||||
{
|
||||
Mesh mesh = GenMeshPlane(width, length, resX, resZ);
|
||||
unsigned int id = LoadFromMesh(mesh);
|
||||
if (id != 0) {
|
||||
ModelSource source;
|
||||
source.type = ModelSourceType::Plane;
|
||||
source.size = {width, 1.0f, length};
|
||||
source.rings = resX;
|
||||
source.slices = resZ;
|
||||
modelSources[id] = source;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
void ModelManager::Unload(unsigned int id)
|
||||
{
|
||||
auto it = models.find(id);
|
||||
@@ -51,6 +99,7 @@ void ModelManager::Unload(unsigned int id)
|
||||
{
|
||||
UnloadModel(it->second);
|
||||
models.erase(it);
|
||||
modelSources.erase(id);
|
||||
log_info("Unloaded model id=%u", id);
|
||||
}
|
||||
}
|
||||
@@ -65,6 +114,16 @@ Model* ModelManager::Get(unsigned int id)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const ModelSource* ModelManager::GetSource(unsigned int id) const
|
||||
{
|
||||
auto it = modelSources.find(id);
|
||||
if (it != modelSources.end())
|
||||
{
|
||||
return &it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ModelManager::UnloadAll()
|
||||
{
|
||||
if (!unloaded) {
|
||||
@@ -73,6 +132,7 @@ void ModelManager::UnloadAll()
|
||||
UnloadModel(pair.second);
|
||||
}
|
||||
models.clear();
|
||||
modelSources.clear();
|
||||
unloaded = true;
|
||||
log_info("Unloaded all models");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
#include "PhysFSManager.h"
|
||||
#include "raylib.h"
|
||||
#include "physfs.h"
|
||||
#include "log/log.h"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <climits>
|
||||
|
||||
namespace PhysFSManager {
|
||||
|
||||
static bool g_initialized = false;
|
||||
|
||||
static unsigned char* RaylibLoadFileDataCallback(const char* fileName, int* dataSize) {
|
||||
if (!fileName || !dataSize) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PHYSFS_File* file = PHYSFS_openRead(fileName);
|
||||
if (!file) {
|
||||
*dataSize = 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PHYSFS_sint64 length = PHYSFS_fileLength(file);
|
||||
if (length <= 0 || length > static_cast<PHYSFS_sint64>(INT32_MAX)) {
|
||||
PHYSFS_close(file);
|
||||
*dataSize = 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
unsigned char* data = static_cast<unsigned char*>(MemAlloc(static_cast<size_t>(length)));
|
||||
if (!data) {
|
||||
PHYSFS_close(file);
|
||||
*dataSize = 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PHYSFS_sint64 read = PHYSFS_readBytes(file, data, length);
|
||||
PHYSFS_close(file);
|
||||
if (read != length) {
|
||||
MemFree(data);
|
||||
*dataSize = 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
*dataSize = static_cast<int>(length);
|
||||
return data;
|
||||
}
|
||||
|
||||
static bool RaylibSaveFileDataCallback(const char* fileName, void* data, int dataSize) {
|
||||
if (!fileName || !data || dataSize <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PHYSFS_File* file = PHYSFS_openWrite(fileName);
|
||||
if (!file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PHYSFS_sint64 written = PHYSFS_writeBytes(file, data, dataSize);
|
||||
PHYSFS_close(file);
|
||||
return written == dataSize;
|
||||
}
|
||||
|
||||
bool Initialize(const char* argv0) {
|
||||
if (g_initialized) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!PHYSFS_init(argv0)) {
|
||||
log_error("PhysFS init failed: %s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
|
||||
return false;
|
||||
}
|
||||
|
||||
PHYSFS_permitSymbolicLinks(0);
|
||||
|
||||
// Configure raylib to use PhysFS for all file reads/writes.
|
||||
SetLoadFileDataCallback(RaylibLoadFileDataCallback);
|
||||
SetSaveFileDataCallback(RaylibSaveFileDataCallback);
|
||||
|
||||
g_initialized = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsInitialized() {
|
||||
return g_initialized;
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
if (!g_initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
ClearSearchPath();
|
||||
SetLoadFileDataCallback(nullptr);
|
||||
SetSaveFileDataCallback(nullptr);
|
||||
PHYSFS_deinit();
|
||||
g_initialized = false;
|
||||
}
|
||||
|
||||
bool MountPath(const std::string& path, const std::string& mountPoint, bool append) {
|
||||
const char* mount = mountPoint.empty() ? nullptr : mountPoint.c_str();
|
||||
if (!PHYSFS_mount(path.c_str(), mount, append ? 1 : 0)) {
|
||||
log_warn("PhysFS mount failed: %s -> %s (%s)", path.c_str(), mountPoint.c_str(),
|
||||
PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnmountPath(const std::string& path) {
|
||||
if (!PHYSFS_unmount(path.c_str())) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ClearSearchPath() {
|
||||
char** paths = PHYSFS_getSearchPath();
|
||||
if (!paths) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (char** i = paths; *i != nullptr; ++i) {
|
||||
PHYSFS_unmount(*i);
|
||||
}
|
||||
|
||||
PHYSFS_freeList(paths);
|
||||
}
|
||||
|
||||
bool SetWriteDir(const std::string& path) {
|
||||
if (!PHYSFS_setWriteDir(path.c_str())) {
|
||||
log_warn("PhysFS set write dir failed: %s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EnsureDir(const std::string& path) {
|
||||
if (path.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string normalized = path;
|
||||
std::replace(normalized.begin(), normalized.end(), '\\', '/');
|
||||
|
||||
size_t start = 0;
|
||||
while (start < normalized.size()) {
|
||||
size_t slash = normalized.find('/', start);
|
||||
std::string segment = normalized.substr(0, slash);
|
||||
if (!segment.empty() && !IsDirectory(segment)) {
|
||||
if (!PHYSFS_mkdir(segment.c_str())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (slash == std::string::npos) {
|
||||
break;
|
||||
}
|
||||
start = slash + 1;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Exists(const std::string& path) {
|
||||
return PHYSFS_exists(path.c_str()) != 0;
|
||||
}
|
||||
|
||||
bool IsDirectory(const std::string& path) {
|
||||
return PHYSFS_isDirectory(path.c_str()) != 0;
|
||||
}
|
||||
|
||||
std::string GetRealDir(const std::string& path) {
|
||||
const char* dir = PHYSFS_getRealDir(path.c_str());
|
||||
return dir ? std::string(dir) : std::string();
|
||||
}
|
||||
|
||||
std::int64_t GetModTime(const std::string& path) {
|
||||
PHYSFS_Stat stat{};
|
||||
if (!PHYSFS_stat(path.c_str(), &stat)) {
|
||||
return 0;
|
||||
}
|
||||
return static_cast<std::int64_t>(stat.modtime);
|
||||
}
|
||||
|
||||
std::vector<std::string> ListFiles(const std::string& path) {
|
||||
std::vector<std::string> result;
|
||||
char** files = PHYSFS_enumerateFiles(path.c_str());
|
||||
if (!files) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (char** i = files; *i != nullptr; ++i) {
|
||||
result.emplace_back(*i);
|
||||
}
|
||||
|
||||
PHYSFS_freeList(files);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ReadTextFile(const std::string& path, std::string& out) {
|
||||
out.clear();
|
||||
PHYSFS_File* file = PHYSFS_openRead(path.c_str());
|
||||
if (!file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PHYSFS_sint64 length = PHYSFS_fileLength(file);
|
||||
if (length < 0) {
|
||||
PHYSFS_close(file);
|
||||
return false;
|
||||
}
|
||||
|
||||
out.resize(static_cast<size_t>(length));
|
||||
PHYSFS_sint64 read = PHYSFS_readBytes(file, out.data(), length);
|
||||
PHYSFS_close(file);
|
||||
if (read != length) {
|
||||
out.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteTextFile(const std::string& path, const std::string& contents) {
|
||||
PHYSFS_File* file = PHYSFS_openWrite(path.c_str());
|
||||
if (!file) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PHYSFS_sint64 written = PHYSFS_writeBytes(file, contents.data(), contents.size());
|
||||
PHYSFS_close(file);
|
||||
return written == static_cast<PHYSFS_sint64>(contents.size());
|
||||
}
|
||||
|
||||
PHYSFS_File* OpenRead(const std::string& path) {
|
||||
return PHYSFS_openRead(path.c_str());
|
||||
}
|
||||
|
||||
PHYSFS_File* OpenWrite(const std::string& path) {
|
||||
return PHYSFS_openWrite(path.c_str());
|
||||
}
|
||||
|
||||
void Close(PHYSFS_File* file) {
|
||||
if (file) {
|
||||
PHYSFS_close(file);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace PhysFSManager
|
||||
@@ -0,0 +1,145 @@
|
||||
#include "SceneGraph.h"
|
||||
#include "ECSComponents.h"
|
||||
#include "raymath.h"
|
||||
#include "entt.hpp"
|
||||
|
||||
static Matrix BuildLocalMatrix(const ECSTransform& local) {
|
||||
Matrix scale = MatrixScale(local.scale.x, local.scale.y, local.scale.z);
|
||||
Matrix rotation = MatrixRotateXYZ(local.rotation);
|
||||
Matrix translation = MatrixTranslate(local.position.x, local.position.y, local.position.z);
|
||||
return MatrixMultiply(MatrixMultiply(translation, rotation), scale);
|
||||
}
|
||||
|
||||
static Vector3 ExtractScale(const Matrix& matrix) {
|
||||
Vector3 scale;
|
||||
scale.x = sqrtf(matrix.m0 * matrix.m0 + matrix.m1 * matrix.m1 + matrix.m2 * matrix.m2);
|
||||
scale.y = sqrtf(matrix.m4 * matrix.m4 + matrix.m5 * matrix.m5 + matrix.m6 * matrix.m6);
|
||||
scale.z = sqrtf(matrix.m8 * matrix.m8 + matrix.m9 * matrix.m9 + matrix.m10 * matrix.m10);
|
||||
return scale;
|
||||
}
|
||||
|
||||
static WorldTransform ComputeWorldFromParent(ECSTransform& local, const WorldTransform* parentWorld) {
|
||||
local.localMatrix = BuildLocalMatrix(local);
|
||||
|
||||
WorldTransform world;
|
||||
if (!parentWorld) {
|
||||
world.worldMatrix = local.localMatrix;
|
||||
world.position = local.position;
|
||||
world.rotation = local.rotation;
|
||||
world.scale = local.scale;
|
||||
return world;
|
||||
}
|
||||
|
||||
world.worldMatrix = MatrixMultiply(parentWorld->worldMatrix, local.localMatrix);
|
||||
world.position = {world.worldMatrix.m12, world.worldMatrix.m13, world.worldMatrix.m14};
|
||||
world.scale = ExtractScale(world.worldMatrix);
|
||||
world.rotation = QuaternionToEuler(QuaternionFromMatrix(world.worldMatrix));
|
||||
return world;
|
||||
}
|
||||
|
||||
void AttachChild(entt::registry& registry, entt::entity child, entt::entity parent) {
|
||||
if (child == parent) return;
|
||||
|
||||
DetachFromParent(registry, child);
|
||||
|
||||
auto& childHierarchy = registry.get_or_emplace<Hierarchy>(child);
|
||||
auto& parentHierarchy = registry.get_or_emplace<Hierarchy>(parent);
|
||||
|
||||
childHierarchy.parent = parent;
|
||||
childHierarchy.prev = entt::null;
|
||||
childHierarchy.next = parentHierarchy.first;
|
||||
|
||||
if (parentHierarchy.first != entt::null) {
|
||||
registry.get_or_emplace<Hierarchy>(parentHierarchy.first).prev = child;
|
||||
}
|
||||
|
||||
parentHierarchy.first = child;
|
||||
}
|
||||
|
||||
void DetachFromParent(entt::registry& registry, entt::entity child) {
|
||||
auto* childHierarchy = registry.try_get<Hierarchy>(child);
|
||||
if (!childHierarchy || childHierarchy->parent == entt::null) {
|
||||
return;
|
||||
}
|
||||
|
||||
entt::entity parent = childHierarchy->parent;
|
||||
auto& parentHierarchy = registry.get_or_emplace<Hierarchy>(parent);
|
||||
|
||||
if (parentHierarchy.first == child) {
|
||||
parentHierarchy.first = childHierarchy->next;
|
||||
}
|
||||
|
||||
if (childHierarchy->prev != entt::null) {
|
||||
registry.get_or_emplace<Hierarchy>(childHierarchy->prev).next = childHierarchy->next;
|
||||
}
|
||||
|
||||
if (childHierarchy->next != entt::null) {
|
||||
registry.get_or_emplace<Hierarchy>(childHierarchy->next).prev = childHierarchy->prev;
|
||||
}
|
||||
|
||||
childHierarchy->parent = entt::null;
|
||||
childHierarchy->prev = entt::null;
|
||||
childHierarchy->next = entt::null;
|
||||
}
|
||||
|
||||
void DetachChildren(entt::registry& registry, entt::entity parent) {
|
||||
auto* parentHierarchy = registry.try_get<Hierarchy>(parent);
|
||||
if (!parentHierarchy) {
|
||||
return;
|
||||
}
|
||||
|
||||
entt::entity child = parentHierarchy->first;
|
||||
parentHierarchy->first = entt::null;
|
||||
|
||||
while (child != entt::null) {
|
||||
auto* childHierarchy = registry.try_get<Hierarchy>(child);
|
||||
entt::entity nextChild = childHierarchy ? childHierarchy->next : entt::null;
|
||||
|
||||
if (childHierarchy) {
|
||||
childHierarchy->parent = entt::null;
|
||||
childHierarchy->prev = entt::null;
|
||||
childHierarchy->next = entt::null;
|
||||
}
|
||||
|
||||
child = nextChild;
|
||||
}
|
||||
}
|
||||
|
||||
static void UpdateWorldRecursive(entt::registry& registry, entt::entity entity, const WorldTransform* parentWorld) {
|
||||
const WorldTransform* currentWorld = parentWorld;
|
||||
|
||||
if (auto* local = registry.try_get<ECSTransform>(entity)) {
|
||||
auto& world = registry.get_or_emplace<WorldTransform>(entity);
|
||||
world = ComputeWorldFromParent(*local, parentWorld);
|
||||
currentWorld = &world;
|
||||
}
|
||||
|
||||
auto* hierarchy = registry.try_get<Hierarchy>(entity);
|
||||
if (!hierarchy) {
|
||||
return;
|
||||
}
|
||||
|
||||
entt::entity child = hierarchy->first;
|
||||
while (child != entt::null) {
|
||||
auto* childHierarchy = registry.try_get<Hierarchy>(child);
|
||||
entt::entity nextChild = childHierarchy ? childHierarchy->next : entt::null;
|
||||
UpdateWorldRecursive(registry, child, currentWorld);
|
||||
child = nextChild;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateWorldTransforms(entt::registry& registry) {
|
||||
// Roots: entities with no hierarchy, or hierarchy parent == null
|
||||
auto transformView = registry.view<ECSTransform>(entt::exclude<Hierarchy>);
|
||||
for (auto entity : transformView) {
|
||||
UpdateWorldRecursive(registry, entity, nullptr);
|
||||
}
|
||||
|
||||
auto hierarchyView = registry.view<Hierarchy>();
|
||||
for (auto entity : hierarchyView) {
|
||||
const auto& hierarchy = hierarchyView.get<Hierarchy>(entity);
|
||||
if (hierarchy.parent == entt::null) {
|
||||
UpdateWorldRecursive(registry, entity, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,6 +13,7 @@ ShaderManager::~ShaderManager()
|
||||
UnloadShader(pair.second);
|
||||
}
|
||||
shaders.clear();
|
||||
shaderSources.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +29,7 @@ unsigned int ShaderManager::Load(const std::string& vsPath, const std::string& f
|
||||
|
||||
unsigned int id = nextId++;
|
||||
shaders[id] = shader;
|
||||
shaderSources[id] = ShaderSource{vsPath, fsPath};
|
||||
log_info("Loaded shader id=%u from %s / %s (raylib id=%u)", id, vsPath.c_str(), fsPath.c_str(), shader.id);
|
||||
|
||||
return id;
|
||||
@@ -40,6 +42,7 @@ void ShaderManager::Unload(unsigned int id)
|
||||
{
|
||||
UnloadShader(it->second);
|
||||
shaders.erase(it);
|
||||
shaderSources.erase(id);
|
||||
log_info("Unloaded shader id=%u", id);
|
||||
}
|
||||
}
|
||||
@@ -54,6 +57,16 @@ Shader* ShaderManager::Get(unsigned int id)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const ShaderSource* ShaderManager::GetSource(unsigned int id) const
|
||||
{
|
||||
auto it = shaderSources.find(id);
|
||||
if (it != shaderSources.end())
|
||||
{
|
||||
return &it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ShaderManager::UnloadAll()
|
||||
{
|
||||
if (!unloaded) {
|
||||
@@ -62,6 +75,7 @@ void ShaderManager::UnloadAll()
|
||||
UnloadShader(pair.second);
|
||||
}
|
||||
shaders.clear();
|
||||
shaderSources.clear();
|
||||
unloaded = true;
|
||||
log_info("Unloaded all shaders");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
#include "TextureManager.h"
|
||||
#include "log/log.h"
|
||||
|
||||
TextureManager::TextureManager() : nextId(1), unloaded(false)
|
||||
{
|
||||
}
|
||||
|
||||
TextureManager::~TextureManager()
|
||||
{
|
||||
if (!unloaded) {
|
||||
for (auto& pair : textures) {
|
||||
UnloadTexture(pair.second);
|
||||
}
|
||||
textures.clear();
|
||||
textureSources.clear();
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int TextureManager::Load(const std::string& path)
|
||||
{
|
||||
Texture2D texture = LoadTexture(path.c_str());
|
||||
if (texture.id == 0) {
|
||||
log_warn("Failed to load texture: %s", path.c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsigned int id = nextId++;
|
||||
textures[id] = texture;
|
||||
textureSources[id] = TextureSource{path};
|
||||
log_info("Loaded texture id=%u from %s", id, path.c_str());
|
||||
return id;
|
||||
}
|
||||
|
||||
void TextureManager::Unload(unsigned int id)
|
||||
{
|
||||
auto it = textures.find(id);
|
||||
if (it != textures.end()) {
|
||||
UnloadTexture(it->second);
|
||||
textures.erase(it);
|
||||
textureSources.erase(id);
|
||||
log_info("Unloaded texture id=%u", id);
|
||||
}
|
||||
}
|
||||
|
||||
Texture2D* TextureManager::Get(unsigned int id)
|
||||
{
|
||||
auto it = textures.find(id);
|
||||
if (it != textures.end()) {
|
||||
return &it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const TextureSource* TextureManager::GetSource(unsigned int id) const
|
||||
{
|
||||
auto it = textureSources.find(id);
|
||||
if (it != textureSources.end()) {
|
||||
return &it->second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void TextureManager::UnloadAll()
|
||||
{
|
||||
if (!unloaded) {
|
||||
for (auto& pair : textures) {
|
||||
UnloadTexture(pair.second);
|
||||
}
|
||||
textures.clear();
|
||||
textureSources.clear();
|
||||
unloaded = true;
|
||||
log_info("Unloaded all textures");
|
||||
}
|
||||
}
|
||||
+1991
-7
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
#include "ImViewGuizmoRaymath.h"
|
||||
+240
-40
@@ -1,5 +1,6 @@
|
||||
#include "scripting/ECSBindings.h"
|
||||
#include "ECSComponents.h"
|
||||
#include "SceneGraph.h"
|
||||
#include <angelscript.h>
|
||||
#include <assert.h>
|
||||
#include <string>
|
||||
@@ -20,7 +21,10 @@ uint32_t AS_CreateEntity() {
|
||||
|
||||
void AS_DestroyEntity(uint32_t entity) {
|
||||
if (!registry) return;
|
||||
registry->destroy((entt::entity)entity);
|
||||
entt::entity e = (entt::entity)entity;
|
||||
DetachFromParent(*registry, e);
|
||||
DetachChildren(*registry, e);
|
||||
registry->destroy(e);
|
||||
}
|
||||
|
||||
bool AS_IsEntityValid(uint32_t entity) {
|
||||
@@ -62,18 +66,34 @@ void AS_SetScale(uint32_t entity, float x, float y, float z) {
|
||||
void AS_SetRotation(uint32_t entity, float rotation) {
|
||||
if (!registry) return;
|
||||
if (auto* t = registry->try_get<ECSTransform>((entt::entity)entity)) {
|
||||
t->rotation = rotation;
|
||||
t->rotation.y = rotation;
|
||||
}
|
||||
}
|
||||
|
||||
float AS_GetRotation(uint32_t entity) {
|
||||
if (!registry) return 0.0f;
|
||||
if (auto* t = registry->try_get<ECSTransform>((entt::entity)entity)) {
|
||||
return t->rotation;
|
||||
return t->rotation.y;
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
void AS_SetRotationEuler(uint32_t entity, float x, float y, float z) {
|
||||
if (!registry) return;
|
||||
if (auto* t = registry->try_get<ECSTransform>((entt::entity)entity)) {
|
||||
t->rotation = {x, y, z};
|
||||
}
|
||||
}
|
||||
|
||||
void AS_GetRotationEuler(uint32_t entity, float* x, float* y, float* z) {
|
||||
if (!registry || !x || !y || !z) return;
|
||||
if (auto* t = registry->try_get<ECSTransform>((entt::entity)entity)) {
|
||||
*x = t->rotation.x;
|
||||
*y = t->rotation.y;
|
||||
*z = t->rotation.z;
|
||||
}
|
||||
}
|
||||
|
||||
bool AS_HasTransform(uint32_t entity) {
|
||||
if (!registry) return false;
|
||||
return registry->all_of<ECSTransform>((entt::entity)entity);
|
||||
@@ -115,57 +135,128 @@ void AS_RemoveVelocity(uint32_t entity) {
|
||||
registry->remove<Velocity>((entt::entity)entity);
|
||||
}
|
||||
|
||||
// === Sprite Component ===
|
||||
// === ModelRenderer Component ===
|
||||
|
||||
void AS_AddSprite(uint32_t entity, int modelId, uint32_t color, float outlineSize) {
|
||||
void AS_AddModelRenderer(uint32_t entity, int modelId, uint32_t color, float outlineSize) {
|
||||
if (!registry) return;
|
||||
Color col;
|
||||
col.r = (color >> 24) & 0xFF;
|
||||
col.g = (color >> 16) & 0xFF;
|
||||
col.b = (color >> 8) & 0xFF;
|
||||
col.a = color & 0xFF;
|
||||
registry->emplace<Sprite>((entt::entity)entity, modelId, col, outlineSize);
|
||||
registry->emplace<ModelRenderer>((entt::entity)entity, modelId, col, outlineSize);
|
||||
}
|
||||
|
||||
void AS_SetSpriteColor(uint32_t entity, uint32_t color) {
|
||||
void AS_SetModelRendererColor(uint32_t entity, uint32_t color) {
|
||||
if (!registry) return;
|
||||
if (auto* s = registry->try_get<Sprite>((entt::entity)entity)) {
|
||||
s->color.r = (color >> 24) & 0xFF;
|
||||
s->color.g = (color >> 16) & 0xFF;
|
||||
s->color.b = (color >> 8) & 0xFF;
|
||||
s->color.a = color & 0xFF;
|
||||
if (auto* renderer = registry->try_get<ModelRenderer>((entt::entity)entity)) {
|
||||
renderer->color.r = (color >> 24) & 0xFF;
|
||||
renderer->color.g = (color >> 16) & 0xFF;
|
||||
renderer->color.b = (color >> 8) & 0xFF;
|
||||
renderer->color.a = color & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
void AS_SetSpriteModel(uint32_t entity, int modelId) {
|
||||
void AS_SetModelRendererModel(uint32_t entity, int modelId) {
|
||||
if (!registry) return;
|
||||
if (auto* s = registry->try_get<Sprite>((entt::entity)entity)) {
|
||||
s->modelId = modelId;
|
||||
if (auto* renderer = registry->try_get<ModelRenderer>((entt::entity)entity)) {
|
||||
renderer->modelId = modelId;
|
||||
}
|
||||
}
|
||||
|
||||
void AS_SetSpriteOutline(uint32_t entity, float outlineSize) {
|
||||
void AS_SetModelRendererOutline(uint32_t entity, float outlineSize) {
|
||||
if (!registry) return;
|
||||
if (auto* s = registry->try_get<Sprite>((entt::entity)entity)) {
|
||||
s->outlineSize = outlineSize;
|
||||
if (auto* renderer = registry->try_get<ModelRenderer>((entt::entity)entity)) {
|
||||
renderer->outlineSize = outlineSize;
|
||||
}
|
||||
}
|
||||
|
||||
void AS_SetSpriteShader(uint32_t entity, unsigned int shaderId) {
|
||||
void AS_SetModelRendererShader(uint32_t entity, unsigned int shaderId) {
|
||||
if (!registry) return;
|
||||
if (auto* s = registry->try_get<Sprite>((entt::entity)entity)) {
|
||||
s->shaderId = shaderId;
|
||||
if (auto* renderer = registry->try_get<ModelRenderer>((entt::entity)entity)) {
|
||||
renderer->shaderId = shaderId;
|
||||
}
|
||||
}
|
||||
|
||||
bool AS_HasSprite(uint32_t entity) {
|
||||
bool AS_HasModelRenderer(uint32_t entity) {
|
||||
if (!registry) return false;
|
||||
return registry->all_of<Sprite>((entt::entity)entity);
|
||||
return registry->all_of<ModelRenderer>((entt::entity)entity);
|
||||
}
|
||||
|
||||
void AS_RemoveSprite(uint32_t entity) {
|
||||
void AS_RemoveModelRenderer(uint32_t entity) {
|
||||
if (!registry) return;
|
||||
registry->remove<Sprite>((entt::entity)entity);
|
||||
registry->remove<ModelRenderer>((entt::entity)entity);
|
||||
}
|
||||
|
||||
// === Texture Component ===
|
||||
|
||||
void AS_AddTexture(uint32_t entity, unsigned int textureId) {
|
||||
if (!registry) return;
|
||||
registry->emplace<TextureComponent>((entt::entity)entity, textureId);
|
||||
}
|
||||
|
||||
void AS_SetTextureId(uint32_t entity, unsigned int textureId) {
|
||||
if (!registry) return;
|
||||
if (auto* t = registry->try_get<TextureComponent>((entt::entity)entity)) {
|
||||
t->textureId = textureId;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int AS_GetTextureId(uint32_t entity) {
|
||||
if (!registry) return 0;
|
||||
if (auto* t = registry->try_get<TextureComponent>((entt::entity)entity)) {
|
||||
return t->textureId;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool AS_HasTexture(uint32_t entity) {
|
||||
if (!registry) return false;
|
||||
return registry->all_of<TextureComponent>((entt::entity)entity);
|
||||
}
|
||||
|
||||
void AS_RemoveTexture(uint32_t entity) {
|
||||
if (!registry) return;
|
||||
registry->remove<TextureComponent>((entt::entity)entity);
|
||||
}
|
||||
|
||||
// === UV Transform Component ===
|
||||
|
||||
void AS_AddUVTransform(uint32_t entity, float scaleX, float scaleY, float offsetX, float offsetY, float rotation) {
|
||||
if (!registry) return;
|
||||
registry->emplace<UVTransform>((entt::entity)entity,
|
||||
Vector2{scaleX, scaleY}, Vector2{offsetX, offsetY}, rotation);
|
||||
}
|
||||
|
||||
void AS_SetUVScale(uint32_t entity, float scaleX, float scaleY) {
|
||||
if (!registry) return;
|
||||
if (auto* uv = registry->try_get<UVTransform>((entt::entity)entity)) {
|
||||
uv->scale = {scaleX, scaleY};
|
||||
}
|
||||
}
|
||||
|
||||
void AS_SetUVOffset(uint32_t entity, float offsetX, float offsetY) {
|
||||
if (!registry) return;
|
||||
if (auto* uv = registry->try_get<UVTransform>((entt::entity)entity)) {
|
||||
uv->offset = {offsetX, offsetY};
|
||||
}
|
||||
}
|
||||
|
||||
void AS_SetUVRotation(uint32_t entity, float rotation) {
|
||||
if (!registry) return;
|
||||
if (auto* uv = registry->try_get<UVTransform>((entt::entity)entity)) {
|
||||
uv->rotation = rotation;
|
||||
}
|
||||
}
|
||||
|
||||
bool AS_HasUVTransform(uint32_t entity) {
|
||||
if (!registry) return false;
|
||||
return registry->all_of<UVTransform>((entt::entity)entity);
|
||||
}
|
||||
|
||||
void AS_RemoveUVTransform(uint32_t entity) {
|
||||
if (!registry) return;
|
||||
registry->remove<UVTransform>((entt::entity)entity);
|
||||
}
|
||||
|
||||
// === Tag Component ===
|
||||
@@ -200,6 +291,44 @@ void AS_RemoveTag(uint32_t entity) {
|
||||
registry->remove<Tag>((entt::entity)entity);
|
||||
}
|
||||
|
||||
// === Hierarchy Component ===
|
||||
|
||||
void AS_SetParent(uint32_t child, uint32_t parent) {
|
||||
if (!registry) return;
|
||||
entt::entity childEntity = (entt::entity)child;
|
||||
entt::entity parentEntity = (entt::entity)parent;
|
||||
|
||||
if (!registry->valid(childEntity)) return;
|
||||
if (parent == 0 || !registry->valid(parentEntity)) {
|
||||
DetachFromParent(*registry, childEntity);
|
||||
return;
|
||||
}
|
||||
|
||||
AttachChild(*registry, childEntity, parentEntity);
|
||||
}
|
||||
|
||||
void AS_ClearParent(uint32_t child) {
|
||||
if (!registry) return;
|
||||
entt::entity childEntity = (entt::entity)child;
|
||||
DetachFromParent(*registry, childEntity);
|
||||
}
|
||||
|
||||
uint32_t AS_GetParent(uint32_t child) {
|
||||
if (!registry) return 0;
|
||||
if (auto* h = registry->try_get<Hierarchy>((entt::entity)child)) {
|
||||
return static_cast<uint32_t>(h->parent);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool AS_HasParent(uint32_t child) {
|
||||
if (!registry) return false;
|
||||
if (auto* h = registry->try_get<Hierarchy>((entt::entity)child)) {
|
||||
return h->parent != entt::null;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// === Camera3D Component ===
|
||||
|
||||
void AS_AddCamera3D(uint32_t entity, float px, float py, float pz, float tx, float ty, float tz, float fovy) {
|
||||
@@ -398,10 +527,18 @@ void RegisterECSBindings(asIScriptEngine *engine) {
|
||||
asFUNCTION(AS_SetRotation), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("void SetRotationEuler(uint, float, float, float)",
|
||||
asFUNCTION(AS_SetRotationEuler), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("float GetRotation(uint)",
|
||||
asFUNCTION(AS_GetRotation), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("void GetRotationEuler(uint, float &out, float &out, float &out)",
|
||||
asFUNCTION(AS_GetRotationEuler), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("bool HasTransform(uint)",
|
||||
asFUNCTION(AS_HasTransform), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
@@ -431,33 +568,79 @@ void RegisterECSBindings(asIScriptEngine *engine) {
|
||||
asFUNCTION(AS_RemoveVelocity), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
// Sprite component
|
||||
r = engine->RegisterGlobalFunction("void AddSprite(uint, int, uint, float = 0.0f)",
|
||||
asFUNCTION(AS_AddSprite), asCALL_CDECL);
|
||||
// ModelRenderer component
|
||||
r = engine->RegisterGlobalFunction("void AddModelRenderer(uint, int, uint, float = 0.0f)",
|
||||
asFUNCTION(AS_AddModelRenderer), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("void SetSpriteColor(uint, uint)",
|
||||
asFUNCTION(AS_SetSpriteColor), asCALL_CDECL);
|
||||
r = engine->RegisterGlobalFunction("void SetModelRendererColor(uint, uint)",
|
||||
asFUNCTION(AS_SetModelRendererColor), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("void SetSpriteModel(uint, int)",
|
||||
asFUNCTION(AS_SetSpriteModel), asCALL_CDECL);
|
||||
r = engine->RegisterGlobalFunction("void SetModelRendererModel(uint, int)",
|
||||
asFUNCTION(AS_SetModelRendererModel), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("void SetSpriteOutline(uint, float)",
|
||||
asFUNCTION(AS_SetSpriteOutline), asCALL_CDECL);
|
||||
r = engine->RegisterGlobalFunction("void SetModelRendererOutline(uint, float)",
|
||||
asFUNCTION(AS_SetModelRendererOutline), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("void SetSpriteShader(uint, uint)",
|
||||
asFUNCTION(AS_SetSpriteShader), asCALL_CDECL);
|
||||
r = engine->RegisterGlobalFunction("void SetModelRendererShader(uint, uint)",
|
||||
asFUNCTION(AS_SetModelRendererShader), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("bool HasSprite(uint)",
|
||||
asFUNCTION(AS_HasSprite), asCALL_CDECL);
|
||||
r = engine->RegisterGlobalFunction("bool HasModelRenderer(uint)",
|
||||
asFUNCTION(AS_HasModelRenderer), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("void RemoveSprite(uint)",
|
||||
asFUNCTION(AS_RemoveSprite), asCALL_CDECL);
|
||||
r = engine->RegisterGlobalFunction("void RemoveModelRenderer(uint)",
|
||||
asFUNCTION(AS_RemoveModelRenderer), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
// Texture component
|
||||
r = engine->RegisterGlobalFunction("void AddTexture(uint, uint)",
|
||||
asFUNCTION(AS_AddTexture), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("void SetTextureId(uint, uint)",
|
||||
asFUNCTION(AS_SetTextureId), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("uint GetTextureId(uint)",
|
||||
asFUNCTION(AS_GetTextureId), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("bool HasTexture(uint)",
|
||||
asFUNCTION(AS_HasTexture), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("void RemoveTexture(uint)",
|
||||
asFUNCTION(AS_RemoveTexture), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
// UV Transform component
|
||||
r = engine->RegisterGlobalFunction("void AddUVTransform(uint, float, float, float = 0.0f, float = 0.0f, float = 0.0f)",
|
||||
asFUNCTION(AS_AddUVTransform), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("void SetUVScale(uint, float, float)",
|
||||
asFUNCTION(AS_SetUVScale), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("void SetUVOffset(uint, float, float)",
|
||||
asFUNCTION(AS_SetUVOffset), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("void SetUVRotation(uint, float)",
|
||||
asFUNCTION(AS_SetUVRotation), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("bool HasUVTransform(uint)",
|
||||
asFUNCTION(AS_HasUVTransform), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("void RemoveUVTransform(uint)",
|
||||
asFUNCTION(AS_RemoveUVTransform), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
// Tag component
|
||||
@@ -481,6 +664,23 @@ void RegisterECSBindings(asIScriptEngine *engine) {
|
||||
asFUNCTION(AS_RemoveTag), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
// Hierarchy component
|
||||
r = engine->RegisterGlobalFunction("void SetParent(uint, uint)",
|
||||
asFUNCTION(AS_SetParent), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("void ClearParent(uint)",
|
||||
asFUNCTION(AS_ClearParent), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("uint GetParent(uint)",
|
||||
asFUNCTION(AS_GetParent), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("bool HasParent(uint)",
|
||||
asFUNCTION(AS_HasParent), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
// Camera3D component
|
||||
r = engine->RegisterGlobalFunction("void AddCamera3D(uint, float, float, float, float, float, float, float)",
|
||||
asFUNCTION(AS_AddCamera3D), asCALL_CDECL);
|
||||
|
||||
@@ -38,10 +38,22 @@ static void AS_BindMouseButton(const std::string& actionName, int mouseButton) {
|
||||
if (g_inputManager) g_inputManager->BindMouseButton(actionName, mouseButton);
|
||||
}
|
||||
|
||||
static void AS_BindGamepadButton(const std::string& actionName, int gamepadId, int button) {
|
||||
if (g_inputManager) g_inputManager->BindGamepadButton(actionName, gamepadId, button);
|
||||
}
|
||||
|
||||
static void AS_BindGamepadAxis(const std::string& actionName, int gamepadId, int axis, float threshold, bool negative) {
|
||||
if (g_inputManager) g_inputManager->BindGamepadAxis(actionName, gamepadId, axis, threshold, negative);
|
||||
}
|
||||
|
||||
static void AS_ClearBindings(const std::string& actionName) {
|
||||
if (g_inputManager) g_inputManager->ClearBindings(actionName);
|
||||
}
|
||||
|
||||
static void AS_ClearAllActions() {
|
||||
if (g_inputManager) g_inputManager->ClearAllActions();
|
||||
}
|
||||
|
||||
void RegisterInputBindings(asIScriptEngine* engine) {
|
||||
int r;
|
||||
|
||||
@@ -74,10 +86,22 @@ void RegisterInputBindings(asIScriptEngine* engine) {
|
||||
r = engine->RegisterGlobalFunction("void BindMouseButton(const string &in, int)",
|
||||
asFUNCTION(AS_BindMouseButton), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("void BindGamepadButton(const string &in, int, int)",
|
||||
asFUNCTION(AS_BindGamepadButton), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("void BindGamepadAxis(const string &in, int, int, float = 0.5f, bool = false)",
|
||||
asFUNCTION(AS_BindGamepadAxis), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("void ClearBindings(const string &in)",
|
||||
asFUNCTION(AS_ClearBindings), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("void ClearAllActions()",
|
||||
asFUNCTION(AS_ClearAllActions), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
// Key constants for binding
|
||||
r = engine->RegisterEnum("Key");
|
||||
@@ -114,4 +138,56 @@ void RegisterInputBindings(asIScriptEngine* engine) {
|
||||
assert(r >= 0);
|
||||
r = engine->RegisterEnumValue("MouseButton", "MIDDLE", MOUSE_BUTTON_MIDDLE);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterEnum("GamepadButton");
|
||||
assert(r >= 0);
|
||||
r = engine->RegisterEnumValue("GamepadButton", "LEFT_FACE_UP", GAMEPAD_BUTTON_LEFT_FACE_UP);
|
||||
assert(r >= 0);
|
||||
r = engine->RegisterEnumValue("GamepadButton", "LEFT_FACE_RIGHT", GAMEPAD_BUTTON_LEFT_FACE_RIGHT);
|
||||
assert(r >= 0);
|
||||
r = engine->RegisterEnumValue("GamepadButton", "LEFT_FACE_DOWN", GAMEPAD_BUTTON_LEFT_FACE_DOWN);
|
||||
assert(r >= 0);
|
||||
r = engine->RegisterEnumValue("GamepadButton", "LEFT_FACE_LEFT", GAMEPAD_BUTTON_LEFT_FACE_LEFT);
|
||||
assert(r >= 0);
|
||||
r = engine->RegisterEnumValue("GamepadButton", "RIGHT_FACE_UP", GAMEPAD_BUTTON_RIGHT_FACE_UP);
|
||||
assert(r >= 0);
|
||||
r = engine->RegisterEnumValue("GamepadButton", "RIGHT_FACE_RIGHT", GAMEPAD_BUTTON_RIGHT_FACE_RIGHT);
|
||||
assert(r >= 0);
|
||||
r = engine->RegisterEnumValue("GamepadButton", "RIGHT_FACE_DOWN", GAMEPAD_BUTTON_RIGHT_FACE_DOWN);
|
||||
assert(r >= 0);
|
||||
r = engine->RegisterEnumValue("GamepadButton", "RIGHT_FACE_LEFT", GAMEPAD_BUTTON_RIGHT_FACE_LEFT);
|
||||
assert(r >= 0);
|
||||
r = engine->RegisterEnumValue("GamepadButton", "LEFT_TRIGGER_1", GAMEPAD_BUTTON_LEFT_TRIGGER_1);
|
||||
assert(r >= 0);
|
||||
r = engine->RegisterEnumValue("GamepadButton", "LEFT_TRIGGER_2", GAMEPAD_BUTTON_LEFT_TRIGGER_2);
|
||||
assert(r >= 0);
|
||||
r = engine->RegisterEnumValue("GamepadButton", "RIGHT_TRIGGER_1", GAMEPAD_BUTTON_RIGHT_TRIGGER_1);
|
||||
assert(r >= 0);
|
||||
r = engine->RegisterEnumValue("GamepadButton", "RIGHT_TRIGGER_2", GAMEPAD_BUTTON_RIGHT_TRIGGER_2);
|
||||
assert(r >= 0);
|
||||
r = engine->RegisterEnumValue("GamepadButton", "MIDDLE_LEFT", GAMEPAD_BUTTON_MIDDLE_LEFT);
|
||||
assert(r >= 0);
|
||||
r = engine->RegisterEnumValue("GamepadButton", "MIDDLE", GAMEPAD_BUTTON_MIDDLE);
|
||||
assert(r >= 0);
|
||||
r = engine->RegisterEnumValue("GamepadButton", "MIDDLE_RIGHT", GAMEPAD_BUTTON_MIDDLE_RIGHT);
|
||||
assert(r >= 0);
|
||||
r = engine->RegisterEnumValue("GamepadButton", "LEFT_THUMB", GAMEPAD_BUTTON_LEFT_THUMB);
|
||||
assert(r >= 0);
|
||||
r = engine->RegisterEnumValue("GamepadButton", "RIGHT_THUMB", GAMEPAD_BUTTON_RIGHT_THUMB);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterEnum("GamepadAxis");
|
||||
assert(r >= 0);
|
||||
r = engine->RegisterEnumValue("GamepadAxis", "LEFT_X", GAMEPAD_AXIS_LEFT_X);
|
||||
assert(r >= 0);
|
||||
r = engine->RegisterEnumValue("GamepadAxis", "LEFT_Y", GAMEPAD_AXIS_LEFT_Y);
|
||||
assert(r >= 0);
|
||||
r = engine->RegisterEnumValue("GamepadAxis", "RIGHT_X", GAMEPAD_AXIS_RIGHT_X);
|
||||
assert(r >= 0);
|
||||
r = engine->RegisterEnumValue("GamepadAxis", "RIGHT_Y", GAMEPAD_AXIS_RIGHT_Y);
|
||||
assert(r >= 0);
|
||||
r = engine->RegisterEnumValue("GamepadAxis", "LEFT_TRIGGER", GAMEPAD_AXIS_LEFT_TRIGGER);
|
||||
assert(r >= 0);
|
||||
r = engine->RegisterEnumValue("GamepadAxis", "RIGHT_TRIGGER", GAMEPAD_AXIS_RIGHT_TRIGGER);
|
||||
assert(r >= 0);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
#include "scripting/PhysFSBindings.h"
|
||||
#include "PhysFSManager.h"
|
||||
#include "scriptarray.h"
|
||||
#include <angelscript.h>
|
||||
#include <cassert>
|
||||
|
||||
static asIScriptEngine* g_engine = nullptr;
|
||||
|
||||
static bool AS_Mount(const std::string& path, const std::string& mountPoint, bool append) {
|
||||
return PhysFSManager::MountPath(path, mountPoint, append);
|
||||
}
|
||||
|
||||
static bool AS_Unmount(const std::string& path) {
|
||||
return PhysFSManager::UnmountPath(path);
|
||||
}
|
||||
|
||||
static bool AS_Exists(const std::string& path) {
|
||||
return PhysFSManager::Exists(path);
|
||||
}
|
||||
|
||||
static bool AS_IsDirectory(const std::string& path) {
|
||||
return PhysFSManager::IsDirectory(path);
|
||||
}
|
||||
|
||||
static std::string AS_GetRealDir(const std::string& path) {
|
||||
return PhysFSManager::GetRealDir(path);
|
||||
}
|
||||
|
||||
static CScriptArray* AS_ListFiles(const std::string& path) {
|
||||
if (!g_engine) return nullptr;
|
||||
const std::vector<std::string> files = PhysFSManager::ListFiles(path);
|
||||
asITypeInfo* arrayType = g_engine->GetTypeInfoByDecl("array<string>");
|
||||
if (!arrayType) return nullptr;
|
||||
|
||||
CScriptArray* array = CScriptArray::Create(arrayType, static_cast<asUINT>(files.size()));
|
||||
for (asUINT i = 0; i < static_cast<asUINT>(files.size()); ++i) {
|
||||
static_cast<std::string*>(array->At(i))->assign(files[i]);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
void RegisterPhysFSBindings(asIScriptEngine* engine) {
|
||||
int r;
|
||||
g_engine = engine;
|
||||
|
||||
engine->SetDefaultNamespace("PhysFS");
|
||||
|
||||
r = engine->RegisterGlobalFunction("bool Mount(const string &in, const string &in mountPoint = \"\", bool append = true)",
|
||||
asFUNCTION(AS_Mount), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("bool Unmount(const string &in)",
|
||||
asFUNCTION(AS_Unmount), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("bool Exists(const string &in)",
|
||||
asFUNCTION(AS_Exists), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("bool IsDirectory(const string &in)",
|
||||
asFUNCTION(AS_IsDirectory), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("string GetRealDir(const string &in)",
|
||||
asFUNCTION(AS_GetRealDir), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("array<string>@ ListFiles(const string &in)",
|
||||
asFUNCTION(AS_ListFiles), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
engine->SetDefaultNamespace("");
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
#include "ShaderManager.h"
|
||||
#include "ModelManager.h"
|
||||
#include "MaterialManager.h"
|
||||
#include "TextureManager.h"
|
||||
#include "AssetManager.h"
|
||||
#include <angelscript.h>
|
||||
#include <assert.h>
|
||||
#include "raylib.h"
|
||||
@@ -10,11 +12,18 @@
|
||||
static ShaderManager* g_shaderManager = nullptr;
|
||||
static ModelManager* g_modelManager = nullptr;
|
||||
static MaterialManager* g_materialManager = nullptr;
|
||||
static TextureManager* g_textureManager = nullptr;
|
||||
static AssetManager* g_assetManager = nullptr;
|
||||
|
||||
void SetResourceManagers(ShaderManager* shaderMgr, ModelManager* modelMgr, MaterialManager* materialMgr) {
|
||||
void SetResourceManagers(ShaderManager* shaderMgr, ModelManager* modelMgr, MaterialManager* materialMgr, TextureManager* textureMgr) {
|
||||
g_shaderManager = shaderMgr;
|
||||
g_modelManager = modelMgr;
|
||||
g_materialManager = materialMgr;
|
||||
g_textureManager = textureMgr;
|
||||
}
|
||||
|
||||
void SetAssetManager(AssetManager* assetMgr) {
|
||||
g_assetManager = assetMgr;
|
||||
}
|
||||
|
||||
// === Shader Management ===
|
||||
@@ -38,20 +47,17 @@ unsigned int AS_LoadModel(const std::string& path) {
|
||||
|
||||
unsigned int AS_LoadModelCube(float width, float height, float length) {
|
||||
if (!g_modelManager) return 0;
|
||||
Mesh mesh = GenMeshCube(width, height, length);
|
||||
return g_modelManager->LoadFromMesh(mesh);
|
||||
return g_modelManager->LoadCube(width, height, length);
|
||||
}
|
||||
|
||||
unsigned int AS_LoadModelSphere(float radius, int rings, int slices) {
|
||||
if (!g_modelManager) return 0;
|
||||
Mesh mesh = GenMeshSphere(radius, rings, slices);
|
||||
return g_modelManager->LoadFromMesh(mesh);
|
||||
return g_modelManager->LoadSphere(radius, rings, slices);
|
||||
}
|
||||
|
||||
unsigned int AS_LoadModelPlane(float width, float length, int resX, int resZ) {
|
||||
if (!g_modelManager) return 0;
|
||||
Mesh mesh = GenMeshPlane(width, length, resX, resZ);
|
||||
return g_modelManager->LoadFromMesh(mesh);
|
||||
return g_modelManager->LoadPlane(width, length, resX, resZ);
|
||||
}
|
||||
|
||||
void AS_UnloadModel(unsigned int id) {
|
||||
@@ -83,6 +89,141 @@ void AS_SetMaterialAlbedo(unsigned int id, unsigned char r, unsigned char g, uns
|
||||
g_materialManager->SetAlbedo(id, albedo);
|
||||
}
|
||||
|
||||
// === Texture Management ===
|
||||
|
||||
unsigned int AS_LoadTextureAsset(const std::string& path) {
|
||||
if (!g_textureManager) return 0;
|
||||
return g_textureManager->Load(path);
|
||||
}
|
||||
|
||||
void AS_UnloadTextureAsset(unsigned int id) {
|
||||
if (!g_textureManager) return;
|
||||
g_textureManager->Unload(id);
|
||||
}
|
||||
|
||||
// === Asset Manager ===
|
||||
|
||||
unsigned int AS_AssetLoadTexture(const std::string& key, const std::string& path) {
|
||||
if (!g_assetManager) return 0;
|
||||
return g_assetManager->LoadTexture(key, path);
|
||||
}
|
||||
|
||||
unsigned int AS_AssetLoadShader(const std::string& key, const std::string& vsPath, const std::string& fsPath) {
|
||||
if (!g_assetManager) return 0;
|
||||
return g_assetManager->LoadShader(key, vsPath, fsPath);
|
||||
}
|
||||
|
||||
unsigned int AS_AssetLoadModel(const std::string& key, const std::string& path) {
|
||||
if (!g_assetManager) return 0;
|
||||
return g_assetManager->LoadModel(key, path);
|
||||
}
|
||||
|
||||
unsigned int AS_AssetLoadCube(const std::string& key, float width, float height, float length) {
|
||||
if (!g_assetManager) return 0;
|
||||
return g_assetManager->LoadCube(key, width, height, length);
|
||||
}
|
||||
|
||||
unsigned int AS_AssetLoadPoly(const std::string& key, int sides, float radius) {
|
||||
if (!g_assetManager) return 0;
|
||||
return g_assetManager->LoadPoly(key, sides, radius);
|
||||
}
|
||||
|
||||
unsigned int AS_AssetLoadSphere(const std::string& key, float radius, int rings, int slices) {
|
||||
if (!g_assetManager) return 0;
|
||||
return g_assetManager->LoadSphere(key, radius, rings, slices);
|
||||
}
|
||||
|
||||
unsigned int AS_AssetLoadHemiSphere(const std::string& key, float radius, int rings, int slices) {
|
||||
if (!g_assetManager) return 0;
|
||||
return g_assetManager->LoadHemiSphere(key, radius, rings, slices);
|
||||
}
|
||||
|
||||
unsigned int AS_AssetLoadPlane(const std::string& key, float width, float length, int resX, int resZ) {
|
||||
if (!g_assetManager) return 0;
|
||||
return g_assetManager->LoadPlane(key, width, length, resX, resZ);
|
||||
}
|
||||
|
||||
unsigned int AS_AssetLoadCylinder(const std::string& key, float radius, float height, int slices) {
|
||||
if (!g_assetManager) return 0;
|
||||
return g_assetManager->LoadCylinder(key, radius, height, slices);
|
||||
}
|
||||
|
||||
unsigned int AS_AssetLoadCone(const std::string& key, float radius, float height, int slices) {
|
||||
if (!g_assetManager) return 0;
|
||||
return g_assetManager->LoadCone(key, radius, height, slices);
|
||||
}
|
||||
|
||||
unsigned int AS_AssetLoadTorus(const std::string& key, float radius, float size, int radSeg, int sides) {
|
||||
if (!g_assetManager) return 0;
|
||||
return g_assetManager->LoadTorus(key, radius, size, radSeg, sides);
|
||||
}
|
||||
|
||||
unsigned int AS_AssetLoadKnot(const std::string& key, float radius, float size, int radSeg, int sides) {
|
||||
if (!g_assetManager) return 0;
|
||||
return g_assetManager->LoadKnot(key, radius, size, radSeg, sides);
|
||||
}
|
||||
|
||||
unsigned int AS_AssetLoadHeightmap(const std::string& key, const std::string& heightmapPath, float sizeX, float sizeY, float sizeZ) {
|
||||
if (!g_assetManager) return 0;
|
||||
return g_assetManager->LoadHeightmap(key, heightmapPath, sizeX, sizeY, sizeZ);
|
||||
}
|
||||
|
||||
unsigned int AS_AssetLoadCubicmap(const std::string& key, const std::string& cubicmapPath, float sizeX, float sizeY, float sizeZ) {
|
||||
if (!g_assetManager) return 0;
|
||||
return g_assetManager->LoadCubicmap(key, cubicmapPath, sizeX, sizeY, sizeZ);
|
||||
}
|
||||
|
||||
unsigned int AS_AssetCreateMaterial(const std::string& key, unsigned int shaderId, unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
|
||||
if (!g_assetManager) return 0;
|
||||
Color albedo = {r, g, b, a};
|
||||
return g_assetManager->CreateMaterial(key, shaderId, albedo);
|
||||
}
|
||||
|
||||
bool AS_AssetUnloadTexture(const std::string& key) {
|
||||
if (!g_assetManager) return false;
|
||||
return g_assetManager->UnloadTexture(key);
|
||||
}
|
||||
|
||||
bool AS_AssetUnloadShader(const std::string& key) {
|
||||
if (!g_assetManager) return false;
|
||||
return g_assetManager->UnloadShader(key);
|
||||
}
|
||||
|
||||
bool AS_AssetUnloadModel(const std::string& key) {
|
||||
if (!g_assetManager) return false;
|
||||
return g_assetManager->UnloadModel(key);
|
||||
}
|
||||
|
||||
bool AS_AssetUnloadMaterial(const std::string& key) {
|
||||
if (!g_assetManager) return false;
|
||||
return g_assetManager->UnloadMaterial(key);
|
||||
}
|
||||
|
||||
unsigned int AS_AssetGetTextureId(const std::string& key) {
|
||||
if (!g_assetManager) return 0;
|
||||
return g_assetManager->GetTextureId(key);
|
||||
}
|
||||
|
||||
unsigned int AS_AssetGetShaderId(const std::string& key) {
|
||||
if (!g_assetManager) return 0;
|
||||
return g_assetManager->GetShaderId(key);
|
||||
}
|
||||
|
||||
unsigned int AS_AssetGetModelId(const std::string& key) {
|
||||
if (!g_assetManager) return 0;
|
||||
return g_assetManager->GetModelId(key);
|
||||
}
|
||||
|
||||
unsigned int AS_AssetGetMaterialId(const std::string& key) {
|
||||
if (!g_assetManager) return 0;
|
||||
return g_assetManager->GetMaterialId(key);
|
||||
}
|
||||
|
||||
bool AS_AssetApplyMeshTangents(const std::string& key, int meshIndex) {
|
||||
if (!g_assetManager) return false;
|
||||
return g_assetManager->ApplyMeshTangents(key, meshIndex);
|
||||
}
|
||||
|
||||
void RegisterResourceBindings(asIScriptEngine *engine)
|
||||
{
|
||||
int r;
|
||||
@@ -140,5 +281,115 @@ void RegisterResourceBindings(asIScriptEngine *engine)
|
||||
asFUNCTION(AS_SetMaterialAlbedo), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
// === TextureAsset namespace ===
|
||||
engine->SetDefaultNamespace("TextureAsset");
|
||||
|
||||
r = engine->RegisterGlobalFunction("uint Load(const string &in)",
|
||||
asFUNCTION(AS_LoadTextureAsset), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("void Unload(uint)",
|
||||
asFUNCTION(AS_UnloadTextureAsset), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
// === Asset namespace ===
|
||||
engine->SetDefaultNamespace("Asset");
|
||||
|
||||
r = engine->RegisterGlobalFunction("uint LoadTexture(const string &in, const string &in)",
|
||||
asFUNCTION(AS_AssetLoadTexture), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("uint LoadShader(const string &in, const string &in, const string &in)",
|
||||
asFUNCTION(AS_AssetLoadShader), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("uint LoadModel(const string &in, const string &in)",
|
||||
asFUNCTION(AS_AssetLoadModel), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("uint LoadCube(const string &in, float, float, float)",
|
||||
asFUNCTION(AS_AssetLoadCube), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("uint LoadPoly(const string &in, int, float)",
|
||||
asFUNCTION(AS_AssetLoadPoly), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("uint LoadSphere(const string &in, float, int, int)",
|
||||
asFUNCTION(AS_AssetLoadSphere), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("uint LoadHemiSphere(const string &in, float, int, int)",
|
||||
asFUNCTION(AS_AssetLoadHemiSphere), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("uint LoadPlane(const string &in, float, float, int, int)",
|
||||
asFUNCTION(AS_AssetLoadPlane), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("uint LoadCylinder(const string &in, float, float, int)",
|
||||
asFUNCTION(AS_AssetLoadCylinder), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("uint LoadCone(const string &in, float, float, int)",
|
||||
asFUNCTION(AS_AssetLoadCone), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("uint LoadTorus(const string &in, float, float, int, int)",
|
||||
asFUNCTION(AS_AssetLoadTorus), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("uint LoadKnot(const string &in, float, float, int, int)",
|
||||
asFUNCTION(AS_AssetLoadKnot), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("uint LoadHeightmap(const string &in, const string &in, float, float, float)",
|
||||
asFUNCTION(AS_AssetLoadHeightmap), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("uint LoadCubicmap(const string &in, const string &in, float, float, float)",
|
||||
asFUNCTION(AS_AssetLoadCubicmap), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("uint CreateMaterial(const string &in, uint, uint8, uint8, uint8, uint8)",
|
||||
asFUNCTION(AS_AssetCreateMaterial), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("bool UnloadTexture(const string &in)",
|
||||
asFUNCTION(AS_AssetUnloadTexture), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("bool UnloadShader(const string &in)",
|
||||
asFUNCTION(AS_AssetUnloadShader), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("bool UnloadModel(const string &in)",
|
||||
asFUNCTION(AS_AssetUnloadModel), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("bool UnloadMaterial(const string &in)",
|
||||
asFUNCTION(AS_AssetUnloadMaterial), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("uint GetTextureId(const string &in)",
|
||||
asFUNCTION(AS_AssetGetTextureId), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("uint GetShaderId(const string &in)",
|
||||
asFUNCTION(AS_AssetGetShaderId), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("uint GetModelId(const string &in)",
|
||||
asFUNCTION(AS_AssetGetModelId), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("uint GetMaterialId(const string &in)",
|
||||
asFUNCTION(AS_AssetGetMaterialId), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("bool ApplyMeshTangents(const string &in, int = 0)",
|
||||
asFUNCTION(AS_AssetApplyMeshTangents), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
engine->SetDefaultNamespace("");
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
#include "scripting/SceneBindings.h"
|
||||
#include "SceneLoader.h"
|
||||
#include <angelscript.h>
|
||||
#include <assert.h>
|
||||
|
||||
static unsigned int AS_LoadScene(const std::string& path, bool clearExisting) {
|
||||
entt::entity root = LoadSceneFromFile(path, clearExisting);
|
||||
return static_cast<unsigned int>(root);
|
||||
}
|
||||
|
||||
static void AS_ClearScene() {
|
||||
ClearScene();
|
||||
}
|
||||
|
||||
void RegisterSceneBindings(asIScriptEngine* engine) {
|
||||
int r;
|
||||
|
||||
engine->SetDefaultNamespace("Scene");
|
||||
|
||||
r = engine->RegisterGlobalFunction("uint Load(const string &in, bool clearExisting = true)",
|
||||
asFUNCTION(AS_LoadScene), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
r = engine->RegisterGlobalFunction("void Clear()", asFUNCTION(AS_ClearScene), asCALL_CDECL);
|
||||
assert(r >= 0);
|
||||
|
||||
engine->SetDefaultNamespace("");
|
||||
}
|
||||
@@ -7,6 +7,8 @@
|
||||
#include "scripting/ECSBindings.h"
|
||||
#include "scripting/ResourceBindings.h"
|
||||
#include "scripting/InputBindings.h"
|
||||
#include "scripting/SceneBindings.h"
|
||||
#include "scripting/PhysFSBindings.h"
|
||||
|
||||
void ScriptBindings::RegisterAll(asIScriptEngine *engine)
|
||||
{
|
||||
@@ -19,4 +21,6 @@ void ScriptBindings::RegisterAll(asIScriptEngine *engine)
|
||||
RegisterECSBindings(engine);
|
||||
RegisterResourceBindings(engine);
|
||||
RegisterInputBindings(engine);
|
||||
RegisterSceneBindings(engine);
|
||||
RegisterPhysFSBindings(engine);
|
||||
}
|
||||
+230
-14
@@ -3,8 +3,7 @@
|
||||
#include "scriptstdstring.h"
|
||||
#include "scriptbuilder.h"
|
||||
#include "scriptarray.h"
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include "PhysFSManager.h"
|
||||
#include <cstring>
|
||||
#include <assert.h>
|
||||
#include "../log/log.h"
|
||||
@@ -110,6 +109,12 @@ void ScriptEngine::Shutdown() {
|
||||
bool ScriptEngine::CompileScript(const std::string& filename) {
|
||||
if (!engine) return false;
|
||||
|
||||
std::string source;
|
||||
if (!PhysFSManager::ReadTextFile(filename, source)) {
|
||||
log_error("Failed to read script file: %s", filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to compile in a temporary module first using CScriptBuilder
|
||||
CScriptBuilder tempBuilder;
|
||||
tempBuilder.SetIncludeCallback(IncludeCallback, nullptr);
|
||||
@@ -120,7 +125,7 @@ bool ScriptEngine::CompileScript(const std::string& filename) {
|
||||
return false;
|
||||
}
|
||||
|
||||
r = tempBuilder.AddSectionFromFile(filename.c_str());
|
||||
r = tempBuilder.AddSectionFromMemory(filename.c_str(), source.c_str(), static_cast<unsigned int>(source.size()));
|
||||
if (r < 0) {
|
||||
log_error("Failed to add script section from file: %s", filename.c_str());
|
||||
engine->DiscardModule("temp");
|
||||
@@ -146,7 +151,7 @@ bool ScriptEngine::CompileScript(const std::string& filename) {
|
||||
|
||||
r = mainBuilder.StartNewModule(engine, "main");
|
||||
if (r >= 0) {
|
||||
r = mainBuilder.AddSectionFromFile(filename.c_str());
|
||||
r = mainBuilder.AddSectionFromMemory(filename.c_str(), source.c_str(), static_cast<unsigned int>(source.size()));
|
||||
}
|
||||
if (r >= 0) {
|
||||
r = mainBuilder.BuildModule();
|
||||
@@ -165,15 +170,68 @@ bool ScriptEngine::CompileScript(const std::string& filename) {
|
||||
// Get the newly created module
|
||||
currentModule = engine->GetModule("main");
|
||||
|
||||
// Cache new functions
|
||||
initFunc = currentModule->GetFunctionByName("Init");
|
||||
updateFunc = currentModule->GetFunctionByName("Update");
|
||||
shutdownFunc = currentModule->GetFunctionByName("Shutdown");
|
||||
// Cache new functions (scene-specific names preferred)
|
||||
initFunc = currentModule->GetFunctionByDecl("void SceneInit()");
|
||||
if (!initFunc) {
|
||||
initFunc = currentModule->GetFunctionByDecl("void Init()");
|
||||
}
|
||||
|
||||
updateFunc = currentModule->GetFunctionByDecl("void SceneUpdate(float)");
|
||||
if (!updateFunc) {
|
||||
updateFunc = currentModule->GetFunctionByDecl("void Update(float)");
|
||||
}
|
||||
|
||||
shutdownFunc = currentModule->GetFunctionByDecl("void SceneShutdown()");
|
||||
if (!shutdownFunc) {
|
||||
shutdownFunc = currentModule->GetFunctionByDecl("void Shutdown()");
|
||||
}
|
||||
hasValidScript = true;
|
||||
log_info("Script compiled and cached: %s", filename.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ScriptEngine::CompileModule(const std::string& moduleName, const std::string& filename, asIScriptModule** outModule) {
|
||||
if (!engine) return false;
|
||||
|
||||
std::string source;
|
||||
if (!PhysFSManager::ReadTextFile(filename, source)) {
|
||||
log_error("Failed to read script module file: %s", filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (engine->GetModule(moduleName.c_str())) {
|
||||
engine->DiscardModule(moduleName.c_str());
|
||||
}
|
||||
|
||||
CScriptBuilder builder;
|
||||
builder.SetIncludeCallback(IncludeCallback, nullptr);
|
||||
|
||||
int r = builder.StartNewModule(engine, moduleName.c_str());
|
||||
if (r < 0) {
|
||||
log_error("Failed to start module: %s", moduleName.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
r = builder.AddSectionFromMemory(filename.c_str(), source.c_str(), static_cast<unsigned int>(source.size()));
|
||||
if (r < 0) {
|
||||
log_error("Failed to add script section from file: %s", filename.c_str());
|
||||
engine->DiscardModule(moduleName.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
r = builder.BuildModule();
|
||||
if (r < 0) {
|
||||
log_error("Failed to build module: %s", moduleName.c_str());
|
||||
engine->DiscardModule(moduleName.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (outModule) {
|
||||
*outModule = engine->GetModule(moduleName.c_str());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScriptEngine::CallScriptFunction(asIScriptFunction* func, float dt) {
|
||||
if (!func || !engine || !hasValidScript) return;
|
||||
|
||||
@@ -200,6 +258,160 @@ void ScriptEngine::CallScriptFunction(asIScriptFunction* func, float dt) {
|
||||
ctx->Release();
|
||||
}
|
||||
|
||||
bool ScriptEngine::CallModuleFunction(asIScriptModule* module, const std::string& decl) {
|
||||
if (!engine || !module) return false;
|
||||
|
||||
asIScriptFunction* func = module->GetFunctionByDecl(decl.c_str());
|
||||
if (!func) return false;
|
||||
|
||||
asIScriptContext* ctx = engine->CreateContext();
|
||||
if (!ctx) return false;
|
||||
|
||||
int r = ctx->Prepare(func);
|
||||
if (r < 0) {
|
||||
ctx->Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
r = ctx->Execute();
|
||||
if (r != asEXECUTION_FINISHED) {
|
||||
if (r == asEXECUTION_EXCEPTION) {
|
||||
log_error("Script exception: %s", ctx->GetExceptionString());
|
||||
}
|
||||
ctx->Release();
|
||||
return false;
|
||||
}
|
||||
|
||||
ctx->Release();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ScriptEngine::CallScriptFunction(asIScriptFunction* func, unsigned int entityId, float dt) {
|
||||
if (!func || !engine) return;
|
||||
|
||||
asIScriptContext* ctx = engine->CreateContext();
|
||||
if (!ctx) return;
|
||||
|
||||
int r = ctx->Prepare(func);
|
||||
if (r < 0) {
|
||||
ctx->Release();
|
||||
return;
|
||||
}
|
||||
|
||||
const asUINT paramCount = func->GetParamCount();
|
||||
if (paramCount >= 1) {
|
||||
ctx->SetArgDWord(0, entityId);
|
||||
}
|
||||
if (paramCount >= 2) {
|
||||
ctx->SetArgFloat(1, dt);
|
||||
}
|
||||
|
||||
r = ctx->Execute();
|
||||
if (r != asEXECUTION_FINISHED) {
|
||||
if (r == asEXECUTION_EXCEPTION) {
|
||||
log_error("Script exception: %s", ctx->GetExceptionString());
|
||||
}
|
||||
}
|
||||
|
||||
ctx->Release();
|
||||
}
|
||||
|
||||
asIScriptObject* ScriptEngine::CreateObjectInstance(asIScriptModule* module, const std::string& className)
|
||||
{
|
||||
if (!engine || !module) return nullptr;
|
||||
asITypeInfo* typeInfo = nullptr;
|
||||
int count = module->GetObjectTypeCount();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
asITypeInfo* t = module->GetObjectTypeByIndex(i);
|
||||
const char* n = t->GetName();
|
||||
if (n && className == n) {
|
||||
typeInfo = t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!typeInfo) return nullptr;
|
||||
void* raw = engine->CreateScriptObject(typeInfo);
|
||||
return reinterpret_cast<asIScriptObject*>(raw);
|
||||
}
|
||||
|
||||
void ScriptEngine::ReleaseObjectInstance(asIScriptObject* obj)
|
||||
{
|
||||
if (!obj) return;
|
||||
// Prefer using the engine helper to properly destroy the script object
|
||||
asITypeInfo* ti = obj->GetObjectType();
|
||||
if (engine && ti) {
|
||||
engine->ReleaseScriptObject(obj, ti);
|
||||
} else {
|
||||
obj->Release();
|
||||
}
|
||||
}
|
||||
|
||||
asIScriptFunction* ScriptEngine::GetObjectMethod(asIScriptModule* module, const std::string& className, const std::string& methodDecl)
|
||||
{
|
||||
if (!module) return nullptr;
|
||||
asITypeInfo* typeInfo = module->GetTypeInfoByName(className.c_str());
|
||||
if (!typeInfo) return nullptr;
|
||||
return typeInfo->GetMethodByDecl(methodDecl.c_str());
|
||||
}
|
||||
|
||||
void ScriptEngine::CallObjectMethod(asIScriptFunction* method, asIScriptObject* object, unsigned int entityId, float dt)
|
||||
{
|
||||
if (!method || !engine || !object) return;
|
||||
|
||||
asIScriptContext* ctx = engine->CreateContext();
|
||||
if (!ctx) return;
|
||||
|
||||
int r = ctx->Prepare(method);
|
||||
if (r < 0) { ctx->Release(); return; }
|
||||
|
||||
// Set the object instance for the call
|
||||
ctx->SetObject(object);
|
||||
|
||||
const asUINT paramCount = method->GetParamCount();
|
||||
if (paramCount >= 1) {
|
||||
ctx->SetArgDWord(0, entityId);
|
||||
}
|
||||
if (paramCount >= 2) {
|
||||
ctx->SetArgFloat(1, dt);
|
||||
}
|
||||
|
||||
r = ctx->Execute();
|
||||
if (r != asEXECUTION_FINISHED) {
|
||||
if (r == asEXECUTION_EXCEPTION) {
|
||||
log_error("Script exception: %s", ctx->GetExceptionString());
|
||||
}
|
||||
}
|
||||
|
||||
ctx->Release();
|
||||
}
|
||||
|
||||
void ScriptEngine::CallSceneObjectMethod(asIScriptFunction* method, asIScriptObject* object, float dt)
|
||||
{
|
||||
if (!method || !engine || !object) return;
|
||||
|
||||
asIScriptContext* ctx = engine->CreateContext();
|
||||
if (!ctx) return;
|
||||
|
||||
int r = ctx->Prepare(method);
|
||||
if (r < 0) { ctx->Release(); return; }
|
||||
|
||||
ctx->SetObject(object);
|
||||
|
||||
const asUINT paramCount = method->GetParamCount();
|
||||
if (paramCount >= 1) {
|
||||
ctx->SetArgFloat(0, dt);
|
||||
}
|
||||
|
||||
r = ctx->Execute();
|
||||
if (r != asEXECUTION_FINISHED) {
|
||||
if (r == asEXECUTION_EXCEPTION) {
|
||||
log_error("Script exception: %s", ctx->GetExceptionString());
|
||||
}
|
||||
}
|
||||
|
||||
ctx->Release();
|
||||
}
|
||||
|
||||
void ScriptEngine::ClearCachedFunctions() {
|
||||
updateFunc = nullptr;
|
||||
hasValidScript = false;
|
||||
@@ -258,7 +470,11 @@ int ScriptEngine::IncludeCallback(const char* include, const char* from, CScript
|
||||
|
||||
int result = -1;
|
||||
for (auto &cand : candidates) {
|
||||
result = builder->AddSectionFromFile(cand.c_str());
|
||||
std::string source;
|
||||
if (!PhysFSManager::ReadTextFile(cand, source)) {
|
||||
continue;
|
||||
}
|
||||
result = builder->AddSectionFromMemory(cand.c_str(), source.c_str(), static_cast<unsigned int>(source.size()));
|
||||
if (result >= 0) {
|
||||
log_info("Successfully included: %s", cand.c_str());
|
||||
return result;
|
||||
@@ -270,9 +486,9 @@ int ScriptEngine::IncludeCallback(const char* include, const char* from, CScript
|
||||
}
|
||||
|
||||
std::string ScriptEngine::ReadFile(const std::string& filename) {
|
||||
std::ifstream file(filename);
|
||||
if (!file) return "";
|
||||
std::stringstream ss;
|
||||
ss << file.rdbuf();
|
||||
return ss.str();
|
||||
std::string out;
|
||||
if (!PhysFSManager::ReadTextFile(filename, out)) {
|
||||
return std::string();
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
+16
-1
@@ -3,9 +3,12 @@ cmake_minimum_required(VERSION 3.16)
|
||||
# Unit tests just need test sources + link to simian_core
|
||||
add_executable(unit_tests
|
||||
test.cpp
|
||||
physfs_test_utils.cpp
|
||||
hot_reload.cpp
|
||||
script_engine_tests.cpp
|
||||
math_bindings_tests.cpp
|
||||
resource_tests.cpp
|
||||
scenegraph_tests.cpp
|
||||
)
|
||||
|
||||
target_include_directories(unit_tests PRIVATE
|
||||
@@ -26,4 +29,16 @@ add_test(NAME unit_tests COMMAND unit_tests)
|
||||
add_test(NAME hotreload_dir COMMAND unit_tests hotreload_dir)
|
||||
add_test(NAME script_compile COMMAND unit_tests script_compile)
|
||||
add_test(NAME script_include COMMAND unit_tests script_include)
|
||||
add_test(NAME math_bindings COMMAND unit_tests math_bindings)
|
||||
add_test(NAME math_bindings COMMAND unit_tests math_bindings)
|
||||
add_test(NAME shader_manager COMMAND unit_tests shader_manager)
|
||||
add_test(NAME model_manager COMMAND unit_tests model_manager)
|
||||
add_test(NAME material_manager COMMAND unit_tests material_manager)
|
||||
add_test(NAME asset_registry_material COMMAND unit_tests asset_registry_material)
|
||||
add_test(NAME resource_bindings COMMAND unit_tests resource_bindings)
|
||||
add_test(NAME scenegraph_world COMMAND unit_tests scenegraph_world)
|
||||
add_test(NAME scenegraph_detach COMMAND unit_tests scenegraph_detach)
|
||||
add_test(NAME scene_loader COMMAND unit_tests scene_loader)
|
||||
add_test(NAME scene_loader_script_path COMMAND unit_tests scene_loader_script_path)
|
||||
add_test(NAME scene_loader_clear_script COMMAND unit_tests scene_loader_clear_script)
|
||||
add_test(NAME scene_loader_assets COMMAND unit_tests scene_loader_assets)
|
||||
add_test(NAME scene_loader_sandbox COMMAND unit_tests scene_loader_sandbox)
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include "../include/HotReload.h"
|
||||
#include "physfs_test_utils.h"
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
@@ -18,7 +19,10 @@ void test_hotreload_directory_watch(void) {
|
||||
ofs << "// test" << std::endl;
|
||||
ofs.close();
|
||||
|
||||
HotReload hr(tmpDir.string());
|
||||
TEST_CHECK(InitPhysFSTest(tmpDir) == true);
|
||||
TEST_CHECK(MountPhysFSTest(tmpDir, "tmp_watch") == true);
|
||||
|
||||
HotReload hr("tmp_watch");
|
||||
TEST_CHECK(hr.CheckForChanges() == false);
|
||||
|
||||
std::this_thread::sleep_for(1s);
|
||||
@@ -28,6 +32,8 @@ void test_hotreload_directory_watch(void) {
|
||||
|
||||
TEST_CHECK(hr.CheckForChanges() == true);
|
||||
|
||||
ShutdownPhysFSTest();
|
||||
|
||||
std::filesystem::remove(f);
|
||||
std::filesystem::remove(tmpDir);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <fstream>
|
||||
#include <cmath>
|
||||
#include "../include/scripting/ScriptEngine.h"
|
||||
#include "physfs_test_utils.h"
|
||||
|
||||
void test_math_bindings(void) {
|
||||
std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_math";
|
||||
@@ -20,9 +21,12 @@ void test_math_bindings(void) {
|
||||
script << "float testAbs = Math::Abs(-42.5f);\n";
|
||||
}
|
||||
|
||||
TEST_CHECK(InitPhysFSTest(tmpDir) == true);
|
||||
TEST_CHECK(MountPhysFSTest(tmpDir, "") == true);
|
||||
|
||||
ScriptEngine se;
|
||||
TEST_CHECK(se.Initialize() == true);
|
||||
bool compiled = se.CompileScript(scriptPath.string());
|
||||
bool compiled = se.CompileScript("math_test.as");
|
||||
TEST_CHECK(compiled == true);
|
||||
|
||||
asIScriptEngine* engine = se.GetEngine();
|
||||
@@ -58,6 +62,8 @@ void test_math_bindings(void) {
|
||||
|
||||
se.Shutdown();
|
||||
|
||||
ShutdownPhysFSTest();
|
||||
|
||||
std::filesystem::remove(scriptPath);
|
||||
std::filesystem::remove(tmpDir);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
#include "physfs_test_utils.h"
|
||||
#include "PhysFSManager.h"
|
||||
|
||||
static bool g_initialized = false;
|
||||
|
||||
bool InitPhysFSTest(const std::filesystem::path& writeDir) {
|
||||
if (!g_initialized) {
|
||||
if (!PhysFSManager::Initialize("unit_tests")) {
|
||||
return false;
|
||||
}
|
||||
g_initialized = true;
|
||||
}
|
||||
|
||||
PhysFSManager::SetWriteDir(writeDir.string());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MountPhysFSTest(const std::filesystem::path& realPath, const std::string& mountPoint) {
|
||||
return PhysFSManager::MountPath(realPath.string(), mountPoint, true);
|
||||
}
|
||||
|
||||
bool UnmountPhysFSTest(const std::filesystem::path& realPath) {
|
||||
return PhysFSManager::UnmountPath(realPath.string());
|
||||
}
|
||||
|
||||
void ShutdownPhysFSTest() {
|
||||
if (g_initialized) {
|
||||
PhysFSManager::Shutdown();
|
||||
g_initialized = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
bool InitPhysFSTest(const std::filesystem::path& writeDir);
|
||||
bool MountPhysFSTest(const std::filesystem::path& realPath, const std::string& mountPoint);
|
||||
bool UnmountPhysFSTest(const std::filesystem::path& realPath);
|
||||
void ShutdownPhysFSTest();
|
||||
@@ -0,0 +1,292 @@
|
||||
#define TEST_NO_MAIN
|
||||
#include "acutest.h"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <cstdlib>
|
||||
#include "../include/ShaderManager.h"
|
||||
#include "../include/ModelManager.h"
|
||||
#include "../include/MaterialManager.h"
|
||||
#include "../include/TextureManager.h"
|
||||
#include "../include/AssetManager.h"
|
||||
#include "../include/scripting/ScriptEngine.h"
|
||||
#include "../include/scripting/ResourceBindings.h"
|
||||
#include "raylib.h"
|
||||
#include "physfs_test_utils.h"
|
||||
extern "C" {
|
||||
void acutest_skip_(const char* file, int line, const char* fmt, ...);
|
||||
}
|
||||
static bool HasDisplay() {
|
||||
#if defined(_WIN32)
|
||||
return true;
|
||||
#elif defined(__APPLE__)
|
||||
return true;
|
||||
#else
|
||||
const char* display = std::getenv("DISPLAY");
|
||||
const char* wayland = std::getenv("WAYLAND_DISPLAY");
|
||||
return (display && *display) || (wayland && *wayland);
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool InitRaylibHidden() {
|
||||
const char* forceHeadless = std::getenv("SIMIAN_HEADLESS");
|
||||
if (forceHeadless && forceHeadless[0] == '1') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!HasDisplay()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsWindowReady()) {
|
||||
SetConfigFlags(FLAG_WINDOW_HIDDEN);
|
||||
InitWindow(1, 1, "simian_test");
|
||||
SetTargetFPS(60);
|
||||
}
|
||||
|
||||
return IsWindowReady();
|
||||
}
|
||||
|
||||
static void ShutdownRaylibHidden() {
|
||||
if (IsWindowReady()) {
|
||||
CloseWindow();
|
||||
}
|
||||
}
|
||||
|
||||
static std::filesystem::path WriteTempShader(const std::filesystem::path& dir, const std::string& name, const std::string& code) {
|
||||
std::filesystem::path path = dir / name;
|
||||
std::ofstream ofs(path);
|
||||
ofs << code;
|
||||
return path;
|
||||
}
|
||||
|
||||
void test_shader_manager(void) {
|
||||
if (!InitRaylibHidden()) {
|
||||
TEST_SKIP("No display available for raylib");
|
||||
return;
|
||||
}
|
||||
|
||||
std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_shader";
|
||||
std::filesystem::create_directories(tmpDir);
|
||||
|
||||
const std::string vsCode =
|
||||
"#version 330\n"
|
||||
"layout(location = 0) in vec3 vertexPosition;\n"
|
||||
"uniform mat4 mvp;\n"
|
||||
"void main() {\n"
|
||||
" gl_Position = mvp * vec4(vertexPosition, 1.0);\n"
|
||||
"}\n";
|
||||
|
||||
const std::string fsCode =
|
||||
"#version 330\n"
|
||||
"out vec4 fragColor;\n"
|
||||
"void main() {\n"
|
||||
" fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
|
||||
"}\n";
|
||||
|
||||
auto vsPath = WriteTempShader(tmpDir, "test.vs", vsCode);
|
||||
auto fsPath = WriteTempShader(tmpDir, "test.fs", fsCode);
|
||||
|
||||
TEST_CHECK(InitPhysFSTest(tmpDir) == true);
|
||||
TEST_CHECK(MountPhysFSTest(tmpDir, "") == true);
|
||||
|
||||
ShaderManager shaderManager;
|
||||
unsigned int shaderId = shaderManager.Load("test.vs", "test.fs");
|
||||
TEST_CHECK(shaderId != 0);
|
||||
TEST_CHECK(shaderManager.Get(shaderId) != nullptr);
|
||||
|
||||
shaderManager.Unload(shaderId);
|
||||
TEST_CHECK(shaderManager.Get(shaderId) == nullptr);
|
||||
|
||||
ShutdownRaylibHidden();
|
||||
|
||||
|
||||
std::filesystem::remove(vsPath);
|
||||
std::filesystem::remove(fsPath);
|
||||
std::filesystem::remove(tmpDir);
|
||||
}
|
||||
|
||||
void test_model_manager(void) {
|
||||
if (!InitRaylibHidden()) {
|
||||
TEST_SKIP("No display available for raylib");
|
||||
return;
|
||||
}
|
||||
|
||||
ModelManager modelManager;
|
||||
Mesh mesh = GenMeshCube(1.0f, 1.0f, 1.0f);
|
||||
unsigned int modelId = modelManager.LoadFromMesh(mesh);
|
||||
TEST_CHECK(modelId != 0);
|
||||
TEST_CHECK(modelManager.Get(modelId) != nullptr);
|
||||
|
||||
modelManager.Unload(modelId);
|
||||
TEST_CHECK(modelManager.Get(modelId) == nullptr);
|
||||
|
||||
ShutdownRaylibHidden();
|
||||
}
|
||||
|
||||
void test_material_manager(void) {
|
||||
MaterialManager materialManager;
|
||||
Color albedo = {1, 2, 3, 4};
|
||||
unsigned int materialId = materialManager.Create(7, albedo);
|
||||
TEST_CHECK(materialId != 0);
|
||||
|
||||
MaterialData* data = materialManager.Get(materialId);
|
||||
TEST_CHECK(data != nullptr);
|
||||
TEST_CHECK(data->shaderId == 7);
|
||||
TEST_CHECK(data->albedo.r == 1);
|
||||
TEST_CHECK(data->albedo.g == 2);
|
||||
TEST_CHECK(data->albedo.b == 3);
|
||||
TEST_CHECK(data->albedo.a == 4);
|
||||
|
||||
materialManager.SetShader(materialId, 42);
|
||||
TEST_CHECK(data->shaderId == 42);
|
||||
|
||||
Color newAlbedo = {10, 20, 30, 40};
|
||||
materialManager.SetAlbedo(materialId, newAlbedo);
|
||||
TEST_CHECK(data->albedo.r == 10);
|
||||
TEST_CHECK(data->albedo.g == 20);
|
||||
TEST_CHECK(data->albedo.b == 30);
|
||||
TEST_CHECK(data->albedo.a == 40);
|
||||
|
||||
materialManager.Remove(materialId);
|
||||
TEST_CHECK(materialManager.Get(materialId) == nullptr);
|
||||
}
|
||||
|
||||
void test_resource_bindings_script(void) {
|
||||
if (!InitRaylibHidden()) {
|
||||
TEST_SKIP("No display available for raylib");
|
||||
return;
|
||||
}
|
||||
|
||||
std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_resource";
|
||||
std::filesystem::create_directories(tmpDir);
|
||||
|
||||
const std::string vsCode =
|
||||
"#version 330\n"
|
||||
"layout(location = 0) in vec3 vertexPosition;\n"
|
||||
"uniform mat4 mvp;\n"
|
||||
"void main() {\n"
|
||||
" gl_Position = mvp * vec4(vertexPosition, 1.0);\n"
|
||||
"}\n";
|
||||
|
||||
const std::string fsCode =
|
||||
"#version 330\n"
|
||||
"out vec4 fragColor;\n"
|
||||
"void main() {\n"
|
||||
" fragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
|
||||
"}\n";
|
||||
|
||||
auto vsPath = WriteTempShader(tmpDir, "script.vs", vsCode);
|
||||
auto fsPath = WriteTempShader(tmpDir, "script.fs", fsCode);
|
||||
|
||||
std::filesystem::path scriptPath = tmpDir / "resource_test.as";
|
||||
{
|
||||
std::ofstream script(scriptPath);
|
||||
script << "uint shaderId = 0;\n";
|
||||
script << "uint modelId = 0;\n";
|
||||
script << "uint materialId = 0;\n";
|
||||
script << "void Init() {\n";
|
||||
script << " shaderId = Shader::Load(\"script.vs\", \"script.fs\");\n";
|
||||
script << " modelId = Model::LoadCube(1.0f, 1.0f, 1.0f);\n";
|
||||
script << " materialId = Material::Create(shaderId, 255, 0, 0, 255);\n";
|
||||
script << "}\n";
|
||||
}
|
||||
|
||||
TEST_CHECK(InitPhysFSTest(tmpDir) == true);
|
||||
TEST_CHECK(MountPhysFSTest(tmpDir, "") == true);
|
||||
|
||||
{
|
||||
ShaderManager shaderManager;
|
||||
ModelManager modelManager;
|
||||
MaterialManager materialManager;
|
||||
TextureManager textureManager;
|
||||
SetResourceManagers(&shaderManager, &modelManager, &materialManager, &textureManager);
|
||||
|
||||
ScriptEngine se;
|
||||
TEST_CHECK(se.Initialize() == true);
|
||||
bool compiled = se.CompileScript("resource_test.as");
|
||||
TEST_CHECK(compiled == true);
|
||||
|
||||
asIScriptFunction* initFunc = se.GetInitFunction();
|
||||
TEST_CHECK(initFunc != nullptr);
|
||||
se.CallScriptFunction(initFunc);
|
||||
|
||||
asIScriptEngine* engine = se.GetEngine();
|
||||
TEST_CHECK(engine != nullptr);
|
||||
asIScriptModule* module = engine ? engine->GetModule("main") : nullptr;
|
||||
TEST_CHECK(module != nullptr);
|
||||
|
||||
auto fetchUInt = [module](const char* name) -> unsigned int {
|
||||
int idx = module->GetGlobalVarIndexByName(name);
|
||||
TEST_CHECK_(idx >= 0, "Global '%s' not found", name);
|
||||
if (idx < 0) return 0;
|
||||
void* address = module->GetAddressOfGlobalVar(idx);
|
||||
TEST_CHECK_(address != nullptr, "Global '%s' address null", name);
|
||||
if (!address) return 0;
|
||||
return *static_cast<unsigned int*>(address);
|
||||
};
|
||||
|
||||
unsigned int shaderId = fetchUInt("shaderId");
|
||||
unsigned int modelId = fetchUInt("modelId");
|
||||
unsigned int materialId = fetchUInt("materialId");
|
||||
|
||||
TEST_CHECK(shaderId != 0);
|
||||
TEST_CHECK(modelId != 0);
|
||||
TEST_CHECK(materialId != 0);
|
||||
|
||||
se.Shutdown();
|
||||
}
|
||||
|
||||
std::filesystem::remove(scriptPath);
|
||||
std::filesystem::remove(vsPath);
|
||||
std::filesystem::remove(fsPath);
|
||||
std::filesystem::remove(tmpDir);
|
||||
|
||||
ShutdownRaylibHidden();
|
||||
|
||||
}
|
||||
|
||||
void test_asset_registry_material(void) {
|
||||
std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_asset_registry";
|
||||
std::filesystem::create_directories(tmpDir);
|
||||
std::filesystem::path registryPath = tmpDir / "registry.toml";
|
||||
|
||||
TEST_CHECK(InitPhysFSTest(tmpDir) == true);
|
||||
TEST_CHECK(MountPhysFSTest(tmpDir, "") == true);
|
||||
|
||||
MaterialManager materialManager;
|
||||
AssetManager assets;
|
||||
assets.SetManagers(nullptr, nullptr, &materialManager, nullptr);
|
||||
|
||||
Color albedo = {10, 20, 30, 40};
|
||||
unsigned int materialId = assets.CreateMaterial("test_mat", 0, albedo);
|
||||
TEST_CHECK(materialId != 0);
|
||||
|
||||
bool saved = assets.SaveRegistry("registry.toml");
|
||||
TEST_CHECK(saved == true);
|
||||
|
||||
assets.Clear();
|
||||
materialManager.Clear();
|
||||
|
||||
MaterialManager materialManagerReload;
|
||||
AssetManager assetsReload;
|
||||
assetsReload.SetManagers(nullptr, nullptr, &materialManagerReload, nullptr);
|
||||
|
||||
bool loaded = assetsReload.LoadRegistry("registry.toml");
|
||||
TEST_CHECK(loaded == true);
|
||||
|
||||
unsigned int loadedId = assetsReload.GetMaterialId("test_mat");
|
||||
TEST_CHECK(loadedId != 0);
|
||||
MaterialData* data = materialManagerReload.Get(loadedId);
|
||||
TEST_CHECK(data != nullptr);
|
||||
if (data) {
|
||||
TEST_CHECK(data->albedo.r == 10);
|
||||
TEST_CHECK(data->albedo.g == 20);
|
||||
TEST_CHECK(data->albedo.b == 30);
|
||||
TEST_CHECK(data->albedo.a == 40);
|
||||
}
|
||||
|
||||
ShutdownPhysFSTest();
|
||||
|
||||
std::filesystem::remove(registryPath);
|
||||
std::filesystem::remove(tmpDir);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
void test_shader_manager(void);
|
||||
void test_model_manager(void);
|
||||
void test_material_manager(void);
|
||||
void test_resource_bindings_script(void);
|
||||
void test_asset_registry_material(void);
|
||||
@@ -0,0 +1,369 @@
|
||||
#define TEST_NO_MAIN
|
||||
#include "acutest.h"
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <cstdlib>
|
||||
#include "../include/ECSComponents.h"
|
||||
#include "../include/SceneGraph.h"
|
||||
#include "../include/SceneLoader.h"
|
||||
#include "../include/ModelManager.h"
|
||||
#include "../include/ShaderManager.h"
|
||||
#include "../include/TextureManager.h"
|
||||
#include "../include/AssetManager.h"
|
||||
#include "raylib.h"
|
||||
#include "entt.hpp"
|
||||
#include "physfs_test_utils.h"
|
||||
|
||||
extern "C" {
|
||||
void acutest_skip_(const char* file, int line, const char* fmt, ...);
|
||||
}
|
||||
static bool HasDisplay() {
|
||||
#if defined(_WIN32)
|
||||
return true;
|
||||
#elif defined(__APPLE__)
|
||||
return true;
|
||||
#else
|
||||
const char* display = std::getenv("DISPLAY");
|
||||
const char* wayland = std::getenv("WAYLAND_DISPLAY");
|
||||
return (display && *display) || (wayland && *wayland);
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool InitRaylibHidden() {
|
||||
const char* forceHeadless = std::getenv("SIMIAN_HEADLESS");
|
||||
if (forceHeadless && forceHeadless[0] == '1') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!HasDisplay()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!IsWindowReady()) {
|
||||
SetConfigFlags(FLAG_WINDOW_HIDDEN);
|
||||
InitWindow(1, 1, "simian_test_scene");
|
||||
SetTargetFPS(60);
|
||||
}
|
||||
|
||||
return IsWindowReady();
|
||||
}
|
||||
|
||||
static void ShutdownRaylibHidden() {
|
||||
if (IsWindowReady()) {
|
||||
CloseWindow();
|
||||
}
|
||||
}
|
||||
|
||||
void test_scenegraph_world_transform(void) {
|
||||
entt::registry registry;
|
||||
|
||||
entt::entity parent = registry.create();
|
||||
entt::entity child = registry.create();
|
||||
|
||||
registry.emplace<ECSTransform>(parent, Vector3{1.0f, 0.0f, 0.0f}, Vector3{2.0f, 2.0f, 2.0f}, 0.0f);
|
||||
registry.emplace<ECSTransform>(child, Vector3{1.0f, 0.0f, 0.0f}, Vector3{1.0f, 1.0f, 1.0f}, 0.0f);
|
||||
|
||||
AttachChild(registry, child, parent);
|
||||
UpdateWorldTransforms(registry);
|
||||
|
||||
auto* childWorld = registry.try_get<WorldTransform>(child);
|
||||
TEST_CHECK(childWorld != nullptr);
|
||||
if (childWorld) {
|
||||
TEST_CHECK(childWorld->position.x == 3.0f);
|
||||
TEST_CHECK(childWorld->position.y == 0.0f);
|
||||
TEST_CHECK(childWorld->position.z == 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void test_scenegraph_detach(void) {
|
||||
entt::registry registry;
|
||||
|
||||
entt::entity parent = registry.create();
|
||||
entt::entity child = registry.create();
|
||||
|
||||
AttachChild(registry, child, parent);
|
||||
DetachFromParent(registry, child);
|
||||
|
||||
auto* childHierarchy = registry.try_get<Hierarchy>(child);
|
||||
TEST_CHECK(childHierarchy != nullptr);
|
||||
if (childHierarchy) {
|
||||
TEST_CHECK(childHierarchy->parent == entt::null);
|
||||
TEST_CHECK(childHierarchy->prev == entt::null);
|
||||
TEST_CHECK(childHierarchy->next == entt::null);
|
||||
}
|
||||
|
||||
auto* parentHierarchy = registry.try_get<Hierarchy>(parent);
|
||||
TEST_CHECK(parentHierarchy != nullptr);
|
||||
if (parentHierarchy) {
|
||||
TEST_CHECK(parentHierarchy->first == entt::null);
|
||||
}
|
||||
}
|
||||
|
||||
void test_scene_loader_basic(void) {
|
||||
if (!InitRaylibHidden()) {
|
||||
TEST_SKIP("No display available for raylib");
|
||||
return;
|
||||
}
|
||||
|
||||
entt::registry registry;
|
||||
|
||||
std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_scene";
|
||||
std::filesystem::create_directories(tmpDir);
|
||||
std::filesystem::path scenePath = tmpDir / "scene.toml";
|
||||
|
||||
{
|
||||
std::ofstream scene(scenePath);
|
||||
scene << "[[entity]]\n";
|
||||
scene << "id = \"root\"\n";
|
||||
scene << "tag = \"Root\"\n";
|
||||
scene << "position = [0.0, 0.0, 0.0]\n";
|
||||
scene << "\n";
|
||||
scene << "[[entity]]\n";
|
||||
scene << "id = \"parent\"\n";
|
||||
scene << "parent = \"root\"\n";
|
||||
scene << "tag = \"Parent\"\n";
|
||||
scene << "position = [1.0, 0.0, 0.0]\n";
|
||||
scene << "model = \"cube\"\n";
|
||||
scene << "model_size = [1.0, 1.0, 1.0]\n";
|
||||
scene << "color = 0x00FF00FF\n";
|
||||
scene << "\n";
|
||||
scene << "[[entity]]\n";
|
||||
scene << "id = \"child\"\n";
|
||||
scene << "parent = \"parent\"\n";
|
||||
scene << "tag = \"Child\"\n";
|
||||
scene << "position = [0.0, 1.0, 0.0]\n";
|
||||
scene << "model = \"sphere\"\n";
|
||||
scene << "radius = 0.5\n";
|
||||
scene << "color = 0xFF0000FF\n";
|
||||
}
|
||||
|
||||
TEST_CHECK(InitPhysFSTest(tmpDir) == true);
|
||||
TEST_CHECK(MountPhysFSTest(tmpDir, "") == true);
|
||||
|
||||
{
|
||||
ModelManager modelManager;
|
||||
ShaderManager shaderManager;
|
||||
TextureManager textureManager;
|
||||
SetSceneContext(®istry, &modelManager, &shaderManager, &textureManager);
|
||||
|
||||
entt::entity root = LoadSceneFromFile("scene.toml", true);
|
||||
TEST_CHECK(root != entt::null);
|
||||
}
|
||||
|
||||
entt::entity parentEntity = entt::null;
|
||||
entt::entity childEntity = entt::null;
|
||||
|
||||
auto tagView = registry.view<Tag>();
|
||||
for (auto entity : tagView) {
|
||||
const auto& tag = tagView.get<Tag>(entity);
|
||||
if (tag.name == "Parent") {
|
||||
parentEntity = entity;
|
||||
} else if (tag.name == "Child") {
|
||||
childEntity = entity;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CHECK(parentEntity != entt::null);
|
||||
TEST_CHECK(childEntity != entt::null);
|
||||
|
||||
if (parentEntity != entt::null && childEntity != entt::null) {
|
||||
auto* childHierarchy = registry.try_get<Hierarchy>(childEntity);
|
||||
TEST_CHECK(childHierarchy != nullptr);
|
||||
if (childHierarchy) {
|
||||
TEST_CHECK(childHierarchy->parent == parentEntity);
|
||||
}
|
||||
TEST_CHECK(registry.all_of<ModelRenderer>(parentEntity));
|
||||
TEST_CHECK(registry.all_of<ModelRenderer>(childEntity));
|
||||
}
|
||||
|
||||
ShutdownRaylibHidden();
|
||||
|
||||
|
||||
std::filesystem::remove(scenePath);
|
||||
std::filesystem::remove(tmpDir);
|
||||
}
|
||||
|
||||
void test_scene_loader_scene_script_path(void) {
|
||||
entt::registry registry;
|
||||
ModelManager modelManager;
|
||||
ShaderManager shaderManager;
|
||||
TextureManager textureManager;
|
||||
SetSceneContext(®istry, &modelManager, &shaderManager, &textureManager);
|
||||
|
||||
std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_scene_script";
|
||||
std::filesystem::create_directories(tmpDir);
|
||||
std::filesystem::path scenePath = tmpDir / "scene.toml";
|
||||
|
||||
{
|
||||
std::ofstream scene(scenePath);
|
||||
scene << "scene_script = \"scripts/demo_scene.as\"\n";
|
||||
scene << "\n";
|
||||
scene << "[[entity]]\n";
|
||||
scene << "id = \"root\"\n";
|
||||
scene << "tag = \"Root\"\n";
|
||||
}
|
||||
|
||||
TEST_CHECK(InitPhysFSTest(tmpDir) == true);
|
||||
TEST_CHECK(MountPhysFSTest(tmpDir, "") == true);
|
||||
|
||||
entt::entity root = LoadSceneFromFile("scene.toml", true);
|
||||
TEST_CHECK(root != entt::null);
|
||||
|
||||
std::string sceneScript;
|
||||
bool changed = ConsumeSceneScriptPath(sceneScript);
|
||||
TEST_CHECK(changed == true);
|
||||
TEST_CHECK(sceneScript == "scripts/demo_scene.as");
|
||||
|
||||
|
||||
std::filesystem::remove(scenePath);
|
||||
std::filesystem::remove(tmpDir);
|
||||
}
|
||||
|
||||
void test_scene_loader_clear_scene_script(void) {
|
||||
entt::registry registry;
|
||||
ModelManager modelManager;
|
||||
ShaderManager shaderManager;
|
||||
TextureManager textureManager;
|
||||
SetSceneContext(®istry, &modelManager, &shaderManager, &textureManager);
|
||||
|
||||
SetSceneScriptPath("scripts/old_scene.as");
|
||||
std::string previous;
|
||||
ConsumeSceneScriptPath(previous);
|
||||
|
||||
std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_scene_clear";
|
||||
std::filesystem::create_directories(tmpDir);
|
||||
std::filesystem::path scenePath = tmpDir / "scene.toml";
|
||||
|
||||
{
|
||||
std::ofstream scene(scenePath);
|
||||
scene << "[[entity]]\n";
|
||||
scene << "id = \"root\"\n";
|
||||
scene << "tag = \"Root\"\n";
|
||||
}
|
||||
|
||||
TEST_CHECK(InitPhysFSTest(tmpDir) == true);
|
||||
TEST_CHECK(MountPhysFSTest(tmpDir, "") == true);
|
||||
|
||||
LoadSceneFromFile("scene.toml", true);
|
||||
|
||||
std::string sceneScript;
|
||||
bool changed = ConsumeSceneScriptPath(sceneScript);
|
||||
TEST_CHECK(changed == true);
|
||||
TEST_CHECK(sceneScript.empty());
|
||||
|
||||
ShutdownPhysFSTest();
|
||||
|
||||
std::filesystem::remove(scenePath);
|
||||
std::filesystem::remove(tmpDir);
|
||||
}
|
||||
|
||||
void test_scene_loader_asset_keys(void) {
|
||||
if (!InitRaylibHidden()) {
|
||||
TEST_SKIP("No display available for raylib");
|
||||
return;
|
||||
}
|
||||
|
||||
std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_scene_assets";
|
||||
std::filesystem::create_directories(tmpDir);
|
||||
std::filesystem::path registryPath = tmpDir / "registry.toml";
|
||||
std::filesystem::path scenePath = tmpDir / "scene_asset.toml";
|
||||
|
||||
TEST_CHECK(InitPhysFSTest(tmpDir) == true);
|
||||
TEST_CHECK(MountPhysFSTest(tmpDir, "") == true);
|
||||
|
||||
{
|
||||
ModelManager modelManager;
|
||||
ShaderManager shaderManager;
|
||||
TextureManager textureManager;
|
||||
AssetManager assets;
|
||||
assets.SetManagers(&modelManager, &shaderManager, nullptr, &textureManager);
|
||||
assets.LoadCube("demo_cube", 1.0f, 1.0f, 1.0f);
|
||||
assets.SaveRegistry("registry.toml");
|
||||
}
|
||||
|
||||
entt::registry registry;
|
||||
{
|
||||
ModelManager modelManager;
|
||||
ShaderManager shaderManager;
|
||||
TextureManager textureManager;
|
||||
AssetManager assets;
|
||||
assets.SetManagers(&modelManager, &shaderManager, nullptr, &textureManager);
|
||||
assets.LoadRegistry("registry.toml");
|
||||
|
||||
SetSceneContext(®istry, &modelManager, &shaderManager, &textureManager);
|
||||
SetSceneAssetManager(&assets);
|
||||
|
||||
{
|
||||
std::ofstream scene(scenePath);
|
||||
scene << "[[entity]]\n";
|
||||
scene << "id = \"root\"\n";
|
||||
scene << "tag = \"Root\"\n";
|
||||
scene << "position = [0.0, 0.0, 0.0]\n";
|
||||
scene << "model_asset = \"demo_cube\"\n";
|
||||
scene << "color = 0xFFFFFFFF\n";
|
||||
scene << "\n";
|
||||
}
|
||||
|
||||
entt::entity root = LoadSceneFromFile("scene_asset.toml", true);
|
||||
TEST_CHECK(root != entt::null);
|
||||
|
||||
auto tagView = registry.view<Tag>();
|
||||
entt::entity rootEntity = entt::null;
|
||||
for (auto entity : tagView) {
|
||||
const auto& tag = tagView.get<Tag>(entity);
|
||||
if (tag.name == "Root") {
|
||||
rootEntity = entity;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CHECK(rootEntity != entt::null);
|
||||
if (rootEntity != entt::null) {
|
||||
auto* renderer = registry.try_get<ModelRenderer>(rootEntity);
|
||||
TEST_CHECK(renderer != nullptr);
|
||||
if (renderer) {
|
||||
unsigned int expectedId = assets.GetModelId("demo_cube");
|
||||
TEST_CHECK(expectedId != 0);
|
||||
TEST_CHECK(renderer->modelId == expectedId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ShutdownRaylibHidden();
|
||||
|
||||
ShutdownPhysFSTest();
|
||||
|
||||
std::filesystem::remove(scenePath);
|
||||
std::filesystem::remove(registryPath);
|
||||
std::filesystem::remove(tmpDir);
|
||||
}
|
||||
|
||||
void test_scene_loader_rejects_escape(void) {
|
||||
entt::registry registry;
|
||||
ModelManager modelManager;
|
||||
ShaderManager shaderManager;
|
||||
TextureManager textureManager;
|
||||
SetSceneContext(®istry, &modelManager, &shaderManager, &textureManager);
|
||||
|
||||
std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_scene_sandbox";
|
||||
std::filesystem::create_directories(tmpDir);
|
||||
std::filesystem::path outsidePath = std::filesystem::current_path() / "outside_scene.toml";
|
||||
|
||||
{
|
||||
std::ofstream scene(outsidePath);
|
||||
scene << "[[entity]]\n";
|
||||
scene << "id = \"root\"\n";
|
||||
scene << "tag = \"Root\"\n";
|
||||
}
|
||||
|
||||
TEST_CHECK(InitPhysFSTest(tmpDir) == true);
|
||||
TEST_CHECK(MountPhysFSTest(tmpDir, "") == true);
|
||||
|
||||
entt::entity root = LoadSceneFromFile("../outside_scene.toml", true);
|
||||
TEST_CHECK(root == entt::null);
|
||||
|
||||
ShutdownPhysFSTest();
|
||||
|
||||
std::filesystem::remove(outsidePath);
|
||||
std::filesystem::remove(tmpDir);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
void test_scenegraph_world_transform(void);
|
||||
void test_scenegraph_detach(void);
|
||||
void test_scene_loader_basic(void);
|
||||
void test_scene_loader_asset_keys(void);
|
||||
void test_scene_loader_scene_script_path(void);
|
||||
void test_scene_loader_clear_scene_script(void);
|
||||
void test_scene_loader_rejects_escape(void);
|
||||
@@ -3,6 +3,7 @@
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include "../include/scripting/ScriptEngine.h"
|
||||
#include "physfs_test_utils.h"
|
||||
|
||||
void test_scriptengine_compile_and_call(void) {
|
||||
std::filesystem::path tmpDir = std::filesystem::current_path() / "tmp_script";
|
||||
@@ -14,9 +15,12 @@ void test_scriptengine_compile_and_call(void) {
|
||||
ofs << "void Update(float dt) { g_x += dt * 10.0f; }\n";
|
||||
}
|
||||
|
||||
TEST_CHECK(InitPhysFSTest(tmpDir) == true);
|
||||
TEST_CHECK(MountPhysFSTest(tmpDir, "") == true);
|
||||
|
||||
ScriptEngine se;
|
||||
TEST_CHECK(se.Initialize() == true);
|
||||
bool ok = se.CompileScript(script.string());
|
||||
bool ok = se.CompileScript("simple.as");
|
||||
TEST_CHECK(ok == true);
|
||||
asIScriptFunction* upd = se.GetUpdateFunction();
|
||||
TEST_CHECK(upd != nullptr);
|
||||
@@ -25,6 +29,8 @@ void test_scriptengine_compile_and_call(void) {
|
||||
|
||||
se.Shutdown();
|
||||
|
||||
ShutdownPhysFSTest();
|
||||
|
||||
std::filesystem::remove(script);
|
||||
std::filesystem::remove(tmpDir);
|
||||
}
|
||||
@@ -46,12 +52,17 @@ void test_scriptengine_include(void) {
|
||||
ofsmain << "void Update(float dt) { Included(); }\n";
|
||||
}
|
||||
|
||||
TEST_CHECK(InitPhysFSTest(tmpDir) == true);
|
||||
TEST_CHECK(MountPhysFSTest(tmpDir, "") == true);
|
||||
|
||||
ScriptEngine se;
|
||||
TEST_CHECK(se.Initialize() == true);
|
||||
bool ok = se.CompileScript(main.string());
|
||||
bool ok = se.CompileScript("main.as");
|
||||
TEST_CHECK(ok == true);
|
||||
se.Shutdown();
|
||||
|
||||
ShutdownPhysFSTest();
|
||||
|
||||
std::filesystem::remove(inc);
|
||||
std::filesystem::remove(main);
|
||||
std::filesystem::remove(tmpDir);
|
||||
|
||||
@@ -2,10 +2,24 @@
|
||||
#include "hot_reload.h"
|
||||
#include "script_engine_tests.h"
|
||||
#include "math_bindings_tests.h"
|
||||
#include "resource_tests.h"
|
||||
#include "scenegraph_tests.h"
|
||||
TEST_LIST = {
|
||||
{ "hotreload_dir", test_hotreload_directory_watch },
|
||||
{ "script_compile", test_scriptengine_compile_and_call },
|
||||
{ "script_include", test_scriptengine_include },
|
||||
{ "math_bindings", test_math_bindings },
|
||||
{ "shader_manager", test_shader_manager },
|
||||
{ "model_manager", test_model_manager },
|
||||
{ "material_manager", test_material_manager },
|
||||
{ "asset_registry_material", test_asset_registry_material },
|
||||
{ "resource_bindings", test_resource_bindings_script },
|
||||
{ "scenegraph_world", test_scenegraph_world_transform },
|
||||
{ "scenegraph_detach", test_scenegraph_detach },
|
||||
{ "scene_loader", test_scene_loader_basic },
|
||||
{ "scene_loader_script_path", test_scene_loader_scene_script_path },
|
||||
{ "scene_loader_clear_script", test_scene_loader_clear_scene_script },
|
||||
{ "scene_loader_assets", test_scene_loader_asset_keys },
|
||||
{ "scene_loader_sandbox", test_scene_loader_rejects_escape },
|
||||
{ NULL, NULL }
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user