Last active
September 26, 2025 22:51
-
-
Save Lightnet/e890c57293bc81712e7a7458ec97c0ce to your computer and use it in GitHub Desktop.
Sample raylib 5.5 flecs 4.1 raygui picking single cube test.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // raylib 5.5 | |
| // flecs v4.1 | |
| // https://github.com/SanderMertens/flecs/blob/master/examples/c/systems/custom_pipeline/src/main.c | |
| // right mouse toggle camera capture | |
| // W,A,S,D = move camera when capture mouse | |
| // escape to close app | |
| // left mouse button to select cube | |
| #include <stdio.h> | |
| #include <flecs.h> | |
| #include "raylib.h" | |
| #include <math.h> | |
| #define RAYGUI_IMPLEMENTATION | |
| #include "raygui.h" | |
| // component | |
| typedef struct { | |
| float x, y; | |
| } Position; | |
| typedef struct { | |
| float x, y; | |
| } Velocity; | |
| // Component definition: Matches DrawText params | |
| typedef struct { | |
| const char* text; | |
| int x; | |
| int y; | |
| int fontSize; | |
| Color color; | |
| } Text; | |
| typedef struct { | |
| Vector3 position; // Cube position | |
| float width; // Cube width | |
| float height; // Cube height | |
| float length; // Cube length | |
| Color color; // Cube color | |
| } CubeWire; | |
| ECS_COMPONENT_DECLARE(CubeWire); | |
| typedef struct { | |
| ecs_entity_t id; // Entity to edit (e.g., cube with CubeWire) | |
| // Later: Add fields for other GUI controls | |
| } TransformGUI; | |
| ECS_COMPONENT_DECLARE(TransformGUI); | |
| // Define the SceneContext struct | |
| typedef struct { | |
| Camera3D camera; | |
| // tmp physics; (assuming this is a placeholder, you can add actual physics data later) | |
| } SceneContext; | |
| //ray cast picking | |
| typedef struct { | |
| Ray ray; // Picking line ray | |
| RayCollision collision; // Ray collision hit info | |
| ecs_entity_t hit_entity; // Entity hit by the ray (0 if none) | |
| } PickingContext; | |
| ECS_COMPONENT_DECLARE(PickingContext); | |
| // Forward declare the system callback | |
| // raylib | |
| void RenderText(ecs_iter_t *it); | |
| void RenderCubeWire(ecs_iter_t *it); | |
| void UpdateCubeWire(ecs_iter_t *it); | |
| void GUI_Transform3D_System(ecs_iter_t *it); | |
| void PickingSystem(ecs_iter_t *it); | |
| void Camera_Input_Control(ecs_iter_t *it); | |
| // start render gl | |
| void RL_BeginDrawing_System(ecs_iter_t *it); | |
| void RL_Render2D0_System(ecs_iter_t *it);// by default render 2d | |
| // begin render 3d mode | |
| void RL_BeginMode3D_System(ecs_iter_t *it); | |
| void RL_Render3D_System(ecs_iter_t *it); | |
| void RL_EndMode3D_System(ecs_iter_t *it); | |
| // exit 3d mode to 2D mode. | |
| void RL_Render2D1_System(ecs_iter_t *it); | |
| // end render gl | |
| void RL_End_Draw_System(ecs_iter_t *it); | |
| Vector3 cubePosition; | |
| // main | |
| int main(void) | |
| { | |
| // Initialization | |
| const int screenWidth = 800; | |
| const int screenHeight = 450; | |
| cubePosition = (Vector3){ 0.0f, 0.0f, 0.0f }; | |
| // ecs world | |
| ecs_world_t *world = ecs_init(); | |
| // Define the Text component (Flecs 4.x: uses ecs_struct_init under the hood) | |
| ECS_COMPONENT(world, Position); | |
| ECS_COMPONENT(world, Velocity); | |
| ECS_COMPONENT(world, Text); | |
| // ECS_COMPONENT(world, CubeWire); //error? can't access from system esc_get(ecs,id, CubeWire) | |
| ECS_COMPONENT_DEFINE(world, CubeWire); // need access from system error | |
| // ECS_COMPONENT(world, TransformGUI); //error? | |
| ECS_COMPONENT_DEFINE(world, TransformGUI); | |
| ECS_COMPONENT_DEFINE(world, PickingContext); // Added PickingContext | |
| // Phases must have the EcsPhase tag | |
| ecs_entity_t RLPHASE_BEGINDRAWING = ecs_new_w_id(world, EcsPhase); // RL BeginDrawing(); | |
| ecs_entity_t RLPHASE_RENDER2D0 = ecs_new_w_id(world, EcsPhase); // RL for gl background color | |
| ecs_entity_t RLPHASE_BEGINMODE3D = ecs_new_w_id(world, EcsPhase); // BeginMode3D(camera); | |
| ecs_entity_t RLPHASE_RENDER3D = ecs_new_w_id(world, EcsPhase); // Draw 3D ex. DrawGrid(10, 1.0f); | |
| ecs_entity_t RLPHASE_ENDMODE3D = ecs_new_w_id(world, EcsPhase); // EndMode3D(); | |
| ecs_entity_t RLPHASE_RENDER2D1 = ecs_new_w_id(world, EcsPhase); // Draw 2D | |
| ecs_entity_t RLPHASE_ENDRAWING = ecs_new_w_id(world, EcsPhase); // EndDrawing(); | |
| // Phases can (but don't have to) depend on other phases which forces ordering | |
| ecs_add_pair(world, RLPHASE_BEGINDRAWING, EcsDependsOn, EcsOnUpdate); | |
| ecs_add_pair(world, RLPHASE_RENDER2D0, EcsDependsOn, RLPHASE_BEGINDRAWING); | |
| ecs_add_pair(world, RLPHASE_BEGINMODE3D, EcsDependsOn, RLPHASE_RENDER2D0); | |
| ecs_add_pair(world, RLPHASE_RENDER3D, EcsDependsOn, RLPHASE_BEGINMODE3D); | |
| ecs_add_pair(world, RLPHASE_ENDMODE3D, EcsDependsOn, RLPHASE_RENDER3D); | |
| ecs_add_pair(world, RLPHASE_RENDER2D1, EcsDependsOn, RLPHASE_ENDMODE3D); | |
| ecs_add_pair(world, RLPHASE_ENDRAWING, EcsDependsOn, RLPHASE_RENDER2D1); | |
| // start gl | |
| ecs_system(world, { | |
| .entity = ecs_entity(world, { | |
| .name = "RLBeginDrawingSystem", | |
| .add = ecs_ids( ecs_dependson(RLPHASE_BEGINDRAWING) ) | |
| }), | |
| .callback = RL_BeginDrawing_System, | |
| }); | |
| // gl 2d for background color | |
| ecs_system(world, { | |
| .entity = ecs_entity(world, { | |
| .name = "RLRender2D0System", | |
| .add = ecs_ids( ecs_dependson(RLPHASE_RENDER2D0) ) | |
| }), | |
| .callback = RL_Render2D0_System, | |
| }); | |
| // start mode 3d | |
| ecs_system(world, { | |
| .entity = ecs_entity(world, { | |
| .name = "RLBeginMode3DSystem", | |
| .add = ecs_ids( ecs_dependson(RLPHASE_BEGINMODE3D) ) | |
| }), | |
| .callback = RL_BeginMode3D_System, | |
| }); | |
| // render 3d | |
| ecs_system(world, { | |
| .entity = ecs_entity(world, { | |
| .name = "RLRender3DSystem", | |
| .add = ecs_ids( ecs_dependson(RLPHASE_RENDER3D) ) | |
| }), | |
| .callback = RL_Render3D_System, | |
| }); | |
| // end mode 3d | |
| ecs_system(world, { | |
| .entity = ecs_entity(world, { | |
| .name = "RLEndMode3DSystem", | |
| .add = ecs_ids( ecs_dependson(RLPHASE_ENDMODE3D) ) | |
| }), | |
| .callback = RL_EndMode3D_System, | |
| }); | |
| // render 2D | |
| ecs_system(world, { | |
| .entity = ecs_entity(world, { | |
| .name = "RLRender2DSystem", | |
| .add = ecs_ids( ecs_dependson(RLPHASE_RENDER2D1) ) | |
| }), | |
| .callback = RL_Render2D1_System, | |
| }); | |
| // end gl | |
| ecs_system(world, { | |
| .entity = ecs_entity(world, { | |
| .name = "RLEndDrawingSystem", | |
| .add = ecs_ids( ecs_dependson(RLPHASE_ENDRAWING) ) | |
| }), | |
| .callback = RL_End_Draw_System, | |
| }); | |
| // Setup RenderText system: Runs every frame, queries Text entities | |
| // Flecs 4.x: Builder pattern for query | |
| ECS_SYSTEM(world, RenderText, RLPHASE_RENDER2D1, Text); | |
| // Define the RenderCubeWire system to run in the 3D rendering phase | |
| ECS_SYSTEM(world, RenderCubeWire, RLPHASE_RENDER3D, CubeWire); | |
| //ECS_SYSTEM(world, UpdateCubeWire, EcsOnUpdate, CubeWire);// test | |
| // Register GUI system in the 2D rendering phase | |
| ECS_SYSTEM(world, GUI_Transform3D_System, RLPHASE_RENDER2D1, TransformGUI); | |
| // Register PickingSystem in EcsOnUpdate phase to handle input | |
| ECS_SYSTEM(world, PickingSystem, RLPHASE_RENDER3D, PickingContext); | |
| // Input | |
| ECS_SYSTEM(world, Camera_Input_Control, RLPHASE_RENDER3D); | |
| // Create entity and add component with params from DrawText | |
| // (x=190, y=200, fontSize=20; text and color as literals) | |
| ecs_entity_t uiText = ecs_new(world); | |
| ecs_set_name(world, uiText, "ui_text"); // Optional: Name for debugging | |
| ecs_set(world, uiText, Text, { | |
| .text = "Congrats! You created your first window!", | |
| .x = 10, | |
| .y = 10, | |
| .fontSize = 20, | |
| // .color = LIGHTGRAY // Raylib predefined color | |
| .color = RED // Raylib predefined color | |
| }); | |
| // entity wire cube | |
| ecs_entity_t cube = ecs_new(world); | |
| ecs_set_name(world, cube, "cube"); // Optional: Name for debugging | |
| ecs_set(world, cube, CubeWire, { | |
| .position = (Vector3){0.0f, 0.0f, 0.0f}, | |
| .width = 2.0f, | |
| .height = 2.0f, | |
| .length = 2.0f, | |
| .color = GREEN | |
| }); | |
| ecs_entity_t cube2 = ecs_new(world); | |
| ecs_set_name(world, cube2, "cube2"); // Optional: Name for debugging | |
| ecs_set(world, cube2, CubeWire, { | |
| .position = (Vector3){-4.0f, 0.0f, 0.0f}, | |
| .width = 2.0f, | |
| .height = 2.0f, | |
| .length = 2.0f, | |
| .color = GREEN | |
| }); | |
| ecs_entity_t gui = ecs_new(world); | |
| ecs_set_name(world, gui, "transform_gui"); // Optional: Name for debugging | |
| ecs_set(world, gui, TransformGUI, { | |
| .id = cube // Reference the cube entity | |
| }); | |
| // Create PickingContext entity | |
| ecs_entity_t picking = ecs_new(world); | |
| ecs_set_name(world, picking, "picking_context"); | |
| ecs_set(world, picking, PickingContext, { | |
| .ray = {0}, | |
| .collision = {0}, | |
| .hit_entity = 0 | |
| }); | |
| printf("Cube ID %llu: No CubeWire component found\n", (unsigned long long)cube); | |
| // Initialize SceneContext and set it as the ECS context | |
| SceneContext sceneContext = { | |
| .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 | |
| } | |
| // Initialize physics or other fields here if needed | |
| }; | |
| // Set SceneContext as the ECS world context | |
| ecs_set_ctx(world, &sceneContext, NULL); | |
| // raylib set up | |
| InitWindow(screenWidth, screenHeight, "raylib flecs v4.1 camera cube"); | |
| SetTargetFPS(60); // Set our game to run at 60 frames-per-second | |
| // Main game loop | |
| while (!WindowShouldClose()) // Detect window close button or ESC key | |
| { | |
| // BeginDrawing(); | |
| // ClearBackground(RAYWHITE); | |
| // DrawText("Congrats! You created your first window!", 190, 200, 20, LIGHTGRAY); | |
| // ecs_progress(world, 0); | |
| // EndDrawing(); | |
| ecs_progress(world, 0); | |
| } | |
| // De-Initialization | |
| ecs_fini(world); | |
| CloseWindow(); // Close window and OpenGL context | |
| return 0; | |
| } | |
| // render gl | |
| void RL_BeginDrawing_System(ecs_iter_t *it){ | |
| // printf("draw\n"); | |
| BeginDrawing(); | |
| } | |
| // render 2d | |
| void RL_Render2D0_System(ecs_iter_t *it){ | |
| // background color | |
| ClearBackground(RAYWHITE); | |
| } | |
| // start mode 3d | |
| void RL_BeginMode3D_System(ecs_iter_t *it){ | |
| // printf("RL_BeginMode3D_System\n"); | |
| // Retrieve the SceneContext from the ECS world | |
| SceneContext *sceneContext = (SceneContext *)ecs_get_ctx(it->world); | |
| if (!sceneContext) return; | |
| // printf("RL_BeginMode3D_System\n"); | |
| // Access the Camera3D from SceneContext | |
| Camera3D *camera = &sceneContext->camera; | |
| BeginMode3D(*camera); | |
| } | |
| // render 3d | |
| // Updated RL_Render3D_System to draw the debug ray | |
| void RL_Render3D_System(ecs_iter_t *it){ | |
| // printf("RL_Render3D_System\n"); | |
| // DrawCubeWires(cubePosition, 2.0f, 2.0f, 2.0f, MAROON); | |
| DrawGrid(10, 1.0f); | |
| // Draw debug ray if a hit occurred | |
| ecs_entity_t picking_entity = ecs_lookup(it->world, "picking_context"); | |
| if (picking_entity && ecs_is_valid(it->world, picking_entity)) { | |
| const PickingContext *picking = ecs_get(it->world, picking_entity, PickingContext); | |
| if (picking && picking->hit_entity) { | |
| DrawRay(picking->ray, MAROON); | |
| // printf("draw test\n"); | |
| } | |
| } | |
| } | |
| // end mode 3D | |
| void RL_EndMode3D_System(ecs_iter_t *it){ | |
| // printf("RL_EndMode3D_System\n"); | |
| // Retrieve the SceneContext from the ECS world | |
| SceneContext *sceneContext = (SceneContext *)ecs_get_ctx(it->world); | |
| if (!sceneContext) return; | |
| // printf("RL_EndMode3D_System\n"); | |
| // End the 3D rendering mode | |
| EndMode3D(); | |
| } | |
| // render 2d | |
| void RL_Render2D1_System(ecs_iter_t *it){ | |
| // ClearBackground(RAYWHITE); | |
| // DrawText("Congrats! You created your first window!", 190, 200, 20, LIGHTGRAY); | |
| // DrawText("Congrats! You created your first window!", 10, 30, 20, LIGHTGRAY); | |
| } | |
| // end gl | |
| void RL_End_Draw_System(ecs_iter_t *it){ | |
| EndDrawing(); | |
| } | |
| // right click mouse toggle capture | |
| // | |
| void Camera_Input_Control(ecs_iter_t *it){ | |
| SceneContext *sceneContext = (SceneContext *)ecs_get_ctx(it->world); | |
| if (!sceneContext) return; | |
| Camera3D *camera = &sceneContext->camera; | |
| // if (IsCursorHidden()) UpdateCamera(&camera, CAMERA_FIRST_PERSON); | |
| if (IsCursorHidden()) UpdateCamera(camera, CAMERA_FIRST_PERSON); | |
| // Toggle camera controls | |
| if (IsMouseButtonPressed(MOUSE_BUTTON_RIGHT)) | |
| { | |
| if (IsCursorHidden()) EnableCursor(); | |
| else DisableCursor(); | |
| } | |
| } | |
| // picking for raycast | |
| void PickingSystem(ecs_iter_t *it) { | |
| SceneContext *sceneContext = (SceneContext *)ecs_get_ctx(it->world); | |
| if (!sceneContext) return; | |
| Camera3D *camera = &sceneContext->camera; | |
| PickingContext *picking = ecs_field(it, PickingContext, 0); | |
| // Reset hit entity | |
| // for (int i = 0; i < it->count; i++) { | |
| // picking[i].hit_entity = 0; //need to debug draw ray | |
| // } | |
| // Find the TransformGUI entity (assuming single entity named "transform_gui") | |
| ecs_entity_t gui_entity = ecs_lookup(it->world, "transform_gui"); | |
| if (!gui_entity || !ecs_is_valid(it->world, gui_entity)) { | |
| printf("TransformGUI entity not found or invalid\n"); | |
| return; | |
| } | |
| // Check for mouse click | |
| if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) { | |
| // Get ray from mouse position | |
| Ray ray = GetScreenToWorldRay(GetMousePosition(), *camera); | |
| // Create a query for all entities with CubeWire | |
| ecs_query_t *query = ecs_query(it->world, { | |
| .terms = {{ ecs_id(CubeWire) }} | |
| }); | |
| ecs_iter_t qit = ecs_query_iter(it->world, query); | |
| while (ecs_query_next(&qit)) { | |
| CubeWire *cubes = ecs_field(&qit, CubeWire, 0); | |
| for (int i = 0; i < qit.count; i++) { | |
| CubeWire *cube = &cubes[i]; | |
| // Calculate bounding box for the cube | |
| BoundingBox box = { | |
| .min = (Vector3){ | |
| cube->position.x - cube->width / 2, | |
| cube->position.y - cube->height / 2, | |
| cube->position.z - cube->length / 2 | |
| }, | |
| .max = (Vector3){ | |
| cube->position.x + cube->width / 2, | |
| cube->position.y + cube->height / 2, | |
| cube->position.z + cube->length / 2 | |
| } | |
| }; | |
| // Check ray collision with the cube | |
| RayCollision collision = GetRayCollisionBox(ray, box); | |
| if (collision.hit) { | |
| // Store the hit entity and collision info | |
| picking[0].hit_entity = qit.entities[i]; | |
| picking[0].collision = collision; | |
| picking[0].ray = ray; | |
| // Change cube color to indicate selection | |
| cube->color = RED; | |
| ecs_modified_id(it->world, qit.entities[i], ecs_id(CubeWire)); | |
| // Update TransformGUI with the selected entity | |
| TransformGUI *gui = ecs_get_mut(it->world, gui_entity, TransformGUI); | |
| if (gui) { | |
| gui->id = qit.entities[i]; // Set to hit entity or 0 if no hit | |
| ecs_modified_id(it->world, gui_entity, ecs_id(TransformGUI)); | |
| } | |
| } else { | |
| // Reset color if not hit | |
| // cube->color = MAROON; | |
| cube->color = GREEN; | |
| ecs_modified_id(it->world, qit.entities[i], ecs_id(CubeWire)); | |
| } | |
| } | |
| } | |
| ecs_query_fini(query); | |
| } | |
| // Optional: Draw the ray for debugging | |
| if (picking[0].hit_entity) { | |
| // Ensure we're in 3D mode for drawing the ray | |
| // Note: This should ideally be in the Render3D phase, but for simplicity, we handle it here | |
| DrawRay(picking[0].ray, MAROON); | |
| } | |
| } | |
| // System callback: Iterate entities with Text and draw | |
| void RenderText(ecs_iter_t *it) { | |
| Text *texts = ecs_field(it, Text, 0); // Field index 0 (after implicit 'this' entity) | |
| for (int i = 0; i < it->count; i++) { | |
| Text *t = &texts[i]; | |
| DrawText(t->text, t->x, t->y, t->fontSize, t->color); | |
| } | |
| } | |
| // wireframe cube | |
| void RenderCubeWire(ecs_iter_t *it) { | |
| CubeWire *cubes = ecs_field(it, CubeWire, 0); // Field index 0 | |
| for (int i = 0; i < it->count; i++) { | |
| CubeWire *c = &cubes[i]; | |
| DrawCubeWires(c->position, c->width, c->height, c->length, c->color); | |
| } | |
| } | |
| // test update postion and color | |
| void UpdateCubeWire(ecs_iter_t *it) { | |
| CubeWire *cubes = ecs_field(it, CubeWire, 0); | |
| float delta = GetFrameTime(); // raylib function for frame time | |
| for (int i = 0; i < it->count; i++) { | |
| CubeWire *c = &cubes[i]; | |
| // Example: Move cube along x-axis | |
| c->position.x += 1.0f * delta; // Move 1 unit per second | |
| if (c->position.x > 5.0f) c->position.x = -5.0f; // Loop back | |
| // Example: Change color over time | |
| c->color.r = (unsigned char)(sinf(GetTime()) * 127.0f + 127.0f); | |
| } | |
| } | |
| // GUI transform x,y,z | |
| void GUI_Transform3D_System(ecs_iter_t *it) { | |
| TransformGUI *guis = ecs_field(it, TransformGUI, 0); // Field index 0 | |
| for (int i = 0; i < it->count; i++) { | |
| TransformGUI *gui = &guis[i]; | |
| // Check if the referenced entity exists | |
| if (!ecs_is_valid(it->world, gui->id)) { | |
| // Disable GUI if entity doesn’t exist | |
| continue; | |
| } | |
| // Get the CubeWire component of the referenced entity | |
| CubeWire *cube = ecs_get_mut(it->world, gui->id, CubeWire); | |
| if (cube) { | |
| // printf("cube\n"); | |
| // Draw raygui controls for x, y, z | |
| Rectangle panel = {10, 50, 200, 120}; // GUI panel position and size | |
| GuiGroupBox(panel, "Transform Controls"); | |
| // Temporary variables for sliders | |
| float x = cube->position.x; | |
| float y = cube->position.y; | |
| float z = cube->position.z; | |
| // Sliders for x, y, z (range: -10 to 10) | |
| GuiLabel((Rectangle){20, 70, 50, 20}, "X:"); | |
| GuiSlider((Rectangle){60, 70, 130, 20}, NULL, NULL, &x, -10.0f, 10.0f); | |
| GuiLabel((Rectangle){20, 100, 50, 20}, "Y:"); | |
| GuiSlider((Rectangle){60, 100, 130, 20}, NULL, NULL, &y, -10.0f, 10.0f); | |
| GuiLabel((Rectangle){20, 130, 50, 20}, "Z:"); | |
| GuiSlider((Rectangle){60, 130, 130, 20}, NULL, NULL, &z, -10.0f, 10.0f); | |
| // Update the CubeWire position if changed | |
| if (x != cube->position.x || y != cube->position.y || z != cube->position.z) { | |
| cube->position = (Vector3){x, y, z}; | |
| ecs_modified_id(it->world, gui->id, ecs_id(CubeWire)); | |
| } | |
| } | |
| } | |
| } | |
| // |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment