Merge branch 'master' of https://github.com/highfidelity/hifi into decouple-avatar-updates

This commit is contained in:
Howard Stearns 2015-09-03 13:55:57 -07:00
commit 32eb51d0fa
49 changed files with 1767 additions and 1096 deletions

View file

@ -11,6 +11,8 @@
#include "NodeConnectionData.h" #include "NodeConnectionData.h"
#include <QtCore/QDataStream>
NodeConnectionData NodeConnectionData::fromDataStream(QDataStream& dataStream, const HifiSockAddr& senderSockAddr, NodeConnectionData NodeConnectionData::fromDataStream(QDataStream& dataStream, const HifiSockAddr& senderSockAddr,
bool isConnectRequest) { bool isConnectRequest) {
NodeConnectionData newHeader; NodeConnectionData newHeader;

View file

@ -1,307 +1,319 @@
//
// hydraGrab.js // hydraGrab.js
// examples // examples
// //
// Created by Clément Brisset on 4/24/14. // Created by Eric Levin on 9/2/15
// Updated by Eric Levin on 5/14/15. // Copyright 2015 High Fidelity, Inc.
// Copyright 2014 High Fidelity, Inc.
// //
// This script allows you to grab and move/rotate physical objects with the hydra // Grab's physically moveable entities with the hydra- works for either near or far objects. User can also grab a far away object and drag it towards them by pressing the "4" button on either the left or ride controller.
//
// Using the hydras :
// grab physical entities with the right trigger
// //
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
var entityProps, currentPosition, currentVelocity, currentRotation, distanceToTarget, velocityTowardTarget, desiredVelocity; var RIGHT_HAND_CLICK = Controller.findAction("RIGHT_HAND_CLICK");
var addedVelocity, newVelocity, angularVelocity, dT, cameraEntityDistance; var rightTriggerAction = RIGHT_HAND_CLICK;
var LEFT = 0;
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 LEFT_HAND_CLICK = Controller.findAction("LEFT_HAND_CLICK");
var DROP_COLOR = { var leftTriggerAction = LEFT_HAND_CLICK;
red: 200,
green: 200,
blue: 200
};
var FULL_STRENGTH = 0.05; var ZERO_VEC = {
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, true);
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, x: 0,
y: 0, y: 0,
z: 0 z: 0
}; }
var LINE_LENGTH = 500;
var THICK_LINE_WIDTH = 7;
var THIN_LINE_WIDTH = 2;
this.laser = Overlays.addOverlay("line3d", { var NO_INTERSECT_COLOR = {
start: { red: 10,
x: 0, green: 10,
y: 0, blue: 255
z: 0 };
}, var INTERSECT_COLOR = {
end: { red: 250,
x: 0, green: 10,
y: 0, blue: 10
z: 0 };
},
color: LASER_COLOR,
alpha: 1,
lineWidth: LASER_WIDTH,
anchor: "MyAvatar"
});
this.dropLine = Overlays.addOverlay("line3d", { var GRAB_RADIUS = 2;
color: DROP_COLOR,
alpha: 1,
visible: false,
lineWidth: 2
});
var GRAB_COLOR = {
red: 250,
green: 10,
blue: 250
};
var SHOW_LINE_THRESHOLD = 0.2;
var DISTANCE_HOLD_THRESHOLD = 0.8;
this.update = function(deltaTime) { var right4Action = 18;
this.updateControllerState(); var left4Action = 17;
this.moveLaser();
this.checkTrigger();
this.checkEntityIntersection();
if (this.grabbing) {
this.updateEntity(deltaTime);
}
this.oldPalmPosition = this.palmPosition; var TRACTOR_BEAM_VELOCITY_THRESHOLD = 0.5;
this.oldTipPosition = this.tipPosition;
}
this.updateEntity = function(deltaTime) { var RIGHT = 1;
this.dControllerPosition = Vec3.subtract(this.palmPosition, this.oldPalmPosition); var LEFT = 0;
this.cameraEntityDistance = Vec3.distance(Camera.getPosition(), this.currentPosition); var rightController = new controller(RIGHT, rightTriggerAction, right4Action, "right")
this.targetPosition = Vec3.sum(this.targetPosition, Vec3.multiply(this.dControllerPosition, this.cameraEntityDistance * SCREEN_TO_METERS * DISTANCE_SCALE_FACTOR)); var leftController = new controller(LEFT, leftTriggerAction, left4Action, "left")
this.entityProps = Entities.getEntityProperties(this.grabbedEntity); function controller(side, triggerAction, pullAction, hand) {
this.currentPosition = this.entityProps.position; this.hand = hand;
this.currentVelocity = this.entityProps.velocity; if (hand === "right") {
this.getHandPosition = MyAvatar.getRightPalmPosition;
var dPosition = Vec3.subtract(this.targetPosition, this.currentPosition); this.getHandRotation = MyAvatar.getRightPalmRotation;
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 { } else {
this.newVelocity = {
x: 0,
y: 0,
z: 0
};
}
this.transformedAngularVelocity = Controller.getSpatialControlRawAngularVelocity(this.tip);
this.transformedAngularVelocity = Vec3.multiplyQbyV(Camera.getOrientation(), this.transformedAngularVelocity);
Entities.editEntity(this.grabbedEntity, { this.getHandPosition = MyAvatar.getLeftPalmPosition;
velocity: this.newVelocity, this.getHandRotation = MyAvatar.getLeftPalmRotation;
angularVelocity: this.transformedAngularVelocity }
this.triggerAction = triggerAction;
this.pullAction = pullAction;
this.actionID = null;
this.tractorBeamActive = false;
this.distanceHolding = false;
this.closeGrabbing = false;
this.triggerValue = 0;
this.prevTriggerValue = 0;
this.palm = 2 * side;
this.tip = 2 * side + 1;
this.pointer = Entities.addEntity({
type: "Line",
name: "pointer",
color: NO_INTERSECT_COLOR,
dimensions: {
x: 1000,
y: 1000,
z: 1000
},
visible: false,
});
}
controller.prototype.updateLine = function() {
var handPosition = Controller.getSpatialControlPosition(this.palm);
var direction = Controller.getSpatialControlNormal(this.tip);
Entities.editEntity(this.pointer, {
position: handPosition,
linePoints: [
ZERO_VEC,
Vec3.multiply(direction, LINE_LENGTH)
]
}); });
this.updateDropLine(this.targetPosition); //only check if we havent already grabbed an object
if (this.distanceHolding) {
} return;
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();
}
} }
}
//move origin a bit away from hand so nothing gets in way
var origin = Vec3.sum(handPosition, direction);
if (this.checkForIntersections(origin, direction)) {
Entities.editEntity(this.pointer, {
color: INTERSECT_COLOR,
});
} else {
Entities.editEntity(this.pointer, {
color: NO_INTERSECT_COLOR,
});
}
}
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() {
controller.prototype.checkForIntersections = function(origin, direction) {
var pickRay = { var pickRay = {
origin: this.palmPosition, origin: origin,
direction: Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition)) direction: direction
}; };
var intersection = getRayIntersection(pickRay, true);
if (intersection.intersects && intersection.properties.collisionsWillMove) { var intersection = Entities.findRayIntersection(pickRay, true);
this.laserWasHovered = true; if (intersection.intersects && intersection.properties.collisionsWillMove === 1) {
if (this.triggerHeld && !this.grabbing) { this.distanceToEntity = Vec3.distance(origin, intersection.properties.position);
this.grab(intersection.entityID); Entities.editEntity(this.pointer, {
} linePoints: [
Overlays.editOverlay(this.laser, { ZERO_VEC,
color: LASER_HOVER_COLOR Vec3.multiply(direction, this.distanceToEntity)
}); ]
} else if (this.laserWasHovered) { });
this.laserWasHovered = false; this.grabbedEntity = intersection.entityID;
Overlays.editOverlay(this.laser, { return true;
color: LASER_COLOR
});
} }
} return false;
}
this.grab = function(entityId) { controller.prototype.attemptMove = function() {
this.grabbing = true; if (this.tractorBeamActive) {
this.grabbedEntity = entityId; return;
this.entityProps = Entities.getEntityProperties(this.grabbedEntity); }
this.targetPosition = this.entityProps.position; if (this.grabbedEntity || this.distanceHolding) {
this.currentPosition = this.targetPosition; var handPosition = Controller.getSpatialControlPosition(this.palm);
this.oldPalmPosition = this.palmPosition; var direction = Controller.getSpatialControlNormal(this.tip);
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() { var newPosition = Vec3.sum(handPosition, Vec3.multiply(direction, this.distanceToEntity))
this.grabbing = false; this.distanceHolding = true;
//TO DO : USE SPRING ACTION UPDATE FOR MOVING
if (this.actionID === null) {
this.actionID = Entities.addAction("spring", this.grabbedEntity, {
targetPosition: newPosition,
linearTimeScale: .1
});
} else {
Entities.updateAction(this.grabbedEntity, this.actionID, {
targetPosition: newPosition
});
}
}
}
controller.prototype.showPointer = function() {
Entities.editEntity(this.pointer, {
visible: true
});
}
controller.prototype.hidePointer = function() {
Entities.editEntity(this.pointer, {
visible: false
});
}
controller.prototype.letGo = function() {
Entities.deleteAction(this.grabbedEntity, this.actionID);
this.grabbedEntity = null; this.grabbedEntity = null;
Overlays.editOverlay(this.laser, { this.actionID = null;
visible: true this.distanceHolding = false;
}); this.tractorBeamActive = false;
Overlays.editOverlay(this.dropLine, { this.checkForEntityArrival = false;
visible: false this.closeGrabbing = false;
}); }
Audio.playSound(releaseSound, { controller.prototype.update = function() {
position: this.entityProps.position, if (this.tractorBeamActive && this.checkForEntityArrival) {
volume: 0.25 var entityVelocity = Entities.getEntityProperties(this.grabbedEntity).velocity
}); if (Vec3.length(entityVelocity) < TRACTOR_BEAM_VELOCITY_THRESHOLD) {
this.letGo();
// 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 return;
// 2. interface A sets the entity's gravity to zero }
// 3. interface B grabs the entity and saves off its gravity (which is zero) this.triggerValue = Controller.getActionValue(this.triggerAction);
// 4. interface A releases the entity and puts the original gravity back if (this.triggerValue > SHOW_LINE_THRESHOLD && this.prevTriggerValue < SHOW_LINE_THRESHOLD) {
// 5. interface B releases the entity and puts the original gravity back (to zero) //First check if an object is within close range and then run the close grabbing logic
if(vectorIsZero(this.originalGravity)) { if (this.checkForInRangeObject()) {
Entities.editEntity(this.grabbedEntity, { this.grabEntity();
gravity: this.originalGravity } else {
}); this.showPointer();
this.shouldDisplayLine = true;
}
} else if (this.triggerValue < SHOW_LINE_THRESHOLD && this.prevTriggerValue > SHOW_LINE_THRESHOLD) {
this.hidePointer();
this.letGo();
this.shouldDisplayLine = false;
} }
}
this.moveLaser = function() { if (this.shouldDisplayLine) {
var inverseRotation = Quat.inverse(MyAvatar.orientation); this.updateLine();
var startPosition = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.palmPosition, MyAvatar.position)); }
// startPosition = Vec3.multiply(startPosition, 1 / MyAvatar.scale); if (this.triggerValue > DISTANCE_HOLD_THRESHOLD && !this.closeGrabbing) {
var direction = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.tipPosition, this.palmPosition)); this.attemptMove();
direction = Vec3.multiply(direction, LASER_LENGTH_FACTOR / (Vec3.length(direction) * MyAvatar.scale)); }
var endPosition = Vec3.sum(startPosition, direction);
Overlays.editOverlay(this.laser, {
start: startPosition, this.prevTriggerValue = this.triggerValue;
end: endPosition }
controller.prototype.grabEntity = function() {
var handRotation = this.getHandRotation();
var handPosition = this.getHandPosition();
var objectRotation = Entities.getEntityProperties(this.grabbedEntity).rotation;
var offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation);
var objectPosition = Entities.getEntityProperties(this.grabbedEntity).position;
var offset = Vec3.subtract(objectPosition, handPosition);
var offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, offsetRotation)), offset);
this.closeGrabbing = true;
this.actionID = Entities.addAction("hold", this.grabbedEntity, {
relativePosition: offsetPosition,
relativeRotation: offsetRotation,
hand: this.hand,
timeScale: 0.05
}); });
}
this.cleanup = function() {
Overlays.deleteOverlay(this.laser);
Overlays.deleteOverlay(this.dropLine);
}
} }
function update(deltaTime) {
rightController.update(deltaTime); controller.prototype.checkForInRangeObject = function() {
leftController.update(deltaTime); var handPosition = Controller.getSpatialControlPosition(this.palm);
var entities = Entities.findEntities(handPosition, GRAB_RADIUS);
var minDistance = GRAB_RADIUS;
var grabbedEntity = null;
//Get nearby entities and assign nearest
for (var i = 0; i < entities.length; i++) {
var props = Entities.getEntityProperties(entities[i]);
var distance = Vec3.distance(props.position, handPosition);
if (distance < minDistance && props.name !== "pointer" && props.collisionsWillMove === 1) {
grabbedEntity = entities[i];
minDistance = distance;
}
}
if (grabbedEntity === null) {
return false;
} else {
this.grabbedEntity = grabbedEntity;
return true;
}
} }
function scriptEnding() {
rightController.cleanup(); controller.prototype.onActionEvent = function(action, state) {
leftController.cleanup(); if (this.pullAction === action && state === 1) {
if (this.actionID !== null) {
var self = this;
this.tractorBeamActive = true;
//We need to wait a bit before checking for entity arrival at target destination (meaning checking for velocity being close to some
//low threshold) because otherwise we'll think the entity has arrived before its even really gotten moving!
Script.setTimeout(function() {
self.checkForEntityArrival = true;
}, 500);
var handPosition = Controller.getSpatialControlPosition(this.palm);
var direction = Controller.getSpatialControlNormal(this.tip);
//move final destination along line a bit, so it doesnt hit avatar hand
Entities.updateAction(this.grabbedEntity, this.actionID, {
targetPosition: Vec3.sum(handPosition, Vec3.multiply(2, direction))
});
}
}
} }
function vectorIsZero(v) { controller.prototype.cleanup = function() {
return v.x === 0 && v.y === 0 && v.z === 0; Entities.deleteEntity(this.pointer);
Entities.deleteAction(this.grabbedEntity, this.actionID);
} }
var rightController = new controller(RIGHT); function update() {
var leftController = new controller(LEFT); rightController.update();
leftController.update();
}
function onActionEvent(action, state) {
rightController.onActionEvent(action, state);
leftController.onActionEvent(action, state);
}
Script.update.connect(update); function cleanup() {
Script.scriptEnding.connect(scriptEnding); rightController.cleanup();
leftController.cleanup();
}
Script.scriptEnding.connect(cleanup);
Script.update.connect(update)
Controller.actionEvent.connect(onActionEvent);

View file

@ -13,32 +13,7 @@ Script.include("http://s3.amazonaws.com/hifi-public/scripts/libraries/toolBars.j
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
var nullActionID = "00000000-0000-0000-0000-000000000000";
var controllerID;
var controllerActive;
var leftHandObjectID = null;
var rightHandObjectID = null;
var leftHandActionID = nullActionID;
var rightHandActionID = nullActionID;
var TRIGGER_THRESHOLD = 0.2;
var GRAB_RADIUS = 0.15;
var LEFT_HAND_CLICK = Controller.findAction("LEFT_HAND_CLICK");
var RIGHT_HAND_CLICK = Controller.findAction("RIGHT_HAND_CLICK");
var ACTION1 = Controller.findAction("ACTION1");
var ACTION2 = Controller.findAction("ACTION2");
var rightHandGrabAction = RIGHT_HAND_CLICK;
var leftHandGrabAction = LEFT_HAND_CLICK;
var rightHandGrabValue = 0;
var leftHandGrabValue = 0;
var prevRightHandGrabValue = 0
var prevLeftHandGrabValue = 0;
var grabColor = { red: 0, green: 255, blue: 0};
var releaseColor = { red: 0, green: 0, blue: 255};
var toolBar = new ToolBar(0, 0, ToolBar.vertical, "highfidelity.toybox.toolbar", function() { var toolBar = new ToolBar(0, 0, ToolBar.vertical, "highfidelity.toybox.toolbar", function() {
return { return {
@ -63,25 +38,6 @@ var cleanupButton = toolBar.addOverlay("image", {
alpha: 1 alpha: 1
}); });
var overlays = false;
var leftHandOverlay;
var rightHandOverlay;
if (overlays) {
leftHandOverlay = Overlays.addOverlay("sphere", {
position: MyAvatar.getLeftPalmPosition(),
size: GRAB_RADIUS,
color: releaseColor,
alpha: 0.5,
solid: false
});
rightHandOverlay = Overlays.addOverlay("sphere", {
position: MyAvatar.getRightPalmPosition(),
size: GRAB_RADIUS,
color: releaseColor,
alpha: 0.5,
solid: false
});
}
var OBJECT_HEIGHT_OFFSET = 0.5; var OBJECT_HEIGHT_OFFSET = 0.5;
var MIN_OBJECT_SIZE = 0.05; var MIN_OBJECT_SIZE = 0.05;
@ -98,8 +54,6 @@ var GRAVITY = {
z: 0.0 z: 0.0
} }
var LEFT = 0;
var RIGHT = 1;
var tableCreated = false; var tableCreated = false;
@ -108,7 +62,6 @@ var tableEntities = Array(NUM_OBJECTS + 1); // Also includes table
var VELOCITY_MAG = 0.3; var VELOCITY_MAG = 0.3;
var entitiesToResize = [];
var MODELS = Array( var MODELS = Array(
{ modelURL: "https://hifi-public.s3.amazonaws.com/ozan/props/sword/sword.fbx" }, { modelURL: "https://hifi-public.s3.amazonaws.com/ozan/props/sword/sword.fbx" },
@ -136,196 +89,15 @@ var COLLISION_SOUNDS = Array(
var RESIZE_TIMER = 0.0; var RESIZE_TIMER = 0.0;
var RESIZE_WAIT = 0.05; // 50 milliseconds var RESIZE_WAIT = 0.05; // 50 milliseconds
var leftFist = Entities.addEntity( {
type: "Sphere",
shapeType: 'sphere',
position: MyAvatar.getLeftPalmPosition(),
dimensions: { x: GRAB_RADIUS, y: GRAB_RADIUS, z: GRAB_RADIUS },
rotation: MyAvatar.getLeftPalmRotation(),
visible: false,
collisionsWillMove: false,
ignoreForCollisions: true
});
var rightFist = Entities.addEntity( {
type: "Sphere",
shapeType: 'sphere',
position: MyAvatar.getRightPalmPosition(),
dimensions: { x: GRAB_RADIUS, y: GRAB_RADIUS, z: GRAB_RADIUS },
rotation: MyAvatar.getRightPalmRotation(),
visible: false,
collisionsWillMove: false,
ignoreForCollisions: true
});
function letGo(hand) {
var actionIDToRemove = (hand == LEFT) ? leftHandActionID : rightHandActionID;
var entityIDToEdit = (hand == LEFT) ? leftHandObjectID : rightHandObjectID;
var handVelocity = (hand == LEFT) ? MyAvatar.getLeftPalmVelocity() : MyAvatar.getRightPalmVelocity();
var handAngularVelocity = (hand == LEFT) ? MyAvatar.getLeftPalmAngularVelocity() :
MyAvatar.getRightPalmAngularVelocity();
if (actionIDToRemove != nullActionID && entityIDToEdit != null) {
Entities.deleteAction(entityIDToEdit, actionIDToRemove);
// TODO: upon successful letGo, restore collision groups
if (hand == LEFT) {
leftHandObjectID = null;
leftHandActionID = nullActionID;
} else {
rightHandObjectID = null;
rightHandActionID = nullActionID;
}
}
}
function setGrabbedObject(hand) {
var handPosition = (hand == LEFT) ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition();
var entities = Entities.findEntities(handPosition, GRAB_RADIUS);
var objectID = null;
var minDistance = GRAB_RADIUS;
for (var i = 0; i < entities.length; i++) {
// Don't grab the object in your other hands, your fists, or the table
if ((hand == LEFT && entities[i] == rightHandObjectID) ||
(hand == RIGHT && entities[i] == leftHandObjectID) ||
entities[i] == leftFist || entities[i] == rightFist ||
(tableCreated && entities[i] == tableEntities[0])) {
continue;
} else {
var distance = Vec3.distance(Entities.getEntityProperties(entities[i]).position, handPosition);
if (distance <= minDistance) {
objectID = entities[i];
minDistance = distance;
}
}
}
if (objectID == null) {
return false;
}
if (hand == LEFT) {
leftHandObjectID = objectID;
} else {
rightHandObjectID = objectID;
}
return true;
}
function grab(hand) {
if (!setGrabbedObject(hand)) {
// If you don't grab an object, make a fist
Entities.editEntity((hand == LEFT) ? leftFist : rightFist, { ignoreForCollisions: false } );
return;
}
var objectID = (hand == LEFT) ? leftHandObjectID : rightHandObjectID;
var handRotation = (hand == LEFT) ? MyAvatar.getLeftPalmRotation() : MyAvatar.getRightPalmRotation();
var handPosition = (hand == LEFT) ? MyAvatar.getLeftPalmPosition() : MyAvatar.getRightPalmPosition();
var objectRotation = Entities.getEntityProperties(objectID).rotation;
var offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation);
var objectPosition = Entities.getEntityProperties(objectID).position;
var offset = Vec3.subtract(objectPosition, handPosition);
var offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, offsetRotation)), offset);
// print(JSON.stringify(offsetPosition));
var actionID = Entities.addAction("hold", objectID, {
relativePosition: { x: 0, y: 0, z: 0 },
relativeRotation: offsetRotation,
hand: (hand == LEFT) ? "left" : "right",
timeScale: 0.05
});
if (actionID == nullActionID) {
if (hand == LEFT) {
leftHandObjectID = null;
} else {
rightHandObjectID = null;
}
} else {
// TODO: upon successful grab, add to collision group so object doesn't collide with immovable entities
if (hand == LEFT) {
leftHandActionID = actionID;
} else {
rightHandActionID = actionID;
}
}
}
function resizeModels() {
var newEntitiesToResize = [];
for (var i = 0; i < entitiesToResize.length; i++) {
var naturalDimensions = Entities.getEntityProperties(entitiesToResize[i]).naturalDimensions;
if (naturalDimensions.x != 1.0 || naturalDimensions.y != 1.0 || naturalDimensions.z != 1.0) {
// bigger range of sizes for models
var dimensions = Vec3.multiply(randFloat(MIN_OBJECT_SIZE, 3.0*MAX_OBJECT_SIZE), Vec3.normalize(naturalDimensions));
Entities.editEntity(entitiesToResize[i], {
dimensions: dimensions,
shapeType: "box"
});
} else {
newEntitiesToResize.push(entitiesToResize[i]);
}
}
entitiesToResize = newEntitiesToResize;
}
function update(deltaTime) {
if (overlays) {
Overlays.editOverlay(leftHandOverlay, { position: MyAvatar.getLeftPalmPosition() });
Overlays.editOverlay(rightHandOverlay, { position: MyAvatar.getRightPalmPosition() });
}
// if (tableCreated && RESIZE_TIMER < RESIZE_WAIT) {
// RESIZE_TIMER += deltaTime;
// } else if (tableCreated) {
// resizeModels();
// }
rightHandGrabValue = Controller.getActionValue(rightHandGrabAction);
leftHandGrabValue = Controller.getActionValue(leftHandGrabAction);
Entities.editEntity(leftFist, { position: MyAvatar.getLeftPalmPosition() });
Entities.editEntity(rightFist, { position: MyAvatar.getRightPalmPosition() });
if (rightHandGrabValue > TRIGGER_THRESHOLD &&
prevRightHandGrabValue < TRIGGER_THRESHOLD) {
if (overlays) {
Overlays.editOverlay(rightHandOverlay, { color: grabColor });
}
grab(RIGHT);
} else if (rightHandGrabValue < TRIGGER_THRESHOLD &&
prevRightHandGrabValue > TRIGGER_THRESHOLD) {
Entities.editEntity(rightFist, { ignoreForCollisions: true } );
if (overlays) {
Overlays.editOverlay(rightHandOverlay, { color: releaseColor });
}
letGo(RIGHT);
}
if (leftHandGrabValue > TRIGGER_THRESHOLD &&
prevLeftHandGrabValue < TRIGGER_THRESHOLD) {
if (overlays) {
Overlays.editOverlay(leftHandOverlay, { color: grabColor });
}
grab(LEFT);
} else if (leftHandGrabValue < TRIGGER_THRESHOLD &&
prevLeftHandGrabValue > TRIGGER_THRESHOLD) {
Entities.editEntity(leftFist, { ignoreForCollisions: true } );
if (overlays) {
Overlays.editOverlay(leftHandOverlay, { color: releaseColor });
}
letGo(LEFT);
}
prevRightHandGrabValue = rightHandGrabValue;
prevLeftHandGrabValue = leftHandGrabValue;
}
function cleanUp() { function cleanUp() {
letGo(RIGHT); print("CLEANUP!!!")
letGo(LEFT);
if (overlays) { if (overlays) {
Overlays.deleteOverlay(leftHandOverlay); Overlays.deleteOverlay(leftHandOverlay);
Overlays.deleteOverlay(rightHandOverlay); Overlays.deleteOverlay(rightHandOverlay);
} }
Entities.deleteEntity(leftFist);
Entities.deleteEntity(rightFist);
removeTable(); removeTable();
toolBar.cleanup(); toolBar.cleanup();
} }
@ -405,7 +177,6 @@ function createTable() {
density: 0.5, density: 0.5,
collisionsWillMove: true, collisionsWillMove: true,
color: { red: randInt(0, 255), green: randInt(0, 255), blue: randInt(0, 255) }, color: { red: randInt(0, 255), green: randInt(0, 255), blue: randInt(0, 255) },
// collisionSoundURL: COLLISION_SOUNDS[randInt(0, COLLISION_SOUNDS.length)]
}); });
if (type == "Model") { if (type == "Model") {
var randModel = randInt(0, MODELS.length); var randModel = randInt(0, MODELS.length);
@ -413,7 +184,6 @@ function createTable() {
shapeType: "box", shapeType: "box",
modelURL: MODELS[randModel].modelURL modelURL: MODELS[randModel].modelURL
}); });
entitiesToResize.push(tableEntities[i]);
} }
} }
} }
@ -426,5 +196,4 @@ function removeTable() {
} }
Script.scriptEnding.connect(cleanUp); Script.scriptEnding.connect(cleanUp);
Script.update.connect(update);
Controller.mousePressEvent.connect(onClick); Controller.mousePressEvent.connect(onClick);

View file

@ -0,0 +1,63 @@
//
// Created by Bradley Austin Davis on 2015/08/27
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
STICK_URL = HIFI_PUBLIC_BUCKET + "models/props/geo_stick.fbx";
ZERO_VECTOR = { x: 0, y: 0, z: 0 };
COLORS = {
WHITE: {
red: 255,
green: 255,
blue: 255,
},
BLACK: {
red: 0,
green: 0,
blue: 0,
},
GREY: {
red: 128,
green: 128,
blue: 128,
},
RED: {
red: 255,
green: 0,
blue: 0
},
BLUE: {
red: 0,
green: 0,
blue: 255
},
GREEN: {
red: 0,
green: 255,
blue: 0
},
CYAN: {
red: 0,
green: 255,
blue: 255
},
YELLOW: {
red: 255,
green: 255,
blue: 0
},
MAGENTA: {
red: 255,
green: 0,
blue: 255
}
}

View file

@ -53,6 +53,12 @@ Highlighter.prototype.setSize = function(newSize) {
}); });
} }
Highlighter.prototype.setRotation = function(newRotation) {
Overlays.editOverlay(this.highlightCube, {
rotation: newRotation
});
}
Highlighter.prototype.updateHighlight = function() { Highlighter.prototype.updateHighlight = function() {
if (this.hightlighted) { if (this.hightlighted) {
var properties = Entities.getEntityProperties(this.hightlighted); var properties = Entities.getEntityProperties(this.hightlighted);

View file

@ -0,0 +1,145 @@
HTML_COLORS = {
AliceBlue: "#F0F8FF",
AntiqueWhite: "#FAEBD7",
Aqua: "#00FFFF",
Aquamarine: "#7FFFD4",
Azure: "#F0FFFF",
Beige: "#F5F5DC",
Bisque: "#FFE4C4",
Black: "#000000",
BlanchedAlmond: "#FFEBCD",
Blue: "#0000FF",
BlueViolet: "#8A2BE2",
Brown: "#A52A2A",
BurlyWood: "#DEB887",
CadetBlue: "#5F9EA0",
Chartreuse: "#7FFF00",
Chocolate: "#D2691E",
Coral: "#FF7F50",
CornflowerBlue: "#6495ED",
Cornsilk: "#FFF8DC",
Crimson: "#DC143C",
Cyan: "#00FFFF",
DarkBlue: "#00008B",
DarkCyan: "#008B8B",
DarkGoldenRod: "#B8860B",
DarkGray: "#A9A9A9",
DarkGreen: "#006400",
DarkKhaki: "#BDB76B",
DarkMagenta: "#8B008B",
DarkOliveGreen: "#556B2F",
DarkOrange: "#FF8C00",
DarkOrchid: "#9932CC",
DarkRed: "#8B0000",
DarkSalmon: "#E9967A",
DarkSeaGreen: "#8FBC8F",
DarkSlateBlue: "#483D8B",
DarkSlateGray: "#2F4F4F",
DarkTurquoise: "#00CED1",
DarkViolet: "#9400D3",
DeepPink: "#FF1493",
DeepSkyBlue: "#00BFFF",
DimGray: "#696969",
DodgerBlue: "#1E90FF",
FireBrick: "#B22222",
FloralWhite: "#FFFAF0",
ForestGreen: "#228B22",
Fuchsia: "#FF00FF",
Gainsboro: "#DCDCDC",
GhostWhite: "#F8F8FF",
Gold: "#FFD700",
GoldenRod: "#DAA520",
Gray: "#808080",
Green: "#008000",
GreenYellow: "#ADFF2F",
HoneyDew: "#F0FFF0",
HotPink: "#FF69B4",
IndianRed: "#CD5C5C",
Indigo: "#4B0082",
Ivory: "#FFFFF0",
Khaki: "#F0E68C",
Lavender: "#E6E6FA",
LavenderBlush: "#FFF0F5",
LawnGreen: "#7CFC00",
LemonChiffon: "#FFFACD",
LightBlue: "#ADD8E6",
LightCoral: "#F08080",
LightCyan: "#E0FFFF",
LightGoldenRodYellow: "#FAFAD2",
LightGray: "#D3D3D3",
LightGreen: "#90EE90",
LightPink: "#FFB6C1",
LightSalmon: "#FFA07A",
LightSeaGreen: "#20B2AA",
LightSkyBlue: "#87CEFA",
LightSlateGray: "#778899",
LightSteelBlue: "#B0C4DE",
LightYellow: "#FFFFE0",
Lime: "#00FF00",
LimeGreen: "#32CD32",
Linen: "#FAF0E6",
Magenta: "#FF00FF",
Maroon: "#800000",
MediumAquaMarine: "#66CDAA",
MediumBlue: "#0000CD",
MediumOrchid: "#BA55D3",
MediumPurple: "#9370DB",
MediumSeaGreen: "#3CB371",
MediumSlateBlue: "#7B68EE",
MediumSpringGreen: "#00FA9A",
MediumTurquoise: "#48D1CC",
MediumVioletRed: "#C71585",
MidnightBlue: "#191970",
MintCream: "#F5FFFA",
MistyRose: "#FFE4E1",
Moccasin: "#FFE4B5",
NavajoWhite: "#FFDEAD",
Navy: "#000080",
OldLace: "#FDF5E6",
Olive: "#808000",
OliveDrab: "#6B8E23",
Orange: "#FFA500",
OrangeRed: "#FF4500",
Orchid: "#DA70D6",
PaleGoldenRod: "#EEE8AA",
PaleGreen: "#98FB98",
PaleTurquoise: "#AFEEEE",
PaleVioletRed: "#DB7093",
PapayaWhip: "#FFEFD5",
PeachPuff: "#FFDAB9",
Peru: "#CD853F",
Pink: "#FFC0CB",
Plum: "#DDA0DD",
PowderBlue: "#B0E0E6",
Purple: "#800080",
RebeccaPurple: "#663399",
Red: "#FF0000",
RosyBrown: "#BC8F8F",
RoyalBlue: "#4169E1",
SaddleBrown: "#8B4513",
Salmon: "#FA8072",
SandyBrown: "#F4A460",
SeaGreen: "#2E8B57",
SeaShell: "#FFF5EE",
Sienna: "#A0522D",
Silver: "#C0C0C0",
SkyBlue: "#87CEEB",
SlateBlue: "#6A5ACD",
SlateGray: "#708090",
Snow: "#FFFAFA",
SpringGreen: "#00FF7F",
SteelBlue: "#4682B4",
Tan: "#D2B48C",
Teal: "#008080",
Thistle: "#D8BFD8",
Tomato: "#FF6347",
Turquoise: "#40E0D0",
Violet: "#EE82EE",
Wheat: "#F5DEB3",
White: "#FFFFFF",
WhiteSmoke: "#F5F5F5",
Yellow: "#FFFF00",
YellowGreen: "#9ACD32",
}

View file

@ -0,0 +1,304 @@
//
// Created by Bradley Austin Davis on 2015/09/01
// Copyright 2015 High Fidelity, Inc.
//
// 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("constants.js");
Script.include("utils.js");
Script.include("highlighter.js");
Script.include("omniTool/models/modelBase.js");
Script.include("omniTool/models/wand.js");
OmniToolModules = {};
OmniToolModuleType = null;
OmniTool = function(side) {
this.OMNI_KEY = "OmniTool";
this.MAX_FRAMERATE = 30;
this.UPDATE_INTERVAL = 1.0 / this.MAX_FRAMERATE
this.SIDE = side;
this.PALM = 2 * side;
this.ACTION = findAction(side ? "ACTION2" : "ACTION1");
this.ALT_ACTION = findAction(side ? "ACTION1" : "ACTION2");
this.highlighter = new Highlighter();
this.ignoreEntities = {};
this.nearestOmniEntity = {
id: null,
inside: false,
position: null,
distance: Infinity,
radius: 0,
omniProperties: {},
boundingBox: null,
};
this.activeOmniEntityId = null;
this.lastUpdateInterval = 0;
this.tipLength = 0.4;
this.active = false;
this.module = null;
this.moduleEntityId = null;
this.lastScanPosition = ZERO_VECTOR;
this.model = new Wand();
this.model.setLength(this.tipLength);
// Connect to desired events
var _this = this;
Controller.actionEvent.connect(function(action, state) {
_this.onActionEvent(action, state);
});
Script.update.connect(function(deltaTime) {
_this.lastUpdateInterval += deltaTime;
if (_this.lastUpdateInterval >= _this.UPDATE_INTERVAL) {
_this.onUpdate(_this.lastUpdateInterval);
_this.lastUpdateInterval = 0;
}
});
Script.scriptEnding.connect(function() {
_this.onCleanup();
});
}
OmniTool.prototype.onActionEvent = function(action, state) {
// FIXME figure out the issues when only one spatial controller is active
// logDebug("Action: " + action + " " + state);
if (this.module && this.module.onActionEvent) {
this.module.onActionEvent(action, state);
}
if (action == this.ACTION) {
if (state) {
this.onClick();
} else {
this.onRelease();
}
}
// FIXME Does not work
//// with only one controller active (listed as 2 here because 'tip' + 'palm')
//// then treat the alt action button as the action button
}
OmniTool.prototype.getOmniToolData = function(entityId) {
return getEntityCustomData(this.OMNI_KEY, entityId, null);
}
OmniTool.prototype.setOmniToolData = function(entityId, data) {
setEntityCustomData(this.OMNI_KEY, entityId, data);
}
OmniTool.prototype.updateOmniToolData = function(entityId, data) {
var currentData = this.getOmniToolData(entityId) || {};
for (var key in data) {
currentData[key] = data[key];
}
setEntityCustomData(this.OMNI_KEY, entityId, currentData);
}
OmniTool.prototype.setActive = function(active) {
if (active === this.active) {
return;
}
logDebug("omnitool changing active state: " + active);
this.active = active;
this.model.setVisible(this.active);
if (this.module && this.module.onActiveChanged) {
this.module.onActiveChanged(this.side);
}
}
OmniTool.prototype.onUpdate = function(deltaTime) {
// FIXME this returns data if either the left or right controller is not on the base
this.position = Controller.getSpatialControlPosition(this.PALM);
// When on the base, hydras report a position of 0
this.setActive(Vec3.length(this.position) > 0.001);
var rawRotation = Controller.getSpatialControlRawRotation(this.PALM);
this.rotation = Quat.multiply(MyAvatar.orientation, rawRotation);
this.model.setTransform({
rotation: this.rotation,
position: this.position,
});
this.scan();
if (this.module && this.module.onUpdate) {
this.module.onUpdate(deltaTime);
}
}
OmniTool.prototype.onClick = function() {
// First check to see if the user is switching to a new omni module
if (this.nearestOmniEntity.inside && this.nearestOmniEntity.omniProperties.script) {
this.activateNewOmniModule();
return;
}
// Next check if there is an active module and if so propagate the click
// FIXME how to I switch to a new module?
if (this.module && this.module.onClick) {
this.module.onClick();
return;
}
}
OmniTool.prototype.onRelease = function() {
// FIXME how to I switch to a new module?
if (this.module && this.module.onRelease) {
this.module.onRelease();
return;
}
logDebug("Base omnitool does nothing on release");
}
// FIXME resturn a structure of all nearby entities to distances
OmniTool.prototype.findNearestOmniEntity = function(maxDistance, selector) {
if (!maxDistance) {
maxDistance = 2.0;
}
var resultDistance = Infinity;
var resultId = null;
var resultProperties = null;
var resultOmniData = null;
var ids = Entities.findEntities(this.model.tipPosition, maxDistance);
for (var i in ids) {
var entityId = ids[i];
if (this.ignoreEntities[entityId]) {
continue;
}
var omniData = this.getOmniToolData(entityId);
if (!omniData) {
// FIXME find a place to flush this information
this.ignoreEntities[entityId] = true;
continue;
}
// Let searchers query specifically
if (selector && !selector(entityId, omniData)) {
continue;
}
var properties = Entities.getEntityProperties(entityId);
var distance = Vec3.distance(this.model.tipPosition, properties.position);
if (distance < resultDistance) {
resultDistance = distance;
resultId = entityId;
}
}
return resultId;
}
OmniTool.prototype.getPosition = function() {
return this.model.tipPosition;
}
OmniTool.prototype.onEnterNearestOmniEntity = function() {
this.nearestOmniEntity.inside = true;
this.highlighter.highlight(this.nearestOmniEntity.id);
logDebug("On enter omniEntity " + this.nearestOmniEntity.id);
}
OmniTool.prototype.onLeaveNearestOmniEntity = function() {
this.nearestOmniEntity.inside = false;
this.highlighter.highlight(null);
logDebug("On leave omniEntity " + this.nearestOmniEntity.id);
}
OmniTool.prototype.setNearestOmniEntity = function(entityId) {
if (entityId && entityId !== this.nearestOmniEntity.id) {
if (this.nearestOmniEntity.id && this.nearestOmniEntity.inside) {
this.onLeaveNearestOmniEntity();
}
this.nearestOmniEntity.id = entityId;
this.nearestOmniEntity.omniProperties = this.getOmniToolData(entityId);
var properties = Entities.getEntityProperties(entityId);
this.nearestOmniEntity.position = properties.position;
// FIXME use a real bounding box, not a sphere
var bbox = properties.boundingBox;
this.nearestOmniEntity.radius = Vec3.length(Vec3.subtract(bbox.center, bbox.brn));
this.highlighter.setRotation(properties.rotation);
this.highlighter.setSize(Vec3.multiply(1.05, bbox.dimensions));
}
}
OmniTool.prototype.scan = function() {
var scanDistance = Vec3.distance(this.model.tipPosition, this.lastScanPosition);
if (scanDistance < 0.005) {
return;
}
this.lastScanPosition = this.model.tipPosition;
this.setNearestOmniEntity(this.findNearestOmniEntity());
if (this.nearestOmniEntity.id) {
var distance = Vec3.distance(this.model.tipPosition, this.nearestOmniEntity.position);
// track distance on a half centimeter basis
if (Math.abs(this.nearestOmniEntity.distance - distance) > 0.005) {
this.nearestOmniEntity.distance = distance;
if (!this.nearestOmniEntity.inside && distance < this.nearestOmniEntity.radius) {
this.onEnterNearestOmniEntity();
}
if (this.nearestOmniEntity.inside && distance > this.nearestOmniEntity.radius + 0.01) {
this.onLeaveNearestOmniEntity();
}
}
}
}
OmniTool.prototype.unloadModule = function() {
if (this.module && this.module.onUnload) {
this.module.onUnload();
}
this.module = null;
this.moduleEntityId = null;
}
OmniTool.prototype.activateNewOmniModule = function() {
// Support the ability for scripts to just run without replacing the current module
var script = this.nearestOmniEntity.omniProperties.script;
if (script.indexOf("/") < 0) {
script = "omniTool/modules/" + script;
}
// Reset the tool type
OmniToolModuleType = null;
logDebug("Including script path: " + script);
try {
Script.include(script);
} catch(err) {
logWarn("Failed to include script: " + script + "\n" + err);
return;
}
// If we're building a new module, unload the old one
if (OmniToolModuleType) {
logDebug("New OmniToolModule: " + OmniToolModuleType);
this.unloadModule();
try {
this.module = new OmniToolModules[OmniToolModuleType](this, this.nearestOmniEntity.id);
this.moduleEntityId = this.nearestOmniEntity.id;
if (this.module.onLoad) {
this.module.onLoad();
}
} catch(err) {
logWarn("Failed to instantiate new module: " + err);
}
}
}
// FIXME find a good way to sync the two omni tools
OMNI_TOOLS = [ new OmniTool(0), new OmniTool(1) ];

View file

@ -0,0 +1,19 @@
ModelBase = function() {
this.length = 0.2;
}
ModelBase.prototype.setVisible = function(visible) {
this.visible = visible;
}
ModelBase.prototype.setLength = function(length) {
this.length = length;
}
ModelBase.prototype.setTransform = function(transform) {
this.rotation = transform.rotation;
this.position = transform.position;
this.tipVector = Vec3.multiplyQbyV(this.rotation, { x: 0, y: this.length, z: 0 });
this.tipPosition = Vec3.sum(this.position, this.tipVector);
}

View file

@ -0,0 +1,120 @@
Wand = function() {
// Max updates fps
this.MAX_FRAMERATE = 30
this.UPDATE_INTERVAL = 1.0 / this.MAX_FRAMERATE
this.DEFAULT_TIP_COLORS = [ {
red: 128,
green: 128,
blue: 128,
}, {
red: 64,
green: 64,
blue: 64,
}];
this.POINTER_ROTATION = Quat.fromPitchYawRollDegrees(45, 0, 45);
// FIXME does this need to be a member of this?
this.lastUpdateInterval = 0;
this.pointers = [
Overlays.addOverlay("cube", {
position: ZERO_VECTOR,
color: this.DEFAULT_TIP_COLORS[0],
alpha: 1.0,
solid: true,
visible: false,
}),
Overlays.addOverlay("cube", {
position: ZERO_VECTOR,
color: this.DEFAULT_TIP_COLORS[1],
alpha: 1.0,
solid: true,
visible: false,
})
];
this.wand = Overlays.addOverlay("cube", {
position: ZERO_VECTOR,
color: COLORS.WHITE,
dimensions: { x: 0.01, y: 0.01, z: 0.2 },
alpha: 1.0,
solid: true,
visible: false
});
var _this = this;
Script.scriptEnding.connect(function() {
Overlays.deleteOverlay(_this.pointers[0]);
Overlays.deleteOverlay(_this.pointers[1]);
Overlays.deleteOverlay(_this.wand);
});
Script.update.connect(function(deltaTime) {
_this.lastUpdateInterval += deltaTime;
if (_this.lastUpdateInterval >= _this.UPDATE_INTERVAL) {
_this.onUpdate(_this.lastUpdateInterval);
_this.lastUpdateInterval = 0;
}
});
}
Wand.prototype = Object.create( ModelBase.prototype );
Wand.prototype.setVisible = function(visible) {
ModelBase.prototype.setVisible.call(this, visible);
Overlays.editOverlay(this.pointers[0], {
visible: this.visible
});
Overlays.editOverlay(this.pointers[1], {
visible: this.visible
});
Overlays.editOverlay(this.wand, {
visible: this.visible
});
}
Wand.prototype.setTransform = function(transform) {
ModelBase.prototype.setTransform.call(this, transform);
var wandPosition = Vec3.sum(this.position, Vec3.multiply(0.5, this.tipVector));
Overlays.editOverlay(this.pointers[0], {
position: this.tipPosition,
rotation: this.rotation,
visible: true,
});
Overlays.editOverlay(this.pointers[1], {
position: this.tipPosition,
rotation: Quat.multiply(this.POINTER_ROTATION, this.rotation),
visible: true,
});
Overlays.editOverlay(this.wand, {
dimensions: { x: 0.01, y: this.length * 0.9, z: 0.01 },
position: wandPosition,
rotation: this.rotation,
visible: true,
});
}
Wand.prototype.setTipColors = function(color1, color2) {
Overlays.editOverlay(this.pointers[0], {
color: color1 || this.DEFAULT_TIP_COLORS[0],
});
Overlays.editOverlay(this.pointers[1], {
color: color2 || this.DEFAULT_TIP_COLORS[1],
});
}
Wand.prototype.onUpdate = function(deltaTime) {
if (this.visible) {
var time = new Date().getTime() / 250;
var scale1 = Math.abs(Math.sin(time));
var scale2 = Math.abs(Math.cos(time));
Overlays.editOverlay(this.pointers[0], {
scale: scale1 * 0.01,
});
Overlays.editOverlay(this.pointers[1], {
scale: scale2 * 0.01,
});
}
}

View file

@ -0,0 +1,9 @@
OmniToolModules.Test = function() {
}
OmniToolModules.Test.prototype.onClick = function() {
logDebug("Test module onClick");
}
OmniToolModuleType = "Test"

View file

@ -53,11 +53,17 @@ getEntityUserData = function(id) {
var results = null; var results = null;
var properties = Entities.getEntityProperties(id); var properties = Entities.getEntityProperties(id);
if (properties.userData) { if (properties.userData) {
results = JSON.parse(properties.userData); try {
results = JSON.parse(properties.userData);
} catch(err) {
logDebug(err);
logDebug(properties.userData);
}
} }
return results ? results : {}; return results ? results : {};
} }
// Non-destructively modify the user data of an entity. // Non-destructively modify the user data of an entity.
setEntityCustomData = function(customKey, id, data) { setEntityCustomData = function(customKey, id, data) {
var userData = getEntityUserData(id); var userData = getEntityUserData(id);
@ -70,14 +76,6 @@ getEntityCustomData = function(customKey, id, defaultValue) {
return userData[customKey] ? userData[customKey] : defaultValue; return userData[customKey] ? userData[customKey] : defaultValue;
} }
getMagBallsData = function(id) {
return getEntityCustomData(CUSTOM_DATA_NAME, id, {});
}
setMagBallsData = function(id, value) {
setEntityCustomData(CUSTOM_DATA_NAME, id, value);
}
mergeObjects = function(proto, custom) { mergeObjects = function(proto, custom) {
var result = {}; var result = {};
for (var attrname in proto) { for (var attrname in proto) {

113
examples/toys/magBalls.js Normal file
View file

@ -0,0 +1,113 @@
//
// Created by Bradley Austin Davis on 2015/08/25
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// FIXME Script paths have to be relative to the caller, in this case libraries/OmniTool.js
Script.include("../toys/magBalls/constants.js");
Script.include("../toys/magBalls/graph.js");
Script.include("../toys/magBalls/edgeSpring.js");
Script.include("../toys/magBalls/magBalls.js");
OmniToolModuleType = "MagBallsController"
OmniToolModules.MagBallsController = function(omniTool, entityId) {
this.omniTool = omniTool;
this.entityId = entityId;
this.highlighter = new Highlighter();
this.magBalls = new MagBalls();
this.highlighter.setSize(BALL_SIZE);
this.ghostEdges = {};
}
var MAG_BALLS_DATA_NAME = "magBalls";
getMagBallsData = function(id) {
return getEntityCustomData(MAG_BALLS_DATA_NAME, id, {});
}
setMagBallsData = function(id, value) {
setEntityCustomData(MAG_BALLS_DATA_NAME, id, value);
}
//var magBalls = new MagBalls();
// DEBUGGING ONLY - Clear any previous balls
// magBalls.clear();
OmniToolModules.MagBallsController.prototype.onClick = function() {
logDebug("MagBallsController onClick: " + vec3toStr(this.tipPosition));
this.selected = this.highlighter.hightlighted;
logDebug("This selected: " + this.selected);
if (!this.selected) {
this.selected = this.magBalls.createBall(this.tipPosition);
}
this.magBalls.selectBall(this.selected);
this.highlighter.highlight(null);
logDebug("Selected " + this.selected);
}
OmniToolModules.MagBallsController.prototype.onRelease = function() {
logDebug("MagBallsController onRelease: " + vec3toStr(this.tipPosition));
this.clearGhostEdges();
if (this.selected) {
this.magBalls.releaseBall(this.selected);
this.selected = null;
}
}
OmniToolModules.MagBallsController.prototype.onUpdate = function(deltaTime) {
this.tipPosition = this.omniTool.getPosition();
if (!this.selected) {
// Find the highlight target and set it.
var target = this.magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS);
this.highlighter.highlight(target);
if (!target) {
this.magBalls.onUpdate(deltaTime);
}
return;
}
this.highlighter.highlight(null);
Entities.editEntity(this.selected, { position: this.tipPosition });
var targetBalls = this.magBalls.findPotentialEdges(this.selected);
for (var ballId in targetBalls) {
if (!this.ghostEdges[ballId]) {
// create the ovleray
this.ghostEdges[ballId] = Overlays.addOverlay("line3d", {
start: this.magBalls.getNodePosition(ballId),
end: this.tipPosition,
color: COLORS.RED,
alpha: 1,
lineWidth: 5,
visible: true,
});
} else {
Overlays.editOverlay(this.ghostEdges[ballId], {
end: this.tipPosition,
});
}
}
for (var ballId in this.ghostEdges) {
if (!targetBalls[ballId]) {
Overlays.deleteOverlay(this.ghostEdges[ballId]);
delete this.ghostEdges[ballId];
}
}
}
OmniToolModules.MagBallsController.prototype.clearGhostEdges = function() {
for(var ballId in this.ghostEdges) {
Overlays.deleteOverlay(this.ghostEdges[ballId]);
delete this.ghostEdges[ballId];
}
}
BallController.prototype.onUnload = function() {
this.clearGhostEdges();
}

View file

@ -1,103 +0,0 @@
Script.include("handController.js");
Script.include("highlighter.js");
BallController = function(side, magBalls) {
HandController.call(this, side);
this.magBalls = magBalls;
this.highlighter = new Highlighter();
this.highlighter.setSize(BALL_SIZE);
this.ghostEdges = {};
}
BallController.prototype = Object.create( HandController.prototype );
BallController.prototype.onUpdate = function(deltaTime) {
HandController.prototype.onUpdate.call(this, deltaTime);
if (!this.selected) {
// Find the highlight target and set it.
var target = this.magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS);
this.highlighter.highlight(target);
return;
}
this.highlighter.highlight(null);
Entities.editEntity(this.selected, { position: this.tipPosition });
var targetBalls = this.magBalls.findPotentialEdges(this.selected);
for (var ballId in targetBalls) {
if (!this.ghostEdges[ballId]) {
// create the ovleray
this.ghostEdges[ballId] = Overlays.addOverlay("line3d", {
start: this.magBalls.getNodePosition(ballId),
end: this.tipPosition,
color: COLORS.RED,
alpha: 1,
lineWidth: 5,
visible: true,
});
} else {
Overlays.editOverlay(this.ghostEdges[ballId], {
end: this.tipPosition,
});
}
}
for (var ballId in this.ghostEdges) {
if (!targetBalls[ballId]) {
Overlays.deleteOverlay(this.ghostEdges[ballId]);
delete this.ghostEdges[ballId];
}
}
}
BallController.prototype.onClick = function() {
this.selected = this.magBalls.grabBall(this.tipPosition, BALL_SELECTION_RADIUS);
this.highlighter.highlight(null);
}
BallController.prototype.onRelease = function() {
this.clearGhostEdges();
this.magBalls.releaseBall(this.selected);
this.selected = null;
}
BallController.prototype.clearGhostEdges = function() {
for(var ballId in this.ghostEdges) {
Overlays.deleteOverlay(this.ghostEdges[ballId]);
delete this.ghostEdges[ballId];
}
}
BallController.prototype.onCleanup = function() {
HandController.prototype.onCleanup.call(this);
this.clearGhostEdges();
}
BallController.prototype.onAltClick = function() {
return;
var target = this.magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS);
if (!target) {
logDebug(target);
return;
}
// FIXME move to delete shape
var toDelete = {};
var deleteQueue = [ target ];
while (deleteQueue.length) {
var curNode = deleteQueue.shift();
if (toDelete[curNode]) {
continue;
}
toDelete[curNode] = true;
for (var nodeId in this.magBalls.getConnectedNodes(curNode)) {
deleteQueue.push(nodeId);
}
}
for (var nodeId in toDelete) {
this.magBalls.destroyNode(nodeId);
}
}
BallController.prototype.onAltRelease = function() {
}

View file

@ -1,13 +1,3 @@
//
// Created by Bradley Austin Davis on 2015/08/27
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
STICK_URL = HIFI_PUBLIC_BUCKET + "models/props/geo_stick.fbx";
// FIXME make this editable through some script UI, so the user can customize the size of the structure built // FIXME make this editable through some script UI, so the user can customize the size of the structure built
SCALE = 0.5; SCALE = 0.5;
@ -16,60 +6,9 @@ STICK_LENGTH = 0.24 * SCALE;
DEBUG_MAGSTICKS = true; DEBUG_MAGSTICKS = true;
CUSTOM_DATA_NAME = "magBalls";
BALL_NAME = "MagBall"; BALL_NAME = "MagBall";
EDGE_NAME = "MagStick"; EDGE_NAME = "MagStick";
ZERO_VECTOR = { x: 0, y: 0, z: 0 };
COLORS = {
WHITE: {
red: 255,
green: 255,
blue: 255,
},
BLACK: {
red: 0,
green: 0,
blue: 0,
},
GREY: {
red: 128,
green: 128,
blue: 128,
},
RED: {
red: 255,
green: 0,
blue: 0
},
BLUE: {
red: 0,
green: 0,
blue: 255
},
GREEN: {
red: 0,
green: 255,
blue: 0
},
CYAN: {
red: 0,
green: 255,
blue: 255
},
YELLOW: {
red: 255,
green: 255,
blue: 0
},
MAGENTA: {
red: 255,
green: 0,
blue: 255
}
}
BALL_RADIUS = BALL_SIZE / 2.0; BALL_RADIUS = BALL_SIZE / 2.0;
BALL_SELECTION_RADIUS = BALL_RADIUS * 1.5; BALL_SELECTION_RADIUS = BALL_RADIUS * 1.5;
@ -136,5 +75,3 @@ EDGE_PROTOTYPE = LINE_PROTOTYPE;
// ignoreCollisions: true, // ignoreCollisions: true,
// collisionsWillMove: false // collisionsWillMove: false
// } // }

View file

@ -101,6 +101,10 @@ Graph.prototype.getConnectedNodes = function(nodeId) {
return result; return result;
} }
Graph.prototype.getNodesForEdge = function(edgeId) {
return Object.keys(this.edges[edgeId]);
}
Graph.prototype.getEdgeLength = function(edgeId) { Graph.prototype.getEdgeLength = function(edgeId) {
var nodesInEdge = Object.keys(this.edges[edgeId]); var nodesInEdge = Object.keys(this.edges[edgeId]);
return this.getNodeDistance(nodesInEdge[0], nodesInEdge[1]); return this.getNodeDistance(nodesInEdge[0], nodesInEdge[1]);

View file

@ -1,127 +0,0 @@
//
// Created by Bradley Austin Davis on 2015/08/29
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
LEFT_CONTROLLER = 0;
RIGHT_CONTROLLER = 1;
// FIXME add a customizable wand model and a mechanism to switch between wands
HandController = function(side) {
this.side = side;
this.palm = 2 * side;
this.tip = 2 * side + 1;
this.action = findAction(side ? "ACTION2" : "ACTION1");
this.altAction = findAction(side ? "ACTION1" : "ACTION2");
this.active = false;
this.tipScale = 1.4;
this.pointer = Overlays.addOverlay("sphere", {
position: ZERO_VECTOR,
size: 0.01,
color: COLORS.YELLOW,
alpha: 1.0,
solid: true,
visible: false,
});
// Connect to desired events
var _this = this;
Controller.actionEvent.connect(function(action, state) {
_this.onActionEvent(action, state);
});
Script.update.connect(function(deltaTime) {
_this.onUpdate(deltaTime);
});
Script.scriptEnding.connect(function() {
_this.onCleanup();
});
}
HandController.prototype.onActionEvent = function(action, state) {
var spatialControlCount = Controller.getNumberOfSpatialControls();
// If only 2 spacial controls, then we only have one controller active, so use either button
// otherwise, only use the specified action
if (action == this.action) {
if (state) {
this.onClick();
} else {
this.onRelease();
}
}
if (action == this.altAction) {
if (state) {
this.onAltClick();
} else {
this.onAltRelease();
}
}
}
HandController.prototype.setActive = function(active) {
if (active == this.active) {
return;
}
logDebug("Hand controller changing active state: " + active);
this.active = active;
Overlays.editOverlay(this.pointer, {
visible: this.active
});
Entities.editEntity(this.wand, {
visible: this.active
});
}
HandController.prototype.updateControllerState = function() {
// FIXME this returns data if either the left or right controller is not on the base
this.palmPos = Controller.getSpatialControlPosition(this.palm);
var tipPos = Controller.getSpatialControlPosition(this.tip);
this.tipPosition = scaleLine(this.palmPos, tipPos, this.tipScale);
// When on the base, hydras report a position of 0
this.setActive(Vec3.length(this.palmPos) > 0.001);
//logDebug(Controller.getTriggerValue(0) + " " + Controller.getTriggerValue(1));
//if (this.active) {
// logDebug("#ctrls " + Controller.getNumberOfSpatialControls() + " Side: " + this.side + " Palm: " + this.palm + " " + vec3toStr(this.palmPos))
//}
}
HandController.prototype.onCleanup = function() {
Overlays.deleteOverlay(this.pointer);
}
HandController.prototype.onUpdate = function(deltaTime) {
this.updateControllerState();
if (this.active) {
Overlays.editOverlay(this.pointer, {
position: this.tipPosition
});
Entities.editEntity(this.wand, {
position: this.tipPosition
});
}
}
HandController.prototype.onClick = function() {
logDebug("Base hand controller does nothing on click");
}
HandController.prototype.onRelease = function() {
logDebug("Base hand controller does nothing on release");
}
HandController.prototype.onAltClick = function() {
logDebug("Base hand controller does nothing on alt click");
}
HandController.prototype.onAltRelease = function() {
logDebug("Base hand controller does nothing on alt click");
}

View file

@ -8,30 +8,34 @@
var UPDATE_INTERVAL = 0.1; var UPDATE_INTERVAL = 0.1;
Script.include("graph.js");
Script.include("edgeSpring.js");
// A collection of balls and edges connecting them. // A collection of balls and edges connecting them.
MagBalls = function() { MagBalls = function() {
Graph.call(this); Graph.call(this);
this.MAX_ADJUST_ITERATIONS = 100; this.MAX_ADJUST_ITERATIONS = 100;
this.REFRESH_WAIT_TICKS = 10;
this.MAX_VARIANCE = 0.25;
this.lastUpdateAge = 0; this.lastUpdateAge = 0;
this.stable = false; this.stable = true;
this.adjustIterations = 0; this.adjustIterations = 0;
this.selectedNodes = {}; this.selectedNodes = {};
this.edgeObjects = {}; this.edgeObjects = {};
this.unstableEdges = {};
this.refresh(); this.refresh();
var _this = this; var _this = this;
Script.update.connect(function(deltaTime) { //Script.update.connect(function(deltaTime) {
_this.onUpdate(deltaTime); // _this.onUpdate(deltaTime);
}); //});
Script.scriptEnding.connect(function() { Script.scriptEnding.connect(function() {
_this.onCleanup(); _this.onCleanup();
}); });
Entities.addingEntity.connect(function(entityId) {
_this.onEntityAdded(entityId);
});
} }
MagBalls.prototype = Object.create( Graph.prototype ); MagBalls.prototype = Object.create( Graph.prototype );
@ -40,14 +44,23 @@ MagBalls.prototype.onUpdate = function(deltaTime) {
this.lastUpdateAge += deltaTime; this.lastUpdateAge += deltaTime;
if (this.lastUpdateAge > UPDATE_INTERVAL) { if (this.lastUpdateAge > UPDATE_INTERVAL) {
this.lastUpdateAge = 0; this.lastUpdateAge = 0;
if (!this.stable) { if (this.refreshNeeded) {
if (++this.refreshNeeded > this.REFRESH_WAIT_TICKS) {
logDebug("Refreshing");
this.refresh();
this.refreshNeeded = 0;
}
}
if (!this.stable && !Object.keys(this.selectedNodes).length) {
this.adjustIterations += 1; this.adjustIterations += 1;
// logDebug("Update");
var adjusted = false; var adjusted = false;
var nodeAdjustResults = {}; var nodeAdjustResults = {};
var fixupEdges = {}; var fixupEdges = {};
for(var edgeId in this.edges) { for(var edgeId in this.edges) {
if (!this.unstableEdges[edgeId]) {
continue;
}
adjusted |= this.edgeObjects[edgeId].adjust(nodeAdjustResults); adjusted |= this.edgeObjects[edgeId].adjust(nodeAdjustResults);
} }
for (var nodeId in nodeAdjustResults) { for (var nodeId in nodeAdjustResults) {
@ -72,8 +85,12 @@ MagBalls.prototype.onUpdate = function(deltaTime) {
}, ((UPDATE_INTERVAL * 1000) / 2)); }, ((UPDATE_INTERVAL * 1000) / 2));
if (!adjusted || this.adjustIterations > this.MAX_ADJUST_ITERATIONS) { if (!adjusted || this.adjustIterations > this.MAX_ADJUST_ITERATIONS) {
if (adjusted) {
logDebug("Could not stabilized after " + this.MAX_ADJUST_ITERATIONS + " abandoning");
}
this.adjustIterations = 0; this.adjustIterations = 0;
this.stable = true; this.stable = true;
this.unstableEdges = {};
} }
} }
} }
@ -118,7 +135,7 @@ MagBalls.prototype.findPotentialEdges = function(nodeId) {
// Check distance to attempt // Check distance to attempt
var distance = this.getNodeDistance(nodeId, otherNodeId); var distance = this.getNodeDistance(nodeId, otherNodeId);
var variance = this.getVariance(distance); var variance = this.getVariance(distance);
if (Math.abs(variance) > 0.25) { if (Math.abs(variance) > this.MAX_VARIANCE) {
continue; continue;
} }
@ -127,26 +144,38 @@ MagBalls.prototype.findPotentialEdges = function(nodeId) {
return variances; return variances;
} }
MagBalls.prototype.grabBall = function(position, maxDist) { MagBalls.prototype.breakEdges = function(nodeId) {
var selected = this.findNearestNode(position, maxDist); //var unstableNodes = this.findShape(Object.keys.target);
if (!selected) { //for (var node in unstableNodes) {
selected = this.createNode({ position: position }); // this.unstableNodes[node] = true;
} //}
if (selected) { Graph.prototype.breakEdges.call(this, nodeId);
this.stable = true;
this.breakEdges(selected);
this.selectedNodes[selected] = true;
}
return selected;
} }
MagBalls.prototype.createBall = function(position) {
var created = this.createNode({ position: position });
this.selectBall(created);
return created;
}
MagBalls.prototype.selectBall = function(selected) {
if (!selected) {
return;
}
// stop updating shapes while manipulating
this.stable = true;
this.selectedNodes[selected] = true;
this.breakEdges(selected);
}
MagBalls.prototype.releaseBall = function(releasedBall) { MagBalls.prototype.releaseBall = function(releasedBall) {
delete this.selectedNodes[releasedBall]; delete this.selectedNodes[releasedBall];
logDebug("Released ball: " + releasedBall); logDebug("Released ball: " + releasedBall);
var releasePosition = this.getNodePosition(releasedBall);
this.stable = false; this.stable = false;
var releasePosition = this.getNodePosition(releasedBall);
// iterate through the other balls and ensure we don't intersect with // iterate through the other balls and ensure we don't intersect with
// any of them. If we do, just delete this ball and return. // any of them. If we do, just delete this ball and return.
@ -169,10 +198,34 @@ MagBalls.prototype.releaseBall = function(releasedBall) {
for (var otherBallId in targets) { for (var otherBallId in targets) {
this.createEdge(otherBallId, releasedBall); this.createEdge(otherBallId, releasedBall);
} }
var unstableNodes = this.findShape(releasedBall);
for (var nodeId in unstableNodes) {
for (var edgeId in this.nodes[nodeId]) {
this.unstableEdges[edgeId] = true;
}
}
this.validate(); this.validate();
} }
MagBalls.prototype.findShape = function(nodeId) {
var result = {};
var queue = [ nodeId ];
while (queue.length) {
var curNode = queue.shift();
if (result[curNode]) {
continue;
}
result[curNode] = true;
for (var otherNodeId in this.getConnectedNodes(curNode)) {
queue.push(otherNodeId);
}
}
return result;
}
MagBalls.prototype.getVariance = function(distance) { MagBalls.prototype.getVariance = function(distance) {
// FIXME different balls or edges might have different ideas of variance... // FIXME different balls or edges might have different ideas of variance...
// let something else handle this // let something else handle this
@ -263,8 +316,11 @@ MagBalls.prototype.refresh = function() {
Script.setTimeout(function() { Script.setTimeout(function() {
for (var i in deleteEdges) { for (var i in deleteEdges) {
var edgeId = deleteEdges[i]; var edgeId = deleteEdges[i];
logDebug("deleting invalid edge " + edgeId); //logDebug("deleting invalid edge " + edgeId);
Entities.deleteEntity(edgeId); //Entities.deleteEntity(edgeId);
Entities.editEntity(edgeId, {
color: COLORS.RED
})
} }
}, 1000); }, 1000);
} }
@ -291,3 +347,14 @@ MagBalls.prototype.fixupEdge = function(edgeId) {
Entities.editEntity(edgeId, this.findEdgeParams(ballsInEdge[0], ballsInEdge[1])); Entities.editEntity(edgeId, this.findEdgeParams(ballsInEdge[0], ballsInEdge[1]));
} }
MagBalls.prototype.onEntityAdded = function(entityId) {
// We already have it
if (this.nodes[entityId] || this.edges[entityId]) {
return;
}
var properties = Entities.getEntityProperties(entityId);
if (properties.name == BALL_NAME || properties.name == EDGE_NAME) {
this.refreshNeeded = 1;
}
}

View file

@ -1,25 +0,0 @@
//
// Created by Bradley Austin Davis on 2015/08/25
// Copyright 2015 High Fidelity, Inc.
//
// 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("constants.js");
Script.include("utils.js");
Script.include("magBalls.js");
Script.include("ballController.js");
var magBalls = new MagBalls();
// Clear any previous balls
// magBalls.clear();
MenuController = function(side) {
HandController.call(this, side);
}
// FIXME resolve some of the issues with dual controllers before allowing both controllers active
var handControllers = [new BallController(LEFT_CONTROLLER, magBalls)]; //, new HandController(RIGHT) ];

View file

@ -1,66 +0,0 @@
Script.include("handController.js");
MenuController = function(side, magBalls) {
HandController.call(this, side);
}
MenuController.prototype = Object.create( HandController.prototype );
MenuController.prototype.onUpdate = function(deltaTime) {
HandController.prototype.onUpdate.call(this, deltaTime);
if (!this.selected) {
// Find the highlight target and set it.
var target = this.magBalls.findNearestNode(this.tipPosition, BALL_SELECTION_RADIUS);
this.highlighter.highlight(target);
return;
}
this.highlighter.highlight(null);
Entities.editEntity(this.selected, { position: this.tipPosition });
var targetBalls = this.magBalls.findPotentialEdges(this.selected);
for (var ballId in targetBalls) {
if (!this.ghostEdges[ballId]) {
// create the ovleray
this.ghostEdges[ballId] = Overlays.addOverlay("line3d", {
start: this.magBalls.getNodePosition(ballId),
end: this.tipPosition,
color: COLORS.RED,
alpha: 1,
lineWidth: 5,
visible: true,
});
} else {
Overlays.editOverlay(this.ghostEdges[ballId], {
end: this.tipPosition,
});
}
}
for (var ballId in this.ghostEdges) {
if (!targetBalls[ballId]) {
Overlays.deleteOverlay(this.ghostEdges[ballId]);
delete this.ghostEdges[ballId];
}
}
}
MenuController.prototype.onClick = function() {
this.selected = this.magBalls.grabBall(this.tipPosition, BALL_SELECTION_RADIUS);
this.highlighter.highlight(null);
}
MenuController.prototype.onRelease = function() {
this.clearGhostEdges();
this.magBalls.releaseBall(this.selected);
this.selected = null;
}
MenuController.prototype.clearGhostEdges = function() {
for(var ballId in this.ghostEdges) {
Overlays.deleteOverlay(this.ghostEdges[ballId]);
delete this.ghostEdges[ballId];
}
}
MenuController.prototype.onCleanup = function() {
HandController.prototype.onCleanup.call(this);
this.clearGhostEdges();
}

View file

@ -64,6 +64,7 @@
#include <EntityScriptingInterface.h> #include <EntityScriptingInterface.h>
#include <ErrorDialog.h> #include <ErrorDialog.h>
#include <Finally.h>
#include <FramebufferCache.h> #include <FramebufferCache.h>
#include <gpu/Batch.h> #include <gpu/Batch.h>
#include <gpu/Context.h> #include <gpu/Context.h>
@ -1010,6 +1011,16 @@ void Application::paintGL() {
if (nullptr == _displayPlugin) { if (nullptr == _displayPlugin) {
return; return;
} }
// Some plugins process message events, potentially leading to
// re-entering a paint event. don't allow further processing if this
// happens
if (_inPaint) {
return;
}
_inPaint = true;
Finally clearFlagLambda([this] { _inPaint = false; });
auto displayPlugin = getActiveDisplayPlugin(); auto displayPlugin = getActiveDisplayPlugin();
displayPlugin->preRender(); displayPlugin->preRender();
_offscreenContext->makeCurrent(); _offscreenContext->makeCurrent();
@ -1204,7 +1215,7 @@ void Application::paintGL() {
// Ensure all operations from the previous context are complete before we try to read the fbo // Ensure all operations from the previous context are complete before we try to read the fbo
glWaitSync(sync, 0, GL_TIMEOUT_IGNORED); glWaitSync(sync, 0, GL_TIMEOUT_IGNORED);
glDeleteSync(sync); glDeleteSync(sync);
{ {
PROFILE_RANGE(__FUNCTION__ "/pluginDisplay"); PROFILE_RANGE(__FUNCTION__ "/pluginDisplay");
displayPlugin->display(finalTexture, toGlm(size)); displayPlugin->display(finalTexture, toGlm(size));
@ -1220,7 +1231,6 @@ void Application::paintGL() {
_frameCount++; _frameCount++;
Stats::getInstance()->setRenderDetails(renderArgs._details); Stats::getInstance()->setRenderDetails(renderArgs._details);
// Reset the gpu::Context Stages // Reset the gpu::Context Stages
// Back to the default framebuffer; // Back to the default framebuffer;
gpu::Batch batch; gpu::Batch batch;
@ -4712,53 +4722,68 @@ void Application::updateDisplayMode() {
auto offscreenUi = DependencyManager::get<OffscreenUi>(); auto offscreenUi = DependencyManager::get<OffscreenUi>();
DisplayPluginPointer oldDisplayPlugin = _displayPlugin; DisplayPluginPointer oldDisplayPlugin = _displayPlugin;
if (oldDisplayPlugin != newDisplayPlugin) { if (newDisplayPlugin == oldDisplayPlugin) {
if (!_currentDisplayPluginActions.isEmpty()) { return;
auto menu = Menu::getInstance();
foreach(auto itemInfo, _currentDisplayPluginActions) {
menu->removeMenuItem(itemInfo.first, itemInfo.second);
}
_currentDisplayPluginActions.clear();
}
if (newDisplayPlugin) {
_offscreenContext->makeCurrent();
_activatingDisplayPlugin = true;
newDisplayPlugin->activate();
_activatingDisplayPlugin = false;
_offscreenContext->makeCurrent();
offscreenUi->resize(fromGlm(newDisplayPlugin->getRecommendedUiSize()));
_offscreenContext->makeCurrent();
}
oldDisplayPlugin = _displayPlugin;
_displayPlugin = newDisplayPlugin;
// If the displayPlugin is a screen based HMD, then it will want the HMDTools displayed
// Direct Mode HMDs (like windows Oculus) will be isHmd() but will have a screen of -1
bool newPluginWantsHMDTools = newDisplayPlugin ?
(newDisplayPlugin->isHmd() && (newDisplayPlugin->getHmdScreen() >= 0)) : false;
bool oldPluginWantedHMDTools = oldDisplayPlugin ?
(oldDisplayPlugin->isHmd() && (oldDisplayPlugin->getHmdScreen() >= 0)) : false;
// Only show the hmd tools after the correct plugin has
// been activated so that it's UI is setup correctly
if (newPluginWantsHMDTools) {
_pluginContainer->showDisplayPluginsTools();
}
if (oldDisplayPlugin) {
oldDisplayPlugin->deactivate();
_offscreenContext->makeCurrent();
// if the old plugin was HMD and the new plugin is not HMD, then hide our hmdtools
if (oldPluginWantedHMDTools && !newPluginWantsHMDTools) {
DependencyManager::get<DialogsManager>()->hmdTools(false);
}
}
emit activeDisplayPluginChanged();
resetSensors();
} }
// Some plugins *cough* Oculus *cough* process message events from inside their
// display function, and we don't want to change the display plugin underneath
// the paintGL call, so we need to guard against that
if (_inPaint) {
qDebug() << "Deferring plugin switch until out of painting";
// Have the old plugin stop requesting renders
oldDisplayPlugin->stop();
QCoreApplication::postEvent(this, new LambdaEvent([this] {
updateDisplayMode();
}));
return;
}
if (!_currentDisplayPluginActions.isEmpty()) {
auto menu = Menu::getInstance();
foreach(auto itemInfo, _currentDisplayPluginActions) {
menu->removeMenuItem(itemInfo.first, itemInfo.second);
}
_currentDisplayPluginActions.clear();
}
if (newDisplayPlugin) {
_offscreenContext->makeCurrent();
_activatingDisplayPlugin = true;
newDisplayPlugin->activate();
_activatingDisplayPlugin = false;
_offscreenContext->makeCurrent();
offscreenUi->resize(fromGlm(newDisplayPlugin->getRecommendedUiSize()));
_offscreenContext->makeCurrent();
}
oldDisplayPlugin = _displayPlugin;
_displayPlugin = newDisplayPlugin;
// If the displayPlugin is a screen based HMD, then it will want the HMDTools displayed
// Direct Mode HMDs (like windows Oculus) will be isHmd() but will have a screen of -1
bool newPluginWantsHMDTools = newDisplayPlugin ?
(newDisplayPlugin->isHmd() && (newDisplayPlugin->getHmdScreen() >= 0)) : false;
bool oldPluginWantedHMDTools = oldDisplayPlugin ?
(oldDisplayPlugin->isHmd() && (oldDisplayPlugin->getHmdScreen() >= 0)) : false;
// Only show the hmd tools after the correct plugin has
// been activated so that it's UI is setup correctly
if (newPluginWantsHMDTools) {
_pluginContainer->showDisplayPluginsTools();
}
if (oldDisplayPlugin) {
oldDisplayPlugin->deactivate();
_offscreenContext->makeCurrent();
// if the old plugin was HMD and the new plugin is not HMD, then hide our hmdtools
if (oldPluginWantedHMDTools && !newPluginWantsHMDTools) {
DependencyManager::get<DialogsManager>()->hmdTools(false);
}
}
emit activeDisplayPluginChanged();
resetSensors();
Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin"); Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin");
} }
@ -4928,7 +4953,7 @@ void Application::setPalmData(Hand* hand, UserInputMapper::PoseValue pose, float
// Store the one fingertip in the palm structure so we can track velocity // Store the one fingertip in the palm structure so we can track velocity
const float FINGER_LENGTH = 0.3f; // meters const float FINGER_LENGTH = 0.3f; // meters
const glm::vec3 FINGER_VECTOR(0.0f, 0.0f, FINGER_LENGTH); const glm::vec3 FINGER_VECTOR(0.0f, FINGER_LENGTH, 0.0f);
const glm::vec3 newTipPosition = position + rotation * FINGER_VECTOR; const glm::vec3 newTipPosition = position + rotation * FINGER_VECTOR;
glm::vec3 oldTipPosition = palm->getTipRawPosition(); glm::vec3 oldTipPosition = palm->getTipRawPosition();
if (deltaTime > 0.0f) { if (deltaTime > 0.0f) {
@ -5061,3 +5086,15 @@ void Application::crashApplication() {
qCDebug(interfaceapp) << "Intentionally crashed Interface"; qCDebug(interfaceapp) << "Intentionally crashed Interface";
} }
void Application::setActiveDisplayPlugin(const QString& pluginName) {
auto menu = Menu::getInstance();
foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getDisplayPlugins()) {
QString name = displayPlugin->getName();
QAction* action = menu->getActionForOption(name);
if (pluginName == name) {
action->setChecked(true);
}
}
updateDisplayMode();
}

View file

@ -683,6 +683,7 @@ private:
int _simsPerSecondReport = 0; int _simsPerSecondReport = 0;
quint64 _lastSimsPerSecondUpdate = 0; quint64 _lastSimsPerSecondUpdate = 0;
bool _isForeground = true; // starts out assumed to be in foreground bool _isForeground = true; // starts out assumed to be in foreground
bool _inPaint = false;
friend class PluginContainerProxy; friend class PluginContainerProxy;
}; };

View file

@ -10,16 +10,39 @@
// //
#include "FileLogger.h" #include "FileLogger.h"
#include "HifiSockAddr.h"
#include <FileUtils.h>
#include <QDateTime>
#include <QFile>
#include <QDir>
#include <QDesktopServices>
const QString FILENAME_FORMAT = "hifi-log_%1_%2.txt"; #include <QtCore/QDateTime>
const QString DATETIME_FORMAT = "yyyy-MM-dd_hh.mm.ss"; #include <QtCore/QFile>
const QString LOGS_DIRECTORY = "Logs"; #include <QtCore/QDir>
#include <QtGui/QDesktopServices>
#include <NumericalConstants.h>
#include <FileUtils.h>
#include <SharedUtil.h>
#include "HifiSockAddr.h"
static const QString FILENAME_FORMAT = "hifi-log_%1_%2.txt";
static const QString DATETIME_FORMAT = "yyyy-MM-dd_hh.mm.ss";
static const QString LOGS_DIRECTORY = "Logs";
// Max log size is 1 MB
static const uint64_t MAX_LOG_SIZE = 1024 * 1024;
// Max log age is 1 hour
static const uint64_t MAX_LOG_AGE_USECS = USECS_PER_SECOND * 3600;
QString getLogRollerFilename() {
QString result = FileUtils::standardPath(LOGS_DIRECTORY);
QHostAddress clientAddress = getLocalAddress();
QDateTime now = QDateTime::currentDateTime();
result.append(QString(FILENAME_FORMAT).arg(clientAddress.toString(), now.toString(DATETIME_FORMAT)));
return result;
}
const QString& getLogFilename() {
static QString fileName = FileUtils::standardPath(LOGS_DIRECTORY) + "hifi-log.txt";
return fileName;
}
class FilePersistThread : public GenericQueueThread < QString > { class FilePersistThread : public GenericQueueThread < QString > {
public: public:
@ -28,8 +51,22 @@ public:
} }
protected: protected:
void rollFileIfNecessary(QFile& file) {
uint64_t now = usecTimestampNow();
if ((file.size() > MAX_LOG_SIZE) || (now - _lastRollTime) > MAX_LOG_AGE_USECS) {
QString newFileName = getLogRollerFilename();
if (file.copy(newFileName)) {
_lastRollTime = now;
file.open(QIODevice::WriteOnly | QIODevice::Truncate);
file.close();
qDebug() << "Rolled log file: " << newFileName;
}
}
}
virtual bool processQueueItems(const Queue& messages) { virtual bool processQueueItems(const Queue& messages) {
QFile file(_logger._fileName); QFile file(_logger._fileName);
rollFileIfNecessary(file);
if (file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) { if (file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
QTextStream out(&file); QTextStream out(&file);
foreach(const QString& message, messages) { foreach(const QString& message, messages) {
@ -40,20 +77,17 @@ protected:
} }
private: private:
const FileLogger& _logger; const FileLogger& _logger;
uint64_t _lastRollTime = 0;
}; };
static FilePersistThread* _persistThreadInstance; static FilePersistThread* _persistThreadInstance;
FileLogger::FileLogger(QObject* parent) : FileLogger::FileLogger(QObject* parent) :
AbstractLoggerInterface(parent) AbstractLoggerInterface(parent), _fileName(getLogFilename())
{ {
_persistThreadInstance = new FilePersistThread(*this); _persistThreadInstance = new FilePersistThread(*this);
_persistThreadInstance->initialize(true, QThread::LowestPriority); _persistThreadInstance->initialize(true, QThread::LowestPriority);
_fileName = FileUtils::standardPath(LOGS_DIRECTORY);
QHostAddress clientAddress = getLocalAddress();
QDateTime now = QDateTime::currentDateTime();
_fileName.append(QString(FILENAME_FORMAT).arg(clientAddress.toString(), now.toString(DATETIME_FORMAT)));
} }
FileLogger::~FileLogger() { FileLogger::~FileLogger() {

View file

@ -27,7 +27,7 @@ public:
virtual void locateLog() override; virtual void locateLog() override;
private: private:
QString _fileName; const QString _fileName;
friend class FilePersistThread; friend class FilePersistThread;
}; };

View file

@ -147,15 +147,3 @@ void PluginContainerProxy::showDisplayPluginsTools() {
QGLWidget* PluginContainerProxy::getPrimarySurface() { QGLWidget* PluginContainerProxy::getPrimarySurface() {
return qApp->_glWidget; return qApp->_glWidget;
} }
void Application::setActiveDisplayPlugin(const QString& pluginName) {
auto menu = Menu::getInstance();
foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getDisplayPlugins()) {
QString name = displayPlugin->getName();
QAction* action = menu->getActionForOption(name);
if (pluginName == name) {
action->setChecked(true);
}
}
updateDisplayMode();
}

View file

@ -785,8 +785,10 @@ Transform Avatar::calculateDisplayNameTransform(const ViewFrustum& frustum, floa
void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, const glm::ivec4& viewport) const { void Avatar::renderDisplayName(gpu::Batch& batch, const ViewFrustum& frustum, const glm::ivec4& viewport) const {
bool shouldShowReceiveStats = DependencyManager::get<AvatarManager>()->shouldShowReceiveStats() && !isMyAvatar(); bool shouldShowReceiveStats = DependencyManager::get<AvatarManager>()->shouldShowReceiveStats() && !isMyAvatar();
// If we have nothing to draw, or it's tottaly transparent, return // If we have nothing to draw, or it's totally transparent, or it's too close or behind the camera, return
if ((_displayName.isEmpty() && !shouldShowReceiveStats) || _displayNameAlpha == 0.0f) { const float CLIP_DISTANCE = 0.2f;
if ((_displayName.isEmpty() && !shouldShowReceiveStats) || _displayNameAlpha == 0.0f
|| (glm::dot(frustum.getDirection(), getDisplayNamePosition() - frustum.getPosition()) <= CLIP_DISTANCE)) {
return; return;
} }
auto renderer = textRenderer(DISPLAYNAME); auto renderer = textRenderer(DISPLAYNAME);

View file

@ -93,7 +93,7 @@ void SkeletonModel::initJointStates(QVector<JointState> states) {
_rig->updateJointState(i, rootTransform); _rig->updateJointState(i, rootTransform);
} }
buildShapes(); computeBoundingShape();
Extents meshExtents = getMeshExtents(); Extents meshExtents = getMeshExtents();
_headClipDistance = -(meshExtents.minimum.z / _scale.z - _defaultEyeModelPosition.z); _headClipDistance = -(meshExtents.minimum.z / _scale.z - _defaultEyeModelPosition.z);
@ -255,6 +255,7 @@ void SkeletonModel::applyHandPosition(int jointIndex, const glm::vec3& position)
rotationBetween(handRotation * glm::vec3(-sign, 0.0f, 0.0f), forearmVector), rotationBetween(handRotation * glm::vec3(-sign, 0.0f, 0.0f), forearmVector),
true, PALM_PRIORITY); true, PALM_PRIORITY);
} }
void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) { void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) {
if (jointIndex == -1 || jointIndex >= _rig->getJointStateCount()) { if (jointIndex == -1 || jointIndex >= _rig->getJointStateCount()) {
return; return;
@ -268,9 +269,7 @@ void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) {
// the palm's position must be transformed into the model-frame // the palm's position must be transformed into the model-frame
glm::quat inverseRotation = glm::inverse(_rotation); glm::quat inverseRotation = glm::inverse(_rotation);
glm::vec3 palmPosition = inverseRotation * (palm.getPosition() - _translation); glm::vec3 palmPosition = inverseRotation * (palm.getPosition() - _translation);
glm::quat palmRotation = inverseRotation * palm.getRotation();
// the palm's "raw" rotation is already in the model-frame
glm::quat palmRotation = palm.getRawRotation();
inverseKinematics(jointIndex, palmPosition, palmRotation, PALM_PRIORITY); inverseKinematics(jointIndex, palmPosition, palmRotation, PALM_PRIORITY);
} }
@ -353,9 +352,9 @@ void SkeletonModel::renderOrientationDirections(gpu::Batch& batch, int jointInde
} }
OrientationLineIDs& jointLineIDs = _jointOrientationLines[jointIndex]; OrientationLineIDs& jointLineIDs = _jointOrientationLines[jointIndex];
glm::vec3 pRight = position + orientation * IDENTITY_RIGHT * size; glm::vec3 pRight = position + orientation * IDENTITY_RIGHT * size;
glm::vec3 pUp = position + orientation * IDENTITY_UP * size; glm::vec3 pUp = position + orientation * IDENTITY_UP * size;
glm::vec3 pFront = position + orientation * IDENTITY_FRONT * size; glm::vec3 pFront = position + orientation * IDENTITY_FRONT * size;
glm::vec3 red(1.0f, 0.0f, 0.0f); glm::vec3 red(1.0f, 0.0f, 0.0f);
geometryCache->renderLine(batch, position, pRight, red, jointLineIDs._right); geometryCache->renderLine(batch, position, pRight, red, jointLineIDs._right);
@ -473,7 +472,7 @@ float MIN_JOINT_MASS = 1.0f;
float VERY_BIG_MASS = 1.0e6f; float VERY_BIG_MASS = 1.0e6f;
// virtual // virtual
void SkeletonModel::buildShapes() { void SkeletonModel::computeBoundingShape() {
if (_geometry == NULL || _rig->jointStatesEmpty()) { if (_geometry == NULL || _rig->jointStatesEmpty()) {
return; return;
} }
@ -483,36 +482,86 @@ void SkeletonModel::buildShapes() {
// rootJointIndex == -1 if the avatar model has no skeleton // rootJointIndex == -1 if the avatar model has no skeleton
return; return;
} }
computeBoundingShape(geometry);
}
void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) { // BOUNDING SHAPE HACK: before we measure the bounds of the joints we use IK to put the
// compute default joint transforms // hands and feet into positions that are more correct than the default pose.
int numStates = _rig->getJointStateCount();
QVector<glm::mat4> transforms; // Measure limb lengths so we can specify IK targets that will pull hands and feet tight to body
transforms.fill(glm::mat4(), numStates); QVector<QString> endEffectors;
endEffectors.push_back("RightHand");
endEffectors.push_back("LeftHand");
endEffectors.push_back("RightFoot");
endEffectors.push_back("LeftFoot");
QVector<QString> baseJoints;
baseJoints.push_back("RightArm");
baseJoints.push_back("LeftArm");
baseJoints.push_back("RightUpLeg");
baseJoints.push_back("LeftUpLeg");
for (int i = 0; i < endEffectors.size(); ++i) {
QString tipName = endEffectors[i];
QString baseName = baseJoints[i];
float limbLength = 0.0f;
int tipIndex = _rig->indexOfJoint(tipName);
if (tipIndex == -1) {
continue;
}
// save tip's relative rotation for later
glm::quat tipRotation = _rig->getJointState(tipIndex).getRotationInConstrainedFrame();
// IK on each endpoint
int jointIndex = tipIndex;
QVector<int> freeLineage;
float priority = 1.0f;
while (jointIndex > -1) {
JointState limbJoint = _rig->getJointState(jointIndex);
freeLineage.push_back(jointIndex);
if (limbJoint.getName() == baseName) {
glm::vec3 targetPosition = limbJoint.getPosition() - glm::vec3(0.0f, 1.5f * limbLength, 0.0f);
// do IK a few times to make sure the endpoint gets close to its target
for (int j = 0; j < 5; ++j) {
_rig->inverseKinematics(tipIndex,
targetPosition,
glm::quat(),
priority,
freeLineage,
glm::mat4());
}
break;
}
limbLength += limbJoint.getDistanceToParent();
jointIndex = limbJoint.getParentIndex();
}
// since this IK target is totally bogus we restore the tip's relative rotation
_rig->setJointRotationInConstrainedFrame(tipIndex, tipRotation, priority);
}
// recompute all joint model-frame transforms
glm::mat4 rootTransform = glm::scale(_scale) * glm::translate(_offset) * geometry.offset;
for (int i = 0; i < _rig->getJointStateCount(); i++) {
_rig->updateJointState(i, rootTransform);
}
// END BOUNDING SHAPE HACK
// compute bounding box that encloses all shapes // compute bounding box that encloses all shapes
Extents totalExtents; Extents totalExtents;
totalExtents.reset(); totalExtents.reset();
totalExtents.addPoint(glm::vec3(0.0f)); totalExtents.addPoint(glm::vec3(0.0f));
int numStates = _rig->getJointStateCount();
for (int i = 0; i < numStates; i++) { for (int i = 0; i < numStates; i++) {
// compute the default transform of this joint // compute the default transform of this joint
const JointState& state = _rig->getJointState(i); const JointState& state = _rig->getJointState(i);
int parentIndex = state.getParentIndex();
if (parentIndex == -1) {
transforms[i] = _rig->getJointTransform(i);
} else {
glm::quat modifiedRotation = state.getPreRotation() * state.getDefaultRotation() * state.getPostRotation();
transforms[i] = transforms[parentIndex] * glm::translate(state.getTranslation())
* state.getPreTransform() * glm::mat4_cast(modifiedRotation) * state.getPostTransform();
}
// Each joint contributes a sphere at its position // HACK WORKAROUND: ignore joints that may have bad translation (e.g. have been flagged as such with zero radius)
glm::vec3 axis(state.getBoneRadius()); if (state.getBoneRadius() > 0.0f) {
glm::vec3 jointPosition = extractTranslation(transforms[i]); // Each joint contributes a sphere at its position
totalExtents.addPoint(jointPosition + axis); glm::vec3 axis(state.getBoneRadius());
totalExtents.addPoint(jointPosition - axis); glm::vec3 jointPosition = state.getPosition();
totalExtents.addPoint(jointPosition + axis);
totalExtents.addPoint(jointPosition - axis);
}
} }
// compute bounding shape parameters // compute bounding shape parameters
@ -524,6 +573,11 @@ void SkeletonModel::computeBoundingShape(const FBXGeometry& geometry) {
glm::vec3 rootPosition = _rig->getJointState(geometry.rootJointIndex).getPosition(); glm::vec3 rootPosition = _rig->getJointState(geometry.rootJointIndex).getPosition();
_boundingCapsuleLocalOffset = 0.5f * (totalExtents.maximum + totalExtents.minimum) - rootPosition; _boundingCapsuleLocalOffset = 0.5f * (totalExtents.maximum + totalExtents.minimum) - rootPosition;
// RECOVER FROM BOUNINDG SHAPE HACK: now that we're all done, restore the default pose
for (int i = 0; i < numStates; i++) {
_rig->restoreJointRotation(i, 1.0f, 1.0f);
}
} }
void SkeletonModel::renderBoundingCollisionShapes(gpu::Batch& batch, float alpha) { void SkeletonModel::renderBoundingCollisionShapes(gpu::Batch& batch, float alpha) {
@ -542,7 +596,7 @@ void SkeletonModel::renderBoundingCollisionShapes(gpu::Batch& batch, float alpha
glm::vec4(0.6f, 0.6f, 0.8f, alpha)); glm::vec4(0.6f, 0.6f, 0.8f, alpha));
// draw a yellow sphere at the capsule bottom point // draw a yellow sphere at the capsule bottom point
glm::vec3 bottomPoint = topPoint - glm::vec3(0.0f, -_boundingCapsuleHeight, 0.0f); glm::vec3 bottomPoint = topPoint - glm::vec3(0.0f, _boundingCapsuleHeight, 0.0f);
glm::vec3 axis = topPoint - bottomPoint; glm::vec3 axis = topPoint - bottomPoint;
transform.setTranslation(bottomPoint); transform.setTranslation(bottomPoint);
batch.setModelTransform(transform); batch.setModelTransform(transform);

View file

@ -94,7 +94,6 @@ public:
/// \return whether or not the head was found. /// \return whether or not the head was found.
glm::vec3 getDefaultEyeModelPosition() const; glm::vec3 getDefaultEyeModelPosition() const;
void computeBoundingShape(const FBXGeometry& geometry);
void renderBoundingCollisionShapes(gpu::Batch& batch, float alpha); void renderBoundingCollisionShapes(gpu::Batch& batch, float alpha);
float getBoundingCapsuleRadius() const { return _boundingCapsuleRadius; } float getBoundingCapsuleRadius() const { return _boundingCapsuleRadius; }
float getBoundingCapsuleHeight() const { return _boundingCapsuleHeight; } float getBoundingCapsuleHeight() const { return _boundingCapsuleHeight; }
@ -114,7 +113,7 @@ signals:
protected: protected:
void buildShapes(); void computeBoundingShape();
/// \param jointIndex index of joint in model /// \param jointIndex index of joint in model
/// \param position position of joint in model-frame /// \param position position of joint in model-frame

View file

@ -66,16 +66,7 @@ Overlay::~Overlay() {
void Overlay::setProperties(const QScriptValue& properties) { void Overlay::setProperties(const QScriptValue& properties) {
QScriptValue color = properties.property("color"); QScriptValue color = properties.property("color");
if (color.isValid()) { xColorFromScriptValue(properties.property("color"), _color);
QScriptValue red = color.property("red");
QScriptValue green = color.property("green");
QScriptValue blue = color.property("blue");
if (red.isValid() && green.isValid() && blue.isValid()) {
_color.red = red.toVariant().toInt();
_color.green = green.toVariant().toInt();
_color.blue = blue.toVariant().toInt();
}
}
if (properties.property("alpha").isValid()) { if (properties.property("alpha").isValid()) {
setAlpha(properties.property("alpha").toVariant().toFloat()); setAlpha(properties.property("alpha").toVariant().toFloat());

View file

@ -30,6 +30,7 @@
#include "Grid3DOverlay.h" #include "Grid3DOverlay.h"
#include "TextOverlay.h" #include "TextOverlay.h"
#include "Text3DOverlay.h" #include "Text3DOverlay.h"
#include "Web3DOverlay.h"
Overlays::Overlays() : _nextOverlayID(1) { Overlays::Overlays() : _nextOverlayID(1) {
@ -170,6 +171,8 @@ unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& prope
thisOverlay = std::make_shared<LocalModelsOverlay>(Application::getInstance()->getEntityClipboardRenderer()); thisOverlay = std::make_shared<LocalModelsOverlay>(Application::getInstance()->getEntityClipboardRenderer());
} else if (type == ModelOverlay::TYPE) { } else if (type == ModelOverlay::TYPE) {
thisOverlay = std::make_shared<ModelOverlay>(); thisOverlay = std::make_shared<ModelOverlay>();
} else if (type == Web3DOverlay::TYPE) {
thisOverlay = std::make_shared<Web3DOverlay>();
} }
if (thisOverlay) { if (thisOverlay) {

View file

@ -0,0 +1,163 @@
//
// Web3DOverlay.cpp
//
//
// Created by Clement on 7/1/14.
// Modified and renamed by Zander Otavka on 8/4/15
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "Web3DOverlay.h"
#include <QtScript/QScriptValue>
#include <QtGui/QOpenGLContext>
#include <QtQuick/QQuickItem>
#include <DeferredLightingEffect.h>
#include <DependencyManager.h>
#include <GeometryCache.h>
#include <GeometryUtil.h>
#include <TextureCache.h>
#include <PathUtils.h>
#include <gpu/Batch.h>
#include <RegisteredMetaTypes.h>
#include <AbstractViewStateInterface.h>
#include <OffscreenQmlSurface.h>
// #include "Application.h"
// #include "GeometryUtil.h"
static const float DPI = 30.47f;
static const float METERS_TO_INCHES = 39.3701f;
static const float INCHES_TO_METERS = 1.0f / 39.3701f;
QString const Web3DOverlay::TYPE = "web3d";
Web3DOverlay::Web3DOverlay() : _dpi(DPI) { }
Web3DOverlay::Web3DOverlay(const Web3DOverlay* Web3DOverlay) :
Billboard3DOverlay(Web3DOverlay),
_url(Web3DOverlay->_url),
_dpi(Web3DOverlay->_dpi),
_resolution(Web3DOverlay->_resolution)
{
}
Web3DOverlay::~Web3DOverlay() {
if (_webSurface) {
_webSurface->pause();
_webSurface->disconnect(_connection);
// The lifetime of the QML surface MUST be managed by the main thread
// Additionally, we MUST use local variables copied by value, rather than
// member variables, since they would implicitly refer to a this that
// is no longer valid
auto webSurface = _webSurface;
AbstractViewStateInterface::instance()->postLambdaEvent([webSurface] {
webSurface->deleteLater();
});
}
}
void Web3DOverlay::update(float deltatime) {
applyTransformTo(_transform);
}
void Web3DOverlay::render(RenderArgs* args) {
if (!_visible || !getParentVisible()) {
return;
}
QOpenGLContext * currentContext = QOpenGLContext::currentContext();
QSurface * currentSurface = currentContext->surface();
if (!_webSurface) {
_webSurface = new OffscreenQmlSurface();
_webSurface->create(currentContext);
_webSurface->setBaseUrl(QUrl::fromLocalFile(PathUtils::resourcesPath() + "/qml/"));
_webSurface->load("WebEntity.qml");
_webSurface->resume();
_webSurface->getRootItem()->setProperty("url", _url);
_webSurface->resize(QSize(_resolution.x, _resolution.y));
_connection = QObject::connect(_webSurface, &OffscreenQmlSurface::textureUpdated, [&](GLuint textureId) {
_texture = textureId;
});
currentContext->makeCurrent(currentSurface);
}
vec2 size = _resolution / _dpi * INCHES_TO_METERS;
vec2 halfSize = size / 2.0f;
vec4 color(toGlm(getColor()), getAlpha());
applyTransformTo(_transform, true);
Transform transform = _transform;
if (glm::length2(getDimensions()) != 1.0f) {
transform.postScale(vec3(getDimensions(), 1.0f));
}
Q_ASSERT(args->_batch);
gpu::Batch& batch = *args->_batch;
if (_texture) {
batch._glActiveBindTexture(GL_TEXTURE0, GL_TEXTURE_2D, _texture);
} else {
batch.setResourceTexture(0, DependencyManager::get<TextureCache>()->getWhiteTexture());
}
batch.setModelTransform(transform);
DependencyManager::get<DeferredLightingEffect>()->bindSimpleProgram(batch, true, false, false, true);
DependencyManager::get<GeometryCache>()->renderQuad(batch, halfSize * -1.0f, halfSize, vec2(0), vec2(1), color);
batch.setResourceTexture(0, args->_whiteTexture); // restore default white color after me
}
void Web3DOverlay::setProperties(const QScriptValue &properties) {
Billboard3DOverlay::setProperties(properties);
QScriptValue urlValue = properties.property("url");
if (urlValue.isValid()) {
QString newURL = urlValue.toVariant().toString();
if (newURL != _url) {
setURL(newURL);
}
}
QScriptValue resolution = properties.property("resolution");
if (resolution.isValid()) {
vec2FromScriptValue(resolution, _resolution);
}
QScriptValue dpi = properties.property("dpi");
if (dpi.isValid()) {
_dpi = dpi.toVariant().toFloat();
}
}
QScriptValue Web3DOverlay::getProperty(const QString& property) {
if (property == "url") {
return _url;
}
if (property == "dpi") {
return _dpi;
}
return Billboard3DOverlay::getProperty(property);
}
void Web3DOverlay::setURL(const QString& url) {
_url = url;
_isLoaded = false;
}
bool Web3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face) {
//// Make sure position and rotation is updated.
applyTransformTo(_transform, true);
vec2 size = _resolution / _dpi * INCHES_TO_METERS * vec2(getDimensions());
// Produce the dimensions of the overlay based on the image's aspect ratio and the overlay's scale.
return findRayRectangleIntersection(origin, direction, getRotation(), getPosition(), size, distance);
}
Web3DOverlay* Web3DOverlay::createClone() const {
return new Web3DOverlay(this);
}

View file

@ -0,0 +1,50 @@
//
// Created by Bradley Austin Davis on 2015/08/31
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_Web3DOverlay_h
#define hifi_Web3DOverlay_h
#include "Billboard3DOverlay.h"
class OffscreenQmlSurface;
class Web3DOverlay : public Billboard3DOverlay {
Q_OBJECT
public:
static QString const TYPE;
virtual QString getType() const { return TYPE; }
Web3DOverlay();
Web3DOverlay(const Web3DOverlay* Web3DOverlay);
virtual ~Web3DOverlay();
virtual void render(RenderArgs* args);
virtual void update(float deltatime);
// setters
void setURL(const QString& url);
virtual void setProperties(const QScriptValue& properties);
virtual QScriptValue getProperty(const QString& property);
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face);
virtual Web3DOverlay* createClone() const;
private:
OffscreenQmlSurface* _webSurface{ nullptr };
QMetaObject::Connection _connection;
uint32_t _texture{ 0 };
QString _url;
float _dpi;
vec2 _resolution{ 640, 480 };
};
#endif // hifi_Web3DOverlay_h

View file

@ -10,7 +10,7 @@
// //
#include "AnimationHandle.h" #include "AnimationHandle.h"
#include "AnimationLogging.h"
void AnimationHandle::setURL(const QUrl& url) { void AnimationHandle::setURL(const QUrl& url) {
if (_url != url) { if (_url != url) {
@ -51,8 +51,8 @@ void AnimationHandle::setMaskedJoints(const QStringList& maskedJoints) {
} }
void AnimationHandle::setRunning(bool running, bool doRestoreJoints) { void AnimationHandle::setRunning(bool running, bool doRestoreJoints) {
if (running && isRunning()) { if (running && isRunning() && (getFadePerSecond() >= 0.0f)) {
// if we're already running, this is the same as a restart // if we're already running, this is the same as a restart -- unless we're fading out.
setFrameIndex(getFirstFrame()); setFrameIndex(getFirstFrame());
return; return;
} }

View file

@ -9,30 +9,31 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
#include "Rig.h"
#include <glm/gtx/vector_angle.hpp> #include <glm/gtx/vector_angle.hpp>
#include <queue> #include <queue>
#include "AnimationHandle.h" #include "AnimationHandle.h"
#include "AnimationLogging.h" #include "AnimationLogging.h"
#include "Rig.h"
void Rig::HeadParameters::dump() const { void Rig::HeadParameters::dump() const {
qCDebug(animation, "HeadParameters ="); qCDebug(animation, "HeadParameters =");
qCDebug(animation, " leanSideways = %0.5f", leanSideways); qCDebug(animation, " leanSideways = %0.5f", (double)leanSideways);
qCDebug(animation, " leanForward = %0.5f", leanForward); qCDebug(animation, " leanForward = %0.5f", (double)leanForward);
qCDebug(animation, " torsoTwist = %0.5f", torsoTwist); qCDebug(animation, " torsoTwist = %0.5f", (double)torsoTwist);
glm::vec3 axis = glm::axis(localHeadOrientation); glm::vec3 axis = glm::axis(localHeadOrientation);
float theta = glm::angle(localHeadOrientation); float theta = glm::angle(localHeadOrientation);
qCDebug(animation, " localHeadOrientation axis = (%.5f, %.5f, %.5f), theta = %0.5f", axis.x, axis.y, axis.z, theta); qCDebug(animation, " localHeadOrientation axis = (%.5f, %.5f, %.5f), theta = %0.5f", (double)axis.x, (double)axis.y, (double)axis.z, (double)theta);
axis = glm::axis(worldHeadOrientation); axis = glm::axis(worldHeadOrientation);
theta = glm::angle(worldHeadOrientation); theta = glm::angle(worldHeadOrientation);
qCDebug(animation, " worldHeadOrientation axis = (%.5f, %.5f, %.5f), theta = %0.5f", axis.x, axis.y, axis.z, theta); qCDebug(animation, " worldHeadOrientation axis = (%.5f, %.5f, %.5f), theta = %0.5f", (double)axis.x, (double)axis.y, (double)axis.z, (double)theta);
axis = glm::axis(modelRotation); axis = glm::axis(modelRotation);
theta = glm::angle(modelRotation); theta = glm::angle(modelRotation);
qCDebug(animation, " modelRotation axis = (%.5f, %.5f, %.5f), theta = %0.5f", axis.x, axis.y, axis.z, theta); qCDebug(animation, " modelRotation axis = (%.5f, %.5f, %.5f), theta = %0.5f", (double)axis.x, (double)axis.y, (double)axis.z, (double)theta);
qCDebug(animation, " modelTranslation = (%.5f, %.5f, %.5f)", modelTranslation.x, modelTranslation.y, modelTranslation.z); qCDebug(animation, " modelTranslation = (%.5f, %.5f, %.5f)", (double)modelTranslation.x, (double)modelTranslation.y, (double)modelTranslation.z);
qCDebug(animation, " eyeLookAt = (%.5f, %.5f, %.5f)", eyeLookAt.x, eyeLookAt.y, eyeLookAt.z); qCDebug(animation, " eyeLookAt = (%.5f, %.5f, %.5f)", (double)eyeLookAt.x, (double)eyeLookAt.y, (double)eyeLookAt.z);
qCDebug(animation, " eyeSaccade = (%.5f, %.5f, %.5f)", eyeSaccade.x, eyeSaccade.y, eyeSaccade.z); qCDebug(animation, " eyeSaccade = (%.5f, %.5f, %.5f)", (double)eyeSaccade.x, (double)eyeSaccade.y, (double)eyeSaccade.z);
qCDebug(animation, " leanJointIndex = %.d", leanJointIndex); qCDebug(animation, " leanJointIndex = %.d", leanJointIndex);
qCDebug(animation, " neckJointIndex = %.d", neckJointIndex); qCDebug(animation, " neckJointIndex = %.d", neckJointIndex);
qCDebug(animation, " leftEyeJointIndex = %.d", leftEyeJointIndex); qCDebug(animation, " leftEyeJointIndex = %.d", leftEyeJointIndex);
@ -103,7 +104,7 @@ AnimationHandlePointer Rig::addAnimationByRole(const QString& role, const QStrin
const QString& base = "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/"; const QString& base = "https://hifi-public.s3.amazonaws.com/ozan/anim/standard_anims/";
if (role == "walk") { if (role == "walk") {
standard = base + "walk_fwd.fbx"; standard = base + "walk_fwd.fbx";
} else if (role == "backup") { } else if (role == "backup") {
standard = base + "walk_bwd.fbx"; standard = base + "walk_bwd.fbx";
} else if (role == "leftTurn") { } else if (role == "leftTurn") {
standard = base + "turn_left.fbx"; standard = base + "turn_left.fbx";
@ -411,9 +412,30 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos
} }
bool isMoving = false; bool isMoving = false;
glm::vec3 front = worldRotation * IDENTITY_FRONT; glm::vec3 front = worldRotation * IDENTITY_FRONT;
float forwardSpeed = glm::dot(worldVelocity, front); glm::vec3 right = worldRotation * IDENTITY_RIGHT;
float rightLateralSpeed = glm::dot(worldVelocity, worldRotation * IDENTITY_RIGHT); const float PERCEPTIBLE_DELTA = 0.001f;
float rightTurningSpeed = glm::orientedAngle(front, _lastFront, IDENTITY_UP) / deltaTime; const float PERCEPTIBLE_SPEED = 0.1f;
// It can be more accurate/smooth to use velocity rather than position,
// but some modes (e.g., hmd standing) update position without updating velocity.
// It's very hard to debug hmd standing. (Look down at yourself, or have a second person observe. HMD third person is a bit undefined...)
// So, let's create our own workingVelocity from the worldPosition...
glm::vec3 positionDelta = worldPosition - _lastPosition;
glm::vec3 workingVelocity = positionDelta / deltaTime;
// But for smoothest (non-hmd standing) results, go ahead and use velocity:
#if !WANT_DEBUG
// Note: Separately, we've arranged for starting/stopping animations by role (as we've done here) to pick up where they've left off when fading,
// so that you wouldn't notice the start/stop if it happens fast enough (e.g., one frame). But the print below would still be noisy.
if (!positionDelta.x && !positionDelta.y && !positionDelta.z) {
workingVelocity = worldVelocity;
}
#endif
float forwardSpeed = glm::dot(workingVelocity, front);
float rightLateralSpeed = glm::dot(workingVelocity, right);
float rightTurningDelta = glm::orientedAngle(front, _lastFront, IDENTITY_UP);
float rightTurningSpeed = rightTurningDelta / deltaTime;
bool isTurning = (std::abs(rightTurningDelta) > PERCEPTIBLE_DELTA) && (std::abs(rightTurningSpeed) > PERCEPTIBLE_SPEED);
bool isStrafing = std::abs(rightLateralSpeed) > PERCEPTIBLE_SPEED;
auto updateRole = [&](const QString& role, bool isOn) { auto updateRole = [&](const QString& role, bool isOn) {
isMoving = isMoving || isOn; isMoving = isMoving || isOn;
if (isOn) { if (isOn) {
@ -428,14 +450,13 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos
} }
} }
}; };
updateRole("walk", forwardSpeed > 0.01f); updateRole("walk", forwardSpeed > PERCEPTIBLE_SPEED);
updateRole("backup", forwardSpeed < -0.01f); updateRole("backup", forwardSpeed < -PERCEPTIBLE_SPEED);
bool isTurning = std::abs(rightTurningSpeed) > 0.5f; updateRole("rightTurn", isTurning && (rightTurningSpeed > 0.0f));
updateRole("rightTurn", isTurning && (rightTurningSpeed > 0)); updateRole("leftTurn", isTurning && (rightTurningSpeed < 0.0f));
updateRole("leftTurn", isTurning && (rightTurningSpeed < 0)); isStrafing = isStrafing && !isMoving;
bool isStrafing = !isTurning && (std::abs(rightLateralSpeed) > 0.01f);
updateRole("rightStrafe", isStrafing && (rightLateralSpeed > 0.0f)); updateRole("rightStrafe", isStrafing && (rightLateralSpeed > 0.0f));
updateRole("leftStrafe", isStrafing && (rightLateralSpeed < 0.0f)); updateRole("leftStrafe", isStrafing && (rightLateralSpeed < 0.0f));
updateRole("idle", !isMoving); // Must be last, as it makes isMoving bogus. updateRole("idle", !isMoving); // Must be last, as it makes isMoving bogus.
_lastFront = front; _lastFront = front;
_lastPosition = worldPosition; _lastPosition = worldPosition;

View file

@ -313,7 +313,7 @@ AudioInjector* AudioInjector::playSound(const QString& soundUrl, const float vol
QByteArray samples = sound->getByteArray(); QByteArray samples = sound->getByteArray();
if (stretchFactor == 1.0f) { if (stretchFactor == 1.0f) {
return playSound(samples, options, NULL); return playSoundAndDelete(samples, options, NULL);
} }
soxr_io_spec_t spec = soxr_io_spec(SOXR_INT16_I, SOXR_INT16_I); soxr_io_spec_t spec = soxr_io_spec(SOXR_INT16_I, SOXR_INT16_I);
@ -333,9 +333,16 @@ AudioInjector* AudioInjector::playSound(const QString& soundUrl, const float vol
qCDebug(audio) << "Unable to resample" << soundUrl << "from" << nInputSamples << "@" << standardRate << "to" << nOutputSamples << "@" << resampledRate; qCDebug(audio) << "Unable to resample" << soundUrl << "from" << nInputSamples << "@" << standardRate << "to" << nOutputSamples << "@" << resampledRate;
resampled = samples; resampled = samples;
} }
return playSound(resampled, options, NULL); return playSoundAndDelete(resampled, options, NULL);
} }
AudioInjector* AudioInjector::playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface) {
AudioInjector* sound = playSound(buffer, options, localInterface);
sound->triggerDeleteAfterFinish();
return sound;
}
AudioInjector* AudioInjector::playSound(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface) { AudioInjector* AudioInjector::playSound(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface) {
QThread* injectorThread = new QThread(); QThread* injectorThread = new QThread();
injectorThread->setObjectName("Audio Injector Thread"); injectorThread->setObjectName("Audio Injector Thread");

View file

@ -46,6 +46,7 @@ public:
void setLocalAudioInterface(AbstractAudioInterface* localAudioInterface) { _localAudioInterface = localAudioInterface; } void setLocalAudioInterface(AbstractAudioInterface* localAudioInterface) { _localAudioInterface = localAudioInterface; }
static AudioInjector* playSoundAndDelete(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface);
static AudioInjector* playSound(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface); static AudioInjector* playSound(const QByteArray& buffer, const AudioInjectorOptions options, AbstractAudioInterface* localInterface);
static AudioInjector* playSound(const QString& soundUrl, const float volume, const float stretchFactor, const glm::vec3 position); static AudioInjector* playSound(const QString& soundUrl, const float volume, const float stretchFactor, const glm::vec3 position);

View file

@ -114,12 +114,14 @@ float HandData::getBaseScale() const {
} }
glm::vec3 PalmData::getFingerDirection() const { glm::vec3 PalmData::getFingerDirection() const {
const glm::vec3 LOCAL_FINGER_DIRECTION(0.0f, 0.0f, 1.0f); // finger points along yAxis in hand-frame
const glm::vec3 LOCAL_FINGER_DIRECTION(0.0f, 1.0f, 0.0f);
return glm::normalize(_owningHandData->localToWorldDirection(_rawRotation * LOCAL_FINGER_DIRECTION)); return glm::normalize(_owningHandData->localToWorldDirection(_rawRotation * LOCAL_FINGER_DIRECTION));
} }
glm::vec3 PalmData::getNormal() const { glm::vec3 PalmData::getNormal() const {
const glm::vec3 LOCAL_PALM_DIRECTION(0.0f, -1.0f, 0.0f); // palm normal points along zAxis in hand-frame
const glm::vec3 LOCAL_PALM_DIRECTION(0.0f, 0.0f, 1.0f);
return glm::normalize(_owningHandData->localToWorldDirection(_rawRotation * LOCAL_PALM_DIRECTION)); return glm::normalize(_owningHandData->localToWorldDirection(_rawRotation * LOCAL_PALM_DIRECTION));
} }

View file

@ -64,12 +64,13 @@ public:
bool findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius, glm::vec3& penetration, bool findSpherePenetration(const glm::vec3& penetratorCenter, float penetratorRadius, glm::vec3& penetration,
const PalmData*& collidingPalm) const; const PalmData*& collidingPalm) const;
glm::quat getBaseOrientation() const;
friend class AvatarData; friend class AvatarData;
protected: protected:
AvatarData* _owningAvatarData; AvatarData* _owningAvatarData;
std::vector<PalmData> _palms; std::vector<PalmData> _palms;
glm::quat getBaseOrientation() const;
glm::vec3 getBasePosition() const; glm::vec3 getBasePosition() const;
float getBaseScale() const; float getBaseScale() const;
@ -95,6 +96,7 @@ public:
void setRawRotation(const glm::quat rawRotation) { _rawRotation = rawRotation; }; void setRawRotation(const glm::quat rawRotation) { _rawRotation = rawRotation; };
glm::quat getRawRotation() const { return _rawRotation; } glm::quat getRawRotation() const { return _rawRotation; }
glm::quat getRotation() const { return _owningHandData->getBaseOrientation() * _rawRotation; }
void setRawPosition(const glm::vec3& pos) { _rawPosition = pos; } void setRawPosition(const glm::vec3& pos) { _rawPosition = pos; }
void setRawVelocity(const glm::vec3& velocity) { _rawVelocity = velocity; } void setRawVelocity(const glm::vec3& velocity) { _rawVelocity = velocity; }
const glm::vec3& getRawVelocity() const { return _rawVelocity; } const glm::vec3& getRawVelocity() const { return _rawVelocity; }
@ -147,6 +149,7 @@ public:
glm::vec3 getNormal() const; glm::vec3 getNormal() const;
private: private:
// unless marked otherwise, these are all in the model-frame
glm::quat _rawRotation; glm::quat _rawRotation;
glm::vec3 _rawPosition; glm::vec3 _rawPosition;
glm::vec3 _rawVelocity; glm::vec3 _rawVelocity;
@ -156,6 +159,7 @@ private:
glm::vec3 _tipPosition; glm::vec3 _tipPosition;
glm::vec3 _tipVelocity; glm::vec3 _tipVelocity;
glm::vec3 _totalPenetration; // accumulator for per-frame penetrations glm::vec3 _totalPenetration; // accumulator for per-frame penetrations
unsigned int _controllerButtons; unsigned int _controllerButtons;
unsigned int _lastControllerButtons; unsigned int _lastControllerButtons;
float _trigger; float _trigger;

View file

@ -57,6 +57,11 @@ public:
// Rendering support // Rendering support
// Stop requesting renders, but don't do full deactivation
// needed to work around the issues caused by Oculus
// processing messages in the middle of submitFrame
virtual void stop() = 0;
/** /**
* Called by the application before the frame rendering. Can be used for * Called by the application before the frame rendering. Can be used for
* render timing related calls (for instance, the Oculus begin frame timing * render timing related calls (for instance, the Oculus begin frame timing
@ -120,6 +125,7 @@ public:
virtual void resetSensors() {} virtual void resetSensors() {}
virtual float devicePixelRatio() { return 1.0; } virtual float devicePixelRatio() { return 1.0; }
static const QString& MENU_PATH(); static const QString& MENU_PATH();
signals: signals:
void recommendedFramebufferSizeChanged(const QSize & size); void recommendedFramebufferSizeChanged(const QSize & size);

View file

@ -30,3 +30,4 @@ void NullDisplayPlugin::finishFrame() {}
void NullDisplayPlugin::activate() {} void NullDisplayPlugin::activate() {}
void NullDisplayPlugin::deactivate() {} void NullDisplayPlugin::deactivate() {}
void NullDisplayPlugin::stop() {}

View file

@ -17,6 +17,7 @@ public:
void activate() override; void activate() override;
void deactivate() override; void deactivate() override;
void stop() override;
virtual glm::uvec2 getRecommendedRenderSize() const override; virtual glm::uvec2 getRecommendedRenderSize() const override;
virtual bool hasFocus() const override; virtual bool hasFocus() const override;

View file

@ -16,7 +16,9 @@
OpenGLDisplayPlugin::OpenGLDisplayPlugin() { OpenGLDisplayPlugin::OpenGLDisplayPlugin() {
connect(&_timer, &QTimer::timeout, this, [&] { connect(&_timer, &QTimer::timeout, this, [&] {
emit requestRender(); if (_active) {
emit requestRender();
}
}); });
} }
@ -56,10 +58,17 @@ void OpenGLDisplayPlugin::customizeContext() {
} }
void OpenGLDisplayPlugin::activate() { void OpenGLDisplayPlugin::activate() {
_active = true;
_timer.start(1); _timer.start(1);
} }
void OpenGLDisplayPlugin::stop() {
_active = false;
_timer.stop();
}
void OpenGLDisplayPlugin::deactivate() { void OpenGLDisplayPlugin::deactivate() {
_active = false;
_timer.stop(); _timer.stop();
makeCurrent(); makeCurrent();

View file

@ -25,7 +25,7 @@ public:
virtual void activate() override; virtual void activate() override;
virtual void deactivate() override; virtual void deactivate() override;
virtual void stop() override;
virtual bool eventFilter(QObject* receiver, QEvent* event) override; virtual bool eventFilter(QObject* receiver, QEvent* event) override;
virtual void display(GLuint sceneTexture, const glm::uvec2& sceneSize) override; virtual void display(GLuint sceneTexture, const glm::uvec2& sceneSize) override;
@ -44,6 +44,7 @@ protected:
mutable QTimer _timer; mutable QTimer _timer;
ProgramPtr _program; ProgramPtr _program;
ShapeWrapperPtr _plane; ShapeWrapperPtr _plane;
bool _active{ false };
bool _vsyncSupported{ false }; bool _vsyncSupported{ false };
}; };

View file

@ -325,19 +325,18 @@ void OculusDisplayPlugin::customizeContext() {
//_texture = DependencyManager::get<TextureCache>()-> //_texture = DependencyManager::get<TextureCache>()->
// getImageTexture(PathUtils::resourcesPath() + "/images/cube_texture.png"); // getImageTexture(PathUtils::resourcesPath() + "/images/cube_texture.png");
uvec2 mirrorSize = toGlm(_window->geometry().size()); uvec2 mirrorSize = toGlm(_window->geometry().size());
_mirrorFbo = MirrorFboPtr(new MirrorFramebufferWrapper(_hmd));
_mirrorFbo->Init(mirrorSize);
_sceneFbo = SwapFboPtr(new SwapFramebufferWrapper(_hmd)); _sceneFbo = SwapFboPtr(new SwapFramebufferWrapper(_hmd));
_sceneFbo->Init(getRecommendedRenderSize()); _sceneFbo->Init(getRecommendedRenderSize());
#endif #endif
enableVsync(false);
isVsyncEnabled();
} }
void OculusDisplayPlugin::deactivate() { void OculusDisplayPlugin::deactivate() {
#if (OVR_MAJOR_VERSION >= 6) #if (OVR_MAJOR_VERSION >= 6)
makeCurrent(); makeCurrent();
_sceneFbo.reset(); _sceneFbo.reset();
_mirrorFbo.reset();
doneCurrent(); doneCurrent();
PerformanceTimer::setActive(false); PerformanceTimer::setActive(false);
@ -350,11 +349,6 @@ void OculusDisplayPlugin::deactivate() {
} }
void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) { void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) {
static bool inDisplay = false;
if (inDisplay) {
return;
}
inDisplay = true;
#if (OVR_MAJOR_VERSION >= 6) #if (OVR_MAJOR_VERSION >= 6)
using namespace oglplus; using namespace oglplus;
// Need to make sure only the display plugin is responsible for // Need to make sure only the display plugin is responsible for
@ -383,12 +377,14 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi
the UI visible in the output window (unlikely). This should be done before the UI visible in the output window (unlikely). This should be done before
_sceneFbo->Increment or we're be using the wrong texture _sceneFbo->Increment or we're be using the wrong texture
*/ */
_sceneFbo->Bound(Framebuffer::Target::Read, [&] { if (_enableMirror) {
glBlitFramebuffer( _sceneFbo->Bound(Framebuffer::Target::Read, [&] {
0, 0, _sceneFbo->size.x, _sceneFbo->size.y, glBlitFramebuffer(
0, 0, windowSize.x, windowSize.y, 0, 0, _sceneFbo->size.x, _sceneFbo->size.y,
GL_COLOR_BUFFER_BIT, GL_NEAREST); 0, 0, windowSize.x, windowSize.y,
}); GL_COLOR_BUFFER_BIT, GL_NEAREST);
});
}
{ {
PerformanceTimer("OculusSubmit"); PerformanceTimer("OculusSubmit");
@ -409,6 +405,7 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi
The other alternative for mirroring is to use the Oculus mirror texture support, which The other alternative for mirroring is to use the Oculus mirror texture support, which
will contain the post-distorted and fully composited scene regardless of how many layers will contain the post-distorted and fully composited scene regardless of how many layers
we send. we send.
Currently generates an error.
*/ */
//auto mirrorSize = _mirrorFbo->size; //auto mirrorSize = _mirrorFbo->size;
//_mirrorFbo->Bound(Framebuffer::Target::Read, [&] { //_mirrorFbo->Bound(Framebuffer::Target::Read, [&] {
@ -420,21 +417,10 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi
++_frameIndex; ++_frameIndex;
#endif #endif
inDisplay = false;
} }
// Pass input events on to the application // Pass input events on to the application
bool OculusDisplayPlugin::eventFilter(QObject* receiver, QEvent* event) { bool OculusDisplayPlugin::eventFilter(QObject* receiver, QEvent* event) {
#if (OVR_MAJOR_VERSION >= 6)
if (event->type() == QEvent::Resize) {
QResizeEvent* resizeEvent = static_cast<QResizeEvent*>(event);
qDebug() << resizeEvent->size().width() << " x " << resizeEvent->size().height();
auto newSize = toGlm(resizeEvent->size());
makeCurrent();
_mirrorFbo->Resize(newSize);
doneCurrent();
}
#endif
return WindowOpenGLDisplayPlugin::eventFilter(receiver, event); return WindowOpenGLDisplayPlugin::eventFilter(receiver, event);
} }
@ -444,7 +430,9 @@ bool OculusDisplayPlugin::eventFilter(QObject* receiver, QEvent* event) {
otherwise the swapbuffer delay will interefere with the framerate of the headset otherwise the swapbuffer delay will interefere with the framerate of the headset
*/ */
void OculusDisplayPlugin::finishFrame() { void OculusDisplayPlugin::finishFrame() {
//swapBuffers(); if (_enableMirror) {
swapBuffers();
}
doneCurrent(); doneCurrent();
}; };

View file

@ -58,6 +58,7 @@ private:
mat4 _compositeEyeProjections[2]; mat4 _compositeEyeProjections[2];
uvec2 _desiredFramebufferSize; uvec2 _desiredFramebufferSize;
ovrTrackingState _trackingState; ovrTrackingState _trackingState;
bool _enableMirror{ false };
#if (OVR_MAJOR_VERSION >= 6) #if (OVR_MAJOR_VERSION >= 6)
ovrHmd _hmd; ovrHmd _hmd;
@ -70,7 +71,6 @@ private:
ovrLayerEyeFov& getSceneLayer(); ovrLayerEyeFov& getSceneLayer();
ovrHmdDesc _hmdDesc; ovrHmdDesc _hmdDesc;
SwapFboPtr _sceneFbo; SwapFboPtr _sceneFbo;
MirrorFboPtr _mirrorFbo;
ovrLayerEyeFov _sceneLayer; ovrLayerEyeFov _sceneLayer;
#endif #endif
#if (OVR_MAJOR_VERSION == 7) #if (OVR_MAJOR_VERSION == 7)

View file

@ -2677,8 +2677,21 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
foreach (const glm::vec3& vertex, extracted.mesh.vertices) { foreach (const glm::vec3& vertex, extracted.mesh.vertices) {
averageRadius += glm::distance(vertex, averageVertex); averageRadius += glm::distance(vertex, averageVertex);
} }
jointShapeInfo.averageRadius = averageRadius * radiusScale; jointShapeInfo.averageRadius = averageRadius * radiusScale / (float)jointShapeInfo.numVertices;
} }
// BUG: the boneBegin and/or boneEnd are incorrect for meshes that are "connected
// under the bone" without weights. Unfortunately we haven't been able to find it yet.
// Although the the mesh vertices are correct in the model-frame, the joint's transform
// in the same frame is just BAD.
//
// HACK WORKAROUND: prevent these shapes from contributing to the collision capsule by setting
// some key members of jointShapeInfo to zero:
jointShapeInfo.numVertices = 0;
jointShapeInfo.sumVertexWeights = 0.0f;
jointShapeInfo.numVertexWeights = 0;
jointShapeInfo.boneBegin = glm::vec3(0.0f);
jointShapeInfo.averageRadius = 0.0f;
} }
extracted.mesh.isEye = (maxJointIndex == geometry.leftEyeJointIndex || maxJointIndex == geometry.rightEyeJointIndex); extracted.mesh.isEye = (maxJointIndex == geometry.leftEyeJointIndex || maxJointIndex == geometry.rightEyeJointIndex);
@ -2728,7 +2741,6 @@ FBXGeometry* extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping
// the average radius to the average point. // the average radius to the average point.
if (jointShapeInfo.numVertexWeights == 0 if (jointShapeInfo.numVertexWeights == 0
&& jointShapeInfo.numVertices > 0) { && jointShapeInfo.numVertices > 0) {
jointShapeInfo.averageRadius /= (float)jointShapeInfo.numVertices;
joint.boneRadius = jointShapeInfo.averageRadius; joint.boneRadius = jointShapeInfo.averageRadius;
} }
} }

View file

@ -34,6 +34,8 @@ Q_LOGGING_CATEGORY(inputplugins, "hifi.inputplugins")
const unsigned int LEFT_MASK = 0; const unsigned int LEFT_MASK = 0;
const unsigned int RIGHT_MASK = 1U << 1; const unsigned int RIGHT_MASK = 1U << 1;
#ifdef HAVE_SIXENSE
const int CALIBRATION_STATE_IDLE = 0; const int CALIBRATION_STATE_IDLE = 0;
const int CALIBRATION_STATE_X = 1; const int CALIBRATION_STATE_X = 1;
const int CALIBRATION_STATE_Y = 2; const int CALIBRATION_STATE_Y = 2;
@ -47,11 +49,15 @@ const float NECK_Z = 0.3f; // meters
const float CONTROLLER_THRESHOLD = 0.35f; const float CONTROLLER_THRESHOLD = 0.35f;
#endif
#ifdef __APPLE__ #ifdef __APPLE__
typedef int (*SixenseBaseFunction)(); typedef int (*SixenseBaseFunction)();
typedef int (*SixenseTakeIntFunction)(int); typedef int (*SixenseTakeIntFunction)(int);
#ifdef HAVE_SIXENSE
typedef int (*SixenseTakeIntAndSixenseControllerData)(int, sixenseControllerData*); typedef int (*SixenseTakeIntAndSixenseControllerData)(int, sixenseControllerData*);
#endif #endif
#endif
const QString SixenseManager::NAME = "Sixense"; const QString SixenseManager::NAME = "Sixense";
@ -475,6 +481,7 @@ void SixenseManager::handlePoseEvent(glm::vec3 position, glm::quat rotation, int
// Qsh = angleAxis(PI, zAxis) * angleAxis(-PI/2, xAxis) // Qsh = angleAxis(PI, zAxis) * angleAxis(-PI/2, xAxis)
// //
const glm::vec3 xAxis = glm::vec3(1.0f, 0.0f, 0.0f); const glm::vec3 xAxis = glm::vec3(1.0f, 0.0f, 0.0f);
const glm::vec3 yAxis = glm::vec3(0.0f, 1.0f, 0.0f);
const glm::vec3 zAxis = glm::vec3(0.0f, 0.0f, 1.0f); const glm::vec3 zAxis = glm::vec3(0.0f, 0.0f, 1.0f);
const glm::quat sixenseToHand = glm::angleAxis(PI, zAxis) * glm::angleAxis(-PI/2.0f, xAxis); const glm::quat sixenseToHand = glm::angleAxis(PI, zAxis) * glm::angleAxis(-PI/2.0f, xAxis);
@ -485,13 +492,15 @@ void SixenseManager::handlePoseEvent(glm::vec3 position, glm::quat rotation, int
const glm::quat preOffset = glm::angleAxis(sign * PI / 2.0f, zAxis); const glm::quat preOffset = glm::angleAxis(sign * PI / 2.0f, zAxis);
// Finally, there is a post-offset (same for both hands) to get the hand's rest orientation // Finally, there is a post-offset (same for both hands) to get the hand's rest orientation
// (fingers forward, palm down) aligned properly in the avatar's model-frame. // (fingers forward, palm down) aligned properly in the avatar's model-frame,
const glm::quat postOffset = glm::angleAxis(PI / 2.0f, xAxis); // and then a flip about the yAxis to get into model-frame.
const glm::quat postOffset = glm::angleAxis(PI, yAxis) * glm::angleAxis(PI / 2.0f, xAxis);
// The total rotation of the hand uses the formula: // The total rotation of the hand uses the formula:
// //
// rotation = postOffset * Qsh^ * (measuredRotation * preOffset) * Qsh // rotation = postOffset * Qsh^ * (measuredRotation * preOffset) * Qsh
// //
// TODO: find a shortcut with fewer rotations.
rotation = postOffset * glm::inverse(sixenseToHand) * rotation * preOffset * sixenseToHand; rotation = postOffset * glm::inverse(sixenseToHand) * rotation * preOffset * sixenseToHand;
_poseStateMap[makeInput(JointChannel(index)).getChannel()] = UserInputMapper::PoseValue(position, rotation); _poseStateMap[makeInput(JointChannel(index)).getChannel()] = UserInputMapper::PoseValue(position, rotation);

View file

@ -362,18 +362,17 @@ void ViveControllerManager::handlePoseEvent(const mat4& mat, int index) {
// //
// Qoffset = glm::inverse(deltaRotation when hand is posed fingers forward, palm down) // Qoffset = glm::inverse(deltaRotation when hand is posed fingers forward, palm down)
// //
// An approximate offset for the Vive can be obtained by inpection: // An approximate offset for the Vive can be obtained by inspection:
// //
// Qoffset = glm::inverse(glm::angleAxis(sign * PI/4.0f, zAxis) * glm::angleAxis(PI/2.0f, xAxis)) // Qoffset = glm::inverse(glm::angleAxis(sign * PI/4.0f, zAxis) * glm::angleAxis(PI/2.0f, xAxis))
// //
// So the full equation is:
// Finally there is another flip around the yAxis to re-align from model to Vive space, so the full equation is:
// //
// Q = yFlip * combinedMeasurement * viveToHand // Q = combinedMeasurement * viveToHand
// //
// Q = yFlip * (deltaQ * QOffset) * (yFlip * quarterTurnAboutX) // Q = (deltaQ * QOffset) * (yFlip * quarterTurnAboutX)
// //
// Q = yFlip * (deltaQ * inverse(deltaQForAlignedHand)) * (yFlip * quarterTurnAboutX) // Q = (deltaQ * inverse(deltaQForAlignedHand)) * (yFlip * quarterTurnAboutX)
const glm::quat quarterX = glm::angleAxis(PI / 2.0f, glm::vec3(1.0f, 0.0f, 0.0f)); const glm::quat quarterX = glm::angleAxis(PI / 2.0f, glm::vec3(1.0f, 0.0f, 0.0f));
const glm::quat yFlip = glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f)); const glm::quat yFlip = glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f));
@ -381,7 +380,7 @@ void ViveControllerManager::handlePoseEvent(const mat4& mat, int index) {
const glm::quat signedQuaterZ = glm::angleAxis(sign * PI / 2.0f, glm::vec3(0.0f, 0.0f, 1.0f)); const glm::quat signedQuaterZ = glm::angleAxis(sign * PI / 2.0f, glm::vec3(0.0f, 0.0f, 1.0f));
const glm::quat eighthX = glm::angleAxis(PI / 4.0f, glm::vec3(1.0f, 0.0f, 0.0f)); const glm::quat eighthX = glm::angleAxis(PI / 4.0f, glm::vec3(1.0f, 0.0f, 0.0f));
const glm::quat offset = glm::inverse(signedQuaterZ * eighthX); const glm::quat offset = glm::inverse(signedQuaterZ * eighthX);
rotation = yFlip * rotation * offset * yFlip * quarterX; rotation = rotation * offset * yFlip * quarterX;
position += rotation * glm::vec3(0, 0, -CONTROLLER_LENGTH_OFFSET); position += rotation * glm::vec3(0, 0, -CONTROLLER_LENGTH_OFFSET);

View file

@ -0,0 +1,27 @@
//
// Created by Bradley Austin Davis on 2015/09/01
// Copyright 2013-2105 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// Simulates a java finally block by executing a lambda when an instance leaves
// scope
#include <functional>
#pragma once
#ifndef hifi_Finally_h
#define hifi_Finally_h
class Finally {
public:
template <typename F>
Finally(F f) : _f(f) {}
~Finally() { _f(); }
private:
std::function<void()> _f;
};
#endif

View file

@ -197,9 +197,23 @@ QScriptValue xColorToScriptValue(QScriptEngine *engine, const xColor& color) {
} }
void xColorFromScriptValue(const QScriptValue &object, xColor& color) { void xColorFromScriptValue(const QScriptValue &object, xColor& color) {
color.red = object.property("red").toVariant().toInt(); if (!object.isValid()) {
color.green = object.property("green").toVariant().toInt(); return;
color.blue = object.property("blue").toVariant().toInt(); }
if (object.isNumber()) {
color.red = color.green = color.blue = (uint8_t)object.toUInt32();
} else if (object.isString()) {
QColor qcolor(object.toString());
if (qcolor.isValid()) {
color.red = (uint8_t)qcolor.red();
color.blue = (uint8_t)qcolor.blue();
color.green = (uint8_t)qcolor.green();
}
} else {
color.red = object.property("red").toVariant().toInt();
color.green = object.property("green").toVariant().toInt();
color.blue = object.property("blue").toVariant().toInt();
}
} }
QScriptValue qColorToScriptValue(QScriptEngine* engine, const QColor& color) { QScriptValue qColorToScriptValue(QScriptEngine* engine, const QColor& color) {