Adding an object perpendicular to a face in Three.js: A step-by-step guide

I am attempting to add a box (referred to as "marker") perpendicular to the face that I click on.

My current approach involves casting a ray on click, checking for intersections, and then transferring the intersecting point values of the normal to the "marker" as its rotation values. Despite researching similar solutions like this one (How do I make an object perpendicular to a face with Three.js?), I have been unable to achieve the desired outcome...

At present, the marker is correctly positioned but has a slightly incorrect orientation except when placed on top of a cube.

The orientation issue on cubes and the uncontrollable Y rotation

I am puzzled by two aspects: 1. Why is there a discrepancy between the directions of faceNormalsHelper and intersects[0].face.normal? 2. How exactly is the Y rotation managed?

Where did I go wrong? In order to assist you in helping me, I have created a Fiddle (https://jsfiddle.net/jpyncf62/1/) and included the code snippet being utilized:

function clickDown(e) {
    e.preventDefault();

    raycaster.setFromCamera(mouse, camera);
    var intersects = raycaster.intersectObjects(objects);

    if (intersects.length > 0) {
        var intersect = intersects[0];
        console.log(intersects[0]);
        console.log(console.log(intersects[0].object.name));

        var markerGeo = new THREE.BoxGeometry(1, 6, 1, 1, 1, 1);
        var markerMat = new THREE.MeshLambertMaterial({ color: 0xff0000 });
        var marker = new THREE.Mesh(markerGeo, markerMat);
        marker.name = "marker";
        marker.position.copy(intersect.point);

        marker.lookAt(intersect.face.normal);

        scene.add(marker);
    }
}

Thank you for any assistance provided!

Answer №1

Combine a cube and plane, assigning them positions and rotations. To ensure their normals intersect correctly, applying a matrix is necessary.

    var container, stats;
    var camera, scene, renderer;
    var objects = [];
    raycaster = new THREE.Raycaster();
    mouse = new THREE.Vector2();

    init();
    animate();

    function init() {
        container = document.getElementById("webgl-output")
        scene = new THREE.Scene();

        camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
        camera.position.set(20, 50,50);
        camera.lookAt( scene.position );

        scene.add ( new THREE.AmbientLight( 0xffffff ) );

        light = new THREE.DirectionalLight( 0xffffff );
        light.position.set( 0, 1, 0 );
        scene.add( light );

        var material = new THREE.MeshLambertMaterial( { color: 0x4fc2c9 } );
        var cube = new THREE.Mesh( new THREE.BoxGeometry( 10, 10, 10, 1, 1, 1 ), material );
        cube.name = "I'm a cube";
        objects.push( cube );
        scene.add( cube );


        cube.position.set( 0, 5, 0 );
        cube.rotation.set( 0, 0, Math.PI / 4);

        applyMatrixOfMesh(cube); // Apply matrix of cube

        // Add helpers after applied matrix
        var faceNormalsHelper = new THREE.FaceNormalsHelper( cube, 1 );
        cube.add( faceNormalsHelper );

        var vertexNormalsHelper = new THREE.VertexNormalsHelper( cube, 1 );
        cube.add( vertexNormalsHelper );

        var planeGeo = new THREE.PlaneGeometry( 60, 20, 1, 1 );
        var planeMat = new THREE.MeshLambertMaterial({ color: 0xffffff });
        var plane = new THREE.Mesh( planeGeo, planeMat );
        plane.position.set(0, 0, 0);
        plane.rotation.x = -0.5 * Math.PI;
        plane.name = "plane01";

        applyMatrixOfMesh(plane); // Apply matrix of plane

                    scene.add( plane );
        objects.push( plane );

        var axes = new THREE.AxisHelper( 20 );
        scene.add( axes );

        renderer = new THREE.WebGLRenderer( { antialias: true });
        renderer.setPixelRatio( window.devicePixelRatio );
        renderer.setClearColor( 0xeeeeee );
        renderer.setSize( window.innerWidth, window.innerHeight );
        container.appendChild( renderer.domElement );

        document.addEventListener('mousemove', mouseMoving, false);
        document.addEventListener('mousedown', clickDown, false);
    }

    function mouseMoving(e) {
        mouse.x = ( e.clientX / window.innerWidth ) * 2 - 1;
        mouse.y = - ( e.clientY / window.innerHeight ) * 2 + 1;            
        raycaster.setFromCamera( mouse, camera );

        var intersects = raycaster.intersectObjects( scene.children );

        for ( var i = 0; i < intersects.length; i++ ) {
            var posY = intersects[ 0 ].point.y.toFixed(2);
        }
    }

    function clickDown(e) {
        e.preventDefault();

        raycaster.setFromCamera( mouse, camera );
        var intersects = raycaster.intersectObjects( objects );            

        if( intersects.length > 0 ) {
            var intersect = intersects[ 0 ];
            console.log( intersects[ 0 ]);
            console.log( console.log( intersects[ 0 ].face.normal ) );

            var markerGeo = new THREE.BoxGeometry( 1, 1, 6, 1, 1, 1 ); // I changed BoxGeometry
            var markerMat = new THREE.MeshLambertMaterial( { color: 0xff0000 } );
            var marker = new THREE.Mesh( markerGeo, markerMat );
            marker.name = "marker";



            marker.lookAt( intersect.face.normal );  // First look At to normal
            marker.position.copy( intersect.point )  // After give position to marker

            scene.add( marker );
        }
    }

    function applyMatrixOfMesh(mesh) { // You should apply Matrix of cube and plane
        mesh.updateMatrix();
        mesh.geometry.applyMatrix(mesh.matrix);

        mesh.position.set(0, 0, 0);
        mesh.rotation.set(0, 0, 0);
        mesh.updateMatrix();
    }


    function animate() {
        requestAnimationFrame( animate );
        render();
    }

    function render() {        
        renderer.render( scene, camera );
    }

Utilize the 'applyMatrixOfMesh' function for each 'THREE.Mesh' used for intersection handling.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Update a row in an angular table when a table cell contains multiple items

I am currently using angular.js to display a table. In my table, each spot contains two important pieces of information which are the data and the color. While I have managed to find solutions for editing the data, I am seeking suggestions on how to change ...

Scheduled tasks on Google Cloud Platform's App Engine are failing to run over the weekend

I am facing an issue with running a cron job in my node/express application using the node-cron library. The application is hosted on Google Cloud App Engine. My aim is to automate sending emails every day at 9 AM, but the cron seems to only work from Mon ...

top margin is functioning properly in Internet Explorer, but not in Google Chrome

margin-top is behaving differently in IE compared to Google Chrome. Two menus are supposed to be displayed one above the other in my design. The issue lies in the line margin-top:30%; within .anothermenu ul. In Chrome, the second menu appears above the f ...

Change the class name using jQuery when scrolling

Currently utilizing bootstrap as my primary css framework. My goal is to dynamically toggle a class on the navbar once the user scrolls past the prominent header image located at the top of the website. UPDATE: Admitting I made an error and had a momenta ...

Having difficulty in executing the node app.js script

I am currently learning node.js and encountering an issue when trying to run the app.js file using the command node app.js. The terminal shows no output, neither errors nor any other information. Here is the sequence of steps I have followed: $ brew insta ...

Ways to revert a modified string back to its original format in JavaScript

I've been working on a sorting algorithm to intelligently sort filesizes regardless of the input format. The function is designed to sort an array of string inputs to produce the following desired outputs; input = ["5.1 GB","19934563 B& ...

Using the jQuery each loop with Waypoints

Having trouble with the $.each() function in Waypoints and could use some assistance. I've checked other Stack Overflow posts using the same method but still can't seem to get it working properly. You can find my code on JSFiddle: https://jsfidd ...

Access the URL or link in an email using Chrome, as opposed to the default browser, IE8

I have an application that sends out an email with a URL link. When the link is clicked, it opens a new tab in the browser. Currently, the application works fine on Chrome but is not compatible with IE8. However, the client's machine has IE8 as the ...

Switching between different CSS files based on the URL using jQuery or another method

Is it feasible to apply specific styles based on the ID or load various CSS files depending on the URL you are visiting? For example: <script> if(location.href == 'http://jpftest2.tumblr.com/about'){ document.write('<style type= ...

Error: Unable to locate module during module creation

I encountered an error while trying to import a module in my test application. ../fetchModule/index.js Module not found: Can't resolve './Myfetch' in '/Users/******/nodework/fetchModule' Here is the folder structure: https:/ ...

Detecting changes in Angular from the parent to the child component can be accomplished by utilizing a specific approach

The Sample Component contains data in the form of an array of objects with child components within a loop. Sample.component export class SampleComponent implements OnInit { data = [{ value: 3 }, { value: 1 }]; constructor() {} ngOnInit(): void {} ...

Adding a query parameter to a dynamic route in NextJS

In my NextJS application, I have implemented a language selector that is displayed on every page. The goal is to change the current URL by adding a query parameter lang=en when a new language is selected. The function responsible for updating the URL look ...

What is the best way to showcase information retrieved from an AJAX request onto a div using Jquery or Javascript?

When data is received in a HTML page through an AJAX call, it typically appears like this: <input type="hidden" id="s1" name="1" value="111" /> <input type="hidden" id="s2" name="2" value="222" /> The data then loads into <div="ajax"> A ...

What is the best way to input keys into the currently selected element?

During my experimentation, I discovered that several modals and dropdowns in my tests open with their input boxes automatically focused. I found a way to verify if an element is in focus, but I'm wondering if there's a quicker method to input ke ...

Showing Variables in JavaScript

<!DOCTYPE HTML> <html> <body> <button onclick="question++">Increment</button> <script> function test() { var question = 0; } </script> </body> </html> Qu ...

Guide to generating a reference and manually activating onclick for a textfield using React hooks

Is there a way to manually trigger a click on <TextField> in material-UI during a specific event? In React, I typically create a ref and then use it to trigger the onClick event. How can this be achieved using React Hooks? Just in case you're c ...

Is it possible to utilize CSS and the @media print feature to ensure that a single page is displayed in a 2up landscape format?

I'm working on a website: where the client needs to print the page for flyers. There are a couple of issues: Two extra blank pages are showing up before and after the actual content. In addition, the client wants the schedule printed in landscape ...

Upon pressing the browser's back button, the page fails to automatically load with the updated URL

Providing a little context: Let's say I initially access the page using URL1. After that, I use window.history.pushState() to change the URL to URL2. Now, when I click the "back" button, I observe that the address bar displays URL1 again, but the pa ...

Error: Unable to assign value to property 'className' of undefined object

On my http://localhost:3000/renewal page, the code looks like this: import Link from 'next/link' export default function Renewal() { return ( <header> <Link href="/"> <a> Go to home page ...

`Custom transitions between sections using fullpage.js`

Has anyone used the fullpage.js extension to achieve an animation similar to this one, where sections are destroyed on scroll? ...