Script stuff
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
// Radius must stay well outside the floor's footprint (a 20x20 plane has a
|
||||
// corner-to-center distance of 10*sqrt(2) =~ 14.1) -- orbiting inside that
|
||||
// puts parts of the floor's own geometry near/behind the camera's view
|
||||
// direction, which the PSP's legacy GU pipeline can't clip properly and
|
||||
// drops the whole triangle instead of clipping it.
|
||||
var CAMERA_ORBIT_RADIUS = 18.0;
|
||||
var CAMERA_ORBIT_HEIGHT = 10.0;
|
||||
var CAMERA_ORBIT_SPEED = 0.5;
|
||||
|
||||
var cameraOrbitAngle = 0.0;
|
||||
var cameraPosition = null;
|
||||
|
||||
// Orbits the camera around the world origin at a fixed radius/height/
|
||||
// speed, always looking back at the origin. Called once up front (so the
|
||||
// very first rendered frame is already positioned correctly) and then
|
||||
// once per frame via update() below.
|
||||
function updateCameraOrbit() {
|
||||
cameraOrbitAngle += Time.delta * CAMERA_ORBIT_SPEED;
|
||||
|
||||
var eyeX = Math.cos(cameraOrbitAngle) * CAMERA_ORBIT_RADIUS;
|
||||
var eyeY = CAMERA_ORBIT_HEIGHT;
|
||||
var eyeZ = Math.sin(cameraOrbitAngle) * CAMERA_ORBIT_RADIUS;
|
||||
|
||||
cameraPosition.lookAt(eyeX, eyeY, eyeZ, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
|
||||
}
|
||||
|
||||
// Called once per engine frame (see engineUpdate() -> scriptManagerCall
|
||||
// Global("update")).
|
||||
function update() {
|
||||
if(cameraPosition) updateCameraOrbit();
|
||||
}
|
||||
|
||||
(function setup() {
|
||||
var scene = new Scene();
|
||||
scene.setActive();
|
||||
|
||||
// Camera, orbiting the origin (see updateCameraOrbit above).
|
||||
var camera = new Entity();
|
||||
cameraPosition = camera.add(POSITION);
|
||||
camera.add(CAMERA);
|
||||
updateCameraOrbit();
|
||||
|
||||
// Static ground plane. Physics ignores the entity's position component --
|
||||
// the shape's own normal/distance fully define the plane in world space.
|
||||
var plane = new Entity();
|
||||
var planePosition = plane.add(POSITION);
|
||||
planePosition.setLocalPosition(-10.0, 0.0, -10.0);
|
||||
planePosition.setLocalScale(20.0, 1.0, 20.0);
|
||||
|
||||
var planePhysics = plane.add(PHYSICS);
|
||||
planePhysics.setBodyType(PHYSICS_BODY_STATIC);
|
||||
planePhysics.setShape(PHYSICS_SHAPE_PLANE, 0.0, 1.0, 0.0, 0.0);
|
||||
|
||||
var planeRenderable = plane.add(RENDERABLE);
|
||||
planeRenderable.setMesh(0, MESH_PLANE);
|
||||
planeRenderable.setColor(128, 128, 128, 255);
|
||||
|
||||
// Player: dynamic capsule body, moved relative to the camera by
|
||||
// PLAYER's own update callback (see entityplayer.c).
|
||||
var player = new Entity();
|
||||
var playerPosition = player.add(POSITION);
|
||||
playerPosition.setLocalPosition(0.0, 2.0, 0.0);
|
||||
|
||||
var playerPhysics = player.add(PHYSICS);
|
||||
playerPhysics.setShape(PHYSICS_SHAPE_CAPSULE, 0.5, 0.5);
|
||||
|
||||
var playerRenderable = player.add(RENDERABLE);
|
||||
playerRenderable.setMesh(0, MESH_CAPSULE);
|
||||
playerRenderable.setColor(0, 0, 255, 255);
|
||||
|
||||
player.add(PLAYER);
|
||||
})();
|
||||
@@ -74,6 +74,7 @@ errorret_t engineUpdate(void) {
|
||||
consoleUpdate();
|
||||
|
||||
errorChain(gameUpdate());
|
||||
errorChain(scriptManagerCallGlobal("update"));
|
||||
errorChain(sceneUpdate());
|
||||
errorChain(assetUpdate());
|
||||
errorChain(uiUpdate());
|
||||
|
||||
@@ -80,6 +80,32 @@ void entityRenderableSetColor(
|
||||
r->data.material.material.unlit.color = color;
|
||||
}
|
||||
|
||||
void entityRenderableSetMesh(
|
||||
entitymanager_t *mgr,
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const uint8_t slot,
|
||||
mesh_t *mesh
|
||||
) {
|
||||
assertNotNull(mesh, "Mesh cannot be null");
|
||||
assertTrue(slot < ENTITY_RENDERABLE_MESHES_MAX, "Mesh slot out of bounds");
|
||||
|
||||
entityrenderable_t *r = componentGetData(
|
||||
mgr, entityId, componentId, COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
assertTrue(
|
||||
r->type == ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL,
|
||||
"Renderable must be ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL to set a mesh"
|
||||
);
|
||||
|
||||
r->data.material.meshes[slot] = mesh;
|
||||
r->data.material.meshOffsets[slot] = 0;
|
||||
r->data.material.meshCounts[slot] = -1;
|
||||
if(slot >= r->data.material.meshCount) {
|
||||
r->data.material.meshCount = slot + 1;
|
||||
}
|
||||
}
|
||||
|
||||
void entityRenderableSetDraw(
|
||||
entitymanager_t *mgr,
|
||||
const entityid_t entityId,
|
||||
|
||||
@@ -142,6 +142,26 @@ void entityRenderableSetColor(
|
||||
const color_t color
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets one of the material renderable's meshes, drawn in full (no offset/
|
||||
* count override). Only meaningful when the renderable is (or defaults
|
||||
* to) ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL -- asserts otherwise.
|
||||
*
|
||||
* @param mgr The entity manager that owns the entity.
|
||||
* @param entityId The entity to configure.
|
||||
* @param componentId The renderable component.
|
||||
* @param slot Index into the material's meshes array (0 to
|
||||
* ENTITY_RENDERABLE_MESHES_MAX - 1).
|
||||
* @param mesh The mesh to draw in that slot.
|
||||
*/
|
||||
void entityRenderableSetMesh(
|
||||
entitymanager_t *mgr,
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
const uint8_t slot,
|
||||
mesh_t *mesh
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets the draw callback, switching the type to
|
||||
* ENTITY_RENDERABLE_TYPE_CUSTOM.
|
||||
|
||||
@@ -10,3 +10,6 @@ target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
|
||||
add_subdirectory(entity)
|
||||
add_subdirectory(scene)
|
||||
add_subdirectory(require)
|
||||
add_subdirectory(display)
|
||||
add_subdirectory(time)
|
||||
|
||||
@@ -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
|
||||
modulemesh.c
|
||||
)
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "modulemesh.h"
|
||||
#include "script/module/modulebase.h"
|
||||
#include "display/mesh/cube.h"
|
||||
#include "display/mesh/plane.h"
|
||||
#include "display/mesh/sphere.h"
|
||||
#include "display/mesh/capsule.h"
|
||||
#include "display/mesh/quad.h"
|
||||
#include "display/mesh/triprism.h"
|
||||
|
||||
void moduleMeshInit(void) {
|
||||
moduleBaseSetWrappedPointer("MESH_CUBE", &CUBE_MESH_SIMPLE);
|
||||
moduleBaseSetWrappedPointer("MESH_PLANE", &PLANE_MESH_SIMPLE);
|
||||
moduleBaseSetWrappedPointer("MESH_SPHERE", &SPHERE_MESH_SIMPLE);
|
||||
moduleBaseSetWrappedPointer("MESH_CAPSULE", &CAPSULE_MESH_SIMPLE);
|
||||
moduleBaseSetWrappedPointer("MESH_TRIPRISM", &TRIPRISM_MESH_SIMPLE);
|
||||
moduleBaseSetWrappedPointer("MESH_QUAD", &QUAD_MESH_SIMPLE);
|
||||
}
|
||||
|
||||
void moduleMeshDispose(void) {
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* Registers the built-in primitive meshes (MESH_CUBE, MESH_PLANE,
|
||||
* MESH_SPHERE, MESH_CAPSULE, MESH_TRIPRISM, MESH_QUAD) as global,
|
||||
* engine-owned pointer values usable with Renderable.setMesh().
|
||||
*/
|
||||
void moduleMeshInit(void);
|
||||
|
||||
/**
|
||||
* Disposes the mesh module's script resources.
|
||||
*/
|
||||
void moduleMeshDispose(void);
|
||||
@@ -6,4 +6,8 @@
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
modulecomponent.c
|
||||
modulecomponentlist.c
|
||||
)
|
||||
|
||||
add_subdirectory(display)
|
||||
add_subdirectory(physics)
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# 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
|
||||
modulepositioncomponent.c
|
||||
modulerenderablecomponent.c
|
||||
)
|
||||
@@ -0,0 +1,150 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "modulepositioncomponent.h"
|
||||
#include "script/module/modulebase.h"
|
||||
#include "entity/component/display/entityposition.h"
|
||||
#include "entity/component.h"
|
||||
#include "util/string.h"
|
||||
|
||||
scriptproto_t MODULE_POSITION_PROTO;
|
||||
|
||||
moduleBaseFunction(modulePositionGetType) {
|
||||
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPositionGet);
|
||||
return jerry_number(h->type);
|
||||
}
|
||||
|
||||
moduleBaseFunction(modulePositionGetEntityId) {
|
||||
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPositionGet);
|
||||
return jerry_number(h->entityId);
|
||||
}
|
||||
|
||||
moduleBaseFunction(modulePositionDisposeMethod) {
|
||||
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPositionGet);
|
||||
componentDispose(h->mgr, h->entityId, h->componentId);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(modulePositionToString) {
|
||||
modulecomponenthandle_t *h = moduleEntityPositionGet(callInfo);
|
||||
if(!h) return jerry_string_sz("Position(?)");
|
||||
|
||||
char_t buf[32];
|
||||
stringFormat(buf, sizeof(buf), "Position(entityId=%d)", h->entityId);
|
||||
return jerry_string_sz(buf);
|
||||
}
|
||||
|
||||
moduleBaseFunction(modulePositionSetLocalPosition) {
|
||||
moduleBaseRequireArgs(3);
|
||||
moduleBaseRequireNumber(0); moduleBaseRequireNumber(1);
|
||||
moduleBaseRequireNumber(2);
|
||||
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPositionGet);
|
||||
|
||||
entityPositionSetLocalPosition(h->mgr, h->entityId, h->componentId, (vec3){
|
||||
moduleBaseArgFloat(0), moduleBaseArgFloat(1), moduleBaseArgFloat(2)
|
||||
});
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(modulePositionGetLocalPosition) {
|
||||
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPositionGet);
|
||||
|
||||
vec3 pos;
|
||||
entityPositionGetLocalPosition(h->mgr, h->entityId, h->componentId, pos);
|
||||
return moduleBaseVec3ToObject(pos);
|
||||
}
|
||||
|
||||
moduleBaseFunction(modulePositionSetLocalScale) {
|
||||
moduleBaseRequireArgs(3);
|
||||
moduleBaseRequireNumber(0); moduleBaseRequireNumber(1);
|
||||
moduleBaseRequireNumber(2);
|
||||
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPositionGet);
|
||||
|
||||
entityPositionSetLocalScale(h->mgr, h->entityId, h->componentId, (vec3){
|
||||
moduleBaseArgFloat(0), moduleBaseArgFloat(1), moduleBaseArgFloat(2)
|
||||
});
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(modulePositionGetLocalScale) {
|
||||
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPositionGet);
|
||||
|
||||
vec3 scale;
|
||||
entityPositionGetLocalScale(h->mgr, h->entityId, h->componentId, scale);
|
||||
return moduleBaseVec3ToObject(scale);
|
||||
}
|
||||
|
||||
moduleBaseFunction(modulePositionLookAt) {
|
||||
moduleBaseRequireArgs(9);
|
||||
for(jerry_length_t i = 0; i < 9; i++) moduleBaseRequireNumber(i);
|
||||
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPositionGet);
|
||||
|
||||
entityPositionLookAt(
|
||||
h->mgr, h->entityId, h->componentId,
|
||||
(vec3){
|
||||
moduleBaseArgFloat(0), moduleBaseArgFloat(1), moduleBaseArgFloat(2)
|
||||
},
|
||||
(vec3){
|
||||
moduleBaseArgFloat(3), moduleBaseArgFloat(4), moduleBaseArgFloat(5)
|
||||
},
|
||||
(vec3){
|
||||
moduleBaseArgFloat(6), moduleBaseArgFloat(7), moduleBaseArgFloat(8)
|
||||
}
|
||||
);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
void moduleEntityPositionInit(void) {
|
||||
scriptProtoInit(
|
||||
&MODULE_POSITION_PROTO, "Position", sizeof(modulecomponenthandle_t), NULL
|
||||
);
|
||||
|
||||
scriptProtoDefineToString(&MODULE_POSITION_PROTO, modulePositionToString);
|
||||
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_POSITION_PROTO, "type", modulePositionGetType, NULL
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_POSITION_PROTO, "entityId", modulePositionGetEntityId, NULL
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_POSITION_PROTO, "dispose", modulePositionDisposeMethod
|
||||
);
|
||||
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_POSITION_PROTO, "setLocalPosition", modulePositionSetLocalPosition
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_POSITION_PROTO, "getLocalPosition", modulePositionGetLocalPosition
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_POSITION_PROTO, "setLocalScale", modulePositionSetLocalScale
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_POSITION_PROTO, "getLocalScale", modulePositionGetLocalScale
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_POSITION_PROTO, "lookAt", modulePositionLookAt
|
||||
);
|
||||
}
|
||||
|
||||
void moduleEntityPositionDispose(void) {
|
||||
}
|
||||
|
||||
jerry_value_t moduleEntityPositionCreate(
|
||||
const modulecomponenthandle_t *handle
|
||||
) {
|
||||
return scriptProtoCreateValue(&MODULE_POSITION_PROTO, handle);
|
||||
}
|
||||
|
||||
modulecomponenthandle_t *moduleEntityPositionGet(
|
||||
const jerry_call_info_t *callInfo
|
||||
) {
|
||||
return (modulecomponenthandle_t *)scriptProtoGetValue(
|
||||
&MODULE_POSITION_PROTO, callInfo->this_value
|
||||
);
|
||||
}
|
||||
@@ -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 "script/scriptproto.h"
|
||||
#include "script/module/entity/component/modulecomponent.h"
|
||||
#include <jerryscript.h>
|
||||
|
||||
extern scriptproto_t MODULE_POSITION_PROTO;
|
||||
|
||||
/**
|
||||
* Registers the Position class: a typed wrapper over COMPONENT_TYPE_
|
||||
* POSITION. Has no constructor -- instances are only ever created via
|
||||
* moduleEntityPositionCreate(), e.g. from Entity.add()/getComponent().
|
||||
*/
|
||||
void moduleEntityPositionInit(void);
|
||||
|
||||
/**
|
||||
* Disposes the Position class's script resources.
|
||||
*/
|
||||
void moduleEntityPositionDispose(void);
|
||||
|
||||
/**
|
||||
* Wraps a component handle as a new JS Position instance.
|
||||
*
|
||||
* @param handle The handle to copy into the new instance.
|
||||
* @return The new JS object.
|
||||
*/
|
||||
jerry_value_t moduleEntityPositionCreate(
|
||||
const modulecomponenthandle_t *handle
|
||||
);
|
||||
|
||||
/**
|
||||
* Internal. Gets the native handle wrapped by a Position instance's
|
||||
* `this` value.
|
||||
*
|
||||
* @param callInfo The JS call info, whose this_value is the instance.
|
||||
* @return The wrapped handle, or NULL if this_value isn't a Position.
|
||||
*/
|
||||
modulecomponenthandle_t *moduleEntityPositionGet(
|
||||
const jerry_call_info_t *callInfo
|
||||
);
|
||||
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "modulerenderablecomponent.h"
|
||||
#include "script/module/modulebase.h"
|
||||
#include "entity/component/display/entityrenderable.h"
|
||||
#include "entity/component.h"
|
||||
#include "display/color.h"
|
||||
#include "util/string.h"
|
||||
|
||||
scriptproto_t MODULE_RENDERABLE_PROTO;
|
||||
|
||||
moduleBaseFunction(moduleRenderableGetType) {
|
||||
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityRenderableGet);
|
||||
return jerry_number(h->type);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleRenderableGetEntityId) {
|
||||
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityRenderableGet);
|
||||
return jerry_number(h->entityId);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleRenderableDisposeMethod) {
|
||||
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityRenderableGet);
|
||||
componentDispose(h->mgr, h->entityId, h->componentId);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleRenderableToString) {
|
||||
modulecomponenthandle_t *h = moduleEntityRenderableGet(callInfo);
|
||||
if(!h) return jerry_string_sz("Renderable(?)");
|
||||
|
||||
char_t buf[32];
|
||||
stringFormat(buf, sizeof(buf), "Renderable(entityId=%d)", h->entityId);
|
||||
return jerry_string_sz(buf);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleRenderableSetColor) {
|
||||
moduleBaseRequireArgs(4);
|
||||
moduleBaseRequireNumber(0); moduleBaseRequireNumber(1);
|
||||
moduleBaseRequireNumber(2); moduleBaseRequireNumber(3);
|
||||
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityRenderableGet);
|
||||
|
||||
entityRenderableSetColor(h->mgr, h->entityId, h->componentId, color(
|
||||
(uint8_t)moduleBaseArgInt(0), (uint8_t)moduleBaseArgInt(1),
|
||||
(uint8_t)moduleBaseArgInt(2), (uint8_t)moduleBaseArgInt(3)
|
||||
));
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleRenderableSetMesh) {
|
||||
moduleBaseRequireArgs(2); moduleBaseRequireNumber(0);
|
||||
moduleBaseRequireObject(1);
|
||||
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityRenderableGet);
|
||||
|
||||
mesh_t *mesh = (mesh_t *)moduleBaseUnwrapPointer(args[1]);
|
||||
if(!mesh) return moduleBaseThrow("Renderable.setMesh: invalid mesh");
|
||||
|
||||
entityRenderableSetMesh(
|
||||
h->mgr, h->entityId, h->componentId, (uint8_t)moduleBaseArgInt(0), mesh
|
||||
);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleRenderableSetPriority) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityRenderableGet);
|
||||
|
||||
entityRenderableSetPriority(
|
||||
h->mgr, h->entityId, h->componentId, (int8_t)moduleBaseArgInt(0)
|
||||
);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
void moduleEntityRenderableInit(void) {
|
||||
scriptProtoInit(
|
||||
&MODULE_RENDERABLE_PROTO,
|
||||
"Renderable", sizeof(modulecomponenthandle_t), NULL
|
||||
);
|
||||
|
||||
scriptProtoDefineToString(&MODULE_RENDERABLE_PROTO, moduleRenderableToString);
|
||||
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_RENDERABLE_PROTO, "type", moduleRenderableGetType, NULL
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_RENDERABLE_PROTO, "entityId", moduleRenderableGetEntityId, NULL
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_RENDERABLE_PROTO, "dispose", moduleRenderableDisposeMethod
|
||||
);
|
||||
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_RENDERABLE_PROTO, "setColor", moduleRenderableSetColor
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_RENDERABLE_PROTO, "setMesh", moduleRenderableSetMesh
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_RENDERABLE_PROTO, "setPriority", moduleRenderableSetPriority
|
||||
);
|
||||
}
|
||||
|
||||
void moduleEntityRenderableDispose(void) {
|
||||
}
|
||||
|
||||
jerry_value_t moduleEntityRenderableCreate(
|
||||
const modulecomponenthandle_t *handle
|
||||
) {
|
||||
return scriptProtoCreateValue(&MODULE_RENDERABLE_PROTO, handle);
|
||||
}
|
||||
|
||||
modulecomponenthandle_t *moduleEntityRenderableGet(
|
||||
const jerry_call_info_t *callInfo
|
||||
) {
|
||||
return (modulecomponenthandle_t *)scriptProtoGetValue(
|
||||
&MODULE_RENDERABLE_PROTO, callInfo->this_value
|
||||
);
|
||||
}
|
||||
@@ -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 "script/scriptproto.h"
|
||||
#include "script/module/entity/component/modulecomponent.h"
|
||||
#include <jerryscript.h>
|
||||
|
||||
extern scriptproto_t MODULE_RENDERABLE_PROTO;
|
||||
|
||||
/**
|
||||
* Registers the Renderable class: a typed wrapper over COMPONENT_TYPE_
|
||||
* RENDERABLE. Has no constructor -- instances are only ever created via
|
||||
* moduleEntityRenderableCreate(), e.g. from Entity.add()/getComponent().
|
||||
*/
|
||||
void moduleEntityRenderableInit(void);
|
||||
|
||||
/**
|
||||
* Disposes the Renderable class's script resources.
|
||||
*/
|
||||
void moduleEntityRenderableDispose(void);
|
||||
|
||||
/**
|
||||
* Wraps a component handle as a new JS Renderable instance.
|
||||
*
|
||||
* @param handle The handle to copy into the new instance.
|
||||
* @return The new JS object.
|
||||
*/
|
||||
jerry_value_t moduleEntityRenderableCreate(
|
||||
const modulecomponenthandle_t *handle
|
||||
);
|
||||
|
||||
/**
|
||||
* Internal. Gets the native handle wrapped by a Renderable instance's
|
||||
* `this` value.
|
||||
*
|
||||
* @param callInfo The JS call info, whose this_value is the instance.
|
||||
* @return The wrapped handle, or NULL if this_value isn't a Renderable.
|
||||
*/
|
||||
modulecomponenthandle_t *moduleEntityRenderableGet(
|
||||
const jerry_call_info_t *callInfo
|
||||
);
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "modulecomponentlist.h"
|
||||
#include "script/module/entity/component/display/modulepositioncomponent.h"
|
||||
#include "script/module/entity/component/physics/modulephysicscomponent.h"
|
||||
#include "script/module/entity/component/display/modulerenderablecomponent.h"
|
||||
|
||||
void moduleComponentListInit(void) {
|
||||
moduleEntityPositionInit();
|
||||
moduleEntityPhysicsInit();
|
||||
moduleEntityRenderableInit();
|
||||
}
|
||||
|
||||
void moduleComponentListDispose(void) {
|
||||
moduleEntityRenderableDispose();
|
||||
moduleEntityPhysicsDispose();
|
||||
moduleEntityPositionDispose();
|
||||
}
|
||||
|
||||
jerry_value_t moduleComponentListCreate(
|
||||
const modulecomponenthandle_t *handle
|
||||
) {
|
||||
switch(handle->type) {
|
||||
case COMPONENT_TYPE_POSITION:
|
||||
return moduleEntityPositionCreate(handle);
|
||||
case COMPONENT_TYPE_PHYSICS:
|
||||
return moduleEntityPhysicsCreate(handle);
|
||||
case COMPONENT_TYPE_RENDERABLE:
|
||||
return moduleEntityRenderableCreate(handle);
|
||||
default:
|
||||
return moduleComponentCreate(handle);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/module/entity/component/modulecomponent.h"
|
||||
#include <jerryscript.h>
|
||||
|
||||
/**
|
||||
* Registers every typed per-component-type JS wrapper module (Position,
|
||||
* Physics, Renderable, ...). Called once by moduleListInit().
|
||||
*/
|
||||
void moduleComponentListInit(void);
|
||||
|
||||
/**
|
||||
* Disposes every typed component wrapper module. Called once by
|
||||
* moduleListDispose().
|
||||
*/
|
||||
void moduleComponentListDispose(void);
|
||||
|
||||
/**
|
||||
* Wraps a component handle using the most specific JS class available
|
||||
* for its type (e.g. Position for COMPONENT_TYPE_POSITION), falling back
|
||||
* to the generic Component wrapper for types with no typed wrapper yet.
|
||||
*
|
||||
* @param handle The handle to wrap.
|
||||
* @return The new JS object.
|
||||
*/
|
||||
jerry_value_t moduleComponentListCreate(
|
||||
const modulecomponenthandle_t *handle
|
||||
);
|
||||
@@ -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
|
||||
modulephysicscomponent.c
|
||||
)
|
||||
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "modulephysicscomponent.h"
|
||||
#include "script/module/modulebase.h"
|
||||
#include "entity/component/physics/entityphysics.h"
|
||||
#include "entity/component.h"
|
||||
#include "util/string.h"
|
||||
#include "util/memory.h"
|
||||
|
||||
scriptproto_t MODULE_PHYSICS_PROTO;
|
||||
|
||||
moduleBaseFunction(modulePhysicsGetType) {
|
||||
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPhysicsGet);
|
||||
return jerry_number(h->type);
|
||||
}
|
||||
|
||||
moduleBaseFunction(modulePhysicsGetEntityId) {
|
||||
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPhysicsGet);
|
||||
return jerry_number(h->entityId);
|
||||
}
|
||||
|
||||
moduleBaseFunction(modulePhysicsDisposeMethod) {
|
||||
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPhysicsGet);
|
||||
componentDispose(h->mgr, h->entityId, h->componentId);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(modulePhysicsToString) {
|
||||
modulecomponenthandle_t *h = moduleEntityPhysicsGet(callInfo);
|
||||
if(!h) return jerry_string_sz("Physics(?)");
|
||||
|
||||
char_t buf[32];
|
||||
stringFormat(buf, sizeof(buf), "Physics(entityId=%d)", h->entityId);
|
||||
return jerry_string_sz(buf);
|
||||
}
|
||||
|
||||
moduleBaseFunction(modulePhysicsSetBodyType) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPhysicsGet);
|
||||
|
||||
entityPhysicsSetBodyType(
|
||||
h->mgr, h->entityId, h->componentId,
|
||||
(physicsbodytype_t)moduleBaseArgInt(0)
|
||||
);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(modulePhysicsGetBodyType) {
|
||||
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPhysicsGet);
|
||||
return jerry_number(
|
||||
entityPhysicsGetBodyType(h->mgr, h->entityId, h->componentId)
|
||||
);
|
||||
}
|
||||
|
||||
moduleBaseFunction(modulePhysicsSetShape) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireNumber(0);
|
||||
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPhysicsGet);
|
||||
|
||||
physicsshape_t shape;
|
||||
memoryZero(&shape, sizeof(shape));
|
||||
shape.type = (physicshapetype_t)moduleBaseArgInt(0);
|
||||
|
||||
switch(shape.type) {
|
||||
case PHYSICS_SHAPE_CUBE:
|
||||
moduleBaseRequireArgs(4);
|
||||
moduleBaseRequireNumber(1); moduleBaseRequireNumber(2);
|
||||
moduleBaseRequireNumber(3);
|
||||
shape.data.cube.halfExtents[0] = moduleBaseArgFloat(1);
|
||||
shape.data.cube.halfExtents[1] = moduleBaseArgFloat(2);
|
||||
shape.data.cube.halfExtents[2] = moduleBaseArgFloat(3);
|
||||
break;
|
||||
|
||||
case PHYSICS_SHAPE_SPHERE:
|
||||
moduleBaseRequireArgs(2); moduleBaseRequireNumber(1);
|
||||
shape.data.sphere.radius = moduleBaseArgFloat(1);
|
||||
break;
|
||||
|
||||
case PHYSICS_SHAPE_CAPSULE:
|
||||
moduleBaseRequireArgs(3);
|
||||
moduleBaseRequireNumber(1); moduleBaseRequireNumber(2);
|
||||
shape.data.capsule.radius = moduleBaseArgFloat(1);
|
||||
shape.data.capsule.halfHeight = moduleBaseArgFloat(2);
|
||||
break;
|
||||
|
||||
case PHYSICS_SHAPE_PLANE:
|
||||
moduleBaseRequireArgs(5);
|
||||
moduleBaseRequireNumber(1); moduleBaseRequireNumber(2);
|
||||
moduleBaseRequireNumber(3); moduleBaseRequireNumber(4);
|
||||
shape.data.plane.normal[0] = moduleBaseArgFloat(1);
|
||||
shape.data.plane.normal[1] = moduleBaseArgFloat(2);
|
||||
shape.data.plane.normal[2] = moduleBaseArgFloat(3);
|
||||
shape.data.plane.distance = moduleBaseArgFloat(4);
|
||||
break;
|
||||
|
||||
default:
|
||||
return moduleBaseThrow("Physics.setShape: unsupported shape type");
|
||||
}
|
||||
|
||||
entityPhysicsSetShape(h->mgr, h->entityId, h->componentId, shape);
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(modulePhysicsSetVelocity) {
|
||||
moduleBaseRequireArgs(3);
|
||||
moduleBaseRequireNumber(0); moduleBaseRequireNumber(1);
|
||||
moduleBaseRequireNumber(2);
|
||||
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPhysicsGet);
|
||||
|
||||
entityPhysicsSetVelocity(h->mgr, h->entityId, h->componentId, (vec3){
|
||||
moduleBaseArgFloat(0), moduleBaseArgFloat(1), moduleBaseArgFloat(2)
|
||||
});
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(modulePhysicsGetVelocity) {
|
||||
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPhysicsGet);
|
||||
|
||||
vec3 velocity;
|
||||
entityPhysicsGetVelocity(h->mgr, h->entityId, h->componentId, velocity);
|
||||
return moduleBaseVec3ToObject(velocity);
|
||||
}
|
||||
|
||||
moduleBaseFunction(modulePhysicsApplyImpulse) {
|
||||
moduleBaseRequireArgs(3);
|
||||
moduleBaseRequireNumber(0); moduleBaseRequireNumber(1);
|
||||
moduleBaseRequireNumber(2);
|
||||
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPhysicsGet);
|
||||
|
||||
entityPhysicsApplyImpulse(h->mgr, h->entityId, h->componentId, (vec3){
|
||||
moduleBaseArgFloat(0), moduleBaseArgFloat(1), moduleBaseArgFloat(2)
|
||||
});
|
||||
return jerry_undefined();
|
||||
}
|
||||
|
||||
moduleBaseFunction(modulePhysicsIsOnGround) {
|
||||
moduleBaseGetOrReturn(modulecomponenthandle_t, h, moduleEntityPhysicsGet);
|
||||
return jerry_boolean(
|
||||
entityPhysicsIsOnGround(h->mgr, h->entityId, h->componentId)
|
||||
);
|
||||
}
|
||||
|
||||
void moduleEntityPhysicsInit(void) {
|
||||
scriptProtoInit(
|
||||
&MODULE_PHYSICS_PROTO, "Physics", sizeof(modulecomponenthandle_t), NULL
|
||||
);
|
||||
|
||||
scriptProtoDefineToString(&MODULE_PHYSICS_PROTO, modulePhysicsToString);
|
||||
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_PHYSICS_PROTO, "type", modulePhysicsGetType, NULL
|
||||
);
|
||||
scriptProtoDefineProp(
|
||||
&MODULE_PHYSICS_PROTO, "entityId", modulePhysicsGetEntityId, NULL
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_PHYSICS_PROTO, "dispose", modulePhysicsDisposeMethod
|
||||
);
|
||||
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_PHYSICS_PROTO, "setBodyType", modulePhysicsSetBodyType
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_PHYSICS_PROTO, "getBodyType", modulePhysicsGetBodyType
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_PHYSICS_PROTO, "setShape", modulePhysicsSetShape
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_PHYSICS_PROTO, "setVelocity", modulePhysicsSetVelocity
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_PHYSICS_PROTO, "getVelocity", modulePhysicsGetVelocity
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_PHYSICS_PROTO, "applyImpulse", modulePhysicsApplyImpulse
|
||||
);
|
||||
scriptProtoDefineFunc(
|
||||
&MODULE_PHYSICS_PROTO, "isOnGround", modulePhysicsIsOnGround
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void moduleEntityPhysicsDispose(void) {
|
||||
}
|
||||
|
||||
jerry_value_t moduleEntityPhysicsCreate(
|
||||
const modulecomponenthandle_t *handle
|
||||
) {
|
||||
return scriptProtoCreateValue(&MODULE_PHYSICS_PROTO, handle);
|
||||
}
|
||||
|
||||
modulecomponenthandle_t *moduleEntityPhysicsGet(
|
||||
const jerry_call_info_t *callInfo
|
||||
) {
|
||||
return (modulecomponenthandle_t *)scriptProtoGetValue(
|
||||
&MODULE_PHYSICS_PROTO, callInfo->this_value
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "script/scriptproto.h"
|
||||
#include "script/module/entity/component/modulecomponent.h"
|
||||
#include <jerryscript.h>
|
||||
|
||||
extern scriptproto_t MODULE_PHYSICS_PROTO;
|
||||
|
||||
/**
|
||||
* Registers the Physics class: a typed wrapper over COMPONENT_TYPE_
|
||||
* PHYSICS. Also registers PHYSICS_BODY_* and PHYSICS_SHAPE_* as global
|
||||
* integer constants for use with setBodyType()/setShape(). Has no
|
||||
* constructor -- instances are only ever created via
|
||||
* moduleEntityPhysicsCreate(), e.g. from Entity.add()/getComponent().
|
||||
*/
|
||||
void moduleEntityPhysicsInit(void);
|
||||
|
||||
/**
|
||||
* Disposes the Physics class's script resources.
|
||||
*/
|
||||
void moduleEntityPhysicsDispose(void);
|
||||
|
||||
/**
|
||||
* Wraps a component handle as a new JS Physics instance.
|
||||
*
|
||||
* @param handle The handle to copy into the new instance.
|
||||
* @return The new JS object.
|
||||
*/
|
||||
jerry_value_t moduleEntityPhysicsCreate(
|
||||
const modulecomponenthandle_t *handle
|
||||
);
|
||||
|
||||
/**
|
||||
* Internal. Gets the native handle wrapped by a Physics instance's
|
||||
* `this` value.
|
||||
*
|
||||
* @param callInfo The JS call info, whose this_value is the instance.
|
||||
* @return The wrapped handle, or NULL if this_value isn't a Physics.
|
||||
*/
|
||||
modulecomponenthandle_t *moduleEntityPhysicsGet(
|
||||
const jerry_call_info_t *callInfo
|
||||
);
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "moduleentity.h"
|
||||
#include "script/module/modulebase.h"
|
||||
#include "script/module/entity/component/modulecomponent.h"
|
||||
#include "script/module/entity/component/modulecomponentlist.h"
|
||||
#include "scene/scene.h"
|
||||
#include "entity/entitymanager.h"
|
||||
#include "entity/entity.h"
|
||||
@@ -67,7 +68,7 @@ moduleBaseFunction(moduleEntityAddComponent) {
|
||||
modulecomponenthandle_t h = {
|
||||
.mgr = inst->mgr, .entityId = inst->id, .componentId = id, .type = type
|
||||
};
|
||||
return moduleComponentCreate(&h);
|
||||
return moduleComponentListCreate(&h);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityGetComponentMethod) {
|
||||
@@ -85,7 +86,7 @@ moduleBaseFunction(moduleEntityGetComponentMethod) {
|
||||
modulecomponenthandle_t h = {
|
||||
.mgr = inst->mgr, .entityId = inst->id, .componentId = id, .type = type
|
||||
};
|
||||
return moduleComponentCreate(&h);
|
||||
return moduleComponentListCreate(&h);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleEntityDisposeMethod) {
|
||||
|
||||
@@ -394,3 +394,30 @@ static inline float_t moduleBaseValueFloat(jerry_value_t val) {
|
||||
static inline int32_t moduleBaseValueInt(jerry_value_t val) {
|
||||
return (int32_t)jerry_value_as_number(val);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a vec3 as a plain JS {x, y, z} object.
|
||||
*/
|
||||
static inline jerry_value_t moduleBaseVec3ToObject(const vec3 v) {
|
||||
jerry_value_t obj = jerry_object();
|
||||
|
||||
jerry_value_t xKey = jerry_string_sz("x");
|
||||
jerry_value_t xVal = jerry_number(v[0]);
|
||||
jerry_object_set(obj, xKey, xVal);
|
||||
jerry_value_free(xVal);
|
||||
jerry_value_free(xKey);
|
||||
|
||||
jerry_value_t yKey = jerry_string_sz("y");
|
||||
jerry_value_t yVal = jerry_number(v[1]);
|
||||
jerry_object_set(obj, yKey, yVal);
|
||||
jerry_value_free(yVal);
|
||||
jerry_value_free(yKey);
|
||||
|
||||
jerry_value_t zKey = jerry_string_sz("z");
|
||||
jerry_value_t zVal = jerry_number(v[2]);
|
||||
jerry_object_set(obj, zKey, zVal);
|
||||
jerry_value_free(zVal);
|
||||
jerry_value_free(zKey);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -7,13 +7,21 @@
|
||||
|
||||
#include "modulelist.h"
|
||||
#include "script/module/moduleplatform.h"
|
||||
#include "script/module/require/modulerequire.h"
|
||||
#include "script/module/display/modulemesh.h"
|
||||
#include "script/module/time/moduletime.h"
|
||||
#include "script/module/entity/component/modulecomponent.h"
|
||||
#include "script/module/entity/component/modulecomponentlist.h"
|
||||
#include "script/module/entity/moduleentity.h"
|
||||
#include "script/module/scene/modulescene.h"
|
||||
|
||||
void moduleListInit(void) {
|
||||
modulePlatform();
|
||||
moduleRequireInit();
|
||||
moduleMeshInit();
|
||||
moduleTimeInit();
|
||||
moduleComponentInit();
|
||||
moduleComponentListInit();
|
||||
moduleEntityInit();
|
||||
moduleSceneInit();
|
||||
}
|
||||
@@ -21,5 +29,9 @@ void moduleListInit(void) {
|
||||
void moduleListDispose(void) {
|
||||
moduleSceneDispose();
|
||||
moduleEntityDispose();
|
||||
moduleComponentListDispose();
|
||||
moduleComponentDispose();
|
||||
moduleTimeDispose();
|
||||
moduleMeshDispose();
|
||||
moduleRequireDispose();
|
||||
}
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* Registers every script module (Component, Entity, Scene, the platform
|
||||
* globals). Called once by scriptManagerInit().
|
||||
* Registers every script module (Component + its typed per-type
|
||||
* wrappers, Entity, Scene, require(), Mesh, Time, the platform globals).
|
||||
* Called once by scriptManagerInit().
|
||||
*/
|
||||
void moduleListInit(void);
|
||||
|
||||
|
||||
@@ -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
|
||||
modulerequire.c
|
||||
)
|
||||
@@ -0,0 +1,271 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "modulerequire.h"
|
||||
#include "script/module/modulebase.h"
|
||||
#include "assert/assert.h"
|
||||
#include "util/memory.h"
|
||||
#include "util/string.h"
|
||||
|
||||
#define MODULE_REQUIRE_WRAP_PREFIX "(function(module){\n"
|
||||
#define MODULE_REQUIRE_WRAP_SUFFIX "\n})"
|
||||
|
||||
modulerequire_t MODULE_REQUIRE;
|
||||
|
||||
moduleBaseFunction(moduleRequireFn) {
|
||||
moduleBaseRequireArgs(1); moduleBaseRequireString(0);
|
||||
|
||||
char_t requested[ASSET_FILE_NAME_MAX];
|
||||
moduleBaseToString(args[0], requested, sizeof(requested));
|
||||
|
||||
char_t resolved[ASSET_FILE_NAME_MAX];
|
||||
moduleRequireResolvePath(requested, resolved, sizeof(resolved));
|
||||
|
||||
jerry_value_t cachedModule;
|
||||
if(moduleRequireCacheGet(resolved, &cachedModule)) {
|
||||
return moduleBaseGetProp(cachedModule, "exports");
|
||||
}
|
||||
|
||||
assetfile_t file;
|
||||
errorret_t err = assetFileInit(&file, resolved, NULL, NULL);
|
||||
if(errorIsNotOk(err)) return moduleBaseThrowError(err);
|
||||
|
||||
uint8_t *buffer = NULL;
|
||||
size_t size = 0;
|
||||
err = assetFileReadEntire(&file, &buffer, &size);
|
||||
if(errorIsNotOk(err)) return moduleBaseThrowError(err);
|
||||
|
||||
err = assetFileDispose(&file);
|
||||
if(errorIsNotOk(err)) {
|
||||
memoryFree(buffer);
|
||||
return moduleBaseThrowError(err);
|
||||
}
|
||||
|
||||
// Wrap the source in a function so `module` is a real parameter, not a
|
||||
// global - each required file's scope stays isolated from the others.
|
||||
const size_t prefixLen = sizeof(MODULE_REQUIRE_WRAP_PREFIX) - 1;
|
||||
const size_t suffixLen = sizeof(MODULE_REQUIRE_WRAP_SUFFIX) - 1;
|
||||
const size_t wrappedLen = prefixLen + size + suffixLen;
|
||||
char_t *wrapped = (char_t *)memoryAllocate(wrappedLen + 1);
|
||||
memoryCopy(wrapped, MODULE_REQUIRE_WRAP_PREFIX, prefixLen);
|
||||
memoryCopy(wrapped + prefixLen, buffer, size);
|
||||
memoryCopy(wrapped + prefixLen + size, MODULE_REQUIRE_WRAP_SUFFIX, suffixLen);
|
||||
wrapped[wrappedLen] = '\0';
|
||||
memoryFree(buffer);
|
||||
|
||||
jerry_value_t fnValue = jerry_eval(
|
||||
(const jerry_char_t *)wrapped, wrappedLen, JERRY_PARSE_NO_OPTS
|
||||
);
|
||||
memoryFree(wrapped);
|
||||
|
||||
if(jerry_value_is_exception(fnValue)) return fnValue;
|
||||
|
||||
// Cache before calling so a circular require() sees this module object
|
||||
// (with whatever `module.exports` has been set so far) instead of
|
||||
// recursing forever.
|
||||
jerry_value_t moduleObj = jerry_object();
|
||||
moduleRequireCacheSet(resolved, moduleObj);
|
||||
|
||||
char_t dir[ASSET_FILE_NAME_MAX];
|
||||
moduleRequireDirname(resolved, dir, sizeof(dir));
|
||||
moduleRequireDirPush(dir);
|
||||
|
||||
jerry_value_t callArgs[1] = { moduleObj };
|
||||
jerry_value_t callResult = jerry_call(fnValue, jerry_undefined(), callArgs, 1);
|
||||
jerry_value_free(fnValue);
|
||||
|
||||
moduleRequireDirPop();
|
||||
|
||||
if(jerry_value_is_exception(callResult)) {
|
||||
moduleRequireCacheDelete(resolved);
|
||||
jerry_value_free(moduleObj);
|
||||
return callResult;
|
||||
}
|
||||
jerry_value_free(callResult);
|
||||
|
||||
return moduleBaseGetProp(moduleObj, "exports");
|
||||
}
|
||||
|
||||
void moduleRequireInit(void) {
|
||||
memoryZero(&MODULE_REQUIRE, sizeof(modulerequire_t));
|
||||
moduleBaseDefineGlobalMethod("require", moduleRequireFn);
|
||||
}
|
||||
|
||||
void moduleRequireDispose(void) {
|
||||
for(uint8_t i = 0; i < MODULE_REQUIRE.cacheCount; i++) {
|
||||
jerry_value_free(MODULE_REQUIRE.cache[i].moduleObj);
|
||||
}
|
||||
MODULE_REQUIRE.cacheCount = 0;
|
||||
MODULE_REQUIRE.dirStackCount = 0;
|
||||
}
|
||||
|
||||
void moduleRequireResolvePath(
|
||||
const char_t *requested,
|
||||
char_t *outResolved,
|
||||
const size_t outResolvedSize
|
||||
) {
|
||||
assertNotNull(requested, "Requested path cannot be NULL");
|
||||
assertNotNull(outResolved, "Output buffer cannot be NULL");
|
||||
|
||||
bool_t isRelative = (
|
||||
(requested[0] == '.' && requested[1] == '/') ||
|
||||
(requested[0] == '.' && requested[1] == '.' && requested[2] == '/')
|
||||
);
|
||||
|
||||
char_t combined[MODULE_REQUIRE_PATH_MAX];
|
||||
if(isRelative) {
|
||||
stringFormat(
|
||||
combined, sizeof(combined) - 1, "%s%s",
|
||||
moduleRequireDirStackTop(), requested
|
||||
);
|
||||
} else {
|
||||
stringCopy(combined, requested, sizeof(combined) - 1);
|
||||
}
|
||||
|
||||
moduleRequireNormalize(combined, outResolved, outResolvedSize);
|
||||
}
|
||||
|
||||
void moduleRequireNormalize(
|
||||
const char_t *path,
|
||||
char_t *outNormalized,
|
||||
const size_t outSize
|
||||
) {
|
||||
assertNotNull(path, "Path cannot be NULL");
|
||||
assertNotNull(outNormalized, "Output buffer cannot be NULL");
|
||||
|
||||
char_t scratch[MODULE_REQUIRE_PATH_MAX];
|
||||
stringCopy(scratch, path, sizeof(scratch) - 1);
|
||||
|
||||
char_t *segments[MODULE_REQUIRE_SEGMENT_MAX];
|
||||
uint8_t segmentCount = 0;
|
||||
|
||||
char_t *cursor = scratch;
|
||||
bool_t more = true;
|
||||
while(more) {
|
||||
char_t *segment = cursor;
|
||||
while(*cursor != '/' && *cursor != '\0') cursor++;
|
||||
more = (*cursor == '/');
|
||||
if(more) { *cursor = '\0'; cursor++; }
|
||||
|
||||
if(segment[0] == '\0' || stringEquals(segment, ".")) {
|
||||
// Skip empty (e.g. leading/double slash) and "." segments.
|
||||
} else if(stringEquals(segment, "..")) {
|
||||
if(segmentCount > 0) segmentCount--;
|
||||
} else {
|
||||
assertTrue(
|
||||
segmentCount < MODULE_REQUIRE_SEGMENT_MAX,
|
||||
"require() path has too many segments"
|
||||
);
|
||||
segments[segmentCount++] = segment;
|
||||
}
|
||||
}
|
||||
|
||||
size_t written = 0;
|
||||
outNormalized[0] = '\0';
|
||||
for(uint8_t i = 0; i < segmentCount; i++) {
|
||||
if(i > 0) {
|
||||
assertTrue(written + 1 < outSize, "require() path too long");
|
||||
outNormalized[written++] = '/';
|
||||
}
|
||||
|
||||
size_t segLen = 0;
|
||||
while(segments[i][segLen] != '\0') segLen++;
|
||||
assertTrue(written + segLen < outSize, "require() path too long");
|
||||
memoryCopy(outNormalized + written, segments[i], segLen);
|
||||
written += segLen;
|
||||
}
|
||||
outNormalized[written] = '\0';
|
||||
}
|
||||
|
||||
void moduleRequireDirname(
|
||||
const char_t *path,
|
||||
char_t *outDir,
|
||||
const size_t outDirSize
|
||||
) {
|
||||
assertNotNull(path, "Path cannot be NULL");
|
||||
assertNotNull(outDir, "Output buffer cannot be NULL");
|
||||
|
||||
char_t *lastSlash = stringFindLastChar(path, '/');
|
||||
if(lastSlash == NULL) {
|
||||
outDir[0] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
size_t len = (size_t)(lastSlash - path) + 1;
|
||||
assertTrue(len < outDirSize, "Directory path too long");
|
||||
memoryCopy(outDir, path, len);
|
||||
outDir[len] = '\0';
|
||||
}
|
||||
|
||||
bool_t moduleRequireCacheGet(
|
||||
const char_t *resolvedPath,
|
||||
jerry_value_t *outModuleObj
|
||||
) {
|
||||
assertNotNull(resolvedPath, "Resolved path cannot be NULL");
|
||||
assertNotNull(outModuleObj, "Output module object cannot be NULL");
|
||||
|
||||
for(uint8_t i = 0; i < MODULE_REQUIRE.cacheCount; i++) {
|
||||
if(stringEquals(MODULE_REQUIRE.cache[i].path, resolvedPath)) {
|
||||
*outModuleObj = MODULE_REQUIRE.cache[i].moduleObj;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void moduleRequireCacheSet(
|
||||
const char_t *resolvedPath,
|
||||
const jerry_value_t moduleObj
|
||||
) {
|
||||
assertNotNull(resolvedPath, "Resolved path cannot be NULL");
|
||||
assertTrue(
|
||||
MODULE_REQUIRE.cacheCount < MODULE_REQUIRE_CACHE_MAX,
|
||||
"require() module cache is full"
|
||||
);
|
||||
|
||||
modulerequirecacheentry_t *entry =
|
||||
&MODULE_REQUIRE.cache[MODULE_REQUIRE.cacheCount++];
|
||||
stringCopy(entry->path, resolvedPath, ASSET_FILE_NAME_MAX - 1);
|
||||
entry->moduleObj = moduleObj;
|
||||
}
|
||||
|
||||
void moduleRequireCacheDelete(const char_t *resolvedPath) {
|
||||
assertNotNull(resolvedPath, "Resolved path cannot be NULL");
|
||||
|
||||
for(uint8_t i = 0; i < MODULE_REQUIRE.cacheCount; i++) {
|
||||
if(!stringEquals(MODULE_REQUIRE.cache[i].path, resolvedPath)) continue;
|
||||
|
||||
MODULE_REQUIRE.cacheCount--;
|
||||
MODULE_REQUIRE.cache[i] = MODULE_REQUIRE.cache[MODULE_REQUIRE.cacheCount];
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void moduleRequireDirPush(const char_t *dir) {
|
||||
assertNotNull(dir, "Directory cannot be NULL");
|
||||
assertTrue(
|
||||
MODULE_REQUIRE.dirStackCount < MODULE_REQUIRE_STACK_MAX,
|
||||
"require() nesting too deep"
|
||||
);
|
||||
|
||||
stringCopy(
|
||||
MODULE_REQUIRE.dirStack[MODULE_REQUIRE.dirStackCount++],
|
||||
dir, ASSET_FILE_NAME_MAX - 1
|
||||
);
|
||||
}
|
||||
|
||||
void moduleRequireDirPop(void) {
|
||||
assertTrue(
|
||||
MODULE_REQUIRE.dirStackCount > 0, "require() dir stack underflow"
|
||||
);
|
||||
MODULE_REQUIRE.dirStackCount--;
|
||||
}
|
||||
|
||||
const char_t * moduleRequireDirStackTop(void) {
|
||||
if(MODULE_REQUIRE.dirStackCount == 0) return "";
|
||||
return MODULE_REQUIRE.dirStack[MODULE_REQUIRE.dirStackCount - 1];
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
/**
|
||||
* 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 "asset/assetfile.h"
|
||||
#include <jerryscript.h>
|
||||
|
||||
/** Max number of distinct files require() will keep resolved/cached. */
|
||||
#define MODULE_REQUIRE_CACHE_MAX 32
|
||||
|
||||
/** Max require() nesting depth (a file requiring a file requiring...). */
|
||||
#define MODULE_REQUIRE_STACK_MAX 16
|
||||
|
||||
/** Max path segments handled while resolving a single require() call. */
|
||||
#define MODULE_REQUIRE_SEGMENT_MAX 16
|
||||
|
||||
/** Scratch buffer size used while joining a base dir with a request. */
|
||||
#define MODULE_REQUIRE_PATH_MAX (ASSET_FILE_NAME_MAX * 2)
|
||||
|
||||
typedef struct {
|
||||
char_t path[ASSET_FILE_NAME_MAX];
|
||||
jerry_value_t moduleObj;
|
||||
} modulerequirecacheentry_t;
|
||||
|
||||
typedef struct {
|
||||
modulerequirecacheentry_t cache[MODULE_REQUIRE_CACHE_MAX];
|
||||
uint8_t cacheCount;
|
||||
|
||||
/** Directories of files currently mid-require(), innermost last. */
|
||||
char_t dirStack[MODULE_REQUIRE_STACK_MAX][ASSET_FILE_NAME_MAX];
|
||||
uint8_t dirStackCount;
|
||||
} modulerequire_t;
|
||||
|
||||
extern modulerequire_t MODULE_REQUIRE;
|
||||
|
||||
/**
|
||||
* Registers the global require() function.
|
||||
*/
|
||||
void moduleRequireInit(void);
|
||||
|
||||
/**
|
||||
* Frees every cached module object. Called once by moduleListDispose().
|
||||
*/
|
||||
void moduleRequireDispose(void);
|
||||
|
||||
/**
|
||||
* Resolves a require() argument to an asset-root-relative path. Paths
|
||||
* starting with "./" or "../" are resolved against the directory of the
|
||||
* file currently being required (or the asset root if require() is not
|
||||
* currently nested); any other path is treated as already asset-root-
|
||||
* relative. "." and ".." segments are collapsed either way.
|
||||
*
|
||||
* @param requested The raw string passed to require().
|
||||
* @param outResolved Output buffer for the resolved path.
|
||||
* @param outResolvedSize Capacity of outResolved, including the null
|
||||
* terminator.
|
||||
*/
|
||||
void moduleRequireResolvePath(
|
||||
const char_t *requested,
|
||||
char_t *outResolved,
|
||||
const size_t outResolvedSize
|
||||
);
|
||||
|
||||
/**
|
||||
* Collapses "." and ".." segments out of a slash-separated path.
|
||||
*
|
||||
* @param path The path to normalize.
|
||||
* @param outNormalized Output buffer for the normalized path.
|
||||
* @param outSize Capacity of outNormalized, including the null
|
||||
* terminator.
|
||||
*/
|
||||
void moduleRequireNormalize(
|
||||
const char_t *path,
|
||||
char_t *outNormalized,
|
||||
const size_t outSize
|
||||
);
|
||||
|
||||
/**
|
||||
* Extracts the directory portion (including trailing slash) of a
|
||||
* resolved path, or an empty string if the path has no directory.
|
||||
*
|
||||
* @param path The resolved path.
|
||||
* @param outDir Output buffer for the directory.
|
||||
* @param outDirSize Capacity of outDir, including the null terminator.
|
||||
*/
|
||||
void moduleRequireDirname(
|
||||
const char_t *path,
|
||||
char_t *outDir,
|
||||
const size_t outDirSize
|
||||
);
|
||||
|
||||
/**
|
||||
* Looks up a previously require()'d module by its resolved path.
|
||||
*
|
||||
* @param resolvedPath The resolved path to look up.
|
||||
* @param outModuleObj Receives the cached module object if found.
|
||||
* @return true if a cache entry was found.
|
||||
*/
|
||||
bool_t moduleRequireCacheGet(
|
||||
const char_t *resolvedPath,
|
||||
jerry_value_t *outModuleObj
|
||||
);
|
||||
|
||||
/**
|
||||
* Registers a module object under a resolved path. Takes ownership of
|
||||
* the passed value - it is only freed by moduleRequireDispose() or
|
||||
* moduleRequireCacheDelete().
|
||||
*
|
||||
* @param resolvedPath The resolved path to cache under.
|
||||
* @param moduleObj The module object to cache.
|
||||
*/
|
||||
void moduleRequireCacheSet(
|
||||
const char_t *resolvedPath,
|
||||
const jerry_value_t moduleObj
|
||||
);
|
||||
|
||||
/**
|
||||
* Evicts and frees a cache entry. Used to roll back a failed load so a
|
||||
* later require() of the same path can retry instead of being stuck
|
||||
* with a broken cache entry.
|
||||
*
|
||||
* @param resolvedPath The resolved path to evict.
|
||||
*/
|
||||
void moduleRequireCacheDelete(const char_t *resolvedPath);
|
||||
|
||||
/**
|
||||
* Pushes a directory onto the require() nesting stack, becoming the new
|
||||
* base for resolving relative requires until popped.
|
||||
*
|
||||
* @param dir The directory (including trailing slash, or empty) to push.
|
||||
*/
|
||||
void moduleRequireDirPush(const char_t *dir);
|
||||
|
||||
/**
|
||||
* Pops the require() nesting stack.
|
||||
*/
|
||||
void moduleRequireDirPop(void);
|
||||
|
||||
/**
|
||||
* Gets the current top of the require() nesting stack.
|
||||
*
|
||||
* @return The current base directory, or an empty string if the stack is
|
||||
* empty (i.e. require() is not currently nested).
|
||||
*/
|
||||
const char_t * moduleRequireDirStackTop(void);
|
||||
@@ -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
|
||||
moduletime.c
|
||||
)
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "moduletime.h"
|
||||
#include "script/module/modulebase.h"
|
||||
#include "time/time.h"
|
||||
|
||||
moduleBaseFunction(moduleTimeGetDelta) {
|
||||
return jerry_number(TIME.delta);
|
||||
}
|
||||
|
||||
moduleBaseFunction(moduleTimeGetTime) {
|
||||
return jerry_number(TIME.time);
|
||||
}
|
||||
|
||||
void moduleTimeInit(void) {
|
||||
jerry_value_t timeObj = jerry_object();
|
||||
moduleBaseDefineProperty(timeObj, "delta", moduleTimeGetDelta, NULL);
|
||||
moduleBaseDefineProperty(timeObj, "time", moduleTimeGetTime, NULL);
|
||||
moduleBaseSetValue("Time", timeObj);
|
||||
jerry_value_free(timeObj);
|
||||
}
|
||||
|
||||
void moduleTimeDispose(void) {
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* Registers the global `Time` object, exposing `Time.delta` and
|
||||
* `Time.time` as live getters over the engine's TIME state (see
|
||||
* time/time.h). Values always reflect the most recent timeUpdate().
|
||||
*/
|
||||
void moduleTimeInit(void);
|
||||
|
||||
/**
|
||||
* Disposes the time module's script resources.
|
||||
*/
|
||||
void moduleTimeDispose(void);
|
||||
@@ -6,12 +6,10 @@
|
||||
*/
|
||||
|
||||
#include "game/game.h"
|
||||
#include "scene/scene.h"
|
||||
#include "scene/overworldscene.h"
|
||||
#include "script/scriptmanager.h"
|
||||
|
||||
errorret_t gameInit(void) {
|
||||
sceneid_t testSceneId = overworldSceneCreate();
|
||||
sceneSetActive(testSceneId);
|
||||
errorChain(scriptManagerExecFile("scripts/overworldscene.js", NULL));
|
||||
errorOk();
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,4 @@
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
target_sources(${DUSK_LIBRARY_TARGET_NAME}
|
||||
PUBLIC
|
||||
overworldscene.c
|
||||
)
|
||||
|
||||
add_subdirectory(prefab)
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "overworldscene.h"
|
||||
#include "scene/scene.h"
|
||||
#include "entity/entitymanager.h"
|
||||
#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 "display/mesh/plane.h"
|
||||
#include "display/mesh/capsule.h"
|
||||
#include "display/color.h"
|
||||
#include "time/time.h"
|
||||
|
||||
// Radius must stay well outside the floor's footprint (a 20x20 plane has a
|
||||
// corner-to-center distance of 10*sqrt(2) =~ 14.1) -- orbiting inside that
|
||||
// puts parts of the floor's own geometry near/behind the camera's view
|
||||
// direction, which the PSP's legacy GU pipeline can't clip properly and
|
||||
// drops the whole triangle instead of clipping it.
|
||||
#define OVERWORLD_SCENE_CAMERA_ORBIT_RADIUS 18.0f
|
||||
#define OVERWORLD_SCENE_CAMERA_ORBIT_HEIGHT 10.0f
|
||||
#define OVERWORLD_SCENE_CAMERA_ORBIT_SPEED 0.5f
|
||||
|
||||
static overworldcameraorbit_t OVERWORLD_SCENE_CAMERA_ORBIT;
|
||||
|
||||
sceneid_t overworldSceneCreate(void) {
|
||||
sceneid_t sceneId = sceneCreate();
|
||||
entitymanager_t *mgr = sceneGetEntities(sceneId);
|
||||
|
||||
// Camera, orbiting the origin (see overworldSceneCameraOrbitUpdate).
|
||||
entityid_t camEntity = entityManagerAdd(mgr);
|
||||
componentid_t camPosition = entityAddComponent(
|
||||
mgr, camEntity, COMPONENT_TYPE_POSITION
|
||||
);
|
||||
entityAddComponent(mgr, camEntity, COMPONENT_TYPE_CAMERA);
|
||||
|
||||
OVERWORLD_SCENE_CAMERA_ORBIT = (overworldcameraorbit_t){
|
||||
.angle = 0.0f,
|
||||
.radius = OVERWORLD_SCENE_CAMERA_ORBIT_RADIUS,
|
||||
.height = OVERWORLD_SCENE_CAMERA_ORBIT_HEIGHT,
|
||||
.speed = OVERWORLD_SCENE_CAMERA_ORBIT_SPEED
|
||||
};
|
||||
entityUpdateAdd(
|
||||
mgr, camEntity, overworldSceneCameraOrbitUpdate, camPosition,
|
||||
&OVERWORLD_SCENE_CAMERA_ORBIT
|
||||
);
|
||||
// Position it correctly for the very first rendered frame, rather than
|
||||
// waiting for the first fixed tick to run.
|
||||
overworldSceneCameraOrbitUpdate(
|
||||
mgr, camEntity, camPosition, &OVERWORLD_SCENE_CAMERA_ORBIT
|
||||
);
|
||||
|
||||
// Static ground plane. Physics ignores the entity's position component --
|
||||
// the shape's own normal/distance fully define the plane in world space.
|
||||
entityid_t planeEntity = entityManagerAdd(mgr);
|
||||
componentid_t planePosition = entityAddComponent(
|
||||
mgr, planeEntity, COMPONENT_TYPE_POSITION
|
||||
);
|
||||
entityPositionSetLocalPosition(
|
||||
mgr, planeEntity, planePosition, (vec3){ -10.0f, 0.0f, -10.0f }
|
||||
);
|
||||
entityPositionSetLocalScale(
|
||||
mgr, planeEntity, planePosition, (vec3){ 20.0f, 1.0f, 20.0f }
|
||||
);
|
||||
componentid_t planePhysics = entityAddComponent(
|
||||
mgr, planeEntity, COMPONENT_TYPE_PHYSICS
|
||||
);
|
||||
entityPhysicsSetBodyType(
|
||||
mgr, planeEntity, planePhysics, PHYSICS_BODY_STATIC
|
||||
);
|
||||
entityPhysicsSetShape(mgr, planeEntity, planePhysics, (physicsshape_t){
|
||||
.type = PHYSICS_SHAPE_PLANE,
|
||||
.data.plane = { .normal = { 0.0f, 1.0f, 0.0f }, .distance = 0.0f }
|
||||
});
|
||||
componentid_t planeRenderable = entityAddComponent(
|
||||
mgr, planeEntity, COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
entityrenderable_t *planeR = componentGetData(
|
||||
mgr, planeEntity, planeRenderable, COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
planeR->data.material.meshes[0] = &PLANE_MESH_SIMPLE;
|
||||
entityRenderableSetColor(mgr, planeEntity, planeRenderable, COLOR_GRAY);
|
||||
|
||||
// Player: dynamic capsule body, moved relative to the camera by
|
||||
// COMPONENT_TYPE_PLAYER's own update callback (see entityplayer.c).
|
||||
entityid_t playerEntity = entityManagerAdd(mgr);
|
||||
componentid_t playerPosition = entityAddComponent(
|
||||
mgr, playerEntity, COMPONENT_TYPE_POSITION
|
||||
);
|
||||
entityPositionSetLocalPosition(
|
||||
mgr, playerEntity, playerPosition, (vec3){ 0.0f, 2.0f, 0.0f }
|
||||
);
|
||||
componentid_t playerPhysics = entityAddComponent(
|
||||
mgr, playerEntity, COMPONENT_TYPE_PHYSICS
|
||||
);
|
||||
entityPhysicsSetShape(mgr, playerEntity, playerPhysics, (physicsshape_t){
|
||||
.type = PHYSICS_SHAPE_CAPSULE,
|
||||
.data.capsule = { .radius = 0.5f, .halfHeight = 0.5f }
|
||||
});
|
||||
componentid_t playerRenderable = entityAddComponent(
|
||||
mgr, playerEntity, COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
entityrenderable_t *playerR = componentGetData(
|
||||
mgr, playerEntity, playerRenderable, COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
playerR->data.material.meshes[0] = &CAPSULE_MESH_SIMPLE;
|
||||
entityRenderableSetColor(mgr, playerEntity, playerRenderable, COLOR_BLUE);
|
||||
entityAddComponent(mgr, playerEntity, COMPONENT_TYPE_PLAYER);
|
||||
|
||||
return sceneId;
|
||||
}
|
||||
|
||||
void overworldSceneCameraOrbitUpdate(
|
||||
entitymanager_t *mgr,
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
void *user
|
||||
) {
|
||||
overworldcameraorbit_t *orbit = (overworldcameraorbit_t *)user;
|
||||
orbit->angle += TIME.delta * orbit->speed;
|
||||
|
||||
vec3 eye = {
|
||||
cosf(orbit->angle) * orbit->radius,
|
||||
orbit->height,
|
||||
sinf(orbit->angle) * orbit->radius
|
||||
};
|
||||
entityPositionLookAt(
|
||||
mgr, entityId, componentId,
|
||||
eye,
|
||||
(vec3){ 0.0f, 0.0f, 0.0f },
|
||||
(vec3){ 0.0f, 1.0f, 0.0f }
|
||||
);
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include "scene/scenebase.h"
|
||||
#include "entity/entitybase.h"
|
||||
|
||||
typedef struct {
|
||||
float_t angle;
|
||||
float_t radius;
|
||||
float_t height;
|
||||
float_t speed;
|
||||
} overworldcameraorbit_t;
|
||||
|
||||
/**
|
||||
* Creates the overworld scene: a static ground plane, a player entity
|
||||
* (dynamic capsule body, moved relative to the camera by
|
||||
* COMPONENT_TYPE_PLAYER's own update callback), and a camera orbiting the
|
||||
* origin (see overworldSceneCameraOrbitUpdate). Does not make the scene
|
||||
* active -- call sceneSetActive with the returned ID.
|
||||
*
|
||||
* @return The ID of the newly created scene.
|
||||
*/
|
||||
sceneid_t overworldSceneCreate(void);
|
||||
|
||||
/**
|
||||
* Per-tick update for the test camera: orbits it around the world origin
|
||||
* at a fixed radius/height/speed, always looking back at the origin.
|
||||
* Registered automatically by overworldSceneCreate.
|
||||
*
|
||||
* @param mgr The entity manager that owns the entity.
|
||||
* @param entityId The camera entity ID.
|
||||
* @param componentId The camera's position component ID.
|
||||
* @param user Pointer to this camera's overworldcameraorbit_t state.
|
||||
*/
|
||||
void overworldSceneCameraOrbitUpdate(
|
||||
entitymanager_t *mgr,
|
||||
const entityid_t entityId,
|
||||
const componentid_t componentId,
|
||||
void *user
|
||||
);
|
||||
@@ -14,5 +14,6 @@ add_subdirectory(entity)
|
||||
add_subdirectory(physics)
|
||||
add_subdirectory(scene)
|
||||
# add_subdirectory(item)
|
||||
add_subdirectory(script)
|
||||
add_subdirectory(time)
|
||||
add_subdirectory(util)
|
||||
@@ -0,0 +1,13 @@
|
||||
# Copyright (c) 2026 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
include(dusktest)
|
||||
|
||||
dusktest(test_modulerequire.c)
|
||||
|
||||
dusktest(test_overworldscene.c)
|
||||
target_compile_definitions(test_overworldscene PRIVATE
|
||||
DUSK_ASSETS_DIR="${DUSK_ASSETS_DIR}"
|
||||
)
|
||||
@@ -0,0 +1,228 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "dusktest.h"
|
||||
#include "asset/asset.h"
|
||||
#include "script/scriptmanager.h"
|
||||
#include "script/module/modulebase.h"
|
||||
#include "util/memory.h"
|
||||
#include <zip.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
// ============================================================
|
||||
// In-memory ZIP fixture + script manager lifecycle
|
||||
// ============================================================
|
||||
|
||||
static zip_t *g_zip = NULL;
|
||||
|
||||
static int require_setup(void **state) {
|
||||
zip_error_t err;
|
||||
zip_error_init(&err);
|
||||
|
||||
zip_source_t *write_src = zip_source_buffer_create(NULL, 0, 1, &err);
|
||||
if(!write_src) return -1;
|
||||
|
||||
zip_t *za = zip_open_from_source(write_src, ZIP_TRUNCATE, &err);
|
||||
if(!za) { zip_source_free(write_src); return -1; }
|
||||
|
||||
#define ADD_FILE(name, content) do { \
|
||||
zip_source_t *s = zip_source_buffer(za, content, strlen(content), 0); \
|
||||
if(zip_file_add(za, name, s, ZIP_FL_OVERWRITE) < 0) { \
|
||||
zip_close(za); return -1; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
ADD_FILE(
|
||||
"lib.js",
|
||||
"module.exports = { test: function() { return 'hello world'; } };"
|
||||
);
|
||||
ADD_FILE(
|
||||
"counted.js",
|
||||
"GLOBAL_COUNTER = "
|
||||
"(typeof GLOBAL_COUNTER === 'undefined' ? 0 : GLOBAL_COUNTER) + 1;\n"
|
||||
"module.exports = { count: GLOBAL_COUNTER };"
|
||||
);
|
||||
ADD_FILE(
|
||||
"dir/parent.js",
|
||||
"var child = require('./child.js');\n"
|
||||
"module.exports = { childValue: child.value };"
|
||||
);
|
||||
ADD_FILE("dir/child.js", "module.exports = { value: 42 };");
|
||||
ADD_FILE(
|
||||
"circular-a.js",
|
||||
"var b = require('./circular-b.js');\n"
|
||||
"module.exports = "
|
||||
"{ name: 'a', bName: b.name, bSawUndefinedA: b.aWasUndefined };"
|
||||
);
|
||||
ADD_FILE(
|
||||
"circular-b.js",
|
||||
"var a = require('./circular-a.js');\n"
|
||||
"module.exports = "
|
||||
"{ name: 'b', aWasUndefined: (typeof a === 'undefined') };"
|
||||
);
|
||||
|
||||
#undef ADD_FILE
|
||||
|
||||
zip_source_keep(write_src);
|
||||
if(zip_close(za) != 0) { zip_source_free(write_src); return -1; }
|
||||
|
||||
zip_stat_t zs;
|
||||
memset(&zs, 0, sizeof(zs));
|
||||
if(zip_source_stat(write_src, &zs) != 0 || !(zs.valid & ZIP_STAT_SIZE)) {
|
||||
zip_source_free(write_src); return -1;
|
||||
}
|
||||
|
||||
void *zipbuf = malloc((size_t)zs.size);
|
||||
if(!zipbuf) { zip_source_free(write_src); return -1; }
|
||||
|
||||
if(zip_source_open(write_src) != 0) {
|
||||
free(zipbuf); zip_source_free(write_src); return -1;
|
||||
}
|
||||
zip_source_read(write_src, zipbuf, (zip_uint64_t)zs.size);
|
||||
zip_source_close(write_src);
|
||||
zip_source_free(write_src);
|
||||
|
||||
zip_error_init(&err);
|
||||
zip_source_t *read_src = zip_source_buffer_create(
|
||||
zipbuf, (zip_uint64_t)zs.size, 1, &err
|
||||
);
|
||||
if(!read_src) { free(zipbuf); return -1; }
|
||||
|
||||
g_zip = zip_open_from_source(read_src, 0, &err);
|
||||
if(!g_zip) { zip_source_free(read_src); return -1; }
|
||||
|
||||
ASSET.zip = g_zip;
|
||||
|
||||
errorret_t ret = scriptManagerInit();
|
||||
if(errorIsNotOk(ret)) { errorCatch(ret); return -1; }
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int require_teardown(void **state) {
|
||||
errorret_t ret = scriptManagerDispose();
|
||||
if(errorIsNotOk(ret)) errorCatch(ret);
|
||||
|
||||
if(g_zip) { zip_close(g_zip); g_zip = NULL; }
|
||||
ASSET.zip = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Tests
|
||||
// ============================================================
|
||||
|
||||
static void test_require_basic(void **state) {
|
||||
jerry_value_t result;
|
||||
errorret_t ret = scriptManagerExec(
|
||||
"var something = require('./lib.js'); something.test();", &result
|
||||
);
|
||||
assert_true(errorIsOk(ret));
|
||||
assert_true(jerry_value_is_string(result));
|
||||
|
||||
char_t buf[64];
|
||||
moduleBaseToString(result, buf, sizeof(buf));
|
||||
assert_string_equal(buf, "hello world");
|
||||
|
||||
jerry_value_free(result);
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
}
|
||||
|
||||
static void test_require_caches_and_executes_once(void **state) {
|
||||
jerry_value_t result;
|
||||
errorret_t ret = scriptManagerExec(
|
||||
"require('./counted.js'); require('./counted.js');"
|
||||
"require('./counted.js').count;",
|
||||
&result
|
||||
);
|
||||
assert_true(errorIsOk(ret));
|
||||
assert_true(jerry_value_is_number(result));
|
||||
assert_int_equal(moduleBaseValueInt(result), 1);
|
||||
|
||||
jerry_value_free(result);
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
}
|
||||
|
||||
static void test_require_relative_path_resolves_to_requiring_files_dir(
|
||||
void **state
|
||||
) {
|
||||
jerry_value_t result;
|
||||
errorret_t ret = scriptManagerExec(
|
||||
"require('dir/parent.js').childValue;", &result
|
||||
);
|
||||
assert_true(errorIsOk(ret));
|
||||
assert_true(jerry_value_is_number(result));
|
||||
assert_int_equal(moduleBaseValueInt(result), 42);
|
||||
|
||||
jerry_value_free(result);
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
}
|
||||
|
||||
static void test_require_circular_does_not_hang(void **state) {
|
||||
jerry_value_t result;
|
||||
errorret_t ret = scriptManagerExec("require('./circular-a.js');", &result);
|
||||
assert_true(errorIsOk(ret));
|
||||
assert_true(jerry_value_is_object(result));
|
||||
|
||||
jerry_value_t nameVal = moduleBaseGetProp(result, "name");
|
||||
char_t nameBuf[16];
|
||||
moduleBaseToString(nameVal, nameBuf, sizeof(nameBuf));
|
||||
assert_string_equal(nameBuf, "a");
|
||||
jerry_value_free(nameVal);
|
||||
|
||||
jerry_value_t bNameVal = moduleBaseGetProp(result, "bName");
|
||||
char_t bNameBuf[16];
|
||||
moduleBaseToString(bNameVal, bNameBuf, sizeof(bNameBuf));
|
||||
assert_string_equal(bNameBuf, "b");
|
||||
jerry_value_free(bNameVal);
|
||||
|
||||
// circular-b.js's require('./circular-a.js') ran while circular-a.js was
|
||||
// still mid-execution (before it set module.exports), so it must have
|
||||
// seen the in-progress module's exports as still undefined.
|
||||
jerry_value_t sawVal = moduleBaseGetProp(result, "bSawUndefinedA");
|
||||
assert_true(jerry_value_is_true(sawVal));
|
||||
jerry_value_free(sawVal);
|
||||
|
||||
jerry_value_free(result);
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
}
|
||||
|
||||
static void test_require_missing_file_throws(void **state) {
|
||||
errorret_t ret = scriptManagerExec("require('./does-not-exist.js');", NULL);
|
||||
assert_true(errorIsNotOk(ret));
|
||||
errorCatch(ret);
|
||||
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// main
|
||||
// ============================================================
|
||||
|
||||
int main(void) {
|
||||
assertInit();
|
||||
const struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test_setup_teardown(
|
||||
test_require_basic, require_setup, require_teardown
|
||||
),
|
||||
cmocka_unit_test_setup_teardown(
|
||||
test_require_caches_and_executes_once, require_setup, require_teardown
|
||||
),
|
||||
cmocka_unit_test_setup_teardown(
|
||||
test_require_relative_path_resolves_to_requiring_files_dir,
|
||||
require_setup, require_teardown
|
||||
),
|
||||
cmocka_unit_test_setup_teardown(
|
||||
test_require_circular_does_not_hang, require_setup, require_teardown
|
||||
),
|
||||
cmocka_unit_test_setup_teardown(
|
||||
test_require_missing_file_throws, require_setup, require_teardown
|
||||
),
|
||||
};
|
||||
return cmocka_run_group_tests(tests, NULL, NULL);
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
/**
|
||||
* Copyright (c) 2026 Dominic Masters
|
||||
*
|
||||
* This software is released under the MIT License.
|
||||
* https://opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
#include "dusktest.h"
|
||||
#include "util/memory.h"
|
||||
#include "time/time.h"
|
||||
#include "scene/scene.h"
|
||||
#include "entity/entitymanager.h"
|
||||
#include "entity/component.h"
|
||||
#include "entity/component/display/entityposition.h"
|
||||
#include "entity/component/physics/entityphysics.h"
|
||||
#include "entity/component/display/entityrenderable.h"
|
||||
#include "display/mesh/plane.h"
|
||||
#include "display/mesh/capsule.h"
|
||||
#include "script/scriptmanager.h"
|
||||
#include <math.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifndef DUSK_ASSETS_DIR
|
||||
#error "DUSK_ASSETS_DIR must be defined"
|
||||
#endif
|
||||
|
||||
// Reads the real, shipped overworldscene.js from disk (not a copy embedded
|
||||
// in this test) so this test actually verifies what ships.
|
||||
static char_t *readScriptSource(void) {
|
||||
char_t path[512];
|
||||
snprintf(
|
||||
path, sizeof(path), "%s/scripts/overworldscene.js", DUSK_ASSETS_DIR
|
||||
);
|
||||
|
||||
FILE *f = fopen(path, "rb");
|
||||
assert_non_null(f);
|
||||
|
||||
fseek(f, 0, SEEK_END);
|
||||
long size = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
|
||||
char_t *buf = (char_t *)memoryAllocate((size_t)size + 1);
|
||||
size_t read = fread(buf, 1, (size_t)size, f);
|
||||
fclose(f);
|
||||
buf[read] = '\0';
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static int overworld_setup(void **state) {
|
||||
sceneInit();
|
||||
|
||||
errorret_t ret = scriptManagerInit();
|
||||
if(errorIsNotOk(ret)) { errorCatch(ret); return -1; }
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int overworld_teardown(void **state) {
|
||||
errorret_t ret = scriptManagerDispose();
|
||||
if(errorIsNotOk(ret)) errorCatch(ret);
|
||||
|
||||
sceneDispose();
|
||||
|
||||
// JerryScript defers freeing native-wrapped handles (Entity/Scene/
|
||||
// Position/Physics/Renderable instances) until GC/jerry_cleanup() runs,
|
||||
// so the leak check can only be meaningful after scriptManagerDispose()
|
||||
// has actually run above -- not inside the test body.
|
||||
assert_int_equal(memoryGetAllocatedCount(), 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void test_overworldscene_builds_expected_entities(void **state) {
|
||||
char_t *src = readScriptSource();
|
||||
errorret_t ret = scriptManagerExec(src, NULL);
|
||||
memoryFree(src);
|
||||
assert_true(errorIsOk(ret));
|
||||
|
||||
sceneid_t sceneId = sceneGetActive();
|
||||
assert_true(sceneId != SCENE_ID_INVALID);
|
||||
entitymanager_t *mgr = sceneGetEntities(sceneId);
|
||||
|
||||
// Entity 0: camera. Position + Camera components, initial orbit
|
||||
// position placed with angle=0 (Time.delta defaults to 0 with no
|
||||
// timeInit()/timeUpdate() in this test), i.e. eye=(radius, height, 0).
|
||||
entityid_t camEntity = 0;
|
||||
componentid_t camPos = entityGetComponent(
|
||||
mgr, camEntity, COMPONENT_TYPE_POSITION
|
||||
);
|
||||
assert_true(camPos != COMPONENT_ID_INVALID);
|
||||
assert_true(
|
||||
entityGetComponent(mgr, camEntity, COMPONENT_TYPE_CAMERA) !=
|
||||
COMPONENT_ID_INVALID
|
||||
);
|
||||
|
||||
vec3 camPosition;
|
||||
entityPositionGetLocalPosition(mgr, camEntity, camPos, camPosition);
|
||||
assert_float_equal(camPosition[0], 18.0f, 0.0001f);
|
||||
assert_float_equal(camPosition[1], 10.0f, 0.0001f);
|
||||
assert_float_equal(camPosition[2], 0.0f, 0.0001f);
|
||||
|
||||
// Entity 1: static ground plane.
|
||||
entityid_t planeEntity = 1;
|
||||
componentid_t planePos = entityGetComponent(
|
||||
mgr, planeEntity, COMPONENT_TYPE_POSITION
|
||||
);
|
||||
assert_true(planePos != COMPONENT_ID_INVALID);
|
||||
|
||||
vec3 planePosition, planeScale;
|
||||
entityPositionGetLocalPosition(mgr, planeEntity, planePos, planePosition);
|
||||
entityPositionGetLocalScale(mgr, planeEntity, planePos, planeScale);
|
||||
assert_float_equal(planePosition[0], -10.0f, 0.0001f);
|
||||
assert_float_equal(planePosition[1], 0.0f, 0.0001f);
|
||||
assert_float_equal(planePosition[2], -10.0f, 0.0001f);
|
||||
assert_float_equal(planeScale[0], 20.0f, 0.0001f);
|
||||
assert_float_equal(planeScale[1], 1.0f, 0.0001f);
|
||||
assert_float_equal(planeScale[2], 20.0f, 0.0001f);
|
||||
|
||||
componentid_t planePhysics = entityGetComponent(
|
||||
mgr, planeEntity, COMPONENT_TYPE_PHYSICS
|
||||
);
|
||||
assert_true(planePhysics != COMPONENT_ID_INVALID);
|
||||
assert_int_equal(
|
||||
entityPhysicsGetBodyType(mgr, planeEntity, planePhysics),
|
||||
PHYSICS_BODY_STATIC
|
||||
);
|
||||
physicsshape_t planeShape = entityPhysicsGetShape(
|
||||
mgr, planeEntity, planePhysics
|
||||
);
|
||||
assert_int_equal(planeShape.type, PHYSICS_SHAPE_PLANE);
|
||||
assert_float_equal(planeShape.data.plane.normal[0], 0.0f, 0.0001f);
|
||||
assert_float_equal(planeShape.data.plane.normal[1], 1.0f, 0.0001f);
|
||||
assert_float_equal(planeShape.data.plane.normal[2], 0.0f, 0.0001f);
|
||||
assert_float_equal(planeShape.data.plane.distance, 0.0f, 0.0001f);
|
||||
|
||||
componentid_t planeRenderable = entityGetComponent(
|
||||
mgr, planeEntity, COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
assert_true(planeRenderable != COMPONENT_ID_INVALID);
|
||||
entityrenderable_t *planeR = componentGetData(
|
||||
mgr, planeEntity, planeRenderable, COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
assert_ptr_equal(planeR->data.material.meshes[0], &PLANE_MESH_SIMPLE);
|
||||
assert_int_equal(planeR->data.material.material.unlit.color.r, 128);
|
||||
assert_int_equal(planeR->data.material.material.unlit.color.g, 128);
|
||||
assert_int_equal(planeR->data.material.material.unlit.color.b, 128);
|
||||
assert_int_equal(planeR->data.material.material.unlit.color.a, 255);
|
||||
|
||||
// Entity 2: player. Dynamic capsule body (default body type), PLAYER
|
||||
// component present.
|
||||
entityid_t playerEntity = 2;
|
||||
componentid_t playerPos = entityGetComponent(
|
||||
mgr, playerEntity, COMPONENT_TYPE_POSITION
|
||||
);
|
||||
assert_true(playerPos != COMPONENT_ID_INVALID);
|
||||
|
||||
vec3 playerPosition;
|
||||
entityPositionGetLocalPosition(mgr, playerEntity, playerPos, playerPosition);
|
||||
assert_float_equal(playerPosition[0], 0.0f, 0.0001f);
|
||||
assert_float_equal(playerPosition[1], 2.0f, 0.0001f);
|
||||
assert_float_equal(playerPosition[2], 0.0f, 0.0001f);
|
||||
|
||||
componentid_t playerPhysics = entityGetComponent(
|
||||
mgr, playerEntity, COMPONENT_TYPE_PHYSICS
|
||||
);
|
||||
assert_true(playerPhysics != COMPONENT_ID_INVALID);
|
||||
assert_int_equal(
|
||||
entityPhysicsGetBodyType(mgr, playerEntity, playerPhysics),
|
||||
PHYSICS_BODY_DYNAMIC
|
||||
);
|
||||
physicsshape_t playerShape = entityPhysicsGetShape(
|
||||
mgr, playerEntity, playerPhysics
|
||||
);
|
||||
assert_int_equal(playerShape.type, PHYSICS_SHAPE_CAPSULE);
|
||||
assert_float_equal(playerShape.data.capsule.radius, 0.5f, 0.0001f);
|
||||
assert_float_equal(playerShape.data.capsule.halfHeight, 0.5f, 0.0001f);
|
||||
|
||||
componentid_t playerRenderable = entityGetComponent(
|
||||
mgr, playerEntity, COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
assert_true(playerRenderable != COMPONENT_ID_INVALID);
|
||||
entityrenderable_t *playerR = componentGetData(
|
||||
mgr, playerEntity, playerRenderable, COMPONENT_TYPE_RENDERABLE
|
||||
);
|
||||
assert_ptr_equal(playerR->data.material.meshes[0], &CAPSULE_MESH_SIMPLE);
|
||||
assert_int_equal(playerR->data.material.material.unlit.color.r, 0);
|
||||
assert_int_equal(playerR->data.material.material.unlit.color.g, 0);
|
||||
assert_int_equal(playerR->data.material.material.unlit.color.b, 255);
|
||||
assert_int_equal(playerR->data.material.material.unlit.color.a, 255);
|
||||
|
||||
assert_true(
|
||||
entityGetComponent(mgr, playerEntity, COMPONENT_TYPE_PLAYER) !=
|
||||
COMPONENT_ID_INVALID
|
||||
);
|
||||
}
|
||||
|
||||
static void test_overworldscene_update_orbits_camera(void **state) {
|
||||
char_t *src = readScriptSource();
|
||||
errorret_t ret = scriptManagerExec(src, NULL);
|
||||
memoryFree(src);
|
||||
assert_true(errorIsOk(ret));
|
||||
|
||||
sceneid_t sceneId = sceneGetActive();
|
||||
entitymanager_t *mgr = sceneGetEntities(sceneId);
|
||||
entityid_t camEntity = 0;
|
||||
componentid_t camPos = entityGetComponent(
|
||||
mgr, camEntity, COMPONENT_TYPE_POSITION
|
||||
);
|
||||
|
||||
// Drive one frame with a known delta and confirm the camera orbited by
|
||||
// exactly angle = delta * speed (0.1 * 0.5 = 0.05 rad).
|
||||
TIME.delta = 0.1f;
|
||||
ret = scriptManagerCallGlobal("update");
|
||||
assert_true(errorIsOk(ret));
|
||||
|
||||
vec3 camPosition;
|
||||
entityPositionGetLocalPosition(mgr, camEntity, camPos, camPosition);
|
||||
const float_t expectedAngle = 0.1f * 0.5f;
|
||||
assert_float_equal(
|
||||
camPosition[0], cosf(expectedAngle) * 18.0f, 0.0001f
|
||||
);
|
||||
assert_float_equal(camPosition[1], 10.0f, 0.0001f);
|
||||
assert_float_equal(
|
||||
camPosition[2], sinf(expectedAngle) * 18.0f, 0.0001f
|
||||
);
|
||||
|
||||
TIME.delta = 0.0f;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
assertInit();
|
||||
const struct CMUnitTest tests[] = {
|
||||
cmocka_unit_test_setup_teardown(
|
||||
test_overworldscene_builds_expected_entities,
|
||||
overworld_setup, overworld_teardown
|
||||
),
|
||||
cmocka_unit_test_setup_teardown(
|
||||
test_overworldscene_update_orbits_camera,
|
||||
overworld_setup, overworld_teardown
|
||||
),
|
||||
};
|
||||
return cmocka_run_group_tests(tests, NULL, NULL);
|
||||
}
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) 2026 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
/**
|
||||
* Built-in primitive meshes, usable with Renderable.setMesh(). Opaque
|
||||
* engine-owned handles -- do not inspect or mutate.
|
||||
*/
|
||||
declare const MESH_CUBE: object;
|
||||
declare const MESH_PLANE: object;
|
||||
declare const MESH_SPHERE: object;
|
||||
declare const MESH_CAPSULE: object;
|
||||
declare const MESH_TRIPRISM: object;
|
||||
declare const MESH_QUAD: object;
|
||||
+7
-5
@@ -4,11 +4,13 @@
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
/**
|
||||
* A component attached to an Entity. Generic across every component
|
||||
* type for now -- there are no typed per-type properties yet (e.g. no
|
||||
* `.position` on a POSITION component), just the operations every
|
||||
* component supports regardless of type. Only ever obtained via
|
||||
* Entity.add()/Entity.getComponent(), never constructed directly.
|
||||
* A component attached to an Entity: the operations every component
|
||||
* supports regardless of type. Entity.add()/Entity.getComponent() return
|
||||
* a typed subclass for component types that have one -- see position.d.ts
|
||||
* (POSITION), physics.d.ts (PHYSICS), and renderable.d.ts (RENDERABLE).
|
||||
* Every other component type (CAMERA, TRIGGER, ANIMATION, PLAYER,
|
||||
* INTERACTABLE) still only returns this generic Component. Never
|
||||
* constructed directly.
|
||||
*/
|
||||
declare class Component {
|
||||
/** The componenttype_t this component was added as, e.g. POSITION. */
|
||||
|
||||
Vendored
+54
@@ -0,0 +1,54 @@
|
||||
// Copyright (c) 2026 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
/// <reference path="component.d.ts" />
|
||||
|
||||
/** Physics body type constants, usable with Physics.setBodyType(). */
|
||||
declare const PHYSICS_BODY_STATIC: number;
|
||||
declare const PHYSICS_BODY_DYNAMIC: number;
|
||||
declare const PHYSICS_BODY_KINEMATIC: number;
|
||||
|
||||
/**
|
||||
* Physics shape type constants, usable with Physics.setShape(). Argument
|
||||
* shape per type:
|
||||
* - PHYSICS_SHAPE_CUBE: (type, halfExtentX, halfExtentY, halfExtentZ)
|
||||
* - PHYSICS_SHAPE_SPHERE: (type, radius)
|
||||
* - PHYSICS_SHAPE_CAPSULE: (type, radius, halfHeight)
|
||||
* - PHYSICS_SHAPE_PLANE: (type, normalX, normalY, normalZ, distance)
|
||||
*/
|
||||
declare const PHYSICS_SHAPE_CUBE: number;
|
||||
declare const PHYSICS_SHAPE_SPHERE: number;
|
||||
declare const PHYSICS_SHAPE_CAPSULE: number;
|
||||
declare const PHYSICS_SHAPE_PLANE: number;
|
||||
|
||||
/**
|
||||
* Typed wrapper for a PHYSICS component. Returned by Entity.add(PHYSICS)
|
||||
* / Entity.getComponent(PHYSICS) instead of the generic Component.
|
||||
*/
|
||||
declare class Physics extends Component {
|
||||
/** Sets the body type (see PHYSICS_BODY_* constants). */
|
||||
setBodyType(type: number): void;
|
||||
|
||||
/** Gets the body type. */
|
||||
getBodyType(): number;
|
||||
|
||||
/**
|
||||
* Sets the collision shape. See PHYSICS_SHAPE_* constants for the
|
||||
* expected trailing arguments per shape type.
|
||||
*/
|
||||
setShape(type: number, ...args: number[]): void;
|
||||
|
||||
/** Sets the body's velocity (not an impulse -- affected by mass/drag). */
|
||||
setVelocity(x: number, y: number, z: number): void;
|
||||
|
||||
/** Gets the body's velocity. */
|
||||
getVelocity(): { x: number; y: number; z: number };
|
||||
|
||||
/** Applies an immediate velocity change, unaffected by mass/drag. */
|
||||
applyImpulse(x: number, y: number, z: number): void;
|
||||
|
||||
/** True if the body rested on a surface during the last physics step. */
|
||||
isOnGround(): boolean;
|
||||
}
|
||||
Vendored
+34
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) 2026 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
/// <reference path="component.d.ts" />
|
||||
|
||||
/**
|
||||
* Typed wrapper for a POSITION component. Returned by Entity.add(POSITION)
|
||||
* / Entity.getComponent(POSITION) instead of the generic Component.
|
||||
*/
|
||||
declare class Position extends Component {
|
||||
/** Sets the local position (not world position -- see entityposition.h). */
|
||||
setLocalPosition(x: number, y: number, z: number): void;
|
||||
|
||||
/** Gets the cached local position. */
|
||||
getLocalPosition(): { x: number; y: number; z: number };
|
||||
|
||||
/** Sets the local scale. */
|
||||
setLocalScale(x: number, y: number, z: number): void;
|
||||
|
||||
/** Gets the cached local scale. */
|
||||
getLocalScale(): { x: number; y: number; z: number };
|
||||
|
||||
/**
|
||||
* Positions and orients the entity at (ex, ey, ez), facing
|
||||
* (tx, ty, tz), with (ux, uy, uz) as up.
|
||||
*/
|
||||
lookAt(
|
||||
ex: number, ey: number, ez: number,
|
||||
tx: number, ty: number, tz: number,
|
||||
ux: number, uy: number, uz: number
|
||||
): void;
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
// Copyright (c) 2026 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
/// <reference path="component.d.ts" />
|
||||
|
||||
/**
|
||||
* Typed wrapper for a RENDERABLE component. Returned by
|
||||
* Entity.add(RENDERABLE) / Entity.getComponent(RENDERABLE) instead of
|
||||
* the generic Component. Only meaningful for the default shader-material
|
||||
* renderable type.
|
||||
*/
|
||||
declare class Renderable extends Component {
|
||||
/** Sets the unlit material color (each channel 0-255). */
|
||||
setColor(r: number, g: number, b: number, a: number): void;
|
||||
|
||||
/**
|
||||
* Sets one of the material's mesh slots. mesh must be one of the
|
||||
* built-in MESH_* constants (see mesh.d.ts).
|
||||
*/
|
||||
setMesh(slot: number, mesh: object): void;
|
||||
|
||||
/** Sets the render priority. 0 = auto (derived from type/flags). */
|
||||
setPriority(priority: number): void;
|
||||
}
|
||||
Vendored
+6
@@ -3,6 +3,12 @@
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
/// <reference path="require.d.ts" />
|
||||
/// <reference path="entity/entity.d.ts" />
|
||||
/// <reference path="entity/component/component.d.ts" />
|
||||
/// <reference path="entity/component/position.d.ts" />
|
||||
/// <reference path="entity/component/physics.d.ts" />
|
||||
/// <reference path="entity/component/renderable.d.ts" />
|
||||
/// <reference path="scene/scene.d.ts" />
|
||||
/// <reference path="display/mesh.d.ts" />
|
||||
/// <reference path="time/time.d.ts" />
|
||||
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) 2026 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
/**
|
||||
* Loads and executes another script file (relative to the asset root,
|
||||
* or to the requiring file's own directory if path starts with "./" or
|
||||
* "../"), returning its `module.exports`. Each resolved path is only
|
||||
* executed once - subsequent require() calls for the same file return
|
||||
* the cached exports.
|
||||
*/
|
||||
declare function require(path: string): any;
|
||||
|
||||
/**
|
||||
* Only defined inside a file loaded via require() - not present in a
|
||||
* top-level script executed directly. Set `module.exports` to whatever
|
||||
* value require() of this file should return.
|
||||
*/
|
||||
declare const module: { exports: any };
|
||||
Vendored
+15
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) 2026 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
/**
|
||||
* Live view over the engine's fixed-timestep clock (see time/time.h).
|
||||
* `delta`/`time` always reflect the most recent timeUpdate() call.
|
||||
*/
|
||||
declare const Time: {
|
||||
/** Fixed simulation timestep, in seconds (always DUSK_TIME_STEP). */
|
||||
readonly delta: number;
|
||||
/** Total elapsed fixed-simulation time, in seconds. */
|
||||
readonly time: number;
|
||||
};
|
||||
Reference in New Issue
Block a user