Skip to content

Instantly share code, notes, and snippets.

@Lightnet
Created March 26, 2025 06:13
Show Gist options
  • Save Lightnet/2daa6b7ee1570619cb0fadf2558b0946 to your computer and use it in GitHub Desktop.
Save Lightnet/2daa6b7ee1570619cb0fadf2558b0946 to your computer and use it in GitHub Desktop.
raylib flecs render ode test
cmake_minimum_required(VERSION 3.14)
project(raylib_luajit LANGUAGES C CXX)
# Set up cache directory for built artifacts
set(CACHE_DIR "${CMAKE_BINARY_DIR}/cache")
file(MAKE_DIRECTORY ${CACHE_DIR})
if(NOT EXISTS "${CMAKE_BINARY_DIR}/cmake/CPM.cmake")
file(DOWNLOAD
"https://github.com/cpm-cmake/CPM.cmake/releases/latest/download/CPM.cmake"
"${CMAKE_BINARY_DIR}/cmake/CPM.cmake"
)
endif()
include(${CMAKE_BINARY_DIR}/cmake/CPM.cmake)
# ODE (Open Dynamics Engine)
# Fetch ODE 0.16.6
# does not work with CPMAddPackage
# this need be here after raylib else error on conflict vars.
FetchContent_Declare(
ode
GIT_REPOSITORY https://bitbucket.org/odedevs/ode.git
GIT_TAG 0.16.6
)
set(ODE_DOUBLE_PRECISION OFF CACHE BOOL "Use single precision" FORCE)
set(ODE_BUILD_TESTS OFF CACHE BOOL "Disable ODE tests" FORCE)
set(ODE_WITH_DEMOS OFF CACHE BOOL "Disable ODE demos" FORCE)
set(ODE_WITH_GIMPACT OFF CACHE BOOL "Disable GIMPACT" FORCE)
set(ODE_WITH_OPCODE OFF CACHE BOOL "Use simpler collision" FORCE)
FetchContent_MakeAvailable(ode)
# # Manually generate precision.h from precision.h.in
# set(ODE_PRECISION "float") # Matches ODE_DOUBLE_PRECISION OFF
# configure_file(
# "${ode_SOURCE_DIR}/include/ode/precision.h.in"
# "${ode_BINARY_DIR}/include/ode/precision.h"
# @ONLY
# )
# Debug ODE configuration
# message(STATUS "ODE_SOURCE_DIR: ${ode_SOURCE_DIR}")
# message(STATUS "ODE_BINARY_DIR: ${ode_BINARY_DIR}")
# file(GLOB ODE_HEADERS "${ode_SOURCE_DIR}/include/ode/*.h")
# message(STATUS "ODE Headers in source: ${ODE_HEADERS}")
# file(GLOB ODE_BUILD_HEADERS "${ode_BINARY_DIR}/include/ode/*.h")
# message(STATUS "ODE Headers in build: ${ODE_BUILD_HEADERS}")
# raylib
CPMAddPackage(
NAME raylib
GITHUB_REPOSITORY raysan5/raylib
GIT_TAG 5.5
OPTIONS
"BUILD_EXAMPLES OFF"
"BUILD_GAMES OFF"
"BUILD_SHARED_LIBS ON"
)
# raygui
CPMAddPackage(
NAME raygui
GITHUB_REPOSITORY raysan5/raygui
GIT_TAG 4.0
DOWNLOAD_ONLY YES
)
# flecs
CPMAddPackage(
NAME flecs
GITHUB_REPOSITORY SanderMertens/flecs
GIT_TAG v4.0.5
OPTIONS
"FLECS_STATIC OFF"
"FLECS_SHARED ON"
)
# mimalloc
CPMAddPackage(
NAME mimalloc
GITHUB_REPOSITORY microsoft/mimalloc
GIT_TAG v2.2.2
OPTIONS
"MI_BUILD_TESTS OFF"
"MI_BUILD_EXAMPLES OFF"
"MI_BUILD_SHARED ON"
)
# Cache and configure DLLs
# raylib.dll
set(RAYLIB_DLL_SRC "${raylib_BINARY_DIR}/raylib/$<CONFIG>/raylib.dll")
set(RAYLIB_DLL "${CACHE_DIR}/raylib.dll")
add_custom_command(
OUTPUT ${RAYLIB_DLL}
COMMAND ${CMAKE_COMMAND} -E echo "Copying raylib.dll from ${RAYLIB_DLL_SRC} to ${RAYLIB_DLL}"
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${RAYLIB_DLL_SRC} ${RAYLIB_DLL}
DEPENDS raylib
COMMENT "Caching raylib.dll"
)
add_custom_target(raylib_cache DEPENDS ${RAYLIB_DLL})
# flecs.dll
set(FLECS_DLL_SRC "${flecs_BINARY_DIR}/$<CONFIG>/flecs.dll")
set(FLECS_DLL "${CACHE_DIR}/flecs.dll")
add_custom_command(
OUTPUT ${FLECS_DLL}
COMMAND ${CMAKE_COMMAND} -E echo "Copying flecs.dll from ${FLECS_DLL_SRC} to ${FLECS_DLL}"
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${FLECS_DLL_SRC} ${FLECS_DLL}
DEPENDS flecs
COMMENT "Caching flecs.dll"
)
add_custom_target(flecs_cache DEPENDS ${FLECS_DLL})
# mimalloc.dll
set(MIMALLOC_DLL_SRC "${mimalloc_BINARY_DIR}/$<CONFIG>/mimalloc-debug.dll")
set(MIMALLOC_DLL "${CACHE_DIR}/mimalloc-debug.dll")
add_custom_command(
OUTPUT ${MIMALLOC_DLL}
COMMAND ${CMAKE_COMMAND} -E echo "Copying mimalloc-debug.dll from ${MIMALLOC_DLL_SRC} to ${MIMALLOC_DLL}"
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${MIMALLOC_DLL_SRC} ${MIMALLOC_DLL}
DEPENDS mimalloc
COMMENT "Caching mimalloc-debug.dll"
)
add_custom_target(mimalloc_cache DEPENDS ${MIMALLOC_DLL})
# ode_singled.dll
set(ODE_DLL_SRC "${ode_BINARY_DIR}/$<CONFIG>/ode_singled.dll")
set(ODE_DLL "${CACHE_DIR}/ode_singled.dll")
add_custom_command(
OUTPUT ${ODE_DLL}
COMMAND ${CMAKE_COMMAND} -E echo "Copying ode_singled.dll from ${ODE_DLL_SRC} to ${ODE_DLL}"
COMMAND ${CMAKE_COMMAND} -E copy_if_different ${ODE_DLL_SRC} ${ODE_DLL}
DEPENDS ode
COMMENT "Caching ode_singled.dll"
)
add_custom_target(ode_cache DEPENDS ${ODE_DLL})
# MAIN
add_executable(${PROJECT_NAME}
src/raylib_flecs_render.c
# src/raylib_ode01.c
)
# Dependencies for caching
add_dependencies(${PROJECT_NAME} raylib_cache flecs_cache mimalloc_cache ode_cache)
# Include directories
target_include_directories(${PROJECT_NAME} PRIVATE
${CMAKE_SOURCE_DIR}/include
${raylib_SOURCE_DIR}/src
${flecs_SOURCE_DIR}/include
${mimalloc_SOURCE_DIR}/include
${ode_SOURCE_DIR}/include
${ode_BINARY_DIR}/include # For generated precision.h
)
# Define dSINGLE for single precision
target_compile_definitions(${PROJECT_NAME} PRIVATE dSINGLE)
# Link libraries
target_link_libraries(${PROJECT_NAME} PRIVATE
raylib
flecs
mimalloc
#ode
${ode_BINARY_DIR}/Debug/ode_singled.lib
)
if(WIN32)
target_link_libraries(${PROJECT_NAME} PRIVATE ws2_32 winmm)
endif()
# Copy DLLs to output directory
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${RAYLIB_DLL}"
"$<TARGET_FILE_DIR:${PROJECT_NAME}>/raylib.dll"
COMMENT "Copying raylib.dll to output directory"
)
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${ODE_DLL}"
"$<TARGET_FILE_DIR:${PROJECT_NAME}>/ode_singled.dll"
COMMENT "Copying ode_singled.dll to output directory"
)
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${FLECS_DLL}"
"$<TARGET_FILE_DIR:${PROJECT_NAME}>/flecs.dll"
COMMENT "Copying flecs.dll to output directory"
)
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_if_different
"${MIMALLOC_DLL}"
"$<TARGET_FILE_DIR:${PROJECT_NAME}>/mimalloc-debug.dll"
COMMENT "Copying mimalloc-debug.dll to output directory"
)
#include <stdio.h>
#include <math.h>
#include "raylib.h"
#include "raymath.h"
#include "ode/ode.h"
#include "flecs.h"
// ODEPhysics struct to encapsulate ODE globals
typedef struct {
dWorldID ode_world;
dSpaceID ode_space;
dJointGroupID ode_joint_group;
} ODEPhysics;
// WorldContext struct to hold both ODEPhysics and Camera3D
typedef struct {
ODEPhysics *physics;
Camera3D *camera;
} WorldContext;
// Transform3D component
typedef struct {
Vector3 position;
Quaternion rotation;
Vector3 scale;
Matrix localMatrix;
Matrix worldMatrix;
} Transform3D;
ECS_COMPONENT_DECLARE(Transform3D);
// Pointer component for raylib Model
typedef struct {
Model* model;
} ModelComponent;
ECS_COMPONENT_DECLARE(ModelComponent);
// PhysicsBody component for ODE
typedef struct {
dBodyID body;
dGeomID geom;
dMass mass;
} PhysicsBody;
ECS_COMPONENT_DECLARE(PhysicsBody);
// Render begin system
void RenderBeginSystem(ecs_iter_t *it) {
BeginDrawing();
ClearBackground(RAYWHITE);
}
// Begin camera system
void BeginCameraSystem(ecs_iter_t *it) {
WorldContext *ctx = (WorldContext *)ecs_get_ctx(it->world);
if (!ctx || !ctx->camera) return;
BeginMode3D(*(ctx->camera));
}
// End camera system
void EndCameraSystem(ecs_iter_t *it) {
WorldContext *ctx = (WorldContext *)ecs_get_ctx(it->world);
if (!ctx || !ctx->camera) return;
EndMode3D();
}
// Render system
void RenderSystem(ecs_iter_t *it) {
Transform3D *t = ecs_field(it, Transform3D, 0);
ModelComponent *m = ecs_field(it, ModelComponent, 1);
for (int i = 0; i < it->count; i++) {
if (!ecs_is_valid(it->world, it->entities[i]) || !m[i].model) continue;
m[i].model->transform = t[i].worldMatrix;
bool isChild = ecs_has_pair(it->world, it->entities[i], EcsChildOf, EcsWildcard);
DrawModel(*(m[i].model), (Vector3){0, 0, 0}, 1.0f, isChild ? BLUE : RED);
}
}
// Collision callback
static void nearCallback(void *data, dGeomID o1, dGeomID o2) {
ODEPhysics *physics = (ODEPhysics *)data;
dBodyID b1 = dGeomGetBody(o1);
dBodyID b2 = dGeomGetBody(o2);
if (b1 && b2 && dAreConnected(b1, b2)) return;
dContact contact[1];
contact[0].surface.mode = dContactBounce | dContactSoftCFM;
contact[0].surface.mu = dInfinity;
contact[0].surface.bounce = 0.5f;
contact[0].surface.bounce_vel = 0.1f;
contact[0].surface.soft_cfm = 0.01f;
if (dCollide(o1, o2, 1, &contact[0].geom, sizeof(dContact))) {
dJointID c = dJointCreateContact(physics->ode_world, physics->ode_joint_group, &contact[0]);
dJointAttach(c, b1, b2);
}
}
// Physics system
void PhysicsSystem(ecs_iter_t *it) {
Transform3D *t = ecs_field(it, Transform3D, 0);
PhysicsBody *p = ecs_field(it, PhysicsBody, 1);
WorldContext *ctx = (WorldContext *)ecs_get_ctx(it->world);
if (!ctx || !ctx->physics || !ctx->physics->ode_space || !ctx->physics->ode_world || !ctx->physics->ode_joint_group) {
return;
}
dSpaceCollide(ctx->physics->ode_space, ctx->physics, &nearCallback);
dWorldQuickStep(ctx->physics->ode_world, 0.016f);
dJointGroupEmpty(ctx->physics->ode_joint_group);
for (int i = 0; i < it->count; i++) {
if (!p[i].body) continue;
const dReal *pos = dBodyGetPosition(p[i].body);
const dReal *rot = dBodyGetQuaternion(p[i].body);
t[i].position = (Vector3){pos[0], pos[1], pos[2]};
t[i].rotation = (Quaternion){rot[1], rot[2], rot[3], rot[0]}; // ODE: wxyz, Raylib: xyzw
Matrix scaleMat = MatrixScale(t[i].scale.x, t[i].scale.y, t[i].scale.z);
Matrix rotMat = QuaternionToMatrix(t[i].rotation);
Matrix transMat = MatrixTranslate(pos[0], pos[1], pos[2]);
t[i].localMatrix = MatrixMultiply(scaleMat, rotMat);
t[i].worldMatrix = MatrixMultiply(t[i].localMatrix, transMat);
}
}
// Logic update system
void LogicUpdateSystem(ecs_iter_t *it) {
Transform3D *t = ecs_field(it, Transform3D, 0);
PhysicsBody *p = ecs_field(it, PhysicsBody, 1);
if (IsKeyPressed(KEY_R)) {
for (int i = 0; i < it->count; i++) {
if (!p[i].body) continue;
dBodySetPosition(p[i].body, 0.0, 5.0, 0.0);
Vector3 randomAxis = Vector3Normalize((Vector3){
(float)(rand() % 100) / 100.0f - 0.5f,
(float)(rand() % 100) / 100.0f - 0.5f,
(float)(rand() % 100) / 100.0f - 0.5f
});
float randomAngle = (float)(rand() % 360) * DEG2RAD;
Quaternion randomRot = QuaternionFromAxisAngle(randomAxis, randomAngle);
dQuaternion odeQuat = {randomRot.w, randomRot.x, randomRot.y, randomRot.z};
dBodySetQuaternion(p[i].body, odeQuat);
dBodySetLinearVel(p[i].body, 0.0, 0.0, 0.0);
dBodySetAngularVel(p[i].body, 0.0, 0.0, 0.0);
t[i].position = (Vector3){0.0f, 5.0f, 0.0f};
t[i].rotation = randomRot;
Matrix scaleMat = MatrixScale(t[i].scale.x, t[i].scale.y, t[i].scale.z);
Matrix rotMat = QuaternionToMatrix(t[i].rotation);
Matrix transMat = MatrixTranslate(0.0f, 5.0f, 0.0f);
t[i].localMatrix = MatrixMultiply(scaleMat, rotMat);
t[i].worldMatrix = MatrixMultiply(t[i].localMatrix, transMat);
}
}
}
// Render end system
void RenderEndSystem(ecs_iter_t *it) {
EndDrawing();
}
int main(void) {
srand((unsigned int)time(NULL));
// Initialize Flecs world
ecs_world_t *world = ecs_init();
// Declare components
ECS_COMPONENT(world, Transform3D);
ECS_COMPONENT(world, ModelComponent);
ECS_COMPONENT(world, PhysicsBody);
// Define custom phases
ecs_entity_t LogicUpdatePhase = ecs_new_w_id(world, EcsPhase);
ecs_entity_t BeginRenderPhase = ecs_new_w_id(world, EcsPhase);
ecs_entity_t BeginCameraPhase = ecs_new_w_id(world, EcsPhase);
ecs_entity_t UpdateCameraPhase = ecs_new_w_id(world, EcsPhase);
ecs_entity_t EndCameraPhase = ecs_new_w_id(world, EcsPhase);
ecs_entity_t EndRenderPhase = ecs_new_w_id(world, EcsPhase);
// Set phase dependencies
ecs_add_pair(world, LogicUpdatePhase, EcsDependsOn, EcsPreUpdate);
ecs_add_pair(world, BeginRenderPhase, EcsDependsOn, LogicUpdatePhase);
ecs_add_pair(world, BeginCameraPhase, EcsDependsOn, BeginRenderPhase);
ecs_add_pair(world, UpdateCameraPhase, EcsDependsOn, BeginCameraPhase);
ecs_add_pair(world, EndCameraPhase, EcsDependsOn, UpdateCameraPhase);
ecs_add_pair(world, EndRenderPhase, EcsDependsOn, EndCameraPhase);
// Initialize systems
ecs_system_init(world, &(ecs_system_desc_t){
.entity = ecs_entity(world, { .name = "LogicUpdateSystem", .add = ecs_ids(ecs_dependson(LogicUpdatePhase)) }),
.query.terms = {
{ .id = ecs_id(Transform3D), .src.id = EcsSelf },
{ .id = ecs_id(PhysicsBody), .src.id = EcsSelf }
},
.callback = LogicUpdateSystem
});
ecs_system_init(world, &(ecs_system_desc_t){
.entity = ecs_entity(world, { .name = "PhysicsSystem", .add = ecs_ids(ecs_dependson(LogicUpdatePhase)) }),
.query.terms = {
{ .id = ecs_id(Transform3D), .src.id = EcsSelf },
{ .id = ecs_id(PhysicsBody), .src.id = EcsSelf }
},
.callback = PhysicsSystem
});
ecs_system_init(world, &(ecs_system_desc_t){
.entity = ecs_entity(world, { .name = "RenderBeginSystem", .add = ecs_ids(ecs_dependson(BeginRenderPhase)) }),
.callback = RenderBeginSystem
});
ecs_system_init(world, &(ecs_system_desc_t){
.entity = ecs_entity(world, { .name = "BeginCameraSystem", .add = ecs_ids(ecs_dependson(BeginCameraPhase)) }),
.callback = BeginCameraSystem
});
ecs_system_init(world, &(ecs_system_desc_t){
.entity = ecs_entity(world, { .name = "RenderSystem", .add = ecs_ids(ecs_dependson(UpdateCameraPhase)) }),
.query.terms = {
{ .id = ecs_id(Transform3D), .src.id = EcsSelf },
{ .id = ecs_id(ModelComponent), .src.id = EcsSelf }
},
.callback = RenderSystem
});
ecs_system_init(world, &(ecs_system_desc_t){
.entity = ecs_entity(world, { .name = "EndCameraSystem", .add = ecs_ids(ecs_dependson(EndCameraPhase)) }),
.callback = EndCameraSystem
});
ecs_system_init(world, &(ecs_system_desc_t){
.entity = ecs_entity(world, { .name = "RenderEndSystem", .add = ecs_ids(ecs_dependson(EndRenderPhase)) }),
.callback = RenderEndSystem
});
// Initialize Raylib
InitWindow(800, 600, "Flecs v4.0.5 with ODE Physics Test");
SetTargetFPS(60);
// Initialize Camera3D
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
};
// Initialize ODEPhysics
ODEPhysics physics = {0};
dInitODE();
physics.ode_world = dWorldCreate();
physics.ode_space = dHashSpaceCreate(0);
physics.ode_joint_group = dJointGroupCreate(0);
dWorldSetGravity(physics.ode_world, 0, -9.81, 0);
// Combine into WorldContext
WorldContext world_ctx = {
.physics = &physics,
.camera = &camera
};
ecs_set_ctx(world, &world_ctx, NULL);
// Create a static ground plane
dGeomID ground = dCreatePlane(physics.ode_space, 0, 1, 0, 0);
// Load Raylib model
Model cube = LoadModelFromMesh(GenMeshCube(1.0f, 1.0f, 1.0f));
// Create entity with physics
ecs_entity_t node1 = ecs_new(world);
ecs_set_name(world, node1, "NodeMesh");
ecs_set(world, node1, Transform3D, {
.position = (Vector3){0.0f, 5.0f, 0.0f},
.rotation = QuaternionIdentity(),
.scale = (Vector3){1.0f, 1.0f, 1.0f},
.localMatrix = MatrixIdentity(),
.worldMatrix = MatrixIdentity()
});
ecs_set(world, node1, ModelComponent, {&cube});
// Add physics body using ODEPhysics
PhysicsBody pb = {0};
pb.body = dBodyCreate(physics.ode_world);
pb.geom = dCreateBox(physics.ode_space, 1.0, 1.0, 1.0);
dMassSetBox(&pb.mass, 1.0, 1.0, 1.0, 1.0);
dBodySetMass(pb.body, &pb.mass);
dGeomSetBody(pb.geom, pb.body);
dBodySetPosition(pb.body, 0.0, 5.0, 0.0);
ecs_set(world, node1, PhysicsBody, {pb.body, pb.geom, pb.mass});
// Main loop
while (!WindowShouldClose()) {
ecs_progress(world, 0);
}
// Cleanup
UnloadModel(cube);
dJointGroupDestroy(physics.ode_joint_group);
dSpaceDestroy(physics.ode_space);
dWorldDestroy(physics.ode_world);
dCloseODE();
ecs_fini(world);
CloseWindow();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment