This demo shoes how you can attach an object to another.
Click to select a box to enable nodeMove command to move the box. The attach will happen when the attach point (yellow sphere) in two boxes are close enough.
Clara.io demo resources:
Visit Base Scenevar sceneId = '487e794c-5b75-467f-a278-baf1c5d85b0d';
var groupIdAttachIdsMap = {};
var attachPointLocation = {};
var attach = {};
var attachHintId;
var baseGroupId;
var api = claraplayer('player');
var THREE = api.deps.THREE;
var opts = {
displayGizmo: false,
mode: 'plane',
plane: {normal: {x: 0, y: 1, z: 0}, constant: 0},
}
api.sceneIO.fetchAndUse(sceneId).then(function() {
attachHintId = api.scene.find({type: 'Null', name: 'HintGroup'});
baseGroupId = api.scene.find({type:'Null', name: 'BaseGroup'});
var attachGroupIds = api.scene.filter({type: 'Null', name: 'BoxGroup'});
attachGroupIds.forEach(function(id) {
groupIdAttachIdsMap[id] = filterAttachPointId(id);
updateAttachPointPosition(id)
});
toggleAllAttachPoints(false);
api.player.showTool('nodeMove');
api.player.removeTool('select');
api.selection.setHighlighting(true);
api.commands.addCommand({
enabled: true,
active: true,
tool: {
mousedown: function(ev) {
if (!api.selection.lastSelectedNode()) return;
toggleAllAttachPoints(true);
},
mouseup: function(ev) {
var groupId = api.selection.lastSelectedNode();
if (!groupId) return;
if (attach.canAttach) attachObject(attach.moveGroupId, attach.moveAttachId);
//always hide hint object and show select object when mouse up
api.selection.setHighlighting(true);
api.scene.setAll({from: {id: attachHintId}, type: 'PolyMesh', plug: 'Properties', property: 'visible'}, false);
api.scene.setAll({from: {id: groupId}, type: 'PolyMesh', plug: 'Properties', property: 'visible'}, true);
toggleAllAttachPoints(false);
},
click: function(ev) {
var selectId = api.player.filterNodesFromPosition(ev);
var groupId = findParentGroupId(selectId[0]);
//active nodeMove tool if box are selected, otherwise active orbit tool
if (!selectId || !groupId) {
api.selection.deselectAll();
api.commands.activateCommand('orbit');
} else {
api.selection.selectNode(groupId);
api.commands.activateCommand('nodeMove');
//If defined, onChange will be called at every frame by nodeMove tool
opts.onChange = moveCheck(groupId);
api.commands.setCommandOptions('nodeMove', opts);
}
},
},
});
document.getElementById('baseScene').setAttribute('href','https://clara.io/view/'+sceneId);
});
function filterAttachPointId(groupId){
var attachGroupIds = api.scene.find({from: {id: groupId}, type: 'Null', name: 'AttachGroup'});
return api.scene.filter({from: {id: attachGroupIds}, type: 'Null'});
};
function updateAttachPointPosition(groupId){
attachPointLocation[groupId] = {};
var pointIds = groupIdAttachIdsMap[groupId];
if (pointIds) {
pointIds.forEach(function(id){
var transformInfo = api.scene.getWorldTransform(id);
attachPointLocation[groupId][id] = new THREE.Vector3().setFromMatrixPosition(transformInfo);
});
}
};
function findParentGroupId(selectId){
var groupId = selectId;
var nodeName = api.scene.get({id: groupId, property: 'name'});
while ( nodeName !== 'BoxGroup' ){
if (!nodeName) return null;
groupId = api.scene.find({id: groupId, parent: true});
nodeName = api.scene.get({id: groupId, property: 'name'});
}
return groupId;
};
function toggleAllAttachPoints( opts ){
for (var groupId in groupIdAttachIdsMap) {
attachGroupIds = api.scene.find({from: {id: groupId}, name: 'AttachGroup'});
api.scene.setAll({from: {id: attachGroupIds}, type: 'PolyMesh', plug: 'Properties', property: 'visible'}, opts);
}
};
// In this demo, the moveCheck function will to called at every frame to check the attach
function moveCheck(groupId){
return function(ev){
updateAttachPointPosition(groupId);
checkAttach(1);
attachHint();
};
};
/**
* if move object can attach
* show hint object, hide move object and attach the hint object to the corresponding position
* if move object can not attach
* hide hint object and show move object
*/
function attachHint(){
var groupId = api.selection.lastSelectedNode();
if (attach.canAttach) {
api.selection.setHighlighting(false);
api.scene.setAll({from: {id: attachHintId}, type: 'PolyMesh', plug: 'Properties', property: 'visible'}, true);
api.scene.setAll({from: {id: groupId}, type: 'PolyMesh', plug: 'Properties', property: 'visible'}, false);
let attachPointName = api.scene.get({id: attach.moveAttachId, property: 'name'});
hintAttachPointId = api.scene.find({from: {id: attachHintId}, name: attachPointName});
attachObject(attachHintId, hintAttachPointId);
} else {
api.selection.setHighlighting(true);
api.scene.setAll({from: {id: attachHintId}, type: 'PolyMesh', plug: 'Properties', property: 'visible'}, false);
api.scene.setAll({from: {id: groupId}, type: 'PolyMesh', plug: 'Properties', property: 'visible'}, true);
}
};
function checkAttach(distance){
var groupId = api.selection.lastSelectedNode();
var dSquare = distance * distance;
attach = {
canAttach: false,
minDistance: dSquare * 2,
moveGroupId: '',
moveAttachId: '',
targetGroupId: '',
targetAttachId: '',
};
var moveAttachIds = groupIdAttachIdsMap[groupId];
for (var attachId in groupIdAttachIdsMap) {
if (attachId === groupId) continue;
var targetAttachIds = groupIdAttachIdsMap[attachId];
moveAttachIds.forEach(function(moveId){
targetAttachIds.forEach(function(targetId){
var moveAttachLocation = attachPointLocation[groupId][moveId];
var targetAttachLocation = attachPointLocation[attachId][targetId];
var d = distanceSquare(moveAttachLocation, targetAttachLocation);
if (d <= dSquare && d < attach.minDistance) {
attach.canAttach = true;
attach.minDistance = d;
attach.moveGroupId = groupId;
attach.targetAttachId = attachId;
attach.moveAttachId = moveId;
attach.targetAttachId = targetId;
}
});
});
}
};
function distanceSquare(v1, v2){
var dx = v1.x - v2.x;
var dy = v1.y - v2.y;
var dz = v1.z - v2.z;
return dx*dx + dy*dy + dz*dz;
}
/**
* attachObject function will attach the moveObject to the targetObject
* by applying the vector between the attach point of move object and the
* attach point of the target object to the moving object. Which will result
* that two attach point have the same world coordinate.
*/
function attachObject(moveGroupId, moveAttachId){
var moveObjectMatrix = api.scene.getWorldTransform(moveGroupId);
var moveAttachMatrix = api.scene.getWorldTransform(moveAttachId);
var targetAttachMatrix = api.scene.getWorldTransform(attach.targetAttachId);
var moveObjectPosition = new THREE.Vector3().setFromMatrixPosition(moveObjectMatrix);
var moveAttachPointPosition = new THREE.Vector3().setFromMatrixPosition(moveAttachMatrix);
var targetAttachPointPosition = new THREE.Vector3().setFromMatrixPosition(targetAttachMatrix);
targetAttachPointPosition.sub(moveAttachPointPosition).add(moveObjectPosition);
api.scene.set({id: moveGroupId, plug: 'Transform', property: 'translation'}, targetAttachPointPosition);
updateAttachPointPosition(moveGroupId);
}