kiss3d 0.44.0

Keep it simple, stupid, 2D and 3D graphics engine for Rust.
Documentation
// Hardware-backend intersection: inline ray queries against a TLAS with
// instancing. Exposes the same contract as the compute backend:
//     trace_closest(origin, dir, tmax) -> Hit
//     trace_any(origin, dir, tmax)     -> bool
//
// The `enable wgpu_ray_query;` directive is emitted by the root kernel module
// (gated `@if(hardware)`). The TLAS holds one instance per scene copy; each
// instance points at a shared per-mesh BLAS and carries (via its custom index) an
// index into the `instances` buffer, where the hit shader reads the mesh +
// material. A committed `primitive_index` is the triangle within that mesh's BLAS;
// the mesh's `tri_offset` maps it to the global (mesh-local) triangle/vertex tables.

// Shared scene data + types from the preamble module (unused items strip away).
import package::rt_preamble::{
    RtVertex, RtTriangle, RtMaterial, RtLight, FrameUniforms, Hit, RtEmitter,
    BSDF_OPAQUE, BSDF_GLASS, BSDF_METAL, BSDF_EMISSIVE, PI, EPS, T_MAX,
    frame, pixels, vertices, triangles, materials, lights, emitters,
    tex_array, tex_sampler, env_tex, env_sampler
};

struct RtMeshDesc {
    node_offset: u32,
    tri_offset: u32,
    pad0: u32,
    pad1: u32,
};

struct RtInstance {
    world_to_object: mat4x4<f32>,
    object_to_world: mat4x4<f32>,
    mesh_id: u32,
    material_id: u32,
    pad0: u32,
    pad1: u32,
};

@group(1) @binding(4) var tlas: acceleration_structure;
@group(1) @binding(11) var<storage, read> meshes: array<RtMeshDesc>;
@group(1) @binding(12) var<storage, read> instances: array<RtInstance>;

const RAY_FLAG_NONE: u32 = 0u;
const RAY_FLAG_TERMINATE_ON_FIRST_HIT: u32 = 0x04u;
const RAY_QUERY_INTERSECTION_NONE: u32 = 0u;

fn trace_closest(ro: vec3<f32>, rd: vec3<f32>, tmax: f32) -> Hit {
    var hit: Hit;
    hit.valid = false;
    hit.t = tmax;
    hit.normal = vec3<f32>(0.0, 1.0, 0.0);
    hit.geom_normal = vec3<f32>(0.0, 1.0, 0.0);
    hit.material_id = 0u;
    hit.tri_area = 0.0;
    hit.uv = vec2<f32>(0.0);

    var rq: ray_query;
    rayQueryInitialize(&rq, tlas, RayDesc(RAY_FLAG_NONE, 0xFFu, EPS, tmax, ro, rd));
    // All geometry is flagged OPAQUE, so the driver commits hits internally and this
    // loop normally runs zero or one iterations. The `guard` is a safety valve: on a
    // misbehaving (experimental) ray-query backend a never-terminating proceed would
    // otherwise hang the GPU — and macOS has no compute watchdog, so that freezes the
    // whole machine. Starting small to test whether bounding the loop is what stops
    // the hang; raise it if a correct backend ever truncates traversal.
    var guard = 0u;
    while (rayQueryProceed(&rq) && guard < 32u) { guard = guard + 1u; }
    let isect = rayQueryGetCommittedIntersection(&rq);

    if (isect.kind != RAY_QUERY_INTERSECTION_NONE) {
        let inst = instances[isect.instance_custom_data];
        let mesh = meshes[inst.mesh_id];
        let tri = triangles[mesh.tri_offset + isect.primitive_index];
        let b = isect.barycentrics;
        let w = 1.0 - b.x - b.y;

        // Mesh-local positions/normal/uv, interpolated at the hit.
        let p0 = vertices[tri.v0].position;
        let p1 = vertices[tri.v1].position;
        let p2 = vertices[tri.v2].position;
        let local_n = vertices[tri.v0].normal * w
                    + vertices[tri.v1].normal * b.x
                    + vertices[tri.v2].normal * b.y;

        // World-space triangle (via the instance transform the query provides) for
        // the geometric normal + area.
        let pw0 = isect.object_to_world * vec4<f32>(p0, 1.0);
        let pw1 = isect.object_to_world * vec4<f32>(p1, 1.0);
        let pw2 = isect.object_to_world * vec4<f32>(p2, 1.0);
        let ng = cross(pw1 - pw0, pw2 - pw0);

        // Shading normal: inverse-transpose of object→world = transpose of the
        // 3x3 part of world→object.
        let w2o = isect.world_to_object;
        let nmat = transpose(mat3x3<f32>(w2o[0], w2o[1], w2o[2]));

        hit.valid = true;
        hit.t = isect.t;
        hit.normal = normalize(nmat * local_n);
        hit.geom_normal = normalize(ng);
        hit.material_id = inst.material_id;
        hit.tri_area = 0.5 * length(ng);
        let uv0 = vec2<f32>(vertices[tri.v0].u, vertices[tri.v0].v);
        let uv1 = vec2<f32>(vertices[tri.v1].u, vertices[tri.v1].v);
        let uv2 = vec2<f32>(vertices[tri.v2].u, vertices[tri.v2].v);
        hit.uv = uv0 * w + uv1 * b.x + uv2 * b.y;
    }

    return hit;
}

fn trace_any(ro: vec3<f32>, rd: vec3<f32>, tmax: f32) -> bool {
    var rq: ray_query;
    rayQueryInitialize(
        &rq,
        tlas,
        RayDesc(RAY_FLAG_TERMINATE_ON_FIRST_HIT, 0xFFu, EPS, tmax, ro, rd),
    );
    // See `trace_closest`: bounded for safety against a non-terminating proceed.
    var guard = 0u;
    while (rayQueryProceed(&rq) && guard < 32u) { guard = guard + 1u; }
    let isect = rayQueryGetCommittedIntersection(&rq);
    return isect.kind != RAY_QUERY_INTERSECTION_NONE;
}