diff --git a/src/dusk/CMakeLists.txt b/src/dusk/CMakeLists.txt index 324ab552..52221cc1 100644 --- a/src/dusk/CMakeLists.txt +++ b/src/dusk/CMakeLists.txt @@ -9,6 +9,11 @@ if(NOT cglm_FOUND) target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC cglm) endif() +if(NOT yyjson_FOUND) + find_package(yyjson REQUIRED) + target_link_libraries(${DUSK_LIBRARY_TARGET_NAME} PUBLIC yyjson::yyjson) +endif() + if(DUSK_BACKTRACE) target_link_options(${DUSK_LIBRARY_TARGET_NAME} PUBLIC -rdynamic) diff --git a/src/dusk/entity/component.c b/src/dusk/entity/component.c index ad23b598..0e58d13d 100644 --- a/src/dusk/entity/component.c +++ b/src/dusk/entity/component.c @@ -12,15 +12,17 @@ componentdefinition_t COMPONENT_DEFINITIONS[] = { [COMPONENT_TYPE_NULL] = { 0 }, - #define X(enm, type, field, iMethod, dMethod, rMethod) \ + #define X(enm, type, field, iMethod, dMethod, rMethod, sMethod, dsMethod) \ [COMPONENT_TYPE_##enm] = { \ .enumName = #enm, \ .name = #field, \ .init = iMethod, \ .dispose = dMethod, \ - .render = rMethod \ + .render = rMethod, \ + .serialize = sMethod, \ + .deserialize = dsMethod \ }, - + #include "componentlist.h" #undef X @@ -130,6 +132,43 @@ errorret_t componentRenderAll(void) { errorOk(); } +void componentSerialize( + const entityid_t entityId, + const componentid_t componentId, + yyjson_mut_doc *doc, + yyjson_mut_val *obj +) { + assertTrue(entityId < ENTITY_COUNT_MAX, "Entity ID OOB"); + assertTrue(componentId < ENTITY_COMPONENT_COUNT_MAX, "Component ID OOB"); + assertNotNull(doc, "JSON doc cannot be null"); + assertNotNull(obj, "JSON obj cannot be null"); + + componentindex_t index = componentGetIndex(entityId, componentId); + component_t *cmp = &ENTITY_MANAGER.components[index]; + if(cmp->type == COMPONENT_TYPE_NULL) return; + if(!COMPONENT_DEFINITIONS[cmp->type].serialize) return; + COMPONENT_DEFINITIONS[cmp->type].serialize(entityId, componentId, doc, obj); +} + +errorret_t componentDeserialize( + const entityid_t entityId, + const componentid_t componentId, + yyjson_val *obj +) { + assertTrue(entityId < ENTITY_COUNT_MAX, "Entity ID OOB"); + assertTrue(componentId < ENTITY_COMPONENT_COUNT_MAX, "Component ID OOB"); + assertNotNull(obj, "JSON obj cannot be null"); + + componentindex_t index = componentGetIndex(entityId, componentId); + component_t *cmp = &ENTITY_MANAGER.components[index]; + if(cmp->type == COMPONENT_TYPE_NULL) errorOk(); + if(!COMPONENT_DEFINITIONS[cmp->type].deserialize) errorOk(); + errorChain( + COMPONENT_DEFINITIONS[cmp->type].deserialize(entityId, componentId, obj) + ); + errorOk(); +} + void componentDispose( const entityid_t entityId, const componentid_t componentId diff --git a/src/dusk/entity/component.h b/src/dusk/entity/component.h index c723ec02..fd87485f 100644 --- a/src/dusk/entity/component.h +++ b/src/dusk/entity/component.h @@ -7,14 +7,18 @@ #pragma once #include "entitybase.h" +#include "error/error.h" -#define X(enumName, type, field, init, dispose, render) \ +#include + +#define X(enumName, type, field, init, dispose, render, serialize, deserialize) \ // do nothing #include "componentlist.h" #undef X typedef union { - #define X(enumName, type, field, init, dispose, render) type field; + #define X(enumName, type, field, init, dispose, render, serialize, deserialize) \ + type field; #include "componentlist.h" #undef X } componentdata_t; @@ -25,12 +29,23 @@ typedef struct { void (*init)(const entityid_t, const componentid_t); void (*dispose)(const entityid_t, const componentid_t); errorret_t (*render)(const entityid_t, const componentid_t); + void (*serialize)( + const entityid_t, + const componentid_t, + yyjson_mut_doc *, + yyjson_mut_val * + ); + errorret_t (*deserialize)( + const entityid_t, + const componentid_t, + yyjson_val * + ); } componentdefinition_t; typedef enum { COMPONENT_TYPE_NULL, - #define X(enumName, type, field, init, dispose, render) \ + #define X(enumName, type, field, init, dispose, render, serialize, deserialize) \ COMPONENT_TYPE_##enumName, #include "componentlist.h" #undef X @@ -117,4 +132,34 @@ void componentDispose( * * @return Error state. */ -errorret_t componentRenderAll(void); \ No newline at end of file +errorret_t componentRenderAll(void); + +/** + * Serializes a component's configurable state into a JSON object. The caller + * is responsible for creating and owning the doc and obj. + * + * @param entityId The entity ID. + * @param componentId The component ID. + * @param doc The mutable JSON document to allocate values from. + * @param obj The JSON object to write fields into. + */ +void componentSerialize( + const entityid_t entityId, + const componentid_t componentId, + yyjson_mut_doc *doc, + yyjson_mut_val *obj +); + +/** + * Deserializes a component's configurable state from a JSON object. + * + * @param entityId The entity ID. + * @param componentId The component ID. + * @param obj The JSON object to read fields from. + * @return Error state. + */ +errorret_t componentDeserialize( + const entityid_t entityId, + const componentid_t componentId, + yyjson_val *obj +); \ No newline at end of file diff --git a/src/dusk/entity/component/display/entitycamera.c b/src/dusk/entity/component/display/entitycamera.c index d9a2727e..14ace6ed 100644 --- a/src/dusk/entity/component/display/entitycamera.c +++ b/src/dusk/entity/component/display/entitycamera.c @@ -10,6 +10,9 @@ #include "entity/component/display/entityposition.h" #include "display/framebuffer/framebuffer.h" #include "display/screen/screen.h" +#include "assert/assert.h" +#include "util/string.h" +#include void entityCameraInit(const entityid_t ent, const componentid_t comp) { entitycamera_t *cam = (entitycamera_t *)componentGetData( @@ -117,4 +120,88 @@ void entityCameraGetRight(const entityid_t entityId, vec2 out) { if(len > 1e-6f) { rx /= len; rz /= len; } out[0] = rx; out[1] = rz; +} + +void entityCameraSerialize( + const entityid_t entityId, + const componentid_t componentId, + yyjson_mut_doc *doc, + yyjson_mut_val *obj +) { + entitycamera_t *cam = (entitycamera_t *)componentGetData( + entityId, componentId, COMPONENT_TYPE_CAMERA + ); + assertNotNull(cam, "Failed to get camera component."); + + const char_t *projStr = "perspective"; + if(cam->projType == ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED) { + projStr = "perspective_flipped"; + } else if(cam->projType == ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) { + projStr = "orthographic"; + } + + yyjson_mut_obj_add_str(doc, obj, "projType", projStr); + yyjson_mut_obj_add_real(doc, obj, "nearClip", (double)cam->nearClip); + yyjson_mut_obj_add_real(doc, obj, "farClip", (double)cam->farClip); + + if(cam->projType == ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) { + yyjson_mut_obj_add_real(doc, obj, "left", (double)cam->orthographic.left); + yyjson_mut_obj_add_real(doc, obj, "right", (double)cam->orthographic.right); + yyjson_mut_obj_add_real(doc, obj, "top", (double)cam->orthographic.top); + yyjson_mut_obj_add_real( + doc, obj, "bottom", (double)cam->orthographic.bottom + ); + } else { + yyjson_mut_obj_add_real(doc, obj, "fov", (double)cam->perspective.fov); + } +} + +errorret_t entityCameraDeserialize( + const entityid_t entityId, + const componentid_t componentId, + yyjson_val *obj +) { + entitycamera_t *cam = (entitycamera_t *)componentGetData( + entityId, componentId, COMPONENT_TYPE_CAMERA + ); + assertNotNull(cam, "Failed to get camera component."); + + yyjson_val *v; + + v = yyjson_obj_get(obj, "projType"); + if(v && yyjson_is_str(v)) { + const char_t *s = yyjson_get_str(v); + if(stringCompare(s, "perspective_flipped") == 0) { + cam->projType = ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE_FLIPPED; + } else if(stringCompare(s, "orthographic") == 0) { + cam->projType = ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC; + } else { + cam->projType = ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE; + } + } + + v = yyjson_obj_get(obj, "nearClip"); + if(v) cam->nearClip = (float_t)yyjson_get_num(v); + + v = yyjson_obj_get(obj, "farClip"); + if(v) cam->farClip = (float_t)yyjson_get_num(v); + + if(cam->projType == ENTITY_CAMERA_PROJECTION_TYPE_ORTHOGRAPHIC) { + v = yyjson_obj_get(obj, "left"); + if(v) cam->orthographic.left = (float_t)yyjson_get_num(v); + + v = yyjson_obj_get(obj, "right"); + if(v) cam->orthographic.right = (float_t)yyjson_get_num(v); + + v = yyjson_obj_get(obj, "top"); + if(v) cam->orthographic.top = (float_t)yyjson_get_num(v); + + v = yyjson_obj_get(obj, "bottom"); + if(v) cam->orthographic.bottom = (float_t)yyjson_get_num(v); + } else { + v = yyjson_obj_get(obj, "fov"); + if(v) cam->perspective.fov = (float_t)yyjson_get_num(v); + } + + errorOk(); } \ No newline at end of file diff --git a/src/dusk/entity/component/display/entitycamera.h b/src/dusk/entity/component/display/entitycamera.h index b866e354..5284b480 100644 --- a/src/dusk/entity/component/display/entitycamera.h +++ b/src/dusk/entity/component/display/entitycamera.h @@ -7,6 +7,8 @@ #pragma once #include "entity/entitybase.h" +#include "error/error.h" +#include typedef enum { ENTITY_CAMERA_PROJECTION_TYPE_PERSPECTIVE, @@ -96,4 +98,35 @@ void entityCameraLookAtPixelPerfect( const vec3 point, const vec3 eyeOffset, const float_t scale +); + +/** + * Serializes camera projection settings to a JSON object. Writes "projType" + * as a string ("perspective", "perspective_flipped", or "orthographic"), + * "nearClip", "farClip", and the projection-specific parameters. + * + * @param entityId The entity ID. + * @param componentId The component ID. + * @param doc The mutable JSON document to allocate values from. + * @param obj The JSON object to write fields into. + */ +void entityCameraSerialize( + const entityid_t entityId, + const componentid_t componentId, + yyjson_mut_doc *doc, + yyjson_mut_val *obj +); + +/** + * Deserializes camera projection settings from a JSON object. + * + * @param entityId The entity ID. + * @param componentId The component ID. + * @param obj The JSON object to read fields from. + * @return Error state. + */ +errorret_t entityCameraDeserialize( + const entityid_t entityId, + const componentid_t componentId, + yyjson_val *obj ); \ No newline at end of file diff --git a/src/dusk/entity/component/display/entityposition.c b/src/dusk/entity/component/display/entityposition.c index 46857036..0f768643 100644 --- a/src/dusk/entity/component/display/entityposition.c +++ b/src/dusk/entity/component/display/entityposition.c @@ -6,6 +6,8 @@ */ #include "entity/entitymanager.h" +#include "assert/assert.h" +#include // Decompose localTransform into the PRS cache. Only called when PRS_DIRTY. static void entityPositionEnsurePRS(entityposition_t *pos) { @@ -628,3 +630,68 @@ void entityPositionDecompose(entityposition_t *pos) { : -atan2f(r01, r11); } } + +void entityPositionSerialize( + const entityid_t entityId, + const componentid_t componentId, + yyjson_mut_doc *doc, + yyjson_mut_val *obj +) { + entityposition_t *pos = entityPositionGet(entityId, componentId); + assertNotNull(pos, "Failed to get position component."); + + yyjson_mut_val *arr; + + arr = yyjson_mut_arr(doc); + yyjson_mut_arr_add_real(doc, arr, (double)pos->position[0]); + yyjson_mut_arr_add_real(doc, arr, (double)pos->position[1]); + yyjson_mut_arr_add_real(doc, arr, (double)pos->position[2]); + yyjson_mut_obj_add_val(doc, obj, "position", arr); + + arr = yyjson_mut_arr(doc); + yyjson_mut_arr_add_real(doc, arr, (double)pos->rotation[0]); + yyjson_mut_arr_add_real(doc, arr, (double)pos->rotation[1]); + yyjson_mut_arr_add_real(doc, arr, (double)pos->rotation[2]); + yyjson_mut_obj_add_val(doc, obj, "rotation", arr); + + arr = yyjson_mut_arr(doc); + yyjson_mut_arr_add_real(doc, arr, (double)pos->scale[0]); + yyjson_mut_arr_add_real(doc, arr, (double)pos->scale[1]); + yyjson_mut_arr_add_real(doc, arr, (double)pos->scale[2]); + yyjson_mut_obj_add_val(doc, obj, "scale", arr); +} + +errorret_t entityPositionDeserialize( + const entityid_t entityId, + const componentid_t componentId, + yyjson_val *obj +) { + entityposition_t *pos = entityPositionGet(entityId, componentId); + assertNotNull(pos, "Failed to get position component."); + + yyjson_val *v; + + v = yyjson_obj_get(obj, "position"); + if(v && yyjson_is_arr(v) && yyjson_arr_size(v) == 3) { + pos->position[0] = (float_t)yyjson_get_num(yyjson_arr_get(v, 0)); + pos->position[1] = (float_t)yyjson_get_num(yyjson_arr_get(v, 1)); + pos->position[2] = (float_t)yyjson_get_num(yyjson_arr_get(v, 2)); + } + + v = yyjson_obj_get(obj, "rotation"); + if(v && yyjson_is_arr(v) && yyjson_arr_size(v) == 3) { + pos->rotation[0] = (float_t)yyjson_get_num(yyjson_arr_get(v, 0)); + pos->rotation[1] = (float_t)yyjson_get_num(yyjson_arr_get(v, 1)); + pos->rotation[2] = (float_t)yyjson_get_num(yyjson_arr_get(v, 2)); + } + + v = yyjson_obj_get(obj, "scale"); + if(v && yyjson_is_arr(v) && yyjson_arr_size(v) == 3) { + pos->scale[0] = (float_t)yyjson_get_num(yyjson_arr_get(v, 0)); + pos->scale[1] = (float_t)yyjson_get_num(yyjson_arr_get(v, 1)); + pos->scale[2] = (float_t)yyjson_get_num(yyjson_arr_get(v, 2)); + } + + entityPositionRebuild(pos); + errorOk(); +} diff --git a/src/dusk/entity/component/display/entityposition.h b/src/dusk/entity/component/display/entityposition.h index 391a58c3..f0a93a8c 100644 --- a/src/dusk/entity/component/display/entityposition.h +++ b/src/dusk/entity/component/display/entityposition.h @@ -7,6 +7,8 @@ #pragma once #include "entity/entitybase.h" +#include "error/error.h" +#include /** Maximum number of child position components this node can track. */ #define ENTITY_POSITION_CHILDREN_MAX 8 @@ -373,3 +375,34 @@ void entityPositionDisposeDeep( * @param pos The position component to decompose. */ void entityPositionDecompose(entityposition_t *pos); + +/** + * Serializes position, rotation, and scale (local space) to a JSON object. + * Parent/child linkage and computed matrices are not included. + * + * @param entityId The entity ID. + * @param componentId The component ID. + * @param doc The mutable JSON document to allocate values from. + * @param obj The JSON object to write fields into. + */ +void entityPositionSerialize( + const entityid_t entityId, + const componentid_t componentId, + yyjson_mut_doc *doc, + yyjson_mut_val *obj +); + +/** + * Deserializes position, rotation, and scale from a JSON object and rebuilds + * the local transform. + * + * @param entityId The entity ID. + * @param componentId The component ID. + * @param obj The JSON object to read fields from. + * @return Error state. + */ +errorret_t entityPositionDeserialize( + const entityid_t entityId, + const componentid_t componentId, + yyjson_val *obj +); diff --git a/src/dusk/entity/component/display/entityrenderable.c b/src/dusk/entity/component/display/entityrenderable.c index a30fa1be..1d6b6b56 100644 --- a/src/dusk/entity/component/display/entityrenderable.c +++ b/src/dusk/entity/component/display/entityrenderable.c @@ -13,6 +13,8 @@ #include "display/mesh/cube.h" #include "util/memory.h" #include "assert/assert.h" +#include "util/string.h" +#include void entityRenderableInit( const entityid_t entityId, @@ -140,3 +142,77 @@ errorret_t entityRenderableDraw( assertUnreachable("Invalid renderable type"); } } + +void entityRenderableSerialize( + const entityid_t entityId, + const componentid_t componentId, + yyjson_mut_doc *doc, + yyjson_mut_val *obj +) { + entityrenderable_t *r = (entityrenderable_t *)componentGetData( + entityId, componentId, COMPONENT_TYPE_RENDERABLE + ); + assertNotNull(r, "Failed to get renderable component."); + + const char_t *typeStr = "custom"; + if(r->type == ENTITY_RENDERABLE_TYPE_SPRITEBATCH) { + typeStr = "spritebatch"; + } else if(r->type == ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL) { + typeStr = "shader_material"; + } + + yyjson_mut_obj_add_str(doc, obj, "type", typeStr); + yyjson_mut_obj_add_int(doc, obj, "priority", (int64_t)r->priority); + + if(r->type == ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL) { + yyjson_mut_obj_add_int( + doc, obj, "shaderType", (int64_t)r->data.material.shaderType + ); + yyjson_mut_obj_add_int( + doc, obj, "stateFlags", (int64_t)r->data.material.state.flags + ); + } +} + +errorret_t entityRenderableDeserialize( + const entityid_t entityId, + const componentid_t componentId, + yyjson_val *obj +) { + entityrenderable_t *r = (entityrenderable_t *)componentGetData( + entityId, componentId, COMPONENT_TYPE_RENDERABLE + ); + assertNotNull(r, "Failed to get renderable component."); + + yyjson_val *v; + + v = yyjson_obj_get(obj, "type"); + if(v && yyjson_is_str(v)) { + const char_t *s = yyjson_get_str(v); + if(stringCompare(s, "spritebatch") == 0) { + entityRenderableSetType(entityId, componentId, + ENTITY_RENDERABLE_TYPE_SPRITEBATCH); + } else if(stringCompare(s, "shader_material") == 0) { + entityRenderableSetType(entityId, componentId, + ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL); + } else { + entityRenderableSetType(entityId, componentId, + ENTITY_RENDERABLE_TYPE_CUSTOM); + } + } + + v = yyjson_obj_get(obj, "priority"); + if(v) r->priority = (int8_t)yyjson_get_sint(v); + + if(r->type == ENTITY_RENDERABLE_TYPE_SHADER_MATERIAL) { + v = yyjson_obj_get(obj, "shaderType"); + if(v) { + r->data.material.shaderType = (shaderlistshader_t)yyjson_get_sint(v); + } + + v = yyjson_obj_get(obj, "stateFlags"); + if(v) r->data.material.state.flags = (uint8_t)yyjson_get_sint(v); + } + + errorOk(); +} diff --git a/src/dusk/entity/component/display/entityrenderable.h b/src/dusk/entity/component/display/entityrenderable.h index 1d87243a..13d777fc 100644 --- a/src/dusk/entity/component/display/entityrenderable.h +++ b/src/dusk/entity/component/display/entityrenderable.h @@ -11,6 +11,7 @@ #include "display/shader/shadermaterial.h" #include "display/spritebatch/spritebatch.h" #include "display/displaystate.h" +#include #define ENTITY_RENDERABLE_SPRITEBATCH_SPRITES_MAX 64 #define ENTITY_RENDERABLE_MESHES_MAX 8 @@ -145,3 +146,33 @@ errorret_t entityRenderableDraw( const entityid_t entityId, const componentid_t componentId ); + +/** + * Serializes renderable type, priority, and material settings to a JSON + * object. Custom and spritebatch renderables only serialize type and priority. + * + * @param entityId The entity ID. + * @param componentId The component ID. + * @param doc The mutable JSON document to allocate values from. + * @param obj The JSON object to write fields into. + */ +void entityRenderableSerialize( + const entityid_t entityId, + const componentid_t componentId, + yyjson_mut_doc *doc, + yyjson_mut_val *obj +); + +/** + * Deserializes renderable settings from a JSON object. + * + * @param entityId The entity ID. + * @param componentId The component ID. + * @param obj The JSON object to read fields from. + * @return Error state. + */ +errorret_t entityRenderableDeserialize( + const entityid_t entityId, + const componentid_t componentId, + yyjson_val *obj +); diff --git a/src/dusk/entity/component/physics/entityphysics.c b/src/dusk/entity/component/physics/entityphysics.c index 3705f8b7..163d461c 100644 --- a/src/dusk/entity/component/physics/entityphysics.c +++ b/src/dusk/entity/component/physics/entityphysics.c @@ -11,6 +11,8 @@ #include "physics/physicsmanager.h" #include "assert/assert.h" #include "util/memory.h" +#include "util/string.h" +#include void entityPhysicsInit( const entityid_t entityId, @@ -124,3 +126,172 @@ void entityPhysicsDispose( const componentid_t componentId ) { } + +void entityPhysicsSerialize( + const entityid_t entityId, + const componentid_t componentId, + yyjson_mut_doc *doc, + yyjson_mut_val *obj +) { + entityphysics_t *phys = entityPhysicsGet(entityId, componentId); + assertNotNull(phys, "Failed to get physics component."); + + const char_t *bodyStr = "static"; + if(phys->type == PHYSICS_BODY_DYNAMIC) { + bodyStr = "dynamic"; + } else if(phys->type == PHYSICS_BODY_KINEMATIC) { + bodyStr = "kinematic"; + } + yyjson_mut_obj_add_str(doc, obj, "bodyType", bodyStr); + + const char_t *shapeStr = "cube"; + if(phys->shape.type == PHYSICS_SHAPE_SPHERE) { + shapeStr = "sphere"; + } else if(phys->shape.type == PHYSICS_SHAPE_CAPSULE) { + shapeStr = "capsule"; + } else if(phys->shape.type == PHYSICS_SHAPE_PLANE) { + shapeStr = "plane"; + } + yyjson_mut_obj_add_str(doc, obj, "shapeType", shapeStr); + + switch(phys->shape.type) { + case PHYSICS_SHAPE_CUBE: { + yyjson_mut_val *he = yyjson_mut_arr(doc); + yyjson_mut_arr_add_real( + doc, he, (double)phys->shape.data.cube.halfExtents[0] + ); + yyjson_mut_arr_add_real( + doc, he, (double)phys->shape.data.cube.halfExtents[1] + ); + yyjson_mut_arr_add_real( + doc, he, (double)phys->shape.data.cube.halfExtents[2] + ); + yyjson_mut_obj_add_val(doc, obj, "halfExtents", he); + break; + } + case PHYSICS_SHAPE_SPHERE: + yyjson_mut_obj_add_real( + doc, obj, "radius", (double)phys->shape.data.sphere.radius + ); + break; + case PHYSICS_SHAPE_CAPSULE: + yyjson_mut_obj_add_real( + doc, obj, "radius", (double)phys->shape.data.capsule.radius + ); + yyjson_mut_obj_add_real( + doc, obj, "halfHeight", (double)phys->shape.data.capsule.halfHeight + ); + break; + case PHYSICS_SHAPE_PLANE: { + yyjson_mut_val *n = yyjson_mut_arr(doc); + yyjson_mut_arr_add_real( + doc, n, (double)phys->shape.data.plane.normal[0] + ); + yyjson_mut_arr_add_real( + doc, n, (double)phys->shape.data.plane.normal[1] + ); + yyjson_mut_arr_add_real( + doc, n, (double)phys->shape.data.plane.normal[2] + ); + yyjson_mut_obj_add_val(doc, obj, "normal", n); + yyjson_mut_obj_add_real( + doc, obj, "distance", (double)phys->shape.data.plane.distance + ); + break; + } + default: + break; + } + + yyjson_mut_val *vel = yyjson_mut_arr(doc); + yyjson_mut_arr_add_real(doc, vel, (double)phys->velocity[0]); + yyjson_mut_arr_add_real(doc, vel, (double)phys->velocity[1]); + yyjson_mut_arr_add_real(doc, vel, (double)phys->velocity[2]); + yyjson_mut_obj_add_val(doc, obj, "velocity", vel); + + yyjson_mut_obj_add_real(doc, obj, "gravityScale", (double)phys->gravityScale); +} + +errorret_t entityPhysicsDeserialize( + const entityid_t entityId, + const componentid_t componentId, + yyjson_val *obj +) { + entityphysics_t *phys = entityPhysicsGet(entityId, componentId); + assertNotNull(phys, "Failed to get physics component."); + + yyjson_val *v; + + v = yyjson_obj_get(obj, "bodyType"); + if(v && yyjson_is_str(v)) { + const char_t *s = yyjson_get_str(v); + if(stringCompare(s, "dynamic") == 0) { + phys->type = PHYSICS_BODY_DYNAMIC; + } else if(stringCompare(s, "kinematic") == 0) { + phys->type = PHYSICS_BODY_KINEMATIC; + } else { + phys->type = PHYSICS_BODY_STATIC; + } + } + + v = yyjson_obj_get(obj, "shapeType"); + if(v && yyjson_is_str(v)) { + physicsshape_t shape; + memoryZero(&shape, sizeof(physicsshape_t)); + const char_t *s = yyjson_get_str(v); + if(stringCompare(s, "sphere") == 0) { + shape.type = PHYSICS_SHAPE_SPHERE; + yyjson_val *r = yyjson_obj_get(obj, "radius"); + if(r) shape.data.sphere.radius = (float_t)yyjson_get_num(r); + } else if(stringCompare(s, "capsule") == 0) { + shape.type = PHYSICS_SHAPE_CAPSULE; + yyjson_val *r = yyjson_obj_get(obj, "radius"); + if(r) shape.data.capsule.radius = (float_t)yyjson_get_num(r); + yyjson_val *hh = yyjson_obj_get(obj, "halfHeight"); + if(hh) shape.data.capsule.halfHeight = (float_t)yyjson_get_num(hh); + } else if(stringCompare(s, "plane") == 0) { + shape.type = PHYSICS_SHAPE_PLANE; + yyjson_val *n = yyjson_obj_get(obj, "normal"); + if(n && yyjson_is_arr(n) && yyjson_arr_size(n) == 3) { + shape.data.plane.normal[0] = (float_t)yyjson_get_num( + yyjson_arr_get(n, 0) + ); + shape.data.plane.normal[1] = (float_t)yyjson_get_num( + yyjson_arr_get(n, 1) + ); + shape.data.plane.normal[2] = (float_t)yyjson_get_num( + yyjson_arr_get(n, 2) + ); + } + yyjson_val *d = yyjson_obj_get(obj, "distance"); + if(d) shape.data.plane.distance = (float_t)yyjson_get_num(d); + } else { + shape.type = PHYSICS_SHAPE_CUBE; + yyjson_val *he = yyjson_obj_get(obj, "halfExtents"); + if(he && yyjson_is_arr(he) && yyjson_arr_size(he) == 3) { + shape.data.cube.halfExtents[0] = (float_t)yyjson_get_num( + yyjson_arr_get(he, 0) + ); + shape.data.cube.halfExtents[1] = (float_t)yyjson_get_num( + yyjson_arr_get(he, 1) + ); + shape.data.cube.halfExtents[2] = (float_t)yyjson_get_num( + yyjson_arr_get(he, 2) + ); + } + } + entityPhysicsSetShape(entityId, componentId, shape); + } + + v = yyjson_obj_get(obj, "velocity"); + if(v && yyjson_is_arr(v) && yyjson_arr_size(v) == 3) { + phys->velocity[0] = (float_t)yyjson_get_num(yyjson_arr_get(v, 0)); + phys->velocity[1] = (float_t)yyjson_get_num(yyjson_arr_get(v, 1)); + phys->velocity[2] = (float_t)yyjson_get_num(yyjson_arr_get(v, 2)); + } + + v = yyjson_obj_get(obj, "gravityScale"); + if(v) phys->gravityScale = (float_t)yyjson_get_num(v); + + errorOk(); +} diff --git a/src/dusk/entity/component/physics/entityphysics.h b/src/dusk/entity/component/physics/entityphysics.h index 6cccfca3..8b495fb4 100644 --- a/src/dusk/entity/component/physics/entityphysics.h +++ b/src/dusk/entity/component/physics/entityphysics.h @@ -9,6 +9,8 @@ #include "entity/entitybase.h" #include "physics/physicsshape.h" #include "physics/physicsbodytype.h" +#include "error/error.h" +#include typedef struct { physicsbodytype_t type; @@ -155,3 +157,34 @@ void entityPhysicsDispose( const entityid_t entityId, const componentid_t componentId ); + +/** + * Serializes body type, shape, velocity, and gravity scale to a JSON object. + * Runtime state (onGround) is not included. + * + * @param entityId The entity ID. + * @param componentId The component ID. + * @param doc The mutable JSON document to allocate values from. + * @param obj The JSON object to write fields into. + */ +void entityPhysicsSerialize( + const entityid_t entityId, + const componentid_t componentId, + yyjson_mut_doc *doc, + yyjson_mut_val *obj +); + +/** + * Deserializes body type, shape, velocity, and gravity scale from a JSON + * object. + * + * @param entityId The entity ID. + * @param componentId The component ID. + * @param obj The JSON object to read fields from. + * @return Error state. + */ +errorret_t entityPhysicsDeserialize( + const entityid_t entityId, + const componentid_t componentId, + yyjson_val *obj +); diff --git a/src/dusk/entity/component/trigger/entitytrigger.c b/src/dusk/entity/component/trigger/entitytrigger.c index bba57269..ef6cdd6b 100644 --- a/src/dusk/entity/component/trigger/entitytrigger.c +++ b/src/dusk/entity/component/trigger/entitytrigger.c @@ -6,6 +6,8 @@ */ #include "entity/entitymanager.h" +#include "assert/assert.h" +#include void entityTriggerInit( const entityid_t entityId, @@ -52,3 +54,54 @@ void entityTriggerSetBounds( glm_vec3_copy((float_t*)min, t->min); glm_vec3_copy((float_t*)max, t->max); } + +void entityTriggerSerialize( + const entityid_t entityId, + const componentid_t componentId, + yyjson_mut_doc *doc, + yyjson_mut_val *obj +) { + entitytrigger_t *trigger = entityTriggerGet(entityId, componentId); + assertNotNull(trigger, "Failed to get trigger component."); + + yyjson_mut_val *arr; + + arr = yyjson_mut_arr(doc); + yyjson_mut_arr_add_real(doc, arr, (double)trigger->min[0]); + yyjson_mut_arr_add_real(doc, arr, (double)trigger->min[1]); + yyjson_mut_arr_add_real(doc, arr, (double)trigger->min[2]); + yyjson_mut_obj_add_val(doc, obj, "min", arr); + + arr = yyjson_mut_arr(doc); + yyjson_mut_arr_add_real(doc, arr, (double)trigger->max[0]); + yyjson_mut_arr_add_real(doc, arr, (double)trigger->max[1]); + yyjson_mut_arr_add_real(doc, arr, (double)trigger->max[2]); + yyjson_mut_obj_add_val(doc, obj, "max", arr); +} + +errorret_t entityTriggerDeserialize( + const entityid_t entityId, + const componentid_t componentId, + yyjson_val *obj +) { + entitytrigger_t *trigger = entityTriggerGet(entityId, componentId); + assertNotNull(trigger, "Failed to get trigger component."); + + yyjson_val *v; + + v = yyjson_obj_get(obj, "min"); + if(v && yyjson_is_arr(v) && yyjson_arr_size(v) == 3) { + trigger->min[0] = (float_t)yyjson_get_num(yyjson_arr_get(v, 0)); + trigger->min[1] = (float_t)yyjson_get_num(yyjson_arr_get(v, 1)); + trigger->min[2] = (float_t)yyjson_get_num(yyjson_arr_get(v, 2)); + } + + v = yyjson_obj_get(obj, "max"); + if(v && yyjson_is_arr(v) && yyjson_arr_size(v) == 3) { + trigger->max[0] = (float_t)yyjson_get_num(yyjson_arr_get(v, 0)); + trigger->max[1] = (float_t)yyjson_get_num(yyjson_arr_get(v, 1)); + trigger->max[2] = (float_t)yyjson_get_num(yyjson_arr_get(v, 2)); + } + + errorOk(); +} diff --git a/src/dusk/entity/component/trigger/entitytrigger.h b/src/dusk/entity/component/trigger/entitytrigger.h index fdbef2c8..072e2c98 100644 --- a/src/dusk/entity/component/trigger/entitytrigger.h +++ b/src/dusk/entity/component/trigger/entitytrigger.h @@ -7,6 +7,8 @@ #pragma once #include "entity/entitybase.h" +#include "error/error.h" +#include typedef struct { vec3 min; @@ -47,3 +49,32 @@ void entityTriggerSetBounds( const vec3 min, const vec3 max ); + +/** + * Serializes the trigger's min and max bounds to a JSON object. + * + * @param entityId The entity ID. + * @param componentId The component ID. + * @param doc The mutable JSON document to allocate values from. + * @param obj The JSON object to write fields into. + */ +void entityTriggerSerialize( + const entityid_t entityId, + const componentid_t componentId, + yyjson_mut_doc *doc, + yyjson_mut_val *obj +); + +/** + * Deserializes the trigger's min and max bounds from a JSON object. + * + * @param entityId The entity ID. + * @param componentId The component ID. + * @param obj The JSON object to read fields from. + * @return Error state. + */ +errorret_t entityTriggerDeserialize( + const entityid_t entityId, + const componentid_t componentId, + yyjson_val *obj +); diff --git a/src/dusk/entity/componentlist.h b/src/dusk/entity/componentlist.h index b5f1bc82..e0141374 100644 --- a/src/dusk/entity/componentlist.h +++ b/src/dusk/entity/componentlist.h @@ -17,11 +17,21 @@ // Init function (optional) // Dispose function (optional) // Render function (optional) +// Serialize function (optional) +// Deserialize function (optional) -X(POSITION, entityposition_t, position, entityPositionInit, NULL, NULL) -X(CAMERA, entitycamera_t, camera, entityCameraInit, NULL, NULL) +X(POSITION, entityposition_t, position, + entityPositionInit, NULL, NULL, + entityPositionSerialize, entityPositionDeserialize) +X(CAMERA, entitycamera_t, camera, + entityCameraInit, NULL, NULL, + entityCameraSerialize, entityCameraDeserialize) X(RENDERABLE, entityrenderable_t, renderable, - entityRenderableInit, entityRenderableDispose, NULL) + entityRenderableInit, entityRenderableDispose, NULL, + entityRenderableSerialize, entityRenderableDeserialize) X(PHYSICS, entityphysics_t, physics, - entityPhysicsInit, entityPhysicsDispose, NULL) -X(TRIGGER, entitytrigger_t, trigger, entityTriggerInit, NULL, NULL) + entityPhysicsInit, entityPhysicsDispose, NULL, + entityPhysicsSerialize, entityPhysicsDeserialize) +X(TRIGGER, entitytrigger_t, trigger, + entityTriggerInit, NULL, NULL, + entityTriggerSerialize, entityTriggerDeserialize)