modelonclicklistenergltf

Threejs gltfLoader make model clickable


I am new to threejs and now trying to make this work since ....

I am loading a GLTF with mappings (jpg) into threejs.

I can't find a way to make the loaded model clickable.

Can you please show, how to make this model clickable (single or double click and touch) and call a js-function ?

this is my scene:

threejs-model-link

function sendMail() {
    //$('#myModal').modal('show');
    alert('click');
}

import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r127/build/three.module.js';
import {OrbitControls} from 'https://threejsfundamentals.org/threejs/resources/threejs/r127/examples/jsm/controls/OrbitControls.js';
import {GLTFLoader} from 'https://threejsfundamentals.org/threejs/resources/threejs/r127/examples/jsm/loaders/GLTFLoader.js';

let model;
let object = new THREE.Object3D();
let raycaster, mouse,  intersects, gltfobject;

function main() {
    
    const canvas = document.querySelector('#c');
    const renderer = new THREE.WebGLRenderer({canvas , antialias: true});

    const fov = 60;
    const aspect = 1;  // the canvas default
    const near = 0.6;
    const far = 1200;
    const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
    camera.position.set(0, 10, 20);

    const controls = new OrbitControls(camera, canvas);
    controls.target.set(0, 5, 0);
    controls.update();

    const scene = new THREE.Scene();
    scene.background = new THREE.Color('black');

    //raycaster = new THREE.Raycaster();
    //mouse = new THREE.Vector2();
    //let pickableMeshes = [];


    
    {
        const skyColor = 0xB1E1FF;  // light blue
        const groundColor = 0xFFFFFF;  // brownish orange
        const intensity = 0.9;
        const light = new THREE.HemisphereLight(skyColor, groundColor, intensity);
        scene.add(light);
    }

    {
        const color = 0xFFFFFF;
        const intensity = 0.3;
        const light = new THREE.DirectionalLight(color, intensity);
        light.position.set(5, 10, 2);
        scene.add(light);
        scene.add(light.target);
    }

    function frameArea(sizeToFitOnScreen, boxSize, boxCenter, camera) {
        
        const halfSizeToFitOnScreen = sizeToFitOnScreen * 0.5;
        const halfFovY = THREE.MathUtils.degToRad(camera.fov * .5);
        const distance = halfSizeToFitOnScreen / Math.tan(halfFovY);
        // compute a unit vector that points in the direction the camera is now
        // in the xz plane from the center of the box
        const direction = (new THREE.Vector3())
        .subVectors(camera.position, boxCenter)
        .multiply(new THREE.Vector3(1, 0, 1))
        .normalize();

        // move the camera to a position distance units way from the center
        // in whatever direction the camera was from the center already
        camera.position.copy(direction.multiplyScalar(distance).add(boxCenter));

        // pick some near and far values for the frustum that
        // will contain the box.
        camera.near = boxSize / 100;
        camera.far = boxSize * 100;

        camera.updateProjectionMatrix();

        // point the camera to look at the center of the box
        camera.lookAt(boxCenter.x, boxCenter.y, boxCenter.z);
    }
    
    window.addEventListener('resize', () => {
        renderer.setSize(window.innerWidth, window.innerHeight); // Update size
        camera.aspect = window.innerWidth / window.innerHeight; // Update aspect ratio
        camera.updateProjectionMatrix(); // Apply changes
    });

    {

        const gltfLoader = new GLTFLoader();

        gltfLoader.load('my_test_id_card_app.gltf', (gltf) => {
            
            const root = gltf.scene;
            scene.add(root);


            // compute the box that contains all the stuff
            // from root and below
            const box = new THREE.Box3().setFromObject(root);
            const boxSize = box.getSize(new THREE.Vector3()).length();
            const boxCenter = box.getCenter(new THREE.Vector3());

            // set the camera to frame the box
            frameArea(boxSize * 2, boxSize, boxCenter, camera);

            scene.rotation.z = 20;
            //scene.rotation.x -= 0.002;
            scene.rotation.y = 20;

            // update the Trackball controls to handle the new size
            controls.maxDistance = boxSize * 10;
            controls.target.copy(boxCenter);
            controls.update();
        });

    }

    
    

    function resizeRendererToDisplaySize(renderer) {
        const canvas = renderer.domElement;
        const width = canvas.clientWidth;
        const height = canvas.clientHeight;
        const needResize = canvas.width !== width || canvas.height !== height;
        if (needResize) {
            renderer.setSize(width, height, false);
        }
        return needResize;
    }

    function render() {
        if (resizeRendererToDisplaySize(renderer)) {
        const canvas = renderer.domElement;
        camera.aspect = canvas.clientWidth / canvas.clientHeight;
        camera.updateProjectionMatrix();
    }

    renderer.setPixelRatio(window.devicePixelRatio);

    // Update trackball controls
    controls.update();

    // Constantly rotate box
    scene.rotation.z -= 0.002;
    scene.rotation.x -= 0.004;
    scene.rotation.y -= 0.006;

    renderer.render(scene, camera);

    requestAnimationFrame(render);
    }

  requestAnimationFrame(render);
}


    

main();

Solution

  • You need to use a pointerevent like pointerup and then use the Raycaster like you already tried.

    This is the relevant code:

    renderer.domElement.addEventListener('pointerup', (event) => {
        mouse.x = (event.clientX / renderer.domElement.clientWidth - renderer.domElement.getBoundingClientRect().x) * 2 - 1;
        mouse.y = -(event.clientY / renderer.domElement.clientHeight + renderer.domElement.getBoundingClientRect().y) * 2 + 1;
        
        console.log(mouse.x, mouse.y);
    
        raycaster.setFromCamera(mouse, camera);
    
        const intersects = raycaster.intersectObjects(scene.children, true);
    
        if (intersects.length > 0) {
          console.log("Model clicked.")
        }
    });
    

    It is based on this ThreeJS example.

    First we need to map the mouse coordinates to the rectangle between (1,1) (1, -1) (-1, -1) and (-1, 1).

    Then we use the mapped mouse coordinates to setFromCamera the raycaster.

    After that use the raycaster to find all objects that intersect with the ray raycaster.intersectObjects(scene.children, true). Note that recursive is set to true in intersectObjects, because we also want to detect a click on nested objects.

    Lastly here is a full JSFiddle.