Skip to content

Instantly share code, notes, and snippets.

@Lightnet
Last active October 20, 2025 22:15
Show Gist options
  • Save Lightnet/5bcce8d1c387aaed5dd6844852065fe0 to your computer and use it in GitHub Desktop.
Save Lightnet/5bcce8d1c387aaed5dd6844852065fe0 to your computer and use it in GitHub Desktop.
Sample refine Raylib, Flecs, and raygui for transform 3D hierarchy.

I kept it simple but with the raygui it easy to debug to control transform to update real time.

// Transform3D component
typedef struct {
    Vector3 position;         // Local position
    Quaternion rotation;      // Local rotation
    Vector3 scale;            // Local scale
    Matrix localMatrix;       // Local transform matrix
    Matrix worldMatrix;       // World transform matrix
    bool isDirty;             // Flag to indicate if transform needs updating
} Transform3D;
ECS_COMPONENT_DECLARE(Transform3D);

position, rotation and scale use for local or global if there none parent. It will translate to worldMatrix with isDirty if there is update. If there no update it will use worldMatrix for mesh render using the Matrix.

// flecs v4.1.1
// raylib 5.5
// raygui 4.0
// about 97 lines for transform 3d hierarchy functions.
#include <stdio.h>
#include "raylib.h"
#include "raymath.h"
#include "flecs.h"
#define RAYGUI_IMPLEMENTATION
#include "raygui.h"
// Transform3D component
typedef struct {
Vector3 position; // Local position
Quaternion rotation; // Local rotation
Vector3 scale; // Local scale
Matrix localMatrix; // Local transform matrix
Matrix worldMatrix; // World transform matrix
bool isDirty; // Flag to indicate if transform needs updating
} Transform3D;
ECS_COMPONENT_DECLARE(Transform3D);
// Pointer component for raylib Model
typedef struct {
Model* model; // Pointer to Model
} model_component_t;
ECS_COMPONENT_DECLARE(model_component_t);
typedef struct {
ecs_entity_t id; // Entity to edit (e.g., cube with CubeWire)
int selectedIndex; // Index of the selected entity in the list
// Later: Add fields for other GUI controls
} transform_3d_gui_t;
ECS_COMPONENT_DECLARE(transform_3d_gui_t);
typedef struct {
bool isMovementMode;
bool tabPressed;
bool moveForward;
bool moveBackward;
bool moveLeft;
bool moveRight;
} player_input_t;
ECS_COMPONENT_DECLARE(player_input_t);
typedef struct {
Camera3D camera;
} main_context_t;
ECS_COMPONENT_DECLARE(main_context_t);
void transform_3D_gui_list_system(ecs_iter_t *it);
// Helper function to update a single transform
void UpdateTransform(ecs_world_t *world, ecs_entity_t entity, Transform3D *transform) {
// Get parent entity
ecs_entity_t parent = ecs_get_parent(world, entity);
// const char *name = ecs_get_name(world, entity) ? ecs_get_name(world, entity) : "(unnamed)"; //check name for debug
bool parentIsDirty = false;
// Check if parent is dirty
if (parent && ecs_is_valid(world, parent)) {
const Transform3D *parent_transform = ecs_get(world, parent, Transform3D);
if (parent_transform && parent_transform->isDirty) {
parentIsDirty = true;
}
}
// Skip update if neither this transform nor its parent is dirty
if (!transform->isDirty && !parentIsDirty) {
// printf("Skipping update for %s (not dirty)\n", name);
return;
}
// Compute local transform
Matrix translation = MatrixTranslate(transform->position.x, transform->position.y, transform->position.z);
Matrix rotation = QuaternionToMatrix(transform->rotation);
Matrix scaling = MatrixScale(transform->scale.x, transform->scale.y, transform->scale.z);
transform->localMatrix = MatrixMultiply(scaling, MatrixMultiply(rotation, translation));
if (!parent || !ecs_is_valid(world, parent)) {
// Root entity: world matrix = local matrix
transform->worldMatrix = transform->localMatrix;
// printf("Root %s position (%.2f, %.2f, %.2f)\n", name, transform->position.x, transform->position.y, transform->position.z);
} else {
// Child entity: world matrix = local matrix * parent world matrix
const Transform3D *parent_transform = ecs_get(world, parent, Transform3D);
if (!parent_transform) {
// printf("Error: Parent %s lacks Transform3D for %s\n",
// ecs_get_name(world, parent) ? ecs_get_name(world, parent) : "(unnamed)", name);
transform->worldMatrix = transform->localMatrix;
return;
}
// Validate parent world matrix
float px = parent_transform->worldMatrix.m12;
float py = parent_transform->worldMatrix.m13;
float pz = parent_transform->worldMatrix.m14;
if (fabs(px) > 1e6 || fabs(py) > 1e6 || fabs(pz) > 1e6) {
// printf("Error: Invalid parent %s world pos (%.2f, %.2f, %.2f) for %s\n",
// ecs_get_name(world, parent) ? ecs_get_name(world, parent) : "(unnamed)",
// px, py, pz, name);
transform->worldMatrix = transform->localMatrix;
return;
}
// Compute world matrix
transform->worldMatrix = MatrixMultiply(transform->localMatrix, parent_transform->worldMatrix);
// Extract world position
float wx = transform->worldMatrix.m12;
float wy = transform->worldMatrix.m13;
float wz = transform->worldMatrix.m14;
// Debug output
// const char *parent_name = ecs_get_name(world, parent) ? ecs_get_name(world, parent) : "(unnamed)";
// printf("Child %s (ID: %llu), parent %s (ID: %llu)\n",
// name, (unsigned long long)entity, parent_name, (unsigned long long)parent);
// printf("Child %s position (%.2f, %.2f, %.2f), parent %s world pos (%.2f, %.2f, %.2f), world pos (%.2f, %.2f, %.2f)\n",
// name, transform->position.x, transform->position.y, transform->position.z,
// parent_name, px, py, pz, wx, wy, wz);
}
// Mark children as dirty to ensure they update in the next frame
ecs_iter_t it = ecs_children(world, entity);
while (ecs_children_next(&it)) {
for (int i = 0; i < it.count; i++) {
Transform3D *child_transform = ecs_get_mut(world, it.entities[i], Transform3D);
if (child_transform) {
child_transform->isDirty = true;
//ecs_set(world, it.entities[i], Transform3D, *child_transform);
}
}
}
// Reset isDirty after updating
transform->isDirty = false;
}
// Function to update a single entity and its descendants
void UpdateChildTransformOnly(ecs_world_t *world, ecs_entity_t entity) {
Transform3D *transform = ecs_get_mut(world, entity, Transform3D);
if (!transform) return;
// Update the entity's transform
UpdateTransform(world, entity, transform);
// ecs_modified(world, entity, Transform3D);
// Recursively update descendants
// note delay in update to sync.
// if comment this will delay sync since frame render tick.
ecs_iter_t it = ecs_children(world, entity);
while (ecs_children_next(&it)) {
for (int i = 0; i < it.count; i++) {
UpdateChildTransformOnly(world, it.entities[i]);
}
}
}
// System to process specific child entities (e.g., triggered by GUI)
void UpdateTransform3DSystem(ecs_iter_t *it) {
Transform3D *transform3d = ecs_field(it, Transform3D, 0);
for (int i = 0; i < it->count; i++) {
// ecs_entity_t entity = transform3d[i].id;
ecs_entity_t entity = it->entities[i];
if (ecs_is_valid(it->world, entity) && ecs_has(it->world, entity, Transform3D)) {
UpdateChildTransformOnly(it->world, entity);
}
}
}
// Render begin system
void RenderBeginSystem(ecs_iter_t *it) {
// printf("RenderBeginSystem\n");
BeginDrawing();
ClearBackground(RAYWHITE);
}
// Render begin system
void BeginCamera3DSystem(ecs_iter_t *it) {
// printf("BeginCamera3DSystem\n");
main_context_t *main_context = ecs_field(it, main_context_t, 0); // Singleton
if (!main_context) return;
BeginMode3D(main_context->camera);
}
// Camera3d system for 3d model
void Camera3DSystem(ecs_iter_t *it) {
Transform3D *t = ecs_field(it, Transform3D, 0);
model_component_t *m = ecs_field(it, model_component_t, 1);
for (int i = 0; i < it->count; i++) {
ecs_entity_t entity = it->entities[i];
if (!ecs_is_valid(it->world, entity)) {
//printf("Skipping invalid entity ID: %llu\n", (unsigned long long)entity);
continue;
}
const char *name = ecs_get_name(it->world, entity) ? ecs_get_name(it->world, entity) : "(unnamed)";
if (!ecs_has(it->world, entity, Transform3D) || !ecs_has(it->world, entity, model_component_t)) {
//printf("Skipping entity %s: missing Transform3D or model_component_t\n", name);
continue;
}
// Check for garbage values in world matrix
if (fabs(t[i].worldMatrix.m12) > 1e6 || fabs(t[i].worldMatrix.m13) > 1e6 || fabs(t[i].worldMatrix.m14) > 1e6) {
// printf("Skipping entity %s: invalid world matrix (%.2f, %.2f, %.2f)\n",
// name, t[i].worldMatrix.m12, t[i].worldMatrix.m13, t[i].worldMatrix.m14);
continue;
}
// printf("Rendering entity %s at world pos (%.2f, %.2f, %.2f)\n",
// name, t[i].worldMatrix.m12, t[i].worldMatrix.m13, t[i].worldMatrix.m14);
if (!m[i].model) {
// printf("Skipping entity %s: null model\n", name);
continue;
}
m[i].model->transform = t[i].worldMatrix;
bool isChild = ecs_has_pair(it->world, entity, EcsChildOf, EcsWildcard);
DrawModel(*(m[i].model), (Vector3){0, 0, 0}, 1.0f, isChild ? BLUE : RED);
}
DrawGrid(10, 1.0f);
// does not work here.
// Camera3D *camera = (Camera3D *)ecs_get_ctx(it->world);
// if (camera) {
// DrawText(TextFormat("Camera Pos: %.2f, %.2f, %.2f",
// camera->position.x, camera->position.y, camera->position.z), 10, 90, 20, DARKGRAY);
// DrawText(TextFormat("Entities Rendered: %d", it->count), 10, 110, 20, DARKGRAY);
// }
}
void EndCamera3DSystem(ecs_iter_t *it) {
//printf("EndCamera3DSystem\n");
main_context_t *main_context = ecs_field(it, main_context_t, 0); // Singleton
if (!main_context) return;
EndMode3D();
}
// Render end system
void EndRenderSystem(ecs_iter_t *it) {
//printf("EndRenderSystem\n");
EndDrawing();
}
// Input handling system
void user_input_system(ecs_iter_t *it) {
player_input_t *pi_ctx = ecs_field(it, player_input_t, 0);
transform_3d_gui_t *transform_3d_gui = ecs_field(it, transform_3d_gui_t, 1);
Transform3D *t = ecs_field(it, Transform3D, 2);
float dt = GetFrameTime();
//test user input
for (int i = 0; i < it->count; i++) {
const char *name = ecs_get_name(it->world, it->entities[i]);
if (name) {
bool isFound = false;
// if (strcmp(name, "NodeParent") == 0) {
if (it->entities[i] == transform_3d_gui->id) {
bool wasModified = false;
if (IsKeyPressed(KEY_TAB)) {
pi_ctx->isMovementMode = !pi_ctx->isMovementMode;
// printf("Toggled mode to: %s\n", pi_ctx->isMovementMode ? "Movement" : "Rotation");
}
if (pi_ctx->isMovementMode) {
if (IsKeyDown(KEY_W)){t[i].position.z -= 2.0f * dt;wasModified = true;}
if (IsKeyDown(KEY_S)){t[i].position.z += 2.0f * dt;wasModified = true;}
if (IsKeyDown(KEY_A)){t[i].position.x -= 2.0f * dt;wasModified = true;}
if (IsKeyDown(KEY_D)){t[i].position.x += 2.0f * dt;wasModified = true;}
} else {
float rotateSpeed = 90.0f;
if (IsKeyDown(KEY_Q)) {
Quaternion rot = QuaternionFromAxisAngle((Vector3){0, 1, 0}, DEG2RAD * rotateSpeed * dt);
t[i].rotation = QuaternionMultiply(t[i].rotation, rot);
wasModified = true;
}
if (IsKeyDown(KEY_E)) {
Quaternion rot = QuaternionFromAxisAngle((Vector3){0, 1, 0}, -DEG2RAD * rotateSpeed * dt);
t[i].rotation = QuaternionMultiply(t[i].rotation, rot);
wasModified = true;
}
if (IsKeyDown(KEY_W)) {
Quaternion rot = QuaternionFromAxisAngle((Vector3){1, 0, 0}, DEG2RAD * rotateSpeed * dt);
t[i].rotation = QuaternionMultiply(t[i].rotation, rot);
wasModified = true;
}
if (IsKeyDown(KEY_S)) {
Quaternion rot = QuaternionFromAxisAngle((Vector3){1, 0, 0}, -DEG2RAD * rotateSpeed * dt);
t[i].rotation = QuaternionMultiply(t[i].rotation, rot);
wasModified = true;
}
if (IsKeyDown(KEY_A)) {
Quaternion rot = QuaternionFromAxisAngle((Vector3){0, 0, 1}, DEG2RAD * rotateSpeed * dt);
t[i].rotation = QuaternionMultiply(t[i].rotation, rot);
wasModified = true;
}
if (IsKeyDown(KEY_D)) {
Quaternion rot = QuaternionFromAxisAngle((Vector3){0, 0, 1}, -DEG2RAD * rotateSpeed * dt);
t[i].rotation = QuaternionMultiply(t[i].rotation, rot);
wasModified = true;
}
}
if (IsKeyPressed(KEY_R)) {
t[i].position = (Vector3){0.0f, 0.0f, 0.0f};
t[i].rotation = QuaternionIdentity();
t[i].scale = (Vector3){1.0f, 1.0f, 1.0f};
wasModified = true;
}
if (wasModified) {
t[i].isDirty = true;
printf("Marked %s as dirty\n", name);
}
}
}
}
}
// render 2d
void render2d_hud_system(ecs_iter_t *it){
player_input_t *player_input = ecs_field(it, player_input_t, 0);
DrawText(player_input->isMovementMode ? "Mode: Movement (WASD)" : "Mode: Rotation (QWE/ASD)", 10, 30, 20, DARKGRAY);
DrawText("Tab: Toggle Mode | R: Reset", 10, 10, 20, DARKGRAY);
DrawFPS(10, 50);
ecs_query_t *query = ecs_query(it->world, {
.terms = {{ ecs_id(Transform3D) }}
});
int entity_count = 0;
ecs_iter_t transform_it = ecs_query_iter(it->world, query);
while (ecs_query_next(&transform_it)) {
entity_count += transform_it.count;
}
DrawText(TextFormat("Entity Count: %d", entity_count), 10, 70, 20, DARKGRAY);
transform_it = ecs_query_iter(it->world, query);
int row_count = 0;
// note this will not over lap in case entity transform 3d since it has parrent and child add together
// note the position is zero by default for some reason
while (ecs_query_next(&transform_it)) {
Transform3D *t = ecs_field(&transform_it, Transform3D, 0);
for (int j = 0; j < transform_it.count; j++) {
DrawText(TextFormat("Entity %s Pos: %.2f, %.2f, %.2f",
ecs_get_name(it->world, transform_it.entities[j]) ? ecs_get_name(it->world, transform_it.entities[j]) : "unnamed",
t[j].position.x, t[j].position.y, t[j].position.z), 10, 90 + row_count * 20, 20, DARKGRAY);
row_count++;
}
}
}
// main
int main(void) {
InitWindow(800, 600, "Transform Hierarchy with Flecs v4.x");
SetTargetFPS(60);
ecs_world_t *world = ecs_init();
ECS_COMPONENT_DEFINE(world, Transform3D);
ECS_COMPONENT_DEFINE(world, model_component_t);
ECS_COMPONENT_DEFINE(world, player_input_t);
ECS_COMPONENT_DEFINE(world, transform_3d_gui_t);
ECS_COMPONENT_DEFINE(world, main_context_t);
// Define custom phases
ecs_entity_t PreLogicUpdatePhase = ecs_new_w_id(world, EcsPhase);
ecs_entity_t LogicUpdatePhase = ecs_new_w_id(world, EcsPhase);
ecs_entity_t BeginRenderPhase = ecs_new_w_id(world, EcsPhase);
ecs_entity_t BeginCamera3DPhase = ecs_new_w_id(world, EcsPhase);
ecs_entity_t Camera3DPhase = ecs_new_w_id(world, EcsPhase);
ecs_entity_t EndCamera3DPhase = ecs_new_w_id(world, EcsPhase);
ecs_entity_t RenderPhase = ecs_new_w_id(world, EcsPhase);
ecs_entity_t EndRenderPhase = ecs_new_w_id(world, EcsPhase);
// order phase not doing correct
ecs_add_pair(world, PreLogicUpdatePhase, EcsDependsOn, EcsPreUpdate); // start game logics
ecs_add_pair(world, LogicUpdatePhase, EcsDependsOn, PreLogicUpdatePhase); // start game logics
ecs_add_pair(world, BeginRenderPhase, EcsDependsOn, LogicUpdatePhase); // BeginDrawing
ecs_add_pair(world, BeginCamera3DPhase, EcsDependsOn, BeginRenderPhase); // EcsOnUpdate, BeginMode3D
ecs_add_pair(world, Camera3DPhase, EcsDependsOn, BeginCamera3DPhase); // 3d model only
ecs_add_pair(world, EndCamera3DPhase, EcsDependsOn, Camera3DPhase); // EndMode3D
ecs_add_pair(world, RenderPhase, EcsDependsOn, EndCamera3DPhase); // 2D only
ecs_add_pair(world, EndRenderPhase, EcsDependsOn, RenderPhase); // render to screen
// user input
ecs_system(world, {
.entity = ecs_entity(world, { .name = "user_input_system", .add = ecs_ids(ecs_dependson(LogicUpdatePhase)) }),
.query.terms = {
{ .id = ecs_id(player_input_t), .src.id = ecs_id(player_input_t) }, // Singleton
{ .id = ecs_id(transform_3d_gui_t), .src.id = ecs_id(transform_3d_gui_t) }, // Singleton
{ .id = ecs_id(Transform3D), .src.id = EcsSelf },
},
.callback = user_input_system
});
//
ecs_system(world, {
.entity = ecs_entity(world, { .name = "render2d_hud_system", .add = ecs_ids(ecs_dependson(RenderPhase)) }),
.query.terms = {
{ .id = ecs_id(player_input_t), .src.id = ecs_id(player_input_t) } // Singleton
},
.callback = render2d_hud_system
});
//
ecs_system(world, {
.entity = ecs_entity(world, {
.name = "UpdateTransform3DSystem",
.add = ecs_ids(ecs_dependson(PreLogicUpdatePhase))
}),
.query.terms = {
{ .id = ecs_id(Transform3D), .src.id = EcsSelf }
},
.callback = UpdateTransform3DSystem
});
//
ecs_system(world,{
.entity = ecs_entity(world, { .name = "RenderBeginSystem",
.add = ecs_ids(ecs_dependson(BeginRenderPhase))
}),
.callback = RenderBeginSystem
});
//note this has be in order of the ECS since push into array. From I guess.
// begin camera 3d
ecs_system(world, {
.entity = ecs_entity(world, {
.name = "BeginCamera3DSystem",
.add = ecs_ids(ecs_dependson(BeginCamera3DPhase))
}),
.query.terms = {
{ .id = ecs_id(main_context_t), .src.id = ecs_id(main_context_t) } // Singleton
},
.callback = BeginCamera3DSystem
});
// render 3d mesh
ecs_system(world, {
.entity = ecs_entity(world, {
.name = "Camera3DSystem",
.add = ecs_ids(ecs_dependson(Camera3DPhase))
}),
.query.terms = {
{ .id = ecs_id(Transform3D), .src.id = EcsSelf },
{ .id = ecs_id(model_component_t), .src.id = EcsSelf }
},
.callback = Camera3DSystem
});
// end camera 3d
ecs_system(world, {
.entity = ecs_entity(world, {
.name = "EndCamera3DSystem",
.add = ecs_ids(ecs_dependson(EndCamera3DPhase))
}),
.query.terms = {
{ .id = ecs_id(main_context_t), .src.id = ecs_id(main_context_t) } // Singleton
},
.callback = EndCamera3DSystem
});
//
ecs_system(world, {
.entity = ecs_entity(world, {
.name = "EndRenderSystem",
.add = ecs_ids(ecs_dependson(EndRenderPhase))
}),
.callback = EndRenderSystem
});
// Register GUI list system in the 2D rendering phase
ecs_system(world, {
.entity = ecs_entity(world, { .name = "transform_3D_gui_list_system", .add = ecs_ids(ecs_dependson(LogicUpdatePhase)) }),
.query.terms = {
{ .id = ecs_id(transform_3d_gui_t), .src.id = ecs_id(transform_3d_gui_t) } // Singleton
},
.callback = transform_3D_gui_list_system
});
// camera
Camera3D camera = {
.position = (Vector3){10.0f, 10.0f, 10.0f},
.target = (Vector3){0.0f, 0.0f, 0.0f},
.up = (Vector3){0.0f, 1.0f, 0.0f},
.fovy = 45.0f,
.projection = CAMERA_PERSPECTIVE
};
ecs_singleton_set(world, main_context_t, {
.camera = camera
});
// player input
ecs_singleton_set(world, player_input_t, {
.isMovementMode=true,
.tabPressed=false
});
Model cube = LoadModelFromMesh(GenMeshCube(1.0f, 1.0f, 1.0f));
ecs_entity_t node1 = ecs_entity(world, {
.name = "NodeParent"
});
ecs_set(world, node1, Transform3D, {
.position = (Vector3){0.0f, 0.0f, 0.0f},
.rotation = QuaternionIdentity(),
.scale = (Vector3){1.0f, 1.0f, 1.0f},
.localMatrix = MatrixIdentity(),
.worldMatrix = MatrixIdentity(),
.isDirty = true
});
ecs_set(world, node1, model_component_t, {&cube});
// printf("Node1 entity ID: %llu (%s)\n", (unsigned long long)node1, ecs_get_name(world, node1));
// printf("- Node1 valid: %d, has Transform3D: %d\n", ecs_is_valid(world, node1), ecs_has(world, node1, Transform3D));
ecs_entity_t node2 = ecs_entity(world, {
.name = "NodeChild",
.parent = node1
});
ecs_set(world, node2, Transform3D, {
.position = (Vector3){2.0f, 0.0f, 0.0f},
.rotation = QuaternionIdentity(),
.scale = (Vector3){0.5f, 0.5f, 0.5f},
.localMatrix = MatrixIdentity(),
.worldMatrix = MatrixIdentity(),
.isDirty = true
});
ecs_set(world, node2, model_component_t, {&cube});
// printf("Node2 entity ID: %llu (%s)\n", (unsigned long long)node2, ecs_get_name(world, node2));
// printf("- Node2 valid: %d, has Transform3D: %d, parent: %s\n",
// ecs_is_valid(world, node2), ecs_has(world, node2, Transform3D),
// ecs_get_name(world, ecs_get_parent(world, node2)));
ecs_entity_t node3 = ecs_entity(world, {
.name = "Node3",
.parent = node1
});
ecs_set(world, node3, Transform3D, {
.position = (Vector3){2.0f, 0.0f, 2.0f},
.rotation = QuaternionIdentity(),
.scale = (Vector3){0.5f, 0.5f, 0.5f},
.localMatrix = MatrixIdentity(),
.worldMatrix = MatrixIdentity(),
.isDirty = true
});
ecs_set(world, node3, model_component_t, {&cube});
// printf("Node3 entity ID: %llu (%s)\n", (unsigned long long)node3, ecs_get_name(world, node3));
// printf("- Node3 valid: %d, has Transform3D: %d, parent: %s\n",
// ecs_is_valid(world, node3), ecs_has(world, node3, Transform3D),
// ecs_get_name(world, ecs_get_parent(world, node3)));
ecs_entity_t node4 = ecs_entity(world, {
.name = "NodeGrandchild",
.parent = node2
});
ecs_set(world, node4, Transform3D, {
.position = (Vector3){1.0f, 0.0f, 1.0f},
.rotation = QuaternionIdentity(),
.scale = (Vector3){0.5f, 0.5f, 0.5f},
.localMatrix = MatrixIdentity(),
.worldMatrix = MatrixIdentity(),
.isDirty = true
});
ecs_set(world, node4, model_component_t, {&cube});
// printf("Node4 entity ID: %llu (%s)\n", (unsigned long long)node4, ecs_get_name(world, node4));
// printf("- Node4 valid: %d, has Transform3D: %d, parent: %s\n",
// ecs_is_valid(world, node4), ecs_has(world, node4, Transform3D),
// ecs_get_name(world, ecs_get_parent(world, node4)));
ecs_entity_t node5 = ecs_entity(world, {
.name = "Node5"
});
ecs_set(world, node5, Transform3D, {
.position = (Vector3){1.0f, 0.0f, 1.0f},
.rotation = QuaternionIdentity(),
.scale = (Vector3){0.5f, 0.5f, 0.5f},
.localMatrix = MatrixIdentity(),
.worldMatrix = MatrixIdentity(),
.isDirty = true
});
ecs_set(world, node5, model_component_t, {&cube});
ecs_singleton_set(world, transform_3d_gui_t, {
.id = node1 // Reference the id entity
});
while (!WindowShouldClose()) {
ecs_progress(world, 0);
}
UnloadModel(cube);
ecs_fini(world);
CloseWindow();
return 0;
}
void transform_3D_gui_list_system(ecs_iter_t *it) {
transform_3d_gui_t *gui = ecs_field(it, transform_3d_gui_t, 0);
// Create a query for all entities with Transform3D
ecs_query_t *query = ecs_query(it->world, {
.terms = {{ ecs_id(Transform3D) }}
});
int entity_count = 0;
ecs_iter_t transform_it = ecs_query_iter(it->world, query);
while (ecs_query_next(&transform_it)) {
entity_count += transform_it.count;
}
// Allocate buffers for entity names and IDs
ecs_entity_t *entity_ids = (ecs_entity_t *)RL_MALLOC(entity_count * sizeof(ecs_entity_t));
char **entity_names = (char **)RL_MALLOC(entity_count * sizeof(char *));
int index = 0;
// Populate entity names and IDs
transform_it = ecs_query_iter(it->world, query);
while (ecs_query_next(&transform_it)) {
for (int j = 0; j < transform_it.count; j++) {
const char *name = ecs_get_name(it->world, transform_it.entities[j]);
entity_names[index] = (char *)RL_MALLOC(256 * sizeof(char));
snprintf(entity_names[index], 256, "%s", name ? name : "(unnamed)");
entity_ids[index] = transform_it.entities[j];
index++;
}
}
// Create a single string for GuiListView
char *name_list = (char *)RL_MALLOC(entity_count * 256 * sizeof(char));
name_list[0] = '\0';
for (int j = 0; j < entity_count; j++) {
if (j > 0) strcat(name_list, ";");
strcat(name_list, entity_names[j]);
}
// Draw the list view on the right side
Rectangle list_rect = {520, 10, 240, 200}; // Reduced height for more controls
int scroll_index = 0;
GuiListView(list_rect, name_list, &scroll_index, &gui->selectedIndex);
// Draw transform controls if an entity is selected
if (gui->selectedIndex >= 0 && gui->selectedIndex < entity_count && ecs_is_valid(it->world, entity_ids[gui->selectedIndex])) {
gui->id = entity_ids[gui->selectedIndex];
Transform3D *transform = ecs_get_mut(it->world, gui->id, Transform3D);
bool modified = false;
if (transform) {
Rectangle control_rect = {520, 220, 240, 360};
GuiGroupBox(control_rect, "Transform Controls");
// Position controls
GuiLabel((Rectangle){530, 230, 100, 20}, "Position");
float new_pos_x = transform->position.x;
float new_pos_y = transform->position.y;
float new_pos_z = transform->position.z;
GuiSlider((Rectangle){530, 250, 200, 20}, "X", TextFormat("%.2f", new_pos_x), &new_pos_x, -10.0f, 10.0f);
GuiSlider((Rectangle){530, 270, 200, 20}, "Y", TextFormat("%.2f", new_pos_y), &new_pos_y, -10.0f, 10.0f);
GuiSlider((Rectangle){530, 290, 200, 20}, "Z", TextFormat("%.2f", new_pos_z), &new_pos_z, -10.0f, 10.0f);
if (new_pos_x != transform->position.x || new_pos_y != transform->position.y || new_pos_z != transform->position.z) {
transform->position.x = new_pos_x;
transform->position.y = new_pos_y;
transform->position.z = new_pos_z;
modified = true;
}
// Rotation controls (using Euler angles)
GuiLabel((Rectangle){530, 310, 100, 20}, "Rotation");
Vector3 euler = QuaternionToEuler(transform->rotation);
euler.x = RAD2DEG * euler.x;
euler.y = RAD2DEG * euler.y;
euler.z = RAD2DEG * euler.z;
float new_rot_x = euler.x;
float new_rot_y = euler.y;
float new_rot_z = euler.z;
GuiSlider((Rectangle){530, 330, 200, 20}, "X", TextFormat("%.2f", new_rot_x), &new_rot_x, -180.0f, 180.0f);
GuiSlider((Rectangle){530, 350, 200, 20}, "Y", TextFormat("%.2f", new_rot_y), &new_rot_y, -180.0f, 180.0f);
GuiSlider((Rectangle){530, 370, 200, 20}, "Z", TextFormat("%.2f", new_rot_z), &new_rot_z, -180.0f, 180.0f);
if (new_rot_x != euler.x || new_rot_y != euler.y || new_rot_z != euler.z) {
transform->rotation = QuaternionFromEuler(DEG2RAD * new_rot_x, DEG2RAD * new_rot_y, DEG2RAD * new_rot_z);
modified = true;
}
// Scale controls
GuiLabel((Rectangle){530, 390, 100, 20}, "Scale");
float new_scale_x = transform->scale.x;
float new_scale_y = transform->scale.y;
float new_scale_z = transform->scale.z;
GuiSlider((Rectangle){530, 410, 200, 20}, "X", TextFormat("%.2f", new_scale_x), &new_scale_x, 0.1f, 5.0f);
GuiSlider((Rectangle){530, 430, 200, 20}, "Y", TextFormat("%.2f", new_scale_y), &new_scale_y, 0.1f, 5.0f);
GuiSlider((Rectangle){530, 450, 200, 20}, "Z", TextFormat("%.2f", new_scale_z), &new_scale_z, 0.1f, 5.0f);
if (new_scale_x != transform->scale.x || new_scale_y != transform->scale.y || new_scale_z != transform->scale.z) {
transform->scale.x = new_scale_x;
transform->scale.y = new_scale_y;
transform->scale.z = new_scale_z;
modified = true;
}
// Mark transform and descendants as dirty if modified
if (modified) {
transform->isDirty = true;
UpdateChildTransformOnly(it->world, gui->id); // Update child and descendants immediately
// ecs_modified(it->world, gui->id, Transform3D);
// Mark all descendants as dirty
ecs_iter_t child_it = ecs_children(it->world, gui->id);
while (ecs_children_next(&child_it)) {
for (int j = 0; j < child_it.count; j++) {
if (ecs_has(child_it.world, child_it.entities[j], Transform3D)) {
Transform3D *child_transform = ecs_get_mut(child_it.world, child_it.entities[j], Transform3D);
if (child_transform) {
child_transform->isDirty = true;
ecs_modified(child_it.world, child_it.entities[j], Transform3D);
}
}
}
}
}
}
}
GuiGroupBox(list_rect, "Entity List");
// Clean up
for (int j = 0; j < entity_count; j++) {
RL_FREE(entity_names[j]);
}
RL_FREE(entity_names);
RL_FREE(entity_ids);
RL_FREE(name_list);
ecs_query_fini(query);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment