From d73edb403f781d68bf713657f0dc8455ac33e5b7 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Mon, 1 Jun 2026 23:04:55 -0500 Subject: [PATCH] Example Camera --- assets/testentity.js | 12 +- src/dusk/scene/initial/initialscene.c | 26 ++- src/dusk/scene/initial/initialscene.h | 6 +- .../entity/component/camera/modulecamera.h | 174 ++++++++++++++++++ .../entity/component/modulecomponentlist.h | 5 + .../component/position/moduleposition.h | 23 +++ src/dusk/script/module/scene/modulescene.h | 35 ++++ types/entity.d.ts | 129 +++++++++++++ types/index.d.ts | 3 + types/scene.d.ts | 30 +++ types/vec3.d.ts | 15 ++ 11 files changed, 436 insertions(+), 22 deletions(-) create mode 100644 src/dusk/script/module/entity/component/camera/modulecamera.h create mode 100644 types/entity.d.ts create mode 100644 types/scene.d.ts create mode 100644 types/vec3.d.ts diff --git a/assets/testentity.js b/assets/testentity.js index 273cd11e..8d3623bb 100644 --- a/assets/testentity.js +++ b/assets/testentity.js @@ -1,4 +1,8 @@ -const e = Entity.create(); -const pos = e.add(Component.POSITION); -pos.localPosition = new Vec3(-1, 0, 1); -Console.print('Entity ID: ' + e.toString()); +const cam = Entity.create(); +const pos = cam.add(Component.POSITION); +cam.add(Component.CAMERA); + +pos.localPosition = new Vec3(3, 3, 3); +pos.lookAt(new Vec3(0, 0, 0)); + +Console.print('Camera entity ID: ' + cam.toString()); diff --git a/src/dusk/scene/initial/initialscene.c b/src/dusk/scene/initial/initialscene.c index ce7575d5..7c70abdd 100644 --- a/src/dusk/scene/initial/initialscene.c +++ b/src/dusk/scene/initial/initialscene.c @@ -9,29 +9,27 @@ #include "console/console.h" #include "scene/scene.h" #include "script/script.h" -#include "time/time.h" -#include "ui/uiloading.h" +#include "entity/entitymanager.h" +#include "entity/entity.h" +#include "entity/component/display/entityposition.h" +#include "entity/component/display/entityrenderable.h" void initialSceneInit(void) { consolePrint("Initial scene initialized"); + + // Cube entity — RENDERABLE init defaults to a white unit cube at origin + entityid_t cubeId = entityManagerAdd(); + SCENE.data.initial.cubeEntityId = cubeId; + entityAddComponent(cubeId, COMPONENT_TYPE_POSITION); + entityAddComponent(cubeId, COMPONENT_TYPE_RENDERABLE); + errorCatch(errorPrint(scriptExecFile("testentity.js"))); - // SCENE.data.initial.timer = 0.0f; - // SCENE.data.initial.hiding = false; - // uiLoadingShow(NULL, NULL); } errorret_t initialSceneUpdate(void) { - // initialscene_t *scene = &SCENE.data.initial; - // if(scene->hiding) errorOk(); - - // scene->timer += TIME.delta; - // if(scene->timer >= INITIAL_SCENE_WAIT) { - // scene->hiding = true; - // uiLoadingHide(NULL, NULL); - // } - errorOk(); } void initialSceneDispose(void) { + entityDispose(SCENE.data.initial.cubeEntityId); } diff --git a/src/dusk/scene/initial/initialscene.h b/src/dusk/scene/initial/initialscene.h index 966eec4d..fb3d255d 100644 --- a/src/dusk/scene/initial/initialscene.h +++ b/src/dusk/scene/initial/initialscene.h @@ -7,12 +7,10 @@ #pragma once #include "error/error.h" - -#define INITIAL_SCENE_WAIT 2.0f +#include "entity/entitybase.h" typedef struct { - float_t timer; - bool_t hiding; + entityid_t cubeEntityId; } initialscene_t; void initialSceneInit(void); diff --git a/src/dusk/script/module/entity/component/camera/modulecamera.h b/src/dusk/script/module/entity/component/camera/modulecamera.h new file mode 100644 index 00000000..d12f0dc6 --- /dev/null +++ b/src/dusk/script/module/entity/component/camera/modulecamera.h @@ -0,0 +1,174 @@ +/** + * 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/entity/modulecomponent.h" +#include "entity/component/display/entitycamera.h" + +static scriptproto_t MODULE_CAMERA_PROTO; + +moduleBaseFunction(moduleCameraCtor) { + (void)callInfo; (void)args; (void)argc; + return moduleBaseThrow("Camera cannot be instantiated with new"); +} + +static inline jscomponent_t *moduleCameraSelf( + const jerry_call_info_t *callInfo +) { + return (jscomponent_t *)scriptProtoGetValue( + &MODULE_CAMERA_PROTO, callInfo->this_value + ); +} + +static inline entitycamera_t *moduleCameraData(const jscomponent_t *c) { + return (entitycamera_t *)componentGetData( + c->entityId, c->componentId, COMPONENT_TYPE_CAMERA + ); +} + +moduleBaseFunction(moduleCameraGetEntity) { + jscomponent_t *c = moduleCameraSelf(callInfo); + if(!c) return jerry_undefined(); + return jerry_number((double)c->entityId); +} + +moduleBaseFunction(moduleCameraGetId) { + jscomponent_t *c = moduleCameraSelf(callInfo); + if(!c) return jerry_undefined(); + return jerry_number((double)c->componentId); +} + +moduleBaseFunction(moduleCameraGetFov) { + jscomponent_t *c = moduleCameraSelf(callInfo); + if(!c) return jerry_undefined(); + entitycamera_t *cam = moduleCameraData(c); + if(!cam) return jerry_undefined(); + return jerry_number((double)cam->perspective.fov); +} + +moduleBaseFunction(moduleCameraSetFov) { + moduleBaseRequireArgs(1); + jscomponent_t *c = moduleCameraSelf(callInfo); + if(!c) return jerry_undefined(); + entitycamera_t *cam = moduleCameraData(c); + if(!cam) return jerry_undefined(); + cam->perspective.fov = moduleBaseArgFloat(0); + return jerry_undefined(); +} + +moduleBaseFunction(moduleCameraGetNearClip) { + jscomponent_t *c = moduleCameraSelf(callInfo); + if(!c) return jerry_undefined(); + entitycamera_t *cam = moduleCameraData(c); + if(!cam) return jerry_undefined(); + return jerry_number((double)cam->nearClip); +} + +moduleBaseFunction(moduleCameraSetNearClip) { + moduleBaseRequireArgs(1); + jscomponent_t *c = moduleCameraSelf(callInfo); + if(!c) return jerry_undefined(); + entitycamera_t *cam = moduleCameraData(c); + if(!cam) return jerry_undefined(); + cam->nearClip = moduleBaseArgFloat(0); + return jerry_undefined(); +} + +moduleBaseFunction(moduleCameraGetFarClip) { + jscomponent_t *c = moduleCameraSelf(callInfo); + if(!c) return jerry_undefined(); + entitycamera_t *cam = moduleCameraData(c); + if(!cam) return jerry_undefined(); + return jerry_number((double)cam->farClip); +} + +moduleBaseFunction(moduleCameraSetFarClip) { + moduleBaseRequireArgs(1); + jscomponent_t *c = moduleCameraSelf(callInfo); + if(!c) return jerry_undefined(); + entitycamera_t *cam = moduleCameraData(c); + if(!cam) return jerry_undefined(); + cam->farClip = moduleBaseArgFloat(0); + return jerry_undefined(); +} + +moduleBaseFunction(moduleCameraGetProjType) { + jscomponent_t *c = moduleCameraSelf(callInfo); + if(!c) return jerry_undefined(); + entitycamera_t *cam = moduleCameraData(c); + if(!cam) return jerry_undefined(); + return jerry_number((double)cam->projType); +} + +moduleBaseFunction(moduleCameraSetProjType) { + moduleBaseRequireArgs(1); + jscomponent_t *c = moduleCameraSelf(callInfo); + if(!c) return jerry_undefined(); + entitycamera_t *cam = moduleCameraData(c); + if(!cam) return jerry_undefined(); + cam->projType = (entitycameraprojectiontype_t)moduleBaseArgInt(0); + return jerry_undefined(); +} + +moduleBaseFunction(moduleCameraToString) { + jscomponent_t *c = moduleCameraSelf(callInfo); + if(!c) return jerry_string_sz("Camera:invalid"); + char_t buf[32]; + snprintf(buf, sizeof(buf), "Camera(%u)", (unsigned)c->componentId); + return jerry_string_sz(buf); +} + +static void moduleCameraInit(void) { + scriptProtoInit( + &MODULE_CAMERA_PROTO, "Camera", + sizeof(jscomponent_t), moduleCameraCtor + ); + + scriptProtoDefineProp( + &MODULE_CAMERA_PROTO, "entity", moduleCameraGetEntity, NULL + ); + scriptProtoDefineProp( + &MODULE_CAMERA_PROTO, "id", moduleCameraGetId, NULL + ); + scriptProtoDefineProp( + &MODULE_CAMERA_PROTO, "fov", moduleCameraGetFov, moduleCameraSetFov + ); + scriptProtoDefineProp( + &MODULE_CAMERA_PROTO, "nearClip", + moduleCameraGetNearClip, moduleCameraSetNearClip + ); + scriptProtoDefineProp( + &MODULE_CAMERA_PROTO, "farClip", + moduleCameraGetFarClip, moduleCameraSetFarClip + ); + scriptProtoDefineProp( + &MODULE_CAMERA_PROTO, "projType", + moduleCameraGetProjType, moduleCameraSetProjType + ); + scriptProtoDefineToString(&MODULE_CAMERA_PROTO, moduleCameraToString); + + /* Camera.PERSPECTIVE, Camera.PERSPECTIVE_FLIPPED, Camera.ORTHOGRAPHIC */ + jerry_value_t ctor = MODULE_CAMERA_PROTO.constructor; + struct { const char_t *name; int val; } projtypes[] = { + { "PERSPECTIVE", ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE }, + { "PERSPECTIVE_FLIPPED", ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED }, + { "ORTHOGRAPHIC", ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC }, + }; + for(int i = 0; i < 3; i++) { + jerry_value_t k = jerry_string_sz(projtypes[i].name); + jerry_value_t v = jerry_number((double)projtypes[i].val); + jerry_object_set(ctor, k, v); + jerry_value_free(v); + jerry_value_free(k); + } +} + +static void moduleCameraDispose(void) { + scriptProtoDispose(&MODULE_CAMERA_PROTO); +} diff --git a/src/dusk/script/module/entity/component/modulecomponentlist.h b/src/dusk/script/module/entity/component/modulecomponentlist.h index 02182b7e..463bfdd8 100644 --- a/src/dusk/script/module/entity/component/modulecomponentlist.h +++ b/src/dusk/script/module/entity/component/modulecomponentlist.h @@ -7,6 +7,7 @@ #pragma once #include "script/module/entity/modulecomponent.h" +#include "camera/modulecamera.h" #include "position/moduleposition.h" /** @@ -20,6 +21,8 @@ static jerry_value_t moduleComponentListCreateInstance( switch(type) { case COMPONENT_TYPE_POSITION: return scriptProtoCreateValue(&MODULE_POSITION_PROTO, comp); + case COMPONENT_TYPE_CAMERA: + return scriptProtoCreateValue(&MODULE_CAMERA_PROTO, comp); default: return scriptProtoCreateValue(&MODULE_COMPONENT_PROTO, comp); } @@ -27,8 +30,10 @@ static jerry_value_t moduleComponentListCreateInstance( static void moduleComponentListInit(void) { modulePositionInit(); + moduleCameraInit(); } static void moduleComponentListDispose(void) { + moduleCameraDispose(); modulePositionDispose(); } diff --git a/src/dusk/script/module/entity/component/position/moduleposition.h b/src/dusk/script/module/entity/component/position/moduleposition.h index 85723dc9..db9a89b3 100644 --- a/src/dusk/script/module/entity/component/position/moduleposition.h +++ b/src/dusk/script/module/entity/component/position/moduleposition.h @@ -147,6 +147,26 @@ moduleBaseFunction(modulePositionSetWorldScale) { return jerry_undefined(); } +moduleBaseFunction(modulePositionLookAt) { + jscomponent_t *c = modulePositionSelf(callInfo); + if(!c) return jerry_undefined(); + moduleBaseRequireArgs(1); + float_t *target = moduleVec3From(args[0]); + if(!target) return moduleBaseThrow("Position.lookAt: expected Vec3 target"); + + vec3 eye; + entityPositionGetLocalPosition(c->entityId, c->componentId, eye); + + vec3 up = { 0.0f, 1.0f, 0.0f }; + if(argc >= 2) { + float_t *upArg = moduleVec3From(args[1]); + if(upArg) glm_vec3_copy(upArg, up); + } + + entityPositionLookAt(c->entityId, c->componentId, eye, target, up); + return jerry_undefined(); +} + moduleBaseFunction(modulePositionSetParent) { jscomponent_t *c = modulePositionSelf(callInfo); if(!c) return jerry_undefined(); @@ -214,6 +234,9 @@ static void modulePositionInit(void) { &MODULE_POSITION_PROTO, "worldScale", modulePositionGetWorldScale, modulePositionSetWorldScale ); + scriptProtoDefineFunc( + &MODULE_POSITION_PROTO, "lookAt", modulePositionLookAt + ); scriptProtoDefineFunc( &MODULE_POSITION_PROTO, "setParent", modulePositionSetParent ); diff --git a/src/dusk/script/module/scene/modulescene.h b/src/dusk/script/module/scene/modulescene.h index 4ea309cd..20f576ce 100644 --- a/src/dusk/script/module/scene/modulescene.h +++ b/src/dusk/script/module/scene/modulescene.h @@ -12,8 +12,43 @@ static scriptproto_t MODULE_SCENE_PROTO; +moduleBaseFunction(moduleSceneGetCurrent) { + return jerry_number((double)SCENE.type); +} + +moduleBaseFunction(moduleSceneSet) { + moduleBaseRequireArgs(1); + moduleBaseRequireNumber(0); + const scenetype_t type = (scenetype_t)moduleBaseArgInt(0); + if(type <= SCENE_TYPE_NULL || type >= SCENE_TYPE_COUNT) { + return moduleBaseThrow("Scene.set: invalid scene type"); + } + sceneSet(type); + return jerry_undefined(); +} + static void moduleSceneInit(void) { scriptProtoInit(&MODULE_SCENE_PROTO, "Scene", sizeof(uint8_t), NULL); + + scriptProtoDefineStaticProp( + &MODULE_SCENE_PROTO, "current", moduleSceneGetCurrent, NULL + ); + scriptProtoDefineStaticFunc( + &MODULE_SCENE_PROTO, "set", moduleSceneSet + ); + + /* Scene.INITIAL, Scene.TEST, Scene.OVERWORLD, ... */ + jerry_value_t global = MODULE_SCENE_PROTO.prototype; + #define X(structName, varName, varNameUpper, initFunc, updateFunc, disposeFunc) \ + do { \ + jerry_value_t _key = jerry_string_sz(#varNameUpper); \ + jerry_value_t _val = jerry_number((double)SCENE_TYPE_##varNameUpper); \ + jerry_object_set(global, _key, _val); \ + jerry_value_free(_val); \ + jerry_value_free(_key); \ + } while(0); + #include "scene/scenelist.h" + #undef X } static void moduleSceneDispose(void) { diff --git a/types/entity.d.ts b/types/entity.d.ts new file mode 100644 index 00000000..dbb72ae0 --- /dev/null +++ b/types/entity.d.ts @@ -0,0 +1,129 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +// --------------------------------------------------------------------------- +// Component (generic base returned by entity.add() for untyped components) +// --------------------------------------------------------------------------- + +/** Base component returned by entity.add() for component types without a specific module. */ +interface Component { + /** Entity ID this component belongs to. */ + readonly entity: number; + /** Component slot ID. */ + readonly id: number; + toString(): string; +} + +interface ComponentConstructor { + /** Sentinel value for an invalid component ID. */ + readonly INVALID: number; + + // Component type constants (generated from componentlist.h) + readonly POSITION: number; + readonly CAMERA: number; + readonly RENDERABLE: number; + readonly PHYSICS: number; + readonly TRIGGER: number; + readonly OVERWORLD: number; + readonly PLAYER: number; + readonly INTERACTABLE: number; + readonly OVERWORLD_CAMERA: number; + readonly OVERWORLD_TRIGGER: number; + + new(): never; +} + +declare var Component: ComponentConstructor; + +// --------------------------------------------------------------------------- +// Position +// --------------------------------------------------------------------------- + +/** Position/rotation/scale transform component with optional parent hierarchy. */ +interface Position extends Component { + /** + * Local-space position. Assigning a Vec3 writes to the C transform and + * marks the world transform dirty. Reading returns a fresh Vec3 copy. + */ + localPosition: Vec3; + /** World-space position (accounts for all parent transforms). */ + worldPosition: Vec3; + /** Local-space euler rotation in radians (XYZ). */ + localRotation: Vec3; + /** World-space euler rotation in radians. */ + worldRotation: Vec3; + /** Local-space scale. */ + localScale: Vec3; + /** World-space scale. */ + worldScale: Vec3; + /** + * Orients the transform to look at a world-space target point. + * Uses the current local position as the eye. Optionally specify an up + * vector (defaults to Y-up). + */ + lookAt(target: Vec3, up?: Vec3): void; + /** + * Sets this component's parent in the transform hierarchy. + * Pass `null` or `undefined` to detach. + */ + setParent(parent: Position | null | undefined): void; + toString(): string; +} + +interface PositionConstructor { + new(): never; +} + +declare var Position: PositionConstructor; + +// --------------------------------------------------------------------------- +// Camera +// --------------------------------------------------------------------------- + +/** Camera projection component. */ +interface Camera extends Component { + /** Field of view in radians (perspective projections only). */ + fov: number; + /** Near clip plane distance. */ + nearClip: number; + /** Far clip plane distance. */ + farClip: number; + /** Projection type — one of the Camera.PERSPECTIVE / Camera.ORTHOGRAPHIC constants. */ + projType: number; + toString(): string; +} + +interface CameraConstructor { + readonly PERSPECTIVE: number; + readonly PERSPECTIVE_FLIPPED: number; + readonly ORTHOGRAPHIC: number; + new(): never; +} + +declare var Camera: CameraConstructor; + +// --------------------------------------------------------------------------- +// Entity +// --------------------------------------------------------------------------- + +interface Entity { + /** Add a component of the given type and return a typed instance. */ + add(type: CameraConstructor["PERSPECTIVE"] | number): Component; + toString(): string; +} + +interface EntityConstructor { + /** Sentinel value for an invalid entity ID. */ + readonly INVALID: number; + /** Creates a new entity and returns it. Returns Entity.INVALID slot if the pool is full. */ + create(): Entity; + /** Disposes the entity and all its components. */ + dispose(entity: Entity): void; + new(): never; +} + +declare var Entity: EntityConstructor; diff --git a/types/index.d.ts b/types/index.d.ts index 6da3d3ba..cebc47f4 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -19,3 +19,6 @@ /// /// /// +/// +/// +/// diff --git a/types/scene.d.ts b/types/scene.d.ts new file mode 100644 index 00000000..79f1dc15 --- /dev/null +++ b/types/scene.d.ts @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** Scene management — request scene transitions and query the active scene. */ +interface SceneNamespace { + /** The type constant of the currently active scene, or 0 if none. */ + readonly current: number; + + /** + * Requests a scene transition. The change takes effect at the start of the + * next safe update tick (current scene is disposed, new scene is initialized). + * + * @param type - A `Scene.*` scene type constant. + * + * @example + * Scene.set(Scene.OVERWORLD); + */ + set(type: number): void; + + // Scene type constants (generated from scenelist.h) + readonly INITIAL: number; + readonly TEST: number; + readonly OVERWORLD: number; +} + +declare var Scene: SceneNamespace; diff --git a/types/vec3.d.ts b/types/vec3.d.ts new file mode 100644 index 00000000..bf8b6811 --- /dev/null +++ b/types/vec3.d.ts @@ -0,0 +1,15 @@ +/** + * Copyright (c) 2026 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +/** A three-component float vector (x, y, z). */ +declare class Vec3 { + constructor(x?: number, y?: number, z?: number); + x: number; + y: number; + z: number; + toString(): string; +}