Two box & two control transform overlapping - how to separate to move only one box? => solution: DragControls

little problem/big problem :slight_smile:
I have two objects/box; each object has its control transform connected; in specific circumstances these objects may overlap; and then the problem arises how to move only one object because the cursor recognizes two control transforms at the same time and unfortunately two boxes are moved;

  • here I think you need some js code fragment to check how many control transforms have been recognized under the cursor and possibly to select only one to move?
  • how to recognize how many control transform are highlighted under the cursor?

<!DOCTYPE html>
<html lang="en">
	<head>
		<title></title>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
		<link type="text/css" rel="stylesheet" href="main.css">
	</head>
	<body>
		<div id="container"></div>
		<div id="info">
		</div>
		<!-- Import maps polyfill -->
		<!-- Remove this when import maps will be widely supported -->
		<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>
		<!-- // "three": "../../lib/js/three.module.min.js", -->
		<!-- // "three/addons/": "../../lib/js/jsm/" -->
		<script type="importmap">
			{
				"imports": {
					"three": "../build/three.module.js",
					"three/addons/": "./jsm/"
				}
			}
		</script>

<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { TransformControls } from 'three/addons/controls/TransformControls.js';

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
camera.position.set( 1, 2, 3 );

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

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

let geometry1 = new THREE.BoxGeometry( 1, 1, 1 );
let material1 = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
let cube1 = new THREE.Mesh( geometry1, material1 );
scene.add( cube1 );

let geometry2 = new THREE.BoxGeometry( 1, 1, 1 );
let material2 = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
let cube2 = new THREE.Mesh( geometry2, material2 );
scene.add( cube2 );

let control1 = new TransformControls( camera, renderer.domElement );
	control1.showX = true;	// only X !
	control1.showY = false;
	control1.showZ = false;
	control1.addEventListener( 'dragging-changed', ( event ) => {
		controls.enabled = ! event.value;
		console.log('control1');
	} );
	control1.attach( cube1 );
	scene.add( control1 );

let control2 = new TransformControls( camera, renderer.domElement );
	control2.showX = true;	// only X !
	control2.showY = false;
	control2.showZ = false;
	control2.addEventListener( 'dragging-changed', ( event ) => {
		controls.enabled = ! event.value;
		console.log('control2');
	} );
	control2.attach( cube2 );
	scene.add( control2 );

function animate() {
	requestAnimationFrame( animate );
	controls.update();
	renderer.render( scene, camera );
}
animate();

</script>
</body>
</html>

Couldn’t you rewrite your code and avoid the usage of two instances of TransformControls?

How about doing it similar to the three.js editor? Meaning you can only transform a single object by selecting it first via a pointerdown interaction. Instead of pointer down, you could also check objects in your scene when pointermove is triggered. In this case the controls are assigned to the object as soon as you hover over it.

1 Like

@Mugen87 thanks for the hints;
I found examples with DragControls e.g. DragControls - Three.js Tutorials
I turned off autoanimation of boxes, activated orbitControl
and fortunately, I miss the option of moving the boxes only along one axis declared in the script, e.g. X, how to save it in js?

let controls = new OrbitControls( camera, renderer.domElement );
...
let drag = new DragControls(......cubes, camera, renderer.domElement)

// drag.addEventListener( 'mousedown', function () { // is mousedown needed?
// console.log('mousedown')
// })
drag.addEventListener( 'dragstart', function () {
controls.enabled = false;
// drag only X..........???
});
drag.addEventListener( 'dragend', function () {
controls.enabled = true;
// end drag only X..........???
});

ok, after various trials and searches, I figured out what DragControls is all about and how to adapt it to my own needs (in moving objects that may overlap in specific situations);
To close the topic, I’m posting my example, which may be useful to a novice like me.

  • there are 6 large visible planes in the scene (e.g. for model cutting etc.);
  • each pair of planes can move along one fixed axis oX,Y,Z;
  • in the js script, 6 hidden small planes placed in the center of large planes are responsible for moving large planes;
  • why so ? because I wanted to use DragControls and DragControls did not recognize the entire large plane, but only its smaller fragments in the center;
  • thanks to this, if the cursor hovers over a large plane but not its center, the scene orbit will not be blocked (!); only hovering over and clicking a fragment of the center of a large plane will activate DragControls;
  • in practice, the hidden small plane is moved and the large plane is visible at the same time (which dynamically captures the position of the small plane)
  • the mechanism …addEventListener( ‘hoveron’… and …addEventListener( ‘hoveroff’… helps to visually distinguish which plane is to be moved

dragcontrols6

<!DOCTYPE html>
<html lang="en">
	<head>
		<title></title>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
		<link type="text/css" rel="stylesheet" href="main.css">
	</head>
	<body>
		<div id="container"></div>
		<div id="info">
		</div>
		<!-- Import maps polyfill -->
		<!-- Remove this when import maps will be widely supported -->
		<script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script>
		<script type="importmap">
			{
				"imports": {
                    "three": "../build/three.module.js",
                    "three/addons/": "./jsm/"
				}
			}
		</script>

<script type="module">

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

let container, scene, camera, renderer, controls, drag;
let x0,y0,z0;   // initial plane position for DragControls

scene = new THREE.Scene()
scene.background = new THREE.Color(0xeeeeee);
scene.add( new THREE.AxesHelper(5) )
scene.add( new THREE.AmbientLight(0xffffff, 2) )

camera = new THREE.PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
)
camera.position.set(2, 2, 3)

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

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

// planes to be moved along the axis
const mathoveroff = 0.2;    // standard opacity
const mathoveron = 0.6;     // opacity when mouse hover
// materials for 3 axes and 6 directions
const material = [
    new THREE.MeshPhongMaterial({ color: 0xff0000, transparent: true, side: THREE.DoubleSide, opacity:mathoveroff }), // +X
    new THREE.MeshPhongMaterial({ color: 0x00ff00, transparent: true, side: THREE.DoubleSide, opacity:mathoveroff }), // +Y
    new THREE.MeshPhongMaterial({ color: 0x0000ff, transparent: true, side: THREE.DoubleSide, opacity:mathoveroff }), // +Z
    new THREE.MeshPhongMaterial({ color: 0xff0000, transparent: true, side: THREE.DoubleSide, opacity:mathoveroff }), // -X
    new THREE.MeshPhongMaterial({ color: 0x00ff00, transparent: true, side: THREE.DoubleSide, opacity:mathoveroff }), // -Y
    new THREE.MeshPhongMaterial({ color: 0x0000ff, transparent: true, side: THREE.DoubleSide, opacity:mathoveroff })  // -Z
]
// obj2drag=[...] hidden small planes to DragControls, fixed dimensions
const geometry = new THREE.PlaneGeometry(0.5, 0.5);
const obj2drag = [
    new THREE.Mesh(geometry, material[0]),
    new THREE.Mesh(geometry, material[1]),
    new THREE.Mesh(geometry, material[2]),
    new THREE.Mesh(geometry, material[3]),
    new THREE.Mesh(geometry, material[4]),
    new THREE.Mesh(geometry, material[5])
]
// ******************************************************************************************
// obj2drag2=[...] visible planes moved according to analogous hidden planes
// visible planes are not related to the hidden ones with the object2.add(object1) method
// and are not recognized by DragControls;
// thanks to this, after hovering over a large plane, orbiting the scene is not blocked,
// only hovering over a small hidden plane (in the center of a large plane) allows you to start DragControls

const geometry2 = new THREE.PlaneGeometry(4, 2);
const obj2drag2 = [
    new THREE.Mesh(geometry2, material[0]),
    new THREE.Mesh(geometry2, material[1]),
    new THREE.Mesh(geometry2, material[2]),
    new THREE.Mesh(geometry2, material[3]),
    new THREE.Mesh(geometry2, material[4]),
    new THREE.Mesh(geometry2, material[5])
]

// add axis shift name tags to the planes, and set the planes perpendicular to the axis
obj2drag[0].name="dragx";   obj2drag[0].rotateY(Math.PI / 2);
obj2drag[1].name="dragy";   obj2drag[1].rotateX(Math.PI / 2);
obj2drag[2].name="dragz";   //obj2drag[2].rotateY(Math.PI / 2);
obj2drag[3].name="dragx2";  obj2drag[3].rotateY(- Math.PI / 2);
obj2drag[4].name="dragy2";  obj2drag[4].rotateX(- Math.PI / 2);
obj2drag[5].name="dragz2";  //obj2drag[5].rotateY(- Math.PI / 2);

obj2drag2[0].name="planex";   obj2drag2[0].rotateY(Math.PI / 2);
obj2drag2[1].name="planey";   obj2drag2[1].rotateX(Math.PI / 2);
obj2drag2[2].name="planez";   //obj2drag2[2].rotateY(Math.PI / 2);
obj2drag2[3].name="planex2";  obj2drag2[3].rotateY(- Math.PI / 2);
obj2drag2[4].name="planey2";  obj2drag2[4].rotateX(- Math.PI / 2);
obj2drag2[5].name="planez2";  //obj2drag2[5].rotateY(- Math.PI / 2);

obj2drag.forEach((c) => c.visible=false)    // hide small planes
obj2drag.forEach((c) => scene.add(c))       // insert hidden small planes into the scene
obj2drag2.forEach((c) => scene.add(c))      // insert visible large planes into the scene

// initialize the DragControls mechanism
drag = new DragControls(obj2drag, camera, renderer.domElement)

drag.addEventListener( 'hoveron', function (e) { // when the cursor hovers over a small plane
    // e = Object { type: "dragstart", target: null,
    //      object: Object { isObject3D: true, uuid: "3a4a0840-f...", name: "dragx...", … }
    //     }
    e=e.object  //  hover object only
    e.material.opacity=mathoveron;
});

drag.addEventListener( 'dragstart', function (e) { // when the cursor moves the hidden small plane
    e=e.object  //  moving object only
	controls.enabled = false;   // !!! lock the scene orbit
    // remember the starting position of the object
        x0=e.position.x
        y0=e.position.y
        z0=e.position.z
});

drag.addEventListener( 'drag', function (e) { // when the cursor clicks and starts to move the hidden small plane
    e=e.object  //  moving object only
    let nam=e.name, nam2=nam.replace('drag','plane'),   // change e.g.: 'dragx' to 'planex'
        q = obj2drag2.find (q => q.name == nam2);	// find an analogous large plane e.g.:'planex'

    // save the position of the small plane along the selected axis
    if (e.name == 'dragx' || e.name == 'dragx2' ){
        e.position.y=y0; e.position.z=z0;
    } else if (e.name == 'dragy' || e.name == 'dragy2' ){
                e.position.x=x0; e.position.z=z0;
                } else {
                e.position.x=x0; e.position.y=y0;
            }

    q.position.copy(e.position) // copy the position of the small plane to the large plane
});

drag.addEventListener( 'dragend', function (e) { // when the cursor finishes moving the hidden small plane
	controls.enabled = true;   // !!! unlock orbit
});

drag.addEventListener( 'hoveroff', function (e) { // when the cursor leaves above the small plane
    e=e.object  // hover object only
    e.material.opacity=mathoveroff; // set material opacity to standard
});

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)
    controls.update()
    render()
}

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

animate()

</script>
</body>
</html>