I have a suggestion for the modification Tranform Controls for transformation on multiple objects simultaneously. Should we expect a this in the near future?
/ping @arodic
Hi, I have a feature which needs a similar implementation. Has the above request been addressed? I don't see the same reflect in TransformControls but has that been handled in some 3rd-party library which extends TransformControls? If so, can you please refer me to the same?
@arodic I've seen multiple question in the past where users try to transform a group of objects. What is your recommended way of doing this? Let's say you select a bunch of objects like in this example and then want to transform it:
@arodic I've seen multiple question in the past where users try to transform a group of objects. What is your recommended way of doing this? Let's say you select a bunch of objects like in this example and then want to transform it:
Position the transform control at the center of group bounding box.
Translate each children of the same amount.
Rotate each children around the center of bounding box.
Scale each child the same amount.
i.e. exactely as per transforming meshes inside an object...?
Hey @Robyk62, this is a feature that I've developed to a certain degree in the past but never got to integrate it into three.js
The approach that worked for me was to create some sort of SelectionTransformHelper. It was an empty Object3D that I would add to the helper scene and it would act as a middleman between TransformControls and a list of objects (multi-selection). TransformControls would attach to the SelectionTransformHelper, and the helper would have a custom updateMatrixWorld logic that would extract the position/rotation/scale before and after matrix update and then I would apply those transformations to selection.
This is how I would extract transformations from SelectionTransformHelper:
updateMatrixWorld(force) {
// Extract tranformations before and after matrix update.
this.matrixWorld.decompose(posOld, quatOld, scaleOld);
super.updateMatrixWorld(force);
this.matrixWorld.decompose(pos, quat, scale);
// Now you can calculate transformation deltas and apply them to a list of objects
}
However, the complete solution is far from simple. There are many other problems. For example:
Hi! I've been working on this the last days and this solves some of the abovementioned issues (local and world matrix transformations for adding selected objects to new group and back). Hope this helps someone :). What is still missing is where to place the control, and to reposition it every time a new object comes in and the "center of gravity" of the group changes. new THREE.Box3().setFromObject(tempgroup)
seems to be the solution for this but I'm thankful for Suggestions for this :)
var control = new THREE.TransformControls(camera, renderer.domElement);
var tempMatrix = new THREE.Matrix4();
//tempgroup: temporary group for grouping
var tempgroup = new THREE.Group();
scene.add(tempgroup);
tempgroup.userData.selected = [];
tempgroup.userData.prevParent = [];
//For grouping e.g. click on objects
object.addEventListener('click', onGrouping);
//for deselection call onGroupingEnd()
function onGrouping(evt) {
tempgroup.matrixWorldNeedsUpdate = true;
if (evt.detail.intersection.object !== undefined) {
var intersectedObject = evt.detail.intersection.object;
//Return if object was already selected before
if (tempgroup.userData.selected.includes(intersectedObject)) {
return;
}
//Recalculate local transform (pos, rot, scale) of intersectedObject for switching parent (tempgroup = new parent)
intersectedObject.matrixWorldNeedsUpdate = true;
tempMatrix.getInverse(tempgroup.matrixWorld);
var intersectedObject_matrix_new = intersectedObject.matrixWorld.premultiply(tempMatrix);
intersectedObject_matrix_new.decompose(intersectedObject.position, intersectedObject.quaternion, intersectedObject.scale);
tempgroup.userData.selected.push(intersectedObject);
tempgroup.userData.prevParent.push(intersectedObject.parent);
tempgroup.add(intersectedObject);
// Coloring intersectedObject blue when selecting
intersectedObject.material.emissive.b = 1;
if (tempgroup.userData.selected.length > 1) {
//Attach control to tempgroup
control.attach(tempgroup);
//Here I adjust the control size based on boundingSpheres and scales with control.setSize( ... )
} else {
//Attach control to intersectedObject
control.attach(intersectedObject);
//Here I adjust the control size based on boundingSpheres and scales with control.setSize( ... )
}
}
}
//Deselecting
function onGroupingEnd() {
if (tempgroup.userData.selected !== []) {
var intersectedObject;
for (var i = 0; i < tempgroup.userData.selected.length; i++) {
intersectedObject = tempgroup.userData.selected[i];
//Recalculate local transform (pos, rot, scale) of intersectedObject for switching back to old parent (prevParent = previous parent)
intersectedObject.matrixWorldNeedsUpdate = true;
tempgroup.userData.prevParent[i].matrixWorldNeedsUpdate = true;
tempMatrix.getInverse(tempgroup.userData.prevParent[i].matrixWorld);
var intersectedObject_matrix_old = intersectedObject.matrixWorld.premultiply(tempMatrix);
intersectedObject_matrix_old.decompose(intersectedObject.position, intersectedObject.quaternion, intersectedObject.scale);
tempgroup.userData.prevParent[i].add(intersectedObject);
// Coloring intersectedObject normal when deselecting
intersectedObject.material.emissive.b = 0;
}
tempgroup.userData.selected = [];
tempgroup.userData.prevParent = [];
}
control.detach();
}