39 Commits

Author SHA1 Message Date
nick 3e23f7da8e feat: added physFS
CI / build-and-test (push) Failing after 10m16s
CI / build-and-test (pull_request) Successful in 3m2s
2026-03-11 16:43:36 +13:00
nick 8c2ce637a2 feat: autoload script
CI / build-and-test (push) Failing after 14m23s
2026-03-11 15:58:51 +13:00
nick 5f031b80c2 chore: more tests
CI / build-and-test (push) Failing after 14m23s
2026-03-10 21:20:30 +13:00
nick eb155da5d8 chore: fix for tests
CI / build-and-test (push) Failing after 14m58s
2026-03-10 20:48:33 +13:00
nick 36fd4047dc fix: scene scripts and asset restore
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Failing after 13m56s
CI / build-and-test (push) Failing after 14m19s
2026-03-10 20:03:44 +13:00
nick 756b818f46 feat: asset management, editor camera
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Successful in 26s
CI / build-and-test (push) Failing after 2m36s
2026-03-09 21:31:51 +13:00
nick d7add3964d feat: additonal components, updated docs
CI / build-and-test (push) Successful in 2m37s
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Successful in 10s
2026-03-09 17:37:29 +13:00
nick 8041286a9d feat: tool tips and groups for editable properties
CI / build-and-test (push) Successful in 2m4s
2026-03-09 13:51:45 +13:00
nick b3871716a1 feat: edit values and persist
CI / build-and-test (push) Successful in 2m10s
2026-03-09 13:33:54 +13:00
nick 2a74648ffd feat: editable properties
CI / build-and-test (push) Successful in 2m19s
2026-03-09 13:24:39 +13:00
nick cd754fda28 feat: class instances for entity scripts
CI / build-and-test (push) Successful in 2m9s
2026-03-09 12:55:55 +13:00
nick 9ebf77daec chore: trying to fix the wiki
CI / build-and-test (push) Successful in 2m28s
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Successful in 10s
2026-03-08 22:07:23 +13:00
nick d6f2cc1ea3 feat: example entity script
CI / build-and-test (push) Successful in 2m13s
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Successful in 10s
2026-03-08 21:48:40 +13:00
nick 7a3810805e fix: issue with duplicating entities when created by the script
CI / build-and-test (push) Successful in 2m21s
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Failing after 21s
2026-03-08 20:47:09 +13:00
nick ae21d4a5d2 chore: docs update
CI / build-and-test (push) Successful in 2m5s
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Failing after 22s
2026-03-08 20:32:14 +13:00
nick 0baabd689f chore: more docs attempts 2026-03-08 20:29:45 +13:00
nick 28328cbc0d feat: editor improvements
CI / build-and-test (push) Successful in 2m5s
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Failing after 23s
2026-03-08 20:25:11 +13:00
nick 18ca7105c5 fix: mazimise window and notification freezing
CI / build-and-test (push) Successful in 2m33s
2026-03-08 19:24:39 +13:00
nick 9a4dba0a50 feat: scene editor
CI / build-and-test (push) Successful in 2m2s
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Failing after 24s
2026-03-08 17:52:44 +13:00
nick b95365f6a6 chore: fixing wiki stuff
CI / build-and-test (push) Failing after 12m35s
2026-03-08 16:41:13 +13:00
Simian CI 271e2ea7b7 Update wiki from docs (CI) 2026-03-08 03:28:05 +00:00
nick d7f9019e61 chore: fix for headless raylib
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Successful in 31s
CI / build-and-test (push) Failing after 11m38s
2026-03-08 16:26:34 +13:00
nick 5367b31f4e chore: fixing CI
CI / build-and-test (push) Failing after 2m9s
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Failing after 23s
2026-03-08 16:04:38 +13:00
nick e6a53baa1a chore: added tests and tidied docs
CI / build-and-test (push) Failing after 1m57s
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Failing after 13s
2026-03-08 15:52:14 +13:00
nick 15e5d580da feat: Scenegraph
CI / build-and-test (push) Successful in 2m4s
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Failing after 8s
2026-03-08 15:37:25 +13:00
nick 50ec04aa12 chore: minor change to commit docs
CI / build-and-test (push) Successful in 2m2s
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Failing after 14s
2026-03-08 15:03:16 +13:00
nick 95deb65106 chore: trying CI workflow
CI / build-and-test (push) Successful in 2m8s
2026-03-08 15:00:19 +13:00
nick 9f8686345c fix: move CI piplein to correct folder
CI / build-and-test (push) Successful in 1m58s
Sync Docs to Gitea Wiki / Sync docs to Gitea wiki (push) Failing after 5s
2026-03-08 14:50:43 +13:00
nick 33226dfae1 feat: Wiki pages and sync
CI / build-and-test (push) Has been cancelled
2026-03-08 14:48:55 +13:00
nick d0c28dc663 feat: input manager, refactor compliation for efficiency
CI / build-and-test (push) Successful in 2m2s
2026-03-08 12:14:50 +13:00
nick a37910de25 chore: remove g_ prfix noise
CI / build-and-test (push) Successful in 2m0s
2026-03-08 11:09:09 +13:00
nick 4116678556 fix: shaders work again now
CI / build-and-test (push) Has been cancelled
2026-03-08 11:08:12 +13:00
nick 0cce80e956 chore: code code tidy and style tweaks
CI / build-and-test (push) Successful in 2m7s
2026-03-08 10:37:38 +13:00
nick 05b0773189 fix: Windows unsigned int type compatibility
CI / build-and-test (push) Successful in 2m32s
2026-03-06 18:03:48 +13:00
nick 283d057507 chore(wip): attempting to get shaders working again
CI / build-and-test (push) Successful in 2m5s
2026-03-06 14:51:54 +13:00
nick 82886349ae feat: refactor to move more elements to the scripting layer
CI / build-and-test (push) Successful in 2m13s
2026-03-06 14:32:25 +13:00
nick 670f65b37b fix: rendering on linux and toon shader 2026-03-06 11:24:28 +13:00
nick 766f7ecd7a fix: include all tests
CI / build-and-test (push) Successful in 2m24s
2026-03-06 09:45:29 +13:00
nick aa4c7cf58a feat: entt scripting support
CI / build-and-test (push) Failing after 11m9s
2026-03-05 23:22:09 +13:00
82 changed files with 11377 additions and 436 deletions
+54
View File
@@ -0,0 +1,54 @@
name: Sync Docs to Gitea Wiki
on:
push:
paths:
- 'docs/**'
jobs:
sync:
name: Sync docs to Gitea wiki
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Start SSH agent
uses: webfactory/ssh-agent@v0.9.0
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"
# 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) [skip ci]"
git push origin main
echo "✅ Synced to simian.wiki main"
fi
+3
View File
@@ -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
+64 -20
View File
@@ -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)
# -------------------------
@@ -42,11 +47,6 @@ else()
set(HAVE_RLIMGUI FALSE)
endif()
# -------------------------
# Tests
# -------------------------
enable_testing()
add_subdirectory(tests)
# -------------------------
# 3️⃣ scriptstdstring add-on
# -------------------------
add_library(scriptstdstring STATIC
@@ -69,26 +69,50 @@ target_include_directories(scriptbuilder
)
# -------------------------
# 4️⃣ Main executable
# 3️⃣.2️⃣ scriptarray add-on
# -------------------------
add_executable(simian
main.cpp
add_library(scriptarray STATIC
external/angelscript/sdk/add_on/scriptarray/scriptarray.cpp
)
target_include_directories(scriptarray
PUBLIC external/angelscript/sdk/angelscript/include
)
# -------------------------
# 4️⃣ Simian Core Library (SHARED CODE)
# -------------------------
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
src/scripting/LogBindings.cpp
src/scripting/DrawBindings.cpp
src/scripting/ImageBindings.cpp
src/scripting/TextureBindings.cpp
src/scripting/MathBindings.cpp
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
)
target_include_directories(simian PUBLIC
target_include_directories(simian_core PUBLIC
include
include/scripting
include/gui
@@ -97,29 +121,41 @@ target_include_directories(simian PUBLIC
external/angelscript/sdk/angelscript/include
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 PRIVATE
target_link_libraries(simian_core PUBLIC
raylib
angelscript
PhysFS::PhysFS-static
scriptstdstring
scriptbuilder
scriptarray
imgui
)
if(HAVE_RLIMGUI)
target_include_directories(simian PUBLIC ${RLIMGUI_ROOT}/include)
target_link_libraries(simian PRIVATE rlImGui)
target_include_directories(simian_core PUBLIC ${RLIMGUI_ROOT}/include)
target_link_libraries(simian_core PUBLIC rlImGui)
# Expose HAVE_RLIMGUI to the C++ compiler so source can guard ImGui calls
target_compile_definitions(simian PRIVATE HAVE_RLIMGUI=1)
message(STATUS "Linking rlImGui into simian")
target_compile_definitions(simian_core PUBLIC HAVE_RLIMGUI=1)
message(STATUS "Linking rlImGui into simian_core")
else()
message(STATUS "rlImGui not found - building without rlImGui integration")
endif()
# -------------------------
# 5️⃣ Platform-specific linking
# 5️⃣ Main executable (just main.cpp)
# -------------------------
add_executable(simian main.cpp)
target_link_libraries(simian PRIVATE simian_core)
# -------------------------
# 6️⃣ Platform-specific linking
# -------------------------
if(WIN32)
target_link_libraries(simian PRIVATE winmm gdi32)
@@ -128,11 +164,19 @@ elseif(UNIX)
endif()
# -------------------------
# 6️⃣ Optional: Compile flags
# 7️⃣ Optional: Compile flags
# -------------------------
if(MSVC)
target_compile_options(simian PRIVATE /W4 /EHsc)
target_compile_options(simian_core PRIVATE /W4 /EHsc)
else()
target_compile_options(simian PRIVATE -Wall -Wextra -pedantic)
target_compile_options(simian_core PRIVATE -Wall -Wextra -pedantic)
if(TARGET raylib)
target_compile_options(raylib PRIVATE -Wno-missing-field-initializers)
endif()
endif()
# -------------------------
# -------------------------
# 8️⃣ Tests
# -------------------------
enable_testing()
add_subdirectory(tests)
+10
View File
@@ -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/`:
+15
View File
@@ -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
View File
@@ -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
+11
View File
@@ -0,0 +1,11 @@
# Simian Documentation
This folder contains project documentation that is synced to the project's Gitea wiki.
Edit markdown files here and push to the repository; CI will mirror them to the wiki.
## 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
+124
View File
@@ -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.
+62
View File
@@ -0,0 +1,62 @@
# Scripting
This page is a starting point for scripting documentation.
## Overview
- 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, scenegraph and input actions.
## How to document APIs
Add Markdown pages in the `docs/` folder. Use relative links and include code fences for examples.
+100
View File
@@ -0,0 +1,100 @@
# Usage
This page documents how to build, run and maintain the Simian project and how to publish documentation to the Gitea wiki.
## Prerequisites
- Git
- CMake (>= 3.16; 3.25 recommended)
- A C++ toolchain (Visual Studio on Windows, GCC/Clang on Linux/macOS)
- Optional: SSH access to your Gitea instance (for wiki sync)
If you just cloned the repository, initialise submodules:
```bash
git submodule update --init --recursive
```
## Build (Windows / MSVC)
```powershell
mkdir build
cd build
cmake -S .. -B . -G "Visual Studio 17 2022" -A x64
cmake --build . --config Release
# Run
.\Release\simian.exe
```
## Build (Linux / macOS)
```bash
mkdir build
cd build
cmake ..
cmake --build . --config Release
./simian
```
## Run tests
From the project root (after configuring the build):
```bash
cmake --build build --config Release --target RUN_TESTS
# or use CTest
ctest --test-dir build --output-on-failure
```
## Scripting / Hot-reload
- Scripts live in the `scripts/` directory (e.g. `scripts/game.as`).
- The engine watches the scripts directory and will hot-reload scripts when changed.
- Predefined API is declared in `scripts/as.predefined`.
## Documentation & Wiki
Docs source lives in the `docs/` folder. The repository contains a CI workflow and a local script to mirror `docs/` to the Gitea wiki.
Manual sync (local):
```bash
bash scripts/sync_wiki.sh
```
CI sync (automated):
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:
```bash
# Generate a keypair (do not commit the private key)
ssh-keygen -t ed25519 -C "simian-wiki-ci" -f simian_wiki_ci -N ""
# Add simian_wiki_ci.pub as a deploy key in your Gitea repo: Settings → Deploy Keys
# Enable write/push access for that key.
# Copy the private key (simian_wiki_ci) into your CI provider as secret WIKI_SSH_KEY.
```
Notes:
- The workflow clones the wiki remote `git@gitea.appstack.me:nick/simian.wiki.git` and pushes updates.
- If your Gitea host/paths differ, update `.github/workflows/sync-docs-to-wiki.yml` or the `scripts/sync_wiki.sh` script accordingly.
## Adding / Updating Documentation
1. Add or edit Markdown files under `docs/`.
2. Put images in `docs/images/` and reference them with relative paths, e.g. `![Alt](images/diagram.png)`.
3. Commit and push to the repo; CI will sync changes automatically (once secrets/deploy-key are configured).
## 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 `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.
File diff suppressed because it is too large Load Diff
Vendored Submodule
+1
Submodule external/physfs added at 49cfd5fdd4
+61 -19
View File
@@ -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=1007,637
Size=253,63
Pos=1035,637
Size=225,63
Collapsed=0
[Window][##TOAST1]
Pos=1064,564
Size=196,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=1048,442
Size=212,57
Pos=1675,741
Size=225,63
Collapsed=0
[Window][##TOAST4]
Pos=1048,375
Size=212,57
Pos=1068,345
Size=192,63
Collapsed=0
[Window][Scene (0 entities)]
@@ -70,8 +70,50 @@ Pos=60,60
Size=71,108
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][##TOAST5]
Pos=1071,272
Size=189,63
Collapsed=0
[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
+69 -4
View File
@@ -3,6 +3,15 @@
#include "HotReload.h"
#include "gui/GuiManager.h"
#include "raylib.h"
#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:
@@ -16,25 +25,81 @@ 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;
RenderTexture2D renderTexture; // renderTexture for Raylib rendering
// ECS
entt::registry registry;
// Resource managers
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();
void SpawnCube();
bool RunAutoloadScript(bool abortOnFailure);
};
+144
View File
@@ -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;
};
+182
View File
@@ -0,0 +1,182 @@
#pragma once
#include "raylib.h"
#include "raymath.h"
#include "entt.hpp"
#include <string>
#include <unordered_map>
// ECSTransform component - position, rotation, scale, and cached local matrix
struct ECSTransform {
Vector3 position;
Vector3 rotation; // Euler rotation (radians)
Vector3 scale;
Matrix localMatrix;
ECSTransform()
: position{0.0f, 0.0f, 0.0f}
, rotation{0.0f, 0.0f, 0.0f}
, scale{1.0f, 1.0f, 1.0f}
, localMatrix(MatrixIdentity()) {}
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
struct Velocity {
Vector3 linear; // Linear velocity (units per second)
float angular; // Angular velocity (radians per second)
Velocity()
: linear{0.0f, 0.0f, 0.0f}
, angular(0.0f) {}
Velocity(Vector3 lin, float ang = 0.0f)
: linear(lin), angular(ang) {}
};
// 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)
ModelRenderer()
: modelId(0)
, color(WHITE)
, outlineSize(0.0f)
, shaderId(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;
Tag() : name("") {}
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;
Vector3 target;
Vector3 up;
float fovy;
int projection; // 0 = CAMERA_PERSPECTIVE, 1 = CAMERA_ORTHOGRAPHIC
Camera3DComponent()
: position{0.0f, 10.0f, 10.0f}
, target{0.0f, 0.0f, 0.0f}
, up{0.0f, 1.0f, 0.0f}
, fovy(45.0f)
, projection(0) {}
};
// Light component - directional light
struct LightComponent {
Vector3 direction;
Color color;
float intensity;
LightComponent()
: direction{0.5f, 0.7f, 0.3f}
, color(WHITE)
, intensity(1.0f) {}
};
// Material component - references shader and material managers
struct MaterialComponent {
unsigned int materialId; // ID in MaterialManager
MaterialComponent() : materialId(0) {}
MaterialComponent(unsigned int id) : materialId(id) {}
};
// RenderPass component - controls rendering order and properties
struct RenderPassComponent {
int order; // Render order (lower = earlier)
bool depthTest; // Enable depth testing
bool depthWrite; // Enable depth writing
unsigned int shaderId; // Override shader for this pass (0 = use material shader)
RenderPassComponent()
: order(0)
, depthTest(true)
, depthWrite(true)
, shaderId(0) {}
};
-1
View File
@@ -1,7 +1,6 @@
#pragma once
#include <string>
#include <chrono>
#include <filesystem>
class HotReload {
public:
+155
View File
@@ -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"
+82
View File
@@ -0,0 +1,82 @@
#pragma once
#include "raylib.h"
#include <string>
#include <vector>
#include <unordered_map>
// Input types supported by the action system
enum class InputType {
Keyboard,
MouseButton,
GamepadButton,
GamepadAxis
};
// Represents a single input binding (e.g., KEY_SPACE, MOUSE_BUTTON_LEFT)
struct InputBinding {
InputType type;
int code; // Key code, mouse button, or gamepad button
int gamepadId; // For gamepad inputs (0-3)
float axisThreshold; // For axis inputs (e.g., trigger > 0.5)
bool negative; // For axis inputs (e.g., left stick left = negative)
InputBinding()
: type(InputType::Keyboard)
, code(0)
, gamepadId(0)
, axisThreshold(0.5f)
, negative(false) {}
InputBinding(InputType t, int c, int gamepad = 0)
: type(t)
, code(c)
, gamepadId(gamepad)
, axisThreshold(0.5f)
, negative(false) {}
};
// Manages input actions with remappable bindings
class InputManager {
public:
InputManager();
~InputManager();
// Action management
void RegisterAction(const std::string& actionName);
void UnregisterAction(const std::string& actionName);
bool HasAction(const std::string& actionName) const;
// Binding management
void BindKeyboard(const std::string& actionName, int keyCode);
void BindMouseButton(const std::string& actionName, int mouseButton);
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;
bool IsActionDown(const std::string& actionName) const;
bool IsActionReleased(const std::string& actionName) const;
float GetActionStrength(const std::string& actionName) const; // For analog inputs
// Configuration
void LoadFromFile(const std::string& filepath);
void SaveToFile(const std::string& filepath) const;
void SetupDefaultBindings(); // Define default controls
// Get list of all actions
std::vector<std::string> GetAllActions() const;
// Get bindings for an action
const std::vector<InputBinding>& GetBindings(const std::string& actionName) const;
private:
std::unordered_map<std::string, std::vector<InputBinding>> actions;
// Helper to check if a single binding is active
bool IsBindingPressed(const InputBinding& binding) const;
bool IsBindingDown(const InputBinding& binding) const;
bool IsBindingReleased(const InputBinding& binding) const;
float GetBindingStrength(const InputBinding& binding) const;
};
+44
View File
@@ -0,0 +1,44 @@
#pragma once
#include "raylib.h"
#include <unordered_map>
#include <string>
struct MaterialData
{
unsigned int shaderId;
Color albedo;
MaterialData() : shaderId(0), albedo(WHITE) {}
};
class MaterialManager
{
public:
MaterialManager();
~MaterialManager();
// Create a material with a shader ID
// Returns a unique ID for the material
unsigned int Create(unsigned int shaderId, Color albedo = WHITE);
// Remove a material by ID
void Remove(unsigned int id);
// Get material data by ID (returns nullptr if not found)
MaterialData* Get(unsigned int id);
// Update material shader
void SetShader(unsigned int id, unsigned int shaderId);
// Update material albedo
void SetAlbedo(unsigned int id, Color albedo);
// Clear all materials
void Clear();
private:
std::unordered_map<unsigned int, MaterialData> materials;
unsigned int nextId;
bool cleared;
};
+64
View File
@@ -0,0 +1,64 @@
#pragma once
#include "raylib.h"
#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:
ModelManager();
~ModelManager();
// Load a model from a file
// Returns a unique ID for the model, or 0 on failure
unsigned int Load(const std::string& path);
// Load a model from a mesh
// 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;
};
+36
View File
@@ -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
+16
View File
@@ -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);
+28
View File
@@ -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);
+39
View File
@@ -0,0 +1,39 @@
#pragma once
#include "raylib.h"
#include <unordered_map>
#include <string>
struct ShaderSource {
std::string vsPath;
std::string fsPath;
};
class ShaderManager
{
public:
ShaderManager();
~ShaderManager();
// Load a shader from vertex and fragment shader files
// Returns a unique ID for the shader, or 0 on failure
unsigned int Load(const std::string& vsPath, const std::string& fsPath);
// Unload a shader by ID
void Unload(unsigned int id);
// 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;
};
+38
View File
@@ -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;
};
+126
View File
@@ -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;
};
+3 -2
View File
@@ -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
+8
View File
@@ -0,0 +1,8 @@
#pragma once
#include <angelscript.h>
#include <entt.hpp>
// Set the global registry that scripts will interact with
void SetGlobalRegistry(entt::registry* reg);
void RegisterECSBindings(asIScriptEngine *engine);
+7
View File
@@ -0,0 +1,7 @@
#pragma once
class asIScriptEngine;
class InputManager;
void SetGlobalInputManager(InputManager* inputManager);
void RegisterInputBindings(asIScriptEngine* engine);
+5
View File
@@ -0,0 +1,5 @@
#pragma once
class asIScriptEngine;
void RegisterPhysFSBindings(asIScriptEngine* engine);
+12
View File
@@ -0,0 +1,12 @@
#pragma once
class asIScriptEngine;
class ShaderManager;
class ModelManager;
class MaterialManager;
class TextureManager;
class AssetManager;
void SetResourceManagers(ShaderManager* shaderMgr, ModelManager* modelMgr, MaterialManager* materialMgr, TextureManager* textureMgr);
void SetAssetManager(AssetManager* assetMgr);
void RegisterResourceBindings(asIScriptEngine *engine);
+5
View File
@@ -0,0 +1,5 @@
#pragma once
class asIScriptEngine;
void RegisterSceneBindings(asIScriptEngine* engine);
+14 -2
View File
@@ -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,15 +27,24 @@ public:
void RestoreScriptState(const std::string &state);
asIScriptEngine* GetEngine() const { return engine; }
asIScriptModule* GetCurrentModule() const { return currentModule; }
asIScriptFunction* GetUpdateFunction() const { return updateFunc; }
asIScriptFunction* GetDrawFunction() const { return drawFunc; }
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;
asIScriptFunction* drawFunc;
asIScriptFunction* initFunc;
asIScriptFunction* shutdownFunc;
asIScriptModule* currentModule;
+65
View File
@@ -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"
+49
View File
@@ -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"
+233 -9
View File
@@ -1,6 +1,18 @@
typedef void string;
string format(const string&in fmt, const ?&in ...);
// Array template (provided by scriptarray add-on)
external shared class array<T> {
uint length() const;
void resize(uint);
void insertAt(uint, const T&in);
void insertLast(const T&in);
void removeAt(uint);
void removeLast();
T& opIndex(uint);
const T& opIndex(uint) const;
}
// Logging functions
void Print(const string&in);
void Log(int level, const string&in);
@@ -19,14 +31,154 @@ namespace Toast {
void Success(const string&in);
}
// Drawing functions
namespace Draw {
void Pixel(int x, int y, int color);
void Line(int startX, int startY, int endX, int endY, int color);
void Circle(int centerX, int centerY, float radius, int color);
void Text(const string&in text, int x, int y, int fontSize, int color);
void Rectangle(int x, int y, int width, int height, int color);
void FPS(int x, int y);
// ECS functions
namespace ECS {
// Entity management
uint CreateEntity();
void DestroyEntity(uint entity);
bool IsValid(uint entity);
// Transform component
void AddTransform(uint entity, float x, float y, float z);
void SetPosition(uint entity, float x, float y, float z);
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);
// Velocity component
void AddVelocity(uint entity, float vx, float vy, float vz, float angular = 0.0f);
void SetVelocity(uint entity, float vx, float vy, float vz);
void SetAngularVelocity(uint entity, float angular);
bool HasVelocity(uint entity);
void RemoveVelocity(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);
string GetTag(uint entity);
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);
void SetCameraPosition(uint entity, float x, float y, float z);
void SetCameraTarget(uint entity, float x, float y, float z);
void SetCameraFovy(uint entity, float fovy);
bool HasCamera3D(uint entity);
void RemoveCamera3D(uint entity);
// Light component
void AddLight(uint entity, float dx, float dy, float dz, float intensity = 1.0f);
void SetLightDirection(uint entity, float x, float y, float z);
void SetLightIntensity(uint entity, float intensity);
void SetLightColor(uint entity, uint color);
bool HasLight(uint entity);
void RemoveLight(uint entity);
// Material component
void AddMaterial(uint entity, uint materialId);
void SetMaterialId(uint entity, uint materialId);
uint GetMaterialId(uint entity);
bool HasMaterial(uint entity);
void RemoveMaterial(uint entity);
// RenderPass component
void AddRenderPass(uint entity, int order, bool depthTest, bool depthWrite, uint shaderId);
void SetRenderPassOrder(uint entity, int order);
void SetRenderPassShader(uint entity, uint shaderId);
bool HasRenderPass(uint entity);
void RemoveRenderPass(uint entity);
}
// Shader resource management
namespace Shader {
uint Load(const string &in vsPath, const string &in fsPath);
void Unload(uint id);
}
// Model resource management
namespace Model {
uint Load(const string &in path);
uint LoadCube(float width, float height, float length);
uint LoadSphere(float radius, int rings, int slices);
uint LoadPlane(float width, float length, int resX, int resZ);
void Unload(uint id);
}
// Material resource management
namespace Material {
uint Create(uint shaderId, uint8 r, uint8 g, uint8 b, uint8 a);
void Remove(uint id);
void SetShader(uint id, uint shaderId);
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 {
@@ -68,4 +220,76 @@ namespace Math {
float Ceil(float value);
float Modf(float x, float y);
float Abs(float value);
}
}
// Raylib input and timing functions
float GetTime();
bool IsKeyPressed(int key);
// Input Action System
bool IsActionPressed(const string &in actionName);
bool IsActionDown(const string &in actionName);
bool IsActionReleased(const string &in actionName);
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;
const int ESCAPE = 256;
const int ENTER = 257;
const int TAB = 258;
const int BACKSPACE = 259;
const int RIGHT = 262;
const int LEFT = 263;
const int DOWN = 264;
const int UP = 265;
const int W = 87;
const int A = 65;
const int S = 83;
const int D = 68;
const int E = 69;
const int LEFT_CONTROL = 341;
}
namespace MouseButton {
const int LEFT = 0;
const int RIGHT = 1;
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;
}
+40
View File
@@ -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.
}
+49
View File
@@ -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
}
}
+52
View File
@@ -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");
}
}
}
+101
View File
@@ -0,0 +1,101 @@
// Example script showing how to use the new script-driven rendering system
// Global variables to track our resources
uint toonShader = 0;
uint outlineShader = 0;
uint cubeModel = 0;
uint sphereModel = 0;
uint redMaterial = 0;
uint blueMaterial = 0;
// Entities
uint camera = 0;
uint light = 0;
uint cube = 0;
uint sphere = 0;
void Init() {
Toast::Info("Initializing script-driven rendering...");
// === 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 Materials ===
// Red material with toon shader
redMaterial = Material::Create(toonShader, 255, 100, 100, 255);
// Blue material with toon shader
blueMaterial = Material::Create(toonShader, 100, 100, 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 Cube with Outline ===
cube = ECS::CreateEntity();
ECS::AddTransform(cube, -1.5, 0.5, 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
// Add outline render pass (order 0 = first)
ECS::AddRenderPass(cube, 0, false, true, outlineShader); // order=0, no depth test
// === Create Sphere ===
sphere = ECS::CreateEntity();
ECS::AddTransform(sphere, 1.5, 0.5, 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
// Add main render pass (order 1 = second, after outlines)
ECS::AddRenderPass(sphere, 1, true, true, 0); // order=1, with depth test
Toast::Success("Script-driven rendering initialized!");
}
bool lightToggle = false;
void Update(float dt) {
// Camera orbital movement
if (ECS::HasCamera3D(camera)) {
float angle = GetTime() * 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);
}
// Example: Toggle light direction with space
if (IsKeyPressed(Key::SPACE)) {
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!");
}
}
}
+7 -11
View File
@@ -1,11 +1,7 @@
#include "update.as"
void Draw() {
Draw::FPS(10, 10);
Draw::Text(format("Hello from AngelScript - at x {} and y {}", int(x), int(y)), int(x), int(y), 20, 0xFF0000FF);
Draw::Rectangle(300, 200, 200, 100, 0x00FF00FF);
Draw::Pixel(250, 200, 0x0000FFFF);
Draw::Line(int(x), int(y), 400, 300, 0xFFFF00FF);
Draw::Circle(600, 400, 50.0f, 0xFF00FFFF);
Texture::Draw(tex, int(ix), int(iy), 0xFFFFFFFF);
}
// Empty scene script to avoid auto-loading demo content.
class SceneScript {
void Init() {}
void Shutdown() {}
void Update(float dt) {
}
}
+20
View File
@@ -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)");
}
}
+27
View File
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
set -euo pipefail
# Script to sync local docs/ to the Gitea wiki for this repo.
# Requires SSH access to the wiki (deploy key or account key).
WIKI_REPO="git@gitea.appstack.me:nick/simian.wiki.git"
TMPDIR="$(mktemp -d)"
echo "Cloning wiki to $TMPDIR"
git clone "$WIKI_REPO" "$TMPDIR"
echo "Syncing docs/ -> wiki/"
rsync -av --delete docs/ "$TMPDIR"/
pushd "$TMPDIR" > /dev/null
git add -A
if git diff --staged --quiet; then
echo "No changes to wiki"
else
git commit -m "Update wiki from docs"
git push origin HEAD
fi
popd > /dev/null
rm -rf "$TMPDIR"
echo "Done."
-35
View File
@@ -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));
}
}
+4 -32
View File
@@ -1,37 +1,9 @@
#version 330
uniform sampler2D texture0;
uniform vec2 textureSize;
uniform int outlineSize = 2;
out vec4 fragColor;
in vec2 fragTexCoord;
out vec4 finalColor;
uniform vec4 outlineColor; // Color of the outline
void main() {
vec2 texel = 1.0 / textureSize;
float center = texture(texture0, fragTexCoord).r;
if (center < 0.5) {
discard; // Transparent, show scene underneath
return;
}
// Edge detection
bool edge = false;
for (int y = -outlineSize; y <= outlineSize; y++) {
for (int x = -outlineSize; x <= outlineSize; x++) {
vec2 offset = vec2(float(x), float(y)) * texel;
if (texture(texture0, fragTexCoord + offset).r < 0.5) {
edge = true;
break;
}
}
if (edge) break;
}
if (edge) {
finalColor = vec4(0, 0, 0, 1); // Black outline
} else {
discard;
}
}
fragColor = outlineColor;
}
+21
View File
@@ -0,0 +1,21 @@
#version 330
layout(location = 0) in vec3 vertexPosition;
layout(location = 2) in vec3 vertexNormal;
uniform mat4 mvp;
uniform mat4 matModel;
uniform mat4 matNormal;
uniform float outlineThickness;
void main() {
// Transform normal to world space
vec3 worldNormal = normalize(vec3(matNormal * vec4(vertexNormal, 0.0)));
// Expand vertices along normals in world space
vec3 worldPos = vec3(matModel * vec4(vertexPosition, 1.0));
vec3 expandedPos = worldPos + worldNormal * outlineThickness;
// Transform to clip space
gl_Position = mvp * vec4(vertexPosition + vertexNormal * outlineThickness, 1.0);
}
+9 -12
View File
@@ -3,23 +3,20 @@
in vec3 fragNormal;
in vec3 fragPosition;
uniform vec3 lightDir;
uniform float bandCount;
uniform vec4 albedo;
uniform vec3 lightDir;
out vec4 fragColor;
void main() {
// Normalize the normal and light direction
vec3 normal = normalize(fragNormal);
// Use uniform light direction, with fallback
vec3 light = normalize(lightDir);
// Calculate the dot product for lighting
float bandCount = 3.0;
vec3 normal = normalize(fragNormal);
float intensity = max(dot(normal, light), 0.0);
// Quantize the intensity for toon shading
intensity = floor(intensity * bandCount) / bandCount;
// Set the fragment color
//fragColor = vec4(vec3(intensity), 1.0);
fragColor = vec4(albedo.rgb , albedo.a);
intensity = max(intensity, 0.3); // Minimum visibility
fragColor = vec4(albedo.rgb * intensity, albedo.a);
}
+17 -7
View File
@@ -1,17 +1,27 @@
#version 330
// Raylib's default attribute locations
layout(location = 0) in vec3 vertexPosition;
layout(location = 1) in vec3 vertexNormal;
layout(location = 1) in vec2 vertexTexCoord;
layout(location = 2) in vec3 vertexNormal;
layout(location = 3) in vec4 vertexColor;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
// Raylib's built-in uniforms (automatically provided)
uniform mat4 mvp; // Model-View-Projection matrix
uniform mat4 matModel; // Model matrix
uniform mat4 matNormal; // Normal matrix for transforming normals
out vec3 fragNormal;
out vec3 fragPosition;
void main() {
fragPosition = vec3(model * vec4(vertexPosition, 1.0));
fragNormal = mat3(transpose(inverse(model))) * vertexNormal; // Transform normals
gl_Position = projection * view * vec4(fragPosition, 1.0);
// Transform position
vec4 worldPos = matModel * vec4(vertexPosition, 1.0);
fragPosition = worldPos.xyz;
// Transform normal properly
fragNormal = normalize(vec3(matNormal * vec4(vertexNormal, 0.0)));
// Final position
gl_Position = mvp * vec4(vertexPosition, 1.0);
}
+807 -193
View File
File diff suppressed because it is too large Load Diff
+1083
View File
File diff suppressed because it is too large Load Diff
+24 -29
View File
@@ -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;
}
+245
View File
@@ -0,0 +1,245 @@
#include "InputManager.h"
#include "log/log.h"
#include <fstream>
#include <sstream>
#include <cmath>
InputManager::InputManager() {
}
InputManager::~InputManager() {
}
void InputManager::RegisterAction(const std::string& actionName) {
if (actions.find(actionName) == actions.end()) {
actions[actionName] = std::vector<InputBinding>();
log_debug("Registered input action: %s", actionName.c_str());
}
}
void InputManager::UnregisterAction(const std::string& actionName) {
actions.erase(actionName);
}
bool InputManager::HasAction(const std::string& actionName) const {
return actions.find(actionName) != actions.end();
}
void InputManager::BindKeyboard(const std::string& actionName, int keyCode) {
RegisterAction(actionName);
actions[actionName].push_back(InputBinding(InputType::Keyboard, keyCode));
log_debug("Bound key %d to action: %s", keyCode, actionName.c_str());
}
void InputManager::BindMouseButton(const std::string& actionName, int mouseButton) {
RegisterAction(actionName);
actions[actionName].push_back(InputBinding(InputType::MouseButton, mouseButton));
}
void InputManager::BindGamepadButton(const std::string& actionName, int gamepadId, int button) {
RegisterAction(actionName);
actions[actionName].push_back(InputBinding(InputType::GamepadButton, button, gamepadId));
}
void InputManager::BindGamepadAxis(const std::string& actionName, int gamepadId, int axis, float threshold, bool negative) {
RegisterAction(actionName);
InputBinding binding(InputType::GamepadAxis, axis, gamepadId);
binding.axisThreshold = threshold;
binding.negative = negative;
actions[actionName].push_back(binding);
}
void InputManager::ClearBindings(const std::string& actionName) {
if (HasAction(actionName)) {
actions[actionName].clear();
}
}
void InputManager::ClearAllActions() {
actions.clear();
}
bool InputManager::IsActionPressed(const std::string& actionName) const {
auto it = actions.find(actionName);
if (it == actions.end()) return false;
for (const auto& binding : it->second) {
if (IsBindingPressed(binding)) {
return true;
}
}
return false;
}
bool InputManager::IsActionDown(const std::string& actionName) const {
auto it = actions.find(actionName);
if (it == actions.end()) return false;
for (const auto& binding : it->second) {
if (IsBindingDown(binding)) {
return true;
}
}
return false;
}
bool InputManager::IsActionReleased(const std::string& actionName) const {
auto it = actions.find(actionName);
if (it == actions.end()) return false;
for (const auto& binding : it->second) {
if (IsBindingReleased(binding)) {
return true;
}
}
return false;
}
float InputManager::GetActionStrength(const std::string& actionName) const {
auto it = actions.find(actionName);
if (it == actions.end()) return 0.0f;
float maxStrength = 0.0f;
for (const auto& binding : it->second) {
float strength = GetBindingStrength(binding);
if (std::abs(strength) > std::abs(maxStrength)) {
maxStrength = strength;
}
}
return maxStrength;
}
bool InputManager::IsBindingPressed(const InputBinding& binding) const {
switch (binding.type) {
case InputType::Keyboard:
return IsKeyPressed(binding.code);
case InputType::MouseButton:
return IsMouseButtonPressed(binding.code);
case InputType::GamepadButton:
if (IsGamepadAvailable(binding.gamepadId)) {
return IsGamepadButtonPressed(binding.gamepadId, binding.code);
}
return false;
case InputType::GamepadAxis: {
// Axis "pressed" is harder to detect - simplified to just checking threshold
if (IsGamepadAvailable(binding.gamepadId)) {
float value = GetGamepadAxisMovement(binding.gamepadId, binding.code);
return binding.negative ? value < -binding.axisThreshold : value > binding.axisThreshold;
}
return false;
}
}
return false;
}
bool InputManager::IsBindingDown(const InputBinding& binding) const {
switch (binding.type) {
case InputType::Keyboard:
return IsKeyDown(binding.code);
case InputType::MouseButton:
return IsMouseButtonDown(binding.code);
case InputType::GamepadButton:
if (IsGamepadAvailable(binding.gamepadId)) {
return IsGamepadButtonDown(binding.gamepadId, binding.code);
}
return false;
case InputType::GamepadAxis:
if (IsGamepadAvailable(binding.gamepadId)) {
float value = GetGamepadAxisMovement(binding.gamepadId, binding.code);
return binding.negative ? value < -binding.axisThreshold : value > binding.axisThreshold;
}
return false;
}
return false;
}
bool InputManager::IsBindingReleased(const InputBinding& binding) const {
switch (binding.type) {
case InputType::Keyboard:
return IsKeyReleased(binding.code);
case InputType::MouseButton:
return IsMouseButtonReleased(binding.code);
case InputType::GamepadButton:
if (IsGamepadAvailable(binding.gamepadId)) {
return IsGamepadButtonReleased(binding.gamepadId, binding.code);
}
return false;
case InputType::GamepadAxis:
return false; // Axes don't have a clear "released" state
}
return false;
}
float InputManager::GetBindingStrength(const InputBinding& binding) const {
switch (binding.type) {
case InputType::Keyboard:
return IsKeyDown(binding.code) ? 1.0f : 0.0f;
case InputType::MouseButton:
return IsMouseButtonDown(binding.code) ? 1.0f : 0.0f;
case InputType::GamepadButton:
if (IsGamepadAvailable(binding.gamepadId)) {
return IsGamepadButtonDown(binding.gamepadId, binding.code) ? 1.0f : 0.0f;
}
return 0.0f;
case InputType::GamepadAxis:
if (IsGamepadAvailable(binding.gamepadId)) {
float value = GetGamepadAxisMovement(binding.gamepadId, binding.code);
return binding.negative ? -value : value;
}
return 0.0f;
}
return 0.0f;
}
void InputManager::SetupDefaultBindings() {
// Movement
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
BindKeyboard("jump", KEY_SPACE);
BindKeyboard("interact", KEY_E);
BindKeyboard("attack", KEY_LEFT_CONTROL);
BindMouseButton("attack", MOUSE_BUTTON_LEFT);
// Gamepad (if available)
BindGamepadButton("jump", 0, GAMEPAD_BUTTON_RIGHT_FACE_DOWN);
BindGamepadButton("interact", 0, GAMEPAD_BUTTON_RIGHT_FACE_RIGHT);
BindGamepadAxis("move_left", 0, GAMEPAD_AXIS_LEFT_X, 0.3f, true);
BindGamepadAxis("move_right", 0, GAMEPAD_AXIS_LEFT_X, 0.3f, false);
log_info("Input manager: Default bindings configured");
}
std::vector<std::string> InputManager::GetAllActions() const {
std::vector<std::string> result;
for (const auto& pair : actions) {
result.push_back(pair.first);
}
return result;
}
const std::vector<InputBinding>& InputManager::GetBindings(const std::string& actionName) const {
static std::vector<InputBinding> empty;
auto it = actions.find(actionName);
if (it != actions.end()) {
return it->second;
}
return empty;
}
void InputManager::LoadFromFile(const std::string& filepath) {
// TODO: Implement config file loading (JSON or simple text format)
log_info("Input config loading not yet implemented: %s", filepath.c_str());
}
void InputManager::SaveToFile(const std::string& filepath) const {
// TODO: Implement config file saving
log_info("Input config saving not yet implemented: %s", filepath.c_str());
}
+76
View File
@@ -0,0 +1,76 @@
#include "MaterialManager.h"
#include "log/log.h"
MaterialManager::MaterialManager() : nextId(1), cleared(false)
{
}
MaterialManager::~MaterialManager()
{
// Only clear if not already done
if (!cleared) {
materials.clear();
}
}
unsigned int MaterialManager::Create(unsigned int shaderId, Color albedo)
{
unsigned int id = nextId++;
MaterialData data;
data.shaderId = shaderId;
data.albedo = albedo;
materials[id] = data;
log_info("Created material id=%u with shader=%u", id, shaderId);
return id;
}
void MaterialManager::Remove(unsigned int id)
{
auto it = materials.find(id);
if (it != materials.end())
{
materials.erase(it);
log_info("Removed material id=%u", id);
}
}
MaterialData* MaterialManager::Get(unsigned int id)
{
auto it = materials.find(id);
if (it != materials.end())
{
return &it->second;
}
return nullptr;
}
void MaterialManager::SetShader(unsigned int id, unsigned int shaderId)
{
auto it = materials.find(id);
if (it != materials.end())
{
it->second.shaderId = shaderId;
log_info("Updated material id=%u shader to %u", id, shaderId);
}
}
void MaterialManager::SetAlbedo(unsigned int id, Color albedo)
{
auto it = materials.find(id);
if (it != materials.end())
{
it->second.albedo = albedo;
}
}
void MaterialManager::Clear()
{
if (!cleared) {
materials.clear();
cleared = true;
log_info("Cleared all materials");
}
}
+139
View File
@@ -0,0 +1,139 @@
#include "ModelManager.h"
#include "log/log.h"
ModelManager::ModelManager() : nextId(1), unloaded(false)
{
}
ModelManager::~ModelManager()
{
// Only unload if not already done
if (!unloaded) {
for (auto& pair : models) {
UnloadModel(pair.second);
}
models.clear();
modelSources.clear();
}
}
unsigned int ModelManager::Load(const std::string& path)
{
Model model = LoadModel(path.c_str());
if (model.meshCount == 0)
{
log_warn("Failed to load model: %s", path.c_str());
return 0;
}
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;
}
unsigned int ModelManager::LoadFromMesh(Mesh mesh)
{
Model model = LoadModelFromMesh(mesh);
unsigned int id = nextId++;
models[id] = model;
log_info("Loaded model id=%u from mesh", id);
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);
if (it != models.end())
{
UnloadModel(it->second);
models.erase(it);
modelSources.erase(id);
log_info("Unloaded model id=%u", id);
}
}
Model* ModelManager::Get(unsigned int id)
{
auto it = models.find(id);
if (it != models.end())
{
return &it->second;
}
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) {
for (auto& pair : models)
{
UnloadModel(pair.second);
}
models.clear();
modelSources.clear();
unloaded = true;
log_info("Unloaded all models");
}
}
+250
View File
@@ -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
+145
View File
@@ -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);
}
}
}
+963
View File
File diff suppressed because it is too large Load Diff
+82
View File
@@ -0,0 +1,82 @@
#include "ShaderManager.h"
#include "log/log.h"
ShaderManager::ShaderManager() : nextId(1), unloaded(false)
{
}
ShaderManager::~ShaderManager()
{
// Only unload if not already done
if (!unloaded) {
for (auto& pair : shaders) {
UnloadShader(pair.second);
}
shaders.clear();
shaderSources.clear();
}
}
unsigned int ShaderManager::Load(const std::string& vsPath, const std::string& fsPath)
{
Shader shader = LoadShader(vsPath.c_str(), fsPath.c_str());
if (shader.id == 0)
{
log_warn("Failed to load shader: %s / %s", vsPath.c_str(), fsPath.c_str());
return 0;
}
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;
}
void ShaderManager::Unload(unsigned int id)
{
auto it = shaders.find(id);
if (it != shaders.end())
{
UnloadShader(it->second);
shaders.erase(it);
shaderSources.erase(id);
log_info("Unloaded shader id=%u", id);
}
}
Shader* ShaderManager::Get(unsigned int id)
{
auto it = shaders.find(id);
if (it != shaders.end())
{
return &it->second;
}
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) {
for (auto& pair : shaders)
{
UnloadShader(pair.second);
}
shaders.clear();
shaderSources.clear();
unloaded = true;
log_info("Unloaded all shaders");
}
}
+74
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
#include "ImViewGuizmoRaymath.h"
File diff suppressed because it is too large Load Diff
+193
View File
@@ -0,0 +1,193 @@
#include "scripting/InputBindings.h"
#include "InputManager.h"
#include <angelscript.h>
#include <cassert>
static InputManager* g_inputManager = nullptr;
void SetGlobalInputManager(InputManager* inputManager) {
g_inputManager = inputManager;
}
// AngelScript wrapper functions
static bool AS_IsActionPressed(const std::string& actionName) {
return g_inputManager ? g_inputManager->IsActionPressed(actionName) : false;
}
static bool AS_IsActionDown(const std::string& actionName) {
return g_inputManager ? g_inputManager->IsActionDown(actionName) : false;
}
static bool AS_IsActionReleased(const std::string& actionName) {
return g_inputManager ? g_inputManager->IsActionReleased(actionName) : false;
}
static float AS_GetActionStrength(const std::string& actionName) {
return g_inputManager ? g_inputManager->GetActionStrength(actionName) : 0.0f;
}
static void AS_RegisterAction(const std::string& actionName) {
if (g_inputManager) g_inputManager->RegisterAction(actionName);
}
static void AS_BindKeyboard(const std::string& actionName, int keyCode) {
if (g_inputManager) g_inputManager->BindKeyboard(actionName, keyCode);
}
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;
// Input action queries
r = engine->RegisterGlobalFunction("bool IsActionPressed(const string &in)",
asFUNCTION(AS_IsActionPressed), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool IsActionDown(const string &in)",
asFUNCTION(AS_IsActionDown), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("bool IsActionReleased(const string &in)",
asFUNCTION(AS_IsActionReleased), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("float GetActionStrength(const string &in)",
asFUNCTION(AS_GetActionStrength), asCALL_CDECL);
assert(r >= 0);
// Allow runtime binding from scripts
r = engine->RegisterGlobalFunction("void RegisterAction(const string &in)",
asFUNCTION(AS_RegisterAction), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void BindKeyboard(const string &in, int)",
asFUNCTION(AS_BindKeyboard), asCALL_CDECL);
assert(r >= 0);
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");
assert(r >= 0);
r = engine->RegisterEnumValue("Key", "W", KEY_W);
assert(r >= 0);
r = engine->RegisterEnumValue("Key", "A", KEY_A);
assert(r >= 0);
r = engine->RegisterEnumValue("Key", "S", KEY_S);
assert(r >= 0);
r = engine->RegisterEnumValue("Key", "D", KEY_D);
assert(r >= 0);
r = engine->RegisterEnumValue("Key", "SPACE", KEY_SPACE);
assert(r >= 0);
r = engine->RegisterEnumValue("Key", "E", KEY_E);
assert(r >= 0);
r = engine->RegisterEnumValue("Key", "LEFT_CONTROL", KEY_LEFT_CONTROL);
assert(r >= 0);
r = engine->RegisterEnumValue("Key", "UP", KEY_UP);
assert(r >= 0);
r = engine->RegisterEnumValue("Key", "DOWN", KEY_DOWN);
assert(r >= 0);
r = engine->RegisterEnumValue("Key", "LEFT", KEY_LEFT);
assert(r >= 0);
r = engine->RegisterEnumValue("Key", "RIGHT", KEY_RIGHT);
assert(r >= 0);
r = engine->RegisterEnum("MouseButton");
assert(r >= 0);
// Mouse button constants
r = engine->RegisterEnumValue("MouseButton", "LEFT", MOUSE_BUTTON_LEFT);
assert(r >= 0);
r= engine->RegisterEnumValue("MouseButton", "RIGHT", MOUSE_BUTTON_RIGHT);
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);
}
+73
View File
@@ -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("");
}
+395
View File
@@ -0,0 +1,395 @@
#include "scripting/ResourceBindings.h"
#include "ShaderManager.h"
#include "ModelManager.h"
#include "MaterialManager.h"
#include "TextureManager.h"
#include "AssetManager.h"
#include <angelscript.h>
#include <assert.h>
#include "raylib.h"
// Global resource manager pointers for script access
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, TextureManager* textureMgr) {
g_shaderManager = shaderMgr;
g_modelManager = modelMgr;
g_materialManager = materialMgr;
g_textureManager = textureMgr;
}
void SetAssetManager(AssetManager* assetMgr) {
g_assetManager = assetMgr;
}
// === Shader Management ===
unsigned int AS_LoadShader(const std::string& vsPath, const std::string& fsPath) {
if (!g_shaderManager) return 0;
return g_shaderManager->Load(vsPath, fsPath);
}
void AS_UnloadShader(unsigned int id) {
if (!g_shaderManager) return;
g_shaderManager->Unload(id);
}
// === Model Management ===
unsigned int AS_LoadModel(const std::string& path) {
if (!g_modelManager) return 0;
return g_modelManager->Load(path);
}
unsigned int AS_LoadModelCube(float width, float height, float length) {
if (!g_modelManager) return 0;
return g_modelManager->LoadCube(width, height, length);
}
unsigned int AS_LoadModelSphere(float radius, int rings, int slices) {
if (!g_modelManager) return 0;
return g_modelManager->LoadSphere(radius, rings, slices);
}
unsigned int AS_LoadModelPlane(float width, float length, int resX, int resZ) {
if (!g_modelManager) return 0;
return g_modelManager->LoadPlane(width, length, resX, resZ);
}
void AS_UnloadModel(unsigned int id) {
if (!g_modelManager) return;
g_modelManager->Unload(id);
}
// === Material Management ===
unsigned int AS_CreateMaterial(unsigned int shaderId, unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
if (!g_materialManager) return 0;
Color albedo = {r, g, b, a};
return g_materialManager->Create(shaderId, albedo);
}
void AS_RemoveMaterial(unsigned int id) {
if (!g_materialManager) return;
g_materialManager->Remove(id);
}
void AS_SetMaterialShader(unsigned int id, unsigned int shaderId) {
if (!g_materialManager) return;
g_materialManager->SetShader(id, shaderId);
}
void AS_SetMaterialAlbedo(unsigned int id, unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
if (!g_materialManager) return;
Color albedo = {r, g, b, a};
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;
// === Shader namespace ===
engine->SetDefaultNamespace("Shader");
r = engine->RegisterGlobalFunction("uint Load(const string &in, const string &in)",
asFUNCTION(AS_LoadShader), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void Unload(uint)",
asFUNCTION(AS_UnloadShader), asCALL_CDECL);
assert(r >= 0);
// === Model namespace ===
engine->SetDefaultNamespace("Model");
r = engine->RegisterGlobalFunction("uint Load(const string &in)",
asFUNCTION(AS_LoadModel), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("uint LoadCube(float, float, float)",
asFUNCTION(AS_LoadModelCube), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("uint LoadSphere(float, int, int)",
asFUNCTION(AS_LoadModelSphere), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("uint LoadPlane(float, float, int, int)",
asFUNCTION(AS_LoadModelPlane), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void Unload(uint)",
asFUNCTION(AS_UnloadModel), asCALL_CDECL);
assert(r >= 0);
// === Material namespace ===
engine->SetDefaultNamespace("Material");
r = engine->RegisterGlobalFunction("uint Create(uint, uint8, uint8, uint8, uint8)",
asFUNCTION(AS_CreateMaterial), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void Remove(uint)",
asFUNCTION(AS_RemoveMaterial), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void SetShader(uint, uint)",
asFUNCTION(AS_SetMaterialShader), asCALL_CDECL);
assert(r >= 0);
r = engine->RegisterGlobalFunction("void SetAlbedo(uint, uint8, uint8, uint8, uint8)",
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("");
}
+28
View File
@@ -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("");
}
+10 -2
View File
@@ -1,18 +1,26 @@
#include "scripting/ScriptBindings.h"
#include "scripting/ToastBindings.h"
#include "scripting/LogBindings.h"
#include "scripting/DrawBindings.h"
#include "scripting/ImageBindings.h"
#include "scripting/TextureBindings.h"
#include "scripting/MathBindings.h"
#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)
{
// Delegate to separate binding files
RegisterToastBindings(engine);
RegisterLogBindings(engine);
RegisterDrawBindings(engine);
RegisterImageBindings(engine);
RegisterTextureBindings(engine);
RegisterMathBindings(engine);
RegisterECSBindings(engine);
RegisterResourceBindings(engine);
RegisterInputBindings(engine);
RegisterSceneBindings(engine);
RegisterPhysFSBindings(engine);
}
+235 -17
View File
@@ -2,8 +2,8 @@
#include "scripting/ScriptBindings.h"
#include "scriptstdstring.h"
#include "scriptbuilder.h"
#include <fstream>
#include <sstream>
#include "scriptarray.h"
#include "PhysFSManager.h"
#include <cstring>
#include <assert.h>
#include "../log/log.h"
@@ -11,7 +11,7 @@
#include <vector>
#include <mutex>
ScriptEngine::ScriptEngine() : engine(nullptr), updateFunc(nullptr), drawFunc(nullptr), initFunc(nullptr), shutdownFunc(nullptr),currentModule(nullptr), hasValidScript(false) {
ScriptEngine::ScriptEngine() : engine(nullptr), updateFunc(nullptr), initFunc(nullptr), shutdownFunc(nullptr),currentModule(nullptr), hasValidScript(false) {
}
ScriptEngine::~ScriptEngine() {
@@ -28,6 +28,9 @@ bool ScriptEngine::Initialize() {
// Register std::string
RegisterStdString(engine);
// Register array template
RegisterScriptArray(engine, true);
// Register script bindings
ScriptBindings::RegisterAll(engine);
@@ -106,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);
@@ -116,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");
@@ -142,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();
@@ -161,16 +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");
drawFunc = currentModule->GetFunctionByName("Draw");
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;
@@ -197,9 +258,162 @@ 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;
drawFunc = nullptr;
hasValidScript = false;
}
@@ -256,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;
@@ -268,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;
}
+10 -1
View File
@@ -3,7 +3,16 @@
#include <assert.h>
#include <string>
Color ColorFromUInt(unsigned int c);
// Utility function to convert uint color to Raylib Color
Color ColorFromUInt(unsigned int c)
{
Color col;
col.r = (c >> 24) & 0xFF;
col.g = (c >> 16) & 0xFF;
col.b = (c >> 8) & 0xFF;
col.a = c & 0xFF;
return col;
}
Texture2D AS_LoadTexture(const std::string &filename)
{
+6 -1
View File
@@ -2,25 +2,30 @@
#include "gui/Toast.h"
#include <angelscript.h>
#include <assert.h>
#include <string>
#include "../log/log.h"
// Toast binding functions
static void AS_ToastInfo(const std::string& message)
{
log_info("%s", message.c_str());
Toast::Info(message);
}
static void AS_ToastWarning(const std::string& message)
{
log_warn("%s", message.c_str());
Toast::Warning(message);
}
static void AS_ToastError(const std::string& message)
{
log_error("%s", message.c_str());
Toast::Error(message);
}
static void AS_ToastSuccess(const std::string& message)
{
log_info("%s", message.c_str());
Toast::Success(message);
}
+29 -28
View File
@@ -1,43 +1,44 @@
cmake_minimum_required(VERSION 3.16)
# Simple unit test executable that compiles acutest.c directly
# 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
${CMAKE_SOURCE_DIR}/src/HotReload.cpp
${CMAKE_SOURCE_DIR}/src/scripting/ScriptEngine.cpp
${CMAKE_SOURCE_DIR}/src/scripting/ScriptBindings.cpp
${CMAKE_SOURCE_DIR}/src/scripting/ToastBindings.cpp
${CMAKE_SOURCE_DIR}/src/scripting/LogBindings.cpp
${CMAKE_SOURCE_DIR}/src/scripting/DrawBindings.cpp
${CMAKE_SOURCE_DIR}/src/scripting/ImageBindings.cpp
${CMAKE_SOURCE_DIR}/src/scripting/TextureBindings.cpp
${CMAKE_SOURCE_DIR}/src/scripting/MathBindings.cpp
${CMAKE_SOURCE_DIR}/src/log/log.c
resource_tests.cpp
scenegraph_tests.cpp
)
target_include_directories(unit_tests PRIVATE
${CMAKE_SOURCE_DIR}/tests
${CMAKE_SOURCE_DIR}/include
${CMAKE_SOURCE_DIR}/src/log
${CMAKE_SOURCE_DIR}/external/angelscript/sdk/angelscript/include
${CMAKE_SOURCE_DIR}/external/angelscript/sdk/add_on/scriptstdstring
${CMAKE_SOURCE_DIR}/external/angelscript/sdk/add_on/scriptbuilder
${CMAKE_SOURCE_DIR}/external/raylib/src
${CMAKE_SOURCE_DIR}/external/imgui
${CMAKE_SOURCE_DIR}/external/rlImGui
${CMAKE_SOURCE_DIR}/external/acutest
)
# Link AngelScript and its add-ons needed by ScriptEngine
target_link_libraries(unit_tests PRIVATE
angelscript
scriptstdstring
scriptbuilder
raylib
imgui
)
# Link the shared simian_core library - that's it!
target_link_libraries(unit_tests PRIVATE simian_core)
add_test(NAME unit_tests COMMAND unit_tests)
# Platform-specific linking for tests too
if(WIN32)
target_link_libraries(unit_tests PRIVATE winmm gdi32)
endif()
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 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)
+7 -1
View File
@@ -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);
}
+7 -1
View File
@@ -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);
}
+31
View File
@@ -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;
}
}
+9
View File
@@ -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();
+292
View File
@@ -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);
}
+7
View File
@@ -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);
+369
View File
@@ -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(&registry, &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(&registry, &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(&registry, &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(&registry, &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(&registry, &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);
}
+9
View File
@@ -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);
+13 -2
View File
@@ -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);
+14
View File
@@ -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 }
};