rusting_engine 1.4.0

Vulkan 3D game engine with GPU-accelerated physics for massive physics-heavy scenes
#version 450
layout(local_size_x = 256, local_size_y = 1, local_size_z = 1) in;

#include <physics_abi.glsl>
#include <physics_shapes.glsl>

struct ConditionInstruction {
    uvec4 words;
    vec4 values;
};
struct RuleState {
    uvec4 config;
    vec4 timing;
    uvec4 state;
};
layout(set = 0, binding = 1) readonly buffer Conditions { ConditionInstruction data[]; } conditions;
layout(set = 0, binding = 2) buffer Rules { RuleState data[]; } rules;
layout(set = 0, binding = 5) readonly buffer Commands { BodyCommand data[]; } commands;

struct Collider {
    mat4 model;
    vec4 shape;
    vec4 velocity;
    vec4 material;
    uvec4 layers;
};
layout(set = 0, binding = 7) readonly buffer Colliders { Collider data[]; } colliders;

struct ColliderNode {
    vec4 lower;
    vec4 upper;
    // x = first node after this subtree; y = collider index for a leaf,
    // or UINT_MAX for an internal node; z = first collider index after it.
    uvec4 links;
};
layout(set = 0, binding = 12) readonly buffer ColliderTree { ColliderNode data[]; } collider_tree;

void collider_distance(Collider collider, vec3 point, out vec3 normal, out float distance) {
    shape_distance(collider.shape, mat3(collider.model), collider.model[3].xyz, point, normal, distance);
}

// Visits nearby CPU/static colliders in their stable upload order. A node's
// sphere test is conservative for every shape it contains. The body's
// rotation only shapes its reach along each contact normal; contacts apply
// no torque.
void collide_with_colliders(inout PhysicsState body, BodyShape own) {
    if (pc.collider_count == 0u) return;
    mat3 rotation = rotation_of(body.model);
    float radius = bounding_radius(own.shape);
    uint node_index = 0u;
    uint next_collider = 0u;
    uint node_count = pc.collider_count * 2u - 1u;
    while (node_index < node_count) {
        ColliderNode node = collider_tree.data[node_index];
        if (node.links.z <= next_collider) {
            node_index = node.links.x;
            continue;
        }
        vec3 center = body.model[3].xyz;
        vec3 nearest = clamp(center, node.lower.xyz, node.upper.xyz);
        precise vec3 delta = center - nearest;
        precise float radius_squared = radius * radius;
        if (sim_dot(delta, delta) > radius_squared) {
            next_collider = node.links.z;
            node_index = node.links.x;
            continue;
        }
        if (node.links.y == 0xffffffffu) {
            node_index++;
            continue;
        }
        uint index = node.links.y;
        next_collider = index + 1u;
        node_index = node.links.x;
        Collider collider = colliders.data[index];
        if ((own.layers.x & collider.layers.y) == 0u || (collider.layers.x & own.layers.y) == 0u) continue;
        vec3 normal;
        float distance;
        collider_distance(collider, body.model[3].xyz, normal, distance);
        precise float depth = support(own.shape, rotation, normal) - distance;
        if (depth <= 0.0) continue;
        precise vec3 position = body.model[3].xyz + normal * depth;
        body.model[3].xyz = position;
        // The correction can move the body into a later collider whose tree
        // branch was skipped. Re-query from the root, but do not revisit any
        // collider the original list-order loop already passed.
        node_index = 0u;
        body.velocity.w = 1.0;
        precise vec3 relative = body.velocity.xyz - collider.velocity.xyz;
        float closing = sim_dot(relative, normal);
        if (closing >= 0.0) continue;
        // Bounce only above 1 m/s so resting contacts settle.
        float restitution = closing < -1.0 ? max(own.material.y, collider.material.y) : 0.0;
        precise vec3 tangent = relative - normal * closing;
        float tangent_speed = sim_length(tangent);
        if (tangent_speed > 0.000001) {
            precise float friction_squared = own.material.x * collider.material.x;
            float friction = sim_sqrt(friction_squared);
            relative -= sim_div(tangent, tangent_speed) * min(tangent_speed, -friction * closing);
        }
        relative -= normal * closing * (1.0 + restitution);
        precise vec3 velocity = relative + collider.velocity.xyz;
        body.velocity.xyz = velocity;
    }
}

bool is_dynamic(PhysicsState body) {
    return body.properties.z > 0.5 && body.properties.z < 1.5 && body.properties.x > 0.0;
}

void apply_command(inout PhysicsState body, BodyCommand command) {
    // The CPU checked the generation too; this guards against a table
    // rebuilt between the check and the dispatch.
    if (command.header.z != body.metadata.y) return;
    uint kind = command.header.y;
    if (kind == COMMAND_TELEPORT) {
        body.model = mat4(command.values[0], command.values[1], command.values[2], command.values[3]);
    } else if (kind == COMMAND_SET_VELOCITY) {
        body.velocity.xyz = command.values[0].xyz;
        body.angular_velocity.xyz = command.values[1].xyz;
    } else if (kind == COMMAND_IMPULSE && is_dynamic(body)) {
        precise vec3 velocity = body.velocity.xyz + sim_div(command.values[0].xyz, body.properties.x);
        body.velocity.xyz = velocity;
    } else if (kind == COMMAND_FORCE && is_dynamic(body)) {
        precise vec3 velocity = body.velocity.xyz + sim_div(command.values[0].xyz, body.properties.x) * pc.dt;
        body.velocity.xyz = velocity;
    } else if (kind == COMMAND_SET_CUSTOM_VALUES) {
        body.custom_values = command.values[0];
    }
}

void apply_commands(inout PhysicsState body, uint body_index) {
    // This step's commands are sorted by body index: find this body's first.
    uint end = pc.command_first + pc.command_count;
    uint low = pc.command_first;
    uint high = end;
    while (low < high) {
        uint middle = (low + high) / 2u;
        if (commands.data[middle].header.x < body_index) low = middle + 1u;
        else high = middle;
    }
    for (uint index = low; index < end && commands.data[index].header.x == body_index; index++) {
        apply_command(body, commands.data[index]);
    }
}

float read_field(PhysicsState body, uint field) {
    vec3 position = body.model[3].xyz;
    vec3 scale = vec3(sim_length(body.model[0].xyz), sim_length(body.model[1].xyz), sim_length(body.model[2].xyz));
    if (field == 0u) return position.x;
    if (field == 1u) return position.y;
    if (field == 2u) return position.z;
    if (field == 3u) return body.velocity.x;
    if (field == 4u) return body.velocity.y;
    if (field == 5u) return body.velocity.z;
    if (field == 6u) return body.angular_velocity.x;
    if (field == 7u) return body.angular_velocity.y;
    if (field == 8u) return body.angular_velocity.z;
    if (field == 9u) return scale.x;
    if (field == 10u) return scale.y;
    if (field == 11u) return scale.z;
    if (field == 12u) return body.properties.x;
    if (field == 13u) return body.properties.y;
    if (field == 14u) return sim_length(body.velocity.xyz);
    if (field >= 0x100u && field < 0x104u) return body.custom_values[field - 0x100u];
    return 0.0;
}

bool compare_value(float left, float right, uint comparison) {
    if (comparison == 0u) return left < right;
    if (comparison == 1u) return left <= right;
    if (comparison == 2u) return left > right;
    if (comparison == 3u) return left >= right;
    precise float difference = abs(left - right);
    if (comparison == 4u) return difference <= 0.00001;
    return difference > 0.00001;
}

bool evaluate_condition(PhysicsState body, uint offset, uint count) {
    bool stack[64];
    uint stack_size = 0u;
    for (uint index = 0u; index < count && index < 64u; index++) {
        ConditionInstruction instruction = conditions.data[offset + index];
        uint operation = instruction.words.x;
        if (operation == 1u) {
            stack[stack_size++] = compare_value(
                read_field(body, instruction.words.y),
                instruction.values.x,
                instruction.words.z
            );
        } else if (operation == 2u) {
            float value = read_field(body, instruction.words.y);
            stack[stack_size++] = value >= instruction.values.x && value <= instruction.values.y;
        } else if (operation == 3u) {
            stack[stack_size++] = body.velocity.w > 0.5;
        } else if (operation == 4u) {
            stack[stack_size++] = sim_length(body.velocity.xyz) < 0.02 && sim_length(body.angular_velocity.xyz) < 0.02;
        } else if (operation == 5u) {
            stack[stack_size++] = pc.elapsed >= instruction.values.x;
        } else if (operation == 16u && stack_size >= 2u) {
            bool right = stack[--stack_size];
            stack[stack_size - 1u] = stack[stack_size - 1u] && right;
        } else if (operation == 17u && stack_size >= 2u) {
            bool right = stack[--stack_size];
            stack[stack_size - 1u] = stack[stack_size - 1u] || right;
        } else if (operation == 18u && stack_size >= 1u) {
            stack[stack_size - 1u] = !stack[stack_size - 1u];
        }
    }
    return stack_size == 1u && stack[0];
}

vec4 event_payload(PhysicsState body, uint payload_kind) {
    if (payload_kind == 1u) return vec4(body.model[3].xyz, 1.0);
    if (payload_kind == 2u) return body.velocity;
    if (payload_kind == 3u) return body.angular_velocity;
    if (payload_kind == 5u) return body.custom_values;
    return vec4(0.0);
}

void main() {
    uint body_index = gl_GlobalInvocationID.x;
    if (body_index >= pc.body_count) return;

    PhysicsState body = bodies.data[body_index];
    if (pc.command_count > 0u) apply_commands(body, body_index);
    // The contact pass before this one already reset velocity.w.
    // properties.z: 0 = fixed, 1 = dynamic, 2 = kinematic.
    // Solver 3 is Custom: its own shader moves the body after this step.
    precise vec3 from_solver = abs(body.custom_values.xxx - vec3(2.0, 3.0, 4.0));
    if (is_dynamic(body) && from_solver.y > 0.5) {
        precise vec3 velocity = body.velocity.xyz;
        if (from_solver.z < 0.5) {
            // Space mode attracts bodies toward the origin. The force is
            // softened near the target so bodies do not explode numerically.
            vec3 to_target = -body.model[3].xyz;
            float distance_squared = sim_dot(to_target, to_target);
            if (distance_squared > 0.000001) {
                vec3 direction = sim_normalize(to_target);
                float safe_distance_squared = max(distance_squared, 4.0);
                velocity += direction
                    * sim_div(500.0, safe_distance_squared)
                    * body.properties.y * pc.dt;
            }
        } else {
            velocity += vec3(pc.gravity_x, pc.gravity_y, pc.gravity_z)
                * body.properties.y * pc.dt;
        }
        precise vec3 position = body.model[3].xyz + velocity * pc.dt;
        body.velocity.xyz = velocity;
        body.model[3].xyz = position;
        BodyShape own = body_shapes.data[body_index];
        // Solver 2 is NoCollision.
        if (own.shape.x < 2.5 && from_solver.x > 0.5) {
            collide_with_colliders(body, own);
        }
    }
    bodies.data[body_index] = body;

    uint rule_offset = body.metadata.z;
    uint rule_count = body.metadata.w;
    for (uint local_rule = 0u; local_rule < rule_count; local_rule++) {
        uint rule_index = rule_offset + local_rule;
        RuleState rule = rules.data[rule_index];
        bool current = evaluate_condition(body, rule.config.x, rule.config.y);
        bool previous = rule.state.z != 0u;
        bool already_emitted = rule.state.y != 0u;
        bool should_emit = false;
        if (rule.config.w == 0u) should_emit = current && !previous;
        else if (rule.config.w == 1u) should_emit = !current && previous;
        else if (rule.config.w == 2u) should_emit = current;
        else if (rule.config.w == 3u) should_emit = current && !already_emitted;

        precise float since_emitted = pc.elapsed - rule.timing.y;
        bool cooldown_ready = since_emitted >= rule.timing.x;
        if (should_emit && cooldown_ready) {
            emit_event(body, rule.config.z, rule.state.x, event_payload(body, rule.state.x));
            rule.timing.y = pc.elapsed;
            rule.state.y = 1u;
        }
        rule.state.z = current ? 1u : 0u;
        rules.data[rule_index] = rule;
    }
}