class Renderer {
        constructor({ width = 256, height = 256, context = null } = {}) {
            this.width = width;
            this.height = height;
            this.context = context;
            if (context) {
                this.imageBuffer = context.createImageData(width, height);
            }
            this.depthBuffer = new Float32Array(width * height).fill(Infinity);
            this.lightDirection = normalize([0, 0, 1]);
        }

        render(scene, camera) {
            const { width, height, context, lightDirection, depthBuffer, imageBuffer } = this;

            if (context) {
                if (imageBuffer) imageBuffer.data.fill(0);
                else this.imageBuffer = context.createImageData(width, height);
                depthBuffer.fill(Infinity);
            }

            const cameraMatrix = camera.cameraMatrix;
            const cameraPosition = camera.position;
            const backfaceCulling = camera.backfaceCullingEnabled;


            const toScreen = (v) => [
                (v[0] / v[3] + 1) * 0.5 * width,
                (v[1] / v[3] + 1) * 0.5 * height,
                v[2] / v[3]
            ];

            for (const mesh of scene.meshes) {
                const {transformMatrix, geometry} = mesh;
                const {vertices, vertexNormals, indices} = geometry;
                const worldVertices = new Array(vertices.length);
                const worldNormals = new Array(vertexNormals.length);
                for (let i = 0; i < vertices.length; i++) {
                    const v = vertices[i];
                    const n = vertexNormals[i];
                    worldVertices[i] = fast4x1mul(transformMatrix, [v[0], v[1], v[2], 1]);
                    worldNormals[i] = normalize(fast4x1mul(transformMatrix, [n[0], n[1], n[2], 0]));
                }
                const viewVertices = new Array(worldVertices.length);
                for (let i = 0; i < worldVertices.length; i++) {
                    viewVertices[i] = fast4x1mul(cameraMatrix, worldVertices[i]);
                }

                for (const triangle of indices) {
                    if (backfaceCulling) {
                        const [wv0, wv1, wv2] = [
                            worldVertices[triangle[0]],
                            worldVertices[triangle[1]],
                            worldVertices[triangle[2]]
                        ];
                        const faceNormal = computeNormal(wv0, wv1, wv2);
                        const camToTri = [
                            wv0[0] - cameraPosition[0],
                            wv0[1] - cameraPosition[1],
                            wv0[2] - cameraPosition[2]
                        ];

                        if (dotProduct(faceNormal, camToTri) <= 0) {
                            continue;
                        }
                    }

                    const [vv0, vv1, vv2] = [
                        viewVertices[triangle[0]],
                        viewVertices[triangle[1]],
                        viewVertices[triangle[2]]
                    ];

                    const [n0, n1, n2] = [
                        worldNormals[triangle[0]],
                        worldNormals[triangle[1]],
                        worldNormals[triangle[2]]
                    ];


                    let triangles = [[vv0, vv1, vv2]];
                    let normalSets = [[n0, n1, n2]];

                    for (const [axis, min, max] of [[0, -1, 1], [1, -1, 1], [2, 0, 1]]) {
                        const newTriangles = [];
                        const newNormals = [];
                        for (let i = 0; i < triangles.length; i++) {
                            const clipped = clipTriangle(triangles[i], normalSets[i], axis, min, max);
                            for (const [clippedVertices, clippedNormals] of clipped) {
                                newTriangles.push(clippedVertices);
                                newNormals.push(clippedNormals);
                            }
                        }
                        triangles = newTriangles;
                        normalSets = newNormals;
                        if (triangles.length === 0) break;
                    }

                    for (let i = 0; i < triangles.length; i++) {
                        const [cV0, cV1, cV2] = triangles[i];
                        const [nV0, nV1, nV2] = normalSets[i];

                        const [vv0, vv1, vv2] = [toScreen(cV0), toScreen(cV1), toScreen(cV2)];

                        const colorV0 = Math.max(0, dotProduct(nV0, lightDirection)) * 255;
                        const colorV1 = Math.max(0, dotProduct(nV1, lightDirection)) * 255;
                        const colorV2 = Math.max(0, dotProduct(nV2, lightDirection)) * 255;

                        this.fillTriangle(vv0, vv1, vv2, colorV0, colorV1, colorV2);
                    }
                }
            }

            context.putImageData(this.imageBuffer, 0, 0);
        }

        fillTriangle(v0, v1, v2, color0, color1, color2) {
            const { width, height, imageBuffer, depthBuffer } = this;
        
            // Combine vertices with their associated colors
            let vertices = [
                { pos: v0, color: color0 },
                { pos: v1, color: color1 },
                { pos: v2, color: color2 },
            ];
        
            // Sort the vertices by y-coordinate ascending (sv0.y <= sv1.y <= sv2.y)
            vertices.sort((a, b) => a.pos[1] - b.pos[1]);
        
            // Unpack the sorted vertices and colors
            const [sv0, sv1, sv2] = vertices.map(v => v.pos);
            const [sc0, sc1, sc2] = vertices.map(v => v.color);
        
            // Precompute the denominator for barycentric coordinates
            const denom = (sv1[1] - sv2[1]) * (sv0[0] - sv2[0]) + (sv2[0] - sv1[0]) * (sv0[1] - sv2[1]);
            if (denom === 0) return; // Degenerate triangle
        
            const invDenom = 1 / denom;
        
            // Determine the bounding box of the triangle
            const yStart = Math.max(0, Math.ceil(sv0[1]));
            const yEnd = Math.min(height - 1, Math.floor(sv2[1]));
        
            // Define the triangle edges for intersection tests
            const edges = [
                { v1: sv0, v2: sv1 },
                { v1: sv1, v2: sv2 },
                { v1: sv2, v2: sv0 }
            ];
        
            for (let y = yStart; y <= yEnd; y++) {
                let xMin = width, xMax = 0;
        
                // Find intersection points of the current scanline with the triangle edges
                for (const edge of edges) {
                    const { v1, v2 } = edge;
                    if (y < Math.min(v1[1], v2[1]) || y > Math.max(v1[1], v2[1])) continue;
        
                    const dy = v2[1] - v1[1];
                    if (dy === 0) continue; // Skip horizontal edges
        
                    const t = (y - v1[1]) / dy;
                    const x = v1[0] + t * (v2[0] - v1[0]);
        
                    xMin = Math.min(xMin, x);
                    xMax = Math.max(xMax, x);
                }
        
                // Clamp the x boundaries to the screen dimensions
                const xStart = Math.max(0, Math.ceil(xMin));
                const xEnd = Math.min(width - 1, Math.floor(xMax));
        
                // Precompute the base index for the current scanline in the buffer
                const baseIndex = y * width;
        
                for (let x = xStart; x <= xEnd; x++) {
                    // Compute barycentric coordinates
                    const w0 = ((sv1[1] - sv2[1]) * (x - sv2[0]) + (sv2[0] - sv1[0]) * (y - sv2[1])) * invDenom;
                    const w1 = ((sv2[1] - sv0[1]) * (x - sv2[0]) + (sv0[0] - sv2[0]) * (y - sv2[1])) * invDenom;
                    const w2 = 1 - w0 - w1;
        
                    // Early rejection if the point is outside the triangle
                    if (w0 < 0 || w1 < 0 || w2 < 0) continue;
        
                    // Depth interpolation (assuming z is at index 2)
                    const zInterpolated = 1 / (w0 / sv0[2] + w1 / sv1[2] + w2 / sv2[2]);
        
                    // Buffer index for depth and color
                    const index = baseIndex + x;
        
                    if (zInterpolated < depthBuffer[index]) {
                        depthBuffer[index] = zInterpolated;
        
                        // Perspective-correct color interpolation
                        const interpolatedColor = (w0 * sc0 + w1 * sc1 + w2 * sc2) / (w0 + w1 + w2);
        
                        const shadedColor = [
                            interpolatedColor,
                            interpolatedColor,
                            interpolatedColor,
                            255
                        ];
        
                        const bufferIndex = index * 4;
                        imageBuffer.data[bufferIndex] = shadedColor[0];
                        imageBuffer.data[bufferIndex + 1] = shadedColor[1];
                        imageBuffer.data[bufferIndex + 2] = shadedColor[2];
                        imageBuffer.data[bufferIndex + 3] = shadedColor[3];
                    }
                }
            }
        }

    }


Modify this code to add web workers. Make sure that the renderer can handle continuous rendering (loop), and does not divy up the image into slices or regions or whatever as this is unnecessary because of the depth buffer implemented. Allow the webworkers to use a shared depth buffer. Make sure that the function names inside of the web worker are the same so i can copy functions.