/**
* @fileOverview
* @author David Gossow - dgossow@willowgarage.com
*/
/**
* The main marker control object for an interactive marker.
*
* @constructor
* @param options - object with following keys:
*
* * parent - the parent of this control
* * message - the interactive marker control message
* * camera - the main camera associated with the viewer for this marker client
* * path (optional) - the base path to any meshes that will be loaded
* * loader (optional) - the Collada loader to use (e.g., an instance of ROS3D.COLLADA_LOADER)
*/
ROS3D.InteractiveMarkerControl = function(options) {
THREE.Object3D.call(this);
var that = this;
options = options || {};
this.parent = options.parent;
var handle = options.handle;
var message = options.message;
this.message = message;
this.name = message.name;
this.camera = options.camera;
this.path = options.path || '/';
this.loader = options.loader;
this.dragging = false;
this.startMousePos = new THREE.Vector2();
this.isShift = false;
// orientation for the control
var controlOri = new THREE.Quaternion(message.orientation.x, message.orientation.y,
message.orientation.z, message.orientation.w);
controlOri.normalize();
// transform x axis into local frame
var controlAxis = new THREE.Vector3(1, 0, 0);
controlAxis.applyQuaternion(controlOri);
this.currentControlOri = new THREE.Quaternion();
// determine mouse interaction
switch (message.interaction_mode) {
case ROS3D.INTERACTIVE_MARKER_MOVE_ROTATE_3D:
case ROS3D.INTERACTIVE_MARKER_MOVE_3D:
this.addEventListener('mousemove', this.parent.move3d.bind(this.parent, this, controlAxis));
break;
case ROS3D.INTERACTIVE_MARKER_MOVE_AXIS:
this.addEventListener('mousemove', this.parent.moveAxis.bind(this.parent, this, controlAxis));
this.addEventListener('touchmove', this.parent.moveAxis.bind(this.parent, this, controlAxis));
break;
case ROS3D.INTERACTIVE_MARKER_ROTATE_AXIS:
this
.addEventListener('mousemove', this.parent.rotateAxis.bind(this.parent, this, controlOri));
break;
case ROS3D.INTERACTIVE_MARKER_MOVE_PLANE:
this
.addEventListener('mousemove', this.parent.movePlane.bind(this.parent, this, controlAxis));
break;
case ROS3D.INTERACTIVE_MARKER_BUTTON:
this.addEventListener('click', this.parent.buttonClick.bind(this.parent, this));
break;
default:
break;
}
/**
* Install default listeners for highlighting / dragging.
*
* @param event - the event to stop
*/
function stopPropagation(event) {
event.stopPropagation();
}
// check the mode
if (message.interaction_mode !== ROS3D.INTERACTIVE_MARKER_NONE) {
this.addEventListener('mousedown', this.parent.startDrag.bind(this.parent, this));
this.addEventListener('mouseup', this.parent.stopDrag.bind(this.parent, this));
this.addEventListener('contextmenu', this.parent.showMenu.bind(this.parent, this));
this.addEventListener('mouseup', function(event3d) {
if (that.startMousePos.distanceToSquared(event3d.mousePos) === 0) {
event3d.type = 'contextmenu';
that.dispatchEvent(event3d);
}
});
this.addEventListener('mouseover', stopPropagation);
this.addEventListener('mouseout', stopPropagation);
this.addEventListener('click', stopPropagation);
this.addEventListener('mousedown', function(event3d) {
that.startMousePos = event3d.mousePos;
});
// touch support
this.addEventListener('touchstart', function(event3d) {
if (event3d.domEvent.touches.length === 1) {
event3d.type = 'mousedown';
event3d.domEvent.button = 0;
that.dispatchEvent(event3d);
}
});
this.addEventListener('touchmove', function(event3d) {
if (event3d.domEvent.touches.length === 1) {
event3d.type = 'mousemove';
event3d.domEvent.button = 0;
that.dispatchEvent(event3d);
}
});
this.addEventListener('touchend', function(event3d) {
if (event3d.domEvent.touches.length === 0) {
event3d.domEvent.button = 0;
event3d.type = 'mouseup';
that.dispatchEvent(event3d);
event3d.type = 'click';
that.dispatchEvent(event3d);
}
});
window.addEventListener('keydown', function(event){
if(event.keyCode === 16){
that.isShift = true;
}
});
window.addEventListener('keyup', function(event){
if(event.keyCode === 16){
that.isShift = false;
}
});
}
// rotation behavior
var rotInv = new THREE.Quaternion();
var posInv = this.parent.position.clone().multiplyScalar(-1);
switch (message.orientation_mode) {
case ROS3D.INTERACTIVE_MARKER_INHERIT:
rotInv = this.parent.quaternion.clone().inverse();
break;
case ROS3D.INTERACTIVE_MARKER_FIXED:
break;
case ROS3D.INTERACTIVE_MARKER_VIEW_FACING:
break;
default:
console.error('Unkown orientation mode: ' + message.orientation_mode);
break;
}
// temporary TFClient to get transformations from InteractiveMarker
// frame to potential child Marker frames
var localTfClient = new ROSLIB.TFClient({
ros : handle.tfClient.ros,
fixedFrame : handle.message.header.frame_id,
serverName : handle.tfClient.serverName
});
// create visuals (markers)
message.markers.forEach(function(markerMsg) {
var addMarker = function(transformMsg) {
var markerHelper = new ROS3D.Marker({
message : markerMsg,
path : that.path,
loader : that.loader
});
// if transformMsg isn't null, this was called by TFClient
if (transformMsg !== null) {
// get the current pose as a ROSLIB.Pose...
var newPose = new ROSLIB.Pose({
position : markerHelper.position,
orientation : markerHelper.quaternion
});
// so we can apply the transform provided by the TFClient
newPose.applyTransform(new ROSLIB.Transform(transformMsg));
// get transform between parent marker's location and its frame
// apply it to sub-marker position to get sub-marker position
// relative to parent marker
var transformMarker = new ROS3D.Marker({
message : markerMsg,
path : that.path,
loader : that.loader
});
transformMarker.position.add(posInv);
transformMarker.position.applyQuaternion(rotInv);
transformMarker.quaternion.multiplyQuaternions(rotInv, transformMarker.quaternion);
var translation = new THREE.Vector3(transformMarker.position.x, transformMarker.position.y, transformMarker.position.z);
var transform = new ROSLIB.Transform({
translation : translation,
orientation : transformMarker.quaternion
});
// apply that transform too
newPose.applyTransform(transform);
markerHelper.setPose(newPose);
markerHelper.updateMatrixWorld();
// we only need to set the pose once - at least, this is what RViz seems to be doing, might change in the future
localTfClient.unsubscribe(markerMsg.header.frame_id);
}
// add the marker
that.add(markerHelper);
};
// If the marker is not relative to the parent marker's position,
// ask the *local* TFClient for the transformation from the
// InteractiveMarker frame to the sub-Marker frame
if (markerMsg.header.frame_id !== '') {
localTfClient.subscribe(markerMsg.header.frame_id, addMarker);
}
// If not, just add the marker without changing its pose
else {
addMarker(null);
}
});
};
ROS3D.InteractiveMarkerControl.prototype.__proto__ = THREE.Object3D.prototype;
ROS3D.InteractiveMarkerControl.prototype.updateMatrixWorld = function (force) {
var that = this;
var message = this.message;
switch (message.orientation_mode) {
case ROS3D.INTERACTIVE_MARKER_INHERIT:
ROS3D.InteractiveMarkerControl.prototype.updateMatrixWorld.call(that, force);
that.currentControlOri.copy(that.quaternion);
that.currentControlOri.normalize();
break;
case ROS3D.INTERACTIVE_MARKER_FIXED:
that.quaternion.copy(that.parent.quaternion.clone().inverse());
that.updateMatrix();
that.matrixWorldNeedsUpdate = true;
ROS3D.InteractiveMarkerControl.prototype.updateMatrixWorld.call(that, force);
that.currentControlOri.copy(that.quaternion);
break;
case ROS3D.INTERACTIVE_MARKER_VIEW_FACING:
that.camera.updateMatrixWorld();
var cameraRot = new THREE.Matrix4().extractRotation(that.camera.matrixWorld);
var ros2Gl = new THREE.Matrix4();
var r90 = Math.PI * 0.5;
var rv = new THREE.Euler(-r90, 0, r90);
ros2Gl.makeRotationFromEuler(rv);
var worldToLocal = new THREE.Matrix4();
worldToLocal.getInverse(that.parent.matrixWorld);
cameraRot.multiplyMatrices(cameraRot, ros2Gl);
cameraRot.multiplyMatrices(worldToLocal, cameraRot);
that.currentControlOri.setFromRotationMatrix(cameraRot);
// check the orientation
if (!message.independent_marker_orientation) {
that.quaternion.copy(that.currentControlOri);
that.updateMatrix();
that.matrixWorldNeedsUpdate = true;
}
ROS3D.InteractiveMarkerControl.prototype.updateMatrixWorld.call(that, force);
break;
default:
console.error('Unkown orientation mode: ' + message.orientation_mode);
break;
}
};