Skip to content

Cloth


WebGPU is not supported in your browser
Please upgrade to latest Chrome/Edge

ts
import { AtmosphericComponent, BoxGeometry, CameraUtil, ComputeGPUBuffer, ComputeShader, DirectLight, Engine3D, HoverCameraController, KeyCode, KeyEvent, LitMaterial, MeshRenderer, Object3D, PlaneGeometry, Scene3D, SphereGeometry, Time, Vector3, VertexAttributeName, View3D, webGPUContext } from '@orillusion/core';

class Demo_Cloth {
    async run() {
        Engine3D.setting.shadow.autoUpdate = true;
        Engine3D.setting.shadow.updateFrameRate = 1;
        Engine3D.setting.shadow.shadowBound = 5;
        Engine3D.setting.shadow.shadowSize = 2048;
        Engine3D.setting.shadow.shadowBias = 0.0002;

        await Engine3D.init({});

        let scene = new Scene3D();
        let sky = scene.addComponent(AtmosphericComponent);
        await this.initScene(scene);

        let camera = CameraUtil.createCamera3DObject(scene);
        
        camera.perspective(60, webGPUContext.aspect, 0.01, 10000.0);
        let ctl = camera.object3D.addComponent(HoverCameraController);
        ctl.setCamera(30, -28, 2);

        let view = new View3D();
        view.scene = scene;
        view.camera = camera;

        Engine3D.startRenderView(view);
    }

    async initScene(scene: Scene3D) {
        let mat = new LitMaterial();
        mat.baseMap = Engine3D.res.grayTexture;
        mat.roughness = 0.8;
        mat.metallic = 0.1;

        let sphere = new Object3D();
        sphere.transform.z = 0.5;
        let mr = sphere.addComponent(MeshRenderer);
        mr.geometry = new SphereGeometry(0.16, 16, 16);
        mr.material = mat;//new HDRLitMaterial();
        mr.castShadow = true;
        scene.addChild(sphere);

        let cloth = new Object3D();
        let simulator = cloth.addComponent(ClothSimulator);
        simulator.castShadow = true;
        simulator.SetInteractionSphere(sphere);
        scene.addChild(cloth);

        {
            let mat = new LitMaterial();
            mat.baseMap = Engine3D.res.grayTexture;
            mat.roughness = 0.8;
            let plane = new Object3D();
            plane.transform.y = -1;
            let planeMesh = plane.addComponent(MeshRenderer);
            planeMesh.geometry = new PlaneGeometry(100, 100);
            planeMesh.material = mat;
            planeMesh.receiveShadow = true;
            scene.addChild(plane);
        }

        {
            var lightObj = new Object3D();
            lightObj.x = 0;
            lightObj.y = 0;
            lightObj.z = 0;
            lightObj.rotationX = 45;
            lightObj.rotationY = 0;
            lightObj.rotationZ = 0;
            let lc = lightObj.addComponent(DirectLight);
            lc.intensity = 3;
            lc.castShadow = true;
            scene.addChild(lightObj);
        }

        {
            let poleMat = new LitMaterial();


            let pole_L = new Object3D();
            pole_L.transform.y = -0.25;
            pole_L.transform.x = -0.59;
            let mr = pole_L.addComponent(MeshRenderer);
            mr.geometry = new BoxGeometry(0.02, 1.5, 0.02);
            mr.material = poleMat
            mr.castShadow = true;
            scene.addChild(pole_L);

            let pole_R = new Object3D();
            pole_R.transform.y = -0.25;
            pole_R.transform.x = 0.59;
            mr = pole_R.addComponent(MeshRenderer);
            mr.geometry = new BoxGeometry(0.02, 1.5, 0.02);
            mr.material = poleMat
            mr.castShadow = true;
            scene.addChild(pole_R);

            let pole_T = new Object3D();
            pole_T.transform.y = 0.5;
            mr = pole_T.addComponent(MeshRenderer);
            mr.geometry = new BoxGeometry(1.2, 0.02, 0.02);
            mr.material = poleMat
            mr.castShadow = true;
            scene.addChild(pole_T);
        }
    }
}

new Demo_Cloth().run();

type ClothSimulatorConfig = {
    NUMPARTICLES: number,
    NUMTSURFACES: number,
    NUMTEDGES: number,
    NUMTBENDS: number,
    GRAVITY: number,
    DELTATIME: number,
    NUMSUBSTEPS: number,
    STRETCHCOMPLIANCE: number,
    BENDCOMPLIANCE: number,
    SPHERERADIUS: number,
    SPHERECENTREX: number,
    SPHERECENTREY: number,
    SPHERECENTREZ: number,
    clothVertex: Float32Array | Uint16Array | Uint32Array,
    clothFaceTriIds: Float32Array | Uint16Array | Uint32Array,
    clothVertexBuffer: ComputeGPUBuffer,
};

class ClothSimulator extends MeshRenderer {
    protected mConfig: ClothSimulatorConfig;
    protected mClothGeometry: PlaneGeometry;
    protected mInteractionSphere: Object3D;
    protected mClothComputePipeline: ClothSimulatorPipeline;
    protected mKeyState: boolean[] = [false, false, false, false];

    constructor() {
        super();
        this.mConfig = {
            NUMPARTICLES: 0,
            NUMTSURFACES: 0,
            NUMTEDGES: 0,
            NUMTBENDS: 0,
            GRAVITY: -10,
            DELTATIME: 1 / 60,
            NUMSUBSTEPS: 15,
            STRETCHCOMPLIANCE: 0.0,
            BENDCOMPLIANCE: 0.0,
            SPHERERADIUS: 0.2,
            SPHERECENTREX: 0.0,
            SPHERECENTREY: 0.5,
            SPHERECENTREZ: 0.25,
            clothVertex: null,
            clothFaceTriIds: null,
            clothVertexBuffer: null,
        };
        this.mClothGeometry = new PlaneGeometry(1, 1, 20, 20, Vector3.Z_AXIS);
        this.mConfig.clothVertex = this.mClothGeometry.getAttribute(VertexAttributeName.position).data as Float32Array;
        this.mConfig.clothFaceTriIds = this.mClothGeometry.getAttribute(VertexAttributeName.indices).data as Uint16Array;
        this.mConfig.NUMPARTICLES = this.mConfig.clothVertex.length / 3;
        this.mConfig.NUMTSURFACES = this.mConfig.clothFaceTriIds.length / 3;
    }

    protected updateKeyState(keyCode: number, state: boolean) {
        switch (keyCode) {
            case KeyCode.Key_W:
                this.mKeyState[0] = state;
                break;
            case KeyCode.Key_S:
                this.mKeyState[1] = state;
                break;
            case KeyCode.Key_A:
                this.mKeyState[2] = state;
                break;
            case KeyCode.Key_D:
                this.mKeyState[3] = state;
                break;
        }
    }

    public init(){
        super.init();
        this.alwaysRender = true;
        this.geometry = this.mClothGeometry;
        var mat = new LitMaterial();
        mat.roughness = 0.8;
        mat.baseMap = Engine3D.res.redTexture;
        mat.cullMode = 'none';
        this.material = mat;
    }

    public start() {
        Engine3D.inputSystem.addEventListener(KeyEvent.KEY_DOWN, (e: KeyEvent) => this.updateKeyState(e.keyCode, true), this);
        Engine3D.inputSystem.addEventListener(KeyEvent.KEY_UP, (e: KeyEvent) => this.updateKeyState(e.keyCode, false), this);
    }

    public SetInteractionSphere(sphere: Object3D) {
        this.mInteractionSphere = sphere;
    }

    private _tickTime = 0;

    public onCompute(view: View3D, command?: GPUCommandEncoder) {
        if (!this.mClothComputePipeline) {
            this.mConfig.clothVertexBuffer = this.mClothGeometry.vertexBuffer.vertexGPUBuffer;
            this.mClothComputePipeline = new ClothSimulatorPipeline(this.mConfig);
        }

        this._tickTime += Time.delta / 1000.0;
        if (this._tickTime >= this.mConfig.DELTATIME) {
            this._tickTime -= this.mConfig.DELTATIME;
            var pos = new Vector3();
            if (this.mInteractionSphere) {
                var transform = this.mInteractionSphere.transform;
                let dt = Time.delta / 1000.0;
                let speed = 0.5 * dt;
                // W S
                if (this.mKeyState[0]) {
                    transform.z -= speed
                } else if (this.mKeyState[1]) {
                    transform.z += speed
                }
                // A D
                if (this.mKeyState[2]) {
                    transform.x -= speed
                } else if (this.mKeyState[3]) {
                    transform.x += speed
                }
                pos.copyFrom(this.mInteractionSphere.transform.worldPosition);
            }

            this.mClothComputePipeline.compute(command, pos);
        }
    }
}

class ClothSimulatorBuffer {
    protected mPositionBuffer: ComputeGPUBuffer;
    protected mNormalBuffer: ComputeGPUBuffer;
    protected mVertexPositionData: Float32Array;
    protected mVertexPositionBuffer: ComputeGPUBuffer;
    protected mNewPositionBuffer: ComputeGPUBuffer;
    protected mAtomicPositionBuffer: ComputeGPUBuffer;
    protected mAtomicNormalBuffer: ComputeGPUBuffer;
    protected mVelocityBuffer: ComputeGPUBuffer;
    protected mStretchInfosBuffer: ComputeGPUBuffer;
    protected mBendInfosBuffer: ComputeGPUBuffer;
    protected mSurfaceInfosBuffer: ComputeGPUBuffer;
    // protected mInputData: Float32Array;
    protected mInputBuffer: ComputeGPUBuffer;
    protected mOutput0Buffer: ComputeGPUBuffer;

    constructor(config: ClothSimulatorConfig) {
        this.initGPUBuffer(config);
    }

    protected initGPUBuffer(config: ClothSimulatorConfig) {

        const { NUMPARTICLES, clothVertex } = config;

        let position_v4 = new Float32Array(NUMPARTICLES * 4)
        for (let i = 0; i < NUMPARTICLES; i++) {
            position_v4[i * 4] = clothVertex[i * 3]
            position_v4[i * 4 + 1] = clothVertex[i * 3 + 1]
            position_v4[i * 4 + 2] = clothVertex[i * 3 + 2]
            position_v4[i * 4 + 3] = 1
        }
        this.mVertexPositionData = position_v4;
        this.mVertexPositionBuffer = new ComputeGPUBuffer(this.mVertexPositionData.length);
        this.mVertexPositionBuffer.setFloat32Array("", this.mVertexPositionData);
        this.mVertexPositionBuffer.apply();

        this.mNewPositionBuffer = new ComputeGPUBuffer(this.mVertexPositionData.length);
        this.mNewPositionBuffer.setFloat32Array("", this.mVertexPositionData);
        this.mNewPositionBuffer.apply();

        this.mAtomicPositionBuffer = new ComputeGPUBuffer(this.mVertexPositionData.length);

        this.mAtomicNormalBuffer = new ComputeGPUBuffer(NUMPARTICLES * 4);
        this.mNormalBuffer = new ComputeGPUBuffer(NUMPARTICLES * 4);

        const { NUMTSURFACES, clothFaceTriIds } = config;

        const invMass = new Float32Array(NUMPARTICLES)
        for (let i = 0; i < NUMTSURFACES; i++) {
            var A = this.getTriArea(clothVertex, clothFaceTriIds, i);
            var pInvMass = A > 0.0 ? 1.0 / A / 3.0 : 0.0;
            invMass[clothFaceTriIds[3 * i]] += pInvMass;
            invMass[clothFaceTriIds[3 * i + 1]] += pInvMass;
            invMass[clothFaceTriIds[3 * i + 2]] += pInvMass;
        }
        let minX = Number.MAX_VALUE;
        let maxX = -Number.MAX_VALUE;
        let minY = Number.MAX_VALUE;
        let maxY = -Number.MAX_VALUE;
        for (let i = 0; i < NUMPARTICLES; i++) {
            minX = Math.min(minX, clothVertex[3 * i]);
            maxX = Math.max(maxX, clothVertex[3 * i]);
            minY = Math.min(minY, clothVertex[3 * i + 1]);
            maxY = Math.max(maxY, clothVertex[3 * i + 1]);
        }
        // console.log(minX, maxX, minY, maxY)
        var eps = 0.0001;
        for (let i = 0; i < NUMPARTICLES; i++) {
            var x = clothVertex[3 * i];
            var y = clothVertex[3 * i + 1];
            if ((y > maxY - eps) && (x < minX + eps || x > maxX - eps))
                invMass[i] = 0.0;
        }

        const velocity = new Float32Array(4 * NUMPARTICLES)
        for (let i = 0; i < NUMPARTICLES; ++i) {
            velocity[i * 4 + 3] = invMass[i]
        }
        this.mVelocityBuffer = new ComputeGPUBuffer(velocity.length);
        this.mVelocityBuffer.setFloat32Array("", velocity);
        this.mVelocityBuffer.apply();

        const neighbors = this.findTriNeighbors(clothFaceTriIds);
        var edgeIds = [];
        var triPairIds = [];
        for (var i = 0; i < NUMTSURFACES; i++) {
            for (var j = 0; j < 3; j++) {
                var id0 = clothFaceTriIds[3 * i + j];
                var id1 = clothFaceTriIds[3 * i + (j + 1) % 3];

                // each edge only once
                var n = neighbors[3 * i + j];
                if (n < 0 || id0 < id1) {
                    edgeIds.push(id0);
                    edgeIds.push(id1);
                }
                // tri pair
                if (n >= 0 && id0 > id1) {
                    // opposite ids
                    var ni = Math.floor(n / 3);
                    var nj = n % 3;
                    var id2 = clothFaceTriIds[3 * i + (j + 2) % 3];
                    var id3 = clothFaceTriIds[3 * ni + (nj + 2) % 3];
                    triPairIds.push(id0);
                    triPairIds.push(id1);
                    triPairIds.push(id2);
                    triPairIds.push(id3);
                }
            }
        }
        const NUMTEDGES = edgeIds.length / 2
        config.NUMTEDGES = NUMTEDGES
        const stretchInfos = new Float32Array(4 * NUMTEDGES)
        for (var i = 0; i < NUMTEDGES; i++) {
            stretchInfos[i * 4 + 0] = edgeIds[2 * i];
            stretchInfos[i * 4 + 1] = edgeIds[2 * i + 1];
            stretchInfos[i * 4 + 2] = Math.sqrt(this.distance(clothVertex, stretchInfos[i * 4], clothVertex, stretchInfos[i * 4 + 1]));
        }
        this.mStretchInfosBuffer = new ComputeGPUBuffer(stretchInfos.length);
        this.mStretchInfosBuffer.setFloat32Array("", stretchInfos);
        this.mStretchInfosBuffer.apply();

        const NUMTBENDS = triPairIds.length / 4
        config.NUMTBENDS = NUMTBENDS
        const bendInfos = new Float32Array(4 * NUMTBENDS)
        for (var i = 0; i < NUMTBENDS; i++) {
            bendInfos[i * 4 + 0] = triPairIds[4 * i + 2];
            bendInfos[i * 4 + 1] = triPairIds[4 * i + 3];
            bendInfos[i * 4 + 2] = Math.sqrt(this.distance(clothVertex, bendInfos[i * 4], clothVertex, bendInfos[i * 4 + 1]));
        }
        this.mBendInfosBuffer = new ComputeGPUBuffer(bendInfos.length);
        this.mBendInfosBuffer.setFloat32Array("", bendInfos);
        this.mBendInfosBuffer.apply();

        let surfaceInfos = new Float32Array(clothFaceTriIds.length / 3 * 4)
        for (let i = 0; i < clothFaceTriIds.length / 3; i++) {
            surfaceInfos[i * 4] = clothFaceTriIds[i * 3]
            surfaceInfos[i * 4 + 1] = clothFaceTriIds[i * 3 + 1]
            surfaceInfos[i * 4 + 2] = clothFaceTriIds[i * 3 + 2]
            surfaceInfos[i * 4 + 3] = 1
        }
        this.mSurfaceInfosBuffer = new ComputeGPUBuffer(surfaceInfos.length);
        this.mSurfaceInfosBuffer.setFloat32Array("", surfaceInfos);
        this.mSurfaceInfosBuffer.apply();

        const { GRAVITY, DELTATIME, NUMSUBSTEPS, STRETCHCOMPLIANCE, BENDCOMPLIANCE, SPHERERADIUS, SPHERECENTREX, SPHERECENTREY, SPHERECENTREZ } = config;
        this.mInputBuffer = new ComputeGPUBuffer(16);
        this.mInputBuffer.setFloat("NUMPARTICLES", NUMPARTICLES);
        this.mInputBuffer.setFloat("NUMTEDGES", NUMTEDGES);
        this.mInputBuffer.setFloat("NUMTBENDS", NUMTBENDS);
        this.mInputBuffer.setFloat("NUMTSURFACES", NUMTSURFACES);
        this.mInputBuffer.setFloat("GRAVITY", GRAVITY);
        this.mInputBuffer.setFloat("DELTATIME", DELTATIME / NUMSUBSTEPS);
        this.mInputBuffer.setFloat("STRETCHCOMPLIANCE", STRETCHCOMPLIANCE);
        this.mInputBuffer.setFloat("BENDCOMPLIANCE", BENDCOMPLIANCE);
        this.mInputBuffer.setFloat("SPHERERADIUS", SPHERERADIUS);
        this.mInputBuffer.setFloat("SPHERECENTREX", SPHERECENTREX);
        this.mInputBuffer.setFloat("SPHERECENTREY", SPHERECENTREY);
        this.mInputBuffer.setFloat("SPHERECENTREZ", SPHERECENTREZ);
        this.mInputBuffer.setFloat("ALPA", 0.0);
        this.mInputBuffer.apply();

        this.mOutput0Buffer = new ComputeGPUBuffer(NUMPARTICLES * 4);
    }

    public updateInputData(pos: Vector3) {
        this.mInputBuffer.setFloat("SPHERECENTREX", pos.x);
        this.mInputBuffer.setFloat("SPHERECENTREY", pos.y);
        this.mInputBuffer.setFloat("SPHERECENTREZ", pos.z);
        this.mInputBuffer.apply();
    }

    protected getTriArea(position: Float32Array | Uint16Array | Uint32Array, triIds: Float32Array | Uint16Array | Uint32Array, i: number) {
        let id0 = triIds[3 * i];
        let id1 = triIds[3 * i + 1];
        let id2 = triIds[3 * i + 2];

        let temp = [0, 0, 0, 0, 0, 0, 0, 0, 0];
        temp[0] = position[id1 * 3] - position[id0 * 3];
        temp[1] = position[id1 * 3 + 1] - position[id0 * 3 + 1];
        temp[2] = position[id1 * 3 + 2] - position[id0 * 3 + 2];
        temp[3] = position[id2 * 3] - position[id0 * 3];
        temp[4] = position[id2 * 3 + 1] - position[id0 * 3 + 1];
        temp[5] = position[id2 * 3 + 2] - position[id0 * 3 + 2];

        this.cross(temp, 2, temp, 0, temp, 1);
        return this.dot(temp, 2, temp, 2) / 2.0;
    }

    protected cross(a: number[], indexa: number, b: number[], indexb: number, c: number[], indexc: number) {
        a[indexa * 3] = b[indexb * 3 + 1] * c[indexc * 3 + 2] - b[indexb * 3 + 2] * c[indexc * 3 + 1];
        a[indexa * 3 + 1] = b[indexb * 3 + 2] * c[indexc * 3 + 0] - b[indexb * 3 + 0] * c[indexc * 3 + 2];
        a[indexa * 3 + 2] = b[indexb * 3 + 0] * c[indexc * 3 + 1] - b[indexb * 3 + 1] * c[indexc * 3 + 0];
    }

    protected dot(a: number[], indexa: number, b: number[], indexb: number) {
        return a[indexa * 3] * b[indexb * 3] + a[indexa * 3 + 1] * b[indexb * 3 + 1] + a[indexa * 3 + 2] * b[indexb * 3 + 2];
    }

    protected findTriNeighbors(triIds: Float32Array | Uint16Array | Uint32Array) {
        // create common edges
        var edges:any = [];
        var numTris = triIds.length / 3;

        for (var i = 0; i < numTris; i++) {
            for (var j = 0; j < 3; j++) {
                var id0 = triIds[3 * i + j];
                var id1 = triIds[3 * i + (j + 1) % 3];
                edges.push({
                    id0: Math.min(id0, id1),
                    id1: Math.max(id0, id1),
                    edgeNr: 3 * i + j
                });
            }
        }

        // sort so common edges are next to each other
        edges.sort((a, b) => ((a.id0 < b.id0) || (a.id0 == b.id0 && a.id1 < b.id1)) ? -1 : 1);

        // find matchign edges
        const neighbors = new Float32Array(3 * numTris);
        neighbors.fill(-1);        // open edge

        var nr = 0;
        while (nr < edges.length) {
            var e0 = edges[nr];
            nr++;
            if (nr < edges.length) {
                var e1 = edges[nr];
                if (e0.id0 == e1.id0 && e0.id1 == e1.id1) {
                    neighbors[e0.edgeNr] = e1.edgeNr;
                    neighbors[e1.edgeNr] = e0.edgeNr;
                }
                // nr++;
            }
        }

        return neighbors;
    }

    protected distance(a: Float32Array | Uint16Array | Uint32Array, indexa: number, b: Float32Array | Uint16Array | Uint32Array, indexb: number) {
        let a0 = a[indexa * 3] - b[indexb * 3], a1 = a[indexa * 3 + 1] - b[indexb * 3 + 1], a2 = a[indexa * 3 + 2] - b[indexb * 3 + 2];
        return a0 * a0 + a1 * a1 + a2 * a2;
    }
}

class ClothSimulatorPipeline extends ClothSimulatorBuffer {
    protected mConfig: ClothSimulatorConfig;
    protected mPreProcessComputeShader: ComputeShader;
    protected mStretchingComputeShader: ComputeShader;
    protected mBendingComputeShader: ComputeShader;
    protected mPositionUpdateComputeShader: ComputeShader;
    protected mPostProcessComputeShader: ComputeShader;
    protected mComputeNormalComputeShader: ComputeShader;
    protected mNormalUpdateComputeShader: ComputeShader;
    protected mUpdateVertexBufferComputeShader: ComputeShader;

    constructor(config: ClothSimulatorConfig) {
        super(config);
        this.mConfig = config;
        this.initPipeline(this.mConfig);
    }

    public compute(command: GPUCommandEncoder, pos: Vector3) {
        let computePass = command.beginComputePass();

        const { NUMSUBSTEPS } = this.mConfig;

        this.updateInputData(pos);

        for (let i = 0; i < NUMSUBSTEPS; i++) {
            this.mPreProcessComputeShader.compute(computePass);
            this.mStretchingComputeShader.compute(computePass);
            this.mBendingComputeShader.compute(computePass);
            this.mPositionUpdateComputeShader.compute(computePass);
            this.mPostProcessComputeShader.compute(computePass);
        }
        this.mComputeNormalComputeShader.compute(computePass);
        this.mNormalUpdateComputeShader.compute(computePass);
        this.mUpdateVertexBufferComputeShader.compute(computePass);

        computePass.end();
    }

    protected initPipeline(config: ClothSimulatorConfig) {
        const { NUMPARTICLES, NUMTSURFACES, NUMTEDGES, NUMTBENDS } = this.mConfig;

        this.mPreProcessComputeShader = new ComputeShader(preprocess.cs);
        this.mPreProcessComputeShader.setStorageBuffer(`input`, this.mInputBuffer);
        this.mPreProcessComputeShader.setStorageBuffer(`position`, this.mVertexPositionBuffer);
        this.mPreProcessComputeShader.setStorageBuffer(`velocity`, this.mVelocityBuffer);
        this.mPreProcessComputeShader.setStorageBuffer(`newposition`, this.mNewPositionBuffer);
        this.mPreProcessComputeShader.setStorageBuffer(`output`, this.mOutput0Buffer);
        this.mPreProcessComputeShader.workerSizeX = Math.ceil(NUMPARTICLES / 128);

        this.mStretchingComputeShader = new ComputeShader(stretching.cs);
        this.mStretchingComputeShader.setStorageBuffer(`input`, this.mInputBuffer);
        this.mStretchingComputeShader.setStorageBuffer(`inposition`, this.mNewPositionBuffer);
        this.mStretchingComputeShader.setStorageBuffer(`velocity`, this.mVelocityBuffer);
        this.mStretchingComputeShader.setStorageBuffer(`atomicposition`, this.mAtomicPositionBuffer);
        this.mStretchingComputeShader.setStorageBuffer(`stretchinfo`, this.mStretchInfosBuffer);
        this.mStretchingComputeShader.setStorageBuffer(`output`, this.mOutput0Buffer);
        this.mStretchingComputeShader.workerSizeX = Math.ceil(NUMTEDGES / 128);
       
        this.mBendingComputeShader = new ComputeShader(bending.cs);
        this.mBendingComputeShader.setStorageBuffer(`input`, this.mInputBuffer);
        this.mBendingComputeShader.setStorageBuffer(`inposition`, this.mNewPositionBuffer);
        this.mBendingComputeShader.setStorageBuffer(`velocity`, this.mVelocityBuffer);
        this.mBendingComputeShader.setStorageBuffer(`atomicposition`, this.mAtomicPositionBuffer);
        this.mBendingComputeShader.setStorageBuffer(`bendinfo`, this.mBendInfosBuffer);
        this.mBendingComputeShader.setStorageBuffer(`output`, this.mOutput0Buffer);
        this.mBendingComputeShader.workerSizeX = Math.ceil(NUMTBENDS / 128);

        this.mPositionUpdateComputeShader = new ComputeShader(positionupdate.cs);
        this.mPositionUpdateComputeShader.setStorageBuffer(`input`, this.mInputBuffer);
        this.mPositionUpdateComputeShader.setStorageBuffer(`atomicposition`, this.mAtomicPositionBuffer);
        this.mPositionUpdateComputeShader.setStorageBuffer(`outposition`, this.mNewPositionBuffer);
        this.mPositionUpdateComputeShader.setStorageBuffer(`output`, this.mOutput0Buffer);
        this.mPositionUpdateComputeShader.workerSizeX = Math.ceil(NUMPARTICLES / 128);
        
        this.mPostProcessComputeShader = new ComputeShader(postprocess.cs);
        this.mPostProcessComputeShader.setStorageBuffer(`input`, this.mInputBuffer);
        this.mPostProcessComputeShader.setStorageBuffer(`newposition`, this.mNewPositionBuffer);
        this.mPostProcessComputeShader.setStorageBuffer(`velocity`, this.mVelocityBuffer);
        this.mPostProcessComputeShader.setStorageBuffer(`position`, this.mVertexPositionBuffer);
        this.mPostProcessComputeShader.setStorageBuffer(`output`, this.mOutput0Buffer);
        this.mPostProcessComputeShader.workerSizeX = Math.ceil(NUMPARTICLES / 128);

        this.mComputeNormalComputeShader = new ComputeShader(computenormal.cs);
        this.mComputeNormalComputeShader.setStorageBuffer(`input`, this.mInputBuffer);
        this.mComputeNormalComputeShader.setStorageBuffer(`position`, this.mVertexPositionBuffer);
        this.mComputeNormalComputeShader.setStorageBuffer(`surfaceinfo`, this.mSurfaceInfosBuffer);
        this.mComputeNormalComputeShader.setStorageBuffer(`atomicnormal`, this.mAtomicNormalBuffer);
        this.mComputeNormalComputeShader.setStorageBuffer(`output`, this.mOutput0Buffer);
        this.mComputeNormalComputeShader.workerSizeX = Math.ceil(NUMTSURFACES / 128);

        this.mNormalUpdateComputeShader = new ComputeShader(normalupdate.cs);
        this.mNormalUpdateComputeShader.setStorageBuffer(`input`, this.mInputBuffer);
        this.mNormalUpdateComputeShader.setStorageBuffer(`atomicnormal`, this.mAtomicNormalBuffer);
        this.mNormalUpdateComputeShader.setStorageBuffer(`normal`, this.mNormalBuffer);
        this.mNormalUpdateComputeShader.setStorageBuffer(`output`, this.mOutput0Buffer);
        this.mNormalUpdateComputeShader.workerSizeX = Math.ceil(NUMPARTICLES / 128);

        this.mUpdateVertexBufferComputeShader = new ComputeShader(updatevertexbuffer.cs);
        this.mUpdateVertexBufferComputeShader.setStorageBuffer(`input`, this.mInputBuffer);
        this.mUpdateVertexBufferComputeShader.setStorageBuffer(`position`, this.mVertexPositionBuffer);
        this.mUpdateVertexBufferComputeShader.setStorageBuffer(`normal`, this.mNormalBuffer);
        this.mUpdateVertexBufferComputeShader.setStorageBuffer(`vertexBuffer`, config.clothVertexBuffer);
        this.mUpdateVertexBufferComputeShader.workerSizeX = Math.ceil(NUMTSURFACES / 128);
    }
}

class bending {
    public static cs: string = /* wgsl */ `
        struct InputArgs {
            NUMPARTICLES: f32,
            NUMTEDGES: f32,
            NUMTBENDS: f32,
            NUMTSURFACES: f32,
            GRAVITY: f32,
            DELTATIME: f32,
            STRETCHCOMPLIANCE: f32,
            BENDCOMPLIANCE: f32,
            SPHERERADIUS: f32,
            SPHERECENTREX: f32,
            SPHERECENTREY: f32,
            SPHERECENTREZ: f32,
            ALPA: f32,
            rsv0: f32,
            rsv1: f32,
            rsv2: f32,
        };

        @group(0) @binding(0) var<storage, read> input: InputArgs;
        @group(0) @binding(1) var<storage, read_write> inposition: array<vec4<f32>>;
        @group(0) @binding(2) var<storage, read_write> velocity: array<vec4<f32>>;
        @group(0) @binding(3) var<storage, read_write> atomicposition: array<atomic<i32>>;
        @group(1) @binding(0) var<storage, read_write> bendinfo: array<vec4<f32>>;
        @group(1) @binding(1) var<storage, read_write> output: array<vec4<f32>>;
        
        const size = u32(128);
        @compute @workgroup_size(size)
        fn CsMain(
            @builtin(global_invocation_id) GlobalInvocationID : vec3<u32>
        ) {
            var index = GlobalInvocationID.x;
            if(index >= u32(input.NUMTBENDS)){
                return;
            }
            var deltatime = input.DELTATIME;
            var compliance = input.BENDCOMPLIANCE;
        
            var alpha = compliance / deltatime / deltatime;
            // for(var i = 0; i < i32(input[2]); i++){
                var index0 = u32(bendinfo[index][0]);
                var index1 = u32(bendinfo[index][1]);
                var weight0 = velocity[index0][3];
                var weight1 = velocity[index1][3];
                var weight = weight0 + weight1;
        
                if(weight == 0.0){
                    return;
                }
                var position0 = vec3<f32>(inposition[index0][0], inposition[index0][1], inposition[index0][2]);
                var position1 = vec3<f32>(inposition[index1][0], inposition[index1][1], inposition[index1][2]);
                var grad = position0 - position1;
                var length = distance(position0, position1);
                if(length == 0.0){
                    return;
                }
        
                grad = grad / length;
                var restlength = bendinfo[index][2];
                var c = length - restlength;
                var lambda = - c / (weight + alpha);
        
                var temp0 = grad * lambda * weight0;
                var temp1 = grad * lambda * weight1;
                atomicAdd(&atomicposition[index0 * u32(4) + u32(0)], i32(temp0.x * 1000000.0));
                atomicAdd(&atomicposition[index0 * u32(4) + u32(1)], i32(temp0.y * 1000000.0));
                atomicAdd(&atomicposition[index0 * u32(4) + u32(2)], i32(temp0.z * 1000000.0));
                atomicAdd(&atomicposition[index0 * u32(4) + u32(3)], i32(1.0));
                atomicSub(&atomicposition[index1 * u32(4) + u32(0)], i32(temp1.x * 1000000.0));
                atomicSub(&atomicposition[index1 * u32(4) + u32(1)], i32(temp1.y * 1000000.0));
                atomicSub(&atomicposition[index1 * u32(4) + u32(2)], i32(temp1.z * 1000000.0));
                atomicAdd(&atomicposition[index1 * u32(4) + u32(3)], i32(1.0));
            // }
            // for(var i = 0; i < i32(input[0]); i++){
            //     outposition[i][0] = inposition[i][0];
            //     outposition[i][1] = inposition[i][1];
            //     outposition[i][2] = inposition[i][2];
            // }
            var debug = output[index];
            // output[index][0] = position0.x;
            // output[index][1] = position0.y;
            // output[index][2] = position[index0][0];
            // output[index][3] = position[index0][1];
        }
    `;
}

class computenormal {
    public static cs: string = /* wgsl */ `
        struct InputArgs {
            NUMPARTICLES: f32,
            NUMTEDGES: f32,
            NUMTBENDS: f32,
            NUMTSURFACES: f32,
            GRAVITY: f32,
            DELTATIME: f32,
            STRETCHCOMPLIANCE: f32,
            BENDCOMPLIANCE: f32,
            SPHERERADIUS: f32,
            SPHERECENTREX: f32,
            SPHERECENTREY: f32,
            SPHERECENTREZ: f32,
            ALPA: f32,
            rsv0: f32,
            rsv1: f32,
            rsv2: f32,
        };

        @group(0) @binding(0) var<storage, read> input: InputArgs;
        @group(0) @binding(1) var<storage, read_write> position: array<vec4<f32>>;
        @group(0) @binding(2) var<storage, read_write> surfaceinfo: array<vec4<f32>>;
        @group(0) @binding(3) var<storage, read_write> atomicnormal: array<atomic<i32>>;
        @group(1) @binding(0) var<storage, read_write> output: array<vec4<f32>>;
        
        const size = u32(128);
        @compute @workgroup_size(size)
        fn CsMain(
            @builtin(global_invocation_id) GlobalInvocationID : vec3<u32>
        ) {
            var index = GlobalInvocationID.x;
            if(index >= u32(input.NUMTSURFACES)){
                return;
            }
        
            var index0 = u32(surfaceinfo[index][0]);
            var index1 = u32(surfaceinfo[index][1]);
            var index2 = u32(surfaceinfo[index][2]);
            var vector1 = vec3<f32>(position[index1][0] - position[index0][0], position[index1][1] - position[index0][1], position[index1][2] - position[index0][2]);
            var vector2 = vec3<f32>(position[index2][0] - position[index1][0], position[index2][1] - position[index1][1], position[index2][2] - position[index1][2]);
            var temp = cross(vector1, vector2);
            temp = temp / length(temp);
                
            atomicAdd(&atomicnormal[index0 * u32(4) + u32(0)], i32(temp.x * 1000000.0));
            atomicAdd(&atomicnormal[index0 * u32(4) + u32(1)], i32(temp.y * 1000000.0));
            atomicAdd(&atomicnormal[index0 * u32(4) + u32(2)], i32(temp.z * 1000000.0));
            atomicAdd(&atomicnormal[index0 * u32(4) + u32(3)], i32(1.0));
            atomicAdd(&atomicnormal[index1 * u32(4) + u32(0)], i32(temp.x * 1000000.0));
            atomicAdd(&atomicnormal[index1 * u32(4) + u32(1)], i32(temp.y * 1000000.0));
            atomicAdd(&atomicnormal[index1 * u32(4) + u32(2)], i32(temp.z * 1000000.0));
            atomicAdd(&atomicnormal[index1 * u32(4) + u32(3)], i32(1.0));
            atomicAdd(&atomicnormal[index2 * u32(4) + u32(0)], i32(temp.x * 1000000.0));
            atomicAdd(&atomicnormal[index2 * u32(4) + u32(1)], i32(temp.y * 1000000.0));
            atomicAdd(&atomicnormal[index2 * u32(4) + u32(2)], i32(temp.z * 1000000.0));
            atomicAdd(&atomicnormal[index2 * u32(4) + u32(3)], i32(1.0));

            var debug = output[index];
            // output[index][0] = velocity[index][0];
            // output[index][1] = velocity[index][1];
            // output[index][2] = currentposition.x;
            // output[index][3] = oldposition.x;
            // output[index][2] = tempvelocity.z;
            // output[index][3] = newposition[index0][1];
        }
    `;
}

class normalupdate {
    public static cs: string = /* wgsl */ `
        struct InputArgs {
            NUMPARTICLES: f32,
            NUMTEDGES: f32,
            NUMTBENDS: f32,
            NUMTSURFACES: f32,
            GRAVITY: f32,
            DELTATIME: f32,
            STRETCHCOMPLIANCE: f32,
            BENDCOMPLIANCE: f32,
            SPHERERADIUS: f32,
            SPHERECENTREX: f32,
            SPHERECENTREY: f32,
            SPHERECENTREZ: f32,
            ALPA: f32,
            rsv0: f32,
            rsv1: f32,
            rsv2: f32,
        };

        @group(0) @binding(0) var<storage, read> input: InputArgs;
        @group(0) @binding(1) var<storage, read_write> atomicnormal: array<atomic<i32>>;
        @group(0) @binding(2) var<storage, read_write> normal: array<vec4<f32>>;
        @group(0) @binding(3) var<storage, read_write> output: array<vec4<f32>>;
        
        const size = u32(128);
        @compute @workgroup_size(size)
        fn CsMain(
            @builtin(global_invocation_id) GlobalInvocationID : vec3<u32>
        ) {
            var index = GlobalInvocationID.x;
            if(index >= u32(input.NUMPARTICLES)){
                return;
            }
    
            var num = f32(atomicLoad(&atomicnormal[index * u32(4) + u32(3)]));
            var temp = vec3<f32>(f32(atomicLoad(&atomicnormal[index * u32(4) + u32(0)])) / (num) / 1000000.0,
                                 f32(atomicLoad(&atomicnormal[index * u32(4) + u32(1)])) / (num) / 1000000.0,
                                 f32(atomicLoad(&atomicnormal[index * u32(4) + u32(2)])) / (num) / 1000000.0);
            // temp = temp / length(temp);
            normal[index][0] = temp.x;
            normal[index][1] = temp.y;
            normal[index][2] = temp.z;
            atomicStore(&atomicnormal[index * u32(4) + u32(0)], i32(0.0));
            atomicStore(&atomicnormal[index * u32(4) + u32(1)], i32(0.0));
            atomicStore(&atomicnormal[index * u32(4) + u32(2)], i32(0.0));
            atomicStore(&atomicnormal[index * u32(4) + u32(3)], i32(0.0));
        
            var debug = output[index];
            // output[index][0] = f32(index);
            // output[index][1] = normal[index][0];
            // output[index][2] = normal[index][0];
            // output[index][3] = normal[index][1];
        }
    `;
}

class positionupdate {
    public static cs: string = /* wgsl */ `
        struct InputArgs {
            NUMPARTICLES: f32,
            NUMTEDGES: f32,
            NUMTBENDS: f32,
            NUMTSURFACES: f32,
            GRAVITY: f32,
            DELTATIME: f32,
            STRETCHCOMPLIANCE: f32,
            BENDCOMPLIANCE: f32,
            SPHERERADIUS: f32,
            SPHERECENTREX: f32,
            SPHERECENTREY: f32,
            SPHERECENTREZ: f32,
            ALPA: f32,
            rsv0: f32,
            rsv1: f32,
            rsv2: f32,
        };

        @group(0) @binding(0) var<storage, read> input: InputArgs;
        @group(0) @binding(1) var<storage, read_write> atomicposition: array<atomic<i32>>;
        @group(0) @binding(2) var<storage, read_write> outposition: array<vec4<f32>>;
        @group(0) @binding(3) var<storage, read_write> output: array<vec4<f32>>;
        
        const size = u32(128);
        @compute @workgroup_size(size)
        fn CsMain(
            @builtin(global_invocation_id) GlobalInvocationID : vec3<u32>
        ) {
            var index = GlobalInvocationID.x;
            if(index >= u32(input.NUMPARTICLES)){
                return;
            }
            var alpha = input.ALPA;
            var num = f32(atomicLoad(&atomicposition[index * u32(4) + u32(3)]));
            outposition[index][0] += f32(atomicLoad(&atomicposition[index * u32(4) + u32(0)])) / (num + alpha) / 1000000.0;
            outposition[index][1] += f32(atomicLoad(&atomicposition[index * u32(4) + u32(1)])) / (num + alpha) / 1000000.0;
            outposition[index][2] += f32(atomicLoad(&atomicposition[index * u32(4) + u32(2)])) / (num + alpha) / 1000000.0;
            atomicStore(&atomicposition[index * u32(4) + u32(0)], i32(0.0));
            atomicStore(&atomicposition[index * u32(4) + u32(1)], i32(0.0));
            atomicStore(&atomicposition[index * u32(4) + u32(2)], i32(0.0));
            atomicStore(&atomicposition[index * u32(4) + u32(3)], i32(0.0));
        
            var debug = output[index];
            // output[index][0] = f32(index);
            // output[index][1] = num;
            // output[index][2] = position[index0][0];
            // output[index][3] = position[index0][1];
        }
    `;
}

class postprocess {
    public static cs: string = /* wgsl */ `
        struct InputArgs {
            NUMPARTICLES: f32,
            NUMTEDGES: f32,
            NUMTBENDS: f32,
            NUMTSURFACES: f32,
            GRAVITY: f32,
            DELTATIME: f32,
            STRETCHCOMPLIANCE: f32,
            BENDCOMPLIANCE: f32,
            SPHERERADIUS: f32,
            SPHERECENTREX: f32,
            SPHERECENTREY: f32,
            SPHERECENTREZ: f32,
            ALPA: f32,
            rsv0: f32,
            rsv1: f32,
            rsv2: f32,
        };

        @group(0) @binding(0) var<storage, read> input: InputArgs;
        @group(0) @binding(1) var<storage, read_write> newposition: array<vec4<f32>>;
        @group(0) @binding(2) var<storage, read_write> velocity: array<vec4<f32>>;
        @group(0) @binding(3) var<storage, read_write> position: array<vec4<f32>>;
        @group(1) @binding(0) var<storage, read_write> output: array<vec4<f32>>;
        
        fn intersectSphere(origin: vec3<f32>, ray: vec3<f32>, spherecenter: vec3<f32>, sphereradius: f32) -> f32{   
            var tosphere = origin - spherecenter;   
            var a = dot(ray, ray);   
            var b = 2.0 * dot(tosphere, ray);   
            var c = dot(tosphere, tosphere) - sphereradius*sphereradius;   
            var discriminant = b*b - 4.0*a*c;   
            if(discriminant > 0.0) {     
                var t = (-b - sqrt(discriminant)) / (2.0 * a);     
                if(t > 0.0) {
                    return t;
                }   
            }   
            return 0.0; 
        }
        
        const size = u32(128);
        @compute @workgroup_size(size)
        fn CsMain(
            @builtin(global_invocation_id) GlobalInvocationID : vec3<u32>
        ) {
            var index = GlobalInvocationID.x;
            if(index >= u32(input.NUMPARTICLES)){
                return;
            }
            var deltatime = input.DELTATIME;
            var spherecentre = vec3<f32>(input.SPHERECENTREX, input.SPHERECENTREY, input.SPHERECENTREZ);
            var sphereradius = input.SPHERERADIUS;
        
            if(velocity[index][3] == 0.0){
                return;
            }
            var oldposition = vec3<f32>(position[index][0], position[index][1], position[index][2]);
            var currentposition = vec3<f32>(newposition[index][0], newposition[index][1], newposition[index][2]);
        
            var tempvelocity = (currentposition - oldposition) / deltatime;
            if(distance(spherecentre, currentposition) < sphereradius){
                var ray = currentposition - spherecentre;
                // var tsphere = intersectSphere(oldposition, ray, spherecentre, sphereradius);
                currentposition = currentposition + ray * (sphereradius - distance(spherecentre, currentposition));
                tempvelocity = (currentposition - oldposition) / deltatime;
                // output[index][0] = distance(spherecentre, currentposition);
                // output[index][1] = ray.z;
                // output[index][2] = ray.x;
                // output[index][3] = ray.y;
            }
            
            velocity[index][0] = tempvelocity.x;
            velocity[index][1] = tempvelocity.y;
            velocity[index][2] = tempvelocity.z;
            position[index][0] = newposition[index][0];
            position[index][1] = newposition[index][1];
            position[index][2] = newposition[index][2];
                
            var debug = output[index];
            // output[index][0] = velocity[index][0];
            // output[index][1] = velocity[index][1];
            // output[index][2] = currentposition.x;
            // output[index][3] = oldposition.x;
            // output[index][2] = tempvelocity.z;
            // output[index][3] = newposition[index0][1];
        }
    `;
}

class preprocess {
    public static cs:string = /* wgsl */ `
        struct InputArgs {
            NUMPARTICLES: f32,
            NUMTEDGES: f32,
            NUMTBENDS: f32,
            NUMTSURFACES: f32,
            GRAVITY: f32,
            DELTATIME: f32,
            STRETCHCOMPLIANCE: f32,
            BENDCOMPLIANCE: f32,
            SPHERERADIUS: f32,
            SPHERECENTREX: f32,
            SPHERECENTREY: f32,
            SPHERECENTREZ: f32,
            ALPA: f32,
            rsv0: f32,
            rsv1: f32,
            rsv2: f32,
        };

        @group(0) @binding(0) var<storage, read> input: InputArgs;
        @group(0) @binding(1) var<storage, read_write> position: array<vec4<f32>>;
        @group(0) @binding(2) var<storage, read_write> velocity: array<vec4<f32>>;
        @group(0) @binding(3) var<storage, read_write> newposition: array<vec4<f32>>;
        @group(1) @binding(0) var<storage, read_write> output: array<vec4<f32>>;
        
        const size = u32(128);
        @compute @workgroup_size(size)
        fn CsMain(
            @builtin(global_invocation_id) GlobalInvocationID : vec3<u32>
        ) {
            var index = GlobalInvocationID.x;
            if(index >= u32(input.NUMPARTICLES)){
                return;
            }
            var gravity = input.GRAVITY;
            var deltatime = input.DELTATIME;
        
            if(velocity[index][3] == 0.0){
                return;
            }
            var oldposition = vec3<f32>(position[index][0], position[index][1], position[index][2]);
            var tempvelocity = vec3<f32>(velocity[index][0], velocity[index][1], velocity[index][2])
                            + vec3<f32>(0.0, gravity * deltatime, 0.0);
            var tempposition = oldposition + tempvelocity * deltatime;
            // if(tempposition.y < 0.0){
            //     tempposition.x = oldposition.x;
            //     tempposition.y = 0.0;
            //     tempposition.z = oldposition.z;
            // }     
            newposition[index][0] = tempposition.x;
            newposition[index][1] = tempposition.y;
            newposition[index][2] = tempposition.z;
        
            var debug = output[index];
            // output[index][0] = tempposition.x;
            // output[index][1] = tempposition.y;
            // output[index][2] = tempposition.z;
            // output[index][3] = f32(index);
        }
    `;
}

class stretching {
    public static cs:string = /* wgsl */ `
        struct InputArgs {
            NUMPARTICLES: f32,
            NUMTEDGES: f32,
            NUMTBENDS: f32,
            NUMTSURFACES: f32,
            GRAVITY: f32,
            DELTATIME: f32,
            STRETCHCOMPLIANCE: f32,
            BENDCOMPLIANCE: f32,
            SPHERERADIUS: f32,
            SPHERECENTREX: f32,
            SPHERECENTREY: f32,
            SPHERECENTREZ: f32,
            ALPA: f32,
            rsv0: f32,
            rsv1: f32,
            rsv2: f32,
        };

        @group(0) @binding(0) var<storage, read> input: InputArgs;
        @group(0) @binding(1) var<storage, read_write> inposition: array<vec4<f32>>;
        @group(0) @binding(2) var<storage, read_write> velocity: array<vec4<f32>>;
        @group(0) @binding(3) var<storage, read_write> atomicposition: array<atomic<i32>>;
        @group(1) @binding(0) var<storage, read_write> stretchinfo: array<vec4<f32>>;
        @group(1) @binding(1) var<storage, read_write> output: array<vec4<f32>>;
        
        const size = u32(128);
        @compute @workgroup_size(size)
        fn CsMain(
            @builtin(global_invocation_id) GlobalInvocationID : vec3<u32>
        ) {
            var index = GlobalInvocationID.x;
            if(index >= u32(input.NUMTEDGES)){
                return;
            }
            var deltatime = input.DELTATIME;
            var compliance = input.STRETCHCOMPLIANCE;
        
            var alpha = compliance / deltatime / deltatime;
            // for(var i = 0; i < i32(input[1]); i++){
                var index0 = u32(stretchinfo[index][0]);
                var index1 = u32(stretchinfo[index][1]);
                var weight0 = velocity[index0][3];
                var weight1 = velocity[index1][3];
                var weight = weight0 + weight1;
        
                if(weight == 0.0){
                    return;
                }
                var position0 = vec3<f32>(inposition[index0][0], inposition[index0][1], inposition[index0][2]);
                var position1 = vec3<f32>(inposition[index1][0], inposition[index1][1], inposition[index1][2]);
                var grad = position0 - position1;
                var length = distance(position0, position1);
                if(length == 0.0){
                    return;
                }
        
                grad = grad / length;
                var restlength = stretchinfo[index][2];
                var c = length - restlength;
                var lambda = - c / (weight + alpha);
        
                var temp0 = grad * lambda * weight0;
                var temp1 = grad * lambda * weight1;
                atomicAdd(&atomicposition[index0 * u32(4) + u32(0)], i32(temp0.x * 1000000.0));
                atomicAdd(&atomicposition[index0 * u32(4) + u32(1)], i32(temp0.y * 1000000.0));
                atomicAdd(&atomicposition[index0 * u32(4) + u32(2)], i32(temp0.z * 1000000.0));
                atomicAdd(&atomicposition[index0 * u32(4) + u32(3)], i32(1.0));
                atomicSub(&atomicposition[index1 * u32(4) + u32(0)], i32(temp1.x * 1000000.0));
                atomicSub(&atomicposition[index1 * u32(4) + u32(1)], i32(temp1.y * 1000000.0));
                atomicSub(&atomicposition[index1 * u32(4) + u32(2)], i32(temp1.z * 1000000.0));
                atomicAdd(&atomicposition[index1 * u32(4) + u32(3)], i32(1.0));
            // }
            // for(var i = 0; i < i32(input[0]); i++){
            //     outposition[i][0] = inposition[i][0];
            //     outposition[i][1] = inposition[i][1];
            //     outposition[i][2] = inposition[i][2];
            // }
            var debug = output[index];
            // output[index][0] = position0.x;
            // output[index][1] = position0.y;
            // output[index][2] = position[index0][0];
            // output[index][3] = position[index0][1];
        }
    `;
}

class updatevertexbuffer {
    public static cs: string =/* wgsl */ `
        struct InputArgs {
            NUMPARTICLES: f32,
            NUMTEDGES: f32,
            NUMTBENDS: f32,
            NUMTSURFACES: f32,
            GRAVITY: f32,
            DELTATIME: f32,
            STRETCHCOMPLIANCE: f32,
            BENDCOMPLIANCE: f32,
            SPHERERADIUS: f32,
            SPHERECENTREX: f32,
            SPHERECENTREY: f32,
            SPHERECENTREZ: f32,
            ALPA: f32,
            rsv0: f32,
            rsv1: f32,
            rsv2: f32,
        };

        @group(0) @binding(0) var<storage, read> input: InputArgs;
        @group(0) @binding(1) var<storage, read> position: array<vec4<f32>>;
        @group(0) @binding(2) var<storage, read> normal: array<vec4<f32>>;
        @group(0) @binding(3) var<storage, read_write> vertexBuffer: array<f32>;
        
        const size = u32(128);
        @compute @workgroup_size(size)
        fn CsMain(
            @builtin(global_invocation_id) GlobalInvocationID : vec3<u32>
        ) {
            var index = GlobalInvocationID.x;
            if(index >= u32(input.NUMTSURFACES)){
                return;
            }
            
            let pos = position[index];
            let nor = normal[index];

            let stride = u32(3+3+2+2);
            index *= stride;
            // update position
            vertexBuffer[index + 0] = pos[0];
            vertexBuffer[index + 1] = pos[1];
            vertexBuffer[index + 2] = pos[2];
            // // update normal
            vertexBuffer[index + 3] = nor[0];
            vertexBuffer[index + 4] = nor[1];
            vertexBuffer[index + 5] = nor[2];
        }
    `;
}

Released under the MIT License