1029 lines
29 KiB
C
1029 lines
29 KiB
C
#include "libsuicmez.h"
|
|
#include "raylib.h"
|
|
#include "rlgl.h"
|
|
#include "suicmez_gc.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#define MAX_TRACKED_ALLOCATIONS 10000
|
|
|
|
typedef struct {
|
|
void *ptr;
|
|
size_t size;
|
|
int in_use;
|
|
} AllocationInfo;
|
|
|
|
static AllocationInfo tracked_allocations[MAX_TRACKED_ALLOCATIONS];
|
|
static int allocation_count = 0;
|
|
static const char *last_error = NULL;
|
|
|
|
// FPS meter state
|
|
static bool fps_meter_visible = false;
|
|
static int fps_meter_x = 0;
|
|
static int fps_meter_y = 0;
|
|
|
|
// Simple linear search to find allocation
|
|
static int find_allocation(void *ptr) {
|
|
for (int i = 0; i < allocation_count; i++) {
|
|
if (tracked_allocations[i].ptr == ptr && tracked_allocations[i].in_use) {
|
|
return i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
// GC-aware allocator
|
|
void *gc_suic_alloc(size_t size) {
|
|
if (size == 0) {
|
|
return NULL;
|
|
}
|
|
|
|
// Use the real GC allocator (for now without type info)
|
|
void *ptr = gc_alloc(NULL, size);
|
|
if (!ptr) {
|
|
last_error = "GC allocation failed";
|
|
return NULL;
|
|
}
|
|
|
|
return ptr;
|
|
}
|
|
|
|
// Free memory (called by gc_suic_free)
|
|
// With GC, we don't actually free - just mark as no longer needed
|
|
void suic_gc_free(void *ptr) {
|
|
// GC will handle freeing when appropriate
|
|
// For now, do nothing
|
|
(void)ptr;
|
|
}
|
|
|
|
// Error handling
|
|
const char *suic_last_error(void) {
|
|
return last_error ? last_error : "No error";
|
|
}
|
|
|
|
void suic_clear_error(void) { last_error = NULL; }
|
|
|
|
// Logging
|
|
static suic_log_level current_log_level = SUIC_LOG_ALL;
|
|
|
|
void suic_set_log_level(suic_log_level level) { current_log_level = level; }
|
|
|
|
// Window management
|
|
bool suic_init_window(int width, int height, const char *title) {
|
|
InitWindow(width, height, title);
|
|
return !WindowShouldClose();
|
|
}
|
|
|
|
void suic_close_window(void) { CloseWindow(); }
|
|
|
|
bool suic_window_should_close(void) { return WindowShouldClose(); }
|
|
|
|
void suic_set_target_fps(int fps) { SetTargetFPS(fps); }
|
|
|
|
void suic_enable_msaa_4x(void) { SetConfigFlags(FLAG_MSAA_4X_HINT); }
|
|
|
|
int suic_get_screen_width(void) { return GetScreenWidth(); }
|
|
|
|
int suic_get_screen_height(void) { return GetScreenHeight(); }
|
|
|
|
// Timing
|
|
float suic_get_frame_time(void) { return GetFrameTime(); }
|
|
|
|
// FPS meter
|
|
void suic_show_fps_meter(suic_vec2 position) {
|
|
fps_meter_visible = true;
|
|
fps_meter_x = (int)position.x;
|
|
fps_meter_y = (int)position.y;
|
|
}
|
|
|
|
void suic_hide_fps_meter(void) {
|
|
fps_meter_visible = false;
|
|
}
|
|
|
|
// Drawing
|
|
void suic_begin_drawing(void) { BeginDrawing(); }
|
|
|
|
void suic_end_drawing(void) {
|
|
// Draw FPS meter if enabled
|
|
if (fps_meter_visible) {
|
|
DrawFPS(fps_meter_x, fps_meter_y);
|
|
}
|
|
EndDrawing();
|
|
}
|
|
|
|
void suic_clear_background(unsigned char r, unsigned char g, unsigned char b,
|
|
unsigned char a) {
|
|
ClearBackground((Color){r, g, b, a});
|
|
}
|
|
|
|
// 3D mode
|
|
Camera3D suic_to_rl_camera3d(suic_camera3d cam) {
|
|
return (Camera3D){.position = suic_to_rl_vec3(cam.position),
|
|
.target = suic_to_rl_vec3(cam.target),
|
|
.up = suic_to_rl_vec3(cam.up),
|
|
.fovy = cam.fovy,
|
|
.projection = cam.projection};
|
|
}
|
|
|
|
void suic_begin_mode3d(float pos_x, float pos_y, float pos_z, float target_x,
|
|
float target_y, float target_z, float up_x, float up_y,
|
|
float up_z, float fovy, int projection) {
|
|
Camera3D camera = {.position = (Vector3){pos_x, pos_y, pos_z},
|
|
.target = (Vector3){target_x, target_y, target_z},
|
|
.up = (Vector3){up_x, up_y, up_z},
|
|
.fovy = fovy,
|
|
.projection = projection};
|
|
BeginMode3D(camera);
|
|
}
|
|
|
|
void suic_end_mode3d(void) { EndMode3D(); }
|
|
|
|
// 3D Drawing
|
|
void suic_draw_cube(float x, float y, float z, float width, float height,
|
|
float length, unsigned char r, unsigned char g,
|
|
unsigned char b, unsigned char a) {
|
|
DrawCube((Vector3){x, y, z}, width, height, length, (Color){r, g, b, a});
|
|
}
|
|
|
|
void suic_draw_cube_wires(float x, float y, float z, float width, float height,
|
|
float length, unsigned char r, unsigned char g,
|
|
unsigned char b, unsigned char a) {
|
|
DrawCubeWires((Vector3){x, y, z}, width, height, length, (Color){r, g, b, a});
|
|
}
|
|
|
|
void suic_draw_sphere(float x, float y, float z, float radius, unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
|
|
DrawSphere((Vector3){x, y, z}, radius, (Color){r, g, b, a});
|
|
}
|
|
|
|
void suic_draw_capsule(float start_x, float start_y, float start_z, float end_x, float end_y, float end_z, float radius, int slices, int rings, unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
|
|
DrawCapsule((Vector3){start_x, start_y, start_z}, (Vector3){end_x, end_y, end_z}, radius, slices, rings, (Color){r, g, b, a});
|
|
}
|
|
|
|
// 2D Drawing (UI Framework Utils)
|
|
void suic_draw_text(const char *text, int x, int y, int font_size, unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
|
|
DrawText(text, x, y, font_size, (Color){r, g, b, a});
|
|
}
|
|
|
|
void suic_draw_rectangle(int x, int y, int width, int height, unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
|
|
DrawRectangle(x, y, width, height, (Color){r, g, b, a});
|
|
}
|
|
|
|
void suic_draw_rectangle_lines(int x, int y, int width, int height, unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
|
|
DrawRectangleLines(x, y, width, height, (Color){r, g, b, a});
|
|
}
|
|
|
|
int suic_measure_text(const char *text, int font_size) {
|
|
return MeasureText(text, font_size);
|
|
}
|
|
|
|
void suic_draw_circle(int center_x, int center_y, float radius, unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
|
|
DrawCircle(center_x, center_y, radius, (Color){r, g, b, a});
|
|
}
|
|
|
|
void suic_draw_line(int start_x, int start_y, int end_x, int end_y, unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
|
|
DrawLine(start_x, start_y, end_x, end_y, (Color){r, g, b, a});
|
|
}
|
|
|
|
void suic_draw_fps(int x, int y) {
|
|
DrawFPS(x, y);
|
|
}
|
|
|
|
// Input - Keyboard
|
|
bool suic_is_key_down(int key) { return IsKeyDown(key); }
|
|
|
|
bool suic_is_key_pressed(int key) { return IsKeyPressed(key); }
|
|
|
|
bool suic_is_key_released(int key) { return IsKeyReleased(key); }
|
|
|
|
// Input - Mouse
|
|
bool suic_is_mouse_button_down(int b) { return IsMouseButtonDown(b); }
|
|
|
|
bool suic_is_mouse_button_pressed(int b) { return IsMouseButtonPressed(b); }
|
|
|
|
bool suic_is_mouse_button_released(int b) { return IsMouseButtonReleased(b); }
|
|
|
|
suic_vec2 suic_get_mouse_position(void) {
|
|
Vector2 pos = GetMousePosition();
|
|
return (suic_vec2){pos.x, pos.y};
|
|
}
|
|
|
|
suic_vec2 suic_get_mouse_delta(void) {
|
|
Vector2 delta = GetMouseDelta();
|
|
return (suic_vec2){delta.x, delta.y};
|
|
}
|
|
|
|
float suic_get_mouse_x(void) { return GetMousePosition().x; }
|
|
|
|
float suic_get_mouse_y(void) { return GetMousePosition().y; }
|
|
|
|
float suic_get_mouse_delta_x(void) { return GetMouseDelta().x; }
|
|
|
|
float suic_get_mouse_delta_y(void) { return GetMouseDelta().y; }
|
|
|
|
// Random number generation
|
|
int suic_get_random_value(int min, int max) { return GetRandomValue(min, max); }
|
|
|
|
// Input - Helper functions for gamedev
|
|
int suic_is_movement_input(void) {
|
|
// Returns bitmask: 1=W (forward), 2=A (left), 4=S (backward), 8=D (right)
|
|
int result = 0;
|
|
if (IsKeyDown(KEY_W))
|
|
result |= 1;
|
|
if (IsKeyDown(KEY_A))
|
|
result |= 2;
|
|
if (IsKeyDown(KEY_S))
|
|
result |= 4;
|
|
if (IsKeyDown(KEY_D))
|
|
result |= 8;
|
|
return result;
|
|
}
|
|
|
|
int suic_is_sprint_held(void) { return IsKeyDown(KEY_LEFT_SHIFT) ? 1 : 0; }
|
|
|
|
int suic_is_jump_pressed(void) { return IsKeyPressed(KEY_SPACE) ? 1 : 0; }
|
|
|
|
// ============================================================================
|
|
// CURSOR MANAGEMENT
|
|
// ============================================================================
|
|
|
|
void suic_disable_cursor(void) { DisableCursor(); }
|
|
|
|
void suic_enable_cursor(void) { EnableCursor(); }
|
|
|
|
bool suic_is_cursor_hidden(void) { return IsCursorHidden(); }
|
|
|
|
// ============================================================================
|
|
// MATRIX OPERATIONS FOR 3D TRANSFORMATIONS
|
|
// ============================================================================
|
|
|
|
void suic_rl_push_matrix(void) { rlPushMatrix(); }
|
|
|
|
void suic_rl_pop_matrix(void) { rlPopMatrix(); }
|
|
|
|
void suic_rl_translate_f(float x, float y, float z) { rlTranslatef(x, y, z); }
|
|
|
|
void suic_rl_rotate_f(float angle, float x, float y, float z) {
|
|
rlRotatef(angle, x, y, z);
|
|
}
|
|
|
|
// Image and Texture
|
|
suic_image_handle suic_image_load(const char *path) {
|
|
Image img = LoadImage(path);
|
|
return (suic_image_handle){img, (img.data != NULL) ? true : false};
|
|
}
|
|
|
|
void suic_image_free(suic_image_handle *img) {
|
|
if (img && img->valid) {
|
|
UnloadImage(img->value);
|
|
img->valid = false;
|
|
}
|
|
}
|
|
|
|
suic_texture_handle suic_texture_load(const char *path) {
|
|
Texture2D tex = LoadTexture(path);
|
|
return (suic_texture_handle){tex, (tex.id != 0) ? true : false};
|
|
}
|
|
|
|
suic_texture_handle suic_texture_from_image(suic_image_handle img) {
|
|
if (!img.valid) {
|
|
return (suic_texture_handle){0, false};
|
|
}
|
|
Texture2D tex = LoadTextureFromImage(img.value);
|
|
return (suic_texture_handle){tex, (tex.id != 0) ? true : false};
|
|
}
|
|
|
|
void suic_texture_free(suic_texture_handle *tex) {
|
|
if (tex && tex->valid) {
|
|
UnloadTexture(tex->value);
|
|
tex->valid = false;
|
|
}
|
|
}
|
|
|
|
void suic_draw_texture(suic_texture_handle tex, int x, int y, unsigned char r,
|
|
unsigned char g, unsigned char b, unsigned char a) {
|
|
if (tex.valid) {
|
|
DrawTexture(tex.value, x, y, (Color){r, g, b, a});
|
|
}
|
|
}
|
|
|
|
// Audio
|
|
bool suic_audio_init(void) {
|
|
InitAudioDevice();
|
|
return IsAudioDeviceReady();
|
|
}
|
|
|
|
void suic_audio_close(void) { CloseAudioDevice(); }
|
|
|
|
suic_sound_handle suic_sound_load(const char *path) {
|
|
Sound snd = LoadSound(path);
|
|
return (suic_sound_handle){snd, (snd.frameCount > 0) ? true : false};
|
|
}
|
|
|
|
void suic_sound_play(suic_sound_handle snd) {
|
|
if (snd.valid) {
|
|
PlaySound(snd.value);
|
|
}
|
|
}
|
|
|
|
void suic_sound_set_volume(suic_sound_handle snd, float volume) {
|
|
if (snd.valid) {
|
|
SetSoundVolume(snd.value, volume);
|
|
}
|
|
}
|
|
|
|
void suic_sound_free(suic_sound_handle *snd) {
|
|
if (snd && snd->valid) {
|
|
UnloadSound(snd->value);
|
|
snd->valid = false;
|
|
}
|
|
}
|
|
|
|
// Raycasting
|
|
suic_ray suic_get_mouse_ray(suic_vec2 mouse, suic_camera3d cam) {
|
|
Ray r = GetMouseRay((Vector2){mouse.x, mouse.y}, suic_to_rl_camera3d(cam));
|
|
return (suic_ray){
|
|
.origin = (suic_vec3){r.position.x, r.position.y, r.position.z},
|
|
.direction = (suic_vec3){r.direction.x, r.direction.y, r.direction.z}};
|
|
}
|
|
|
|
suic_rayhit suic_raycast_aabb(suic_ray ray, suic_aabb box) {
|
|
Ray r = (Ray){.position = (Vector3){ray.origin.x, ray.origin.y, ray.origin.z},
|
|
.direction = (Vector3){ray.direction.x, ray.direction.y,
|
|
ray.direction.z}};
|
|
|
|
BoundingBox bbox =
|
|
(BoundingBox){.min = (Vector3){box.min.x, box.min.y, box.min.z},
|
|
.max = (Vector3){box.max.x, box.max.y, box.max.z}};
|
|
|
|
RayCollision collision = GetRayCollisionBox(r, bbox);
|
|
|
|
return (suic_rayhit){
|
|
.hit = collision.hit,
|
|
.distance = collision.distance,
|
|
.point =
|
|
(suic_vec3){collision.point.x, collision.point.y, collision.point.z},
|
|
.normal = (suic_vec3){collision.normal.x, collision.normal.y,
|
|
collision.normal.z}};
|
|
}
|
|
|
|
// ODE Physics Engine Implementation
|
|
static int ode_initialized = 0;
|
|
|
|
void suic_ode_init(void) {
|
|
if (!ode_initialized) {
|
|
dInitODE();
|
|
ode_initialized = 1;
|
|
}
|
|
}
|
|
|
|
void suic_ode_close(void) {
|
|
if (ode_initialized) {
|
|
dCloseODE();
|
|
ode_initialized = 0;
|
|
}
|
|
}
|
|
|
|
suic_ode_world_id suic_ode_world_create(void) { return dWorldCreate(); }
|
|
|
|
void suic_ode_world_destroy(suic_ode_world_id world) {
|
|
if (world) {
|
|
dWorldDestroy(world);
|
|
}
|
|
}
|
|
|
|
void suic_ode_world_set_gravity(suic_ode_world_id world, dReal x, dReal y,
|
|
dReal z) {
|
|
if (world) {
|
|
dWorldSetGravity(world, x, y, z);
|
|
}
|
|
}
|
|
|
|
void suic_ode_world_step(suic_ode_world_id world, dReal stepsize) {
|
|
if (world) {
|
|
dWorldStep(world, stepsize);
|
|
}
|
|
}
|
|
|
|
// Body management
|
|
suic_ode_body_id suic_ode_body_create(suic_ode_world_id world) {
|
|
return dBodyCreate(world);
|
|
}
|
|
|
|
void suic_ode_body_destroy(suic_ode_body_id body) {
|
|
if (body) {
|
|
dBodyDestroy(body);
|
|
}
|
|
}
|
|
|
|
void suic_ode_body_set_position(suic_ode_body_id body, dReal x, dReal y,
|
|
dReal z) {
|
|
if (body) {
|
|
dBodySetPosition(body, x, y, z);
|
|
}
|
|
}
|
|
|
|
void suic_ode_body_set_linear_vel(suic_ode_body_id body, dReal x, dReal y,
|
|
dReal z) {
|
|
if (body) {
|
|
dBodySetLinearVel(body, x, y, z);
|
|
}
|
|
}
|
|
|
|
void suic_ode_body_get_position(suic_ode_body_id body, float *x, float *y,
|
|
float *z) {
|
|
if (body) {
|
|
const dReal *pos = dBodyGetPosition(body);
|
|
*x = (float)pos[0];
|
|
*y = (float)pos[1];
|
|
*z = (float)pos[2];
|
|
}
|
|
}
|
|
|
|
// Mass and geometry
|
|
void suic_ode_body_set_box_mass(suic_ode_body_id body, dReal density, dReal lx,
|
|
dReal ly, dReal lz) {
|
|
if (body) {
|
|
dMass mass;
|
|
dMassSetBox(&mass, density, lx, ly, lz);
|
|
dBodySetMass(body, &mass);
|
|
}
|
|
}
|
|
|
|
suic_ode_geom_id suic_ode_create_box_geom(suic_ode_space_id space, dReal lx,
|
|
dReal ly, dReal lz) {
|
|
return dCreateBox(space, lx, ly, lz);
|
|
}
|
|
|
|
suic_ode_geom_id suic_ode_create_plane_geom(suic_ode_space_id space, dReal a,
|
|
dReal b, dReal c, dReal d) {
|
|
return dCreatePlane(space, a, b, c, d);
|
|
}
|
|
|
|
void suic_ode_geom_set_body(suic_ode_geom_id geom, suic_ode_body_id body) {
|
|
if (geom) {
|
|
dGeomSetBody(geom, body);
|
|
}
|
|
}
|
|
|
|
void suic_ode_geom_destroy(suic_ode_geom_id geom) {
|
|
if (geom) {
|
|
dGeomDestroy(geom);
|
|
}
|
|
}
|
|
|
|
// Collision space
|
|
suic_ode_space_id suic_ode_simple_space_create(suic_ode_space_id parent) {
|
|
return dSimpleSpaceCreate(parent);
|
|
}
|
|
|
|
void suic_ode_space_destroy(suic_ode_space_id space) {
|
|
if (space) {
|
|
dSpaceDestroy(space);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// COLLISION DETECTION AND CONTACT JOINTS
|
|
// ============================================================================
|
|
|
|
// Global contact configuration
|
|
static dReal contact_max_force = 10000.0;
|
|
static dReal contact_erp = 0.8; // Error reduction parameter
|
|
static dReal contact_cfm = 0.00001; // Constraint force mixing
|
|
|
|
// Store world and contactgroup for collision callback
|
|
typedef struct {
|
|
dWorldID world;
|
|
dJointGroupID contactgroup;
|
|
} CollisionContext;
|
|
|
|
// Collision callback for detecting contacts between geometries
|
|
static void near_callback(void *data, dGeomID o1, dGeomID o2) {
|
|
CollisionContext *ctx = (CollisionContext *)data;
|
|
dBodyID b1 = dGeomGetBody(o1);
|
|
dBodyID b2 = dGeomGetBody(o2);
|
|
|
|
// Don't process if both bodies are static or same body
|
|
if ((b1 && b2 && b1 == b2) || (!b1 && !b2)) {
|
|
return;
|
|
}
|
|
|
|
const int N = 4; // Max contacts per collision
|
|
dContact contact[N];
|
|
|
|
// Check for collisions
|
|
int n = dCollide(o1, o2, N, &contact[0].geom, sizeof(dContact));
|
|
if (n > 0) {
|
|
for (int i = 0; i < n; i++) {
|
|
// Set contact properties
|
|
contact[i].surface.mu = 0.5; // Friction coefficient
|
|
contact[i].surface.mu2 = 0.5;
|
|
contact[i].surface.bounce = 0.1; // Bounciness
|
|
contact[i].surface.bounce_vel = 0.1;
|
|
contact[i].surface.mode =
|
|
dContactBounce | dContactSoftERP | dContactSoftCFM;
|
|
contact[i].surface.soft_erp = contact_erp;
|
|
contact[i].surface.soft_cfm = contact_cfm;
|
|
|
|
// Create contact joint
|
|
dJointID c =
|
|
dJointCreateContact(ctx->world, ctx->contactgroup, &contact[i]);
|
|
dJointAttach(c, b1, b2);
|
|
}
|
|
}
|
|
}
|
|
|
|
void suic_ode_space_collide(suic_ode_world_id world, suic_ode_space_id space,
|
|
suic_ode_joint_group_id contactgroup) {
|
|
if (space && contactgroup && world) {
|
|
CollisionContext ctx = {.world = world, .contactgroup = contactgroup};
|
|
dSpaceCollide(space, (void *)&ctx, &near_callback);
|
|
}
|
|
}
|
|
|
|
suic_ode_joint_group_id suic_ode_joint_group_create(int max_size) {
|
|
return dJointGroupCreate(max_size);
|
|
}
|
|
|
|
void suic_ode_joint_group_destroy(suic_ode_joint_group_id group) {
|
|
if (group) {
|
|
dJointGroupDestroy(group);
|
|
}
|
|
}
|
|
|
|
void suic_ode_joint_group_empty(suic_ode_joint_group_id group) {
|
|
if (group) {
|
|
dJointGroupEmpty(group);
|
|
}
|
|
}
|
|
|
|
void suic_ode_set_contact_max_force(dReal force) { contact_max_force = force; }
|
|
|
|
void suic_ode_set_contact_erp(dReal erp) { contact_erp = erp; }
|
|
|
|
void suic_ode_set_contact_cfm(dReal cfm) { contact_cfm = cfm; }
|
|
|
|
// ============================================================================
|
|
// ADDITIONAL BODY FUNCTIONS
|
|
// ============================================================================
|
|
|
|
void suic_ode_body_get_linear_vel(suic_ode_body_id body, float *x, float *y,
|
|
float *z) {
|
|
if (body) {
|
|
const dReal *vel = dBodyGetLinearVel(body);
|
|
*x = (float)vel[0];
|
|
*y = (float)vel[1];
|
|
*z = (float)vel[2];
|
|
}
|
|
}
|
|
|
|
void suic_ode_body_get_rotation(suic_ode_body_id body, float *q_w, float *q_x,
|
|
float *q_y, float *q_z) {
|
|
if (body) {
|
|
const dReal *q = dBodyGetQuaternion(body);
|
|
*q_w = (float)q[0];
|
|
*q_x = (float)q[1];
|
|
*q_y = (float)q[2];
|
|
*q_z = (float)q[3];
|
|
}
|
|
}
|
|
|
|
void suic_ode_body_set_rotation(suic_ode_body_id body, dReal q_w, dReal q_x,
|
|
dReal q_y, dReal q_z) {
|
|
if (body) {
|
|
dQuaternion q;
|
|
q[0] = q_w;
|
|
q[1] = q_x;
|
|
q[2] = q_y;
|
|
q[3] = q_z;
|
|
dBodySetQuaternion(body, q);
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// GAME OBJECT SYSTEM - Unified Physics & Rendering
|
|
// ============================================================================
|
|
|
|
suic_game_object *suic_game_object_create_box(
|
|
suic_ode_world_id world, suic_ode_space_id space, float x, float y, float z,
|
|
float width, float height, float length, float density, unsigned char r,
|
|
unsigned char g, unsigned char b, unsigned char a) {
|
|
suic_game_object *obj =
|
|
(suic_game_object *)gc_suic_alloc(sizeof(suic_game_object));
|
|
if (!obj)
|
|
return NULL;
|
|
|
|
// Create physics body
|
|
obj->body = dBodyCreate(world);
|
|
if (!obj->body) {
|
|
suic_gc_free(obj);
|
|
return NULL;
|
|
}
|
|
|
|
// Set position
|
|
dBodySetPosition(obj->body, x, y, z);
|
|
|
|
// Create and attach geometry
|
|
obj->geom = dCreateBox(space, width, height, length);
|
|
if (!obj->geom) {
|
|
dBodyDestroy(obj->body);
|
|
suic_gc_free(obj);
|
|
return NULL;
|
|
}
|
|
|
|
dGeomSetBody(obj->geom, obj->body);
|
|
|
|
// Set mass
|
|
dMass mass;
|
|
dMassSetBox(&mass, density, width, height, length);
|
|
dBodySetMass(obj->body, &mass);
|
|
|
|
// Store rendering data
|
|
obj->width = width;
|
|
obj->height = height;
|
|
obj->length = length;
|
|
obj->r = r;
|
|
obj->g = g;
|
|
obj->b = b;
|
|
obj->a = a;
|
|
obj->shape_type = 0; // 0 = box
|
|
|
|
return obj;
|
|
}
|
|
|
|
void suic_game_object_destroy(suic_game_object *obj) {
|
|
if (obj) {
|
|
if (obj->geom) {
|
|
dGeomDestroy(obj->geom);
|
|
}
|
|
if (obj->body) {
|
|
dBodyDestroy(obj->body);
|
|
}
|
|
suic_gc_free(obj);
|
|
}
|
|
}
|
|
|
|
void suic_game_object_set_position(suic_game_object *obj, float x, float y,
|
|
float z) {
|
|
if (obj && obj->body) {
|
|
dBodySetPosition(obj->body, x, y, z);
|
|
}
|
|
}
|
|
|
|
void suic_game_object_get_position(suic_game_object *obj, float *x, float *y,
|
|
float *z) {
|
|
if (obj && obj->body) {
|
|
const dReal *pos = dBodyGetPosition(obj->body);
|
|
*x = (float)pos[0];
|
|
*y = (float)pos[1];
|
|
*z = (float)pos[2];
|
|
}
|
|
}
|
|
|
|
void suic_game_object_set_velocity(suic_game_object *obj, float x, float y,
|
|
float z) {
|
|
if (obj && obj->body) {
|
|
dBodySetLinearVel(obj->body, x, y, z);
|
|
}
|
|
}
|
|
|
|
void suic_game_object_get_velocity(suic_game_object *obj, float *x, float *y,
|
|
float *z) {
|
|
if (obj && obj->body) {
|
|
const dReal *vel = dBodyGetLinearVel(obj->body);
|
|
*x = (float)vel[0];
|
|
*y = (float)vel[1];
|
|
*z = (float)vel[2];
|
|
}
|
|
}
|
|
|
|
void suic_game_object_draw(suic_game_object *obj) {
|
|
if (obj && obj->body) {
|
|
float x, y, z;
|
|
suic_game_object_get_position(obj, &x, &y, &z);
|
|
|
|
if (obj->shape_type == 0) { // Box
|
|
DrawCube((Vector3){x, y, z}, obj->width, obj->height, obj->length,
|
|
(Color){obj->r, obj->g, obj->b, obj->a});
|
|
// Draw wireframe for visual clarity
|
|
DrawCubeWires((Vector3){x, y, z}, obj->width, obj->height, obj->length,
|
|
(Color){0, 0, 0, 200});
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// PLAYER CONTROLLER - FPS STYLE MOVEMENT
|
|
// ============================================================================
|
|
|
|
suic_player_controller *suic_player_controller_create(float x, float y,
|
|
float z) {
|
|
suic_player_controller *player =
|
|
(suic_player_controller *)gc_suic_alloc(sizeof(suic_player_controller));
|
|
if (!player)
|
|
return NULL;
|
|
|
|
player->position = (suic_vec3){x, y, z};
|
|
player->velocity = (suic_vec3){0.0, 0.0, 0.0};
|
|
player->forward = (suic_vec3){0.0, 0.0, -1.0}; // Looking down -Z
|
|
player->right = (suic_vec3){1.0, 0.0, 0.0}; // Right is +X
|
|
player->move_speed = 5.0; // units per second
|
|
player->sprint_speed = 10.0; // units per second
|
|
player->jump_force = 10.0; // units per second
|
|
player->gravity = -9.81;
|
|
player->is_grounded = 1;
|
|
|
|
return player;
|
|
}
|
|
|
|
void suic_player_controller_destroy(suic_player_controller *player) {
|
|
if (player) {
|
|
suic_gc_free(player);
|
|
}
|
|
}
|
|
|
|
void suic_player_controller_update(suic_player_controller *player,
|
|
float delta_time) {
|
|
if (!player)
|
|
return;
|
|
|
|
// Apply gravity
|
|
player->velocity.y += player->gravity * delta_time;
|
|
|
|
// Update position
|
|
player->position.x += player->velocity.x * delta_time;
|
|
player->position.y += player->velocity.y * delta_time;
|
|
player->position.z += player->velocity.z * delta_time;
|
|
}
|
|
|
|
void suic_player_controller_set_direction(suic_player_controller *player,
|
|
float forward_x, float forward_y,
|
|
float forward_z, float right_x,
|
|
float right_y, float right_z) {
|
|
if (!player)
|
|
return;
|
|
player->forward = (suic_vec3){forward_x, forward_y, forward_z};
|
|
player->right = (suic_vec3){right_x, right_y, right_z};
|
|
}
|
|
|
|
void suic_player_controller_move_forward(suic_player_controller *player,
|
|
float amount) {
|
|
if (!player)
|
|
return;
|
|
|
|
float speed = player->move_speed;
|
|
if (suic_is_sprint_held()) {
|
|
speed = player->sprint_speed;
|
|
}
|
|
|
|
player->velocity.x += player->forward.x * speed * amount;
|
|
player->velocity.z += player->forward.z * speed * amount;
|
|
}
|
|
|
|
void suic_player_controller_move_right(suic_player_controller *player,
|
|
float amount) {
|
|
if (!player)
|
|
return;
|
|
|
|
float speed = player->move_speed;
|
|
if (suic_is_sprint_held()) {
|
|
speed = player->sprint_speed;
|
|
}
|
|
|
|
player->velocity.x += player->right.x * speed * amount;
|
|
player->velocity.z += player->right.z * speed * amount;
|
|
}
|
|
|
|
void suic_player_controller_jump(suic_player_controller *player) {
|
|
if (!player || !player->is_grounded)
|
|
return;
|
|
|
|
player->velocity.y = player->jump_force;
|
|
player->is_grounded = 0;
|
|
}
|
|
|
|
void suic_player_controller_get_position(suic_player_controller *player,
|
|
float *x, float *y, float *z) {
|
|
if (player) {
|
|
*x = player->position.x;
|
|
*y = player->position.y;
|
|
*z = player->position.z;
|
|
}
|
|
}
|
|
|
|
void suic_player_controller_set_position(suic_player_controller *player,
|
|
float x, float y, float z) {
|
|
if (player) {
|
|
player->position.x = x;
|
|
player->position.y = y;
|
|
player->position.z = z;
|
|
}
|
|
}
|
|
|
|
|
|
/* ===== FILE I/O IMPLEMENTATION ===== */
|
|
|
|
FileHandle suic_file_open(const char* path, FileMode mode) {
|
|
const char* mode_str;
|
|
switch (mode) {
|
|
case FILE_READ: mode_str = "rb"; break;
|
|
case FILE_WRITE: mode_str = "wb"; break;
|
|
case FILE_APPEND: mode_str = "ab"; break;
|
|
default: return NULL;
|
|
}
|
|
FILE* f = fopen(path, mode_str);
|
|
return (FileHandle)f;
|
|
}
|
|
|
|
void suic_file_close(FileHandle handle) {
|
|
if (handle) {
|
|
fclose((FILE*)handle);
|
|
}
|
|
}
|
|
|
|
int suic_file_read(FileHandle handle, void* buffer, int size) {
|
|
if (!handle) return -1;
|
|
return (int)fread(buffer, 1, size, (FILE*)handle);
|
|
}
|
|
|
|
int suic_file_write(FileHandle handle, const void* buffer, int size) {
|
|
if (!handle) return -1;
|
|
return (int)fwrite(buffer, 1, size, (FILE*)handle);
|
|
}
|
|
|
|
int suic_file_size(FileHandle handle) {
|
|
if (!handle) return -1;
|
|
FILE* f = (FILE*)handle;
|
|
long current = ftell(f);
|
|
fseek(f, 0, SEEK_END);
|
|
long size = ftell(f);
|
|
fseek(f, current, SEEK_SET);
|
|
return (int)size;
|
|
}
|
|
|
|
/* ===== RANDOM NUMBER GENERATION IMPLEMENTATION ===== */
|
|
|
|
static uint32_t _random_state = 12345;
|
|
|
|
void suic_random_seed(uint32_t seed) {
|
|
_random_state = seed ? seed : 12345;
|
|
srand(seed);
|
|
}
|
|
|
|
/* Linear congruential generator */
|
|
uint32_t suic_random_int(void) {
|
|
_random_state = (_random_state * 1103515245 + 12345) & 0x7fffffff;
|
|
return _random_state;
|
|
}
|
|
|
|
int suic_random_int_range(int min, int max) {
|
|
if (min > max) {
|
|
int tmp = min;
|
|
min = max;
|
|
max = tmp;
|
|
}
|
|
uint32_t range = max - min + 1;
|
|
return min + (suic_random_int() % range);
|
|
}
|
|
|
|
float suic_random_float(void) {
|
|
return (float)suic_random_int() / 2147483647.0f;
|
|
}
|
|
|
|
float suic_random_float_range(float min, float max) {
|
|
return min + (max - min) * suic_random_float();
|
|
}
|
|
|
|
/* ===== TIME IMPLEMENTATION ===== */
|
|
|
|
#include <time.h>
|
|
#include <sys/time.h>
|
|
#include <unistd.h>
|
|
|
|
static uint64_t _start_time_ms = 0;
|
|
|
|
static void _init_time(void) {
|
|
if (_start_time_ms == 0) {
|
|
struct timeval tv;
|
|
gettimeofday(&tv, NULL);
|
|
_start_time_ms = (uint64_t)tv.tv_sec * 1000 + (uint64_t)tv.tv_usec / 1000;
|
|
}
|
|
}
|
|
|
|
uint64_t suic_get_time_ms(void) {
|
|
_init_time();
|
|
struct timeval tv;
|
|
gettimeofday(&tv, NULL);
|
|
uint64_t now = (uint64_t)tv.tv_sec * 1000 + (uint64_t)tv.tv_usec / 1000;
|
|
return now - _start_time_ms;
|
|
}
|
|
|
|
uint64_t suic_get_time_us(void) {
|
|
_init_time();
|
|
struct timeval tv;
|
|
gettimeofday(&tv, NULL);
|
|
uint64_t now = (uint64_t)tv.tv_sec * 1000000 + (uint64_t)tv.tv_usec;
|
|
return now - (_start_time_ms * 1000);
|
|
}
|
|
|
|
uint64_t suic_get_unix_time(void) {
|
|
return (uint64_t)time(NULL);
|
|
}
|
|
|
|
void suic_sleep_ms(uint32_t ms) {
|
|
usleep(ms * 1000);
|
|
}
|
|
|
|
/* ===== UUID GENERATION IMPLEMENTATION ===== */
|
|
|
|
/* Simple UUID v4 generator - not cryptographically secure but good enough for game IDs */
|
|
const char* suic_uuid_generate_v4(void) {
|
|
static char uuid_buffer[37]; // UUID is 36 chars + null terminator
|
|
|
|
uint32_t a = suic_random_int();
|
|
uint32_t b = suic_random_int();
|
|
uint32_t c = suic_random_int();
|
|
uint32_t d = suic_random_int();
|
|
|
|
/* Set version 4 (random) and variant bits */
|
|
b = (b & 0x0fff) | 0x4000;
|
|
c = (c & 0x3fff) | 0x8000;
|
|
|
|
snprintf(uuid_buffer, sizeof(uuid_buffer),
|
|
"%08x-%04x-%04x-%04x-%08x%04x",
|
|
a,
|
|
(uint16_t)(b >> 16),
|
|
(uint16_t)b,
|
|
(uint16_t)(c >> 16),
|
|
(uint32_t)c,
|
|
(uint16_t)d);
|
|
|
|
return uuid_buffer;
|
|
}
|
|
|
|
const char* suic_uuid_generate_v4_seeded(uint64_t seed) {
|
|
suic_random_seed((uint32_t)seed);
|
|
return suic_uuid_generate_v4();
|
|
}
|
|
|
|
/* ===== FISHSOUP NETWORKING IMPLEMENTATION ===== */
|
|
|
|
/* Client functions */
|
|
fishsoup_client_t* suic_fishsoup_client_create(const char* host, int port) {
|
|
return fishsoup_client(host, port);
|
|
}
|
|
|
|
void suic_fishsoup_client_on_packet(fishsoup_client_t* client, suic_fishsoup_client_on_packet_fn on_packet) {
|
|
fishsoup_client_on_packet(client, on_packet);
|
|
}
|
|
|
|
int suic_fishsoup_client_poll(fishsoup_client_t* client) {
|
|
return fishsoup_client_poll(client);
|
|
}
|
|
|
|
void suic_fishsoup_client_destroy(fishsoup_client_t* client) {
|
|
fishsoup_client_destroy(client);
|
|
}
|
|
|
|
/* Server functions */
|
|
fishsoup_server_t* suic_fishsoup_server_create(int port) {
|
|
return fishsoup_server(port);
|
|
}
|
|
|
|
int suic_fishsoup_server_clients(fishsoup_server_t* server) {
|
|
return fishsoup_server_clients(server);
|
|
}
|
|
|
|
void suic_fishsoup_server_loop(fishsoup_server_t* server) {
|
|
fishsoup_server_loop(server);
|
|
}
|
|
|
|
void suic_fishsoup_server_on_client(fishsoup_server_t* server, suic_fishsoup_server_on_client_fn on_client) {
|
|
fishsoup_server_on_client(server, on_client);
|
|
}
|
|
|
|
void suic_fishsoup_server_destroy(fishsoup_server_t* server) {
|
|
fishsoup_server_destroy(server);
|
|
}
|
|
|
|
/* Packet functions */
|
|
int suic_fishsoup_packet_send(int fd, fishsoup_packet_t* packet) {
|
|
return fishsoup_packet_send(fd, packet);
|
|
}
|
|
|
|
int suic_fishsoup_packet_recv(int fd, fishsoup_packet_t* packet) {
|
|
return fishsoup_packet_recv(fd, packet);
|
|
}
|
|
|
|
// ============================================================================
|
|
// MATH FUNCTIONS
|
|
// ============================================================================
|
|
|
|
float suic_sinf(float x) { return sinf(x); }
|
|
|
|
float suic_cosf(float x) { return cosf(x); }
|
|
|
|
float suic_expf(float x) { return expf(x); }
|
|
|
|
float suic_fmaxf(float a, float b) { return fmaxf(a, b); }
|