Merge branch 'master' of github.com:highfidelity/hifi into polyvox-again

This commit is contained in:
Seth Alves 2015-09-03 09:38:58 -07:00
commit bb96ad1346
34 changed files with 1532 additions and 1007 deletions

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>
@ -1009,6 +1010,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();
@ -1203,7 +1214,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));
@ -1219,7 +1230,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;
@ -4703,53 +4713,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");
} }
@ -5052,3 +5077,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

@ -673,6 +673,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

@ -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

@ -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

@ -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

@ -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) {