diff --git a/examples/controllers/hydra/hydraGrab.js b/examples/controllers/hydra/hydraGrab.js
index dc8cd14eaa..9e49838d88 100644
--- a/examples/controllers/hydra/hydraGrab.js
+++ b/examples/controllers/hydra/hydraGrab.js
@@ -3,750 +3,301 @@
// examples
//
// Created by Clément Brisset on 4/24/14.
+// Updated by Eric Levin on 5/14/15.
// Copyright 2014 High Fidelity, Inc.
//
-// This script allows you to edit models either with the razor hydras or with your mouse
+// This script allows you to grab and move/rotate physical objects with the hydra
//
// Using the hydras :
-// grab models with the triggers, you can then move the models around or scale them with both hands.
-// You can switch mode using the bumpers so that you can move models around more easily.
+// grab physical entities with the right trigger
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
-Script.include("../../libraries/entityPropertyDialogBox.js");
-var entityPropertyDialogBox = EntityPropertyDialogBox;
-var MIN_ANGULAR_SIZE = 2;
-var MAX_ANGULAR_SIZE = 45;
-var allowLargeModels = true;
-var allowSmallModels = true;
-var wantEntityGlow = false;
-var LEFT = 0;
+var entityProps, currentPosition, currentVelocity, currentRotation, distanceToTarget, velocityTowardTarget, desiredVelocity;
+var addedVelocity, newVelocity, angularVelocity, dT, cameraEntityDistance;
var RIGHT = 1;
-
-var jointList = MyAvatar.getJointNames();
-
var LASER_WIDTH = 3;
-var LASER_COLOR = { red: 50, green: 150, blue: 200 };
-var DROP_COLOR = { red: 200, green: 200, blue: 200 };
-var DROP_WIDTH = 4;
+var LASER_COLOR = {
+ red: 50,
+ green: 150,
+ blue: 200
+};
+var LASER_HOVER_COLOR = {
+ red: 200,
+ green: 50,
+ blue: 50
+};
+
var DROP_DISTANCE = 5.0;
+var DROP_COLOR = {
+ red: 200,
+ green: 200,
+ blue: 200
+};
+var FULL_STRENGTH = 0.05;
var LASER_LENGTH_FACTOR = 500;
+var CLOSE_ENOUGH = 0.001;
+var SPRING_RATE = 1.5;
+var DAMPING_RATE = 0.8;
+var SCREEN_TO_METERS = 0.001;
+var DISTANCE_SCALE_FACTOR = 1000
-var velocity = { x: 0, y: 0, z: 0 };
+var grabSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/CloseClamp.wav");
+var releaseSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/ReleaseClamp.wav");
-var lastAccurateIntersection = null;
-var accurateIntersections = 0;
-var totalIntersections = 0;
-var inaccurateInARow = 0;
-var maxInaccurateInARow = 0;
-function getRayIntersection(pickRay) { // pickRay : { origin : {xyz}, direction : {xyz} }
- if (lastAccurateIntersection === null) {
- lastAccurateIntersection = Entities.findRayIntersectionBlocking(pickRay);
- } else {
- var intersection = Entities.findRayIntersection(pickRay);
- if (intersection.accurate) {
- lastAccurateIntersection = intersection;
- accurateIntersections++;
- maxInaccurateInARow = (maxInaccurateInARow > inaccurateInARow) ? maxInaccurateInARow : inaccurateInARow;
- inaccurateInARow = 0;
- } else {
- inaccurateInARow++;
- }
- totalIntersections++;
+function getRayIntersection(pickRay) {
+ var intersection = Entities.findRayIntersection(pickRay);
+ return intersection;
+}
+
+
+function controller(side) {
+ this.triggerHeld = false;
+ this.triggerThreshold = 0.9;
+ this.side = side;
+ this.palm = 2 * side;
+ this.tip = 2 * side + 1;
+ this.trigger = side;
+ this.originalGravity = {
+ x: 0,
+ y: 0,
+ z: 0
+ };
+
+ this.laser = Overlays.addOverlay("line3d", {
+ start: {
+ x: 0,
+ y: 0,
+ z: 0
+ },
+ end: {
+ x: 0,
+ y: 0,
+ z: 0
+ },
+ color: LASER_COLOR,
+ alpha: 1,
+ lineWidth: LASER_WIDTH,
+ anchor: "MyAvatar"
+ });
+
+ this.dropLine = Overlays.addOverlay("line3d", {
+ color: DROP_COLOR,
+ alpha: 1,
+ visible: false,
+ lineWidth: 2
+ });
+
+
+ this.update = function(deltaTime) {
+ this.updateControllerState();
+ this.moveLaser();
+ this.checkTrigger();
+ this.checkEntityIntersection();
+ if (this.grabbing) {
+ this.updateEntity(deltaTime);
}
- return lastAccurateIntersection;
-}
-function printIntersectionsStats() {
- var ratio = accurateIntersections / totalIntersections;
- print("Out of " + totalIntersections + " intersections, " + accurateIntersections + " where accurate. (" + ratio * 100 +"%)");
- print("Worst case was " + maxInaccurateInARow + " inaccurate intersections in a row.");
-}
+ this.oldPalmPosition = this.palmPosition;
+ this.oldTipPosition = this.tipPosition;
+ }
+
+ this.updateEntity = function(deltaTime) {
+ this.dControllerPosition = Vec3.subtract(this.palmPosition, this.oldPalmPosition);
+ this.cameraEntityDistance = Vec3.distance(Camera.getPosition(), this.currentPosition);
+ this.targetPosition = Vec3.sum(this.targetPosition, Vec3.multiply(this.dControllerPosition, this.cameraEntityDistance * SCREEN_TO_METERS * DISTANCE_SCALE_FACTOR));
+
+ this.entityProps = Entities.getEntityProperties(this.grabbedEntity);
+ this.currentPosition = this.entityProps.position;
+ this.currentVelocity = this.entityProps.velocity;
+
+ var dPosition = Vec3.subtract(this.targetPosition, this.currentPosition);
+ this.distanceToTarget = Vec3.length(dPosition);
+ if (this.distanceToTarget > CLOSE_ENOUGH) {
+ // compute current velocity in the direction we want to move
+ this.velocityTowardTarget = Vec3.dot(this.currentVelocity, Vec3.normalize(dPosition));
+ this.velocityTowardTarget = Vec3.multiply(Vec3.normalize(dPosition), this.velocityTowardTarget);
+ // compute the speed we would like to be going toward the target position
+
+ this.desiredVelocity = Vec3.multiply(dPosition, (1.0 / deltaTime) * SPRING_RATE);
+ // compute how much we want to add to the existing velocity
+ this.addedVelocity = Vec3.subtract(this.desiredVelocity, this.velocityTowardTarget);
+ //If target is to far, roll off force as inverse square of distance
+ if(this.distanceToTarget/ this.cameraEntityDistance > FULL_STRENGTH) {
+ this.addedVelocity = Vec3.multiply(this.addedVelocity, Math.pow(FULL_STRENGTH/ this.distanceToTarget, 2.0));
+ }
+ this.newVelocity = Vec3.sum(this.currentVelocity, this.addedVelocity);
+ this.newVelocity = Vec3.subtract(this.newVelocity, Vec3.multiply(this.newVelocity, DAMPING_RATE));
+ } else {
+ this.newVelocity = {
+ x: 0,
+ y: 0,
+ z: 0
+ };
+ }
+ this.transformedAngularVelocity = Controller.getSpatialControlRawAngularVelocity(this.tip);
+ this.transformedAngularVelocity = Vec3.multiplyQbyV(Camera.getOrientation(), this.transformedAngularVelocity);
+
+ Entities.editEntity(this.grabbedEntity, {
+ velocity: this.newVelocity,
+ angularVelocity: this.transformedAngularVelocity
+ });
+
+ this.updateDropLine(this.targetPosition);
+
+ }
-function controller(wichSide) {
- this.side = wichSide;
- this.palm = 2 * wichSide;
- this.tip = 2 * wichSide + 1;
- this.trigger = wichSide;
- this.bumper = 6 * wichSide + 5;
-
- this.oldPalmPosition = Controller.getSpatialControlPosition(this.palm);
- this.palmPosition = this.oldPalmPosition;
-
- this.oldTipPosition = Controller.getSpatialControlPosition(this.tip);
- this.tipPosition = this.oldTipPosition;
-
- this.oldUp = Controller.getSpatialControlNormal(this.palm);
- this.up = this.oldUp;
-
- this.oldFront = Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition));
- this.front = this.oldFront;
-
- this.oldRight = Vec3.cross(this.front, this.up);
- this.right = this.oldRight;
-
- this.oldRotation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(this.palm));
- this.rotation = this.oldRotation;
-
+ this.updateControllerState = function() {
+ this.palmPosition = Controller.getSpatialControlPosition(this.palm);
+ this.tipPosition = Controller.getSpatialControlPosition(this.tip);
this.triggerValue = Controller.getTriggerValue(this.trigger);
- this.bumperValue = Controller.isButtonPressed(this.bumper);
+ }
- this.pressed = false; // is trigger pressed
- this.pressing = false; // is trigger being pressed (is pressed now but wasn't previously)
+ this.checkTrigger = function() {
+ if (this.triggerValue > this.triggerThreshold && !this.triggerHeld) {
+ this.triggerHeld = true;
+ } else if (this.triggerValue < this.triggerThreshold && this.triggerHeld) {
+ this.triggerHeld = false;
+ if (this.grabbing) {
+ this.release();
+ }
+ }
+ }
+
+ this.updateDropLine = function(position) {
+
+ Overlays.editOverlay(this.dropLine, {
+ visible: true,
+ start: {
+ x: position.x,
+ y: position.y + DROP_DISTANCE,
+ z: position.z
+ },
+ end: {
+ x: position.x,
+ y: position.y - DROP_DISTANCE,
+ z: position.z
+ }
+ });
+
+ }
+
+ this.checkEntityIntersection = function() {
+
+ var pickRay = {
+ origin: this.palmPosition,
+ direction: Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition))
+ };
+ var intersection = getRayIntersection(pickRay);
+ if (intersection.intersects && intersection.properties.collisionsWillMove) {
+ this.laserWasHovered = true;
+ if (this.triggerHeld && !this.grabbing) {
+ this.grab(intersection.entityID);
+ }
+ Overlays.editOverlay(this.laser, {
+ color: LASER_HOVER_COLOR
+ });
+ } else if (this.laserWasHovered) {
+ this.laserWasHovered = false;
+ Overlays.editOverlay(this.laser, {
+ color: LASER_COLOR
+ });
+ }
+ }
+
+ this.grab = function(entityId) {
+ this.grabbing = true;
+ this.grabbedEntity = entityId;
+ this.entityProps = Entities.getEntityProperties(this.grabbedEntity);
+ this.targetPosition = this.entityProps.position;
+ this.currentPosition = this.targetPosition;
+ this.oldPalmPosition = this.palmPosition;
+ this.originalGravity = this.entityProps.gravity;
+ Entities.editEntity(this.grabbedEntity, {
+ gravity: {
+ x: 0,
+ y: 0,
+ z: 0
+ }
+ });
+ Overlays.editOverlay(this.laser, {
+ visible: false
+ });
+ Audio.playSound(grabSound, {
+ position: this.entityProps.position,
+ volume: 0.25
+ });
+ }
+
+ this.release = function() {
this.grabbing = false;
- this.entityID = { isKnownID: false };
- this.modelURL = "";
- this.oldModelRotation;
- this.oldModelPosition;
- this.oldModelHalfDiagonal;
-
- this.positionAtGrab;
- this.rotationAtGrab;
- this.gravityAtGrab;
- this.modelPositionAtGrab;
- this.modelRotationAtGrab;
- this.jointsIntersectingFromStart = [];
-
- this.laser = Overlays.addOverlay("line3d", {
- start: { x: 0, y: 0, z: 0 },
- end: { x: 0, y: 0, z: 0 },
- color: LASER_COLOR,
- alpha: 1,
- visible: false,
- lineWidth: LASER_WIDTH,
- anchor: "MyAvatar"
+ this.grabbedEntity = null;
+ Overlays.editOverlay(this.laser, {
+ visible: true
+ });
+ Overlays.editOverlay(this.dropLine, {
+ visible: false
});
- this.dropLine = Overlays.addOverlay("line3d", {
- start: { x: 0, y: 0, z: 0 },
- end: { x: 0, y: 0, z: 0 },
- color: DROP_COLOR,
- alpha: 1,
- visible: false,
- lineWidth: DROP_WIDTH });
-
- this.guideScale = 0.02;
- this.ball = Overlays.addOverlay("sphere", {
- position: { x: 0, y: 0, z: 0 },
- size: this.guideScale,
- solid: true,
- color: { red: 0, green: 255, blue: 0 },
- alpha: 1,
- visible: false,
- anchor: "MyAvatar"
+ Audio.playSound(releaseSound, {
+ position: this.entityProps.position,
+ volume: 0.25
});
- this.leftRight = Overlays.addOverlay("line3d", {
- start: { x: 0, y: 0, z: 0 },
- end: { x: 0, y: 0, z: 0 },
- color: { red: 0, green: 0, blue: 255 },
- alpha: 1,
- visible: false,
- lineWidth: LASER_WIDTH,
- anchor: "MyAvatar"
+
+ // only restore the original gravity if it's not zero. This is to avoid...
+ // 1. interface A grabs an entity and locally saves off its gravity
+ // 2. interface A sets the entity's gravity to zero
+ // 3. interface B grabs the entity and saves off its gravity (which is zero)
+ // 4. interface A releases the entity and puts the original gravity back
+ // 5. interface B releases the entity and puts the original gravity back (to zero)
+ if(vectorIsZero(this.originalGravity)) {
+ Entities.editEntity(this.grabbedEntity, {
+ gravity: this.originalGravity
+ });
+ }
+ }
+
+ this.moveLaser = function() {
+ var inverseRotation = Quat.inverse(MyAvatar.orientation);
+ var startPosition = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.palmPosition, MyAvatar.position));
+ // startPosition = Vec3.multiply(startPosition, 1 / MyAvatar.scale);
+ var direction = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.tipPosition, this.palmPosition));
+ direction = Vec3.multiply(direction, LASER_LENGTH_FACTOR / (Vec3.length(direction) * MyAvatar.scale));
+ var endPosition = Vec3.sum(startPosition, direction);
+
+ Overlays.editOverlay(this.laser, {
+ start: startPosition,
+ end: endPosition
});
- this.topDown = Overlays.addOverlay("line3d", {
- start: { x: 0, y: 0, z: 0 },
- end: { x: 0, y: 0, z: 0 },
- color: { red: 0, green: 0, blue: 255 },
- alpha: 1,
- visible: false,
- lineWidth: LASER_WIDTH,
- anchor: "MyAvatar"
- });
-
-
- this.grab = function (entityID, properties) {
- print("Grabbing " + entityID.id);
- this.grabbing = true;
- this.entityID = entityID;
- this.modelURL = properties.modelURL;
+ }
-
- this.oldModelPosition = properties.position;
- this.oldModelRotation = properties.rotation;
- this.oldModelHalfDiagonal = Vec3.length(properties.dimensions) / 2.0;
-
- this.positionAtGrab = this.palmPosition;
- this.rotationAtGrab = this.rotation;
- this.modelPositionAtGrab = properties.position;
- this.modelRotationAtGrab = properties.rotation;
- this.gravityAtGrab = properties.gravity;
- Entities.editEntity(entityID, { gravity: { x: 0, y: 0, z: 0 }, velocity: { x: 0, y: 0, z: 0 } });
-
-
- this.jointsIntersectingFromStart = [];
- for (var i = 0; i < jointList.length; i++) {
- var distance = Vec3.distance(MyAvatar.getJointPosition(jointList[i]), this.oldModelPosition);
- if (distance < this.oldModelHalfDiagonal) {
- this.jointsIntersectingFromStart.push(i);
- }
- }
- this.showLaser(false);
- Overlays.editOverlay(this.dropLine, { visible: true });
- }
-
- this.release = function () {
- if (this.grabbing) {
-
- Entities.editEntity(this.entityID, { gravity: this.gravityAtGrab });
-
- jointList = MyAvatar.getJointNames();
-
- var closestJointIndex = -1;
- var closestJointDistance = 10;
- for (var i = 0; i < jointList.length; i++) {
- var distance = Vec3.distance(MyAvatar.getJointPosition(jointList[i]), this.oldModelPosition);
- if (distance < closestJointDistance) {
- closestJointDistance = distance;
- closestJointIndex = i;
- }
- }
-
- if (closestJointIndex != -1) {
- print("closestJoint: " + jointList[closestJointIndex]);
- print("closestJointDistance (attach max distance): " + closestJointDistance + " (" + this.oldModelHalfDiagonal + ")");
- }
-
- if (closestJointDistance < this.oldModelHalfDiagonal) {
-
- if (this.jointsIntersectingFromStart.indexOf(closestJointIndex) != -1 ||
- (leftController.grabbing && rightController.grabbing &&
- leftController.entityID.id == rightController.entityID.id)) {
- // Do nothing
- } else {
- print("Attaching to " + jointList[closestJointIndex]);
- var jointPosition = MyAvatar.getJointPosition(jointList[closestJointIndex]);
- var jointRotation = MyAvatar.getJointCombinedRotation(jointList[closestJointIndex]);
-
- var attachmentOffset = Vec3.subtract(this.oldModelPosition, jointPosition);
- attachmentOffset = Vec3.multiplyQbyV(Quat.inverse(jointRotation), attachmentOffset);
- var attachmentRotation = Quat.multiply(Quat.inverse(jointRotation), this.oldModelRotation);
-
- MyAvatar.attach(this.modelURL, jointList[closestJointIndex],
- attachmentOffset, attachmentRotation, 2.0 * this.oldModelHalfDiagonal,
- true, false);
- Entities.deleteEntity(this.entityID);
- }
- }
-
- Overlays.editOverlay(this.dropLine, { visible: false });
- }
-
- this.grabbing = false;
- this.entityID.isKnownID = false;
- this.jointsIntersectingFromStart = [];
- this.showLaser(true);
- }
-
- this.checkTrigger = function () {
- if (this.triggerValue > 0.9) {
- if (this.pressed) {
- this.pressing = false;
- } else {
- this.pressing = true;
- }
- this.pressed = true;
- } else {
- this.pressing = false;
- this.pressed = false;
- }
- }
-
- this.checkEntity = function (properties) {
- // P P - Model
- // /| A - Palm
- // / | d B - unit vector toward tip
- // / | X - base of the perpendicular line
- // A---X----->B d - distance fom axis
- // x x - distance from A
- //
- // |X-A| = (P-A).B
- // X == A + ((P-A).B)B
- // d = |P-X|
-
- var A = this.palmPosition;
- var B = this.front;
- var P = properties.position;
-
- var x = Vec3.dot(Vec3.subtract(P, A), B);
- var y = Vec3.dot(Vec3.subtract(P, A), this.up);
- var z = Vec3.dot(Vec3.subtract(P, A), this.right);
- var X = Vec3.sum(A, Vec3.multiply(B, x));
- var d = Vec3.length(Vec3.subtract(P, X));
- var halfDiagonal = Vec3.length(properties.dimensions) / 2.0;
-
- var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14;
-
- var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE)
- && (allowSmallModels || angularSize > MIN_ANGULAR_SIZE);
-
- if (0 < x && sizeOK) {
- return { valid: true, x: x, y: y, z: z };
- }
- return { valid: false };
- }
-
- this.glowedIntersectingModel = { isKnownID: false };
- this.moveLaser = function () {
- // the overlays here are anchored to the avatar, which means they are specified in the avatar's local frame
-
- var inverseRotation = Quat.inverse(MyAvatar.orientation);
- var startPosition = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.palmPosition, MyAvatar.position));
- startPosition = Vec3.multiply(startPosition, 1 / MyAvatar.scale);
- var direction = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.tipPosition, this.palmPosition));
- direction = Vec3.multiply(direction, LASER_LENGTH_FACTOR / (Vec3.length(direction) * MyAvatar.scale));
- var endPosition = Vec3.sum(startPosition, direction);
-
- Overlays.editOverlay(this.laser, {
- start: startPosition,
- end: endPosition
- });
-
- Overlays.editOverlay(this.ball, {
- position: endPosition
- });
- Overlays.editOverlay(this.leftRight, {
- start: Vec3.sum(endPosition, Vec3.multiply(this.right, 2 * this.guideScale)),
- end: Vec3.sum(endPosition, Vec3.multiply(this.right, -2 * this.guideScale))
- });
- Overlays.editOverlay(this.topDown, {
- start: Vec3.sum(endPosition, Vec3.multiply(this.up, 2 * this.guideScale)),
- end: Vec3.sum(endPosition, Vec3.multiply(this.up, -2 * this.guideScale))
- });
- this.showLaser(!this.grabbing);
-
- if (this.glowedIntersectingModel.isKnownID) {
- Entities.editEntity(this.glowedIntersectingModel, { glowLevel: 0.0 });
- this.glowedIntersectingModel.isKnownID = false;
- }
- if (!this.grabbing) {
- var intersection = getRayIntersection({ origin: this.palmPosition,
- direction: this.front
- });
-
- var halfDiagonal = Vec3.length(intersection.properties.dimensions) / 2.0;
-
- var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), intersection.properties.position)) * 180 / 3.14;
- var sizeOK = (allowLargeModels || angularSize < MAX_ANGULAR_SIZE)
- && (allowSmallModels || angularSize > MIN_ANGULAR_SIZE);
- if (intersection.accurate && intersection.entityID.isKnownID && sizeOK) {
- this.glowedIntersectingModel = intersection.entityID;
-
- if (wantEntityGlow) {
- Entities.editEntity(this.glowedIntersectingModel, { glowLevel: 0.25 });
- }
- }
- }
- }
-
- this.showLaser = function (show) {
- Overlays.editOverlay(this.laser, { visible: show });
- Overlays.editOverlay(this.ball, { visible: show });
- Overlays.editOverlay(this.leftRight, { visible: show });
- Overlays.editOverlay(this.topDown, { visible: show });
- }
- this.moveEntity = function (deltaTime) {
- if (this.grabbing) {
- if (!this.entityID.isKnownID) {
- print("Unknown grabbed ID " + this.entityID.id + ", isKnown: " + this.entityID.isKnownID);
- this.entityID = getRayIntersection({ origin: this.palmPosition,
- direction: this.front
- }).entityID;
- print("Identified ID " + this.entityID.id + ", isKnown: " + this.entityID.isKnownID);
- }
- var newPosition;
- var newRotation;
-
- var CONSTANT_SCALING_FACTOR = 5.0;
- var MINIMUM_SCALING_DISTANCE = 2.0;
- var distanceToModel = Vec3.length(Vec3.subtract(this.oldModelPosition, this.palmPosition));
- if (distanceToModel < MINIMUM_SCALING_DISTANCE) {
- distanceToModel = MINIMUM_SCALING_DISTANCE;
- }
-
- var deltaPalm = Vec3.multiply(distanceToModel * CONSTANT_SCALING_FACTOR, Vec3.subtract(this.palmPosition, this.oldPalmPosition));
- newPosition = Vec3.sum(this.oldModelPosition, deltaPalm);
-
- newRotation = Quat.multiply(this.rotation,
- Quat.inverse(this.rotationAtGrab));
- newRotation = Quat.multiply(newRotation, newRotation);
- newRotation = Quat.multiply(newRotation,
- this.modelRotationAtGrab);
-
- velocity = Vec3.multiply(1.0 / deltaTime, Vec3.subtract(newPosition, this.oldModelPosition));
-
- Entities.editEntity(this.entityID, {
- position: newPosition,
- rotation: newRotation,
- velocity: velocity
- });
- this.oldModelRotation = newRotation;
- this.oldModelPosition = newPosition;
-
- Overlays.editOverlay(this.dropLine, { start: newPosition, end: Vec3.sum(newPosition, { x: 0, y: -DROP_DISTANCE, z: 0 }) });
-
- var indicesToRemove = [];
- for (var i = 0; i < this.jointsIntersectingFromStart.length; ++i) {
- var distance = Vec3.distance(MyAvatar.getJointPosition(this.jointsIntersectingFromStart[i]), this.oldModelPosition);
- if (distance >= this.oldModelHalfDiagonal) {
- indicesToRemove.push(this.jointsIntersectingFromStart[i]);
- }
-
- }
- for (var i = 0; i < indicesToRemove.length; ++i) {
- this.jointsIntersectingFromStart.splice(this.jointsIntersectingFromStart.indexOf(indicesToRemove[i], 1));
- }
- }
- }
-
- this.update = function () {
- this.oldPalmPosition = this.palmPosition;
- this.oldTipPosition = this.tipPosition;
- this.palmPosition = Controller.getSpatialControlPosition(this.palm);
- this.tipPosition = Controller.getSpatialControlPosition(this.tip);
-
- this.oldUp = this.up;
- this.up = Vec3.normalize(Controller.getSpatialControlNormal(this.palm));
-
- this.oldFront = this.front;
- this.front = Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition));
-
- this.oldRight = this.right;
- this.right = Vec3.normalize(Vec3.cross(this.front, this.up));
-
- this.oldRotation = this.rotation;
- this.rotation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(this.palm));
-
- this.triggerValue = Controller.getTriggerValue(this.trigger);
-
- var bumperValue = Controller.isButtonPressed(this.bumper);
- this.bumperValue = bumperValue;
-
-
- this.checkTrigger();
-
- this.moveLaser();
-
- if (!this.pressed && this.grabbing) {
- // release if trigger not pressed anymore.
- this.release();
- }
-
- if (this.pressing) {
- // Checking for attachments intersecting
- var attachments = MyAvatar.getAttachmentData();
- var attachmentIndex = -1;
- var attachmentX = LASER_LENGTH_FACTOR;
-
- var newModel;
- var newProperties;
-
- for (var i = 0; i < attachments.length; ++i) {
- var position = Vec3.sum(MyAvatar.getJointPosition(attachments[i].jointName),
- Vec3.multiplyQbyV(MyAvatar.getJointCombinedRotation(attachments[i].jointName), attachments[i].translation));
- var scale = attachments[i].scale;
-
- var A = this.palmPosition;
- var B = this.front;
- var P = position;
-
- var x = Vec3.dot(Vec3.subtract(P, A), B);
- var X = Vec3.sum(A, Vec3.multiply(B, x));
- var d = Vec3.length(Vec3.subtract(P, X));
-
- if (d < scale / 2.0 && 0 < x && x < attachmentX) {
- attachmentIndex = i;
- attachmentX = d;
- }
- }
-
- if (attachmentIndex != -1) {
- print("Detaching: " + attachments[attachmentIndex].modelURL);
- MyAvatar.detachOne(attachments[attachmentIndex].modelURL, attachments[attachmentIndex].jointName);
-
- newProperties = {
- type: "Model",
- position: Vec3.sum(MyAvatar.getJointPosition(attachments[attachmentIndex].jointName),
- Vec3.multiplyQbyV(MyAvatar.getJointCombinedRotation(attachments[attachmentIndex].jointName), attachments[attachmentIndex].translation)),
- rotation: Quat.multiply(MyAvatar.getJointCombinedRotation(attachments[attachmentIndex].jointName),
- attachments[attachmentIndex].rotation),
-
- // TODO: how do we know the correct dimensions for detachment???
- dimensions: { x: attachments[attachmentIndex].scale / 2.0,
- y: attachments[attachmentIndex].scale / 2.0,
- z: attachments[attachmentIndex].scale / 2.0 },
-
- modelURL: attachments[attachmentIndex].modelURL
- };
-
- newModel = Entities.addEntity(newProperties);
-
-
- } else {
- // There is none so ...
- // Checking model tree
- Vec3.print("Looking at: ", this.palmPosition);
- var pickRay = { origin: this.palmPosition,
- direction: Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition)) };
- var foundIntersection = getRayIntersection(pickRay);
-
- if(!foundIntersection.intersects) {
- print("No intersection");
- return;
- }
- newModel = foundIntersection.entityID;
- if (!newModel.isKnownID) {
- var identify = Entities.identifyEntity(newModel);
- if (!identify.isKnownID) {
- print("Unknown ID " + identify.id + " (update loop " + newModel.id + ")");
- return;
- }
- newModel = identify;
- }
- newProperties = Entities.getEntityProperties(newModel);
- }
- print("foundEntity.modelURL=" + newProperties.modelURL);
- var check = this.checkEntity(newProperties);
- if (!check.valid) {
- return;
- }
-
- this.grab(newModel, newProperties);
-
- this.x = check.x;
- this.y = check.y;
- this.z = check.z;
- return;
- }
- }
-
- this.cleanup = function () {
- Overlays.deleteOverlay(this.laser);
- Overlays.deleteOverlay(this.ball);
- Overlays.deleteOverlay(this.leftRight);
- Overlays.deleteOverlay(this.topDown);
- }
+ this.cleanup = function() {
+ Overlays.deleteOverlay(this.laser);
+ Overlays.deleteOverlay(this.dropLine);
+ }
}
-var leftController = new controller(LEFT);
-var rightController = new controller(RIGHT);
-
-function moveEntities(deltaTime) {
- if (leftController.grabbing && rightController.grabbing && rightController.entityID.id == leftController.entityID.id) {
- var newPosition = leftController.oldModelPosition;
- var rotation = leftController.oldModelRotation;
- var ratio = 1;
-
- var u = Vec3.normalize(Vec3.subtract(rightController.oldPalmPosition, leftController.oldPalmPosition));
- var v = Vec3.normalize(Vec3.subtract(rightController.palmPosition, leftController.palmPosition));
-
- var cos_theta = Vec3.dot(u, v);
- if (cos_theta > 1) {
- cos_theta = 1;
- }
- var angle = Math.acos(cos_theta) / Math.PI * 180;
- if (angle < 0.1) {
- return;
- }
- var w = Vec3.normalize(Vec3.cross(u, v));
-
- rotation = Quat.multiply(Quat.angleAxis(angle, w), leftController.oldModelRotation);
-
-
- leftController.positionAtGrab = leftController.palmPosition;
- leftController.rotationAtGrab = leftController.rotation;
- leftController.modelPositionAtGrab = leftController.oldModelPosition;
- leftController.modelRotationAtGrab = rotation;
- rightController.positionAtGrab = rightController.palmPosition;
- rightController.rotationAtGrab = rightController.rotation;
- rightController.modelPositionAtGrab = rightController.oldModelPosition;
- rightController.modelRotationAtGrab = rotation;
-
- Entities.editEntity(leftController.entityID, {
- position: newPosition,
- rotation: rotation,
- // TODO: how do we know the correct dimensions for detachment???
- //radius: leftController.oldModelHalfDiagonal * ratio
- dimensions: { x: leftController.oldModelHalfDiagonal * ratio,
- y: leftController.oldModelHalfDiagonal * ratio,
- z: leftController.oldModelHalfDiagonal * ratio }
-
- });
- leftController.oldModelPosition = newPosition;
- leftController.oldModelRotation = rotation;
- leftController.oldModelHalfDiagonal *= ratio;
-
- rightController.oldModelPosition = newPosition;
- rightController.oldModelRotation = rotation;
- rightController.oldModelHalfDiagonal *= ratio;
- return;
- }
- leftController.moveEntity(deltaTime);
- rightController.moveEntity(deltaTime);
-}
-
-var hydraConnected = false;
-function checkController(deltaTime) {
- var numberOfButtons = Controller.getNumberOfButtons();
- var numberOfTriggers = Controller.getNumberOfTriggers();
- var numberOfSpatialControls = Controller.getNumberOfSpatialControls();
- var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers;
-
- // this is expected for hydras
- if (numberOfButtons == 12 && numberOfTriggers == 2 && controllersPerTrigger == 2) {
- if (!hydraConnected) {
- hydraConnected = true;
- }
-
- leftController.update();
- rightController.update();
- moveEntities(deltaTime);
- } else {
- if (hydraConnected) {
- hydraConnected = false;
-
- leftController.showLaser(false);
- rightController.showLaser(false);
- }
- }
-}
-
-var glowedEntityID = { id: -1, isKnownID: false };
-
-// In order for editVoxels and editModels to play nice together, they each check to see if a "delete" menu item already
-// exists. If it doesn't they add it. If it does they don't. They also only delete the menu item if they were the one that
-// added it.
-var ROOT_MENU = "Edit";
-var ITEM_BEFORE = "Physics";
-var MENU_SEPARATOR = "Models";
-var EDIT_PROPERTIES = "Edit Properties...";
-var INTERSECTION_STATS = "Print Intersection Stats";
-var DELETE = "Delete";
-var LARGE_MODELS = "Allow Selecting of Large Models";
-var SMALL_MODELS = "Allow Selecting of Small Models";
- var LIGHTS = "Allow Selecting of Lights";
-
-var modelMenuAddedDelete = false;
-var originalLightsArePickable = Entities.getLightsArePickable();
-function setupModelMenus() {
- print("setupModelMenus()");
- // adj our menuitems
- Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: MENU_SEPARATOR, isSeparator: true, beforeItem: ITEM_BEFORE });
- Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: EDIT_PROPERTIES,
- shortcutKeyEvent: { text: "`" }, afterItem: MENU_SEPARATOR });
- Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: INTERSECTION_STATS, afterItem: MENU_SEPARATOR });
- if (!Menu.menuItemExists(ROOT_MENU, DELETE)) {
- print("no delete... adding ours");
- Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: DELETE,
- shortcutKeyEvent: { text: "backspace" }, afterItem: MENU_SEPARATOR });
- modelMenuAddedDelete = true;
- } else {
- print("delete exists... don't add ours");
- }
-
- Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: LARGE_MODELS, shortcutKey: "CTRL+META+L",
- afterItem: DELETE, isCheckable: true });
- Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: SMALL_MODELS, shortcutKey: "CTRL+META+S",
- afterItem: LARGE_MODELS, isCheckable: true });
- Menu.addMenuItem({ menuName: ROOT_MENU, menuItemName: LIGHTS, shortcutKey: "CTRL+SHIFT+META+L",
- afterItem: SMALL_MODELS, isCheckable: true });
-
- Entities.setLightsArePickable(false);
-}
-
-function cleanupModelMenus() {
- Menu.removeSeparator(ROOT_MENU, MENU_SEPARATOR);
- Menu.removeMenuItem(ROOT_MENU, EDIT_PROPERTIES);
- Menu.removeMenuItem(ROOT_MENU, INTERSECTION_STATS);
- if (modelMenuAddedDelete) {
- // delete our menuitems
- Menu.removeMenuItem(ROOT_MENU, DELETE);
- }
-
- Menu.removeMenuItem(ROOT_MENU, LARGE_MODELS);
- Menu.removeMenuItem(ROOT_MENU, SMALL_MODELS);
- Menu.removeMenuItem(ROOT_MENU, LIGHTS);
-
+function update(deltaTime) {
+ rightController.update(deltaTime);
}
function scriptEnding() {
- leftController.cleanup();
- rightController.cleanup();
- cleanupModelMenus();
- Entities.setLightsArePickable(originalLightsArePickable);
-}
-Script.scriptEnding.connect(scriptEnding);
-
-// register the call back so it fires before each data send
-Script.update.connect(checkController);
-
-setupModelMenus();
-
-var editModelID = -1;
-function showPropertiesForm(editModelID) {
- entityPropertyDialogBox.openDialog(editModelID);
+ rightController.cleanup();
}
-Menu.menuItemEvent.connect(function (menuItem) {
- print("menuItemEvent() in JS... menuItem=" + menuItem);
- if (menuItem == SMALL_MODELS) {
- allowSmallModels = Menu.isOptionChecked(SMALL_MODELS);
- } else if (menuItem == LARGE_MODELS) {
- allowLargeModels = Menu.isOptionChecked(LARGE_MODELS);
- } else if (menuItem == LIGHTS) {
- Entities.setLightsArePickable(Menu.isOptionChecked(LIGHTS));
- } else if (menuItem == DELETE) {
- if (leftController.grabbing) {
- print(" Delete Entity.... leftController.entityID="+ leftController.entityID);
- Entities.deleteEntity(leftController.entityID);
- leftController.grabbing = false;
- if (glowedEntityID.id == leftController.entityID.id) {
- glowedEntityID = { id: -1, isKnownID: false };
- }
- } else if (rightController.grabbing) {
- print(" Delete Entity.... rightController.entityID="+ rightController.entityID);
- Entities.deleteEntity(rightController.entityID);
- rightController.grabbing = false;
- if (glowedEntityID.id == rightController.entityID.id) {
- glowedEntityID = { id: -1, isKnownID: false };
- }
- } else {
- print(" Delete Entity.... not holding...");
- }
- } else if (menuItem == EDIT_PROPERTIES) {
- editModelID = -1;
- if (leftController.grabbing) {
- print(" Edit Properties.... leftController.entityID="+ leftController.entityID);
- editModelID = leftController.entityID;
- } else if (rightController.grabbing) {
- print(" Edit Properties.... rightController.entityID="+ rightController.entityID);
- editModelID = rightController.entityID;
- } else {
- print(" Edit Properties.... not holding...");
- }
- if (editModelID != -1) {
- print(" Edit Properties.... about to edit properties...");
- showPropertiesForm(editModelID);
- }
- } else if (menuItem == INTERSECTION_STATS) {
- printIntersectionsStats();
- }
-});
+function vectorIsZero(v) {
+ return v.x === 0 && v.y === 0 && v.z === 0;
+}
-Controller.keyReleaseEvent.connect(function (event) {
- // since sometimes our menu shortcut keys don't work, trap our menu items here also and fire the appropriate menu items
- if (event.text == "`") {
- handeMenuEvent(EDIT_PROPERTIES);
- }
- if (event.text == "BACKSPACE") {
- handeMenuEvent(DELETE);
- }
-});
+var rightController = new controller(RIGHT);
+
+Script.update.connect(update);
+Script.scriptEnding.connect(scriptEnding);
\ No newline at end of file
diff --git a/examples/example/games/hydraGrabHockey.js b/examples/example/games/hydraGrabHockey.js
new file mode 100644
index 0000000000..9834dbad9f
--- /dev/null
+++ b/examples/example/games/hydraGrabHockey.js
@@ -0,0 +1,299 @@
+//
+// hydraGrabHockey.js
+// examples
+//
+// Created by Eric Levin on 5/14/15.
+// Copyright 2015 High Fidelity, Inc.
+//
+// This script allows you to grab and move physical objects with the hydra
+// Same as hydraGrab.js, but you object movement is constrained to xz plane and cannot rotate object
+//
+//
+// Using the hydras :
+// grab physical entities with the right hydra trigger
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+
+var addedVelocity, newVelocity, angularVelocity, dT, cameraEntityDistance;
+var RIGHT = 1;
+var LASER_WIDTH = 3;
+var LASER_COLOR = {
+ red: 50,
+ green: 150,
+ blue: 200
+};
+var LASER_HOVER_COLOR = {
+ red: 200,
+ green: 50,
+ blue: 50
+};
+
+var DROP_DISTANCE = 5.0;
+var DROP_COLOR = {
+ red: 200,
+ green: 200,
+ blue: 200
+};
+
+var FULL_STRENGTH = 0.2;
+var LASER_LENGTH_FACTOR = 500;
+var CLOSE_ENOUGH = 0.001;
+var SPRING_RATE = 1.5;
+var DAMPING_RATE = 0.8;
+var SCREEN_TO_METERS = 0.001;
+var DISTANCE_SCALE_FACTOR = 1000
+
+var grabSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/CloseClamp.wav");
+var releaseSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/ReleaseClamp.wav");
+
+function getRayIntersection(pickRay) {
+ var intersection = Entities.findRayIntersection(pickRay);
+ return intersection;
+}
+
+function controller(side) {
+ this.triggerHeld = false;
+ this.triggerThreshold = 0.9;
+ this.side = side;
+ this.palm = 2 * side;
+ this.tip = 2 * side + 1;
+ this.trigger = side;
+ this.originalGravity = {
+ x: 0,
+ y: 0,
+ z: 0
+ };
+
+ this.laser = Overlays.addOverlay("line3d", {
+ start: {
+ x: 0,
+ y: 0,
+ z: 0
+ },
+ end: {
+ x: 0,
+ y: 0,
+ z: 0
+ },
+ color: LASER_COLOR,
+ alpha: 1,
+ lineWidth: LASER_WIDTH,
+ anchor: "MyAvatar"
+ });
+
+ this.dropLine = Overlays.addOverlay("line3d", {
+ color: DROP_COLOR,
+ alpha: 1,
+ visible: false,
+ lineWidth: 2
+ });
+
+
+ this.update = function(deltaTime) {
+ this.updateControllerState();
+ this.moveLaser();
+ this.checkTrigger();
+ this.checkEntityIntersection();
+ if (this.grabbing) {
+ this.updateEntity(deltaTime);
+ }
+
+ this.oldPalmPosition = this.palmPosition;
+ this.oldTipPosition = this.tipPosition;
+ }
+
+ this.updateEntity = function(deltaTime) {
+ this.dControllerPosition = Vec3.subtract(this.palmPosition, this.oldPalmPosition);
+ this.dControllerPosition.y = 0;
+ this.cameraEntityDistance = Vec3.distance(Camera.getPosition(), this.currentPosition);
+ this.targetPosition = Vec3.sum(this.targetPosition, Vec3.multiply(this.dControllerPosition, this.cameraEntityDistance * SCREEN_TO_METERS * DISTANCE_SCALE_FACTOR));
+
+ this.entityProps = Entities.getEntityProperties(this.grabbedEntity);
+ this.currentPosition = this.entityProps.position;
+ this.currentVelocity = this.entityProps.velocity;
+
+ var dPosition = Vec3.subtract(this.targetPosition, this.currentPosition);
+ this.distanceToTarget = Vec3.length(dPosition);
+ if (this.distanceToTarget > CLOSE_ENOUGH) {
+ // compute current velocity in the direction we want to move
+ this.velocityTowardTarget = Vec3.dot(this.currentVelocity, Vec3.normalize(dPosition));
+ this.velocityTowardTarget = Vec3.multiply(Vec3.normalize(dPosition), this.velocityTowardTarget);
+ // compute the speed we would like to be going toward the target position
+
+ this.desiredVelocity = Vec3.multiply(dPosition, (1.0 / deltaTime) * SPRING_RATE);
+ // compute how much we want to add to the existing velocity
+ this.addedVelocity = Vec3.subtract(this.desiredVelocity, this.velocityTowardTarget);
+ //If target is to far, roll off force as inverse square of distance
+ if(this.distanceToTarget/ this.cameraEntityDistance > FULL_STRENGTH) {
+ this.addedVelocity = Vec3.multiply(this.addedVelocity, Math.pow(FULL_STRENGTH/ this.distanceToTarget, 2.0));
+ }
+ this.newVelocity = Vec3.sum(this.currentVelocity, this.addedVelocity);
+ this.newVelocity = Vec3.subtract(this.newVelocity, Vec3.multiply(this.newVelocity, DAMPING_RATE));
+ } else {
+ this.newVelocity = {
+ x: 0,
+ y: 0,
+ z: 0
+ };
+ }
+ Entities.editEntity(this.grabbedEntity, {
+ velocity: this.newVelocity,
+ });
+
+ this.updateDropLine(this.targetPosition);
+
+ }
+
+
+ this.updateControllerState = function() {
+ this.palmPosition = Controller.getSpatialControlPosition(this.palm);
+ this.tipPosition = Controller.getSpatialControlPosition(this.tip);
+ this.triggerValue = Controller.getTriggerValue(this.trigger);
+ }
+
+ this.checkTrigger = function() {
+ if (this.triggerValue > this.triggerThreshold && !this.triggerHeld) {
+ this.triggerHeld = true;
+ } else if (this.triggerValue < this.triggerThreshold && this.triggerHeld) {
+ this.triggerHeld = false;
+ if (this.grabbing) {
+ this.release();
+ }
+ }
+ }
+
+
+ this.updateDropLine = function(position) {
+
+ Overlays.editOverlay(this.dropLine, {
+ visible: true,
+ start: {
+ x: position.x,
+ y: position.y + DROP_DISTANCE,
+ z: position.z
+ },
+ end: {
+ x: position.x,
+ y: position.y - DROP_DISTANCE,
+ z: position.z
+ }
+ });
+
+ }
+
+ this.checkEntityIntersection = function() {
+
+ var pickRay = {
+ origin: this.palmPosition,
+ direction: Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition))
+ };
+ var intersection = getRayIntersection(pickRay);
+ if (intersection.intersects && intersection.properties.collisionsWillMove) {
+ this.laserWasHovered = true;
+ if (this.triggerHeld && !this.grabbing) {
+ this.grab(intersection.entityID);
+ }
+ Overlays.editOverlay(this.laser, {
+ color: LASER_HOVER_COLOR
+ });
+ } else if (this.laserWasHovered) {
+ this.laserWasHovered = false;
+ Overlays.editOverlay(this.laser, {
+ color: LASER_COLOR
+ });
+ }
+ }
+
+ this.grab = function(entityId) {
+ this.grabbing = true;
+ this.grabbedEntity = entityId;
+ this.entityProps = Entities.getEntityProperties(this.grabbedEntity);
+ this.targetPosition = this.entityProps.position;
+ this.currentPosition = this.targetPosition;
+ this.oldPalmPosition = this.palmPosition;
+ this.originalGravity = this.entityProps.gravity;
+ Entities.editEntity(this.grabbedEntity, {
+ gravity: {
+ x: 0,
+ y: 0,
+ z: 0
+ }
+ });
+ Overlays.editOverlay(this.laser, {
+ visible: false
+ });
+ Audio.playSound(grabSound, {
+ position: this.entityProps.position,
+ volume: 0.25
+ });
+ }
+
+ this.release = function() {
+ this.grabbing = false;
+ this.grabbedEntity = null;
+ Overlays.editOverlay(this.laser, {
+ visible: true
+ });
+ Overlays.editOverlay(this.dropLine, {
+ visible: false
+ });
+
+ Audio.playSound(releaseSound, {
+ position: this.entityProps.position,
+ volume: 0.25
+ });
+
+ // only restore the original gravity if it's not zero. This is to avoid...
+ // 1. interface A grabs an entity and locally saves off its gravity
+ // 2. interface A sets the entity's gravity to zero
+ // 3. interface B grabs the entity and saves off its gravity (which is zero)
+ // 4. interface A releases the entity and puts the original gravity back
+ // 5. interface B releases the entity and puts the original gravity back (to zero)
+ if(vectorIsZero(this.originalGravity)) {
+ Entities.editEntity(this.grabbedEntity, {
+ gravity: this.originalGravity
+ });
+ }
+ }
+
+ this.moveLaser = function() {
+ var inverseRotation = Quat.inverse(MyAvatar.orientation);
+ var startPosition = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.palmPosition, MyAvatar.position));
+ // startPosition = Vec3.multiply(startPosition, 1 / MyAvatar.scale);
+ var direction = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.tipPosition, this.palmPosition));
+ direction = Vec3.multiply(direction, LASER_LENGTH_FACTOR / (Vec3.length(direction) * MyAvatar.scale));
+ var endPosition = Vec3.sum(startPosition, direction);
+
+ Overlays.editOverlay(this.laser, {
+ start: startPosition,
+ end: endPosition
+ });
+
+ }
+
+ this.cleanup = function() {
+ Overlays.deleteOverlay(this.laser);
+ Overlays.deleteOverlay(this.dropLine);
+ }
+}
+
+function update(deltaTime) {
+ rightController.update(deltaTime);
+}
+
+function scriptEnding() {
+ rightController.cleanup();
+}
+
+function vectorIsZero(v) {
+ return v.x === 0 && v.y === 0 && v.z === 0;
+}
+
+var rightController = new controller(RIGHT);
+
+
+Script.update.connect(update);
+Script.scriptEnding.connect(scriptEnding);
\ No newline at end of file
diff --git a/examples/html/entityProperties.html b/examples/html/entityProperties.html
index 25467f7573..0e3494bff8 100644
--- a/examples/html/entityProperties.html
+++ b/examples/html/entityProperties.html
@@ -233,6 +233,9 @@
var elAngularVelocityZ = document.getElementById("property-avel-z");
var elAngularDamping = document.getElementById("property-adamping");
+ var elRestitution = document.getElementById("property-restitution");
+ var elFriction = document.getElementById("property-friction");
+
var elGravityX = document.getElementById("property-grav-x");
var elGravityY = document.getElementById("property-grav-y");
var elGravityZ = document.getElementById("property-grav-z");
@@ -427,6 +430,9 @@
elAngularVelocityZ.value = (properties.angularVelocity.z * RADIANS_TO_DEGREES).toFixed(2);
elAngularDamping.value = properties.angularDamping.toFixed(2);
+ elRestitution.value = properties.restitution.toFixed(2);
+ elFriction.value = properties.friction.toFixed(2);
+
elGravityX.value = properties.gravity.x.toFixed(2);
elGravityY.value = properties.gravity.y.toFixed(2);
elGravityZ.value = properties.gravity.z.toFixed(2);
@@ -607,7 +613,10 @@
elAngularVelocityX.addEventListener('change', angularVelocityChangeFunction);
elAngularVelocityY.addEventListener('change', angularVelocityChangeFunction);
elAngularVelocityZ.addEventListener('change', angularVelocityChangeFunction);
- elAngularDamping.addEventListener('change', createEmitNumberPropertyUpdateFunction('angularDamping'))
+ elAngularDamping.addEventListener('change', createEmitNumberPropertyUpdateFunction('angularDamping'));
+
+ elRestitution.addEventListener('change', createEmitNumberPropertyUpdateFunction('restitution'));
+ elFriction.addEventListener('change', createEmitNumberPropertyUpdateFunction('friction'));
var gravityChangeFunction = createEmitVec3PropertyUpdateFunction(
'gravity', elGravityX, elGravityY, elGravityZ);
@@ -963,6 +972,18 @@
+
+
Gravity
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index b8e17738f3..20c93a7ae5 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -2456,7 +2456,7 @@ void Application::update(float deltaTime) {
if (_physicsEngine.hasOutgoingChanges()) {
_entitySimulation.lock();
- _entitySimulation.handleOutgoingChanges(_physicsEngine.getOutgoingChanges());
+ _entitySimulation.handleOutgoingChanges(_physicsEngine.getOutgoingChanges(), _physicsEngine.getSessionID());
_entitySimulation.handleCollisionEvents(_physicsEngine.getCollisionEvents());
_entitySimulation.unlock();
_physicsEngine.dumpStatsIfNecessary();
diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp
index 09a3407d08..8a627c019c 100644
--- a/interface/src/avatar/Avatar.cpp
+++ b/interface/src/avatar/Avatar.cpp
@@ -81,7 +81,7 @@ Avatar::Avatar() :
{
// we may have been created in the network thread, but we live in the main thread
moveToThread(Application::getInstance()->thread());
-
+
// give the pointer to our head to inherited _headData variable from AvatarData
_headData = static_cast
(new Head(this));
_handData = static_cast(new Hand(this));
@@ -122,7 +122,7 @@ float Avatar::getLODDistance() const {
void Avatar::simulate(float deltaTime) {
PerformanceTimer perfTimer("simulate");
-
+
// update the avatar's position according to its referential
if (_referential) {
if (_referential->hasExtraData()) {
@@ -143,10 +143,10 @@ void Avatar::simulate(float deltaTime) {
break;
}
}
-
+
_referential->update();
}
-
+
if (_scale != _targetScale) {
setScale(_targetScale);
}
@@ -171,7 +171,7 @@ void Avatar::simulate(float deltaTime) {
getHand()->simulate(deltaTime, false);
}
_skeletonModel.setLODDistance(getLODDistance());
-
+
if (!_shouldRenderBillboard && inViewFrustum) {
{
PerformanceTimer perfTimer("skeleton");
@@ -198,7 +198,7 @@ void Avatar::simulate(float deltaTime) {
// update animation for display name fade in/out
if ( _displayNameTargetAlpha != _displayNameAlpha) {
- // the alpha function is
+ // the alpha function is
// Fade out => alpha(t) = factor ^ t => alpha(t+dt) = alpha(t) * factor^(dt)
// Fade in => alpha(t) = 1 - factor^t => alpha(t+dt) = 1-(1-alpha(t))*coef^(dt)
// factor^(dt) = coef
@@ -213,17 +213,17 @@ void Avatar::simulate(float deltaTime) {
_displayNameAlpha = abs(_displayNameAlpha - _displayNameTargetAlpha) < 0.01f ? _displayNameTargetAlpha : _displayNameAlpha;
}
- // NOTE: we shouldn't extrapolate an Avatar instance forward in time...
+ // NOTE: we shouldn't extrapolate an Avatar instance forward in time...
// until velocity is included in AvatarData update message.
//_position += _velocity * deltaTime;
measureMotionDerivatives(deltaTime);
}
-void Avatar::slamPosition(const glm::vec3& newPosition) {
+void Avatar::slamPosition(const glm::vec3& newPosition) {
AvatarData::setPosition(newPosition);
_positionDeltaAccumulator = glm::vec3(0.0f);
_velocity = glm::vec3(0.0f);
- _lastVelocity = glm::vec3(0.0f);
+ _lastVelocity = glm::vec3(0.0f);
}
void Avatar::applyPositionDelta(const glm::vec3& delta) {
@@ -249,7 +249,7 @@ void Avatar::measureMotionDerivatives(float deltaTime) {
}
enum TextRendererType {
- CHAT,
+ CHAT,
DISPLAYNAME
};
@@ -272,7 +272,7 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderArgs::RenderMode rend
if (_referential) {
_referential->update();
}
-
+
if (postLighting &&
glm::distance(DependencyManager::get()->getMyAvatar()->getPosition(), _position) < 10.0f) {
auto geometryCache = DependencyManager::get();
@@ -303,15 +303,15 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderArgs::RenderMode rend
float angle = glm::degrees(glm::angle(rotation));
glm::vec3 axis = glm::axis(rotation);
glRotatef(angle, axis.x, axis.y, axis.z);
-
+
geometryCache->renderLine(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, laserLength, 0.0f), laserColor);
-
+
} glPopMatrix();
}
}
if (_handState & RIGHT_HAND_POINTING_FLAG) {
-
+
if (_handState & IS_FINGER_POINTING_FLAG) {
int rightIndexTip = getJointIndex("RightHandIndex4");
int rightIndexTipJoint = getJointIndex("RightHandIndex3");
@@ -330,12 +330,12 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderArgs::RenderMode rend
glm::vec3 axis = glm::axis(rotation);
glRotatef(angle, axis.x, axis.y, axis.z);
geometryCache->renderLine(glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, laserLength, 0.0f), laserColor);
-
+
} glPopMatrix();
}
}
}
-
+
// simple frustum check
float boundingRadius = getBillboardSize();
ViewFrustum* frustum = nullptr;
@@ -351,24 +351,24 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderArgs::RenderMode rend
glm::vec3 toTarget = cameraPosition - getPosition();
float distanceToTarget = glm::length(toTarget);
-
+
{
// glow when moving far away
const float GLOW_DISTANCE = 20.0f;
const float GLOW_MAX_LOUDNESS = 2500.0f;
const float MAX_GLOW = 0.5f;
-
+
float GLOW_FROM_AVERAGE_LOUDNESS = ((this == DependencyManager::get()->getMyAvatar())
? 0.0f
: MAX_GLOW * getHeadData()->getAudioLoudness() / GLOW_MAX_LOUDNESS);
if (!Menu::getInstance()->isOptionChecked(MenuOption::GlowWhenSpeaking)) {
GLOW_FROM_AVERAGE_LOUDNESS = 0.0f;
}
-
+
float glowLevel = _moving && distanceToTarget > GLOW_DISTANCE && renderMode == RenderArgs::NORMAL_RENDER_MODE
? 1.0f
: GLOW_FROM_AVERAGE_LOUDNESS;
-
+
// render body
renderBody(frustum, renderMode, postLighting, glowLevel);
@@ -386,7 +386,7 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderArgs::RenderMode rend
distance * 2.0f, light.color, 0.5f, orientation, LIGHT_EXPONENT, LIGHT_CUTOFF);
}
}
-
+
if (postLighting) {
bool renderSkeleton = Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes);
bool renderHead = Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionShapes);
@@ -435,7 +435,7 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderArgs::RenderMode rend
glm::vec3 delta = height * (getHead()->getCameraOrientation() * IDENTITY_UP) / 2.0f;
float angle = abs(angleBetween(toTarget + delta, toTarget - delta));
float sphereRadius = getHead()->getAverageLoudness() * SPHERE_LOUDNESS_SCALING;
-
+
if (renderMode == RenderArgs::NORMAL_RENDER_MODE && (sphereRadius > MIN_SPHERE_SIZE) &&
(angle < MAX_SPHERE_ANGLE) && (angle > MIN_SPHERE_ANGLE)) {
glPushMatrix();
@@ -483,7 +483,7 @@ void Avatar::renderBody(ViewFrustum* renderFrustum, RenderArgs::RenderMode rende
Model::RenderMode modelRenderMode = renderMode;
{
Glower glower(glowLevel);
-
+
if (_shouldRenderBillboard || !(_skeletonModel.isRenderable() && getHead()->getFaceModel().isRenderable())) {
if (postLighting || renderMode == RenderArgs::SHADOW_RENDER_MODE) {
// render the billboard until both models are loaded
@@ -491,7 +491,7 @@ void Avatar::renderBody(ViewFrustum* renderFrustum, RenderArgs::RenderMode rende
}
return;
}
-
+
if (postLighting) {
getHand()->render(false, modelRenderMode);
} else {
@@ -553,43 +553,43 @@ void Avatar::renderBillboard() {
if (!_billboardTexture->isLoaded()) {
return;
}
-
+
glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_GREATER, 0.5f);
-
+
glEnable(GL_TEXTURE_2D);
glDisable(GL_LIGHTING);
glBindTexture(GL_TEXTURE_2D, _billboardTexture->getID());
-
+
glPushMatrix();
glTranslatef(_position.x, _position.y, _position.z);
-
+
// rotate about vertical to face the camera
glm::quat rotation = getOrientation();
glm::vec3 cameraVector = glm::inverse(rotation) * (Application::getInstance()->getCamera()->getPosition() - _position);
rotation = rotation * glm::angleAxis(atan2f(-cameraVector.x, -cameraVector.z), glm::vec3(0.0f, 1.0f, 0.0f));
glm::vec3 axis = glm::axis(rotation);
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
-
+
// compute the size from the billboard camera parameters and scale
float size = getBillboardSize();
glScalef(size, size, size);
-
+
glm::vec2 topLeft(-1.0f, -1.0f);
glm::vec2 bottomRight(1.0f, 1.0f);
glm::vec2 texCoordTopLeft(0.0f, 0.0f);
glm::vec2 texCoordBottomRight(1.0f, 1.0f);
- DependencyManager::get()->renderQuad(topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight,
+ DependencyManager::get()->renderQuad(topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight,
glm::vec4(1.0f, 1.0f, 1.0f, 1.0f));
-
+
glPopMatrix();
-
+
glDisable(GL_TEXTURE_2D);
glEnable(GL_LIGHTING);
glDisable(GL_ALPHA_TEST);
-
+
glBindTexture(GL_TEXTURE_2D, 0);
}
@@ -611,26 +611,26 @@ glm::vec3 Avatar::getDisplayNamePosition() {
float Avatar::calculateDisplayNameScaleFactor(const glm::vec3& textPosition, bool inHMD) {
// We need to compute the scale factor such as the text remains with fixed size respect to window coordinates
- // We project a unit vector and check the difference in screen coordinates, to check which is the
+ // We project a unit vector and check the difference in screen coordinates, to check which is the
// correction scale needed
- // save the matrices for later scale correction factor
+ // save the matrices for later scale correction factor
// The up vector must be relative to the rotation current rotation matrix:
// we set the identity
glm::vec3 testPoint0 = textPosition;
glm::vec3 testPoint1 = textPosition + (Application::getInstance()->getCamera()->getRotation() * IDENTITY_UP);
-
+
double textWindowHeight;
-
+
GLint viewportMatrix[4];
glGetIntegerv(GL_VIEWPORT, viewportMatrix);
glm::dmat4 modelViewMatrix;
float windowSizeX = viewportMatrix[2] - viewportMatrix[0];
float windowSizeY = viewportMatrix[3] - viewportMatrix[1];
-
+
glm::dmat4 projectionMatrix;
Application::getInstance()->getModelViewMatrix(&modelViewMatrix);
Application::getInstance()->getProjectionMatrix(&projectionMatrix);
-
+
glm::dvec4 p0 = modelViewMatrix * glm::dvec4(testPoint0, 1.0);
p0 = projectionMatrix * p0;
@@ -655,23 +655,25 @@ float Avatar::calculateDisplayNameScaleFactor(const glm::vec3& textPosition, boo
void Avatar::renderDisplayName() {
- if (_displayName.isEmpty() || _displayNameAlpha == 0.0f) {
+ bool shouldShowReceiveStats = DependencyManager::get()->shouldShowReceiveStats();
+
+ if ((_displayName.isEmpty() && !shouldShowReceiveStats) || _displayNameAlpha == 0.0f) {
return;
}
-
+
// which viewing mode?
bool inHMD = Application::getInstance()->isHMDMode();
-
+
glDisable(GL_LIGHTING);
-
+
glPushMatrix();
glm::vec3 textPosition = getDisplayNamePosition();
-
- glTranslatef(textPosition.x, textPosition.y, textPosition.z);
+
+ glTranslatef(textPosition.x, textPosition.y, textPosition.z);
// we need "always facing camera": we must remove the camera rotation from the stack
-
+
glm::vec3 frontAxis(0.0f, 0.0f, 1.0f);
if (inHMD) {
glm::vec3 camPosition = Application::getInstance()->getCamera()->getPosition();
@@ -680,22 +682,48 @@ void Avatar::renderDisplayName() {
glm::quat rotation = Application::getInstance()->getCamera()->getRotation();
frontAxis = glm::rotate(rotation, frontAxis);
}
-
+
frontAxis = glm::normalize(glm::vec3(frontAxis.z, 0.0f, -frontAxis.x));
float angle = acos(frontAxis.x) * ((frontAxis.z < 0) ? 1.0f : -1.0f);
glRotatef(glm::degrees(angle), 0.0f, 1.0f, 0.0f);
-
+
float scaleFactor = calculateDisplayNameScaleFactor(textPosition, inHMD);
glScalef(scaleFactor, -scaleFactor, scaleFactor); // TextRenderer::draw paints the text upside down in y axis
- int text_x = -_displayNameBoundingRect.width() / 2;
- int text_y = -_displayNameBoundingRect.height() / 2;
+ // optionally render timing stats for this avatar with the display name
+ QString renderedDisplayName = _displayName;
+ QRect nameDynamicRect = _displayNameBoundingRect;
+
+ if (shouldShowReceiveStats) {
+ float kilobitsPerSecond = getAverageBytesReceivedPerSecond() / (float) BYTES_PER_KILOBIT;
+
+ QString statsFormat = QString("(%1 Kbps, %2 Hz)");
+
+ if (!renderedDisplayName.isEmpty()) {
+ statsFormat.prepend(" - ");
+ }
+
+ QString statsText = statsFormat.arg(QString::number(kilobitsPerSecond, 'f', 2)).arg(getReceiveRate());
+ glm::vec2 extent = textRenderer(DISPLAYNAME)->computeExtent(statsText);
+
+ // add the extent required for the stats to whatever was calculated for the display name
+ nameDynamicRect.setWidth(nameDynamicRect.width() + extent.x);
+
+ if (extent.y > nameDynamicRect.height()) {
+ nameDynamicRect.setHeight(extent.y);
+ }
+
+ renderedDisplayName += statsText;
+ }
+
+ int text_x = -nameDynamicRect.width() / 2;
+ int text_y = -nameDynamicRect.height() / 2;
// draw a gray background
- int left = text_x + _displayNameBoundingRect.x();
- int right = left + _displayNameBoundingRect.width();
- int bottom = text_y + _displayNameBoundingRect.y();
- int top = bottom + _displayNameBoundingRect.height();
+ int left = text_x + nameDynamicRect.x();
+ int right = left + nameDynamicRect.width();
+ int bottom = text_y + nameDynamicRect.y();
+ int top = bottom + nameDynamicRect.height();
const int border = 8;
bottom -= border;
left -= border;
@@ -708,22 +736,11 @@ void Avatar::renderDisplayName() {
DependencyManager::get()->renderBevelCornersRect(left, bottom, right - left, top - bottom, 3,
glm::vec4(0.2f, 0.2f, 0.2f, _displayNameAlpha * DISPLAYNAME_BACKGROUND_ALPHA / DISPLAYNAME_ALPHA));
-
+
glm::vec4 color(0.93f, 0.93f, 0.93f, _displayNameAlpha);
-
- // optionally render timing stats for this avatar with the display name
- QString renderedDisplayName = _displayName;
-
- if (DependencyManager::get()->shouldShowReceiveStats()) {
- float kilobitsPerSecond = getAverageBytesReceivedPerSecond() / (float) BYTES_PER_KILOBIT;
-
- renderedDisplayName += QString(" - (%1 Kbps, %2 Hz)")
- .arg(QString::number(kilobitsPerSecond, 'f', 2))
- .arg(getReceiveRate());
- }
-
+
QByteArray nameUTF8 = renderedDisplayName.toLocal8Bit();
-
+
glDisable(GL_POLYGON_OFFSET_FILL);
textRenderer(DISPLAYNAME)->draw(text_x, text_y, nameUTF8.data(), color);
@@ -769,11 +786,11 @@ void Avatar::setSkeletonOffset(const glm::vec3& offset) {
}
}
-glm::vec3 Avatar::getSkeletonPosition() const {
- // The avatar is rotated PI about the yAxis, so we have to correct for it
+glm::vec3 Avatar::getSkeletonPosition() const {
+ // The avatar is rotated PI about the yAxis, so we have to correct for it
// to get the skeleton offset contribution in the world-frame.
const glm::quat FLIP = glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f));
- return _position + getOrientation() * FLIP * _skeletonOffset;
+ return _position + getOrientation() * FLIP * _skeletonOffset;
}
QVector Avatar::getJointRotations() const {
@@ -868,7 +885,7 @@ const float SCRIPT_PRIORITY = DEFAULT_PRIORITY + 1.0f;
void Avatar::setJointModelPositionAndOrientation(int index, glm::vec3 position, const glm::quat& rotation) {
if (QThread::currentThread() != thread()) {
- QMetaObject::invokeMethod(const_cast(this), "setJointModelPositionAndOrientation",
+ QMetaObject::invokeMethod(const_cast(this), "setJointModelPositionAndOrientation",
Qt::AutoConnection, Q_ARG(const int, index), Q_ARG(const glm::vec3, position),
Q_ARG(const glm::quat&, rotation));
} else {
@@ -878,7 +895,7 @@ void Avatar::setJointModelPositionAndOrientation(int index, glm::vec3 position,
void Avatar::setJointModelPositionAndOrientation(const QString& name, glm::vec3 position, const glm::quat& rotation) {
if (QThread::currentThread() != thread()) {
- QMetaObject::invokeMethod(const_cast(this), "setJointModelPositionAndOrientation",
+ QMetaObject::invokeMethod(const_cast(this), "setJointModelPositionAndOrientation",
Qt::AutoConnection, Q_ARG(const QString&, name), Q_ARG(const glm::vec3, position),
Q_ARG(const glm::quat&, rotation));
} else {
@@ -919,7 +936,7 @@ void Avatar::setAttachmentData(const QVector& attachmentData) {
while (_attachmentModels.size() > attachmentData.size()) {
delete _attachmentModels.takeLast();
}
-
+
// update the urls
for (int i = 0; i < attachmentData.size(); i++) {
_attachmentModels[i]->setURL(attachmentData.at(i).modelURL);
@@ -932,12 +949,12 @@ void Avatar::setDisplayName(const QString& displayName) {
AvatarData::setDisplayName(displayName);
// FIXME is this a sufficient replacement for tightBoundingRect?
glm::vec2 extent = textRenderer(DISPLAYNAME)->computeExtent(displayName);
- _displayNameBoundingRect = QRect(QPoint(0, 0), QPoint((int)extent.x, (int)extent.y));
+ _displayNameBoundingRect = QRect(0, 0, (int)extent.x, (int)extent.y);
}
void Avatar::setBillboard(const QByteArray& billboard) {
AvatarData::setBillboard(billboard);
-
+
// clear out any existing billboard texture
_billboardTexture.reset();
}
@@ -947,65 +964,65 @@ int Avatar::parseDataAtOffset(const QByteArray& packet, int offset) {
// now that we have data for this Avatar we are go for init
init();
}
-
+
// change in position implies movement
glm::vec3 oldPosition = _position;
-
+
int bytesRead = AvatarData::parseDataAtOffset(packet, offset);
-
+
const float MOVE_DISTANCE_THRESHOLD = 0.001f;
_moving = glm::distance(oldPosition, _position) > MOVE_DISTANCE_THRESHOLD;
-
+
return bytesRead;
}
int Avatar::_jointConesID = GeometryCache::UNKNOWN_ID;
// render a makeshift cone section that serves as a body part connecting joint spheres
-void Avatar::renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2,
+void Avatar::renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2,
float radius1, float radius2, const glm::vec4& color) {
-
+
auto geometryCache = DependencyManager::get();
-
+
if (_jointConesID == GeometryCache::UNKNOWN_ID) {
_jointConesID = geometryCache->allocateID();
}
-
+
glm::vec3 axis = position2 - position1;
float length = glm::length(axis);
-
+
if (length > 0.0f) {
-
+
axis /= length;
-
+
glm::vec3 perpSin = glm::vec3(1.0f, 0.0f, 0.0f);
glm::vec3 perpCos = glm::normalize(glm::cross(axis, perpSin));
perpSin = glm::cross(perpCos, axis);
-
+
float anglea = 0.0f;
float angleb = 0.0f;
QVector points;
-
+
for (int i = 0; i < NUM_BODY_CONE_SIDES; i ++) {
-
+
// the rectangles that comprise the sides of the cone section are
// referenced by "a" and "b" in one dimension, and "1", and "2" in the other dimension.
anglea = angleb;
angleb = ((float)(i+1) / (float)NUM_BODY_CONE_SIDES) * TWO_PI;
-
+
float sa = sinf(anglea);
float sb = sinf(angleb);
float ca = cosf(anglea);
float cb = cosf(angleb);
-
+
glm::vec3 p1a = position1 + perpSin * sa * radius1 + perpCos * ca * radius1;
- glm::vec3 p1b = position1 + perpSin * sb * radius1 + perpCos * cb * radius1;
- glm::vec3 p2a = position2 + perpSin * sa * radius2 + perpCos * ca * radius2;
- glm::vec3 p2b = position2 + perpSin * sb * radius2 + perpCos * cb * radius2;
-
+ glm::vec3 p1b = position1 + perpSin * sb * radius1 + perpCos * cb * radius1;
+ glm::vec3 p2a = position2 + perpSin * sa * radius2 + perpCos * ca * radius2;
+ glm::vec3 p2b = position2 + perpSin * sb * radius2 + perpCos * cb * radius2;
+
points << p1a << p1b << p2a << p1b << p2a << p2b;
}
-
+
// TODO: this is really inefficient constantly recreating these vertices buffers. It would be
// better if the avatars cached these buffers for each of the joints they are rendering
geometryCache->updateVertices(_jointConesID, points, color);
@@ -1052,7 +1069,7 @@ void Avatar::setShowDisplayName(bool showDisplayName) {
_displayNameAlpha = 0.0f;
return;
}
-
+
// For myAvatar, the alpha update is not done (called in simulate for other avatars)
if (DependencyManager::get()->getMyAvatar() == this) {
if (showDisplayName) {
@@ -1060,7 +1077,7 @@ void Avatar::setShowDisplayName(bool showDisplayName) {
} else {
_displayNameAlpha = 0.0f;
}
- }
+ }
if (showDisplayName) {
_displayNameTargetAlpha = DISPLAYNAME_ALPHA;
diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp
index 470426d55e..874dc079ca 100644
--- a/libraries/entities/src/EntityItem.cpp
+++ b/libraries/entities/src/EntityItem.cpp
@@ -51,6 +51,8 @@ EntityItem::EntityItem(const EntityItemID& entityItemID) :
_gravity(ENTITY_ITEM_DEFAULT_GRAVITY),
_acceleration(ENTITY_ITEM_DEFAULT_ACCELERATION),
_damping(ENTITY_ITEM_DEFAULT_DAMPING),
+ _restitution(ENTITY_ITEM_DEFAULT_RESTITUTION),
+ _friction(ENTITY_ITEM_DEFAULT_FRICTION),
_lifetime(ENTITY_ITEM_DEFAULT_LIFETIME),
_script(ENTITY_ITEM_DEFAULT_SCRIPT),
_collisionSoundURL(ENTITY_ITEM_DEFAULT_COLLISION_SOUND_URL),
@@ -100,6 +102,8 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
requestedProperties += PROP_GRAVITY;
requestedProperties += PROP_ACCELERATION;
requestedProperties += PROP_DAMPING;
+ requestedProperties += PROP_RESTITUTION;
+ requestedProperties += PROP_FRICTION;
requestedProperties += PROP_LIFETIME;
requestedProperties += PROP_SCRIPT;
requestedProperties += PROP_COLLISION_SOUND_URL;
@@ -227,6 +231,8 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
APPEND_ENTITY_PROPERTY(PROP_GRAVITY, getGravity());
APPEND_ENTITY_PROPERTY(PROP_ACCELERATION, getAcceleration());
APPEND_ENTITY_PROPERTY(PROP_DAMPING, getDamping());
+ APPEND_ENTITY_PROPERTY(PROP_RESTITUTION, getRestitution());
+ APPEND_ENTITY_PROPERTY(PROP_FRICTION, getFriction());
APPEND_ENTITY_PROPERTY(PROP_LIFETIME, getLifetime());
APPEND_ENTITY_PROPERTY(PROP_SCRIPT, getScript());
APPEND_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, getRegistrationPoint());
@@ -552,7 +558,9 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
READ_ENTITY_PROPERTY(PROP_ACCELERATION, glm::vec3, setAcceleration);
}
- READ_ENTITY_PROPERTY(PROP_DAMPING, float, setDamping);
+ READ_ENTITY_PROPERTY(PROP_DAMPING, float, updateDamping);
+ READ_ENTITY_PROPERTY(PROP_RESTITUTION, float, updateRestitution);
+ READ_ENTITY_PROPERTY(PROP_FRICTION, float, updateFriction);
READ_ENTITY_PROPERTY(PROP_LIFETIME, float, updateLifetime);
READ_ENTITY_PROPERTY(PROP_SCRIPT, QString, setScript);
READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, setRegistrationPoint);
@@ -561,7 +569,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
} else {
READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocityInDegrees);
}
- READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, setAngularDamping);
+ READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, updateAngularDamping);
READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, setVisible);
READ_ENTITY_PROPERTY(PROP_IGNORE_FOR_COLLISIONS, bool, updateIgnoreForCollisions);
READ_ENTITY_PROPERTY(PROP_COLLISIONS_WILL_MOVE, bool, updateCollisionsWillMove);
@@ -569,7 +577,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
READ_ENTITY_PROPERTY(PROP_USER_DATA, QString, setUserData);
if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ACCELERATION) {
- READ_ENTITY_PROPERTY(PROP_SIMULATOR_ID, QUuid, setSimulatorID);
+ READ_ENTITY_PROPERTY(PROP_SIMULATOR_ID, QUuid, updateSimulatorID);
}
if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_MARKETPLACE_ID) {
@@ -697,17 +705,6 @@ void EntityItem::setMass(float mass) {
}
}
-const float DEFAULT_ENTITY_RESTITUTION = 0.5f;
-const float DEFAULT_ENTITY_FRICTION = 0.5f;
-
-float EntityItem::getRestitution() const {
- return DEFAULT_ENTITY_RESTITUTION;
-}
-
-float EntityItem::getFriction() const {
- return DEFAULT_ENTITY_FRICTION;
-}
-
void EntityItem::simulate(const quint64& now) {
if (_lastSimulated == 0) {
_lastSimulated = now;
@@ -900,6 +897,8 @@ EntityItemProperties EntityItem::getProperties() const {
COPY_ENTITY_PROPERTY_TO_PROPERTIES(gravity, getGravity);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(acceleration, getAcceleration);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(damping, getDamping);
+ COPY_ENTITY_PROPERTY_TO_PROPERTIES(restitution, getRestitution);
+ COPY_ENTITY_PROPERTY_TO_PROPERTIES(friction, getFriction);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(lifetime, getLifetime);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(script, getScript);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(collisionSoundURL, getCollisionSoundURL);
@@ -933,6 +932,8 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
SET_ENTITY_PROPERTY_FROM_PROPERTIES(gravity, updateGravity);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(acceleration, setAcceleration);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(damping, updateDamping);
+ SET_ENTITY_PROPERTY_FROM_PROPERTIES(restitution, updateRestitution);
+ SET_ENTITY_PROPERTY_FROM_PROPERTIES(friction, updateFriction);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifetime, updateLifetime);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(script, setScript);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionSoundURL, setCollisionSoundURL);
@@ -946,7 +947,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) {
SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionsWillMove, updateCollisionsWillMove);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(locked, setLocked);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(userData, setUserData);
- SET_ENTITY_PROPERTY_FROM_PROPERTIES(simulatorID, setSimulatorID);
+ SET_ENTITY_PROPERTY_FROM_PROPERTIES(simulatorID, updateSimulatorID);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(marketplaceID, setMarketplaceID);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(name, setName);
@@ -1191,16 +1192,16 @@ void EntityItem::updateVelocityInDomainUnits(const glm::vec3& value) {
void EntityItem::updateVelocity(const glm::vec3& value) {
auto delta = glm::distance(_velocity, value);
if (delta > IGNORE_LINEAR_VELOCITY_DELTA) {
+ _dirtyFlags |= EntityItem::DIRTY_LINEAR_VELOCITY;
const float MIN_LINEAR_SPEED = 0.001f;
if (glm::length(value) < MIN_LINEAR_SPEED) {
_velocity = ENTITY_ITEM_ZERO_VEC3;
} else {
_velocity = value;
- }
- _dirtyFlags |= EntityItem::DIRTY_LINEAR_VELOCITY;
-
- if (delta > ACTIVATION_LINEAR_VELOCITY_DELTA) {
- _dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION;
+ // only activate when setting non-zero velocity
+ if (delta > ACTIVATION_LINEAR_VELOCITY_DELTA) {
+ _dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION;
+ }
}
}
}
@@ -1238,9 +1239,10 @@ void EntityItem::updateAngularVelocity(const glm::vec3& value) {
_angularVelocity = ENTITY_ITEM_ZERO_VEC3;
} else {
_angularVelocity = value;
- }
- if (delta > ACTIVATION_ANGULAR_VELOCITY_DELTA) {
- _dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION;
+ // only activate when setting non-zero velocity
+ if (delta > ACTIVATION_ANGULAR_VELOCITY_DELTA) {
+ _dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION;
+ }
}
}
}
@@ -1267,6 +1269,32 @@ void EntityItem::updateCollisionsWillMove(bool value) {
}
}
+void EntityItem::updateRestitution(float value) {
+ float clampedValue = glm::max(glm::min(ENTITY_ITEM_MAX_RESTITUTION, value), ENTITY_ITEM_MIN_RESTITUTION);
+ if (_restitution != clampedValue) {
+ _restitution = clampedValue;
+ _dirtyFlags |= EntityItem::DIRTY_MATERIAL;
+ }
+}
+
+void EntityItem::updateFriction(float value) {
+ float clampedValue = glm::max(glm::min(ENTITY_ITEM_MAX_FRICTION, value), ENTITY_ITEM_MIN_FRICTION);
+ if (_friction != clampedValue) {
+ _friction = clampedValue;
+ _dirtyFlags |= EntityItem::DIRTY_MATERIAL;
+ }
+}
+
+void EntityItem::setRestitution(float value) {
+ float clampedValue = glm::max(glm::min(ENTITY_ITEM_MAX_RESTITUTION, value), ENTITY_ITEM_MIN_RESTITUTION);
+ _restitution = clampedValue;
+}
+
+void EntityItem::setFriction(float value) {
+ float clampedValue = glm::max(glm::min(ENTITY_ITEM_MAX_FRICTION, value), ENTITY_ITEM_MIN_FRICTION);
+ _friction = clampedValue;
+}
+
void EntityItem::updateLifetime(float value) {
if (_lifetime != value) {
_lifetime = value;
@@ -1275,8 +1303,14 @@ void EntityItem::updateLifetime(float value) {
}
void EntityItem::setSimulatorID(const QUuid& value) {
+ _simulatorID = value;
+ _simulatorIDChangedTime = usecTimestampNow();
+}
+
+void EntityItem::updateSimulatorID(const QUuid& value) {
if (_simulatorID != value) {
_simulatorID = value;
_simulatorIDChangedTime = usecTimestampNow();
+ _dirtyFlags |= EntityItem::DIRTY_SIMULATOR_ID;
}
}
diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h
index b9dc14251c..e49efb1329 100644
--- a/libraries/entities/src/EntityItem.h
+++ b/libraries/entities/src/EntityItem.h
@@ -82,6 +82,7 @@ public:
DIRTY_UPDATEABLE = 0x0200,
DIRTY_MATERIAL = 0x00400,
DIRTY_PHYSICS_ACTIVATION = 0x0800, // we want to activate the object
+ DIRTY_SIMULATOR_ID = 0x1000,
DIRTY_TRANSFORM = DIRTY_POSITION | DIRTY_ROTATION,
DIRTY_VELOCITIES = DIRTY_LINEAR_VELOCITY | DIRTY_ANGULAR_VELOCITY
};
@@ -221,8 +222,11 @@ public:
float getDamping() const { return _damping; }
void setDamping(float value) { _damping = value; }
- float getRestitution() const;
- float getFriction() const;
+ float getRestitution() const { return _restitution; }
+ void setRestitution(float value);
+
+ float getFriction() const { return _friction; }
+ void setFriction(float value);
// lifetime related properties.
float getLifetime() const { return _lifetime; } /// get the lifetime in seconds for the entity
@@ -286,6 +290,7 @@ public:
QUuid getSimulatorID() const { return _simulatorID; }
void setSimulatorID(const QUuid& value);
+ void updateSimulatorID(const QUuid& value);
quint64 getSimulatorIDChangedTime() const { return _simulatorIDChangedTime; }
const QString& getMarketplaceID() const { return _marketplaceID; }
@@ -314,6 +319,8 @@ public:
void updateVelocityInDomainUnits(const glm::vec3& value);
void updateVelocity(const glm::vec3& value);
void updateDamping(float value);
+ void updateRestitution(float value);
+ void updateFriction(float value);
void updateGravityInDomainUnits(const glm::vec3& value);
void updateGravity(const glm::vec3& value);
void updateAngularVelocity(const glm::vec3& value);
@@ -375,6 +382,8 @@ protected:
glm::vec3 _gravity;
glm::vec3 _acceleration;
float _damping;
+ float _restitution;
+ float _friction;
float _lifetime;
QString _script;
QString _collisionSoundURL;
diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp
index 2d68a20022..fd3d0e9361 100644
--- a/libraries/entities/src/EntityItemProperties.cpp
+++ b/libraries/entities/src/EntityItemProperties.cpp
@@ -44,6 +44,8 @@ EntityItemProperties::EntityItemProperties() :
CONSTRUCT_PROPERTY(gravity, ENTITY_ITEM_DEFAULT_GRAVITY),
CONSTRUCT_PROPERTY(acceleration, ENTITY_ITEM_DEFAULT_ACCELERATION),
CONSTRUCT_PROPERTY(damping, ENTITY_ITEM_DEFAULT_DAMPING),
+ CONSTRUCT_PROPERTY(restitution, ENTITY_ITEM_DEFAULT_RESTITUTION),
+ CONSTRUCT_PROPERTY(friction, ENTITY_ITEM_DEFAULT_FRICTION),
CONSTRUCT_PROPERTY(lifetime, ENTITY_ITEM_DEFAULT_LIFETIME),
CONSTRUCT_PROPERTY(script, ENTITY_ITEM_DEFAULT_SCRIPT),
CONSTRUCT_PROPERTY(collisionSoundURL, ENTITY_ITEM_DEFAULT_COLLISION_SOUND_URL),
@@ -287,6 +289,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_GRAVITY, gravity);
CHECK_PROPERTY_CHANGE(PROP_ACCELERATION, acceleration);
CHECK_PROPERTY_CHANGE(PROP_DAMPING, damping);
+ CHECK_PROPERTY_CHANGE(PROP_RESTITUTION, restitution);
+ CHECK_PROPERTY_CHANGE(PROP_FRICTION, friction);
CHECK_PROPERTY_CHANGE(PROP_LIFETIME, lifetime);
CHECK_PROPERTY_CHANGE(PROP_SCRIPT, script);
CHECK_PROPERTY_CHANGE(PROP_COLLISION_SOUND_URL, collisionSoundURL);
@@ -362,6 +366,8 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
COPY_PROPERTY_TO_QSCRIPTVALUE(gravity);
COPY_PROPERTY_TO_QSCRIPTVALUE(acceleration);
COPY_PROPERTY_TO_QSCRIPTVALUE(damping);
+ COPY_PROPERTY_TO_QSCRIPTVALUE(restitution);
+ COPY_PROPERTY_TO_QSCRIPTVALUE(friction);
COPY_PROPERTY_TO_QSCRIPTVALUE(density);
COPY_PROPERTY_TO_QSCRIPTVALUE(lifetime);
if (!skipDefaults) {
@@ -470,6 +476,8 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) {
COPY_PROPERTY_FROM_QSCRIPTVALUE(gravity, glmVec3, setGravity);
COPY_PROPERTY_FROM_QSCRIPTVALUE(acceleration, glmVec3, setAcceleration);
COPY_PROPERTY_FROM_QSCRIPTVALUE(damping, float, setDamping);
+ COPY_PROPERTY_FROM_QSCRIPTVALUE(restitution, float, setRestitution);
+ COPY_PROPERTY_FROM_QSCRIPTVALUE(friction, float, setFriction);
COPY_PROPERTY_FROM_QSCRIPTVALUE(lifetime, float, setLifetime);
COPY_PROPERTY_FROM_QSCRIPTVALUE(script, QString, setScript);
COPY_PROPERTY_FROM_QSCRIPTVALUE(registrationPoint, glmVec3, setRegistrationPoint);
@@ -495,7 +503,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) {
COPY_PROPERTY_FROM_QSCRIPTVALUE(locked, bool, setLocked);
COPY_PROPERTY_FROM_QSCRIPTVALUE(textures, QString, setTextures);
COPY_PROPERTY_FROM_QSCRIPTVALUE(userData, QString, setUserData);
- COPY_PROPERTY_FROM_QSCRIPTVALUE(simulatorID, QUuid, setSimulatorID);
+ //COPY_PROPERTY_FROM_QSCRIPTVALUE(simulatorID, QUuid, setSimulatorID); DO NOT accept this info from QScriptValue
COPY_PROPERTY_FROM_QSCRIPTVALUE(text, QString, setText);
COPY_PROPERTY_FROM_QSCRIPTVALUE(lineHeight, float, setLineHeight);
COPY_PROPERTY_FROM_QSCRIPTVALUE(textColor, xColor, setTextColor);
@@ -662,6 +670,8 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
APPEND_ENTITY_PROPERTY(PROP_GRAVITY, properties.getGravity());
APPEND_ENTITY_PROPERTY(PROP_ACCELERATION, properties.getAcceleration());
APPEND_ENTITY_PROPERTY(PROP_DAMPING, properties.getDamping());
+ APPEND_ENTITY_PROPERTY(PROP_RESTITUTION, properties.getRestitution());
+ APPEND_ENTITY_PROPERTY(PROP_FRICTION, properties.getFriction());
APPEND_ENTITY_PROPERTY(PROP_LIFETIME, properties.getLifetime());
APPEND_ENTITY_PROPERTY(PROP_SCRIPT, properties.getScript());
APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor());
@@ -922,6 +932,8 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_GRAVITY, glm::vec3, setGravity);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ACCELERATION, glm::vec3, setAcceleration);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DAMPING, float, setDamping);
+ READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_RESTITUTION, float, setRestitution);
+ READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FRICTION, float, setFriction);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LIFETIME, float, setLifetime);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SCRIPT,QString, setScript);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, xColor, setColor);
@@ -1036,6 +1048,8 @@ void EntityItemProperties::markAllChanged() {
_gravityChanged = true;
_accelerationChanged = true;
_dampingChanged = true;
+ _restitutionChanged = true;
+ _frictionChanged = true;
_lifetimeChanged = true;
_userDataChanged = true;
_simulatorIDChanged = true;
diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h
index 7d8cbfe9b1..530013bbb6 100644
--- a/libraries/entities/src/EntityItemProperties.h
+++ b/libraries/entities/src/EntityItemProperties.h
@@ -95,6 +95,8 @@ public:
DEFINE_PROPERTY_REF(PROP_GRAVITY, Gravity, gravity, glm::vec3);
DEFINE_PROPERTY_REF(PROP_ACCELERATION, Acceleration, acceleration, glm::vec3);
DEFINE_PROPERTY(PROP_DAMPING, Damping, damping, float);
+ DEFINE_PROPERTY(PROP_RESTITUTION, Restitution, restitution, float);
+ DEFINE_PROPERTY(PROP_FRICTION, Friction, friction, float);
DEFINE_PROPERTY(PROP_LIFETIME, Lifetime, lifetime, float);
DEFINE_PROPERTY_REF(PROP_SCRIPT, Script, script, QString);
DEFINE_PROPERTY_REF(PROP_COLLISION_SOUND_URL, CollisionSoundURL, collisionSoundURL, QString);
@@ -243,6 +245,8 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) {
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Gravity, gravity, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Acceleration, acceleration, "in meters per second");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Damping, damping, "");
+ DEBUG_PROPERTY_IF_CHANGED(debug, properties, Restitution, restitution, "");
+ DEBUG_PROPERTY_IF_CHANGED(debug, properties, Friction, friction, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Lifetime, lifetime, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Script, script, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, CollisionSoundURL, collisionSoundURL, "");
diff --git a/libraries/entities/src/EntityItemPropertiesDefaults.h b/libraries/entities/src/EntityItemPropertiesDefaults.h
index ae44322377..b33e6de1ac 100644
--- a/libraries/entities/src/EntityItemPropertiesDefaults.h
+++ b/libraries/entities/src/EntityItemPropertiesDefaults.h
@@ -53,6 +53,14 @@ const glm::vec3 ENTITY_ITEM_DEFAULT_ACCELERATION = ENTITY_ITEM_ZERO_VEC3;
const float ENTITY_ITEM_DEFAULT_DAMPING = 0.39347f; // approx timescale = 2.0 sec (see damping timescale formula in header)
const float ENTITY_ITEM_DEFAULT_ANGULAR_DAMPING = 0.39347f; // approx timescale = 2.0 sec (see damping timescale formula in header)
+const float ENTITY_ITEM_MIN_RESTITUTION = 0.0f;
+const float ENTITY_ITEM_MAX_RESTITUTION = 0.99f;
+const float ENTITY_ITEM_DEFAULT_RESTITUTION = 0.5f;
+
+const float ENTITY_ITEM_MIN_FRICTION = 0.0f;
+const float ENTITY_ITEM_MAX_FRICTION = 0.99f;
+const float ENTITY_ITEM_DEFAULT_FRICTION = 0.5f;
+
const bool ENTITY_ITEM_DEFAULT_IGNORE_FOR_COLLISIONS = false;
const bool ENTITY_ITEM_DEFAULT_COLLISIONS_WILL_MOVE = false;
diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h
index 66ec70ae60..8a5d96e8d2 100644
--- a/libraries/entities/src/EntityPropertyFlags.h
+++ b/libraries/entities/src/EntityPropertyFlags.h
@@ -107,6 +107,8 @@ enum EntityPropertyList {
PROP_SIMULATOR_ID, // all entities
PROP_NAME, // all entities
PROP_COLLISION_SOUND_URL,
+ PROP_RESTITUTION,
+ PROP_FRICTION,
////////////////////////////////////////////////////////////////////////////////////////////////////
// ATTENTION: add new properties ABOVE this line
diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp
index 46ca70aa43..62e61dc039 100644
--- a/libraries/entities/src/EntityScriptingInterface.cpp
+++ b/libraries/entities/src/EntityScriptingInterface.cpp
@@ -61,13 +61,12 @@ void EntityScriptingInterface::setEntityTree(EntityTree* modelTree) {
}
}
-
-
-void setSimId(EntityItemProperties& propertiesWithSimID, EntityItem* entity) {
+void bidForSimulationOwnership(EntityItemProperties& properties) {
+ // We make a bid for simulation ownership by declaring our sessionID as simulation owner
+ // in the outgoing properties. The EntityServer may accept the bid or might not.
auto nodeList = DependencyManager::get();
const QUuid myNodeID = nodeList->getSessionUUID();
- propertiesWithSimID.setSimulatorID(myNodeID);
- entity->setSimulatorID(myNodeID);
+ properties.setSimulatorID(myNodeID);
}
@@ -89,7 +88,7 @@ EntityItemID EntityScriptingInterface::addEntity(const EntityItemProperties& pro
entity->setLastBroadcast(usecTimestampNow());
if (entity) {
// This Node is creating a new object. If it's in motion, set this Node as the simulator.
- setSimId(propertiesWithSimID, entity);
+ bidForSimulationOwnership(propertiesWithSimID);
} else {
qCDebug(entities) << "script failed to add new Entity to local Octree";
success = false;
@@ -163,29 +162,31 @@ EntityItemID EntityScriptingInterface::editEntity(EntityItemID entityID, const E
}
}
- EntityItemProperties propertiesWithSimID = properties;
-
// If we have a local entity tree set, then also update it. We can do this even if we don't know
// the actual id, because we can edit out local entities just with creatorTokenID
if (_entityTree) {
_entityTree->lockForWrite();
- _entityTree->updateEntity(entityID, propertiesWithSimID, canAdjustLocks());
+ _entityTree->updateEntity(entityID, properties);
_entityTree->unlock();
}
// if at this point, we know the id, send the update to the entity server
if (entityID.isKnownID) {
// make sure the properties has a type, so that the encode can know which properties to include
- if (propertiesWithSimID.getType() == EntityTypes::Unknown) {
+ if (properties.getType() == EntityTypes::Unknown) {
EntityItem* entity = _entityTree->findEntityByEntityItemID(entityID);
if (entity) {
+ // we need to change the outgoing properties, so we make a copy, modify, and send.
+ EntityItemProperties modifiedProperties = properties;
entity->setLastBroadcast(usecTimestampNow());
- propertiesWithSimID.setType(entity->getType());
- setSimId(propertiesWithSimID, entity);
+ modifiedProperties.setType(entity->getType());
+ bidForSimulationOwnership(modifiedProperties);
+ queueEntityMessage(PacketTypeEntityAddOrEdit, entityID, modifiedProperties);
+ return entityID;
}
}
- queueEntityMessage(PacketTypeEntityAddOrEdit, entityID, propertiesWithSimID);
+ queueEntityMessage(PacketTypeEntityAddOrEdit, entityID, properties);
}
return entityID;
diff --git a/libraries/entities/src/EntitySimulation.h b/libraries/entities/src/EntitySimulation.h
index a73afe9fd7..f5a100eba0 100644
--- a/libraries/entities/src/EntitySimulation.h
+++ b/libraries/entities/src/EntitySimulation.h
@@ -37,7 +37,8 @@ const int DIRTY_SIMULATION_FLAGS =
EntityItem::DIRTY_SHAPE |
EntityItem::DIRTY_LIFETIME |
EntityItem::DIRTY_UPDATEABLE |
- EntityItem::DIRTY_MATERIAL;
+ EntityItem::DIRTY_MATERIAL |
+ EntityItem::DIRTY_SIMULATOR_ID;
class EntitySimulation : public QObject {
Q_OBJECT
diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp
index ef49aca87a..e90a01383b 100644
--- a/libraries/entities/src/EntityTree.cpp
+++ b/libraries/entities/src/EntityTree.cpp
@@ -86,7 +86,7 @@ void EntityTree::postAddEntity(EntityItem* entity) {
emit addingEntity(entity->getEntityItemID());
}
-bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties, bool allowLockChange) {
+bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties, const SharedNodePointer& senderNode) {
EntityTreeElement* containingElement = getContainingElement(entityID);
if (!containingElement) {
qCDebug(entities) << "UNEXPECTED!!!! EntityTree::updateEntity() entityID doesn't exist!!! entityID=" << entityID;
@@ -99,22 +99,34 @@ bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProp
return false;
}
- return updateEntityWithElement(existingEntity, properties, containingElement, allowLockChange);
+ return updateEntityWithElement(existingEntity, properties, containingElement, senderNode);
}
-bool EntityTree::updateEntity(EntityItem* entity, const EntityItemProperties& properties, bool allowLockChange) {
+bool EntityTree::updateEntity(EntityItem* entity, const EntityItemProperties& properties, const SharedNodePointer& senderNode) {
EntityTreeElement* containingElement = getContainingElement(entity->getEntityItemID());
if (!containingElement) {
qCDebug(entities) << "UNEXPECTED!!!! EntityTree::updateEntity() entity-->element lookup failed!!! entityID="
<< entity->getEntityItemID();
return false;
}
- return updateEntityWithElement(entity, properties, containingElement, allowLockChange);
+ return updateEntityWithElement(entity, properties, containingElement, senderNode);
}
bool EntityTree::updateEntityWithElement(EntityItem* entity, const EntityItemProperties& origProperties,
- EntityTreeElement* containingElement, bool allowLockChange) {
+ EntityTreeElement* containingElement, const SharedNodePointer& senderNode) {
EntityItemProperties properties = origProperties;
+
+ bool allowLockChange;
+ QUuid senderID;
+ if (senderNode.isNull()) {
+ auto nodeList = DependencyManager::get();
+ allowLockChange = nodeList->getThisNodeCanAdjustLocks();
+ senderID = nodeList->getSessionUUID();
+ } else {
+ allowLockChange = senderNode->getCanAdjustLocks();
+ senderID = senderNode->getUUID();
+ }
+
if (!allowLockChange && (entity->getLocked() != properties.getLocked())) {
qCDebug(entities) << "Refusing disallowed lock adjustment.";
return false;
@@ -134,22 +146,41 @@ bool EntityTree::updateEntityWithElement(EntityItem* entity, const EntityItemPro
}
}
} else {
- if (properties.simulatorIDChanged() &&
- !entity->getSimulatorID().isNull() &&
- properties.getSimulatorID() != entity->getSimulatorID()) {
- // A Node is trying to take ownership of the simulation of this entity from another Node. Only allow this
- // if ownership hasn't recently changed.
- if (usecTimestampNow() - entity->getSimulatorIDChangedTime() < SIMULATOR_CHANGE_LOCKOUT_PERIOD) {
- qCDebug(entities) << "simulator_change_lockout_period:"
- << entity->getSimulatorID() << "to" << properties.getSimulatorID();
+ if (getIsServer()) {
+ bool simulationBlocked = !entity->getSimulatorID().isNull();
+ if (properties.simulatorIDChanged()) {
+ QUuid submittedID = properties.getSimulatorID();
+ // a legit interface will only submit their own ID or NULL:
+ if (submittedID.isNull()) {
+ if (entity->getSimulatorID() == senderID) {
+ // We only allow the simulation owner to clear their own simulationID's.
+ simulationBlocked = false;
+ }
+ // else: We assume the sender really did believe it was the simulation owner when it sent
+ } else if (submittedID == senderID) {
+ // the sender is trying to take or continue ownership
+ if (entity->getSimulatorID().isNull() || entity->getSimulatorID() == senderID) {
+ simulationBlocked = false;
+ } else {
+ // the sender is trying to steal ownership from another simulator
+ // so we apply the ownership change filter
+ if (usecTimestampNow() - entity->getSimulatorIDChangedTime() > SIMULATOR_CHANGE_LOCKOUT_PERIOD) {
+ simulationBlocked = false;
+ }
+ }
+ } else {
+ // the entire update is suspect --> ignore it
+ return false;
+ }
+ }
+ if (simulationBlocked) {
// squash the physics-related changes.
properties.setSimulatorIDChanged(false);
properties.setPositionChanged(false);
properties.setRotationChanged(false);
- } else {
- qCDebug(entities) << "allowing simulatorID change";
}
}
+ // else client accepts what the server says
QString entityScriptBefore = entity->getScript();
uint32_t preFlags = entity->getDirtyFlags();
@@ -664,7 +695,7 @@ int EntityTree::processEditPacketData(PacketType packetType, const unsigned char
qCDebug(entities) << "User [" << senderNode->getUUID() << "] editing entity. ID:" << entityItemID;
qCDebug(entities) << " properties:" << properties;
}
- updateEntity(entityItemID, properties, senderNode->getCanAdjustLocks());
+ updateEntity(entityItemID, properties, senderNode);
existingEntity->markAsChangedOnServer();
} else {
qCDebug(entities) << "User attempted to edit an unknown entity. ID:" << entityItemID;
diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h
index 76d648cf46..fb6e0f26eb 100644
--- a/libraries/entities/src/EntityTree.h
+++ b/libraries/entities/src/EntityTree.h
@@ -88,10 +88,10 @@ public:
EntityItem* addEntity(const EntityItemID& entityID, const EntityItemProperties& properties);
// use this method if you only know the entityID
- bool updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties, bool allowLockChange);
+ bool updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties, const SharedNodePointer& senderNode = SharedNodePointer(nullptr));
// use this method if you have a pointer to the entity (avoid an extra entity lookup)
- bool updateEntity(EntityItem* entity, const EntityItemProperties& properties, bool allowLockChange);
+ bool updateEntity(EntityItem* entity, const EntityItemProperties& properties, const SharedNodePointer& senderNode = SharedNodePointer(nullptr));
void deleteEntity(const EntityItemID& entityID, bool force = false, bool ignoreWarnings = false);
void deleteEntities(QSet entityIDs, bool force = false, bool ignoreWarnings = false);
@@ -180,7 +180,8 @@ private:
void processRemovedEntities(const DeleteEntityOperator& theOperator);
bool updateEntityWithElement(EntityItem* entity, const EntityItemProperties& properties,
- EntityTreeElement* containingElement, bool allowLockChange);
+ EntityTreeElement* containingElement,
+ const SharedNodePointer& senderNode = SharedNodePointer(nullptr));
static bool findNearPointOperation(OctreeElement* element, void* extraData);
static bool findInSphereOperation(OctreeElement* element, void* extraData);
static bool findInCubeOperation(OctreeElement* element, void* extraData);
diff --git a/libraries/entities/src/SimpleEntitySimulation.cpp b/libraries/entities/src/SimpleEntitySimulation.cpp
index 18d5c4ecb0..07c56e7121 100644
--- a/libraries/entities/src/SimpleEntitySimulation.cpp
+++ b/libraries/entities/src/SimpleEntitySimulation.cpp
@@ -32,6 +32,7 @@ void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) {
SharedNodePointer ownerNode = nodeList->nodeWithUUID(entity->getSimulatorID());
if (ownerNode.isNull() || !ownerNode->isAlive()) {
qCDebug(entities) << "auto-removing simulation owner" << entity->getSimulatorID();
+ // TODO: zero velocities when we clear simulatorID?
entity->setSimulatorID(QUuid());
itemItr = _hasSimulationOwnerEntities.erase(itemItr);
} else {
diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp
index 8ab5171fce..464deb1059 100644
--- a/libraries/fbx/src/FBXReader.cpp
+++ b/libraries/fbx/src/FBXReader.cpp
@@ -29,7 +29,7 @@
#include
#include
#include
-
+#include
#include "FBXReader.h"
#include "ModelFormatLogging.h"
@@ -1281,9 +1281,11 @@ FBXLight extractLight(const FBXNode& object) {
#if USE_MODEL_MESH
void buildModelMesh(ExtractedMesh& extracted) {
+ static QString repeatedMessage = LogHandler::getInstance().addRepeatedMessageRegex("buildModelMesh failed -- .*");
+
if (extracted.mesh.vertices.size() == 0) {
extracted.mesh._mesh = model::Mesh();
- qDebug() << "buildModelMesh failed -- no vertices";
+ qCDebug(modelformat) << "buildModelMesh failed -- no vertices";
return;
}
FBXMesh& fbxMesh = extracted.mesh;
@@ -1370,7 +1372,7 @@ void buildModelMesh(ExtractedMesh& extracted) {
if (! totalIndices) {
extracted.mesh._mesh = model::Mesh();
- qDebug() << "buildModelMesh failed -- no indices";
+ qCDebug(modelformat) << "buildModelMesh failed -- no indices";
return;
}
@@ -1410,7 +1412,7 @@ void buildModelMesh(ExtractedMesh& extracted) {
mesh.setPartBuffer(pbv);
} else {
extracted.mesh._mesh = model::Mesh();
- qDebug() << "buildModelMesh failed -- no parts";
+ qCDebug(modelformat) << "buildModelMesh failed -- no parts";
return;
}
diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp
index 0dcd30a55c..689c958cbb 100644
--- a/libraries/networking/src/PacketHeaders.cpp
+++ b/libraries/networking/src/PacketHeaders.cpp
@@ -72,7 +72,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
return 1;
case PacketTypeEntityAddOrEdit:
case PacketTypeEntityData:
- return VERSION_ENTITIES_HAVE_COLLISION_SOUND_URL;
+ return VERSION_ENTITIES_HAVE_FRICTION;
case PacketTypeEntityErase:
return 2;
case PacketTypeAudioStreamStats:
diff --git a/libraries/networking/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h
index 01eb488c14..b4e630c677 100644
--- a/libraries/networking/src/PacketHeaders.h
+++ b/libraries/networking/src/PacketHeaders.h
@@ -177,5 +177,6 @@ const PacketVersion VERSION_ENTITIES_ZONE_ENTITIES_STAGE_HAS_AUTOMATIC_HOURDAY =
const PacketVersion VERSION_ENTITIES_PARTICLE_ENTITIES_HAVE_TEXTURES = 23;
const PacketVersion VERSION_ENTITIES_HAVE_LINE_TYPE = 24;
const PacketVersion VERSION_ENTITIES_HAVE_COLLISION_SOUND_URL = 25;
+const PacketVersion VERSION_ENTITIES_HAVE_FRICTION = 26;
#endif // hifi_PacketHeaders_h
diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp
index b5d14e4814..3986762f6f 100644
--- a/libraries/physics/src/EntityMotionState.cpp
+++ b/libraries/physics/src/EntityMotionState.cpp
@@ -25,7 +25,7 @@ static const quint8 STEPS_TO_DECIDE_BALLISTIC = 4;
EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItem* entity) :
ObjectMotionState(shape),
_entity(entity),
- _sentMoving(false),
+ _sentActive(false),
_numNonMovingUpdates(0),
_lastStep(0),
_serverPosition(0.0f),
@@ -35,8 +35,9 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItem* entity
_serverGravity(0.0f),
_serverAcceleration(0.0f),
_accelerationNearlyGravityCount(0),
- _shouldClaimSimulationOwnership(false),
- _movingStepsWithoutSimulationOwner(0)
+ _candidateForOwnership(false),
+ _loopsSinceOwnershipBid(0),
+ _loopsWithoutOwner(0)
{
_type = MOTION_STATE_TYPE_ENTITY;
assert(entity != nullptr);
@@ -66,6 +67,28 @@ void EntityMotionState::updateServerPhysicsVariables(uint32_t flags) {
void EntityMotionState::handleEasyChanges(uint32_t flags) {
updateServerPhysicsVariables(flags);
ObjectMotionState::handleEasyChanges(flags);
+ if (flags & EntityItem::DIRTY_SIMULATOR_ID) {
+ _loopsWithoutOwner = 0;
+ _candidateForOwnership = 0;
+ if (_entity->getSimulatorID().isNull()
+ && !_entity->isMoving()
+ && _body->isActive()) {
+ // remove the ACTIVATION flag because this object is coming to rest
+ // according to a remote simulation and we don't want to wake it up again
+ flags &= ~EntityItem::DIRTY_PHYSICS_ACTIVATION;
+ _body->setActivationState(WANTS_DEACTIVATION);
+ } else {
+ auto nodeList = DependencyManager::get();
+ const QUuid& sessionID = nodeList->getSessionUUID();
+ if (_entity->getSimulatorID() != sessionID) {
+ _loopsSinceOwnershipBid = 0;
+ }
+ }
+ }
+ if ((flags & EntityItem::DIRTY_PHYSICS_ACTIVATION) && !_body->isActive()) {
+ _body->activate();
+ }
+
}
@@ -95,15 +118,6 @@ bool EntityMotionState::isMoving() const {
return _entity && _entity->isMoving();
}
-bool EntityMotionState::isMovingVsServer() const {
- auto alignmentDot = glm::abs(glm::dot(_serverRotation, _entity->getRotation()));
- if (glm::distance(_serverPosition, _entity->getPosition()) > IGNORE_POSITION_DELTA ||
- alignmentDot < IGNORE_ALIGNMENT_DOT) {
- return true;
- }
- return false;
-}
-
// This callback is invoked by the physics simulation in two cases:
// (1) when the RigidBody is first added to the world
// (irregardless of MotionType: STATIC, DYNAMIC, or KINEMATIC)
@@ -143,19 +157,16 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) {
_entity->setLastSimulated(usecTimestampNow());
- // if (_entity->getSimulatorID().isNull() && isMoving()) {
- if (_entity->getSimulatorID().isNull() && isMovingVsServer()) {
- // if object is moving and has no owner, attempt to claim simulation ownership.
- _movingStepsWithoutSimulationOwner++;
+ if (_entity->getSimulatorID().isNull()) {
+ _loopsWithoutOwner++;
+
+ const uint32_t OWNERSHIP_BID_DELAY = 50;
+ if (_loopsWithoutOwner > OWNERSHIP_BID_DELAY) {
+ //qDebug() << "Warning -- claiming something I saw moving." << getName();
+ _candidateForOwnership = true;
+ }
} else {
- _movingStepsWithoutSimulationOwner = 0;
- }
-
- uint32_t ownershipClaimDelay = 50; // TODO -- how to pick this? based on meters from our characterController?
-
- if (_movingStepsWithoutSimulationOwner > ownershipClaimDelay) {
- //qDebug() << "Warning -- claiming something I saw moving." << getName();
- setShouldClaimSimulationOwnership(true);
+ _loopsWithoutOwner = 0;
}
#ifdef WANT_DEBUG
@@ -177,8 +188,11 @@ void EntityMotionState::computeObjectShapeInfo(ShapeInfo& shapeInfo) {
// we alwasy resend packets for objects that have stopped moving up to some max limit.
const int MAX_NUM_NON_MOVING_UPDATES = 5;
-bool EntityMotionState::doesNotNeedToSendUpdate() const {
- return !_body || (_body->isActive() && _numNonMovingUpdates > MAX_NUM_NON_MOVING_UPDATES);
+bool EntityMotionState::isCandidateForOwnership(const QUuid& sessionID) const {
+ if (!_body || !_entity) {
+ return false;
+ }
+ return _candidateForOwnership || sessionID == _entity->getSimulatorID();
}
bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
@@ -191,6 +205,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
_serverVelocity = bulletToGLM(_body->getLinearVelocity());
_serverAngularVelocity = bulletToGLM(_body->getAngularVelocity());
_lastStep = simulationStep;
+ _sentActive = false;
return false;
}
@@ -202,21 +217,26 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
int numSteps = simulationStep - _lastStep;
float dt = (float)(numSteps) * PHYSICS_ENGINE_FIXED_SUBSTEP;
- _lastStep = simulationStep;
- bool isActive = _body->isActive();
+ const float INACTIVE_UPDATE_PERIOD = 0.5f;
+ if (!_sentActive) {
+ // we resend the inactive update every INACTIVE_UPDATE_PERIOD
+ // until it is removed from the outgoing updates
+ // (which happens when we don't own the simulation and it isn't touching our simulation)
+ return (dt > INACTIVE_UPDATE_PERIOD);
+ }
+
+ bool isActive = _body->isActive();
if (!isActive) {
- if (_sentMoving) {
- // this object just went inactive so send an update immediately
- return true;
- } else {
- const float NON_MOVING_UPDATE_PERIOD = 1.0f;
- if (dt > NON_MOVING_UPDATE_PERIOD && _numNonMovingUpdates < MAX_NUM_NON_MOVING_UPDATES) {
- // RELIABLE_SEND_HACK: since we're not yet using a reliable method for non-moving update packets we repeat these
- // at a faster rate than the MAX period above, and only send a limited number of them.
- return true;
- }
- }
+ // object has gone inactive but our last send was moving --> send non-moving update immediately
+ return true;
+ }
+
+ _lastStep = simulationStep;
+ if (glm::length2(_serverVelocity) > 0.0f) {
+ _serverVelocity += _serverAcceleration * dt;
+ _serverVelocity *= powf(1.0f - _body->getLinearDamping(), dt);
+ _serverPosition += dt * _serverVelocity;
}
// Else we measure the error between current and extrapolated transform (according to expected behavior
@@ -224,15 +244,10 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
// NOTE: math is done in the simulation-frame, which is NOT necessarily the same as the world-frame
// due to _worldOffset.
+ // TODO: compensate for _worldOffset offset here
// compute position error
- if (glm::length2(_serverVelocity) > 0.0f) {
- _serverVelocity += _serverAcceleration * dt;
- _serverVelocity *= powf(1.0f - _body->getLinearDamping(), dt);
- _serverPosition += dt * _serverVelocity;
- }
- // TODO: compensate for simulation offset here
btTransform worldTrans = _body->getWorldTransform();
glm::vec3 position = bulletToGLM(worldTrans.getOrigin());
@@ -285,42 +300,50 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
return (fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT);
}
-bool EntityMotionState::shouldSendUpdate(uint32_t simulationFrame) {
- if (!_entity || !remoteSimulationOutOfSync(simulationFrame)) {
+bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep, const QUuid& sessionID) {
+ // NOTE: we expect _entity and _body to be valid in this context, since shouldSendUpdate() is only called
+ // after doesNotNeedToSendUpdate() returns false and that call should return 'true' if _entity or _body are NULL.
+ assert(_entity);
+ assert(_body);
+
+ if (!remoteSimulationOutOfSync(simulationStep)) {
+ _candidateForOwnership = false;
return false;
}
- if (getShouldClaimSimulationOwnership()) {
+ if (_entity->getSimulatorID() == sessionID) {
+ // we own the simulation
+ _candidateForOwnership = false;
return true;
}
- auto nodeList = DependencyManager::get();
- const QUuid& myNodeID = nodeList->getSessionUUID();
- const QUuid& simulatorID = _entity->getSimulatorID();
-
- if (simulatorID != myNodeID) {
- // some other Node owns the simulating of this, so don't broadcast the results of local simulation.
- return false;
+ const uint32_t FRAMES_BETWEEN_OWNERSHIP_CLAIMS = 30;
+ if (_candidateForOwnership) {
+ _candidateForOwnership = false;
+ ++_loopsSinceOwnershipBid;
+ if (_loopsSinceOwnershipBid > FRAMES_BETWEEN_OWNERSHIP_CLAIMS) {
+ // we don't own the simulation, but it's time to bid for it
+ _loopsSinceOwnershipBid = 0;
+ return true;
+ }
}
- return true;
+ _candidateForOwnership = false;
+ return false;
}
-void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) {
- if (!_entity || !_entity->isKnownID()) {
- return; // never update entities that are unknown
- }
+void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const QUuid& sessionID, uint32_t step) {
+ assert(_entity);
+ assert(_entity->isKnownID());
bool active = _body->isActive();
if (!active) {
- if (_sentMoving) {
- // make sure all derivatives are zero
- glm::vec3 zero(0.0f);
- _entity->setVelocity(zero);
- _entity->setAngularVelocity(zero);
- _entity->setAcceleration(zero);
- }
-
+ // make sure all derivatives are zero
+ glm::vec3 zero(0.0f);
+ _entity->setVelocity(zero);
+ _entity->setAngularVelocity(zero);
+ _entity->setAcceleration(zero);
+ _sentActive = false;
} else {
float gravityLength = glm::length(_entity->getGravity());
float accVsGravity = glm::abs(glm::length(_measuredAcceleration) - gravityLength);
@@ -343,6 +366,21 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
} else {
_entity->setAcceleration(glm::vec3(0.0f));
}
+
+ const float DYNAMIC_LINEAR_VELOCITY_THRESHOLD = 0.05f; // 5 cm/sec
+ const float DYNAMIC_ANGULAR_VELOCITY_THRESHOLD = 0.087266f; // ~5 deg/sec
+ bool movingSlowly = glm::length2(_entity->getVelocity()) < (DYNAMIC_LINEAR_VELOCITY_THRESHOLD * DYNAMIC_LINEAR_VELOCITY_THRESHOLD)
+ && glm::length2(_entity->getAngularVelocity()) < (DYNAMIC_ANGULAR_VELOCITY_THRESHOLD * DYNAMIC_ANGULAR_VELOCITY_THRESHOLD)
+ && _entity->getAcceleration() == glm::vec3(0.0f);
+
+ if (movingSlowly) {
+ // velocities might not be zero, but we'll fake them as such, which will hopefully help convince
+ // other simulating observers to deactivate their own copies
+ glm::vec3 zero(0.0f);
+ _entity->setVelocity(zero);
+ _entity->setAngularVelocity(zero);
+ }
+ _sentActive = true;
}
// remember properties for local server prediction
@@ -352,59 +390,41 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
_serverAcceleration = _entity->getAcceleration();
_serverAngularVelocity = _entity->getAngularVelocity();
- _sentMoving = _serverVelocity != glm::vec3(0.0f) || _serverAngularVelocity != glm::vec3(0.0f);
-
EntityItemProperties properties = _entity->getProperties();
- // explicitly set the properties that changed
+ // explicitly set the properties that changed so that they will be packed
properties.setPosition(_serverPosition);
properties.setRotation(_serverRotation);
properties.setVelocity(_serverVelocity);
properties.setAcceleration(_serverAcceleration);
properties.setAngularVelocity(_serverAngularVelocity);
- // RELIABLE_SEND_HACK: count number of updates for entities at rest
- // so we can stop sending them after some limit.
- if (_sentMoving) {
- _numNonMovingUpdates = 0;
+ // we only update lastEdited when we're sending new physics data
+ quint64 lastSimulated = _entity->getLastSimulated();
+ _entity->setLastEdited(lastSimulated);
+ properties.setLastEdited(lastSimulated);
+
+ #ifdef WANT_DEBUG
+ quint64 now = usecTimestampNow();
+ qCDebug(physics) << "EntityMotionState::sendUpdate()";
+ qCDebug(physics) << " EntityItemId:" << _entity->getEntityItemID()
+ << "---------------------------------------------";
+ qCDebug(physics) << " lastSimulated:" << debugTime(lastSimulated, now);
+ #endif //def WANT_DEBUG
+
+ if (sessionID == _entity->getSimulatorID()) {
+ // we think we own the simulation
+ if (!active) {
+ // we own the simulation but the entity has stopped, so we tell the server that we're clearing simulatorID
+ // but we remember that we do still own it... and rely on the server to tell us that we don't
+ properties.setSimulatorID(QUuid());
+ } else {
+ // explicitly set the property's simulatorID so that it is flagged as changed and will be packed
+ properties.setSimulatorID(sessionID);
+ }
} else {
- _numNonMovingUpdates++;
- }
- if (_numNonMovingUpdates <= 1) {
- // we only update lastEdited when we're sending new physics data
- quint64 lastSimulated = _entity->getLastSimulated();
- _entity->setLastEdited(lastSimulated);
- properties.setLastEdited(lastSimulated);
-
- #ifdef WANT_DEBUG
- quint64 now = usecTimestampNow();
- qCDebug(physics) << "EntityMotionState::sendUpdate()";
- qCDebug(physics) << " EntityItemId:" << _entity->getEntityItemID()
- << "---------------------------------------------";
- qCDebug(physics) << " lastSimulated:" << debugTime(lastSimulated, now);
- #endif //def WANT_DEBUG
-
- } else {
- properties.setLastEdited(_entity->getLastEdited());
- }
-
- auto nodeList = DependencyManager::get();
- QUuid myNodeID = nodeList->getSessionUUID();
- QUuid simulatorID = _entity->getSimulatorID();
-
- if (getShouldClaimSimulationOwnership()) {
- // we think we should own it, so we tell the server that we do,
- // but we don't remember that we own it...
- // instead we expect the sever to tell us later whose ownership it has accepted
- properties.setSimulatorID(myNodeID);
- setShouldClaimSimulationOwnership(false);
- } else if (simulatorID == myNodeID
- && !_sentMoving
- && _numNonMovingUpdates == MAX_NUM_NON_MOVING_UPDATES) {
- // we own it, the entity has stopped, and we're sending the last non-moving update
- // --> give up ownership
- _entity->setSimulatorID(QUuid());
- properties.setSimulatorID(QUuid());
+ // we don't own the simulation for this entity yet, but we're sending a bid for it
+ properties.setSimulatorID(sessionID);
}
if (EntityItem::getSendPhysicsUpdates()) {
@@ -453,7 +473,7 @@ QUuid EntityMotionState::getSimulatorID() const {
// virtual
void EntityMotionState::bump() {
- setShouldClaimSimulationOwnership(true);
+ _candidateForOwnership = true;
}
void EntityMotionState::resetMeasuredBodyAcceleration() {
diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h
index 6028662aa0..83b89a5a29 100644
--- a/libraries/physics/src/EntityMotionState.h
+++ b/libraries/physics/src/EntityMotionState.h
@@ -36,7 +36,6 @@ public:
virtual MotionType computeObjectMotionType() const;
virtual bool isMoving() const;
- virtual bool isMovingVsServer() const;
// this relays incoming position/rotation to the RigidBody
virtual void getWorldTransform(btTransform& worldTrans) const;
@@ -46,13 +45,10 @@ public:
virtual void computeObjectShapeInfo(ShapeInfo& shapeInfo);
- bool doesNotNeedToSendUpdate() const;
+ bool isCandidateForOwnership(const QUuid& sessionID) const;
bool remoteSimulationOutOfSync(uint32_t simulationStep);
- bool shouldSendUpdate(uint32_t simulationFrame);
- void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step);
-
- void setShouldClaimSimulationOwnership(bool value) { _shouldClaimSimulationOwnership = value; }
- bool getShouldClaimSimulationOwnership() { return _shouldClaimSimulationOwnership; }
+ bool shouldSendUpdate(uint32_t simulationStep, const QUuid& sessionID);
+ void sendUpdate(OctreeEditPacketSender* packetSender, const QUuid& sessionID, uint32_t step);
virtual uint32_t getAndClearIncomingDirtyFlags() const;
@@ -92,7 +88,7 @@ protected:
EntityItem* _entity;
- bool _sentMoving; // true if last update was moving
+ bool _sentActive; // true if body was active when we sent last update
int _numNonMovingUpdates; // RELIABLE_SEND_HACK for "not so reliable" resends of packets for non-moving objects
// these are for the prediction of the remote server's simple extrapolation
@@ -109,8 +105,9 @@ protected:
glm::vec3 _measuredAcceleration;
quint8 _accelerationNearlyGravityCount;
- bool _shouldClaimSimulationOwnership;
- quint32 _movingStepsWithoutSimulationOwner;
+ bool _candidateForOwnership;
+ uint32_t _loopsSinceOwnershipBid;
+ uint32_t _loopsWithoutOwner;
};
#endif // hifi_EntityMotionState_h
diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp
index c1258ad6bc..c5288cfa76 100644
--- a/libraries/physics/src/ObjectMotionState.cpp
+++ b/libraries/physics/src/ObjectMotionState.cpp
@@ -146,10 +146,6 @@ void ObjectMotionState::handleEasyChanges(uint32_t flags) {
_body->setMassProps(mass, inertia);
_body->updateInertiaTensor();
}
-
- if (flags & EntityItem::DIRTY_PHYSICS_ACTIVATION) {
- _body->activate();
- }
}
void ObjectMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine) {
diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h
index 1407be0d20..bfc9310ec6 100644
--- a/libraries/physics/src/ObjectMotionState.h
+++ b/libraries/physics/src/ObjectMotionState.h
@@ -36,11 +36,11 @@ enum MotionStateType {
// and re-added to the physics engine and "easy" which just updates the body properties.
const uint32_t HARD_DIRTY_PHYSICS_FLAGS = (uint32_t)(EntityItem::DIRTY_MOTION_TYPE | EntityItem::DIRTY_SHAPE);
const uint32_t EASY_DIRTY_PHYSICS_FLAGS = (uint32_t)(EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES |
- EntityItem::DIRTY_MASS | EntityItem::DIRTY_COLLISION_GROUP |
- EntityItem::DIRTY_MATERIAL | EntityItem::DIRTY_PHYSICS_ACTIVATION);
+ EntityItem::DIRTY_MASS | EntityItem::DIRTY_COLLISION_GROUP);
// These are the set of incoming flags that the PhysicsEngine needs to hear about:
-const uint32_t DIRTY_PHYSICS_FLAGS = HARD_DIRTY_PHYSICS_FLAGS | EASY_DIRTY_PHYSICS_FLAGS;
+const uint32_t DIRTY_PHYSICS_FLAGS = HARD_DIRTY_PHYSICS_FLAGS | EASY_DIRTY_PHYSICS_FLAGS |
+ EntityItem::DIRTY_MATERIAL | (uint32_t)EntityItem::DIRTY_PHYSICS_ACTIVATION;
// These are the outgoing flags that the PhysicsEngine can affect:
const uint32_t OUTGOING_DIRTY_PHYSICS_FLAGS = EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES;
diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp
index 50e81b4788..3e43ab7454 100644
--- a/libraries/physics/src/PhysicalEntitySimulation.cpp
+++ b/libraries/physics/src/PhysicalEntitySimulation.cpp
@@ -188,7 +188,7 @@ VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToChange() {
return _tempVector;
}
-void PhysicalEntitySimulation::handleOutgoingChanges(VectorOfMotionStates& motionStates) {
+void PhysicalEntitySimulation::handleOutgoingChanges(VectorOfMotionStates& motionStates, const QUuid& sessionID) {
// walk the motionStates looking for those that correspond to entities
for (auto stateItr : motionStates) {
ObjectMotionState* state = &(*stateItr);
@@ -196,24 +196,32 @@ void PhysicalEntitySimulation::handleOutgoingChanges(VectorOfMotionStates& motio
EntityMotionState* entityState = static_cast(state);
EntityItem* entity = entityState->getEntity();
if (entity) {
- _outgoingChanges.insert(entityState);
+ if (entity->isKnownID() && entityState->isCandidateForOwnership(sessionID)) {
+ _outgoingChanges.insert(entityState);
+ }
_entitiesToSort.insert(entityState->getEntity());
}
}
}
- // send outgoing packets
uint32_t numSubsteps = _physicsEngine->getNumSubsteps();
if (_lastStepSendPackets != numSubsteps) {
_lastStepSendPackets = numSubsteps;
+ if (sessionID.isNull()) {
+ // usually don't get here, but if so --> nothing to do
+ _outgoingChanges.clear();
+ return;
+ }
+
+ // send outgoing packets
QSet::iterator stateItr = _outgoingChanges.begin();
while (stateItr != _outgoingChanges.end()) {
EntityMotionState* state = *stateItr;
- if (state->doesNotNeedToSendUpdate()) {
+ if (!state->isCandidateForOwnership(sessionID)) {
stateItr = _outgoingChanges.erase(stateItr);
- } else if (state->shouldSendUpdate(numSubsteps)) {
- state->sendUpdate(_entityPacketSender, numSubsteps);
+ } else if (state->shouldSendUpdate(numSubsteps, sessionID)) {
+ state->sendUpdate(_entityPacketSender, sessionID, numSubsteps);
++stateItr;
} else {
++stateItr;
diff --git a/libraries/physics/src/PhysicalEntitySimulation.h b/libraries/physics/src/PhysicalEntitySimulation.h
index 4fd54c60fb..b3ee7af1e1 100644
--- a/libraries/physics/src/PhysicalEntitySimulation.h
+++ b/libraries/physics/src/PhysicalEntitySimulation.h
@@ -48,7 +48,7 @@ public:
VectorOfMotionStates& getObjectsToAdd();
VectorOfMotionStates& getObjectsToChange();
- void handleOutgoingChanges(VectorOfMotionStates& motionStates);
+ void handleOutgoingChanges(VectorOfMotionStates& motionStates, const QUuid& sessionID);
void handleCollisionEvents(CollisionEvents& collisionEvents);
private:
diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h
index 8b947d2510..d1dc5bcd79 100644
--- a/libraries/physics/src/PhysicsEngine.h
+++ b/libraries/physics/src/PhysicsEngine.h
@@ -53,6 +53,7 @@ public:
void init();
void setSessionUUID(const QUuid& sessionID) { _sessionID = sessionID; }
+ const QUuid& getSessionID() const { return _sessionID; }
void addObject(ObjectMotionState* motionState);
void removeObject(ObjectMotionState* motionState);
diff --git a/tests/octree/src/ModelTests.cpp b/tests/octree/src/ModelTests.cpp
index e4309100af..2e2b873115 100644
--- a/tests/octree/src/ModelTests.cpp
+++ b/tests/octree/src/ModelTests.cpp
@@ -103,7 +103,7 @@ void EntityTests::entityTreeTests(bool verbose) {
properties.setPosition(newPosition);
- tree.updateEntity(entityID, properties, true);
+ tree.updateEntity(entityID, properties);
float targetRadius = oneMeter * 2.0f;
const EntityItem* foundEntityByRadius = tree.findClosestEntity(positionNearOrigin, targetRadius);
@@ -143,7 +143,7 @@ void EntityTests::entityTreeTests(bool verbose) {
properties.setPosition(newPosition);
- tree.updateEntity(entityID, properties, true);
+ tree.updateEntity(entityID, properties);
float targetRadius = oneMeter * 2.0f;
const EntityItem* foundEntityByRadius = tree.findClosestEntity(positionAtCenter, targetRadius);