Created
March 26, 2025 06:13
-
-
Save Lightnet/2daa6b7ee1570619cb0fadf2558b0946 to your computer and use it in GitHub Desktop.
raylib flecs render ode 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
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" | |
) |
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
#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