tjallo
tjallo

Reputation: 791

Selecting faces with Raycaster in ThreeJS with STL Loader

I am currently working on a .stl viewer using Three.js. The goal is select and calculate certain areas. For this area calculation I need to be able to select (e.g. change the color) faces.

I have found something similar, but from what I have seen in my own research this only seems to be working with ready made meshes (like the cube in the example).

I am looking to implement this example in my own code.

It is evident that something like this has been done before, but I just can't seem to implement any kind of working way in my own code:

My current code has a fully functional .stl loader and viewer. The raycaster is there, but doesn't seem to be working properly so I have commented it out for the moment. Mugen87, thank you for the fix!

You can download my code and an example .stl file from github. Which only requires a Live Server environment which can easily be ran using VSCode (see readme).

My current code:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>3d viewer tjalle</title>
    <link rel="stylesheet" type="text/css" href="../style/render.css">
</head>

<body>
    <script src="https://rawcdn.githack.com/mrdoob/three.js/r117/build/three.min.js"></script>
    <script src="https://rawcdn.githack.com/mrdoob/three.js/r117/examples/js/loaders/STLLoader.js"></script>
    <script src="https://rawcdn.githack.com/mrdoob/three.js/r117/examples/js/controls/OrbitControls.js"></script>

    <script>
        function init() {
            var raycaster = new THREE.Raycaster();
            var mouse = new THREE.Vector2();
            document.addEventListener( 'mousemove', onMouseMove, false );


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

            // Setup some basic stuff
            scene = new THREE.Scene();
            scene.background = new THREE.Color(0xdddddd);


            // Setup Camera 
            camera = new THREE.PerspectiveCamera(40, window.innerWidth / window.innerHeight, 1, 5000);

            // Setup renerer and add to page
            renderer = new THREE.WebGLRenderer({
                antialias: true
            });
            renderer.setSize(window.innerWidth, window.innerHeight);

            document.body.appendChild(renderer.domElement);


            window.addEventListener('resize', onWindowResize, false);

            function onWindowResize() {
                camera.aspect = window.innerWidth / window.innerHeight;
                camera.updateProjectionMatrix();

                renderer.setSize(window.innerWidth, window.innerHeight);

            }

            // Setup Camera Position
            camera.rotation.y = 45 / 180 * Math.PI;
            camera.position.x = 800;
            camera.position.y = 100;
            camera.position.z = 1000;

            // Add Camera Control through orbit.js
            let controls = new THREE.OrbitControls(camera, renderer.domElement);

            // Add some basic ambient lighting (Does light all parts equally and does not cast shadows)
            hlight = new THREE.AmbientLight(0xffffff, 5.3);
            scene.add(hlight);


            //Add some point lights to simulate real lights
            light = new THREE.PointLight(0xffffff, 1, 10000);
            light.position.set(0, 300, 500);
            scene.add(light);

            controls.update();
            // Animation Script
            function animate() {
                raycaster.setFromCamera(mouse, camera);
                scene.children[2].material.color.set(0x1313)
                // calculate objects intersecting the picking ray
                var intersects = raycaster.intersectObjects(scene.children);
                for (var i = 0; i < intersects.length; i++) {
                    intersects[i].object.material.color.set(0xff0000);
                }
                

                renderer.render(scene, camera);
                requestAnimationFrame(animate);
            }

            // Setup GLTF Loader and load in car
            let loader = new THREE.STLLoader();
            loader.load('../converter/output/output.stl', function (geometry) {

                // console.log(gltf);
                var material = new THREE.MeshLambertMaterial({
                    color: 0x1313,
                    wireframe: false
                });
                var mesh = new THREE.Mesh(geometry, material);
                mesh.castShadow = true;
                mesh.receiveShadow = true;
                mesh.position.set(0, 0, 0);

                scene.add(mesh);
                renderer.render(scene, camera)
                animate();
                console.log("SCene: ", )
            });


        }

        // Call method for starting init
        init();
    </script>

</body>

</html>

Upvotes: 2

Views: 762

Answers (2)

tjallo
tjallo

Reputation: 791

Only selecting faces with use of a raycaster (which was my question), can be done by reading out the intersects data from the raycaster. Which gives you the ability to manipulate the face, thus 'select' it.

e.g.

for (var i = 0; i < intersects.length; i++) {
    console.log(intersects[i].face)
}

face has many built-in methods and gives you the ability to manipulate the face and get info from it. To give you an idea, this is directly copy pasted from my console.log(intersects[i].face) output, and is the related to only one face (note: these are only the top/parental level methods, many of them have more children.).

Ac {a: 5307, b: 5308, c: 5309, normal: p, vertexNormals: Array(0), …}
    a: 5307
    b: 5308
    c: 5309
color: D
    __proto__:
    add: ƒ (a)
    addColors: ƒ (a,b)
    addScalar: ƒ (a)
    b: 1
    clone: ƒ ()
    convertGammaToLinear: ƒ (a)
    convertLinearToGamma: ƒ (a)
    convertLinearToSRGB: ƒ ()
    convertSRGBToLinear: ƒ ()
    copy: ƒ (a)
    copyGammaToLinear: ƒ (a,b)
    copyLinearToGamma: ƒ (a,b)
    copyLinearToSRGB: ƒ (a)
    copySRGBToLinear: ƒ (a)
    equals: ƒ (a)
    fromArray: ƒ (a,b)
    g: 1
    getHSL: ƒ (a)
    getHex: ƒ ()
    getHexString: ƒ ()
    getStyle: ƒ ()
    isColor: true
    lerp: ƒ (a,b)
    lerpHSL: ƒ (a,b)
    multiply: ƒ (a)
    multiplyScalar: ƒ (a)
    offsetHSL: ƒ (a,b,c)
    r: 1
    set: ƒ (a)
    setColorName: ƒ (a)
    setHSL: ƒ (a,b,c)
    setHex: ƒ (a)
    setRGB: ƒ (a,b,c)
    setScalar: ƒ (a)
    setStyle: ƒ (a)
    sub: ƒ (a)
    toArray: ƒ (a,b)
    toJSON: ƒ ()
    constructor: ƒ D(a,b,c)
    __proto__: Object
materialIndex: 0
normal: p
    x: 0.9839296339696827
    y: 0
    z: 0.17855664478334757
    __proto__:
    add: ƒ (a,b)
    addScalar: ƒ (a)
    addScaledVector: ƒ (a,b)
    addVectors: ƒ (a,b)
    angleTo: ƒ (a)
    applyAxisAngle: ƒ (a,b)
    applyEuler: ƒ (a)
    applyMatrix3: ƒ (a)
    applyMatrix4: ƒ (a)
    applyNormalMatrix: ƒ (a)
    applyProjection: ƒ (a)
    applyQuaternion: ƒ (a)
    ceil: ƒ ()
    clamp: ƒ (a,b)
    clampLength: ƒ (a,b)
    clampScalar: ƒ (a,b)
    clone: ƒ ()
    copy: ƒ (a)
    cross: ƒ (a,b)
    crossVectors: ƒ (a,b)
    distanceTo: ƒ (a)
    distanceToManhattan: ƒ (a)
    distanceToSquared: ƒ (a)
    divide: ƒ (a)
    divideScalar: ƒ (a)
    dot: ƒ (a)
    equals: ƒ (a)
    floor: ƒ ()
    fromArray: ƒ (a,b)
    fromAttribute: ƒ (a,b,c)
    fromBufferAttribute: ƒ (a,b,c)
    getColumnFromMatrix: ƒ (a,b)
    getComponent: ƒ (a)
    getPositionFromMatrix: ƒ (a)
    getScaleFromMatrix: ƒ (a)
    isVector3: true
    length: ƒ ()
    lengthManhattan: ƒ ()
    lengthSq: ƒ ()
    lerp: ƒ (a,b)
    lerpVectors: ƒ (a,b,c)
    manhattanDistanceTo: ƒ (a)
    manhattanLength: ƒ ()
    max: ƒ (a)
    min: ƒ (a)
    multiply: ƒ (a,b)
    multiplyScalar: ƒ (a)
    multiplyVectors: ƒ (a,b)
    negate: ƒ ()
    normalize: ƒ ()
    project: ƒ (a)
    projectOnPlane: ƒ (a)
    projectOnVector: ƒ (a)
    random: ƒ ()
    reflect: ƒ (a)
    round: ƒ ()
    roundToZero: ƒ ()
    set: ƒ (a,b,c)
    setComponent: ƒ (a,b)
    setEulerFromQuaternion: ƒ ()
    setEulerFromRotationMatrix: ƒ ()
    setFromCylindrical: ƒ (a)
    setFromCylindricalCoords: ƒ (a,b,c)
    setFromMatrix3Column: ƒ (a, b)
    setFromMatrixColumn: ƒ (a,b)
    setFromMatrixPosition: ƒ (a)
    setFromMatrixScale: ƒ (a)
    setFromSpherical: ƒ (a)
    setFromSphericalCoords: ƒ (a,b,c)
    setLength: ƒ (a)
    setScalar: ƒ (a)
    setX: ƒ (a)
    setY: ƒ (a)
    setZ: ƒ (a)
    sub: ƒ (a,b)
    subScalar: ƒ (a)
    subVectors: ƒ (a,b)
    toArray: ƒ (a,b)
    transformDirection: ƒ (a)
    unproject: ƒ (a)
    constructor: ƒ p(a,b, c)
    __proto__: Object
vertexColors: Array(0)
    length: 0
    __proto__: Array(0)
vertexNormals: Array(0)
    length: 0
    __proto__: Array(0)
__proto__:
    clone: ƒ ()
    copy: ƒ (a)
    constructor: ƒ Ac(a,b,c,d,e,f)
    __proto__: Object

Upvotes: 0

Mugen87
Mugen87

Reputation: 31076

The raycaster is there, but doesn't seem to be working properly so I have commented it out for the moment.

I have debugged your code locally. Raycasting does not work since onMouseMove() is not yet called. You have to register it as an event listener first. So try to add the following line to your example:

document.addEventListener( 'mousemove', onMouseMove, false );

If you then comment in your raycasting code inside the animation loop, the model should become red when hovering with the mouse.

Upvotes: 1

Related Questions