Experiencing performance issues when using Three.js CSG on two BufferGeometries

I am currently working on a project using Three.js where I’m facing performance issues when using the Three.js CSG library on two BufferGeometries. The scenario involves creating two independent geometries, object_1 and object_2, using the renderSVG function. The goal is to perform a CSG operation between these two geometries.

The issue arises when using object_2 alongside object_1. As soon as object_2 is introduced, the performance degrades significantly and the software becomes unmanageable. Any insights or suggestions on optimizing the performance and resolving this problem would be greatly appreciated!

Here is the code:

import * as THREE from 'three';
import { SUBTRACTION, INTERSECTION, ADDITION, Brush, Evaluator } from 'three-bvh-csg';

import { SVGLoader } from 'three/addons/loaders/SVGLoader';
import { OrbitControls } from 'three/addons/controls/OrbitControls';

const loader = new SVGLoader();


let renderer, camera, scene;
let brush1, brush2, result;

const modelViewerWidth = (window.innerWidth / 4) * 3;
const modelViewerHeight = window.innerHeight;

let csgEvaluator = new Evaluator();
csgEvaluator.attributes = [ 'position', 'normal' ];
csgEvaluator.useGroups = false;


const params = {
	displayBrush: true,
	operation: SUBTRACTION,
};


init();

async function init() {

    // create the scene and set a background color
    scene = new THREE.Scene();
    scene.background = new THREE.Color( "#dadada" );

    camera = new THREE.PerspectiveCamera( 50, modelViewerWidth/modelViewerHeight, 1, 100 );
    camera.position.set(0, 15, 20);
    camera.rotation.x = Math.PI * -0.2;


    // set up the renderer and the size of the model viewer
    renderer = new THREE.WebGLRenderer( { antialias: true } );
    renderer.setSize(modelViewerWidth, modelViewerHeight);
	renderer.setPixelRatio( window.devicePixelRatio );
	renderer.shadowMap.enabled = true;
	renderer.shadowMap.type = THREE.PCFSoftShadowMap;
    
    

    // add the renderer to the modelViewer element 
    document.getElementById("modelViewer").appendChild( renderer.domElement );

    // add the gridHelper to show orientation 
    const gridHelper = new THREE.GridHelper( 100, 100 );
    scene.add(gridHelper);

    // add the grid axis
    const axesHelper = new THREE.AxesHelper( 3 );
    axesHelper.position.set(-10, 0, -10);
    scene.add(axesHelper);

    // add controls to enable the user to view the model from different angles
    const controls = new OrbitControls( camera, renderer.domElement );

    brush1 = new Brush(
        renderSVG(0.05),
        new THREE.MeshBasicMaterial({ color: "#ff0000" })
    );
    

    brush2 = new Brush(
        renderSVG(0.05),
        new THREE.MeshBasicMaterial({ color: "#ff0000" })
    );    


    result = new THREE.Mesh();
    result.material.color.set("#004D9A");

scene.add( result );
    render()

}

    
function renderSVG(size) {
    let bufferGeometry = new THREE.BufferGeometry();

    const positions = [];
    const normals = [];

    loader.load(
        'Merry Christmas.svg',
        function (data) {
            const paths = data.paths;

            for (let i = 0; i < paths.length; i++) {
                const path = paths[i];
                const shapes = path.toShapes(true);
                const extrudeSettings = {
                    depth: 20,
                    bevelEnabled: false,
                };

                for (let j = 0; j < shapes.length; j++) {
                    const shape = shapes[j];
                    const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);

                    // Extract data from geometry and add it to the arrays
                    const vertices = geometry.getAttribute('position').array;
                    const vertexCount = vertices.length / 3;

                    for (let k = 0; k < vertexCount; k++) {
                        positions.push(vertices[k * 3], vertices[k * 3 + 1], vertices[k * 3 + 2]);
                        normals.push(0, 0, 1);
                    }
                }
            }

            // Set attributes to bufferGeometry
            bufferGeometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
            bufferGeometry.setAttribute('normal', new THREE.Float32BufferAttribute(normals, 3));

            // Optional: Scale or transform the bufferGeometry if needed
            bufferGeometry.scale(size, size, 0.1);
        }
    );

    return bufferGeometry;
}

function render() {

        requestAnimationFrame( render );

            result = csgEvaluator.evaluate(brush1, brush2, params.operation, result);
            result.castShadow = true;
            result.receiveShadow = true;      

    renderer.render(scene, camera);

}


Thank you!

A quick guess, you are running csgEvaluator.evaluate every frame, probably 60 times a second.

Yes. But I’ve tried limiting the framerate by doing this:


function render() {

    requestAnimationFrame( render );

    setTimeout( function() {
        result = csgEvaluator.evaluate(brush1, brush2, params.operation, result);
    }, 1000 / 60 );
   
    result.castShadow = true;
    result.receiveShadow = true;      

    renderer.render(scene, camera);

}


And I cant notice a difference, subtracting the mesh with a BoxGeometry works perfect, but as soon as I use another mesh the program start to lag.

now you are creating a timeout 60 times a second.

you are also running this 60 times a second

result.castShadow = true;
result.receiveShadow = true;

Lookup what requestAnimationFrame does.

Do you really need to continually re-execute csgEvaluator.evaluate all the time?
If so, then you will need to consider how many vertices are in your buffer geometries if you want it to be fast.