nightshade 0.13.3

A cross-platform data-oriented game engine.
Documentation
const BRICK_SIZE: u32 = 8u;
const BRICK_CORNER_COUNT: u32 = 9u;

struct ComputeUniforms {
    brick_count: u32,
    terrain_enabled: u32,
    terrain_base_height: f32,
    terrain_material_id: u32,
    edit_count: u32,
    smoothness_scale: f32,
    terrain_seed: u32,
    terrain_octaves: u32,
    terrain_frequency: f32,
    terrain_amplitude: f32,
    terrain_gain: f32,
    _pad0: u32,
    _pad1: u32,
    _pad2: u32,
    _pad3: u32,
    _pad4: u32,
}

struct GpuSdfEdit {
    inverse_transform: mat4x4<f32>,
    primitive_type: u32,
    operation_type: u32,
    material_id: u32,
    smoothness: f32,
    uniform_scale: f32,
    param0: f32,
    param1: f32,
    param2: f32,
    param3: f32,
    _pad0: u32,
    _pad1: u32,
    _pad2: u32,
}

struct DirtyBrick {
    world_origin: vec3<f32>,
    voxel_size: f32,
    atlas_slot: u32,
    material_atlas_x: u32,
    material_atlas_y: u32,
    material_atlas_z: u32,
}

@group(0) @binding(0) var<uniform> uniforms: ComputeUniforms;
@group(0) @binding(1) var<storage, read> edits: array<GpuSdfEdit>;
@group(0) @binding(2) var<storage, read> dirty_bricks: array<DirtyBrick>;
@group(0) @binding(3) var brick_atlas: texture_storage_3d<r32float, write>;
@group(0) @binding(4) var material_atlas: texture_storage_3d<r32uint, write>;
@group(0) @binding(5) var<storage, read_write> surface_flags: array<atomic<u32>>;

fn sdf_sphere(point: vec3<f32>, radius: f32) -> f32 {
    return length(point) - radius;
}

fn sdf_box(point: vec3<f32>, half_extents: vec3<f32>) -> f32 {
    let q = abs(point) - half_extents;
    return length(max(q, vec3<f32>(0.0))) + min(max(q.x, max(q.y, q.z)), 0.0);
}

fn sdf_cylinder(point: vec3<f32>, radius: f32, half_height: f32) -> f32 {
    let d_xz = length(point.xz) - radius;
    let d_y = abs(point.y) - half_height;
    return length(max(vec2<f32>(d_xz, d_y), vec2<f32>(0.0))) + min(max(d_xz, d_y), 0.0);
}

fn sdf_torus(point: vec3<f32>, major_radius: f32, minor_radius: f32) -> f32 {
    let q = vec2<f32>(length(point.xz) - major_radius, point.y);
    return length(q) - minor_radius;
}

fn sdf_capsule(point: vec3<f32>, radius: f32, half_height: f32) -> f32 {
    let clamped_y = clamp(point.y, -half_height, half_height);
    let closest = vec3<f32>(0.0, clamped_y, 0.0);
    return length(point - closest) - radius;
}

fn sdf_plane(point: vec3<f32>, normal: vec3<f32>, offset: f32) -> f32 {
    return dot(point, normal) - offset;
}

fn evaluate_primitive(edit: GpuSdfEdit, local_point: vec3<f32>) -> f32 {
    var distance: f32;

    switch edit.primitive_type {
        case 0u: {
            distance = sdf_sphere(local_point, edit.param0);
        }
        case 1u: {
            distance = sdf_box(local_point, vec3<f32>(edit.param0, edit.param1, edit.param2));
        }
        case 2u: {
            distance = sdf_cylinder(local_point, edit.param0, edit.param1);
        }
        case 3u: {
            distance = sdf_torus(local_point, edit.param0, edit.param1);
        }
        case 4u: {
            distance = sdf_capsule(local_point, edit.param0, edit.param1);
        }
        case 5u: {
            distance = sdf_plane(local_point, vec3<f32>(edit.param0, edit.param1, edit.param2), edit.param3);
        }
        default: {
            distance = 1000.0;
        }
    }

    return distance * edit.uniform_scale;
}

fn smooth_min(a: f32, b: f32, k: f32) -> f32 {
    if k <= 0.0 {
        return min(a, b);
    }
    let h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);
    return mix(b, a, h) - k * h * (1.0 - h);
}

fn apply_csg(distance_a: f32, distance_b: f32, operation_type: u32, smoothness: f32) -> f32 {
    switch operation_type {
        case 0u: {
            return min(distance_a, distance_b);
        }
        case 1u: {
            return max(distance_a, -distance_b);
        }
        case 2u: {
            return max(distance_a, distance_b);
        }
        case 3u: {
            return smooth_min(distance_a, distance_b, smoothness);
        }
        case 4u: {
            return -smooth_min(-distance_a, distance_b, smoothness);
        }
        case 5u: {
            return -smooth_min(-distance_a, -distance_b, smoothness);
        }
        default: {
            return min(distance_a, distance_b);
        }
    }
}

fn apply_csg_with_material(
    distance_a: f32,
    material_a: u32,
    distance_b: f32,
    material_b: u32,
    operation_type: u32,
    smoothness: f32,
    dither: f32,
) -> vec2<f32> {
    var new_distance: f32;
    var new_material_f: f32;

    switch operation_type {
        case 0u: {
            new_distance = min(distance_a, distance_b);
            new_material_f = select(f32(material_b), f32(material_a), distance_a < distance_b);
        }
        case 1u: {
            new_distance = max(distance_a, -distance_b);
            new_material_f = f32(material_a);
        }
        case 2u: {
            new_distance = max(distance_a, distance_b);
            new_material_f = select(f32(material_b), f32(material_a), distance_a > distance_b);
        }
        case 3u: {
            let h = clamp(0.5 + 0.5 * (distance_b - distance_a) / smoothness, 0.0, 1.0);
            new_distance = mix(distance_b, distance_a, h) - smoothness * h * (1.0 - h);
            new_material_f = select(f32(material_b), f32(material_a), h > dither);
        }
        case 4u: {
            let h = clamp(0.5 - 0.5 * (distance_a + distance_b) / smoothness, 0.0, 1.0);
            new_distance = mix(distance_a, -distance_b, h) + smoothness * h * (1.0 - h);
            new_material_f = f32(material_a);
        }
        case 5u: {
            let h = clamp(0.5 - 0.5 * (distance_b - distance_a) / smoothness, 0.0, 1.0);
            new_distance = mix(distance_b, distance_a, h) + smoothness * h * (1.0 - h);
            new_material_f = select(f32(material_b), f32(material_a), h > dither);
        }
        default: {
            new_distance = min(distance_a, distance_b);
            new_material_f = select(f32(material_b), f32(material_a), distance_a < distance_b);
        }
    }

    return vec2<f32>(new_distance, new_material_f);
}

fn position_hash(pos: vec3<f32>) -> f32 {
    let x = sin(pos.x * 127.1 + pos.y * 311.7 + pos.z * 74.7) * 43758.5453;
    return abs(fract(x));
}

fn terrain_hash(ix: i32, iz: i32, seed: u32) -> f32 {
    var n = (ix * 1619) ^ (iz * 6971) ^ (i32(seed) * 1013);
    n = n * n * n;
    n = n ^ (n >> 13);
    return f32(n & 0x7fffffff) / 2147483647.0;
}

fn noised_2d(px: f32, pz: f32, seed: u32) -> vec3<f32> {
    let cell_x = i32(floor(px));
    let cell_z = i32(floor(pz));

    let wx = px - f32(cell_x);
    let wz = pz - f32(cell_z);

    let ux = wx * wx * wx * (wx * (wx * 6.0 - 15.0) + 10.0);
    let uz = wz * wz * wz * (wz * (wz * 6.0 - 15.0) + 10.0);

    let dux = 30.0 * wx * wx * (wx * (wx - 2.0) + 1.0);
    let duz = 30.0 * wz * wz * (wz * (wz - 2.0) + 1.0);

    let h00 = terrain_hash(cell_x, cell_z, seed);
    let h10 = terrain_hash(cell_x + 1, cell_z, seed);
    let h01 = terrain_hash(cell_x, cell_z + 1, seed);
    let h11 = terrain_hash(cell_x + 1, cell_z + 1, seed);

    let k0 = h00;
    let k1 = h10 - h00;
    let k2 = h01 - h00;
    let k3 = h00 - h10 - h01 + h11;

    let value = -1.0 + 2.0 * (k0 + k1 * ux + k2 * uz + k3 * ux * uz);
    let dvdx = 2.0 * dux * (k1 + k3 * uz);
    let dvdz = 2.0 * duz * (k2 + k3 * ux);

    return vec3<f32>(value, dvdx, dvdz);
}

fn terrain_fbm(world_pos: vec3<f32>) -> f32 {
    var px = world_pos.x * uniforms.terrain_frequency;
    var pz = world_pos.z * uniforms.terrain_frequency;
    var total = 0.0;
    var amplitude = uniforms.terrain_amplitude;
    var dx_sum = 0.0;
    var dz_sum = 0.0;

    for (var octave = 0u; octave < uniforms.terrain_octaves; octave += 1u) {
        let n = noised_2d(px, pz, uniforms.terrain_seed);
        dx_sum += n.y * 0.5;
        dz_sum += n.z * 0.5;
        let dampening = 1.0 / (1.0 + dx_sum * dx_sum + dz_sum * dz_sum);
        total += amplitude * n.x * dampening;
        let new_px = 1.6 * px - 1.2 * pz;
        let new_pz = 1.2 * px + 1.6 * pz;
        px = new_px;
        pz = new_pz;
        amplitude *= uniforms.terrain_gain;
    }

    return world_pos.y - uniforms.terrain_base_height - total;
}

fn atlas_base_for_slot(slot: u32, atlas_size: u32) -> vec3<u32> {
    let bricks_per_dim = atlas_size / BRICK_CORNER_COUNT;
    let bricks_per_slice = bricks_per_dim * bricks_per_dim;
    let z = slot / bricks_per_slice;
    let remainder = slot % bricks_per_slice;
    let y = remainder / bricks_per_dim;
    let x = remainder % bricks_per_dim;
    return vec3<u32>(x * BRICK_CORNER_COUNT, y * BRICK_CORNER_COUNT, z * BRICK_CORNER_COUNT);
}

@compute @workgroup_size(128)
fn evaluate_corners(@builtin(global_invocation_id) global_id: vec3<u32>) {
    let total_corners_per_brick = BRICK_CORNER_COUNT * BRICK_CORNER_COUNT * BRICK_CORNER_COUNT;
    let brick_index = global_id.x / total_corners_per_brick;
    let corner_index = global_id.x % total_corners_per_brick;

    if brick_index >= uniforms.brick_count {
        return;
    }

    let brick = dirty_bricks[brick_index];
    let corner_z = corner_index / (BRICK_CORNER_COUNT * BRICK_CORNER_COUNT);
    let remainder = corner_index % (BRICK_CORNER_COUNT * BRICK_CORNER_COUNT);
    let corner_y = remainder / BRICK_CORNER_COUNT;
    let corner_x = remainder % BRICK_CORNER_COUNT;

    let world_pos = brick.world_origin + vec3<f32>(f32(corner_x), f32(corner_y), f32(corner_z)) * brick.voxel_size;

    var distance: f32;
    if uniforms.terrain_enabled != 0u {
        distance = terrain_fbm(world_pos);
    } else {
        distance = 3.40282e+38;
    }

    for (var edit_index = 0u; edit_index < uniforms.edit_count; edit_index += 1u) {
        let edit = edits[edit_index];
        let local_point = (edit.inverse_transform * vec4<f32>(world_pos, 1.0)).xyz;
        let edit_distance = evaluate_primitive(edit, local_point);
        let scaled_smoothness = edit.smoothness * uniforms.smoothness_scale;
        distance = apply_csg(distance, edit_distance, edit.operation_type, scaled_smoothness);
    }

    let atlas_base = atlas_base_for_slot(brick.atlas_slot, textureDimensions(brick_atlas).x);
    let atlas_coord = vec3<i32>(atlas_base) + vec3<i32>(i32(corner_x), i32(corner_y), i32(corner_z));
    textureStore(brick_atlas, atlas_coord, vec4<f32>(distance, 0.0, 0.0, 0.0));

    if distance <= 0.0 {
        atomicOr(&surface_flags[brick_index], 1u);
    } else {
        atomicOr(&surface_flags[brick_index], 2u);
    }
}

@compute @workgroup_size(128)
fn evaluate_materials(@builtin(global_invocation_id) global_id: vec3<u32>) {
    let total_voxels_per_brick = BRICK_SIZE * BRICK_SIZE * BRICK_SIZE;
    let brick_index = global_id.x / total_voxels_per_brick;
    let voxel_index = global_id.x % total_voxels_per_brick;

    if brick_index >= uniforms.brick_count {
        return;
    }

    let brick = dirty_bricks[brick_index];
    let voxel_z = voxel_index / (BRICK_SIZE * BRICK_SIZE);
    let remainder = voxel_index % (BRICK_SIZE * BRICK_SIZE);
    let voxel_y = remainder / BRICK_SIZE;
    let voxel_x = remainder % BRICK_SIZE;

    let world_pos = brick.world_origin
        + vec3<f32>(f32(voxel_x) + 0.5, f32(voxel_y) + 0.5, f32(voxel_z) + 0.5) * brick.voxel_size;

    let dither = position_hash(world_pos);

    var distance: f32;
    var material: u32;
    if uniforms.terrain_enabled != 0u {
        distance = terrain_fbm(world_pos);
        material = uniforms.terrain_material_id;
    } else {
        distance = 3.40282e+38;
        material = 0u;
    }

    for (var edit_index = 0u; edit_index < uniforms.edit_count; edit_index += 1u) {
        let edit = edits[edit_index];
        let local_point = (edit.inverse_transform * vec4<f32>(world_pos, 1.0)).xyz;
        let edit_distance = evaluate_primitive(edit, local_point);
        let scaled_smoothness = edit.smoothness * uniforms.smoothness_scale;
        let result = apply_csg_with_material(distance, material, edit_distance, edit.material_id, edit.operation_type, scaled_smoothness, dither);
        distance = result.x;
        material = u32(result.y);
    }

    let material_coord = vec3<i32>(
        i32(brick.material_atlas_x + voxel_x),
        i32(brick.material_atlas_y + voxel_y),
        i32(brick.material_atlas_z + voxel_z)
    );
    textureStore(material_atlas, material_coord, vec4<u32>(material, 0u, 0u, 0u));
}