@robot-web-tools/ros3djs
Version:
The standard ROS Javascript Visualization Library
324 lines (280 loc) • 9.95 kB
JavaScript
/**
* @author David Gossow - dgossow@willowgarage.com
*/
/**
* The main interactive marker object.
*
* @constructor
* @param options - object with following keys:
*
* * handle - the ROS3D.InteractiveMarkerHandle for this marker
* * camera - the main camera associated with the viewer for this marker
* * 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.COLLADA_LOADER_2) -- defaults to ROS3D.COLLADA_LOADER_2
*/
ROS3D.InteractiveMarker = function(options) {
THREE.Object3D.call(this);
THREE.EventDispatcher.call(this);
var that = this;
options = options || {};
var handle = options.handle;
this.name = handle.name;
var camera = options.camera;
var path = options.path || '/';
var loader = options.loader || ROS3D.COLLADA_LOADER_2;
this.dragging = false;
// set the initial pose
this.onServerSetPose({
pose : handle.pose
});
// information on where the drag started
this.dragStart = {
position : new THREE.Vector3(),
orientation : new THREE.Quaternion(),
positionWorld : new THREE.Vector3(),
orientationWorld : new THREE.Quaternion(),
event3d : {}
};
// add each control message
handle.controls.forEach(function(controlMessage) {
that.add(new ROS3D.InteractiveMarkerControl({
parent : that,
handle : handle,
message : controlMessage,
camera : camera,
path : path,
loader : loader
}));
});
// check for any menus
if (handle.menuEntries.length > 0) {
this.menu = new ROS3D.InteractiveMarkerMenu({
menuEntries : handle.menuEntries,
menuFontSize : handle.menuFontSize
});
// forward menu select events
this.menu.addEventListener('menu-select', function(event) {
that.dispatchEvent(event);
});
}
};
ROS3D.InteractiveMarker.prototype.__proto__ = THREE.Object3D.prototype;
/**
* Show the interactive marker menu associated with this marker.
*
* @param control - the control to use
* @param event - the event that caused this
*/
ROS3D.InteractiveMarker.prototype.showMenu = function(control, event) {
if (this.menu) {
this.menu.show(control, event);
}
};
/**
* Move the axis based on the given event information.
*
* @param control - the control to use
* @param origAxis - the origin of the axis
* @param event3d - the event that caused this
*/
ROS3D.InteractiveMarker.prototype.moveAxis = function(control, origAxis, event3d) {
if (this.dragging) {
var currentControlOri = control.currentControlOri;
var axis = origAxis.clone().applyQuaternion(currentControlOri);
// get move axis in world coords
var originWorld = this.dragStart.event3d.intersection.point;
var axisWorld = axis.clone().applyQuaternion(this.dragStart.orientationWorld.clone());
var axisRay = new THREE.Ray(originWorld, axisWorld);
// find closest point to mouse on axis
var t = ROS3D.closestAxisPoint(axisRay, event3d.camera, event3d.mousePos);
// offset from drag start position
var p = new THREE.Vector3();
p.addVectors(this.dragStart.position, axis.clone().applyQuaternion(this.dragStart.orientation)
.multiplyScalar(t));
this.setPosition(control, p);
event3d.stopPropagation();
}
};
/**
* Move with respect to the plane based on the contorl and event.
*
* @param control - the control to use
* @param origNormal - the normal of the origin
* @param event3d - the event that caused this
*/
ROS3D.InteractiveMarker.prototype.movePlane = function(control, origNormal, event3d) {
if (this.dragging) {
var currentControlOri = control.currentControlOri;
var normal = origNormal.clone().applyQuaternion(currentControlOri);
// get plane params in world coords
var originWorld = this.dragStart.event3d.intersection.point;
var normalWorld = normal.clone().applyQuaternion(this.dragStart.orientationWorld);
// intersect mouse ray with plane
var intersection = ROS3D.intersectPlane(event3d.mouseRay, originWorld, normalWorld);
// offset from drag start position
var p = new THREE.Vector3();
p.subVectors(intersection, originWorld);
p.add(this.dragStart.positionWorld);
this.setPosition(control, p);
event3d.stopPropagation();
}
};
/**
* Rotate based on the control and event given.
*
* @param control - the control to use
* @param origOrientation - the orientation of the origin
* @param event3d - the event that caused this
*/
ROS3D.InteractiveMarker.prototype.rotateAxis = function(control, origOrientation, event3d) {
if (this.dragging) {
control.updateMatrixWorld();
var currentControlOri = control.currentControlOri;
var orientation = currentControlOri.clone().multiply(origOrientation.clone());
var normal = (new THREE.Vector3(1, 0, 0)).applyQuaternion(orientation);
// get plane params in world coords
var originWorld = this.dragStart.event3d.intersection.point;
var normalWorld = normal.applyQuaternion(this.dragStart.orientationWorld);
// intersect mouse ray with plane
var intersection = ROS3D.intersectPlane(event3d.mouseRay, originWorld, normalWorld);
// offset local origin to lie on intersection plane
var normalRay = new THREE.Ray(this.dragStart.positionWorld, normalWorld);
var rotOrigin = ROS3D.intersectPlane(normalRay, originWorld, normalWorld);
// rotates from world to plane coords
var orientationWorld = this.dragStart.orientationWorld.clone().multiply(orientation);
var orientationWorldInv = orientationWorld.clone().inverse();
// rotate original and current intersection into local coords
intersection.sub(rotOrigin);
intersection.applyQuaternion(orientationWorldInv);
var origIntersection = this.dragStart.event3d.intersection.point.clone();
origIntersection.sub(rotOrigin);
origIntersection.applyQuaternion(orientationWorldInv);
// compute relative 2d angle
var a1 = Math.atan2(intersection.y, intersection.z);
var a2 = Math.atan2(origIntersection.y, origIntersection.z);
var a = a2 - a1;
var rot = new THREE.Quaternion();
rot.setFromAxisAngle(normal, a);
// rotate
this.setOrientation(control, rot.multiply(this.dragStart.orientationWorld));
// offset from drag start position
event3d.stopPropagation();
}
};
/**
* Dispatch the given event type.
*
* @param type - the type of event
* @param control - the control to use
*/
ROS3D.InteractiveMarker.prototype.feedbackEvent = function(type, control) {
this.dispatchEvent({
type : type,
position : this.position.clone(),
orientation : this.quaternion.clone(),
controlName : control.name
});
};
/**
* Start a drag action.
*
* @param control - the control to use
* @param event3d - the event that caused this
*/
ROS3D.InteractiveMarker.prototype.startDrag = function(control, event3d) {
if (event3d.domEvent.button === 0) {
event3d.stopPropagation();
this.dragging = true;
this.updateMatrixWorld(true);
var scale = new THREE.Vector3();
this.matrixWorld
.decompose(this.dragStart.positionWorld, this.dragStart.orientationWorld, scale);
this.dragStart.position = this.position.clone();
this.dragStart.orientation = this.quaternion.clone();
this.dragStart.event3d = event3d;
this.feedbackEvent('user-mousedown', control);
}
};
/**
* Stop a drag action.
*
* @param control - the control to use
* @param event3d - the event that caused this
*/
ROS3D.InteractiveMarker.prototype.stopDrag = function(control, event3d) {
if (event3d.domEvent.button === 0) {
event3d.stopPropagation();
this.dragging = false;
this.dragStart.event3d = {};
this.onServerSetPose(this.bufferedPoseEvent);
this.bufferedPoseEvent = undefined;
this.feedbackEvent('user-mouseup', control);
}
};
/**
* Handle a button click.
*
* @param control - the control to use
* @param event3d - the event that caused this
*/
ROS3D.InteractiveMarker.prototype.buttonClick = function(control, event3d) {
event3d.stopPropagation();
this.feedbackEvent('user-button-click', control);
};
/**
* Handle a user pose change for the position.
*
* @param control - the control to use
* @param event3d - the event that caused this
*/
ROS3D.InteractiveMarker.prototype.setPosition = function(control, position) {
this.position = position;
this.feedbackEvent('user-pose-change', control);
};
/**
* Handle a user pose change for the orientation.
*
* @param control - the control to use
* @param event3d - the event that caused this
*/
ROS3D.InteractiveMarker.prototype.setOrientation = function(control, orientation) {
orientation.normalize();
this.quaternion = orientation;
this.feedbackEvent('user-pose-change', control);
};
/**
* Update the marker based when the pose is set from the server.
*
* @param event - the event that caused this
*/
ROS3D.InteractiveMarker.prototype.onServerSetPose = function(event) {
if (event !== undefined) {
// don't update while dragging
if (this.dragging) {
this.bufferedPoseEvent = event;
} else {
var pose = event.pose;
this.position.x = pose.position.x;
this.position.y = pose.position.y;
this.position.z = pose.position.z;
this.quaternion = new THREE.Quaternion(pose.orientation.x, pose.orientation.y,
pose.orientation.z, pose.orientation.w);
this.updateMatrixWorld(true);
}
}
};
/**
* Free memory of elements in this marker.
*/
ROS3D.InteractiveMarker.prototype.dispose = function() {
var that = this;
this.children.forEach(function(intMarkerControl) {
intMarkerControl.children.forEach(function(marker) {
marker.dispose();
intMarkerControl.remove(marker);
});
that.remove(intMarkerControl);
});
};
THREE.EventDispatcher.prototype.apply( ROS3D.InteractiveMarker.prototype );