From 1e6198cf597fef194fc657ce2a992c01cd705409 Mon Sep 17 00:00:00 2001 From: ericrius1 Date: Wed, 2 Sep 2015 11:29:37 -0700 Subject: [PATCH 1/9] update dhydra grab to grab near and far objects, and removed grabbing logic from toybox.js --- examples/controllers/hydra/hydraGrab.js | 524 ++++++++++++------------ examples/controllers/toybox.js | 233 +---------- 2 files changed, 267 insertions(+), 490 deletions(-) diff --git a/examples/controllers/hydra/hydraGrab.js b/examples/controllers/hydra/hydraGrab.js index 34484eb9e8..dfb0fdcadf 100644 --- a/examples/controllers/hydra/hydraGrab.js +++ b/examples/controllers/hydra/hydraGrab.js @@ -1,307 +1,315 @@ -// // hydraGrab.js // examples // -// Created by Clément Brisset on 4/24/14. -// Updated by Eric Levin on 5/14/15. -// Copyright 2014 High Fidelity, Inc. +// Created by Eric Levin on 9/2/15 +// Copyright 2015 High Fidelity, Inc. // -// This script allows you to grab and move/rotate physical objects with the hydra -// -// Using the hydras : -// grab physical entities with the right trigger +// 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. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +var RIGHT_HAND_CLICK = Controller.findAction("RIGHT_HAND_CLICK"); +var rightTriggerAction = RIGHT_HAND_CLICK; +var LEFT_HAND_CLICK = Controller.findAction("LEFT_HAND_CLICK"); +var leftTriggerAction = LEFT_HAND_CLICK; -var entityProps, currentPosition, currentVelocity, currentRotation, distanceToTarget, velocityTowardTarget, desiredVelocity; -var addedVelocity, newVelocity, angularVelocity, dT, cameraEntityDistance; -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 DROP_COLOR = { - red: 200, - green: 200, - blue: 200 -}; - -var FULL_STRENGTH = 0.05; -var LASER_LENGTH_FACTOR = 500; -var CLOSE_ENOUGH = 0.001; -var SPRING_RATE = 1.5; -var DAMPING_RATE = 0.8; -var SCREEN_TO_METERS = 0.001; -var DISTANCE_SCALE_FACTOR = 1000 - -var 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 = { +var ZERO_VEC = { x: 0, y: 0, z: 0 - }; +} +var LINE_LENGTH = 500; +var THICK_LINE_WIDTH = 7; +var THIN_LINE_WIDTH = 2; - this.laser = Overlays.addOverlay("line3d", { - start: { - x: 0, - y: 0, - z: 0 - }, - end: { - x: 0, - y: 0, - z: 0 - }, - color: LASER_COLOR, - alpha: 1, - lineWidth: LASER_WIDTH, - anchor: "MyAvatar" - }); +var NO_INTERSECT_COLOR = { + red: 10, + green: 10, + blue: 255 +}; +var INTERSECT_COLOR = { + red: 250, + green: 10, + blue: 10 +}; - this.dropLine = Overlays.addOverlay("line3d", { - color: DROP_COLOR, - alpha: 1, - visible: false, - lineWidth: 2 - }); +var GRAB_RADIUS = 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) { - this.updateControllerState(); - this.moveLaser(); - this.checkTrigger(); - this.checkEntityIntersection(); - if (this.grabbing) { - this.updateEntity(deltaTime); - } +var right4Action = 18; +var left4Action = 17; - this.oldPalmPosition = this.palmPosition; - this.oldTipPosition = this.tipPosition; - } +var TRACTOR_BEAM_VELOCITY_THRESHOLD = 0.5; - this.updateEntity = function(deltaTime) { - this.dControllerPosition = Vec3.subtract(this.palmPosition, this.oldPalmPosition); - this.cameraEntityDistance = Vec3.distance(Camera.getPosition(), this.currentPosition); - this.targetPosition = Vec3.sum(this.targetPosition, Vec3.multiply(this.dControllerPosition, this.cameraEntityDistance * SCREEN_TO_METERS * DISTANCE_SCALE_FACTOR)); +var RIGHT = 1; +var LEFT = 0; +var rightController = new controller(RIGHT, rightTriggerAction, right4Action, "right") +var leftController = new controller(LEFT, leftTriggerAction, left4Action, "left") - this.entityProps = Entities.getEntityProperties(this.grabbedEntity); - this.currentPosition = this.entityProps.position; - this.currentVelocity = this.entityProps.velocity; - - var dPosition = Vec3.subtract(this.targetPosition, this.currentPosition); - this.distanceToTarget = Vec3.length(dPosition); - if (this.distanceToTarget > CLOSE_ENOUGH) { - // compute current velocity in the direction we want to move - this.velocityTowardTarget = Vec3.dot(this.currentVelocity, Vec3.normalize(dPosition)); - this.velocityTowardTarget = Vec3.multiply(Vec3.normalize(dPosition), this.velocityTowardTarget); - // compute the speed we would like to be going toward the target position - - this.desiredVelocity = Vec3.multiply(dPosition, (1.0 / deltaTime) * SPRING_RATE); - // compute how much we want to add to the existing velocity - this.addedVelocity = Vec3.subtract(this.desiredVelocity, this.velocityTowardTarget); - //If target is to far, roll off force as inverse square of distance - if(this.distanceToTarget/ this.cameraEntityDistance > FULL_STRENGTH) { - this.addedVelocity = Vec3.multiply(this.addedVelocity, Math.pow(FULL_STRENGTH/ this.distanceToTarget, 2.0)); - } - this.newVelocity = Vec3.sum(this.currentVelocity, this.addedVelocity); - this.newVelocity = Vec3.subtract(this.newVelocity, Vec3.multiply(this.newVelocity, DAMPING_RATE)); +function controller(side, triggerAction, pullAction, hand) { + this.hand = hand; + if (hand === "right") { + this.getHandPosition = MyAvatar.getRightPalmPosition; + this.getHandRotation = MyAvatar.getRightPalmRotation; } else { - this.newVelocity = { - x: 0, - y: 0, - z: 0 - }; - } - this.transformedAngularVelocity = Controller.getSpatialControlRawAngularVelocity(this.tip); - this.transformedAngularVelocity = Vec3.multiplyQbyV(Camera.getOrientation(), this.transformedAngularVelocity); - Entities.editEntity(this.grabbedEntity, { - velocity: this.newVelocity, - angularVelocity: this.transformedAngularVelocity + this.getHandPosition = MyAvatar.getLeftPalmPosition; + this.getHandRotation = MyAvatar.getLeftPalmRotation; + } + this.triggerAction = triggerAction; + this.pullAction = pullAction; + this.actionID = null; + this.tractorBeamActive = false; + this.distanceHolding = 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); - - } - - - 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(); - } + //only check if we havent already grabbed an object + if (this.distanceHolding) { + return; } - } + + //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 = { - origin: this.palmPosition, - direction: Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition)) + origin: origin, + direction: direction }; - var intersection = getRayIntersection(pickRay, true); - if (intersection.intersects && intersection.properties.collisionsWillMove) { - this.laserWasHovered = true; - if (this.triggerHeld && !this.grabbing) { - this.grab(intersection.entityID); - } - Overlays.editOverlay(this.laser, { - color: LASER_HOVER_COLOR - }); - } else if (this.laserWasHovered) { - this.laserWasHovered = false; - Overlays.editOverlay(this.laser, { - color: LASER_COLOR - }); + + var intersection = Entities.findRayIntersection(pickRay, true); + + if (intersection.intersects) { + this.distanceToEntity = Vec3.distance(origin, intersection.properties.position); + Entities.editEntity(this.pointer, { + linePoints: [ + ZERO_VEC, + Vec3.multiply(direction, this.distanceToEntity) + ] + }); + this.grabbedEntity = intersection.entityID; + return true; } - } + return false; +} - this.grab = function(entityId) { - this.grabbing = true; - this.grabbedEntity = entityId; - this.entityProps = Entities.getEntityProperties(this.grabbedEntity); - this.targetPosition = this.entityProps.position; - this.currentPosition = this.targetPosition; - this.oldPalmPosition = this.palmPosition; - this.originalGravity = this.entityProps.gravity; - Entities.editEntity(this.grabbedEntity, { - gravity: { - x: 0, - y: 0, - z: 0 - } - }); - Overlays.editOverlay(this.laser, { - visible: false - }); - Audio.playSound(grabSound, { - position: this.entityProps.position, - volume: 0.25 - }); - } +controller.prototype.attemptMove = function() { + if (this.tractorBeamActive) { + return; + } + if (this.grabbedEntity || this.distanceHolding) { + var handPosition = Controller.getSpatialControlPosition(this.palm); + var direction = Controller.getSpatialControlNormal(this.tip); - this.release = function() { - this.grabbing = false; + var newPosition = Vec3.sum(handPosition, Vec3.multiply(direction, this.distanceToEntity)) + 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: 0.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; - Overlays.editOverlay(this.laser, { - visible: true - }); - Overlays.editOverlay(this.dropLine, { - visible: false - }); + this.actionID = null; + this.distanceHolding = false; + this.tractorBeamActive = false; + this.checkForEntityArrival = false; +} - Audio.playSound(releaseSound, { - position: this.entityProps.position, - volume: 0.25 - }); - - // only restore the original gravity if it's not zero. This is to avoid... - // 1. interface A grabs an entity and locally saves off its gravity - // 2. interface A sets the entity's gravity to zero - // 3. interface B grabs the entity and saves off its gravity (which is zero) - // 4. interface A releases the entity and puts the original gravity back - // 5. interface B releases the entity and puts the original gravity back (to zero) - if(vectorIsZero(this.originalGravity)) { - Entities.editEntity(this.grabbedEntity, { - gravity: this.originalGravity - }); +controller.prototype.update = function() { + if (this.tractorBeamActive && this.checkForEntityArrival) { + var entityVelocity = Entities.getEntityProperties(this.grabbedEntity).velocity + if (Vec3.length(entityVelocity) < TRACTOR_BEAM_VELOCITY_THRESHOLD) { + this.letGo(); + } + return; + } + this.triggerValue = Controller.getActionValue(this.triggerAction); + if (this.triggerValue > SHOW_LINE_THRESHOLD && this.prevTriggerValue < SHOW_LINE_THRESHOLD) { + //First check if an object is within close range and then run the close grabbing logic + if (this.checkForInRangeObject()) { + this.grabEntity(); + } 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() { - var inverseRotation = Quat.inverse(MyAvatar.orientation); - var startPosition = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.palmPosition, MyAvatar.position)); - // startPosition = Vec3.multiply(startPosition, 1 / MyAvatar.scale); - var direction = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.tipPosition, this.palmPosition)); - direction = Vec3.multiply(direction, LASER_LENGTH_FACTOR / (Vec3.length(direction) * MyAvatar.scale)); - var endPosition = Vec3.sum(startPosition, direction); + if (this.shouldDisplayLine) { + this.updateLine(); + } + if (this.triggerValue > DISTANCE_HOLD_THRESHOLD) { + this.attemptMove(); + } - Overlays.editOverlay(this.laser, { - start: startPosition, - end: endPosition + + this.prevTriggerValue = this.triggerValue; +} + +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.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); - leftController.update(deltaTime); + +controller.prototype.checkForInRangeObject = function() { + 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") { + grabbedEntity = entities[i]; + minDistance = distance; + } + } + if (grabbedEntity === null) { + return false; + } else { + this.grabbedEntity = grabbedEntity; + return true; + } } -function scriptEnding() { - rightController.cleanup(); - leftController.cleanup(); + +controller.prototype.onActionEvent = function(action, state) { + 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) { - return v.x === 0 && v.y === 0 && v.z === 0; +controller.prototype.cleanup = function() { + Entities.deleteEntity(this.pointer); + Entities.deleteAction(this.grabbedEntity, this.actionID); } -var rightController = new controller(RIGHT); -var leftController = new controller(LEFT); +function update() { + rightController.update(); + leftController.update(); +} + +function onActionEvent(action, state) { + rightController.onActionEvent(action, state); + leftController.onActionEvent(action, state); + +} -Script.update.connect(update); -Script.scriptEnding.connect(scriptEnding); \ No newline at end of file +function cleanup() { + rightController.cleanup(); + leftController.cleanup(); +} + + +Script.scriptEnding.connect(cleanup); +Script.update.connect(update) +Controller.actionEvent.connect(onActionEvent); \ No newline at end of file diff --git a/examples/controllers/toybox.js b/examples/controllers/toybox.js index 4c0925f3b6..bf03974fda 100644 --- a/examples/controllers/toybox.js +++ b/examples/controllers/toybox.js @@ -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/"; -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() { return { @@ -63,25 +38,6 @@ var cleanupButton = toolBar.addOverlay("image", { 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 MIN_OBJECT_SIZE = 0.05; @@ -98,8 +54,6 @@ var GRAVITY = { z: 0.0 } -var LEFT = 0; -var RIGHT = 1; var tableCreated = false; @@ -108,7 +62,6 @@ var tableEntities = Array(NUM_OBJECTS + 1); // Also includes table var VELOCITY_MAG = 0.3; -var entitiesToResize = []; var MODELS = Array( { 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_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() { - letGo(RIGHT); - letGo(LEFT); + print("CLEANUP!!!") if (overlays) { Overlays.deleteOverlay(leftHandOverlay); Overlays.deleteOverlay(rightHandOverlay); } - Entities.deleteEntity(leftFist); - Entities.deleteEntity(rightFist); removeTable(); toolBar.cleanup(); } @@ -405,7 +177,6 @@ function createTable() { density: 0.5, collisionsWillMove: true, color: { red: randInt(0, 255), green: randInt(0, 255), blue: randInt(0, 255) }, - // collisionSoundURL: COLLISION_SOUNDS[randInt(0, COLLISION_SOUNDS.length)] }); if (type == "Model") { var randModel = randInt(0, MODELS.length); @@ -413,7 +184,6 @@ function createTable() { shapeType: "box", modelURL: MODELS[randModel].modelURL }); - entitiesToResize.push(tableEntities[i]); } } } @@ -426,5 +196,4 @@ function removeTable() { } Script.scriptEnding.connect(cleanUp); -Script.update.connect(update); Controller.mousePressEvent.connect(onClick); \ No newline at end of file From 54b7a063e2d23ab326d03fefa2172f07d81f44cb Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 31 Aug 2015 20:31:33 -0700 Subject: [PATCH 2/9] Support HTML colors in overlays --- interface/src/ui/overlays/Overlay.cpp | 11 +---------- libraries/shared/src/RegisteredMetaTypes.cpp | 20 +++++++++++++++++--- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/interface/src/ui/overlays/Overlay.cpp b/interface/src/ui/overlays/Overlay.cpp index 7824c0c498..0c909a1bfb 100644 --- a/interface/src/ui/overlays/Overlay.cpp +++ b/interface/src/ui/overlays/Overlay.cpp @@ -66,16 +66,7 @@ Overlay::~Overlay() { void Overlay::setProperties(const QScriptValue& properties) { QScriptValue color = properties.property("color"); - if (color.isValid()) { - 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(); - } - } + xColorFromScriptValue(properties.property("color"), _color); if (properties.property("alpha").isValid()) { setAlpha(properties.property("alpha").toVariant().toFloat()); diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index c4e05a68fb..2c4b213fcb 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -197,9 +197,23 @@ QScriptValue xColorToScriptValue(QScriptEngine *engine, const xColor& color) { } void xColorFromScriptValue(const QScriptValue &object, xColor& color) { - color.red = object.property("red").toVariant().toInt(); - color.green = object.property("green").toVariant().toInt(); - color.blue = object.property("blue").toVariant().toInt(); + if (!object.isValid()) { + return; + } + 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) { From 849249d7fec44004ea95d6e6a4c746588bd84872 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 31 Aug 2015 20:39:08 -0700 Subject: [PATCH 3/9] Fixing colors Conflicts: examples/toys/magBalls/handController.js --- examples/libraries/htmlColors.js | 145 +++++++++++++++++++++++ examples/toys/magBalls/handController.js | 1 + examples/toys/magBalls/magBallsMain.js | 1 + 3 files changed, 147 insertions(+) create mode 100644 examples/libraries/htmlColors.js diff --git a/examples/libraries/htmlColors.js b/examples/libraries/htmlColors.js new file mode 100644 index 0000000000..e9ebbba841 --- /dev/null +++ b/examples/libraries/htmlColors.js @@ -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", +} + diff --git a/examples/toys/magBalls/handController.js b/examples/toys/magBalls/handController.js index 998d22c6f8..4aba43d412 100644 --- a/examples/toys/magBalls/handController.js +++ b/examples/toys/magBalls/handController.js @@ -10,6 +10,7 @@ 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; diff --git a/examples/toys/magBalls/magBallsMain.js b/examples/toys/magBalls/magBallsMain.js index e54b818e4a..4eb98fab26 100644 --- a/examples/toys/magBalls/magBallsMain.js +++ b/examples/toys/magBalls/magBallsMain.js @@ -6,6 +6,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +Script.include("../../libraries/htmlColors.js"); Script.include("constants.js"); Script.include("utils.js"); Script.include("magBalls.js"); From d303c7e119021c3fd438c940e6c56e3197d8003c Mon Sep 17 00:00:00 2001 From: ericrius1 Date: Wed, 2 Sep 2015 16:45:50 -0700 Subject: [PATCH 4/9] only add actions to physical objects --- examples/controllers/hydra/hydraGrab.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/examples/controllers/hydra/hydraGrab.js b/examples/controllers/hydra/hydraGrab.js index dfb0fdcadf..71e4d2a07e 100644 --- a/examples/controllers/hydra/hydraGrab.js +++ b/examples/controllers/hydra/hydraGrab.js @@ -9,6 +9,8 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // + + var RIGHT_HAND_CLICK = Controller.findAction("RIGHT_HAND_CLICK"); var rightTriggerAction = RIGHT_HAND_CLICK; @@ -70,6 +72,7 @@ function controller(side, triggerAction, pullAction, hand) { this.actionID = null; this.tractorBeamActive = false; this.distanceHolding = false; + this.closeGrabbing = false; this.triggerValue = 0; this.prevTriggerValue = 0; this.palm = 2 * side; @@ -127,8 +130,7 @@ controller.prototype.checkForIntersections = function(origin, direction) { }; var intersection = Entities.findRayIntersection(pickRay, true); - - if (intersection.intersects) { + if (intersection.intersects && intersection.properties.collisionsWillMove === 1) { this.distanceToEntity = Vec3.distance(origin, intersection.properties.position); Entities.editEntity(this.pointer, { linePoints: [ @@ -156,7 +158,7 @@ controller.prototype.attemptMove = function() { if (this.actionID === null) { this.actionID = Entities.addAction("spring", this.grabbedEntity, { targetPosition: newPosition, - linearTimeScale: 0.1 + linearTimeScale: .1 }); } else { Entities.updateAction(this.grabbedEntity, this.actionID, { @@ -188,6 +190,7 @@ controller.prototype.letGo = function() { this.distanceHolding = false; this.tractorBeamActive = false; this.checkForEntityArrival = false; + this.closeGrabbing = false; } controller.prototype.update = function() { @@ -216,7 +219,7 @@ controller.prototype.update = function() { if (this.shouldDisplayLine) { this.updateLine(); } - if (this.triggerValue > DISTANCE_HOLD_THRESHOLD) { + if (this.triggerValue > DISTANCE_HOLD_THRESHOLD && !this.closeGrabbing) { this.attemptMove(); } @@ -234,6 +237,7 @@ controller.prototype.grabEntity = function() { 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, @@ -252,7 +256,7 @@ controller.prototype.checkForInRangeObject = function() { 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") { + if (distance < minDistance && props.name !== "pointer" && props.collisionsWillMove === 1) { grabbedEntity = entities[i]; minDistance = distance; } From ea415071b48d1c3060c6b455536e54e951aa90fe Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 31 Aug 2015 22:54:23 -0700 Subject: [PATCH 5/9] OmniTool first pass --- .../{toys/magBalls => libraries}/constants.js | 0 .../magBalls => libraries}/highlighter.js | 6 + examples/libraries/omniTool.js | 300 ++++++++++++++++++ .../libraries/omniTool/models/modelBase.js | 19 ++ examples/libraries/omniTool/models/wand.js | 120 +++++++ examples/libraries/omniTool/modules/test.js | 9 + .../{toys/magBalls => libraries}/utils.js | 8 +- examples/toys/magBalls/magBallsMain.js | 26 -- 8 files changed, 461 insertions(+), 27 deletions(-) rename examples/{toys/magBalls => libraries}/constants.js (100%) rename examples/{toys/magBalls => libraries}/highlighter.js (92%) create mode 100644 examples/libraries/omniTool.js create mode 100644 examples/libraries/omniTool/models/modelBase.js create mode 100644 examples/libraries/omniTool/models/wand.js create mode 100644 examples/libraries/omniTool/modules/test.js rename examples/{toys/magBalls => libraries}/utils.js (93%) delete mode 100644 examples/toys/magBalls/magBallsMain.js diff --git a/examples/toys/magBalls/constants.js b/examples/libraries/constants.js similarity index 100% rename from examples/toys/magBalls/constants.js rename to examples/libraries/constants.js diff --git a/examples/toys/magBalls/highlighter.js b/examples/libraries/highlighter.js similarity index 92% rename from examples/toys/magBalls/highlighter.js rename to examples/libraries/highlighter.js index 149d9ec5b7..b3550b6c8a 100644 --- a/examples/toys/magBalls/highlighter.js +++ b/examples/libraries/highlighter.js @@ -53,6 +53,12 @@ Highlighter.prototype.setSize = function(newSize) { }); } +Highlighter.prototype.setRotation = function(newRotation) { + Overlays.editOverlay(this.highlightCube, { + rotation: newRotation + }); +} + Highlighter.prototype.updateHighlight = function() { if (this.hightlighted) { var properties = Entities.getEntityProperties(this.hightlighted); diff --git a/examples/libraries/omniTool.js b/examples/libraries/omniTool.js new file mode 100644 index 0000000000..643b789a0e --- /dev/null +++ b/examples/libraries/omniTool.js @@ -0,0 +1,300 @@ +// +// 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.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) ]; diff --git a/examples/libraries/omniTool/models/modelBase.js b/examples/libraries/omniTool/models/modelBase.js new file mode 100644 index 0000000000..7697856d3f --- /dev/null +++ b/examples/libraries/omniTool/models/modelBase.js @@ -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); +} diff --git a/examples/libraries/omniTool/models/wand.js b/examples/libraries/omniTool/models/wand.js new file mode 100644 index 0000000000..8f0fe92b53 --- /dev/null +++ b/examples/libraries/omniTool/models/wand.js @@ -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, + }); + } +} diff --git a/examples/libraries/omniTool/modules/test.js b/examples/libraries/omniTool/modules/test.js new file mode 100644 index 0000000000..1ca806affa --- /dev/null +++ b/examples/libraries/omniTool/modules/test.js @@ -0,0 +1,9 @@ + +OmniToolModules.Test = function() { +} + +OmniToolModules.Test.prototype.onClick = function() { + logDebug("Test module onClick"); +} + +OmniToolModuleType = "Test" \ No newline at end of file diff --git a/examples/toys/magBalls/utils.js b/examples/libraries/utils.js similarity index 93% rename from examples/toys/magBalls/utils.js rename to examples/libraries/utils.js index ea1446f858..b15ea6c313 100644 --- a/examples/toys/magBalls/utils.js +++ b/examples/libraries/utils.js @@ -53,11 +53,17 @@ getEntityUserData = function(id) { var results = null; var properties = Entities.getEntityProperties(id); if (properties.userData) { - results = JSON.parse(properties.userData); + try { + results = JSON.parse(properties.userData); + } catch(err) { + logDebug(err); + logDebug(properties.userData); + } } return results ? results : {}; } + // Non-destructively modify the user data of an entity. setEntityCustomData = function(customKey, id, data) { var userData = getEntityUserData(id); diff --git a/examples/toys/magBalls/magBallsMain.js b/examples/toys/magBalls/magBallsMain.js deleted file mode 100644 index 4eb98fab26..0000000000 --- a/examples/toys/magBalls/magBallsMain.js +++ /dev/null @@ -1,26 +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("../../libraries/htmlColors.js"); -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) ]; From 5717b7783748ff543403d877e3770f594270f319 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 2 Sep 2015 18:01:47 -0700 Subject: [PATCH 6/9] Updating magballs to omnitool --- examples/libraries/constants.js | 77 -------------- examples/libraries/omniTool.js | 4 + examples/libraries/utils.js | 8 -- examples/toys/magBalls.js | 113 ++++++++++++++++++++ examples/toys/magBalls/ballController.js | 103 ------------------ examples/toys/magBalls/constants.js | 77 ++++++++++++++ examples/toys/magBalls/graph.js | 4 + examples/toys/magBalls/handController.js | 128 ----------------------- examples/toys/magBalls/magBalls.js | 115 +++++++++++++++----- examples/toys/magBalls/menuController.js | 66 ------------ 10 files changed, 289 insertions(+), 406 deletions(-) create mode 100644 examples/toys/magBalls.js delete mode 100644 examples/toys/magBalls/ballController.js create mode 100644 examples/toys/magBalls/constants.js delete mode 100644 examples/toys/magBalls/handController.js delete mode 100644 examples/toys/magBalls/menuController.js diff --git a/examples/libraries/constants.js b/examples/libraries/constants.js index d154910f91..3ed7c02633 100644 --- a/examples/libraries/constants.js +++ b/examples/libraries/constants.js @@ -9,17 +9,6 @@ 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 -SCALE = 0.5; -BALL_SIZE = 0.08 * SCALE; -STICK_LENGTH = 0.24 * SCALE; - -DEBUG_MAGSTICKS = true; - -CUSTOM_DATA_NAME = "magBalls"; -BALL_NAME = "MagBall"; -EDGE_NAME = "MagStick"; - ZERO_VECTOR = { x: 0, y: 0, z: 0 }; COLORS = { @@ -70,71 +59,5 @@ COLORS = { } } -BALL_RADIUS = BALL_SIZE / 2.0; - -BALL_SELECTION_RADIUS = BALL_RADIUS * 1.5; - -BALL_DIMENSIONS = { - x: BALL_SIZE, - y: BALL_SIZE, - z: BALL_SIZE -}; - -BALL_COLOR = { - red: 128, - green: 128, - blue: 128 -}; - -STICK_DIMENSIONS = { - x: STICK_LENGTH / 6, - y: STICK_LENGTH / 6, - z: STICK_LENGTH -}; - -BALL_DISTANCE = STICK_LENGTH + BALL_SIZE; - -BALL_PROTOTYPE = { - type: "Sphere", - name: BALL_NAME, - dimensions: BALL_DIMENSIONS, - color: BALL_COLOR, - ignoreCollisions: true, - collisionsWillMove: false -}; - -// 2 millimeters -BALL_EPSILON = (.002) / BALL_DISTANCE; - -LINE_DIMENSIONS = { - x: 5, - y: 5, - z: 5 -} - -LINE_PROTOTYPE = { - type: "Line", - name: EDGE_NAME, - color: COLORS.CYAN, - dimensions: LINE_DIMENSIONS, - lineWidth: 5, - visible: true, - ignoreCollisions: true, - collisionsWillMove: false, -} - -EDGE_PROTOTYPE = LINE_PROTOTYPE; - -// var EDGE_PROTOTYPE = { -// type: "Sphere", -// name: EDGE_NAME, -// color: { red: 0, green: 255, blue: 255 }, -// //dimensions: STICK_DIMENSIONS, -// dimensions: { x: 0.02, y: 0.02, z: 0.02 }, -// rotation: rotation, -// visible: true, -// ignoreCollisions: true, -// collisionsWillMove: false -// } diff --git a/examples/libraries/omniTool.js b/examples/libraries/omniTool.js index 643b789a0e..c9f041d672 100644 --- a/examples/libraries/omniTool.js +++ b/examples/libraries/omniTool.js @@ -199,6 +199,10 @@ OmniTool.prototype.findNearestOmniEntity = function(maxDistance, selector) { return resultId; } +OmniTool.prototype.getPosition = function() { + return this.model.tipPosition; +} + OmniTool.prototype.onEnterNearestOmniEntity = function() { this.nearestOmniEntity.inside = true; this.highlighter.highlight(this.nearestOmniEntity.id); diff --git a/examples/libraries/utils.js b/examples/libraries/utils.js index b15ea6c313..6e6012cfe3 100644 --- a/examples/libraries/utils.js +++ b/examples/libraries/utils.js @@ -76,14 +76,6 @@ getEntityCustomData = function(customKey, id, 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) { var result = {}; for (var attrname in proto) { diff --git a/examples/toys/magBalls.js b/examples/toys/magBalls.js new file mode 100644 index 0000000000..8e441901a2 --- /dev/null +++ b/examples/toys/magBalls.js @@ -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(); +} diff --git a/examples/toys/magBalls/ballController.js b/examples/toys/magBalls/ballController.js deleted file mode 100644 index 0f178b2804..0000000000 --- a/examples/toys/magBalls/ballController.js +++ /dev/null @@ -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() { -} diff --git a/examples/toys/magBalls/constants.js b/examples/toys/magBalls/constants.js new file mode 100644 index 0000000000..b692f0908c --- /dev/null +++ b/examples/toys/magBalls/constants.js @@ -0,0 +1,77 @@ + +// FIXME make this editable through some script UI, so the user can customize the size of the structure built +SCALE = 0.5; +BALL_SIZE = 0.08 * SCALE; +STICK_LENGTH = 0.24 * SCALE; + +DEBUG_MAGSTICKS = true; + +BALL_NAME = "MagBall"; +EDGE_NAME = "MagStick"; + +BALL_RADIUS = BALL_SIZE / 2.0; + +BALL_SELECTION_RADIUS = BALL_RADIUS * 1.5; + +BALL_DIMENSIONS = { + x: BALL_SIZE, + y: BALL_SIZE, + z: BALL_SIZE +}; + +BALL_COLOR = { + red: 128, + green: 128, + blue: 128 +}; + +STICK_DIMENSIONS = { + x: STICK_LENGTH / 6, + y: STICK_LENGTH / 6, + z: STICK_LENGTH +}; + +BALL_DISTANCE = STICK_LENGTH + BALL_SIZE; + +BALL_PROTOTYPE = { + type: "Sphere", + name: BALL_NAME, + dimensions: BALL_DIMENSIONS, + color: BALL_COLOR, + ignoreCollisions: true, + collisionsWillMove: false +}; + +// 2 millimeters +BALL_EPSILON = (.002) / BALL_DISTANCE; + +LINE_DIMENSIONS = { + x: 5, + y: 5, + z: 5 +} + +LINE_PROTOTYPE = { + type: "Line", + name: EDGE_NAME, + color: COLORS.CYAN, + dimensions: LINE_DIMENSIONS, + lineWidth: 5, + visible: true, + ignoreCollisions: true, + collisionsWillMove: false, +} + +EDGE_PROTOTYPE = LINE_PROTOTYPE; + +// var EDGE_PROTOTYPE = { +// type: "Sphere", +// name: EDGE_NAME, +// color: { red: 0, green: 255, blue: 255 }, +// //dimensions: STICK_DIMENSIONS, +// dimensions: { x: 0.02, y: 0.02, z: 0.02 }, +// rotation: rotation, +// visible: true, +// ignoreCollisions: true, +// collisionsWillMove: false +// } diff --git a/examples/toys/magBalls/graph.js b/examples/toys/magBalls/graph.js index df02ee3628..198c1b0c16 100644 --- a/examples/toys/magBalls/graph.js +++ b/examples/toys/magBalls/graph.js @@ -101,6 +101,10 @@ Graph.prototype.getConnectedNodes = function(nodeId) { return result; } +Graph.prototype.getNodesForEdge = function(edgeId) { + return Object.keys(this.edges[edgeId]); +} + Graph.prototype.getEdgeLength = function(edgeId) { var nodesInEdge = Object.keys(this.edges[edgeId]); return this.getNodeDistance(nodesInEdge[0], nodesInEdge[1]); diff --git a/examples/toys/magBalls/handController.js b/examples/toys/magBalls/handController.js deleted file mode 100644 index 4aba43d412..0000000000 --- a/examples/toys/magBalls/handController.js +++ /dev/null @@ -1,128 +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"); -} - - diff --git a/examples/toys/magBalls/magBalls.js b/examples/toys/magBalls/magBalls.js index 187c550073..307be9f5e1 100644 --- a/examples/toys/magBalls/magBalls.js +++ b/examples/toys/magBalls/magBalls.js @@ -8,30 +8,34 @@ var UPDATE_INTERVAL = 0.1; -Script.include("graph.js"); -Script.include("edgeSpring.js"); // A collection of balls and edges connecting them. MagBalls = function() { Graph.call(this); - this.MAX_ADJUST_ITERATIONS = 100; + this.REFRESH_WAIT_TICKS = 10; + this.MAX_VARIANCE = 0.25; this.lastUpdateAge = 0; - this.stable = false; + this.stable = true; this.adjustIterations = 0; this.selectedNodes = {}; this.edgeObjects = {}; + this.unstableEdges = {}; this.refresh(); var _this = this; - Script.update.connect(function(deltaTime) { - _this.onUpdate(deltaTime); - }); + //Script.update.connect(function(deltaTime) { + // _this.onUpdate(deltaTime); + //}); Script.scriptEnding.connect(function() { _this.onCleanup(); }); + + Entities.addingEntity.connect(function(entityId) { + _this.onEntityAdded(entityId); + }); } MagBalls.prototype = Object.create( Graph.prototype ); @@ -40,14 +44,23 @@ MagBalls.prototype.onUpdate = function(deltaTime) { this.lastUpdateAge += deltaTime; if (this.lastUpdateAge > UPDATE_INTERVAL) { 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; - // logDebug("Update"); var adjusted = false; var nodeAdjustResults = {}; var fixupEdges = {}; for(var edgeId in this.edges) { + if (!this.unstableEdges[edgeId]) { + continue; + } adjusted |= this.edgeObjects[edgeId].adjust(nodeAdjustResults); } for (var nodeId in nodeAdjustResults) { @@ -72,8 +85,12 @@ MagBalls.prototype.onUpdate = function(deltaTime) { }, ((UPDATE_INTERVAL * 1000) / 2)); if (!adjusted || this.adjustIterations > this.MAX_ADJUST_ITERATIONS) { + if (adjusted) { + logDebug("Could not stabilized after " + this.MAX_ADJUST_ITERATIONS + " abandoning"); + } this.adjustIterations = 0; this.stable = true; + this.unstableEdges = {}; } } } @@ -118,7 +135,7 @@ MagBalls.prototype.findPotentialEdges = function(nodeId) { // Check distance to attempt var distance = this.getNodeDistance(nodeId, otherNodeId); var variance = this.getVariance(distance); - if (Math.abs(variance) > 0.25) { + if (Math.abs(variance) > this.MAX_VARIANCE) { continue; } @@ -127,26 +144,38 @@ MagBalls.prototype.findPotentialEdges = function(nodeId) { return variances; } -MagBalls.prototype.grabBall = function(position, maxDist) { - var selected = this.findNearestNode(position, maxDist); - if (!selected) { - selected = this.createNode({ position: position }); - } - if (selected) { - this.stable = true; - this.breakEdges(selected); - this.selectedNodes[selected] = true; - } - return selected; +MagBalls.prototype.breakEdges = function(nodeId) { + //var unstableNodes = this.findShape(Object.keys.target); + //for (var node in unstableNodes) { + // this.unstableNodes[node] = true; + //} + Graph.prototype.breakEdges.call(this, nodeId); } +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) { delete this.selectedNodes[releasedBall]; logDebug("Released ball: " + releasedBall); + var releasePosition = this.getNodePosition(releasedBall); this.stable = false; - var releasePosition = this.getNodePosition(releasedBall); // iterate through the other balls and ensure we don't intersect with // 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) { 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(); } +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) { // FIXME different balls or edges might have different ideas of variance... // let something else handle this @@ -263,8 +316,11 @@ MagBalls.prototype.refresh = function() { Script.setTimeout(function() { for (var i in deleteEdges) { var edgeId = deleteEdges[i]; - logDebug("deleting invalid edge " + edgeId); - Entities.deleteEntity(edgeId); + //logDebug("deleting invalid edge " + edgeId); + //Entities.deleteEntity(edgeId); + Entities.editEntity(edgeId, { + color: COLORS.RED + }) } }, 1000); } @@ -291,3 +347,14 @@ MagBalls.prototype.fixupEdge = function(edgeId) { 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; + } +} \ No newline at end of file diff --git a/examples/toys/magBalls/menuController.js b/examples/toys/magBalls/menuController.js deleted file mode 100644 index 0a076d1ff8..0000000000 --- a/examples/toys/magBalls/menuController.js +++ /dev/null @@ -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(); -} From b6ef2e314ff04f41621bee27d247e5ad26f408e9 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 31 Aug 2015 22:48:30 -0700 Subject: [PATCH 7/9] Adding 'web3d' overlay type --- interface/src/ui/overlays/Overlays.cpp | 3 + interface/src/ui/overlays/Web3DOverlay.cpp | 163 +++++++++++++++++++++ interface/src/ui/overlays/Web3DOverlay.h | 50 +++++++ 3 files changed, 216 insertions(+) create mode 100644 interface/src/ui/overlays/Web3DOverlay.cpp create mode 100644 interface/src/ui/overlays/Web3DOverlay.h diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 39b8892f13..563d90f6b9 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -30,6 +30,7 @@ #include "Grid3DOverlay.h" #include "TextOverlay.h" #include "Text3DOverlay.h" +#include "Web3DOverlay.h" Overlays::Overlays() : _nextOverlayID(1) { @@ -170,6 +171,8 @@ unsigned int Overlays::addOverlay(const QString& type, const QScriptValue& prope thisOverlay = std::make_shared(Application::getInstance()->getEntityClipboardRenderer()); } else if (type == ModelOverlay::TYPE) { thisOverlay = std::make_shared(); + } else if (type == Web3DOverlay::TYPE) { + thisOverlay = std::make_shared(); } if (thisOverlay) { diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp new file mode 100644 index 0000000000..c173c5927f --- /dev/null +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -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 +#include +#include + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// #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()->getWhiteTexture()); + } + + batch.setModelTransform(transform); + DependencyManager::get()->bindSimpleProgram(batch, true, false, false, true); + DependencyManager::get()->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); +} diff --git a/interface/src/ui/overlays/Web3DOverlay.h b/interface/src/ui/overlays/Web3DOverlay.h new file mode 100644 index 0000000000..59c8fae0fe --- /dev/null +++ b/interface/src/ui/overlays/Web3DOverlay.h @@ -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 From 1f83d04423d476b25310783ecb1c6a2b5ff2c2e3 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 1 Sep 2015 14:55:09 -0700 Subject: [PATCH 8/9] Fix oculus crash on switching display plugin to something else --- interface/src/Application.cpp | 133 +++++++++++------- interface/src/Application.h | 1 + interface/src/PluginContainerProxy.cpp | 12 -- .../src/display-plugins/DisplayPlugin.h | 6 + .../src/display-plugins/NullDisplayPlugin.cpp | 1 + .../src/display-plugins/NullDisplayPlugin.h | 1 + .../display-plugins/OpenGLDisplayPlugin.cpp | 11 +- .../src/display-plugins/OpenGLDisplayPlugin.h | 3 +- .../oculus/OculusDisplayPlugin.cpp | 6 - libraries/shared/src/Finally.h | 27 ++++ 10 files changed, 133 insertions(+), 68 deletions(-) create mode 100644 libraries/shared/src/Finally.h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e0726311c9..391ba748d0 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -64,6 +64,7 @@ #include #include +#include #include #include #include @@ -1009,6 +1010,16 @@ void Application::paintGL() { if (nullptr == _displayPlugin) { 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(); displayPlugin->preRender(); _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 glWaitSync(sync, 0, GL_TIMEOUT_IGNORED); glDeleteSync(sync); - + { PROFILE_RANGE(__FUNCTION__ "/pluginDisplay"); displayPlugin->display(finalTexture, toGlm(size)); @@ -1219,7 +1230,6 @@ void Application::paintGL() { _frameCount++; Stats::getInstance()->setRenderDetails(renderArgs._details); - // Reset the gpu::Context Stages // Back to the default framebuffer; gpu::Batch batch; @@ -4703,53 +4713,68 @@ void Application::updateDisplayMode() { auto offscreenUi = DependencyManager::get(); DisplayPluginPointer oldDisplayPlugin = _displayPlugin; - if (oldDisplayPlugin != newDisplayPlugin) { - 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()->hmdTools(false); - } - } - emit activeDisplayPluginChanged(); - resetSensors(); + if (newDisplayPlugin == oldDisplayPlugin) { + return; } + + // 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()->hmdTools(false); + } + } + emit activeDisplayPluginChanged(); + resetSensors(); 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"; } + +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(); +} diff --git a/interface/src/Application.h b/interface/src/Application.h index 9b819eae53..6c3b68cf6f 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -673,6 +673,7 @@ private: int _simsPerSecondReport = 0; quint64 _lastSimsPerSecondUpdate = 0; bool _isForeground = true; // starts out assumed to be in foreground + bool _inPaint = false; friend class PluginContainerProxy; }; diff --git a/interface/src/PluginContainerProxy.cpp b/interface/src/PluginContainerProxy.cpp index e172dbbd9e..4ef755bd1b 100644 --- a/interface/src/PluginContainerProxy.cpp +++ b/interface/src/PluginContainerProxy.cpp @@ -147,15 +147,3 @@ void PluginContainerProxy::showDisplayPluginsTools() { QGLWidget* PluginContainerProxy::getPrimarySurface() { 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(); -} diff --git a/libraries/display-plugins/src/display-plugins/DisplayPlugin.h b/libraries/display-plugins/src/display-plugins/DisplayPlugin.h index a9220d68f6..86cfabe724 100644 --- a/libraries/display-plugins/src/display-plugins/DisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/DisplayPlugin.h @@ -57,6 +57,11 @@ public: // 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 * render timing related calls (for instance, the Oculus begin frame timing @@ -120,6 +125,7 @@ public: virtual void resetSensors() {} virtual float devicePixelRatio() { return 1.0; } + static const QString& MENU_PATH(); signals: void recommendedFramebufferSizeChanged(const QSize & size); diff --git a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp index e5a96d167e..1f8242f081 100644 --- a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.cpp @@ -30,3 +30,4 @@ void NullDisplayPlugin::finishFrame() {} void NullDisplayPlugin::activate() {} void NullDisplayPlugin::deactivate() {} +void NullDisplayPlugin::stop() {} diff --git a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h index 90e717b5ee..bb1ab2d97f 100644 --- a/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/NullDisplayPlugin.h @@ -17,6 +17,7 @@ public: void activate() override; void deactivate() override; + void stop() override; virtual glm::uvec2 getRecommendedRenderSize() const override; virtual bool hasFocus() const override; diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 4101bebfa8..cfe101e1ab 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -16,7 +16,9 @@ OpenGLDisplayPlugin::OpenGLDisplayPlugin() { connect(&_timer, &QTimer::timeout, this, [&] { - emit requestRender(); + if (_active) { + emit requestRender(); + } }); } @@ -56,10 +58,17 @@ void OpenGLDisplayPlugin::customizeContext() { } void OpenGLDisplayPlugin::activate() { + _active = true; _timer.start(1); } +void OpenGLDisplayPlugin::stop() { + _active = false; + _timer.stop(); +} + void OpenGLDisplayPlugin::deactivate() { + _active = false; _timer.stop(); makeCurrent(); diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h index 0dc94b72f5..52715ebde7 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.h @@ -25,7 +25,7 @@ public: virtual void activate() override; virtual void deactivate() override; - + virtual void stop() override; virtual bool eventFilter(QObject* receiver, QEvent* event) override; virtual void display(GLuint sceneTexture, const glm::uvec2& sceneSize) override; @@ -44,6 +44,7 @@ protected: mutable QTimer _timer; ProgramPtr _program; ShapeWrapperPtr _plane; + bool _active{ false }; bool _vsyncSupported{ false }; }; diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp index ff218987ec..c214b7e627 100644 --- a/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp @@ -350,11 +350,6 @@ void OculusDisplayPlugin::deactivate() { } void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) { - static bool inDisplay = false; - if (inDisplay) { - return; - } - inDisplay = true; #if (OVR_MAJOR_VERSION >= 6) using namespace oglplus; // Need to make sure only the display plugin is responsible for @@ -420,7 +415,6 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi ++_frameIndex; #endif - inDisplay = false; } // Pass input events on to the application diff --git a/libraries/shared/src/Finally.h b/libraries/shared/src/Finally.h new file mode 100644 index 0000000000..59e8be0228 --- /dev/null +++ b/libraries/shared/src/Finally.h @@ -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 + +#pragma once +#ifndef hifi_Finally_h +#define hifi_Finally_h + +class Finally { +public: + template + Finally(F f) : _f(f) {} + ~Finally() { _f(); } +private: + std::function _f; +}; + +#endif From 2901155a7bc1550e9172d38513280cbddeede492 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 1 Sep 2015 15:34:25 -0700 Subject: [PATCH 9/9] Fix Oculus mirroring to window --- .../oculus/OculusDisplayPlugin.cpp | 34 ++++++++----------- .../oculus/OculusDisplayPlugin.h | 2 +- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp index c214b7e627..2ed5e69fe7 100644 --- a/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.cpp @@ -325,19 +325,18 @@ void OculusDisplayPlugin::customizeContext() { //_texture = DependencyManager::get()-> // getImageTexture(PathUtils::resourcesPath() + "/images/cube_texture.png"); uvec2 mirrorSize = toGlm(_window->geometry().size()); - _mirrorFbo = MirrorFboPtr(new MirrorFramebufferWrapper(_hmd)); - _mirrorFbo->Init(mirrorSize); _sceneFbo = SwapFboPtr(new SwapFramebufferWrapper(_hmd)); _sceneFbo->Init(getRecommendedRenderSize()); #endif + enableVsync(false); + isVsyncEnabled(); } void OculusDisplayPlugin::deactivate() { #if (OVR_MAJOR_VERSION >= 6) makeCurrent(); _sceneFbo.reset(); - _mirrorFbo.reset(); doneCurrent(); PerformanceTimer::setActive(false); @@ -378,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 _sceneFbo->Increment or we're be using the wrong texture */ - _sceneFbo->Bound(Framebuffer::Target::Read, [&] { - glBlitFramebuffer( - 0, 0, _sceneFbo->size.x, _sceneFbo->size.y, - 0, 0, windowSize.x, windowSize.y, - GL_COLOR_BUFFER_BIT, GL_NEAREST); - }); + if (_enableMirror) { + _sceneFbo->Bound(Framebuffer::Target::Read, [&] { + glBlitFramebuffer( + 0, 0, _sceneFbo->size.x, _sceneFbo->size.y, + 0, 0, windowSize.x, windowSize.y, + GL_COLOR_BUFFER_BIT, GL_NEAREST); + }); + } { PerformanceTimer("OculusSubmit"); @@ -404,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 will contain the post-distorted and fully composited scene regardless of how many layers we send. + Currently generates an error. */ //auto mirrorSize = _mirrorFbo->size; //_mirrorFbo->Bound(Framebuffer::Target::Read, [&] { @@ -419,16 +421,6 @@ void OculusDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSi // Pass input events on to the application bool OculusDisplayPlugin::eventFilter(QObject* receiver, QEvent* event) { -#if (OVR_MAJOR_VERSION >= 6) - if (event->type() == QEvent::Resize) { - QResizeEvent* resizeEvent = static_cast(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); } @@ -438,7 +430,9 @@ bool OculusDisplayPlugin::eventFilter(QObject* receiver, QEvent* event) { otherwise the swapbuffer delay will interefere with the framerate of the headset */ void OculusDisplayPlugin::finishFrame() { - //swapBuffers(); + if (_enableMirror) { + swapBuffers(); + } doneCurrent(); }; diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.h index 42f8d5763f..d30356daa0 100644 --- a/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusDisplayPlugin.h @@ -58,6 +58,7 @@ private: mat4 _compositeEyeProjections[2]; uvec2 _desiredFramebufferSize; ovrTrackingState _trackingState; + bool _enableMirror{ false }; #if (OVR_MAJOR_VERSION >= 6) ovrHmd _hmd; @@ -70,7 +71,6 @@ private: ovrLayerEyeFov& getSceneLayer(); ovrHmdDesc _hmdDesc; SwapFboPtr _sceneFbo; - MirrorFboPtr _mirrorFbo; ovrLayerEyeFov _sceneLayer; #endif #if (OVR_MAJOR_VERSION == 7)