Script ezy
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
var Actions;
|
||||
var camera, cameraPosition;
|
||||
var cube, cubePosition, cubeRenderable, cubeMesh;
|
||||
|
||||
// init() is called via scriptManagerCallGlobal(), which pumps the asset
|
||||
// system + job queue until any promise it returns settles - so it's safe
|
||||
// to await include() here even though this runs before the main loop.
|
||||
async function init() {
|
||||
Actions = await include("input.js");
|
||||
|
||||
camera = new Entity();
|
||||
cameraPosition = camera.add(POSITION);
|
||||
camera.add(CAMERA);
|
||||
cameraPosition.position = new Vec3(3, 3, -6);
|
||||
cameraPosition.lookAt(new Vec3(0, 0, 0));
|
||||
|
||||
cube = new Entity();
|
||||
cubePosition = cube.add(POSITION);
|
||||
cubeRenderable = cube.add(RENDERABLE);
|
||||
cubeMesh = Mesh.createCube();
|
||||
cubeRenderable.mesh = cubeMesh;
|
||||
cubeRenderable.color = Color.red();
|
||||
}
|
||||
|
||||
// Runs every frame (including dynamic/interpolation frames) - use for
|
||||
// smooth, purely presentational animation.
|
||||
function update() {
|
||||
cubePosition.rotation.y += TIME.delta * 1.5;
|
||||
cubePosition.rotation.x += TIME.delta * 0.7;
|
||||
}
|
||||
|
||||
// Runs once per fixed timestep only - use for gameplay logic that should
|
||||
// be deterministic and independent of display refresh rate.
|
||||
function fixedUpdate() {
|
||||
var move = 3.0 * TIME.delta;
|
||||
if(Input.isDown(Actions.LEFT)) cubePosition.position.x -= move;
|
||||
if(Input.isDown(Actions.RIGHT)) cubePosition.position.x += move;
|
||||
if(Input.isDown(Actions.UP)) cubePosition.position.z += move;
|
||||
if(Input.isDown(Actions.DOWN)) cubePosition.position.z -= move;
|
||||
if(Input.pressed(Actions.ACCEPT)) cubePosition.position = new Vec3(0, 0, 0);
|
||||
}
|
||||
|
||||
function deinit() {
|
||||
cube.dispose();
|
||||
camera.dispose();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Binds physical buttons to abstract actions, then exports the action
|
||||
// constants so other scripts don't need to know raw INPUT_ACTION_* names.
|
||||
Input.bind("w", INPUT_ACTION_UP);
|
||||
Input.bind("s", INPUT_ACTION_DOWN);
|
||||
Input.bind("a", INPUT_ACTION_LEFT);
|
||||
Input.bind("d", INPUT_ACTION_RIGHT);
|
||||
Input.bind("space", INPUT_ACTION_ACCEPT);
|
||||
Input.bind("escape", INPUT_ACTION_RAGEQUIT);
|
||||
|
||||
if(typeof INPUT_GAMEPAD !== "undefined") {
|
||||
Input.bind("gamepad_up", INPUT_ACTION_UP);
|
||||
Input.bind("gamepad_down", INPUT_ACTION_DOWN);
|
||||
Input.bind("gamepad_left", INPUT_ACTION_LEFT);
|
||||
Input.bind("gamepad_right", INPUT_ACTION_RIGHT);
|
||||
Input.bind("gamepad_a", INPUT_ACTION_ACCEPT);
|
||||
Input.bind("gamepad_start", INPUT_ACTION_RAGEQUIT);
|
||||
}
|
||||
|
||||
module = {
|
||||
UP: INPUT_ACTION_UP,
|
||||
DOWN: INPUT_ACTION_DOWN,
|
||||
LEFT: INPUT_ACTION_LEFT,
|
||||
RIGHT: INPUT_ACTION_RIGHT,
|
||||
ACCEPT: INPUT_ACTION_ACCEPT,
|
||||
CANCEL: INPUT_ACTION_CANCEL,
|
||||
RAGEQUIT: INPUT_ACTION_RAGEQUIT
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
msgid "test.string"
|
||||
msgstr "This is a test string"
|
||||
@@ -0,0 +1,96 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Turn things off we don't need
|
||||
set(JERRY_CMDLINE OFF CACHE BOOL "" FORCE)
|
||||
set(JERRY_EXT ON CACHE BOOL "" FORCE)
|
||||
set(JERRY_DEBUGGER OFF CACHE BOOL "" FORCE)
|
||||
set(JERRY_BUILTIN_DATE OFF CACHE BOOL "" FORCE)
|
||||
set(ENABLE_LTO OFF CACHE BOOL "" FORCE)
|
||||
|
||||
# Fetch Jerry
|
||||
include(FetchContent)
|
||||
FetchContent_Declare(
|
||||
jerryscript
|
||||
GIT_REPOSITORY https://git.wish.moe/YourWishes/jerryscript
|
||||
GIT_TAG float32-fix
|
||||
)
|
||||
FetchContent_MakeAvailable(jerryscript)
|
||||
|
||||
# Mark found
|
||||
set(jerryscript_FOUND ON)
|
||||
|
||||
# Define targets
|
||||
if(TARGET jerryscript-core)
|
||||
set(JERRY_CORE_TARGET jerryscript-core)
|
||||
elseif(TARGET jerry-core)
|
||||
set(JERRY_CORE_TARGET jerry-core)
|
||||
endif()
|
||||
|
||||
if(TARGET jerryscript-ext)
|
||||
set(JERRY_EXT_TARGET jerryscript-ext)
|
||||
elseif(TARGET jerry-ext)
|
||||
set(JERRY_EXT_TARGET jerry-ext)
|
||||
endif()
|
||||
|
||||
if(TARGET jerryscript-port-default)
|
||||
set(JERRY_PORT_TARGET jerryscript-port-default)
|
||||
elseif(TARGET jerry-port-default)
|
||||
set(JERRY_PORT_TARGET jerry-port-default)
|
||||
elseif(TARGET jerryscript-port)
|
||||
set(JERRY_PORT_TARGET jerryscript-port)
|
||||
elseif(TARGET jerry-port)
|
||||
set(JERRY_PORT_TARGET jerry-port)
|
||||
endif()
|
||||
|
||||
if(NOT JERRY_CORE_TARGET)
|
||||
message(FATAL_ERROR "JerryScript core target not found")
|
||||
endif()
|
||||
|
||||
if(NOT JERRY_EXT_TARGET)
|
||||
message(FATAL_ERROR "JerryScript ext target not found")
|
||||
endif()
|
||||
|
||||
if(NOT JERRY_PORT_TARGET)
|
||||
message(FATAL_ERROR "JerryScript port target not found")
|
||||
endif()
|
||||
|
||||
foreach(tgt IN ITEMS
|
||||
${JERRY_CORE_TARGET}
|
||||
${JERRY_EXT_TARGET}
|
||||
${JERRY_PORT_TARGET}
|
||||
)
|
||||
if(TARGET ${tgt})
|
||||
set_property(TARGET ${tgt} PROPERTY INTERPROCEDURAL_OPTIMIZATION OFF)
|
||||
target_compile_definitions(${JERRY_CORE_TARGET} PRIVATE
|
||||
JERRY_NUMBER_TYPE_FLOAT64=0
|
||||
JERRY_BUILTIN_DATE=0
|
||||
)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# Export include dirs through the targets
|
||||
target_include_directories(${JERRY_CORE_TARGET} INTERFACE
|
||||
${jerryscript_SOURCE_DIR}/jerry-core/include
|
||||
)
|
||||
|
||||
target_include_directories(${JERRY_EXT_TARGET} INTERFACE
|
||||
${jerryscript_SOURCE_DIR}/jerry-ext/include
|
||||
)
|
||||
|
||||
target_include_directories(${JERRY_PORT_TARGET} INTERFACE
|
||||
${jerryscript_SOURCE_DIR}/jerry-port/default/include
|
||||
)
|
||||
|
||||
# Suppress JerryScript-only warning
|
||||
if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang")
|
||||
target_compile_options(${JERRY_CORE_TARGET} PRIVATE
|
||||
-Wno-error
|
||||
)
|
||||
endif()
|
||||
|
||||
add_library(jerryscript::core ALIAS ${JERRY_CORE_TARGET})
|
||||
add_library(jerryscript::ext ALIAS ${JERRY_EXT_TARGET})
|
||||
add_library(jerryscript::port ALIAS ${JERRY_PORT_TARGET})
|
||||
@@ -32,6 +32,15 @@ if(NOT yyjson_FOUND)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT jerryscript_FOUND)
|
||||
find_package(jerryscript REQUIRED)
|
||||
target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC
|
||||
jerryscript::core
|
||||
jerryscript::ext
|
||||
jerryscript::port
|
||||
)
|
||||
endif()
|
||||
|
||||
if(DUSK_BACKTRACE)
|
||||
target_link_options(${DUSK_LIBRARY_TARGET_NAME} PUBLIC -rdynamic)
|
||||
target_compile_definitions(${DUSK_BINARY_TARGET_NAME} PUBLIC
|
||||
@@ -57,12 +66,15 @@ add_subdirectory(assert)
|
||||
add_subdirectory(asset)
|
||||
add_subdirectory(console)
|
||||
add_subdirectory(display)
|
||||
add_subdirectory(entity)
|
||||
add_subdirectory(log)
|
||||
add_subdirectory(engine)
|
||||
add_subdirectory(error)
|
||||
add_subdirectory(input)
|
||||
add_subdirectory(locale)
|
||||
add_subdirectory(physics)
|
||||
add_subdirectory(scene)
|
||||
add_subdirectory(script)
|
||||
add_subdirectory(system)
|
||||
add_subdirectory(time)
|
||||
add_subdirectory(ui)
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#define ASSET_FILE_NAME "dusk.dsk"
|
||||
#define ASSET_HEADER_SIZE 3
|
||||
|
||||
#define ASSET_LOADING_COUNT_MAX 10
|
||||
#define ASSET_LOADING_COUNT_MAX 16
|
||||
#define ASSET_ENTRY_COUNT_MAX 64
|
||||
|
||||
typedef struct asset_s {
|
||||
|
||||
@@ -15,4 +15,5 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
add_subdirectory(display)
|
||||
add_subdirectory(locale)
|
||||
add_subdirectory(json)
|
||||
add_subdirectory(dmf)
|
||||
add_subdirectory(dmf)
|
||||
add_subdirectory(script)
|
||||
@@ -45,4 +45,9 @@ assetloadercallbacks_t ASSET_LOADER_CALLBACKS[ASSET_LOADER_TYPE_COUNT] = {
|
||||
.loadAsync = assetJsonLoaderAsync,
|
||||
.dispose = assetJsonDispose
|
||||
},
|
||||
|
||||
[ASSET_LOADER_TYPE_SCRIPT] = {
|
||||
.loadSync = assetScriptLoaderSync,
|
||||
.dispose = assetScriptDispose
|
||||
},
|
||||
};
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "asset/loader/display/assettilesetloader.h"
|
||||
#include "asset/loader/locale/assetlocaleloader.h"
|
||||
#include "asset/loader/json/assetjsonloader.h"
|
||||
#include "asset/loader/script/assetscriptloader.h"
|
||||
|
||||
typedef enum {
|
||||
ASSET_LOADER_TYPE_NULL,
|
||||
@@ -22,6 +23,7 @@ typedef enum {
|
||||
ASSET_LOADER_TYPE_TILESET,
|
||||
ASSET_LOADER_TYPE_LOCALE,
|
||||
ASSET_LOADER_TYPE_JSON,
|
||||
ASSET_LOADER_TYPE_SCRIPT,
|
||||
|
||||
ASSET_LOADER_TYPE_COUNT
|
||||
} assetloadertype_t;
|
||||
@@ -42,6 +44,7 @@ typedef union {
|
||||
assettilesetoutput_t tileset;
|
||||
assetlocaleoutput_t locale;
|
||||
assetjsonoutput_t json;
|
||||
assetscriptoutput_t script;
|
||||
} assetloaderoutput_t;
|
||||
|
||||
typedef union {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
assetscriptloader.c
|
||||
)
|
||||
@@ -0,0 +1,115 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "assetscriptloader.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "asset/loader/assetloading.h"
|
||||
#include "asset/loader/assetentry.h"
|
||||
|
||||
static void assetScriptSettleWithError(
|
||||
assetscriptoutput_t *output,
|
||||
const char_t *message
|
||||
) {
|
||||
jerry_value_t errVal = jerry_string_sz(message);
|
||||
jerry_value_t rejectResult = jerry_promise_reject(output->promise, errVal);
|
||||
jerry_value_free(rejectResult);
|
||||
jerry_value_free(errVal);
|
||||
}
|
||||
|
||||
errorret_t assetScriptLoaderSync(assetloading_t *loading) {
|
||||
assertNotNull(loading, "Loading cannot be NULL");
|
||||
assertTrue(loading->type == ASSET_LOADER_TYPE_SCRIPT, "Invalid type.");
|
||||
assertIsMainThread("Must be called from the main thread.");
|
||||
|
||||
assetentry_t *entry = loading->entry;
|
||||
assetscriptoutput_t *output = &entry->data.script;
|
||||
assertTrue(
|
||||
output->promise != 0,
|
||||
"Script entry has no promise - was it requested via include()?"
|
||||
);
|
||||
|
||||
assetfile_t file;
|
||||
uint8_t *buffer = NULL;
|
||||
size_t size = 0;
|
||||
|
||||
errorret_t err = assetFileInit(&file, entry->name, NULL, NULL);
|
||||
if(errorIsOk(err)) err = assetFileReadEntire(&file, &buffer, &size);
|
||||
if(errorIsOk(err)) err = assetFileDispose(&file);
|
||||
|
||||
if(errorIsNotOk(err)) {
|
||||
assetScriptSettleWithError(output, err.state->message);
|
||||
entry->state = ASSET_ENTRY_STATE_ERROR;
|
||||
errorChain(err);
|
||||
}
|
||||
|
||||
char_t *src = (char_t *)memoryAllocate(size + 1);
|
||||
memoryCopy(src, buffer, size);
|
||||
src[size] = '\0';
|
||||
memoryFree(buffer);
|
||||
|
||||
// Scripts export their public API by assigning to the global `module`.
|
||||
// Swap it out around the eval so this doesn't clobber a caller's own
|
||||
// in-flight include() of a different file.
|
||||
jerry_value_t global = jerry_current_realm();
|
||||
jerry_value_t moduleKey = jerry_string_sz("module");
|
||||
jerry_value_t prevModule = jerry_object_get(global, moduleKey);
|
||||
jerry_value_t undef = jerry_undefined();
|
||||
jerry_object_set(global, moduleKey, undef);
|
||||
jerry_value_free(undef);
|
||||
|
||||
jerry_value_t evalResult = jerry_eval(
|
||||
(const jerry_char_t *)src, size, JERRY_PARSE_NO_OPTS
|
||||
);
|
||||
memoryFree(src);
|
||||
|
||||
if(jerry_value_is_exception(evalResult)) {
|
||||
jerry_value_t errVal = jerry_exception_value(evalResult, false);
|
||||
jerry_value_t rejectResult = jerry_promise_reject(output->promise, errVal);
|
||||
jerry_value_free(rejectResult);
|
||||
jerry_value_free(errVal);
|
||||
jerry_value_free(evalResult);
|
||||
|
||||
jerry_value_t moduleVal = jerry_object_get(global, moduleKey);
|
||||
jerry_value_free(moduleVal);
|
||||
jerry_object_set(global, moduleKey, prevModule);
|
||||
jerry_value_free(prevModule);
|
||||
jerry_value_free(moduleKey);
|
||||
jerry_value_free(global);
|
||||
|
||||
entry->state = ASSET_ENTRY_STATE_ERROR;
|
||||
errorThrow("Script error in '%s'", entry->name);
|
||||
}
|
||||
jerry_value_free(evalResult);
|
||||
|
||||
jerry_value_t moduleVal = jerry_object_get(global, moduleKey);
|
||||
jerry_object_set(global, moduleKey, prevModule);
|
||||
jerry_value_free(prevModule);
|
||||
jerry_value_free(moduleKey);
|
||||
jerry_value_free(global);
|
||||
|
||||
jerry_value_t resolveResult = jerry_promise_resolve(output->promise, moduleVal);
|
||||
jerry_value_free(resolveResult);
|
||||
jerry_value_free(moduleVal);
|
||||
|
||||
entry->state = ASSET_ENTRY_STATE_LOADED;
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t assetScriptDispose(assetentry_t *entry) {
|
||||
assertNotNull(entry, "Asset entry cannot be NULL");
|
||||
assertTrue(entry->type == ASSET_LOADER_TYPE_SCRIPT, "Invalid type.");
|
||||
assertIsMainThread("Must be called from the main thread.");
|
||||
|
||||
assetscriptoutput_t *output = &entry->data.script;
|
||||
if(output->promise != 0) {
|
||||
jerry_value_free(output->promise);
|
||||
output->promise = 0;
|
||||
}
|
||||
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "asset/assetfile.h"
|
||||
#include <jerryscript.h>
|
||||
|
||||
typedef struct assetloading_s assetloading_t;
|
||||
typedef struct assetentry_s assetentry_t;
|
||||
|
||||
/**
|
||||
* Output data for a script asset entry. The promise is created once, the
|
||||
* first time a script requests this file (see moduleIncludeInclude), and is
|
||||
* owned by the entry for its lifetime - every subsequent request for the
|
||||
* same file is handed a copy of this same promise instead of starting a
|
||||
* second load. It is resolved (with the script's exported `module` value)
|
||||
* or rejected (with the JS exception) exactly once, the moment the entry
|
||||
* transitions to ASSET_ENTRY_STATE_LOADED or ASSET_ENTRY_STATE_ERROR.
|
||||
*/
|
||||
typedef struct {
|
||||
jerry_value_t promise;
|
||||
} assetscriptoutput_t;
|
||||
|
||||
/**
|
||||
* Loads and evaluates a script asset synchronously (reads the whole file
|
||||
* from the archive, then runs it) and resolves/rejects the entry's promise
|
||||
* with the outcome. Scripts are small and read instantly, so there is no
|
||||
* async (background-thread) phase - this is the only loader function.
|
||||
*
|
||||
* @param loading The asset loading slot.
|
||||
* @return An error if reading failed, or errorOk() (a script exception is
|
||||
* reported via promise rejection, not a returned error).
|
||||
*/
|
||||
errorret_t assetScriptLoaderSync(assetloading_t *loading);
|
||||
|
||||
/**
|
||||
* Releases the promise reference held by a script asset entry.
|
||||
*
|
||||
* @param entry The asset entry to dispose.
|
||||
*/
|
||||
errorret_t assetScriptDispose(assetentry_t *entry);
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "display/display.h"
|
||||
#include "display/framebuffer/framebuffer.h"
|
||||
#include "scene/scene.h"
|
||||
#include "entity/entityrender.h"
|
||||
#include "display/spritebatch/spritebatch.h"
|
||||
#include "display/mesh/quad.h"
|
||||
#include "display/mesh/cube.h"
|
||||
@@ -80,6 +81,7 @@ errorret_t displayUpdate(void) {
|
||||
);
|
||||
|
||||
errorChain(sceneRender());
|
||||
errorChain(entityRenderAll());
|
||||
|
||||
// Finish up
|
||||
screenUnbind();
|
||||
|
||||
@@ -15,10 +15,23 @@
|
||||
#include "asset/asset.h"
|
||||
#include "ui/ui.h"
|
||||
#include "assert/assert.h"
|
||||
#include "entity/entitymanager.h"
|
||||
#include "physics/physicsmanager.h"
|
||||
#include "script/scriptmanager.h"
|
||||
#include "network/network.h"
|
||||
#include "system/system.h"
|
||||
#include "console/console.h"
|
||||
|
||||
double jerry_port_current_time(void) {
|
||||
dusktimeepoch_t epoch = timeGetEpoch();
|
||||
return epoch.time * 1000.0;
|
||||
}
|
||||
|
||||
int32_t jerry_port_local_tza(double unix_ms) {
|
||||
(void) unix_ms;
|
||||
return 0;
|
||||
}
|
||||
|
||||
engine_t ENGINE;
|
||||
|
||||
errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
||||
@@ -36,11 +49,17 @@ errorret_t engineInit(const int32_t argc, const char_t **argv) {
|
||||
errorChain(inputInit());
|
||||
errorChain(assetInit());
|
||||
errorChain(localeManagerInit());
|
||||
errorChain(scriptManagerInit());
|
||||
errorChain(displayInit());
|
||||
errorChain(uiInit());
|
||||
entityManagerInit();
|
||||
physicsManagerInit();
|
||||
errorChain(networkInit());
|
||||
errorChain(sceneInit());
|
||||
|
||||
errorChain(scriptManagerExecFile("engine.js", NULL));
|
||||
errorChain(scriptManagerCallGlobal("init"));
|
||||
|
||||
consolePrint("Engine initialized");
|
||||
|
||||
#ifdef DUSK_ASSERTIONS_FAKED
|
||||
@@ -58,7 +77,22 @@ errorret_t engineUpdate(void) {
|
||||
timeUpdate();
|
||||
inputUpdate();
|
||||
consoleUpdate();
|
||||
physicsManagerUpdate();
|
||||
|
||||
// Fixed-step logic: runs once per DUSK_TIME_STEP tick. On platforms
|
||||
// without dynamic timing every frame is a fixed step; on platforms with
|
||||
// it, dynamic (interpolation) frames are skipped.
|
||||
#ifdef DUSK_TIME_DYNAMIC
|
||||
if(!TIME.dynamicUpdate) {
|
||||
#endif
|
||||
entityManagerFixedUpdate();
|
||||
errorChain(scriptManagerCallGlobal("fixedUpdate"));
|
||||
#ifdef DUSK_TIME_DYNAMIC
|
||||
}
|
||||
#endif
|
||||
|
||||
errorChain(sceneUpdate());
|
||||
errorChain(scriptManagerCallGlobal("update"));
|
||||
errorChain(assetUpdate());
|
||||
errorChain(uiUpdate());
|
||||
|
||||
@@ -73,13 +107,21 @@ void engineExit(void) {
|
||||
}
|
||||
|
||||
errorret_t engineDispose(void) {
|
||||
errorChain(scriptManagerCallGlobal("deinit"));
|
||||
errorChain(sceneDispose());
|
||||
errorChain(networkDispose());
|
||||
entityManagerDispose();
|
||||
localeManagerDispose();
|
||||
errorChain(uiDispose());
|
||||
consoleDispose();
|
||||
errorChain(displayDispose());
|
||||
|
||||
// Must run before scriptManagerDispose(): asset entries (e.g. loaded
|
||||
// scripts) hold jerry_value_t references (promises) that need to be
|
||||
// released via their loader's dispose callback before jerry_cleanup()
|
||||
// runs, which fatally asserts if anything is still held.
|
||||
errorChain(assetDispose());
|
||||
errorChain(scriptManagerDispose());
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
entity.c
|
||||
entitymanager.c
|
||||
component.c
|
||||
entityrender.c
|
||||
)
|
||||
|
||||
# Subdirs
|
||||
add_subdirectory(component)
|
||||
@@ -0,0 +1,149 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entitymanager.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
|
||||
componentdefinition_t COMPONENT_DEFINITIONS[] = {
|
||||
[COMPONENT_TYPE_NULL] = { 0 },
|
||||
|
||||
#define X(enm, type, field, iMethod, dMethod, fMethod) \
|
||||
[COMPONENT_TYPE_##enm] = { \
|
||||
.enumName = #enm, \
|
||||
.name = #field, \
|
||||
.init = iMethod, \
|
||||
.dispose = dMethod, \
|
||||
.fixedUpdate = fMethod \
|
||||
},
|
||||
|
||||
#include "componentlist.h"
|
||||
#undef X
|
||||
|
||||
[COMPONENT_TYPE_COUNT] = { 0 }
|
||||
};
|
||||
|
||||
void componentInit(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const componenttype_t type
|
||||
) {
|
||||
assertTrue(entityId < ENTITY_COUNT_MAX, "Entity ID OOB");
|
||||
assertTrue(componentId < ENTITY_COMPONENT_COUNT_MAX, "Component ID OOB");
|
||||
assertTrue(type < COMPONENT_TYPE_COUNT, "Component type OOB");
|
||||
assertTrue(type != COMPONENT_TYPE_NULL, "Cannot initialize null component");
|
||||
|
||||
componentindex_t index = componentGetIndex(entityId, componentId);
|
||||
component_t *cmp = &ENTITY_MANAGER.components[index];
|
||||
memoryZero(cmp, sizeof(component_t));
|
||||
|
||||
cmp->type = type;
|
||||
if(COMPONENT_DEFINITIONS[type].init) {
|
||||
COMPONENT_DEFINITIONS[type].init(entityId, componentId);
|
||||
}
|
||||
}
|
||||
|
||||
void * componentGetData(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const componenttype_t type
|
||||
) {
|
||||
assertTrue(entityId < ENTITY_COUNT_MAX, "Entity ID OOB");
|
||||
assertTrue(componentId < ENTITY_COMPONENT_COUNT_MAX, "Component ID OOB");
|
||||
assertTrue(type < COMPONENT_TYPE_COUNT, "Component type OOB");
|
||||
assertTrue(type != COMPONENT_TYPE_NULL, "Cannot get data of null component");
|
||||
|
||||
componentindex_t index = componentGetIndex(entityId, componentId);
|
||||
component_t *cmp = &ENTITY_MANAGER.components[index];
|
||||
assertTrue(cmp->type == type, "Component type mismatch");
|
||||
|
||||
return &cmp->data;
|
||||
}
|
||||
|
||||
componentindex_t componentGetIndex(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
assertTrue(entityId < ENTITY_COUNT_MAX, "Entity ID OOB");
|
||||
assertTrue(componentId < ENTITY_COMPONENT_COUNT_MAX, "Component ID OOB");
|
||||
return (entityId * ENTITY_COMPONENT_COUNT_MAX) + componentId;
|
||||
}
|
||||
|
||||
entityid_t componentGetEntitiesWithComponent(
|
||||
const componenttype_t type,
|
||||
entityid_t outEntities[ENTITY_COUNT_MAX],
|
||||
componentid_t outComponents[ENTITY_COUNT_MAX]
|
||||
) {
|
||||
assertTrue(type < COMPONENT_TYPE_COUNT, "Component type OOB");
|
||||
assertTrue(type != COMPONENT_TYPE_NULL, "Cannot check NULL type");
|
||||
assertNotNull(outEntities, "Output entities array cannot be null");
|
||||
assertNotNull(outComponents, "Output components array cannot be null");
|
||||
|
||||
entityid_t written = 0;
|
||||
for(entityid_t i = 0; i < ENTITY_COUNT_MAX; i++) {
|
||||
componentid_t used = ENTITY_MANAGER.entitiesWithComponent[
|
||||
type * ENTITY_COUNT_MAX + i
|
||||
];
|
||||
if(used == COMPONENT_ID_INVALID) continue;
|
||||
assertTrue(
|
||||
ENTITY_MANAGER.components[componentGetIndex(i, used)].type == type,
|
||||
"Component type mismatch in entitiesWithComponent lookup"
|
||||
);
|
||||
assertTrue(
|
||||
(ENTITY_MANAGER.entities[i].state & ENTITY_STATE_ACTIVE) != 0,
|
||||
"Inactive entity in entitiesWithComponent lookup"
|
||||
);
|
||||
assertTrue(
|
||||
used < ENTITY_COMPONENT_COUNT_MAX,
|
||||
"Component ID OOB in entitiesWithComponent lookup"
|
||||
);
|
||||
assertTrue(
|
||||
componentGetIndex(i,used) < ENTITY_COUNT_MAX*ENTITY_COMPONENT_COUNT_MAX,
|
||||
"Component index OOB in entitiesWithComponent lookup"
|
||||
);
|
||||
assertTrue(
|
||||
ENTITY_MANAGER.components[componentGetIndex(i,used)].type == type,
|
||||
"Component type mismatch in entitiesWithComponent lookup"
|
||||
);
|
||||
outComponents[written] = used;
|
||||
outEntities[written++] = i;
|
||||
}
|
||||
return written;
|
||||
}
|
||||
|
||||
void componentDispose(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
assertTrue(entityId < ENTITY_COUNT_MAX, "Entity ID OOB");
|
||||
assertTrue(componentId < ENTITY_COMPONENT_COUNT_MAX, "Component ID OOB");
|
||||
|
||||
componentindex_t index = componentGetIndex(entityId, componentId);
|
||||
component_t *cmp = &ENTITY_MANAGER.components[index];
|
||||
if(cmp->type == COMPONENT_TYPE_NULL) return;
|
||||
|
||||
if(COMPONENT_DEFINITIONS[cmp->type].dispose) {
|
||||
COMPONENT_DEFINITIONS[cmp->type].dispose(entityId, componentId);
|
||||
}
|
||||
|
||||
cmp->type = COMPONENT_TYPE_NULL;
|
||||
}
|
||||
|
||||
void componentFixedUpdateAll(void) {
|
||||
for(entityid_t e = 0; e < ENTITY_COUNT_MAX; e++) {
|
||||
if((ENTITY_MANAGER.entities[e].state & ENTITY_STATE_ACTIVE) == 0) continue;
|
||||
|
||||
for(componentid_t c = 0; c < ENTITY_COMPONENT_COUNT_MAX; c++) {
|
||||
componentindex_t index = componentGetIndex(e, c);
|
||||
componenttype_t type = ENTITY_MANAGER.components[index].type;
|
||||
if(type == COMPONENT_TYPE_NULL) continue;
|
||||
if(COMPONENT_DEFINITIONS[type].fixedUpdate) {
|
||||
COMPONENT_DEFINITIONS[type].fixedUpdate(e, c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "entitybase.h"
|
||||
|
||||
#define X(enumName, type, field, init, dispose, fixedUpdate) \
|
||||
// do nothing
|
||||
#include "componentlist.h"
|
||||
#undef X
|
||||
|
||||
typedef union {
|
||||
#define X(enumName, type, field, init, dispose, fixedUpdate) type field;
|
||||
#include "componentlist.h"
|
||||
#undef X
|
||||
} componentdata_t;
|
||||
|
||||
typedef struct {
|
||||
const char_t *enumName;
|
||||
const char_t *name;
|
||||
void (*init)(const entityid_t, const componentid_t);
|
||||
void (*dispose)(const entityid_t, const componentid_t);
|
||||
void (*fixedUpdate)(const entityid_t, const componentid_t);
|
||||
} componentdefinition_t;
|
||||
|
||||
typedef enum {
|
||||
COMPONENT_TYPE_NULL,
|
||||
|
||||
#define X(enumName, type, field, init, dispose, fixedUpdate) \
|
||||
COMPONENT_TYPE_##enumName,
|
||||
#include "componentlist.h"
|
||||
#undef X
|
||||
|
||||
COMPONENT_TYPE_COUNT
|
||||
} componenttype_t;
|
||||
|
||||
typedef struct {
|
||||
componenttype_t type;
|
||||
componentdata_t data;
|
||||
} component_t;
|
||||
|
||||
extern componentdefinition_t COMPONENT_DEFINITIONS[];
|
||||
|
||||
/**
|
||||
* Initializes a component of the given type for the entity with component ID.
|
||||
*
|
||||
* @param entityId The entity ID.
|
||||
* @param componentId The component ID.
|
||||
* @param type The type of the component to initialize.
|
||||
*/
|
||||
void componentInit(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const componenttype_t type
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the pointer to the data of a component for the entity with component ID.
|
||||
*
|
||||
* @param entityId The entity ID.
|
||||
* @param componentId The component ID.
|
||||
* @param type The type of the component to get, only used for assertion.
|
||||
* @return A pointer to the component data.
|
||||
*/
|
||||
void * componentGetData(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const componenttype_t type
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the index of a component for the entity with component ID.
|
||||
*
|
||||
* @param entityId The entity ID.
|
||||
* @param componentId The component ID.
|
||||
* @return The index of the component in the component array.
|
||||
*/
|
||||
componentindex_t componentGetIndex(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the entity IDs of all entities with a component of the given type.
|
||||
*
|
||||
* @param type The type of the component to get entities for.
|
||||
* @param outEntities An array to write the entity IDs to, must be at least
|
||||
* ENTITY_COUNT_MAX in size.
|
||||
* @param outComponents An array to write the component IDs to.
|
||||
* @return The number of entity IDs written to outEntities.
|
||||
*/
|
||||
entityid_t componentGetEntitiesWithComponent(
|
||||
const componenttype_t type,
|
||||
entityid_t outEntities[ENTITY_COUNT_MAX],
|
||||
componentid_t outComponents[ENTITY_COUNT_MAX]
|
||||
);
|
||||
|
||||
/**
|
||||
* Disposes of a component for the entity with component ID.
|
||||
*
|
||||
* @param entityId The entity ID.
|
||||
* @param componentId The component ID.
|
||||
*/
|
||||
void componentDispose(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Calls fixedUpdate on every active component that defines one. Intended to
|
||||
* be called once per fixed timestep - see entityManagerFixedUpdate().
|
||||
*/
|
||||
void componentFixedUpdateAll(void);
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
add_subdirectory(display)
|
||||
add_subdirectory(physics)
|
||||
add_subdirectory(script)
|
||||
add_subdirectory(trigger)
|
||||
@@ -0,0 +1,12 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
entityposition.c
|
||||
entitycamera.c
|
||||
entityrenderable.c
|
||||
)
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entity/entitymanager.h"
|
||||
#include "entity/entity.h"
|
||||
#include "entity/component/display/entityposition.h"
|
||||
#include "display/framebuffer/framebuffer.h"
|
||||
#include "display/screen/screen.h"
|
||||
|
||||
void entityCameraInit(const entityid_t ent, const componentid_t comp) {
|
||||
entitycamera_t *cam = (entitycamera_t *)componentGetData(
|
||||
ent, comp, COMPONENT_TYPE_CAMERA
|
||||
);
|
||||
cam->nearClip = 0.1f;
|
||||
cam->farClip = 100.0f;
|
||||
cam->projType = ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE;
|
||||
cam->perspective.fov = glm_rad(45.0f);
|
||||
}
|
||||
|
||||
void entityCameraGetProjection(
|
||||
const entityid_t ent,
|
||||
const componentid_t comp,
|
||||
mat4 out
|
||||
) {
|
||||
entitycamera_t *cam = (entitycamera_t *)componentGetData(
|
||||
ent, comp, COMPONENT_TYPE_CAMERA
|
||||
);
|
||||
|
||||
if(
|
||||
cam->projType == ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE ||
|
||||
cam->projType == ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED
|
||||
) {
|
||||
glm_mat4_identity(out);
|
||||
glm_perspective(
|
||||
cam->perspective.fov,
|
||||
SCREEN.aspect,
|
||||
cam->nearClip,
|
||||
cam->farClip,
|
||||
out
|
||||
);
|
||||
|
||||
if(cam->projType == ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED) {
|
||||
out[1][1] *= -1.0f;
|
||||
}
|
||||
} else if(cam->projType == ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) {
|
||||
glm_mat4_identity(out);
|
||||
glm_ortho(
|
||||
cam->orthographic.left,
|
||||
cam->orthographic.right,
|
||||
cam->orthographic.top,
|
||||
cam->orthographic.bottom,
|
||||
cam->nearClip,
|
||||
cam->farClip,
|
||||
out
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
entityid_t entityCameraGetCurrent(void) {
|
||||
entityid_t camEnts[ENTITY_COUNT_MAX];
|
||||
componentid_t camComps[ENTITY_COUNT_MAX];
|
||||
entityid_t count = componentGetEntitiesWithComponent(
|
||||
COMPONENT_TYPE_CAMERA, camEnts, camComps
|
||||
);
|
||||
if(count == 0) return ENTITY_COUNT_MAX;
|
||||
return camEnts[0];
|
||||
}
|
||||
|
||||
void entityCameraGetForward(const entityid_t entityId, vec2 out) {
|
||||
componentid_t posComp = entityGetComponent(entityId, COMPONENT_TYPE_POSITION);
|
||||
entityposition_t *pos = entityPositionGet(entityId, posComp);
|
||||
// View matrix column layout: M[col][row],
|
||||
// forward = {-M[0][2], -M[1][2], -M[2][2]}
|
||||
float_t fx = -pos->worldTransform[0][2];
|
||||
float_t fz = -pos->worldTransform[2][2];
|
||||
float_t len = sqrtf(fx * fx + fz * fz);
|
||||
if(len > 1e-6f) { fx /= len; fz /= len; }
|
||||
out[0] = fx;
|
||||
out[1] = fz;
|
||||
}
|
||||
|
||||
void entityCameraGetRight(const entityid_t entityId, vec2 out) {
|
||||
componentid_t posComp = entityGetComponent(entityId, COMPONENT_TYPE_POSITION);
|
||||
entityposition_t *pos = entityPositionGet(entityId, posComp);
|
||||
// View matrix column layout: right = {M[0][0], M[1][0], M[2][0]}
|
||||
float_t rx = pos->worldTransform[0][0];
|
||||
float_t rz = pos->worldTransform[2][0];
|
||||
float_t len = sqrtf(rx * rx + rz * rz);
|
||||
if(len > 1e-6f) { rx /= len; rz /= len; }
|
||||
out[0] = rx;
|
||||
out[1] = rz;
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "entity/entitybase.h"
|
||||
|
||||
typedef enum {
|
||||
ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE,
|
||||
ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED,
|
||||
ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC
|
||||
} entitycameraprojectiontype_t;
|
||||
|
||||
typedef struct {
|
||||
union {
|
||||
struct {
|
||||
float_t fov;
|
||||
} perspective;
|
||||
|
||||
struct {
|
||||
float_t left;
|
||||
float_t right;
|
||||
float_t top;
|
||||
float_t bottom;
|
||||
} orthographic;
|
||||
};
|
||||
|
||||
float_t nearClip;
|
||||
float_t farClip;
|
||||
entitycameraprojectiontype_t projType;
|
||||
} entitycamera_t;
|
||||
|
||||
/**
|
||||
* Initializes an entity camera component.
|
||||
*
|
||||
* @param ent The entity ID.
|
||||
* @param comp The component ID.
|
||||
*/
|
||||
void entityCameraInit(const entityid_t ent, const componentid_t comp);
|
||||
|
||||
/**
|
||||
* Renders out the projection matrix for the given camera.
|
||||
*
|
||||
* @param ent The entity ID.
|
||||
* @param comp The component ID.
|
||||
* @param out The output projection matrix.
|
||||
*/
|
||||
void entityCameraGetProjection(
|
||||
const entityid_t ent,
|
||||
const componentid_t comp,
|
||||
mat4 out
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns the entity ID of the first active camera, or ENTITY_COUNT_MAX if
|
||||
* none are active.
|
||||
*/
|
||||
entityid_t entityCameraGetCurrent(void);
|
||||
|
||||
/**
|
||||
* Gets the camera's horizontal forward direction (XZ plane) from its position
|
||||
* component. Automatically finds the position component on the entity.
|
||||
*
|
||||
* @param entityId The camera entity ID.
|
||||
* @param out Output vec2: {forwardX, forwardZ} normalized.
|
||||
*/
|
||||
void entityCameraGetForward(const entityid_t entityId, vec2 out);
|
||||
|
||||
/**
|
||||
* Gets the camera's horizontal right direction (XZ plane) from its position
|
||||
* component. Automatically finds the position component on the entity.
|
||||
*
|
||||
* @param entityId The camera entity ID.
|
||||
* @param out Output vec2: {rightX, rightZ} normalized.
|
||||
*/
|
||||
void entityCameraGetRight(const entityid_t entityId, vec2 out);
|
||||
@@ -0,0 +1,316 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entity/entitymanager.h"
|
||||
|
||||
// Lazily recompute worldTransform from the parent chain.
|
||||
static void entityPositionUpdateWorld(entityposition_t *pos) {
|
||||
if(!pos->dirty) return;
|
||||
|
||||
if(pos->parentEntityId == ENTITY_ID_INVALID) {
|
||||
glm_mat4_copy(pos->localTransform, pos->worldTransform);
|
||||
} else {
|
||||
entityposition_t *parent = componentGetData(
|
||||
pos->parentEntityId, pos->parentComponentId, COMPONENT_TYPE_POSITION
|
||||
);
|
||||
entityPositionUpdateWorld(parent);
|
||||
glm_mat4_mul(parent->worldTransform, pos->localTransform, pos->worldTransform);
|
||||
}
|
||||
|
||||
pos->dirty = false;
|
||||
}
|
||||
|
||||
void entityPositionMarkDirty(entityposition_t *pos) {
|
||||
pos->dirty = true;
|
||||
for(uint8_t i = 0; i < pos->childCount; i++) {
|
||||
entityposition_t *child = componentGetData(
|
||||
pos->childEntityIds[i], pos->childComponentIds[i], COMPONENT_TYPE_POSITION
|
||||
);
|
||||
entityPositionMarkDirty(child);
|
||||
}
|
||||
}
|
||||
|
||||
void entityPositionInit(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
entityposition_t *pos = componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_POSITION
|
||||
);
|
||||
|
||||
glm_vec3_zero(pos->position);
|
||||
glm_vec3_zero(pos->rotation);
|
||||
glm_vec3_one(pos->scale);
|
||||
glm_mat4_identity(pos->localTransform);
|
||||
glm_mat4_identity(pos->worldTransform);
|
||||
pos->dirty = false;
|
||||
pos->parentEntityId = ENTITY_ID_INVALID;
|
||||
pos->parentComponentId = COMPONENT_ID_INVALID;
|
||||
pos->childCount = 0;
|
||||
}
|
||||
|
||||
void entityPositionLookAt(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
vec3 target,
|
||||
vec3 up,
|
||||
vec3 eye
|
||||
) {
|
||||
entityposition_t *pos = componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_POSITION
|
||||
);
|
||||
glm_lookat(eye, target, up, pos->localTransform);
|
||||
entityPositionDecompose(pos);
|
||||
entityPositionMarkDirty(pos);
|
||||
}
|
||||
|
||||
void entityPositionGetTransform(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
mat4 dest
|
||||
) {
|
||||
entityposition_t *pos = componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_POSITION
|
||||
);
|
||||
entityPositionUpdateWorld(pos);
|
||||
glm_mat4_copy(pos->worldTransform, dest);
|
||||
}
|
||||
|
||||
void entityPositionGetLocalTransform(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
mat4 dest
|
||||
) {
|
||||
entityposition_t *pos = componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_POSITION
|
||||
);
|
||||
glm_mat4_copy(pos->localTransform, dest);
|
||||
}
|
||||
|
||||
void entityPositionGetPosition(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
vec3 dest
|
||||
) {
|
||||
entityposition_t *pos = componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_POSITION
|
||||
);
|
||||
glm_vec3_copy(pos->position, dest);
|
||||
}
|
||||
|
||||
void entityPositionSetPosition(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
vec3 position
|
||||
) {
|
||||
entityposition_t *pos = componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_POSITION
|
||||
);
|
||||
glm_vec3_copy(position, pos->position);
|
||||
entityPositionRebuild(pos);
|
||||
}
|
||||
|
||||
void entityPositionGetRotation(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
vec3 dest
|
||||
) {
|
||||
entityposition_t *pos = componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_POSITION
|
||||
);
|
||||
glm_vec3_copy(pos->rotation, dest);
|
||||
}
|
||||
|
||||
void entityPositionSetRotation(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
vec3 rotation
|
||||
) {
|
||||
entityposition_t *pos = componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_POSITION
|
||||
);
|
||||
glm_vec3_copy(rotation, pos->rotation);
|
||||
entityPositionRebuild(pos);
|
||||
}
|
||||
|
||||
void entityPositionGetScale(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
vec3 dest
|
||||
) {
|
||||
entityposition_t *pos = componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_POSITION
|
||||
);
|
||||
glm_vec3_copy(pos->scale, dest);
|
||||
}
|
||||
|
||||
void entityPositionSetScale(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
vec3 scale
|
||||
) {
|
||||
entityposition_t *pos = componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_POSITION
|
||||
);
|
||||
glm_vec3_copy(scale, pos->scale);
|
||||
entityPositionRebuild(pos);
|
||||
}
|
||||
|
||||
void entityPositionSetParent(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const entityid_t parentEntityId,
|
||||
const componentid_t parentComponentId
|
||||
) {
|
||||
entityposition_t *pos = componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_POSITION
|
||||
);
|
||||
|
||||
// Remove from old parent's child list.
|
||||
if(pos->parentEntityId != ENTITY_ID_INVALID) {
|
||||
entityposition_t *oldParent = componentGetData(
|
||||
pos->parentEntityId, pos->parentComponentId, COMPONENT_TYPE_POSITION
|
||||
);
|
||||
for(uint8_t i = 0; i < oldParent->childCount; i++) {
|
||||
if(
|
||||
oldParent->childEntityIds[i] == entityId &&
|
||||
oldParent->childComponentIds[i] == componentId
|
||||
) {
|
||||
oldParent->childCount--;
|
||||
for(uint8_t j = i; j < oldParent->childCount; j++) {
|
||||
oldParent->childEntityIds[j] = oldParent->childEntityIds[j + 1];
|
||||
oldParent->childComponentIds[j] = oldParent->childComponentIds[j + 1];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pos->parentEntityId = parentEntityId;
|
||||
pos->parentComponentId = parentComponentId;
|
||||
|
||||
// Register with new parent.
|
||||
if(parentEntityId != ENTITY_ID_INVALID) {
|
||||
entityposition_t *parent = componentGetData(
|
||||
parentEntityId, parentComponentId, COMPONENT_TYPE_POSITION
|
||||
);
|
||||
if(parent->childCount < ENTITY_POSITION_CHILDREN_MAX) {
|
||||
parent->childEntityIds[parent->childCount] = entityId;
|
||||
parent->childComponentIds[parent->childCount] = componentId;
|
||||
parent->childCount++;
|
||||
}
|
||||
}
|
||||
|
||||
entityPositionMarkDirty(pos);
|
||||
}
|
||||
|
||||
entityposition_t *entityPositionGet(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
return componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_POSITION
|
||||
);
|
||||
}
|
||||
|
||||
void entityPositionRebuild(entityposition_t *pos) {
|
||||
glm_mat4_identity(pos->localTransform);
|
||||
glm_translate(pos->localTransform, pos->position);
|
||||
glm_rotate_x(pos->localTransform, pos->rotation[0], pos->localTransform);
|
||||
glm_rotate_y(pos->localTransform, pos->rotation[1], pos->localTransform);
|
||||
glm_rotate_z(pos->localTransform, pos->rotation[2], pos->localTransform);
|
||||
glm_scale(pos->localTransform, pos->scale);
|
||||
entityPositionMarkDirty(pos);
|
||||
}
|
||||
|
||||
void entityPositionDisposeDeep(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
entityposition_t *pos = entityPositionGet(entityId, componentId);
|
||||
|
||||
// Detach from parent so the parent's child list stays consistent.
|
||||
if(pos->parentEntityId != ENTITY_ID_INVALID) {
|
||||
entityPositionSetParent(entityId, componentId, ENTITY_ID_INVALID, COMPONENT_ID_INVALID);
|
||||
}
|
||||
|
||||
// Copy the child list before disposing self (entityDispose invalidates pos).
|
||||
uint8_t childCount = pos->childCount;
|
||||
entityid_t childEntityIds[ENTITY_POSITION_CHILDREN_MAX];
|
||||
componentid_t childComponentIds[ENTITY_POSITION_CHILDREN_MAX];
|
||||
for(uint8_t i = 0; i < childCount; i++) {
|
||||
childEntityIds[i] = pos->childEntityIds[i];
|
||||
childComponentIds[i] = pos->childComponentIds[i];
|
||||
// Sever the child's parent link so it won't try to modify our disposed data.
|
||||
entityposition_t *child = entityPositionGet(childEntityIds[i], childComponentIds[i]);
|
||||
child->parentEntityId = ENTITY_ID_INVALID;
|
||||
child->parentComponentId = COMPONENT_ID_INVALID;
|
||||
}
|
||||
|
||||
entityDispose(entityId);
|
||||
|
||||
for(uint8_t i = 0; i < childCount; i++) {
|
||||
entityPositionDisposeDeep(childEntityIds[i], childComponentIds[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void entityPositionDecompose(entityposition_t *pos) {
|
||||
// Translation: column 3
|
||||
pos->position[0] = pos->localTransform[3][0];
|
||||
pos->position[1] = pos->localTransform[3][1];
|
||||
pos->position[2] = pos->localTransform[3][2];
|
||||
|
||||
// Scale: length of each basis column (xyz only)
|
||||
pos->scale[0] = sqrtf(
|
||||
pos->localTransform[0][0] * pos->localTransform[0][0] +
|
||||
pos->localTransform[0][1] * pos->localTransform[0][1] +
|
||||
pos->localTransform[0][2] * pos->localTransform[0][2]
|
||||
);
|
||||
pos->scale[1] = sqrtf(
|
||||
pos->localTransform[1][0] * pos->localTransform[1][0] +
|
||||
pos->localTransform[1][1] * pos->localTransform[1][1] +
|
||||
pos->localTransform[1][2] * pos->localTransform[1][2]
|
||||
);
|
||||
pos->scale[2] = sqrtf(
|
||||
pos->localTransform[2][0] * pos->localTransform[2][0] +
|
||||
pos->localTransform[2][1] * pos->localTransform[2][1] +
|
||||
pos->localTransform[2][2] * pos->localTransform[2][2]
|
||||
);
|
||||
|
||||
// Normalize columns to isolate the rotation matrix.
|
||||
float invS0 = pos->scale[0] > 0.0f ? 1.0f / pos->scale[0] : 0.0f;
|
||||
float invS1 = pos->scale[1] > 0.0f ? 1.0f / pos->scale[1] : 0.0f;
|
||||
float invS2 = pos->scale[2] > 0.0f ? 1.0f / pos->scale[2] : 0.0f;
|
||||
|
||||
mat4 r;
|
||||
glm_mat4_identity(r);
|
||||
r[0][0] = pos->localTransform[0][0] * invS0;
|
||||
r[0][1] = pos->localTransform[0][1] * invS0;
|
||||
r[0][2] = pos->localTransform[0][2] * invS0;
|
||||
r[1][0] = pos->localTransform[1][0] * invS1;
|
||||
r[1][1] = pos->localTransform[1][1] * invS1;
|
||||
r[1][2] = pos->localTransform[1][2] * invS1;
|
||||
r[2][0] = pos->localTransform[2][0] * invS2;
|
||||
r[2][1] = pos->localTransform[2][1] * invS2;
|
||||
r[2][2] = pos->localTransform[2][2] * invS2;
|
||||
|
||||
// Extract XYZ euler angles (R = Rx * Ry * Rz, column-major)
|
||||
float sinBeta = glm_clamp(r[2][0], -1.0f, 1.0f);
|
||||
pos->rotation[1] = asinf(sinBeta);
|
||||
float cosBeta = cosf(pos->rotation[1]);
|
||||
|
||||
if(fabsf(cosBeta) > 1e-6f) {
|
||||
pos->rotation[0] = atan2f(-r[2][1], r[2][2]);
|
||||
pos->rotation[2] = atan2f(-r[1][0], r[0][0]);
|
||||
} else {
|
||||
// Gimbal lock: pin Z to 0, recover X.
|
||||
pos->rotation[2] = 0.0f;
|
||||
pos->rotation[0] = (sinBeta > 0.0f)
|
||||
? atan2f(r[0][1], r[1][1])
|
||||
: -atan2f(r[0][1], r[1][1]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "entity/entitybase.h"
|
||||
|
||||
#define ENTITY_POSITION_CHILDREN_MAX 8
|
||||
|
||||
typedef struct {
|
||||
mat4 localTransform;
|
||||
mat4 worldTransform;
|
||||
vec3 position;
|
||||
vec3 rotation;
|
||||
vec3 scale;
|
||||
bool dirty;
|
||||
entityid_t parentEntityId;
|
||||
componentid_t parentComponentId;
|
||||
uint8_t childCount;
|
||||
entityid_t childEntityIds[ENTITY_POSITION_CHILDREN_MAX];
|
||||
componentid_t childComponentIds[ENTITY_POSITION_CHILDREN_MAX];
|
||||
} entityposition_t;
|
||||
|
||||
/**
|
||||
* Initialize the entity position component.
|
||||
*/
|
||||
void entityPositionInit(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Transforms the entity's local transform to look at a target point.
|
||||
*
|
||||
* @param entityId The entity ID.
|
||||
* @param componentId The component ID.
|
||||
* @param target The target point to look at.
|
||||
* @param up The up vector.
|
||||
* @param eye The eye/camera position.
|
||||
*/
|
||||
void entityPositionLookAt(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
vec3 target,
|
||||
vec3 up,
|
||||
vec3 eye
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the world-space transform matrix, recomputing it lazily if dirty.
|
||||
*
|
||||
* @param entityId The entity ID.
|
||||
* @param componentId The component ID.
|
||||
* @param dest Destination matrix.
|
||||
*/
|
||||
void entityPositionGetTransform(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
mat4 dest
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the local transform matrix (does not include parent transforms).
|
||||
*
|
||||
* @param entityId The entity ID.
|
||||
* @param componentId The component ID.
|
||||
* @param dest Destination matrix.
|
||||
*/
|
||||
void entityPositionGetLocalTransform(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
mat4 dest
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the cached local position.
|
||||
*/
|
||||
void entityPositionGetPosition(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
vec3 dest
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets the local position and marks the world transform dirty.
|
||||
*/
|
||||
void entityPositionSetPosition(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
vec3 position
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the cached local euler rotation (XYZ, radians).
|
||||
*/
|
||||
void entityPositionGetRotation(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
vec3 dest
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets the local euler rotation (XYZ, radians) and marks the world transform dirty.
|
||||
*/
|
||||
void entityPositionSetRotation(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
vec3 rotation
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the cached local scale.
|
||||
*/
|
||||
void entityPositionGetScale(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
vec3 dest
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets the local scale and marks the world transform dirty.
|
||||
*/
|
||||
void entityPositionSetScale(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
vec3 scale
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets the parent of this entity's position component.
|
||||
* Pass ENTITY_ID_INVALID / COMPONENT_ID_INVALID to detach from any parent.
|
||||
*
|
||||
* @param entityId The child entity ID.
|
||||
* @param componentId The child component ID.
|
||||
* @param parentEntityId The parent entity ID.
|
||||
* @param parentComponentId The parent component ID.
|
||||
*/
|
||||
void entityPositionSetParent(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const entityid_t parentEntityId,
|
||||
const componentid_t parentComponentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns a direct pointer to the entity position component data.
|
||||
* After modifying localTransform directly, call entityPositionMarkDirty().
|
||||
*/
|
||||
entityposition_t *entityPositionGet(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Rebuilds the local transform matrix from the cached position/rotation/scale,
|
||||
* then marks this node and all descendants dirty.
|
||||
*/
|
||||
void entityPositionRebuild(entityposition_t *pos);
|
||||
|
||||
/**
|
||||
* Marks this node and all descendants as having a stale world transform.
|
||||
*/
|
||||
void entityPositionMarkDirty(entityposition_t *pos);
|
||||
|
||||
/**
|
||||
* Disposes this entity and all of its position-component descendants
|
||||
* recursively. Detaches from any parent before destroying.
|
||||
*
|
||||
* @param entityId The root entity ID.
|
||||
* @param componentId The root position component ID.
|
||||
*/
|
||||
void entityPositionDisposeDeep(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Decomposes the local transform matrix back into the position, rotation
|
||||
* (XYZ euler, radians), and scale cache fields.
|
||||
*/
|
||||
void entityPositionDecompose(entityposition_t *pos);
|
||||
@@ -0,0 +1,149 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entityrenderable.h"
|
||||
#include "entity/entitymanager.h"
|
||||
#include "display/shader/shaderunlit.h"
|
||||
#include "display/mesh/cube.h"
|
||||
|
||||
void entityRenderableInit(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
entityrenderable_t *r = componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
r->type = ENTITY_RENDERABLE_TYPE_MATERIAL;
|
||||
r->mesh = &CUBE_MESH_SIMPLE;
|
||||
r->shader = &SHADER_UNLIT;
|
||||
r->material.unlit.color = COLOR_WHITE;
|
||||
}
|
||||
|
||||
entityrenderabletype_t entityRenderableGetType(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
entityrenderable_t *r = componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
return r->type;
|
||||
}
|
||||
|
||||
void entityRenderableSetType(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const entityrenderabletype_t type
|
||||
) {
|
||||
entityrenderable_t *r = componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
r->type = type;
|
||||
}
|
||||
|
||||
mesh_t * entityRenderableGetMesh(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
entityrenderable_t *r = componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
return r->mesh;
|
||||
}
|
||||
|
||||
void entityRenderableSetMesh(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
mesh_t *mesh
|
||||
) {
|
||||
entityrenderable_t *r = componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
r->mesh = mesh;
|
||||
}
|
||||
|
||||
shader_t * entityRenderableGetShader(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
entityrenderable_t *r = componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
return r->shader;
|
||||
}
|
||||
|
||||
void entityRenderableSetShader(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
shader_t *shader
|
||||
) {
|
||||
entityrenderable_t *r = componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
r->shader = shader;
|
||||
}
|
||||
|
||||
shadermaterial_t * entityRenderableGetShaderMaterial(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
entityrenderable_t *r = componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
return &r->material;
|
||||
}
|
||||
|
||||
void entityRenderableSetColor(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const color_t color
|
||||
) {
|
||||
entityrenderable_t *r = componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
r->material.unlit.color = color;
|
||||
}
|
||||
|
||||
void entityRenderableSpriteBatchAdd(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const spritebatchsprite_t *sprite
|
||||
) {
|
||||
entityrenderable_t *r = componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
if(r->spritebatch.spriteCount >= ENTITY_RENDERABLE_SPRITEBATCH_SPRITES_MAX) return;
|
||||
r->spritebatch.sprites[r->spritebatch.spriteCount++] = *sprite;
|
||||
}
|
||||
|
||||
void entityRenderableSpriteBatchClear(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
entityrenderable_t *r = componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
r->spritebatch.spriteCount = 0;
|
||||
}
|
||||
|
||||
void entityRenderableDispose(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
entityrenderable_t *r = componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
if(
|
||||
r->type == ENTITY_RENDERABLE_TYPE_CALLBACK &&
|
||||
r->userFree &&
|
||||
r->user
|
||||
) {
|
||||
r->userFree(r->user);
|
||||
r->user = NULL;
|
||||
}
|
||||
r->mesh = NULL;
|
||||
r->shader = NULL;
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "entity/entitybase.h"
|
||||
#include "display/mesh/mesh.h"
|
||||
#include "display/shader/shadermaterial.h"
|
||||
#include "display/spritebatch/spritebatch.h"
|
||||
|
||||
#define ENTITY_RENDERABLE_SPRITEBATCH_SPRITES_MAX 64
|
||||
|
||||
typedef enum {
|
||||
ENTITY_RENDERABLE_TYPE_MATERIAL = 0,
|
||||
ENTITY_RENDERABLE_TYPE_SPRITEBATCH,
|
||||
ENTITY_RENDERABLE_TYPE_CALLBACK,
|
||||
} entityrenderabletype_t;
|
||||
|
||||
typedef errorret_t (*entityrenderablecallback_t)(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const mat4 view,
|
||||
const mat4 proj,
|
||||
const mat4 model,
|
||||
void *user
|
||||
);
|
||||
|
||||
typedef struct {
|
||||
spritebatchsprite_t sprites[ENTITY_RENDERABLE_SPRITEBATCH_SPRITES_MAX];
|
||||
uint16_t spriteCount;
|
||||
} entityrenderablespritebatch_t;
|
||||
|
||||
typedef struct {
|
||||
entityrenderabletype_t type;
|
||||
shader_t *shader;
|
||||
union {
|
||||
struct {
|
||||
mesh_t *mesh;
|
||||
shadermaterial_t material;
|
||||
};
|
||||
entityrenderablespritebatch_t spritebatch;
|
||||
struct {
|
||||
entityrenderablecallback_t callback;
|
||||
void (*userFree)(void *user);
|
||||
void *user;
|
||||
};
|
||||
};
|
||||
} entityrenderable_t;
|
||||
|
||||
/**
|
||||
* Initializes the entity renderable component. Defaults to
|
||||
* ENTITY_RENDERABLE_TYPE_MATERIAL, the unlit shader, white color, no mesh.
|
||||
*/
|
||||
void entityRenderableInit(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Disposes the entity renderable component, freeing any callback user data.
|
||||
*/
|
||||
void entityRenderableDispose(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the renderable type.
|
||||
*/
|
||||
entityrenderabletype_t entityRenderableGetType(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets the renderable type.
|
||||
*/
|
||||
void entityRenderableSetType(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const entityrenderabletype_t type
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the mesh pointer (ENTITY_RENDERABLE_TYPE_MATERIAL only).
|
||||
*/
|
||||
mesh_t * entityRenderableGetMesh(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets the mesh pointer (ENTITY_RENDERABLE_TYPE_MATERIAL only).
|
||||
*/
|
||||
void entityRenderableSetMesh(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
mesh_t *mesh
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the shader pointer.
|
||||
*/
|
||||
shader_t * entityRenderableGetShader(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets the shader pointer.
|
||||
*/
|
||||
void entityRenderableSetShader(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
shader_t *shader
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets a pointer to the shader material union
|
||||
* (ENTITY_RENDERABLE_TYPE_MATERIAL only).
|
||||
*/
|
||||
shadermaterial_t * entityRenderableGetShaderMaterial(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets the unlit color (ENTITY_RENDERABLE_TYPE_MATERIAL only).
|
||||
*/
|
||||
void entityRenderableSetColor(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const color_t color
|
||||
);
|
||||
|
||||
/**
|
||||
* Appends a sprite to the spritebatch renderable
|
||||
* (ENTITY_RENDERABLE_TYPE_SPRITEBATCH only).
|
||||
* Does nothing if the sprite buffer is full.
|
||||
*/
|
||||
void entityRenderableSpriteBatchAdd(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const spritebatchsprite_t *sprite
|
||||
);
|
||||
|
||||
/**
|
||||
* Clears all buffered sprites from the spritebatch renderable
|
||||
* (ENTITY_RENDERABLE_TYPE_SPRITEBATCH only).
|
||||
*/
|
||||
void entityRenderableSpriteBatchClear(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
@@ -0,0 +1,10 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
entityphysics.c
|
||||
)
|
||||
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entityphysics.h"
|
||||
#include "entity/entitymanager.h"
|
||||
#include "entity/component/display/entityposition.h"
|
||||
#include "physics/physicsmanager.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
|
||||
void entityPhysicsInit(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
entityphysics_t *phys = entityPhysicsGet(entityId, componentId);
|
||||
assertNotNull(phys, "Failed to get physics component data");
|
||||
|
||||
memoryZero(phys, sizeof(entityphysics_t));
|
||||
|
||||
// Default to cube
|
||||
phys->type = PHYSICS_BODY_DYNAMIC;
|
||||
phys->shape.type = PHYSICS_SHAPE_CUBE;
|
||||
phys->shape.data.cube.halfExtents[0] = 0.5f;
|
||||
phys->shape.data.cube.halfExtents[1] = 0.5f;
|
||||
phys->shape.data.cube.halfExtents[2] = 0.5f;
|
||||
phys->gravityScale = 1.0f;
|
||||
phys->onGround = false;
|
||||
}
|
||||
|
||||
entityphysics_t *entityPhysicsGet(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
return componentGetData(entityId, componentId, COMPONENT_TYPE_PHYSICS);
|
||||
}
|
||||
|
||||
void entityPhysicsSetShape(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const physicsshape_t shape
|
||||
) {
|
||||
entityphysics_t *phys = entityPhysicsGet(entityId, componentId);
|
||||
assertNotNull(phys, "Failed to get physics component data");
|
||||
phys->shape = shape;
|
||||
// TODO: Do I need to reset the state for ground/active?
|
||||
}
|
||||
|
||||
physicsshape_t entityPhysicsGetShape(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
entityphysics_t *phys = entityPhysicsGet(entityId, componentId);
|
||||
assertNotNull(phys, "Failed to get physics component data");
|
||||
return phys->shape;
|
||||
}
|
||||
|
||||
void entityPhysicsGetVelocity(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
vec3 dest
|
||||
) {
|
||||
entityphysics_t *phys = entityPhysicsGet(entityId, componentId);
|
||||
assertNotNull(phys, "Failed to get physics component data");
|
||||
|
||||
glm_vec3_copy(phys->velocity, dest);
|
||||
}
|
||||
|
||||
void entityPhysicsSetVelocity(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
vec3 velocity
|
||||
) {
|
||||
entityphysics_t *phys = entityPhysicsGet(entityId, componentId);
|
||||
assertNotNull(phys, "Failed to get physics component data");
|
||||
glm_vec3_copy(velocity, phys->velocity);
|
||||
}
|
||||
|
||||
void entityPhysicsApplyImpulse(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
vec3 impulse
|
||||
) {
|
||||
entityphysics_t *phys = entityPhysicsGet(entityId, componentId);
|
||||
assertNotNull(phys, "Failed to get physics component data");
|
||||
|
||||
if(phys->type == PHYSICS_BODY_STATIC) return;
|
||||
glm_vec3_add(phys->velocity, impulse, phys->velocity);
|
||||
}
|
||||
|
||||
bool_t entityPhysicsIsOnGround(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
entityphysics_t *phys = entityPhysicsGet(entityId, componentId);
|
||||
assertNotNull(phys, "Failed to get physics component data");
|
||||
return phys->onGround;
|
||||
}
|
||||
|
||||
void entityPhysicsSetBodyType(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const physicsbodytype_t type
|
||||
) {
|
||||
entityphysics_t *phys = entityPhysicsGet(entityId, componentId);
|
||||
assertNotNull(phys, "Failed to get physics component data");
|
||||
phys->type = type;
|
||||
}
|
||||
|
||||
physicsbodytype_t entityPhysicsGetBodyType(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
entityphysics_t *phys = entityPhysicsGet(entityId, componentId);
|
||||
assertNotNull(phys, "Failed to get physics component data");
|
||||
return phys->type;
|
||||
}
|
||||
|
||||
void entityPhysicsDispose(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "entity/entitybase.h"
|
||||
#include "physics/physicsshape.h"
|
||||
#include "physics/physicsbodytype.h"
|
||||
|
||||
typedef struct {
|
||||
physicsbodytype_t type;
|
||||
physicsshape_t shape;
|
||||
vec3 velocity;
|
||||
float_t gravityScale;
|
||||
bool_t onGround;
|
||||
} entityphysics_t;
|
||||
|
||||
/**
|
||||
* Initializes the physics component: allocates a body in PHYSICS_WORLD.
|
||||
* Asserts if the world body limit is reached.
|
||||
*/
|
||||
void entityPhysicsInit(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the underlying physics structure (temporarily) for the given entity.
|
||||
* This is really just intended for doing operations faster than using the
|
||||
* getters and setters, but it is preferred that you use those.
|
||||
*
|
||||
* @param entityId The entity ID.
|
||||
* @param componentId The component ID.
|
||||
* @return The physics component data for the given entity and component ID.
|
||||
*/
|
||||
entityphysics_t *entityPhysicsGet(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets the shape of the entity's physics body. This will not reset the body
|
||||
* state, so if you change from a cube to a sphere, it will keep the same
|
||||
* velocity and onGround state.
|
||||
*
|
||||
* @param entityId The entity ID.
|
||||
* @param componentId The component ID.
|
||||
* @param shape The new shape to set on the physics body.
|
||||
*/
|
||||
void entityPhysicsSetShape(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const physicsshape_t shape
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the shape of the entity's physics body.
|
||||
*
|
||||
* @param entityId The entity ID.
|
||||
* @param componentId The component ID.
|
||||
* @return The shape of the physics body.
|
||||
*/
|
||||
physicsshape_t entityPhysicsGetShape(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the velocity of the entity's physics body.
|
||||
*
|
||||
* @param entityId The entity ID.
|
||||
* @param componentId The component ID.
|
||||
* @param dest The destination vec3 to write the velocity to.
|
||||
*/
|
||||
void entityPhysicsGetVelocity(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
vec3 dest
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets the velocity of the entity's physics body. This is not an impulse, so
|
||||
* it will be affected by mass and drag.
|
||||
*
|
||||
* @param entityId The entity ID.
|
||||
* @param componentId The component ID.
|
||||
* @param velocity The new velocity to set on the physics body.
|
||||
*/
|
||||
void entityPhysicsSetVelocity(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
vec3 velocity
|
||||
);
|
||||
|
||||
/**
|
||||
* Applies an impulse to the entity's physics body. This is an immediate
|
||||
* velocity change that is not affected by mass or drag. No-op on STATIC bodies.
|
||||
*
|
||||
* @param entityId The entity ID.
|
||||
* @param componentId The component ID.
|
||||
* @param impulse The impulse to apply to the physics body.
|
||||
*/
|
||||
void entityPhysicsApplyImpulse(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
vec3 impulse
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns true if the entity's physics body rested on a surface during the last
|
||||
* step or move.
|
||||
*
|
||||
* @param entityId The entity ID.
|
||||
* @param componentId The component ID.
|
||||
* @return True if the body is on the ground, false otherwise.
|
||||
*/
|
||||
bool_t entityPhysicsIsOnGround(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets the body type of the entity's physics body.
|
||||
*
|
||||
* @param entityId The entity ID.
|
||||
* @param componentId The component ID.
|
||||
* @param type The body type to set.
|
||||
*/
|
||||
void entityPhysicsSetBodyType(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const physicsbodytype_t type
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the body type of the entity's physics body.
|
||||
*
|
||||
* @param entityId The entity ID.
|
||||
* @param componentId The component ID.
|
||||
* @return The body type of the physics body.
|
||||
*/
|
||||
physicsbodytype_t entityPhysicsGetBodyType(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Releases the body slot back to PHYSICS_WORLD. Called automatically when
|
||||
* the component is disposed via the component system.
|
||||
*/
|
||||
void entityPhysicsDispose(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
@@ -0,0 +1,7 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
entitytrigger.c
|
||||
)
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entity/entitymanager.h"
|
||||
|
||||
void entityTriggerInit(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
entitytrigger_t *t = componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_TRIGGER
|
||||
);
|
||||
glm_vec3_zero(t->min);
|
||||
glm_vec3_zero(t->max);
|
||||
}
|
||||
|
||||
entitytrigger_t * entityTriggerGet(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
) {
|
||||
return componentGetData(entityId, componentId, COMPONENT_TYPE_TRIGGER);
|
||||
}
|
||||
|
||||
bool_t entityTriggerContains(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const vec3 point
|
||||
) {
|
||||
entitytrigger_t *t = componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_TRIGGER
|
||||
);
|
||||
return (
|
||||
point[0] >= t->min[0] && point[0] <= t->max[0] &&
|
||||
point[1] >= t->min[1] && point[1] <= t->max[1] &&
|
||||
point[2] >= t->min[2] && point[2] <= t->max[2]
|
||||
);
|
||||
}
|
||||
|
||||
void entityTriggerSetBounds(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const vec3 min,
|
||||
const vec3 max
|
||||
) {
|
||||
entitytrigger_t *t = componentGetData(
|
||||
entityId, componentId, COMPONENT_TYPE_TRIGGER
|
||||
);
|
||||
glm_vec3_copy((float_t*)min, t->min);
|
||||
glm_vec3_copy((float_t*)max, t->max);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "entity/entitybase.h"
|
||||
|
||||
typedef struct {
|
||||
vec3 min;
|
||||
vec3 max;
|
||||
} entitytrigger_t;
|
||||
|
||||
/**
|
||||
* Initializes the trigger component with zeroed bounds.
|
||||
*/
|
||||
void entityTriggerInit(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns a pointer to the trigger component data.
|
||||
*/
|
||||
entitytrigger_t * entityTriggerGet(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns true if the given world-space point lies within [min, max].
|
||||
*/
|
||||
bool_t entityTriggerContains(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const vec3 point
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets both bounds at once.
|
||||
*/
|
||||
void entityTriggerSetBounds(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const vec3 min,
|
||||
const vec3 max
|
||||
);
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entity/component/display/entityposition.h"
|
||||
#include "entity/component/display/entitycamera.h"
|
||||
#include "entity/component/display/entityrenderable.h"
|
||||
#include "entity/component/physics/entityphysics.h"
|
||||
#include "entity/component/trigger/entitytrigger.h"
|
||||
|
||||
// Name (Uppercase)
|
||||
// Structure
|
||||
// Field name (lowercase)
|
||||
// Init function (optional)
|
||||
// Dispose function (optional)
|
||||
// FixedUpdate function (optional) - called once per fixed timestep
|
||||
|
||||
X(POSITION, entityposition_t, position, entityPositionInit, NULL, NULL)
|
||||
X(CAMERA, entitycamera_t, camera, entityCameraInit, NULL, NULL)
|
||||
X(RENDERABLE, entityrenderable_t, renderable, entityRenderableInit, entityRenderableDispose, NULL)
|
||||
X(PHYSICS, entityphysics_t, physics, entityPhysicsInit, entityPhysicsDispose, NULL)
|
||||
X(TRIGGER, entitytrigger_t, trigger, entityTriggerInit, NULL, NULL)
|
||||
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entitymanager.h"
|
||||
#include "component/display/entityposition.h"
|
||||
#include "util/memory.h"
|
||||
#include "assert/assert.h"
|
||||
|
||||
void entityInit(const entityid_t entityId) {
|
||||
entity_t *ent = &ENTITY_MANAGER.entities[entityId];
|
||||
|
||||
memoryZero(ent, sizeof(entity_t));
|
||||
|
||||
// Mark all component types not using this entity.
|
||||
for(
|
||||
componenttype_t compType = 0;
|
||||
compType < COMPONENT_TYPE_COUNT;
|
||||
compType++
|
||||
) {
|
||||
ENTITY_MANAGER.entitiesWithComponent[
|
||||
compType * ENTITY_COUNT_MAX + entityId
|
||||
] = COMPONENT_ID_INVALID;
|
||||
}
|
||||
|
||||
ent->state |= ENTITY_STATE_ACTIVE;
|
||||
}
|
||||
|
||||
componentid_t entityAddComponent(
|
||||
const entityid_t entityId,
|
||||
const componenttype_t type
|
||||
) {
|
||||
componentindex_t compInd;
|
||||
entity_t *ent = &ENTITY_MANAGER.entities[entityId];
|
||||
|
||||
for(componentid_t i = 0; i < ENTITY_COMPONENT_COUNT_MAX; i++) {
|
||||
compInd = componentGetIndex(entityId, i);
|
||||
if(ENTITY_MANAGER.components[compInd].type != COMPONENT_TYPE_NULL) {
|
||||
assertTrue(
|
||||
ENTITY_MANAGER.components[compInd].type != type,
|
||||
"Entity already has component of this type"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
componentInit(entityId, i, type);
|
||||
ENTITY_MANAGER.entitiesWithComponent[
|
||||
type * ENTITY_COUNT_MAX + entityId
|
||||
] = i;
|
||||
return i;
|
||||
}
|
||||
|
||||
assertUnreachable("Entity has no more component slots available");
|
||||
return COMPONENT_ID_INVALID;
|
||||
}
|
||||
|
||||
componentid_t entityGetComponent(
|
||||
const entityid_t entityId,
|
||||
const componenttype_t type
|
||||
) {
|
||||
componentid_t compId = ENTITY_MANAGER.entitiesWithComponent[
|
||||
type * ENTITY_COUNT_MAX + entityId
|
||||
];
|
||||
if(compId == COMPONENT_ID_INVALID) return compId;
|
||||
assertTrue(
|
||||
ENTITY_MANAGER.components[componentGetIndex(entityId, compId)].type == type,
|
||||
"Component type mismatch"
|
||||
);
|
||||
return compId;
|
||||
}
|
||||
|
||||
void entityDisposeDeep(const entityid_t entityId) {
|
||||
componentid_t posComp = entityGetComponent(entityId, COMPONENT_TYPE_POSITION);
|
||||
if(posComp != COMPONENT_ID_INVALID) {
|
||||
entityPositionDisposeDeep(entityId, posComp);
|
||||
} else {
|
||||
entityDispose(entityId);
|
||||
}
|
||||
}
|
||||
|
||||
void entityDispose(const entityid_t entityId) {
|
||||
componentindex_t compInd;
|
||||
entity_t *ent = &ENTITY_MANAGER.entities[entityId];
|
||||
|
||||
for(componentid_t i = 0; i < ENTITY_COMPONENT_COUNT_MAX; i++) {
|
||||
compInd = componentGetIndex(entityId, i);
|
||||
componenttype_t type = ENTITY_MANAGER.components[compInd].type;
|
||||
if(type == COMPONENT_TYPE_NULL) continue;
|
||||
ENTITY_MANAGER.entitiesWithComponent[
|
||||
type * ENTITY_COUNT_MAX + entityId
|
||||
] = COMPONENT_ID_INVALID;
|
||||
componentDispose(entityId, i);
|
||||
}
|
||||
|
||||
ent->state = 0;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "component.h"
|
||||
|
||||
#define ENTITY_STATE_ACTIVE (1 << 0)
|
||||
|
||||
typedef struct {
|
||||
uint8_t state;
|
||||
} entity_t;
|
||||
|
||||
/**
|
||||
* Initializes an entity with the given ID.
|
||||
*
|
||||
* @param entityId The ID of the entity to initialize.
|
||||
*/
|
||||
void entityInit(const entityid_t entityId);
|
||||
|
||||
/**
|
||||
* Adds a component of the given type to the entity with the given ID.
|
||||
*
|
||||
* @param entityId The ID of the entity to add the component to.
|
||||
* @param type The type of the component to add.
|
||||
* @return The ID of the entity with component.
|
||||
*/
|
||||
componentid_t entityAddComponent(
|
||||
const entityid_t entityId,
|
||||
const componenttype_t type
|
||||
);
|
||||
|
||||
/**
|
||||
* Gets the ID of the component of the given type on the entity with the given
|
||||
* ID, or COMPONENT_ID_INVALID if the entity lacks the component.
|
||||
*
|
||||
* @param entityId The ID of the entity to get the component from.
|
||||
* @param type The type of the component to get.
|
||||
* @return The ID of the component.
|
||||
*/
|
||||
componentid_t entityGetComponent(
|
||||
const entityid_t entityId,
|
||||
const componenttype_t type
|
||||
);
|
||||
|
||||
/**
|
||||
* Disposes of an entity with the given ID.
|
||||
*
|
||||
* @param entityId The ID of the entity to dispose of.
|
||||
*/
|
||||
void entityDispose(const entityid_t entityId);
|
||||
|
||||
/**
|
||||
* Disposes of an entity and all of its position-component descendants
|
||||
* recursively. If the entity has no position component, behaves like
|
||||
* entityDispose.
|
||||
*
|
||||
* @param entityId The root entity ID.
|
||||
*/
|
||||
void entityDisposeDeep(const entityid_t entityId);
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
#define ENTITY_COUNT_MAX 20
|
||||
#define ENTITY_COMPONENT_COUNT_MAX 8
|
||||
|
||||
#define ENTITY_ID_INVALID 0xFF
|
||||
#define COMPONENT_ID_INVALID 0xFF
|
||||
|
||||
typedef uint8_t entityid_t;
|
||||
typedef uint8_t componentid_t;
|
||||
typedef uint16_t componentindex_t;
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entitymanager.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "console/console.h"
|
||||
|
||||
entitymanager_t ENTITY_MANAGER;
|
||||
|
||||
void entityManagerInit(void) {
|
||||
memoryZero(&ENTITY_MANAGER, sizeof(entitymanager_t));
|
||||
memorySet(
|
||||
ENTITY_MANAGER.entitiesWithComponent, COMPONENT_ID_INVALID,
|
||||
sizeof(entityid_t) * COMPONENT_TYPE_COUNT * ENTITY_COUNT_MAX
|
||||
);
|
||||
|
||||
consolePrint(
|
||||
"Entity Manager size: %zu bytes (%.2f KB)",
|
||||
sizeof(entitymanager_t),
|
||||
sizeof(entitymanager_t) / 1024.0f
|
||||
);
|
||||
}
|
||||
|
||||
entityid_t entityManagerAdd() {
|
||||
for(entityid_t i = 0; i < ENTITY_COUNT_MAX; i++) {
|
||||
if((ENTITY_MANAGER.entities[i].state & ENTITY_STATE_ACTIVE) != 0) continue;
|
||||
entityInit(i);
|
||||
return i;
|
||||
}
|
||||
assertUnreachable("No more entity IDs available");
|
||||
return ENTITY_ID_INVALID;
|
||||
}
|
||||
|
||||
void entityManagerFixedUpdate(void) {
|
||||
componentFixedUpdateAll();
|
||||
}
|
||||
|
||||
void entityManagerDispose(void) {
|
||||
for(entityid_t i = 0; i < ENTITY_COUNT_MAX; i++) {
|
||||
if((ENTITY_MANAGER.entities[i].state & ENTITY_STATE_ACTIVE) == 0) continue;
|
||||
entityDispose(i);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "entity.h"
|
||||
|
||||
typedef struct {
|
||||
entity_t entities[ENTITY_COUNT_MAX];
|
||||
component_t components[ENTITY_COUNT_MAX * ENTITY_COMPONENT_COUNT_MAX];
|
||||
componentid_t entitiesWithComponent[COMPONENT_TYPE_COUNT * ENTITY_COUNT_MAX];
|
||||
} entitymanager_t;
|
||||
|
||||
extern entitymanager_t ENTITY_MANAGER;
|
||||
|
||||
/**
|
||||
* Initializes the entity manager.
|
||||
*/
|
||||
void entityManagerInit(void);
|
||||
|
||||
/**
|
||||
* Adds / Reserves a new entity ID.
|
||||
*
|
||||
* @return The new entity ID.
|
||||
*/
|
||||
entityid_t entityManagerAdd();
|
||||
|
||||
/**
|
||||
* Runs fixedUpdate on every active component that defines one. Should be
|
||||
* called once per fixed timestep (see time.h's DUSK_TIME_DYNAMIC/
|
||||
* TIME.dynamicUpdate) - i.e. every frame on platforms without dynamic
|
||||
* timing, or only on non-dynamic frames on platforms with it.
|
||||
*/
|
||||
void entityManagerFixedUpdate(void);
|
||||
|
||||
/**
|
||||
* Disposes of the entity manager, in turn freeing all entities and components.
|
||||
*/
|
||||
void entityManagerDispose(void);
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "entityrender.h"
|
||||
#include "entity.h"
|
||||
#include "entitymanager.h"
|
||||
#include "entity/component/display/entityposition.h"
|
||||
#include "entity/component/display/entitycamera.h"
|
||||
#include "entity/component/display/entityrenderable.h"
|
||||
#include "display/display.h"
|
||||
#include "display/displaystate.h"
|
||||
#include "display/shader/shaderunlit.h"
|
||||
#include "display/mesh/mesh.h"
|
||||
|
||||
errorret_t entityRenderAll(void) {
|
||||
entityid_t camId = entityCameraGetCurrent();
|
||||
if(camId == ENTITY_COUNT_MAX) errorOk();
|
||||
|
||||
componentid_t camPosComp = entityGetComponent(camId, COMPONENT_TYPE_POSITION);
|
||||
componentid_t camCompId = entityGetComponent(camId, COMPONENT_TYPE_CAMERA);
|
||||
if(camPosComp == COMPONENT_ID_INVALID) errorOk();
|
||||
|
||||
mat4 view, proj;
|
||||
entityPositionGetTransform(camId, camPosComp, view);
|
||||
entityCameraGetProjection(camId, camCompId, proj);
|
||||
|
||||
errorChain(shaderBind(&SHADER_UNLIT));
|
||||
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_VIEW, view));
|
||||
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_PROJECTION, proj));
|
||||
|
||||
errorChain(displaySetState((displaystate_t){
|
||||
.flags = DISPLAY_STATE_FLAG_DEPTH_TEST | DISPLAY_STATE_FLAG_CULL
|
||||
}));
|
||||
|
||||
entityid_t rendEnts[ENTITY_COUNT_MAX];
|
||||
componentid_t rendComps[ENTITY_COUNT_MAX];
|
||||
entityid_t count = componentGetEntitiesWithComponent(
|
||||
COMPONENT_TYPE_RENDERABLE, rendEnts, rendComps
|
||||
);
|
||||
|
||||
for(entityid_t i = 0; i < count; i++) {
|
||||
entityrenderable_t *r = (entityrenderable_t *)componentGetData(
|
||||
rendEnts[i], rendComps[i], COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
if(r->type != ENTITY_RENDERABLE_TYPE_MATERIAL) continue;
|
||||
if(r->mesh == NULL) continue;
|
||||
|
||||
mat4 model;
|
||||
componentid_t posComp = entityGetComponent(rendEnts[i], COMPONENT_TYPE_POSITION);
|
||||
if(posComp != COMPONENT_ID_INVALID) {
|
||||
entityPositionGetTransform(rendEnts[i], posComp, model);
|
||||
} else {
|
||||
glm_mat4_identity(model);
|
||||
}
|
||||
|
||||
errorChain(shaderSetMatrix(&SHADER_UNLIT, SHADER_UNLIT_MODEL, model));
|
||||
errorChain(shaderSetMaterial(&SHADER_UNLIT, &r->material));
|
||||
errorChain(meshDraw(r->mesh, 0, -1));
|
||||
}
|
||||
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
|
||||
/**
|
||||
* Draws every entity with a RENDERABLE component, from the perspective of
|
||||
* whichever entity currently holds the active CAMERA component. A no-op if
|
||||
* no camera is active. Entities and their components are created and
|
||||
* managed entirely from script - this is the native side of that contract.
|
||||
*
|
||||
* @return An error if rendering failed, or errorOk() if it succeeded.
|
||||
*/
|
||||
errorret_t entityRenderAll(void);
|
||||
@@ -0,0 +1,12 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
physicsmanager.c
|
||||
physicsworld.c
|
||||
physicstest.c
|
||||
)
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef enum {
|
||||
/**
|
||||
* Never moves. Acts as an immovable collision surface.
|
||||
*/
|
||||
PHYSICS_BODY_STATIC,
|
||||
|
||||
/**
|
||||
* Simulated by the world step: gravity, forces, and collision response.
|
||||
*/
|
||||
PHYSICS_BODY_DYNAMIC,
|
||||
|
||||
/**
|
||||
* Moved programmatically via physicsWorldMoveBody; collides but is not
|
||||
* driven by the simulation. Typical use: player character controller.
|
||||
*/
|
||||
PHYSICS_BODY_KINEMATIC
|
||||
} physicsbodytype_t;
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "physicsmanager.h"
|
||||
#include "time/time.h"
|
||||
|
||||
void physicsManagerInit(void) {
|
||||
physicsWorldInit();
|
||||
}
|
||||
|
||||
void physicsManagerUpdate() {
|
||||
#if DUSK_TIME_DYNAMIC
|
||||
if(TIME.dynamicUpdate) return; // Don't update on dynamic updates.
|
||||
#endif
|
||||
|
||||
physicsWorldStep(TIME.delta);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "physicsworld.h"
|
||||
|
||||
/**
|
||||
* Initializes the physics manager.
|
||||
*/
|
||||
void physicsManagerInit(void);
|
||||
|
||||
/**
|
||||
* Advances the physics simulation.
|
||||
*/
|
||||
void physicsManagerUpdate();
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
typedef enum {
|
||||
PHYSICS_SHAPE_CUBE,
|
||||
PHYSICS_SHAPE_SPHERE,
|
||||
PHYSICS_SHAPE_CAPSULE,
|
||||
PHYSICS_SHAPE_PLANE
|
||||
} physicshapetype_t;
|
||||
|
||||
typedef struct {
|
||||
vec3 halfExtents;
|
||||
} physicsshapecube_t;
|
||||
|
||||
typedef struct {
|
||||
float_t radius;
|
||||
} physicsshapesphere_t;
|
||||
|
||||
typedef struct {
|
||||
float_t radius;
|
||||
float_t halfHeight;
|
||||
} physicsshapecapsule_t;
|
||||
|
||||
typedef struct {
|
||||
vec3 normal;
|
||||
float_t distance;
|
||||
} physicsshapeplane_t;
|
||||
|
||||
typedef union {
|
||||
physicsshapecube_t cube;
|
||||
physicsshapesphere_t sphere;
|
||||
physicsshapecapsule_t capsule;
|
||||
physicsshapeplane_t plane;
|
||||
} physicsshapedata_t;
|
||||
|
||||
typedef struct {
|
||||
physicshapetype_t type;
|
||||
physicsshapedata_t data;
|
||||
} physicsshape_t;
|
||||
@@ -0,0 +1,402 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "physicstest.h"
|
||||
|
||||
bool physicsTestAabbVsAabb(
|
||||
const vec3 ac, const vec3 ah,
|
||||
const vec3 bc, const vec3 bh,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
) {
|
||||
float_t dx = ac[0] - bc[0];
|
||||
float_t dy = ac[1] - bc[1];
|
||||
float_t dz = ac[2] - bc[2];
|
||||
|
||||
float_t px = (ah[0] + bh[0]) - fabsf(dx);
|
||||
float_t py = (ah[1] + bh[1]) - fabsf(dy);
|
||||
float_t pz = (ah[2] + bh[2]) - fabsf(dz);
|
||||
|
||||
if(px <= 0.0f || py <= 0.0f || pz <= 0.0f) return false;
|
||||
|
||||
outNormal[0] = outNormal[1] = outNormal[2] = 0.0f;
|
||||
if(px < py && px < pz) {
|
||||
*outDepth = px;
|
||||
outNormal[0] = dx >= 0.0f ? 1.0f : -1.0f;
|
||||
} else if(py < pz) {
|
||||
*outDepth = py;
|
||||
outNormal[1] = dy >= 0.0f ? 1.0f : -1.0f;
|
||||
} else {
|
||||
*outDepth = pz;
|
||||
outNormal[2] = dz >= 0.0f ? 1.0f : -1.0f;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool physicsTestSphereVsSphere(
|
||||
const vec3 ac, const float_t ar,
|
||||
const vec3 bc, const float_t br,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
) {
|
||||
vec3 diff;
|
||||
glm_vec3_sub((float_t *)ac, (float_t *)bc, diff);
|
||||
float_t dist2 = glm_vec3_norm2(diff);
|
||||
float_t sumR = ar + br;
|
||||
|
||||
if(dist2 >= sumR * sumR) return false;
|
||||
|
||||
float_t dist = sqrtf(dist2);
|
||||
*outDepth = sumR - dist;
|
||||
|
||||
if(dist > 1e-6f) {
|
||||
glm_vec3_scale(diff, 1.0f / dist, outNormal);
|
||||
} else {
|
||||
outNormal[0] = 0.0f;
|
||||
outNormal[1] = 1.0f;
|
||||
outNormal[2] = 0.0f;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool physicsTestSphereVsAabb(
|
||||
const vec3 sc, const float_t sr,
|
||||
const vec3 ac, const vec3 ah,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
) {
|
||||
vec3 closest = {
|
||||
glm_clamp(sc[0], ac[0] - ah[0], ac[0] + ah[0]),
|
||||
glm_clamp(sc[1], ac[1] - ah[1], ac[1] + ah[1]),
|
||||
glm_clamp(sc[2], ac[2] - ah[2], ac[2] + ah[2])
|
||||
};
|
||||
|
||||
vec3 diff;
|
||||
glm_vec3_sub((float_t *)sc, closest, diff);
|
||||
float_t dist2 = glm_vec3_norm2(diff);
|
||||
|
||||
bool inside = (dist2 < 1e-10f);
|
||||
if(!inside && dist2 >= sr * sr) return false;
|
||||
|
||||
if(!inside) {
|
||||
float_t dist = sqrtf(dist2);
|
||||
*outDepth = sr - dist;
|
||||
glm_vec3_scale(diff, 1.0f / dist, outNormal);
|
||||
} else {
|
||||
float_t faces[6] = {
|
||||
(ac[0] + ah[0]) - sc[0],
|
||||
sc[0] - (ac[0] - ah[0]),
|
||||
(ac[1] + ah[1]) - sc[1],
|
||||
sc[1] - (ac[1] - ah[1]),
|
||||
(ac[2] + ah[2]) - sc[2],
|
||||
sc[2] - (ac[2] - ah[2])
|
||||
};
|
||||
const float_t normals[6][3] = {
|
||||
{1,0,0},{-1,0,0},{0,1,0},{0,-1,0},{0,0,1},{0,0,-1}
|
||||
};
|
||||
int mi = 0;
|
||||
for(int k = 1; k < 6; k++) {
|
||||
if(faces[k] < faces[mi]) mi = k;
|
||||
}
|
||||
*outDepth = sr + faces[mi];
|
||||
outNormal[0] = normals[mi][0];
|
||||
outNormal[1] = normals[mi][1];
|
||||
outNormal[2] = normals[mi][2];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool physicsTestSphereVsPlane(
|
||||
const vec3 sc, const float_t sr,
|
||||
const vec3 pn, const float_t pd,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
) {
|
||||
float_t signedDist = glm_vec3_dot((float_t *)pn, (float_t *)sc) - pd;
|
||||
*outDepth = sr - signedDist;
|
||||
if(*outDepth <= 0.0f) return false;
|
||||
glm_vec3_copy((float_t *)pn, outNormal);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool physicsTestAabbVsPlane(
|
||||
const vec3 ac, const vec3 ah,
|
||||
const vec3 pn, const float_t pd,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
) {
|
||||
float_t proj = fabsf(pn[0] * ah[0])
|
||||
+ fabsf(pn[1] * ah[1])
|
||||
+ fabsf(pn[2] * ah[2]);
|
||||
float_t signedDist = glm_vec3_dot((float_t *)pn, (float_t *)ac) - pd;
|
||||
*outDepth = proj - signedDist;
|
||||
if(*outDepth <= 0.0f) return false;
|
||||
glm_vec3_copy((float_t *)pn, outNormal);
|
||||
return true;
|
||||
}
|
||||
|
||||
void physicsTestClosestPointOnSegment(
|
||||
const vec3 a, const vec3 b, const vec3 p, vec3 out
|
||||
) {
|
||||
vec3 ab, ap;
|
||||
glm_vec3_sub((float_t *)b, (float_t *)a, ab);
|
||||
glm_vec3_sub((float_t *)p, (float_t *)a, ap);
|
||||
float_t denom = glm_vec3_dot(ab, ab);
|
||||
float_t t = (denom > 1e-10f)
|
||||
? glm_clamp(glm_vec3_dot(ap, ab) / denom, 0.0f, 1.0f)
|
||||
: 0.0f;
|
||||
glm_vec3_lerp((float_t *)a, (float_t *)b, t, out);
|
||||
}
|
||||
|
||||
void physicsTestClosestPointsBetweenSegments(
|
||||
const vec3 a1, const vec3 b1,
|
||||
const vec3 a2, const vec3 b2,
|
||||
vec3 outP1, vec3 outP2
|
||||
) {
|
||||
vec3 d1, d2, r;
|
||||
glm_vec3_sub((float_t *)b1, (float_t *)a1, d1);
|
||||
glm_vec3_sub((float_t *)b2, (float_t *)a2, d2);
|
||||
glm_vec3_sub((float_t *)a1, (float_t *)a2, r);
|
||||
|
||||
float_t a = glm_vec3_dot(d1, d1);
|
||||
float_t e = glm_vec3_dot(d2, d2);
|
||||
float_t f = glm_vec3_dot(d2, r);
|
||||
float_t s, t;
|
||||
|
||||
if(a <= 1e-10f && e <= 1e-10f) {
|
||||
glm_vec3_copy((float_t *)a1, outP1);
|
||||
glm_vec3_copy((float_t *)a2, outP2);
|
||||
return;
|
||||
}
|
||||
if(a <= 1e-10f) {
|
||||
t = 0.0f;
|
||||
s = glm_clamp(f / e, 0.0f, 1.0f);
|
||||
} else {
|
||||
float_t c = glm_vec3_dot(d1, r);
|
||||
if(e <= 1e-10f) {
|
||||
s = 0.0f;
|
||||
t = glm_clamp(-c / a, 0.0f, 1.0f);
|
||||
} else {
|
||||
float_t b = glm_vec3_dot(d1, d2);
|
||||
float_t denom = a * e - b * b;
|
||||
t = (fabsf(denom) > 1e-10f)
|
||||
? glm_clamp((b * f - c * e) / denom, 0.0f, 1.0f)
|
||||
: 0.0f;
|
||||
s = glm_clamp((b * t + f) / e, 0.0f, 1.0f);
|
||||
t = glm_clamp((b * s - c) / a, 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
glm_vec3_lerp((float_t *)a1, (float_t *)b1, t, outP1);
|
||||
glm_vec3_lerp((float_t *)a2, (float_t *)b2, s, outP2);
|
||||
}
|
||||
|
||||
bool physicsTestCapsuleVsSphere(
|
||||
const vec3 cc, const float_t cr, const float_t chh,
|
||||
const vec3 sc, const float_t sr,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
) {
|
||||
vec3 capA = { cc[0], cc[1] - chh, cc[2] };
|
||||
vec3 capB = { cc[0], cc[1] + chh, cc[2] };
|
||||
vec3 closest;
|
||||
physicsTestClosestPointOnSegment(capA, capB, sc, closest);
|
||||
return physicsTestSphereVsSphere(
|
||||
closest, cr, sc, sr, outNormal, outDepth
|
||||
);
|
||||
}
|
||||
|
||||
bool physicsTestCapsuleVsAabb(
|
||||
const vec3 cc, const float_t cr, const float_t chh,
|
||||
const vec3 ac, const vec3 ah,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
) {
|
||||
vec3 capA = { cc[0], cc[1] - chh, cc[2] };
|
||||
vec3 capB = { cc[0], cc[1] + chh, cc[2] };
|
||||
vec3 closest;
|
||||
physicsTestClosestPointOnSegment(capA, capB, ac, closest);
|
||||
return physicsTestSphereVsAabb(
|
||||
closest, cr, ac, ah, outNormal, outDepth
|
||||
);
|
||||
}
|
||||
|
||||
bool physicsTestCapsuleVsPlane(
|
||||
const vec3 cc, const float_t cr, const float_t chh,
|
||||
const vec3 pn, const float_t pd,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
) {
|
||||
vec3 capA = { cc[0], cc[1] - chh, cc[2] };
|
||||
vec3 capB = { cc[0], cc[1] + chh, cc[2] };
|
||||
float_t da = glm_vec3_dot((float_t *)pn, capA) - pd;
|
||||
float_t db = glm_vec3_dot((float_t *)pn, capB) - pd;
|
||||
float_t minDist = (da < db) ? da : db;
|
||||
*outDepth = cr - minDist;
|
||||
if(*outDepth <= 0.0f) return false;
|
||||
glm_vec3_copy((float_t *)pn, outNormal);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool physicsTestCapsuleVsCapsule(
|
||||
const vec3 c1, const float_t r1, const float_t hh1,
|
||||
const vec3 c2, const float_t r2, const float_t hh2,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
) {
|
||||
vec3 a1 = { c1[0], c1[1] - hh1, c1[2] };
|
||||
vec3 b1 = { c1[0], c1[1] + hh1, c1[2] };
|
||||
vec3 a2 = { c2[0], c2[1] - hh2, c2[2] };
|
||||
vec3 b2 = { c2[0], c2[1] + hh2, c2[2] };
|
||||
vec3 p1, p2;
|
||||
physicsTestClosestPointsBetweenSegments(a1, b1, a2, b2, p1, p2);
|
||||
return physicsTestSphereVsSphere(p1, r1, p2, r2, outNormal, outDepth);
|
||||
}
|
||||
|
||||
bool physicsTestDispatch(
|
||||
const vec3 aPos, const physicsshape_t aShape,
|
||||
const vec3 bPos, const physicsshape_t bShape,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
) {
|
||||
physicshapetype_t ta = aShape.type;
|
||||
physicshapetype_t tb = bShape.type;
|
||||
|
||||
if(tb == PHYSICS_SHAPE_PLANE) {
|
||||
const float_t *pn = bShape.data.plane.normal;
|
||||
const float_t pd = bShape.data.plane.distance;
|
||||
switch (ta) {
|
||||
case PHYSICS_SHAPE_CUBE:
|
||||
return physicsTestAabbVsPlane(
|
||||
aPos, aShape.data.cube.halfExtents,
|
||||
pn, pd, outNormal, outDepth
|
||||
);
|
||||
case PHYSICS_SHAPE_SPHERE:
|
||||
return physicsTestSphereVsPlane(
|
||||
aPos, aShape.data.sphere.radius,
|
||||
pn, pd, outNormal, outDepth
|
||||
);
|
||||
case PHYSICS_SHAPE_CAPSULE:
|
||||
return physicsTestCapsuleVsPlane(
|
||||
aPos,
|
||||
aShape.data.capsule.radius,
|
||||
aShape.data.capsule.halfHeight,
|
||||
pn, pd, outNormal, outDepth
|
||||
);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(ta == PHYSICS_SHAPE_PLANE) {
|
||||
vec3 tmp; float_t d;
|
||||
if(!physicsTestDispatch(
|
||||
bPos, bShape, aPos, aShape, tmp, &d
|
||||
)) return false;
|
||||
glm_vec3_scale(tmp, -1.0f, outNormal);
|
||||
*outDepth = d;
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (ta) {
|
||||
case PHYSICS_SHAPE_CUBE: {
|
||||
const float_t *ac = aPos;
|
||||
const float_t *ah = aShape.data.cube.halfExtents;
|
||||
switch (tb) {
|
||||
case PHYSICS_SHAPE_CUBE:
|
||||
return physicsTestAabbVsAabb(
|
||||
ac, ah,
|
||||
bPos, bShape.data.cube.halfExtents,
|
||||
outNormal, outDepth
|
||||
);
|
||||
case PHYSICS_SHAPE_SPHERE: {
|
||||
vec3 tmp; float_t d;
|
||||
if(!physicsTestSphereVsAabb(
|
||||
bPos, bShape.data.sphere.radius,
|
||||
ac, ah, tmp, &d
|
||||
)) return false;
|
||||
glm_vec3_scale(tmp, -1.0f, outNormal);
|
||||
*outDepth = d;
|
||||
return true;
|
||||
}
|
||||
case PHYSICS_SHAPE_CAPSULE: {
|
||||
vec3 tmp; float_t d;
|
||||
if(!physicsTestCapsuleVsAabb(
|
||||
bPos,
|
||||
bShape.data.capsule.radius,
|
||||
bShape.data.capsule.halfHeight,
|
||||
ac, ah, tmp, &d
|
||||
)) return false;
|
||||
glm_vec3_scale(tmp, -1.0f, outNormal);
|
||||
*outDepth = d;
|
||||
return true;
|
||||
}
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
case PHYSICS_SHAPE_SPHERE: {
|
||||
const float_t sr = aShape.data.sphere.radius;
|
||||
switch (tb) {
|
||||
case PHYSICS_SHAPE_CUBE:
|
||||
return physicsTestSphereVsAabb(
|
||||
aPos, sr,
|
||||
bPos, bShape.data.cube.halfExtents,
|
||||
outNormal, outDepth
|
||||
);
|
||||
case PHYSICS_SHAPE_SPHERE:
|
||||
return physicsTestSphereVsSphere(
|
||||
aPos, sr,
|
||||
bPos, bShape.data.sphere.radius,
|
||||
outNormal, outDepth
|
||||
);
|
||||
case PHYSICS_SHAPE_CAPSULE: {
|
||||
vec3 tmp; float_t d;
|
||||
if(!physicsTestCapsuleVsSphere(
|
||||
bPos,
|
||||
bShape.data.capsule.radius,
|
||||
bShape.data.capsule.halfHeight,
|
||||
aPos, sr, tmp, &d
|
||||
)) return false;
|
||||
glm_vec3_scale(tmp, -1.0f, outNormal);
|
||||
*outDepth = d;
|
||||
return true;
|
||||
}
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
case PHYSICS_SHAPE_CAPSULE: {
|
||||
const float_t cr = aShape.data.capsule.radius;
|
||||
const float_t chh = aShape.data.capsule.halfHeight;
|
||||
switch (tb) {
|
||||
case PHYSICS_SHAPE_CUBE:
|
||||
return physicsTestCapsuleVsAabb(
|
||||
aPos, cr, chh,
|
||||
bPos, bShape.data.cube.halfExtents,
|
||||
outNormal, outDepth
|
||||
);
|
||||
case PHYSICS_SHAPE_SPHERE:
|
||||
return physicsTestCapsuleVsSphere(
|
||||
aPos, cr, chh,
|
||||
bPos, bShape.data.sphere.radius,
|
||||
outNormal, outDepth
|
||||
);
|
||||
case PHYSICS_SHAPE_CAPSULE:
|
||||
return physicsTestCapsuleVsCapsule(
|
||||
aPos, cr, chh,
|
||||
bPos,
|
||||
bShape.data.capsule.radius,
|
||||
bShape.data.capsule.halfHeight,
|
||||
outNormal, outDepth
|
||||
);
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool_t physicsTestShapeVsShape(
|
||||
const vec3 aPos, const physicsshape_t aShape,
|
||||
const vec3 bPos, const physicsshape_t bShape,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
) {
|
||||
return physicsTestDispatch(
|
||||
aPos, aShape, bPos, bShape, outNormal, outDepth
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "physicsshape.h"
|
||||
|
||||
/**
|
||||
* Tests overlap between two axis-aligned bounding boxes.
|
||||
* outNormal points from B toward A.
|
||||
*
|
||||
* @param ac Center of AABB A.
|
||||
* @param ah Half-extents of AABB A.
|
||||
* @param bc Center of AABB B.
|
||||
* @param bh Half-extents of AABB B.
|
||||
* @param outNormal Push-out normal (B toward A).
|
||||
* @param outDepth Penetration depth.
|
||||
* @return true if overlapping.
|
||||
*/
|
||||
bool_t physicsTestAabbVsAabb(
|
||||
const vec3 ac, const vec3 ah,
|
||||
const vec3 bc, const vec3 bh,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
);
|
||||
|
||||
/**
|
||||
* Tests overlap between two spheres.
|
||||
* outNormal points from B toward A.
|
||||
*
|
||||
* @param ac Center of sphere A.
|
||||
* @param ar Radius of sphere A.
|
||||
* @param bc Center of sphere B.
|
||||
* @param br Radius of sphere B.
|
||||
* @param outNormal Push-out normal (B toward A).
|
||||
* @param outDepth Penetration depth.
|
||||
* @return true if overlapping.
|
||||
*/
|
||||
bool_t physicsTestSphereVsSphere(
|
||||
const vec3 ac, const float_t ar,
|
||||
const vec3 bc, const float_t br,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
);
|
||||
|
||||
/**
|
||||
* Tests overlap between a sphere and an axis-aligned bounding box.
|
||||
* outNormal points from the AABB toward the sphere.
|
||||
*
|
||||
* @param sc Center of the sphere.
|
||||
* @param sr Radius of the sphere.
|
||||
* @param ac Center of the AABB.
|
||||
* @param ah Half-extents of the AABB.
|
||||
* @param outNormal Push-out normal (AABB toward sphere).
|
||||
* @param outDepth Penetration depth.
|
||||
* @return true if overlapping.
|
||||
*/
|
||||
bool_t physicsTestSphereVsAabb(
|
||||
const vec3 sc, const float_t sr,
|
||||
const vec3 ac, const vec3 ah,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
);
|
||||
|
||||
/**
|
||||
* Tests overlap between a sphere and an infinite plane.
|
||||
* outNormal equals the plane normal (pointing away from the surface).
|
||||
*
|
||||
* @param sc Center of the sphere.
|
||||
* @param sr Radius of the sphere.
|
||||
* @param pn Plane normal (unit vector, world-space).
|
||||
* @param pd Plane offset: dot(pn, surfacePoint) == pd.
|
||||
* @param outNormal Push-out normal (equals pn).
|
||||
* @param outDepth Penetration depth.
|
||||
* @return true if overlapping.
|
||||
*/
|
||||
bool_t physicsTestSphereVsPlane(
|
||||
const vec3 sc, const float_t sr,
|
||||
const vec3 pn, const float_t pd,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
);
|
||||
|
||||
/**
|
||||
* Tests overlap between an AABB and an infinite plane.
|
||||
* outNormal equals the plane normal.
|
||||
*
|
||||
* @param ac Center of the AABB.
|
||||
* @param ah Half-extents of the AABB.
|
||||
* @param pn Plane normal (unit vector, world-space).
|
||||
* @param pd Plane offset (see physicsTestSphereVsPlane).
|
||||
* @param outNormal Push-out normal (equals pn).
|
||||
* @param outDepth Penetration depth.
|
||||
* @return true if overlapping.
|
||||
*/
|
||||
bool_t physicsTestAabbVsPlane(
|
||||
const vec3 ac, const vec3 ah,
|
||||
const vec3 pn, const float_t pd,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
);
|
||||
|
||||
/**
|
||||
* Finds the closest point on segment [a, b] to query point p.
|
||||
*
|
||||
* @param a Start of the segment.
|
||||
* @param b End of the segment.
|
||||
* @param p Query point.
|
||||
* @param out Receives the closest point on [a, b] to p.
|
||||
*/
|
||||
void physicsTestClosestPointOnSegment(
|
||||
const vec3 a, const vec3 b, const vec3 p, vec3 out
|
||||
);
|
||||
|
||||
/**
|
||||
* Finds the closest points between two line segments.
|
||||
*
|
||||
* @param a1 Start of segment 1.
|
||||
* @param b1 End of segment 1.
|
||||
* @param a2 Start of segment 2.
|
||||
* @param b2 End of segment 2.
|
||||
* @param outP1 Receives the closest point on segment 1.
|
||||
* @param outP2 Receives the closest point on segment 2.
|
||||
*/
|
||||
void physicsTestClosestPointsBetweenSegments(
|
||||
const vec3 a1, const vec3 b1,
|
||||
const vec3 a2, const vec3 b2,
|
||||
vec3 outP1, vec3 outP2
|
||||
);
|
||||
|
||||
/**
|
||||
* Tests overlap between a Y-axis-aligned capsule and a sphere.
|
||||
* outNormal points from the sphere toward the capsule.
|
||||
*
|
||||
* @param cc Center of the capsule.
|
||||
* @param cr Radius of the capsule.
|
||||
* @param chh Half-height of the capsule's cylindrical segment.
|
||||
* @param sc Center of the sphere.
|
||||
* @param sr Radius of the sphere.
|
||||
* @param outNormal Push-out normal (sphere toward capsule).
|
||||
* @param outDepth Penetration depth.
|
||||
* @return true if overlapping.
|
||||
*/
|
||||
bool_t physicsTestCapsuleVsSphere(
|
||||
const vec3 cc, const float_t cr, const float_t chh,
|
||||
const vec3 sc, const float_t sr,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
);
|
||||
|
||||
/**
|
||||
* Tests overlap between a Y-axis-aligned capsule and an AABB.
|
||||
* outNormal points from the AABB toward the capsule.
|
||||
*
|
||||
* @param cc Center of the capsule.
|
||||
* @param cr Radius of the capsule.
|
||||
* @param chh Half-height of the capsule's cylindrical segment.
|
||||
* @param ac Center of the AABB.
|
||||
* @param ah Half-extents of the AABB.
|
||||
* @param outNormal Push-out normal (AABB toward capsule).
|
||||
* @param outDepth Penetration depth.
|
||||
* @return true if overlapping.
|
||||
*/
|
||||
bool_t physicsTestCapsuleVsAabb(
|
||||
const vec3 cc, const float_t cr, const float_t chh,
|
||||
const vec3 ac, const vec3 ah,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
);
|
||||
|
||||
/**
|
||||
* Tests overlap between a Y-axis-aligned capsule and an infinite plane.
|
||||
* outNormal equals the plane normal.
|
||||
*
|
||||
* @param cc Center of the capsule.
|
||||
* @param cr Radius of the capsule.
|
||||
* @param chh Half-height of the capsule's cylindrical segment.
|
||||
* @param pn Plane normal (unit vector, world-space).
|
||||
* @param pd Plane offset (see physicsTestSphereVsPlane).
|
||||
* @param outNormal Push-out normal (equals pn).
|
||||
* @param outDepth Penetration depth.
|
||||
* @return true if overlapping.
|
||||
*/
|
||||
bool_t physicsTestCapsuleVsPlane(
|
||||
const vec3 cc, const float_t cr, const float_t chh,
|
||||
const vec3 pn, const float_t pd,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
);
|
||||
|
||||
/**
|
||||
* Tests overlap between two Y-axis-aligned capsules.
|
||||
* outNormal points from capsule B toward capsule A.
|
||||
*
|
||||
* @param c1 Center of capsule A.
|
||||
* @param r1 Radius of capsule A.
|
||||
* @param hh1 Half-height of capsule A's cylindrical segment.
|
||||
* @param c2 Center of capsule B.
|
||||
* @param r2 Radius of capsule B.
|
||||
* @param hh2 Half-height of capsule B's cylindrical segment.
|
||||
* @param outNormal Push-out normal (B toward A).
|
||||
* @param outDepth Penetration depth.
|
||||
* @return true if overlapping.
|
||||
*/
|
||||
bool_t physicsTestCapsuleVsCapsule(
|
||||
const vec3 c1, const float_t r1, const float_t hh1,
|
||||
const vec3 c2, const float_t r2, const float_t hh2,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
);
|
||||
|
||||
/**
|
||||
* Routes a shape-pair collision test to the correct primitive.
|
||||
* When A is a plane, delegates with swapped arguments and negates
|
||||
* the resulting normal. outNormal points from B toward A.
|
||||
*
|
||||
* @param aPos Position of shape A.
|
||||
* @param aShape Shape descriptor of A.
|
||||
* @param bPos Position of shape B.
|
||||
* @param bShape Shape descriptor of B.
|
||||
* @param outNormal Push-out normal (B toward A).
|
||||
* @param outDepth Penetration depth.
|
||||
* @return true if overlapping.
|
||||
*/
|
||||
bool_t physicsTestDispatch(
|
||||
const vec3 aPos, const physicsshape_t aShape,
|
||||
const vec3 bPos, const physicsshape_t bShape,
|
||||
vec3 outNormal, float_t *outDepth
|
||||
);
|
||||
|
||||
/**
|
||||
* Tests for collision between two shapes. Returns true if they
|
||||
* overlap, and if so, outputs the push-out normal and depth.
|
||||
*
|
||||
* outNormal always points from shape B toward shape A, so adding
|
||||
* (outNormal * outDepth) to A's position separates the two shapes.
|
||||
*
|
||||
* @param aPos Position of shape A.
|
||||
* @param aShape Shape descriptor of A.
|
||||
* @param bPos Position of shape B.
|
||||
* @param bShape Shape descriptor of B.
|
||||
* @param outNormal Push-out normal, pointing from B toward A.
|
||||
* @param outDepth Penetration depth (positive when overlapping).
|
||||
* @return true if the shapes overlap, false otherwise.
|
||||
*/
|
||||
bool_t physicsTestShapeVsShape(
|
||||
const vec3 aPos,
|
||||
const physicsshape_t aShape,
|
||||
const vec3 bPos,
|
||||
const physicsshape_t bShape,
|
||||
vec3 outNormal,
|
||||
float_t *outDepth
|
||||
);
|
||||
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "physicsworld.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "entity/entity.h"
|
||||
#include "entity/component.h"
|
||||
#include "physicstest.h"
|
||||
|
||||
physicsworld_t PHYSICS_WORLD;
|
||||
|
||||
void physicsWorldInit() {
|
||||
memoryZero(&PHYSICS_WORLD, sizeof(physicsworld_t));
|
||||
|
||||
PHYSICS_WORLD.gravity[0] = 0.0f;
|
||||
PHYSICS_WORLD.gravity[1] = -9.81f;
|
||||
PHYSICS_WORLD.gravity[2] = 0.0f;
|
||||
}
|
||||
|
||||
void physicsWorldStep(const float_t dt) {
|
||||
assertTrue(dt > 0.0f, "Delta time must be positive");
|
||||
|
||||
entityid_t physEnts[ENTITY_COUNT_MAX];
|
||||
componentid_t physComps[ENTITY_COUNT_MAX];
|
||||
entityid_t physCount = componentGetEntitiesWithComponent(
|
||||
COMPONENT_TYPE_PHYSICS, physEnts, physComps
|
||||
);
|
||||
|
||||
/* Pre-fetch all position and physics pointers once. */
|
||||
entityposition_t *positions[ENTITY_COUNT_MAX];
|
||||
entityphysics_t *physBodies[ENTITY_COUNT_MAX];
|
||||
for(entityid_t i = 0; i < physCount; i++) {
|
||||
componentid_t posComp = entityGetComponent(
|
||||
physEnts[i], COMPONENT_TYPE_POSITION
|
||||
);
|
||||
positions[i] = (posComp != 0xFF)
|
||||
? entityPositionGet(physEnts[i], posComp)
|
||||
: NULL;
|
||||
physBodies[i] = entityPhysicsGet(physEnts[i], physComps[i]);
|
||||
}
|
||||
|
||||
/* Phase 1: integrate dynamic bodies (gravity + velocity → position).
|
||||
* Writes directly to pos->position, matrix rebuilt at end. */
|
||||
for(entityid_t i = 0; i < physCount; i++) {
|
||||
if(!positions[i]) continue;
|
||||
entityphysics_t *phys = physBodies[i];
|
||||
if(phys->type != PHYSICS_BODY_DYNAMIC) continue;
|
||||
|
||||
phys->onGround = false;
|
||||
|
||||
phys->velocity[0] += PHYSICS_WORLD.gravity[0] * phys->gravityScale * dt;
|
||||
phys->velocity[1] += PHYSICS_WORLD.gravity[1] * phys->gravityScale * dt;
|
||||
phys->velocity[2] += PHYSICS_WORLD.gravity[2] * phys->gravityScale * dt;
|
||||
|
||||
float_t *pos = positions[i]->position;
|
||||
pos[0] += phys->velocity[0] * dt;
|
||||
pos[1] += phys->velocity[1] * dt;
|
||||
pos[2] += phys->velocity[2] * dt;
|
||||
}
|
||||
|
||||
/* Phase 2: dynamic vs static/kinematic. */
|
||||
for(entityid_t i = 0; i < physCount; i++) {
|
||||
if(!positions[i]) continue;
|
||||
entityphysics_t *phys = physBodies[i];
|
||||
if(phys->type != PHYSICS_BODY_DYNAMIC) continue;
|
||||
|
||||
float_t *pos = positions[i]->position;
|
||||
|
||||
for(entityid_t j = 0; j < physCount; j++) {
|
||||
if(i == j || !positions[j]) continue;
|
||||
entityphysics_t *otherPhys = physBodies[j];
|
||||
if(otherPhys->type == PHYSICS_BODY_DYNAMIC) continue;
|
||||
|
||||
vec3 normal; float_t depth;
|
||||
if(!physicsTestShapeVsShape(
|
||||
pos, phys->shape,
|
||||
positions[j]->position, otherPhys->shape,
|
||||
normal, &depth
|
||||
)) continue;
|
||||
|
||||
pos[0] += normal[0] * depth;
|
||||
pos[1] += normal[1] * depth;
|
||||
pos[2] += normal[2] * depth;
|
||||
|
||||
float_t vn = glm_vec3_dot(phys->velocity, normal);
|
||||
if(vn < 0.0f) {
|
||||
phys->velocity[0] -= vn * normal[0];
|
||||
phys->velocity[1] -= vn * normal[1];
|
||||
phys->velocity[2] -= vn * normal[2];
|
||||
}
|
||||
|
||||
if(normal[1] > PHYSICS_GROUND_THRESHOLD) phys->onGround = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Phase 3: dynamic vs dynamic. */
|
||||
for(entityid_t i = 0; i < physCount; i++) {
|
||||
if(!positions[i]) continue;
|
||||
entityphysics_t *physA = physBodies[i];
|
||||
if(physA->type != PHYSICS_BODY_DYNAMIC) continue;
|
||||
|
||||
float_t *posA = positions[i]->position;
|
||||
|
||||
for(entityid_t j = i + 1; j < physCount; j++) {
|
||||
if(!positions[j]) continue;
|
||||
entityphysics_t *physB = physBodies[j];
|
||||
if(physB->type != PHYSICS_BODY_DYNAMIC) continue;
|
||||
|
||||
float_t *posB = positions[j]->position;
|
||||
|
||||
vec3 normal; float_t depth;
|
||||
if(!physicsTestShapeVsShape(
|
||||
posA, physA->shape, posB, physB->shape, normal, &depth
|
||||
)) continue;
|
||||
|
||||
posA[0] += normal[0] * depth * 0.5f;
|
||||
posA[1] += normal[1] * depth * 0.5f;
|
||||
posA[2] += normal[2] * depth * 0.5f;
|
||||
|
||||
posB[0] -= normal[0] * depth * 0.5f;
|
||||
posB[1] -= normal[1] * depth * 0.5f;
|
||||
posB[2] -= normal[2] * depth * 0.5f;
|
||||
|
||||
float_t v_rel = glm_vec3_dot(physA->velocity, normal)
|
||||
- glm_vec3_dot(physB->velocity, normal);
|
||||
if(v_rel < 0.0f) {
|
||||
physA->velocity[0] -= v_rel * normal[0];
|
||||
physA->velocity[1] -= v_rel * normal[1];
|
||||
physA->velocity[2] -= v_rel * normal[2];
|
||||
physB->velocity[0] += v_rel * normal[0];
|
||||
physB->velocity[1] += v_rel * normal[1];
|
||||
physB->velocity[2] += v_rel * normal[2];
|
||||
}
|
||||
|
||||
if( normal[1] > PHYSICS_GROUND_THRESHOLD) physA->onGround = true;
|
||||
if(-normal[1] > PHYSICS_GROUND_THRESHOLD) physB->onGround = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Rebuild transforms for all dynamic bodies once, after all phases. */
|
||||
for(entityid_t i = 0; i < physCount; i++) {
|
||||
if(!positions[i]) continue;
|
||||
if(physBodies[i]->type != PHYSICS_BODY_DYNAMIC) continue;
|
||||
entityPositionRebuild(positions[i]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "physics/physicsshape.h"
|
||||
#include "physics/physicsbodytype.h"
|
||||
|
||||
#define PHYSICS_GROUND_THRESHOLD 0.707f
|
||||
|
||||
typedef struct {
|
||||
vec3 gravity;
|
||||
} physicsworld_t;
|
||||
|
||||
extern physicsworld_t PHYSICS_WORLD;
|
||||
|
||||
/**
|
||||
* Initializes the physics world.
|
||||
*/
|
||||
void physicsWorldInit(void);
|
||||
|
||||
/**
|
||||
* Steps the physics simulation forward.
|
||||
*
|
||||
* @param dt The time delta in seconds since the last step.
|
||||
*/
|
||||
void physicsWorldStep(const float_t dt);
|
||||
@@ -0,0 +1,13 @@
|
||||
# Copyright (c) 2025 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
scriptmanager.c
|
||||
scriptproto.c
|
||||
)
|
||||
|
||||
# Subdirectories
|
||||
@@ -0,0 +1,70 @@
|
||||
// Copyright (c) 2026 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "animation/easing.h"
|
||||
|
||||
static scriptproto_t MODULE_EASING_PROTO;
|
||||
|
||||
// Generate one handler per easing curve.
|
||||
#define EASING_MODULE_TABLE \
|
||||
X(Linear, "linear", EASING_LINEAR, easingLinear) \
|
||||
X(InSine, "inSine", EASING_IN_SINE, easingInSine) \
|
||||
X(OutSine, "outSine", EASING_OUT_SINE, easingOutSine) \
|
||||
X(InOutSine, "inOutSine", EASING_IN_OUT_SINE, easingInOutSine) \
|
||||
X(InQuad, "inQuad", EASING_IN_QUAD, easingInQuad) \
|
||||
X(OutQuad, "outQuad", EASING_OUT_QUAD, easingOutQuad) \
|
||||
X(InOutQuad, "inOutQuad", EASING_IN_OUT_QUAD, easingInOutQuad) \
|
||||
X(InCubic, "inCubic", EASING_IN_CUBIC, easingInCubic) \
|
||||
X(OutCubic, "outCubic", EASING_OUT_CUBIC, easingOutCubic) \
|
||||
X(InOutCubic, "inOutCubic", EASING_IN_OUT_CUBIC, easingInOutCubic) \
|
||||
X(InQuart, "inQuart", EASING_IN_QUART, easingInQuart) \
|
||||
X(OutQuart, "outQuart", EASING_OUT_QUART, easingOutQuart) \
|
||||
X(InOutQuart, "inOutQuart", EASING_IN_OUT_QUART, easingInOutQuart) \
|
||||
X(InBack, "inBack", EASING_IN_BACK, easingInBack) \
|
||||
X(OutBack, "outBack", EASING_OUT_BACK, easingOutBack) \
|
||||
X(InOutBack, "inOutBack", EASING_IN_OUT_BACK, easingInOutBack)
|
||||
|
||||
#define X(CName, jsName, type, fn) \
|
||||
moduleBaseFunction(moduleEasing##CName) { \
|
||||
if(argc < 1 || !jerry_value_is_number(args[0])) { \
|
||||
return moduleBaseThrow("Expected number t"); \
|
||||
} \
|
||||
float_t t = (float_t)jerry_value_as_number(args[0]); \
|
||||
return jerry_number((double)fn(t)); \
|
||||
}
|
||||
EASING_MODULE_TABLE
|
||||
#undef X
|
||||
|
||||
// Adds a callable easing function with a .type property to the Easing object.
|
||||
static void moduleEasingRegister(
|
||||
const char_t *jsName,
|
||||
uint8_t type,
|
||||
jerry_external_handler_t fn
|
||||
) {
|
||||
jerry_value_t fnVal = jerry_function_external(fn);
|
||||
|
||||
jerry_value_t typeKey = jerry_string_sz("type");
|
||||
jerry_value_t typeVal = jerry_number((double)type);
|
||||
jerry_object_set(fnVal, typeKey, typeVal);
|
||||
jerry_value_free(typeKey);
|
||||
jerry_value_free(typeVal);
|
||||
|
||||
jerry_value_t nameKey = jerry_string_sz(jsName);
|
||||
jerry_object_set(MODULE_EASING_PROTO.prototype, nameKey, fnVal);
|
||||
jerry_value_free(nameKey);
|
||||
jerry_value_free(fnVal);
|
||||
}
|
||||
|
||||
static void moduleEasing(void) {
|
||||
scriptProtoInit(&MODULE_EASING_PROTO, "Easing", sizeof(uint8_t), NULL);
|
||||
|
||||
#define X(CName, jsName, type, fn) \
|
||||
moduleEasingRegister(jsName, type, moduleEasing##CName);
|
||||
EASING_MODULE_TABLE
|
||||
#undef X
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "console/console.h"
|
||||
|
||||
static scriptproto_t MODULE_CONSOLE_PROTO;
|
||||
|
||||
moduleBaseFunction(moduleConsolePrint) {
|
||||
char_t buf[512];
|
||||
char_t msg[4096];
|
||||
size_t msgLen = 0;
|
||||
|
||||
for(jerry_length_t i = 0; i < argc; ++i) {
|
||||
jerry_value_t strVal = jerry_value_to_string(args[i]);
|
||||
moduleBaseToString(strVal, buf, sizeof(buf));
|
||||
jerry_value_free(strVal);
|
||||
|
||||
size_t partLen = strlen(buf);
|
||||
if(msgLen + partLen + 1 < sizeof(msg)) {
|
||||
stringCopy(msg + msgLen, buf, sizeof(msg) - msgLen);
|
||||
msgLen += partLen;
|
||||
}
|
||||
|
||||
if(i + 1 < argc && msgLen + 1 < sizeof(msg)) {
|
||||
msg[msgLen++] = '\t';
|
||||
msg[msgLen] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
consolePrint("%s", msg);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleConsoleGetVisible) {
|
||||
return jerry_boolean(CONSOLE.visible);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleConsoleSetVisible) {
|
||||
moduleBaseRequireArgs(1);
|
||||
CONSOLE.visible = moduleBaseArgBool(0);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
static void moduleConsole(void) {
|
||||
scriptProtoInit(
|
||||
&MODULE_CONSOLE_PROTO, "Console",
|
||||
sizeof(uint8_t), NULL
|
||||
);
|
||||
|
||||
scriptProtoDefineStaticFunc(
|
||||
&MODULE_CONSOLE_PROTO, "print", moduleConsolePrint
|
||||
);
|
||||
scriptProtoDefineStaticProp(
|
||||
&MODULE_CONSOLE_PROTO, "visible",
|
||||
moduleConsoleGetVisible, moduleConsoleSetVisible
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "display/color.h"
|
||||
#include "time/time.h"
|
||||
#include "script/scriptproto.h"
|
||||
|
||||
static scriptproto_t MODULE_COLOR_PROTO;
|
||||
|
||||
static inline color_t * moduleColorGet(
|
||||
const jerry_call_info_t *callInfo
|
||||
) {
|
||||
return (color_t*)scriptProtoGetValue(
|
||||
&MODULE_COLOR_PROTO, callInfo->this_value
|
||||
);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleColorGetR) {
|
||||
moduleBaseGetOrReturn(color_t, c, moduleColorGet);
|
||||
return jerry_number(c->r);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleColorGetG) {
|
||||
moduleBaseGetOrReturn(color_t, c, moduleColorGet);
|
||||
return jerry_number(c->g);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleColorGetB) {
|
||||
moduleBaseGetOrReturn(color_t, c, moduleColorGet);
|
||||
return jerry_number(c->b);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleColorGetA) {
|
||||
moduleBaseGetOrReturn(color_t, c, moduleColorGet);
|
||||
return jerry_number(c->a);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleColorSetR) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
moduleBaseGetOrReturn(color_t, c, moduleColorGet);
|
||||
c->r = (colorchannel8_t)moduleBaseArgInt(0);
|
||||
return args[0];
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleColorSetG) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
moduleBaseGetOrReturn(color_t, c, moduleColorGet);
|
||||
c->g = (colorchannel8_t)moduleBaseArgInt(0);
|
||||
return args[0];
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleColorSetB) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
moduleBaseGetOrReturn(color_t, c, moduleColorGet);
|
||||
c->b = (colorchannel8_t)moduleBaseArgInt(0);
|
||||
return args[0];
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleColorSetA) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
moduleBaseGetOrReturn(color_t, c, moduleColorGet);
|
||||
c->a = (colorchannel8_t)moduleBaseArgInt(0);
|
||||
return args[0];
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleColorToString) {
|
||||
moduleBaseGetOrReturn(color_t, c, moduleColorGet);
|
||||
char_t buf[64];
|
||||
stringFormat(
|
||||
buf, sizeof(buf),
|
||||
"{ \"r\": %d, \"g\": %d, \"b\": %d, \"a\": %d }",
|
||||
(int32_t)c->r, (int32_t)c->g,
|
||||
(int32_t)c->b, (int32_t)c->a
|
||||
);
|
||||
return jerry_string_sz(buf);
|
||||
}
|
||||
|
||||
static jerry_value_t moduleColorMakeObject(color_t color) {
|
||||
return scriptProtoCreateValue(&MODULE_COLOR_PROTO, &color);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleColorConstructor) {
|
||||
if(argc > 0 && !jerry_value_is_number(args[0])) return moduleBaseThrow("Color: r must be a number");
|
||||
if(argc > 1 && !jerry_value_is_number(args[1])) return moduleBaseThrow("Color: g must be a number");
|
||||
if(argc > 2 && !jerry_value_is_number(args[2])) return moduleBaseThrow("Color: b must be a number");
|
||||
if(argc > 3 && !jerry_value_is_number(args[3])) return moduleBaseThrow("Color: a must be a number");
|
||||
color_t c;
|
||||
c.r = (colorchannel8_t)moduleBaseOptInt(0, 255);
|
||||
c.g = (colorchannel8_t)moduleBaseOptInt(1, 255);
|
||||
c.b = (colorchannel8_t)moduleBaseOptInt(2, 255);
|
||||
c.a = (colorchannel8_t)moduleBaseOptInt(3, 255);
|
||||
return moduleColorMakeObject(c);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleColorRainbow) {
|
||||
float_t t = moduleBaseOptFloat(0, TIME.time * 4.0f);
|
||||
if(argc >= 2 && jerry_value_is_number(args[1])) t *= moduleBaseArgFloat(1);
|
||||
|
||||
color_t c;
|
||||
c.r = (colorchannel8_t)((sinf(t) + 1.0f) * 0.5f * 255.0f);
|
||||
c.g = (colorchannel8_t)((sinf(t + 2.0f) + 1.0f) * 0.5f * 255.0f);
|
||||
c.b = (colorchannel8_t)((sinf(t + 4.0f) + 1.0f) * 0.5f * 255.0f);
|
||||
c.a = 255;
|
||||
return moduleColorMakeObject(c);
|
||||
}
|
||||
|
||||
static void moduleColor(void) {
|
||||
scriptProtoInit(
|
||||
&MODULE_COLOR_PROTO, "Color", sizeof(color_t), moduleColorConstructor
|
||||
);
|
||||
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_COLOR_PROTO, "r", moduleColorGetR, moduleColorSetR
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_COLOR_PROTO, "g", moduleColorGetG, moduleColorSetG
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_COLOR_PROTO, "b", moduleColorGetB, moduleColorSetB
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_COLOR_PROTO, "a", moduleColorGetA, moduleColorSetA
|
||||
);
|
||||
|
||||
scriptProtoDefineStaticFunc(
|
||||
&MODULE_COLOR_PROTO, "rainbow", moduleColorRainbow
|
||||
);
|
||||
scriptProtoDefineToString(&MODULE_COLOR_PROTO, moduleColorToString);
|
||||
moduleBaseEval(COLOR_SCRIPT);
|
||||
}
|
||||
@@ -0,0 +1,420 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "script/module/math/modulevec3ref.h"
|
||||
#include "display/mesh/mesh.h"
|
||||
#include "display/mesh/cube.h"
|
||||
#include "display/mesh/quad.h"
|
||||
#include "display/mesh/sphere.h"
|
||||
#include "display/mesh/plane.h"
|
||||
#include "display/mesh/capsule.h"
|
||||
#include "display/mesh/triprism.h"
|
||||
|
||||
typedef struct {
|
||||
mesh_t mesh;
|
||||
meshvertex_t *vertices;
|
||||
int32_t vertexCount;
|
||||
bool_t initialized;
|
||||
} meshscript_t;
|
||||
|
||||
typedef struct {
|
||||
meshvertex_t *vertex;
|
||||
} meshvertexscript_t;
|
||||
|
||||
static scriptproto_t MODULE_MESH_PROTO;
|
||||
static scriptproto_t MODULE_MESH_VERTEX_PROTO;
|
||||
|
||||
static inline meshscript_t * moduleMeshGet(
|
||||
const jerry_call_info_t *callInfo
|
||||
) {
|
||||
return (meshscript_t*)scriptProtoGetValue(
|
||||
&MODULE_MESH_PROTO, callInfo->this_value
|
||||
);
|
||||
}
|
||||
|
||||
static inline meshscript_t * moduleMeshFrom(const jerry_value_t val) {
|
||||
return (meshscript_t*)scriptProtoGetValue(&MODULE_MESH_PROTO, val);
|
||||
}
|
||||
|
||||
static void moduleMeshFreeData(
|
||||
void *ptr,
|
||||
jerry_object_native_info_t *info
|
||||
) {
|
||||
(void)info;
|
||||
meshscript_t *ms = (meshscript_t*)ptr;
|
||||
if(ms->initialized) (void)meshDispose(&ms->mesh);
|
||||
if(ms->vertices) memoryFree(ms->vertices);
|
||||
memoryFree(ptr);
|
||||
}
|
||||
|
||||
// Creates a new JS Mesh object from an already-filled heap-allocated meshscript_t.
|
||||
static jerry_value_t moduleMeshWrapNew(meshscript_t *ms) {
|
||||
jerry_value_t obj = jerry_object();
|
||||
jerry_object_set_native_ptr(obj, &MODULE_MESH_PROTO.info, ms);
|
||||
jerry_object_set_proto(obj, MODULE_MESH_PROTO.prototype);
|
||||
|
||||
jerry_value_t arr = jerry_array((jerry_length_t)ms->vertexCount);
|
||||
for(int32_t i = 0; i < ms->vertexCount; i++) {
|
||||
meshvertexscript_t mv = { .vertex = &ms->vertices[i] };
|
||||
jerry_value_t vobj = scriptProtoCreateValue(&MODULE_MESH_VERTEX_PROTO, &mv);
|
||||
jerry_value_t res = jerry_object_set_index(arr, (uint32_t)i, vobj);
|
||||
jerry_value_free(res);
|
||||
jerry_value_free(vobj);
|
||||
}
|
||||
jerry_value_t key = jerry_string_sz("_verts");
|
||||
jerry_object_set(obj, key, arr);
|
||||
jerry_value_free(key);
|
||||
jerry_value_free(arr);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
// Allocates a meshscript_t for the given vertex count and buffers it.
|
||||
// Caller is responsible for populating ms->vertices before calling meshInit.
|
||||
static meshscript_t * moduleMeshAlloc(const int32_t vertexCount) {
|
||||
meshscript_t *ms = (meshscript_t*)memoryAllocate(sizeof(meshscript_t));
|
||||
memoryZero(ms, sizeof(meshscript_t));
|
||||
ms->vertices = (meshvertex_t*)memoryAllocate(
|
||||
(size_t)vertexCount * sizeof(meshvertex_t)
|
||||
);
|
||||
memoryZero(ms->vertices, (size_t)vertexCount * sizeof(meshvertex_t));
|
||||
ms->vertexCount = vertexCount;
|
||||
return ms;
|
||||
}
|
||||
|
||||
// Finalizes a meshscript_t: uploads to GPU and wraps in a JS object.
|
||||
static jerry_value_t moduleMeshFinalize(
|
||||
meshscript_t *ms,
|
||||
const meshprimitivetype_t type
|
||||
) {
|
||||
(void)meshInit(&ms->mesh, type, ms->vertexCount, ms->vertices);
|
||||
ms->initialized = true;
|
||||
return moduleMeshWrapNew(ms);
|
||||
}
|
||||
|
||||
// ---- MeshVertex ----
|
||||
|
||||
static inline meshvertexscript_t * moduleMeshVertexGet(
|
||||
const jerry_call_info_t *callInfo
|
||||
) {
|
||||
return (meshvertexscript_t*)scriptProtoGetValue(
|
||||
&MODULE_MESH_VERTEX_PROTO, callInfo->this_value
|
||||
);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleMeshVertexGetPosition) {
|
||||
moduleBaseGetOrReturn(meshvertexscript_t, mv, moduleMeshVertexGet);
|
||||
return moduleVec3RefPush(mv->vertex->pos, NULL, NULL);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleMeshVertexSetPosition) {
|
||||
moduleBaseRequireArgs(1);
|
||||
moduleBaseGetOrReturn(meshvertexscript_t, mv, moduleMeshVertexGet);
|
||||
vec3 v;
|
||||
if(!moduleVec3AnyCheck(args[0], v)) return moduleBaseThrow("Expected Vec3");
|
||||
glm_vec3_copy(v, mv->vertex->pos);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
// ---- Mesh instance ----
|
||||
|
||||
moduleBaseFunction(moduleMeshConstructor) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
|
||||
int32_t vertexCount = moduleBaseArgInt(0);
|
||||
if(vertexCount <= 0) return moduleBaseThrow("Vertex count must be > 0");
|
||||
|
||||
meshscript_t *ms = moduleMeshAlloc(vertexCount);
|
||||
jerry_object_set_native_ptr(
|
||||
callInfo->this_value, &MODULE_MESH_PROTO.info, ms
|
||||
);
|
||||
|
||||
jerry_value_t arr = jerry_array((jerry_length_t)vertexCount);
|
||||
for(int32_t i = 0; i < vertexCount; i++) {
|
||||
meshvertexscript_t mv = { .vertex = &ms->vertices[i] };
|
||||
jerry_value_t vobj = scriptProtoCreateValue(&MODULE_MESH_VERTEX_PROTO, &mv);
|
||||
jerry_value_t res = jerry_object_set_index(arr, (uint32_t)i, vobj);
|
||||
jerry_value_free(res);
|
||||
jerry_value_free(vobj);
|
||||
}
|
||||
jerry_value_t key = jerry_string_sz("_verts");
|
||||
jerry_object_set(callInfo->this_value, key, arr);
|
||||
jerry_value_free(key);
|
||||
jerry_value_free(arr);
|
||||
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleMeshGetVertices) {
|
||||
jerry_value_t key = jerry_string_sz("_verts");
|
||||
jerry_value_t arr = jerry_object_get(callInfo->this_value, key);
|
||||
jerry_value_free(key);
|
||||
return arr;
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleMeshGetVertexCount) {
|
||||
moduleBaseGetOrReturn(meshscript_t, ms, moduleMeshGet);
|
||||
return jerry_number((double)ms->vertexCount);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleMeshFlush) {
|
||||
moduleBaseGetOrReturn(meshscript_t, ms, moduleMeshGet);
|
||||
if(!ms->initialized) {
|
||||
(void)meshInit(
|
||||
&ms->mesh,
|
||||
MESH_PRIMITIVE_TYPE_TRIANGLES,
|
||||
ms->vertexCount,
|
||||
ms->vertices
|
||||
);
|
||||
ms->initialized = true;
|
||||
} else {
|
||||
(void)meshFlush(&ms->mesh, 0, -1);
|
||||
}
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleMeshDispose) {
|
||||
moduleBaseGetOrReturn(meshscript_t, ms, moduleMeshGet);
|
||||
if(ms->initialized) {
|
||||
(void)meshDispose(&ms->mesh);
|
||||
ms->initialized = false;
|
||||
}
|
||||
if(ms->vertices) {
|
||||
memoryFree(ms->vertices);
|
||||
ms->vertices = NULL;
|
||||
}
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleMeshToString) {
|
||||
meshscript_t *ms = moduleMeshGet(callInfo);
|
||||
if(!ms) return jerry_string_sz("Mesh(?)");
|
||||
char_t buf[64];
|
||||
stringFormat(buf, sizeof(buf), "Mesh(%d)", ms->vertexCount);
|
||||
return jerry_string_sz(buf);
|
||||
}
|
||||
|
||||
// ---- Static defaults (engine-owned, not GC'd) ----
|
||||
|
||||
moduleBaseFunction(moduleMeshDefaultCube) {
|
||||
return moduleBaseWrapPointer(&CUBE_MESH_SIMPLE);
|
||||
}
|
||||
moduleBaseFunction(moduleMeshDefaultQuad) {
|
||||
return moduleBaseWrapPointer(&QUAD_MESH_SIMPLE);
|
||||
}
|
||||
moduleBaseFunction(moduleMeshDefaultSphere) {
|
||||
return moduleBaseWrapPointer(&SPHERE_MESH_SIMPLE);
|
||||
}
|
||||
moduleBaseFunction(moduleMeshDefaultPlane) {
|
||||
return moduleBaseWrapPointer(&PLANE_MESH_SIMPLE);
|
||||
}
|
||||
moduleBaseFunction(moduleMeshDefaultCapsule) {
|
||||
return moduleBaseWrapPointer(&CAPSULE_MESH_SIMPLE);
|
||||
}
|
||||
moduleBaseFunction(moduleMeshDefaultTriPrism) {
|
||||
return moduleBaseWrapPointer(&TRIPRISM_MESH_SIMPLE);
|
||||
}
|
||||
|
||||
// ---- Static factory methods ----
|
||||
|
||||
// Mesh.createCube(min?, max?) — defaults to (-0.5,-0.5,-0.5)..(0.5,0.5,0.5)
|
||||
moduleBaseFunction(moduleMeshCreateCube) {
|
||||
vec3 min = { -0.5f, -0.5f, -0.5f };
|
||||
vec3 max = { 0.5f, 0.5f, 0.5f };
|
||||
if(argc >= 2) {
|
||||
if(!moduleVec3AnyCheck(args[0], min)) {
|
||||
return moduleBaseThrow("Mesh.createCube: expected Vec3 min");
|
||||
}
|
||||
if(!moduleVec3AnyCheck(args[1], max)) {
|
||||
return moduleBaseThrow("Mesh.createCube: expected Vec3 max");
|
||||
}
|
||||
}
|
||||
meshscript_t *ms = moduleMeshAlloc(CUBE_VERTEX_COUNT);
|
||||
cubeBuffer(
|
||||
ms->vertices, min, max
|
||||
#if MESH_ENABLE_COLOR
|
||||
, COLOR_WHITE_4B
|
||||
#endif
|
||||
);
|
||||
return moduleMeshFinalize(ms, CUBE_PRIMITIVE_TYPE);
|
||||
}
|
||||
|
||||
// Mesh.createQuad(minX, minY, maxX, maxY) — 2D XY quad, u0-u1=0-1 v0-v1=0-1
|
||||
moduleBaseFunction(moduleMeshCreateQuad) {
|
||||
float_t minX = -0.5f, minY = -0.5f, maxX = 0.5f, maxY = 0.5f;
|
||||
if(argc >= 4) {
|
||||
if(!jerry_value_is_number(args[0]) || !jerry_value_is_number(args[1]) ||
|
||||
!jerry_value_is_number(args[2]) || !jerry_value_is_number(args[3])) {
|
||||
return moduleBaseThrow("Mesh.createQuad: expected (minX, minY, maxX, maxY)");
|
||||
}
|
||||
minX = moduleBaseArgFloat(0);
|
||||
minY = moduleBaseArgFloat(1);
|
||||
maxX = moduleBaseArgFloat(2);
|
||||
maxY = moduleBaseArgFloat(3);
|
||||
}
|
||||
meshscript_t *ms = moduleMeshAlloc(QUAD_VERTEX_COUNT);
|
||||
quadBuffer(
|
||||
ms->vertices,
|
||||
minX, minY, maxX, maxY,
|
||||
0.0f, 0.0f, 1.0f, 1.0f
|
||||
#if MESH_ENABLE_COLOR
|
||||
, COLOR_WHITE_4B
|
||||
#endif
|
||||
);
|
||||
return moduleMeshFinalize(ms, QUAD_PRIMITIVE_TYPE);
|
||||
}
|
||||
|
||||
// Mesh.createSphere(radius?, stacks?, sectors?)
|
||||
moduleBaseFunction(moduleMeshCreateSphere) {
|
||||
float_t radius = moduleBaseOptFloat(0, 0.5f);
|
||||
int32_t stacks = moduleBaseOptInt(1, SPHERE_STACKS);
|
||||
int32_t sectors = moduleBaseOptInt(2, SPHERE_SECTORS);
|
||||
int32_t vertexCount = stacks * sectors * 6;
|
||||
vec3 center = { 0.0f, 0.0f, 0.0f };
|
||||
meshscript_t *ms = moduleMeshAlloc(vertexCount);
|
||||
sphereBuffer(
|
||||
ms->vertices, center, radius, stacks, sectors
|
||||
#if MESH_ENABLE_COLOR
|
||||
, COLOR_WHITE_4B
|
||||
#endif
|
||||
);
|
||||
return moduleMeshFinalize(ms, SPHERE_PRIMITIVE_TYPE);
|
||||
}
|
||||
|
||||
// Mesh.createPlane(width?, height?) — XZ-aligned, centered at origin
|
||||
moduleBaseFunction(moduleMeshCreatePlane) {
|
||||
float_t width = moduleBaseOptFloat(0, 1.0f);
|
||||
float_t height = moduleBaseOptFloat(1, 1.0f);
|
||||
vec3 min = { -width * 0.5f, 0.0f, -height * 0.5f };
|
||||
vec3 max = { width * 0.5f, 0.0f, height * 0.5f };
|
||||
vec2 uvMin = { 0.0f, 0.0f };
|
||||
vec2 uvMax = { 1.0f, 1.0f };
|
||||
meshscript_t *ms = moduleMeshAlloc(PLANE_VERTEX_COUNT);
|
||||
planeBuffer(
|
||||
ms->vertices, PLANE_AXIS_XZ, min, max
|
||||
#if MESH_ENABLE_COLOR
|
||||
, COLOR_WHITE_4B
|
||||
#endif
|
||||
, uvMin, uvMax
|
||||
);
|
||||
return moduleMeshFinalize(ms, PLANE_PRIMITIVE_TYPE);
|
||||
}
|
||||
|
||||
// Mesh.createCapsule(radius?, halfHeight?, capRings?, sectors?)
|
||||
moduleBaseFunction(moduleMeshCreateCapsule) {
|
||||
float_t radius = moduleBaseOptFloat(0, 0.5f);
|
||||
float_t halfHeight = moduleBaseOptFloat(1, 0.5f);
|
||||
int32_t capRings = moduleBaseOptInt(2, CAPSULE_CAP_RINGS);
|
||||
int32_t sectors = moduleBaseOptInt(3, CAPSULE_SECTORS);
|
||||
int32_t vertexCount = (2 * capRings + 1) * sectors * 6;
|
||||
vec3 center = { 0.0f, 0.0f, 0.0f };
|
||||
meshscript_t *ms = moduleMeshAlloc(vertexCount);
|
||||
capsuleBuffer(
|
||||
ms->vertices, center, radius, halfHeight, capRings, sectors
|
||||
#if MESH_ENABLE_COLOR
|
||||
, COLOR_WHITE_4B
|
||||
#endif
|
||||
);
|
||||
return moduleMeshFinalize(ms, CAPSULE_PRIMITIVE_TYPE);
|
||||
}
|
||||
|
||||
// Mesh.createTriPrism(x0, y0, x1, y1, x2, y2, minZ, maxZ)
|
||||
moduleBaseFunction(moduleMeshCreateTriPrism) {
|
||||
moduleBaseRequireArgs(8);
|
||||
float_t x0 = moduleBaseArgFloat(0);
|
||||
float_t y0 = moduleBaseArgFloat(1);
|
||||
float_t x1 = moduleBaseArgFloat(2);
|
||||
float_t y1 = moduleBaseArgFloat(3);
|
||||
float_t x2 = moduleBaseArgFloat(4);
|
||||
float_t y2 = moduleBaseArgFloat(5);
|
||||
float_t minZ = moduleBaseArgFloat(6);
|
||||
float_t maxZ = moduleBaseArgFloat(7);
|
||||
meshscript_t *ms = moduleMeshAlloc(TRIPRISM_VERTEX_COUNT);
|
||||
triPrismBuffer(
|
||||
ms->vertices, x0, y0, x1, y1, x2, y2, minZ, maxZ
|
||||
#if MESH_ENABLE_COLOR
|
||||
, COLOR_WHITE_4B
|
||||
#endif
|
||||
);
|
||||
return moduleMeshFinalize(ms, TRIPRISM_PRIMITIVE_TYPE);
|
||||
}
|
||||
|
||||
// ---- Registration ----
|
||||
|
||||
static void moduleMesh(void) {
|
||||
// MeshVertex - internal type, no global constructor
|
||||
scriptProtoInit(
|
||||
&MODULE_MESH_VERTEX_PROTO, NULL,
|
||||
sizeof(meshvertexscript_t), NULL
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_MESH_VERTEX_PROTO, "position",
|
||||
moduleMeshVertexGetPosition, moduleMeshVertexSetPosition
|
||||
);
|
||||
|
||||
// Mesh - global constructor: new Mesh(vertexCount)
|
||||
scriptProtoInit(
|
||||
&MODULE_MESH_PROTO, "Mesh",
|
||||
sizeof(meshscript_t), moduleMeshConstructor
|
||||
);
|
||||
MODULE_MESH_PROTO.info.free_cb = moduleMeshFreeData;
|
||||
|
||||
scriptProtoDefineToString(&MODULE_MESH_PROTO, moduleMeshToString);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_MESH_PROTO, "vertices",
|
||||
moduleMeshGetVertices, NULL
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_MESH_PROTO, "vertexCount",
|
||||
moduleMeshGetVertexCount, NULL
|
||||
);
|
||||
scriptProtoDefineFunc(&MODULE_MESH_PROTO, "flush", moduleMeshFlush);
|
||||
scriptProtoDefineFunc(&MODULE_MESH_PROTO, "dispose", moduleMeshDispose);
|
||||
|
||||
// Static default mesh references
|
||||
scriptProtoDefineStaticProp(
|
||||
&MODULE_MESH_PROTO, "DEFAULT_CUBE", moduleMeshDefaultCube, NULL
|
||||
);
|
||||
scriptProtoDefineStaticProp(
|
||||
&MODULE_MESH_PROTO, "DEFAULT_QUAD", moduleMeshDefaultQuad, NULL
|
||||
);
|
||||
scriptProtoDefineStaticProp(
|
||||
&MODULE_MESH_PROTO, "DEFAULT_SPHERE", moduleMeshDefaultSphere, NULL
|
||||
);
|
||||
scriptProtoDefineStaticProp(
|
||||
&MODULE_MESH_PROTO, "DEFAULT_PLANE", moduleMeshDefaultPlane, NULL
|
||||
);
|
||||
scriptProtoDefineStaticProp(
|
||||
&MODULE_MESH_PROTO, "DEFAULT_CAPSULE", moduleMeshDefaultCapsule, NULL
|
||||
);
|
||||
scriptProtoDefineStaticProp(
|
||||
&MODULE_MESH_PROTO, "DEFAULT_TRIPRISM", moduleMeshDefaultTriPrism, NULL
|
||||
);
|
||||
|
||||
// Static factory methods
|
||||
scriptProtoDefineStaticFunc(
|
||||
&MODULE_MESH_PROTO, "createCube", moduleMeshCreateCube
|
||||
);
|
||||
scriptProtoDefineStaticFunc(
|
||||
&MODULE_MESH_PROTO, "createQuad", moduleMeshCreateQuad
|
||||
);
|
||||
scriptProtoDefineStaticFunc(
|
||||
&MODULE_MESH_PROTO, "createSphere", moduleMeshCreateSphere
|
||||
);
|
||||
scriptProtoDefineStaticFunc(
|
||||
&MODULE_MESH_PROTO, "createPlane", moduleMeshCreatePlane
|
||||
);
|
||||
scriptProtoDefineStaticFunc(
|
||||
&MODULE_MESH_PROTO, "createCapsule", moduleMeshCreateCapsule
|
||||
);
|
||||
scriptProtoDefineStaticFunc(
|
||||
&MODULE_MESH_PROTO, "createTriPrism", moduleMeshCreateTriPrism
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/display/modulecolor.h"
|
||||
#include "display/screen/screen.h"
|
||||
|
||||
static scriptproto_t MODULE_SCREEN_PROTO;
|
||||
|
||||
moduleBaseFunction(moduleScreenGetWidth) {
|
||||
return jerry_number(SCREEN.width);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleScreenGetHeight) {
|
||||
return jerry_number(SCREEN.height);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleScreenGetAspect) {
|
||||
return jerry_number(SCREEN.aspect);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleScreenGetBackground) {
|
||||
return moduleColorMakeObject(SCREEN.background);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleScreenSetBackground) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireObject(0);
|
||||
color_t *color = (color_t*)scriptProtoGetValue(&MODULE_COLOR_PROTO, args[0]);
|
||||
if(!color) return moduleBaseThrow("Background must be a valid color object");
|
||||
memoryCopy(&SCREEN.background, color, sizeof(color_t));
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleScreenToString) {
|
||||
char_t buf[128];
|
||||
stringFormat(
|
||||
buf, sizeof(buf),
|
||||
"{ \"width\": %d, \"height\": %d, \"aspect\": %.2f }",
|
||||
SCREEN.width, SCREEN.height, SCREEN.aspect
|
||||
);
|
||||
return jerry_string_sz(buf);
|
||||
}
|
||||
|
||||
static void moduleScreen(void) {
|
||||
scriptProtoInit(
|
||||
&MODULE_SCREEN_PROTO, "Screen", sizeof(screen_t), NULL
|
||||
);
|
||||
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_SCREEN_PROTO, "width", moduleScreenGetWidth, NULL
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_SCREEN_PROTO, "height", moduleScreenGetHeight, NULL
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_SCREEN_PROTO, "aspect", moduleScreenGetAspect, NULL
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_SCREEN_PROTO, "background",
|
||||
moduleScreenGetBackground, moduleScreenSetBackground
|
||||
);
|
||||
|
||||
scriptProtoDefineToString(&MODULE_SCREEN_PROTO, moduleScreenToString);
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "script/module/display/modulecolor.h"
|
||||
#include "script/module/math/modulevec2.h"
|
||||
#include "script/module/math/modulevec3.h"
|
||||
#include "display/spritebatch/spritebatch.h"
|
||||
|
||||
static scriptproto_t MODULE_SPRITEBATCH_PROTO;
|
||||
|
||||
moduleBaseFunction(moduleSpriteBatchGetSpriteCount) {
|
||||
return jerry_number(SPRITEBATCH.spriteCount);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleSpriteBatchPush) {
|
||||
#if MESH_ENABLE_COLOR
|
||||
moduleBaseRequireArgs(9);
|
||||
#else
|
||||
moduleBaseRequireArgs(8);
|
||||
#endif
|
||||
|
||||
moduleBaseRequireNumber(0);
|
||||
moduleBaseRequireNumber(1);
|
||||
moduleBaseRequireNumber(2);
|
||||
moduleBaseRequireNumber(3);
|
||||
|
||||
#if MESH_ENABLE_COLOR
|
||||
color_t *col = (color_t *)scriptProtoGetValue(&MODULE_COLOR_PROTO, args[4]);
|
||||
if(!col) return moduleBaseThrow("color must be a Color object");
|
||||
moduleBaseRequireNumber(5);
|
||||
moduleBaseRequireNumber(6);
|
||||
moduleBaseRequireNumber(7);
|
||||
moduleBaseRequireNumber(8);
|
||||
spriteBatchPush(
|
||||
moduleBaseArgFloat(0), moduleBaseArgFloat(1),
|
||||
moduleBaseArgFloat(2), moduleBaseArgFloat(3),
|
||||
*col,
|
||||
moduleBaseArgFloat(5), moduleBaseArgFloat(6),
|
||||
moduleBaseArgFloat(7), moduleBaseArgFloat(8)
|
||||
);
|
||||
#else
|
||||
moduleBaseRequireNumber(4);
|
||||
moduleBaseRequireNumber(5);
|
||||
moduleBaseRequireNumber(6);
|
||||
moduleBaseRequireNumber(7);
|
||||
spriteBatchPush(
|
||||
moduleBaseArgFloat(0), moduleBaseArgFloat(1),
|
||||
moduleBaseArgFloat(2), moduleBaseArgFloat(3),
|
||||
moduleBaseArgFloat(4), moduleBaseArgFloat(5),
|
||||
moduleBaseArgFloat(6), moduleBaseArgFloat(7)
|
||||
);
|
||||
#endif
|
||||
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleSpriteBatchPush3D) {
|
||||
#if MESH_ENABLE_COLOR
|
||||
moduleBaseRequireArgs(5);
|
||||
#else
|
||||
moduleBaseRequireArgs(4);
|
||||
#endif
|
||||
|
||||
float_t *min = moduleVec3From(args[0]);
|
||||
if(!min) return moduleBaseThrow("min must be a Vec3");
|
||||
float_t *max = moduleVec3From(args[1]);
|
||||
if(!max) return moduleBaseThrow("max must be a Vec3");
|
||||
|
||||
#if MESH_ENABLE_COLOR
|
||||
color_t *col = (color_t *)scriptProtoGetValue(&MODULE_COLOR_PROTO, args[2]);
|
||||
if(!col) return moduleBaseThrow("color must be a Color object");
|
||||
float_t *uvMin = moduleVec2From(args[3]);
|
||||
if(!uvMin) return moduleBaseThrow("uvMin must be a Vec2");
|
||||
float_t *uvMax = moduleVec2From(args[4]);
|
||||
if(!uvMax) return moduleBaseThrow("uvMax must be a Vec2");
|
||||
spriteBatchPush3D(min, max, *col, uvMin, uvMax);
|
||||
#else
|
||||
float_t *uvMin = moduleVec2From(args[2]);
|
||||
if(!uvMin) return moduleBaseThrow("uvMin must be a Vec2");
|
||||
float_t *uvMax = moduleVec2From(args[3]);
|
||||
if(!uvMax) return moduleBaseThrow("uvMax must be a Vec2");
|
||||
spriteBatchPush3D(min, max, uvMin, uvMax);
|
||||
#endif
|
||||
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleSpriteBatchClear) {
|
||||
spriteBatchClear();
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleSpriteBatchFlush) {
|
||||
spriteBatchFlush();
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
static void moduleSpriteBatch(void) {
|
||||
scriptProtoInit(
|
||||
&MODULE_SPRITEBATCH_PROTO, "SpriteBatch", sizeof(uint8_t), NULL
|
||||
);
|
||||
|
||||
scriptProtoDefineStaticProp(
|
||||
&MODULE_SPRITEBATCH_PROTO, "spriteCount",
|
||||
moduleSpriteBatchGetSpriteCount, NULL
|
||||
);
|
||||
|
||||
scriptProtoDefineStaticFunc(
|
||||
&MODULE_SPRITEBATCH_PROTO, "push", moduleSpriteBatchPush
|
||||
);
|
||||
scriptProtoDefineStaticFunc(
|
||||
&MODULE_SPRITEBATCH_PROTO, "push3D", moduleSpriteBatchPush3D
|
||||
);
|
||||
scriptProtoDefineStaticFunc(
|
||||
&MODULE_SPRITEBATCH_PROTO, "clear", moduleSpriteBatchClear
|
||||
);
|
||||
scriptProtoDefineStaticFunc(
|
||||
&MODULE_SPRITEBATCH_PROTO, "flush", moduleSpriteBatchFlush
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "script/module/display/modulecolor.h"
|
||||
#include "display/text/text.h"
|
||||
|
||||
static scriptproto_t MODULE_TEXT_PROTO;
|
||||
|
||||
moduleBaseFunction(moduleTextDraw) {
|
||||
moduleBaseRequireArgs(3); moduleBaseRequireNumber(0); moduleBaseRequireNumber(1);
|
||||
moduleBaseRequireString(2);
|
||||
|
||||
float_t x = moduleBaseArgFloat(0);
|
||||
float_t y = moduleBaseArgFloat(1);
|
||||
|
||||
char_t text[1024];
|
||||
moduleBaseToString(args[2], text, sizeof(text));
|
||||
|
||||
color_t col = COLOR_WHITE;
|
||||
if(argc >= 4) {
|
||||
color_t *c = (color_t *)scriptProtoGetValue(&MODULE_COLOR_PROTO, args[3]);
|
||||
if(c) col = *c;
|
||||
}
|
||||
|
||||
errorret_t err = textDraw(
|
||||
x, y, text, col, &FONT_DEFAULT
|
||||
);
|
||||
if(err.code != ERROR_OK) {
|
||||
errorCatch(errorPrint(err));
|
||||
return moduleBaseThrow("Text draw failed");
|
||||
}
|
||||
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleTextMeasure) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireString(0);
|
||||
|
||||
char_t text[1024];
|
||||
moduleBaseToString(args[0], text, sizeof(text));
|
||||
|
||||
int32_t w, h;
|
||||
textMeasure(text, &FONT_DEFAULT, &w, &h);
|
||||
|
||||
jerry_value_t obj = jerry_object();
|
||||
jerry_value_t wKey = jerry_string_sz("width");
|
||||
jerry_value_t hKey = jerry_string_sz("height");
|
||||
jerry_value_t wVal = jerry_number(w);
|
||||
jerry_value_t hVal = jerry_number(h);
|
||||
jerry_object_set(obj, wKey, wVal);
|
||||
jerry_object_set(obj, hKey, hVal);
|
||||
jerry_value_free(wKey);
|
||||
jerry_value_free(hKey);
|
||||
jerry_value_free(wVal);
|
||||
jerry_value_free(hVal);
|
||||
return obj;
|
||||
}
|
||||
|
||||
static void moduleText(void) {
|
||||
scriptProtoInit(&MODULE_TEXT_PROTO, "Text", sizeof(uint8_t), NULL);
|
||||
scriptProtoDefineStaticFunc(&MODULE_TEXT_PROTO, "draw", moduleTextDraw);
|
||||
scriptProtoDefineStaticFunc(&MODULE_TEXT_PROTO, "measure", moduleTextMeasure);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "engine/engine.h"
|
||||
|
||||
static scriptproto_t MODULE_ENGINE_PROTO;
|
||||
|
||||
moduleBaseFunction(moduleEngineExit) {
|
||||
ENGINE.running = false;
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
static void moduleEngine(void) {
|
||||
scriptProtoInit(
|
||||
&MODULE_ENGINE_PROTO, "Engine",
|
||||
sizeof(uint8_t), NULL
|
||||
);
|
||||
|
||||
scriptProtoDefineStaticFunc(
|
||||
&MODULE_ENGINE_PROTO, "exit", moduleEngineExit
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "entity/entity.h"
|
||||
#include "entity/component/display/entitycamera.h"
|
||||
#include "moduleentityposition.h"
|
||||
|
||||
static scriptproto_t MODULE_ENTITY_CAMERA_PROTO;
|
||||
|
||||
static entitycamera_t * moduleEntityCameraGet(
|
||||
const jerry_call_info_t *callInfo
|
||||
) {
|
||||
componenthandle_t *h = scriptProtoGetValue(
|
||||
&MODULE_ENTITY_CAMERA_PROTO, callInfo->this_value
|
||||
);
|
||||
if(!h) return NULL;
|
||||
return (entitycamera_t*)componentGetData(h->eid, h->cid, COMPONENT_TYPE_CAMERA);
|
||||
}
|
||||
|
||||
// ---- Getters ----
|
||||
|
||||
moduleBaseFunction(moduleEntityCameraGetZNear) {
|
||||
moduleBaseGetOrReturn(entitycamera_t, cam, moduleEntityCameraGet);
|
||||
return jerry_number(cam->nearClip);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityCameraGetZFar) {
|
||||
moduleBaseGetOrReturn(entitycamera_t, cam, moduleEntityCameraGet);
|
||||
return jerry_number(cam->farClip);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityCameraGetFov) {
|
||||
moduleBaseGetOrReturn(entitycamera_t, cam, moduleEntityCameraGet);
|
||||
if(
|
||||
cam->projType != ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE &&
|
||||
cam->projType != ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED
|
||||
) return jerry_undefined();
|
||||
return jerry_number(cam->perspective.fov);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityCameraGetProjectionType) {
|
||||
moduleBaseGetOrReturn(entitycamera_t, cam, moduleEntityCameraGet);
|
||||
return jerry_number(cam->projType);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityCameraGetOrthoTop) {
|
||||
moduleBaseGetOrReturn(entitycamera_t, cam, moduleEntityCameraGet);
|
||||
if(cam->projType != ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) return jerry_undefined();
|
||||
return jerry_number(cam->orthographic.top);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityCameraGetOrthoBottom) {
|
||||
moduleBaseGetOrReturn(entitycamera_t, cam, moduleEntityCameraGet);
|
||||
if(cam->projType != ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) return jerry_undefined();
|
||||
return jerry_number(cam->orthographic.bottom);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityCameraGetOrthoLeft) {
|
||||
moduleBaseGetOrReturn(entitycamera_t, cam, moduleEntityCameraGet);
|
||||
if(cam->projType != ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) return jerry_undefined();
|
||||
return jerry_number(cam->orthographic.left);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityCameraGetOrthoRight) {
|
||||
moduleBaseGetOrReturn(entitycamera_t, cam, moduleEntityCameraGet);
|
||||
if(cam->projType != ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) return jerry_undefined();
|
||||
return jerry_number(cam->orthographic.right);
|
||||
}
|
||||
|
||||
// ---- Setters ----
|
||||
|
||||
moduleBaseFunction(moduleEntityCameraSetZNear) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
moduleBaseGetOrReturn(entitycamera_t, cam, moduleEntityCameraGet);
|
||||
cam->nearClip = moduleBaseArgFloat(0);
|
||||
return args[0];
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityCameraSetZFar) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
moduleBaseGetOrReturn(entitycamera_t, cam, moduleEntityCameraGet);
|
||||
cam->farClip = moduleBaseArgFloat(0);
|
||||
return args[0];
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityCameraSetFov) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
moduleBaseGetOrReturn(entitycamera_t, cam, moduleEntityCameraGet);
|
||||
if(
|
||||
cam->projType != ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE &&
|
||||
cam->projType != ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED
|
||||
) return jerry_undefined();
|
||||
cam->perspective.fov = moduleBaseArgFloat(0);
|
||||
return args[0];
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityCameraSetProjectionType) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
moduleBaseGetOrReturn(entitycamera_t, cam, moduleEntityCameraGet);
|
||||
int32_t t = moduleBaseArgInt(0);
|
||||
if(
|
||||
t < ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE ||
|
||||
t > ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC
|
||||
) return moduleBaseThrow("Invalid projection type");
|
||||
cam->projType = (entitycameraprojectiontype_t)t;
|
||||
return args[0];
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityCameraSetOrthoTop) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
moduleBaseGetOrReturn(entitycamera_t, cam, moduleEntityCameraGet);
|
||||
if(cam->projType != ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) return jerry_undefined();
|
||||
cam->orthographic.top = moduleBaseArgFloat(0);
|
||||
return args[0];
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityCameraSetOrthoBottom) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
moduleBaseGetOrReturn(entitycamera_t, cam, moduleEntityCameraGet);
|
||||
if(cam->projType != ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) return jerry_undefined();
|
||||
cam->orthographic.bottom = moduleBaseArgFloat(0);
|
||||
return args[0];
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityCameraSetOrthoLeft) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
moduleBaseGetOrReturn(entitycamera_t, cam, moduleEntityCameraGet);
|
||||
if(cam->projType != ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) return jerry_undefined();
|
||||
cam->orthographic.left = moduleBaseArgFloat(0);
|
||||
return args[0];
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityCameraSetOrthoRight) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
moduleBaseGetOrReturn(entitycamera_t, cam, moduleEntityCameraGet);
|
||||
if(cam->projType != ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) return jerry_undefined();
|
||||
cam->orthographic.right = moduleBaseArgFloat(0);
|
||||
return args[0];
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityCameraAdd) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
entityid_t id = (entityid_t)moduleBaseArgInt(0);
|
||||
componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_CAMERA);
|
||||
componenthandle_t h = { .eid = id, .cid = comp };
|
||||
return scriptProtoCreateValue(&MODULE_ENTITY_CAMERA_PROTO, &h);
|
||||
}
|
||||
|
||||
static void moduleEntityCAMERA(void) {
|
||||
scriptProtoInit(
|
||||
&MODULE_ENTITY_CAMERA_PROTO, NULL, sizeof(componenthandle_t), NULL
|
||||
);
|
||||
|
||||
scriptProtoDefineProp(&MODULE_ENTITY_CAMERA_PROTO, "zNear",
|
||||
moduleEntityCameraGetZNear, moduleEntityCameraSetZNear);
|
||||
scriptProtoDefineProp(&MODULE_ENTITY_CAMERA_PROTO, "zFar",
|
||||
moduleEntityCameraGetZFar, moduleEntityCameraSetZFar);
|
||||
scriptProtoDefineProp(&MODULE_ENTITY_CAMERA_PROTO, "fov",
|
||||
moduleEntityCameraGetFov, moduleEntityCameraSetFov);
|
||||
scriptProtoDefineProp(&MODULE_ENTITY_CAMERA_PROTO, "projectionType",
|
||||
moduleEntityCameraGetProjectionType, moduleEntityCameraSetProjectionType);
|
||||
scriptProtoDefineProp(&MODULE_ENTITY_CAMERA_PROTO, "orthoTop",
|
||||
moduleEntityCameraGetOrthoTop, moduleEntityCameraSetOrthoTop);
|
||||
scriptProtoDefineProp(&MODULE_ENTITY_CAMERA_PROTO, "orthoBottom",
|
||||
moduleEntityCameraGetOrthoBottom, moduleEntityCameraSetOrthoBottom);
|
||||
scriptProtoDefineProp(&MODULE_ENTITY_CAMERA_PROTO, "orthoLeft",
|
||||
moduleEntityCameraGetOrthoLeft, moduleEntityCameraSetOrthoLeft);
|
||||
scriptProtoDefineProp(&MODULE_ENTITY_CAMERA_PROTO, "orthoRight",
|
||||
moduleEntityCameraGetOrthoRight, moduleEntityCameraSetOrthoRight);
|
||||
|
||||
moduleBaseSetInt("CAMERA_TYPE_ORTHOGRAPHIC",
|
||||
ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC);
|
||||
moduleBaseSetInt("CAMERA_TYPE_PERSPECTIVE",
|
||||
ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE);
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/module/math/modulevec3ref.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "entity/entity.h"
|
||||
#include "entity/component/physics/entityphysics.h"
|
||||
#include "moduleentityposition.h"
|
||||
|
||||
static scriptproto_t MODULE_ENTITY_PHYSICS_PROTO;
|
||||
|
||||
static entityphysics_t * moduleEntityPhysicsGet(
|
||||
const jerry_call_info_t *callInfo
|
||||
) {
|
||||
componenthandle_t *h = scriptProtoGetValue(
|
||||
&MODULE_ENTITY_PHYSICS_PROTO, callInfo->this_value
|
||||
);
|
||||
if(!h) return NULL;
|
||||
return entityPhysicsGet(h->eid, h->cid);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityPhysicsGetVelocity) {
|
||||
moduleBaseGetOrReturn(entityphysics_t, phys, moduleEntityPhysicsGet);
|
||||
return moduleVec3RefPush(phys->velocity, NULL, NULL);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityPhysicsSetVelocity) {
|
||||
moduleBaseRequireArgs(1);
|
||||
moduleBaseGetOrReturn(entityphysics_t, phys, moduleEntityPhysicsGet);
|
||||
vec3 v;
|
||||
if(!moduleVec3AnyCheck(args[0], v)) return moduleBaseThrow("Expected Vec3");
|
||||
glm_vec3_copy(v, phys->velocity);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityPhysicsGetOnGround) {
|
||||
moduleBaseGetOrReturn(entityphysics_t, phys, moduleEntityPhysicsGet);
|
||||
return jerry_boolean(phys->onGround);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityPhysicsGetBodyType) {
|
||||
moduleBaseGetOrReturn(entityphysics_t, phys, moduleEntityPhysicsGet);
|
||||
return jerry_number(phys->type);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityPhysicsSetBodyType) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
moduleBaseGetOrReturn(entityphysics_t, phys, moduleEntityPhysicsGet);
|
||||
phys->type = (physicsbodytype_t)moduleBaseArgInt(0);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityPhysicsApplyImpulse) {
|
||||
moduleBaseRequireArgs(1);
|
||||
moduleBaseGetOrReturn(entityphysics_t, phys, moduleEntityPhysicsGet);
|
||||
if(phys->type == PHYSICS_BODY_STATIC) return jerry_undefined();
|
||||
vec3 impulse;
|
||||
if(!moduleVec3Check(args[0], impulse)) return moduleBaseThrow("Expected Vec3 impulse");
|
||||
glm_vec3_add(phys->velocity, impulse, phys->velocity);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityPhysicsSetShapeCube) {
|
||||
moduleBaseRequireArgs(1);
|
||||
moduleBaseGetOrReturn(entityphysics_t, phys, moduleEntityPhysicsGet);
|
||||
vec3 half;
|
||||
if(!moduleVec3Check(args[0], half)) return moduleBaseThrow("Expected Vec3 halfExtents");
|
||||
phys->shape.type = PHYSICS_SHAPE_CUBE;
|
||||
glm_vec3_copy(half, phys->shape.data.cube.halfExtents);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityPhysicsSetShapeSphere) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
moduleBaseGetOrReturn(entityphysics_t, phys, moduleEntityPhysicsGet);
|
||||
phys->shape.type = PHYSICS_SHAPE_SPHERE;
|
||||
phys->shape.data.sphere.radius = moduleBaseArgFloat(0);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityPhysicsSetShapeCapsule) {
|
||||
moduleBaseRequireArgs(2); moduleBaseRequireNumber(0); moduleBaseRequireNumber(1);
|
||||
moduleBaseGetOrReturn(entityphysics_t, phys, moduleEntityPhysicsGet);
|
||||
phys->shape.type = PHYSICS_SHAPE_CAPSULE;
|
||||
phys->shape.data.capsule.radius = moduleBaseArgFloat(0);
|
||||
phys->shape.data.capsule.halfHeight = moduleBaseArgFloat(1);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityPhysicsSetShapePlane) {
|
||||
moduleBaseRequireArgs(2); moduleBaseRequireNumber(1);
|
||||
moduleBaseGetOrReturn(entityphysics_t, phys, moduleEntityPhysicsGet);
|
||||
vec3 normal;
|
||||
if(!moduleVec3Check(args[0], normal)) return moduleBaseThrow("Expected Vec3 normal");
|
||||
phys->shape.type = PHYSICS_SHAPE_PLANE;
|
||||
glm_vec3_copy(normal, phys->shape.data.plane.normal);
|
||||
phys->shape.data.plane.distance = moduleBaseArgFloat(1);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityPhysicsAdd) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
entityid_t id = (entityid_t)moduleBaseArgInt(0);
|
||||
componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_PHYSICS);
|
||||
componenthandle_t h = { .eid = id, .cid = comp };
|
||||
return scriptProtoCreateValue(&MODULE_ENTITY_PHYSICS_PROTO, &h);
|
||||
}
|
||||
|
||||
static void moduleEntityPHYSICS(void) {
|
||||
scriptProtoInit(
|
||||
&MODULE_ENTITY_PHYSICS_PROTO, NULL, sizeof(componenthandle_t), NULL
|
||||
);
|
||||
|
||||
scriptProtoDefineProp(&MODULE_ENTITY_PHYSICS_PROTO, "velocity",
|
||||
moduleEntityPhysicsGetVelocity, moduleEntityPhysicsSetVelocity);
|
||||
scriptProtoDefineProp(&MODULE_ENTITY_PHYSICS_PROTO, "onGround",
|
||||
moduleEntityPhysicsGetOnGround, NULL);
|
||||
scriptProtoDefineProp(&MODULE_ENTITY_PHYSICS_PROTO, "bodyType",
|
||||
moduleEntityPhysicsGetBodyType, moduleEntityPhysicsSetBodyType);
|
||||
|
||||
scriptProtoDefineFunc(&MODULE_ENTITY_PHYSICS_PROTO, "applyImpulse",
|
||||
moduleEntityPhysicsApplyImpulse);
|
||||
scriptProtoDefineFunc(&MODULE_ENTITY_PHYSICS_PROTO, "setShapeCube",
|
||||
moduleEntityPhysicsSetShapeCube);
|
||||
scriptProtoDefineFunc(&MODULE_ENTITY_PHYSICS_PROTO, "setShapeSphere",
|
||||
moduleEntityPhysicsSetShapeSphere);
|
||||
scriptProtoDefineFunc(&MODULE_ENTITY_PHYSICS_PROTO, "setShapeCapsule",
|
||||
moduleEntityPhysicsSetShapeCapsule);
|
||||
scriptProtoDefineFunc(&MODULE_ENTITY_PHYSICS_PROTO, "setShapePlane",
|
||||
moduleEntityPhysicsSetShapePlane);
|
||||
|
||||
moduleBaseSetInt("PHYSICS_BODY_STATIC", PHYSICS_BODY_STATIC);
|
||||
moduleBaseSetInt("PHYSICS_BODY_DYNAMIC", PHYSICS_BODY_DYNAMIC);
|
||||
moduleBaseSetInt("PHYSICS_BODY_KINEMATIC", PHYSICS_BODY_KINEMATIC);
|
||||
|
||||
moduleBaseSetInt("PHYSICS_SHAPE_CUBE", PHYSICS_SHAPE_CUBE);
|
||||
moduleBaseSetInt("PHYSICS_SHAPE_SPHERE", PHYSICS_SHAPE_SPHERE);
|
||||
moduleBaseSetInt("PHYSICS_SHAPE_CAPSULE", PHYSICS_SHAPE_CAPSULE);
|
||||
moduleBaseSetInt("PHYSICS_SHAPE_PLANE", PHYSICS_SHAPE_PLANE);
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/module/math/modulevec3ref.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "entity/entity.h"
|
||||
#include "entity/component/display/entityposition.h"
|
||||
|
||||
#ifndef COMPONENT_HANDLE_DEFINED
|
||||
#define COMPONENT_HANDLE_DEFINED
|
||||
typedef struct {
|
||||
entityid_t eid;
|
||||
componentid_t cid;
|
||||
} componenthandle_t;
|
||||
#endif
|
||||
|
||||
static scriptproto_t MODULE_ENTITY_POSITION_PROTO;
|
||||
|
||||
static entityposition_t * moduleEntityPositionGet(
|
||||
const jerry_call_info_t *callInfo
|
||||
) {
|
||||
componenthandle_t *h = scriptProtoGetValue(
|
||||
&MODULE_ENTITY_POSITION_PROTO, callInfo->this_value
|
||||
);
|
||||
if(!h) return NULL;
|
||||
return entityPositionGet(h->eid, h->cid);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityPositionGetPosition) {
|
||||
moduleBaseGetOrReturn(entityposition_t, pos, moduleEntityPositionGet);
|
||||
return moduleVec3RefPush(pos->position, (void(*)(void*))entityPositionRebuild, pos);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityPositionSetPosition) {
|
||||
moduleBaseRequireArgs(1);
|
||||
moduleBaseGetOrReturn(entityposition_t, pos, moduleEntityPositionGet);
|
||||
vec3 v;
|
||||
if(!moduleVec3AnyCheck(args[0], v)) return moduleBaseThrow("Expected Vec3");
|
||||
glm_vec3_copy(v, pos->position);
|
||||
entityPositionRebuild(pos);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityPositionGetRotation) {
|
||||
moduleBaseGetOrReturn(entityposition_t, pos, moduleEntityPositionGet);
|
||||
return moduleVec3RefPush(pos->rotation, (void(*)(void*))entityPositionRebuild, pos);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityPositionSetRotation) {
|
||||
moduleBaseRequireArgs(1);
|
||||
moduleBaseGetOrReturn(entityposition_t, pos, moduleEntityPositionGet);
|
||||
vec3 v;
|
||||
if(!moduleVec3AnyCheck(args[0], v)) return moduleBaseThrow("Expected Vec3");
|
||||
glm_vec3_copy(v, pos->rotation);
|
||||
entityPositionRebuild(pos);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityPositionGetScale) {
|
||||
moduleBaseGetOrReturn(entityposition_t, pos, moduleEntityPositionGet);
|
||||
return moduleVec3RefPush(pos->scale, (void(*)(void*))entityPositionRebuild, pos);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityPositionSetScale) {
|
||||
moduleBaseRequireArgs(1);
|
||||
moduleBaseGetOrReturn(entityposition_t, pos, moduleEntityPositionGet);
|
||||
vec3 v;
|
||||
if(!moduleVec3AnyCheck(args[0], v)) return moduleBaseThrow("Expected Vec3");
|
||||
glm_vec3_copy(v, pos->scale);
|
||||
entityPositionRebuild(pos);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityPositionLookAt) {
|
||||
moduleBaseRequireArgs(1);
|
||||
moduleBaseGetOrReturn(entityposition_t, pos, moduleEntityPositionGet);
|
||||
vec3 target;
|
||||
if(!moduleVec3AnyCheck(args[0], target)) return moduleBaseThrow("Expected Vec3 target");
|
||||
vec3 up = { 0.0f, 1.0f, 0.0f };
|
||||
if(argc >= 2 && !moduleVec3AnyCheck(args[1], up)) return moduleBaseThrow("Expected Vec3 up");
|
||||
componenthandle_t *h = scriptProtoGetValue(
|
||||
&MODULE_ENTITY_POSITION_PROTO, callInfo->this_value
|
||||
);
|
||||
entityPositionLookAt(h->eid, h->cid, target, up, pos->position);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityPositionGetParent) {
|
||||
moduleBaseGetOrReturn(entityposition_t, pos, moduleEntityPositionGet);
|
||||
if(pos->parentEntityId == ENTITY_ID_INVALID) return jerry_null();
|
||||
componenthandle_t ph = { .eid = pos->parentEntityId, .cid = pos->parentComponentId };
|
||||
return scriptProtoCreateValue(&MODULE_ENTITY_POSITION_PROTO, &ph);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityPositionSetParentProp) {
|
||||
componenthandle_t *h = scriptProtoGetValue(
|
||||
&MODULE_ENTITY_POSITION_PROTO, callInfo->this_value
|
||||
);
|
||||
if(!h) return jerry_undefined();
|
||||
if(argc < 1 || jerry_value_is_null(args[0]) || jerry_value_is_undefined(args[0])) {
|
||||
entityPositionSetParent(h->eid, h->cid, ENTITY_ID_INVALID, COMPONENT_ID_INVALID);
|
||||
return jerry_undefined();
|
||||
}
|
||||
componenthandle_t *ph = scriptProtoGetValue(&MODULE_ENTITY_POSITION_PROTO, args[0]);
|
||||
if(!ph) return moduleBaseThrow("Expected EntityPosition");
|
||||
entityPositionSetParent(h->eid, h->cid, ph->eid, ph->cid);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityPositionAdd) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
entityid_t id = (entityid_t)moduleBaseArgInt(0);
|
||||
componentid_t comp = entityAddComponent(id, COMPONENT_TYPE_POSITION);
|
||||
componenthandle_t h = { .eid = id, .cid = comp };
|
||||
return scriptProtoCreateValue(&MODULE_ENTITY_POSITION_PROTO, &h);
|
||||
}
|
||||
|
||||
static void moduleEntityPOSITION(void) {
|
||||
scriptProtoInit(
|
||||
&MODULE_ENTITY_POSITION_PROTO, NULL, sizeof(componenthandle_t), NULL
|
||||
);
|
||||
scriptProtoDefineProp(&MODULE_ENTITY_POSITION_PROTO, "position",
|
||||
moduleEntityPositionGetPosition, moduleEntityPositionSetPosition);
|
||||
scriptProtoDefineProp(&MODULE_ENTITY_POSITION_PROTO, "rotation",
|
||||
moduleEntityPositionGetRotation, moduleEntityPositionSetRotation);
|
||||
scriptProtoDefineProp(&MODULE_ENTITY_POSITION_PROTO, "scale",
|
||||
moduleEntityPositionGetScale, moduleEntityPositionSetScale);
|
||||
scriptProtoDefineProp(&MODULE_ENTITY_POSITION_PROTO, "parent",
|
||||
moduleEntityPositionGetParent, moduleEntityPositionSetParentProp);
|
||||
scriptProtoDefineFunc(&MODULE_ENTITY_POSITION_PROTO, "lookAt",
|
||||
moduleEntityPositionLookAt);
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/module/display/modulemesh.h"
|
||||
#include "script/module/display/modulecolor.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "entity/entity.h"
|
||||
#include "entity/component/display/entityrenderable.h"
|
||||
#include "moduleentityposition.h"
|
||||
|
||||
static scriptproto_t MODULE_ENTITY_RENDERABLE_PROTO;
|
||||
|
||||
static entityrenderable_t * moduleEntityRenderableGet(
|
||||
const jerry_call_info_t *callInfo
|
||||
) {
|
||||
componenthandle_t *h = scriptProtoGetValue(
|
||||
&MODULE_ENTITY_RENDERABLE_PROTO, callInfo->this_value
|
||||
);
|
||||
if(!h) return NULL;
|
||||
return (entityrenderable_t*)componentGetData(
|
||||
h->eid, h->cid, COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityRenderableGetType) {
|
||||
moduleBaseGetOrReturn(entityrenderable_t, r, moduleEntityRenderableGet);
|
||||
return jerry_number(r->type);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityRenderableSetType) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
moduleBaseGetOrReturn(entityrenderable_t, r, moduleEntityRenderableGet);
|
||||
r->type = (entityrenderabletype_t)moduleBaseArgInt(0);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityRenderableGetMesh) {
|
||||
moduleBaseGetOrReturn(entityrenderable_t, r, moduleEntityRenderableGet);
|
||||
if(!r->mesh) return jerry_undefined();
|
||||
return moduleBaseWrapPointer(r->mesh);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityRenderableSetMesh) {
|
||||
moduleBaseRequireArgs(1);
|
||||
moduleBaseGetOrReturn(entityrenderable_t, r, moduleEntityRenderableGet);
|
||||
meshscript_t *ms = moduleMeshFrom(args[0]);
|
||||
if(ms) { r->mesh = &ms->mesh; return jerry_undefined(); }
|
||||
mesh_t *raw = (mesh_t*)moduleBaseUnwrapPointer(args[0]);
|
||||
if(raw) { r->mesh = raw; return jerry_undefined(); }
|
||||
return moduleBaseThrow("Expected a Mesh object or mesh constant");
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityRenderableGetColor) {
|
||||
moduleBaseGetOrReturn(entityrenderable_t, r, moduleEntityRenderableGet);
|
||||
return moduleColorMakeObject(r->material.unlit.color);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityRenderableSetColor) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireObject(0);
|
||||
color_t *color = (color_t*)scriptProtoGetValue(&MODULE_COLOR_PROTO, args[0]);
|
||||
if(!color) return moduleBaseThrow("Renderable.color: expected valid color object");
|
||||
moduleBaseGetOrReturn(entityrenderable_t, r, moduleEntityRenderableGet);
|
||||
memoryCopy(&r->material.unlit.color, color, sizeof(color_t));
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityRenderableSpriteBatchAdd) {
|
||||
moduleBaseRequireArgs(1);
|
||||
componenthandle_t *h = scriptProtoGetValue(
|
||||
&MODULE_ENTITY_RENDERABLE_PROTO, callInfo->this_value
|
||||
);
|
||||
if(!h) return jerry_undefined();
|
||||
|
||||
spritebatchsprite_t sprite;
|
||||
glm_vec3_zero(sprite.min);
|
||||
glm_vec3_zero(sprite.max);
|
||||
glm_vec2_zero(sprite.uvMin);
|
||||
glm_vec2_zero(sprite.uvMax);
|
||||
|
||||
jerry_value_t obj = args[0];
|
||||
#define getVecField(name, dst, n) do { \
|
||||
jerry_value_t _v = jerry_object_get_sz(obj, name); \
|
||||
if(!jerry_value_is_undefined(_v)) moduleVec##n##AnyCheck(_v, dst); \
|
||||
jerry_value_free(_v); \
|
||||
} while(0)
|
||||
getVecField("min", sprite.min, 3);
|
||||
getVecField("max", sprite.max, 3);
|
||||
getVecField("uvMin", sprite.uvMin, 2);
|
||||
getVecField("uvMax", sprite.uvMax, 2);
|
||||
#undef getVecField
|
||||
|
||||
entityRenderableSpriteBatchAdd(h->eid, h->cid, &sprite);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityRenderableSpriteBatchClear) {
|
||||
componenthandle_t *h = scriptProtoGetValue(
|
||||
&MODULE_ENTITY_RENDERABLE_PROTO, callInfo->this_value
|
||||
);
|
||||
if(!h) return jerry_undefined();
|
||||
entityRenderableSpriteBatchClear(h->eid, h->cid);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
static void jsRenderCallbackFree(void *user) {
|
||||
jerry_value_t *cb = (jerry_value_t *)user;
|
||||
jerry_value_free(*cb);
|
||||
memoryFree(cb);
|
||||
}
|
||||
|
||||
static errorret_t jsRenderCallbackBridge(
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const mat4 view,
|
||||
const mat4 proj,
|
||||
const mat4 model,
|
||||
void *user
|
||||
) {
|
||||
(void)entityId; (void)componentId;
|
||||
(void)view; (void)proj; (void)model;
|
||||
jerry_value_t *cb = (jerry_value_t *)user;
|
||||
jerry_value_t ret = jerry_call(*cb, jerry_undefined(), NULL, 0);
|
||||
if(jerry_value_is_exception(ret)) {
|
||||
jerry_value_free(ret);
|
||||
errorThrow("Renderable callback threw a JS exception");
|
||||
}
|
||||
jerry_value_free(ret);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityRenderableSetCallback) {
|
||||
componenthandle_t *h = scriptProtoGetValue(
|
||||
&MODULE_ENTITY_RENDERABLE_PROTO, callInfo->this_value
|
||||
);
|
||||
if(!h) return jerry_undefined();
|
||||
|
||||
entityrenderable_t *r = (entityrenderable_t *)componentGetData(
|
||||
h->eid, h->cid, COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
|
||||
if(
|
||||
r->type == ENTITY_RENDERABLE_TYPE_CALLBACK &&
|
||||
r->userFree &&
|
||||
r->user
|
||||
) {
|
||||
r->userFree(r->user);
|
||||
r->user = NULL;
|
||||
}
|
||||
|
||||
if(argc >= 1 && jerry_value_is_function(args[0])) {
|
||||
jerry_value_t *cb = (jerry_value_t *)memoryAllocate(sizeof(jerry_value_t));
|
||||
*cb = jerry_value_copy(args[0]);
|
||||
r->type = ENTITY_RENDERABLE_TYPE_CALLBACK;
|
||||
r->callback = jsRenderCallbackBridge;
|
||||
r->userFree = jsRenderCallbackFree;
|
||||
r->user = cb;
|
||||
} else {
|
||||
r->type = ENTITY_RENDERABLE_TYPE_CALLBACK;
|
||||
r->callback = NULL;
|
||||
r->userFree = NULL;
|
||||
r->user = NULL;
|
||||
}
|
||||
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
static void moduleEntityRENDERABLE(void) {
|
||||
scriptProtoInit(
|
||||
&MODULE_ENTITY_RENDERABLE_PROTO, NULL, sizeof(componenthandle_t), NULL
|
||||
);
|
||||
scriptProtoDefineProp(&MODULE_ENTITY_RENDERABLE_PROTO, "type",
|
||||
moduleEntityRenderableGetType, moduleEntityRenderableSetType);
|
||||
scriptProtoDefineProp(&MODULE_ENTITY_RENDERABLE_PROTO, "mesh",
|
||||
moduleEntityRenderableGetMesh, moduleEntityRenderableSetMesh);
|
||||
scriptProtoDefineProp(&MODULE_ENTITY_RENDERABLE_PROTO, "color",
|
||||
moduleEntityRenderableGetColor, moduleEntityRenderableSetColor);
|
||||
scriptProtoDefineFunc(&MODULE_ENTITY_RENDERABLE_PROTO, "addSprite",
|
||||
moduleEntityRenderableSpriteBatchAdd);
|
||||
scriptProtoDefineFunc(&MODULE_ENTITY_RENDERABLE_PROTO, "clearSprites",
|
||||
moduleEntityRenderableSpriteBatchClear);
|
||||
|
||||
scriptProtoDefineFunc(&MODULE_ENTITY_RENDERABLE_PROTO, "setCallback",
|
||||
moduleEntityRenderableSetCallback);
|
||||
|
||||
moduleBaseSetInt("ENTITY_RENDERABLE_TYPE_MATERIAL",
|
||||
ENTITY_RENDERABLE_TYPE_MATERIAL);
|
||||
moduleBaseSetInt("ENTITY_RENDERABLE_TYPE_SPRITEBATCH",
|
||||
ENTITY_RENDERABLE_TYPE_SPRITEBATCH);
|
||||
moduleBaseSetInt("ENTITY_RENDERABLE_TYPE_CALLBACK",
|
||||
ENTITY_RENDERABLE_TYPE_CALLBACK);
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/module/math/modulevec3.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "entity/entity.h"
|
||||
#include "entity/component/trigger/entitytrigger.h"
|
||||
#include "moduleentityposition.h"
|
||||
|
||||
static scriptproto_t MODULE_ENTITY_TRIGGER_PROTO;
|
||||
|
||||
static entitytrigger_t * moduleEntityTriggerGet(
|
||||
const jerry_call_info_t *callInfo
|
||||
) {
|
||||
componenthandle_t *h = scriptProtoGetValue(
|
||||
&MODULE_ENTITY_TRIGGER_PROTO, callInfo->this_value
|
||||
);
|
||||
if(!h) return NULL;
|
||||
return entityTriggerGet(h->eid, h->cid);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityTriggerGetMin) {
|
||||
moduleBaseGetOrReturn(entitytrigger_t, t, moduleEntityTriggerGet);
|
||||
return moduleVec3Push(t->min);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityTriggerSetMin) {
|
||||
moduleBaseRequireArgs(1);
|
||||
moduleBaseGetOrReturn(entitytrigger_t, t, moduleEntityTriggerGet);
|
||||
vec3 v;
|
||||
if(!moduleVec3AnyCheck(args[0], v)) return moduleBaseThrow("Expected Vec3");
|
||||
glm_vec3_copy(v, t->min);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityTriggerGetMax) {
|
||||
moduleBaseGetOrReturn(entitytrigger_t, t, moduleEntityTriggerGet);
|
||||
return moduleVec3Push(t->max);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityTriggerSetMax) {
|
||||
moduleBaseRequireArgs(1);
|
||||
moduleBaseGetOrReturn(entitytrigger_t, t, moduleEntityTriggerGet);
|
||||
vec3 v;
|
||||
if(!moduleVec3AnyCheck(args[0], v)) return moduleBaseThrow("Expected Vec3");
|
||||
glm_vec3_copy(v, t->max);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityTriggerSetBounds) {
|
||||
moduleBaseRequireArgs(2);
|
||||
componenthandle_t *h = scriptProtoGetValue(
|
||||
&MODULE_ENTITY_TRIGGER_PROTO, callInfo->this_value
|
||||
);
|
||||
if(!h) return jerry_undefined();
|
||||
vec3 mn, mx;
|
||||
if(!moduleVec3AnyCheck(args[0], mn)) return moduleBaseThrow("Expected Vec3 min");
|
||||
if(!moduleVec3AnyCheck(args[1], mx)) return moduleBaseThrow("Expected Vec3 max");
|
||||
entityTriggerSetBounds(h->eid, h->cid, mn, mx);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityTriggerContains) {
|
||||
moduleBaseRequireArgs(1);
|
||||
componenthandle_t *h = scriptProtoGetValue(
|
||||
&MODULE_ENTITY_TRIGGER_PROTO, callInfo->this_value
|
||||
);
|
||||
if(!h) return jerry_boolean(false);
|
||||
vec3 point;
|
||||
if(!moduleVec3AnyCheck(args[0], point)) return moduleBaseThrow("Expected Vec3");
|
||||
return jerry_boolean(entityTriggerContains(h->eid, h->cid, point));
|
||||
}
|
||||
|
||||
static void moduleEntityTRIGGER(void) {
|
||||
scriptProtoInit(
|
||||
&MODULE_ENTITY_TRIGGER_PROTO, NULL, sizeof(componenthandle_t), NULL
|
||||
);
|
||||
scriptProtoDefineProp(&MODULE_ENTITY_TRIGGER_PROTO, "min",
|
||||
moduleEntityTriggerGetMin, moduleEntityTriggerSetMin);
|
||||
scriptProtoDefineProp(&MODULE_ENTITY_TRIGGER_PROTO, "max",
|
||||
moduleEntityTriggerGetMax, moduleEntityTriggerSetMax);
|
||||
scriptProtoDefineFunc(&MODULE_ENTITY_TRIGGER_PROTO, "setBounds",
|
||||
moduleEntityTriggerSetBounds);
|
||||
scriptProtoDefineFunc(&MODULE_ENTITY_TRIGGER_PROTO, "contains",
|
||||
moduleEntityTriggerContains);
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "entity/entity.h"
|
||||
#include "entity/entitymanager.h"
|
||||
|
||||
#include "component/moduleentityposition.h"
|
||||
#include "component/moduleentitycamera.h"
|
||||
#include "component/moduleentityrenderable.h"
|
||||
#include "component/moduleentityphysics.h"
|
||||
#include "component/moduleentitytrigger.h"
|
||||
|
||||
typedef struct {
|
||||
entityid_t id;
|
||||
} entityscript_t;
|
||||
|
||||
static scriptproto_t MODULE_ENTITY_PROTO;
|
||||
|
||||
static inline entityscript_t * moduleEntityGet(
|
||||
const jerry_call_info_t *callInfo
|
||||
) {
|
||||
return (entityscript_t*)scriptProtoGetValue(
|
||||
&MODULE_ENTITY_PROTO, callInfo->this_value
|
||||
);
|
||||
}
|
||||
|
||||
// Getters
|
||||
moduleBaseFunction(moduleEntityGetId) {
|
||||
moduleBaseGetOrReturn(entityscript_t, inst, moduleEntityGet);
|
||||
return jerry_number(inst->id);
|
||||
}
|
||||
|
||||
// Getter defined for each component type
|
||||
static jerry_value_t moduleEntityGetComponent(
|
||||
const jerry_call_info_t *callInfo,
|
||||
const jerry_value_t args[],
|
||||
const jerry_length_t argc,
|
||||
const componenttype_t type,
|
||||
scriptproto_t *proto
|
||||
) {
|
||||
assertNotNull(callInfo, "Call info must not be null");
|
||||
assertTrue(argc >= 0, "Argc must be non-negative");
|
||||
assertTrue(
|
||||
type > COMPONENT_TYPE_NULL && type < COMPONENT_TYPE_COUNT,
|
||||
"Invalid component type"
|
||||
);
|
||||
|
||||
entityid_t entityId;
|
||||
componentid_t compId;
|
||||
|
||||
entityscript_t *inst = moduleEntityGet(callInfo);
|
||||
if(!inst) return jerry_undefined();
|
||||
entityId = inst->id;
|
||||
|
||||
// Find the component ID of the requested type.
|
||||
compId = entityGetComponent(entityId, type);
|
||||
if(compId == COMPONENT_ID_INVALID) {
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
componenthandle_t h = { .eid = entityId, .cid = compId };
|
||||
return scriptProtoCreateValue(proto, &h);
|
||||
}
|
||||
|
||||
#define X(enumName, type, field, init, dispose, fixedUpdate) \
|
||||
moduleBaseFunction(moduleEntityGet##enumName) { \
|
||||
return moduleEntityGetComponent( \
|
||||
callInfo, \
|
||||
args, \
|
||||
argc, \
|
||||
COMPONENT_TYPE_##enumName, \
|
||||
&MODULE_ENTITY_##enumName##_PROTO \
|
||||
); \
|
||||
}
|
||||
#include "entity/componentlist.h"
|
||||
#undef X
|
||||
|
||||
moduleBaseFunction(moduleEntityToString) {
|
||||
entityscript_t *inst = moduleEntityGet(callInfo);
|
||||
if(!inst) return jerry_string_sz("Entity(?)");
|
||||
|
||||
char_t components[128];
|
||||
size_t clen = 0;
|
||||
bool_t first = true;
|
||||
|
||||
for(componenttype_t t = 1; t < COMPONENT_TYPE_COUNT; t++) {
|
||||
if(entityGetComponent(inst->id, t) == COMPONENT_ID_INVALID) continue;
|
||||
if(!first) {
|
||||
stringCopy(components + clen, ", ", sizeof(components) - clen);
|
||||
clen += 2;
|
||||
}
|
||||
const char_t *name = COMPONENT_DEFINITIONS[t].enumName;
|
||||
stringCopy(components + clen, name, sizeof(components) - clen);
|
||||
clen += strlen(name);
|
||||
first = false;
|
||||
}
|
||||
|
||||
char_t buf[256];
|
||||
if(first) {
|
||||
stringFormat(
|
||||
buf, sizeof(buf),
|
||||
"{ \"id\": %d, \"components\": [] }", inst->id
|
||||
);
|
||||
} else {
|
||||
stringFormat(
|
||||
buf, sizeof(buf),
|
||||
"{ \"id\": %d, \"components\": [ %s ] }", inst->id, components
|
||||
);
|
||||
}
|
||||
return jerry_string_sz(buf);
|
||||
}
|
||||
|
||||
// Methods
|
||||
moduleBaseFunction(moduleEntityConstructor) {
|
||||
entityscript_t *inst = (entityscript_t*)memoryAllocate(
|
||||
sizeof(entityscript_t)
|
||||
);
|
||||
inst->id = entityManagerAdd();
|
||||
jerry_object_set_native_ptr(
|
||||
callInfo->this_value, &MODULE_ENTITY_PROTO.info, inst
|
||||
);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityAddComponent) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
componenttype_t type = (componenttype_t)moduleBaseArgInt(0);
|
||||
if(type <= COMPONENT_TYPE_NULL || type >= COMPONENT_TYPE_COUNT) {
|
||||
return moduleBaseThrow("Entity.add: invalid component type");
|
||||
}
|
||||
|
||||
entityscript_t *inst = moduleEntityGet(callInfo);
|
||||
if(!inst) return moduleBaseThrow("Entity.add: invalid entity");
|
||||
|
||||
componentid_t id = entityAddComponent(inst->id, type);
|
||||
componenthandle_t h = { .eid = inst->id, .cid = id };
|
||||
switch(type) {
|
||||
#define X(enumName, stype, field, init, dispose, fixedUpdate) \
|
||||
case COMPONENT_TYPE_##enumName: \
|
||||
return scriptProtoCreateValue(&MODULE_ENTITY_##enumName##_PROTO, &h);
|
||||
#include "entity/componentlist.h"
|
||||
#undef X
|
||||
default: return jerry_number(id);
|
||||
}
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityDisposeMethod) {
|
||||
moduleBaseGetOrReturn(entityscript_t, inst, moduleEntityGet);
|
||||
entityDispose(inst->id);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
static void moduleEntity(void) {
|
||||
// Init the entity prototype
|
||||
scriptProtoInit(
|
||||
&MODULE_ENTITY_PROTO,
|
||||
"Entity",
|
||||
sizeof(entityscript_t),
|
||||
moduleEntityConstructor
|
||||
);
|
||||
|
||||
scriptProtoDefineToString(&MODULE_ENTITY_PROTO, moduleEntityToString);
|
||||
|
||||
// Entity Methods
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_ENTITY_PROTO, "add", moduleEntityAddComponent
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_ENTITY_PROTO, "dispose", moduleEntityDisposeMethod
|
||||
);
|
||||
|
||||
// Entity props
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_ENTITY_PROTO, "id", moduleEntityGetId, NULL
|
||||
);
|
||||
|
||||
// Init component type modules.
|
||||
#define X(enumName, type, field, iMethod, dMethod, fMethod) \
|
||||
moduleEntity##enumName(); \
|
||||
scriptProtoDefineProp( \
|
||||
&MODULE_ENTITY_PROTO, \
|
||||
COMPONENT_DEFINITIONS[COMPONENT_TYPE_##enumName].name, \
|
||||
moduleEntityGet##enumName, \
|
||||
NULL \
|
||||
); \
|
||||
moduleBaseSetInt(#enumName, COMPONENT_TYPE_##enumName);
|
||||
#include "entity/componentlist.h"
|
||||
#undef X
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "script/module/math/modulevec2.h"
|
||||
#include "input/input.h"
|
||||
|
||||
static scriptproto_t MODULE_INPUT_PROTO;
|
||||
|
||||
// Static Methods
|
||||
moduleBaseFunction(moduleInputBind) {
|
||||
moduleBaseRequireArgs(2); moduleBaseRequireString(0); moduleBaseRequireNumber(1);
|
||||
|
||||
char_t strBtn[128];
|
||||
moduleBaseToString(args[0], strBtn, sizeof(strBtn));
|
||||
if(strBtn[0] == '\0') {
|
||||
return moduleBaseThrow("Input.bind: button name cannot be empty");
|
||||
}
|
||||
|
||||
const inputaction_t action = (inputaction_t)moduleBaseArgInt(1);
|
||||
if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) {
|
||||
return moduleBaseThrow("Input.bind: invalid action");
|
||||
}
|
||||
|
||||
inputbutton_t btn = inputButtonGetByName(strBtn);
|
||||
if(btn.type == INPUT_BUTTON_TYPE_NONE) {
|
||||
return moduleBaseThrow("Input.bind: invalid button name");
|
||||
}
|
||||
|
||||
inputBind(btn, action);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleInputIsDown) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
const inputaction_t action = (inputaction_t)moduleBaseArgInt(0);
|
||||
if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) {
|
||||
return moduleBaseThrow("Input.isDown: invalid action");
|
||||
}
|
||||
return jerry_boolean(inputIsDown(action));
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleInputPressed) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
const inputaction_t action = (inputaction_t)moduleBaseArgInt(0);
|
||||
if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) {
|
||||
return moduleBaseThrow("Input.pressed: invalid action");
|
||||
}
|
||||
return jerry_boolean(inputPressed(action));
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleInputReleased) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
const inputaction_t action = (inputaction_t)moduleBaseArgInt(0);
|
||||
if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) {
|
||||
return moduleBaseThrow("Input.released: invalid action");
|
||||
}
|
||||
return jerry_boolean(inputReleased(action));
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleInputGetValue) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
const inputaction_t action = (inputaction_t)moduleBaseArgInt(0);
|
||||
if(action <= INPUT_ACTION_NULL || action >= INPUT_ACTION_COUNT) {
|
||||
return moduleBaseThrow("Input.getValue: invalid action");
|
||||
}
|
||||
return jerry_number(inputGetCurrentValue(action));
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleInputAxis) {
|
||||
moduleBaseRequireArgs(2); moduleBaseRequireNumber(0); moduleBaseRequireNumber(1);
|
||||
const inputaction_t neg = (inputaction_t)moduleBaseArgInt(0);
|
||||
const inputaction_t pos = (inputaction_t)moduleBaseArgInt(1);
|
||||
if(neg <= INPUT_ACTION_NULL || neg >= INPUT_ACTION_COUNT) {
|
||||
return moduleBaseThrow("Input.axis: invalid negative action");
|
||||
}
|
||||
if(pos <= INPUT_ACTION_NULL || pos >= INPUT_ACTION_COUNT) {
|
||||
return moduleBaseThrow("Input.axis: invalid positive action");
|
||||
}
|
||||
return jerry_number(inputAxis(neg, pos));
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleInputAxis2D) {
|
||||
moduleBaseRequireArgs(4);
|
||||
moduleBaseRequireNumber(0); moduleBaseRequireNumber(1);
|
||||
moduleBaseRequireNumber(2); moduleBaseRequireNumber(3);
|
||||
const inputaction_t negX = (inputaction_t)moduleBaseArgInt(0);
|
||||
const inputaction_t posX = (inputaction_t)moduleBaseArgInt(1);
|
||||
const inputaction_t negY = (inputaction_t)moduleBaseArgInt(2);
|
||||
const inputaction_t posY = (inputaction_t)moduleBaseArgInt(3);
|
||||
if(negX <= INPUT_ACTION_NULL || negX >= INPUT_ACTION_COUNT) {
|
||||
return moduleBaseThrow("Input.axis2D: invalid negX action");
|
||||
}
|
||||
if(posX <= INPUT_ACTION_NULL || posX >= INPUT_ACTION_COUNT) {
|
||||
return moduleBaseThrow("Input.axis2D: invalid posX action");
|
||||
}
|
||||
if(negY <= INPUT_ACTION_NULL || negY >= INPUT_ACTION_COUNT) {
|
||||
return moduleBaseThrow("Input.axis2D: invalid negY action");
|
||||
}
|
||||
if(posY <= INPUT_ACTION_NULL || posY >= INPUT_ACTION_COUNT) {
|
||||
return moduleBaseThrow("Input.axis2D: invalid posY action");
|
||||
}
|
||||
vec2 result;
|
||||
inputAxis2D(negX, posX, negY, posY, result);
|
||||
return moduleVec2Push(result);
|
||||
}
|
||||
|
||||
static void moduleInput(void) {
|
||||
moduleBaseEval(INPUT_ACTION_SCRIPT);
|
||||
|
||||
moduleBaseEval(
|
||||
""
|
||||
#ifdef DUSK_INPUT_KEYBOARD
|
||||
"var INPUT_KEYBOARD = true;\n"
|
||||
#endif
|
||||
#ifdef DUSK_INPUT_GAMEPAD
|
||||
"var INPUT_GAMEPAD = true;\n"
|
||||
#endif
|
||||
#ifdef DUSK_INPUT_POINTER
|
||||
"var INPUT_POINTER = true;\n"
|
||||
#endif
|
||||
#ifdef DUSK_INPUT_TOUCH
|
||||
"var INPUT_TOUCH = true;\n"
|
||||
#endif
|
||||
);
|
||||
|
||||
scriptProtoInit(
|
||||
&MODULE_INPUT_PROTO, "Input", sizeof(uint8_t), NULL
|
||||
);
|
||||
|
||||
scriptProtoDefineStaticFunc(
|
||||
&MODULE_INPUT_PROTO, "bind", moduleInputBind
|
||||
);
|
||||
scriptProtoDefineStaticFunc(
|
||||
&MODULE_INPUT_PROTO, "isDown", moduleInputIsDown
|
||||
);
|
||||
scriptProtoDefineStaticFunc(
|
||||
&MODULE_INPUT_PROTO, "pressed", moduleInputPressed
|
||||
);
|
||||
scriptProtoDefineStaticFunc(
|
||||
&MODULE_INPUT_PROTO, "released", moduleInputReleased
|
||||
);
|
||||
scriptProtoDefineStaticFunc(
|
||||
&MODULE_INPUT_PROTO, "getValue", moduleInputGetValue
|
||||
);
|
||||
scriptProtoDefineStaticFunc(
|
||||
&MODULE_INPUT_PROTO, "axis", moduleInputAxis
|
||||
);
|
||||
scriptProtoDefineStaticFunc(
|
||||
&MODULE_INPUT_PROTO, "axis2D", moduleInputAxis2D
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "cglm/cglm.h"
|
||||
#include "modulevec3.h"
|
||||
#include "modulevec4.h"
|
||||
|
||||
static scriptproto_t MODULE_MAT4_PROTO;
|
||||
|
||||
static inline void * moduleMatGet(const jerry_call_info_t *callInfo) {
|
||||
return scriptProtoGetValue(&MODULE_MAT4_PROTO, callInfo->this_value);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleMatConstructor) {
|
||||
float_t (*ptr)[4] = (float_t (*)[4])memoryAllocate(sizeof(mat4));
|
||||
glm_mat4_identity(ptr);
|
||||
jerry_object_set_native_ptr(
|
||||
callInfo->this_value, &MODULE_MAT4_PROTO.info, ptr
|
||||
);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleMatMul) {
|
||||
moduleBaseRequireArgs(1);
|
||||
float_t (*a)[4] = (float_t (*)[4])moduleMatGet(callInfo);
|
||||
if(!a) return moduleBaseThrow("Mat4.mul: invalid this");
|
||||
float_t (*b)[4] = (float_t (*)[4])scriptProtoGetValue(
|
||||
&MODULE_MAT4_PROTO, args[0]
|
||||
);
|
||||
if(!b) return moduleBaseThrow("Mat4.mul: argument must be a Mat4");
|
||||
mat4 r;
|
||||
glm_mat4_mul(a, b, r);
|
||||
return scriptProtoCreateValue(&MODULE_MAT4_PROTO, r);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleMatTranspose) {
|
||||
float_t (*m)[4] = (float_t (*)[4])moduleMatGet(callInfo);
|
||||
if(!m) return moduleBaseThrow("Mat4.transpose: invalid this");
|
||||
mat4 r;
|
||||
glm_mat4_transpose_to(m, r);
|
||||
return scriptProtoCreateValue(&MODULE_MAT4_PROTO, r);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleMatInverse) {
|
||||
float_t (*m)[4] = (float_t (*)[4])moduleMatGet(callInfo);
|
||||
if(!m) return moduleBaseThrow("Mat4.inverse: invalid this");
|
||||
mat4 r;
|
||||
glm_mat4_inv(m, r);
|
||||
return scriptProtoCreateValue(&MODULE_MAT4_PROTO, r);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleMatDeterminant) {
|
||||
float_t (*m)[4] = (float_t (*)[4])moduleMatGet(callInfo);
|
||||
if(!m) return moduleBaseThrow("Mat4.determinant: invalid this");
|
||||
return jerry_number(glm_mat4_det(m));
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleMatMulVec3) {
|
||||
moduleBaseRequireArgs(1);
|
||||
float_t (*m)[4] = (float_t (*)[4])moduleMatGet(callInfo);
|
||||
if(!m) return moduleBaseThrow("Mat4.mulVec3: invalid this");
|
||||
vec3 vin;
|
||||
if(!moduleVec3Check(args[0], vin)) {
|
||||
return moduleBaseThrow("Mat4.mulVec3: argument must be a Vec3");
|
||||
}
|
||||
float_t w = moduleBaseOptFloat(1, 1.0f);
|
||||
vec3 vout;
|
||||
glm_mat4_mulv3(m, vin, w, vout);
|
||||
return moduleVec3Push(vout);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleMatMulVec4) {
|
||||
moduleBaseRequireArgs(1);
|
||||
float_t (*m)[4] = (float_t (*)[4])moduleMatGet(callInfo);
|
||||
if(!m) return moduleBaseThrow("Mat4.mulVec4: invalid this");
|
||||
vec4 vin;
|
||||
if(!moduleVec4Check(args[0], vin)) {
|
||||
return moduleBaseThrow("Mat4.mulVec4: argument must be a Vec4");
|
||||
}
|
||||
vec4 vout;
|
||||
glm_mat4_mulv(m, vin, vout);
|
||||
return moduleVec4Push(vout);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleMatTranslate) {
|
||||
moduleBaseRequireArgs(1);
|
||||
float_t (*m)[4] = (float_t (*)[4])moduleMatGet(callInfo);
|
||||
if(!m) return moduleBaseThrow("Mat4.translate: invalid this");
|
||||
vec3 tv;
|
||||
if(!moduleVec3Check(args[0], tv)) {
|
||||
return moduleBaseThrow("Mat4.translate: argument must be a Vec3");
|
||||
}
|
||||
mat4 r;
|
||||
glm_mat4_copy(m, r);
|
||||
glm_translate(r, tv);
|
||||
return scriptProtoCreateValue(&MODULE_MAT4_PROTO, r);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleMatScale) {
|
||||
moduleBaseRequireArgs(1);
|
||||
float_t (*m)[4] = (float_t (*)[4])moduleMatGet(callInfo);
|
||||
if(!m) return moduleBaseThrow("Mat4.scale: invalid this");
|
||||
vec3 sv;
|
||||
if(!moduleVec3Check(args[0], sv)) {
|
||||
return moduleBaseThrow("Mat4.scale: argument must be a Vec3");
|
||||
}
|
||||
mat4 r;
|
||||
glm_mat4_copy(m, r);
|
||||
glm_scale(r, sv);
|
||||
return scriptProtoCreateValue(&MODULE_MAT4_PROTO, r);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleMatStaticIdentity) {
|
||||
mat4 r;
|
||||
glm_mat4_identity(r);
|
||||
return scriptProtoCreateValue(&MODULE_MAT4_PROTO, r);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleMatStaticPerspective) {
|
||||
moduleBaseRequireArgs(4);
|
||||
moduleBaseRequireNumber(0); moduleBaseRequireNumber(1);
|
||||
moduleBaseRequireNumber(2); moduleBaseRequireNumber(3);
|
||||
mat4 r;
|
||||
glm_perspective(
|
||||
moduleBaseArgFloat(0), moduleBaseArgFloat(1),
|
||||
moduleBaseArgFloat(2), moduleBaseArgFloat(3),
|
||||
r
|
||||
);
|
||||
return scriptProtoCreateValue(&MODULE_MAT4_PROTO, r);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleMatStaticLookAt) {
|
||||
moduleBaseRequireArgs(3);
|
||||
vec3 eye, center, up;
|
||||
if(!moduleVec3Check(args[0], eye)) {
|
||||
return moduleBaseThrow("Mat4.lookAt: eye must be a Vec3");
|
||||
}
|
||||
if(!moduleVec3Check(args[1], center)) {
|
||||
return moduleBaseThrow("Mat4.lookAt: center must be a Vec3");
|
||||
}
|
||||
if(!moduleVec3Check(args[2], up)) {
|
||||
return moduleBaseThrow("Mat4.lookAt: up must be a Vec3");
|
||||
}
|
||||
mat4 r;
|
||||
glm_lookat(eye, center, up, r);
|
||||
return scriptProtoCreateValue(&MODULE_MAT4_PROTO, r);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleMatToString) {
|
||||
float_t (*m)[4] = (float_t (*)[4])moduleMatGet(callInfo);
|
||||
if(!m) return jerry_string_sz("Mat4(?)");
|
||||
char_t buf[256];
|
||||
stringFormat(
|
||||
buf, sizeof(buf),
|
||||
"Mat4([%g,%g,%g,%g], [%g,%g,%g,%g],"
|
||||
" [%g,%g,%g,%g], [%g,%g,%g,%g])",
|
||||
m[0][0], m[0][1], m[0][2], m[0][3],
|
||||
m[1][0], m[1][1], m[1][2], m[1][3],
|
||||
m[2][0], m[2][1], m[2][2], m[2][3],
|
||||
m[3][0], m[3][1], m[3][2], m[3][3]
|
||||
);
|
||||
return jerry_string_sz(buf);
|
||||
}
|
||||
|
||||
static inline jerry_value_t moduleMat4Push(float (*m)[4]) {
|
||||
return scriptProtoCreateValue(&MODULE_MAT4_PROTO, m);
|
||||
}
|
||||
|
||||
static inline bool_t moduleMat4Check(jerry_value_t val, float (*out)[4]) {
|
||||
float_t (*m)[4] = (float_t (*)[4])scriptProtoGetValue(
|
||||
&MODULE_MAT4_PROTO, val
|
||||
);
|
||||
if(!m) return false;
|
||||
glm_mat4_copy(m, out);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void moduleMat4(void) {
|
||||
scriptProtoInit(
|
||||
&MODULE_MAT4_PROTO, "Mat4", sizeof(mat4), moduleMatConstructor
|
||||
);
|
||||
|
||||
scriptProtoDefineToString(&MODULE_MAT4_PROTO, moduleMatToString);
|
||||
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_MAT4_PROTO, "mul", moduleMatMul
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_MAT4_PROTO, "transpose", moduleMatTranspose
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_MAT4_PROTO, "inverse", moduleMatInverse
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_MAT4_PROTO, "determinant", moduleMatDeterminant
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_MAT4_PROTO, "mulVec3", moduleMatMulVec3
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_MAT4_PROTO, "mulVec4", moduleMatMulVec4
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_MAT4_PROTO, "translate", moduleMatTranslate
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_MAT4_PROTO, "scale", moduleMatScale
|
||||
);
|
||||
|
||||
scriptProtoDefineStaticFunc(
|
||||
&MODULE_MAT4_PROTO, "identity", moduleMatStaticIdentity
|
||||
);
|
||||
scriptProtoDefineStaticFunc(
|
||||
&MODULE_MAT4_PROTO, "perspective", moduleMatStaticPerspective
|
||||
);
|
||||
scriptProtoDefineStaticFunc(
|
||||
&MODULE_MAT4_PROTO, "lookAt", moduleMatStaticLookAt
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "modulevec2.h"
|
||||
#include "modulevec3.h"
|
||||
#include "modulevec3ref.h"
|
||||
#include "modulevec4.h"
|
||||
#include "modulemat4.h"
|
||||
|
||||
static void moduleMath(void) {
|
||||
moduleVec2();
|
||||
moduleVec3();
|
||||
moduleVec3Ref();
|
||||
moduleVec4();
|
||||
moduleMat4();
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "cglm/cglm.h"
|
||||
|
||||
static scriptproto_t MODULE_VEC2_PROTO;
|
||||
|
||||
static inline float_t * moduleVec2Get(const jerry_call_info_t *callInfo) {
|
||||
return (float_t *)scriptProtoGetValue(
|
||||
&MODULE_VEC2_PROTO, callInfo->this_value
|
||||
);
|
||||
}
|
||||
|
||||
static inline float_t * moduleVec2From(jerry_value_t val) {
|
||||
return (float_t *)scriptProtoGetValue(&MODULE_VEC2_PROTO, val);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec2Constructor) {
|
||||
float_t *ptr = (float_t *)memoryAllocate(sizeof(vec2));
|
||||
ptr[0] = moduleBaseOptFloat(0, 0.0f);
|
||||
ptr[1] = moduleBaseOptFloat(1, 0.0f);
|
||||
jerry_object_set_native_ptr(
|
||||
callInfo->this_value, &MODULE_VEC2_PROTO.info, ptr
|
||||
);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec2GetX) {
|
||||
moduleBaseGetOrReturn(float_t, v, moduleVec2Get);
|
||||
return jerry_number(v[0]);
|
||||
}
|
||||
moduleBaseFunction(moduleVec2SetX) {
|
||||
moduleBaseRequireArgs(1);
|
||||
moduleBaseGetOrReturn(float_t, v, moduleVec2Get);
|
||||
v[0] = moduleBaseArgFloat(0);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec2GetY) {
|
||||
moduleBaseGetOrReturn(float_t, v, moduleVec2Get);
|
||||
return jerry_number(v[1]);
|
||||
}
|
||||
moduleBaseFunction(moduleVec2SetY) {
|
||||
moduleBaseRequireArgs(1);
|
||||
moduleBaseGetOrReturn(float_t, v, moduleVec2Get);
|
||||
v[1] = moduleBaseArgFloat(0);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec2Dot) {
|
||||
moduleBaseRequireArgs(1);
|
||||
float_t *a = moduleVec2Get(callInfo);
|
||||
if(!a) return moduleBaseThrow("Vec2.dot: invalid this");
|
||||
float_t *b = moduleVec2From(args[0]);
|
||||
if(!b) return moduleBaseThrow("Vec2.dot: argument must be a Vec2");
|
||||
return jerry_number(glm_vec2_dot(a, b));
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec2Length) {
|
||||
float_t *v = moduleVec2Get(callInfo);
|
||||
if(!v) return moduleBaseThrow("Vec2.length: invalid this");
|
||||
return jerry_number(glm_vec2_norm(v));
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec2LengthSq) {
|
||||
float_t *v = moduleVec2Get(callInfo);
|
||||
if(!v) return moduleBaseThrow("Vec2.lengthSq: invalid this");
|
||||
return jerry_number(glm_vec2_norm2(v));
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec2Normalize) {
|
||||
float_t *v = moduleVec2Get(callInfo);
|
||||
if(!v) return moduleBaseThrow("Vec2.normalize: invalid this");
|
||||
vec2 r;
|
||||
glm_vec2_normalize_to(v, r);
|
||||
return scriptProtoCreateValue(&MODULE_VEC2_PROTO, r);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec2Negate) {
|
||||
float_t *v = moduleVec2Get(callInfo);
|
||||
if(!v) return moduleBaseThrow("Vec2.negate: invalid this");
|
||||
vec2 r;
|
||||
glm_vec2_negate_to(v, r);
|
||||
return scriptProtoCreateValue(&MODULE_VEC2_PROTO, r);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec2Add) {
|
||||
moduleBaseRequireArgs(1);
|
||||
float_t *a = moduleVec2Get(callInfo);
|
||||
if(!a) return moduleBaseThrow("Vec2.add: invalid this");
|
||||
float_t *b = moduleVec2From(args[0]);
|
||||
if(!b) return moduleBaseThrow("Vec2.add: argument must be a Vec2");
|
||||
vec2 r;
|
||||
glm_vec2_add(a, b, r);
|
||||
return scriptProtoCreateValue(&MODULE_VEC2_PROTO, r);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec2Sub) {
|
||||
moduleBaseRequireArgs(1);
|
||||
float_t *a = moduleVec2Get(callInfo);
|
||||
if(!a) return moduleBaseThrow("Vec2.sub: invalid this");
|
||||
float_t *b = moduleVec2From(args[0]);
|
||||
if(!b) return moduleBaseThrow("Vec2.sub: argument must be a Vec2");
|
||||
vec2 r;
|
||||
glm_vec2_sub(a, b, r);
|
||||
return scriptProtoCreateValue(&MODULE_VEC2_PROTO, r);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec2Scale) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
float_t *v = moduleVec2Get(callInfo);
|
||||
if(!v) return moduleBaseThrow("Vec2.scale: invalid this");
|
||||
vec2 r;
|
||||
glm_vec2_scale(v, moduleBaseArgFloat(0), r);
|
||||
return scriptProtoCreateValue(&MODULE_VEC2_PROTO, r);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec2Lerp) {
|
||||
moduleBaseRequireArgs(2); moduleBaseRequireNumber(1);
|
||||
float_t *a = moduleVec2Get(callInfo);
|
||||
if(!a) return moduleBaseThrow("Vec2.lerp: invalid this");
|
||||
float_t *b = moduleVec2From(args[0]);
|
||||
if(!b) return moduleBaseThrow("Vec2.lerp: first argument must be a Vec2");
|
||||
vec2 r;
|
||||
glm_vec2_lerp(a, b, moduleBaseArgFloat(1), r);
|
||||
return scriptProtoCreateValue(&MODULE_VEC2_PROTO, r);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec2Distance) {
|
||||
moduleBaseRequireArgs(1);
|
||||
float_t *a = moduleVec2Get(callInfo);
|
||||
if(!a) return moduleBaseThrow("Vec2.distance: invalid this");
|
||||
float_t *b = moduleVec2From(args[0]);
|
||||
if(!b) return moduleBaseThrow("Vec2.distance: argument must be a Vec2");
|
||||
return jerry_number(glm_vec2_distance(a, b));
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec2ToString) {
|
||||
float_t *v = moduleVec2Get(callInfo);
|
||||
if(!v) return jerry_string_sz("Vec2(?, ?)");
|
||||
char_t buf[64];
|
||||
stringFormat(buf, sizeof(buf), "Vec2(%g, %g)", v[0], v[1]);
|
||||
return jerry_string_sz(buf);
|
||||
}
|
||||
|
||||
static inline jerry_value_t moduleVec2Push(const float_t *v) {
|
||||
return scriptProtoCreateValue(&MODULE_VEC2_PROTO, v);
|
||||
}
|
||||
|
||||
static inline bool_t moduleVec2Check(jerry_value_t val, float_t *out) {
|
||||
float_t *v = moduleVec2From(val);
|
||||
if(!v) return false;
|
||||
out[0] = v[0];
|
||||
out[1] = v[1];
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool_t moduleVec2AnyCheck(jerry_value_t val, float_t *out) {
|
||||
return moduleVec2Check(val, out);
|
||||
}
|
||||
|
||||
static void moduleVec2(void) {
|
||||
scriptProtoInit(
|
||||
&MODULE_VEC2_PROTO, "Vec2", sizeof(vec2), moduleVec2Constructor
|
||||
);
|
||||
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_VEC2_PROTO, "x", moduleVec2GetX, moduleVec2SetX
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_VEC2_PROTO, "y", moduleVec2GetY, moduleVec2SetY
|
||||
);
|
||||
|
||||
scriptProtoDefineToString(&MODULE_VEC2_PROTO, moduleVec2ToString);
|
||||
|
||||
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "dot", moduleVec2Dot);
|
||||
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "length", moduleVec2Length);
|
||||
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "lengthSq", moduleVec2LengthSq);
|
||||
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "normalize", moduleVec2Normalize);
|
||||
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "negate", moduleVec2Negate);
|
||||
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "add", moduleVec2Add);
|
||||
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "sub", moduleVec2Sub);
|
||||
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "scale", moduleVec2Scale);
|
||||
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "lerp", moduleVec2Lerp);
|
||||
scriptProtoDefineFunc(&MODULE_VEC2_PROTO, "distance", moduleVec2Distance);
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "cglm/cglm.h"
|
||||
|
||||
static scriptproto_t MODULE_VEC3_PROTO;
|
||||
|
||||
static inline float_t * moduleVec3Get(const jerry_call_info_t *callInfo) {
|
||||
return (float_t *)scriptProtoGetValue(
|
||||
&MODULE_VEC3_PROTO, callInfo->this_value
|
||||
);
|
||||
}
|
||||
|
||||
static inline float_t * moduleVec3From(jerry_value_t val) {
|
||||
return (float_t *)scriptProtoGetValue(&MODULE_VEC3_PROTO, val);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec3Constructor) {
|
||||
float_t *ptr = (float_t *)memoryAllocate(sizeof(vec3));
|
||||
ptr[0] = moduleBaseOptFloat(0, 0.0f);
|
||||
ptr[1] = moduleBaseOptFloat(1, 0.0f);
|
||||
ptr[2] = moduleBaseOptFloat(2, 0.0f);
|
||||
jerry_object_set_native_ptr(
|
||||
callInfo->this_value, &MODULE_VEC3_PROTO.info, ptr
|
||||
);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec3GetX) {
|
||||
moduleBaseGetOrReturn(float_t, v, moduleVec3Get);
|
||||
return jerry_number(v[0]);
|
||||
}
|
||||
moduleBaseFunction(moduleVec3SetX) {
|
||||
moduleBaseRequireArgs(1);
|
||||
moduleBaseGetOrReturn(float_t, v, moduleVec3Get);
|
||||
v[0] = moduleBaseArgFloat(0);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec3GetY) {
|
||||
moduleBaseGetOrReturn(float_t, v, moduleVec3Get);
|
||||
return jerry_number(v[1]);
|
||||
}
|
||||
moduleBaseFunction(moduleVec3SetY) {
|
||||
moduleBaseRequireArgs(1);
|
||||
moduleBaseGetOrReturn(float_t, v, moduleVec3Get);
|
||||
v[1] = moduleBaseArgFloat(0);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec3GetZ) {
|
||||
moduleBaseGetOrReturn(float_t, v, moduleVec3Get);
|
||||
return jerry_number(v[2]);
|
||||
}
|
||||
moduleBaseFunction(moduleVec3SetZ) {
|
||||
moduleBaseRequireArgs(1);
|
||||
moduleBaseGetOrReturn(float_t, v, moduleVec3Get);
|
||||
v[2] = moduleBaseArgFloat(0);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec3Dot) {
|
||||
moduleBaseRequireArgs(1);
|
||||
float_t *a = moduleVec3Get(callInfo);
|
||||
if(!a) return moduleBaseThrow("Vec3.dot: invalid this");
|
||||
float_t *b = moduleVec3From(args[0]);
|
||||
if(!b) return moduleBaseThrow("Vec3.dot: argument must be a Vec3");
|
||||
return jerry_number(glm_vec3_dot(a, b));
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec3Cross) {
|
||||
moduleBaseRequireArgs(1);
|
||||
float_t *a = moduleVec3Get(callInfo);
|
||||
if(!a) return moduleBaseThrow("Vec3.cross: invalid this");
|
||||
float_t *b = moduleVec3From(args[0]);
|
||||
if(!b) return moduleBaseThrow("Vec3.cross: argument must be a Vec3");
|
||||
vec3 r;
|
||||
glm_vec3_cross(a, b, r);
|
||||
return scriptProtoCreateValue(&MODULE_VEC3_PROTO, r);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec3Length) {
|
||||
float_t *v = moduleVec3Get(callInfo);
|
||||
if(!v) return moduleBaseThrow("Vec3.length: invalid this");
|
||||
return jerry_number(glm_vec3_norm(v));
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec3LengthSq) {
|
||||
float_t *v = moduleVec3Get(callInfo);
|
||||
if(!v) return moduleBaseThrow("Vec3.lengthSq: invalid this");
|
||||
return jerry_number(glm_vec3_norm2(v));
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec3Normalize) {
|
||||
float_t *v = moduleVec3Get(callInfo);
|
||||
if(!v) return moduleBaseThrow("Vec3.normalize: invalid this");
|
||||
vec3 r;
|
||||
glm_vec3_normalize_to(v, r);
|
||||
return scriptProtoCreateValue(&MODULE_VEC3_PROTO, r);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec3Negate) {
|
||||
float_t *v = moduleVec3Get(callInfo);
|
||||
if(!v) return moduleBaseThrow("Vec3.negate: invalid this");
|
||||
vec3 r;
|
||||
glm_vec3_negate_to(v, r);
|
||||
return scriptProtoCreateValue(&MODULE_VEC3_PROTO, r);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec3Add) {
|
||||
moduleBaseRequireArgs(1);
|
||||
float_t *a = moduleVec3Get(callInfo);
|
||||
if(!a) return moduleBaseThrow("Vec3.add: invalid this");
|
||||
float_t *b = moduleVec3From(args[0]);
|
||||
if(!b) return moduleBaseThrow("Vec3.add: argument must be a Vec3");
|
||||
vec3 r;
|
||||
glm_vec3_add(a, b, r);
|
||||
return scriptProtoCreateValue(&MODULE_VEC3_PROTO, r);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec3Sub) {
|
||||
moduleBaseRequireArgs(1);
|
||||
float_t *a = moduleVec3Get(callInfo);
|
||||
if(!a) return moduleBaseThrow("Vec3.sub: invalid this");
|
||||
float_t *b = moduleVec3From(args[0]);
|
||||
if(!b) return moduleBaseThrow("Vec3.sub: argument must be a Vec3");
|
||||
vec3 r;
|
||||
glm_vec3_sub(a, b, r);
|
||||
return scriptProtoCreateValue(&MODULE_VEC3_PROTO, r);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec3Scale) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
float_t *v = moduleVec3Get(callInfo);
|
||||
if(!v) return moduleBaseThrow("Vec3.scale: invalid this");
|
||||
vec3 r;
|
||||
glm_vec3_scale(v, moduleBaseArgFloat(0), r);
|
||||
return scriptProtoCreateValue(&MODULE_VEC3_PROTO, r);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec3Lerp) {
|
||||
moduleBaseRequireArgs(2); moduleBaseRequireNumber(1);
|
||||
float_t *a = moduleVec3Get(callInfo);
|
||||
if(!a) return moduleBaseThrow("Vec3.lerp: invalid this");
|
||||
float_t *b = moduleVec3From(args[0]);
|
||||
if(!b) return moduleBaseThrow("Vec3.lerp: first argument must be a Vec3");
|
||||
vec3 r;
|
||||
glm_vec3_lerp(a, b, moduleBaseArgFloat(1), r);
|
||||
return scriptProtoCreateValue(&MODULE_VEC3_PROTO, r);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec3Distance) {
|
||||
moduleBaseRequireArgs(1);
|
||||
float_t *a = moduleVec3Get(callInfo);
|
||||
if(!a) return moduleBaseThrow("Vec3.distance: invalid this");
|
||||
float_t *b = moduleVec3From(args[0]);
|
||||
if(!b) return moduleBaseThrow("Vec3.distance: argument must be a Vec3");
|
||||
return jerry_number(glm_vec3_distance(a, b));
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec3ToString) {
|
||||
float_t *v = moduleVec3Get(callInfo);
|
||||
if(!v) return jerry_string_sz("Vec3(?, ?, ?)");
|
||||
char_t buf[80];
|
||||
stringFormat(buf, sizeof(buf), "Vec3(%g, %g, %g)", v[0], v[1], v[2]);
|
||||
return jerry_string_sz(buf);
|
||||
}
|
||||
|
||||
static inline jerry_value_t moduleVec3Push(const float_t *v) {
|
||||
return scriptProtoCreateValue(&MODULE_VEC3_PROTO, v);
|
||||
}
|
||||
|
||||
static inline bool_t moduleVec3Check(jerry_value_t val, float_t *out) {
|
||||
float_t *v = moduleVec3From(val);
|
||||
if(!v) return false;
|
||||
out[0] = v[0];
|
||||
out[1] = v[1];
|
||||
out[2] = v[2];
|
||||
return true;
|
||||
}
|
||||
|
||||
static void moduleVec3(void) {
|
||||
scriptProtoInit(
|
||||
&MODULE_VEC3_PROTO, "Vec3", sizeof(vec3), moduleVec3Constructor
|
||||
);
|
||||
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_VEC3_PROTO, "x", moduleVec3GetX, moduleVec3SetX
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_VEC3_PROTO, "y", moduleVec3GetY, moduleVec3SetY
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_VEC3_PROTO, "z", moduleVec3GetZ, moduleVec3SetZ
|
||||
);
|
||||
|
||||
scriptProtoDefineToString(&MODULE_VEC3_PROTO, moduleVec3ToString);
|
||||
|
||||
scriptProtoDefineFunc(&MODULE_VEC3_PROTO, "dot", moduleVec3Dot);
|
||||
scriptProtoDefineFunc(&MODULE_VEC3_PROTO, "cross", moduleVec3Cross);
|
||||
scriptProtoDefineFunc(&MODULE_VEC3_PROTO, "length", moduleVec3Length);
|
||||
scriptProtoDefineFunc(&MODULE_VEC3_PROTO, "lengthSq", moduleVec3LengthSq);
|
||||
scriptProtoDefineFunc(&MODULE_VEC3_PROTO, "normalize", moduleVec3Normalize);
|
||||
scriptProtoDefineFunc(&MODULE_VEC3_PROTO, "negate", moduleVec3Negate);
|
||||
scriptProtoDefineFunc(&MODULE_VEC3_PROTO, "add", moduleVec3Add);
|
||||
scriptProtoDefineFunc(&MODULE_VEC3_PROTO, "sub", moduleVec3Sub);
|
||||
scriptProtoDefineFunc(&MODULE_VEC3_PROTO, "scale", moduleVec3Scale);
|
||||
scriptProtoDefineFunc(&MODULE_VEC3_PROTO, "lerp", moduleVec3Lerp);
|
||||
scriptProtoDefineFunc(&MODULE_VEC3_PROTO, "distance", moduleVec3Distance);
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "cglm/cglm.h"
|
||||
#include "modulevec3.h"
|
||||
|
||||
typedef struct {
|
||||
float_t *data;
|
||||
void (*onChange)(void *ctx);
|
||||
void *ctx;
|
||||
} vec3ref_t;
|
||||
|
||||
static scriptproto_t MODULE_VEC3_REF_PROTO;
|
||||
|
||||
static inline vec3ref_t * moduleVec3RefGet(
|
||||
const jerry_call_info_t *callInfo
|
||||
) {
|
||||
return (vec3ref_t*)scriptProtoGetValue(
|
||||
&MODULE_VEC3_REF_PROTO, callInfo->this_value
|
||||
);
|
||||
}
|
||||
|
||||
static inline vec3ref_t * moduleVec3RefFrom(jerry_value_t val) {
|
||||
return (vec3ref_t*)scriptProtoGetValue(&MODULE_VEC3_REF_PROTO, val);
|
||||
}
|
||||
|
||||
static inline void moduleVec3RefNotify(vec3ref_t *ref) {
|
||||
if(ref->onChange) ref->onChange(ref->ctx);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec3RefGetX) {
|
||||
moduleBaseGetOrReturn(vec3ref_t, ref, moduleVec3RefGet);
|
||||
return jerry_number(ref->data[0]);
|
||||
}
|
||||
moduleBaseFunction(moduleVec3RefSetX) {
|
||||
moduleBaseRequireArgs(1);
|
||||
moduleBaseGetOrReturn(vec3ref_t, ref, moduleVec3RefGet);
|
||||
ref->data[0] = moduleBaseArgFloat(0);
|
||||
moduleVec3RefNotify(ref);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec3RefGetY) {
|
||||
moduleBaseGetOrReturn(vec3ref_t, ref, moduleVec3RefGet);
|
||||
return jerry_number(ref->data[1]);
|
||||
}
|
||||
moduleBaseFunction(moduleVec3RefSetY) {
|
||||
moduleBaseRequireArgs(1);
|
||||
moduleBaseGetOrReturn(vec3ref_t, ref, moduleVec3RefGet);
|
||||
ref->data[1] = moduleBaseArgFloat(0);
|
||||
moduleVec3RefNotify(ref);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec3RefGetZ) {
|
||||
moduleBaseGetOrReturn(vec3ref_t, ref, moduleVec3RefGet);
|
||||
return jerry_number(ref->data[2]);
|
||||
}
|
||||
moduleBaseFunction(moduleVec3RefSetZ) {
|
||||
moduleBaseRequireArgs(1);
|
||||
moduleBaseGetOrReturn(vec3ref_t, ref, moduleVec3RefGet);
|
||||
ref->data[2] = moduleBaseArgFloat(0);
|
||||
moduleVec3RefNotify(ref);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec3RefToString) {
|
||||
vec3ref_t *ref = moduleVec3RefGet(callInfo);
|
||||
if(!ref) return jerry_string_sz("Vec3(?, ?, ?)");
|
||||
char_t buf[80];
|
||||
stringFormat(
|
||||
buf, sizeof(buf),
|
||||
"Vec3(%g, %g, %g)", ref->data[0], ref->data[1], ref->data[2]
|
||||
);
|
||||
return jerry_string_sz(buf);
|
||||
}
|
||||
|
||||
static inline jerry_value_t moduleVec3RefPush(
|
||||
float_t *data, void (*onChange)(void *), void *ctx
|
||||
) {
|
||||
vec3ref_t ref = { .data = data, .onChange = onChange, .ctx = ctx };
|
||||
return scriptProtoCreateValue(&MODULE_VEC3_REF_PROTO, &ref);
|
||||
}
|
||||
|
||||
static inline bool_t moduleVec3RefCheck(jerry_value_t val, float_t *out) {
|
||||
vec3ref_t *ref = moduleVec3RefFrom(val);
|
||||
if(!ref) return false;
|
||||
out[0] = ref->data[0];
|
||||
out[1] = ref->data[1];
|
||||
out[2] = ref->data[2];
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool_t moduleVec3AnyCheck(jerry_value_t val, float_t *out) {
|
||||
return moduleVec3Check(val, out) || moduleVec3RefCheck(val, out);
|
||||
}
|
||||
|
||||
static void moduleVec3Ref(void) {
|
||||
scriptProtoInit(
|
||||
&MODULE_VEC3_REF_PROTO, NULL, sizeof(vec3ref_t), NULL
|
||||
);
|
||||
scriptProtoDefineToString(&MODULE_VEC3_REF_PROTO, moduleVec3RefToString);
|
||||
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_VEC3_REF_PROTO, "x",
|
||||
moduleVec3RefGetX, moduleVec3RefSetX
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_VEC3_REF_PROTO, "y",
|
||||
moduleVec3RefGetY, moduleVec3RefSetY
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_VEC3_REF_PROTO, "z",
|
||||
moduleVec3RefGetZ, moduleVec3RefSetZ
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "cglm/cglm.h"
|
||||
|
||||
static scriptproto_t MODULE_VEC4_PROTO;
|
||||
|
||||
static inline float_t * moduleVec4Get(const jerry_call_info_t *callInfo) {
|
||||
return (float_t *)scriptProtoGetValue(
|
||||
&MODULE_VEC4_PROTO, callInfo->this_value
|
||||
);
|
||||
}
|
||||
|
||||
static inline float_t * moduleVec4From(jerry_value_t val) {
|
||||
return (float_t *)scriptProtoGetValue(&MODULE_VEC4_PROTO, val);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec4Constructor) {
|
||||
float_t *ptr = (float_t *)memoryAllocate(sizeof(vec4));
|
||||
ptr[0] = moduleBaseOptFloat(0, 0.0f);
|
||||
ptr[1] = moduleBaseOptFloat(1, 0.0f);
|
||||
ptr[2] = moduleBaseOptFloat(2, 0.0f);
|
||||
ptr[3] = moduleBaseOptFloat(3, 0.0f);
|
||||
jerry_object_set_native_ptr(
|
||||
callInfo->this_value, &MODULE_VEC4_PROTO.info, ptr
|
||||
);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
// x/y/z/w
|
||||
moduleBaseFunction(moduleVec4GetX) {
|
||||
moduleBaseGetOrReturn(float_t, v, moduleVec4Get); return jerry_number(v[0]);
|
||||
}
|
||||
moduleBaseFunction(moduleVec4SetX) {
|
||||
moduleBaseRequireArgs(1); moduleBaseGetOrReturn(float_t, v, moduleVec4Get);
|
||||
v[0] = moduleBaseArgFloat(0); return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec4GetY) {
|
||||
moduleBaseGetOrReturn(float_t, v, moduleVec4Get); return jerry_number(v[1]);
|
||||
}
|
||||
moduleBaseFunction(moduleVec4SetY) {
|
||||
moduleBaseRequireArgs(1); moduleBaseGetOrReturn(float_t, v, moduleVec4Get);
|
||||
v[1] = moduleBaseArgFloat(0); return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec4GetZ) {
|
||||
moduleBaseGetOrReturn(float_t, v, moduleVec4Get); return jerry_number(v[2]);
|
||||
}
|
||||
moduleBaseFunction(moduleVec4SetZ) {
|
||||
moduleBaseRequireArgs(1); moduleBaseGetOrReturn(float_t, v, moduleVec4Get);
|
||||
v[2] = moduleBaseArgFloat(0); return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec4GetW) {
|
||||
moduleBaseGetOrReturn(float_t, v, moduleVec4Get); return jerry_number(v[3]);
|
||||
}
|
||||
moduleBaseFunction(moduleVec4SetW) {
|
||||
moduleBaseRequireArgs(1); moduleBaseGetOrReturn(float_t, v, moduleVec4Get);
|
||||
v[3] = moduleBaseArgFloat(0); return jerry_undefined();
|
||||
}
|
||||
|
||||
// u0/v0/u1/v1 aliases for UV coordinates
|
||||
moduleBaseFunction(moduleVec4GetU0) {
|
||||
moduleBaseGetOrReturn(float_t, v, moduleVec4Get); return jerry_number(v[0]);
|
||||
}
|
||||
moduleBaseFunction(moduleVec4SetU0) {
|
||||
moduleBaseRequireArgs(1); moduleBaseGetOrReturn(float_t, v, moduleVec4Get);
|
||||
v[0] = moduleBaseArgFloat(0); return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec4GetV0) {
|
||||
moduleBaseGetOrReturn(float_t, v, moduleVec4Get); return jerry_number(v[1]);
|
||||
}
|
||||
moduleBaseFunction(moduleVec4SetV0) {
|
||||
moduleBaseRequireArgs(1); moduleBaseGetOrReturn(float_t, v, moduleVec4Get);
|
||||
v[1] = moduleBaseArgFloat(0); return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec4GetU1) {
|
||||
moduleBaseGetOrReturn(float_t, v, moduleVec4Get); return jerry_number(v[2]);
|
||||
}
|
||||
moduleBaseFunction(moduleVec4SetU1) {
|
||||
moduleBaseRequireArgs(1); moduleBaseGetOrReturn(float_t, v, moduleVec4Get);
|
||||
v[2] = moduleBaseArgFloat(0); return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec4GetV1) {
|
||||
moduleBaseGetOrReturn(float_t, v, moduleVec4Get); return jerry_number(v[3]);
|
||||
}
|
||||
moduleBaseFunction(moduleVec4SetV1) {
|
||||
moduleBaseRequireArgs(1); moduleBaseGetOrReturn(float_t, v, moduleVec4Get);
|
||||
v[3] = moduleBaseArgFloat(0); return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec4Dot) {
|
||||
moduleBaseRequireArgs(1);
|
||||
float_t *a = moduleVec4Get(callInfo);
|
||||
if(!a) return moduleBaseThrow("Vec4.dot: invalid this");
|
||||
float_t *b = moduleVec4From(args[0]);
|
||||
if(!b) return moduleBaseThrow("Vec4.dot: argument must be a Vec4");
|
||||
return jerry_number(glm_vec4_dot(a, b));
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec4Length) {
|
||||
float_t *v = moduleVec4Get(callInfo);
|
||||
if(!v) return moduleBaseThrow("Vec4.length: invalid this");
|
||||
return jerry_number(glm_vec4_norm(v));
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec4LengthSq) {
|
||||
float_t *v = moduleVec4Get(callInfo);
|
||||
if(!v) return moduleBaseThrow("Vec4.lengthSq: invalid this");
|
||||
return jerry_number(glm_vec4_norm2(v));
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec4Normalize) {
|
||||
float_t *v = moduleVec4Get(callInfo);
|
||||
if(!v) return moduleBaseThrow("Vec4.normalize: invalid this");
|
||||
vec4 r;
|
||||
glm_vec4_normalize_to(v, r);
|
||||
return scriptProtoCreateValue(&MODULE_VEC4_PROTO, r);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec4Negate) {
|
||||
float_t *v = moduleVec4Get(callInfo);
|
||||
if(!v) return moduleBaseThrow("Vec4.negate: invalid this");
|
||||
vec4 r;
|
||||
glm_vec4_negate_to(v, r);
|
||||
return scriptProtoCreateValue(&MODULE_VEC4_PROTO, r);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec4Add) {
|
||||
moduleBaseRequireArgs(1);
|
||||
float_t *a = moduleVec4Get(callInfo);
|
||||
if(!a) return moduleBaseThrow("Vec4.add: invalid this");
|
||||
float_t *b = moduleVec4From(args[0]);
|
||||
if(!b) return moduleBaseThrow("Vec4.add: argument must be a Vec4");
|
||||
vec4 r;
|
||||
glm_vec4_add(a, b, r);
|
||||
return scriptProtoCreateValue(&MODULE_VEC4_PROTO, r);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec4Sub) {
|
||||
moduleBaseRequireArgs(1);
|
||||
float_t *a = moduleVec4Get(callInfo);
|
||||
if(!a) return moduleBaseThrow("Vec4.sub: invalid this");
|
||||
float_t *b = moduleVec4From(args[0]);
|
||||
if(!b) return moduleBaseThrow("Vec4.sub: argument must be a Vec4");
|
||||
vec4 r;
|
||||
glm_vec4_sub(a, b, r);
|
||||
return scriptProtoCreateValue(&MODULE_VEC4_PROTO, r);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec4Scale) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
float_t *v = moduleVec4Get(callInfo);
|
||||
if(!v) return moduleBaseThrow("Vec4.scale: invalid this");
|
||||
vec4 r;
|
||||
glm_vec4_scale(v, moduleBaseArgFloat(0), r);
|
||||
return scriptProtoCreateValue(&MODULE_VEC4_PROTO, r);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec4Lerp) {
|
||||
moduleBaseRequireArgs(2); moduleBaseRequireNumber(1);
|
||||
float_t *a = moduleVec4Get(callInfo);
|
||||
if(!a) return moduleBaseThrow("Vec4.lerp: invalid this");
|
||||
float_t *b = moduleVec4From(args[0]);
|
||||
if(!b) return moduleBaseThrow("Vec4.lerp: first argument must be a Vec4");
|
||||
vec4 r;
|
||||
glm_vec4_lerp(a, b, moduleBaseArgFloat(1), r);
|
||||
return scriptProtoCreateValue(&MODULE_VEC4_PROTO, r);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleVec4ToString) {
|
||||
float_t *v = moduleVec4Get(callInfo);
|
||||
if(!v) return jerry_string_sz("Vec4(?, ?, ?, ?)");
|
||||
char_t buf[96];
|
||||
stringFormat(
|
||||
buf, sizeof(buf),
|
||||
"Vec4(%g, %g, %g, %g)", v[0], v[1], v[2], v[3]
|
||||
);
|
||||
return jerry_string_sz(buf);
|
||||
}
|
||||
|
||||
static inline jerry_value_t moduleVec4Push(const float_t *v) {
|
||||
return scriptProtoCreateValue(&MODULE_VEC4_PROTO, v);
|
||||
}
|
||||
|
||||
static inline bool_t moduleVec4Check(jerry_value_t val, float_t *out) {
|
||||
float_t *v = moduleVec4From(val);
|
||||
if(!v) return false;
|
||||
out[0] = v[0];
|
||||
out[1] = v[1];
|
||||
out[2] = v[2];
|
||||
out[3] = v[3];
|
||||
return true;
|
||||
}
|
||||
|
||||
static void moduleVec4(void) {
|
||||
scriptProtoInit(
|
||||
&MODULE_VEC4_PROTO, "Vec4", sizeof(vec4), moduleVec4Constructor
|
||||
);
|
||||
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_VEC4_PROTO, "x", moduleVec4GetX, moduleVec4SetX
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_VEC4_PROTO, "y", moduleVec4GetY, moduleVec4SetY
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_VEC4_PROTO, "z", moduleVec4GetZ, moduleVec4SetZ
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_VEC4_PROTO, "w", moduleVec4GetW, moduleVec4SetW
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_VEC4_PROTO, "u0", moduleVec4GetU0, moduleVec4SetU0
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_VEC4_PROTO, "v0", moduleVec4GetV0, moduleVec4SetV0
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_VEC4_PROTO, "u1", moduleVec4GetU1, moduleVec4SetU1
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_VEC4_PROTO, "v1", moduleVec4GetV1, moduleVec4SetV1
|
||||
);
|
||||
|
||||
scriptProtoDefineToString(&MODULE_VEC4_PROTO, moduleVec4ToString);
|
||||
|
||||
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "dot", moduleVec4Dot);
|
||||
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "length", moduleVec4Length);
|
||||
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "lengthSq", moduleVec4LengthSq);
|
||||
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "normalize", moduleVec4Normalize);
|
||||
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "negate", moduleVec4Negate);
|
||||
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "add", moduleVec4Add);
|
||||
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "sub", moduleVec4Sub);
|
||||
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "scale", moduleVec4Scale);
|
||||
scriptProtoDefineFunc(&MODULE_VEC4_PROTO, "lerp", moduleVec4Lerp);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/script/moduleinclude.h"
|
||||
#include "script/module/math/modulemath.h"
|
||||
#include "script/module/display/modulecolor.h"
|
||||
#include "script/module/display/modulemesh.h"
|
||||
#include "script/module/entity/moduleentity.h"
|
||||
#include "script/module/input/moduleinput.h"
|
||||
#include "script/module/moduleplatform.h"
|
||||
#include "script/module/time/moduletime.h"
|
||||
|
||||
static void moduleRegister(void) {
|
||||
moduleInclude();
|
||||
moduleMath();
|
||||
moduleColor();
|
||||
moduleMesh();
|
||||
moduleEntity();
|
||||
moduleInput();
|
||||
modulePlatform();
|
||||
moduleTime();
|
||||
}
|
||||
@@ -0,0 +1,513 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/scriptmanager.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/string.h"
|
||||
#include "util/memory.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
/**
|
||||
* Define a function for a module in JavaScript.
|
||||
*
|
||||
* @param name Name of the method (in C, not JS)
|
||||
* @return A C function with that name, containing the standard JS sig.
|
||||
*/
|
||||
#define moduleBaseFunction(name) \
|
||||
static jerry_value_t name( \
|
||||
const jerry_call_info_t *callInfo, \
|
||||
const jerry_value_t args[], \
|
||||
const jerry_length_t argc)
|
||||
|
||||
/**
|
||||
* Define a standard JS prototype for a module.
|
||||
* This creates the INFO struct, and a prototype reference value.
|
||||
*
|
||||
* Values are;
|
||||
* {NAME}_INFO and {NAME_PROTOTYPE}
|
||||
*
|
||||
* @param name Name of the module.
|
||||
* @return See above.
|
||||
*/
|
||||
#define moduleBaseProtoDefine(name) \
|
||||
static const jerry_object_native_info_t name##_INFO = { \
|
||||
.free_cb = moduleBaseFreeProto, \
|
||||
.number_of_references = 0, \
|
||||
.offset_of_references = 0 \
|
||||
}; \
|
||||
static jerry_value_t name##_PROTOTYPE = 0;
|
||||
|
||||
/**
|
||||
* Gets the pointer to what is otherwise a prototype in the JerryScript code.
|
||||
* This, for example, allows you to define a pointer in C and have it be a
|
||||
* prototype for objects in JavaScript.
|
||||
*
|
||||
* @param object The JavaScript object to get the native pointer from.
|
||||
* @param info The native info "prototype" struct used to get the pointer.
|
||||
* @return The pointer to the proto (or NULL).
|
||||
*/
|
||||
static void* moduleBaseGetProto(
|
||||
const jerry_value_t object,
|
||||
const jerry_object_native_info_t *info
|
||||
) {
|
||||
assertNotNull(info, "Native info must not be null");
|
||||
if(!jerry_value_is_object(object)) return NULL;
|
||||
|
||||
return (void*)jerry_object_get_native_ptr(object, info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an object based on a C value.
|
||||
*
|
||||
* @param input The pointer to the value to create a JS object for.
|
||||
* @param size The size of the data provided in input.
|
||||
* @param info The native info "prototype" struct used to create the object.
|
||||
* @return A JS object wrapping the provided C value.
|
||||
*/
|
||||
static jerry_value_t moduleBaseCreateProto(
|
||||
void *input,
|
||||
const size_t size,
|
||||
const jerry_object_native_info_t *info,
|
||||
const jerry_value_t prototype
|
||||
) {
|
||||
assertNotNull(input, "Input pointer must not be null");
|
||||
assertTrue(size > 0, "Struct size must be greater than 0");
|
||||
assertNotNull(info, "Native info must not be null");
|
||||
|
||||
void *ptr = memoryAllocate(size);
|
||||
memoryCopy(ptr, input, size);
|
||||
jerry_value_t proto = jerry_object();
|
||||
jerry_object_set_native_ptr(proto, info, ptr);
|
||||
jerry_object_set_proto(proto, prototype);
|
||||
return proto;
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard JerryScript free callback.
|
||||
*
|
||||
* @param ptr The pointer to free.
|
||||
* @param info The native info struct associated with the pointer.
|
||||
*/
|
||||
static void moduleBaseFreeProto(void *ptr, jerry_object_native_info_t *info) {
|
||||
assertNotNull(ptr, "Pointer must not be null");
|
||||
assertNotNull(info, "Native info must not be null");
|
||||
memoryFree(ptr);
|
||||
}
|
||||
|
||||
/**
|
||||
* Quickly defines a property on a prototype with a getter and setter.
|
||||
*/
|
||||
static void moduleBaseProtoDefineProp(
|
||||
const jerry_value_t prototype,
|
||||
const char_t *name,
|
||||
jerry_external_handler_t getter,
|
||||
jerry_external_handler_t setter
|
||||
) {
|
||||
assertTrue(prototype != 0, "Prototype must not be null");
|
||||
assertNotNull(name, "Property name must not be null");
|
||||
assertNotNull(getter, "Getter must not be null");
|
||||
|
||||
jerry_property_descriptor_t desc;
|
||||
memset(&desc, 0, sizeof(desc));
|
||||
desc.flags = (uint16_t)(
|
||||
JERRY_PROP_IS_GET_DEFINED |
|
||||
JERRY_PROP_IS_ENUMERABLE_DEFINED | JERRY_PROP_IS_ENUMERABLE |
|
||||
JERRY_PROP_IS_CONFIGURABLE_DEFINED | JERRY_PROP_IS_CONFIGURABLE
|
||||
);
|
||||
|
||||
desc.getter = jerry_function_external(getter);
|
||||
|
||||
if(setter != NULL) {
|
||||
desc.flags |= JERRY_PROP_IS_SET_DEFINED;
|
||||
desc.setter = jerry_function_external(setter);
|
||||
}
|
||||
|
||||
jerry_value_t key = jerry_string_sz(name);
|
||||
jerry_value_t result = jerry_object_define_own_prop(prototype, key, &desc);
|
||||
jerry_value_free(result);
|
||||
jerry_value_free(key);
|
||||
jerry_value_free(desc.getter);
|
||||
if(setter != NULL) jerry_value_free(desc.setter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a global function for a module.
|
||||
*
|
||||
* @param name The name of the function as seen in JavaScript.
|
||||
* @param fn The C handler function for the method.
|
||||
*/
|
||||
static void moduleBaseFunctionRegister(
|
||||
const char_t *name,
|
||||
jerry_external_handler_t fn
|
||||
) {
|
||||
assertNotNull(name, "Function name must not be null");
|
||||
assertNotNull(fn, "Function handler must not be null");
|
||||
|
||||
jerry_value_t global = jerry_current_realm();
|
||||
jerry_value_t key = jerry_string_sz(name);
|
||||
jerry_value_t func = jerry_function_external(fn);
|
||||
jerry_object_set(global, key, func);
|
||||
jerry_value_free(func);
|
||||
jerry_value_free(key);
|
||||
jerry_value_free(global);
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluates a script in the global scope.
|
||||
*
|
||||
* @param script The script to evaluate.
|
||||
*/
|
||||
static void moduleBaseEval(const char_t *script) {
|
||||
assertNotNull(script, "Script must not be null");
|
||||
|
||||
jerry_value_t result = jerry_eval(
|
||||
(const jerry_char_t *)script,
|
||||
strlen(script),
|
||||
JERRY_PARSE_NO_OPTS
|
||||
);
|
||||
jerry_value_free(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw a type error from a module function.
|
||||
*
|
||||
* @param message The error message to throw.
|
||||
* @return A JerryScript error value.
|
||||
*/
|
||||
static jerry_value_t moduleBaseThrow(const char_t *message) {
|
||||
assertStrLenMin(message, 1, "Error message must not be empty");
|
||||
return jerry_throw_sz(JERRY_ERROR_TYPE, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a C errorret_t into a JS exception, forwarding the error message
|
||||
* so that try/catch in JS sees the real error text. Clears the C error state.
|
||||
*
|
||||
* @param err The errorret_t returned by a failing C function.
|
||||
* @return A JerryScript error value carrying the C error message.
|
||||
*/
|
||||
static jerry_value_t moduleBaseThrowError(const errorret_t err) {
|
||||
assertNotNull(err.state, "Error state must not be NULL");
|
||||
assertNotNull(err.state->message, "Error message must not be NULL");
|
||||
jerry_value_t jsErr = jerry_throw_sz(JERRY_ERROR_TYPE, err.state->message);
|
||||
errorCatch(err);
|
||||
return jsErr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a global string constant.
|
||||
*/
|
||||
static void moduleBaseSetString(const char_t *name, const char_t *value) {
|
||||
jerry_value_t global = jerry_current_realm();
|
||||
jerry_value_t key = jerry_string_sz(name);
|
||||
jerry_value_t val = jerry_string_sz(value);
|
||||
jerry_object_set(global, key, val);
|
||||
jerry_value_free(val);
|
||||
jerry_value_free(key);
|
||||
jerry_value_free(global);
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines a global object with the provided name and prototype.
|
||||
*
|
||||
* @param name The name of the global object to create.
|
||||
* @param prototype The prototype to set on the global object.
|
||||
*/
|
||||
static void moduleBaseCreateGlobalObject(
|
||||
const char_t *name, jerry_value_t prototype
|
||||
) {
|
||||
jerry_value_t global = jerry_current_realm();
|
||||
jerry_value_t key = jerry_string_sz(name);
|
||||
jerry_object_set(global, key, prototype);
|
||||
jerry_value_free(key);
|
||||
jerry_value_free(global);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert an argument is a number; return type error if not.
|
||||
*/
|
||||
#define moduleBaseRequireNumber(i) do { \
|
||||
if(!jerry_value_is_number(args[(i)])) { \
|
||||
return moduleBaseThrow("Expected number argument"); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/**
|
||||
* Assert an argument is a string; return type error if not.
|
||||
*/
|
||||
#define moduleBaseRequireString(i) do { \
|
||||
if(!jerry_value_is_string(args[(i)])) { \
|
||||
return moduleBaseThrow("Expected string argument"); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/**
|
||||
* Assert an argument is a function; return type error if not.
|
||||
*/
|
||||
#define moduleBaseRequireFunction(i) do { \
|
||||
if(!jerry_value_is_function(args[(i)])) { \
|
||||
return moduleBaseThrow("Expected function argument"); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/**
|
||||
* Assert an argument is an object; return type error if not.
|
||||
*/
|
||||
#define moduleBaseRequireObject(i) do { \
|
||||
if(!jerry_value_is_object(args[(i)])) { \
|
||||
return moduleBaseThrow("Expected object argument"); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/**
|
||||
* Require at least N arguments; throw a TypeError if fewer were provided.
|
||||
*
|
||||
* Example: moduleBaseRequireArgs(2);
|
||||
*/
|
||||
#define moduleBaseRequireArgs(n) do { \
|
||||
if(argc < (jerry_length_t)(n)) { \
|
||||
return moduleBaseThrow("Expected at least " #n " argument(s)"); \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
/**
|
||||
* Declare a typed pointer from a getter and immediately return undefined if it
|
||||
* is NULL. The named variable is available for the rest of the function.
|
||||
*
|
||||
* Example:
|
||||
* moduleBaseGetOrReturn(entitycamera_t, cam, moduleEntityCameraGet);
|
||||
* return jerry_number(cam->nearClip);
|
||||
*/
|
||||
#define moduleBaseGetOrReturn(type, var, getter) \
|
||||
type *var = (getter)(callInfo); \
|
||||
if(!(var)) return jerry_undefined()
|
||||
|
||||
/**
|
||||
* Cast argument i to float_t. Call after validating the arg is a number.
|
||||
*/
|
||||
#define moduleBaseArgFloat(i) ((float_t)jerry_value_as_number(args[(i)]))
|
||||
|
||||
/**
|
||||
* Cast argument i to int32_t. Call after validating the arg is a number.
|
||||
*/
|
||||
#define moduleBaseArgInt(i) ((int32_t)jerry_value_as_number(args[(i)]))
|
||||
|
||||
/**
|
||||
* Read argument i as a boolean (true/false).
|
||||
*/
|
||||
#define moduleBaseArgBool(i) (jerry_value_is_true(args[(i)]))
|
||||
|
||||
/**
|
||||
* Read optional argument i as float_t. Returns def if the argument is missing
|
||||
* or not a number.
|
||||
*/
|
||||
#define moduleBaseOptFloat(i, def) \
|
||||
((jerry_length_t)(i) < argc && jerry_value_is_number(args[(i)]) \
|
||||
? (float_t)jerry_value_as_number(args[(i)]) : (def))
|
||||
|
||||
/**
|
||||
* Read optional argument i as int32_t. Returns def if the argument is missing
|
||||
* or not a number.
|
||||
*/
|
||||
#define moduleBaseOptInt(i, def) \
|
||||
((jerry_length_t)(i) < argc && jerry_value_is_number(args[(i)]) \
|
||||
? (int32_t)jerry_value_as_number(args[(i)]) : (def))
|
||||
|
||||
/**
|
||||
* Set a global numeric constant.
|
||||
*/
|
||||
static inline void moduleBaseSetNumber(const char_t *name, double value) {
|
||||
jerry_value_t global = jerry_current_realm();
|
||||
jerry_value_t key = jerry_string_sz(name);
|
||||
jerry_value_t val = jerry_number(value);
|
||||
jerry_object_set(global, key, val);
|
||||
jerry_value_free(val);
|
||||
jerry_value_free(key);
|
||||
jerry_value_free(global);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a global integer constant.
|
||||
*/
|
||||
static inline void moduleBaseSetInt(const char_t *name, int32_t value) {
|
||||
moduleBaseSetNumber(name, (double)value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a global JS value. Caller retains ownership of the value and must free
|
||||
* it independently.
|
||||
*/
|
||||
static inline void moduleBaseSetValue(const char_t *name, jerry_value_t value) {
|
||||
jerry_value_t global = jerry_current_realm();
|
||||
jerry_value_t key = jerry_string_sz(name);
|
||||
jerry_object_set(global, key, value);
|
||||
jerry_value_free(key);
|
||||
jerry_value_free(global);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap an engine-owned C pointer as a JS object (no GC free callback).
|
||||
* Used for global singletons like INPUT_EVENT_PRESSED, SHADER_UNLIT, etc.
|
||||
*/
|
||||
static inline jerry_value_t moduleBaseWrapPointer(void *ptr) {
|
||||
jerry_value_t obj = jerry_object();
|
||||
jerry_object_set_native_ptr(obj, &JS_PTR_NATIVE_INFO, ptr);
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a named global to a wrapped engine-owned C pointer.
|
||||
* Combines moduleBaseWrapPointer and moduleBaseSetValue in one call.
|
||||
*/
|
||||
static inline void moduleBaseSetWrappedPointer(const char_t *name, void *ptr) {
|
||||
jerry_value_t val = moduleBaseWrapPointer(ptr);
|
||||
moduleBaseSetValue(name, val);
|
||||
jerry_value_free(val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwrap a C pointer from a JS object created by moduleBaseWrapPointer.
|
||||
* Returns NULL if the object does not carry a matching native pointer.
|
||||
*/
|
||||
static inline void *moduleBaseUnwrapPointer(jerry_value_t val) {
|
||||
if(!jerry_value_is_object(val)) return NULL;
|
||||
return jerry_object_get_native_ptr(val, &JS_PTR_NATIVE_INFO);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a JerryScript string value into a C buffer (null-terminated).
|
||||
*
|
||||
* @param val Jerry string value.
|
||||
* @param buf Output buffer.
|
||||
* @param buflen Buffer capacity including the null terminator.
|
||||
*/
|
||||
static inline void moduleBaseToString(
|
||||
jerry_value_t val,
|
||||
char_t *buf,
|
||||
jerry_size_t buflen
|
||||
) {
|
||||
jerry_size_t len = jerry_string_to_buffer(
|
||||
val, JERRY_ENCODING_UTF8, (jerry_char_t *)buf, buflen - 1
|
||||
);
|
||||
buf[len] = '\0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a named property on a JS object with getter and optional setter.
|
||||
*
|
||||
* @param obj Target object (e.g. a prototype).
|
||||
* @param name Property name.
|
||||
* @param getter C getter handler.
|
||||
* @param setter C setter handler, or NULL for read-only property.
|
||||
*/
|
||||
static inline void moduleBaseDefineProperty(
|
||||
jerry_value_t obj,
|
||||
const char_t *name,
|
||||
jerry_external_handler_t getter,
|
||||
jerry_external_handler_t setter
|
||||
) {
|
||||
jerry_property_descriptor_t desc;
|
||||
memset(&desc, 0, sizeof(desc));
|
||||
desc.flags = (uint16_t)(
|
||||
JERRY_PROP_IS_GET_DEFINED |
|
||||
JERRY_PROP_IS_ENUMERABLE_DEFINED | JERRY_PROP_IS_ENUMERABLE |
|
||||
JERRY_PROP_IS_CONFIGURABLE_DEFINED | JERRY_PROP_IS_CONFIGURABLE
|
||||
);
|
||||
desc.getter = jerry_function_external(getter);
|
||||
if(setter != NULL) {
|
||||
desc.flags |= JERRY_PROP_IS_SET_DEFINED;
|
||||
desc.setter = jerry_function_external(setter);
|
||||
}
|
||||
jerry_value_t key = jerry_string_sz(name);
|
||||
jerry_value_t result = jerry_object_define_own_prop(obj, key, &desc);
|
||||
jerry_value_free(result);
|
||||
jerry_value_free(key);
|
||||
jerry_value_free(desc.getter);
|
||||
if(setter != NULL) jerry_value_free(desc.setter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a named method (C function) on a JS object.
|
||||
*
|
||||
* @param obj Target object (e.g. a prototype).
|
||||
* @param name Method name.
|
||||
* @param fn C handler function.
|
||||
*/
|
||||
static inline void moduleBaseDefineMethod(
|
||||
jerry_value_t obj,
|
||||
const char_t *name,
|
||||
jerry_external_handler_t fn
|
||||
) {
|
||||
jerry_value_t key = jerry_string_sz(name);
|
||||
jerry_value_t func = jerry_function_external(fn);
|
||||
jerry_object_set(obj, key, func);
|
||||
jerry_value_free(func);
|
||||
jerry_value_free(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format an error message from a JerryScript exception value.
|
||||
* Caller must ensure buf is large enough.
|
||||
*/
|
||||
static inline void moduleBaseExceptionMessage(
|
||||
jerry_value_t exception,
|
||||
char_t *buf,
|
||||
size_t buflen
|
||||
) {
|
||||
jerry_value_t errVal = jerry_exception_value(exception, false);
|
||||
jerry_value_t errStr = jerry_value_to_string(errVal);
|
||||
jerry_size_t len = jerry_string_to_buffer(
|
||||
errStr, JERRY_ENCODING_UTF8, (jerry_char_t *)buf, (jerry_size_t)(buflen - 1)
|
||||
);
|
||||
buf[len] = '\0';
|
||||
jerry_value_free(errStr);
|
||||
jerry_value_free(errVal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a named global function.
|
||||
*
|
||||
* @param name The name of the function as seen in JavaScript.
|
||||
* @param fn The C handler function for the method.
|
||||
*/
|
||||
static inline void moduleBaseDefineGlobalMethod(
|
||||
const char_t *name,
|
||||
jerry_external_handler_t fn
|
||||
) {
|
||||
jerry_value_t global = jerry_current_realm();
|
||||
moduleBaseDefineMethod(global, name, fn);
|
||||
jerry_value_free(global);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a named property from a JS object. Caller must free the returned value.
|
||||
*/
|
||||
static inline jerry_value_t moduleBaseGetProp(
|
||||
jerry_value_t obj, const char_t *name
|
||||
) {
|
||||
jerry_value_t key = jerry_string_sz(name);
|
||||
jerry_value_t val = jerry_object_get(obj, key);
|
||||
jerry_value_free(key);
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast a JS value to float_t. Use for non-args[] values (e.g. object
|
||||
* properties). For args[], prefer moduleBaseArgFloat.
|
||||
*/
|
||||
static inline float_t moduleBaseValueFloat(jerry_value_t val) {
|
||||
return (float_t)jerry_value_as_number(val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast a JS value to int32_t. Use for non-args[] values (e.g. object
|
||||
* properties). For args[], prefer moduleBaseArgInt.
|
||||
*/
|
||||
static inline int32_t moduleBaseValueInt(jerry_value_t val) {
|
||||
return (int32_t)jerry_value_as_number(val);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/module/moduleplatformplatform.h"
|
||||
|
||||
#ifndef DUSK_TARGET_SYSTEM
|
||||
#error "DUSK_TARGET_SYSTEM must be defined"
|
||||
#endif
|
||||
|
||||
#define MODULE_PLATFORM_VALUE "var PLATFORM = '" DUSK_TARGET_SYSTEM "';\n"
|
||||
|
||||
static void modulePlatform(void) {
|
||||
moduleBaseEval(MODULE_PLATFORM_VALUE);
|
||||
|
||||
#ifdef modulePlatformPlatform
|
||||
modulePlatformPlatform();
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptproto.h"
|
||||
#include "scene/scene.h"
|
||||
|
||||
static scriptproto_t MODULE_SCENE_PROTO;
|
||||
|
||||
moduleBaseFunction(moduleSceneDefaultUpdate) {
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleSceneDefaultDispose) {
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleSceneDefaultConstructor) {
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleSceneSet) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireString(0);
|
||||
|
||||
char_t name[ASSET_FILE_NAME_MAX];
|
||||
moduleBaseToString(args[0], name, sizeof(name));
|
||||
if(name[0] == '\0') return moduleBaseThrow("Scene.set: name cannot be empty");
|
||||
|
||||
sceneSet(name);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleSceneGetCurrent) {
|
||||
if(SCENE.sceneCurrent[0] == '\0') return jerry_undefined();
|
||||
return jerry_string_sz(SCENE.sceneCurrent);
|
||||
}
|
||||
|
||||
static void moduleSceneReset(void) {
|
||||
if(SCENE.scriptRef != SCENE_SCRIPT_REF_NONE) {
|
||||
jerry_value_free(SCENE.scriptRef);
|
||||
SCENE.scriptRef = SCENE_SCRIPT_REF_NONE;
|
||||
}
|
||||
|
||||
// Drop the 'module' global reference to the scene class so JerryScript's
|
||||
// GC can collect it and all associated closures.
|
||||
jerry_value_t global = jerry_current_realm();
|
||||
jerry_value_t key = jerry_string_sz("module");
|
||||
jerry_value_t undef = jerry_undefined();
|
||||
jerry_object_set(global, key, undef);
|
||||
jerry_value_free(undef);
|
||||
jerry_value_free(key);
|
||||
jerry_value_free(global);
|
||||
}
|
||||
|
||||
static errorret_t moduleSceneCall(const char_t *method) {
|
||||
assertStrLenMin(method, 1, "Method name cannot be empty");
|
||||
|
||||
if(SCENE.scriptRef == SCENE_SCRIPT_REF_NONE) {
|
||||
errorThrow("No active scene script to call method on");
|
||||
}
|
||||
|
||||
jerry_value_t key = jerry_string_sz(method);
|
||||
jerry_value_t fn = jerry_object_get(SCENE.scriptRef, key);
|
||||
jerry_value_free(key);
|
||||
|
||||
if(!jerry_value_is_function(fn)) {
|
||||
jerry_value_free(fn);
|
||||
errorThrow("Scene method '%s' not found", method);
|
||||
}
|
||||
|
||||
jerry_value_t result = jerry_call(fn, SCENE.scriptRef, NULL, 0);
|
||||
jerry_value_free(fn);
|
||||
|
||||
if(jerry_value_is_exception(result)) {
|
||||
char_t errMsg[512];
|
||||
moduleBaseExceptionMessage(result, errMsg, sizeof(errMsg));
|
||||
jerry_value_free(result);
|
||||
errorThrow("Scene:%s failed: %s", method, errMsg);
|
||||
}
|
||||
|
||||
jerry_value_free(result);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
static void moduleScene(void) {
|
||||
scriptProtoInit(
|
||||
&MODULE_SCENE_PROTO,
|
||||
"Scene",
|
||||
sizeof(uint8_t),
|
||||
moduleSceneDefaultConstructor
|
||||
);
|
||||
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_SCENE_PROTO, "update", moduleSceneDefaultUpdate
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_SCENE_PROTO, "dispose", moduleSceneDefaultDispose
|
||||
);
|
||||
|
||||
scriptProtoDefineStaticFunc(&MODULE_SCENE_PROTO, "set", moduleSceneSet);
|
||||
scriptProtoDefineStaticProp(
|
||||
&MODULE_SCENE_PROTO, "current", moduleSceneGetCurrent, NULL
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/scriptmanager.h"
|
||||
#include "asset/asset.h"
|
||||
#include "asset/loader/assetloader.h"
|
||||
|
||||
// include() always returns a Promise, and must be awaited (or .then()'d).
|
||||
// Multiple include() calls for the same file share a single underlying
|
||||
// asset entry, and therefore a single promise: the first call creates it,
|
||||
// every later call for that same file is handed a copy of that same
|
||||
// promise instead of starting a second load. It resolves with the script's
|
||||
// exported `module` value, or rejects with the script's exception, exactly
|
||||
// once - see assetScriptLoaderSync().
|
||||
moduleBaseFunction(moduleIncludeInclude) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireString(0);
|
||||
|
||||
char_t filename[ASSET_FILE_NAME_MAX];
|
||||
moduleBaseToString(args[0], filename, sizeof(filename));
|
||||
|
||||
if(filename[0] == '\0') {
|
||||
return moduleBaseThrow("include: filename cannot be empty");
|
||||
}
|
||||
|
||||
size_t len = strlen(filename);
|
||||
if(len < 3 || stringCompare(&filename[len - 3], ".js") != 0) {
|
||||
return moduleBaseThrow("include: filename must end with .js");
|
||||
}
|
||||
|
||||
assetentry_t *entry = assetGetEntry(filename, ASSET_LOADER_TYPE_SCRIPT, NULL);
|
||||
assetscriptoutput_t *output = &entry->data.script;
|
||||
|
||||
if(output->promise == 0) {
|
||||
// First request for this file - this is what kicks off the load;
|
||||
// assetUpdate() picks up entries sitting in NOT_STARTED every frame.
|
||||
output->promise = jerry_promise();
|
||||
}
|
||||
|
||||
return jerry_value_copy(output->promise);
|
||||
}
|
||||
|
||||
static void moduleInclude(void) {
|
||||
moduleBaseDefineGlobalMethod("include", moduleIncludeInclude);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/modulebase.h"
|
||||
#include "time/time.h"
|
||||
|
||||
moduleBaseFunction(moduleTimeGetDelta) {
|
||||
return jerry_number(TIME.delta);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleTimeGetTime) {
|
||||
return jerry_number(TIME.time);
|
||||
}
|
||||
|
||||
static void moduleTime(void) {
|
||||
jerry_value_t obj = jerry_object();
|
||||
moduleBaseDefineProperty(obj, "delta", moduleTimeGetDelta, NULL);
|
||||
moduleBaseDefineProperty(obj, "time", moduleTimeGetTime, NULL);
|
||||
moduleBaseSetValue("TIME", obj);
|
||||
jerry_value_free(obj);
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "scriptmanager.h"
|
||||
#include "assert/assert.h"
|
||||
#include "asset/asset.h"
|
||||
#include "util/memory.h"
|
||||
#include "scriptproto.h"
|
||||
#include "script/module/module.h"
|
||||
|
||||
#ifdef SCRIPT_GAME_INIT
|
||||
#include "script/scriptgame.h"
|
||||
#endif
|
||||
|
||||
scriptmanager_t SCRIPT_MANAGER;
|
||||
|
||||
const jerry_object_native_info_t JS_PTR_NATIVE_INFO = {
|
||||
.free_cb = NULL,
|
||||
.number_of_references = 0,
|
||||
.offset_of_references = 0
|
||||
};
|
||||
|
||||
errorret_t scriptManagerInit(void) {
|
||||
memoryZero(&SCRIPT_MANAGER, sizeof(scriptmanager_t));
|
||||
|
||||
jerry_init(JERRY_INIT_EMPTY);
|
||||
|
||||
moduleRegister();
|
||||
|
||||
#ifdef SCRIPT_GAME_INIT
|
||||
SCRIPT_GAME_INIT();
|
||||
#endif
|
||||
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t scriptManagerExec(const char_t *script, jerry_value_t *resultOut) {
|
||||
assertNotNull(script, "Script cannot be NULL");
|
||||
|
||||
jerry_value_t result = jerry_eval(
|
||||
(const jerry_char_t *)script,
|
||||
strlen(script),
|
||||
JERRY_PARSE_NO_OPTS
|
||||
);
|
||||
|
||||
if(jerry_value_is_exception(result)) {
|
||||
jerry_value_t errVal = jerry_exception_value(result, false);
|
||||
jerry_value_t errStr = jerry_value_to_string(errVal);
|
||||
char_t buf[256];
|
||||
jerry_size_t len = jerry_string_to_buffer(
|
||||
errStr, JERRY_ENCODING_UTF8, (jerry_char_t *)buf, sizeof(buf) - 1
|
||||
);
|
||||
buf[len] = '\0';
|
||||
jerry_value_free(errStr);
|
||||
jerry_value_free(errVal);
|
||||
jerry_value_free(result);
|
||||
errorThrow("Failed to execute script: %s", buf);
|
||||
}
|
||||
|
||||
if(resultOut != NULL) {
|
||||
*resultOut = result;
|
||||
} else {
|
||||
jerry_value_free(result);
|
||||
}
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t scriptManagerExecFile(
|
||||
const char_t *fname,
|
||||
jerry_value_t *resultOut
|
||||
) {
|
||||
assertNotNull(fname, "Filename cannot be NULL");
|
||||
|
||||
assetfile_t file;
|
||||
errorChain(assetFileInit(&file, fname, NULL, NULL));
|
||||
|
||||
uint8_t *buffer = NULL;
|
||||
size_t size = 0;
|
||||
errorChain(assetFileReadEntire(&file, &buffer, &size));
|
||||
errorChain(assetFileDispose(&file));
|
||||
|
||||
char_t *src = (char_t *)memoryAllocate(size + 1);
|
||||
memoryCopy(src, buffer, size);
|
||||
src[size] = '\0';
|
||||
memoryFree(buffer);
|
||||
|
||||
errorret_t ret = scriptManagerExec(src, resultOut);
|
||||
memoryFree(src);
|
||||
errorChain(ret);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
static errorret_t scriptManagerFormatValueError(
|
||||
const char_t *context,
|
||||
const char_t *name,
|
||||
jerry_value_t value
|
||||
) {
|
||||
jerry_value_t errStr = jerry_value_to_string(value);
|
||||
char_t buf[256];
|
||||
jerry_size_t len = jerry_string_to_buffer(
|
||||
errStr, JERRY_ENCODING_UTF8, (jerry_char_t *)buf, sizeof(buf) - 1
|
||||
);
|
||||
buf[len] = '\0';
|
||||
jerry_value_free(errStr);
|
||||
errorThrow("%s '%s': %s", context, name, buf);
|
||||
}
|
||||
|
||||
static errorret_t scriptManagerFormatException(
|
||||
const char_t *context,
|
||||
const char_t *name,
|
||||
jerry_value_t exception
|
||||
) {
|
||||
jerry_value_t errVal = jerry_exception_value(exception, false);
|
||||
errorret_t err = scriptManagerFormatValueError(context, name, errVal);
|
||||
jerry_value_free(errVal);
|
||||
return err;
|
||||
}
|
||||
|
||||
errorret_t scriptManagerCallGlobal(const char_t *name) {
|
||||
assertNotNull(name, "Function name cannot be NULL");
|
||||
|
||||
jerry_value_t global = jerry_current_realm();
|
||||
jerry_value_t key = jerry_string_sz(name);
|
||||
jerry_value_t fn = jerry_object_get(global, key);
|
||||
jerry_value_free(key);
|
||||
jerry_value_free(global);
|
||||
|
||||
if(!jerry_value_is_function(fn)) {
|
||||
jerry_value_free(fn);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
jerry_value_t result = jerry_call(fn, jerry_undefined(), NULL, 0);
|
||||
jerry_value_free(fn);
|
||||
|
||||
if(jerry_value_is_exception(result)) {
|
||||
errorret_t err = scriptManagerFormatException("Global function", name, result);
|
||||
jerry_value_free(result);
|
||||
errorChain(err);
|
||||
}
|
||||
|
||||
// If this was an `async function`, its work (including anything it
|
||||
// awaited) isn't actually done yet - it just returned a pending promise.
|
||||
// Drive both the JerryScript job queue and the asset system forward
|
||||
// (an awaited include() only progresses via assetUpdate()) until it
|
||||
// settles, so the caller can rely on the function being fully complete.
|
||||
if(jerry_value_is_promise(result)) {
|
||||
while(jerry_promise_state(result) == JERRY_PROMISE_STATE_PENDING) {
|
||||
errorret_t updateErr = assetUpdate();
|
||||
if(errorIsNotOk(updateErr)) {
|
||||
jerry_value_free(result);
|
||||
errorChain(updateErr);
|
||||
}
|
||||
jerry_value_t jobsResult = jerry_run_jobs();
|
||||
jerry_value_free(jobsResult);
|
||||
}
|
||||
|
||||
if(jerry_promise_state(result) == JERRY_PROMISE_STATE_REJECTED) {
|
||||
jerry_value_t rejectVal = jerry_promise_result(result);
|
||||
errorret_t err = scriptManagerFormatValueError(
|
||||
"Global async function", name, rejectVal
|
||||
);
|
||||
jerry_value_free(rejectVal);
|
||||
jerry_value_free(result);
|
||||
errorChain(err);
|
||||
}
|
||||
}
|
||||
|
||||
jerry_value_free(result);
|
||||
errorOk();
|
||||
}
|
||||
|
||||
errorret_t scriptManagerDispose(void) {
|
||||
scriptProtoDisposeAll();
|
||||
jerry_cleanup();
|
||||
errorOk();
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "error/error.h"
|
||||
#include "scriptvalue.h"
|
||||
#include <jerryscript.h>
|
||||
|
||||
#define SCRIPT_MANAGER_MAX_EVENT_SUBSCRIPTIONS 64
|
||||
|
||||
typedef struct {
|
||||
void* nothing;
|
||||
} scriptmanager_t;
|
||||
|
||||
extern scriptmanager_t SCRIPT_MANAGER;
|
||||
|
||||
/**
|
||||
* Singleton native-info tag for engine-owned C pointers wrapped in JS objects.
|
||||
* A single global instance ensures jerry_object_get_native_ptr() matches across
|
||||
* all compilation units (including event.c and module headers).
|
||||
*/
|
||||
extern const jerry_object_native_info_t JS_PTR_NATIVE_INFO;
|
||||
|
||||
/**
|
||||
* Initialize the script manager (and the underlying JerryScript context).
|
||||
*
|
||||
* @return The error return value.
|
||||
*/
|
||||
errorret_t scriptManagerInit(void);
|
||||
|
||||
/**
|
||||
* Execute a JS string in the active script context.
|
||||
*
|
||||
* @param script The JS source to execute.
|
||||
* @param result Optional out-parameter for the script's return value.
|
||||
* Caller must call jerry_value_free() on it when done.
|
||||
* Pass NULL to discard the return value.
|
||||
* @return The error return value.
|
||||
*/
|
||||
errorret_t scriptManagerExec(const char_t *script, jerry_value_t *result);
|
||||
|
||||
/**
|
||||
* Execute a JS file in the active script context.
|
||||
*
|
||||
* @param fname The filename of the script to execute.
|
||||
* @param result Optional out-parameter for the script's return value.
|
||||
* Caller must call jerry_value_free() on it when done.
|
||||
* Pass NULL to discard the return value.
|
||||
* @return The error return value.
|
||||
*/
|
||||
errorret_t scriptManagerExecFile(
|
||||
const char_t *fname,
|
||||
jerry_value_t *result
|
||||
);
|
||||
|
||||
/**
|
||||
* Calls a global JS function by name, if one is defined. Silently does
|
||||
* nothing if the global isn't a function (e.g. the script never defined it).
|
||||
*
|
||||
* If the function returns a Promise (e.g. it's an `async function`), the
|
||||
* JerryScript job queue is pumped (alongside assetUpdate(), so pending
|
||||
* include()s can actually progress) until it settles, and a rejection is
|
||||
* surfaced as an error - callers can rely on the function's work, including
|
||||
* anything it awaited, being complete by the time this returns.
|
||||
*
|
||||
* @param name The name of the global function to call.
|
||||
* @return The error return value. An error is thrown if the JS function
|
||||
* itself throws, or if its returned promise rejects.
|
||||
*/
|
||||
errorret_t scriptManagerCallGlobal(const char_t *name);
|
||||
|
||||
/**
|
||||
* Dispose of the script manager.
|
||||
*
|
||||
* @return The error return value.
|
||||
*/
|
||||
errorret_t scriptManagerDispose(void);
|
||||
@@ -0,0 +1,215 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "scriptproto.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "script/module/modulebase.h"
|
||||
|
||||
#define SCRIPT_PROTO_REGISTRY_MAX 64
|
||||
|
||||
static scriptproto_t *SCRIPT_PROTO_REGISTRY[SCRIPT_PROTO_REGISTRY_MAX];
|
||||
static size_t SCRIPT_PROTO_REGISTRY_COUNT = 0;
|
||||
|
||||
void scriptProtoInit(
|
||||
scriptproto_t *proto,
|
||||
const char_t *name,
|
||||
const size_t size,
|
||||
jerry_external_handler_t constructor
|
||||
) {
|
||||
assertNotNull(proto, "Script prototype struct must not be null");
|
||||
memoryZero(proto, sizeof(scriptproto_t));
|
||||
|
||||
assertTrue(
|
||||
SCRIPT_PROTO_REGISTRY_COUNT < SCRIPT_PROTO_REGISTRY_MAX,
|
||||
"Script prototype registry capacity exceeded"
|
||||
);
|
||||
SCRIPT_PROTO_REGISTRY[SCRIPT_PROTO_REGISTRY_COUNT++] = proto;
|
||||
|
||||
proto->info = (jerry_object_native_info_t){
|
||||
.free_cb = moduleBaseFreeProto,
|
||||
.number_of_references = 0,
|
||||
.offset_of_references = 0
|
||||
};
|
||||
proto->prototype = jerry_object();
|
||||
proto->size = size;
|
||||
|
||||
if(constructor != NULL) {
|
||||
proto->constructor = jerry_function_external(constructor);
|
||||
jerry_value_t protoKey = jerry_string_sz("prototype");
|
||||
jerry_object_set(proto->constructor, protoKey, proto->prototype);
|
||||
jerry_value_free(protoKey);
|
||||
jerry_value_t ctorKey = jerry_string_sz("constructor");
|
||||
jerry_object_set(proto->prototype, ctorKey, proto->constructor);
|
||||
jerry_value_free(ctorKey);
|
||||
}
|
||||
|
||||
if(name != NULL) {
|
||||
jerry_value_t global = jerry_current_realm();
|
||||
jerry_value_t key = jerry_string_sz(name);
|
||||
jerry_value_t val;
|
||||
if(proto->constructor) {
|
||||
val = proto->constructor;
|
||||
} else {
|
||||
val = proto->prototype;
|
||||
}
|
||||
jerry_object_set(global, key, val);
|
||||
jerry_value_free(key);
|
||||
jerry_value_free(global);
|
||||
}
|
||||
}
|
||||
|
||||
void scriptProtoDefineProp(
|
||||
scriptproto_t *proto,
|
||||
const char_t *name,
|
||||
jerry_external_handler_t getter,
|
||||
jerry_external_handler_t setter
|
||||
) {
|
||||
assertNotNull(proto, "Script prototype struct must not be null");
|
||||
assertStrLenMin(name, 1, "Property name must not be empty");
|
||||
assertNotNull(getter, "Getter must not be null");
|
||||
|
||||
jerry_property_descriptor_t desc;
|
||||
memoryZero(&desc, sizeof(desc));
|
||||
desc.flags = (uint16_t)(
|
||||
JERRY_PROP_IS_GET_DEFINED |
|
||||
JERRY_PROP_IS_ENUMERABLE_DEFINED | JERRY_PROP_IS_ENUMERABLE |
|
||||
JERRY_PROP_IS_CONFIGURABLE_DEFINED | JERRY_PROP_IS_CONFIGURABLE
|
||||
);
|
||||
|
||||
desc.getter = jerry_function_external(getter);
|
||||
|
||||
if(setter != NULL) {
|
||||
desc.flags |= JERRY_PROP_IS_SET_DEFINED;
|
||||
desc.setter = jerry_function_external(setter);
|
||||
}
|
||||
|
||||
jerry_value_t key = jerry_string_sz(name);
|
||||
jerry_value_t result = jerry_object_define_own_prop(
|
||||
proto->prototype, key, &desc
|
||||
);
|
||||
jerry_value_free(result);
|
||||
jerry_value_free(key);
|
||||
jerry_value_free(desc.getter);
|
||||
if(setter != NULL) jerry_value_free(desc.setter);
|
||||
}
|
||||
|
||||
void scriptProtoDefineFunc(
|
||||
scriptproto_t *proto,
|
||||
const char_t *name,
|
||||
jerry_external_handler_t fn
|
||||
) {
|
||||
assertNotNull(proto, "Script prototype struct must not be null");
|
||||
assertStrLenMin(name, 1, "Method name must not be empty");
|
||||
assertNotNull(fn, "Function handler must not be null");
|
||||
|
||||
jerry_value_t key = jerry_string_sz(name);
|
||||
jerry_value_t func = jerry_function_external(fn);
|
||||
jerry_object_set(proto->prototype, key, func);
|
||||
jerry_value_free(func);
|
||||
jerry_value_free(key);
|
||||
}
|
||||
|
||||
void scriptProtoDefineStaticProp(
|
||||
scriptproto_t *proto,
|
||||
const char_t *name,
|
||||
jerry_external_handler_t getter,
|
||||
jerry_external_handler_t setter
|
||||
) {
|
||||
assertNotNull(proto, "Script prototype struct must not be null");
|
||||
assertStrLenMin(name, 1, "Property name must not be empty");
|
||||
assertNotNull(getter, "Getter must not be null");
|
||||
|
||||
jerry_value_t target = (
|
||||
proto->constructor ? proto->constructor : proto->prototype
|
||||
);
|
||||
|
||||
jerry_property_descriptor_t desc;
|
||||
memoryZero(&desc, sizeof(desc));
|
||||
desc.flags = (uint16_t)(
|
||||
JERRY_PROP_IS_GET_DEFINED |
|
||||
JERRY_PROP_IS_ENUMERABLE_DEFINED | JERRY_PROP_IS_ENUMERABLE |
|
||||
JERRY_PROP_IS_CONFIGURABLE_DEFINED | JERRY_PROP_IS_CONFIGURABLE
|
||||
);
|
||||
|
||||
desc.getter = jerry_function_external(getter);
|
||||
|
||||
if(setter != NULL) {
|
||||
desc.flags |= JERRY_PROP_IS_SET_DEFINED;
|
||||
desc.setter = jerry_function_external(setter);
|
||||
}
|
||||
|
||||
jerry_value_t key = jerry_string_sz(name);
|
||||
jerry_value_t result = jerry_object_define_own_prop(target, key, &desc);
|
||||
jerry_value_free(result);
|
||||
jerry_value_free(key);
|
||||
jerry_value_free(desc.getter);
|
||||
if(setter != NULL) jerry_value_free(desc.setter);
|
||||
}
|
||||
|
||||
void scriptProtoDefineStaticFunc(
|
||||
scriptproto_t *proto,
|
||||
const char_t *name,
|
||||
jerry_external_handler_t fn
|
||||
) {
|
||||
assertNotNull(proto, "Script prototype struct must not be null");
|
||||
assertStrLenMin(name, 1, "Method name must not be empty");
|
||||
assertNotNull(fn, "Function handler must not be null");
|
||||
|
||||
jerry_value_t target = (
|
||||
proto->constructor ? proto->constructor : proto->prototype
|
||||
);
|
||||
jerry_value_t key = jerry_string_sz(name);
|
||||
jerry_value_t func = jerry_function_external(fn);
|
||||
jerry_object_set(target, key, func);
|
||||
jerry_value_free(func);
|
||||
jerry_value_free(key);
|
||||
}
|
||||
|
||||
jerry_value_t scriptProtoCreateValue(
|
||||
const scriptproto_t *proto,
|
||||
const void *value
|
||||
) {
|
||||
assertNotNull(proto, "Script prototype struct must not be null");
|
||||
assertNotNull(value, "Value pointer must not be null");
|
||||
|
||||
void *ptr = memoryAllocate(proto->size);
|
||||
memoryCopy(ptr, value, proto->size);
|
||||
jerry_value_t obj = jerry_object();
|
||||
jerry_object_set_native_ptr(obj, &proto->info, ptr);
|
||||
jerry_object_set_proto(obj, proto->prototype);
|
||||
return obj;
|
||||
}
|
||||
|
||||
void *scriptProtoGetValue(
|
||||
const scriptproto_t *proto,
|
||||
const jerry_value_t obj
|
||||
) {
|
||||
assertNotNull(proto, "Script prototype struct must not be null");
|
||||
if(!jerry_value_is_object(obj)) return NULL;
|
||||
return jerry_object_get_native_ptr(obj, &proto->info);
|
||||
}
|
||||
|
||||
void scriptProtoDefineToString(
|
||||
scriptproto_t *proto,
|
||||
jerry_external_handler_t fn
|
||||
) {
|
||||
scriptProtoDefineFunc(proto, "toString", fn);
|
||||
}
|
||||
|
||||
void scriptProtoDispose(scriptproto_t *proto) {
|
||||
assertNotNull(proto, "Script prototype struct must not be null");
|
||||
jerry_value_free(proto->prototype);
|
||||
if(proto->constructor) jerry_value_free(proto->constructor);
|
||||
}
|
||||
|
||||
void scriptProtoDisposeAll(void) {
|
||||
for(size_t i = 0; i < SCRIPT_PROTO_REGISTRY_COUNT; i++) {
|
||||
scriptProtoDispose(SCRIPT_PROTO_REGISTRY[i]);
|
||||
}
|
||||
SCRIPT_PROTO_REGISTRY_COUNT = 0;
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
#include <jerryscript.h>
|
||||
|
||||
typedef struct {
|
||||
jerry_object_native_info_t info;
|
||||
jerry_value_t prototype;
|
||||
jerry_value_t constructor;
|
||||
size_t size;
|
||||
} scriptproto_t;
|
||||
|
||||
/**
|
||||
* Initialize a JS class prototype.
|
||||
*
|
||||
* If name is non-NULL the class is registered as a global. When ctor is also
|
||||
* non-NULL the global is the constructor function (enabling `new Name(...)`);
|
||||
* otherwise the prototype object itself becomes the global.
|
||||
*
|
||||
* @param proto The struct to initialize.
|
||||
* @param name JS global name, or NULL to skip global registration.
|
||||
* @param size sizeof the C struct this class wraps.
|
||||
* @param ctor Constructor handler, or NULL if the class has no constructor.
|
||||
*/
|
||||
void scriptProtoInit(
|
||||
scriptproto_t *proto,
|
||||
const char_t *name,
|
||||
const size_t size,
|
||||
jerry_external_handler_t ctor
|
||||
);
|
||||
|
||||
/**
|
||||
* Define an instance property with a getter and optional setter.
|
||||
*
|
||||
* @param proto The class prototype.
|
||||
* @param name Property name.
|
||||
* @param getter Getter handler (must not be NULL).
|
||||
* @param setter Setter handler, or NULL for a read-only property.
|
||||
*/
|
||||
void scriptProtoDefineProp(
|
||||
scriptproto_t *proto,
|
||||
const char_t *name,
|
||||
jerry_external_handler_t getter,
|
||||
jerry_external_handler_t setter
|
||||
);
|
||||
|
||||
/**
|
||||
* Define an instance method on the class prototype.
|
||||
*
|
||||
* @param proto The class prototype.
|
||||
* @param name Method name.
|
||||
* @param fn C handler called when the method is invoked.
|
||||
*/
|
||||
void scriptProtoDefineFunc(
|
||||
scriptproto_t *proto,
|
||||
const char_t *name,
|
||||
jerry_external_handler_t fn
|
||||
);
|
||||
|
||||
/**
|
||||
* Define a static property on the class (e.g. Scene.current).
|
||||
*
|
||||
* Attaches to the constructor function when one exists, otherwise attaches
|
||||
* directly to the prototype object (which is the global in that case).
|
||||
*
|
||||
* @param proto The class prototype.
|
||||
* @param name Property name.
|
||||
* @param getter Getter handler (must not be NULL).
|
||||
* @param setter Setter handler, or NULL for a read-only property.
|
||||
*/
|
||||
void scriptProtoDefineStaticProp(
|
||||
scriptproto_t *proto,
|
||||
const char_t *name,
|
||||
jerry_external_handler_t getter,
|
||||
jerry_external_handler_t setter
|
||||
);
|
||||
|
||||
/**
|
||||
* Define a static method on the class (e.g. Color.fromRGBA).
|
||||
*
|
||||
* Attaches to the constructor function when one exists, otherwise attaches
|
||||
* directly to the prototype object (which is the global in that case).
|
||||
*
|
||||
* @param proto The class prototype.
|
||||
* @param name Method name.
|
||||
* @param fn C handler called when the static method is invoked.
|
||||
*/
|
||||
void scriptProtoDefineStaticFunc(
|
||||
scriptproto_t *proto,
|
||||
const char_t *name,
|
||||
jerry_external_handler_t fn
|
||||
);
|
||||
|
||||
/**
|
||||
* Create a JS instance wrapping a copy of a C value.
|
||||
*
|
||||
* @param proto The class prototype.
|
||||
* @param value Pointer to the C value to copy into the new JS object.
|
||||
* @return A new JS object with the class prototype and native pointer set.
|
||||
*/
|
||||
jerry_value_t scriptProtoCreateValue(
|
||||
const scriptproto_t *proto,
|
||||
const void *value
|
||||
);
|
||||
|
||||
/**
|
||||
* Unwrap the native C pointer from a JS object.
|
||||
*
|
||||
* @param proto The class prototype.
|
||||
* @param obj The JS object to inspect.
|
||||
* @return Pointer to the wrapped C value, or NULL if not an instance.
|
||||
*/
|
||||
void *scriptProtoGetValue(
|
||||
const scriptproto_t *proto,
|
||||
const jerry_value_t obj
|
||||
);
|
||||
|
||||
/**
|
||||
* Define the toString() method on the class prototype.
|
||||
*
|
||||
* @param proto The class prototype.
|
||||
* @param fn C handler called when toString() is invoked on an instance.
|
||||
*/
|
||||
void scriptProtoDefineToString(
|
||||
scriptproto_t *proto,
|
||||
jerry_external_handler_t fn
|
||||
);
|
||||
|
||||
/**
|
||||
* Release all JerryScript resources held by the prototype.
|
||||
*
|
||||
* @param proto The class prototype to dispose.
|
||||
*/
|
||||
void scriptProtoDispose(scriptproto_t *proto);
|
||||
|
||||
/**
|
||||
* Disposes every prototype ever initialized via scriptProtoInit. Must be
|
||||
* called before jerry_cleanup() - this JerryScript build fatally asserts
|
||||
* during cleanup if any jerry_value_t handle is still held by the embedder,
|
||||
* and every scriptproto_t retains its prototype/constructor handles for the
|
||||
* lifetime of the module system.
|
||||
*/
|
||||
void scriptProtoDisposeAll(void);
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2025 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "dusk.h"
|
||||
|
||||
#define SCRIPT_VALUE_TYPE_NIL 0
|
||||
#define SCRIPT_VALUE_TYPE_INT 1
|
||||
#define SCRIPT_VALUE_TYPE_FLOAT 2
|
||||
#define SCRIPT_VALUE_TYPE_STRING 3
|
||||
#define SCRIPT_VALUE_TYPE_BOOL 4
|
||||
#define SCRIPT_VALUE_TYPE_USERDATA 5
|
||||
|
||||
typedef struct scriptvalue_s {
|
||||
uint8_t type;
|
||||
|
||||
union {
|
||||
int32_t intValue;
|
||||
float_t floatValue;
|
||||
char_t *strValue;
|
||||
bool_t boolValue;
|
||||
} value;
|
||||
} scriptvalue_t;
|
||||
Reference in New Issue
Block a user