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:
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();
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.