Nimbin2git.christianimmanuel.de / Games / strichmaennchen / object.c

strichmaennchen git · main

git clone https://git.christianimmanuel.de/games/strichmaennchen.gitwget https://git.christianimmanuel.de/games/strichmaennchen/archive/strichmaennchen.tar.gz
object.c 14.2 KB · 452 lines raw
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <float.h>

double fabs(double x);

#include "object.h"
#include "linalg.h"

void Collider_update(Collider *c);
Object *Object_create(ObjectType type, VertexPreset vertex, Vec3 position, Vec3 size, float weight) {
    Object *obj = malloc(sizeof(Object));
    if (!obj) return NULL;

    obj->type     = type;
    obj->position = position;
    obj->weight   = weight;
    obj->strength = 100;

    obj->vertex   = vertex;
    obj->rotation = (Vec3){0,0,0};
    obj->mesh     = mesh_default;
    obj->velocity = (Vec3){0,0,0};
    obj->angular_velocity = (Vec3){0,0,0};
    obj->collider.half_extents = vec3_scale(size, 0.5);

    switch (type) {
        case OBJ_TRIANGLE:
            obj->mesh = Mesh_createTriangle();
			obj->collider.isStatic = true;
            break;

        case OBJ_AXES:
            obj->mesh = Mesh_createAxes(200);
			obj->collider.isStatic = true;
            break;

        case OBJ_PYRAMID:
            obj->mesh = Mesh_createPyramid(4.0f, 4.0f);
			obj->collider.isStatic = true;
            break;

        case OBJ_CUBE:
            obj->mesh = Mesh_createCube(size, vertex);
			obj->collider.isStatic = false;
            break;
        case OBJ_CYLINDER:
            obj->mesh = Mesh_createCylinder(size.x, size.y, 30, vertex);
			obj->collider.isStatic = false;
            break;
        case OBJ_CYLINDER_STATIC:
            obj->mesh = Mesh_createCylinder(size.x, size.y, 30, vertex);
			obj->collider.isStatic = true;
            break;
        case OBJ_WALL:
            obj->mesh = Mesh_createCube(size, vertex);
			obj->collider.isStatic = true;
            break;
        case OBJ_HUMAN:
            obj->mesh = Mesh_createHuman(vertex, false);
			obj->collider.isStatic = false;
            break;
        case OBJ_HUMAN_PLAYER:
            obj->mesh = Mesh_createHuman(vertex, true);
			obj->collider.isStatic = false;
            break;
        case OBJ_GROUND:
            obj->mesh = Mesh_createCube(size, vertex);
			obj->collider.isStatic = true;
            break;
        case OBJ_TREE:
            obj->mesh = Mesh_createTree(3.0f, 0.3f, 8, 1.5f);
			obj->collider.isStatic = true;
            obj->position.y = -size.y/2.0f;
            break;
        case OBJ_HOUSE_MAIN:
            obj->mesh = Mesh_createHouse();
            obj->collider.isStatic = true;
            obj->position.y = -size.y/2.0f;
            break;
        case OBJ_BARN:
            obj->mesh = Mesh_createBarn();
            obj->collider.isStatic = true;
            obj->position.y = -size.y/2.0f;
            break;

        default:
            obj->mesh = mesh_default;
            obj->collider.half_extents = (Vec3){0,0,0};
			obj->collider.isStatic = true;
            fprintf(stderr, "Warning: unsupported ObjectType (%d)\n", type);
            break;
    }

    obj->position_center = (Vec3){0, obj->collider.half_extents.y, 0};
    obj->collider.position = obj->position_center;

    Collider_update(&obj->collider);
    return obj;
}

void Object_destroy(Object* obj) {
    free(obj);
}

void Object_render(Object *obj, Renderer *renderer, Camera *camera, Time* time) {
    if (!obj) return;

    TransformMatrices m;

    m.projection = camera->projection;

    m.view = camera->view;

    // Translate by bottom position + half height to center the mesh
    Vec3 renderPos = vec3_add(obj->position, (Vec3){0, obj->collider.half_extents.y, 0});

    m.model = mat4_identity();
    m.model = mat4_mul(m.model, mat4_translate(renderPos));

    // yaw around global Y (physics)
    m.model = mat4_mul(m.model, mat4_rotate_y(obj->rotation.y));

    // then apply local tilt (visual placement)
    m.model = mat4_mul(m.model, mat4_rotate_x(obj->rotation.x));
    m.model = mat4_mul(m.model, mat4_rotate_z(obj->rotation.z));

    //Mat4 mvp = mat4_mul(m.projection, mat4_mul(m.view, m.model));

    Mesh_draw(obj->mesh, renderer, &m, time, camera->position);
}
















Vec3 colliderCenterWorld(Object *o) {
    return vec3_add(o->position, o->collider.position);
}

float OBB_project(Collider *c, Vec3 axis) {
    // Projection of half-extents along axis
    return fabsf(c->half_extents.x * vec3_dot(axis, c->axes[0])) +
           fabsf(c->half_extents.y * vec3_dot(axis, c->axes[1])) +
           fabsf(c->half_extents.z * vec3_dot(axis, c->axes[2]));
}

bool OBB_overlap_MTV(Object *o_a, Object *o_b, Vec3 *mtv_axis, float *penetration) {
    Vec3 t = vec3_sub(colliderCenterWorld(o_b), colliderCenterWorld(o_a));

    Collider* a = &o_a->collider;
    Collider* b = &o_b->collider;

    Vec3 axesA[3] = {a->axes[0], a->axes[1], a->axes[2]};
    Vec3 axesB[3] = {b->axes[0], b->axes[1], b->axes[2]};

    Vec3 testAxes[15];
    int idx = 0;

    for (int i = 0; i < 3; i++) testAxes[idx++] = axesA[i];
    for (int i = 0; i < 3; i++) testAxes[idx++] = axesB[i];
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++)
            testAxes[idx++] = vec3_cross(axesA[i], axesB[j]);

    float minPen = FLT_MAX;
    Vec3 minAxis = {0,0,0};

    for (int i = 0; i < 15; i++) {
        Vec3 axis = testAxes[i];
        if (vec3_length(axis) < 1e-6f) continue;
        axis = vec3_normalize(axis);

        float projA = OBB_project(a, axis);
        float projB = OBB_project(b, axis);
        float dist  = fabsf(vec3_dot(t, axis));
        float pen   = projA + projB - dist;

        if (pen < 0)
            return false; // separating axis found

        if (pen < minPen) {
            minPen = pen;
            minAxis = axis;
        }
    }

    *mtv_axis = minAxis;
    *penetration = minPen;
    return true;
}


void Object_applyGravity(Object *obj, float dt) {
    if (obj->collider.isStatic || obj->is_grounded) return;

    obj->velocity.y -= 9.81f * dt;
}

void Collider_positionGround(Object* obj, Object* ground) {
    if (!ground->collider.isStatic) return;

    Collider* g = &ground->collider;
    Collider* o = &obj->collider;

    // Ground's Y-axis in world space
    Vec3 up = g->axes[1]; // assuming Y is "up"

    // Vector from obj center to ground center
    Vec3 delta = vec3_sub(colliderCenterWorld(obj), colliderCenterWorld(ground));

    // Project delta onto ground up-axis
    float distAlongUp = vec3_dot(delta, up);

    // Compute half extents along up-axis
    float objHalf = OBB_project(o, up);
    float groundHalf = OBB_project(g, up);

    // penetration = distance we need to move obj down to sit on top
    float penetration = (distAlongUp - (groundHalf + objHalf));
    if (penetration > 1.0f) penetration = 1.0f;

    // Only snap if penetrating (obj is above ground)
    if (penetration < 0) {
        // maximum step height: 1/3 of object height
        float max_step = obj->collider.half_extents.y*2/3.0f;
        if (obj->type != OBJ_HUMAN && obj->type != OBJ_HUMAN_PLAYER)
            max_step = 1e6f; // effectively unlimited for other objects

        if (-penetration <= max_step) {
            // move object along negative up-axis to sit on top
            obj->position = vec3_add(obj->position, vec3_scale(up, -penetration));
            obj->velocity.y = 0;
            obj->is_grounded = true;
        } else {
            obj->is_grounded = false;
        }
    }
}

static float calc_PushFactor(int obj_weight, int obj_strength, int other_weight) {
    if (obj_strength <= 0 || obj_weight <= 0)
        return 0.0f;

    float max_weight = obj_weight * (obj_strength / 50.0f);

    if (other_weight <= 0)
        return 1.0f;
    if (other_weight >= max_weight)
        return 0.0f;

    return 1.0f - (other_weight / max_weight);
}


void Object_resolveDynamicCollision(Object *obj, Object *other) {
    if (obj->collider.isStatic && other->collider.isStatic) return;

    Vec3 mtv_axis;
    float penetration;

    if (!OBB_overlap_MTV(obj, other, &mtv_axis, &penetration)) return;

    // Ensure MTV points from obj -> other
    Vec3 dir = vec3_sub(colliderCenterWorld(other), colliderCenterWorld(obj));
    if (vec3_dot(dir, mtv_axis) < 0) mtv_axis = vec3_scale(mtv_axis, -1.0f);
    mtv_axis = vec3_normalize(mtv_axis);

    const Vec3 world_up = {0,1,0};
    float verticalAlignment = fabsf(vec3_dot(mtv_axis, world_up));

    // --- Handle vertical collisions ---
    if (verticalAlignment > 0.70f) {
        if (other->collider.isStatic) {
            float dotUp = vec3_dot(mtv_axis, world_up);
            if (dotUp < 0.0f) {
                Collider_positionGround(obj, other);
                obj->velocity.y = 0.0f;
                obj->is_grounded = true;
                return;
            } else {
                if (obj->velocity.y > 0.0f) obj->velocity.y = 0.0f;
                return;
            }
        } else {
            obj->velocity.y = 0.0f;
            obj->is_grounded = true;
        }
    }

    // --- 1. Separate objects along MTV ---
    const float MAX_CORRECTION = 0.5f;
    if (penetration > MAX_CORRECTION) penetration = MAX_CORRECTION;

    if (!other->collider.isStatic) {
        float pushRatioObjToOther = calc_PushFactor(obj->weight, obj->strength, other->weight);
        if (pushRatioObjToOther > 0.0f) {
            Vec3 pushVec = vec3_scale(mtv_axis, penetration * pushRatioObjToOther);
            other->position = vec3_add(other->position, pushVec);
        } else {
            obj->position = vec3_sub(obj->position, vec3_scale(mtv_axis, penetration));
            obj->velocity = vec3_scale(obj->velocity, 0.2f);
        }
    } else if (!obj->collider.isStatic) {
        obj->position = vec3_sub(obj->position, vec3_scale(mtv_axis, penetration));
    }

    // --- 2. Apply horizontal linear velocity ---
    Vec3 horizontalAxis = (Vec3){ mtv_axis.x, 0.0f, mtv_axis.z };
    if (vec3_length(horizontalAxis) > 1e-6f)
        horizontalAxis = vec3_normalize(horizontalAxis);
    else
        horizontalAxis = (Vec3){0,0,0};

    float pushStrength = 2.0f;
    float pushRatioObjToOther = calc_PushFactor(obj->weight, obj->strength, other->weight);

    if (!other->collider.isStatic && pushRatioObjToOther > 0.0f) {
        Vec3 dv = vec3_scale(horizontalAxis, pushStrength * pushRatioObjToOther);
        other->velocity = vec3_add(other->velocity, dv);
    }

    if (!obj->collider.isStatic && pushRatioObjToOther > 0.0f) {
        obj->velocity = vec3_sub(obj->velocity, vec3_scale(horizontalAxis, pushStrength * 0.2f * pushRatioObjToOther));
    }

    // --- 3. Apply angular velocity with stronger torque ---
    float angularPushFactor = 10.5f; // increase this for stronger spin
    if (!other->collider.isStatic && vec3_length(horizontalAxis) > 1e-6f && pushRatioObjToOther > 0.0f) {
        Vec3 offset = vec3_sub(colliderCenterWorld(obj), colliderCenterWorld(other));
        offset.y = 0.0f;
        Vec3 impulse = vec3_scale(horizontalAxis, pushStrength * pushRatioObjToOther);
        float torque = (offset.x * impulse.z - offset.z * impulse.x) * angularPushFactor;
        other->angular_velocity.y += torque / (other->weight > 0 ? other->weight : 1.0f);
    }
}


void Object_applyFriction(Object *obj, float dt) {
    if (obj->collider.isStatic) return;

    // Base friction coefficient (tweak)
    float frictionCoeff = 0.5f;

    // Friction proportional to weight (mass)
    float friction = frictionCoeff * fminf(obj->weight, 10.0f);

    // Apply only to horizontal velocity (XZ)
    obj->velocity.x -= obj->velocity.x * friction * dt;
    obj->velocity.z -= obj->velocity.z * friction * dt;

    // Stop very small velocities to prevent jitter
    if (fabs(obj->velocity.x) < 0.01f) obj->velocity.x = 0;
    if (fabs(obj->velocity.z) < 0.01f) obj->velocity.z = 0;
}

void Object_resolveCollisions(Object *obj, Object **worldObjects, int count) {
    for (int j = 0; j < count; j++) {
        Object *other = worldObjects[j];
        if (other == obj) continue;

        // Only snap to static ground objects
        if (other->collider.isStatic) {
            Vec3 mtv_axis;
            float penetration;

            if (OBB_overlap_MTV(obj, other, &mtv_axis, &penetration)) {
                // Check if collision is mainly vertical (i.e., obj landed on top)
                float verticalAlignment = fabsf(vec3_dot(mtv_axis, other->collider.axes[1]));

                if (verticalAlignment > 0.7f) { // mostly vertical collision
                    Collider_positionGround(obj, other);
                }
            }
        }

        // Resolve dynamic collisions for everything
        Object_resolveDynamicCollision(obj, other);
    }
}

void Collider_update(Collider *c) {
    // Build rotation matrix from Euler angles
    Mat4 rot = mat4_identity();
    rot = mat4_mul(rot, mat4_rotate_y(c->rotation.y));
    rot = mat4_mul(rot, mat4_rotate_x(c->rotation.x));
    rot = mat4_mul(rot, mat4_rotate_z(c->rotation.z));

    // Local basis
    Vec3 x = {1,0,0};
    Vec3 y = {0,1,0};
    Vec3 z = {0,0,1};

    // Transform into world space
    c->axes[0] = mat4_mul_vec3_dir(rot, x);
    c->axes[1] = mat4_mul_vec3_dir(rot, y);
    c->axes[2] = mat4_mul_vec3_dir(rot, z);

    // Normalize (in case scaling sneaks in later)
    c->axes[0] = vec3_normalize(c->axes[0]);
    c->axes[1] = vec3_normalize(c->axes[1]);
    c->axes[2] = vec3_normalize(c->axes[2]);
}

void Object_setRotation(Object *obj, Vec3 rot) {
    obj->rotation = rot;
    obj->collider.rotation = rot;
    Collider_update(&obj->collider);
}

void Object_rotate(Object *obj, Vec3 delta) {
    obj->rotation = vec3_add(obj->rotation, delta);
    obj->collider.rotation = obj->rotation;  // keep collider in sync
    Collider_update(&obj->collider);        // update axes
}

void Object_update(Object *obj, Object **worldObjects, int count, float dt) {
    if (obj->collider.isStatic) return;

    Object_applyGravity(obj, dt);

    // --- integrate physics yaw only (prevent physics from changing visual tilt) ---
    float yaw_delta = obj->angular_velocity.y * dt;
    obj->rotation.y += yaw_delta;           // only affect yaw from physics
    obj->angular_velocity.y *= 0.9f;        // damping for yaw

    // keep collider orientation in sync with current rotation
    obj->collider.rotation = obj->rotation;
    Collider_update(&obj->collider);

    Vec3 total = vec3_add(obj->movement, obj->velocity);
    if (vec3_length(total) > 0.0f) {
        obj->is_grounded = false;
        obj->position = vec3_add(obj->position, vec3_scale(total, dt));
        Object_resolveCollisions(obj, worldObjects, count);
    }

    Object_applyFriction(obj, dt);
    obj->movement = (Vec3){0,0,0};
}