|
|
|
/** @namespace x3dom.nodeTypes */ |
|
/* |
|
* X3DOM JavaScript Library |
|
* http://www.x3dom.org |
|
* |
|
* (C)2009 Fraunhofer IGD, Darmstadt, Germany |
|
* Dual licensed under the MIT and GPL |
|
*/ |
|
|
|
/** |
|
* The plane sensor node translates drag gestures, performed with a pointing device like a mouse, |
|
* into 3D transformations. |
|
*/ |
|
|
|
|
|
x3dom.registerNodeType( |
|
"PlaneSensor", |
|
"PointingDeviceSensor", |
|
defineClass(x3dom.nodeTypes.X3DDragSensorNode, |
|
|
|
/** |
|
* Constructor for PlaneSensor |
|
* @constructs x3dom.nodeTypes.PlaneSensor |
|
* @x3d 3.3 |
|
* @component PointingDeviceSensor |
|
* @status experimental |
|
* @extends x3dom.nodeTypes.X3DDragSensorNode |
|
* @param {Object} [ctx=null] - context object, containing initial settings like namespace |
|
* @classdesc PlaneSensor converts pointing device motion into 2D translation, parallel to the local Z=0 plane. |
|
* Hint: You can constrain translation output to one axis by setting the respective minPosition and maxPosition |
|
* members to equal values for that axis. |
|
*/ |
|
function (ctx) |
|
{ |
|
x3dom.nodeTypes.PlaneSensor.superClass.call(this, ctx); |
|
|
|
//--------------------------------------- |
|
// FIELDS |
|
//--------------------------------------- |
|
/** |
|
* The local sensor coordinate system is created by additionally applying the axisRotation field value to |
|
* the local coordinate system of the sensor node. |
|
* @var {x3dom.fields.SFRotation} axisRotation |
|
* @memberof x3dom.nodeTypes.PlaneSensor |
|
* @initvalue 0,0,1,0 |
|
* @field x3d |
|
* @instance |
|
*/ |
|
this.addField_SFRotation(ctx, 'axisRotation', 0, 0, 1, 0); |
|
|
|
|
|
/** |
|
* The minPosition and maxPosition fields allow to constrain the 2D output of the plane sensor, along each |
|
* 2D component. If the value of a component in maxPosition is smaller than the value of a component in |
|
* minPosition, output is not constrained along the corresponding direction. |
|
* @var {x3dom.fields.SFVec2f} minPosition |
|
* @memberof x3dom.nodeTypes.PlaneSensor |
|
* @initvalue 0,0 |
|
* @field x3d |
|
* @instance |
|
*/ |
|
this.addField_SFVec2f(ctx, 'minPosition', 0, 0); |
|
|
|
|
|
/** |
|
* The minPosition and maxPosition fields allow to constrain the 2D output of the plane sensor, along each |
|
* 2D component. If the value of a component in maxPosition is smaller than the value of a component in |
|
* minPosition, output is not constrained along the corresponding direction. |
|
* @var {x3dom.fields.SFVec2f} maxPosition |
|
* @memberof x3dom.nodeTypes.PlaneSensor |
|
* @initvalue -1,-1 |
|
* @field x3d |
|
* @instance |
|
*/ |
|
this.addField_SFVec2f(ctx, 'maxPosition', -1, -1); |
|
|
|
|
|
/** |
|
* Offset value that is incorporated into the translation output of the sensor. |
|
* This value is automatically updated if the value of the autoOffset field is 'true'. |
|
* @var {x3dom.fields.SFVec3f} offset |
|
* @memberof x3dom.nodeTypes.PlaneSensor |
|
* @initvalue 0,0,0 |
|
* @field x3d |
|
* @instance |
|
*/ |
|
this.addField_SFVec3f(ctx, 'offset', 0, 0, 0); |
|
|
|
/** |
|
* Tracking plane orientation in local coordinate system. |
|
* Valid values are "XY" and "screen". "screen" uses the current orientation of the screen. |
|
* @var {x3dom.fields.SFString} plaenOrientation |
|
* @memberof x3dom.nodeTypes.PlaneSensor |
|
* @initvalue 'XY' |
|
* @field x3dom |
|
* @instance |
|
*/ |
|
this.addField_SFString(ctx, 'planeOrientation', 'XY'); |
|
|
|
|
|
//route-able output fields |
|
//this.addField_SFVec3f(ctx, 'translation_changed', 0, 0, 0); |
|
|
|
|
|
//--------------------------------------- |
|
// PROPERTIES |
|
//--------------------------------------- |
|
|
|
/** |
|
* |
|
* @type {x3dom.fields.Quaternion} |
|
* @private |
|
*/ |
|
//TODO: update on change |
|
this._rotationMatrix = this._vf.axisRotation.toMatrix(); |
|
|
|
/** |
|
* World-To-Local matrix for this node, including the axisRotation of the sensor |
|
*/ |
|
this._worldToLocalMatrix = null; |
|
|
|
|
|
/** |
|
* Initial intersection point with the sensor's plane, at the time the sensor was activated |
|
* @type {x3dom.fields.SFVec3f} |
|
* @private |
|
*/ |
|
this._initialPlaneIntersection = null; |
|
|
|
/** |
|
* Plane normal, computed on drag start and used during dragging to compute plane intersections |
|
* @type {x3dom.fields.SFVec3f} |
|
* @private |
|
*/ |
|
this._planeNormal = null; |
|
|
|
/** |
|
* Current viewarea that is used for dragging, needed for ray setup to compute the plane intersection |
|
* |
|
* @type {x3dom.Viewarea} |
|
* @private |
|
*/ |
|
this._viewArea = null; |
|
|
|
/** |
|
* Current translation that is produced by this drag sensor |
|
* @type {x3dom.fields.SFVec3f} |
|
* @private |
|
*/ |
|
this._currentTranslation = new x3dom.fields.SFVec3f(0.0, 0.0, 0.0); |
|
|
|
//special LineSensor mode |
|
this._lineModeAxis = null; |
|
|
|
if ( this._vf.minPosition.x == this._vf.maxPosition.x ) |
|
this._lineModeAxis = new x3dom.fields.SFVec3f (0, 1, 0); |
|
if ( this._vf.minPosition.y == this._vf.maxPosition.y ) |
|
this._lineModeAxis = new x3dom.fields.SFVec3f (1, 0, 0); |
|
|
|
}, |
|
{ |
|
//---------------------------------------------------------------------------------------------------------------------- |
|
// PUBLIC FUNCTIONS |
|
//---------------------------------------------------------------------------------------------------------------------- |
|
|
|
/** |
|
* This function returns the parent transformation of this node, combined with its current axisRotation |
|
* @overrides x3dom.nodeTypes.X3DPointingDeviceSensorNode.getCurrentTransform |
|
*/ |
|
getCurrentTransform: function () |
|
{ |
|
var parentTransform = x3dom.nodeTypes.X3DDragSensorNode.prototype.getCurrentTransform.call(this); |
|
|
|
return this._rotationMatrix.mult(parentTransform); |
|
}, |
|
|
|
//---------------------------------------------------------------------------------------------------------------------- |
|
// PRIVATE FUNCTIONS |
|
//---------------------------------------------------------------------------------------------------------------------- |
|
|
|
/** |
|
* @overrides x3dom.nodeTypes.X3DDragSensorNode.prototype._startDragging |
|
* @private |
|
*/ |
|
_startDragging: function(viewarea, x, y, wx, wy, wz) |
|
{ |
|
x3dom.nodeTypes.X3DDragSensorNode.prototype._startDragging.call(this, viewarea, x, y, wx, wy, wz); |
|
|
|
this._viewArea = viewarea; |
|
|
|
//save viewMatrix |
|
this._viewMat = this._viewArea.getViewMatrix(); |
|
this._viewMatInv = this._viewMat.inverse(); |
|
|
|
this._currentTranslation = new x3dom.fields.SFVec3f(0.0, 0.0, 0.0).add(this._vf.offset); |
|
|
|
//TODO: handle multi-path nodes |
|
|
|
//get model matrix for this node, combined with the axis rotation |
|
this._localToWorldMatrix = this.getCurrentTransform(); |
|
this._worldToLocalMatrix = this._localToWorldMatrix.inverse(); |
|
|
|
//remember initial point of intersection with the plane, transform it to local sensor coordinates |
|
this._initialPlaneIntersection = this._worldToLocalMatrix.multMatrixPnt(new x3dom.fields.SFVec3f(wx, wy, wz)); |
|
|
|
//compute plane normal in local coordinates |
|
this._planeNormal = new x3dom.fields.SFVec3f(0.0, 0.0, 1.0); |
|
|
|
var viewRay; |
|
//handle screen mode |
|
if (this._vf.planeOrientation == 'screen') { |
|
viewRay = viewarea.calcViewRay(viewarea._width/2, viewarea._height/2); |
|
this._planeNormal = this._worldToLocalMatrix.multMatrixVec (viewRay.dir.normalize()); |
|
} |
|
|
|
//handle LineSensor mode robustly |
|
else if ( this._lineModeAxis ) { |
|
viewRay = viewarea.calcViewRay(x, y); |
|
//viewRay.pos = this._worldToLocalMatrix.multMatrixPnt (viewRay.pos); |
|
var viewDir = this._worldToLocalMatrix.multMatrixVec (viewRay.dir.normalize()); |
|
|
|
var axis = this._lineModeAxis; |
|
//generate suitable intersection plane even if on edge view; |
|
this._planeNormal = axis.cross ( axis.cross (viewDir) ).normalize(); |
|
} |
|
}, |
|
|
|
//---------------------------------------------------------------------------------------------------------------------- |
|
|
|
/** |
|
* @overrides x3dom.nodeTypes.X3DDragSensorNode._process2DDrag |
|
* @private |
|
*/ |
|
_process2DDrag: function(x, y, dx, dy) |
|
{ |
|
x3dom.nodeTypes.X3DDragSensorNode.prototype._process2DDrag.call(this, x, y, dx, dy); |
|
|
|
var intersectionPoint = null; |
|
var minPos, maxPos; |
|
|
|
if (this._initialPlaneIntersection) |
|
{ |
|
//compute point of intersection with the plane |
|
var viewRay = this._viewArea.calcViewRay(x, y); |
|
|
|
//transform the world coordinates, used for the ray, to local sensor coordinates |
|
viewRay.pos = this._worldToLocalMatrix.multMatrixPnt(viewRay.pos); |
|
viewRay.dir = this._worldToLocalMatrix.multMatrixVec(viewRay.dir.normalize()).normalize(); |
|
|
|
if ( Math.abs(this._planeNormal.dot(viewRay.dir)) < 0.01 ) return; |
|
|
|
intersectionPoint = viewRay.intersectPlane(this._initialPlaneIntersection, this._planeNormal); |
|
|
|
//allow interaction from both sides of the plane |
|
if (!intersectionPoint) |
|
{ |
|
intersectionPoint = viewRay.intersectPlane(this._initialPlaneIntersection, this._planeNormal.negate()); |
|
} |
|
|
|
if (intersectionPoint) |
|
{ |
|
//compute difference between new point of intersection and initial point |
|
_translation = intersectionPoint.subtract(this._initialPlaneIntersection); |
|
this._currentTranslation = _translation.add(this._vf.offset); |
|
|
|
//clamp translation components, if desired |
|
minPos = this._vf.minPosition; |
|
maxPos = this._vf.maxPosition; |
|
|
|
if (this._vf.planeOrientation == 'screen') |
|
{ |
|
if (minPos.x <= maxPos.x || minPos.y <= maxPos.y) // proejct/reproject only if necessary |
|
{ |
|
//project currentTranslation into screen plane |
|
var screenTranslation = this._localToWorldMatrix.multMatrixVec(this._currentTranslation); |
|
screenTranslation = this._viewMat.multMatrixVec(screenTranslation); |
|
_clampTranslation (screenTranslation, minPos, maxPos); |
|
// and reproject |
|
screenTranslation = this._viewMatInv.multMatrixVec(screenTranslation); |
|
this._currentTranslation = this._worldToLocalMatrix.multMatrixVec(screenTranslation); |
|
} |
|
} |
|
|
|
else { |
|
//recalc track point for line sensor |
|
if (this._lineModeAxis) { |
|
_translation.z = 0; |
|
intersectionPoint = this._initialPlaneIntersection.add(_translation); |
|
//intersectionPoint.z = this._initialPlaneIntersection.z; |
|
if (this._lineModeAxis.x == 0) { |
|
intersectionPoint.x = minPos.x; |
|
} |
|
else { |
|
intersectionPoint.y = minPos.y; |
|
} |
|
|
|
} |
|
//normally 0 but force for LineSensor plane |
|
this._currentTranslation.z = 0; |
|
_clampTranslation (this._currentTranslation, minPos, maxPos); |
|
} |
|
|
|
//output trackpoint_changed event |
|
this.postMessage('trackPoint_changed', intersectionPoint); |
|
//output translation_changed event |
|
this.postMessage('translation_changed', x3dom.fields.SFVec3f.copy(this._currentTranslation));//this._rotationMatrix.multMatrixPnt(this._currentTranslation));// |
|
|
|
} |
|
} |
|
//helper |
|
function _clampTranslation (translation, minPos, maxPos) |
|
{ |
|
if (minPos.x <= maxPos.x) |
|
{ |
|
translation.x = Math.min(translation.x, maxPos.x); |
|
translation.x = Math.max(translation.x, minPos.x); |
|
} |
|
|
|
if (minPos.y <= maxPos.y) |
|
{ |
|
translation.y = Math.min(translation.y, maxPos.y); |
|
translation.y = Math.max(translation.y, minPos.y); |
|
} |
|
} |
|
}, |
|
|
|
//---------------------------------------------------------------------------------------------------------------------- |
|
|
|
/** |
|
* @overrides x3dom.nodeTypes.X3DDragSensorNode._stopDragging |
|
* @private |
|
*/ |
|
_stopDragging: function() |
|
{ |
|
x3dom.nodeTypes.X3DDragSensorNode.prototype._stopDragging.call(this); |
|
|
|
if (this._vf.autoOffset) |
|
{ |
|
this._vf.offset = x3dom.fields.SFVec3f.copy(this._currentTranslation); |
|
this.postMessage('offset_changed', this._vf.offset); |
|
} |
|
} |
|
|
|
//---------------------------------------------------------------------------------------------------------------------- |
|
} |
|
) |
|
); |