540 lines · 25.2 KB · JavaScript
RawDownload
"use strict"; //  farGrabEntity.js////  Distributed under the Apache License, Version 2.0.//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html /* jslint bitwise: true */ /* global Script, Controller, RIGHT_HAND, LEFT_HAND, Mat4, MyAvatar, Vec3, Quat, getEnabledModuleByName, makeRunningValues,   Entities, enableDispatcherModule, disableDispatcherModule, entityIsGrabbable, makeDispatcherModuleParameters, MSECS_PER_SEC,   HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC,   projectOntoEntityXYPlane, HMD, Picks, makeLaserLockInfo, makeLaserParams, AddressManager,   getEntityParents, Selection, DISPATCHER_HOVERING_LIST, unhighlightTargetEntity, Messages, findGrabbableGroupParent,   worldPositionToRegistrationFrameMatrix, DISPATCHER_PROPERTIES, handsAreTracked*/ Script.include("/~/system/libraries/controllerDispatcherUtils.js");Script.include("/~/system/libraries/controllers.js"); (function () {    var controllerStandard = Controller.Standard;     var MARGIN = 25;     function TargetObject(entityID, entityProps) {        this.entityID = entityID;        this.entityProps = entityProps;        this.targetEntityID = null;        this.targetEntityProps = null;         this.getTargetEntity = function () {            var parentPropsLength = this.parentProps.length;            if (parentPropsLength !== 0) {                var targetEntity = {                    id: this.parentProps[parentPropsLength - 1].id,                    props: this.parentProps[parentPropsLength - 1]                };                this.targetEntityID = targetEntity.id;                this.targetEntityProps = targetEntity.props;                return targetEntity;            }            this.targetEntityID = this.entityID;            this.targetEntityProps = this.entityProps;            return {                id: this.entityID,                props: this.entityProps            };        };    }     function FarGrabEntity(hand) {        this.hand = hand;        this.grabbing = false;        this.targetEntityID = null;        this.targetObject = null;        this.previouslyUnhooked = {};        this.reticleMinX = MARGIN;        this.reticleMaxX = 0;        this.reticleMinY = MARGIN;        this.reticleMaxY = 0;        this.endedGrab = 0;        this.MIN_HAPTIC_PULSE_INTERVAL = 500; // ms        this.disabled = false;        this.initialControllerRotation = Quat.IDENTITY;        this.currentControllerRotation = Quat.IDENTITY;        this.manipulating = false;        this.wasManipulating = false;         var FAR_GRAB_JOINTS = [65527, 65528]; // FARGRAB_LEFTHAND_INDEX, FARGRAB_RIGHTHAND_INDEX         var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand and object        var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position        var DISTANCE_HOLDING_UNITY_MASS = 1200; //  The mass at which the distance holding action timeframe is unmodified        var DISTANCE_HOLDING_UNITY_DISTANCE = 6; //  The distance at which the distance holding action timeframe is unmodified         this.parameters = makeDispatcherModuleParameters(            540,            this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],            [],            100,            makeLaserParams(this.hand, false));         this.getOtherModule = function () {            return getEnabledModuleByName(this.hand === RIGHT_HAND ? ("LeftFarGrabEntity") : ("RightFarGrabEntity"));        };         // Get the rotation of the fargrabbed entity.        this.getTargetRotation = function () {            if (this.targetIsNull()) {                return null;            } else {                var props = Entities.getEntityProperties(this.targetEntityID, ["rotation"]);                return props.rotation;            }        };         this.getOffhand = function () {            return (this.hand === RIGHT_HAND ? LEFT_HAND : RIGHT_HAND);        };         // Activation criteria for rotating a fargrabbed entity. If we're changing the mapping, this is where to do it.        this.shouldManipulateTarget = function (controllerData) {            return (controllerData.triggerValues[this.getOffhand()] > TRIGGER_ON_VALUE || controllerData.secondaryValues[this.getOffhand()] > TRIGGER_ON_VALUE) ? true : false;        };         // Get the delta between the current rotation and where the controller was when manipulation started.        this.calculateEntityRotationManipulation = function (controllerRotation) {            return Quat.multiply(controllerRotation, Quat.inverse(this.initialControllerRotation));        };         this.setJointTranslation = function (newTargetPosLocal) {            MyAvatar.setJointTranslation(FAR_GRAB_JOINTS[this.hand], newTargetPosLocal);        };         this.setJointRotation = function (newTargetRotLocal) {            MyAvatar.setJointRotation(FAR_GRAB_JOINTS[this.hand], newTargetRotLocal);        };         this.setJointRotation = function (newTargetRotLocal) {            MyAvatar.setJointRotation(FAR_GRAB_JOINTS[this.hand], newTargetRotLocal);        };         this.handToController = function () {            return (this.hand === RIGHT_HAND) ? controllerStandard.RightHand : controllerStandard.LeftHand;        };         this.distanceGrabTimescale = function (mass, distance) {            var timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME * mass /                DISTANCE_HOLDING_UNITY_MASS * distance /                DISTANCE_HOLDING_UNITY_DISTANCE;            if (timeScale < DISTANCE_HOLDING_ACTION_TIMEFRAME) {                timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME;            }            return timeScale;        };         this.getMass = function (dimensions, density) {            return (dimensions.x * dimensions.y * dimensions.z) * density;        };         this.startFarGrabEntity = function (controllerData, targetProps) {            var controllerLocation = controllerData.controllerLocations[this.hand];            var worldControllerPosition = controllerLocation.position;            var worldControllerRotation = controllerLocation.orientation;            // transform the position into room space            var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix());            var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition);             var now = Date.now();             // add the action and initialize some variables            this.currentObjectPosition = targetProps.position;            this.currentObjectRotation = targetProps.rotation;            this.currentObjectTime = now;             this.grabRadius = this.grabbedDistance;            this.grabRadialVelocity = 0.0;             // offset between controller vector at the grab radius and the entity position            var targetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation));            targetPosition = Vec3.sum(targetPosition, worldControllerPosition);            this.offsetPosition = Vec3.subtract(this.currentObjectPosition, targetPosition);             // compute a constant based on the initial conditions which we use below to exaggerate hand motion            // onto the held object            this.radiusScalar = Math.log(this.grabRadius + 1.0);            if (this.radiusScalar < 1.0) {                this.radiusScalar = 1.0;            }             // compute the mass for the purpose of energy and how quickly to move object            this.mass = this.getMass(targetProps.dimensions, targetProps.density);             // Debounce haptic pules. Can occur as near grab controller module vacillates between being ready or not due to            // changing positions and floating point rounding.            if (Date.now() - this.endedGrab > this.MIN_HAPTIC_PULSE_INTERVAL) {                Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);            }             unhighlightTargetEntity(this.targetEntityID);            var message = {                hand: this.hand,                entityID: this.targetEntityID            };             Messages.sendLocalMessage('Hifi-unhighlight-entity', JSON.stringify(message));             var newTargetPosLocal = MyAvatar.worldToJointPoint(targetProps.position);            var newTargetRotLocal = targetProps.rotation;            this.setJointTranslation(newTargetPosLocal);            this.setJointRotation(newTargetRotLocal);             var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];            Entities.callEntityMethod(targetProps.id, "startDistanceGrab", args);             this.targetEntityID = targetProps.id;              if (this.grabID) {                MyAvatar.releaseGrab(this.grabID);            }            var farJointIndex = FAR_GRAB_JOINTS[this.hand];            this.grabID = MyAvatar.grab(targetProps.id, farJointIndex,                Entities.worldToLocalPosition(targetProps.position, MyAvatar.SELF_ID, farJointIndex),                Entities.worldToLocalRotation(targetProps.rotation, MyAvatar.SELF_ID, farJointIndex));             Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({                action: 'grab',                grabbedEntity: targetProps.id,                joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"            }));            this.grabbing = true;             this.previousRoomControllerPosition = roomControllerPosition;        };         this.continueDistanceHolding = function (controllerData) {            var controllerLocation = controllerData.controllerLocations[this.hand];            var worldControllerPosition = controllerLocation.position;            var worldControllerRotation = controllerLocation.orientation;             // also transform the position into room space            var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix());            var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition);             var targetProps = Entities.getEntityProperties(this.targetEntityID, "position");            var now = Date.now();            var deltaObjectTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds            this.currentObjectTime = now;             // the action was set up when this.distanceHolding was called.  update the targets.            var radius = Vec3.distance(this.currentObjectPosition, worldControllerPosition) *                this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR;            if (radius < 1.0) {                radius = 1.0;            }             var roomHandDelta = Vec3.subtract(roomControllerPosition, this.previousRoomControllerPosition);            var worldHandDelta = Mat4.transformVector(MyAvatar.getSensorToWorldMatrix(), roomHandDelta);            var handMoved = Vec3.multiply(worldHandDelta, radius);            this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, handMoved);             var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];            Entities.callEntityMethod(this.targetEntityID, "continueDistanceGrab", args);             //  Update radialVelocity            var lastVelocity = Vec3.multiply(worldHandDelta, 1.0 / deltaObjectTime);            var delta = Vec3.normalize(Vec3.subtract(targetProps.position, worldControllerPosition));            var newRadialVelocity = Vec3.dot(lastVelocity, delta);             var VELOCITY_AVERAGING_TIME = 0.016;            var blendFactor = deltaObjectTime / VELOCITY_AVERAGING_TIME;            if (blendFactor < 0.0) {                blendFactor = 0.0;            } else if (blendFactor > 1.0) {                blendFactor = 1.0;            }            this.grabRadialVelocity = blendFactor * newRadialVelocity + (1.0 - blendFactor) * this.grabRadialVelocity;             var RADIAL_GRAB_AMPLIFIER = 10.0;            if (Math.abs(this.grabRadialVelocity) > 0.0) {                this.grabRadius = this.grabRadius + (this.grabRadialVelocity * deltaObjectTime *                    this.grabRadius * RADIAL_GRAB_AMPLIFIER);            }             // don't let grabRadius go all the way to zero, because it can't come back from that            var MINIMUM_GRAB_RADIUS = 0.1;            if (this.grabRadius < MINIMUM_GRAB_RADIUS) {                this.grabRadius = MINIMUM_GRAB_RADIUS;            }            var newTargetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation));            newTargetPosition = Vec3.sum(newTargetPosition, worldControllerPosition);            newTargetPosition = Vec3.sum(newTargetPosition, this.offsetPosition);             var newTargetPosLocal = MyAvatar.worldToJointPoint(newTargetPosition);             // This block handles the user's ability to rotate the object they're FarGrabbing            if (this.shouldManipulateTarget(controllerData)) {                // Get the pose of the controller that is not grabbing.                var pose = Controller.getPoseValue((this.getOffhand() ? controllerStandard.RightHand : controllerStandard.LeftHand));                if (pose.valid) {                    // If we weren't manipulating the object yet, initialize the entity's original position.                    if (!this.manipulating) {                        // This will only be triggered if we've let go of the off-hand trigger and pulled it again without ending a grab.                        // Need to poll the entity's rotation again here.                        if (!this.wasManipulating) {                            this.initialEntityRotation = this.getTargetRotation();                        }                        // Save the original controller orientation, we only care about the delta between this rotation and wherever                        // the controller rotates, so that we can apply it to the entity's rotation.                        this.initialControllerRotation = Quat.multiply(pose.rotation, MyAvatar.orientation);                        this.manipulating = true;                    }                }                 var rot = Quat.multiply(pose.rotation, MyAvatar.orientation);                var rotBetween = this.calculateEntityRotationManipulation(rot);                var doubleRot = Quat.multiply(rotBetween, rotBetween);                this.lastJointRotation = Quat.multiply(doubleRot, this.initialEntityRotation);                this.setJointRotation(this.lastJointRotation);            } else {                // If we were manipulating but the user isn't currently expressing this intent, we want to know so we preserve the rotation                // between manipulations without ending the fargrab.                if (this.manipulating) {                    this.initialEntityRotation = this.lastJointRotation;                    this.wasManipulating = true;                }                this.manipulating = false;                // Reset the inital controller position.                this.initialControllerRotation = Quat.IDENTITY;            }            this.setJointTranslation(newTargetPosLocal);             this.previousRoomControllerPosition = roomControllerPosition;        };         this.endFarGrabEntity = function (controllerData) {            if (this.grabID) {                MyAvatar.releaseGrab(this.grabID);                this.grabID = null;            }             this.endedGrab = Date.now();             var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];            Entities.callEntityMethod(this.targetEntityID, "releaseGrab", args);            Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({                action: 'release',                grabbedEntity: this.targetEntityID,                joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"            }));            unhighlightTargetEntity(this.targetEntityID);            this.grabbing = false;            MyAvatar.clearJointData(FAR_GRAB_JOINTS[this.hand]);            this.initialEntityRotation = Quat.IDENTITY;            this.initialControllerRotation = Quat.IDENTITY;            this.targetEntityID = null;            this.manipulating = false;            this.wasManipulating = false;            var otherModule = this.getOtherModule();            otherModule.disabled = false;        };         this.updateRecommendedArea = function () {            var dims = Controller.getViewportDimensions();            this.reticleMaxX = dims.x - MARGIN;            this.reticleMaxY = dims.y - MARGIN;        };         this.calculateNewReticlePosition = function (intersection) {            this.updateRecommendedArea();            var point2d = HMD.overlayFromWorldPoint(intersection);            point2d.x = Math.max(this.reticleMinX, Math.min(point2d.x, this.reticleMaxX));            point2d.y = Math.max(this.reticleMinY, Math.min(point2d.y, this.reticleMaxY));            return point2d;        };         this.notPointingAtEntity = function (controllerData) {             var intersection = controllerData.rayPicks[this.hand];            var entityProperty = Entities.getEntityProperties(intersection.objectID, "type");            var entityType = entityProperty.type;            var hudRayPick = controllerData.hudRayPicks[this.hand];            if (!hudRayPick.intersects) return false;            var point2d = this.calculateNewReticlePosition(hudRayPick.intersection);             if (entityType === "Web" ||                Window.isPointOnDesktopWindow(point2d)) {                return true;            }            return false;        };         this.targetIsNull = function () {            var properties = Entities.getEntityProperties(this.targetEntityID, "type");            if (Object.keys(properties).length === 0 && this.distanceHolding) {                return true;            }            return false;        };         this.getTargetProps = function (controllerData) {            var targetEntity = controllerData.rayPicks[this.hand].objectID;            if (targetEntity) {                var gtProps = Entities.getEntityProperties(targetEntity, DISPATCHER_PROPERTIES);                if (entityIsGrabbable(gtProps)) {                    // if we've attempted to grab a child, roll up to the root of the tree                    var groupRootProps = findGrabbableGroupParent(controllerData, gtProps);                    if (entityIsGrabbable(groupRootProps)) {                        return groupRootProps;                    }                    return gtProps;                }            }            return null;        };         this.isReady = function (controllerData) {            if (HMD.active) {                if (handsAreTracked()) {                    return makeRunningValues(false, [], []);                }                if (this.notPointingAtEntity(controllerData)) {                    return makeRunningValues(false, [], []);                }                 this.distanceHolding = false;                 if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE && !this.disabled) {                    var otherModule = this.getOtherModule();                    otherModule.disabled = true;                    return makeRunningValues(true, [], []);                }            }            return makeRunningValues(false, [], []);        };         this.run = function (controllerData) {            if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || this.targetIsNull()) {                this.endFarGrabEntity(controllerData);                return makeRunningValues(false, [], []);            }            this.intersectionDistance = controllerData.rayPicks[this.hand].distance;             // gather up the readiness of the near-grab modules            var nearGrabNames = [                this.hand === RIGHT_HAND ? "RightScaleAvatar" : "LeftScaleAvatar",                this.hand === RIGHT_HAND ? "RightFarTriggerEntity" : "LeftFarTriggerEntity",                this.hand === RIGHT_HAND ? "RightNearGrabEntity" : "LeftNearGrabEntity"            ];            if (!this.grabbing) {                nearGrabNames.push(this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay");                nearGrabNames.push(this.hand === RIGHT_HAND ? "RightNearTabletHighlight" : "LeftNearTabletHighlight");            }             var nearGrabReadiness = [];            for (var i = 0; i < nearGrabNames.length; i++) {                var nearGrabModule = getEnabledModuleByName(nearGrabNames[i]);                var ready = nearGrabModule ? nearGrabModule.isReady(controllerData) : makeRunningValues(false, [], []);                nearGrabReadiness.push(ready);            }             if (this.targetEntityID) {                // if we are doing a distance grab and the object gets close enough to the controller,                // stop the far-grab so the near-grab or equip can take over.                for (var k = 0; k < nearGrabReadiness.length; k++) {                    if (nearGrabReadiness[k].active && (nearGrabReadiness[k].targets[0] === this.targetEntityID)) {                        this.endFarGrabEntity(controllerData);                        return makeRunningValues(false, [], []);                    }                }                 this.continueDistanceHolding(controllerData);            } else {                // if we are doing a distance search and this controller moves into a position                // where it could near-grab something, stop searching.                for (var j = 0; j < nearGrabReadiness.length; j++) {                    if (nearGrabReadiness[j].active) {                        this.endFarGrabEntity(controllerData);                        return makeRunningValues(false, [], []);                    }                }                 var rayPickInfo = controllerData.rayPicks[this.hand];                if (controllerData.triggerClicks[this.hand] && rayPickInfo.intersects) {                    var entityID = rayPickInfo.objectID;                    var targetProps = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES);                    if (targetProps.href !== "") {                        AddressManager.handleLookupString(targetProps.href);                        return makeRunningValues(false, [], []);                    }                     this.targetObject = new TargetObject(entityID, targetProps);                    this.targetObject.parentProps = getEntityParents(targetProps);                     Selection.removeFromSelectedItemsList("contextOverlayHighlightList", "entity", entityID);                     var targetEntity = this.targetObject.getTargetEntity();                    entityID = targetEntity.id;                    targetProps = targetEntity.props;                     if (                        entityID !== HMD.tabletID &&                        !Keyboard.containsID(entityID) &&                        (entityIsGrabbable(targetProps) ||                        entityIsGrabbable(this.targetObject.entityProps))                    ) {                         this.targetEntityID = entityID;                        this.grabbedDistance = rayPickInfo.distance;                        this.distanceHolding = true;                        this.startFarGrabEntity(controllerData, targetProps);                    }                }            }            return this.exitIfDisabled(controllerData);        };         this.exitIfDisabled = function (controllerData) {

Lines 1–500 of 540.