WebGPU Cloth Simulation 07 - WebGPU Cloth Simulation Shader Example (Mass Spring System) - Part 2

Real-Time Web-Based Cloth Simulation Shader Code (WGSL) Breakdown






WebGPU Shading Language (WGSL) is a modern, robust, and efficient shading language designed to work seamlessly with the WebGPU API. WGSL is tailored for WebGPU, offering a syntax and semantics that is powerful for developers aiming to create advanced graphical and computational effects directly in web browsers.

WGSL draws inspiration from existing shading languages like GLSL and HLSL but introduces a more streamlined and consistent syntax. This language is designed to be secure and avoid pitfalls that commonly occur in GPU programming, making it ideal for both experienced developers and those new to GPU programming.


WGSL Shader Code Breakdown:

1. Object Material Shader - MakeModelData()

The WGSL material shader is designed to handle the lighting and shading of 3D objects in a scene. It includes both vertex and fragment shaders to transform vertex data into screen space and apply lighting effects based on the object's material properties and lighting conditions. Additionally, this shader uses uniform buffers to pass transformation matrices, camera position, and light information to the GPU.

    a. Uniform Buffers:

        - TransformData mat4x4<f32>: transforms vertex positions and normals from model space to screen space.
        - cameraPos vec3<f32>: represents the position of the camera in the scene.
        - LightData: provides lighting information for shading calculations.

    b. Structures:

        TransformData: contains the model, view, and projection matrices.
        LightData: contains the light position, color, intensity, specular strength, and shininess.
        VertexOutput: defines the output of the vertex shader, including position, texture coordinates, normal, and fragment position.

    c. Vertex Shader:

        Inputs:
            vertexPosition: The position of the vertex in model space.
            vertexTexCoord: The texture coordinates of the vertex.
            vertexNormal: The normal vector of the vertex.
        Outputs:
            Position: The transformed position of the vertex in screen space.
            TexCoord: The texture coordinates passed to the fragment shader.
            Normal: The transformed normal vector in world space.
            FragPos: The position of the fragment in world space.
        Transformations:
            Transforms vertex positions and normals using the model, view, and projection matrices.
            Calculates the fragment position in world space for lighting calculations.

    d. Fragment Shader:

        Inputs:
            TexCoord: The texture coordinates from the vertex shader.
            Normal: The normal vector from the vertex shader.
            FragPos: The fragment position from the vertex shader.
        Lighting Calculations:
            Ambient Lighting: Adds a base color to the fragment.
            Diffuse Lighting: Calculates the diffuse component based on the light direction and normal vector.
            Specular Lighting: Calculates the specular highlight based on the view direction and reflection vector.
        Final Color Calculation:
            Combines the ambient, diffuse, and specular components to determine the final color of the fragment.
        Output:
            The final color of the fragment, including lighting effects.

struct TransformData {
    model: mat4x4<f32>,
    view: mat4x4<f32>,
    projection: mat4x4<f32>,
};

@binding(0) @group(0) var<uniform> transformUBO: TransformData;    
@binding(1) @group(0) var<uniform> cameraPos: vec3<f32>;
@binding(2) @group(0) var<uniform> lightUBO: LightData; // Bind Light information
   
struct LightData {
    position: vec3<f32>,
    color: vec4<f32>,
    intensity: f32,
    specularStrength: f32,
    shininess: f32,
};
   
struct VertexOutput {
    @builtin(position) Position: vec4<f32>,
    @location(0) TexCoord: vec2<f32>,
    @location(1) Normal: vec3<f32>,
    @location(2) FragPos: vec3<f32>, // Add fragment position
};
   
@vertex
fn vs_main(
    @location(0) vertexPosition: vec3<f32>,
    @location(1) vertexTexCoord: vec2<f32>,
    @location(2) vertexNormal: vec3<f32>
) -> VertexOutput {
    var output: VertexOutput;
    output.Position =
        transformUBO.projection * transformUBO.view *
        transformUBO.model * vec4<f32>(vertexPosition, 1.0);
    output.TexCoord = vertexTexCoord;
    output.Normal = (transformUBO.model * vec4<f32>(vertexNormal, 0.0)).xyz;
    output.FragPos = (transformUBO.model * vec4<f32>(vertexPosition, 1.0)).xyz;
    return output;
}
   
@fragment
fn fs_main(
    @location(0) TexCoord: vec2<f32>,
    @location(1) Normal: vec3<f32>,
    @location(2) FragPos: vec3<f32>
) -> @location(0) vec4<f32> {

    let lightPos: vec3<f32> = lightUBO.position;
    let lightColor: vec4<f32> = lightUBO.color;
    let lightIntensity: f32 = lightUBO.intensity;
       
    // Calculate ambient light
    let ambientColor: vec4<f32> = vec4<f32>(0.319551, 0.435879, 0.802236, 1.0);
   
    // Calculate diffuse
    let norm: vec3<f32> = normalize(Normal);
    let lightDir: vec3<f32> = normalize(lightPos - FragPos);
    let diff: f32 = max(dot(norm, lightDir), 0.0);
    let diffuse: vec4<f32> =
        lightColor * diff * lightIntensity *
        vec4<f32>(0.319551, 0.435879, 0.802236, 1.0);
   
    // Calculate specular
    let viewDir: vec3<f32> = normalize(cameraPos - FragPos);
    let reflectDir: vec3<f32> = reflect(-lightDir, norm);
    let spec: f32 = pow(max(dot(viewDir, reflectDir), 0.0), 16);
    let specular: vec4<f32> =
        lightColor * spec * vec4<f32>(0.429134, 0.429134, 0.429134, 1.0);

    // Calculate final color
    var finalColor: vec4<f32> =
        ambientColor + diffuse +
        specular + vec4<f32>(0.000000, 0.000000, 0.000000, 1.0);

    return vec4<f32>(finalColor.x, finalColor.y, finalColor.z, 1.0);
       
}


2. Particle Shader - createRenderPipeline()

The WGSL particle shader is designed for rendering particles in the cloth simulation. This shader includes both vertex and fragment stages to transform particle positions and pass color information through the pipeline, resulting in the particles being displayed on the screen with their respective colors.

    a. Uniform Buffers:

        - TransformData mat4x4<f32>: transforms particle positions from model space to screen space.

    b. Structures:

        TransformData: contains the model, view, and projection matrices.
        - VertexInput: defines the input (position, color) to the vertex shader
        - FragmentOutput: defines the output of the vertex shader, including (position, color) and input to the fragment shader.

    c. Vertex Shader:

        Inputs:
            vertexInput: A VertexInput struct containing the position and color of the particle. 
        Outputs:
            FragmentOutput: A struct containing the transformed position and color of the particle.
        Transformations:
            Computes the Model-View-Projection (MVP) matrix by multiplying the projection, view, and model matrices.
            Transforms the particle position from model space to screen space using the MVP matrix.
            Passes the color of the particle through to the fragment shader.

    d. Fragment Shader:

        Inputs:
            in: A FragmentOutput struct containing the position and color of the particle from the vertex shader.
        Output:
            The final color of the fragment, which is the same as the input color from the vertex shader.

struct TransformData {
    model: mat4x4<f32>,
    view: mat4x4<f32>,
    projection: mat4x4<f32>,
};

@group(0) @binding(0) var<uniform> transformUBO: TransformData;

struct VertexInput {
    @location(0) position: vec3<f32>,
    @location(1) color: vec3<f32>,
};

struct FragmentOutput {
    @builtin(position) Position: vec4<f32>,
    @location(0) Color: vec4<f32>,
};

@vertex
fn vs_main(vertexInput: VertexInput) -> FragmentOutput {
    var output: FragmentOutput;
    let modelViewProj =
        transformUBO.projection * transformUBO.view * transformUBO.model;

    output.Position = modelViewProj * vec4<f32>(vertexInput.position, 1.0);
    output.Color = vec4<f32>(vertexInput.color, 1.0);
    return output;
}

@fragment
fn fs_main(in: FragmentOutput) -> @location(0) vec4<f32> {
    return in. Color;
}


3. Spring Shader - createSpringPipeline()

The WGSL spring shader is designed for rendering springs in a cloth simulation where springs connect particles. This shader includes both vertex and fragment stages to transform vertex positions and apply basic lighting effects to render the springs in green color.

    a. Uniform Buffers:

        - TransformData mat4x4<f32>: transforms particle positions from model space to clip space.

    b. Structures:

        TransformData: contains the model, view, and projection matrices.
        - VertexInput: defines the input (position) to the vertex shader
        - FragmentOutput: defines the output of the vertex shader, including (position, color) and input to the fragment shader.

    c. Vertex Shader:

        Inputs:
            vertexInput: A VertexInput struct containing the position of the vertex.
        Outputs:
            FragmentOutput: A struct containing the transformed position and color of the particle.
        Transformations:
            Computes the Model-View-Projection (MVP) matrix by multiplying the projection, view, and model matrices.
            Transforms the vertex position from model space to clip space using the MVP matrix.
            Set the output color to green (RGBA: 0.0, 1.0, 0.0, 1.0)

    d. Fragment Shader:

        Inputs:
            in: A FragmentOutput struct containing the position and color of the particle from the vertex shader.
        Output:
            The final color of the fragment, with the basic lighting effects applied.
        Lighting Calculations:
            Defines and normalizes a light direction vector (not used further in this example).
            Defines an ambient lighting factor.
            Calculates the total lighting as the sum of ambient and diffuse components (simplified to 1.0).
            Returns the final color multiplied by the lighting factor, with the alpha component set to 1.0.

struct TransformData {
    model: mat4x4<f32>,
    view: mat4x4<f32>,
    projection: mat4x4<f32>,
};

@group(0) @binding(0) var<uniform> transformUBO: TransformData;

struct VertexInput {
    @location(0) position: vec3<f32>
};

struct FragmentOutput {
    @builtin(position) Position: vec4<f32>,
    @location(0) Color: vec4<f32>,
};

@vertex
fn vs_main(vertexInput: VertexInput) -> FragmentOutput {
    var output: FragmentOutput;
    let modelViewProj =
    transformUBO.projection * transformUBO.view * transformUBO.model;
    output.Position = modelViewProj * vec4<f32>(vertexInput.position, 1.0);
    // Sets the output color to green (RGBA: 0.0, 1.0, 0.0, 1.0).
    output.Color = vec4<f32>(0.0, 1.0, 0.0, 1.0);
    return output;
}

@fragment
fn fs_main(in: FragmentOutput) -> @location(0) vec4<f32> {
    let lightDir = normalize(vec3<f32>(0.0, 20.0, 5.0));
    // Defines the ambient lighting factor.
    let ambient = 0.1;
    let lighting = ambient + (1.0 - ambient);
    let color = vec3<f32>(in.Color.xyz);
    return vec4<f32>(color * lighting, 1.0);
}


4. Texture Shader - createTrianglePipeline()

The WGSL texture shader is designed to apply textures to cloth objects and simulate realistic lighting effects. This shader includes both vertex and fragment stages to transform vertex data, apply textures, and compute lighting based on a light source and camera position. The shader uses uniform buffers for transformation matrices, light information, and camera position.

    a. Uniform Buffers:

        - TransformData `mat4x4<f32>`: transforms vertex positions and normals from model space to clip space.
        - cameraPos `vec3<f32>`: represents the position of the camera in the scene.
        - LightData: provides lighting information for shading calculations.
        - Alpha `f32`: represents the alpha (transparency) value of the texture.

    b. Textures and Samplers:

        myTexture:
            Type: texture_2d<f32>
            Usage: Represents the 2D texture applied to the object.
            Binding: @binding(1) @group(0) specifies the binding location for the texture.
        mySampler:
            Type: sampler
            Usage: Used to sample the texture.
            Binding: @binding(2) @group(0) specifies the binding location for the sampler.

    c. Structures:

        TransformData: contains the model, view, and projection matrices.
        LightData: contains the light position, color, intensity, specular strength, and shininess.
        VertexOutput: defines the output of the vertex shader, including position, texture coordinates, normal, and fragment position.

    d. Vertex Shader:

        Inputs:
            vertexPosition: The position of the vertex in model space.
            vertexTexCoord: The texture coordinates of the vertex.
            vertexNormal: The normal vector of the vertex.
        Outputs:
            Position: The transformed position of the vertex in screen space.
            TexCoord: The texture coordinates passed to the fragment shader.
            Normal: The transformed normal vector in world space.
            FragPos: The position of the fragment in world space.
        Transformations:
            Computes the Model-View-Projection (MVP) matrix by multiplying the projection, view, and model matrices.
            Transforms the vertex position from model space to clip space using the MVP matrix.
            Passes the texture coordinates and transformed normal to the fragment shader.
            Calculates the fragment position in world space for lighting calculations.

    e. Fragment Shader:

        Inputs:
            TexCoord: The texture coordinates from the vertex shader.
            Normal: The normal vector from the vertex shader.
            FragPos: The fragment position from the vertex shader.
        Output:
            The final color of the fragment, including lighting effects.
        Lighting Calculations:
            Ambient Lighting: Adds a base color to the fragment.
            Diffuse Lighting: Calculates the diffuse component based on the light direction and normal vector.
            Specular Lighting: Calculates the specular highlight based on the view direction and reflection vector.
        Final Color Calculation:
            Combines the ambient, diffuse, and specular components to determine the final color of the fragment.
            Returns the final color with the alpha value applied.

struct TransformData {
    model: mat4x4<f32>,
    view: mat4x4<f32>,
    projection: mat4x4<f32>,
};

@binding(0) @group(0) var<uniform> transformUBO: TransformData;
@binding(1) @group(0) var myTexture: texture_2d<f32>;
@binding(2) @group(0) var mySampler: sampler;
@binding(3) @group(0) var<uniform> cameraPos: vec3<f32>;    
   
struct LightData {
    position: vec3<f32>,
    color: vec4<f32>,
    intensity: f32,
    specularStrength: f32,
    shininess: f32,
};

@binding(4) @group(0) var<uniform> lightUBO: LightData; // Bind light information
@binding(5) @group(0) var<uniform> alpha: f32;
   
struct VertexOutput {
    @builtin(position) Position: vec4<f32>,
    @location(0) TexCoord: vec2<f32>,
    @location(1) Normal: vec3<f32>,
    @location(2) FragPos: vec3<f32>, // Add fragment position
};
   
@vertex
fn vs_main(
    @location(0) vertexPosition: vec3<f32>,
    @location(1) vertexTexCoord: vec2<f32>,
    @location(2) vertexNormal: vec3<f32>
) -> VertexOutput {

    var output: VertexOutput;
    output.Position =
        transformUBO.projection * transformUBO.view *
        transformUBO.model * vec4<f32>(vertexPosition, 1.0);
    output.TexCoord = vertexTexCoord;
    output.Normal = (transformUBO.model * vec4<f32>(vertexNormal, 0.0)).xyz;
    output.FragPos = (transformUBO.model * vec4<f32>(vertexPosition, 1.0)).xyz;
    return output;
}
   
@fragment
fn fs_main(
    @location(0) TexCoord: vec2<f32>,
    @location(1) Normal: vec3<f32>,
    @location(2) FragPos: vec3<f32>
) -> @location(0) vec4<f32> {

    let lightPos: vec3<f32> = lightUBO.position;
    let lightColor: vec4<f32> = lightUBO.color;
    let lightIntensity: f32 = lightUBO.intensity;
       
    // Calculate ambient light
    let ambientColor: vec4<f32> = vec4<f32>(1.0, 1.0, 1.0, 1.0) * 0.001;
   
    // Calculate diffuse
    let norm: vec3<f32> = normalize(Normal);
    let lightDir: vec3<f32> = normalize(lightPos - FragPos);
    let diff: f32 = max(dot(norm, lightDir), 0.0);
    let diffuse: vec4<f32> =
        lightColor * diff * lightIntensity * vec4<f32>(1.0, 1.0, 1.0, 1.0);
   
    // Calculate specular
    let viewDir: vec3<f32> = normalize(cameraPos - FragPos);
    let reflectDir: vec3<f32> = reflect(-lightDir, norm);
    let spec: f32 = pow(max(dot(viewDir, reflectDir), 0.0), lightUBO.shininess);
    let specular: vec4<f32> =
        lightColor * spec * vec4<f32>(0.429134, 0.429134, 0.429134, 1.0);

    // Calculate final color
    var finalColor: vec4<f32> = ambientColor + diffuse + specular;

    return vec4<f32>(finalColor.x, finalColor.y, finalColor.z, 1.0);
}


5. Compute Shader - createParticlePipeline()

The compute shader provided is designed to update the positions and velocities of particles in a simulation. It handles external forces, gravity, and floor collisions. The shader reads from and writes to storage buffers to perform its calculations.

    a. Bindings:

        positions:
            Type: array<f32>
            Usage: Stores the positions of the particles.
            Binding: @group(0) @binding(0)

        velocities:
            Type: array<f32>
            Usage: Stores the velocities of the particles.
            Binding: @group(0) @binding(1)

        fixed:
            Type: array<u32>
            Usage: Indicates whether a particle is fixed (immovable).
            Binding: @group(0) @binding(2)

        force:
            Type: array<f32>
            Usage: Stores the forces acting on the particles.
            Binding: @group(0) @binding(3)

        prevPosition:
            Type: array<f32>
            Usage: Stores the previous positions of the particles.
            Binding: @group(0) @binding(4)

        externalForce:
            Type: vec3<f32>
            Usage: Represents an external force applied to the particles.
            Binding: @group(0) @binding(5)

    b. Helper Functions:

        getPosition(index: u32) -> vec3<f32>:
            Retrieves the position of a particle given its index.

        getVelocity(index: u32) -> vec3<f32>:
            Retrieves the velocity of a particle given its index.

        getForce(index: u32) -> vec3<f32>:
            Retrieves the force acting on a particle given its index.

    c. Main Compute Function: main

        Attributes:
            @compute: Indicates this is a compute shader.
            @workgroup_size(256): Specifies the number of workgroups to execute in parallel.

        Inputs:
            @builtin(global_invocation_id) global_id: Provides the global invocation ID, which is used to identify the current workgroup and particle index.

        Implementation:
            Fixed Particle Check: Checks if the particle is fixed (immovable) and skips updates if true.
            Position and Velocity Retrieval: Retrieves the current position and velocity of the particle.
            Force Application: Retrieves the force acting on the particle and scales it.
            Store Previous Position: Stores the current position in the prevPosition buffer.
            Floor Collision: Checks if the particle is below the floor (y < 0) and adjusts position and velocity accordingly.
            Gravity Application: Applies gravity to the particle.
            Velocity and Position Update: Updates the velocity and position of the particle based on the applied forces and gravity.
            External Force Handling: Applies an external force to the particle if specified.
            Buffer Updates: Writes the updated velocities and positions back to their respective buffers.

@group(0) @binding(0) var<storage, read_write> positions: array<f32>;
@group(0) @binding(1) var<storage, read_write> velocities: array<f32>;    
@group(0) @binding(2) var<storage, read_write> fixed: array<u32>;
@group(0) @binding(3) var<storage, read_write> force: array<f32>;  
@group(0) @binding(4) var<storage, read_write> prevPosition: array<f32>;
@group(0) @binding(5) var<uniform> externalForce : vec3<f32>;    

fn getPosition(index: u32) -> vec3<f32> {
    return vec3<f32>
    (positions[index * 3], positions[index * 3 + 1], positions[index * 3 + 2]);
}
   
fn getVelocity(index: u32) -> vec3<f32> {
    return vec3<f32>
    (velocities[index * 3], velocities[index * 3 + 1], velocities[index * 3 + 2]);
}

fn getForce(index: u32) -> vec3<f32> {
    return vec3<f32>
    (force[index * 3] / 2.0, force[index * 3 + 1] / 2.0, force[index * 3 + 2] / 2.0);
}

@compute
@workgroup_size(256)
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
    let index: u32 = global_id.x;
    var fixed = fixed[index];

    if fixed == 1 {
        return;
    }

    var pos = getPosition(index);
    var vel = getVelocity(index);
    var f = getForce(index) * 0.1;

    prevPosition[index * 3 + 0] = pos.x;
    prevPosition[index * 3 + 1] = pos.y;
    prevPosition[index * 3 + 2] = pos.z;
       
        //floor collisions
    if pos.y < 0.0 {
        pos.y += 0.0001;
        vel *= -0.0;
    }

    var gravity: vec3<f32> = vec3<f32>(0.0, -32.6, 0.0);
    var deltaTime: f32 = 0.001; // Assuming 60 FPS for simplicity
    vel += ((f + gravity) * deltaTime);
    pos += (vel * deltaTime);

    if externalForce.z != 0.0 {
        if index < 600 && pos.z < 120.0 {
            pos.z += 0.1;
            vel.y = -32.6;
            vel.z = 20.0;
        } else {
                //pos.y += 0.01;
        }
        vel.z = 10.0;
    }


    velocities[index * 3 + 0] = vel.x;
    velocities[index * 3 + 1] = vel.y;
    velocities[index * 3 + 2] = vel.z;

    positions[index * 3 + 0] = pos.x;
    positions[index * 3 + 1] = pos.y;
    positions[index * 3 + 2] = pos.z;

}    


6. Normal Update Compute Shader - createUpdateNormalPipeline()

The WGSL normal update compute shader is designed to calculate and update the normals for vertices. This is crucial for rendering tasks, as normals are used in lighting calculations to determine how light interacts with surfaces. The shader processes each triangle in the mesh, computes the normal vector, and accumulates these normals at each vertex.

    a. Bindings:

        positions:
            Type: array<f32>
            Usage: Stores the positions of the particles.
            Binding: @group(0) @binding(0)

        - triangleCalculationBuffer:
            Type: array<triangles>
            Usage: Stores the indices of the vertices that form each triangle.
            Binding: @group(0) @binding(1)

        - tempNormal:
            Type: array<atomicI32>
            Usage: Temporarily stores the accumulated normals for each vertex.
            Binding: @group(0) @binding(2)

        - numTriangles:
            Type: u32
            Usage: Represents the total number of triangles in the mesh.
            Binding: @group(0) @binding(3)

    b. Structures:

        triangles: Fields: v1, v2, v3 - Indices of the vertices that form a triangle.
        atomicI32: Fields: value - An atomic integer used for safely accumulating normal values.

    c. Helper Functions:

        getPosition(index: u32) -> vec3<f32>:
            Retrieves the position of a particle given its index from the `positions` buffer.

        calculateNormal(p0: vec3<f32>, p1: vec3<f32>, p2: vec3<f32>) -> vec3<f32>:
            Calculates the normal vector for a triangle formed by the vertices at positions p0, p1, and p2.

    d. Main Compute Function: main

        Attributes:
            @compute: Indicates this is a compute shader.
            @workgroup_size(256): Specifies the number of workgroups to execute in parallel.

        Inputs:
            @builtin(global_invocation_id) global_id: Provides the global invocation ID, which is used to identify the current workgroup and particle index.

        Implementation:
            Bounds Check: Checks if the current ID exceeds the number of triangles.
            Triangle Data Retrieval: Fetches vertex indices for the current triangle from the triangleCalculationBuffer.
            Position Retrieval: Gets the positions of the vertices forming the triangle.
            Normal Calculation: Computes the normal vector for the triangle using the cross product of two edges.
            Normal Accumulation: Atomically adds the calculated normal to the tempNormal buffer for each vertex of the triangle, scaled by 1000 for precision.

@group(0) @binding(0) var<storage, read_write> positions: array<f32>;
@group(0) @binding(1) var<storage, read_write> triangleCalculationBuffer: array<triangles>;
@group(0) @binding(2) var<storage, read_write> tempNormal: array<atomicI32>;
@group(0) @binding(3) var<uniform> numTriangles: u32;

fn getPosition(index: u32) -> vec3<f32> {
    return vec3<f32>
    (positions[index * 3], positions[index * 3 + 1], positions[index * 3 + 2]);
}

fn calculateNormal(p0: vec3<f32>, p1: vec3<f32>, p2: vec3<f32>) -> vec3<f32> {
    var u = p1 - p0;
    var v = p2 - p0;
    return normalize(cross(u, v));
}

struct triangles {
        v1: f32,
        v2: f32,
        v3: f32,
    }

struct atomicI32 {
        value: atomic<i32>
    }

    @compute @workgroup_size(256)
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
    let id: u32 = global_id.x;
    if id >= numTriangles {
        return;
    }

        // Accessing triangle calculation data
    var triangle = triangleCalculationBuffer[id];
    var v1: u32 = u32(triangle.v1);
    var v2: u32 = u32(triangle.v2);
    var v3: u32 = u32(triangle.v3);

        // Fetching positions from the positions buffer
    var p0: vec3<f32> = getPosition(v1);
    var p1: vec3<f32> = getPosition(v2);
    var p2: vec3<f32> = getPosition(v3);

        // Calculate the normal for this triangle
    var normal: vec3<f32> = calculateNormal(p0, p1, p2);

    atomicAdd(&tempNormal[v1 * 3 + 0].value, i32(normal.x * 1000));
    atomicAdd(&tempNormal[v1 * 3 + 1].value, i32(normal.y * 1000));
    atomicAdd(&tempNormal[v1 * 3 + 2].value, i32(normal.z * 1000));

    atomicAdd(&tempNormal[v2 * 3 + 0].value, i32(normal.x * 1000));
    atomicAdd(&tempNormal[v2 * 3 + 1].value, i32(normal.y * 1000));
    atomicAdd(&tempNormal[v2 * 3 + 2].value, i32(normal.z * 1000));

    atomicAdd(&tempNormal[v3 * 3 + 0].value, i32(normal.x * 1000));
    atomicAdd(&tempNormal[v3 * 3 + 1].value, i32(normal.y * 1000));
    atomicAdd(&tempNormal[v3 * 3 + 2].value, i32(normal.z * 1000));
}


7. Normal Summation Compute Shader - createUpdateNormalPipeline()

The WGSL normal summation compute shader is designed to normalize and finalize the vertex normals calculated during a previous compute pass. This shader reads from a temporary buffer where normals were accumulated, normalizes them, and writes the final values to a normals buffer. This process ensures that the normals are ready for use in lighting calculations during rendering.

    a. Bindings:

        - tempNormal:
            Type: array<atomicI32>
            Usage: Temporarily stores the accumulated normals for each vertex.
            Binding: @group(0) @binding(0)

        - normal:
            Type: array<f32>
            Usage: Stores the final normalized normals for each vertex.
            Binding: @group(0) @binding(1)

        - maxConnectedTriangle:
            Type: u32
            Usage: Represents the maximum number of triangles connected to a vertex (not directly used in the shader).
            Binding: @group(0) @binding(2)

        - numParticles:
            Type: u32
            Usage: Represents the total number of particles (vertices).
            Binding: @group(0) @binding(3)

    b. Structures:

        atomicI32: Fields: value - An atomic integer used for safely accumulating normal values.

    c. Helper Functions:

        getNormal(index: u32) -> vec3<f32>:
            Retrieves the normal vector for a vertex given its index from the normal buffer.

    d. Main Compute Function: main

        Attributes:
            @compute: Indicates this is a compute shader.
            @workgroup_size(256): Specifies the number of workgroups to execute in parallel.

        Inputs:
            @builtin(global_invocation_id) global_id: Provides the global invocation ID, used to identify the current workgroup and vertex index.

        Implementation:
            Bounds Check: Ensures the current ID does not exceed the number of particles.
            Normal Initialization: Initializes a normal vector n to zero.
            Load Accumulated Normals: Reads the accumulated normal components from the tempNormal buffer using atomic loads.
            Reset Temporary Normals: Resets the accumulated normals in the tempNormal buffer to zero using atomic stores.
            Convert and Normalize: Converts the accumulated normal values from integers to floats, scales them down, and normalizes the resulting vector.

@group(0) @binding(0) var<storage, read_write> tempNormal: array<atomicI32>;
@group(0) @binding(1) var<storage, read_write> normal: array<f32>;
@group(0) @binding(2) var<uniform> maxConnectedTriangle: u32;
@group(0) @binding(3) var<uniform> numParticles: u32;

fn getNormal(index: u32) -> vec3<f32> {
    return vec3<f32>
    (normal[index * 3], normal[index * 3 + 1], normal[index * 3 + 2]);
}

struct atomicI32 {
    value: atomic<i32>
}

@compute
@workgroup_size(256)
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
    let id = global_id.x;

    if id >= numParticles {return;}

    var n = getNormal(id);
    n.x = 0.0;
    n.y = 0.0;
    n.z = 0.0;

    let tempX = atomicLoad(&tempNormal[id * 3 + 0].value);
    let tempY = atomicLoad(&tempNormal[id * 3 + 1].value);
    let tempZ = atomicLoad(&tempNormal[id * 3 + 2].value);

    atomicStore(&tempNormal[id * 3 + 0].value, i32(0));
    atomicStore(&tempNormal[id * 3 + 1].value, i32(0));
    atomicStore(&tempNormal[id * 3 + 2].value, i32(0));

    n.x = f32(tempX) / 1000.0;
    n.y = f32(tempY) / 1000.0;
    n.z = f32(tempZ) / 1000.0;

    n = normalize(n);

    normal[id * 3 + 0] = f32(n.x);
    normal[id * 3 + 1] = f32(n.y);
    normal[id * 3 + 2] = f32(n.z);
}


8. Spring Update Shader - createSpringForceComputePipeline()

The WGSL spring update compute shader is designed to calculate the forces exerted by springs in a physical simulation. This shader updates the forces acting on particles connected by springs, taking into account spring constants and damping effects. It processes each spring in the system, computes the spring force, and accumulates these forces atomically in a buffer. Below is a detailed breakdown of its functionality.

    a. Bindings:

        - positions:
            Type: array<f32>
            Usage: Stores the positions of the particles.
            Binding: @group(0) @binding(0)

        - velocities:
            Type: array<f32>
            Usage: Stores the velocities of the particles.
            Binding: @group(0) @binding(1)

        - springs:
            Type: array<Spring>
            Usage: Stores the data for each spring in the system.
            Binding: @group(0) @binding(2)

        - numSprings:
            Type: u32
            Usage: Represents the total number of springs in the system.
            Binding: @group(0) @binding(3)

        - nodeForce:
            Type: array<atomicI32>
            Usage: Stores the forces acting on each particle, accumulated atomically.
            Binding: @group(0) @binding(4)

        - numParticles:
            Type: u32
            Usage: Represents the total number of particles in the system
            Binding: @group(0) @binding(5)

    b. Structures:

        Spring: index1, index2, ks, kd, mRestLen, target1, target2 - Indices of the connected particles, spring constants, rest length, and target indices for connections.
        atomicI32: value - An atomic integer used for safely accumulating force values.

    c. Helper Functions:

        getPosition(index: u32) -> vec3<f32>:
            Retrieves the position of a particle given its index from the positions buffer.

        getVelocity(index: u32) -> vec3<f32>:
            Retrieves the velocity of a particle given its index from the velocities buffer.

    d. Main Compute Function: main

        Attributes:
            @compute: Indicates this is a compute shader.
            @workgroup_size(256): Specifies the number of workgroups to execute in parallel.

        - Inputs:
            @builtin(global_invocation_id) global_id: Provides the global invocation ID, used to identify the current workgroup and spring index.

        - Implementation:
            Bounds Check: Ensures the current ID does not exceed the number of springs.
            Spring Data Retrieval: Fetches the indices of the connected particles and spring parameters from the springs buffer.
            Position and Velocity Retrieval: Gets the positions and velocities of the particles connected by the spring.
            Force Calculation:
                - Direction Vectors: Computes the direction and length of the spring.
                - Spring Force: Calculates the spring force based on Hooke's Law (F = -kx).
                - Damping Force: Computes the damping force based on the relative velocities of the particles.
                - Total Force: Combines the spring and damping forces.
            Force Accumulation: 
                Atomically adds the calculated forces to the nodeForce buffer for each connected particle.
                Scales the force values by 100.0 for precision during accumulation.

@group(0) @binding(0) var<storage, read_write> positions: array<f32>;
@group(0) @binding(1) var<storage, read_write> velocities: array<f32>;
@group(0) @binding(2) var<storage, read> springs: array<Spring>;
@group(0) @binding(3) var<uniform> numSprings: u32;
@group(0) @binding(4) var<storage, read_write> nodeForce: array<atomicI32>;
@group(0) @binding(5) var<uniform> numParticles: u32;

struct Spring {
    index1: f32,
    index2: f32,
    ks: f32,
    kd: f32,
    mRestLen: f32,
    target1: f32,
    target2: f32,
};

struct atomicI32 {
    value: atomic<i32>
}

fn getPosition(index: u32) -> vec3<f32> {
    return vec3<f32>
    (positions[index * 3], positions[index * 3 + 1], positions[index * 3 + 2]);
}
   
fn getVelocity(index: u32) -> vec3<f32> {
    return vec3<f32>
    (velocities[index * 3], velocities[index * 3 + 1], velocities[index * 3 + 2]);
}


@compute
@workgroup_size(256)
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
    let id = global_id.x;

    if id >= numSprings {return;}

    var spring = springs[id];

    var i1 = u32(spring.index1);
    var i2 = u32(spring.index2);

    var pos1 = getPosition(i1);
    var pos2 = getPosition(i2);

    var vel1 = getVelocity(i1);
    var vel2 = getVelocity(i2);

    var posDirection = pos2 - pos1;
    var velDirection = vel2 - vel1;

    var len = length(posDirection);
    var forceDirection = normalize(posDirection);
    var spforce = (len - spring.mRestLen) * spring.ks;

    var s1 = dot(vel1, forceDirection);
    var s2 = dot(vel2, forceDirection);

    var damp = dot(velDirection, forceDirection) / len * spring.kd;

    var estimatedForce = forceDirection * (spforce + damp) / len;              

    atomicAdd(&nodeForce[i1 * 3 + 0].value, i32(estimatedForce.x * 100.0));
    atomicAdd(&nodeForce[i1 * 3 + 1].value, i32(estimatedForce.y * 100.0));
    atomicAdd(&nodeForce[i1 * 3 + 2].value, i32(estimatedForce.z * 100.0));

    atomicAdd(&nodeForce[i2 * 3 + 0].value, i32(-estimatedForce.x * 100.0));
    atomicAdd(&nodeForce[i2 * 3 + 1].value, i32(-estimatedForce.y * 100.0));
    atomicAdd(&nodeForce[i2 * 3 + 2].value, i32(-estimatedForce.z * 100.0));
}


9. Node Force Summation Compute Shader - createNodeForceSummationPipeline()

The WGSL node force summation compute shader is designed to accumulate and normalize the forces acting on each particle (node) in a simulation. This shader consists of two main functions: main, which performs the force accumulation and normalization, and initialize, which resets the forces and prepares the buffers for the next simulation step. Below is a detailed breakdown of its functionality.

    a. Bindings:

        - nodeForce:
            Type: array<atomicI32>
            Usage: Stores the accumulated forces for each particle, allowing for atomic operations.
            Binding: @group(0) @binding(0)

        - force:
            Type: array<f32>
            Usage: Stores the final forces for each particle.
            Binding: @group(0) @binding(1)

        - numParticles:
            Type: u32
            Usage: Represents the total number of particles in the system.
            Binding: @group(0) @binding(2)

    b. Structures:

        atomicI32: value - An atomic integer used for safely accumulating force values.

    c. Helper Functions:

        getForce(index: u32) -> vec3<f32>:
            Retrieves the force vector for a particle given its index from the force buffer.

    d. Main Compute Function: main

        Attributes:
            @compute: Indicates this is a compute shader.
            @workgroup_size(256): Specifies the number of workgroups to execute in parallel.

        - Inputs:
            @builtin(global_invocation_id) global_id: Provides the global invocation ID, used to identify the current workgroup and spring index.

        - Implementation:
            Bounds Check: Ensures the current ID does not exceed the number of particles.
            Force Retrieval: Retrieves the current force for the particle.
            Load Accumulated Forces: Reads the accumulated force components from the nodeForce buffer using atomic loads.
            Normalize and Update Forces: Converts the accumulated forces from integers to floats, scales them down, and writes the normalized forces back to the force buffer.

    e. Initialization Function: initialize

        Attributes:
            @compute: Indicates this is a compute shader.
            @workgroup_size(256): Specifies the number of workgroups to execute in parallel.

        - Inputs:
            @builtin(global_invocation_id) global_id: Provides the global invocation ID, used to identify the current workgroup and spring index.

        - Implementation:
            Bounds Check: Ensures the current ID does not exceed the number of particles.
            Reset Accumulated Forces: Resets the accumulated forces in the nodeForce buffer using atomic stores.
            Reset Final Forces: Resets the forces in the force buffer to zero.

@group(0) @binding(0) var<storage, read_write> nodeForce: array<atomicI32>;
@group(0) @binding(1) var<storage, read_write> force: array<f32>;
@group(0) @binding(2) var<uniform> numParticles: u32;

fn getForce(index: u32) -> vec3<f32> {
    return vec3<f32>(force[index * 3], force[index * 3 + 1], force[index * 3 + 2]);
}

struct atomicI32 {
    value: atomic<i32>
}

@compute
@workgroup_size(256)
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
    let id = global_id.x;

    if id >= numParticles {return;}

    var f = getForce(id);

    let tempX = atomicLoad(&nodeForce[id * 3 + 0].value);
    let tempY = atomicLoad(&nodeForce[id * 3 + 1].value);
    let tempZ = atomicLoad(&nodeForce[id * 3 + 2].value);

    f.x = f32(tempX) / 100.0;
    f.y = f32(tempY) / 100.0;
    f.z = f32(tempZ) / 100.0;

    force[id * 3 + 0] = f32(f.x);
    force[id * 3 + 1] = f32(f.y);
    force[id * 3 + 2] = f32(f.z);
}

@compute
@workgroup_size(256)
fn initialize(@builtin(global_invocation_id) global_id: vec3<u32>) {
    let id = global_id.x;

    if id >= numParticles {return;}

    atomicStore(&nodeForce[id * 3 + 0].value, i32(0));
    atomicStore(&nodeForce[id * 3 + 1].value, i32(0));
    atomicStore(&nodeForce[id * 3 + 2].value, i32(0));

    force[id * 3 + 0] = f32(0.0);
    force[id * 3 + 1] = f32(0.0);
    force[id * 3 + 2] = f32(0.0);
}


10. Vertex Triangle Intersection Shader - createIntersectionPipeline()

The Vertex-Triangle Intersection Shader is designed to detect and handle collisions between vertices of a cloth simulation and the triangles of an object in the scene. This shader comprises two main compute functions: main, which detects intersections and accumulates positional corrections, and response, which applies the corrections and updates the velocities of the cloth vertices.

    a. Bindings:

        - positionsCloth:
            Type: array<f32>
            Usage: Stores the positions of the cloth vertices.
            Binding: @group(0) @binding(0)

        - velocities:
            Type: array<f32>
            Usage: Stores the velocities of the cloth vertices.
            Binding: @group(0) @binding(1)

        - positionsObject:
            Type: array<f32>
            Usage: Stores the positions of the object vertices.
            Binding: @group(0) @binding(2)

        - triangleObject:
            Type: array<triangles_object>
            Usage: Stores the indices of the vertices that form each triangle of the object.
            Binding: @group(0) @binding(3)

        - numParticles:
            Type: u32
            Usage: Represents the total number of particles (cloth vertices).
            Binding: @group(0) @binding(4)

        - numTrianglesObject:
            Type: u32
            Usage: Represents the total number of triangles in the object.
            Binding: @group(0) @binding(5)

        - tempBuffer:
            Type: array<atomicI32>
            Usage: Temporarily stores positional corrections for each cloth vertex.
            Binding: @group(0) @binding(6)

        - fixed:
            Type: array<u32>
            Usage: Indicates whether a cloth vertex is fixed (immovable).
            Binding: @group(0) @binding(7)

        - tempCountBuffer:
            Type: array<atomicI32>
            Usage: Counts the number of intersections per vertex for averaging corrections.
            Binding: @group(0) @binding(8)

        - prevPosition:
            Type: array<f32>
            Usage: Stores the previous positions of the cloth vertices.
            Binding: @group(0) @binding(9)

    b. Structures:

        triangles_object: v1, v2, v3 - Indices of the vertices that form a triangle.
        atomicI32: value - An atomic integer used for safely accumulating values.
        CollisionResult: hit, point - Indicates if a collision occurred and the intersection point.

    c. Helper Functions:

        getClothVertexPosition(index: u32) -> vec3<f32>:
            Retrieves the position of a cloth vertex given its index.

        getPrevPosition(index: u32) -> vec3<f32>:
            Retrieves the previous position of a cloth vertex given its index.

        getClothVertexVelocity(index: u32) -> vec3<f32>:
            Retrieves the velocity of a cloth vertex given its index.

        getObjectVertexPosition(index: u32) -> vec3<f32>:
            Retrieves the position of an object vertex given its index.

        barycentricCoords(A: vec3<f32>, B: vec3<f32>, C: vec3<f32>, P: vec3<f32>) -> vec3<f32>:
            Computes the barycentric coordinates of point P with respect to triangle ABC.

        pointInTriangle(barycentricCoords: vec3<f32>) -> bool:
            Determines if a point (given in barycentric coordinates) is inside a triangle.

        isPointInPlane(A: vec3<f32>, B: vec3<f32>, C: vec3<f32>, P: vec3<f32>, epsilon: f32) -> bool:
            Checks if a point P is in the plane of triangle ABC within a tolerance epsilon.

        isPointInPlane2(A: vec3<f32>, B: vec3<f32>, C: vec3<f32>, P: vec3<f32>, epsilon: f32) -> f32:
            Returns the absolute value of the dot product of the plane normal and the point vector, used to check point-to-plane distance.

        checkEdgeTriangleCollision(startPos: vec3<f32>, endPos: vec3<f32>, triangleVertices: array<vec3<f32>, 3>) -> CollisionResult:
            Checks if an edge (defined by startPos and endPos) intersects with a triangle and returns the result.

        calculateNormal(p0: vec3<f32>, p1: vec3<f32>, p2: vec3<f32>) -> vec3<f32>:
            Calculates and returns the normal vector of a triangle given its vertices.

    d. Main Compute Function: main

        Attributes:
            @compute: Indicates this is a compute shader.
            @workgroup_size(16, 16, 1): Specifies the number of workgroups to execute in parallel.

        - Inputs:
            @builtin(global_invocation_id) global_id: Provides the global invocation ID, used to identify the current workgroup and particle index.

        - Implementation:
            Bounds Check: Ensures the current ID does not exceed the number of particles or triangles.
            Fixed Vertex Check: Skips processing if the vertex is fixed.
            Retrieve Positions and Velocities: Retrieves the current and previous positions, and the velocity of the cloth vertex.
            Retrieve Triangle Vertices: Retrieves the positions of the vertices forming the current object triangle.
            Calculate Normal: Computes the normal vector of the triangle.
            Compute Next and Previous Positions: Calculates the next and previous positions of the cloth vertex based on velocity and deltaTime.
            Check Intersection: Determines if the cloth vertex intersects the triangle at its current, previous, or next position using barycentric coordinates and plane checks.
            Accumulate Corrections: If an intersection is detected, calculates the positional correction and accumulates it in the tempBuffer.

    e. Initialization Function: initialize

        Attributes:
            @compute: Indicates this is a compute shader.
            @workgroup_size(256): Specifies the number of workgroups to execute in parallel.

        - Inputs:
            @builtin(global_invocation_id) global_id: Provides the global invocation ID, used to identify the current workgroup and particle index.

        - Implementation:
            Bounds Check: Ensures the current ID does not exceed the number of particles.
            Fixed Vertex Check: Skips processing if the vertex is fixed.
            Retrieve Positions and Velocities: Retrieves the previous position and velocity of the cloth vertex.
            Load and Normalize Corrections: Loads the accumulated corrections from tempBuffer and normalizes them.
            Apply Corrections: Applies the normalized corrections to the position and velocity of the cloth vertex.
            Store Updated Values: Writes the updated positions and velocities back to their respective buffers.
            Reset Buffers: Resets the tempBuffer and tempCountBuffer for the next iteration.

@group(0) @binding(0) var<storage, read_write> positionsCloth: array<f32>;
@group(0) @binding(1) var<storage, read_write> velocities: array<f32>;
   
@group(0) @binding(2) var<storage, read_write> positionsObject: array<f32>;
@group(0) @binding(3) var<storage, read_write> triangleObject: array<triangles_object>;
   
@group(0) @binding(4) var<uniform> numParticles: u32;
@group(0) @binding(5) var<uniform> numTrianglesObject: u32;

@group(0) @binding(6) var<storage, read_write> tempBuffer: array<atomicI32>;
@group(0) @binding(7) var<storage, read_write> fixed: array<u32>;    

@group(0) @binding(8) var<storage, read_write> tempCountBuffer: array<atomicI32>;
@group(0) @binding(9) var<storage, read_write> prevPosition: array<f32>;  

struct atomicI32 {
        value: atomic<i32>
}

// Defines a struct representing a triangle with three vertex indices.
struct triangles_object {
        v1: u32,
        v2: u32,
        v3: u32
}

// Computes and returns the position of a cloth vertex given its index.
fn getClothVertexPosition(index: u32) -> vec3<f32> {
    let i = index * 3u;
    return vec3<f32>
    (positionsCloth[i], positionsCloth[i + 1u], positionsCloth[i + 2u]);
}

// Computes and returns the previous position of a cloth vertex given its index.
fn getPrevPosition(index: u32) -> vec3<f32> {
    let i = index * 3u;
    return vec3<f32>(prevPosition[i], prevPosition[i + 1u], prevPosition[i + 2u]);
}

// Computes and returns the velocity of a cloth vertex given its index.
fn getClothVertexVelocity(index: u32) -> vec3<f32> {
    let i = index * 3u;
    return vec3<f32>(velocities[i], velocities[i + 1u], velocities[i + 2u]);
}

// Computes and returns the position of an object vertex given its index.
fn getObjectVertexPosition(index: u32) -> vec3<f32> {
    let i = index * 3u;
    return vec3<f32>
    (positionsObject[i], positionsObject[i + 1u], positionsObject[i + 2u]);
}
   
// Computes the barycentric coordinates of point P with respect to triangle ABC.
fn barycentricCoords(
A: vec3<f32>,
B: vec3<f32>,
C: vec3<f32>,
P: vec3<f32>
) -> vec3<f32> {

    let v0 = B - A;
    let v1 = C - A;
    let v2 = P - A;

    let d00 = dot(v0, v0);
    let d01 = dot(v0, v1);
    let d11 = dot(v1, v1);
    let d20 = dot(v2, v0);
    let d21 = dot(v2, v1);
    let denom = d00 * d11 - d01 * d01;

    let v = (d11 * d20 - d01 * d21) / denom;
    let w = (d00 * d21 - d01 * d20) / denom;
    let u = 1.0 - v - w;

    return vec3<f32>(u, v, w);
}
   
// Determines if a point (given in barycentric coordinates) is inside a triangle.
fn pointInTriangle(barycentricCoords: vec3<f32>) -> bool {
    let u = barycentricCoords.x;
    let v = barycentricCoords.y;
    let w = barycentricCoords.z;

    return (u >= 0.0) && (v >= 0.0) && (w >= 0.0) && (u + v + w <= 1.0);
}

// Checks if a point P is in the plane of triangle ABC within a tolerance epsilon.
fn isPointInPlane(
    A: vec3<f32>,
    B: vec3<f32>,
    C: vec3<f32>,
    P: vec3<f32>,
    epsilon: f32
) -> bool {

    let planeNormal = cross(B - A, C - A);
    let pointVector = P - A;
    let dotProduct = dot(planeNormal, pointVector);

    return abs(dotProduct) <= epsilon;
}

// Returns the absolute value of the dot product of the plane normal
// and the point vector, used to check point-to-plane distance.
fn isPointInPlane2(
    A: vec3<f32>,
    B: vec3<f32>,
    C: vec3<f32>,
    P: vec3<f32>,
    epsilon: f32
) -> f32 {

    let planeNormal = cross(B - A, C - A);
    let pointVector = P - A;
    let dotProduct = dot(planeNormal, pointVector);
    return abs(dotProduct);
}
   
// Represents the result of a collision check between an edge and a triangle.
struct CollisionResult {
        hit: bool,
        point: vec3<f32>,
};
   
// Checks if an edge (defined by startPos and endPos)
// intersects with a triangle and returns the result.
fn checkEdgeTriangleCollision(
    startPos: vec3<f32>,
    endPos: vec3<f32>,
    triangleVertices: array<vec3<f32>, 3>
) -> CollisionResult {

    let edgeVector = endPos - startPos;
    let rayVector = normalize(edgeVector);
    let edge1 = triangleVertices[1] - triangleVertices[0];
    let edge2 = triangleVertices[2] - triangleVertices[0];
    let h = cross(rayVector, edge2);
    let a = dot(edge1, h);

    let epsilon = 0.01;
    if a > -epsilon && a < epsilon {
    // Parallel, no intersection
        return CollisionResult(false, vec3<f32>(0.0, 0.0, 0.0));
    }

    let f = 1.0 / a;
    let s = startPos - triangleVertices[0];
    let u = f * dot(s, h);
    if u < 0.0 || u > 1.0 {
        return CollisionResult(false, vec3<f32>(0.0, 0.0, 0.0));
    }

    let q = cross(s, edge1);
    let v = f * dot(rayVector, q);
    if v < 0.0 || u + v > 1.0 {
        return CollisionResult(false, vec3<f32>(0.0, 0.0, 0.0));
    }

    let t = f * dot(edge2, q);
    // Check if intersection is within the edge bounds
    if t > epsilon && t < length(edgeVector) {
        // Calculate intersection point
        let intersectionPoint = startPos + t * rayVector;
        return CollisionResult(true, intersectionPoint);
        }
    return CollisionResult(false, vec3<f32>(0.0, 0.0, 0.0));
}
   
// Calculates and returns the normal vector of a triangle given its vertices.
fn calculateNormal(p0: vec3<f32>, p1: vec3<f32>, p2: vec3<f32>) -> vec3<f32> {
    var u = p1 - p0;
    var v = p2 - p0;
    return normalize(cross(u, v));
}

@compute
@workgroup_size(16, 16, 1)
// The main compute function that is executed for each workgroup invocation.
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
// Extract the global ID of the current thread.
    let x: u32 = global_id.x;
    let y: u32 = global_id.y;
       
    if x >= numParticles {return;}
    if y >= numTrianglesObject {return;}

// Retrieve the position of the cloth vertex and the object triangle vertices.
    var fix = fixed[x];
    if fix == 1 {return;}

// Retrieve the position of the cloth vertex and the object triangle vertices.
    var pos = getClothVertexPosition(x);
    var vel = getClothVertexVelocity(x);

// Retrieves the triangle information for the current triangle.
    var object_tri_info = triangleObject[y];
// Creates a vector of the vertex indices of the current triangle.
    var f2: vec3<u32> = vec3<u32>
        (u32(object_tri_info.v1), u32(object_tri_info.v2), u32(object_tri_info.v3));

// Retrieves the positions of the vertices of the current triangle.
    var tri2_vtx: array<vec3<f32>, 3> = array<vec3<f32>, 3>(
        getObjectVertexPosition(f2.x),
        getObjectVertexPosition(f2.y),
        getObjectVertexPosition(f2.z)
    );

// Calculates the normal of the current triangle.
    var tri_normal = calculateNormal(tri2_vtx[0], tri2_vtx[1], tri2_vtx[2]);

    var deltaTime: f32 = 0.001; // Assuming 60 FPS for simplicity
       
    var next_pos = pos + (vel * deltaTime);
    var prev_pos = pos - (vel * deltaTime);

// Checks if the cloth vertex is inside the plane of the object triangle.
    var threshold = 0.01;

    var rC = isPointInPlane(tri2_vtx[0], tri2_vtx[1], tri2_vtx[2], prev_pos, threshold);
    var bC = barycentricCoords(tri2_vtx[0], tri2_vtx[1], tri2_vtx[2], prev_pos);

    var rC1 = isPointInPlane(tri2_vtx[0], tri2_vtx[1], tri2_vtx[2], pos, threshold);
    var bC1 = barycentricCoords(tri2_vtx[0], tri2_vtx[1], tri2_vtx[2], pos);

    var rC2 = isPointInPlane(tri2_vtx[0], tri2_vtx[1], tri2_vtx[2], next_pos, threshold);
    var bC2 = barycentricCoords(tri2_vtx[0], tri2_vtx[1], tri2_vtx[2], next_pos);

    var targetValue = pos - (vel * deltaTime * 2) * 100.0;

        // Checks if the cloth vertex is inside the object triangle.
    if (
        rC && pointInTriangle(bC)) ||
        (rC1 && pointInTriangle(bC1)) ||
        (rC2 && pointInTriangle(bC2)
    ) {
        atomicAdd(&tempBuffer[x * 3 + 0].value, i32(targetValue.x));
        atomicAdd(&tempBuffer[x * 3 + 1].value, i32(targetValue.y));
        atomicAdd(&tempBuffer[x * 3 + 2].value, i32(targetValue.z));
    }
}

@compute
@workgroup_size(256)
fn response(@builtin(global_invocation_id) global_id: vec3<u32>) {
    let x: u32 = global_id.x;

    if x > numParticles {return;}

    var fix = fixed[x];
    if fix == 1 {return;}

    var pos = getPrevPosition(x);
    var vel = getClothVertexVelocity(x);

// to load x, y, z-component and countBufferData values atomically
// from tempBuffer and tempCountBuffer
    let tempX = atomicLoad(&tempBuffer[x * 3 + 0].value);
    let tempY = atomicLoad(&tempBuffer[x * 3 + 1].value);
    let tempZ = atomicLoad(&tempBuffer[x * 3 + 2].value);
    let countBufferData = atomicLoad(&tempCountBuffer[x].value);

// calculated separatePos by normalizing the loaded values
// and dividing by countBufferData after scaling them by 100.0.
    var separatePos: vec3<f32> =
        vec3<f32>((f32(tempX) / 100.0) / f32(countBufferData), (f32(tempY) / 100.0) /
        f32(countBufferData), (f32(tempZ) / 100.0) / f32(countBufferData));

    if countBufferData > 0 {
        vel *= -0.0001;

        pos.x += (separatePos.x * 0.03);
        pos.y += (separatePos.y * 0.03);
        pos.z += (separatePos.z * 0.03);

// The updated velocity components are stored back in the velocities array.
        velocities[x * 3 + 0] = vel.x;
        velocities[x * 3 + 1] = vel.y;
        velocities[x * 3 + 2] = vel.z;

// The updated position components are stored back in the positionsCloth array.
        positionsCloth[x * 3 + 0] = pos.x;
        positionsCloth[x * 3 + 1] = pos.y;
        positionsCloth[x * 3 + 2] = pos.z;
    }

    atomicStore(&tempCountBuffer[x].value, i32(0));
    atomicStore(&tempBuffer[x * 3 + 0].value, i32(0));
    atomicStore(&tempBuffer[x * 3 + 1].value, i32(0));
    atomicStore(&tempBuffer[x * 3 + 2].value, i32(0));
}


11. Triangle Triangle Intersection Shader - createTriTriIntersectionPipeline()

This WGSL (WebGPU Shading Language) shader is designed to handle collisions between triangles in a cloth simulation and triangles representing an object in the environment. The shader performs intersection detection and collision resolution to simulate realistic interactions between cloth and objects.

    a. Bindings:

        positionsCloth: 
            Storage buffer containing the positions of the cloth vertices.

        - triangleCloth: 
            Storage buffer containing the indices of the vertices that form the triangles of the cloth.

        - positionsObject: 
            Storage buffer containing the positions of the object vertices.

        - triangleObject: 
            Storage buffer containing the indices of the vertices that form the triangles of the object.

        - numTriangleCloth: 
            Uniform representing the total number of triangles in the cloth.

        - numTrianglesObject: 
            Uniform representing the total number of triangles in the object.

        - tempBuffer: 
            Storage buffer used for temporarily storing collision responses.

        - tempCountBuffer: 
            Storage buffer used for counting collision responses.

        - velocityCloth: 
            Storage buffer containing the velocities of the cloth vertices.

    b. Structures:

        struct atomicI32: A structure to represent atomic integer values, used for thread-safe operations on shared data.
        struct triangles: A structure representing a triangle with three vertex indices.

    c. Helper Functions:

        getClothVertexPosition: 
            Returns the position of a cloth vertex given its index.

        getClothVertexVelocity: 
            Returns the velocity of a cloth vertex given its index.

        getObjectVertexPosition: 
            Returns the position of an object vertex given its index.

        calculateSpace: 
            Translates a position vector into a spatial grid index for broad-phase collision detection.

        distanceSquared: 
            Computes the squared distance between two points in space.

        compareVec3: 
            Compares two vec3<i32> vectors for equality.

        areTriangleSameVoxel: 
            Checks if any vertices of two triangles are in the same voxel.

        areTrianglesClose: 
            Checks if two triangles are sufficiently close to warrant detailed collision detection.

        calculateNormal: 
            Computes the normal vector of a triangle.

        direction: 
            Computes a normalized direction vector between two points.

        isPointInsideTriangle: 
            Determines if a point is inside a triangle using the barycentric coordinate method.

        intersect: 
            Checks if a line segment intersects with a triangle and returns the intersection point if it exists.

        FindClosestVertex: 
            Finds the closest vertex of a triangle to a given point.

    d. Main Compute Function: main

The main compute function for detecting intersections between cloth and object triangles. It uses spatial partitioning to optimize the collision detection process.

    @group(0) @binding(0) var<storage, read_write> positionsCloth: array<f32>;
    @group(0) @binding(1) var<storage, read_write> triangleCloth: array<triangles>;
   
    @group(0) @binding(2) var<storage, read_write> positionsObject: array<f32>;
    @group(0) @binding(3) var<storage, read_write> triangleObject: array<triangles>;
   
    @group(0) @binding(4) var<uniform> numTriangleCloth: u32;
    @group(0) @binding(5) var<uniform> numTrianglesObject: u32;

    @group(0) @binding(6) var<storage, read_write> tempBuffer: array<atomicI32>;
    @group(0) @binding(7) var<storage, read_write> tempCountBuffer: array<atomicI32>;

    @group(0) @binding(8) var<storage, read_write> velocityCloth: array<f32>;

struct atomicI32 {
        value: atomic<i32>
    }

struct triangles {
        v1: u32,
        v2: u32,
        v3: u32,
    }

fn getClothVertexPosition(index: u32) -> vec3<f32> {
    let i = index * 3u;
    return vec3<f32>
    (positionsCloth[i], positionsCloth[i + 1u], positionsCloth[i + 2u]);
}

fn getClothVertexVelocity(index: u32) -> vec3<f32> {
    let i = index * 3u;
    return vec3<f32>
    (velocityCloth[i], velocityCloth[i + 1u], velocityCloth[i + 2u]);
}

fn getObjectVertexPosition(index: u32) -> vec3<f32> {
    let i = index * 3u;
    return vec3<f32>
    (positionsObject[i], positionsObject[i + 1u], positionsObject[i + 2u]);
}

fn calculateSpace(position: vec3<f32>) -> vec3<i32> {
    return vec3<i32>
    (i32((position.x + 500.0) / 5.0), i32((position.y + 500.0) / 5.0),
    i32((position.z + 500.0) / 5.0));
}

fn distanceSquared(a: vec3<i32>, b: vec3<i32>) -> f32 {
    let delta = vec3<f32>(f32(a.x - b.x), f32(a.y - b.y), f32(a.z - b.z));
    return dot(delta, delta);
}

fn compareVec3(a: vec3<i32>, b: vec3<i32>) -> bool {
    return a.x == b.x && a.y == b.y && a.z == b.z;
}

fn areTriangleSameVoxel(
    cloth_space1: vec3<i32>,
    cloth_space2: vec3<i32>,
    cloth_space3: vec3<i32>,
    object_space1: vec3<i32>,
    object_space2: vec3<i32>,
    object_space3: vec3<i32>
) -> bool {

    if compareVec3(cloth_space1, object_space1) ||
        compareVec3(cloth_space1, object_space2) ||
        compareVec3(cloth_space1, object_space3) {
        return true;
    }
    if compareVec3(cloth_space2, object_space1) ||
        compareVec3(cloth_space2, object_space2) ||
        compareVec3(cloth_space2, object_space3) {
        return true;
    }
    if compareVec3(cloth_space3, object_space1) ||
        compareVec3(cloth_space3, object_space2) ||
        compareVec3(cloth_space3, object_space3) {
        return true;
    }
    return false;
}

fn areTrianglesClose(
    cloth_space1: vec3<i32>,
    cloth_space2: vec3<i32>,
    cloth_space3: vec3<i32>,
    object_space1: vec3<i32>,
    object_space2: vec3<i32>,
    object_space3: vec3<i32>
) -> bool {

    let minDistanceSquared = 100.0;

// Calculate the center point of the spatial coordinates of each triangle.
    let cloth_center = (cloth_space1 + cloth_space2 + cloth_space3) / 3;
    let object_center = (object_space1 + object_space2 + object_space3) / 3;

// Calculate the distance between the two center points.
    let distanceSquared = distanceSquared(cloth_center, object_center);

    return distanceSquared < minDistanceSquared;
}

fn calculateNormal(p0: vec3<f32>, p1: vec3<f32>, p2: vec3<f32>) -> vec3<f32> {
    var u = p1 - p0;
    var v = p2 - p0;
    return normalize(cross(u, v));
}
   
fn direction(p0: vec3<f32>, p1: vec3<f32>) -> vec3<f32> {
    return normalize(p1 - p0);
}

fn isPointInsideTriangle(
    point: vec3<f32>,
    vertex0: vec3<f32>,
    vertex1: vec3<f32>,
    vertex2: vec3<f32>
) -> bool {

    let normal = cross(vertex1 - vertex0, vertex2 - vertex0);
    let edge1 = vertex1 - vertex0;
    let vp1 = point - vertex0;
    if dot(cross(edge1, vp1), normal) < 0.0 {
        return false;
    }
    let edge2 = vertex2 - vertex1;
    let vp2 = point - vertex1;
    if dot(cross(edge2, vp2), normal) < 0.0 {
        return false;
    }
    let edge3 = vertex0 - vertex2;
    let vp3 = point - vertex2;
    if dot(cross(edge3, vp3), normal) < 0.0 {
        return false;
    }
    return true;
}

struct CollisionResult {
        hit: bool,
        point: vec3<f32>,
    };

fn intersect(
    p0: vec3<f32>,
    p1: vec3<f32>,
    p2: vec3<f32>,
    src: vec3<f32>,
    dst: vec3<f32>
) -> CollisionResult {

    let e1 = p1 - p0;
    let e2 = p2 - p0;

    let epsilon = 0.000001;

    var hit = vec3<f32>(0.0, 0.0, 0.0);

    var ray_direction = direction(src, dst);
    var ray_origin = src;
       
    // Calculate determinant
    let p: vec3<f32> = cross(ray_direction, e2);
   
    // Calculate determinant
    let det: f32 = dot(e1, p);
   
    // If determinant is near zero, ray lies in plane of triangle otherwise not
    if det > -epsilon && det < epsilon {
        return CollisionResult(false, hit);
    }
    let invDet: f32 = 1.0 / det;
   
    // Calculate distance from p1 to ray origin
    let t: vec3<f32> = ray_origin - p1;
   
    // Calculate u parameter
    let u: f32 = dot(t, p) * invDet;
   
    // Check for ray hit
    if u < 0.0 || u > 1.0 {
        return CollisionResult(false, hit);
    }
   
    // Prepare to test v parameter
    let q: vec3<f32> = cross(t, e1);
   
    // Calculate v parameter
    let v: f32 = dot(ray_direction, q) * invDet;
   
    // Check for ray hit
    if v < 0.0 || u + v > 1.0 {
        return CollisionResult(false, hit);
    }
   
    // Intersection point
    hit = p1 + u * e1 + v * e2;

    if dot(e2, q) * invDet > epsilon {
        // Ray does intersect
        return CollisionResult(true, hit);
    }
   
    // No hit at all
    return CollisionResult(false, hit);
}

fn FindClosestVertex(
    p0: vec3<f32>,
    p1: vec3<f32>,
    p2: vec3<f32>,
    point: vec3<f32>
) -> vec3<f32> {

    var minDistance: f32 = 1e10; // Simulating Mathf.Infinity
    var closestVertex: vec3<f32> = vec3<f32>(0.0, 0.0, 0.0);

    let distance0 = distance(p0, point);
    let distance1 = distance(p1, point);
    let distance2 = distance(p2, point);

    if distance0 < minDistance {
        minDistance = distance0;
        closestVertex = p0;
    }
    if distance1 < minDistance {
        minDistance = distance1;
        closestVertex = p1;
    }
    if distance2 < minDistance {
        minDistance = distance2;
        closestVertex = p2;
    }

    return closestVertex;
}

    @compute @workgroup_size(16, 16, 1)
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
    let x: u32 = global_id.x;
    let y: u32 = global_id.y;

    if x >= numTriangleCloth {return;}
    if y >= numTrianglesObject {return;}

    var cloth_tri_info = triangleCloth[x];
    var object_tri_info = triangleObject[y];

    var f1: vec3<u32> = vec3<u32>
        (u32(cloth_tri_info.v1), u32(cloth_tri_info.v2), u32(cloth_tri_info.v3));

    var f2: vec3<u32> = vec3<u32>
        (u32(object_tri_info.v1), u32(object_tri_info.v2), u32(object_tri_info.v3));

        // Spatial calculation

    var tri1_vtx: array<vec3<f32>, 3> = array<vec3<f32>, 3>(
        getClothVertexPosition(f1.x),
        getClothVertexPosition(f1.y),
        getClothVertexPosition(f1.z)
    );
    var tri2_vtx: array<vec3<f32>, 3> = array<vec3<f32>, 3>(
        getObjectVertexPosition(f2.x),
        getObjectVertexPosition(f2.y),
        getObjectVertexPosition(f2.z)
    );

    var cloth_space1 = calculateSpace(tri1_vtx[0]);
    var cloth_space2 = calculateSpace(tri1_vtx[1]);
    var cloth_space3 = calculateSpace(tri1_vtx[2]);

    var object_space1 = calculateSpace(tri2_vtx[0]);
    var object_space2 = calculateSpace(tri2_vtx[1]);
    var object_space3 = calculateSpace(tri2_vtx[2]);

    if !areTriangleSameVoxel(cloth_space1, cloth_space2, cloth_space3,
        object_space1, object_space2, object_space3) {
        return;
    }


    var res1 =
    intersect(tri2_vtx[0], tri2_vtx[1], tri2_vtx[2], tri1_vtx[0], tri1_vtx[1]);
    var res2 =
    intersect(tri2_vtx[0], tri2_vtx[1], tri2_vtx[2], tri1_vtx[0], tri1_vtx[2]);
    var res3 =
    intersect(tri2_vtx[0], tri2_vtx[1], tri2_vtx[2], tri1_vtx[1], tri1_vtx[2]);
    var collisionPoint: vec3<f32> = vec3<f32>(0.0, 0.0, 0.0);
    if !res1.hit && !res2.hit && !res3.hit { return; }

    var count: i32 = 0;
    if res1.hit {
        collisionPoint += res1.point;
        count += 1;
    }
    if res2.hit {
        collisionPoint += res2.point;
        count += 1;
    }
    if res3.hit {
        collisionPoint += res3.point;
        count += 1;
    }

    collisionPoint = collisionPoint / f32(count);

    var closestVertex =
    FindClosestVertex(tri1_vtx[0], tri1_vtx[1], tri1_vtx[2], collisionPoint);
    var triNormal = (tri1_vtx[0] + tri1_vtx[1] + tri1_vtx[2]) / 3.0;

    var separationVector = triNormal * 0.07;
    if closestVertex.x == tri1_vtx[0].x && closestVertex.y == tri1_vtx[0].y &&
        closestVertex.z == tri1_vtx[0].z {

        atomicAdd(&tempBuffer[f1.x * 3 + 0].value, i32(separationVector.x * 100.0));
        atomicAdd(&tempBuffer[f1.x * 3 + 1].value, i32(separationVector.y * 100.0));
        atomicAdd(&tempBuffer[f1.x * 3 + 2].value, i32(separationVector.z * 100.0));
        atomicAdd(&tempCountBuffer[f1.x].value, i32(1));

    } else if closestVertex.x == tri1_vtx[1].x && closestVertex.y == tri1_vtx[1].y &&
        closestVertex.z == tri1_vtx[1].z {

        atomicAdd(&tempBuffer[f1.y * 3 + 0].value, i32(separationVector.x * 100.0));
        atomicAdd(&tempBuffer[f1.y * 3 + 1].value, i32(separationVector.y * 100.0));
        atomicAdd(&tempBuffer[f1.y * 3 + 2].value, i32(separationVector.z * 100.0));
        atomicAdd(&tempCountBuffer[f1.y].value, i32(1));

    } else if closestVertex.x == tri1_vtx[2].x && closestVertex.y == tri1_vtx[2].y &&
        closestVertex.z == tri1_vtx[2].z {

        atomicAdd(&tempBuffer[f1.z * 3 + 0].value, i32(separationVector.x * 100.0));
        atomicAdd(&tempBuffer[f1.z * 3 + 1].value, i32(separationVector.y * 100.0));
        atomicAdd(&tempBuffer[f1.z * 3 + 2].value, i32(separationVector.z * 100.0));
        atomicAdd(&tempCountBuffer[f1.z].value, i32(1));
    }
}



















Comments