React threejs merge two tubes to create a tee-piece with CSG not working as expected

200 views Asked by At

I am trying to create a tee-piece which is a fitting in the plumbing domain. It consist of 2 tubes that are merged together and has 3 openings as shown in this picture.

I have written some code in threejs where I am trying to create a tube mesh1 and another tube mesh2 and then try to union them into mesh3 with the library @enable3d/three-graphics/jsm/csg - thanks to @Marquizzo. After using the function CSG.union and adding the mesh to the scene I can see that I get one tee-piece but it has also created a hole in geometry 1, which was not expected. You can see a picture of the correct holes(green) and the wrongly created hole (red) here:

this

it should instead look like this and be as one geometry.

enter image description here

Can anyone tell me how CSG works and why I am getting an extra hole on the backside of the first geometry?

import React, { Component } from 'react';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { CSG } from '@enable3d/three-graphics/jsm/csg';

export default class TubeViewer extends Component {
    componentDidMount() {
        //Add Scene
        this.scene = new THREE.Scene();

        //Add Renderer
        this.renderer = new THREE.WebGLRenderer({ antialias: true });
        this.renderer.setClearColor('#808080');
        this.renderer.shadowMap.enabled = true;
        this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
        this.renderer.setPixelRatio(window.devicePixelRatio);
        this.renderer.setSize(window.innerWidth, window.innerHeight);
        this.mount.appendChild(this.renderer.domElement);

        //Add Camera
        const fov = 60;
        const aspect = window.innerWidth / window.innerHeight;
        const near = 1.0;
        const far = 1000.0;
        this.camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
        this.camera.position.set(1, aspect, 1, 1000);


        //Tee-piece

        const curve1 = new THREE.LineCurve(new THREE.Vector3(2, 0, 0), new THREE.Vector3(2, 0, 0.1));
        const curve11 = new THREE.LineCurve(new THREE.Vector3(2.0, 0, 0.05), new THREE.Vector3(2.05, 0, 0.05));

        const geometry1 = new THREE.TubeGeometry(curve1, 20, 0.025, 8, false);
        const geometry2 = new THREE.TubeGeometry(curve2, 20, 0.025, 8, false);

        const material = new THREE.MeshBasicMaterial({ color: '#C0C0C0' });

        const mesh1 = new THREE.Mesh(geometry1, material);
        const mesh2 = new THREE.Mesh(geometry2, material);

        const mesh3 = CSG.union(mesh1, mesh2);

        this.scene.add(mesh3);


        //Add raycaster to for interactivity
        this.raycaster = new THREE.Raycaster();
        this.mouse = new THREE.Vector2();

        this.renderer.domElement.addEventListener('click', onClick.bind(this), false);

        function onClick(event) {
            event.preventDefault();

            this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
            this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

            this.raycaster.setFromCamera(this.mouse, this.camera);

            var intersects = this.raycaster.intersectObjects(this.scene.children, true);

            if (intersects.length > 0) {
                console.log('Intersection:', intersects[0]);
                //console.log(intersects[0].object.uuid);
                // console.log(`GUID: ${intersects[0]}`);
                let object = intersects[0].object;

                object.material.color.set(Math.random() * 0xffffff);
            }
        }

        //Settings
        //Add Camera Controls
        const controls = new OrbitControls(this.camera, this.renderer.domElement);
        controls.addEventListener('change', this.render); // use if there is no animation loop
        controls.minDistance = 2;
        controls.maxDistance = 10;
        controls.target.set(0, 0, -0.2);
        controls.update();

        ///Add AMBIENT LIGHT
        let light = new THREE.DirectionalLight(0xffffff, 1.0);
        light.position.set(20, 100, 10);
        light.target.position.set(0, 0, 0);
        light.castShadow = true;
        light.shadow.bias = -0.001;
        light.shadow.mapSize.width = 2048;
        light.shadow.mapSize.height = 2048;
        light.shadow.camera.near = 0.1;
        light.shadow.camera.far = 500.0;
        light.shadow.camera.near = 0.5;
        light.shadow.camera.far = 500.0;
        light.shadow.camera.left = 100;
        light.shadow.camera.right = -100;
        light.shadow.camera.top = 100;
        light.shadow.camera.bottom = -100;
        this.scene.add(light);
        light = new THREE.AmbientLight(0xffffff, 0.7);
        this.scene.add(light);

        //Start animation
        this.start();
    }

    //Unmount when animation has stopped
    componentWillUnmount() {
        this.stop();
        this.mount.removeChild(this.renderer.domElement);
    }

    //Function to start animation
    start = () => {
        //Rotate Models
        if (!this.frameId) {
            this.frameId = requestAnimationFrame(this.animate);
        }
    };

    //Function to stop animation
    stop = () => {
        cancelAnimationFrame(this.frameId);
    };

    //Animate models here
    animate = () => {
        //ReDraw scene with camera and scene object
        if (this.cubeMesh) this.cubeMesh.rotation.y += 0.01;
        this.renderScene();
        this.frameId = window.requestAnimationFrame(this.animate);
    };

    //Render the scene
    renderScene = () => {
        if (this.renderer) this.renderer.render(this.scene, this.camera);
    };

    render() {
        return (
            <div
                style={{ width: '800px', height: '800px' }}
                ref={(mount) => {
                    this.mount = mount;
                }}
            />
        );
    }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>

enter code here?

1

There are 1 answers

0
flatworldstudio On

For CSG you'll need solid bodies. These tubes are open.

I created an example using cylinders (tubes are involved to cap) so you can test it.

These cylinders are open ended, so they fail in the same way as your tubes. https://codepen.io/flatworldstudio/pen/bGBjmrP

 const geometry1 = new THREE.CylinderGeometry(0.1, 0.1, 0.5, 20, 1, true);

These are closed, and CSG works as expected. https://codepen.io/flatworldstudio/pen/VwmBRoL

const geometry1 = new THREE.CylinderGeometry(0.1, 0.1, 0.5, 20, 1, false);

(I'm using a different version of CSG, but they all seem to be built on the same code)