mirror of
https://github.com/overte-org/overte.git
synced 2025-08-07 13:50:35 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into yellow
This commit is contained in:
commit
e3d80088da
27 changed files with 1031 additions and 1012 deletions
|
@ -3,303 +3,270 @@
|
|||
// 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 {
|
||||
function getRayIntersection(pickRay) {
|
||||
var intersection = Entities.findRayIntersection(pickRay);
|
||||
if (intersection.accurate) {
|
||||
lastAccurateIntersection = intersection;
|
||||
accurateIntersections++;
|
||||
maxInaccurateInARow = (maxInaccurateInARow > inaccurateInARow) ? maxInaccurateInARow : inaccurateInARow;
|
||||
inaccurateInARow = 0;
|
||||
} else {
|
||||
inaccurateInARow++;
|
||||
}
|
||||
totalIntersections++;
|
||||
}
|
||||
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.");
|
||||
return intersection;
|
||||
}
|
||||
|
||||
|
||||
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.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.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 = [];
|
||||
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 },
|
||||
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.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"
|
||||
});
|
||||
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"
|
||||
});
|
||||
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"
|
||||
lineWidth: 2
|
||||
});
|
||||
|
||||
|
||||
|
||||
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 () {
|
||||
this.update = function(deltaTime) {
|
||||
this.updateControllerState();
|
||||
this.moveLaser();
|
||||
this.checkTrigger();
|
||||
this.checkEntityIntersection();
|
||||
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;
|
||||
}
|
||||
this.updateEntity(deltaTime);
|
||||
}
|
||||
|
||||
if (closestJointIndex != -1) {
|
||||
print("closestJoint: " + jointList[closestJointIndex]);
|
||||
print("closestJointDistance (attach max distance): " + closestJointDistance + " (" + this.oldModelHalfDiagonal + ")");
|
||||
this.oldPalmPosition = this.palmPosition;
|
||||
this.oldTipPosition = this.tipPosition;
|
||||
}
|
||||
|
||||
if (closestJointDistance < this.oldModelHalfDiagonal) {
|
||||
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));
|
||||
|
||||
if (this.jointsIntersectingFromStart.indexOf(closestJointIndex) != -1 ||
|
||||
(leftController.grabbing && rightController.grabbing &&
|
||||
leftController.entityID.id == rightController.entityID.id)) {
|
||||
// Do nothing
|
||||
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 {
|
||||
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);
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
Overlays.editOverlay(this.dropLine, { visible: false });
|
||||
}
|
||||
|
||||
this.grabbing = false;
|
||||
this.entityID.isKnownID = false;
|
||||
this.jointsIntersectingFromStart = [];
|
||||
this.showLaser(true);
|
||||
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 > 0.9) {
|
||||
if (this.pressed) {
|
||||
this.pressing = false;
|
||||
} else {
|
||||
this.pressing = true;
|
||||
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.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;
|
||||
this.updateDropLine = function(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 };
|
||||
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
|
||||
});
|
||||
}
|
||||
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);
|
||||
// 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);
|
||||
|
@ -309,444 +276,28 @@ function controller(wichSide) {
|
|||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
299
examples/example/games/hydraGrabHockey.js
Normal file
299
examples/example/games/hydraGrabHockey.js
Normal file
|
@ -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);
|
|
@ -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 @@
|
|||
<input class="coord" type='number' id="property-adamping"></input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="property">
|
||||
<div class="label">Restitution</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-restitution"></input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="property">
|
||||
<div class="label">Friction</div>
|
||||
<div class="value">
|
||||
<input class="coord" type='number' id="property-friction"></input>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="property">
|
||||
<div class="label">Gravity</div>
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -655,7 +655,9 @@ float Avatar::calculateDisplayNameScaleFactor(const glm::vec3& textPosition, boo
|
|||
|
||||
void Avatar::renderDisplayName() {
|
||||
|
||||
if (_displayName.isEmpty() || _displayNameAlpha == 0.0f) {
|
||||
bool shouldShowReceiveStats = DependencyManager::get<AvatarManager>()->shouldShowReceiveStats();
|
||||
|
||||
if ((_displayName.isEmpty() && !shouldShowReceiveStats) || _displayNameAlpha == 0.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -688,14 +690,40 @@ void Avatar::renderDisplayName() {
|
|||
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;
|
||||
|
@ -711,17 +739,6 @@ void Avatar::renderDisplayName() {
|
|||
|
||||
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<AvatarManager>()->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);
|
||||
|
@ -932,7 +949,7 @@ 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) {
|
||||
|
|
|
@ -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,19 +1192,19 @@ 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;
|
||||
|
||||
// only activate when setting non-zero velocity
|
||||
if (delta > ACTIVATION_LINEAR_VELOCITY_DELTA) {
|
||||
_dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EntityItem::updateDamping(float value) {
|
||||
auto clampedDamping = glm::clamp(value, 0.0f, 1.0f);
|
||||
|
@ -1238,12 +1239,13 @@ void EntityItem::updateAngularVelocity(const glm::vec3& value) {
|
|||
_angularVelocity = ENTITY_ITEM_ZERO_VEC3;
|
||||
} else {
|
||||
_angularVelocity = value;
|
||||
}
|
||||
// only activate when setting non-zero velocity
|
||||
if (delta > ACTIVATION_ANGULAR_VELOCITY_DELTA) {
|
||||
_dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EntityItem::updateAngularDamping(float value) {
|
||||
auto clampedDamping = glm::clamp(value, 0.0f, 1.0f);
|
||||
|
@ -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) {
|
||||
if (_simulatorID != value) {
|
||||
_simulatorID = value;
|
||||
_simulatorIDChangedTime = usecTimestampNow();
|
||||
}
|
||||
|
||||
void EntityItem::updateSimulatorID(const QUuid& value) {
|
||||
if (_simulatorID != value) {
|
||||
_simulatorID = value;
|
||||
_simulatorIDChangedTime = usecTimestampNow();
|
||||
_dirtyFlags |= EntityItem::DIRTY_SIMULATOR_ID;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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, "");
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<NodeList>();
|
||||
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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<NodeList>();
|
||||
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;
|
||||
|
|
|
@ -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<EntityItemID> 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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
#include <OctalCode.h>
|
||||
#include <Shape.h>
|
||||
#include <gpu/Format.h>
|
||||
|
||||
#include <LogHandler.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<NodeList>();
|
||||
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++;
|
||||
} else {
|
||||
_movingStepsWithoutSimulationOwner = 0;
|
||||
}
|
||||
if (_entity->getSimulatorID().isNull()) {
|
||||
_loopsWithoutOwner++;
|
||||
|
||||
uint32_t ownershipClaimDelay = 50; // TODO -- how to pick this? based on meters from our characterController?
|
||||
|
||||
if (_movingStepsWithoutSimulationOwner > ownershipClaimDelay) {
|
||||
const uint32_t OWNERSHIP_BID_DELAY = 50;
|
||||
if (_loopsWithoutOwner > OWNERSHIP_BID_DELAY) {
|
||||
//qDebug() << "Warning -- claiming something I saw moving." << getName();
|
||||
setShouldClaimSimulationOwnership(true);
|
||||
_candidateForOwnership = true;
|
||||
}
|
||||
} else {
|
||||
_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.
|
||||
// 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<NodeList>();
|
||||
const QUuid& myNodeID = nodeList->getSessionUUID();
|
||||
const QUuid& simulatorID = _entity->getSimulatorID();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
if (simulatorID != myNodeID) {
|
||||
// some other Node owns the simulating of this, so don't broadcast the results of local simulation.
|
||||
_candidateForOwnership = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
_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,25 +390,15 @@ 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;
|
||||
} else {
|
||||
_numNonMovingUpdates++;
|
||||
}
|
||||
if (_numNonMovingUpdates <= 1) {
|
||||
// we only update lastEdited when we're sending new physics data
|
||||
quint64 lastSimulated = _entity->getLastSimulated();
|
||||
_entity->setLastEdited(lastSimulated);
|
||||
|
@ -384,27 +412,19 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
|
|||
qCDebug(physics) << " lastSimulated:" << debugTime(lastSimulated, now);
|
||||
#endif //def WANT_DEBUG
|
||||
|
||||
} else {
|
||||
properties.setLastEdited(_entity->getLastEdited());
|
||||
}
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
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());
|
||||
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 {
|
||||
// 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() {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<EntityMotionState*>(state);
|
||||
EntityItem* entity = entityState->getEntity();
|
||||
if (entity) {
|
||||
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<EntityMotionState*>::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;
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue