dynamis-soft 0.7.0

Soft body and fluid particle simulation
Documentation
@group(0) @binding(4) var<uniform> params: StepParams;
@group(0) @binding(5) var<storage, read> particles: array<SoftParticle>;
@group(0) @binding(6) var<storage, read> body_states: array<BodyState>;
@group(0) @binding(7) var<storage, read> body_descs: array<BodyDescriptor>;
@group(0) @binding(8) var<storage, read> colliders: array<Collider>;
@group(0) @binding(9) var<storage, read> elements: array<SoftElement>;
@group(0) @binding(10) var<storage, read> adjacency: array<u32>;
@group(0) @binding(11) var<storage, read_write> contacts: array<SoftContact>;
@group(0) @binding(12) var<storage, read> bodies: array<SoftBody>;

struct ParticleContact {
    separation: f32,
    normal: vec3f,
    point: vec3f,
    kind: u32,
    partner: u32,
    group: u32,
    friction: f32,
}

fn no_contact() -> ParticleContact {
    var contact: ParticleContact;
    contact.separation = 3.402823466e38;
    contact.normal = vec3f(0.0, 1.0, 0.0);
    contact.point = vec3f(0.0);
    contact.kind = ENTRY_KIND_COLLIDER;
    contact.partner = NO_SLOT;
    contact.group = NO_BODY;
    contact.friction = 0.0;
    return contact;
}

fn sphere_probe(center: vec3f, radius: f32) -> WorldShape {
    var world: WorldShape;
    world.kind = SHAPE_SPHERE;
    world.radius = radius;
    world.half_height = 0.0;
    world.center = center;
    world.half_extents = vec3f(0.0);
    world.rotation = vec4f(0.0, 0.0, 0.0, 1.0);
    world.source = 0u;
    world.scale = vec3f(1.0);
    return world;
}

fn particle_contact(
    world: WorldShape,
    center: vec3f,
    radius: f32,
    out_triangle: ptr<function, u32>,
) -> ParticleContact {
    var contact = no_contact();
    if (world.kind == SHAPE_PLANE) {
        let normal = plane_normal(world);
        contact.separation = dot(center - world.center, normal) - radius;
        contact.normal = normal;
        contact.point = center - normal * radius;
        return contact;
    }
    let probe = sphere_probe(center, radius);
    if (world.kind == SHAPE_MESH || world.kind == SHAPE_HEIGHTFIELD) {
        let closest = scene_convex_closest(world, probe, out_triangle);
        contact.separation = closest.distance;
        contact.normal = closest.normal;
        contact.point = closest.point_a;
        return contact;
    }
    let hit = convex_hit(probe, world);
    contact.separation = hit.distance;
    contact.normal = -hit.normal;
    contact.point = hit.point;
    return contact;
}

fn contact_precedes(candidate: ParticleContact, held: ParticleContact) -> bool {
    if (candidate.separation < held.separation) {
        return true;
    }
    return candidate.separation == held.separation && candidate.partner < held.partner;
}

fn directly_linked(first: u32, second: u32) -> bool {
    let particle = particles[first];
    for (var slot = 0u; slot < particle.neighbour_count; slot = slot + 1u) {
        let element = elements[adjacency[particle.neighbour_offset + slot] >> ELEMENT_ROLE_BITS];
        if ((element.kind & ELEMENT_BROKEN) != 0u) {
            continue;
        }
        for (var role = 0u; role < ELEMENT_PARTICLES; role = role + 1u) {
            if (element.particles[role] == second) {
                return true;
            }
        }
    }
    return false;
}

fn visit_entry(
    node: u32,
    box: Aabb,
    cell_size: f32,
    center: vec3f,
    radius: f32,
    self_index: u32,
    self_owner: u32,
    held: ptr<function, ParticleContact>,
) {
    if (!aabb_overlaps(entry_box(node), box)) {
        return;
    }
    if (!reach_holds(node, box, cell_size)) {
        return;
    }
    let info = entries[node].info;
    if (entry_kind(info) == ENTRY_KIND_COLLIDER) {
        let collider = colliders[entry_index(info)];
        if (collider.kind == SHAPE_NONE || (collider.flags & COLLIDER_SENSOR) != 0u) {
            return;
        }
        var triangle = NO_TRIANGLE;
        var contact = particle_contact(world_collider(body_states[entry_group(node)], collider), center, radius, &triangle);
        contact.kind = ENTRY_KIND_COLLIDER;
        contact.partner = entry_index(info);
        contact.group = entry_group(node);
        contact.friction = surface_material(collider, triangle).friction;
        if (contact_precedes(contact, *held)) {
            *held = contact;
        }
        return;
    }
    let other_index = entry_index(info);
    if (other_index == self_index) {
        return;
    }
    let other = particles[other_index];
    if (other.owner == NO_BODY) {
        return;
    }
    if (other.owner == self_owner && directly_linked(self_index, other_index)) {
        return;
    }
    let delta = other.position.xyz - center;
    let distance = length(delta);
    let separation = distance - (radius + other.position.w);
    if (separation >= 0.0) {
        return;
    }
    var contact = no_contact();
    contact.separation = separation;
    contact.normal = sign_normalize(delta);
    contact.point = center + contact.normal * (radius - separation * 0.5);
    contact.kind = ENTRY_KIND_PARTICLE;
    contact.partner = other_index;
    contact.group = other.owner;
    contact.friction = other.velocity.w;
    if (contact_precedes(contact, *held)) {
        *held = contact;
    }
}

fn scan_cell(
    level: u32,
    cell_size: f32,
    coord: vec3i,
    box: Aabb,
    center: vec3f,
    radius: f32,
    self_index: u32,
    self_owner: u32,
    held: ptr<function, ParticleContact>,
) {
    let range = entry_bounds_of(level, coord);
    for (var entry = range.x; entry < range.y; entry = entry + 1u) {
        visit_entry(
            entry_node(entry),
            box,
            cell_size,
            center,
            radius,
            self_index,
            self_owner,
            held,
        );
    }
}

fn scan_level(
    level: u32,
    box: Aabb,
    center: vec3f,
    radius: f32,
    self_index: u32,
    self_owner: u32,
    held: ptr<function, ParticleContact>,
) {
    let live = entry_live();
    let first = entry_bounds(live, level << LEVEL_KEY_SHIFT).x;
    let end = entry_bounds(live, (level + 1u) << LEVEL_KEY_SHIFT).x;
    let cell_size = level_cell_size(level, grid_base_cell());
    var scanned = 0u;
    for (var entry = first; entry < end; entry = entry + 1u) {
        if (scanned >= REACH_CELL_BUDGET) {
            break;
        }
        scanned = scanned + 1u;
        visit_entry(
            entry_node(entry),
            box,
            cell_size,
            center,
            radius,
            self_index,
            self_owner,
            held,
        );
    }
}

fn no_contact_record() -> SoftContact {
    var record: SoftContact;
    record.normal = vec3f(0.0, 1.0, 0.0);
    record.depth = 0.0;
    record.point = vec3f(0.0);
    record.friction = 0.0;
    record.partner = NO_SLOT;
    record.group = NO_BODY;
    record.kind = ENTRY_KIND_PARTICLE;
    record.partner_inverse_mass = 0.0;
    return record;
}

fn work(index: u32) {
    let particle = particles[index];
    if (particle.owner == NO_BODY || bodies[particle.owner].sleeping != 0u) {
        contacts[index] = no_contact_record();
        return;
    }
    let radius = particle.position.w;
    let center = particle.position.xyz;
    var held = no_contact();
    if (radius > 0.0) {
        let reach = max(bitcast<f32>(counter_load(COUNTER_PARTICLE_REACH)), 0.0);
        let box = reach_box(center, radius + reach);
        var occupied = counter_load(COUNTER_GRID_LEVELS);
        while (occupied != 0u) {
            let level = countTrailingZeros(occupied);
            occupied = occupied & (occupied - 1u);
            let cell_size = level_cell_size(level, grid_base_cell());
            let cells = reach_cells(box, cell_size);
            if (reach_span(cells) <= REACH_CELL_BUDGET) {
                for (var x = cells.min.x; x <= cells.max.x; x = x + 1) {
                    for (var y = cells.min.y; y <= cells.max.y; y = y + 1) {
                        for (var z = cells.min.z; z <= cells.max.z; z = z + 1) {
                            scan_cell(
                                level,
                                cell_size,
                                vec3i(x, y, z),
                                box,
                                center,
                                radius,
                                index,
                                particle.owner,
                                &held,
                            );
                        }
                    }
                }
            } else {
                scan_level(
                    level,
                    box,
                    center,
                    radius,
                    index,
                    particle.owner,
                    &held,
                );
            }
        }
    }
    var record = no_contact_record();
    if (held.partner != NO_SLOT && held.separation < 0.0) {
        record.normal = held.normal;
        record.depth = -held.separation;
        record.point = held.point;
        record.friction = max(held.friction, 0.0);
        record.partner = held.partner;
        record.group = held.group;
        record.kind = held.kind;
        if (held.kind == ENTRY_KIND_PARTICLE) {
            record.partner_inverse_mass = particles[held.partner].prev_position.w;
        }
    }
    contacts[index] = record;
}