Hi everyone, I’ve been racking my brain for more than 10 days trying to solve this problem. The solution is probably obvious, but I just can’t get there.
I’m building a scene editor, and I’ve gotten to the point where I need to handle multiple selection of multiple meshes. So, looking around a bit, I saw that the most popular solution is to add all the selected meshes to a group, then manipulate them through it.
It wasn’t easy for me, but in the end I managed to get what I wanted.
Now my problem is in the centering of the group relative to the selected instances: every time I add a mesh to the selection, I detach all the meshes from the group and reattach them to the scene, I calculate the new center between the meshes marked for the selection, I position the group at the calculated center, I reattach the meshes to the group.
the problem is that when I add or remove meshes to the selection, both the meshes and the group move.
I add here an example that I created by simplifying only the salient points of the editor as much as possible. Running the example, using the buttons at the top, you can add and remove meshes to the group. By doing this, the problem will soon be visible (even adding the same mesh to the group multiple times, although the object is not actually added, will cause the center to be recalculated, still showing the problem).
My need is that by adding and removing meshes to the group, they remain in place, so that the TransformControls can act on them correctly, and that once released, they maintain their set position, rotation and scale.
The offending function, in the example code, is called centerGroup.
The center calculation works correctly (you can see this by commenting out the last forEach of the function).
I would be immensely grateful if you could modify the example code to solve the problem… I don’t know what to try anymore.
here is the code:
<!DOCTYPE html>
<html>
<head>
<style>
html, body { background-color: #333; width: 100%; height: 100vh; margin: 0; padding: 0; font-size: 18px; font-family: Verdana; color: silver; }
button, input, label { background-color: gray; color: #333; margin: 4px; font-size: 18px; user-select: none; }
#test { width: 100%; height: 85%; }
</style>
<!-- change with your path -->
<script async src="../libs/es-module-shims-1-6-3.js"></script>
<!-- change with your path -->
<script type="importmap">
{
"imports": {
"three": "../libs/three/build/three.module.js",
"three/addons/": "../libs/three/examples/jsm/"
}
}
</script>
</head>
<body>
<button id="addRed">Add Red</button>
<button id="addGreen">Add Green</button>
<button id="addBlue">Add Blue</button>
<button id="addYellow">Add Yellow</button>
to the group
<br>
<button id="removeRed">Remove Red</button>
<button id="removeGreen">Remove Green</button>
<button id="removeBlue">Remove Blue</button>
<button id="removeYellow">Remove Yellow</button>
from the group
<br>
<button id="translate">Translate</button>
<button id="rotate">Rotate</button>
<button id="scale">Scale</button>
<canvas id="test"></canvas>
<script type="module">
import * as THREE from 'three';
import { TransformControls } from 'three/addons/controls/TransformControls.js';
function animate()
{
requestAnimationFrame( animate );
renderer.render( scene, camera );
};
/**
* selection
*/
//#region
function getLinearizedMeshes()
{
return selection;
};
function addToSelection( what )
{
if ( selection.indexOf( what ) < 0 )
selection.push( what );
centerGroup();
};
function removeFromSelection( what )
{
const pos = selection.indexOf( what );
if ( pos >= 0 )
selection.splice( pos, 1 );
centerGroup();
};
//#endregion
function centerGroup()
{
/**
* I attach all the objects associated with the group to the scene
*/
group.children.reverse().forEach( ( item ) => {
scene.attach( item );
} );
/**
* I empty the group
*/
group.clear( );
/**
* I retrieve the list of selected objects
*/
let selections = getLinearizedMeshes( );
/**
* I calculate a box that contains all the selected objects
*/
const __transformOrigin = new THREE.Vector3();
const __transformBox = new THREE.Box3();
__transformBox.makeEmpty( );
selections.forEach( ( item ) => {
__transformBox.expandByObject( item );
} );
/**
* I get the center of the box (which will be the center of the group)
*/
__transformBox.getCenter( __transformOrigin );
const { x, y, z } = __transformOrigin;
/**
* I position the group at the new center
*/
group.position.set(x, y, z);
/**
* I remove all the objects from the scene and attach them to the group
* Note: the center calculation works correctly (it is evident if I comment
* the following lines)
*/
selections.forEach( ( item ) => {
group.attach( item );
scene.remove( item );
} );
};
/**
* Init
*/
//#region
const width = test.offsetWidth;
const height = test.offsetHeight;
let canvas = document.getElementById( "test" );
const renderer = new THREE.WebGLRenderer();
renderer.setSize( width, height );
document.body.replaceChild( renderer.domElement, canvas );
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 45, width / height, 0.1, 1000 );
camera.rotation.order = 'YXZ';
camera.position.x = 0.5;
camera.position.y = -0.5;
camera.position.z = 15;
const axesHelper = new THREE.AxesHelper( 200 );
scene.add( axesHelper );
const selection = [];
const control = new TransformControls( camera, renderer.domElement );
control.setMode( 'translate' );
scene.add( control );
const group = new THREE.Group( );
scene.add( group );
control.attach( group );
//#endregion
/**
* Add 4 cuboids to the scene
*/
//#region
const geometry = new THREE.BoxGeometry( 1, 1, 1 );
const materialRed = new THREE.MeshBasicMaterial( { color: "#990000" } );
const materialGreen = new THREE.MeshBasicMaterial( { color: "#009900" } );
const materialBlue = new THREE.MeshBasicMaterial( { color: "#000099" } );
const materialYellow = new THREE.MeshBasicMaterial( { color: "#999900" } );
const first = new THREE.Mesh( geometry, materialRed );
first.position.set( -5, 3, 0 );
scene.add( first );
const second = new THREE.Mesh( geometry, materialGreen );
second.position.set( 4, 4, 0 );
scene.add( second );
const third = new THREE.Mesh( geometry, materialBlue );
third.position.set( -3, -5, 0 );
scene.add( third );
const fourth = new THREE.Mesh( geometry, materialYellow );
fourth.position.set( 4, -3, 0 );
scene.add( fourth );
//#endregion
/**
* UI
*/
//#region
const modes = [ "translate", "rotate", "scale" ];
modes.forEach( mode => {
document.getElementById( mode )
.addEventListener( "click", ( e ) => control.setMode( e.target.getAttribute( "id" ) ) );
} );
const colors = { Red: first, Green: second, Blue: third, Yellow: fourth };
Object.keys( colors ).forEach(c => {
document.getElementById( "add" + c )
.addEventListener( "click", () => addToSelection( colors[ c ] ) );
document.getElementById( "remove" + c )
.addEventListener( "click", () => removeFromSelection( colors[ c ] ) );
} );
//#endregion
animate();
</script>
</body>
</html>