diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 0c042a1f22..bc8fd504ef 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -9,6 +9,7 @@ // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt, pointInExtents, vec3equal, setEntityCustomData, getEntityCustomData */ Script.include("../libraries/utils.js"); @@ -18,7 +19,8 @@ Script.include("../libraries/utils.js"); // var TRIGGER_SMOOTH_RATIO = 0.1; // 0.0 disables smoothing of trigger value -var TRIGGER_ON_VALUE = 0.2; +var TRIGGER_ON_VALUE = 0.4; +var TRIGGER_OFF_VALUE = 0.15; ///////////////////////////////////////////////////////////////// // @@ -53,7 +55,11 @@ var RELEASE_VELOCITY_MULTIPLIER = 1.5; // affects throwing things var RIGHT_HAND = 1; var LEFT_HAND = 0; -var ZERO_VEC = { x: 0, y: 0, z: 0}; +var ZERO_VEC = { + x: 0, + y: 0, + z: 0 +}; var NULL_ACTION_ID = "{00000000-0000-0000-000000000000}"; var MSEC_PER_SEC = 1000.0; @@ -88,9 +94,11 @@ function MyController(hand, triggerAction) { this.getHandRotation = MyAvatar.getLeftPalmRotation; } + var SPATIAL_CONTROLLERS_PER_PALM = 2; + var TIP_CONTROLLER_OFFSET = 1; this.triggerAction = triggerAction; - this.palm = 2 * hand; - // this.tip = 2 * hand + 1; // unused, but I'm leaving this here for fear it will be needed + this.palm = SPATIAL_CONTROLLERS_PER_PALM * hand; + this.tip = SPATIAL_CONTROLLERS_PER_PALM * hand + TIP_CONTROLLER_OFFSET; this.actionID = null; // action this script created... this.grabbedEntity = null; // on this entity. @@ -100,7 +108,8 @@ function MyController(hand, triggerAction) { this.triggerValue = 0; // rolling average of trigger value // HACK -- until we have collision groups, don't allow held object to collide with avatar - this.initialavatarCollisionsMenu = Menu.isOptionChecked(AVATAR_COLLISIONS_MENU_ITEM); + this.initialAvatarCollisionsMenu = Menu.isOptionChecked(AVATAR_COLLISIONS_MENU_ITEM); + this.currentAvatarCollisionsMenu = this.initialAvatarCollisionsMenu; var _this = this; @@ -113,6 +122,8 @@ function MyController(hand, triggerAction) { this.previousState = this.state; // XXX + this.updateSmoothedTrigger(); + switch (this.state) { case STATE_OFF: this.off(); @@ -145,6 +156,24 @@ function MyController(hand, triggerAction) { } }; + this.disableAvatarCollisions = function() { + if (this.currentAvatarCollisionsMenu != false) { + print("avatar collisions --> off"); + this.currentAvatarCollisionsMenu = false; + Menu.setIsOptionChecked(AVATAR_COLLISIONS_MENU_ITEM, false); + MyAvatar.updateMotionBehaviorFromMenu(); + } + } + + this.revertAvatarCollisions = function() { + if (this.currentAvatarCollisionsMenu != this.initialAvatarCollisionsMenu) { + print("avatar collisions --> revert"); + this.currentAvatarCollisionsMenu = this.initialAvatarCollisionsMenu; + Menu.setIsOptionChecked(AVATAR_COLLISIONS_MENU_ITEM, this.initialAvatarCollisionsMenu); + MyAvatar.updateMotionBehaviorFromMenu(); + } + } + this.lineOn = function(closePoint, farPoint, color) { // draw a line if (this.pointer === null) { @@ -176,14 +205,21 @@ function MyController(hand, triggerAction) { this.pointer = null; }; - this.triggerSmoothedSqueezed = function() { + this.updateSmoothedTrigger = function() { var triggerValue = Controller.getActionValue(this.triggerAction); // smooth out trigger value this.triggerValue = (this.triggerValue * TRIGGER_SMOOTH_RATIO) + (triggerValue * (1.0 - TRIGGER_SMOOTH_RATIO)); + } + + this.triggerSmoothedSqueezed = function() { return this.triggerValue > TRIGGER_ON_VALUE; }; + this.triggerSmoothedReleased = function() { + return this.triggerValue < TRIGGER_OFF_VALUE; + }; + this.triggerSqueezed = function() { var triggerValue = Controller.getActionValue(this.triggerAction); return triggerValue > TRIGGER_ON_VALUE; @@ -200,7 +236,7 @@ function MyController(hand, triggerAction) { } this.search = function() { - if (!this.triggerSmoothedSqueezed()) { + if (this.triggerSmoothedReleased()) { this.state = STATE_RELEASE; return; } @@ -271,22 +307,6 @@ function MyController(hand, triggerAction) { }; - this.disableAvatarCollisions = function() { - if (Menu.isOptionChecked(AVATAR_COLLISIONS_MENU_ITEM) != false) { - Menu.setIsOptionChecked(AVATAR_COLLISIONS_MENU_ITEM, false); - print("avatar collisions --> off"); - MyAvatar.updateMotionBehaviorFromMenu(); - } - } - - this.revertAvatarCollisions = function() { - if (Menu.isOptionChecked(AVATAR_COLLISIONS_MENU_ITEM) != this.initialavatarCollisionsMenu) { - Menu.setIsOptionChecked(AVATAR_COLLISIONS_MENU_ITEM, this.initialavatarCollisionsMenu); - print("avatar collisions --> revert"); - MyAvatar.updateMotionBehaviorFromMenu(); - } - } - this.distanceHolding = function() { // HACK -- until we have collision groups, don't allow held object to collide with avatar @@ -324,10 +344,13 @@ function MyController(hand, triggerAction) { Entities.callEntityMethod(this.grabbedEntity, "startDistantGrab"); } + this.currentAvatarPosition = MyAvatar.position; + this.currentAvatarOrientation = MyAvatar.orientation; + }; this.continueDistanceHolding = function() { - if (!this.triggerSmoothedSqueezed()) { + if (this.triggerSmoothedReleased()) { this.state = STATE_RELEASE; return; } @@ -342,11 +365,46 @@ function MyController(hand, triggerAction) { // the action was set up on a previous call. update the targets. var radius = Math.max(Vec3.distance(this.currentObjectPosition, handControllerPosition) * DISTANCE_HOLDING_RADIUS_FACTOR, DISTANCE_HOLDING_RADIUS_FACTOR); + // how far did avatar move this timestep? + var currentPosition = MyAvatar.position; + var avatarDeltaPosition = Vec3.subtract(currentPosition, this.currentAvatarPosition); + this.currentAvatarPosition = currentPosition; + + // How far did the avatar turn this timestep? + // Note: The following code is too long because we need a Quat.quatBetween() function + // that returns the minimum quaternion between two quaternions. + var currentOrientation = MyAvatar.orientation; + if (Quat.dot(currentOrientation, this.currentAvatarOrientation) < 0.0) { + var negativeCurrentOrientation = { + x: -currentOrientation.x, + y: -currentOrientation.y, + z: -currentOrientation.z, + w: -currentOrientation.w + }; + var avatarDeltaOrientation = Quat.multiply(negativeCurrentOrientation, Quat.inverse(this.currentAvatarOrientation)); + } else { + var avatarDeltaOrientation = Quat.multiply(currentOrientation, Quat.inverse(this.currentAvatarOrientation)); + } + var handToAvatar = Vec3.subtract(handControllerPosition, this.currentAvatarPosition); + var objectToAvatar = Vec3.subtract(this.currentObjectPosition, this.currentAvatarPosition); + var handMovementFromTurning = Vec3.subtract(Quat.multiply(avatarDeltaOrientation, handToAvatar), handToAvatar); + var objectMovementFromTurning = Vec3.subtract(Quat.multiply(avatarDeltaOrientation, objectToAvatar), objectToAvatar); + this.currentAvatarOrientation = currentOrientation; + + // how far did hand move this timestep? var handMoved = Vec3.subtract(handControllerPosition, this.handPreviousPosition); this.handPreviousPosition = handControllerPosition; + + // magnify the hand movement but not the change from avatar movement & rotation + handMoved = Vec3.subtract(handMoved, avatarDeltaPosition); + handMoved = Vec3.subtract(handMoved, handMovementFromTurning); var superHandMoved = Vec3.multiply(handMoved, radius); + // Move the object by the magnified amount and then by amount from avatar movement & rotation var newObjectPosition = Vec3.sum(this.currentObjectPosition, superHandMoved); + newObjectPosition = Vec3.sum(newObjectPosition, avatarDeltaPosition); + newObjectPosition = Vec3.sum(newObjectPosition, objectMovementFromTurning); + var deltaPosition = Vec3.subtract(newObjectPosition, this.currentObjectPosition); // meters var now = Date.now(); var deltaTime = (now - this.currentObjectTime) / MSEC_PER_SEC; // convert to seconds @@ -375,7 +433,7 @@ function MyController(hand, triggerAction) { // HACK -- until we have collision groups, don't allow held object to collide with avatar this.disableAvatarCollisions(); - if (!this.triggerSmoothedSqueezed()) { + if (this.triggerSmoothedReleased()) { this.state = STATE_RELEASE; return; } @@ -415,40 +473,52 @@ function MyController(hand, triggerAction) { } - this.currentHandControllerPosition = Controller.getSpatialControlPosition(this.palm); + this.currentHandControllerTipPosition = Controller.getSpatialControlPosition(this.tip); + this.currentObjectTime = Date.now(); }; this.continueNearGrabbing = function() { - if (!this.triggerSmoothedSqueezed()) { + if (this.triggerSmoothedReleased()) { this.state = STATE_RELEASE; return; } - // keep track of the measured velocity of the held object - var handControllerPosition = Controller.getSpatialControlPosition(this.palm); + // Keep track of the fingertip velocity to impart when we release the object + // Note that the idea of using a constant 'tip' velocity regardless of the + // object's actual held offset is an idea intended to make it easier to throw things: + // Because we might catch something or transfer it between hands without a good idea + // of it's actual offset, let's try imparting a velocity which is at a fixed radius + // from the palm. + + var handControllerPosition = Controller.getSpatialControlPosition(this.tip); var now = Date.now(); - var deltaPosition = Vec3.subtract(handControllerPosition, this.currentHandControllerPosition); // meters + var deltaPosition = Vec3.subtract(handControllerPosition, this.currentHandControllerTipPosition); // meters var deltaTime = (now - this.currentObjectTime) / MSEC_PER_SEC; // convert to seconds this.computeReleaseVelocity(deltaPosition, deltaTime, true); - this.currentHandControllerPosition = handControllerPosition; + this.currentHandControllerTipPosition = handControllerPosition; this.currentObjectTime = now; Entities.callEntityMethod(this.grabbedEntity, "continueNearGrab"); }; this.nearGrabbingNonColliding = function() { - if (!this.triggerSmoothedSqueezed()) { + if (this.triggerSmoothedReleased()) { this.state = STATE_RELEASE; return; } + if (this.hand === RIGHT_HAND) { + Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); + } else { + Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); + } Entities.callEntityMethod(this.grabbedEntity, "startNearGrabNonColliding"); this.state = STATE_CONTINUE_NEAR_GRABBING_NON_COLLIDING; }; this.continueNearGrabbingNonColliding = function() { - if (!this.triggerSmoothedSqueezed()) { + if (this.triggerSmoothedReleased()) { this.state = STATE_RELEASE; return; } @@ -511,17 +581,14 @@ function MyController(hand, triggerAction) { }; this.startTouch = function(entityID) { - // print('START TOUCH' + entityID); Entities.callEntityMethod(entityID, "startTouch"); }; this.continueTouch = function(entityID) { - // print('CONTINUE TOUCH' + entityID); Entities.callEntityMethod(entityID, "continueTouch"); }; this.stopTouch = function(entityID) { - // print('STOP TOUCH' + entityID); Entities.callEntityMethod(entityID, "stopTouch"); }; @@ -545,8 +612,10 @@ function MyController(hand, triggerAction) { this.release = function() { this.lineOff(); - if (this.grabbedEntity !== null && this.actionID !== null) { - Entities.deleteAction(this.grabbedEntity, this.actionID); + if (this.grabbedEntity !== null) { + if(this.actionID !== null) { + Entities.deleteAction(this.grabbedEntity, this.actionID); + } Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); } @@ -599,6 +668,3 @@ function cleanup() { Script.scriptEnding.connect(cleanup); Script.update.connect(update); - -// this comment keeps jslint quiet. -/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt, pointInExtents, vec3equal, setEntityCustomData, getEntityCustomData */ diff --git a/examples/example/hmd/colorCube.fs b/examples/example/hmd/colorCube.fs new file mode 100644 index 0000000000..2687b70807 --- /dev/null +++ b/examples/example/hmd/colorCube.fs @@ -0,0 +1,10 @@ + +float getProceduralColors(inout vec3 diffuse, inout vec3 specular, inout float shininess) { + + specular = _modelNormal.rgb; + if (any(lessThan(specular, vec3(0.0)))) { + specular = vec3(1.0) + specular; + } + diffuse = vec3(1.0, 1.0, 1.0); + return 1.0; +} \ No newline at end of file diff --git a/examples/example/hmd/colorCube.js b/examples/example/hmd/colorCube.js new file mode 100644 index 0000000000..a138f38190 --- /dev/null +++ b/examples/example/hmd/colorCube.js @@ -0,0 +1,42 @@ +function avatarRelativePosition(position) { + return Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, position)); +} + +ColorCube = function() {}; +ColorCube.prototype.NAME = "ColorCube"; +ColorCube.prototype.POSITION = { x: 0, y: 0.5, z: -0.5 }; +ColorCube.prototype.USER_DATA = { ProceduralEntity: { + version: 2, shaderUrl: Script.resolvePath("colorCube.fs"), +} }; + +// Clear any previous entities within 50 meters +ColorCube.prototype.clear = function() { + var ids = Entities.findEntities(MyAvatar.position, 50); + var that = this; + ids.forEach(function(id) { + var properties = Entities.getEntityProperties(id); + if (properties.name == that.NAME) { + Entities.deleteEntity(id); + } + }, this); +} + +ColorCube.prototype.create = function() { + var that = this; + var size = HMD.ipd; + var id = Entities.addEntity({ + type: "Box", + position: avatarRelativePosition(that.POSITION), + name: that.NAME, + color: that.COLOR, + ignoreCollisions: true, + collisionsWillMove: false, + dimensions: { x: size, y: size, z: size }, + lifetime: 3600, + userData: JSON.stringify(that.USER_DATA) + }); +} + +var colorCube = new ColorCube(); +colorCube.clear(); +colorCube.create(); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 16964735da..e3575e5bb9 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1764,6 +1764,12 @@ void MyAvatar::goToLocation(const glm::vec3& newPosition, } void MyAvatar::updateMotionBehaviorFromMenu() { + + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "updateMotionBehaviorFromMenu"); + return; + } + Menu* menu = Menu::getInstance(); if (menu->isOptionChecked(MenuOption::KeyboardMotorControl)) { _motionBehaviors |= AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED; diff --git a/interface/src/scripting/HMDScriptingInterface.cpp b/interface/src/scripting/HMDScriptingInterface.cpp index f65d638ccc..68ac511eaf 100644 --- a/interface/src/scripting/HMDScriptingInterface.cpp +++ b/interface/src/scripting/HMDScriptingInterface.cpp @@ -10,7 +10,7 @@ // #include "HMDScriptingInterface.h" - +#include "display-plugins/DisplayPlugin.h" #include HMDScriptingInterface& HMDScriptingInterface::getInstance() { @@ -53,3 +53,7 @@ QScriptValue HMDScriptingInterface::getHUDLookAtPosition3D(QScriptContext* conte } return QScriptValue::NullValue; } + +float HMDScriptingInterface::getIPD() const { + return Application::getInstance()->getActiveDisplayPlugin()->getIPD(); +} diff --git a/interface/src/scripting/HMDScriptingInterface.h b/interface/src/scripting/HMDScriptingInterface.h index 4bcced1fa2..82b444abaa 100644 --- a/interface/src/scripting/HMDScriptingInterface.h +++ b/interface/src/scripting/HMDScriptingInterface.h @@ -20,6 +20,7 @@ class HMDScriptingInterface : public QObject { Q_OBJECT Q_PROPERTY(bool magnifier READ getMagnifier) Q_PROPERTY(bool active READ isHMDMode) + Q_PROPERTY(float ipd READ getIPD) public: static HMDScriptingInterface& getInstance(); @@ -33,6 +34,7 @@ private: HMDScriptingInterface() {}; bool getMagnifier() const { return Application::getInstance()->getApplicationCompositor().hasMagnifier(); }; bool isHMDMode() const { return Application::getInstance()->isHMDMode(); } + float getIPD() const; bool getHUDLookAtPosition3D(glm::vec3& result) const; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 104b592c1a..3bd147d398 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -989,8 +989,11 @@ void AvatarData::setFaceModelURL(const QUrl& faceModelURL) { } void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) { - _skeletonModelURL = skeletonModelURL.isEmpty() ? AvatarData::defaultFullAvatarModelUrl() : skeletonModelURL; - + const QUrl& expanded = skeletonModelURL.isEmpty() ? AvatarData::defaultFullAvatarModelUrl() : skeletonModelURL; + if (expanded == _skeletonModelURL) { + return; + } + _skeletonModelURL = expanded; qCDebug(avatars) << "Changing skeleton model for avatar to" << _skeletonModelURL.toString(); updateJointMappings(); diff --git a/libraries/display-plugins/src/display-plugins/DisplayPlugin.h b/libraries/display-plugins/src/display-plugins/DisplayPlugin.h index 86cfabe724..8b9d249bd4 100644 --- a/libraries/display-plugins/src/display-plugins/DisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/DisplayPlugin.h @@ -121,6 +121,8 @@ public: static const glm::mat4 pose; return pose; } + virtual float getIPD() const { return 0.0f; } + virtual void abandonCalibration() {} virtual void resetSensors() {} virtual float devicePixelRatio() { return 1.0; } diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.cpp index fa9d09e392..f2a7b06510 100644 --- a/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.cpp @@ -149,3 +149,11 @@ void OculusBaseDisplayPlugin::deactivate() { void OculusBaseDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) { ++_frameIndex; } + +float OculusBaseDisplayPlugin::getIPD() const { + float result = 0.0f; +#if (OVR_MAJOR_VERSION >= 6) + result = ovr_GetFloat(_hmd, OVR_KEY_IPD, OVR_DEFAULT_IPD); +#endif + return result; +} \ No newline at end of file diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.h index 12023db1ae..d879085b8f 100644 --- a/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.h @@ -31,6 +31,7 @@ public: virtual void resetSensors() override final; virtual glm::mat4 getEyePose(Eye eye) const override final; virtual glm::mat4 getHeadPose() const override final; + virtual float getIPD() const override final; protected: virtual void preRender() override final; diff --git a/libraries/render-utils/src/simple.slf b/libraries/render-utils/src/simple.slf index 5901a72838..576acf9340 100644 --- a/libraries/render-utils/src/simple.slf +++ b/libraries/render-utils/src/simple.slf @@ -17,6 +17,7 @@ // the interpolated normal in vec3 _normal; +in vec3 _modelNormal; in vec3 _color; in vec2 _texCoord0; in vec4 _position; diff --git a/libraries/render-utils/src/simple.slv b/libraries/render-utils/src/simple.slv index e7fed4a6b4..823654ec27 100644 --- a/libraries/render-utils/src/simple.slv +++ b/libraries/render-utils/src/simple.slv @@ -22,6 +22,7 @@ uniform bool Instanced = false; // the interpolated normal out vec3 _normal; +out vec3 _modelNormal; out vec3 _color; out vec2 _texCoord0; out vec4 _position; @@ -30,6 +31,7 @@ void main(void) { _color = inColor.rgb; _texCoord0 = inTexCoord0.st; _position = inPosition; + _modelNormal = inNormal.xyz; // standard transform TransformCamera cam = getTransformCamera();