ThreeJS CSG Problem intersecting extruded shapes

676 Views Asked by At

I have many views that I'm trying to extrude and then intersect in order to create a final polygon. The problem is that the result is not the expected, it has some floating extra parts. I need to correct this somehow, even if the solution is a method to detect these floating extra parts and erase them.

I'm using this library https://www.npmjs.com/package/three-csg-ts/v/3.1.10 to make the binary operation of intersection.

I dont know if it is a bug or if I'm doing something wrong. I've tried so many different configurations for the extrude settings but I still have the same problem.

I don't have much experience with js or ThreeJS, so I'm sorry if my code is not that readable, I've tried my best.

import './style.css'
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import { CSG } from 'three-csg-ts'

// Canvas
const canvas = document.querySelector('canvas.webgl')

/**
 * Sizes
 */
 const sizes = {
    width: 1677,
    height: 1287
}

// Scene
const scene = new THREE.Scene()

// View Points
const view_1 = [1019, 516, 1005, 502, 968, 481, 944, 482, 911, 492, 902, 505, 892, 510, 879, 522, 880, 595, 889, 612, 899, 619, 941, 621, 998, 620, 1011, 615, 1018, 599, 1017, 594, 1022, 575, 1023, 541]
const view_2 = [874.9, 221, 878.4, 274.3, 888.2, 296.3, 893.9, 306.1, 902.5, 320.9, 916.5, 327.1, 937.6, 337.1, 960, 329.6, 973.8, 323.1, 983.5, 314.6, 994.3, 307.3, 1008.4, 297.5, 1018, 271, 1019, 253, 1019.1, 239.5, 1006.5, 230, 996.6, 225.2, 987.8, 218.9, 958.8, 204.1, 939.5, 198.9, 892.7, 203.7]
const view_3 = [1002, 867, 985.9, 885.3, 984.7, 918.4, 986.7, 931, 994.9, 941.1, 1001.2, 957.6, 1015, 970.1, 1028.9, 980.9, 1046, 982.2, 1061.3, 980.9, 1077.6, 968.8, 1100.4, 945.5, 1103.4, 900.7, 1093.1, 879.7, 1077.1, 864, 1064.4, 855.6, 1053, 856.9, 1046, 853.7, 1025.7, 856.9, 1012, 859.4]
const view_4 = [619, 648, 592, 681, 597, 702, 607, 719.3, 624.5, 725.9, 646.2, 731.7, 658.2, 735.7, 669.3, 739.3, 680, 742.3, 690.3, 737.5, 697.1, 726.6, 690.1, 711.5, 686, 706, 683, 694, 683, 682, 674, 663, 668, 660, 659, 649, 650, 647, 644, 642, 629, 643]
const view_5 = [1282, 499, 1261, 504, 1256, 509, 1255.7, 508.2, 1251.4, 509.9, 1240, 515.5, 1227.7, 527.7, 1212.2, 587.7, 1210.9, 609, 1213.9, 615.8, 1216, 617.8, 1219.7, 623.3, 1226, 628.6, 1259.3, 625.1, 1267, 615, 1280, 561]
const views = [view_1,view_2,view_3, view_4, view_5]

//Split array in pairs
function chunk(arr, size) {
    return Array.from({ length: Math.ceil(arr.length / size) }, (v, i) =>
      arr.slice(i * size, i * size + size))
}

//Shape constructor and extruder
const extrudeSettings = {
    steps: 8,
    depth: 600,
    bevelEnabled: false,
    bevelThickness: 1,
    bevelSize: 0,
    bevelOffset: 0,
    bevelSegments: 1
};
var degree = 0
var pos = 0
var meshes = []

for(let view in views){
    var point_list = []
    var pair_array = chunk(views[view],2) //separate in pairs
    for (let pair in pair_array){
        point_list.push(new THREE.Vector2(-pair_array[pair][0]+sizes.width/2,-pair_array[pair][1]+sizes.height/2)) //creating vectors
    }
    console.log(point_list)
    var shape = new THREE.Shape(point_list); //constructing the Shape
    extrudeSettings.depth = 600
    var shapeGeom = new THREE.ExtrudeGeometry( shape, extrudeSettings ); //Extruding the shape

    //position transformations
    shapeGeom.rotateX(Math.PI * 0.5); 
    shapeGeom.rotateZ(THREE.MathUtils.degToRad(degree));
    degree += 45
    const shapeMat = new THREE.MeshPhongMaterial({color: "aqua"});
    shapeMat.side = THREE.DoubleSide
    var shapeMesh = new THREE.Mesh(shapeGeom, shapeMat); 
    
    //more position transformations
    shapeMesh.geometry.computeBoundingBox();
    var boundingBox = new THREE.Box3();
    boundingBox.copy( shapeMesh.geometry.boundingBox );
    shapeMesh.updateMatrixWorld( true ); // ensure world matrix is up to date
    boundingBox.applyMatrix4( shapeMesh.matrixWorld );
    shapeMesh.position.x = shapeMesh.position.x - (boundingBox.min.x + boundingBox.max.x)/2
    shapeMesh.position.y = shapeMesh.position.y - (boundingBox.min.y + boundingBox.max.y)/2
    shapeMesh.position.z = shapeMesh.position.z - (boundingBox.min.z + boundingBox.max.z)/2

    //adding to the scene
    shapeMesh.updateMatrix()
    meshes.push(shapeMesh)
    scene.add(shapeMesh); //comment if doesn't want to show the extruded shapes, but only the final result
}

//Intersection
var intersection = meshes[0]
for (let mesh in meshes){
    meshes[mesh].updateMatrix()
    intersection = CSG.intersect(intersection,meshes[mesh])
}
intersection.material = new THREE.MeshNormalMaterial()
scene.add(intersection)

// Lights
scene.add( new THREE.HemisphereLight(0xffffbb,0x080820,2) );

/**
 * Camera
 */
// Base camera
const camera = new THREE.OrthographicCamera( sizes.width / - 2, sizes.width / 2, sizes.height / 2, sizes.height / - 2, 0.1, 3000 );
camera.position.set( 0, 800, 0 );
camera.up = new THREE.Vector3( 0, 0, 1 );
scene.add(camera)

var camera_pivot = new THREE.Object3D()
scene.add( camera_pivot );
camera.lookAt( camera_pivot.position );

// Controls
const controls = new OrbitControls(camera, canvas)
controls.enableDamping = true

/**
 * Renderer
 */
const renderer = new THREE.WebGLRenderer({
    canvas: canvas, 
    preserveDrawingBuffer: true
})
renderer.setSize(sizes.width, sizes.height)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))

/**
 * Animate
 */

const clock = new THREE.Clock()
const tick = () =>
{
    const elapsedTime = clock.getElapsedTime()  
    // Update Orbital Controls
    controls.update()
    // Render
    renderer.render(scene, camera)
    // Call tick again on the next frame
    window.requestAnimationFrame(tick)
}

tick()


Here are some screenshots to ilustrate the problem:

Extruded shapes

Intersection of the extruded shapes

I really think that the problem might be in the extrusion method I'm using, but I don't understand how it should be done to work properly. Or perhaps it's the way i'm trying to do the intersection, one by one intersecting with the previous result. I've tried to do it two objects by time and then intersect two intersection results, but with no progress.

1

There are 1 best solutions below

2
On

Well, after playing around with your code for a while, I think my result is correct.

As you guessed, the problem lies here in the intersection part:

//Intersection
var intersection = meshes[0]
for (let mesh in meshes){
    meshes[mesh].updateMatrix()
    intersection = CSG.intersect(intersection,meshes[mesh])
}
intersection.material = new THREE.MeshNormalMaterial()
scene.add(intersection)

There is something wrong here. If I have understood correctly, by the associative property of logical intersection of sets, it shouldn't matter if you do the intesection mesh by mesh (someone correct me if I'm wrong), so the problem isn't that.

To be honest, I'm not sure what the issue is, maybe it's that you set your first intersectee as mesh[0] and then loop the whole mesh array, intersecting at least one shape with itself. When I added if(meshes[mesh].geometry.uuid === meshes[0].geometry.uuid) continue; into the loop, that seemed to work pretty nicely already, but I wouldn't propose that as a solution.

The solution I'm proposing is to replace that whole intersection part of the code with:

//Intersection
meshes.forEach((mesh) => mesh.updateMatrix());
var intersection = CSG.intersect(...meshes);
intersection.material = new THREE.MeshNormalMaterial()
scene.add(intersection)

First, I replaced the loop with a more functional forEach-clause to update the matrices.

Second, I used spread syntax to call CSG.intersect with all of the meshes in the mesh array at once.

This results in a shape like this:

intersected shape

I am not 100% sure if that is the correct result of intersecting so many polygons, but it looks relatively clean and doesn't have any extra floating parts like in your example.

P.S Thanks for the fun intersection code! I have only dabbled with Three.js and read about CSG, so it was really fun to play around with both. Here is my codesandbox.