"use strict";
// Copyright 2012 United States Government, as represented by the Secretary of Defense, Under
// Secretary of Defense (Personnel & Readiness).
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
// in compliance with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distributed under the License
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
// or implied. See the License for the specific language governing permissions and limitations under
// the License.
define( [ "module",
"vwf/view",
"vwf/utility",
"hammer",
"jquery"
],
function( module, view, utility, Hammer, $ ) {
var self;
// Navigation: Private global variables for navigation
var navObjectRequested;
var navObjectName;
var navmode;
var touchmode;
var ownerlessNavObjects = [];
var numNavCandidates;
var translationSpeed = 100; // Units per second
var rotationSpeed = 90; // Degrees per second
var makeOwnAvatarVisible = false;
var pointerLockImplemented = "pointerLockElement" in document ||
"mozPointerLockElement" in document ||
"webkitPointerLockElement" in document;
var pointerLocked = false;
var pickDirection = undefined;
var raycaster = undefined;
var pitchMatrix;
var rollMatrix;
var yawMatrix;
var translationMatrix;
var positionUnderMouseClick;
var boundingBox = undefined;
var userObjectRequested = false;
var usersShareView = true;
var degreesToRadians = Math.PI / 180;
var movingForward = false;
var movingBack = false;
var movingLeft = false;
var movingRight = false;
var rotatingLeft = false;
var rotatingRight = false;
var startMousePosition;
var startTouchPosition;
// HACK: This is to deal with an issue with webkitMovementX in Chrome:
// https://code.google.com/p/chromium/issues/detail?id=386791&thanks=386791&ts=1403213097
// where the two values after pointerLock are inaccurate.
// This is used to ignore those values.
// Please check frequently to see if this can be removed.
// Last checked status of issue on 6/19/14.
var nextMouseMoveIsErroneous = false;
var nextTwoMouseMovesAreErroneous = false;
// END HACK
// End Navigation
var lastXPos = -1;
var lastYPos = -1;
var mouseDown = {
left: false,
right: false,
middle: false
};
var touchGesture = false;
var prevGesture = undefined;
var Vec3 = goog.vec.Vec3;
var Quaternion = goog.vec.Quaternion;
var enableStereo = false;
return view.load( module, {
initialize: function( options ) {
self = this;
checkCompatibility.call(this);
this.state.appInitialized = false;
this.pickInterval = 10;
this.enableInputs = true;
this.applicationWantsPointerEvents = false;
// Store parameter options for persistence functionality
this.parameters = options;
if ( typeof options == "object" ) {
this.rootSelector = options[ "application-root" ];
if ( "pick-interval" in options ) {
this.pickInterval = options[ "pick-interval" ];
}
if ( "enable-inputs" in options ) {
this.enableInputs = options[ "enable-inputs" ];
}
enableStereo = ( options.stereo !== undefined ) ? options.stereo : false;
if ( options.shaders ) {
var scriptEle = undefined;
// jQuery.getScript()
for ( var i = 0; i < options.shaders.length; i++ ) {
var scriptEle = document.createElement( 'script' );
scriptEle.setAttribute( "type", "text/javascript" );
scriptEle.setAttribute( "src", options.shaders[ i ] );
}
}
}
else {
this.rootSelector = options;
}
this.height = 600;
this.width = 800;
this.canvasQuery = null;
if ( window && window.innerHeight ) this.height = window.innerHeight;
if ( window && window.innerWidth ) this.width = window.innerWidth;
this.keyStates = { keysDown: {}, mods: {}, keysUp: {} };
pitchMatrix = new THREE.Matrix4();
rollMatrix = new THREE.Matrix4();
yawMatrix = new THREE.Matrix4();
translationMatrix = new THREE.Matrix4();
pickDirection = new THREE.Vector3();
raycaster = new THREE.Raycaster();
window._dView = this;
this.nodes = {};
this.interpolateTransforms = true;
this.tickTime = 0;
this.realTickDif = 50;
this.lastrealTickDif = 50;
this.lastRealTick = performance.now();
this.leftover = 0;
},
createdNode: function( nodeID, childID, childExtendsID, childImplementsIDs,
childSource, childType, childIndex, childName, callback /* ( ready ) */) {
//the created node is a scene, and has already been added to the state by the model.
//how/when does the model set the state object?
if ( this.state.scenes[ childID ] )
{
this.canvasQuery = $(this.rootSelector).append(""
).children(":last");
initScene.call(this,this.state.scenes[childID]);
}
else if ( this.state.scenes[ this.kernel.application() ] ) {
var sceneNode = this.state.scenes[ this.kernel.application() ];
if ( sceneNode.camera.ID == childID ) {
setActiveCamera.call( this, sceneNode.camera.ID );
}
}
if(this.state.nodes[childID] && this.state.nodes[childID].threeObject instanceof THREE.Object3D) {
this.nodes[childID] = {id:childID,extends:childExtendsID};
}
},
initializedNode: function( nodeID, childID ) {
// If the node that was initialized is the application node, find the user's navigation object
var appID = this.kernel.application();
if ( childID == appID ) {
if ( enableStereo ) {
var viewCam = this.state.cameraInUse;
var sceneNode = this.state.scenes[ childID ];
if ( sceneNode ) {
sceneNode.stereo = {
"effect": new THREE.StereoEffect( sceneNode.renderer ),
"element": sceneNode.renderer.domElement,
"controls": viewCam ? createControls( viewCam, sceneNode.renderer.domElement ) : undefined
}
var effect = sceneNode.stereo.effect;
effect.separation = this.parameters.IPD ? this.parameters.IPD : 0.2;
effect.offset = this.parameters.offset ? this.parameters.offset : -0.2;
effect.delta = this.parameters.delta ? this.parameters.delta : 0.01;
effect.setSize( this.width, this.height );
}
}
this.state.appInitialized = true;
} else {
//TODO: This is a temporary workaround until the callback functionality is implemented for
// kernel.createChild()
// Listening specifically for this.findNavObject>>createChild() creating a new navObject if
// one does not exist.
// Can be removed once kernel.createChild callback works properly
var initNode = this.state.nodes[ childID ];
if ( initNode && ( initNode.name == navObjectName ) ) {
initNode.owner = this.kernel.moniker();
controlNavObject( initNode );
}
// in the case that camera was not defined when the scene was created
// we need to see if that camera is now defined, and set up the camera controls
if ( enableStereo ) {
var sceneNode = this.state.scenes[ appID ];
if ( sceneNode && sceneNode.stereo && sceneNode.stereo.controls === undefined ) {
if ( this.state.cameraInUse !== undefined ) {
sceneNode.stereo.controls = createControls( this.state.cameraInUse, sceneNode.renderer.domElement );
}
}
}
}
//End TODO
},
// -- deletedNode ------------------------------------------------------------------------------
deletedNode: function(childID)
{
delete this.nodes[childID];
},
// -- addedChild -------------------------------------------------------------------------------
//addedChild: function( nodeID, childID, childName ) { },
// -- removedChild -----------------------------------------------------------------------------
//removedChild: function( nodeID, childID ) { },
// -- createdProperty --------------------------------------------------------------------------
//createdProperty: function (nodeID, propertyName, propertyValue) { },
// -- initializedProperty ----------------------------------------------------------------------
initializedProperty: function ( nodeID, propertyName, propertyValue ) {
this.satProperty(nodeID, propertyName, propertyValue);
},
// TODO: deletedProperty
// -- satProperty ------------------------------------------------------------------------------
satProperty: function ( nodeID, propertyName, propertyValue ) {
// If this is this user's navObject, pay attention to changes in navmode, translationSpeed, and
// rotationSpeed
if ( navObject && ( nodeID == navObject.ID ) ) {
if ( propertyName == "navmode" ) {
navmode = propertyValue;
if ( pointerLockImplemented && !self.appRequestsPointerLock( navmode, mouseDown ) ) {
document.exitPointerLock();
}
} else if ( propertyName == "translationSpeed" ) {
translationSpeed = propertyValue;
} else if ( propertyName == "rotationSpeed" ) {
rotationSpeed = propertyValue;
}
} else if ( nodeID == this.kernel.application() ) {
if ( propertyName == "makeOwnAvatarVisible" ) {
makeOwnAvatarVisible = propertyValue;
if ( navObject ) {
setVisibleRecursively( navObject.threeObject, makeOwnAvatarVisible );
}
} else if ( propertyName == "boundingBox" ) {
boundingBox = propertyValue;
} else if ( propertyName == "activeCamera" ) {
setActiveCamera.call( this, this.state.scenes[ this.kernel.application() ].camera.ID );
} else if ( propertyName == "usersShareView" ) {
usersShareView = propertyValue;
}
}
// Pay attention to these properties for all nodes
if ( propertyName == "transform" ) {
receiveModelTransformChanges( nodeID, propertyValue );
} else if ( propertyName == "lookAt") {
var node = this.state.nodes[ nodeID ];
// If the state knows about the node, it is in the scene and should be updated
// Otherwise, it is a prototype and can be ignored
if ( node ) {
nodeLookAt( node );
}
}
},
// -- gotProperty ------------------------------------------------------------------------------
gotProperty: function ( nodeID, propertyName, propertyValue ) {
var clientThatGotProperty = this.kernel.client();
var me = this.kernel.moniker();
var sceneRootID = this.kernel.application();
if ( clientThatGotProperty == me ) {
if ( propertyName == "owner") {
// Get the navigable object
var navCandidate = this.state.nodes[ nodeID ];
// If a node w/ nodeID exists, then this is a real owner value to be processed
// (otherwise it is the behavior itself and should be ignored)
if ( navCandidate ) {
// If we haven't already found the navigation object....
if ( !navObject ) {
var owner = propertyValue;
// If I'm the owner, take control
// Else, if it doesn't have an owner, push it on the list of ownerless navigation
// objects that we can pull from if none is found that has an owner of this client
if ( owner == me ) {
controlNavObject( navCandidate );
} else if ( !owner ) {
ownerlessNavObjects.push( navCandidate );
}
}
// If we did not take control of this navigation object (its owner wasn't this client)
if ( !navObject ) {
// Decrement the counter of navigation objects that we are waiting for
numNavCandidates--;
// If we're out of navigation candidates for which we might be the owner, see if
// there are any ownerless nav objects that we could control
// If so, take control
// Else, create one
if ( !numNavCandidates ) {
if ( ownerlessNavObjects.length ) {
controlNavObject( ownerlessNavObjects[ 0 ] );
} else {
// Retrieve the userObject property so we may create a navigation object from
// it for this user (the rest of the logic is in the gotProperty call for
// userObject)
this.kernel.getProperty( this.kernel.application(), "userObject" );
userObjectRequested = true;
}
}
}
}
} else if ( propertyName == "userObject" ) {
if ( userObjectRequested ) {
// The userObject property is only requested when the system wishes to create one for this
// user. We do that here.
// Set the userObject from the value received or a default if it is null/undefined
var userObject = propertyValue || {
"extends": "http://vwf.example.com/camera.vwf",
"implements": [ "http://vwf.example.com/navigable.vwf" ]
};
// Makes sure that the userObject has a properties field
userObject[ "properties" ] = userObject[ "properties" ] || {};
// Set the object's owner to be this object
userObject[ "properties" ][ "owner" ] = me;
// Save the name of the object globally so we can recognize it in
// initializedNode so we can take control of it there
navObjectName = "navobj_" + me;
// TODO: The callback function is commented out because callbacks have not yet been
// implemented for createChild - see workaround in initializedNode
this.kernel.createChild( this.kernel.application(), navObjectName, userObject, undefined, undefined /*,
function( nodeID ) {
controlNavObject( this.state.nodes[ nodeID ] );
} */ );
userObjectRequested = false;
}
} else if ( propertyName == "makeOwnAvatarVisible" ) {
makeOwnAvatarVisible = propertyValue;
if ( navObject ) {
setVisibleRecursively( navObject.threeObject, makeOwnAvatarVisible );
}
} else if ( propertyName == "boundingBox" ) {
boundingBox = propertyValue;
} else if ( navObject && ( nodeID == navObject.ID ) ) {
// These were requested in controlNavObject
if ( propertyName == "navmode" ) {
navmode = propertyValue;
} else if ( propertyName == "touchmode" ) {
touchmode = propertyValue;
} else if ( propertyName == "translationSpeed" ) {
translationSpeed = propertyValue;
} else if ( propertyName == "rotationSpeed" ) {
rotationSpeed = propertyValue;
}
}
}
},
// -- calledMethod -----------------------------------------------------------------------------
calledMethod: function( nodeID, methodName, methodParameters, methodValue ) {
switch(methodName) {
case "translateBy":
case "translateTo":
// No need for rotateBy or rotateTo because they call the quaternion methods
case "quaterniateBy":
case "quaterniateTo":
case "scaleBy":
case "scaleTo":
// No need for transformBy or worldTransformBy because they call transformTo and worldTransformTo
case "transformTo":
case "worldTransformTo":
// If the duration of the transform is 0, set the transforms to their final value so it doesn't interpolate
if(methodParameters.length < 2 || methodParameters[1] == 0) {
this.nodes[nodeID].lastTickTransform = getTransform(nodeID);
this.nodes[nodeID].selfTickTransform = goog.vec.Mat4.clone(this.nodes[nodeID].lastTickTransform);
}
break;
}
},
// -- addedEventListener -------------------------------------------------------------------
addedEventListener: function( nodeID, eventName, eventHandler, eventContextID, eventPhases ) {
switch( eventName ) {
case "pointerClick":
case "pointerDown":
case "pointerMove":
case "pointerUp":
case "pointerOver":
case "pointerOut":
case "pointerWheel":
case "touchHold":
case "touchTap":
case "touchDoubleTap":
case "touchDrag":
case "touchDragStart":
case "touchDragEnd":
case "touchDragUp":
case "touchDragDown":
case "touchDragLeft":
case "touchDragRight":
case "touchSwipe":
case "touchSwipeUp":
case "touchSwipeDown":
case "touchSwipeLeft":
case "touchSwipeRight":
case "touchTransform":
case "touchTransformStart":
case "touchTransformEnd":
case "touchRotate":
case "touchPinch":
case "touchPinchIn":
case "touchPinchOut":
case "touchStart":
case "touchRelease":
if ( this.kernel.find( nodeID,
"self::element(*,'http://vwf.example.com/node3.vwf')" ).length ||
this.kernel.find( nodeID,
"self::element(*,'http://vwf.example.com/scene.vwf')" ).length ) {
this.applicationWantsPointerEvents = true;
}
break;
}
},
// -- firedEvent -----------------------------------------------------------------------------
firedEvent: function( nodeID, eventName ) {
if ( eventName == "changingTransformFromView" ) {
var clientThatSatProperty = self.kernel.client();
var me = self.kernel.moniker();
// If the transform property was initially updated by this view....
if ( clientThatSatProperty == me ) {
var node = this.state.nodes[ nodeID ];
node.ignoreNextTransformUpdate = true;
}
}
else if (eventName == "resetViewport") {
if(this.state.scenes[nodeID]) {
this.state.scenes[nodeID].renderer.setViewport(0,0,window.innerWidth,window.innerHeight);
}
}
},
// -- ticked -----------------------------------------------------------------------------------
ticked: function() {
// This is the first place that we know that the entire app is loaded because the queue has been
// resumed (and therefore, it is ticking) - we will search for the user's navigation object here
// We want to only search for the navigation object if we haven't before (!navObjectRequested),
// and we want to make sure that the app has been initialized, and where not at the brief period of
// ticking before the app starts loading (appInitialized)
if ( !navObjectRequested && this.state.appInitialized ) {
navObjectRequested = true;
findNavObject();
}
lerpTick();
},
// -- render -----------------------------------------------------------------------------------
render: function( renderer, scene, camera ) {
renderer.render( scene, camera );
},
// -- Navigation -------------------------------------------------------------------------------
navigationKeyMapping: {
"w": "forward",
"a": "left",
"s": "back",
"d": "right",
"uparrow": "forward",
"leftarrow": "left",
"downarrow": "back",
"rightarrow": "right",
"q": "rotateLeft",
"e": "rotateRight"
},
handleMouseNavigation: function( deltaX, deltaY, navObj, navMode, rotationSpeed, translationSpeed, mouseDown, mouseEventData ) {
var yawQuat = new THREE.Quaternion();
var pitchQuat = new THREE.Quaternion();
var rotationSpeedRadians = degreesToRadians * rotationSpeed;
var orbiting = mouseDown.middle && ( navmode == "fly" );
// We will soon want to use the yawMatrix and pitchMatrix,
// so let's update them
extractRotationAndTranslation( navObject.threeObject );
if ( orbiting ) {
var pitchRadians = deltaY * rotationSpeedRadians;
var yawRadians = deltaX * rotationSpeedRadians;
orbit( pitchRadians, yawRadians );
} else if ( mouseDown.right ) {
var navThreeObject = navObj.threeObject;
// --------------------
// Calculate new pitch
// --------------------
// deltaY is negated because a positive change (downward) generates a negative rotation
// around the horizontal x axis (clockwise as viewed from the right)
pitchQuat.setFromAxisAngle( new THREE.Vector3( 1, 0, 0 ), -deltaY * rotationSpeedRadians );
var pitchDeltaMatrix = new THREE.Matrix4();
pitchDeltaMatrix.makeRotationFromQuaternion( pitchQuat );
if ( ( navMode == "fly" ) ||
( ( navMode == "walk" ) && ( cameraNode == navObj ) ) ) {
pitchMatrix.multiplyMatrices( pitchDeltaMatrix, pitchMatrix );
// Constrain the camera's pitch to +/- 90 degrees
// We need to do something if zAxis.z is < 0
var pitchMatrixElements = pitchMatrix.elements;
if ( pitchMatrixElements[ 10 ] < 0 ) {
var xAxis = goog.vec.Vec3.create();
xAxis = goog.vec.Vec3.setFromArray( xAxis, [ pitchMatrixElements[ 0 ],
pitchMatrixElements[ 1 ],
pitchMatrixElements[ 2 ] ] );
var yAxis = goog.vec.Vec3.create();
// If forward vector is tipped up
if ( pitchMatrixElements[ 6 ] > 0 ) {
yAxis = goog.vec.Vec3.setFromArray( yAxis, [ 0, 0, 1 ] );
} else {
yAxis = goog.vec.Vec3.setFromArray( yAxis, [ 0, 0, -1 ] );
}
// Calculate the zAxis as a crossProduct of x and y
var zAxis = goog.vec.Vec3.cross( xAxis, yAxis, goog.vec.Vec3.create() );
// Put these values back in the camera matrix
pitchMatrixElements[ 4 ] = yAxis[ 0 ];
pitchMatrixElements[ 5 ] = yAxis[ 1 ];
pitchMatrixElements[ 6 ] = yAxis[ 2 ];
pitchMatrixElements[ 8 ] = zAxis[ 0 ];
pitchMatrixElements[ 9 ] = zAxis[ 1 ];
pitchMatrixElements[ 10 ] = zAxis[ 2 ];
}
} else if ( navMode == "walk" ) {
// Perform pitch on camera - right-multiply to keep pitch separate from yaw
var camera = self.state.cameraInUse;
if ( camera ) {
var cameraMatrix = camera.matrix;
var originalCameraTransform = goog.vec.Mat4.clone( cameraMatrix.elements );
var cameraPos = new THREE.Vector3();
cameraPos.setFromMatrixPosition( cameraMatrix );
cameraMatrix.multiply( pitchDeltaMatrix );
// Constrain the camera's pitch to +/- 90 degrees
var camWorldMatrix = camera.matrixWorld;
var camWorldMatrixElements = camWorldMatrix.elements;
// We need to do something if zAxis.z is < 0
// This can get a little weird because this matrix is in three.js coordinates,
// but we care about VWF coordinates:
// -the VWF y-axis is the three.js -z axis
// -the VWF z-axis is the three.js y axis
if ( camWorldMatrixElements[ 6 ] < 0 ) {
var xAxis = goog.vec.Vec3.create();
xAxis = goog.vec.Vec3.setFromArray( xAxis, [ camWorldMatrixElements[ 0 ],
camWorldMatrixElements[ 1 ],
camWorldMatrixElements[ 2 ] ] );
var yAxis = goog.vec.Vec3.create();
// If forward vector is tipped up
if ( camWorldMatrixElements[ 10 ] > 0 ) {
yAxis = goog.vec.Vec3.setFromArray( yAxis, [ 0, 0, -1 ] );
} else {
yAxis = goog.vec.Vec3.setFromArray( yAxis, [ 0, 0, 1 ] );
}
// Calculate the zAxis as a crossProduct of x and y
var zAxis = goog.vec.Vec3.cross( xAxis, yAxis, goog.vec.Vec3.create() );
// Put these values back in the camera matrix
camWorldMatrixElements[ 4 ] = zAxis[ 0 ];
camWorldMatrixElements[ 5 ] = zAxis[ 1 ];
camWorldMatrixElements[ 6 ] = zAxis[ 2 ];
camWorldMatrixElements[ 8 ] = -yAxis[ 0 ];
camWorldMatrixElements[ 9 ] = -yAxis[ 1 ];
camWorldMatrixElements[ 10 ] = -yAxis[ 2 ];
setTransformFromWorldTransform( camera );
}
// Restore camera position so rotation is done around camera center
cameraMatrix.setPosition( cameraPos );
updateRenderObjectTransform( camera );
callModelTransformBy( cameraNode, originalCameraTransform,
cameraMatrix.elements );
} else {
self.logger.warnx( "There is no camera to move" );
}
}
// ------------------
// Calculate new yaw
// ------------------
// deltaX is negated because a positive change (to the right) generates a negative rotation
// around the vertical z axis (clockwise as viewed from above)
yawQuat.setFromAxisAngle( new THREE.Vector3( 0, 0, 1 ), -deltaX * rotationSpeedRadians );
var yawDeltaMatrix = new THREE.Matrix4();
yawDeltaMatrix.makeRotationFromQuaternion( yawQuat );
yawMatrix.multiplyMatrices( yawDeltaMatrix, yawMatrix );
// -------------------------------------------------
// Put all components together and set the new pose
// -------------------------------------------------
var navObjectWorldMatrix = navObject.threeObject.matrixWorld;
navObjectWorldMatrix.multiplyMatrices( yawMatrix, pitchMatrix );
navObjectWorldMatrix.multiplyMatrices( translationMatrix, navObjectWorldMatrix );
if ( navObject.threeObject instanceof THREE.Camera ) {
var navObjWrldTrnsfmArr = navObjectWorldMatrix.elements;
navObjectWorldMatrix.elements = convertCameraTransformFromVWFtoThreejs( navObjWrldTrnsfmArr );
}
}
},
handleScroll: function ( wheelDelta, navObj, navMode, rotationSpeed, translationSpeed, distanceToTarget ) {
if ( navMode !== "fly" ) {
return;
}
var orbiting = ( navMode == "fly" ) && ( mouseDown.middle )
if ( orbiting || !pickDirection ) {
return;
}
var navThreeObject = navObj.threeObject;
// wheelDelta has a value of 3 for every click
var numClicks = Math.abs( wheelDelta / 3 );
// Prepare variables for calculation
var dist = Math.min( Math.max( distanceToTarget || translationSpeed,
2 * self.state.cameraInUse.near ),
9 * translationSpeed );
var percentDistRemainingEachStep = 0.8;
var amountToMove = 0;
// If wheelDelta is negative, user pushed wheel forward - move toward the object
// Else, user pulled wheel back - move away from object
if ( wheelDelta < 0 ) {
amountToMove = dist * ( 1 - Math.pow( percentDistRemainingEachStep, numClicks ) );
} else {
amountToMove = dist * ( 1 - Math.pow( 1 / percentDistRemainingEachStep, numClicks ) );
}
// We are about to use the translationMatrix, so let's update it
extractRotationAndTranslation( navObject.threeObject );
var translationArray = translationMatrix.elements;
translationArray[ 12 ] += amountToMove * pickDirection.x;
translationArray[ 13 ] += amountToMove * pickDirection.y;
translationArray[ 14 ] += amountToMove * pickDirection.z;
if ( boundingBox != undefined ) {
if ( translationArray[ 12 ] < boundingBox[ 0 ][ 0 ] ) {
translationArray[ 12 ] = boundingBox[ 0 ][ 0 ];
}
else if ( translationArray[ 12 ] > boundingBox[ 0 ][ 1 ] ) {
translationArray[ 12 ] = boundingBox[ 0 ][ 1 ];
}
if ( translationArray[ 13 ] < boundingBox[ 1 ][ 0 ] ) {
translationArray[ 13 ] = boundingBox[ 1 ][ 0 ];
}
else if ( translationArray[ 13 ] > boundingBox[ 1 ][ 1 ] ) {
translationArray[ 13 ] = boundingBox[ 1 ][ 1 ];
}
if ( translationArray[ 14 ] < boundingBox[ 2 ][ 0 ] ) {
translationArray[ 14 ] = boundingBox[ 2 ][ 0 ];
}
else if ( translationArray[ 14 ] > boundingBox[ 2 ][ 1 ] ) {
translationArray[ 14 ] = boundingBox[ 2 ][ 1 ];
}
}
var worldTransformArray = navThreeObject.matrixWorld.elements;
worldTransformArray[ 12 ] = translationArray[ 12 ];
worldTransformArray[ 13 ] = translationArray[ 13 ];
worldTransformArray[ 14 ] = translationArray[ 14 ];
},
handleTouchNavigation: function ( touchEventData ) {
var currentMousePosition = touchEventData[ 0 ].position;
var deltaX = 0;
var deltaY = 0;
if ( startTouchPosition ) {
deltaX = currentMousePosition[ 0 ] - startTouchPosition [ 0 ];
deltaY = currentMousePosition[ 1 ] - startTouchPosition [ 1 ];
}
// We will soon want to use the yawMatrix and pitchMatrix,
// so let's update them
extractRotationAndTranslation( navObject.threeObject );
if ( deltaX || deltaY ) {
var yawQuat = new THREE.Quaternion();
var pitchQuat = new THREE.Quaternion();
var rotationSpeedRadians = degreesToRadians * rotationSpeed;
if ( touchmode == "orbit" ) {
var pitchRadians = deltaY * rotationSpeedRadians;
var yawRadians = deltaX * rotationSpeedRadians;
orbit( pitchRadians, yawRadians );
} else if ( touchmode == "look" ) {
if ( navObject ) {
var navThreeObject = navObject.threeObject;
var originalTransform = goog.vec.Mat4.clone( navThreeObject.matrix.elements );
// --------------------
// Calculate new pitch
// --------------------
// deltaY is negated because a positive change (downward) generates a negative rotation
// around the horizontal x axis (clockwise as viewed from the right)
pitchQuat.setFromAxisAngle( new THREE.Vector3( 1, 0, 0 ), -deltaY * rotationSpeedRadians );
var pitchDeltaMatrix = new THREE.Matrix4();
pitchDeltaMatrix.makeRotationFromQuaternion( pitchQuat );
if ( ( touchmode == "look" ) ||
( ( touchmode == "orbit" ) && ( cameraNode == navObject ) ) ) {
pitchMatrix.multiplyMatrices( pitchDeltaMatrix, pitchMatrix );
// Constrain the camera's pitch to +/- 90 degrees
// We need to do something if zAxis.z is < 0
var pitchMatrixElements = pitchMatrix.elements;
if ( pitchMatrixElements[ 10 ] < 0 ) {
var xAxis = goog.vec.Vec3.create();
xAxis = goog.vec.Vec3.setFromArray( xAxis, [ pitchMatrixElements[ 0 ],
pitchMatrixElements[ 1 ],
pitchMatrixElements[ 2 ] ] );
var yAxis = goog.vec.Vec3.create();
// If forward vector is tipped up
if ( pitchMatrixElements[ 6 ] > 0 ) {
yAxis = goog.vec.Vec3.setFromArray( yAxis, [ 0, 0, 1 ] );
} else {
yAxis = goog.vec.Vec3.setFromArray( yAxis, [ 0, 0, -1 ] );
}
// Calculate the zAxis as a crossProduct of x and y
var zAxis = goog.vec.Vec3.cross( xAxis, yAxis, goog.vec.Vec3.create() );
// Put these values back in the camera matrix
pitchMatrixElements[ 4 ] = yAxis[ 0 ];
pitchMatrixElements[ 5 ] = yAxis[ 1 ];
pitchMatrixElements[ 6 ] = yAxis[ 2 ];
pitchMatrixElements[ 8 ] = zAxis[ 0 ];
pitchMatrixElements[ 9 ] = zAxis[ 1 ];
pitchMatrixElements[ 10 ] = zAxis[ 2 ];
}
}
// ------------------
// Calculate new yaw
// ------------------
// deltaX is negated because a positive change (to the right) generates a negative rotation
// around the vertical z axis (clockwise as viewed from above)
yawQuat.setFromAxisAngle( new THREE.Vector3( 0, 0, 1 ), -deltaX * rotationSpeedRadians );
var yawDeltaMatrix = new THREE.Matrix4();
yawDeltaMatrix.makeRotationFromQuaternion( yawQuat );
yawMatrix.multiplyMatrices( yawDeltaMatrix, yawMatrix );
// -------------------------------------------------
// Put all components together and set the new pose
// -------------------------------------------------
var navObjectWorldMatrix = navThreeObject.matrixWorld;
navObjectWorldMatrix.multiplyMatrices( yawMatrix, pitchMatrix );
navObjectWorldMatrix.multiplyMatrices( translationMatrix, navObjectWorldMatrix );
if ( navThreeObject instanceof THREE.Camera ) {
var navObjWrldTrnsfmArr = navObjectWorldMatrix.elements;
navObjectWorldMatrix.elements = convertCameraTransformFromVWFtoThreejs( navObjWrldTrnsfmArr );
}
setTransformFromWorldTransform( navObject.threeObject );
callModelTransformBy( navObject, originalTransform, navThreeObject.matrix.elements );
} else {
self.logger.warnx( "handleTouchNavigation: There is no navigation object to move" );
}
}
}
startTouchPosition = currentMousePosition;
},
appRequestsPointerLock: function( navmode, mouseDown ) {
// By default, an app will request pointer lock when:
// - the middle mouse button is hit in fly mode (for orbit)
// - the right mouse button is hit in any mode other than "none" (for look)
if ( mouseDown.middle && ( navmode === "fly" ) ) {
return true;
}
if ( mouseDown.right && ( navmode !== "none" ) ) {
return true;
}
return false;
}
} );
// private ===============================================================================
var navObject = undefined;
var cameraNode = undefined;
function lerpTick () {
var now = performance.now();
self.realTickDif = now - self.lastRealTick;
self.lastRealTick = now;
//reset - loading can cause us to get behind and always but up against the max prediction value
self.tickTime = 0;
for ( var nodeID in self.nodes ) {
if ( self.state.nodes[nodeID] ) {
self.nodes[nodeID].lastTickTransform = self.nodes[nodeID].selfTickTransform;
self.nodes[nodeID].selfTickTransform = getTransform(nodeID);
}
}
}
function lerp(a,b,l,c) {
if(c) l = Math.min(1,Math.max(l,0));
return (b*l) + a*(1.0-l);
}
function matCmp (a,b,delta) {
for(var i =0; i < 16; i++) {
if(Math.abs(a[i] - b[i]) > delta)
return false;
}
return true;
}
function rotMatFromVec(x,y,z) {
var n = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1];
n[0] = x[0];n[1] = x[1];n[2] = x[2];
n[4] = y[0];n[5] = y[1];n[6] = y[2];
n[8] = z[0];n[9] = z[1];n[10] = z[2];
return n;
}
function isLeftHandedOrthogonalMatrix( elements ) {
if ( !elements ) {
throw new Error('matrix was null');
}
var xAxis = new THREE.Vector3(elements[0],elements[1],elements[2]);
var yAxis = new THREE.Vector3(elements[4],elements[5],elements[6]);
var zAxis = new THREE.Vector3(elements[8],elements[9],elements[10]);
xAxis.normalize();
yAxis.normalize();
zAxis.normalize();
var XYdotZ = xAxis.cross( yAxis ).dot( zAxis );
if( XYdotZ > 0.999999 ) {
return true;
} else {
return false;
}
}
function matrixLerp( a, b, l ) {
// If either of the matrices is not left-handed or not orthogonal, interpolation won't work
// Just return the second matrix
if ( !( isLeftHandedOrthogonalMatrix( a ) && isLeftHandedOrthogonalMatrix( b ) ) ) {
return b;
}
var n = goog.vec.Mat4.clone(a);
n[12] = lerp(a[12],b[12],l);
n[13] = lerp(a[13],b[13],l);
n[14] = lerp(a[14],b[14],l);
var x = [a[0],a[1],a[2]];
var xl = Vec3.magnitude(x);
var y = [a[4],a[5],a[6]];
var yl = Vec3.magnitude(y);
var z = [a[8],a[9],a[10]];
var zl = Vec3.magnitude(z);
var x2 = [b[0],b[1],b[2]];
var xl2 = Vec3.magnitude(x2);
var y2 = [b[4],b[5],b[6]];
var yl2 = Vec3.magnitude(y2);
var z2 = [b[8],b[9],b[10]];
var zl2 = Vec3.magnitude(z2);
var nxl = lerp(xl,xl2,l);
var nyl = lerp(yl,yl2,l);
var nzl = lerp(zl,zl2,l);
x = Vec3.normalize(x,[]);
y = Vec3.normalize(y,[]);
z = Vec3.normalize(z,[]);
x2 = Vec3.normalize(x2,[]);
y2 = Vec3.normalize(y2,[]);
z2 = Vec3.normalize(z2,[]);
var q = Quaternion.fromRotationMatrix4(rotMatFromVec(x,y,z),[]);
var q2 = Quaternion.fromRotationMatrix4(rotMatFromVec(x2,y2,z2),[]);
var nq = Quaternion.slerp(q,q2,l,[]);
var nqm = Quaternion.toRotationMatrix4(nq,[]);
var nx = [nqm[0],nqm[1],nqm[2]];
var ny = [nqm[4],nqm[5],nqm[6]];
var nz = [nqm[8],nqm[9],nqm[10]];
nx = Vec3.scale(nx,nxl,[]);
ny = Vec3.scale(ny,nyl,[]);
nz = Vec3.scale(nz,nzl,[]);
nqm = rotMatFromVec(nx,ny,nz);
nqm[12] = n[12];
nqm[13] = n[13];
nqm[14] = n[14];
return nqm;
}
function getTransform(id) {
var interp = goog.vec.Mat4.clone(self.state.nodes[id].threeObject.matrix.elements);
return interp;
}
function setTransform(id,interp) {
interp = goog.vec.Mat4.clone(interp)
self.state.nodes[id].threeObject.matrix.elements = interp;
self.state.nodes[id].threeObject.updateMatrixWorld(true);
}
function setInterpolatedTransforms(deltaTime) {
var step = (self.tickTime) / (self.realTickDif);
step = Math.min(step,1);
deltaTime = Math.min(deltaTime, self.realTickDif)
self.tickTime += deltaTime || 0;
for(var nodeID in self.nodes) {
var last = self.nodes[nodeID].lastTickTransform;
var now = self.nodes[nodeID].selfTickTransform;
if(last && now && !matCmp(last,now,.0001) ) {
var interp = matrixLerp(last, now, step || 0);
var objectIsControlledByUser = ( ( navmode !== "none" ) &&
( ( navObject && ( nodeID === navObject.ID ) ) ||
( cameraNode && ( nodeID === cameraNode.ID ) ) ) );
if ( !objectIsControlledByUser ) {
setTransform(nodeID, interp);
self.nodes[nodeID].needTransformRestore = true;
}
}
}
}
function restoreTransforms() {
for(var nodeID in self.nodes) {
var now = self.nodes[nodeID].selfTickTransform;
if(self.node != navObject && now && self.nodes[nodeID].needTransformRestore) {
self.state.nodes[nodeID].threeObject.matrix.elements = goog.vec.Mat4.clone(now);
self.state.nodes[nodeID].threeObject.updateMatrixWorld(true);
self.nodes[nodeID].needTransformRestore = false;
}
}
}
function checkCompatibility() {
this.compatibilityStatus = { compatible:true, errors:{} }
var contextNames = ["webgl","experimental-webgl","moz-webgl","webkit-3d"];
for(var i = 0; i < contextNames.length; i++){
try{
var canvas = document.createElement('canvas');
var gl = canvas.getContext(contextNames[i]);
if(gl){
return true;
}
}
catch(e){}
}
this.compatibilityStatus.compatible = false;
this.compatibilityStatus.errors["WGL"] = "This browser is not compatible. The vwf/view/threejs driver requires WebGL.";
return false;
}
function initScene( sceneNode ) {
var lastPickTime = 0;
var mobileDevice = isMobile();
function GetParticleSystems(node,list)
{
if(!list)
list = [];
for(var i =0; i self.pickInterval ) ) && self.enableInputs )
{
sceneNode.frameCount = 0;
var newPick, newPickId;
if ( self.applicationWantsPointerEvents ) {
newPick = ThreeJSPick.call( self, mycanvas, sceneNode, false );
newPickId = newPick ? getPickObjectID.call( view, newPick.object ) : view.kernel.application();
} else {
newPick = undefined;
newPickId = undefined;
}
if ( self.lastPickId != newPickId && self.lastEventData )
{
if ( self.lastPickId ) {
view.kernel.dispatchEvent( self.lastPickId, "pointerOut",
self.lastEventData.eventData,
self.lastEventData.eventNodeData );
}
if ( newPickId ) {
view.kernel.dispatchEvent( newPickId, "pointerOver",
self.lastEventData.eventData,
self.lastEventData.eventNodeData );
}
}
if ( view.lastEventData &&
( view.lastEventData.eventData[0].screenPosition[0] != oldMouseX ||
view.lastEventData.eventData[0].screenPosition[1] != oldMouseY ) ) {
oldMouseX = view.lastEventData.eventData[0].screenPosition[0];
oldMouseY = view.lastEventData.eventData[0].screenPosition[1];
hovering = false;
}
else if(self.lastEventData && self.mouseOverCanvas && !hovering && newPick) {
view.kernel.dispatchEvent( newPickId, "pointerHover", self.lastEventData.eventData, self.lastEventData.eventNodeData );
hovering = true;
}
self.lastPickId = newPickId;
self.lastPick = newPick;
lastPickTime = now;
}
self.mouseJustEnteredCanvas = false;
}
if ( enableStereo && sceneNode && sceneNode.stereo ) {
if ( mobileDevice && sceneNode.stereo.controls ) {
sceneNode.stereo.controls.update( timepassed );
}
sceneNode.stereo.effect.render( scene, camera );
} else {
self.render( renderer, scene, camera );
}
sceneNode.lastTime = now;
if ( self.interpolateTransforms ) {
restoreTransforms();
}
};
var mycanvas = this.canvasQuery.get( 0 );
function detectWebGL()
{
var asa; var canvas; var dcanvas; var gl; var expmt;
$(document.body).append('');
canvas = $('#testWebGLSupport');
console.log(canvas);
// check to see if we can do webgl
// ALERT FOR JQUERY PEEPS: canvas is a jquery obj - access the dom obj at canvas[0]
dcanvas = canvas[0];
expmt = false;
if ("WebGLRenderingContext" in window) {
console.log("browser at least knows what webgl is.");
}
// some browsers don't have a .getContext for canvas...
try { gl = dcanvas.getContext("webgl"); }
catch (x) { gl = null; }
if (gl == null) {
try { gl = dcanvas.getContext("experimental-webgl"); }
catch (x) { gl = null; }
if (gl == null) { console.log('but can\'t speak it'); }
else { expmt = true; console.log('and speaks it experimentally.'); }
} else {
console.log('and speaks it natively.');
}
if (gl || expmt) {
console.log("loading webgl content."); canvas.remove(); return true;
} else {
console.log("image-only fallback. no webgl.");
canvas.remove();
return false;
}
}
function getURLParameter(name) {
return decodeURI(
(RegExp(name + '=' + '(.+?)(&|$)').exec(location.search)||[,null])[1]
);
}
if ( mycanvas ) {
var oldMouseX = 0;
var oldMouseY = 0;
var hovering = false;
var view = this;
var viewCam;
window.onresize = function () {
var origWidth = self.width;
var origHeight = self.height;
var viewWidth = mycanvas.width / sceneNode.renderer.devicePixelRatio;
var viewHeight = mycanvas.height / sceneNode.renderer.devicePixelRatio;
if ( window && window.innerHeight ) self.height = window.innerHeight;
if ( window && window.innerWidth ) self.width = window.innerWidth;
if ( ( origWidth != self.width || origHeight != self.height ) ) {
// If canvas changed size, use canvas dimentions instead
if ( viewWidth != mycanvas.clientWidth || viewHeight != mycanvas.clientHeight ) {
self.width = mycanvas.clientWidth;
self.height = mycanvas.clientHeight;
}
sceneNode.renderer.setSize( self.width, self.height, true );
if ( enableStereo && sceneNode.stereo && sceneNode.stereo.effect ) {
sceneNode.stereo.effect.setSize( self.width, self.height );
}
}
viewCam = view.state.cameraInUse;
if ( viewCam ) {
viewCam.aspect = mycanvas.clientWidth / mycanvas.clientHeight;
viewCam.updateProjectionMatrix();
}
}
if ( detectWebGL() && getURLParameter('disableWebGL') == 'null' ){
sceneNode.renderer = new THREE.WebGLRenderer( { canvas: mycanvas, antialias: true } );
} else {
sceneNode.renderer = new THREE.CanvasRenderer( { canvas: mycanvas, antialias: true } );
sceneNode.renderer.setSize( window.innerWidth,window.innerHeight );
}
sceneNode.renderer.setSize( self.width, self.height, true );
// backgroundColor, enableShadows, shadowMapCullFace and shadowMapType are dependent on the renderer object, but if they are set in a prototype,
// the renderer is not available yet, so set them now.
for(var key in sceneNode.rendererProperties) {
if(key == "backgroundColor") {
var vwfColor = new utility.color( sceneNode.rendererProperties["backgroundColor"] );
if ( vwfColor ) {
sceneNode.renderer.setClearColor( vwfColor.getHex(), vwfColor.alpha() );
}
}
else if(key == "enableShadows") {
value = Boolean( sceneNode.rendererProperties["enableShadows"] );
sceneNode.renderer.shadowMapEnabled = value;
}
else if(key == 'shadowMapCullFace') {
sceneNode.renderer.shadowMapCullFace = Number( sceneNode.rendererProperties["shadowMapCullFace"] );
}
else if(key == 'shadowMapType') {
sceneNode.renderer.shadowMapType = Number( sceneNode.rendererProperties["shadowMapType"] );
}
}
rebuildAllMaterials.call(this);
if ( sceneNode.renderer.setFaceCulling )
sceneNode.renderer.setFaceCulling( THREE.CullFaceBack );
// Schedule the renderer.
var scene = sceneNode.threeScene;
var renderer = sceneNode.renderer;
var scenenode = sceneNode;
window._dScene = scene;
window._dRenderer = renderer;
window._dSceneNode = sceneNode;
if ( this.enableInputs ) {
initInputEvents.call(this,mycanvas);
}
renderScene( ( +new Date ) );
}
// If scene is already loaded, find the user's navigation object
var sceneView = this;
var appID = sceneView.kernel.application( true );
if ( appID ) {
this.state.appInitialized = true;
}
} // initScene
function rebuildAllMaterials(start)
{
if(!start)
{
for(var i in this.state.scenes)
{
rebuildAllMaterials(this.state.scenes[i].threeScene);
}
}else
{
if(start && start.material)
{
start.material.needsUpdate = true;
}
if(start && start.children)
{
for(var i in start.children)
rebuildAllMaterials(start.children[i]);
}
}
}
// -- initInputEvents ------------------------------------------------------------------------
function initInputEvents( canvas ) {
var sceneID = this.kernel.application();
var sceneNode = this.state.scenes[ sceneID ], child;
var sceneView = this;
var touchID = undefined;
var touchPick = undefined;
var pointerDownID = undefined;
var pointerOverID = undefined;
var pointerPickID = undefined;
var threeActualObj = undefined;
var win = window;
var container = document.getElementById( "container" );
var sceneCanvas = canvas;
//var mouse = new GLGE.MouseInput( sceneCanvas );
canvas.requestPointerLock = canvas.requestPointerLock ||
canvas.mozRequestPointerLock ||
canvas.webkitRequestPointerLock ||
function() {};
document.exitPointerLock = document.exitPointerLock ||
document.mozExitPointerLock ||
document.webkitExitPointerLock ||
function() {};
var getEventData = function( e, debug ) {
var returnData = { eventData: undefined, eventNodeData: undefined };
var pickInfo = self.lastPick;
pointerPickID = undefined;
threeActualObj = pickInfo ? pickInfo.object : undefined;
pointerPickID = pickInfo ? getPickObjectID.call( sceneView, pickInfo.object, debug ) : undefined;
var mouseButton = "left";
switch( e.button ) {
case 2:
mouseButton = "right";
break;
case 1:
mouseButton = "middle";
break;
default:
mouseButton = "left";
break;
};
var mousePos = utility.coordinates.contentFromWindow( e.target, { x: e.clientX, y: e.clientY } ); // canvas coordinates from window coordinates
returnData.eventData = [ {
/*client: "123456789ABCDEFG", */
button: mouseButton,
clicks: 1,
buttons: {
left: mouseDown.left,
middle: mouseDown.middle,
right: mouseDown.right,
},
gestures: touchGesture,
modifiers: {
alt: e.altKey,
ctrl: e.ctrlKey,
shift: e.shiftKey,
meta: e.metaKey,
},
position: [ mousePos.x / canvas.clientWidth, mousePos.y / canvas.clientHeight ],
screenPosition: [ mousePos.x, mousePos.y ]
} ];
if ( pointerLocked ) {
returnData.eventData.movementX = e.movementX || e.mozMovementX || e.webkitMovementX || 0;
returnData.eventData.movementY = e.movementY || e.mozMovementY || e.webkitMovementY || 0;
}
var camera = sceneView.state.cameraInUse;
var worldCamPos, worldCamTrans, camInverse;
var localPickNormal, worldPickNormal, worldTransform;
if ( camera ) {
var worldCamTrans = new THREE.Vector3();
worldCamTrans.setFromMatrixPosition( camera.matrixWorld );
// Convert THREE.Vector3 to array
// QUESTION: Is the double use of y a bug? I would assume so, but then why not
// just use worldCamTrans as-is?
worldCamPos = [ worldCamTrans.x, worldCamTrans.y, worldCamTrans.z];
}
if ( pickInfo ) {
if ( sceneView.state.nodes[ pointerPickID ] ) {
var pickObj = sceneView.state.nodes[ pointerPickID ];
var nml;
if ( pickObj.threeObject.matrixWorld ) {
worldTransform = goog.vec.Mat4.createFromArray( pickObj.threeObject.matrixWorld.elements );
} else {
worldTransform = goog.vec.Mat4.createFromArray( getWorldTransform( pickObj ).elements );
}
if ( pickInfo.face ) {
nml = pickInfo.face.normal
localPickNormal = goog.vec.Vec3.createFloat32FromValues( nml.x, nml.y, nml.z );
} else if ( pickInfo.normal ) {
nml = pickInfo.normal;
localPickNormal = goog.vec.Vec3.createFloat32FromValues( nml[0], nml[1], nml[2] );
}
if ( localPickNormal !== undefined ) {
localPickNormal = goog.vec.Vec3.normalize( localPickNormal, goog.vec.Vec3.create() );
worldPickNormal = goog.vec.Mat4.multVec3NoTranslate( worldTransform, localPickNormal, goog.vec.Vec3.create() );
}
}
}
returnData.eventNodeData = { "": [ {
pickID: pointerPickID,
pointerVector: pickDirection ? vec3ToArray( pickDirection ) : undefined,
distance: pickInfo ? pickInfo.distance : undefined,
origin: pickInfo ? pickInfo.worldCamPos : undefined,
globalPosition: pickInfo ? [pickInfo.point.x,pickInfo.point.y,pickInfo.point.z] : undefined,
globalNormal: worldPickNormal ? worldPickNormal : localPickNormal, //** not implemented by threejs
globalSource: worldCamPos
} ] };
if ( sceneView && sceneView.state.nodes[ pointerPickID ] ) {
var camera = sceneView.state.cameraInUse;
var childID = pointerPickID;
var child = sceneView.state.nodes[ childID ];
var parentID = child.parentID;
var parent = sceneView.state.nodes[ child.parentID ];
var transform, parentTrans, localTrans, localNormal, parentInverse, relativeCamPos;
while ( child ) {
transform = goog.vec.Mat4.createFromArray( child.threeObject.matrix.elements );
goog.vec.Mat4.transpose( transform, transform );
if ( parent ) {
parentTrans = goog.vec.Mat4.createFromArray( parent.threeObject.matrix.elements );
goog.vec.Mat4.transpose( parentTrans, parentTrans );
} else {
parentTrans = undefined;
}
if ( transform && parentTrans ) {
// get the parent inverse, and multiply by the world
// transform to get the local transform
parentInverse = goog.vec.Mat4.create();
if ( goog.vec.Mat4.invert( parentTrans, parentInverse ) ) {
localTrans = goog.vec.Mat4.multMat( parentInverse, transform,
goog.vec.Mat4.create()
);
}
}
// transform the global normal into local
if ( transform && pickInfo && pickInfo.face ) {
localNormal = goog.vec.Mat4.multVec3Projective( transform, pickInfo.face.normal,
goog.vec.Vec3.create() );
} else {
localNormal = undefined;
}
if ( worldCamPos ) {
relativeCamPos = goog.vec.Mat4.multVec3Projective( transform, worldCamPos,
goog.vec.Vec3.create() );
} else {
relativeCamPos = undefined;
}
returnData.eventNodeData[ childID ] = [ {
pickID: pointerPickID,
pointerVector: pickDirection ? vec3ToArray( pickDirection ) : undefined,
position: localTrans,
normal: localNormal,
source: relativeCamPos,
distance: pickInfo ? pickInfo.distance : undefined,
globalPosition: pickInfo ? [pickInfo.point.x,pickInfo.point.y,pickInfo.point.z] : undefined,
globalNormal: worldPickNormal ? worldPickNormal : localPickNormal,
globalSource: worldCamPos,
} ];
childID = parentID;
child = sceneView.state.nodes[ childID ];
parentID = child ? child.parentID : undefined;
parent = parentID ? sceneView.state.nodes[ child.parentID ] : undefined;
}
}
self.lastEventData = returnData;
return returnData;
}
var getTouchEventData = function( e, debug ) {
var returnData = { eventData: undefined, eventNodeData: undefined };
var mousePos = utility.coordinates.contentFromWindow( e.target, { x: e.gesture.center.pageX, y: e.gesture.center.pageY } ); // canvas coordinates from window coordinates
touchPick = ThreeJSTouchPick.call( self, canvas, sceneNode, mousePos );
var pickInfo = touchPick;
var gestureTouches = {};
for (var i = 0; i < e.gesture.touches.length; i++) {
gestureTouches[i] = {x: e.gesture.touches[i].clientX, y: e.gesture.touches[i].clientY};
gestureTouches.length = i + 1;
}
returnData.eventData = [ {
gestures: touchGesture,
position: [ mousePos.x / sceneView.width, mousePos.y / sceneView.height ],
screenPosition: [ mousePos.x, mousePos.y ],
angle: e.gesture.angle,
touches: gestureTouches
} ];
returnData.eventNodeData = { "": [ {
distance: pickInfo ? pickInfo.distance : undefined,
globalPosition: pickInfo ? [pickInfo.point.x,pickInfo.point.y,pickInfo.point.z] : undefined
} ] };
if ( returnData.eventNodeData[ "" ][ 0 ].globalPosition ) {
positionUnderMouseClick = returnData.eventNodeData[ "" ][ 0 ].globalPosition;
}
self.lastEventData = returnData;
return returnData;
}
// Do not emulate mouse events on touch
Hammer.NO_MOUSEEVENTS = true;
$(canvas).hammer({ drag_lock_to_axis: false }).on("touch release", handleHammer);
$(canvas).hammer({ drag_lock_to_axis: false }).on("hold tap doubletap", handleHammer);
$(canvas).hammer({ drag_lock_to_axis: false }).on("drag dragstart dragend dragup dragdown dragleft dragright", handleHammer);
$(canvas).hammer({ drag_lock_to_axis: false }).on("swipe swipeup swipedown swipeleft,swiperight", handleHammer);
$(canvas).hammer({ drag_lock_to_axis: false }).on("transform transformstart transformend", handleHammer);
$(canvas).hammer({ drag_lock_to_axis: false }).on("rotate", handleHammer);
$(canvas).hammer({ drag_lock_to_axis: false }).on("pinch pinchin pinchout", handleHammer);
function handleHammer( ev ) {
// disable browser scrolling
ev.gesture.preventDefault();
var eData = getTouchEventData( ev, false );
touchID = touchPick ? getPickObjectID.call( sceneView, touchPick.object, false ) : sceneID;
switch(ev.type) {
case 'hold':
sceneView.kernel.dispatchEvent( touchID, "touchHold", eData.eventData, eData.eventNodeData );
break;
case 'tap':
sceneView.kernel.dispatchEvent( touchID, "touchTap", eData.eventData, eData.eventNodeData );
// Emulate pointer events
eData.eventData[0].button = "left";
sceneView.kernel.dispatchEvent( touchID, "pointerClick", eData.eventData, eData.eventNodeData );
sceneView.kernel.dispatchEvent( touchID, "pointerDown", eData.eventData, eData.eventNodeData );
sceneView.kernel.dispatchEvent( touchID, "pointerUp", eData.eventData, eData.eventNodeData );
break;
case 'doubletap':
sceneView.kernel.dispatchEvent( touchID, "touchDoubleTap", eData.eventData, eData.eventNodeData );
break;
case 'drag':
// Fly or Orbit Navigation Behavior
if ( touchmode != "none") {
if ( prevGesture == "drag" || prevGesture == "dragleft" || prevGesture == "dragright") {
self.handleTouchNavigation( eData.eventData );
}
}
sceneView.kernel.dispatchEvent( touchID, "touchDrag", eData.eventData, eData.eventNodeData );
break;
case 'dragstart':
sceneView.kernel.dispatchEvent( touchID, "touchDragStart", eData.eventData, eData.eventNodeData );
break;
case 'dragend':
sceneView.kernel.dispatchEvent( touchID, "touchDragEnd", eData.eventData, eData.eventNodeData );
break;
case 'dragup':
sceneView.kernel.dispatchEvent( touchID, "touchDragUp", eData.eventData, eData.eventNodeData );
break;
case 'dragdown':
sceneView.kernel.dispatchEvent( touchID, "touchDragDown", eData.eventData, eData.eventNodeData );
break;
case 'dragleft':
sceneView.kernel.dispatchEvent( touchID, "touchDragLeft", eData.eventData, eData.eventNodeData );
break;
case 'dragright':
sceneView.kernel.dispatchEvent( touchID, "touchDragRight", eData.eventData, eData.eventNodeData );
break;
case 'swipe':
sceneView.kernel.dispatchEvent( touchID, "touchSwipe", eData.eventData, eData.eventNodeData );
break;
case 'swipeup':
sceneView.kernel.dispatchEvent( touchID, "touchSwipeUp", eData.eventData, eData.eventNodeData );
break;
case 'swipedown':
sceneView.kernel.dispatchEvent( touchID, "touchSwipeDown", eData.eventData, eData.eventNodeData );
break;
case 'swipeleft':
sceneView.kernel.dispatchEvent( touchID, "touchSwipeLeft", eData.eventData, eData.eventNodeData );
break;
case 'swiperight':
sceneView.kernel.dispatchEvent( touchID, "touchSwipeRight", eData.eventData, eData.eventNodeData );
break;
case 'transform':
sceneView.kernel.dispatchEvent( touchID, "touchTransform", eData.eventData, eData.eventNodeData );
break;
case 'transformstart':
sceneView.kernel.dispatchEvent( touchID, "touchTransformStart", eData.eventData, eData.eventNodeData );
break;
case 'transformend':
sceneView.kernel.dispatchEvent( touchID, "touchTransformEnd", eData.eventData, eData.eventNodeData );
break;
case 'rotate':
sceneView.kernel.dispatchEvent( touchID, "touchRotate", eData.eventData, eData.eventNodeData );
break;
case 'pinch':
sceneView.kernel.dispatchEvent( touchID, "touchPinch", eData.eventData, eData.eventNodeData );
break;
case 'pinchin':
// Zoom Out
if ( touchmode != "none" ) {
inputHandleScroll( ev.gesture.scale, eData.eventNodeData[ "" ][ 0 ].distance );
}
sceneView.kernel.dispatchEvent( touchID, "touchPinchIn", eData.eventData, eData.eventNodeData );
break;
case 'pinchout':
// Zoom In
if ( touchmode != "none" ) {
inputHandleScroll( -1 * ev.gesture.scale, eData.eventNodeData[ "" ][ 0 ].distance );
}
sceneView.kernel.dispatchEvent( touchID, "touchPinchOut", eData.eventData, eData.eventNodeData );
break;
case 'touch':
touchGesture = true;
sceneView.kernel.dispatchEvent( touchID, "touchStart", eData.eventData, eData.eventNodeData );
break;
case 'release':
touchGesture = false;
sceneView.kernel.dispatchEvent( touchID, "touchRelease", eData.eventData, eData.eventNodeData );
break;
}
// Set previous gesture (only perform drag if the previous is not a pinch gesture - causes jumpiness)
prevGesture = ev.type;
}
canvas.onmousedown = function( e ) {
// Set appropriate button / key states
// Shift+click of any button is treated as the middle button to accomodate mice that
// don't have a middle button
var shiftDown = e.shiftKey;
switch( e.button ) {
case 0: // Left button
if ( shiftDown ) {
mouseDown.middle = true;
} else {
mouseDown.left = true;
}
break;
case 1: // Middle button
mouseDown.middle = true;
break;
case 2: // Right button
if ( shiftDown ) {
mouseDown.middle = true;
} else {
mouseDown.right = true;
}
break;
};
// Set pointerLock if appropriate
var event = getEventData( e, false );
if ( pointerLockImplemented && self.appRequestsPointerLock( navmode, mouseDown ) ) {
// HACK: This is to deal with an issue with webkitMovementX in Chrome:
nextMouseMoveIsErroneous = true;
nextTwoMouseMovesAreErroneous = true;
// END HACK
canvas.requestPointerLock();
positionUnderMouseClick = event && event.eventNodeData[ "" ][ 0 ].globalPosition;
}
// Process mouse down event
if ( event ) {
pointerDownID = pointerPickID ? pointerPickID : sceneID;
sceneView.kernel.dispatchEvent( pointerDownID, "pointerDown", event.eventData, event.eventNodeData );
// TODO: Navigation - see main "TODO: Navigation" comment for explanation
startMousePosition = event.eventData[ 0 ].position;
// END TODO
}
e.preventDefault();
}
// Listen for onmouseup from the document (instead of the canvas like all the other mouse events)
// because it will catch mouseup events that occur outside the window, whereas canvas.onmouseup does
// not.
document.onmouseup = function( e ) {
// Set appropriate button / key states
var ctrlDown = e.ctrlKey;
var atlDown = e.altKey;
var ctrlAndAltDown = ctrlDown && atlDown;
// Shift+click w/ any button is considered a middle click to accomodate mice w/o a
// middle mouse button. Therefore, if the left or right mouse button is released,
// but the system did not record that it was down, it must be a Shift+click for the
// middle mouse button that was released
switch( e.button ) {
case 0: // Left button
if ( mouseDown.left ) {
mouseDown.left = false;
} else {
mouseDown.middle = false;
}
break;
case 1: // Middle button
mouseDown.middle = false;
break;
case 2: // Right button
if ( mouseDown.right ) {
mouseDown.right = false;
} else {
mouseDown.middle = false;
}
break;
};
// Release pointerLock if appropriate
if ( pointerLockImplemented && !self.appRequestsPointerLock( navmode, mouseDown ) ) {
document.exitPointerLock();
}
// Process mouse up event
var eData = getEventData( e, ctrlAndAltDown );
if ( eData !== undefined ) {
var mouseUpObjectID = pointerPickID;
if ( mouseUpObjectID && pointerDownID && mouseUpObjectID == pointerDownID ) {
sceneView.kernel.dispatchEvent( mouseUpObjectID, "pointerClick", eData.eventData, eData.eventNodeData );
// TODO: hierarchy output, helpful for setting up applications
var objNode = sceneView.state.nodes[mouseUpObjectID];
var obj3js = objNode.threeObject;
if ( obj3js ) {
if ( atlDown && !ctrlDown ) {
var colladaParent = obj3js;
while ( colladaParent.parent ) {
if ( colladaParent.loadedColladaNode ) {
break;
} else {
colladaParent = colladaParent.parent;
}
}
if ( colladaParent === undefined ) {
colladaParent = obj3js;
}
console.info( "===== YAML ===== START" );
recurseObject3D.call( sceneView, colladaParent, "", 0 );
console.info( "===== YAML ===== END" );
console.info( "===== JSON ===== START" );
recurseJsonObject3D.call( sceneView, colladaParent, "", 0 );
console.info( "===== JSON ===== END" );
console.info( "===== THREEJS ===== START" );
consoleScene.call( this, sceneNode.threeScene, 0 );
console.info( "===== THREEJS ===== END" );
}
}
} else {
if ( atlDown && !ctrlDown ) {
recurseObject3D.call( sceneView, sceneNode.threeScene, "", 0 );
consoleScene.call( this, sceneNode.threeScene, 0 );
}
}
if ( pointerDownID ) {
sceneView.kernel.dispatchEvent( pointerDownID, "pointerUp", eData.eventData,
eData.eventNodeData );
}
}
if ( !( mouseDown.left || mouseDown.right || mouseDown.middle ) ) {
pointerDownID = undefined;
// TODO: Navigation - see main "TODO: Navigation" comment for explanation
startMousePosition = undefined;
// END TODO
}
e.preventDefault();
}
canvas.onmouseover = function( e ) {
if ( !self.mouseOverCanvas ) {
self.mouseJustEnteredCanvas = true;
self.mouseOverCanvas = true;
}
var eData = getEventData( e, false );
if ( eData ) {
pointerOverID = pointerPickID ? pointerPickID : sceneID;
sceneView.kernel.dispatchEvent( pointerOverID, "pointerOver", eData.eventData, eData.eventNodeData );
}
e.preventDefault();
}
canvas.onmousemove = function( e ) {
// HACK: This is to deal with an issue with webkitMovementX in Chrome:
if ( nextMouseMoveIsErroneous ) {
if ( nextTwoMouseMovesAreErroneous ) {
nextTwoMouseMovesAreErroneous = false;
} else {
nextMouseMoveIsErroneous = false;
}
return;
}
// END HACK
var eData = getEventData( e, false );
if ( eData ) {
if ( mouseDown.left || mouseDown.right || mouseDown.middle ) {
// TODO: Navigation - see main "TODO: Navigation" comment for explanation
if ( navmode != "none" ) {
if ( cameraNode ) {
if ( !cameraNode.lookatval ) {
inputHandleMouseNavigation( eData.eventData );
}
} else {
self.logger.warnx( "canvas.onmousemove: camera does not exist" );
}
}
// END TODO
sceneView.kernel.dispatchEvent( pointerDownID, "pointerMove", eData.eventData, eData.eventNodeData );
} else {
if ( pointerPickID ) {
if ( pointerOverID ) {
if ( pointerPickID != pointerOverID ) {
sceneView.kernel.dispatchEvent( pointerOverID, "pointerOut", eData.eventData, eData.eventNodeData );
pointerOverID = pointerPickID;
sceneView.kernel.dispatchEvent( pointerOverID, "pointerOver", eData.eventData, eData.eventNodeData );
}
} else {
pointerOverID = pointerPickID;
sceneView.kernel.dispatchEvent( pointerOverID, "pointerOver", eData.eventData, eData.eventNodeData );
}
} else {
if ( pointerOverID ) {
sceneView.kernel.dispatchEvent( pointerOverID, "pointerOut", eData.eventData, eData.eventNodeData );
pointerOverID = undefined;
}
}
}
}
e.preventDefault();
}
canvas.onmouseout = function( e ) {
if ( pointerOverID ) {
sceneView.kernel.dispatchEvent( pointerOverID, "pointerOut" );
pointerOverID = undefined;
}
self.mouseOverCanvas = false;
e.preventDefault();
}
canvas.setAttribute( "onmousewheel", '' );
window.onkeydown = function (event) {
var key = undefined;
var validKey = false;
var keyAlreadyDown = false;
switch ( event.keyCode ) {
case 17:
case 16:
case 18:
case 19:
case 20:
break;
default:
key = getKeyValue.call( sceneView, event.keyCode);
keyAlreadyDown = !!sceneView.keyStates.keysDown[key.key];
sceneView.keyStates.keysDown[key.key] = key;
validKey = true;
// TODO: Navigation - see main "TODO: Navigation" comment for explanation
handleKeyNavigation( event.keyCode, true );
// END TODO
break;
}
if (!sceneView.keyStates.mods) sceneView.keyStates.mods = {};
sceneView.keyStates.mods.alt = event.altKey;
sceneView.keyStates.mods.shift = event.shiftKey;
sceneView.keyStates.mods.ctrl = event.ctrlKey;
sceneView.keyStates.mods.meta = event.metaKey;
var sceneNode = sceneView.state.scenes[ sceneView.kernel.application() ];
if (validKey && sceneNode && !keyAlreadyDown /*&& Object.keys( sceneView.keyStates.keysDown ).length > 0*/) {
//var params = JSON.stringify( sceneView.keyStates );
sceneView.kernel.dispatchEvent(sceneNode.ID, "keyDown", [sceneView.keyStates]);
}
};
window.onkeyup = function (event) {
var key = undefined;
var validKey = false;
switch (event.keyCode) {
case 16:
case 17:
case 18:
case 19:
case 20:
break;
default:
key = getKeyValue.call( sceneView, event.keyCode);
delete sceneView.keyStates.keysDown[key.key];
sceneView.keyStates.keysUp[key.key] = key;
validKey = true;
// TODO: Navigation - see main "TODO: Navigation" comment for explanation
handleKeyNavigation( event.keyCode, false );
// END TODO
break;
}
sceneView.keyStates.mods.alt = event.altKey;
sceneView.keyStates.mods.shift = event.shiftKey;
sceneView.keyStates.mods.ctrl = event.ctrlKey;
sceneView.keyStates.mods.meta = event.metaKey;
var sceneNode = sceneView.state.scenes[ sceneView.kernel.application() ];
if (validKey && sceneNode) {
//var params = JSON.stringify( sceneView.keyStates );
sceneView.kernel.dispatchEvent(sceneNode.ID, "keyUp", [sceneView.keyStates]);
delete sceneView.keyStates.keysUp[key.key];
}
};
window.oncontextmenu = function() {
if ( navmode == "none" )
return true;
else
return false;
}
window.onblur = function() {
// Stop all key movement when window goes out of focus since key events are now going to a
// different window
movingForward = false;
movingBack = false;
movingLeft = false;
movingRight = false;
rotatingLeft = false;
rotatingRight = false;
}
// As of this writing, Chrome and Opera Next use canvas.onmousewheel
// Firefox uses canvas.onwheel
if ( canvas.onmousewheel !== undefined ) {
canvas.removeAttribute("onmousewheel");
canvas.onmousewheel = function( e ) {
var eData = getEventData( e, false );
if ( eData ) {
var eventNodeData = eData.eventNodeData[ "" ][ 0 ];
eventNodeData.wheel = {
delta: e.wheelDelta / -40,
deltaX: e.wheelDeltaX / -40,
deltaY: e.wheelDeltaY / -40,
};
var id = sceneID;
if ( pointerDownID && mouseDown.right || mouseDown.left || mouseDown.middle )
id = pointerDownID;
else if ( pointerOverID )
id = pointerOverID;
sceneView.kernel.dispatchEvent( id, "pointerWheel", eData.eventData, eData.eventNodeData );
inputHandleScroll( eventNodeData.wheel.delta, eventNodeData.distance );
}
};
} else if ( canvas.onwheel !== undefined ) {
canvas.removeAttribute("onmousewheel");
canvas.onwheel = function( e ) {
var eData = getEventData( e, false );
if ( eData ) {
if ( e.deltaMode != 1 ) {
self.logger.warnx( "canvas.onwheel: This browser uses an unsupported deltaMode: " +
e.deltaMode );
}
var eventNodeData = eData.eventNodeData[ "" ][ 0 ];
eventNodeData.wheel = {
delta: e.deltaY,
deltaX: e.deltaX,
deltaY: e.deltaY,
};
var id = sceneID;
if ( pointerDownID && mouseDown.right || mouseDown.left || mouseDown.middle )
id = pointerDownID;
else if ( pointerOverID )
id = pointerOverID;
sceneView.kernel.dispatchEvent( id, "pointerWheel", eData.eventData, eData.eventNodeData );
inputHandleScroll( eventNodeData.wheel.delta, eventNodeData.distance );
}
};
} else {
this.logger.warnx( "initInputEvents: Neither onmousewheel nor onwheel are supported in this " +
"browser so mouse scrolling is not supported - request that the VWF team " +
"support the DOMMouseScroll event to support your browser" );
}
// TODO: Navigation - This section should become a view component as soon as that system is available
// When altering this, search for other sections that say "TODO: Navigation"
var onPointerLockChange = function() {
if ( document.pointerLockElement === canvas ||
document.mozPointerLockElement === canvas ||
document.webkitPointerLockElement === canvas ) {
pointerLocked = true;
} else {
pointerLocked = false;
}
}
document.addEventListener( "pointerlockchange", onPointerLockChange, false);
document.addEventListener( "mozpointerlockchange", onPointerLockChange, false);
document.addEventListener( "webkitpointerlockchange", onPointerLockChange, false);
this.moveNavObject = function( x, y, navObj, navMode, rotationSpeed, translationSpeed, msSinceLastFrame ) {
var navThreeObject = navObj.threeObject;
// Compute the distance traveled in the elapsed time
// Constrain the time to be less than 0.5 seconds, so that if a user has a very low frame rate,
// one key press doesn't send them off in space
var dist = translationSpeed * Math.min( msSinceLastFrame * 0.001, 0.5 );
var dir = [ 0, 0, 0 ];
var camera = self.state.cameraInUse;
var cameraWorldTransformArray = camera.matrixWorld.elements;
var orbiting = ( navMode == "fly" ) && mouseDown.middle && positionUnderMouseClick;
if ( orbiting ) {
if ( y ) {
dir = [ positionUnderMouseClick[ 0 ] - cameraWorldTransformArray[ 12 ],
positionUnderMouseClick[ 1 ] - cameraWorldTransformArray[ 13 ],
positionUnderMouseClick[ 2 ] - cameraWorldTransformArray[ 14 ] ];
if ( y > 0 ) {
var distToOrbitTarget = Math.sqrt( dir[ 0 ] * dir[ 0 ] + dir[ 1 ] * dir[ 1 ] + dir[ 2 ] * dir[ 2 ] );
var epsilon = 0.01;
var almostDistToOrbit = distToOrbitTarget - epsilon;
if ( dist > almostDistToOrbit ) {
dist = almostDistToOrbit;
}
if ( dist < epsilon ) {
dir = [ 0, 0, 0 ];
}
} else {
dir = [ -dir[ 0 ], -dir[ 1 ], -dir[ 2 ] ];
}
}
if ( x ) {
var pitchRadians = 0;
var yawRadians = x * ( rotationSpeed * degreesToRadians ) *
Math.min( msSinceLastFrame * 0.001, 0.5 );
orbit( pitchRadians, yawRadians );
}
} else {
// Get the camera's rotation matrix in the world's frame of reference
// (remove its translation component so it is just a rotation matrix)
var camWorldRotMat = goog.vec.Mat4.createFromArray( cameraWorldTransformArray );
camWorldRotMat[ 12 ] = 0;
camWorldRotMat[ 13 ] = 0;
camWorldRotMat[ 14 ] = 0;
// Calculate a unit direction vector in the camera's parent's frame of reference
var moveVectorInCameraFrame = goog.vec.Vec4.createFromValues( x, 0, -y, 1 ); // Accounts for z-up (VWF) to y-up (three.js) change
moveVectorInCameraFrame = goog.vec.Vec4.createFromValues( x, 0, -y, 1 ); // Accounts for z-up (VWF) to y-up (three.js) change
dir = goog.vec.Mat4.multVec4( camWorldRotMat, moveVectorInCameraFrame, goog.vec.Vec3.create() );
}
// If user is walking, constrain movement to the horizontal plane
if ( navMode == "walk") {
dir[ 2 ] = 0;
}
var length = Math.sqrt( dir[ 0 ] * dir[ 0 ] + dir[ 1 ] * dir[ 1 ] + dir[ 2 ] * dir[ 2 ] );
if ( length ) {
goog.vec.Vec3.normalize( dir, dir );
// Extract the navObject world position so we can add to it
var navObjectWorldTransformMatrixArray = navThreeObject.matrixWorld.elements;
var navObjectWorldPos = [ navObjectWorldTransformMatrixArray[ 12 ],
navObjectWorldTransformMatrixArray[ 13 ],
navObjectWorldTransformMatrixArray[ 14 ] ];
// Take the direction and apply a calculated magnitude
// to that direction to compute the displacement vector
var deltaTranslation = goog.vec.Vec3.scale( dir, dist, goog.vec.Vec3.create() );
// Add the displacement to the current navObject position
goog.vec.Vec3.add( navObjectWorldPos, deltaTranslation, navObjectWorldPos );
if ( boundingBox != undefined ) {
if ( navObjectWorldPos[ 0 ] < boundingBox[ 0 ][ 0 ] ) {
navObjectWorldPos[ 0 ] = boundingBox[ 0 ][ 0 ];
}
else if ( navObjectWorldPos[ 0 ] > boundingBox[ 0 ][ 1 ] ) {
navObjectWorldPos[ 0 ] = boundingBox[ 0 ][ 1 ];
}
if ( navObjectWorldPos[ 1 ] < boundingBox[ 1 ][ 0 ] ) {
navObjectWorldPos[ 1 ] = boundingBox[ 1 ][ 0 ];
}
else if ( navObjectWorldPos[ 1 ] > boundingBox[ 1 ][ 1 ] ) {
navObjectWorldPos[ 1 ] = boundingBox[ 1 ][ 1 ];
}
if ( navObjectWorldPos[ 2 ] < boundingBox[ 2 ][ 0 ] ) {
navObjectWorldPos[ 2 ] = boundingBox[ 2 ][ 0 ];
}
else if ( navObjectWorldPos[ 2 ] > boundingBox[ 2 ][ 1 ] ) {
navObjectWorldPos[ 2 ] = boundingBox[ 2 ][ 1 ];
}
}
// We are about to use the translationMatrix, so let's update it
extractRotationAndTranslation( navObject.threeObject );
// Insert the new navObject position into the translation array
var translationArray = translationMatrix.elements;
translationArray[ 12 ] = navObjectWorldPos [ 0 ];
translationArray[ 13 ] = navObjectWorldPos [ 1 ];
translationArray[ 14 ] = navObjectWorldPos [ 2 ];
// Since this translation already accounts for pitch and yaw, insert it directly into the navObject
// transform
navObjectWorldTransformMatrixArray[ 12 ] = navObjectWorldPos [ 0 ];
navObjectWorldTransformMatrixArray[ 13 ] = navObjectWorldPos [ 1 ];
navObjectWorldTransformMatrixArray[ 14 ] = navObjectWorldPos [ 2 ];
}
}
this.rotateNavObjectByKey = function( direction, navObj, navMode, rotationSpeed, translationSpeed, msSinceLastFrame ) {
var navThreeObject = navObj.threeObject;
// Compute the distance rotated in the elapsed time
// Constrain the time to be less than 0.5 seconds, so that if a user has a very low frame rate,
// one key press doesn't send them off in space
var theta = direction * ( rotationSpeed * degreesToRadians ) *
Math.min( msSinceLastFrame * 0.001, 0.5 );
var orbiting = ( navMode == "fly" ) && mouseDown.middle && positionUnderMouseClick;
if ( orbiting ) {
var pitchRadians = 0;
var yawRadians = -theta;
orbit( pitchRadians, yawRadians );
} else {
// We will soon want to use the yawMatrix and pitchMatrix,
// so let's update them
extractRotationAndTranslation( navObject.threeObject );
var cos = Math.cos( theta );
var sin = Math.sin( theta );
var rotation = [ cos, sin, 0, 0,
-sin, cos, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1 ];
// Left multiply the current transform matrix by the rotation transform
// and assign the result back to the navObject's transform
var yawArray = yawMatrix.elements;
// Perform the rotation
goog.vec.Mat4.multMat( rotation, yawArray, yawArray );
// Construct the new transform from pitch, yaw, and translation
var navObjectWorldTransform = navThreeObject.matrixWorld;
navObjectWorldTransform.multiplyMatrices( yawMatrix, pitchMatrix );
navObjectWorldTransform.multiplyMatrices( translationMatrix, navObjectWorldTransform );
if ( navThreeObject instanceof THREE.Camera ) {
var navObjectWorldTransformArray = navObjectWorldTransform.elements;
navObjectWorldTransform.elements = convertCameraTransformFromVWFtoThreejs( navObjectWorldTransformArray );
}
}
}
var handleKeyNavigation = function( keyCode, keyIsDown ) {
var key = getKeyValue( keyCode ).key;
key = key && key.toLowerCase();
switch ( self.navigationKeyMapping[ key ] ) {
case "forward":
movingForward = keyIsDown;
break;
case "back":
movingBack = keyIsDown;
break;
case "left":
movingLeft = keyIsDown;
break;
case "right":
movingRight = keyIsDown;
break;
case "rotateLeft":
rotatingLeft = keyIsDown;
break;
case "rotateRight":
rotatingRight = keyIsDown;
break;
}
}
// END TODO
// == Draggable Content ========================================================================
// canvas.addEventListener( "dragenter", function( e ) {
// e.stopPropagation();
// e.preventDefault();
// }, false );
// canvas.addEventListener( "dragexit", function( e ) {
// e.stopPropagation();
// e.preventDefault();
// }, false );
// -- dragOver ---------------------------------------------------------------------------------
canvas.ondragover = function( e ) {
self.mouseOverCanvas = true;
sceneCanvas.mouseX=e.clientX;
sceneCanvas.mouseY=e.clientY;
var eData = getEventData( e, false );
if ( eData ) {
e.dataTransfer.dropEffect = "copy";
}
e.preventDefault();
};
// -- drop ---------------------------------------------------------------------------------
canvas.ondrop = function( e ) {
e.preventDefault();
var eData = getEventData( e, false );
if ( eData ) {
var fileData, fileName, fileUrl, rotation, scale, translation, match, object;
try {
fileData = JSON.parse( e.dataTransfer.getData('text/plain') );
fileName = decodeURIComponent(fileData.fileName);
fileUrl = decodeURIComponent(fileData.fileUrl);
rotation = decodeURIComponent(fileData.rotation);
rotation = rotation ? JSON.parse(rotation) : undefined;
scale = decodeURIComponent(fileData.scale);
scale = scale ? JSON.parse(scale) : [1, 1, 1];
translation = decodeURIComponent(fileData.translation);
translation = translation ? JSON.parse(translation) : [0, 0, 0];
if($.isArray(translation) && translation.length == 3) {
translation[0] += eData.eventNodeData[""][0].globalPosition[0];
translation[1] += eData.eventNodeData[""][0].globalPosition[1];
translation[2] += eData.eventNodeData[""][0].globalPosition[2];
}
else {
translation = eData.eventNodeData[""][0].globalPosition;
}
if ( match = /* assignment! */ fileUrl.match( /(.*\.vwf)\.(json|yaml)$/i ) ) {
object = {
extends: match[1],
properties: {
translation: translation,
rotation : rotation,
scale: scale,
},
};
fileName = fileName.replace( /\.(json|yaml)$/i, "" );
} else if ( match = /* assignment! */ fileUrl.match( /\.dae$/i ) ) {
object = {
extends: "http://vwf.example.com/node3.vwf",
source: fileUrl,
type: "model/vnd.collada+xml",
properties: {
translation: translation,
rotation : rotation,
scale: scale,
},
};
}
if ( object ) {
sceneView.kernel.createChild( sceneView.kernel.application(), fileName, object );
}
} catch ( e ) {
// TODO: invalid JSON
}
}
};
};
// TODO: is this function needed?
// seems to be an exact copy of the ThreeJSPick
// should be tested and removed if this is not needed
function ThreeJSTouchPick ( canvas, sceneNode, mousepos )
{
if(!this.lastEventData) return;
var threeCam = this.state.cameraInUse;
if ( !threeCam ) {
this.logger.errorx( "Cannot perform pick because there is no camera to pick from" );
return;
}
var intersects = undefined;
var mousepos = {
"x": this.lastEventData.eventData[0].position[0],
"y": this.lastEventData.eventData[0].position[1]
}; // window coordinates
var x = ( mousepos.x ) * 2 - 1;
var y = -( mousepos.y ) * 2 + 1;
pickDirection.set( x, y, 0.5 );
var camPos = new THREE.Vector3(
threeCam.matrixWorld.elements[ 12 ],
threeCam.matrixWorld.elements[ 13 ],
threeCam.matrixWorld.elements[ 14 ]
);
pickDirection.unproject( threeCam );
raycaster.ray.set( camPos, pickDirection.sub( camPos ).normalize() );
intersects = raycaster.intersectObjects( sceneNode.threeScene.children, true );
// Cycle through the list of intersected objects and return the first visible one
for ( var i = 0; i < intersects.length; i++ ) {
if ( intersects[ i ].object.visible ) {
if ( getPickObjectID( intersects[ i ].object ) !== null ) {
return intersects[ i ];
}
}
}
return null;
}
function ThreeJSPick( canvas, sceneNode, debug )
{
if(!this.lastEventData) return;
var threeCam = this.state.cameraInUse;
if ( !threeCam ) {
this.logger.errorx( "Cannot perform pick because there is no camera to pick from" );
return;
}
var intersects = undefined;
var target = undefined;
var mousepos = {
"x": this.lastEventData.eventData[0].position[0],
"y": this.lastEventData.eventData[0].position[1]
}; // window coordinates
var x = ( mousepos.x ) * 2 - 1;
var y = -( mousepos.y ) * 2 + 1;
pickDirection.set( x, y, 0.5 );
var camPos = new THREE.Vector3(
threeCam.matrixWorld.elements[ 12 ],
threeCam.matrixWorld.elements[ 13 ],
threeCam.matrixWorld.elements[ 14 ]
);
pickDirection.unproject( threeCam );
raycaster.ray.set( camPos, pickDirection.sub( camPos ).normalize() );
intersects = raycaster.intersectObjects( sceneNode.threeScene.children, true );
// Cycle through the list of intersected objects and return the first visible one
for ( var i = 0; i < intersects.length && target === undefined; i++ ) {
if ( debug ) {
for ( var j = 0; j < intersects.length; j++ ) {
console.info( j + ". " + getPickObjectID( intersects[ j ].object ) );
}
}
if ( intersects[ i ].object.visible ) {
if ( getPickObjectID( intersects[ i ].object ) !== null ) {
target = intersects[ i ];
}
}
}
return target;
}
function getPickObjectID(threeObject)
{
if(threeObject.vwfID)
return threeObject.vwfID;
else if(threeObject.parent)
return getPickObjectID(threeObject.parent);
return null;
}
function vec3ToArray( vec ) {
return [ vec.x, vec.y, vec.z ];
}
function indentStr() {
return " ";
}
function indent(iIndent) {
var sOut = "";
for ( var j = 0; j < iIndent; j++ ) {
sOut = sOut + indentStr.call( this );
}
return sOut;
}
function indent2(iIndent) {
var sOut = "";
var idt = indentStr.call( this )
for ( var j = 0; j < iIndent; j++ ) {
sOut = sOut + idt + idt;
}
return sOut;
}
function getObjectType( object3 ) {
var type = "object3D";
if ( object3 instanceof THREE.Camera ) {
type = "camera"
} else if ( object3 instanceof THREE.Light ) {
type = "light"
} else if ( object3 instanceof THREE.Mesh ) {
type = "mesh"
} else if ( object3 instanceof THREE.Scene ) {
type = "scene";
}
return type;
}
function getExtendType( object3 ) {
var exts = "extends: http://vwf.example.com/node3.vwf";
if ( object3 instanceof THREE.Camera ) {
exts = "extends: http://vwf.example.com/camera.vwf"
} else if ( object3 instanceof THREE.Light ) {
exts = "extends: http://vwf.example.com/light.vwf"
}
return exts;
}
function consoleOut( msg ) {
console.info( msg );
//this.logger.info( msg );
}
function getBindableCount( object3 ) {
var count = 0, tp ;
if ( object3 instanceof THREE.Mesh ){
count++;
}
for ( var i = 0; i < object3.children.length; i++ ) {
tp = getObjectType.call( this, object3.children[i] );
if ( object3.children[i].name != "" ) {
count++;
}
}
//consoleOut.call( this, count + " = getBindableCount( "+object3.name+" )");
return count;
}
function recurseJsonObject3D( object3, parentName, depth ) {
var tp = getObjectType.call( this, object3 );
if ( object3 && object3.name != "" ) {
var sOut = indent.call( this, depth );
var sIndent = indent.call( this, depth+1 );
var bindCount = ( object3.children !== undefined ) ? getBindableCount.call( this, object3 ) : 0;
consoleOut.call( this, sOut + object3.name + ": {");
consoleOut.call( this, sIndent + getExtendType.call( this, object3 ) );
if ( bindCount > 0 ) {
var recursedCount = 0;
consoleOut.call( this, sIndent + "children: {" );
for ( var i = 0; i < object3.children.length; i++ ) {
depth++;
recurseJsonObject3D.call( this, object3.children[i], object3.name, depth + 1 );
depth--;
recursedCount++;
}
if ( tp == "mesh" ) {
outputJsonMaterial.call( this, depth+2, 0 );
}
consoleOut.call( this, sIndent + "}," );
}
consoleOut.call( this, sOut + "}," );
}
}
function outputJsonMaterial( iIndent, index ) {
var sOut = indent.call( this, iIndent + 1 );
consoleOut.call( this, indent.call( this, iIndent) + "material" + ( index > 0 ? index : "" ) + ": {" );
consoleOut.call( this, sOut + "extends: http://vwf.example.com/material.vwf" );
consoleOut.call( this, indent.call( this, iIndent) + "}," );
}
function outputObject3D( object3, parentName, iIndent ) {
var sOut = indent.call( this, iIndent + 1);
var tp = getObjectType.call( this, object3 );
var bindCount = ( object3.children !== undefined ) ? getBindableCount.call( this, object3 ) : 0;
if ( object3.name != "" ) {
consoleOut.call( this, indent.call( this, iIndent ) + object3.name + ":");
consoleOut.call( this, sOut + getExtendType.call( this, object3 ) );
if ( bindCount > 0 ) {
consoleOut.call( this, sOut + "children: " );
if ( tp == "mesh" ) {
// need to check the multimaterial list here
outputMaterial.call( this, iIndent + 2, 0 );
}
}
}
}
function recurseObject3D( object3, parentName, depth ) {
var tp = getObjectType.call( this, object3 );
if ( object3 ) {
var sOut = indent.call( this, depth );
outputObject3D.call( this, object3, parentName, depth );
if ( getBindableCount.call( this, object3 ) > 0 ) {
for ( var i = 0; i < object3.children.length; i++ ) {
depth++;
recurseObject3D.call( this, object3.children[i], object3.name, depth + 1 );
depth--;
}
}
}
}
function getWorldTransform( node ) {
var parent = self.state.nodes[ node.parentID ];
if ( parent ) {
var worldTransform = new THREE.Matrix4();
if ( node.transform === undefined ) {
node.transform = new THREE.Matrix4();
}
return worldTransform.multiplyMatrices( getWorldTransform( parent ), node.transform );
} else {
return node.transform;
}
}
function setWorldTransform( node, worldTransform ) {
if ( node.parent ) {
var parentInverse = goog.vec.Mat4.create();
if ( goog.vec.Mat4.invert( getWorldTransform( node.parent ), parentInverse ) ) {
node.transform = goog.vec.Mat4.multMat( parentInverse, worldTransform,
goog.vec.Mat4.create() );
} else {
self.logger.errorx( "Parent world transform is not invertible - did not set world transform " +
"on node '" + node.id + "'" );
}
} else {
node.transform = worldTransform;
}
}
function outputMaterial( iIndent, index ) {
var sOut = indent.call( this, iIndent + 1 );
consoleOut.call( this, indent.call( this, iIndent) + "material" + ( index > 0 ? index : "" ) + ":" );
consoleOut.call( this, sOut + "extends: http://vwf.example.com/material.vwf" );
}
function consoleObject( object3, depth ) {
consoleOut.call( this, indent2.call( this, depth ) + object3.name + " -> " + " type = " + getObjectType.call( this, object3 ) );
}
function consoleScene( parent, depth ) {
consoleObject.call( this, parent, depth );
for ( var i = 0; i < parent.children.length; i++ ) {
consoleScene.call( this, parent.children[i], depth+1 );
}
}
function getKeyValue( keyCode ) {
var key = { key: undefined, code: keyCode, char: undefined };
switch ( keyCode ) {
case 8:
key.key = "backspace";
break;
case 9:
key.key = "tab";
break;
case 13:
key.key = "enter";
break;
case 16:
key.key = "shift";
break;
case 17:
key.key = "ctrl";
break;
case 18:
key = "alt";
break;
case 19:
key.key = "pausebreak";
break;
case 20:
key.key = "capslock";
break;
case 27:
key.key = "escape";
break;
case 33:
key.key = "pageup";
break;
case 34:
key.key = "pagedown";
break;
case 35:
key.key = "end";
break;
case 36:
key.key = "home";
break;
case 37:
key.key = "leftarrow";
break;
case 38:
key.key = "uparrow";
break;
case 39:
key.key = "rightarrow";
break;
case 40:
key.key = "downarrow";
break;
case 45:
key.key = "insert";
break;
case 46:
key.key = "delete";
break;
case 48:
key.key = "0";
key.char = "0";
break;
case 49:
key.key = "1";
key.char = "1";
break;
case 50:
key.key = "2";
key.char = "2";
break;
case 51:
key.key = "3";
key.char = "3";
break;
case 52:
key.key = "4";
key.char = "4";
break;
case 53:
key.key = "5";
key.char = "5";
break;
case 54:
key.key = "6";
key.char = "6";
break;
case 55:
key.key = "7";
key.char = "7";
break;
case 56:
key.key = "8";
key.char = "8";
break;
case 57:
key.key = "9";
key.char = "9";
break;
case 65:
key.key = "A";
key.char = "A";
break;
case 66:
key.key = "B";
key.char = "B";
break;
case 67:
key.key = "C";
key.char = "C";
break;
case 68:
key.key = "D";
key.char = "D";
break;
case 69:
key.key = "E";
key.char = "E";
break;
case 70:
key.key = "F";
key.char = "F";
break;
case 71:
key.key = "G";
key.char = "G";
break;
case 72:
key.key = "H";
key.char = "H";
break;
case 73:
key.key = "I";
key.char = "I";
break;
case 74:
key.key = "J";
key.char = "J";
break;
case 75:
key.key = "K";
key.char = "K";
break;
case 76:
key.key = "L";
key.char = "L";
break;
case 77:
key.key = "M";
key.char = "M";
break;
case 78:
key.key = "N";
key.char = "N";
break;
case 79:
key.key = "O";
key.char = "O";
break;
case 80:
key.key = "P";
key.char = "P";
break;
case 81:
key.key = "Q";
key.char = "Q";
break;
case 82:
key.key = "R";
key.char = "R";
break;
case 83:
key.key = "S";
key.char = "S";
break;
case 84:
key.key = "T";
key.char = "T";
break;
case 85:
key.key = "U";
key.char = "U";
break;
case 86:
key.key = "V";
key.char = "V";
break;
case 87:
key.key = "W";
key.char = "W";
break;
case 88:
key.key = "X";
key.char = "X";
break;
case 89:
key.key = "Y";
key.char = "Y";
break;
case 90:
key.key = "Z";
key.char = "Z";
break;
case 91:
key.key = "leftwindow";
break;
case 92:
key.key = "rightwindow";
break;
case 93:
key.key = "select";
break;
case 96:
key.key = "numpad0";
key.char = "0";
break;
case 97:
key.key = "numpad1";
key.char = "1";
break;
case 98:
key.key = "numpad2";
key.char = "2";
break;
case 99:
key.key = "numpad3";
key.char = "3";
break;
case 100:
key.key = "numpad4";
key.char = "4";
break;
case 101:
key.key = "numpad5";
key.char = "5";
break;
case 102:
key.key = "numpad6";
key.char = "6";
break;
case 103:
key.key = "numpad7";
key.char = "7";
break;
case 104:
key.key = "numpad8";
key.char = "8";
break;
case 105:
key.key = "numpad9";
key.char = "9";
break;
case 106:
key.key = "multiply";
key.char = "*";
break;
case 107:
key.key = "add";
key.char = "+";
break;
case 109:
key.key = "subtract";
key.char = "-";
break;
case 110:
key.key = "decimalpoint";
key.char = ".";
break;
case 111:
key.key = "divide";
key.char = "/";
break;
case 112:
key.key = "f1";
break;
case 113:
key.key = "f2";
break;
case 114:
key.key = "f3";
break;
case 115:
key.key = "f4";
break;
case 116:
key.key = "f5";
break;
case 117:
key.key = "f6";
break;
case 118:
key.key = "f7";
break;
case 119:
key.key = "f8";
break;
case 120:
key.key = "f9";
break;
case 121:
key.key = "f10";
break;
case 122:
key.key = "f11";
break;
case 123:
key.key = "f12";
break;
case 144:
key.key = "numlock";
break;
case 145:
key.key = "scrolllock";
break;
case 186:
key.key = "semicolon";
key.char = ";";
break;
case 187:
key.key = "equal";
key.char = "=";
break;
case 188:
key.key = "comma";
key.char = ",";
break;
case 189:
key.key = "dash";
key.char = "-";
break;
case 190:
key.key = "period";
key.char = ".";
break;
case 191:
key.key = "forwardslash";
key.char = "/";
break;
case 192:
key.key = "graveaccent";
break;
case 219:
key.key = "openbracket";
key.char = "{";
break;
case 220:
key.key = "backslash";
key.char = "\\";
break;
case 221:
key.key = "closebracket";
key.char = "}";
break;
case 222:
key.key = "singlequote";
key.char = "'";
break;
case 32:
key.key = "space";
key.char = " ";
break;
}
return key;
}
function controlNavObject( node ) {
if ( !node ) {
self.logger.error( "Attempted to control non-existent navigation object" );
return;
}
// If there is already a navObject, make that object opaque if we had made it transparent
if ( navObject && !makeOwnAvatarVisible ) {
setVisibleRecursively( navObject.threeObject, true );
}
// Set the new navigation object
navObject = node;
// Set the 3D model transparent if requested
if ( !makeOwnAvatarVisible ) {
setVisibleRecursively( navObject.threeObject, false );
}
// TODO: The model should keep track of a shared navObject, not just the shared camera that it tracks now. See Redmine #3145.
if( !usersShareView ) {
// Search for a camera in the navigation object and if it exists, make it active
var cameraIds = self.kernel.find( navObject.ID,
"descendant-or-self::element(*,'http://vwf.example.com/camera.vwf')" );
if ( cameraIds.length ) {
// Set the view's active camera
var rendererState = self.state;
var cameraId = cameraIds[ 0 ];
cameraNode = rendererState.nodes[ cameraId ];
rendererState.cameraInUse = cameraNode.threeObject;
}
}
// Request properties from the navigation object
vwf_view.kernel.getProperty( navObject.ID, "navmode" );
vwf_view.kernel.getProperty( navObject.ID, "touchmode" );
vwf_view.kernel.getProperty( navObject.ID, "translationSpeed" );
vwf_view.kernel.getProperty( navObject.ID, "rotationSpeed" );
}
function findNavObject() {
// Find the navigable objects in the scene
var sceneRootID = self.kernel.application();
var navObjectIds = self.kernel.find( sceneRootID,
".//element(*,'http://vwf.example.com/navigable.vwf')" );
numNavCandidates = navObjectIds.length;
// If there are navigation objects in the scene, get their owner property values (The rest of the logic
// of choosing the correct navigation object is in the gotProperty call for owner)
// Else, retrieve the userObject property so we may create a navigation object from it for this user
if ( numNavCandidates ) {
for ( var i = 0; i < numNavCandidates; i++ ) {
vwf_view.kernel.getProperty( navObjectIds[ i ], "owner" );
}
} else {
vwf_view.kernel.getProperty( sceneRootID, "makeOwnAvatarVisible" );
vwf_view.kernel.getProperty( sceneRootID, "boundingBox" );
vwf_view.kernel.getProperty( sceneRootID, "userObject" );
userObjectRequested = true;
}
}
function inputHandleMouseNavigation( mouseEventData ) {
var deltaX = 0;
var deltaY = 0;
if ( pointerLocked ) {
deltaX = mouseEventData.movementX / self.width;
deltaY = mouseEventData.movementY / self.height;
} else if ( startMousePosition ) {
var currentMousePosition = mouseEventData[ 0 ].position;
deltaX = currentMousePosition[ 0 ] - startMousePosition [ 0 ];
deltaY = currentMousePosition[ 1 ] - startMousePosition [ 1 ];
}
if ( deltaX || deltaY ) {
if ( navObject ) {
var navThreeObject = navObject.threeObject;
var originalTransform = goog.vec.Mat4.clone( navThreeObject.matrix.elements );
self.handleMouseNavigation( deltaX, deltaY, navObject, navmode,
rotationSpeed, translationSpeed, mouseDown, mouseEventData );
setTransformFromWorldTransform( navThreeObject );
callModelTransformBy( navObject, originalTransform, navThreeObject.matrix.elements );
} else {
self.logger.warnx( "handleMouseNavigation: There is no navigation object to move" );
}
startMousePosition = currentMousePosition;
}
}
function inputHandleScroll( wheelDelta, distanceToTarget ) {
if ( navObject && navObject.threeObject ) {
var navThreeObject = navObject.threeObject;
var originalTransform = goog.vec.Mat4.clone( navThreeObject.matrix.elements );
self.handleScroll( wheelDelta, navObject, navmode, rotationSpeed, translationSpeed, distanceToTarget );
setTransformFromWorldTransform( navThreeObject );
callModelTransformBy( navObject, originalTransform, navThreeObject.matrix.elements );
}
}
function inputMoveNavObject( msSinceLastFrame ) {
var x = 0;
var y = 0;
// Calculate the movement increments
if ( movingForward )
y += 1;
if ( movingBack )
y -= 1;
if ( movingLeft )
x -= 1;
if ( movingRight )
x += 1;
// If there is no movement since last frame, return
if ( ! ( x || y ) )
return;
if ( navObject ) {
var navThreeObject = navObject.threeObject;
var originalTransform = goog.vec.Mat4.clone( navThreeObject.matrix.elements );
self.moveNavObject( x, y, navObject, navmode, rotationSpeed, translationSpeed, msSinceLastFrame );
// Update the navigation object's local transform from its new world transform
setTransformFromWorldTransform( navThreeObject );
callModelTransformBy( navObject, originalTransform, navThreeObject.matrix.elements );
} else {
self.logger.warnx( "moveNavObject: There is no navigation object to move" );
}
}
function inputRotateNavObjectByKey( msSinceLastFrame ) {
var direction = 0;
// Calculate movement increment
if ( rotatingLeft )
direction += 1;
if ( rotatingRight )
direction -= 1;
// If there is no rotation this frame, return
if ( !direction )
return;
if ( navObject ) {
var navThreeObject = navObject.threeObject;
var originalTransform = goog.vec.Mat4.clone( navThreeObject.matrix.elements );
self.rotateNavObjectByKey( direction, navObject, navmode,
rotationSpeed, translationSpeed, msSinceLastFrame );
// Force the navObject's world transform to update from its local transform
setTransformFromWorldTransform( navThreeObject );
callModelTransformBy( navObject, originalTransform, navThreeObject.matrix.elements );
} else {
self.logger.warnx( "rotateNavObjectByKey: There is no navigation object to move" );
}
}
// Receive Model Transform Changes algorithm
// 1.0 If (own view changes) then IGNORE (only if no external changes have occurred since the user’s view
// requested this change – otherwise, will need to treat like 1.1 or 1.2)
// 1.1 Elseif (other external changes and no outstanding own view changes) then ADOPT
// 1.2 Else Interpolate to the model’s transform (conflict b/w own view and external sourced model changes)
function receiveModelTransformChanges( nodeID, transformMatrix ) {
var node = self.state.nodes[ nodeID ];
// If the node does not exist in the state's list of nodes, then this update is from a prototype and we
// should ignore it
if ( !node ) {
return;
}
// If the transform property was initially updated by this view....
if ( node.ignoreNextTransformUpdate ) {
node.outstandingTransformRequests.shift();
node.ignoreNextTransformUpdate = false;
} else { // this transform change request is not from me
adoptTransform( node, transformMatrix );
if ( node.outstandingTransformRequests ) {
var threeObject = node.threeObject;
for ( var i = 0; i < node.outstandingTransformRequests.length; i++ ) {
goog.vec.Mat4.multMat( node.outstandingTransformRequests[ i ],
threeObject.matrix.elements,
threeObject.matrix.elements );
}
updateRenderObjectTransform( threeObject );
}
}
}
function adoptTransform ( node, transform ) {
var transformMatrix = goog.vec.Mat4.clone( transform );
var threeObject = node.threeObject;
if ( threeObject instanceof THREE.Camera ) {
transformMatrix = convertCameraTransformFromVWFtoThreejs( transformMatrix );
} else if( threeObject instanceof THREE.PointCloud ) {
// I don't see where this function is defined. Maybe a copy-paste bug from
// GLGE driver? - Eric (5/13/13)
threeObject.updateTransform( transformMatrix );
}
threeObject.matrix.elements = transformMatrix;
updateRenderObjectTransform( threeObject );
nodeLookAt( node );
}
function callModelTransformBy( node, originalViewTransform, goalViewTransform ) {
var nodeID = node.ID;
if ( nodeID ) {
var inverseOriginalViewTransform = goog.vec.Mat4.createFloat32();
if ( goog.vec.Mat4.invert( originalViewTransform, inverseOriginalViewTransform ) ) {
var deltaViewTransform = goog.vec.Mat4.multMat( goalViewTransform, inverseOriginalViewTransform,
goog.vec.Mat4.createFloat32() );
var deltaModelTransform;
if ( node.threeObject instanceof THREE.Camera ) {
var originalModelTransform = convertCameraTransformFromThreejsToVWF( originalViewTransform );
var goalModelTransform = convertCameraTransformFromThreejsToVWF( goalViewTransform );
var inverseOriginalModelTransform = goog.vec.Mat4.createFloat32();
if ( goog.vec.Mat4.invert( originalModelTransform, inverseOriginalModelTransform ) ) {
deltaModelTransform = goog.vec.Mat4.multMat( goalModelTransform,
inverseOriginalModelTransform,
goog.vec.Mat4.createFloat32() );
} else {
self.logger.errorx( "callModelTransformBy: Original model transform is not invertible" );
}
} else {
deltaModelTransform = deltaViewTransform;
}
vwf_view.kernel.fireEvent( nodeID, "changingTransformFromView");
vwf_view.kernel.callMethod( nodeID, "transformBy", [ deltaModelTransform ] );
node.outstandingTransformRequests = node.outstandingTransformRequests || [];
node.outstandingTransformRequests.push( deltaViewTransform );
} else {
self.logger.errorx( "callModelTransformBy: Original view transform is not invertible" );
}
} else {
self.logger.errorx( "callModelTransformBy: Cannot set property on node that does not have a " +
"valid ID" );
}
}
function setTransformFromWorldTransform( threeObject ) {
if ( !threeObject ) {
self.logger.warnx( "setTransformFromWorldTransform: There is no threeObject to update" );
return;
}
var parent = threeObject.parent;
if ( parent ) {
var inverseParentWorldMatrix = new THREE.Matrix4();
inverseParentWorldMatrix.getInverse( parent.matrixWorld );
threeObject.matrix.multiplyMatrices( inverseParentWorldMatrix, threeObject.matrixWorld );
} else {
threeObject.matrix.elements = goog.vec.Mat4.clone( threeObject.matrixWorld.elements );
}
updateRenderObjectTransform( threeObject );
}
function updateRenderObjectTransform( threeObject ) {
// Tell three.js not to update the transform matrix from position and rotation values (which are older)
threeObject.matrixAutoUpdate = false;
// Update the object's world transform
threeObject.updateMatrixWorld( true );
}
// Function to make the object continuously look at a position or node
// (for use when setting 'transform' or 'lookAt')
// An almost identical function is copied in model/threejs.js, so if any modifications are made here, they
// should be made there, also
function nodeLookAt( node ) {
if ( !node ) {
self.logger.warnx( "nodeLookAt: Node does not exist" );
return;
}
// Function to make the object look at a particular position
// (For use in the following conditional)
var lookAtWorldPosition = function( targetWorldPos ) {
// Get the eye position
var eye = new THREE.Vector3();
var threeObject = node.threeObject;
eye.setFromMatrixPosition( threeObject.matrixWorld );
var look = new THREE.Vector3();
look.subVectors( targetWorldPos, eye );
if ( look.length() > 0 ) {
look.normalize();
// Set the up vector to be z
var roughlyUp = new THREE.Vector3();
roughlyUp.set( 0, 0, 1 );
var right = new THREE.Vector3();
right.crossVectors( look, roughlyUp );
if ( right.length() == 0 ) {
look.x += 0.0001;
right.crossVectors( look, roughlyUp );
}
right.normalize();
var up = new THREE.Vector3();
up.crossVectors( right, look );
var worldTransform = threeObject.matrixWorld.elements;
worldTransform[ 0 ] = right.x; worldTransform[ 4 ] = look.x; worldTransform[ 8 ] = up.x;
worldTransform[ 1 ] = right.y; worldTransform[ 5 ] = look.y; worldTransform[ 9 ] = up.y;
worldTransform[ 2 ] = right.z; worldTransform[ 6 ] = look.z; worldTransform[ 10 ] = up.z;
setTransformFromWorldTransform( threeObject );
if ( threeObject instanceof THREE.Camera ) {
var nodeTransformArray = threeObject.matrix.elements;
threeObject.matrix.elements = convertCameraTransformFromVWFtoThreejs( nodeTransformArray );
updateRenderObjectTransform( threeObject );
}
}
}
// The position for the object to look at - to be set in the following conditional
var targetWorldPos = new THREE.Vector3();
//Threejs does not currently support auto tracking the lookat,
//instead, we'll take the position of the node and look at that.
if ( utility.isString( node.lookatval ) ) {
var lookatNode = self.state.nodes[ node.lookatval ];
if ( lookatNode )
{
targetWorldPos.setFromMatrixPosition( lookatNode.threeObject.matrixWorld );
lookAtWorldPosition( targetWorldPos );
}
} else if ( node.lookatval instanceof Array ) {
targetWorldPos.set( node.lookatval[0], node.lookatval[1], node.lookatval[2] );
lookAtWorldPosition( targetWorldPos );
}
}
function convertCameraTransformFromVWFtoThreejs( transform ) {
// Rotate 90 degrees around X to convert from VWF Z-up to three.js Y-up.
var newTransform = goog.vec.Mat4.clone( transform );
// Get column y and z out of the matrix
var columny = goog.vec.Vec4.create();
goog.vec.Mat4.getColumn( newTransform, 1, columny );
var columnz = goog.vec.Vec4.create();
goog.vec.Mat4.getColumn( newTransform, 2, columnz );
// Swap the two columns, negating columny
goog.vec.Mat4.setColumn( newTransform, 1, columnz );
goog.vec.Mat4.setColumn( newTransform, 2, goog.vec.Vec4.negate( columny, columny ) );
return newTransform;
}
function convertCameraTransformFromThreejsToVWF( transform ) {
// Rotate -90 degrees around X to convert from three.js Y-up to VWF Z-up.
var newTransform = goog.vec.Mat4.clone( transform );
// Get column y and z out of the matrix
var columny = goog.vec.Vec4.create();
goog.vec.Mat4.getColumn( newTransform, 1, columny );
var columnz = goog.vec.Vec4.create();
goog.vec.Mat4.getColumn( newTransform, 2, columnz );
// Swap the two columns, negating columnz
goog.vec.Mat4.setColumn( newTransform, 1, goog.vec.Vec4.negate( columnz, columnz ) );
goog.vec.Mat4.setColumn( newTransform, 2, columny );
return newTransform;
}
// TODO: This should be replaced with self.state.setMeshPropertyRecursively
function setVisibleRecursively( threeObject, visible ) {
if ( !threeObject ) {
return;
}
threeObject.visible = visible;
for ( var i = 0; i < threeObject.children.length; i++ ) {
setVisibleRecursively( threeObject.children[ i ], visible );
}
}
function extractRotationAndTranslation( threeObject ) {
// Pull the pitch, yaw, and translation out of the transform
var worldTransformArray = threeObject.matrixWorld.elements;
var vwfWorldTransformArray;
// If this threeObject is a camera, it has a 90-degree rotation on it to account for the different
// coordinate systems of VWF and three.js. We need to undo that rotation before using it as a VWF
// property.
// Else, just use the transform as-is
if ( threeObject instanceof THREE.Camera ) {
vwfWorldTransformArray = convertCameraTransformFromThreejsToVWF( worldTransformArray );
} else {
vwfWorldTransformArray = goog.vec.Mat4.clone( worldTransformArray );
}
pitchMatrix = new THREE.Matrix4();
var pitchArray = pitchMatrix.elements;
var costheta = vwfWorldTransformArray[ 10 ];
var sintheta = vwfWorldTransformArray[ 6 ];
pitchArray[ 5 ] = costheta;
pitchArray[ 6 ] = sintheta;
pitchArray[ 9 ] = -sintheta;
pitchArray[ 10 ] = costheta;
yawMatrix = new THREE.Matrix4();
var yawArray = yawMatrix.elements;
var cosphi = vwfWorldTransformArray[ 0 ];
var sinphi = vwfWorldTransformArray[ 1 ];
yawArray[ 0 ] = cosphi;
yawArray[ 1 ] = sinphi;
yawArray[ 4 ] = -sinphi;
yawArray[ 5 ] = cosphi;
translationMatrix = new THREE.Matrix4();
var translationArray = translationMatrix.elements;
translationArray[ 12 ] = vwfWorldTransformArray[ 12 ];
translationArray[ 13 ] = vwfWorldTransformArray[ 13 ];
translationArray[ 14 ] = vwfWorldTransformArray[ 14 ];
}
function orbit( pitchRadians, yawRadians ) {
if ( navObject ) {
// We can only orbit around a point if there is a point to orbit around
if ( positionUnderMouseClick ) {
// We will soon want to use the yawMatrix and pitchMatrix,
// so let's update them
extractRotationAndTranslation( navObject.threeObject );
var navThreeObject = navObject.threeObject;
var originalTransform = goog.vec.Mat4.clone( navThreeObject.matrix.elements );
var originalPitchMatrix = pitchMatrix.clone();
// --------------------
// Calculate new pitch
// --------------------
var pitchQuat = new THREE.Quaternion();
pitchQuat.setFromAxisAngle( new THREE.Vector3( 1, 0, 0 ), pitchRadians );
var pitchDeltaMatrix = new THREE.Matrix4();
pitchDeltaMatrix.makeRotationFromQuaternion( pitchQuat );
pitchMatrix.multiplyMatrices( pitchDeltaMatrix, pitchMatrix );
// Constrain the camera's pitch to +/- 90 degrees
// We need to do something if zAxis.z is < 0
var pitchMatrixElements = pitchMatrix.elements;
var pitchIsConstrained = false;
var zenithOrNadirMult = 0;
if ( pitchMatrixElements[ 10 ] < 0 ) {
var xAxis = goog.vec.Vec3.create();
xAxis = goog.vec.Vec3.setFromArray( xAxis, [ pitchMatrixElements[ 0 ],
pitchMatrixElements[ 1 ],
pitchMatrixElements[ 2 ] ] );
var yAxis = goog.vec.Vec3.create();
// If forward vector is tipped up
if ( pitchMatrixElements[ 6 ] > 0 ) {
yAxis = goog.vec.Vec3.setFromArray( yAxis, [ 0, 0, 1 ] );
} else {
yAxis = goog.vec.Vec3.setFromArray( yAxis, [ 0, 0, -1 ] );
}
// Calculate the zAxis as a crossProduct of x and y
var zAxis = goog.vec.Vec3.cross( xAxis, yAxis, goog.vec.Vec3.create() );
// Put these values back in the camera matrix
pitchMatrixElements[ 4 ] = yAxis[ 0 ];
pitchMatrixElements[ 5 ] = yAxis[ 1 ];
pitchMatrixElements[ 6 ] = yAxis[ 2 ];
pitchMatrixElements[ 8 ] = zAxis[ 0 ];
pitchMatrixElements[ 9 ] = zAxis[ 1 ];
pitchMatrixElements[ 10 ] = zAxis[ 2 ];
pitchIsConstrained = true;
zenithOrNadirMult = -yAxis[ 2 ];
}
// ------------------
// Calculate new yaw
// ------------------
var yawQuat = new THREE.Quaternion();
yawQuat.setFromAxisAngle( new THREE.Vector3( 0, 0, 1 ), yawRadians );
var yawDeltaMatrix = new THREE.Matrix4();
yawDeltaMatrix.makeRotationFromQuaternion( yawQuat );
yawMatrix.multiplyMatrices( yawDeltaMatrix, yawMatrix );
// --------------------------
// Calculate new translation
// --------------------------
if ( pitchIsConstrained ) {
var inverseOriginalPitchMatrix = new THREE.Matrix4();
inverseOriginalPitchMatrix.getInverse( originalPitchMatrix );
pitchDeltaMatrix.multiplyMatrices( pitchMatrix, inverseOriginalPitchMatrix );
}
var rotatedOrbitFrameInWorld = new THREE.Matrix4();
//rotatedOrbitFrameInWorld.multiplyMatrices( yawMatrix, pitchMatrix );
rotatedOrbitFrameInWorld = yawMatrix.clone();
rotatedOrbitFrameInWorld.setPosition( new THREE.Vector3( positionUnderMouseClick[ 0 ],
positionUnderMouseClick[ 1 ],
positionUnderMouseClick[ 2 ] ) );
var worldToRotatedOrbit = new THREE.Matrix4();
worldToRotatedOrbit.getInverse( rotatedOrbitFrameInWorld );
var translationInRotatedOrbitFrame = new THREE.Matrix4();
translationInRotatedOrbitFrame.multiplyMatrices( worldToRotatedOrbit, translationMatrix );
// Apply pitch and then yaw
translationInRotatedOrbitFrame.multiplyMatrices( pitchDeltaMatrix, translationInRotatedOrbitFrame );
translationInRotatedOrbitFrame.multiplyMatrices( yawDeltaMatrix, translationInRotatedOrbitFrame );
// Transform back to world
var newTranslationInWorld = new THREE.Matrix4();
newTranslationInWorld.multiplyMatrices( rotatedOrbitFrameInWorld, translationInRotatedOrbitFrame );
var translationArray = translationMatrix.elements;
var newTranslationInWorldArray = newTranslationInWorld.elements;
translationArray[ 12 ] = newTranslationInWorldArray[ 12 ];
translationArray[ 13 ] = newTranslationInWorldArray[ 13 ];
translationArray[ 14 ] = newTranslationInWorldArray[ 14 ];
var boundByBoundingBox = false;
if ( boundingBox != undefined ) {
if ( translationArray[ 12 ] < boundingBox[ 0 ][ 0 ] ) {
boundByBoundingBox = true;
}
else if ( translationArray[ 12 ] > boundingBox[ 0 ][ 1 ] ) {
boundByBoundingBox = true;
}
if ( translationArray[ 13 ] < boundingBox[ 1 ][ 0 ] ) {
boundByBoundingBox = true;
}
else if ( translationArray[ 13 ] > boundingBox[ 1 ][ 1 ] ) {
boundByBoundingBox = true;
}
if ( translationArray[ 14 ] < boundingBox[ 2 ][ 0 ] ) {
boundByBoundingBox = true;
}
else if ( translationArray[ 14 ] > boundingBox[ 2 ][ 1 ] ) {
boundByBoundingBox = true;
}
}
// -------------------------------------------------
// Put all components together and set the new pose
// -------------------------------------------------
if ( boundByBoundingBox == false ) {
var navObjectWorldMatrix = navThreeObject.matrixWorld;
navObjectWorldMatrix.multiplyMatrices( yawMatrix, pitchMatrix );
navObjectWorldMatrix.multiplyMatrices( translationMatrix, navObjectWorldMatrix );
if ( navThreeObject instanceof THREE.Camera ) {
var navObjWrldTrnsfmArr = navObjectWorldMatrix.elements;
navObjectWorldMatrix.elements = convertCameraTransformFromVWFtoThreejs( navObjWrldTrnsfmArr );
}
}
setTransformFromWorldTransform( navThreeObject );
callModelTransformBy( navObject, originalTransform, navThreeObject.matrix.elements );
}
} else {
self.logger.warnx( "orbit: There is no navigation object to move" );
}
}
function setActiveCamera( cameraID ) {
if ( usersShareView && this.state.nodes[ cameraID ] !== undefined ) {
var sceneID = this.kernel.application();
var sceneNode = this.state.scenes[ sceneID ];
//var cameras = this.kernel.find( sceneID, "./element(*,'http://vwf.example.com/camera.vwf')" );
cameraNode = this.state.nodes[ cameraID ];
this.state.cameraInUse = cameraNode.threeObject;
var canvas = this.canvasQuery[ 0 ];
this.state.cameraInUse.aspect = canvas.clientWidth / canvas.clientHeight;
}
}
function createControls( camera, element ) {
var controls;
if ( !isMobile() ) {
controls = new THREE.OrbitControls( camera, element );
//controls.rotateUp(Math.PI / 4);
controls.target.set(
camera.position.x + 0.1,
camera.position.y,
camera.position.z
);
controls.noZoom = true;
controls.noPan = true;
controls.autoRotate = false;
} else {
controls = new THREE.DeviceOrientationControls( camera, true );
controls.connect();
controls.update();
element.addEventListener( 'click', fullscreen, false );
}
return controls;
}
function fullscreen() {
var container = document.getElementById( "container" );
if ( container ) {
if ( container.requestFullscreen ) {
container.requestFullscreen();
} else if ( container.msRequestFullscreen ) {
container.msRequestFullscreen();
} else if ( container.mozRequestFullScreen ) {
container.mozRequestFullScreen();
} else if ( container.webkitRequestFullscreen ) {
container.webkitRequestFullscreen();
}
}
}
function isMobileAndroid() {
return navigator.userAgent.match(/Android/i);
}
function isMobileBlackBerry() {
return navigator.userAgent.match(/BlackBerry/i);
}
function isMobileiOS() {
return navigator.userAgent.match(/iPhone|iPad|iPod/i);
}
function isMobileOpera() {
return navigator.userAgent.match(/Opera Mini/i);
}
function isMobileWindows() {
return navigator.userAgent.match(/IEMobile/i);
}
function isMobile(){
return (isMobileAndroid() || isMobileBlackBerry() || isMobileiOS() || isMobileOpera() || isMobileWindows());
}
});