javascriptthree.jsgltf

How to place object on top of other object in ThreeJS


I'm new to ThreeJS so apologies if this is a basic question. I have multiple GLTF files which I've loaded into my scene and I want to place one of the objects on top of the other object (but it can be moved later on)

For example: image on top of other image

Here is my code:

import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';

const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );

const backgroundColor = new THREE.Color('rgb(67, 177, 232)');

const scene = new THREE.Scene();
scene.background = backgroundColor;

const light = new THREE.AmbientLight(0x222222, 200)
light.position.set(10, 130, 15)
scene.add(light)

const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 10000 );

const controls = new OrbitControls( camera, renderer.domElement );

const loader = new GLTFLoader();

// Load in cup
loader.load('assets/models/Cup.glb', (gltf) => {
    scene.add(gltf.scene);
}
)

// Load in laptop
loader.load('assets/models/Laptop.glb', (gltf) => {
    scene.add(gltf.scene);
}
)

//controls.update() must be called after any manual changes to the camera's transform
camera.position.set(-0.03, 0.859, 1.59);
controls.update();

window.addEventListener('resize', onWindowResize, false)
function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight
    camera.updateProjectionMatrix()
    renderer.setSize(window.innerWidth, window.innerHeight)
    // render()
}

function animate() {

    requestAnimationFrame(animate );

    // required if controls.enableDamping or controls.autoRotate are set to true
    controls.update();

    renderer.render(scene, camera);
}

renderer.setAnimationLoop(animate)
animate()

I want to place the cup on top of the desk. Is there an official method to do this instead of manually having to type out the coordinates?


Solution

  • Assuming the laptop is coplanar with xz, we can do math on the bounding boxes like this...

    function positionCupOnLaptop(cup, laptop) {
      const laptopBox = new THREE.Box3().setFromObject(laptop);
      const cupBox = new THREE.Box3().setFromObject(cup);
    
      const { x: laptopX, y: laptopY, z: laptopZ } = laptopBox.max;
      const cupHeight = cupBox.max.y - cupBox.min.y;
    
      // here's the main idea: match the cup's position to the laptop's, except translate
      // in the y direction to the laptop surface (and half the cup height
      cup.position.set(laptopX, laptopTopY + cupHeight / 2, laptopZ);
    }
    
    

    This can be called once both models are loaded. The simple way to do that is to check for the other model in each load callback. (Or, less simple, but ultimately prettier: wrap loader.load in a promise).

    let cup, laptop;
    
    loader.load('assets/models/Cup.glb', (gltf) => {
        cup = gltf.scene;
        scene.add(cup);
        if (laptop) positionCupOnLaptop();
    });
    
    loader.load('assets/models/Laptop.glb', (gltf) => {
        laptop = gltf.scene;
        scene.add(laptop);
        if (cup) positionCupOnLaptop();
    });