diff --git a/cmake/externals/neuron/CMakeLists.txt b/cmake/externals/neuron/CMakeLists.txt new file mode 100644 index 0000000000..6936725571 --- /dev/null +++ b/cmake/externals/neuron/CMakeLists.txt @@ -0,0 +1,56 @@ +include(ExternalProject) + +set(EXTERNAL_NAME neuron) + +string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) + +set(NEURON_URL "https://s3.amazonaws.com/hifi-public/dependencies/neuron_datareader_b.12.zip") +set(NEURON_URL_MD5 "0ab54ca04c9cc8094e0fa046c226e574") + +ExternalProject_Add(${EXTERNAL_NAME} + URL ${NEURON_URL} + URL_MD5 ${NEURON_URL_MD5} + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + LOG_DOWNLOAD 1) + +ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR) + +set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals") + +# set include dir +if(WIN32) + set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS "${SOURCE_DIR}/NeuronDataReader_Windows/include" CACHE TYPE INTERNAL) +elseif(APPLE) + set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS "${SOURCE_DIR}/NeuronDataReader_Mac/include" CACHE TYPE INTERNAL) +else() + # Unsupported +endif() + +if(WIN32) + + if("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") + set(ARCH_DIR "x64") + else() + set(ARCH_DIR "x86") + endif() + + set(${EXTERNAL_NAME_UPPER}_LIB_PATH "${SOURCE_DIR}/NeuronDataReader_Windows/lib/${ARCH_DIR}") + set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/NeuronDataReader.lib" CACHE TYPE INTERNAL) + set(${EXTERNAL_NAME_UPPER}_LIBRARIES "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/NeuronDataReader.lib" CACHE TYPE INTERNAL) + + add_paths_to_fixup_libs("${${EXTERNAL_NAME_UPPER}_LIB_PATH}") + +elseif(APPLE) + + set(${EXTERNAL_NAME_UPPER}_LIB_PATH "${SOURCE_DIR}/NeuronDataReader_Mac/dylib") + set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/NeuronDataReader.dylib" CACHE TYPE INTERNAL) + set(${EXTERNAL_NAME_UPPER}_LIBRARIES "${${EXTERNAL_NAME_UPPER}_LIB_PATH}/NeuronDataReader.dylib" CACHE TYPE INTERNAL) + + add_paths_to_fixup_libs("${${EXTERNAL_NAME_UPPER}_LIB_PATH}") + +else() + # UNSUPPORTED +endif() + diff --git a/cmake/macros/TargetNeuron.cmake b/cmake/macros/TargetNeuron.cmake new file mode 100644 index 0000000000..11a92e68ad --- /dev/null +++ b/cmake/macros/TargetNeuron.cmake @@ -0,0 +1,17 @@ +# +# Copyright 2015 High Fidelity, Inc. +# Created by Anthony J. Thibault on 2015/12/21 +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# +macro(TARGET_NEURON) + # Neuron data reader is only available on these platforms + if (WIN32 OR APPLE) + add_dependency_external_projects(neuron) + find_package(Neuron REQUIRED) + target_include_directories(${TARGET_NAME} PRIVATE ${NEURON_INCLUDE_DIRS}) + target_link_libraries(${TARGET_NAME} ${NEURON_LIBRARIES}) + add_definitions(-DHAVE_NEURON) + endif(WIN32 OR APPLE) +endmacro() diff --git a/cmake/modules/FindNeuron.cmake b/cmake/modules/FindNeuron.cmake new file mode 100644 index 0000000000..6a93b3a016 --- /dev/null +++ b/cmake/modules/FindNeuron.cmake @@ -0,0 +1,28 @@ +# +# FindNeuron.cmake +# +# Try to find the Perception Neuron SDK +# +# You must provide a NEURON_ROOT_DIR which contains lib and include directories +# +# Once done this will define +# +# NEURON_FOUND - system found Neuron SDK +# NEURON_INCLUDE_DIRS - the Neuron SDK include directory +# NEURON_LIBRARIES - Link this to use Neuron +# +# Created on 12/21/2015 by Anthony J. Thibault +# 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 +# + +include(SelectLibraryConfigurations) +select_library_configurations(NEURON) + +set(NEURON_REQUIREMENTS NEURON_INCLUDE_DIRS NEURON_LIBRARIES) +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Neuron DEFAULT_MSG NEURON_INCLUDE_DIRS NEURON_LIBRARIES) +mark_as_advanced(NEURON_LIBRARIES NEURON_INCLUDE_DIRS NEURON_SEARCH_DIRS) + diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 3c8f1f0014..8af8731c8d 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -23,8 +23,9 @@ var WANT_DEBUG = false; // these tune time-averaging and "on" value for analog trigger // -var TRIGGER_SMOOTH_RATIO = 0.1; // 0.0 disables smoothing of trigger value -var TRIGGER_ON_VALUE = 0.4; +var TRIGGER_SMOOTH_RATIO = 0.1; // Time averaging of trigger - 0.0 disables smoothing +var TRIGGER_ON_VALUE = 0.4; // Squeezed just enough to activate search or near grab +var TRIGGER_GRAB_VALUE = 0.85; // Squeezed far enough to complete distant grab var TRIGGER_OFF_VALUE = 0.15; var BUMPER_ON_VALUE = 0.5; @@ -96,7 +97,7 @@ var MSEC_PER_SEC = 1000.0; var LIFETIME = 10; var ACTION_TTL = 15; // seconds var ACTION_TTL_REFRESH = 5; -var PICKS_PER_SECOND_PER_HAND = 5; +var PICKS_PER_SECOND_PER_HAND = 60; var MSECS_PER_SEC = 1000.0; var GRABBABLE_PROPERTIES = [ "position", @@ -123,8 +124,8 @@ var blacklist = []; //we've created various ways of visualizing looking for and moving distant objects var USE_ENTITY_LINES_FOR_SEARCHING = false; -var USE_OVERLAY_LINES_FOR_SEARCHING = false; -var USE_PARTICLE_BEAM_FOR_SEARCHING = true; +var USE_OVERLAY_LINES_FOR_SEARCHING = true; +var USE_PARTICLE_BEAM_FOR_SEARCHING = false; var USE_ENTITY_LINES_FOR_MOVING = false; var USE_OVERLAY_LINES_FOR_MOVING = false; @@ -285,12 +286,17 @@ function MyController(hand) { //for visualizations this.overlayLine = null; this.particleBeam = null; - + //for lights this.spotlight = null; this.pointlight = null; this.overlayLine = null; - + this.searchSphere = null; + + // how far from camera to search intersection? + this.intersectionDistance = 0.0; + this.searchSphereDistance = 0.0; + this.ignoreIK = false; this.offsetPosition = Vec3.ZERO; this.offsetRotation = Quat.IDENTITY; @@ -395,7 +401,7 @@ function MyController(hand) { userData: JSON.stringify({ grabbableKey: { grabbable: false - } + } }) }); } else { @@ -409,6 +415,23 @@ function MyController(hand) { } }; + var SEARCH_SPHERE_ALPHA = 0.5; + this.searchSphereOn = function(location, size, color) { + if (this.searchSphere === null) { + var sphereProperties = { + position: location, + size: size, + color: color, + alpha: SEARCH_SPHERE_ALPHA, + solid: true, + visible: true + } + this.searchSphere = Overlays.addOverlay("sphere", sphereProperties); + } else { + Overlays.editOverlay(this.searchSphere, { position: location, size: size, color: color, visible: true }); + } + } + this.overlayLineOn = function(closePoint, farPoint, color) { if (this.overlayLine === null) { var lineProperties = { @@ -452,7 +475,7 @@ function MyController(hand) { this.createParticleBeam(position, finalRotation, color, speed, spread, lifespan); } else { this.updateParticleBeam(position, finalRotation, color, speed, spread, lifespan); - } + } }; this.handleDistantParticleBeam = function(handPosition, objectPosition, color) { @@ -540,12 +563,12 @@ function MyController(hand) { Entities.editEntity(this.particleBeam, { rotation: orientation, position: position, - visible: true, - color: color, + visible: true, + color: color, emitSpeed: speed, speedSpread: spread, lifespan: lifespan - }) + }) }; @@ -561,7 +584,7 @@ function MyController(hand) { x: 1, y: 0, z: 0 - }); + }); return { p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, MODEL_LIGHT_POSITION)), @@ -654,6 +677,17 @@ function MyController(hand) { this.overlayLine = null; }; + this.searchSphereOff = function() { + if (this.searchSphere !== null) { + //Overlays.editOverlay(this.searchSphere, { visible: false }); + Overlays.deleteOverlay(this.searchSphere); + this.searchSphere = null; + this.searchSphereDistance = 0.0; + this.intersectionDistance = 0.0; + } + + }; + this.particleBeamOff = function() { if (this.particleBeam !== null) { Entities.editEntity(this.particleBeam, { @@ -687,6 +721,7 @@ function MyController(hand) { if (USE_PARTICLE_BEAM_FOR_SEARCHING === true || USE_PARTICLE_BEAM_FOR_MOVING === true) { this.particleBeamOff(); } + this.searchSphereOff(); }; this.triggerPress = function(value) { @@ -704,6 +739,10 @@ function MyController(hand) { (triggerValue * (1.0 - TRIGGER_SMOOTH_RATIO)); }; + this.triggerSmoothedGrab = function() { + return this.triggerValue > TRIGGER_GRAB_VALUE; + }; + this.triggerSmoothedSqueezed = function() { return this.triggerValue > TRIGGER_ON_VALUE; }; @@ -712,11 +751,6 @@ function MyController(hand) { return this.triggerValue < TRIGGER_OFF_VALUE; }; - this.triggerSqueezed = function() { - var triggerValue = this.rawTriggerValue; - return triggerValue > TRIGGER_ON_VALUE; - }; - this.bumperSqueezed = function() { return _this.rawBumperValue > BUMPER_ON_VALUE; }; @@ -726,15 +760,15 @@ function MyController(hand) { }; this.off = function() { - if (this.triggerSmoothedSqueezed()) { + if (this.triggerSmoothedSqueezed() || this.bumperSqueezed()) { this.lastPickTime = 0; - this.setState(STATE_SEARCHING); - return; - } - if (this.bumperSqueezed()) { - this.lastPickTime = 0; - this.setState(STATE_EQUIP_SEARCHING); - return; + var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + this.startingHandRotation = Controller.getPoseValue(controllerHandInput).rotation; + if (this.triggerSmoothedSqueezed()) { + this.setState(STATE_SEARCHING); + } else { + this.setState(STATE_EQUIP_SEARCHING); + } } }; @@ -746,15 +780,20 @@ function MyController(hand) { return; } - // the trigger is being pressed, do a ray test + // the trigger is being pressed, so do a ray test to see what we are hitting var handPosition = this.getHandPosition(); + + var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + var currentHandRotation = Controller.getPoseValue(controllerHandInput).rotation; + var handDeltaRotation = Quat.multiply(currentHandRotation, Quat.inverse(this.startingHandRotation)); + var distantPickRay = { - origin: handPosition, - direction: Quat.getUp(this.getHandRotation()), + origin: Camera.position, + direction: Quat.getFront(Quat.multiply(Camera.orientation, handDeltaRotation)), length: PICK_MAX_DISTANCE }; - // don't pick 60x per second. + // Pick at some maximum rate, not always var pickRays = []; var now = Date.now(); if (now - this.lastPickTime > MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { @@ -779,19 +818,28 @@ function MyController(hand) { }) } + Messages.sendMessage('Hifi-Light-Overlay-Ray-Check', JSON.stringify(pickRayBacked)); + var intersection; if (USE_BLACKLIST === true && blacklist.length !== 0) { - intersection = Entities.findRayIntersection(pickRay, true, [], blacklist); + intersection = Entities.findRayIntersection(pickRayBacked, true, [], blacklist); } else { intersection = Entities.findRayIntersection(pickRayBacked, true); } - + + if (intersection.intersects) { + // the ray is intersecting something we can move. - var intersectionDistance = Vec3.distance(pickRay.origin, intersection.intersection); + this.intersectionDistance = Vec3.distance(pickRay.origin, intersection.intersection); var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, intersection.entityID, DEFAULT_GRABBABLE_DATA); + var defaultDisableNearGrabData = { + disableNearGrab: false + }; + //sometimes we want things to stay right where they are when we let go. + var disableNearGrabData = getEntityCustomData('handControllerKey', intersection.entityID, defaultDisableNearGrabData); if (intersection.properties.name == "Grab Debug Entity") { continue; @@ -800,11 +848,11 @@ function MyController(hand) { if (typeof grabbableData.grabbable !== 'undefined' && !grabbableData.grabbable) { continue; } - if (intersectionDistance > pickRay.length) { + if (this.intersectionDistance > pickRay.length) { // too far away for this ray. continue; } - if (intersectionDistance <= NEAR_PICK_MAX_DISTANCE) { + if (this.intersectionDistance <= NEAR_PICK_MAX_DISTANCE) { // the hand is very close to the intersected object. go into close-grabbing mode. if (grabbableData.wantsTrigger) { this.grabbedEntity = intersection.entityID; @@ -813,7 +861,11 @@ function MyController(hand) { } else if (!intersection.properties.locked) { this.grabbedEntity = intersection.entityID; if (this.state == STATE_SEARCHING) { - this.setState(STATE_NEAR_GRABBING); + if (disableNearGrabData.disableNearGrab !== true) { + this.setState(STATE_NEAR_GRABBING); + } else { + //disable near grab on this thing + } } else { // equipping if (typeof grabbableData.spatialKey !== 'undefined') { // TODO @@ -838,11 +890,11 @@ function MyController(hand) { // this.setState(STATE_EQUIP_SPRING); this.setState(STATE_EQUIP); return; - } else if (this.state == STATE_SEARCHING) { + } else if ((this.state == STATE_SEARCHING) && this.triggerSmoothedGrab()) { this.setState(STATE_DISTANCE_HOLDING); return; } - } else if (grabbableData.wantsTrigger) { + } else if (grabbableData.wantsTrigger && this.triggerSmoothedGrab()) { this.grabbedEntity = intersection.entityID; this.setState(STATE_FAR_TRIGGER); return; @@ -851,6 +903,7 @@ function MyController(hand) { } } + // forward ray test failed, try sphere test. if (WANT_DEBUG) { Entities.addEntity({ @@ -936,7 +989,18 @@ function MyController(hand) { this.setState(STATE_NEAR_TRIGGER); return; } else if (!props.locked && props.collisionsWillMove) { - this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP) + var defaultDisableNearGrabData = { + disableNearGrab: false + }; + //sometimes we want things to stay right where they are when we let go. + var disableNearGrabData = getEntityCustomData('handControllerKey', this.grabbedEntity, defaultDisableNearGrabData); + if (disableNearGrabData.disableNearGrab === true) { + //do nothing because near grab is disabled for this object + } else { + this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP) + + } + return; } } @@ -946,14 +1010,23 @@ function MyController(hand) { this.lineOn(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); } - if (USE_OVERLAY_LINES_FOR_SEARCHING === true) { - this.overlayLineOn(distantPickRay.origin, Vec3.sum(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, LINE_LENGTH)), NO_INTERSECT_COLOR); - } - if (USE_PARTICLE_BEAM_FOR_SEARCHING === true) { this.handleParticleBeam(distantPickRay.origin, this.getHandRotation(), NO_INTERSECT_COLOR); } - + if (this.intersectionDistance > 0) { + var SPHERE_INTERSECTION_SIZE = 0.011; + var SEARCH_SPHERE_FOLLOW_RATE = 0.50; + var SEARCH_SPHERE_CHASE_DROP = 0.2; + this.searchSphereDistance = this.searchSphereDistance * SEARCH_SPHERE_FOLLOW_RATE + this.intersectionDistance * (1.0 - SEARCH_SPHERE_FOLLOW_RATE); + var searchSphereLocation = Vec3.sum(distantPickRay.origin, Vec3.multiply(distantPickRay.direction, this.searchSphereDistance)); + searchSphereLocation.y -= ((this.intersectionDistance - this.searchSphereDistance) / this.intersectionDistance) * SEARCH_SPHERE_CHASE_DROP; + this.searchSphereOn(searchSphereLocation, SPHERE_INTERSECTION_SIZE * this.intersectionDistance, this.triggerSmoothedGrab() ? INTERSECT_COLOR : NO_INTERSECT_COLOR); + if (USE_OVERLAY_LINES_FOR_SEARCHING === true) { + var OVERLAY_BEAM_SETBACK = 0.9; + var startBeam = Vec3.sum(handPosition, Vec3.multiply(Vec3.subtract(searchSphereLocation, handPosition), OVERLAY_BEAM_SETBACK)); + this.overlayLineOn(startBeam, searchSphereLocation, this.triggerSmoothedGrab() ? INTERSECT_COLOR : NO_INTERSECT_COLOR); + } + } }; this.distanceHolding = function() { @@ -1095,22 +1168,54 @@ function MyController(hand) { this.currentObjectRotation = Quat.multiply(handChange, this.currentObjectRotation); Entities.callEntityMethod(this.grabbedEntity, "continueDistantGrab"); - // mix in head motion - if (MOVE_WITH_HEAD) { - var objDistance = Vec3.length(objectToAvatar); - var before = Vec3.multiplyQbyV(this.currentCameraOrientation, { - x: 0.0, - y: 0.0, - z: objDistance - }); - var after = Vec3.multiplyQbyV(Camera.orientation, { - x: 0.0, - y: 0.0, - z: objDistance - }); - var change = Vec3.subtract(before, after); - this.currentCameraOrientation = Camera.orientation; - this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, change); + + var defaultMoveWithHeadData = { + disableMoveWithHead: false + }; + + var handControllerData = getEntityCustomData('handControllerKey', this.grabbedEntity, defaultMoveWithHeadData); + + if (handControllerData.disableMoveWithHead !== true) { + // mix in head motion + if (MOVE_WITH_HEAD) { + var objDistance = Vec3.length(objectToAvatar); + var before = Vec3.multiplyQbyV(this.currentCameraOrientation, { + x: 0.0, + y: 0.0, + z: objDistance + }); + var after = Vec3.multiplyQbyV(Camera.orientation, { + x: 0.0, + y: 0.0, + z: objDistance + }); + var change = Vec3.subtract(before, after); + this.currentCameraOrientation = Camera.orientation; + this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, change); + } + } else { + // print('should not head move!'); + } + + + var defaultConstraintData = { + axisStart: false, + axisEnd: false, + } + + var constraintData = getEntityCustomData('lightModifierKey', this.grabbedEntity, defaultConstraintData); + var clampedVector; + var targetPosition; + if (constraintData.axisStart !== false) { + clampedVector = this.projectVectorAlongAxis(this.currentObjectPosition, constraintData.axisStart, constraintData.axisEnd); + targetPosition = clampedVector; + } else { + targetPosition = { + x: this.currentObjectPosition.x, + y: this.currentObjectPosition.y, + z: this.currentObjectPosition.z + } + } @@ -1133,7 +1238,7 @@ function MyController(hand) { } Entities.updateAction(this.grabbedEntity, this.actionID, { - targetPosition: this.currentObjectPosition, + targetPosition: targetPosition, linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, targetRotation: this.currentObjectRotation, angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, @@ -1141,98 +1246,122 @@ function MyController(hand) { }); this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); + }; - this.nearGrabbing = function() { - var now = Date.now(); - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); + this.projectVectorAlongAxis = function(position, axisStart, axisEnd) { - if (this.state == STATE_NEAR_GRABBING && this.triggerSmoothedReleased()) { - this.setState(STATE_RELEASE); - Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); - return; - } + var aPrime = Vec3.subtract(position, axisStart); - this.turnOffVisualizations(); - var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); - this.activateEntity(this.grabbedEntity, grabbedProperties); - if (grabbedProperties.collisionsWillMove && NEAR_GRABBING_KINEMATIC) { - Entities.editEntity(this.grabbedEntity, { - collisionsWillMove: false - }); - } + var bPrime = Vec3.subtract(axisEnd, axisStart); - var handRotation = this.getHandRotation(); - var handPosition = this.getHandPosition(); - var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); - var objectRotation = grabbedProperties.rotation; - var currentObjectPosition = grabbedProperties.position; - var offset = Vec3.subtract(currentObjectPosition, handPosition); - if (this.state != STATE_NEAR_GRABBING && grabbableData.spatialKey) { - // if an object is "equipped" and has a spatialKey, use it. - this.ignoreIK = grabbableData.spatialKey.ignoreIK ? grabbableData.spatialKey.ignoreIK : false; - if (grabbableData.spatialKey.relativePosition || grabbableData.spatialKey.rightRelativePosition - || grabbableData.spatialKey.leftRelativePosition) { - this.offsetPosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey); - } else { - this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset); + var bPrimeMagnitude = Vec3.length(bPrime); + + var dotProduct = Vec3.dot(aPrime, bPrime); + + + var scalar = dotProduct / bPrimeMagnitude; + + if (scalar < 0) { + scalar = 0; } - if (grabbableData.spatialKey.relativeRotation || grabbableData.spatialKey.rightRelativeRotation - || grabbableData.spatialKey.leftRelativeRotation) { + + if (scalar > 1) { + scalar = 1; + } + + var projection = Vec3.sum(axisStart, Vec3.multiply(scalar, Vec3.normalize(bPrime))); + + return projection + + }, + + this.nearGrabbing = function() { + var now = Date.now(); + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); + + if (this.state == STATE_NEAR_GRABBING && this.triggerSmoothedReleased()) { + this.setState(STATE_RELEASE); + Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); + return; + } + + this.lineOff(); + this.overlayLineOff(); + + var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES); + this.activateEntity(this.grabbedEntity, grabbedProperties); + if (grabbedProperties.collisionsWillMove && NEAR_GRABBING_KINEMATIC) { + Entities.editEntity(this.grabbedEntity, { + collisionsWillMove: false + }); + } + + var handRotation = this.getHandRotation(); + var handPosition = this.getHandPosition(); + + var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); + + if (this.state != STATE_NEAR_GRABBING && grabbableData.spatialKey) { + // if an object is "equipped" and has a spatialKey, use it. + this.ignoreIK = grabbableData.spatialKey.ignoreIK ? grabbableData.spatialKey.ignoreIK : false; + this.offsetPosition = getSpatialOffsetPosition(this.hand, grabbableData.spatialKey); this.offsetRotation = getSpatialOffsetRotation(this.hand, grabbableData.spatialKey); } else { + this.ignoreIK = false; + + var objectRotation = grabbedProperties.rotation; this.offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation); - } - } else { - this.ignoreIK = false; - this.offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation); - this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset); - } - this.actionID = NULL_ACTION_ID; - this.actionID = Entities.addAction("hold", this.grabbedEntity, { - hand: this.hand === RIGHT_HAND ? "right" : "left", - timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, - relativePosition: this.offsetPosition, - relativeRotation: this.offsetRotation, - ttl: ACTION_TTL, - kinematic: NEAR_GRABBING_KINEMATIC, - kinematicSetVelocity: true, - ignoreIK: this.ignoreIK - }); - if (this.actionID === NULL_ACTION_ID) { - this.actionID = null; - } else { - this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); - if (this.state == STATE_NEAR_GRABBING) { - this.setState(STATE_CONTINUE_NEAR_GRABBING); + var currentObjectPosition = grabbedProperties.position; + var offset = Vec3.subtract(currentObjectPosition, handPosition); + this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset); + } + + this.actionID = NULL_ACTION_ID; + this.actionID = Entities.addAction("hold", this.grabbedEntity, { + hand: this.hand === RIGHT_HAND ? "right" : "left", + timeScale: NEAR_GRABBING_ACTION_TIMEFRAME, + relativePosition: this.offsetPosition, + relativeRotation: this.offsetRotation, + ttl: ACTION_TTL, + kinematic: NEAR_GRABBING_KINEMATIC, + kinematicSetVelocity: true, + ignoreIK: this.ignoreIK + }); + if (this.actionID === NULL_ACTION_ID) { + this.actionID = null; } else { - // equipping - Entities.callEntityMethod(this.grabbedEntity, "startEquip", [JSON.stringify(this.hand)]); - this.startHandGrasp(); + this.actionTimeout = now + (ACTION_TTL * MSEC_PER_SEC); + if (this.state == STATE_NEAR_GRABBING) { + this.setState(STATE_CONTINUE_NEAR_GRABBING); + } else { + // equipping + Entities.callEntityMethod(this.grabbedEntity, "startEquip", [JSON.stringify(this.hand)]); + this.startHandGrasp(); + + this.setState(STATE_CONTINUE_EQUIP_BD); + } + + if (this.hand === RIGHT_HAND) { + Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); + } else { + Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); + } + + Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); + + Entities.callEntityMethod(this.grabbedEntity, "startNearGrab"); - this.setState(STATE_CONTINUE_EQUIP_BD); } - if (this.hand === RIGHT_HAND) { - Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); - } else { - Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); - } + this.currentHandControllerTipPosition = + (this.hand === RIGHT_HAND) ? MyAvatar.rightHandTipPosition : MyAvatar.leftHandTipPosition; - Entities.callEntityMethod(this.grabbedEntity, "setHand", [this.hand]); - - Entities.callEntityMethod(this.grabbedEntity, "startNearGrab"); - - } - - this.currentHandControllerTipPosition = - (this.hand === RIGHT_HAND) ? MyAvatar.rightHandTipPosition : MyAvatar.leftHandTipPosition; - - this.currentObjectTime = Date.now(); - }; + this.currentObjectTime = Date.now(); + }; this.continueNearGrabbing = function() { if (this.state == STATE_CONTINUE_NEAR_GRABBING && this.triggerSmoothedReleased()) { @@ -1421,7 +1550,7 @@ function MyController(hand) { } if (USE_ENTITY_LINES_FOR_MOVING === true) { - this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); + this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); } Entities.callEntityMethod(this.grabbedEntity, "continueFarTrigger"); @@ -1502,7 +1631,34 @@ function MyController(hand) { if (this.grabbedEntity !== null) { if (this.actionID !== null) { - Entities.deleteAction(this.grabbedEntity, this.actionID); + //add velocity whatnot + var defaultReleaseVelocityData = { + disableReleaseVelocity: false + }; + //sometimes we want things to stay right where they are when we let go. + var releaseVelocityData = getEntityCustomData('handControllerKey', this.grabbedEntity, defaultReleaseVelocityData); + if (releaseVelocityData.disableReleaseVelocity === true) { + Entities.deleteAction(this.grabbedEntity, this.actionID); + + Entities.editEntity(this.grabbedEntity, { + velocity: { + x: 0, + y: 0, + z: 0 + }, + angularVelocity: { + x: 0, + y: 0, + z: 0 + } + }) + Entities.deleteAction(this.grabbedEntity, this.actionID); + + } else { + //don't make adjustments + Entities.deleteAction(this.grabbedEntity, this.actionID); + + } } } @@ -1632,7 +1788,7 @@ Controller.enableMapping(MAPPING_NAME); var handToDisable = 'none'; function update() { - if (handToDisable !== LEFT_HAND && handToDisable!=='both') { + if (handToDisable !== LEFT_HAND && handToDisable !== 'both') { leftController.update(); } if (handToDisable !== RIGHT_HAND && handToDisable !== 'both') { @@ -1658,22 +1814,22 @@ handleHandMessages = function(channel, message, sender) { } } else if (channel === 'Hifi-Hand-Grab') { try { - var data = JSON.parse(message); - var selectedController = (data.hand === 'left') ? leftController : rightController; - selectedController.release(); - selectedController.setState(STATE_EQUIP); - selectedController.grabbedEntity = data.entityID; - - } catch (e) { } - } - else if (channel === 'Hifi-Hand-RayPick-Blacklist' && sender === MyAvatar.sessionUUID) { + var data = JSON.parse(message); + var selectedController = (data.hand === 'left') ? leftController : rightController; + selectedController.release(); + selectedController.setState(STATE_EQUIP); + selectedController.grabbedEntity = data.entityID; + + } catch (e) {} + + } else if (channel === 'Hifi-Hand-RayPick-Blacklist') { try { var data = JSON.parse(message); var action = data.action; var id = data.id; var index = blacklist.indexOf(id); - - if (action === 'add' && index ===-1) { + + if (action === 'add' && index === -1) { blacklist.push(id); } if (action === 'remove') { diff --git a/examples/controllers/neuron/neuronAvatar.js b/examples/controllers/neuron/neuronAvatar.js new file mode 100644 index 0000000000..7cebc13feb --- /dev/null +++ b/examples/controllers/neuron/neuronAvatar.js @@ -0,0 +1,239 @@ +var JOINT_PARENT_MAP = { + Hips: "", + RightUpLeg: "Hips", + RightLeg: "RightUpLeg", + RightFoot: "RightLeg", + LeftUpLeg: "Hips", + LeftLeg: "LeftUpLeg", + LeftFoot: "LeftLeg", + Spine: "Hips", + Spine1: "Spine", + Spine2: "Spine1", + Spine3: "Spine2", + Neck: "Spine3", + Head: "Neck", + RightShoulder: "Spine3", + RightArm: "RightShoulder", + RightForeArm: "RightArm", + RightHand: "RightForeArm", + RightHandThumb1: "RightHand", + RightHandThumb2: "RightHandThumb1", + RightHandThumb3: "RightHandThumb2", + RightHandThumb4: "RightHandThumb3", + RightHandIndex1: "RightHand", + RightHandIndex2: "RightHandIndex1", + RightHandIndex3: "RightHandIndex2", + RightHandIndex4: "RightHandIndex3", + RightHandMiddle1: "RightHand", + RightHandMiddle2: "RightHandMiddle1", + RightHandMiddle3: "RightHandMiddle2", + RightHandMiddle4: "RightHandMiddle3", + RightHandRing1: "RightHand", + RightHandRing2: "RightHandRing1", + RightHandRing3: "RightHandRing2", + RightHandRing4: "RightHandRing3", + RightHandPinky1: "RightHand", + RightHandPinky2: "RightHandPinky1", + RightHandPinky3: "RightHandPinky2", + RightHandPinky4: "RightHandPinky3", + LeftShoulder: "Spine3", + LeftArm: "LeftShoulder", + LeftForeArm: "LeftArm", + LeftHand: "LeftForeArm", + LeftHandThumb1: "LeftHand", + LeftHandThumb2: "LeftHandThumb1", + LeftHandThumb3: "LeftHandThumb2", + LeftHandThumb4: "LeftHandThumb3", + LeftHandIndex1: "LeftHand", + LeftHandIndex2: "LeftHandIndex1", + LeftHandIndex3: "LeftHandIndex2", + LeftHandIndex4: "LeftHandIndex3", + LeftHandMiddle1: "LeftHand", + LeftHandMiddle2: "LeftHandMiddle1", + LeftHandMiddle3: "LeftHandMiddle2", + LeftHandMiddle4: "LeftHandMiddle3", + LeftHandRing1: "LeftHand", + LeftHandRing2: "LeftHandRing1", + LeftHandRing3: "LeftHandRing2", + LeftHandRing4: "LeftHandRing3", + LeftHandPinky1: "LeftHand", + LeftHandPinky2: "LeftHandPinky1", + LeftHandPinky3: "LeftHandPinky2", + LeftHandPinky: "LeftHandPinky3", +}; + +var USE_TRANSLATIONS = false; + +// ctor +function Xform(rot, pos) { + this.rot = rot; + this.pos = pos; +}; +Xform.mul = function (lhs, rhs) { + var rot = Quat.multiply(lhs.rot, rhs.rot); + var pos = Vec3.sum(lhs.pos, Vec3.multiplyQbyV(lhs.rot, rhs.pos)); + return new Xform(rot, pos); +}; +Xform.prototype.inv = function () { + var invRot = Quat.inverse(this.rot); + var invPos = Vec3.multiply(-1, this.pos); + return new Xform(invRot, Vec3.multiplyQbyV(invRot, invPos)); +}; +Xform.prototype.toString = function () { + var rot = this.rot; + var pos = this.pos; + return "Xform rot = (" + rot.x + ", " + rot.y + ", " + rot.z + ", " + rot.w + "), pos = (" + pos.x + ", " + pos.y + ", " + pos.z + ")"; +}; + +function dumpHardwareMapping() { + Object.keys(Controller.Hardware).forEach(function (deviceName) { + Object.keys(Controller.Hardware[deviceName]).forEach(function (input) { + print("Controller.Hardware." + deviceName + "." + input + ":" + Controller.Hardware[deviceName][input]); + }); + }); +} + +// ctor +function NeuronAvatar() { + var self = this; + Script.scriptEnding.connect(function () { + self.shutdown(); + }); + Controller.hardwareChanged.connect(function () { + self.hardwareChanged(); + }); + + if (Controller.Hardware.Neuron) { + this.activate(); + } else { + this.deactivate(); + } +} + +NeuronAvatar.prototype.shutdown = function () { + this.deactivate(); +}; + +NeuronAvatar.prototype.hardwareChanged = function () { + if (Controller.Hardware.Neuron) { + this.activate(); + } else { + this.deactivate(); + } +}; + +NeuronAvatar.prototype.activate = function () { + if (!this._active) { + Script.update.connect(updateCallback); + } + this._active = true; + + // build absDefaultPoseMap + this._defaultAbsRotMap = {}; + this._defaultAbsPosMap = {}; + this._defaultAbsRotMap[""] = {x: 0, y: 0, z: 0, w: 1}; + this._defaultAbsPosMap[""] = {x: 0, y: 0, z: 0}; + var keys = Object.keys(JOINT_PARENT_MAP); + var i, l = keys.length; + for (i = 0; i < l; i++) { + var jointName = keys[i]; + var j = MyAvatar.getJointIndex(jointName); + var parentJointName = JOINT_PARENT_MAP[jointName]; + this._defaultAbsRotMap[jointName] = Quat.multiply(this._defaultAbsRotMap[parentJointName], MyAvatar.getDefaultJointRotation(j)); + this._defaultAbsPosMap[jointName] = Vec3.sum(this._defaultAbsPosMap[parentJointName], + Quat.multiply(this._defaultAbsRotMap[parentJointName], MyAvatar.getDefaultJointTranslation(j))); + } +}; + +NeuronAvatar.prototype.deactivate = function () { + if (this._active) { + var self = this; + Script.update.disconnect(updateCallback); + } + this._active = false; + MyAvatar.clearJointsData(); +}; + +NeuronAvatar.prototype.update = function (deltaTime) { + + var hmdActive = HMD.active; + var keys = Object.keys(JOINT_PARENT_MAP); + var i, l = keys.length; + var absDefaultRot = {}; + var jointName, channel, pose, parentJointName, j, parentDefaultAbsRot; + var localRotations = {}; + var localTranslations = {}; + for (i = 0; i < l; i++) { + var jointName = keys[i]; + var channel = Controller.Hardware.Neuron[jointName]; + if (channel) { + pose = Controller.getPoseValue(channel); + parentJointName = JOINT_PARENT_MAP[jointName]; + j = MyAvatar.getJointIndex(jointName); + defaultAbsRot = this._defaultAbsRotMap[jointName]; + parentDefaultAbsRot = this._defaultAbsRotMap[parentJointName]; + + // Rotations from the neuron controller are in world orientation but are delta's from the default pose. + // So first we build the absolute rotation of the default pose (local into world). + // Then apply the rotation from the controller, in world space. + // Then we transform back into joint local by multiplying by the inverse of the parents absolute rotation. + var localRotation = Quat.multiply(Quat.inverse(parentDefaultAbsRot), Quat.multiply(pose.rotation, defaultAbsRot)); + if (!hmdActive || jointName !== "Hips") { + MyAvatar.setJointRotation(j, localRotation); + } + localRotations[jointName] = localRotation; + + // translation proportions might be different from the neuron avatar and the user avatar skeleton. + // so this is disabled by default + if (USE_TRANSLATIONS) { + var localTranslation = Vec3.multiplyQbyV(Quat.inverse(parentDefaultAbsRot), pose.translation); + MyAvatar.setJointTranslation(j, localTranslation); + localTranslations[jointName] = localTranslation; + } else { + localTranslations[jointName] = MyAvatar.getDefaultJointTranslation(j); + } + } + } + + // it attempts to adjust the hips so that the avatar's head is at the same location & oreintation as the HMD. + // however it's fighting with the internal c++ code that also attempts to adjust the hips. + if (hmdActive) { + var UNIT_SCALE = 1 / 100; + var hmdXform = new Xform(HMD.orientation, Vec3.multiply(1 / UNIT_SCALE, HMD.position)); // convert to cm + var y180Xform = new Xform({x: 0, y: 1, z: 0, w: 0}, {x: 0, y: 0, z: 0}); + var avatarXform = new Xform(MyAvatar.orientation, Vec3.multiply(1 / UNIT_SCALE, MyAvatar.position)); // convert to cm + var hipsJointIndex = MyAvatar.getJointIndex("Hips"); + var modelOffsetInvXform = new Xform({x: 0, y: 0, z: 0, w: 1}, MyAvatar.getDefaultJointTranslation(hipsJointIndex)); + var defaultHipsXform = new Xform(MyAvatar.getDefaultJointRotation(hipsJointIndex), MyAvatar.getDefaultJointTranslation(hipsJointIndex)); + + var headXform = new Xform(localRotations["Head"], localTranslations["Head"]); + + // transform eyes down the heirarchy chain into avatar space. + var hierarchy = ["Neck", "Spine3", "Spine2", "Spine1", "Spine"]; + var i, l = hierarchy.length; + for (i = 0; i < l; i++) { + var xform = new Xform(localRotations[hierarchy[i]], localTranslations[hierarchy[i]]); + headXform = Xform.mul(xform, headXform); + } + headXform = Xform.mul(defaultHipsXform, headXform); + + var preXform = Xform.mul(headXform, y180Xform); + var postXform = Xform.mul(avatarXform, Xform.mul(y180Xform, modelOffsetInvXform.inv())); + + // solve for the offset that will put the eyes at the hmd position & orientation. + var hipsOffsetXform = Xform.mul(postXform.inv(), Xform.mul(hmdXform, preXform.inv())); + + // now combine it with the default hips transform + var hipsXform = Xform.mul(hipsOffsetXform, defaultHipsXform); + + MyAvatar.setJointRotation("Hips", hipsXform.rot); + MyAvatar.setJointTranslation("Hips", hipsXform.pos); + } +}; + +var neuronAvatar = new NeuronAvatar(); + +function updateCallback(deltaTime) { + neuronAvatar.update(deltaTime); +} + diff --git a/examples/controllers/reticleHandRotationTest.js b/examples/controllers/reticleHandRotationTest.js index ece9283deb..a303e5e7b4 100644 --- a/examples/controllers/reticleHandRotationTest.js +++ b/examples/controllers/reticleHandRotationTest.js @@ -22,33 +22,13 @@ function length(posA, posB) { return length; } -var EXPECTED_CHANGE = 50; -var lastPos = Controller.getReticlePosition(); function moveReticleAbsolute(x, y) { var globalPos = Controller.getReticlePosition(); - var dX = x - globalPos.x; - var dY = y - globalPos.y; - - // some debugging to see if position is jumping around on us... - var distanceSinceLastMove = length(lastPos, globalPos); - if (distanceSinceLastMove > EXPECTED_CHANGE) { - debugPrint("------------------ distanceSinceLastMove:" + distanceSinceLastMove + "----------------------------"); - } - - if (Math.abs(dX) > EXPECTED_CHANGE) { - debugPrint("surpressing unexpectedly large change dX:" + dX + "----------------------------"); - } - if (Math.abs(dY) > EXPECTED_CHANGE) { - debugPrint("surpressing unexpectedly large change dY:" + dY + "----------------------------"); - } - globalPos.x = x; globalPos.y = y; Controller.setReticlePosition(globalPos); - lastPos = globalPos; } - var MAPPING_NAME = "com.highfidelity.testing.reticleWithHandRotation"; var mapping = Controller.newMapping(MAPPING_NAME); mapping.from(Controller.Standard.LT).peek().constrainToInteger().to(Controller.Actions.ReticleClick); @@ -56,8 +36,6 @@ mapping.from(Controller.Standard.RT).peek().constrainToInteger().to(Controller.A mapping.enable(); -var lastRotatedLeft = Vec3.UNIT_NEG_Y; -var lastRotatedRight = Vec3.UNIT_NEG_Y; function debugPrint(message) { if (DEBUGGING) { @@ -65,24 +43,9 @@ function debugPrint(message) { } } -var MAX_WAKE_UP_DISTANCE = 0.005; -var MIN_WAKE_UP_DISTANCE = 0.001; -var INITIAL_WAKE_UP_DISTANCE = MIN_WAKE_UP_DISTANCE; -var INCREMENTAL_WAKE_UP_DISTANCE = 0.001; - -var MAX_SLEEP_DISTANCE = 0.0004; -var MIN_SLEEP_DISTANCE = 0.00001; //0.00002; -var INITIAL_SLEEP_DISTANCE = MIN_SLEEP_DISTANCE; -var INCREMENTAL_SLEEP_DISTANCE = 0.000002; // 0.00002; - -var leftAsleep = true; -var rightAsleep = true; - -var leftWakeUpDistance = INITIAL_WAKE_UP_DISTANCE; -var rightWakeUpDistance = INITIAL_WAKE_UP_DISTANCE; - -var leftSleepDistance = INITIAL_SLEEP_DISTANCE; -var rightSleepDistance = INITIAL_SLEEP_DISTANCE; +var leftRightBias = 0.0; +var filteredRotatedLeft = Vec3.UNIT_NEG_Y; +var filteredRotatedRight = Vec3.UNIT_NEG_Y; Script.update.connect(function(deltaTime) { @@ -96,153 +59,46 @@ Script.update.connect(function(deltaTime) { var rotatedRight = Vec3.multiplyQbyV(poseRight.rotation, Vec3.UNIT_NEG_Y); var rotatedLeft = Vec3.multiplyQbyV(poseLeft.rotation, Vec3.UNIT_NEG_Y); - var suppressRight = false; - var suppressLeft = false; - - // What I really want to do is to slowly increase the epsilon you have to move it - // to wake up, the longer you go without moving it - var leftDistance = Vec3.distance(rotatedLeft, lastRotatedLeft); - var rightDistance = Vec3.distance(rotatedRight, lastRotatedRight); - - // check to see if hand should wakeup or sleep - if (leftAsleep) { - if (leftDistance > leftWakeUpDistance) { - leftAsleep = false; - leftSleepDistance = INITIAL_SLEEP_DISTANCE; - leftWakeUpDistance = INITIAL_WAKE_UP_DISTANCE; - } else { - // grow the wake up distance to make it harder to wake up - leftWakeUpDistance = Math.min(leftWakeUpDistance + INCREMENTAL_WAKE_UP_DISTANCE, MAX_WAKE_UP_DISTANCE); - } - } else { - // we are awake, determine if we should fall asleep, if we haven't moved - // at least as much as our sleep distance then we sleep - if (leftDistance < leftSleepDistance) { - leftAsleep = true; - leftSleepDistance = INITIAL_SLEEP_DISTANCE; - leftWakeUpDistance = INITIAL_WAKE_UP_DISTANCE; - } else { - // if we moved more than the sleep amount, but we moved less than the max sleep - // amount, then increase our liklihood of sleep. - if (leftDistance < MAX_SLEEP_DISTANCE) { - print("growing sleep...."); - leftSleepDistance = Math.max(leftSleepDistance + INCREMENTAL_SLEEP_DISTANCE, MAX_SLEEP_DISTANCE); - } else { - // otherwise reset it to initial - leftSleepDistance = INITIAL_SLEEP_DISTANCE; - } - } - } - if (leftAsleep) { - suppressLeft = true; - debugPrint("suppressing left not moving enough"); - } - - // check to see if hand should wakeup or sleep - if (rightAsleep) { - if (rightDistance > rightWakeUpDistance) { - rightAsleep = false; - rightSleepDistance = INITIAL_SLEEP_DISTANCE; - rightWakeUpDistance = INITIAL_WAKE_UP_DISTANCE; - } else { - // grow the wake up distance to make it harder to wake up - rightWakeUpDistance = Math.min(rightWakeUpDistance + INCREMENTAL_WAKE_UP_DISTANCE, MAX_WAKE_UP_DISTANCE); - } - } else { - // we are awake, determine if we should fall asleep, if we haven't moved - // at least as much as our sleep distance then we sleep - if (rightDistance < rightSleepDistance) { - rightAsleep = true; - rightSleepDistance = INITIAL_SLEEP_DISTANCE; - rightWakeUpDistance = INITIAL_WAKE_UP_DISTANCE; - } else { - // if we moved more than the sleep amount, but we moved less than the max sleep - // amount, then increase our liklihood of sleep. - if (rightDistance < MAX_SLEEP_DISTANCE) { - print("growing sleep...."); - rightSleepDistance = Math.max(rightSleepDistance + INCREMENTAL_SLEEP_DISTANCE, MAX_SLEEP_DISTANCE); - } else { - // otherwise reset it to initial - rightSleepDistance = INITIAL_SLEEP_DISTANCE; - } - } - } - if (rightAsleep) { - suppressRight = true; - debugPrint("suppressing right not moving enough"); - } - - // check to see if hand is on base station - if (Vec3.equal(rotatedLeft, Vec3.UNIT_NEG_Y)) { - suppressLeft = true; - debugPrint("suppressing left on base station"); - } - if (Vec3.equal(rotatedRight, Vec3.UNIT_NEG_Y)) { - suppressRight = true; - debugPrint("suppressing right on base station"); - } - - // Keep track of last rotations, to detect resting (but not on base station hands) in the future - lastRotatedLeft = rotatedLeft; lastRotatedRight = rotatedRight; - if (suppressLeft && suppressRight) { - debugPrint("both hands suppressed bail out early"); - return; + + // Decide which hand should be controlling the pointer + // by comparing which one is moving more, and by + // tending to stay with the one moving more. + var BIAS_ADJUST_RATE = 0.5; + var BIAS_ADJUST_DEADZONE = 0.05; + leftRightBias += (Vec3.length(poseRight.angularVelocity) - Vec3.length(poseLeft.angularVelocity)) * BIAS_ADJUST_RATE; + if (leftRightBias < BIAS_ADJUST_DEADZONE) { + leftRightBias = 0.0; + } else if (leftRightBias > (1.0 - BIAS_ADJUST_DEADZONE)) { + leftRightBias = 1.0; } - if (suppressLeft) { - debugPrint("right only"); - rotatedLeft = rotatedRight; - } - if (suppressRight) { - debugPrint("left only"); - rotatedRight = rotatedLeft; - } - - // Average the two hand positions, if either hand is on base station, the - // other hand becomes the only used hand and the average is the hand in use - var rotated = Vec3.multiply(Vec3.sum(rotatedRight,rotatedLeft), 0.5); - - if (DEBUGGING) { - Vec3.print("rotatedRight:", rotatedRight); - Vec3.print("rotatedLeft:", rotatedLeft); - Vec3.print("rotated:", rotated); - } + // Velocity filter the hand rotation used to position reticle so that it is easier to target small things with the hand controllers + var VELOCITY_FILTER_GAIN = 1.0; + filteredRotatedLeft = Vec3.mix(filteredRotatedLeft, rotatedLeft, Math.clamp(Vec3.length(poseLeft.angularVelocity) * VELOCITY_FILTER_GAIN, 0.0, 1.0)); + filteredRotatedRight = Vec3.mix(filteredRotatedRight, rotatedRight, Math.clamp(Vec3.length(poseRight.angularVelocity) * VELOCITY_FILTER_GAIN, 0.0, 1.0)); + var rotated = Vec3.mix(filteredRotatedLeft, filteredRotatedRight, leftRightBias); var absolutePitch = rotated.y; // from 1 down to -1 up ... but note: if you rotate down "too far" it starts to go up again... var absoluteYaw = -rotated.x; // from -1 left to 1 right - if (DEBUGGING) { - print("absolutePitch:" + absolutePitch); - print("absoluteYaw:" + absoluteYaw); - Vec3.print("rotated:", rotated); - } - var ROTATION_BOUND = 0.6; var clampYaw = Math.clamp(absoluteYaw, -ROTATION_BOUND, ROTATION_BOUND); var clampPitch = Math.clamp(absolutePitch, -ROTATION_BOUND, ROTATION_BOUND); - if (DEBUGGING) { - print("clampYaw:" + clampYaw); - print("clampPitch:" + clampPitch); - } // using only from -ROTATION_BOUND to ROTATION_BOUND var xRatio = (clampYaw + ROTATION_BOUND) / (2 * ROTATION_BOUND); var yRatio = (clampPitch + ROTATION_BOUND) / (2 * ROTATION_BOUND); - if (DEBUGGING) { - print("xRatio:" + xRatio); - print("yRatio:" + yRatio); - } - var x = screenSizeX * xRatio; var y = screenSizeY * yRatio; - if (DEBUGGING) { - print("position x:" + x + " y:" + y); - } - if (!(xRatio == 0.5 && yRatio == 0)) { + // don't move the reticle with the hand controllers unless the controllers are actually being moved + var MINIMUM_CONTROLLER_ANGULAR_VELOCITY = 0.0001; + var angularVelocityMagnitude = Vec3.length(poseLeft.angularVelocity) * (1.0 - leftRightBias) + Vec3.length(poseRight.angularVelocity) * leftRightBias; + + if (!(xRatio == 0.5 && yRatio == 0) && (angularVelocityMagnitude > MINIMUM_CONTROLLER_ANGULAR_VELOCITY)) { moveReticleAbsolute(x, y); } }); diff --git a/examples/libraries/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js index 6edbe6844b..94ffb48a71 100644 --- a/examples/libraries/entitySelectionTool.js +++ b/examples/libraries/entitySelectionTool.js @@ -16,23 +16,72 @@ HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; SPACE_LOCAL = "local"; SPACE_WORLD = "world"; + SelectionManager = (function() { var that = {}; + function subscribeToUpdateMessages() { + Messages.subscribe('entityToolUpdates'); + Messages.messageReceived.connect(handleEntitySelectionToolUpdates); + } + + function handleEntitySelectionToolUpdates(channel, message, sender) { + if (channel !== 'entityToolUpdates') { + return; + } + if (sender !== MyAvatar.sessionUUID) { + return; + } + + if (message === 'callUpdate') { + that._update(); + } + } + + subscribeToUpdateMessages(); + that.savedProperties = {}; that.selections = []; var listeners = []; that.localRotation = Quat.fromPitchYawRollDegrees(0, 0, 0); - that.localPosition = { x: 0, y: 0, z: 0 }; - that.localDimensions = { x: 0, y: 0, z: 0 }; - that.localRegistrationPoint = { x: 0.5, y: 0.5, z: 0.5 }; + that.localPosition = { + x: 0, + y: 0, + z: 0 + }; + that.localDimensions = { + x: 0, + y: 0, + z: 0 + }; + that.localRegistrationPoint = { + x: 0.5, + y: 0.5, + z: 0.5 + }; that.worldRotation = Quat.fromPitchYawRollDegrees(0, 0, 0); - that.worldPosition = { x: 0, y: 0, z: 0 }; - that.worldDimensions = { x: 0, y: 0, z: 0 }; - that.worldRegistrationPoint = { x: 0.5, y: 0.5, z: 0.5 }; - that.centerPosition = { x: 0, y: 0, z: 0 }; + that.worldPosition = { + x: 0, + y: 0, + z: 0 + }; + that.worldDimensions = { + x: 0, + y: 0, + z: 0 + }; + that.worldRegistrationPoint = { + x: 0.5, + y: 0.5, + z: 0.5 + }; + that.centerPosition = { + x: 0, + y: 0, + z: 0 + }; that.saveProperties = function() { that.savedProperties = {}; @@ -177,9 +226,9 @@ function getRelativeCenterPosition(dimensions, registrationPoint) { } } -SelectionDisplay = (function () { +SelectionDisplay = (function() { var that = {}; - + var MINIMUM_DIMENSION = 0.001; var GRABBER_DISTANCE_TO_SIZE_RATIO = 0.0075; @@ -194,14 +243,18 @@ SelectionDisplay = (function () { var ROTATE_ARROW_WEST_SOUTH_URL = HIFI_PUBLIC_BUCKET + "images/rotate-arrow-west-south.svg"; var showExtendedStretchHandles = false; - + var spaceMode = SPACE_LOCAL; var mode = "UNKNOWN"; var overlayNames = new Array(); var lastCameraPosition = Camera.getPosition(); var lastCameraOrientation = Camera.getOrientation(); - var handleHoverColor = { red: 224, green: 67, blue: 36 }; + var handleHoverColor = { + red: 224, + green: 67, + blue: 36 + }; var handleHoverAlpha = 1.0; var rotateOverlayTargetSize = 10000; // really big target @@ -221,19 +274,27 @@ SelectionDisplay = (function () { var pitchNormal; var rollNormal; var rotationNormal; - + var originalRotation; var originalPitch; var originalYaw; var originalRoll; - - var handleColor = { red: 255, green: 255, blue: 255 }; + + var handleColor = { + red: 255, + green: 255, + blue: 255 + }; var handleAlpha = 0.7; - var highlightedHandleColor = { red: 183, green: 64, blue: 44 }; + var highlightedHandleColor = { + red: 183, + green: 64, + blue: 44 + }; var highlightedHandleAlpha = 0.9; - + var previousHandle = false; var previousHandleColor; var previousHandleAlpha; @@ -242,115 +303,182 @@ SelectionDisplay = (function () { var grabberSizeEdge = 0.015; var grabberSizeFace = 0.025; var grabberAlpha = 1; - var grabberColorCorner = { red: 120, green: 120, blue: 120 }; - var grabberColorEdge = { red: 0, green: 0, blue: 0 }; - var grabberColorFace = { red: 120, green: 120, blue: 120 }; + var grabberColorCorner = { + red: 120, + green: 120, + blue: 120 + }; + var grabberColorEdge = { + red: 0, + green: 0, + blue: 0 + }; + var grabberColorFace = { + red: 120, + green: 120, + blue: 120 + }; var grabberLineWidth = 0.5; var grabberSolid = true; - var grabberMoveUpPosition = { x: 0, y: 0, z: 0 }; + var grabberMoveUpPosition = { + x: 0, + y: 0, + z: 0 + }; - var lightOverlayColor = { red: 255, green: 153, blue: 0 }; + var lightOverlayColor = { + red: 255, + green: 153, + blue: 0 + }; var grabberPropertiesCorner = { - position: { x:0, y: 0, z: 0}, - size: grabberSizeCorner, - color: grabberColorCorner, - alpha: 1, - solid: grabberSolid, - visible: false, - dashed: false, - lineWidth: grabberLineWidth, - drawInFront: true, - borderSize: 1.4, - }; + position: { + x: 0, + y: 0, + z: 0 + }, + size: grabberSizeCorner, + color: grabberColorCorner, + alpha: 1, + solid: grabberSolid, + visible: false, + dashed: false, + lineWidth: grabberLineWidth, + drawInFront: true, + borderSize: 1.4, + }; var grabberPropertiesEdge = { - position: { x:0, y: 0, z: 0}, - size: grabberSizeEdge, - color: grabberColorEdge, - alpha: 1, - solid: grabberSolid, - visible: false, - dashed: false, - lineWidth: grabberLineWidth, - drawInFront: true, - borderSize: 1.4, - }; + position: { + x: 0, + y: 0, + z: 0 + }, + size: grabberSizeEdge, + color: grabberColorEdge, + alpha: 1, + solid: grabberSolid, + visible: false, + dashed: false, + lineWidth: grabberLineWidth, + drawInFront: true, + borderSize: 1.4, + }; var grabberPropertiesFace = { - position: { x:0, y: 0, z: 0}, - size: grabberSizeFace, - color: grabberColorFace, - alpha: 1, - solid: grabberSolid, - visible: false, - dashed: false, - lineWidth: grabberLineWidth, - drawInFront: true, - borderSize: 1.4, - }; - + position: { + x: 0, + y: 0, + z: 0 + }, + size: grabberSizeFace, + color: grabberColorFace, + alpha: 1, + solid: grabberSolid, + visible: false, + dashed: false, + lineWidth: grabberLineWidth, + drawInFront: true, + borderSize: 1.4, + }; + var spotLightLineProperties = { color: lightOverlayColor, lineWidth: 1.5, }; - + var highlightBox = Overlays.addOverlay("cube", { - position: { x:0, y: 0, z: 0}, - size: 1, - color: { red: 90, green: 90, blue: 90}, - alpha: 1, - solid: false, - visible: false, - dashed: true, - lineWidth: 2.0, - ignoreRayIntersection: true, // this never ray intersects - drawInFront: true - }); + position: { + x: 0, + y: 0, + z: 0 + }, + size: 1, + color: { + red: 90, + green: 90, + blue: 90 + }, + alpha: 1, + solid: false, + visible: false, + dashed: true, + lineWidth: 2.0, + ignoreRayIntersection: true, // this never ray intersects + drawInFront: true + }); var selectionBox = Overlays.addOverlay("cube", { - position: { x:0, y: 0, z: 0}, - size: 1, - color: { red: 255, green: 0, blue: 0}, - alpha: 1, - solid: false, - visible: false, - dashed: false, - lineWidth: 1.0, - }); + position: { + x: 0, + y: 0, + z: 0 + }, + size: 1, + color: { + red: 255, + green: 0, + blue: 0 + }, + alpha: 1, + solid: false, + visible: false, + dashed: false, + lineWidth: 1.0, + }); var selectionBoxes = []; var rotationDegreesDisplay = Overlays.addOverlay("text3d", { - position: { x:0, y: 0, z: 0}, - text: "", - color: { red: 0, green: 0, blue: 0}, - backgroundColor: { red: 255, green: 255, blue: 255 }, - alpha: 0.7, - backgroundAlpha: 0.7, - visible: false, - isFacingAvatar: true, - drawInFront: true, - ignoreRayIntersection: true, - dimensions: { x: 0, y: 0 }, - lineHeight: 0.0, - topMargin: 0, - rightMargin: 0, - bottomMargin: 0, - leftMargin: 0, - }); + position: { + x: 0, + y: 0, + z: 0 + }, + text: "", + color: { + red: 0, + green: 0, + blue: 0 + }, + backgroundColor: { + red: 255, + green: 255, + blue: 255 + }, + alpha: 0.7, + backgroundAlpha: 0.7, + visible: false, + isFacingAvatar: true, + drawInFront: true, + ignoreRayIntersection: true, + dimensions: { + x: 0, + y: 0 + }, + lineHeight: 0.0, + topMargin: 0, + rightMargin: 0, + bottomMargin: 0, + leftMargin: 0, + }); var grabberMoveUp = Overlays.addOverlay("image3d", { - url: HIFI_PUBLIC_BUCKET + "images/up-arrow.svg", - position: { x:0, y: 0, z: 0}, - color: handleColor, - alpha: handleAlpha, - visible: false, - size: 0.1, - scale: 0.1, - isFacingAvatar: true, - drawInFront: true, - }); + url: HIFI_PUBLIC_BUCKET + "images/up-arrow.svg", + position: { + x: 0, + y: 0, + z: 0 + }, + color: handleColor, + alpha: handleAlpha, + visible: false, + size: 0.1, + scale: 0.1, + isFacingAvatar: true, + drawInFront: true, + }); // var normalLine = Overlays.addOverlay("line3d", { // visible: true, @@ -360,7 +488,7 @@ SelectionDisplay = (function () { // color: { red: 255, green: 255, blue: 0 }, // ignoreRayIntersection: true, // }); - + var grabberLBN = Overlays.addOverlay("cube", grabberPropertiesCorner); var grabberRBN = Overlays.addOverlay("cube", grabberPropertiesCorner); var grabberLBF = Overlays.addOverlay("cube", grabberPropertiesCorner); @@ -481,171 +609,324 @@ SelectionDisplay = (function () { ]; - var baseOverlayAngles = { x: 0, y: 0, z: 0 }; + var baseOverlayAngles = { + x: 0, + y: 0, + z: 0 + }; var baseOverlayRotation = Quat.fromVec3Degrees(baseOverlayAngles); var baseOfEntityProjectionOverlay = Overlays.addOverlay("rectangle3d", { - position: { x: 1, y: 0, z: 0}, - color: { red: 51, green: 152, blue: 203 }, - alpha: 0.5, - solid: true, - visible: false, - width: 300, height: 200, - rotation: baseOverlayRotation, - ignoreRayIntersection: true, // always ignore this - }); + position: { + x: 1, + y: 0, + z: 0 + }, + color: { + red: 51, + green: 152, + blue: 203 + }, + alpha: 0.5, + solid: true, + visible: false, + width: 300, + height: 200, + rotation: baseOverlayRotation, + ignoreRayIntersection: true, // always ignore this + }); - var yawOverlayAngles = { x: 90, y: 0, z: 0 }; + var yawOverlayAngles = { + x: 90, + y: 0, + z: 0 + }; var yawOverlayRotation = Quat.fromVec3Degrees(yawOverlayAngles); - var pitchOverlayAngles = { x: 0, y: 90, z: 0 }; + var pitchOverlayAngles = { + x: 0, + y: 90, + z: 0 + }; var pitchOverlayRotation = Quat.fromVec3Degrees(pitchOverlayAngles); - var rollOverlayAngles = { x: 0, y: 180, z: 0 }; + var rollOverlayAngles = { + x: 0, + y: 180, + z: 0 + }; var rollOverlayRotation = Quat.fromVec3Degrees(rollOverlayAngles); var xRailOverlay = Overlays.addOverlay("line3d", { - visible: false, - lineWidth: 1.0, - start: { x: 0, y: 0, z: 0 }, - end: { x: 0, y: 0, z: 0 }, - color: { red: 255, green: 0, blue: 0 }, - ignoreRayIntersection: true, // always ignore this - visible: false, - }); + visible: false, + lineWidth: 1.0, + start: { + x: 0, + y: 0, + z: 0 + }, + end: { + x: 0, + y: 0, + z: 0 + }, + color: { + red: 255, + green: 0, + blue: 0 + }, + ignoreRayIntersection: true, // always ignore this + visible: false, + }); var yRailOverlay = Overlays.addOverlay("line3d", { - visible: false, - lineWidth: 1.0, - start: { x: 0, y: 0, z: 0 }, - end: { x: 0, y: 0, z: 0 }, - color: { red: 0, green: 255, blue: 0 }, - ignoreRayIntersection: true, // always ignore this - visible: false, - }); + visible: false, + lineWidth: 1.0, + start: { + x: 0, + y: 0, + z: 0 + }, + end: { + x: 0, + y: 0, + z: 0 + }, + color: { + red: 0, + green: 255, + blue: 0 + }, + ignoreRayIntersection: true, // always ignore this + visible: false, + }); var zRailOverlay = Overlays.addOverlay("line3d", { - visible: false, - lineWidth: 1.0, - start: { x: 0, y: 0, z: 0 }, - end: { x: 0, y: 0, z: 0 }, - color: { red: 0, green: 0, blue: 255 }, - ignoreRayIntersection: true, // always ignore this - visible: false, - }); + visible: false, + lineWidth: 1.0, + start: { + x: 0, + y: 0, + z: 0 + }, + end: { + x: 0, + y: 0, + z: 0 + }, + color: { + red: 0, + green: 0, + blue: 255 + }, + ignoreRayIntersection: true, // always ignore this + visible: false, + }); var rotateZeroOverlay = Overlays.addOverlay("line3d", { - visible: false, - lineWidth: 2.0, - start: { x: 0, y: 0, z: 0 }, - end: { x: 0, y: 0, z: 0 }, - color: { red: 255, green: 0, blue: 0 }, - ignoreRayIntersection: true, // always ignore this - }); + visible: false, + lineWidth: 2.0, + start: { + x: 0, + y: 0, + z: 0 + }, + end: { + x: 0, + y: 0, + z: 0 + }, + color: { + red: 255, + green: 0, + blue: 0 + }, + ignoreRayIntersection: true, // always ignore this + }); var rotateCurrentOverlay = Overlays.addOverlay("line3d", { - visible: false, - lineWidth: 2.0, - start: { x: 0, y: 0, z: 0 }, - end: { x: 0, y: 0, z: 0 }, - color: { red: 0, green: 0, blue: 255 }, - ignoreRayIntersection: true, // always ignore this - }); + visible: false, + lineWidth: 2.0, + start: { + x: 0, + y: 0, + z: 0 + }, + end: { + x: 0, + y: 0, + z: 0 + }, + color: { + red: 0, + green: 0, + blue: 255 + }, + ignoreRayIntersection: true, // always ignore this + }); var rotateOverlayTarget = Overlays.addOverlay("circle3d", { - position: { x:0, y: 0, z: 0}, - size: rotateOverlayTargetSize, - color: { red: 0, green: 0, blue: 0 }, - alpha: 0.0, - solid: true, - visible: false, - rotation: yawOverlayRotation, - }); + position: { + x: 0, + y: 0, + z: 0 + }, + size: rotateOverlayTargetSize, + color: { + red: 0, + green: 0, + blue: 0 + }, + alpha: 0.0, + solid: true, + visible: false, + rotation: yawOverlayRotation, + }); var rotateOverlayInner = Overlays.addOverlay("circle3d", { - position: { x:0, y: 0, z: 0}, - size: 1, - color: { red: 51, green: 152, blue: 203 }, - alpha: 0.2, - solid: true, - visible: false, - rotation: yawOverlayRotation, - hasTickMarks: true, - majorTickMarksAngle: innerSnapAngle, - minorTickMarksAngle: 0, - majorTickMarksLength: -0.25, - minorTickMarksLength: 0, - majorTickMarksColor: { red: 0, green: 0, blue: 0 }, - minorTickMarksColor: { red: 0, green: 0, blue: 0 }, - ignoreRayIntersection: true, // always ignore this - }); + position: { + x: 0, + y: 0, + z: 0 + }, + size: 1, + color: { + red: 51, + green: 152, + blue: 203 + }, + alpha: 0.2, + solid: true, + visible: false, + rotation: yawOverlayRotation, + hasTickMarks: true, + majorTickMarksAngle: innerSnapAngle, + minorTickMarksAngle: 0, + majorTickMarksLength: -0.25, + minorTickMarksLength: 0, + majorTickMarksColor: { + red: 0, + green: 0, + blue: 0 + }, + minorTickMarksColor: { + red: 0, + green: 0, + blue: 0 + }, + ignoreRayIntersection: true, // always ignore this + }); var rotateOverlayOuter = Overlays.addOverlay("circle3d", { - position: { x:0, y: 0, z: 0}, - size: 1, - color: { red: 51, green: 152, blue: 203 }, - alpha: 0.2, - solid: true, - visible: false, - rotation: yawOverlayRotation, + position: { + x: 0, + y: 0, + z: 0 + }, + size: 1, + color: { + red: 51, + green: 152, + blue: 203 + }, + alpha: 0.2, + solid: true, + visible: false, + rotation: yawOverlayRotation, - hasTickMarks: true, - majorTickMarksAngle: 45.0, - minorTickMarksAngle: 5, - majorTickMarksLength: 0.25, - minorTickMarksLength: 0.1, - majorTickMarksColor: { red: 0, green: 0, blue: 0 }, - minorTickMarksColor: { red: 0, green: 0, blue: 0 }, - ignoreRayIntersection: true, // always ignore this - }); + hasTickMarks: true, + majorTickMarksAngle: 45.0, + minorTickMarksAngle: 5, + majorTickMarksLength: 0.25, + minorTickMarksLength: 0.1, + majorTickMarksColor: { + red: 0, + green: 0, + blue: 0 + }, + minorTickMarksColor: { + red: 0, + green: 0, + blue: 0 + }, + ignoreRayIntersection: true, // always ignore this + }); var rotateOverlayCurrent = Overlays.addOverlay("circle3d", { - position: { x:0, y: 0, z: 0}, - size: 1, - color: { red: 224, green: 67, blue: 36}, - alpha: 0.8, - solid: true, - visible: false, - rotation: yawOverlayRotation, - ignoreRayIntersection: true, // always ignore this - hasTickMarks: true, - majorTickMarksColor: { red: 0, green: 0, blue: 0 }, - minorTickMarksColor: { red: 0, green: 0, blue: 0 }, - }); + position: { + x: 0, + y: 0, + z: 0 + }, + size: 1, + color: { + red: 224, + green: 67, + blue: 36 + }, + alpha: 0.8, + solid: true, + visible: false, + rotation: yawOverlayRotation, + ignoreRayIntersection: true, // always ignore this + hasTickMarks: true, + majorTickMarksColor: { + red: 0, + green: 0, + blue: 0 + }, + minorTickMarksColor: { + red: 0, + green: 0, + blue: 0 + }, + }); var yawHandle = Overlays.addOverlay("image3d", { - url: ROTATE_ARROW_WEST_NORTH_URL, - position: { x:0, y: 0, z: 0}, - color: handleColor, - alpha: handleAlpha, - visible: false, - size: 0.1, - scale: 0.1, - isFacingAvatar: false, - drawInFront: true, - }); + url: ROTATE_ARROW_WEST_NORTH_URL, + position: { + x: 0, + y: 0, + z: 0 + }, + color: handleColor, + alpha: handleAlpha, + visible: false, + size: 0.1, + scale: 0.1, + isFacingAvatar: false, + drawInFront: true, + }); var pitchHandle = Overlays.addOverlay("image3d", { - url: ROTATE_ARROW_WEST_NORTH_URL, - position: { x:0, y: 0, z: 0}, - color: handleColor, - alpha: handleAlpha, - visible: false, - size: 0.1, - scale: 0.1, - isFacingAvatar: false, - drawInFront: true, - }); + url: ROTATE_ARROW_WEST_NORTH_URL, + position: { + x: 0, + y: 0, + z: 0 + }, + color: handleColor, + alpha: handleAlpha, + visible: false, + size: 0.1, + scale: 0.1, + isFacingAvatar: false, + drawInFront: true, + }); var rollHandle = Overlays.addOverlay("image3d", { - url: ROTATE_ARROW_WEST_NORTH_URL, - position: { x:0, y: 0, z: 0}, - color: handleColor, - alpha: handleAlpha, - visible: false, - size: 0.1, - scale: 0.1, - isFacingAvatar: false, - drawInFront: true, - }); + url: ROTATE_ARROW_WEST_NORTH_URL, + position: { + x: 0, + y: 0, + z: 0 + }, + color: handleColor, + alpha: handleAlpha, + visible: false, + size: 0.1, + scale: 0.1, + isFacingAvatar: false, + drawInFront: true, + }); var allOverlays = [ highlightBox, @@ -703,7 +984,7 @@ SelectionDisplay = (function () { overlayNames[grabberEdgeNL] = "grabberEdgeNL"; overlayNames[grabberEdgeFR] = "grabberEdgeFR"; overlayNames[grabberEdgeFL] = "grabberEdgeFL"; - + overlayNames[yawHandle] = "yawHandle"; overlayNames[pitchHandle] = "pitchHandle"; overlayNames[rollHandle] = "rollHandle"; @@ -718,6 +999,7 @@ SelectionDisplay = (function () { var activeTool = null; var grabberTools = {}; + function addGrabberTool(overlay, tool) { grabberTools[overlay] = { mode: tool.mode, @@ -727,8 +1009,8 @@ SelectionDisplay = (function () { } } - - that.cleanup = function () { + + that.cleanup = function() { for (var i = 0; i < allOverlays.length; i++) { Overlays.deleteOverlay(allOverlays[i]); } @@ -741,19 +1023,21 @@ SelectionDisplay = (function () { var properties = Entities.getEntityProperties(entityID); Overlays.editOverlay(highlightBox, { visible: true, - position: properties.boundingBox.center, + position: properties.boundingBox.center, dimensions: properties.dimensions, rotation: properties.rotation }); }; that.unhighlightSelectable = function(entityID) { - Overlays.editOverlay(highlightBox,{ visible: false}); + Overlays.editOverlay(highlightBox, { + visible: false + }); }; that.select = function(entityID, event) { var properties = Entities.getEntityProperties(SelectionManager.selections[0]); - + lastCameraPosition = Camera.getPosition(); lastCameraOrientation = Camera.getOrientation(); @@ -766,12 +1050,16 @@ SelectionDisplay = (function () { print(" event.y:" + event.y); Vec3.print(" current position:", properties.position); } - - + + } - Entities.editEntity(entityID, { localRenderAlpha: 0.1 }); - Overlays.editOverlay(highlightBox, { visible: false }); + Entities.editEntity(entityID, { + localRenderAlpha: 0.1 + }); + Overlays.editOverlay(highlightBox, { + visible: false + }); that.updateHandles(); } @@ -789,7 +1077,7 @@ SelectionDisplay = (function () { } var rotateHandleOffset = 0.05; - + var top, far, left, bottom, near, right, boundsCenter, objectCenter, BLN, BRN, BLF, TLN, TRN, TLF, TRF; var dimensions, rotation; @@ -828,136 +1116,320 @@ SelectionDisplay = (function () { * ------------------------------*/ - + var cameraPosition = Camera.getPosition(); if (cameraPosition.x > objectCenter.x) { // must be BRF or BRN if (cameraPosition.z < objectCenter.z) { - yawHandleRotation = Quat.fromVec3Degrees({ x: 270, y: 90, z: 0 }); - pitchHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 90, z: 0 }); - rollHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 0, z: 0 }); + yawHandleRotation = Quat.fromVec3Degrees({ + x: 270, + y: 90, + z: 0 + }); + pitchHandleRotation = Quat.fromVec3Degrees({ + x: 0, + y: 90, + z: 0 + }); + rollHandleRotation = Quat.fromVec3Degrees({ + x: 0, + y: 0, + z: 0 + }); - yawNormal = { x: 0, y: 1, z: 0 }; - pitchNormal = { x: 1, y: 0, z: 0 }; - rollNormal = { x: 0, y: 0, z: 1 }; + yawNormal = { + x: 0, + y: 1, + z: 0 + }; + pitchNormal = { + x: 1, + y: 0, + z: 0 + }; + rollNormal = { + x: 0, + y: 0, + z: 1 + }; - yawCorner = { x: left + rotateHandleOffset, - y: bottom - rotateHandleOffset, - z: near - rotateHandleOffset }; + yawCorner = { + x: left + rotateHandleOffset, + y: bottom - rotateHandleOffset, + z: near - rotateHandleOffset + }; - pitchCorner = { x: right - rotateHandleOffset, - y: top + rotateHandleOffset, - z: near - rotateHandleOffset}; + pitchCorner = { + x: right - rotateHandleOffset, + y: top + rotateHandleOffset, + z: near - rotateHandleOffset + }; - rollCorner = { x: left + rotateHandleOffset, - y: top + rotateHandleOffset, - z: far + rotateHandleOffset }; + rollCorner = { + x: left + rotateHandleOffset, + y: top + rotateHandleOffset, + z: far + rotateHandleOffset + }; - yawCenter = { x: boundsCenter.x, y: bottom, z: boundsCenter.z }; - pitchCenter = { x: right, y: boundsCenter.y, z: boundsCenter.z}; - rollCenter = { x: boundsCenter.x, y: boundsCenter.y, z: far }; - + yawCenter = { + x: boundsCenter.x, + y: bottom, + z: boundsCenter.z + }; + pitchCenter = { + x: right, + y: boundsCenter.y, + z: boundsCenter.z + }; + rollCenter = { + x: boundsCenter.x, + y: boundsCenter.y, + z: far + }; + + + Overlays.editOverlay(pitchHandle, { + url: ROTATE_ARROW_WEST_SOUTH_URL + }); + Overlays.editOverlay(rollHandle, { + url: ROTATE_ARROW_WEST_SOUTH_URL + }); - Overlays.editOverlay(pitchHandle, { url: ROTATE_ARROW_WEST_SOUTH_URL }); - Overlays.editOverlay(rollHandle, { url: ROTATE_ARROW_WEST_SOUTH_URL }); - } else { - - yawHandleRotation = Quat.fromVec3Degrees({ x: 270, y: 0, z: 0 }); - pitchHandleRotation = Quat.fromVec3Degrees({ x: 180, y: 270, z: 0 }); - rollHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 0, z: 90 }); - yawNormal = { x: 0, y: 1, z: 0 }; - pitchNormal = { x: 1, y: 0, z: 0 }; - rollNormal = { x: 0, y: 0, z: 1 }; + yawHandleRotation = Quat.fromVec3Degrees({ + x: 270, + y: 0, + z: 0 + }); + pitchHandleRotation = Quat.fromVec3Degrees({ + x: 180, + y: 270, + z: 0 + }); + rollHandleRotation = Quat.fromVec3Degrees({ + x: 0, + y: 0, + z: 90 + }); + + yawNormal = { + x: 0, + y: 1, + z: 0 + }; + pitchNormal = { + x: 1, + y: 0, + z: 0 + }; + rollNormal = { + x: 0, + y: 0, + z: 1 + }; - yawCorner = { x: left + rotateHandleOffset, - y: bottom - rotateHandleOffset, - z: far + rotateHandleOffset }; + yawCorner = { + x: left + rotateHandleOffset, + y: bottom - rotateHandleOffset, + z: far + rotateHandleOffset + }; - pitchCorner = { x: right - rotateHandleOffset, - y: top + rotateHandleOffset, - z: far + rotateHandleOffset }; + pitchCorner = { + x: right - rotateHandleOffset, + y: top + rotateHandleOffset, + z: far + rotateHandleOffset + }; - rollCorner = { x: left + rotateHandleOffset, - y: top + rotateHandleOffset, - z: near - rotateHandleOffset}; + rollCorner = { + x: left + rotateHandleOffset, + y: top + rotateHandleOffset, + z: near - rotateHandleOffset + }; - yawCenter = { x: boundsCenter.x, y: bottom, z: boundsCenter.z }; - pitchCenter = { x: right, y: boundsCenter.y, z: boundsCenter.z }; - rollCenter = { x: boundsCenter.x, y: boundsCenter.y, z: near}; + yawCenter = { + x: boundsCenter.x, + y: bottom, + z: boundsCenter.z + }; + pitchCenter = { + x: right, + y: boundsCenter.y, + z: boundsCenter.z + }; + rollCenter = { + x: boundsCenter.x, + y: boundsCenter.y, + z: near + }; - Overlays.editOverlay(pitchHandle, { url: ROTATE_ARROW_WEST_NORTH_URL }); - Overlays.editOverlay(rollHandle, { url: ROTATE_ARROW_WEST_NORTH_URL }); + Overlays.editOverlay(pitchHandle, { + url: ROTATE_ARROW_WEST_NORTH_URL + }); + Overlays.editOverlay(rollHandle, { + url: ROTATE_ARROW_WEST_NORTH_URL + }); } } else { - + // must be BLF or BLN if (cameraPosition.z < objectCenter.z) { - - yawHandleRotation = Quat.fromVec3Degrees({ x: 270, y: 180, z: 0 }); - pitchHandleRotation = Quat.fromVec3Degrees({ x: 90, y: 0, z: 90 }); - rollHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 0, z: 180 }); - yawNormal = { x: 0, y: 1, z: 0 }; - pitchNormal = { x: 1, y: 0, z: 0 }; - rollNormal = { x: 0, y: 0, z: 1 }; + yawHandleRotation = Quat.fromVec3Degrees({ + x: 270, + y: 180, + z: 0 + }); + pitchHandleRotation = Quat.fromVec3Degrees({ + x: 90, + y: 0, + z: 90 + }); + rollHandleRotation = Quat.fromVec3Degrees({ + x: 0, + y: 0, + z: 180 + }); - yawCorner = { x: right - rotateHandleOffset, - y: bottom - rotateHandleOffset, - z: near - rotateHandleOffset }; + yawNormal = { + x: 0, + y: 1, + z: 0 + }; + pitchNormal = { + x: 1, + y: 0, + z: 0 + }; + rollNormal = { + x: 0, + y: 0, + z: 1 + }; - pitchCorner = { x: left + rotateHandleOffset, - y: top + rotateHandleOffset, - z: near - rotateHandleOffset }; + yawCorner = { + x: right - rotateHandleOffset, + y: bottom - rotateHandleOffset, + z: near - rotateHandleOffset + }; - rollCorner = { x: right - rotateHandleOffset, - y: top + rotateHandleOffset, - z: far + rotateHandleOffset}; + pitchCorner = { + x: left + rotateHandleOffset, + y: top + rotateHandleOffset, + z: near - rotateHandleOffset + }; - yawCenter = { x: boundsCenter.x, y: bottom, z: boundsCenter.z }; - pitchCenter = { x: left, y: boundsCenter.y, z: boundsCenter.z }; - rollCenter = { x: boundsCenter.x, y: boundsCenter.y, z: far}; + rollCorner = { + x: right - rotateHandleOffset, + y: top + rotateHandleOffset, + z: far + rotateHandleOffset + }; - Overlays.editOverlay(pitchHandle, { url: ROTATE_ARROW_WEST_NORTH_URL }); - Overlays.editOverlay(rollHandle, { url: ROTATE_ARROW_WEST_NORTH_URL }); + yawCenter = { + x: boundsCenter.x, + y: bottom, + z: boundsCenter.z + }; + pitchCenter = { + x: left, + y: boundsCenter.y, + z: boundsCenter.z + }; + rollCenter = { + x: boundsCenter.x, + y: boundsCenter.y, + z: far + }; + + Overlays.editOverlay(pitchHandle, { + url: ROTATE_ARROW_WEST_NORTH_URL + }); + Overlays.editOverlay(rollHandle, { + url: ROTATE_ARROW_WEST_NORTH_URL + }); } else { - - yawHandleRotation = Quat.fromVec3Degrees({ x: 270, y: 270, z: 0 }); - pitchHandleRotation = Quat.fromVec3Degrees({ x: 180, y: 270, z: 0 }); - rollHandleRotation = Quat.fromVec3Degrees({ x: 0, y: 0, z: 180 }); - yawNormal = { x: 0, y: 1, z: 0 }; - rollNormal = { x: 0, y: 0, z: 1 }; - pitchNormal = { x: 1, y: 0, z: 0 }; + yawHandleRotation = Quat.fromVec3Degrees({ + x: 270, + y: 270, + z: 0 + }); + pitchHandleRotation = Quat.fromVec3Degrees({ + x: 180, + y: 270, + z: 0 + }); + rollHandleRotation = Quat.fromVec3Degrees({ + x: 0, + y: 0, + z: 180 + }); - yawCorner = { x: right - rotateHandleOffset, - y: bottom - rotateHandleOffset, - z: far + rotateHandleOffset }; + yawNormal = { + x: 0, + y: 1, + z: 0 + }; + rollNormal = { + x: 0, + y: 0, + z: 1 + }; + pitchNormal = { + x: 1, + y: 0, + z: 0 + }; - rollCorner = { x: right - rotateHandleOffset, - y: top + rotateHandleOffset, - z: near - rotateHandleOffset }; + yawCorner = { + x: right - rotateHandleOffset, + y: bottom - rotateHandleOffset, + z: far + rotateHandleOffset + }; - pitchCorner = { x: left + rotateHandleOffset, - y: top + rotateHandleOffset, - z: far + rotateHandleOffset}; + rollCorner = { + x: right - rotateHandleOffset, + y: top + rotateHandleOffset, + z: near - rotateHandleOffset + }; - yawCenter = { x: boundsCenter.x, y: bottom, z: boundsCenter.z }; - rollCenter = { x: boundsCenter.x, y: boundsCenter.y, z: near }; - pitchCenter = { x: left, y: boundsCenter.y, z: boundsCenter.z}; + pitchCorner = { + x: left + rotateHandleOffset, + y: top + rotateHandleOffset, + z: far + rotateHandleOffset + }; - Overlays.editOverlay(pitchHandle, { url: ROTATE_ARROW_WEST_NORTH_URL }); - Overlays.editOverlay(rollHandle, { url: ROTATE_ARROW_WEST_NORTH_URL }); + yawCenter = { + x: boundsCenter.x, + y: bottom, + z: boundsCenter.z + }; + rollCenter = { + x: boundsCenter.x, + y: boundsCenter.y, + z: near + }; + pitchCenter = { + x: left, + y: boundsCenter.y, + z: boundsCenter.z + }; + + Overlays.editOverlay(pitchHandle, { + url: ROTATE_ARROW_WEST_NORTH_URL + }); + Overlays.editOverlay(rollHandle, { + url: ROTATE_ARROW_WEST_NORTH_URL + }); } } - + var rotateHandlesVisible = true; var rotationOverlaysVisible = false; var translateHandlesVisible = true; @@ -984,20 +1456,38 @@ SelectionDisplay = (function () { rotateHandlesVisible = false; translateHandlesVisible = false; } - + var rotation = selectionManager.worldRotation; var dimensions = selectionManager.worldDimensions; var position = selectionManager.worldPosition; - - Overlays.editOverlay(rotateOverlayTarget, { visible: rotationOverlaysVisible }); - Overlays.editOverlay(rotateZeroOverlay, { visible: rotationOverlaysVisible }); - Overlays.editOverlay(rotateCurrentOverlay, { visible: rotationOverlaysVisible }); + + Overlays.editOverlay(rotateOverlayTarget, { + visible: rotationOverlaysVisible + }); + Overlays.editOverlay(rotateZeroOverlay, { + visible: rotationOverlaysVisible + }); + Overlays.editOverlay(rotateCurrentOverlay, { + visible: rotationOverlaysVisible + }); // TODO: we have not implemented the rotating handle/controls yet... so for now, these handles are hidden - Overlays.editOverlay(yawHandle, { visible: rotateHandlesVisible, position: yawCorner, rotation: yawHandleRotation}); - Overlays.editOverlay(pitchHandle, { visible: rotateHandlesVisible, position: pitchCorner, rotation: pitchHandleRotation}); - Overlays.editOverlay(rollHandle, { visible: rotateHandlesVisible, position: rollCorner, rotation: rollHandleRotation}); + Overlays.editOverlay(yawHandle, { + visible: rotateHandlesVisible, + position: yawCorner, + rotation: yawHandleRotation + }); + Overlays.editOverlay(pitchHandle, { + visible: rotateHandlesVisible, + position: pitchCorner, + rotation: pitchHandleRotation + }); + Overlays.editOverlay(rollHandle, { + visible: rotateHandlesVisible, + position: rollCorner, + rotation: rollHandleRotation + }); }; that.setSpaceMode = function(newSpaceMode) { @@ -1016,8 +1506,7 @@ SelectionDisplay = (function () { that.updateHandles(); }; - that.unselectAll = function () { - }; + that.unselectAll = function() {}; that.updateHandles = function() { if (SelectionManager.selections.length == 0) { @@ -1061,34 +1550,138 @@ SelectionDisplay = (function () { var worldTop = SelectionManager.worldDimensions.y / 2; - var LBN = { x: left, y: bottom, z: near }; - var RBN = { x: right, y: bottom, z: near }; - var LBF = { x: left, y: bottom, z: far }; - var RBF = { x: right, y: bottom, z: far }; - var LTN = { x: left, y: top, z: near }; - var RTN = { x: right, y: top, z: near }; - var LTF = { x: left, y: top, z: far }; - var RTF = { x: right, y: top, z: far }; + var LBN = { + x: left, + y: bottom, + z: near + }; + var RBN = { + x: right, + y: bottom, + z: near + }; + var LBF = { + x: left, + y: bottom, + z: far + }; + var RBF = { + x: right, + y: bottom, + z: far + }; + var LTN = { + x: left, + y: top, + z: near + }; + var RTN = { + x: right, + y: top, + z: near + }; + var LTF = { + x: left, + y: top, + z: far + }; + var RTF = { + x: right, + y: top, + z: far + }; - var TOP = { x: center.x, y: top, z: center.z }; - var BOTTOM = { x: center.x, y: bottom, z: center.z }; - var LEFT = { x: left, y: center.y, z: center.z }; - var RIGHT = { x: right, y: center.y, z: center.z }; - var NEAR = { x: center.x, y: center.y, z: near }; - var FAR = { x: center.x, y: center.y, z: far }; + var TOP = { + x: center.x, + y: top, + z: center.z + }; + var BOTTOM = { + x: center.x, + y: bottom, + z: center.z + }; + var LEFT = { + x: left, + y: center.y, + z: center.z + }; + var RIGHT = { + x: right, + y: center.y, + z: center.z + }; + var NEAR = { + x: center.x, + y: center.y, + z: near + }; + var FAR = { + x: center.x, + y: center.y, + z: far + }; - var EdgeTR = { x: right, y: top, z: center.z }; - var EdgeTL = { x: left, y: top, z: center.z }; - var EdgeTF = { x: center.x, y: top, z: front }; - var EdgeTN = { x: center.x, y: top, z: near }; - var EdgeBR = { x: right, y: bottom, z: center.z }; - var EdgeBL = { x: left, y: bottom, z: center.z }; - var EdgeBF = { x: center.x, y: bottom, z: front }; - var EdgeBN = { x: center.x, y: bottom, z: near }; - var EdgeNR = { x: right, y: center.y, z: near }; - var EdgeNL = { x: left, y: center.y, z: near }; - var EdgeFR = { x: right, y: center.y, z: front }; - var EdgeFL = { x: left, y: center.y, z: front }; + var EdgeTR = { + x: right, + y: top, + z: center.z + }; + var EdgeTL = { + x: left, + y: top, + z: center.z + }; + var EdgeTF = { + x: center.x, + y: top, + z: front + }; + var EdgeTN = { + x: center.x, + y: top, + z: near + }; + var EdgeBR = { + x: right, + y: bottom, + z: center.z + }; + var EdgeBL = { + x: left, + y: bottom, + z: center.z + }; + var EdgeBF = { + x: center.x, + y: bottom, + z: front + }; + var EdgeBN = { + x: center.x, + y: bottom, + z: near + }; + var EdgeNR = { + x: right, + y: center.y, + z: near + }; + var EdgeNL = { + x: left, + y: center.y, + z: near + }; + var EdgeFR = { + x: right, + y: center.y, + z: front + }; + var EdgeFL = { + x: left, + y: center.y, + z: front + }; LBN = Vec3.multiplyQbyV(rotation, LBN); RBN = Vec3.multiplyQbyV(rotation, RBN); @@ -1151,7 +1744,7 @@ SelectionDisplay = (function () { var stretchHandlesVisible = spaceMode == SPACE_LOCAL; var extendedStretchHandlesVisible = stretchHandlesVisible && showExtendedStretchHandles; - if (selectionManager.selections.length == 1 ) { + if (selectionManager.selections.length == 1) { var properties = Entities.getEntityProperties(selectionManager.selections[0]); if (properties.type == "Light" && properties.isSpotlight == true) { var stretchHandlesVisible = false; @@ -1190,7 +1783,11 @@ SelectionDisplay = (function () { }); Overlays.editOverlay(grabberSpotLightCircle, { position: NEAR, - dimensions: { x: distance, y: distance, z: 1 }, + dimensions: { + x: distance, + y: distance, + z: 1 + }, lineWidth: 1.5, rotation: rotation, visible: true, @@ -1217,15 +1814,33 @@ SelectionDisplay = (function () { visible: true, }); - Overlays.editOverlay(grabberPointLightCircleX, { visible: false }); - Overlays.editOverlay(grabberPointLightCircleY, { visible: false }); - Overlays.editOverlay(grabberPointLightCircleZ, { visible: false }); - Overlays.editOverlay(grabberPointLightT, { visible: false }); - Overlays.editOverlay(grabberPointLightB, { visible: false }); - Overlays.editOverlay(grabberPointLightL, { visible: false }); - Overlays.editOverlay(grabberPointLightR, { visible: false }); - Overlays.editOverlay(grabberPointLightF, { visible: false }); - Overlays.editOverlay(grabberPointLightN, { visible: false }); + Overlays.editOverlay(grabberPointLightCircleX, { + visible: false + }); + Overlays.editOverlay(grabberPointLightCircleY, { + visible: false + }); + Overlays.editOverlay(grabberPointLightCircleZ, { + visible: false + }); + Overlays.editOverlay(grabberPointLightT, { + visible: false + }); + Overlays.editOverlay(grabberPointLightB, { + visible: false + }); + Overlays.editOverlay(grabberPointLightL, { + visible: false + }); + Overlays.editOverlay(grabberPointLightR, { + visible: false + }); + Overlays.editOverlay(grabberPointLightF, { + visible: false + }); + Overlays.editOverlay(grabberPointLightN, { + visible: false + }); } else if (properties.type == "Light" && properties.isSpotlight == false) { var stretchHandlesVisible = false; var extendedStretchHandlesVisible = false; @@ -1262,75 +1877,203 @@ SelectionDisplay = (function () { Overlays.editOverlay(grabberPointLightCircleX, { position: position, rotation: Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(0, 90, 0)), - dimensions: { x: properties.dimensions.z / 2.0, y: properties.dimensions.z / 2.0, z: 1 }, + dimensions: { + x: properties.dimensions.z / 2.0, + y: properties.dimensions.z / 2.0, + z: 1 + }, visible: true, }); Overlays.editOverlay(grabberPointLightCircleY, { position: position, rotation: Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(90, 0, 0)), - dimensions: { x: properties.dimensions.z / 2.0, y: properties.dimensions.z / 2.0, z: 1 }, + dimensions: { + x: properties.dimensions.z / 2.0, + y: properties.dimensions.z / 2.0, + z: 1 + }, visible: true, }); Overlays.editOverlay(grabberPointLightCircleZ, { position: position, rotation: rotation, - dimensions: { x: properties.dimensions.z / 2.0, y: properties.dimensions.z / 2.0, z: 1 }, + dimensions: { + x: properties.dimensions.z / 2.0, + y: properties.dimensions.z / 2.0, + z: 1 + }, visible: true, }); - Overlays.editOverlay(grabberSpotLightRadius, { visible: false }); - Overlays.editOverlay(grabberSpotLightL, { visible: false }); - Overlays.editOverlay(grabberSpotLightR, { visible: false }); - Overlays.editOverlay(grabberSpotLightT, { visible: false }); - Overlays.editOverlay(grabberSpotLightB, { visible: false }); - Overlays.editOverlay(grabberSpotLightCircle, { visible: false }); - Overlays.editOverlay(grabberSpotLightLineL, { visible: false }); - Overlays.editOverlay(grabberSpotLightLineR, { visible: false }); - Overlays.editOverlay(grabberSpotLightLineT, { visible: false }); - Overlays.editOverlay(grabberSpotLightLineB, { visible: false }); + Overlays.editOverlay(grabberSpotLightRadius, { + visible: false + }); + Overlays.editOverlay(grabberSpotLightL, { + visible: false + }); + Overlays.editOverlay(grabberSpotLightR, { + visible: false + }); + Overlays.editOverlay(grabberSpotLightT, { + visible: false + }); + Overlays.editOverlay(grabberSpotLightB, { + visible: false + }); + Overlays.editOverlay(grabberSpotLightCircle, { + visible: false + }); + Overlays.editOverlay(grabberSpotLightLineL, { + visible: false + }); + Overlays.editOverlay(grabberSpotLightLineR, { + visible: false + }); + Overlays.editOverlay(grabberSpotLightLineT, { + visible: false + }); + Overlays.editOverlay(grabberSpotLightLineB, { + visible: false + }); } else { - Overlays.editOverlay(grabberSpotLightCenter, { visible: false }); - Overlays.editOverlay(grabberSpotLightRadius, { visible: false }); - Overlays.editOverlay(grabberSpotLightL, { visible: false }); - Overlays.editOverlay(grabberSpotLightR, { visible: false }); - Overlays.editOverlay(grabberSpotLightT, { visible: false }); - Overlays.editOverlay(grabberSpotLightB, { visible: false }); - Overlays.editOverlay(grabberSpotLightCircle, { visible: false }); - Overlays.editOverlay(grabberSpotLightLineL, { visible: false }); - Overlays.editOverlay(grabberSpotLightLineR, { visible: false }); - Overlays.editOverlay(grabberSpotLightLineT, { visible: false }); - Overlays.editOverlay(grabberSpotLightLineB, { visible: false }); + Overlays.editOverlay(grabberSpotLightCenter, { + visible: false + }); + Overlays.editOverlay(grabberSpotLightRadius, { + visible: false + }); + Overlays.editOverlay(grabberSpotLightL, { + visible: false + }); + Overlays.editOverlay(grabberSpotLightR, { + visible: false + }); + Overlays.editOverlay(grabberSpotLightT, { + visible: false + }); + Overlays.editOverlay(grabberSpotLightB, { + visible: false + }); + Overlays.editOverlay(grabberSpotLightCircle, { + visible: false + }); + Overlays.editOverlay(grabberSpotLightLineL, { + visible: false + }); + Overlays.editOverlay(grabberSpotLightLineR, { + visible: false + }); + Overlays.editOverlay(grabberSpotLightLineT, { + visible: false + }); + Overlays.editOverlay(grabberSpotLightLineB, { + visible: false + }); - Overlays.editOverlay(grabberPointLightCircleX, { visible: false }); - Overlays.editOverlay(grabberPointLightCircleY, { visible: false }); - Overlays.editOverlay(grabberPointLightCircleZ, { visible: false }); - Overlays.editOverlay(grabberPointLightT, { visible: false }); - Overlays.editOverlay(grabberPointLightB, { visible: false }); - Overlays.editOverlay(grabberPointLightL, { visible: false }); - Overlays.editOverlay(grabberPointLightR, { visible: false }); - Overlays.editOverlay(grabberPointLightF, { visible: false }); - Overlays.editOverlay(grabberPointLightN, { visible: false }); + Overlays.editOverlay(grabberPointLightCircleX, { + visible: false + }); + Overlays.editOverlay(grabberPointLightCircleY, { + visible: false + }); + Overlays.editOverlay(grabberPointLightCircleZ, { + visible: false + }); + Overlays.editOverlay(grabberPointLightT, { + visible: false + }); + Overlays.editOverlay(grabberPointLightB, { + visible: false + }); + Overlays.editOverlay(grabberPointLightL, { + visible: false + }); + Overlays.editOverlay(grabberPointLightR, { + visible: false + }); + Overlays.editOverlay(grabberPointLightF, { + visible: false + }); + Overlays.editOverlay(grabberPointLightN, { + visible: false + }); } } - Overlays.editOverlay(grabberLBN, { visible: stretchHandlesVisible, rotation: rotation, position: LBN }); - Overlays.editOverlay(grabberRBN, { visible: stretchHandlesVisible, rotation: rotation, position: RBN }); - Overlays.editOverlay(grabberLBF, { visible: stretchHandlesVisible, rotation: rotation, position: LBF }); - Overlays.editOverlay(grabberRBF, { visible: stretchHandlesVisible, rotation: rotation, position: RBF }); + Overlays.editOverlay(grabberLBN, { + visible: stretchHandlesVisible, + rotation: rotation, + position: LBN + }); + Overlays.editOverlay(grabberRBN, { + visible: stretchHandlesVisible, + rotation: rotation, + position: RBN + }); + Overlays.editOverlay(grabberLBF, { + visible: stretchHandlesVisible, + rotation: rotation, + position: LBF + }); + Overlays.editOverlay(grabberRBF, { + visible: stretchHandlesVisible, + rotation: rotation, + position: RBF + }); - Overlays.editOverlay(grabberLTN, { visible: extendedStretchHandlesVisible, rotation: rotation, position: LTN }); - Overlays.editOverlay(grabberRTN, { visible: extendedStretchHandlesVisible, rotation: rotation, position: RTN }); - Overlays.editOverlay(grabberLTF, { visible: extendedStretchHandlesVisible, rotation: rotation, position: LTF }); - Overlays.editOverlay(grabberRTF, { visible: extendedStretchHandlesVisible, rotation: rotation, position: RTF }); + Overlays.editOverlay(grabberLTN, { + visible: extendedStretchHandlesVisible, + rotation: rotation, + position: LTN + }); + Overlays.editOverlay(grabberRTN, { + visible: extendedStretchHandlesVisible, + rotation: rotation, + position: RTN + }); + Overlays.editOverlay(grabberLTF, { + visible: extendedStretchHandlesVisible, + rotation: rotation, + position: LTF + }); + Overlays.editOverlay(grabberRTF, { + visible: extendedStretchHandlesVisible, + rotation: rotation, + position: RTF + }); - Overlays.editOverlay(grabberTOP, { visible: stretchHandlesVisible, rotation: rotation, position: TOP }); - Overlays.editOverlay(grabberBOTTOM, { visible: stretchHandlesVisible, rotation: rotation, position: BOTTOM }); - Overlays.editOverlay(grabberLEFT, { visible: extendedStretchHandlesVisible, rotation: rotation, position: LEFT }); - Overlays.editOverlay(grabberRIGHT, { visible: extendedStretchHandlesVisible, rotation: rotation, position: RIGHT }); - Overlays.editOverlay(grabberNEAR, { visible: extendedStretchHandlesVisible, rotation: rotation, position: NEAR }); - Overlays.editOverlay(grabberFAR, { visible: extendedStretchHandlesVisible, rotation: rotation, position: FAR }); + Overlays.editOverlay(grabberTOP, { + visible: stretchHandlesVisible, + rotation: rotation, + position: TOP + }); + Overlays.editOverlay(grabberBOTTOM, { + visible: stretchHandlesVisible, + rotation: rotation, + position: BOTTOM + }); + Overlays.editOverlay(grabberLEFT, { + visible: extendedStretchHandlesVisible, + rotation: rotation, + position: LEFT + }); + Overlays.editOverlay(grabberRIGHT, { + visible: extendedStretchHandlesVisible, + rotation: rotation, + position: RIGHT + }); + Overlays.editOverlay(grabberNEAR, { + visible: extendedStretchHandlesVisible, + rotation: rotation, + position: NEAR + }); + Overlays.editOverlay(grabberFAR, { + visible: extendedStretchHandlesVisible, + rotation: rotation, + position: FAR + }); var boxPosition = Vec3.multiplyQbyV(rotation, center); boxPosition = Vec3.sum(position, boxPosition); @@ -1346,9 +2089,17 @@ SelectionDisplay = (function () { for (var i = 0; i < overlaysNeeded; i++) { selectionBoxes.push( Overlays.addOverlay("cube", { - position: { x: 0, y: 0, z: 0 }, + position: { + x: 0, + y: 0, + z: 0 + }, size: 1, - color: { red: 255, green: 153, blue: 0 }, + color: { + red: 255, + green: 153, + blue: 0 + }, alpha: 1, solid: false, visible: false, @@ -1366,7 +2117,11 @@ SelectionDisplay = (function () { // Adjust overlay position to take registrationPoint into account // centeredRP = registrationPoint with range [-0.5, 0.5] - var centeredRP = Vec3.subtract(properties.registrationPoint, { x: 0.5, y: 0.5, z: 0.5 }); + var centeredRP = Vec3.subtract(properties.registrationPoint, { + x: 0.5, + y: 0.5, + z: 0.5 + }); var offset = vec3Mult(properties.dimensions, centeredRP); offset = Vec3.multiply(-1, offset); offset = Vec3.multiplyQbyV(properties.rotation, offset); @@ -1382,25 +2137,81 @@ SelectionDisplay = (function () { } // Hide any remaining selection boxes for (; i < selectionBoxes.length; i++) { - Overlays.editOverlay(selectionBoxes[i], { visible: false }); + Overlays.editOverlay(selectionBoxes[i], { + visible: false + }); } - Overlays.editOverlay(grabberEdgeTR, { visible: extendedStretchHandlesVisible, rotation: rotation, position: EdgeTR }); - Overlays.editOverlay(grabberEdgeTL, { visible: extendedStretchHandlesVisible, rotation: rotation, position: EdgeTL }); - Overlays.editOverlay(grabberEdgeTF, { visible: extendedStretchHandlesVisible, rotation: rotation, position: EdgeTF }); - Overlays.editOverlay(grabberEdgeTN, { visible: extendedStretchHandlesVisible, rotation: rotation, position: EdgeTN }); - Overlays.editOverlay(grabberEdgeBR, { visible: stretchHandlesVisible, rotation: rotation, position: EdgeBR }); - Overlays.editOverlay(grabberEdgeBL, { visible: stretchHandlesVisible, rotation: rotation, position: EdgeBL }); - Overlays.editOverlay(grabberEdgeBF, { visible: stretchHandlesVisible, rotation: rotation, position: EdgeBF }); - Overlays.editOverlay(grabberEdgeBN, { visible: stretchHandlesVisible, rotation: rotation, position: EdgeBN }); - Overlays.editOverlay(grabberEdgeNR, { visible: extendedStretchHandlesVisible, rotation: rotation, position: EdgeNR }); - Overlays.editOverlay(grabberEdgeNL, { visible: extendedStretchHandlesVisible, rotation: rotation, position: EdgeNL }); - Overlays.editOverlay(grabberEdgeFR, { visible: extendedStretchHandlesVisible, rotation: rotation, position: EdgeFR }); - Overlays.editOverlay(grabberEdgeFL, { visible: extendedStretchHandlesVisible, rotation: rotation, position: EdgeFL }); + Overlays.editOverlay(grabberEdgeTR, { + visible: extendedStretchHandlesVisible, + rotation: rotation, + position: EdgeTR + }); + Overlays.editOverlay(grabberEdgeTL, { + visible: extendedStretchHandlesVisible, + rotation: rotation, + position: EdgeTL + }); + Overlays.editOverlay(grabberEdgeTF, { + visible: extendedStretchHandlesVisible, + rotation: rotation, + position: EdgeTF + }); + Overlays.editOverlay(grabberEdgeTN, { + visible: extendedStretchHandlesVisible, + rotation: rotation, + position: EdgeTN + }); + Overlays.editOverlay(grabberEdgeBR, { + visible: stretchHandlesVisible, + rotation: rotation, + position: EdgeBR + }); + Overlays.editOverlay(grabberEdgeBL, { + visible: stretchHandlesVisible, + rotation: rotation, + position: EdgeBL + }); + Overlays.editOverlay(grabberEdgeBF, { + visible: stretchHandlesVisible, + rotation: rotation, + position: EdgeBF + }); + Overlays.editOverlay(grabberEdgeBN, { + visible: stretchHandlesVisible, + rotation: rotation, + position: EdgeBN + }); + Overlays.editOverlay(grabberEdgeNR, { + visible: extendedStretchHandlesVisible, + rotation: rotation, + position: EdgeNR + }); + Overlays.editOverlay(grabberEdgeNL, { + visible: extendedStretchHandlesVisible, + rotation: rotation, + position: EdgeNL + }); + Overlays.editOverlay(grabberEdgeFR, { + visible: extendedStretchHandlesVisible, + rotation: rotation, + position: EdgeFR + }); + Overlays.editOverlay(grabberEdgeFL, { + visible: extendedStretchHandlesVisible, + rotation: rotation, + position: EdgeFL + }); var grabberMoveUpOffset = 0.1; - grabberMoveUpPosition = { x: position.x, y: position.y + worldTop + grabberMoveUpOffset, z: position.z } - Overlays.editOverlay(grabberMoveUp, { visible: activeTool == null || mode == "TRANSLATE_UP_DOWN" }); + grabberMoveUpPosition = { + x: position.x, + y: position.y + worldTop + grabberMoveUpOffset, + z: position.z + } + Overlays.editOverlay(grabberMoveUp, { + visible: activeTool == null || mode == "TRANSLATE_UP_DOWN" + }); Overlays.editOverlay(baseOfEntityProjectionOverlay, { visible: mode != "ROTATE_YAW" && mode != "ROTATE_PITCH" && mode != "ROTATE_ROLL", @@ -1423,16 +2234,19 @@ SelectionDisplay = (function () { that.setOverlaysVisible = function(isVisible) { var length = allOverlays.length; for (var i = 0; i < length; i++) { - Overlays.editOverlay(allOverlays[i], { visible: isVisible }); + Overlays.editOverlay(allOverlays[i], { + visible: isVisible + }); } length = selectionBoxes.length; for (var i = 0; i < length; i++) { - Overlays.editOverlay(selectionBoxes[i], { visible: isVisible }); + Overlays.editOverlay(selectionBoxes[i], { + visible: isVisible + }); } }; - that.unselect = function (entityID) { - }; + that.unselect = function(entityID) {}; var initialXZPick = null; var isConstrained = false; @@ -1447,7 +2261,11 @@ SelectionDisplay = (function () { var dimensions = SelectionManager.worldDimensions; var pickRay = Camera.computePickRay(event.x, event.y); - initialXZPick = rayPlaneIntersection(pickRay, startPosition, { x: 0, y: 1, z: 0 }); + initialXZPick = rayPlaneIntersection(pickRay, startPosition, { + x: 0, + y: 1, + z: 0 + }); // Duplicate entities if alt is pressed. This will make a // copy of the selected entities and move the _original_ entities, not @@ -1473,13 +2291,21 @@ SelectionDisplay = (function () { onEnd: function(event, reason) { pushCommandForSelections(duplicatedEntityIDs); - Overlays.editOverlay(xRailOverlay, { visible: false }); - Overlays.editOverlay(zRailOverlay, { visible: false }); + Overlays.editOverlay(xRailOverlay, { + visible: false + }); + Overlays.editOverlay(zRailOverlay, { + visible: false + }); }, onMove: function(event) { pickRay = Camera.computePickRay(event.x, event.y); - var pick = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, { x: 0, y: 1, z: 0 }); + var pick = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, { + x: 0, + y: 1, + z: 0 + }); var vector = Vec3.subtract(pick, initialXZPick); // If shifted, constrain to one axis @@ -1490,19 +2316,49 @@ SelectionDisplay = (function () { vector.x = 0; } if (!isConstrained) { - Overlays.editOverlay(xRailOverlay, { visible: true }); - var xStart = Vec3.sum(startPosition, { x: -10000, y: 0, z: 0 }); - var xEnd = Vec3.sum(startPosition, { x: 10000, y: 0, z: 0 }); - var zStart = Vec3.sum(startPosition, { x: 0, y: 0, z: -10000 }); - var zEnd = Vec3.sum(startPosition, { x: 0, y: 0, z: 10000 }); - Overlays.editOverlay(xRailOverlay, { start: xStart, end: xEnd, visible: true }); - Overlays.editOverlay(zRailOverlay, { start: zStart, end: zEnd, visible: true }); + Overlays.editOverlay(xRailOverlay, { + visible: true + }); + var xStart = Vec3.sum(startPosition, { + x: -10000, + y: 0, + z: 0 + }); + var xEnd = Vec3.sum(startPosition, { + x: 10000, + y: 0, + z: 0 + }); + var zStart = Vec3.sum(startPosition, { + x: 0, + y: 0, + z: -10000 + }); + var zEnd = Vec3.sum(startPosition, { + x: 0, + y: 0, + z: 10000 + }); + Overlays.editOverlay(xRailOverlay, { + start: xStart, + end: xEnd, + visible: true + }); + Overlays.editOverlay(zRailOverlay, { + start: zStart, + end: zEnd, + visible: true + }); isConstrained = true; } } else { if (isConstrained) { - Overlays.editOverlay(xRailOverlay, { visible: false }); - Overlays.editOverlay(zRailOverlay, { visible: false }); + Overlays.editOverlay(xRailOverlay, { + visible: false + }); + Overlays.editOverlay(zRailOverlay, { + visible: false + }); isConstrained = false; } } @@ -1510,14 +2366,18 @@ SelectionDisplay = (function () { constrainMajorOnly = event.isControl; var cornerPosition = Vec3.sum(startPosition, Vec3.multiply(-0.5, selectionManager.worldDimensions)); vector = Vec3.subtract( - grid.snapToGrid(Vec3.sum(cornerPosition, vector), constrainMajorOnly), - cornerPosition); + grid.snapToGrid(Vec3.sum(cornerPosition, vector), constrainMajorOnly), + cornerPosition); var wantDebug = false; for (var i = 0; i < SelectionManager.selections.length; i++) { var properties = SelectionManager.savedProperties[SelectionManager.selections[i]]; - var newPosition = Vec3.sum(properties.position, { x: vector.x, y: 0, z: vector.z }); + var newPosition = Vec3.sum(properties.position, { + x: vector.x, + y: 0, + z: vector.z + }); Entities.editEntity(SelectionManager.selections[i], { position: newPosition, }); @@ -1533,7 +2393,7 @@ SelectionDisplay = (function () { SelectionManager._update(); } }; - + var lastXYPick = null var upDownPickNormal = null; addGrabberTool(grabberMoveUp, { @@ -1579,11 +2439,11 @@ SelectionDisplay = (function () { var vector = Vec3.subtract(newIntersection, lastXYPick); vector = grid.snapToGrid(vector); - + // we only care about the Y axis vector.x = 0; vector.z = 0; - + var wantDebug = false; if (wantDebug) { print("translateUpDown... "); @@ -1609,12 +2469,16 @@ SelectionDisplay = (function () { }); var vec3Mult = function(v1, v2) { - return { x: v1.x * v2.x, y: v1.y * v2.y, z: v1.z * v2.z }; - } - // stretchMode - name of mode - // direction - direction to stretch in - // pivot - point to use as a pivot - // offset - the position of the overlay tool relative to the selections center position + return { + x: v1.x * v2.x, + y: v1.y * v2.y, + z: v1.z * v2.z + }; + } + // stretchMode - name of mode + // direction - direction to stretch in + // pivot - point to use as a pivot + // offset - the position of the overlay tool relative to the selections center position var makeStretchTool = function(stretchMode, direction, pivot, offset, customOnMove) { var signs = { x: direction.x < 0 ? -1 : (direction.x > 0 ? 1 : 0), @@ -1659,7 +2523,11 @@ SelectionDisplay = (function () { } // Modify range of registrationPoint to be [-0.5, 0.5] - var centeredRP = Vec3.subtract(registrationPoint, { x: 0.5, y: 0.5, z: 0.5 }); + var centeredRP = Vec3.subtract(registrationPoint, { + x: 0.5, + y: 0.5, + z: 0.5 + }); // Scale pivot to be in the same range as registrationPoint var scaledPivot = Vec3.multiply(0.5, pivot) @@ -1675,9 +2543,17 @@ SelectionDisplay = (function () { pickRayPosition = Vec3.sum(initialPosition, Vec3.multiplyQbyV(rotation, scaledOffsetWorld)); if (numDimensions == 1 && mask.x) { - var start = Vec3.multiplyQbyV(rotation, { x: -10000, y: 0, z: 0 }); + var start = Vec3.multiplyQbyV(rotation, { + x: -10000, + y: 0, + z: 0 + }); start = Vec3.sum(start, properties.position); - var end = Vec3.multiplyQbyV(rotation, { x: 10000, y: 0, z: 0 }); + var end = Vec3.multiplyQbyV(rotation, { + x: 10000, + y: 0, + z: 0 + }); end = Vec3.sum(end, properties.position); Overlays.editOverlay(xRailOverlay, { start: start, @@ -1686,9 +2562,17 @@ SelectionDisplay = (function () { }); } if (numDimensions == 1 && mask.y) { - var start = Vec3.multiplyQbyV(rotation, { x: 0, y: -10000, z: 0 }); + var start = Vec3.multiplyQbyV(rotation, { + x: 0, + y: -10000, + z: 0 + }); start = Vec3.sum(start, properties.position); - var end = Vec3.multiplyQbyV(rotation, { x: 0, y: 10000, z: 0 }); + var end = Vec3.multiplyQbyV(rotation, { + x: 0, + y: 10000, + z: 0 + }); end = Vec3.sum(end, properties.position); Overlays.editOverlay(yRailOverlay, { start: start, @@ -1697,9 +2581,17 @@ SelectionDisplay = (function () { }); } if (numDimensions == 1 && mask.z) { - var start = Vec3.multiplyQbyV(rotation, { x: 0, y: 0, z: -10000 }); + var start = Vec3.multiplyQbyV(rotation, { + x: 0, + y: 0, + z: -10000 + }); start = Vec3.sum(start, properties.position); - var end = Vec3.multiplyQbyV(rotation, { x: 0, y: 0, z: 10000 }); + var end = Vec3.multiplyQbyV(rotation, { + x: 0, + y: 0, + z: 10000 + }); end = Vec3.sum(end, properties.position); Overlays.editOverlay(zRailOverlay, { start: start, @@ -1709,26 +2601,50 @@ SelectionDisplay = (function () { } if (numDimensions == 1) { if (mask.x == 1) { - planeNormal = { x: 0, y: 1, z: 0 }; + planeNormal = { + x: 0, + y: 1, + z: 0 + }; } else if (mask.y == 1) { - planeNormal = { x: 1, y: 0, z: 0 }; + planeNormal = { + x: 1, + y: 0, + z: 0 + }; } else { - planeNormal = { x: 0, y: 1, z: 0 }; + planeNormal = { + x: 0, + y: 1, + z: 0 + }; } } else if (numDimensions == 2) { if (mask.x == 0) { - planeNormal = { x: 1, y: 0, z: 0 }; + planeNormal = { + x: 1, + y: 0, + z: 0 + }; } else if (mask.y == 0) { - planeNormal = { x: 0, y: 1, z: 0 }; + planeNormal = { + x: 0, + y: 1, + z: 0 + }; } else { - planeNormal = { x: 0, y: 0, z: z }; + planeNormal = { + x: 0, + y: 0, + z: z + }; } } planeNormal = Vec3.multiplyQbyV(rotation, planeNormal); var pickRay = Camera.computePickRay(event.x, event.y); lastPick = rayPlaneIntersection(pickRay, - pickRayPosition, - planeNormal); + pickRayPosition, + planeNormal); // Overlays.editOverlay(normalLine, { // start: initialPosition, @@ -1739,9 +2655,15 @@ SelectionDisplay = (function () { }; var onEnd = function(event, reason) { - Overlays.editOverlay(xRailOverlay, { visible: false }); - Overlays.editOverlay(yRailOverlay, { visible: false }); - Overlays.editOverlay(zRailOverlay, { visible: false }); + Overlays.editOverlay(xRailOverlay, { + visible: false + }); + Overlays.editOverlay(yRailOverlay, { + visible: false + }); + Overlays.editOverlay(zRailOverlay, { + visible: false + }); pushCommandForSelections(); }; @@ -1762,8 +2684,8 @@ SelectionDisplay = (function () { var pickRay = Camera.computePickRay(event.x, event.y); newPick = rayPlaneIntersection(pickRay, - pickRayPosition, - planeNormal); + pickRayPosition, + planeNormal); var vector = Vec3.subtract(newPick, lastPick); vector = Vec3.multiplyQbyV(Quat.inverse(rotation), vector); @@ -1798,14 +2720,14 @@ SelectionDisplay = (function () { } else { newDimensions = Vec3.sum(initialDimensions, changeInDimensions); } - + newDimensions.x = Math.max(newDimensions.x, MINIMUM_DIMENSION); newDimensions.y = Math.max(newDimensions.y, MINIMUM_DIMENSION); newDimensions.z = Math.max(newDimensions.z, MINIMUM_DIMENSION); - + var changeInPosition = Vec3.multiplyQbyV(rotation, vec3Mult(deltaPivot, changeInDimensions)); var newPosition = Vec3.sum(initialPosition, changeInPosition); - + for (var i = 0; i < SelectionManager.selections.length; i++) { Entities.editEntity(SelectionManager.selections[i], { position: newPosition, @@ -1882,7 +2804,11 @@ SelectionDisplay = (function () { size = props.dimensions.z + change.z; } - var newDimensions = { x: size, y: size, z: size }; + var newDimensions = { + x: size, + y: size, + z: size + }; Entities.editEntity(selectionManager.selections[0], { dimensions: newDimensions, @@ -1891,47 +2817,411 @@ SelectionDisplay = (function () { SelectionManager._update(); } - addStretchTool(grabberNEAR, "STRETCH_NEAR", { x: 0, y: 0, z: 1 }, { x: 0, y: 0, z: 1 }, { x: 0, y: 0, z: -1 }); - addStretchTool(grabberFAR, "STRETCH_FAR", { x: 0, y: 0, z: -1 }, { x: 0, y: 0, z: -1 }, { x: 0, y: 0, z: 1 }); - addStretchTool(grabberTOP, "STRETCH_TOP", { x: 0, y: -1, z: 0 }, { x: 0, y: -1, z: 0 }, { x: 0, y: 1, z: 0 }); - addStretchTool(grabberBOTTOM, "STRETCH_BOTTOM", { x: 0, y: 1, z: 0 }, { x: 0, y: 1, z: 0 }, { x: 0, y: -1, z: 0 }); - addStretchTool(grabberRIGHT, "STRETCH_RIGHT", { x: -1, y: 0, z: 0 }, { x: -1, y: 0, z: 0 }, { x: 1, y: 0, z: 0 }); - addStretchTool(grabberLEFT, "STRETCH_LEFT", { x: 1, y: 0, z: 0 }, { x: 1, y: 0, z: 0 }, { x: -1, y: 0, z: 0 }); + addStretchTool(grabberNEAR, "STRETCH_NEAR", { + x: 0, + y: 0, + z: 1 + }, { + x: 0, + y: 0, + z: 1 + }, { + x: 0, + y: 0, + z: -1 + }); + addStretchTool(grabberFAR, "STRETCH_FAR", { + x: 0, + y: 0, + z: -1 + }, { + x: 0, + y: 0, + z: -1 + }, { + x: 0, + y: 0, + z: 1 + }); + addStretchTool(grabberTOP, "STRETCH_TOP", { + x: 0, + y: -1, + z: 0 + }, { + x: 0, + y: -1, + z: 0 + }, { + x: 0, + y: 1, + z: 0 + }); + addStretchTool(grabberBOTTOM, "STRETCH_BOTTOM", { + x: 0, + y: 1, + z: 0 + }, { + x: 0, + y: 1, + z: 0 + }, { + x: 0, + y: -1, + z: 0 + }); + addStretchTool(grabberRIGHT, "STRETCH_RIGHT", { + x: -1, + y: 0, + z: 0 + }, { + x: -1, + y: 0, + z: 0 + }, { + x: 1, + y: 0, + z: 0 + }); + addStretchTool(grabberLEFT, "STRETCH_LEFT", { + x: 1, + y: 0, + z: 0 + }, { + x: 1, + y: 0, + z: 0 + }, { + x: -1, + y: 0, + z: 0 + }); - addStretchTool(grabberSpotLightRadius, "STRETCH_RADIUS", { x: 0, y: 0, z: 0 }, { x: 0, y: 0, z: 1 }, { x: 0, y: 0, z: -1 }); - addStretchTool(grabberSpotLightT, "STRETCH_CUTOFF_T", { x: 0, y: 0, z: 0 }, { x: 0, y: -1, z: 0 }, { x: 0, y: 1, z: 0 }, cutoffStretchFunc); - addStretchTool(grabberSpotLightB, "STRETCH_CUTOFF_B", { x: 0, y: 0, z: 0 }, { x: 0, y: 1, z: 0 }, { x: 0, y: -1, z: 0 }, cutoffStretchFunc); - addStretchTool(grabberSpotLightL, "STRETCH_CUTOFF_L", { x: 0, y: 0, z: 0 }, { x: 1, y: 0, z: 0 }, { x: -1, y: 0, z: 0 }, cutoffStretchFunc); - addStretchTool(grabberSpotLightR, "STRETCH_CUTOFF_R", { x: 0, y: 0, z: 0 }, { x: -1, y: 0, z: 0 }, { x: 1, y: 0, z: 0 }, cutoffStretchFunc); + addStretchTool(grabberSpotLightRadius, "STRETCH_RADIUS", { + x: 0, + y: 0, + z: 0 + }, { + x: 0, + y: 0, + z: 1 + }, { + x: 0, + y: 0, + z: -1 + }); + addStretchTool(grabberSpotLightT, "STRETCH_CUTOFF_T", { + x: 0, + y: 0, + z: 0 + }, { + x: 0, + y: -1, + z: 0 + }, { + x: 0, + y: 1, + z: 0 + }, cutoffStretchFunc); + addStretchTool(grabberSpotLightB, "STRETCH_CUTOFF_B", { + x: 0, + y: 0, + z: 0 + }, { + x: 0, + y: 1, + z: 0 + }, { + x: 0, + y: -1, + z: 0 + }, cutoffStretchFunc); + addStretchTool(grabberSpotLightL, "STRETCH_CUTOFF_L", { + x: 0, + y: 0, + z: 0 + }, { + x: 1, + y: 0, + z: 0 + }, { + x: -1, + y: 0, + z: 0 + }, cutoffStretchFunc); + addStretchTool(grabberSpotLightR, "STRETCH_CUTOFF_R", { + x: 0, + y: 0, + z: 0 + }, { + x: -1, + y: 0, + z: 0 + }, { + x: 1, + y: 0, + z: 0 + }, cutoffStretchFunc); - addStretchTool(grabberPointLightT, "STRETCH_RADIUS_T", { x: 0, y: 0, z: 0 }, { x: 0, y: -1, z: 0 }, { x: 0, y: 0, z: 1 }, radiusStretchFunc); - addStretchTool(grabberPointLightB, "STRETCH_RADIUS_B", { x: 0, y: 0, z: 0 }, { x: 0, y: 1, z: 0 }, { x: 0, y: 0, z: 1 }, radiusStretchFunc); - addStretchTool(grabberPointLightL, "STRETCH_RADIUS_L", { x: 0, y: 0, z: 0 }, { x: 1, y: 0, z: 0 }, { x: 0, y: 0, z: 1 }, radiusStretchFunc); - addStretchTool(grabberPointLightR, "STRETCH_RADIUS_R", { x: 0, y: 0, z: 0 }, { x: -1, y: 0, z: 0 }, { x: 0, y: 0, z: 1 }, radiusStretchFunc); - addStretchTool(grabberPointLightF, "STRETCH_RADIUS_F", { x: 0, y: 0, z: 0 }, { x: 0, y: 0, z: -1 }, { x: 0, y: 0, z: 1 }, radiusStretchFunc); - addStretchTool(grabberPointLightN, "STRETCH_RADIUS_N", { x: 0, y: 0, z: 0 }, { x: 0, y: 0, z: 1 }, { x: 0, y: 0, z: -1 }, radiusStretchFunc); + addStretchTool(grabberPointLightT, "STRETCH_RADIUS_T", { + x: 0, + y: 0, + z: 0 + }, { + x: 0, + y: -1, + z: 0 + }, { + x: 0, + y: 0, + z: 1 + }, radiusStretchFunc); + addStretchTool(grabberPointLightB, "STRETCH_RADIUS_B", { + x: 0, + y: 0, + z: 0 + }, { + x: 0, + y: 1, + z: 0 + }, { + x: 0, + y: 0, + z: 1 + }, radiusStretchFunc); + addStretchTool(grabberPointLightL, "STRETCH_RADIUS_L", { + x: 0, + y: 0, + z: 0 + }, { + x: 1, + y: 0, + z: 0 + }, { + x: 0, + y: 0, + z: 1 + }, radiusStretchFunc); + addStretchTool(grabberPointLightR, "STRETCH_RADIUS_R", { + x: 0, + y: 0, + z: 0 + }, { + x: -1, + y: 0, + z: 0 + }, { + x: 0, + y: 0, + z: 1 + }, radiusStretchFunc); + addStretchTool(grabberPointLightF, "STRETCH_RADIUS_F", { + x: 0, + y: 0, + z: 0 + }, { + x: 0, + y: 0, + z: -1 + }, { + x: 0, + y: 0, + z: 1 + }, radiusStretchFunc); + addStretchTool(grabberPointLightN, "STRETCH_RADIUS_N", { + x: 0, + y: 0, + z: 0 + }, { + x: 0, + y: 0, + z: 1 + }, { + x: 0, + y: 0, + z: -1 + }, radiusStretchFunc); - addStretchTool(grabberLBN, "STRETCH_LBN", null, {x: 1, y: 0, z: 1}, { x: -1, y: -1, z: -1 }); - addStretchTool(grabberRBN, "STRETCH_RBN", null, {x: -1, y: 0, z: 1}, { x: 1, y: -1, z: -1 }); - addStretchTool(grabberLBF, "STRETCH_LBF", null, {x: 1, y: 0, z: -1}, { x: -1, y: -1, z: 1 }); - addStretchTool(grabberRBF, "STRETCH_RBF", null, {x: -1, y: 0, z: -1}, { x: 1, y: -1, z: 1 }); - addStretchTool(grabberLTN, "STRETCH_LTN", null, {x: 1, y: 0, z: 1}, { x: -1, y: 1, z: -1 }); - addStretchTool(grabberRTN, "STRETCH_RTN", null, {x: -1, y: 0, z: 1}, { x: 1, y: 1, z: -1 }); - addStretchTool(grabberLTF, "STRETCH_LTF", null, {x: 1, y: 0, z: -1}, { x: -1, y: 1, z: 1 }); - addStretchTool(grabberRTF, "STRETCH_RTF", null, {x: -1, y: 0, z: -1}, { x: 1, y: 1, z: 1 }); + addStretchTool(grabberLBN, "STRETCH_LBN", null, { + x: 1, + y: 0, + z: 1 + }, { + x: -1, + y: -1, + z: -1 + }); + addStretchTool(grabberRBN, "STRETCH_RBN", null, { + x: -1, + y: 0, + z: 1 + }, { + x: 1, + y: -1, + z: -1 + }); + addStretchTool(grabberLBF, "STRETCH_LBF", null, { + x: 1, + y: 0, + z: -1 + }, { + x: -1, + y: -1, + z: 1 + }); + addStretchTool(grabberRBF, "STRETCH_RBF", null, { + x: -1, + y: 0, + z: -1 + }, { + x: 1, + y: -1, + z: 1 + }); + addStretchTool(grabberLTN, "STRETCH_LTN", null, { + x: 1, + y: 0, + z: 1 + }, { + x: -1, + y: 1, + z: -1 + }); + addStretchTool(grabberRTN, "STRETCH_RTN", null, { + x: -1, + y: 0, + z: 1 + }, { + x: 1, + y: 1, + z: -1 + }); + addStretchTool(grabberLTF, "STRETCH_LTF", null, { + x: 1, + y: 0, + z: -1 + }, { + x: -1, + y: 1, + z: 1 + }); + addStretchTool(grabberRTF, "STRETCH_RTF", null, { + x: -1, + y: 0, + z: -1 + }, { + x: 1, + y: 1, + z: 1 + }); - addStretchTool(grabberEdgeTR, "STRETCH_EdgeTR", null, {x: 1, y: 1, z: 0}, { x: 1, y: 1, z: 0 }); - addStretchTool(grabberEdgeTL, "STRETCH_EdgeTL", null, {x: -1, y: 1, z: 0}, { x: -1, y: 1, z: 0 }); - addStretchTool(grabberEdgeTF, "STRETCH_EdgeTF", null, {x: 0, y: 1, z: -1}, { x: 0, y: 1, z: -1 }); - addStretchTool(grabberEdgeTN, "STRETCH_EdgeTN", null, {x: 0, y: 1, z: 1}, { x: 0, y: 1, z: 1 }); - addStretchTool(grabberEdgeBR, "STRETCH_EdgeBR", null, {x: -1, y: 0, z: 0}, { x: 1, y: -1, z: 0 }); - addStretchTool(grabberEdgeBL, "STRETCH_EdgeBL", null, {x: 1, y: 0, z: 0}, { x: -1, y: -1, z: 0 }); - addStretchTool(grabberEdgeBF, "STRETCH_EdgeBF", null, {x: 0, y: 0, z: -1}, { x: 0, y: -1, z: -1 }); - addStretchTool(grabberEdgeBN, "STRETCH_EdgeBN", null, {x: 0, y: 0, z: 1}, { x: 0, y: -1, z: 1 }); - addStretchTool(grabberEdgeNR, "STRETCH_EdgeNR", null, {x: -1, y: 0, z: 1}, { x: 1, y: 0, z: -1 }); - addStretchTool(grabberEdgeNL, "STRETCH_EdgeNL", null, {x: 1, y: 0, z: 1}, { x: -1, y: 0, z: -1 }); - addStretchTool(grabberEdgeFR, "STRETCH_EdgeFR", null, {x: -1, y: 0, z: -1}, { x: 1, y: 0, z: 1 }); - addStretchTool(grabberEdgeFL, "STRETCH_EdgeFL", null, {x: 1, y: 0, z: -1}, { x: -1, y: 0, z: 1 }); + addStretchTool(grabberEdgeTR, "STRETCH_EdgeTR", null, { + x: 1, + y: 1, + z: 0 + }, { + x: 1, + y: 1, + z: 0 + }); + addStretchTool(grabberEdgeTL, "STRETCH_EdgeTL", null, { + x: -1, + y: 1, + z: 0 + }, { + x: -1, + y: 1, + z: 0 + }); + addStretchTool(grabberEdgeTF, "STRETCH_EdgeTF", null, { + x: 0, + y: 1, + z: -1 + }, { + x: 0, + y: 1, + z: -1 + }); + addStretchTool(grabberEdgeTN, "STRETCH_EdgeTN", null, { + x: 0, + y: 1, + z: 1 + }, { + x: 0, + y: 1, + z: 1 + }); + addStretchTool(grabberEdgeBR, "STRETCH_EdgeBR", null, { + x: -1, + y: 0, + z: 0 + }, { + x: 1, + y: -1, + z: 0 + }); + addStretchTool(grabberEdgeBL, "STRETCH_EdgeBL", null, { + x: 1, + y: 0, + z: 0 + }, { + x: -1, + y: -1, + z: 0 + }); + addStretchTool(grabberEdgeBF, "STRETCH_EdgeBF", null, { + x: 0, + y: 0, + z: -1 + }, { + x: 0, + y: -1, + z: -1 + }); + addStretchTool(grabberEdgeBN, "STRETCH_EdgeBN", null, { + x: 0, + y: 0, + z: 1 + }, { + x: 0, + y: -1, + z: 1 + }); + addStretchTool(grabberEdgeNR, "STRETCH_EdgeNR", null, { + x: -1, + y: 0, + z: 1 + }, { + x: 1, + y: 0, + z: -1 + }); + addStretchTool(grabberEdgeNL, "STRETCH_EdgeNL", null, { + x: 1, + y: 0, + z: 1 + }, { + x: -1, + y: 0, + z: -1 + }); + addStretchTool(grabberEdgeFR, "STRETCH_EdgeFR", null, { + x: -1, + y: 0, + z: -1 + }, { + x: 1, + y: 0, + z: 1 + }); + addStretchTool(grabberEdgeFL, "STRETCH_EdgeFL", null, { + x: 1, + y: 0, + z: -1 + }, { + x: -1, + y: 0, + z: 1 + }); function updateRotationDegreesOverlay(angleFromZero, handleRotation, centerPosition) { var angle = angleFromZero * (Math.PI / 180); @@ -1967,34 +3257,31 @@ SelectionDisplay = (function () { outerRadius = diagonal * 1.15; var innerAlpha = 0.2; var outerAlpha = 0.2; - Overlays.editOverlay(rotateOverlayInner, - { - visible: true, - size: innerRadius, - innerRadius: 0.9, - startAt: 0, - endAt: 360, - alpha: innerAlpha - }); + Overlays.editOverlay(rotateOverlayInner, { + visible: true, + size: innerRadius, + innerRadius: 0.9, + startAt: 0, + endAt: 360, + alpha: innerAlpha + }); - Overlays.editOverlay(rotateOverlayOuter, - { - visible: true, - size: outerRadius, - innerRadius: 0.9, - startAt: 0, - endAt: 360, - alpha: outerAlpha, - }); + Overlays.editOverlay(rotateOverlayOuter, { + visible: true, + size: outerRadius, + innerRadius: 0.9, + startAt: 0, + endAt: 360, + alpha: outerAlpha, + }); - Overlays.editOverlay(rotateOverlayCurrent, - { - visible: true, - size: outerRadius, - startAt: 0, - endAt: 0, - innerRadius: 0.9, - }); + Overlays.editOverlay(rotateOverlayCurrent, { + visible: true, + size: outerRadius, + startAt: 0, + endAt: 0, + innerRadius: 0.9, + }); Overlays.editOverlay(rotationDegreesDisplay, { visible: true, @@ -2003,19 +3290,35 @@ SelectionDisplay = (function () { updateRotationDegreesOverlay(0, yawHandleRotation, yawCenter); }, onEnd: function(event, reason) { - Overlays.editOverlay(rotateOverlayInner, { visible: false }); - Overlays.editOverlay(rotateOverlayOuter, { visible: false }); - Overlays.editOverlay(rotateOverlayCurrent, { visible: false }); - Overlays.editOverlay(rotationDegreesDisplay, { visible: false }); + Overlays.editOverlay(rotateOverlayInner, { + visible: false + }); + Overlays.editOverlay(rotateOverlayOuter, { + visible: false + }); + Overlays.editOverlay(rotateOverlayCurrent, { + visible: false + }); + Overlays.editOverlay(rotationDegreesDisplay, { + visible: false + }); pushCommandForSelections(); }, onMove: function(event) { var pickRay = Camera.computePickRay(event.x, event.y); - Overlays.editOverlay(selectionBox, { ignoreRayIntersection: true, visible: false}); - Overlays.editOverlay(baseOfEntityProjectionOverlay, { ignoreRayIntersection: true, visible: false }); - Overlays.editOverlay(rotateOverlayTarget, { ignoreRayIntersection: false }); - + Overlays.editOverlay(selectionBox, { + ignoreRayIntersection: true, + visible: false + }); + Overlays.editOverlay(baseOfEntityProjectionOverlay, { + ignoreRayIntersection: true, + visible: false + }); + Overlays.editOverlay(rotateOverlayTarget, { + ignoreRayIntersection: false + }); + var result = Overlays.findRayIntersection(pickRay); if (result.intersects) { @@ -2028,7 +3331,11 @@ SelectionDisplay = (function () { var snapToInner = distanceFromCenter < innerRadius; var snapAngle = snapToInner ? innerSnapAngle : 1.0; angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle; - var yawChange = Quat.fromVec3Degrees({ x: 0, y: angleFromZero, z: 0 }); + var yawChange = Quat.fromVec3Degrees({ + x: 0, + y: angleFromZero, + z: 0 + }); // Entities should only reposition if we are rotating multiple selections around // the selections center point. Otherwise, the rotation will be around the entities @@ -2066,19 +3373,43 @@ SelectionDisplay = (function () { endAtRemainder = startAtCurrent; } if (snapToInner) { - Overlays.editOverlay(rotateOverlayOuter, { startAt: 0, endAt: 360 }); - Overlays.editOverlay(rotateOverlayInner, { startAt: startAtRemainder, endAt: endAtRemainder }); - Overlays.editOverlay(rotateOverlayCurrent, { startAt: startAtCurrent, endAt: endAtCurrent, size: innerRadius, - majorTickMarksAngle: innerSnapAngle, minorTickMarksAngle: 0, - majorTickMarksLength: -0.25, minorTickMarksLength: 0, }); + Overlays.editOverlay(rotateOverlayOuter, { + startAt: 0, + endAt: 360 + }); + Overlays.editOverlay(rotateOverlayInner, { + startAt: startAtRemainder, + endAt: endAtRemainder + }); + Overlays.editOverlay(rotateOverlayCurrent, { + startAt: startAtCurrent, + endAt: endAtCurrent, + size: innerRadius, + majorTickMarksAngle: innerSnapAngle, + minorTickMarksAngle: 0, + majorTickMarksLength: -0.25, + minorTickMarksLength: 0, + }); } else { - Overlays.editOverlay(rotateOverlayInner, { startAt: 0, endAt: 360 }); - Overlays.editOverlay(rotateOverlayOuter, { startAt: startAtRemainder, endAt: endAtRemainder }); - Overlays.editOverlay(rotateOverlayCurrent, { startAt: startAtCurrent, endAt: endAtCurrent, size: outerRadius, - majorTickMarksAngle: 45.0, minorTickMarksAngle: 5, - majorTickMarksLength: 0.25, minorTickMarksLength: 0.1, }); + Overlays.editOverlay(rotateOverlayInner, { + startAt: 0, + endAt: 360 + }); + Overlays.editOverlay(rotateOverlayOuter, { + startAt: startAtRemainder, + endAt: endAtRemainder + }); + Overlays.editOverlay(rotateOverlayCurrent, { + startAt: startAtCurrent, + endAt: endAtCurrent, + size: outerRadius, + majorTickMarksAngle: 45.0, + minorTickMarksAngle: 5, + majorTickMarksLength: 0.25, + minorTickMarksLength: 0.1, + }); } - + } } }); @@ -2096,34 +3427,31 @@ SelectionDisplay = (function () { outerRadius = diagonal * 1.15; var innerAlpha = 0.2; var outerAlpha = 0.2; - Overlays.editOverlay(rotateOverlayInner, - { - visible: true, - size: innerRadius, - innerRadius: 0.9, - startAt: 0, - endAt: 360, - alpha: innerAlpha - }); + Overlays.editOverlay(rotateOverlayInner, { + visible: true, + size: innerRadius, + innerRadius: 0.9, + startAt: 0, + endAt: 360, + alpha: innerAlpha + }); - Overlays.editOverlay(rotateOverlayOuter, - { - visible: true, - size: outerRadius, - innerRadius: 0.9, - startAt: 0, - endAt: 360, - alpha: outerAlpha, - }); + Overlays.editOverlay(rotateOverlayOuter, { + visible: true, + size: outerRadius, + innerRadius: 0.9, + startAt: 0, + endAt: 360, + alpha: outerAlpha, + }); - Overlays.editOverlay(rotateOverlayCurrent, - { - visible: true, - size: outerRadius, - startAt: 0, - endAt: 0, - innerRadius: 0.9, - }); + Overlays.editOverlay(rotateOverlayCurrent, { + visible: true, + size: outerRadius, + startAt: 0, + endAt: 0, + innerRadius: 0.9, + }); Overlays.editOverlay(rotationDegreesDisplay, { visible: true, @@ -2132,18 +3460,34 @@ SelectionDisplay = (function () { updateRotationDegreesOverlay(0, pitchHandleRotation, pitchCenter); }, onEnd: function(event, reason) { - Overlays.editOverlay(rotateOverlayInner, { visible: false }); - Overlays.editOverlay(rotateOverlayOuter, { visible: false }); - Overlays.editOverlay(rotateOverlayCurrent, { visible: false }); - Overlays.editOverlay(rotationDegreesDisplay, { visible: false }); + Overlays.editOverlay(rotateOverlayInner, { + visible: false + }); + Overlays.editOverlay(rotateOverlayOuter, { + visible: false + }); + Overlays.editOverlay(rotateOverlayCurrent, { + visible: false + }); + Overlays.editOverlay(rotationDegreesDisplay, { + visible: false + }); pushCommandForSelections(); }, onMove: function(event) { var pickRay = Camera.computePickRay(event.x, event.y); - Overlays.editOverlay(selectionBox, { ignoreRayIntersection: true, visible: false}); - Overlays.editOverlay(baseOfEntityProjectionOverlay, { ignoreRayIntersection: true, visible: false }); - Overlays.editOverlay(rotateOverlayTarget, { ignoreRayIntersection: false }); + Overlays.editOverlay(selectionBox, { + ignoreRayIntersection: true, + visible: false + }); + Overlays.editOverlay(baseOfEntityProjectionOverlay, { + ignoreRayIntersection: true, + visible: false + }); + Overlays.editOverlay(rotateOverlayTarget, { + ignoreRayIntersection: false + }); var result = Overlays.findRayIntersection(pickRay); if (result.intersects) { @@ -2158,8 +3502,12 @@ SelectionDisplay = (function () { var snapToInner = distanceFromCenter < innerRadius; var snapAngle = snapToInner ? innerSnapAngle : 1.0; angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle; - - var pitchChange = Quat.fromVec3Degrees({ x: angleFromZero, y: 0, z: 0 }); + + var pitchChange = Quat.fromVec3Degrees({ + x: angleFromZero, + y: 0, + z: 0 + }); for (var i = 0; i < SelectionManager.selections.length; i++) { var entityID = SelectionManager.selections[i]; @@ -2188,17 +3536,41 @@ SelectionDisplay = (function () { endAtRemainder = startAtCurrent; } if (snapToInner) { - Overlays.editOverlay(rotateOverlayOuter, { startAt: 0, endAt: 360 }); - Overlays.editOverlay(rotateOverlayInner, { startAt: startAtRemainder, endAt: endAtRemainder }); - Overlays.editOverlay(rotateOverlayCurrent, { startAt: startAtCurrent, endAt: endAtCurrent, size: innerRadius, - majorTickMarksAngle: innerSnapAngle, minorTickMarksAngle: 0, - majorTickMarksLength: -0.25, minorTickMarksLength: 0, }); + Overlays.editOverlay(rotateOverlayOuter, { + startAt: 0, + endAt: 360 + }); + Overlays.editOverlay(rotateOverlayInner, { + startAt: startAtRemainder, + endAt: endAtRemainder + }); + Overlays.editOverlay(rotateOverlayCurrent, { + startAt: startAtCurrent, + endAt: endAtCurrent, + size: innerRadius, + majorTickMarksAngle: innerSnapAngle, + minorTickMarksAngle: 0, + majorTickMarksLength: -0.25, + minorTickMarksLength: 0, + }); } else { - Overlays.editOverlay(rotateOverlayInner, { startAt: 0, endAt: 360 }); - Overlays.editOverlay(rotateOverlayOuter, { startAt: startAtRemainder, endAt: endAtRemainder }); - Overlays.editOverlay(rotateOverlayCurrent, { startAt: startAtCurrent, endAt: endAtCurrent, size: outerRadius, - majorTickMarksAngle: 45.0, minorTickMarksAngle: 5, - majorTickMarksLength: 0.25, minorTickMarksLength: 0.1, }); + Overlays.editOverlay(rotateOverlayInner, { + startAt: 0, + endAt: 360 + }); + Overlays.editOverlay(rotateOverlayOuter, { + startAt: startAtRemainder, + endAt: endAtRemainder + }); + Overlays.editOverlay(rotateOverlayCurrent, { + startAt: startAtCurrent, + endAt: endAtCurrent, + size: outerRadius, + majorTickMarksAngle: 45.0, + minorTickMarksAngle: 5, + majorTickMarksLength: 0.25, + minorTickMarksLength: 0.1, + }); } } } @@ -2217,34 +3589,31 @@ SelectionDisplay = (function () { outerRadius = diagonal * 1.15; var innerAlpha = 0.2; var outerAlpha = 0.2; - Overlays.editOverlay(rotateOverlayInner, - { - visible: true, - size: innerRadius, - innerRadius: 0.9, - startAt: 0, - endAt: 360, - alpha: innerAlpha - }); + Overlays.editOverlay(rotateOverlayInner, { + visible: true, + size: innerRadius, + innerRadius: 0.9, + startAt: 0, + endAt: 360, + alpha: innerAlpha + }); - Overlays.editOverlay(rotateOverlayOuter, - { - visible: true, - size: outerRadius, - innerRadius: 0.9, - startAt: 0, - endAt: 360, - alpha: outerAlpha, - }); + Overlays.editOverlay(rotateOverlayOuter, { + visible: true, + size: outerRadius, + innerRadius: 0.9, + startAt: 0, + endAt: 360, + alpha: outerAlpha, + }); - Overlays.editOverlay(rotateOverlayCurrent, - { - visible: true, - size: outerRadius, - startAt: 0, - endAt: 0, - innerRadius: 0.9, - }); + Overlays.editOverlay(rotateOverlayCurrent, { + visible: true, + size: outerRadius, + startAt: 0, + endAt: 0, + innerRadius: 0.9, + }); Overlays.editOverlay(rotationDegreesDisplay, { visible: true, @@ -2253,18 +3622,34 @@ SelectionDisplay = (function () { updateRotationDegreesOverlay(0, rollHandleRotation, rollCenter); }, onEnd: function(event, reason) { - Overlays.editOverlay(rotateOverlayInner, { visible: false }); - Overlays.editOverlay(rotateOverlayOuter, { visible: false }); - Overlays.editOverlay(rotateOverlayCurrent, { visible: false }); - Overlays.editOverlay(rotationDegreesDisplay, { visible: false }); + Overlays.editOverlay(rotateOverlayInner, { + visible: false + }); + Overlays.editOverlay(rotateOverlayOuter, { + visible: false + }); + Overlays.editOverlay(rotateOverlayCurrent, { + visible: false + }); + Overlays.editOverlay(rotationDegreesDisplay, { + visible: false + }); pushCommandForSelections(); }, onMove: function(event) { var pickRay = Camera.computePickRay(event.x, event.y); - Overlays.editOverlay(selectionBox, { ignoreRayIntersection: true, visible: false}); - Overlays.editOverlay(baseOfEntityProjectionOverlay, { ignoreRayIntersection: true, visible: false }); - Overlays.editOverlay(rotateOverlayTarget, { ignoreRayIntersection: false }); + Overlays.editOverlay(selectionBox, { + ignoreRayIntersection: true, + visible: false + }); + Overlays.editOverlay(baseOfEntityProjectionOverlay, { + ignoreRayIntersection: true, + visible: false + }); + Overlays.editOverlay(rotateOverlayTarget, { + ignoreRayIntersection: false + }); var result = Overlays.findRayIntersection(pickRay); if (result.intersects) { @@ -2280,7 +3665,11 @@ SelectionDisplay = (function () { var snapAngle = snapToInner ? innerSnapAngle : 1.0; angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle; - var rollChange = Quat.fromVec3Degrees({ x: 0, y: 0, z: angleFromZero }); + var rollChange = Quat.fromVec3Degrees({ + x: 0, + y: 0, + z: angleFromZero + }); for (var i = 0; i < SelectionManager.selections.length; i++) { var entityID = SelectionManager.selections[i]; var properties = Entities.getEntityProperties(entityID); @@ -2308,17 +3697,41 @@ SelectionDisplay = (function () { endAtRemainder = startAtCurrent; } if (snapToInner) { - Overlays.editOverlay(rotateOverlayOuter, { startAt: 0, endAt: 360 }); - Overlays.editOverlay(rotateOverlayInner, { startAt: startAtRemainder, endAt: endAtRemainder }); - Overlays.editOverlay(rotateOverlayCurrent, { startAt: startAtCurrent, endAt: endAtCurrent, size: innerRadius, - majorTickMarksAngle: innerSnapAngle, minorTickMarksAngle: 0, - majorTickMarksLength: -0.25, minorTickMarksLength: 0, }); + Overlays.editOverlay(rotateOverlayOuter, { + startAt: 0, + endAt: 360 + }); + Overlays.editOverlay(rotateOverlayInner, { + startAt: startAtRemainder, + endAt: endAtRemainder + }); + Overlays.editOverlay(rotateOverlayCurrent, { + startAt: startAtCurrent, + endAt: endAtCurrent, + size: innerRadius, + majorTickMarksAngle: innerSnapAngle, + minorTickMarksAngle: 0, + majorTickMarksLength: -0.25, + minorTickMarksLength: 0, + }); } else { - Overlays.editOverlay(rotateOverlayInner, { startAt: 0, endAt: 360 }); - Overlays.editOverlay(rotateOverlayOuter, { startAt: startAtRemainder, endAt: endAtRemainder }); - Overlays.editOverlay(rotateOverlayCurrent, { startAt: startAtCurrent, endAt: endAtCurrent, size: outerRadius, - majorTickMarksAngle: 45.0, minorTickMarksAngle: 5, - majorTickMarksLength: 0.25, minorTickMarksLength: 0.1, }); + Overlays.editOverlay(rotateOverlayInner, { + startAt: 0, + endAt: 360 + }); + Overlays.editOverlay(rotateOverlayOuter, { + startAt: startAtRemainder, + endAt: endAtRemainder + }); + Overlays.editOverlay(rotateOverlayCurrent, { + startAt: startAtCurrent, + endAt: endAtCurrent, + size: outerRadius, + majorTickMarksAngle: 45.0, + minorTickMarksAngle: 5, + majorTickMarksLength: 0.25, + minorTickMarksLength: 0.1, + }); } } } @@ -2329,10 +3742,10 @@ SelectionDisplay = (function () { // FIXME - this cause problems with editing in the entity properties window //SelectionManager._update(); - + if (!Vec3.equal(Camera.getPosition(), lastCameraPosition) || !Quat.equal(Camera.getOrientation(), lastCameraOrientation)) { - + that.updateRotationHandles(); } } @@ -2347,12 +3760,20 @@ SelectionDisplay = (function () { var somethingClicked = false; var pickRay = Camera.computePickRay(event.x, event.y); - + // before we do a ray test for grabbers, disable the ray intersection for our selection box - Overlays.editOverlay(selectionBox, { ignoreRayIntersection: true }); - Overlays.editOverlay(yawHandle, { ignoreRayIntersection: true }); - Overlays.editOverlay(pitchHandle, { ignoreRayIntersection: true }); - Overlays.editOverlay(rollHandle, { ignoreRayIntersection: true }); + Overlays.editOverlay(selectionBox, { + ignoreRayIntersection: true + }); + Overlays.editOverlay(yawHandle, { + ignoreRayIntersection: true + }); + Overlays.editOverlay(pitchHandle, { + ignoreRayIntersection: true + }); + Overlays.editOverlay(rollHandle, { + ignoreRayIntersection: true + }); var result = Overlays.findRayIntersection(pickRay); if (result.intersects) { @@ -2377,14 +3798,16 @@ SelectionDisplay = (function () { activeTool.onBegin(event); } } else { - switch(result.overlayID) { + switch (result.overlayID) { case grabberMoveUp: mode = "TRANSLATE_UP_DOWN"; somethingClicked = true; // in translate mode, we hide our stretch handles... for (var i = 0; i < stretchHandles.length; i++) { - Overlays.editOverlay(stretchHandles[i], { visible: false }); + Overlays.editOverlay(stretchHandles[i], { + visible: false + }); } break; @@ -2397,8 +3820,8 @@ SelectionDisplay = (function () { break; case grabberFAR: - case grabberEdgeTF: // TODO: maybe this should be TOP+FAR stretching? - case grabberEdgeBF: // TODO: maybe this should be BOTTOM+FAR stretching? + case grabberEdgeTF: // TODO: maybe this should be TOP+FAR stretching? + case grabberEdgeBF: // TODO: maybe this should be BOTTOM+FAR stretching? mode = "STRETCH_FAR"; somethingClicked = true; break; @@ -2411,14 +3834,14 @@ SelectionDisplay = (function () { somethingClicked = true; break; case grabberRIGHT: - case grabberEdgeTR: // TODO: maybe this should be TOP+RIGHT stretching? - case grabberEdgeBR: // TODO: maybe this should be BOTTOM+RIGHT stretching? + case grabberEdgeTR: // TODO: maybe this should be TOP+RIGHT stretching? + case grabberEdgeBR: // TODO: maybe this should be BOTTOM+RIGHT stretching? mode = "STRETCH_RIGHT"; somethingClicked = true; break; case grabberLEFT: - case grabberEdgeTL: // TODO: maybe this should be TOP+LEFT stretching? - case grabberEdgeBL: // TODO: maybe this should be BOTTOM+LEFT stretching? + case grabberEdgeTL: // TODO: maybe this should be TOP+LEFT stretching? + case grabberEdgeBL: // TODO: maybe this should be BOTTOM+LEFT stretching? mode = "STRETCH_LEFT"; somethingClicked = true; break; @@ -2429,29 +3852,43 @@ SelectionDisplay = (function () { } } } - + // if one of the items above was clicked, then we know we are in translate or stretch mode, and we // should hide our rotate handles... if (somethingClicked) { - Overlays.editOverlay(yawHandle, { visible: false }); - Overlays.editOverlay(pitchHandle, { visible: false }); - Overlays.editOverlay(rollHandle, { visible: false }); - + Overlays.editOverlay(yawHandle, { + visible: false + }); + Overlays.editOverlay(pitchHandle, { + visible: false + }); + Overlays.editOverlay(rollHandle, { + visible: false + }); + if (mode != "TRANSLATE_UP_DOWN") { - Overlays.editOverlay(grabberMoveUp, { visible: false }); + Overlays.editOverlay(grabberMoveUp, { + visible: false + }); } } - + if (!somethingClicked) { - + print("rotate handle case..."); - + // After testing our stretch handles, then check out rotate handles - Overlays.editOverlay(yawHandle, { ignoreRayIntersection: false }); - Overlays.editOverlay(pitchHandle, { ignoreRayIntersection: false }); - Overlays.editOverlay(rollHandle, { ignoreRayIntersection: false }); + Overlays.editOverlay(yawHandle, { + ignoreRayIntersection: false + }); + Overlays.editOverlay(pitchHandle, { + ignoreRayIntersection: false + }); + Overlays.editOverlay(rollHandle, { + ignoreRayIntersection: false + }); var result = Overlays.findRayIntersection(pickRay); - + var overlayOrientation; var overlayCenter; @@ -2460,12 +3897,12 @@ SelectionDisplay = (function () { var pitch = angles.x; var yaw = angles.y; var roll = angles.z; - + originalRotation = properties.rotation; originalPitch = pitch; originalYaw = yaw; originalRoll = roll; - + if (result.intersects) { var tool = grabberTools[result.overlayID]; if (tool) { @@ -2476,7 +3913,7 @@ SelectionDisplay = (function () { activeTool.onBegin(event); } } - switch(result.overlayID) { + switch (result.overlayID) { case yawHandle: mode = "ROTATE_YAW"; somethingClicked = true; @@ -2514,59 +3951,147 @@ SelectionDisplay = (function () { print(" somethingClicked:" + somethingClicked); print(" mode:" + mode); - + if (somethingClicked) { - - Overlays.editOverlay(rotateOverlayTarget, { visible: true, rotation: overlayOrientation, position: overlayCenter }); - Overlays.editOverlay(rotateOverlayInner, { visible: true, rotation: overlayOrientation, position: overlayCenter }); - Overlays.editOverlay(rotateOverlayOuter, { visible: true, rotation: overlayOrientation, position: overlayCenter, startAt: 0, endAt: 360 }); - Overlays.editOverlay(rotateOverlayCurrent, { visible: true, rotation: overlayOrientation, position: overlayCenter, startAt: 0, endAt: 0 }); - Overlays.editOverlay(yawHandle, { visible: false }); - Overlays.editOverlay(pitchHandle, { visible: false }); - Overlays.editOverlay(rollHandle, { visible: false }); + + Overlays.editOverlay(rotateOverlayTarget, { + visible: true, + rotation: overlayOrientation, + position: overlayCenter + }); + Overlays.editOverlay(rotateOverlayInner, { + visible: true, + rotation: overlayOrientation, + position: overlayCenter + }); + Overlays.editOverlay(rotateOverlayOuter, { + visible: true, + rotation: overlayOrientation, + position: overlayCenter, + startAt: 0, + endAt: 360 + }); + Overlays.editOverlay(rotateOverlayCurrent, { + visible: true, + rotation: overlayOrientation, + position: overlayCenter, + startAt: 0, + endAt: 0 + }); + Overlays.editOverlay(yawHandle, { + visible: false + }); + Overlays.editOverlay(pitchHandle, { + visible: false + }); + Overlays.editOverlay(rollHandle, { + visible: false + }); - Overlays.editOverlay(yawHandle, { visible: false }); - Overlays.editOverlay(pitchHandle, { visible: false }); - Overlays.editOverlay(rollHandle, { visible: false }); - Overlays.editOverlay(grabberMoveUp, { visible: false }); - Overlays.editOverlay(grabberLBN, { visible: false }); - Overlays.editOverlay(grabberLBF, { visible: false }); - Overlays.editOverlay(grabberRBN, { visible: false }); - Overlays.editOverlay(grabberRBF, { visible: false }); - Overlays.editOverlay(grabberLTN, { visible: false }); - Overlays.editOverlay(grabberLTF, { visible: false }); - Overlays.editOverlay(grabberRTN, { visible: false }); - Overlays.editOverlay(grabberRTF, { visible: false }); + Overlays.editOverlay(yawHandle, { + visible: false + }); + Overlays.editOverlay(pitchHandle, { + visible: false + }); + Overlays.editOverlay(rollHandle, { + visible: false + }); + Overlays.editOverlay(grabberMoveUp, { + visible: false + }); + Overlays.editOverlay(grabberLBN, { + visible: false + }); + Overlays.editOverlay(grabberLBF, { + visible: false + }); + Overlays.editOverlay(grabberRBN, { + visible: false + }); + Overlays.editOverlay(grabberRBF, { + visible: false + }); + Overlays.editOverlay(grabberLTN, { + visible: false + }); + Overlays.editOverlay(grabberLTF, { + visible: false + }); + Overlays.editOverlay(grabberRTN, { + visible: false + }); + Overlays.editOverlay(grabberRTF, { + visible: false + }); - Overlays.editOverlay(grabberTOP, { visible: false }); - Overlays.editOverlay(grabberBOTTOM, { visible: false }); - Overlays.editOverlay(grabberLEFT, { visible: false }); - Overlays.editOverlay(grabberRIGHT, { visible: false }); - Overlays.editOverlay(grabberNEAR, { visible: false }); - Overlays.editOverlay(grabberFAR, { visible: false }); + Overlays.editOverlay(grabberTOP, { + visible: false + }); + Overlays.editOverlay(grabberBOTTOM, { + visible: false + }); + Overlays.editOverlay(grabberLEFT, { + visible: false + }); + Overlays.editOverlay(grabberRIGHT, { + visible: false + }); + Overlays.editOverlay(grabberNEAR, { + visible: false + }); + Overlays.editOverlay(grabberFAR, { + visible: false + }); - Overlays.editOverlay(grabberEdgeTR, { visible: false }); - Overlays.editOverlay(grabberEdgeTL, { visible: false }); - Overlays.editOverlay(grabberEdgeTF, { visible: false }); - Overlays.editOverlay(grabberEdgeTN, { visible: false }); - Overlays.editOverlay(grabberEdgeBR, { visible: false }); - Overlays.editOverlay(grabberEdgeBL, { visible: false }); - Overlays.editOverlay(grabberEdgeBF, { visible: false }); - Overlays.editOverlay(grabberEdgeBN, { visible: false }); - Overlays.editOverlay(grabberEdgeNR, { visible: false }); - Overlays.editOverlay(grabberEdgeNL, { visible: false }); - Overlays.editOverlay(grabberEdgeFR, { visible: false }); - Overlays.editOverlay(grabberEdgeFL, { visible: false }); + Overlays.editOverlay(grabberEdgeTR, { + visible: false + }); + Overlays.editOverlay(grabberEdgeTL, { + visible: false + }); + Overlays.editOverlay(grabberEdgeTF, { + visible: false + }); + Overlays.editOverlay(grabberEdgeTN, { + visible: false + }); + Overlays.editOverlay(grabberEdgeBR, { + visible: false + }); + Overlays.editOverlay(grabberEdgeBL, { + visible: false + }); + Overlays.editOverlay(grabberEdgeBF, { + visible: false + }); + Overlays.editOverlay(grabberEdgeBN, { + visible: false + }); + Overlays.editOverlay(grabberEdgeNR, { + visible: false + }); + Overlays.editOverlay(grabberEdgeNL, { + visible: false + }); + Overlays.editOverlay(grabberEdgeFR, { + visible: false + }); + Overlays.editOverlay(grabberEdgeFL, { + visible: false + }); } } if (!somethingClicked) { - Overlays.editOverlay(selectionBox, { ignoreRayIntersection: false }); + Overlays.editOverlay(selectionBox, { + ignoreRayIntersection: false + }); var result = Overlays.findRayIntersection(pickRay); if (result.intersects) { - switch(result.overlayID) { + switch (result.overlayID) { case selectionBox: activeTool = translateXZTool; mode = translateXZTool.mode; @@ -2584,17 +4109,25 @@ SelectionDisplay = (function () { if (somethingClicked) { pickRay = Camera.computePickRay(event.x, event.y); if (wantDebug) { - print("mousePressEvent()...... " + overlayNames[result.overlayID]); + print("mousePressEvent()...... " + overlayNames[result.overlayID]); } } // reset everything as intersectable... // TODO: we could optimize this since some of these were already flipped back - Overlays.editOverlay(selectionBox, { ignoreRayIntersection: false }); - Overlays.editOverlay(yawHandle, { ignoreRayIntersection: false }); - Overlays.editOverlay(pitchHandle, { ignoreRayIntersection: false }); - Overlays.editOverlay(rollHandle, { ignoreRayIntersection: false }); - + Overlays.editOverlay(selectionBox, { + ignoreRayIntersection: false + }); + Overlays.editOverlay(yawHandle, { + ignoreRayIntersection: false + }); + Overlays.editOverlay(pitchHandle, { + ignoreRayIntersection: false + }); + Overlays.editOverlay(rollHandle, { + ignoreRayIntersection: false + }); + return somethingClicked; }; @@ -2613,7 +4146,7 @@ SelectionDisplay = (function () { var highlightNeeded = false; if (result.intersects) { - switch(result.overlayID) { + switch (result.overlayID) { case yawHandle: case pitchHandle: case rollHandle: @@ -2621,7 +4154,7 @@ SelectionDisplay = (function () { pickedAlpha = handleAlpha; highlightNeeded = true; break; - + case grabberMoveUp: pickedColor = handleColor; pickedAlpha = handleAlpha; @@ -2682,30 +4215,42 @@ SelectionDisplay = (function () { default: if (previousHandle) { - Overlays.editOverlay(previousHandle, { color: previousHandleColor, alpha: previousHandleAlpha }); + Overlays.editOverlay(previousHandle, { + color: previousHandleColor, + alpha: previousHandleAlpha + }); previousHandle = false; } break; } - + if (highlightNeeded) { if (previousHandle) { - Overlays.editOverlay(previousHandle, { color: previousHandleColor, alpha: previousHandleAlpha }); + Overlays.editOverlay(previousHandle, { + color: previousHandleColor, + alpha: previousHandleAlpha + }); previousHandle = false; } - Overlays.editOverlay(result.overlayID, { color: highlightedHandleColor, alpha: highlightedHandleAlpha }); + Overlays.editOverlay(result.overlayID, { + color: highlightedHandleColor, + alpha: highlightedHandleAlpha + }); previousHandle = result.overlayID; previousHandleColor = pickedColor; previousHandleAlpha = pickedAlpha; } - + } else { if (previousHandle) { - Overlays.editOverlay(previousHandle, { color: previousHandleColor, alpha: previousHandleAlpha }); + Overlays.editOverlay(previousHandle, { + color: previousHandleColor, + alpha: previousHandleAlpha + }); previousHandle = false; } } - + return false; }; @@ -2728,7 +4273,11 @@ SelectionDisplay = (function () { Overlays.editOverlay(rollHandle, { scale: handleSize, }); - var pos = Vec3.sum(grabberMoveUpPosition, { x: 0, y: Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 3, z: 0 }); + var pos = Vec3.sum(grabberMoveUpPosition, { + x: 0, + y: Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 3, + z: 0 + }); Overlays.editOverlay(grabberMoveUp, { position: pos, scale: handleSize / 2, @@ -2745,34 +4294,43 @@ SelectionDisplay = (function () { activeTool = null; // hide our rotation overlays..., and show our handles if (mode == "ROTATE_YAW" || mode == "ROTATE_PITCH" || mode == "ROTATE_ROLL") { - Overlays.editOverlay(rotateOverlayTarget, { visible: false }); - Overlays.editOverlay(rotateOverlayInner, { visible: false }); - Overlays.editOverlay(rotateOverlayOuter, { visible: false }); - Overlays.editOverlay(rotateOverlayCurrent, { visible: false }); + Overlays.editOverlay(rotateOverlayTarget, { + visible: false + }); + Overlays.editOverlay(rotateOverlayInner, { + visible: false + }); + Overlays.editOverlay(rotateOverlayOuter, { + visible: false + }); + Overlays.editOverlay(rotateOverlayCurrent, { + visible: false + }); showHandles = true; } if (mode != "UNKNOWN") { showHandles = true; } - + mode = "UNKNOWN"; - + // if something is selected, then reset the "original" properties for any potential next click+move operation if (SelectionManager.hasSelection()) { if (showHandles) { that.select(SelectionManager.selections[0], event); } } - + }; // NOTE: mousePressEvent and mouseMoveEvent from the main script should call us., so we don't hook these: // Controller.mousePressEvent.connect(that.mousePressEvent); // Controller.mouseMoveEvent.connect(that.mouseMoveEvent); Controller.mouseReleaseEvent.connect(that.mouseReleaseEvent); - + + + return that; -}()); - +}()); \ No newline at end of file diff --git a/examples/libraries/lightOverlayManager.js b/examples/libraries/lightOverlayManager.js index 0942fae723..2d3618096b 100644 --- a/examples/libraries/lightOverlayManager.js +++ b/examples/libraries/lightOverlayManager.js @@ -53,7 +53,9 @@ LightOverlayManager = function() { if (visible != isVisible) { visible = isVisible; for (var id in entityOverlays) { - Overlays.editOverlay(entityOverlays[id], { visible: visible }); + Overlays.editOverlay(entityOverlays[id], { + visible: visible + }); } } }; @@ -61,8 +63,7 @@ LightOverlayManager = function() { // Allocate or get an unused overlay function getOverlay() { if (unusedOverlays.length == 0) { - var overlay = Overlays.addOverlay("image3d", { - }); + var overlay = Overlays.addOverlay("image3d", {}); allOverlays.push(overlay); } else { var overlay = unusedOverlays.pop(); @@ -72,7 +73,9 @@ LightOverlayManager = function() { function releaseOverlay(overlay) { unusedOverlays.push(overlay); - Overlays.editOverlay(overlay, { visible: false }); + Overlays.editOverlay(overlay, { + visible: false + }); } function addEntity(entityID) { @@ -88,7 +91,11 @@ LightOverlayManager = function() { visible: visible, alpha: 0.9, scale: 0.5, - color: { red: 255, green: 255, blue: 255 } + color: { + red: 255, + green: 255, + blue: 255 + } }); } } @@ -123,4 +130,4 @@ LightOverlayManager = function() { Overlays.deleteOverlay(allOverlays[i]); } }); -}; +}; \ No newline at end of file diff --git a/examples/light_modifier/README.md b/examples/light_modifier/README.md new file mode 100644 index 0000000000..f23f22148a --- /dev/null +++ b/examples/light_modifier/README.md @@ -0,0 +1,29 @@ +This PR demonstrates one way in-world editing of objects might work. + +Running this script will show light overlay icons in-world. Enter edit mode by running your distance beam through a light overlay. Exit using the red X. + +When you distant grab the sliders, you can move them along their axis to change their values. You may also rotate / move the block to which the spotlight is attached. + +To test: https://rawgit.com/imgntn/hifi/light_mod/examples/lights/lightLoader.js +To reset, I recommend stopping all scripts then re-loading lightLoader.js + +When you run the lightLoader.js script, several scripts will be loaded: +- handControllerGrab.js (will not impart velocity when you move the parent or a slider, will not move sliders with head movement,will constrain movement for a slider to a given axis start and end, will support blacklisting of entities for raypicking during search for objects) +- lightModifier.js (listens for message to create sliders for a given light. will start with slider set to the light's initial properties) +- lightModifierTestScene.js (creates a light) +- slider.js (attached to each slider entity) +- lightParent.js (attached to a 3d model of a light, to which a light is parented, so you can move it around. or keep the current parent if a light already has a parent) +- visiblePanel.js (the transparent panel) +- closeButton.js (for closing the ui) +- ../libraries/lightOverlayManager.js (shows 2d overlays for lights in the world) +- ../libraries/entitySelectionTool.js (visualizes volume of the lights) + +Current sliders are (top to bottom): +red +green +blue +intensity +cutoff +exponent + +![capture](https://cloud.githubusercontent.com/assets/843228/11910139/afaaf1ae-a5a5-11e5-8b66-0eb3fc6976df.PNG) diff --git a/examples/light_modifier/closeButton.js b/examples/light_modifier/closeButton.js new file mode 100644 index 0000000000..72fcfbc382 --- /dev/null +++ b/examples/light_modifier/closeButton.js @@ -0,0 +1,36 @@ +// +// closeButton.js +// +// Created by James Pollack @imgntn on 12/15/2015 +// Copyright 2015 High Fidelity, Inc. +// +// Entity script that closes sliders when interacted with. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { + + function CloseButton() { + return this; + } + + CloseButton.prototype = { + preload: function(entityID) { + this.entityID = entityID; + var entityProperties = Entities.getEntityProperties(this.entityID, "userData"); + this.initialProperties = entityProperties + this.userData = JSON.parse(entityProperties.userData); + }, + startNearGrab: function() { + + }, + startFarTrigger: function() { + Messages.sendMessage('Hifi-Light-Modifier-Cleanup', 'callCleanup') + } + + }; + + return new CloseButton(); +}); \ No newline at end of file diff --git a/examples/light_modifier/lightLoader.js b/examples/light_modifier/lightLoader.js new file mode 100644 index 0000000000..83618f85c2 --- /dev/null +++ b/examples/light_modifier/lightLoader.js @@ -0,0 +1,20 @@ +// +// lightLoader.js +// +// Created by James Pollack @imgntn on 12/15/2015 +// Copyright 2015 High Fidelity, Inc. +// +// Loads a test scene showing sliders that you can grab and move to change entity properties. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var grabScript = Script.resolvePath('../controllers/handControllerGrab.js?' + Math.random(0 - 100)); +Script.load(grabScript); +var lightModifier = Script.resolvePath('lightModifier.js?' + Math.random(0 - 100)); +Script.load(lightModifier); +Script.setTimeout(function() { + var lightModifierTestScene = Script.resolvePath('lightModifierTestScene.js?' + Math.random(0 - 100)); + Script.load(lightModifierTestScene); +}, 750) \ No newline at end of file diff --git a/examples/light_modifier/lightModifier.js b/examples/light_modifier/lightModifier.js new file mode 100644 index 0000000000..b50bbe9478 --- /dev/null +++ b/examples/light_modifier/lightModifier.js @@ -0,0 +1,876 @@ +// +// lightModifier.js +// +// Created by James Pollack @imgntn on 12/15/2015 +// Copyright 2015 High Fidelity, Inc. +// +// Given a selected light, instantiate some entities that represent various values you can dynamically adjust by grabbing and moving. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +//some experimental options +var ONLY_I_CAN_EDIT = false; +var SLIDERS_SHOULD_STAY_WITH_AVATAR = false; +var VERTICAL_SLIDERS = false; +var SHOW_OVERLAYS = true; +var SHOW_LIGHT_VOLUME = true; +var USE_PARENTED_PANEL = true; +var VISIBLE_PANEL = true; +var USE_LABELS = true; +var LEFT_LABELS = false; +var RIGHT_LABELS = true; +var ROTATE_CLOSE_BUTTON = false; + +//variables for managing overlays +var selectionDisplay; +var selectionManager; +var lightOverlayManager; + +//for when we make a 3d model of a light a parent for the light +var PARENT_SCRIPT_URL = Script.resolvePath('lightParent.js?' + Math.random(0 - 100)); + +if (SHOW_OVERLAYS === true) { + + Script.include('../libraries/gridTool.js'); + Script.include('../libraries/entitySelectionTool.js?' + Math.random(0 - 100)); + Script.include('../libraries/lightOverlayManager.js'); + + var grid = Grid(); + gridTool = GridTool({ + horizontalGrid: grid + }); + gridTool.setVisible(false); + + selectionDisplay = SelectionDisplay; + selectionManager = SelectionManager; + lightOverlayManager = new LightOverlayManager(); + selectionManager.addEventListener(function() { + selectionDisplay.updateHandles(); + lightOverlayManager.updatePositions(); + }); + lightOverlayManager.setVisible(true); +} + +var DEFAULT_PARENT_ID = '{00000000-0000-0000-0000-000000000000}' + +var AXIS_SCALE = 1; +var COLOR_MAX = 255; +var INTENSITY_MAX = 0.05; +var CUTOFF_MAX = 360; +var EXPONENT_MAX = 1; + +var SLIDER_SCRIPT_URL = Script.resolvePath('slider.js?' + Math.random(0, 100)); +var LIGHT_MODEL_URL = 'http://hifi-content.s3.amazonaws.com/james/light_modifier/source4_very_good.fbx'; +var CLOSE_BUTTON_MODEL_URL = 'http://hifi-content.s3.amazonaws.com/james/light_modifier/red_x.fbx'; +var CLOSE_BUTTON_SCRIPT_URL = Script.resolvePath('closeButton.js?' + Math.random(0, 100)); +var TRANSPARENT_PANEL_URL = 'http://hifi-content.s3.amazonaws.com/james/light_modifier/transparent_box_alpha_15.fbx'; +var VISIBLE_PANEL_SCRIPT_URL = Script.resolvePath('visiblePanel.js?' + Math.random(0, 100)); + +var RED = { + red: 255, + green: 0, + blue: 0 +}; + +var GREEN = { + red: 0, + green: 255, + blue: 0 +}; + +var BLUE = { + red: 0, + green: 0, + blue: 255 +}; + +var PURPLE = { + red: 255, + green: 0, + blue: 255 +}; + +var WHITE = { + red: 255, + green: 255, + blue: 255 +}; + +var ORANGE = { + red: 255, + green: 165, + blue: 0 +} + +var SLIDER_DIMENSIONS = { + x: 0.075, + y: 0.075, + z: 0.075 +}; + +var CLOSE_BUTTON_DIMENSIONS = { + x: 0.1, + y: 0.025, + z: 0.1 +} + +var LIGHT_MODEL_DIMENSIONS = { + x: 0.58, + y: 1.21, + z: 0.57 +} + +var PER_ROW_OFFSET = { + x: 0, + y: -0.2, + z: 0 +}; +var sliders = []; +var slidersRef = { + 'color_red': null, + 'color_green': null, + 'color_blue': null, + intensity: null, + cutoff: null, + exponent: null +}; +var light = null; + +var basePosition; +var avatarRotation; + +function entitySlider(light, color, sliderType, displayText, row) { + this.light = light; + this.lightID = light.id.replace(/[{}]/g, ""); + this.initialProperties = light.initialProperties; + this.color = color; + this.sliderType = sliderType; + this.displayText = displayText; + this.verticalOffset = Vec3.multiply(row, PER_ROW_OFFSET); + this.avatarRot = Quat.fromPitchYawRollDegrees(0, MyAvatar.bodyYaw, 0.0); + this.basePosition = Vec3.sum(MyAvatar.position, Vec3.multiply(1.5, Quat.getFront(this.avatarRot))); + this.basePosition.y += 1; + basePosition = this.basePosition; + avatarRot = this.avatarRot; + + var message = { + lightID: this.lightID, + sliderType: this.sliderType, + sliderValue: null + }; + + if (this.sliderType === 'color_red') { + message.sliderValue = this.initialProperties.color.red + this.setValueFromMessage(message); + } + if (this.sliderType === 'color_green') { + message.sliderValue = this.initialProperties.color.green + this.setValueFromMessage(message); + } + if (this.sliderType === 'color_blue') { + message.sliderValue = this.initialProperties.color.blue + this.setValueFromMessage(message); + } + + if (this.sliderType === 'intensity') { + message.sliderValue = this.initialProperties.intensity + this.setValueFromMessage(message); + } + + if (this.sliderType === 'exponent') { + message.sliderValue = this.initialProperties.exponent + this.setValueFromMessage(message); + } + + if (this.sliderType === 'cutoff') { + message.sliderValue = this.initialProperties.cutoff + this.setValueFromMessage(message); + } + + this.setInitialSliderPositions(); + this.createAxis(); + this.createSliderIndicator(); + if (USE_LABELS === true) { + this.createLabel() + } + return this; +} + +//what's the ux for adjusting values? start with simple entities, try image overlays etc +entitySlider.prototype = { + createAxis: function() { + //start of line + var position; + var extension; + + if (VERTICAL_SLIDERS == true) { + position = Vec3.sum(this.basePosition, Vec3.multiply(row, (Vec3.multiply(0.2, Quat.getRight(this.avatarRot))))); + //line starts on bottom and goes up + var upVector = Quat.getUp(this.avatarRot); + extension = Vec3.multiply(AXIS_SCALE, upVector); + } else { + position = Vec3.sum(this.basePosition, this.verticalOffset); + //line starts on left and goes to right + //set the end of the line to the right + var rightVector = Quat.getRight(this.avatarRot); + extension = Vec3.multiply(AXIS_SCALE, rightVector); + } + + + this.axisStart = position; + this.endOfAxis = Vec3.sum(position, extension); + this.createEndOfAxisEntity(); + + var properties = { + type: 'Line', + name: 'Hifi-Slider-Axis::' + this.sliderType, + color: this.color, + collisionsWillMove: false, + ignoreForCollisions: true, + dimensions: { + x: 3, + y: 3, + z: 3 + }, + position: position, + linePoints: [{ + x: 0, + y: 0, + z: 0 + }, extension], + lineWidth: 5, + }; + + this.axis = Entities.addEntity(properties); + }, + createEndOfAxisEntity: function() { + //we use this to track the end of the axis while parented to a panel + var properties = { + name: 'Hifi-End-Of-Axis', + type: 'Box', + collisionsWillMove: false, + ignoreForCollisions: true, + dimensions: { + x: 0.01, + y: 0.01, + z: 0.01 + }, + color: { + red: 255, + green: 255, + blue: 255 + }, + position: this.endOfAxis, + parentID: this.axis, + visible: false + } + + this.endOfAxisEntity = Entities.addEntity(this.endOfAxis); + }, + createLabel: function() { + + var LABEL_WIDTH = 0.25 + var PER_LETTER_SPACING = 0.1; + var textWidth = this.displayText.length * PER_LETTER_SPACING; + + var position; + if (LEFT_LABELS === true) { + var leftVector = Vec3.multiply(-1, Quat.getRight(this.avatarRot)); + + var extension = Vec3.multiply(textWidth, leftVector); + + position = Vec3.sum(this.axisStart, extension); + } + + if (RIGHT_LABELS === true) { + var rightVector = Quat.getRight(this.avatarRot); + + var extension = Vec3.multiply(textWidth / 1.75, rightVector); + + position = Vec3.sum(this.endOfAxis, extension); + } + + + var labelProperties = { + name: 'Hifi-Slider-Label-' + this.sliderType, + type: 'Text', + dimensions: { + x: textWidth, + y: 0.2, + z: 0.1 + }, + textColor: { + red: 255, + green: 255, + blue: 255 + }, + text: this.displayText, + lineHeight: 0.14, + backgroundColor: { + red: 0, + green: 0, + blue: 0 + }, + position: position, + rotation: this.avatarRot, + } + print('BEFORE CREATE LABEL' + JSON.stringify(labelProperties)) + this.label = Entities.addEntity(labelProperties); + print('AFTER CREATE LABEL') + }, + createSliderIndicator: function() { + var extensionVector; + var position; + if (VERTICAL_SLIDERS == true) { + position = Vec3.sum(this.basePosition, Vec3.multiply(row, (Vec3.multiply(0.2, Quat.getRight(this.avatarRot))))); + extensionVector = Quat.getUp(this.avatarRot); + + } else { + position = Vec3.sum(this.basePosition, this.verticalOffset); + extensionVector = Quat.getRight(this.avatarRot); + + } + + var initialDistance; + if (this.sliderType === 'color_red') { + initialDistance = this.distanceRed; + } + if (this.sliderType === 'color_green') { + initialDistance = this.distanceGreen; + } + if (this.sliderType === 'color_blue') { + initialDistance = this.distanceBlue; + } + if (this.sliderType === 'intensity') { + initialDistance = this.distanceIntensity; + } + if (this.sliderType === 'cutoff') { + initialDistance = this.distanceCutoff; + } + if (this.sliderType === 'exponent') { + initialDistance = this.distanceExponent; + } + + var extension = Vec3.multiply(initialDistance, extensionVector); + var sliderPosition = Vec3.sum(position, extension); + + var properties = { + type: 'Sphere', + name: 'Hifi-Slider-' + this.sliderType, + dimensions: SLIDER_DIMENSIONS, + collisionsWillMove: true, + color: this.color, + position: sliderPosition, + script: SLIDER_SCRIPT_URL, + ignoreForCollisions: true, + userData: JSON.stringify({ + lightModifierKey: { + lightID: this.lightID, + sliderType: this.sliderType, + axisStart: position, + axisEnd: this.endOfAxis, + }, + handControllerKey: { + disableReleaseVelocity: true, + disableMoveWithHead: true, + disableNearGrab:true + } + }), + }; + + this.sliderIndicator = Entities.addEntity(properties); + }, + setValueFromMessage: function(message) { + + //message is not for our light + if (message.lightID !== this.lightID) { + // print('not our light') + return; + } + + //message is not our type + if (message.sliderType !== this.sliderType) { + // print('not our slider type') + return + } + + var lightProperties = Entities.getEntityProperties(this.lightID); + + if (this.sliderType === 'color_red') { + Entities.editEntity(this.lightID, { + color: { + red: message.sliderValue, + green: lightProperties.color.green, + blue: lightProperties.color.blue + } + }); + } + + if (this.sliderType === 'color_green') { + Entities.editEntity(this.lightID, { + color: { + red: lightProperties.color.red, + green: message.sliderValue, + blue: lightProperties.color.blue + } + }); + } + + if (this.sliderType === 'color_blue') { + Entities.editEntity(this.lightID, { + color: { + red: lightProperties.color.red, + green: lightProperties.color.green, + blue: message.sliderValue, + } + }); + } + + if (this.sliderType === 'intensity') { + Entities.editEntity(this.lightID, { + intensity: message.sliderValue + }); + } + + if (this.sliderType === 'cutoff') { + Entities.editEntity(this.lightID, { + cutoff: message.sliderValue + }); + } + + if (this.sliderType === 'exponent') { + Entities.editEntity(this.lightID, { + exponent: message.sliderValue + }); + } + }, + setInitialSliderPositions: function() { + this.distanceRed = (this.initialProperties.color.red / COLOR_MAX) * AXIS_SCALE; + this.distanceGreen = (this.initialProperties.color.green / COLOR_MAX) * AXIS_SCALE; + this.distanceBlue = (this.initialProperties.color.blue / COLOR_MAX) * AXIS_SCALE; + this.distanceIntensity = (this.initialProperties.intensity / INTENSITY_MAX) * AXIS_SCALE; + this.distanceCutoff = (this.initialProperties.cutoff / CUTOFF_MAX) * AXIS_SCALE; + this.distanceExponent = (this.initialProperties.exponent / EXPONENT_MAX) * AXIS_SCALE; + } + +}; + + +var panel; +var visiblePanel; + +function makeSliders(light) { + + if (USE_PARENTED_PANEL === true) { + panel = createPanelEntity(MyAvatar.position); + } + + if (light.type === 'spotlight') { + var USE_COLOR_SLIDER = true; + var USE_INTENSITY_SLIDER = true; + var USE_CUTOFF_SLIDER = true; + var USE_EXPONENT_SLIDER = true; + } + if (light.type === 'pointlight') { + var USE_COLOR_SLIDER = true; + var USE_INTENSITY_SLIDER = true; + var USE_CUTOFF_SLIDER = false; + var USE_EXPONENT_SLIDER = false; + } + if (USE_COLOR_SLIDER === true) { + slidersRef.color_red = new entitySlider(light, RED, 'color_red', 'Red', 1); + slidersRef.color_green = new entitySlider(light, GREEN, 'color_green', 'Green', 2); + slidersRef.color_blue = new entitySlider(light, BLUE, 'color_blue', 'Blue', 3); + + sliders.push(slidersRef.color_red); + sliders.push(slidersRef.color_green); + sliders.push(slidersRef.color_blue); + + } + if (USE_INTENSITY_SLIDER === true) { + slidersRef.intensity = new entitySlider(light, WHITE, 'intensity', 'Intensity', 4); + sliders.push(slidersRef.intensity); + } + if (USE_CUTOFF_SLIDER === true) { + slidersRef.cutoff = new entitySlider(light, PURPLE, 'cutoff', 'Cutoff', 5); + sliders.push(slidersRef.cutoff); + } + if (USE_EXPONENT_SLIDER === true) { + slidersRef.exponent = new entitySlider(light, ORANGE, 'exponent', 'Exponent', 6); + sliders.push(slidersRef.exponent); + } + + createCloseButton(slidersRef.color_red.axisStart); + + subscribeToSliderMessages(); + + if (USE_PARENTED_PANEL === true) { + parentEntitiesToPanel(panel); + } + + if (SLIDERS_SHOULD_STAY_WITH_AVATAR === true) { + parentPanelToAvatar(panel); + } + + if (VISIBLE_PANEL === true) { + visiblePanel = createVisiblePanel(); + } +}; + +function parentPanelToAvatar(panel) { + //this is going to need some more work re: the sliders actually being grabbable. probably something to do with updating axis movement + Entities.editEntity(panel, { + parentID: MyAvatar.sessionUUID, + //actually figure out which one to parent it to -- probably a spine or something. + parentJointIndex: 1, + }) +} + + +function parentEntitiesToPanel(panel) { + + sliders.forEach(function(slider) { + Entities.editEntity(slider.axis, { + parentID: panel + }) + Entities.editEntity(slider.sliderIndicator, { + parentID: panel + }) + }) + + closeButtons.forEach(function(button) { + Entities.editEntity(button, { + parentID: panel + }) + }) +} + +function createPanelEntity(position) { + print('CREATING PANEL at ' + JSON.stringify(position)); + var panelProperties = { + name: 'Hifi-Slider-Panel', + type: 'Box', + dimensions: { + x: 0.1, + y: 0.1, + z: 0.1 + }, + visible: false, + collisionsWillMove: false, + ignoreForCollisions: true + } + + var panel = Entities.addEntity(panelProperties); + return panel +} + +function createVisiblePanel() { + var totalOffset = -PER_ROW_OFFSET.y * sliders.length; + + var moveRight = Vec3.sum(basePosition, Vec3.multiply(AXIS_SCALE / 2, Quat.getRight(avatarRot))); + + var moveDown = Vec3.sum(moveRight, Vec3.multiply((sliders.length + 1) / 2, PER_ROW_OFFSET)) + var panelProperties = { + name: 'Hifi-Visible-Transparent-Panel', + type: 'Model', + modelURL: TRANSPARENT_PANEL_URL, + dimensions: { + x: AXIS_SCALE + 0.1, + y: totalOffset, + z: SLIDER_DIMENSIONS.z / 4 + }, + visible: true, + collisionsWillMove: false, + ignoreForCollisions: true, + position: moveDown, + rotation: avatarRot, + script: VISIBLE_PANEL_SCRIPT_URL + } + + var panel = Entities.addEntity(panelProperties); + + return panel +} + + +function createLightModel(position, rotation) { + var blockProperties = { + name: 'Hifi-Spotlight-Model', + type: 'Model', + shapeType: 'box', + modelURL: LIGHT_MODEL_URL, + dimensions: LIGHT_MODEL_DIMENSIONS, + collisionsWillMove: true, + position: position, + rotation: rotation, + script: PARENT_SCRIPT_URL, + userData: JSON.stringify({ + handControllerKey: { + disableReleaseVelocity: true + } + }) + }; + + var block = Entities.addEntity(blockProperties); + + return block +} + +var closeButtons = []; + +function createCloseButton(axisStart) { + var MARGIN = 0.10; + var VERTICAL_OFFFSET = { + x: 0, + y: 0.15, + z: 0 + }; + var leftVector = Vec3.multiply(-1, Quat.getRight(avatarRot)); + var extension = Vec3.multiply(MARGIN, leftVector); + var position = Vec3.sum(axisStart, extension); + + var buttonProperties = { + name: 'Hifi-Close-Button', + type: 'Model', + modelURL: CLOSE_BUTTON_MODEL_URL, + dimensions: CLOSE_BUTTON_DIMENSIONS, + position: Vec3.sum(position, VERTICAL_OFFFSET), + rotation: Quat.multiply(avatarRot, Quat.fromPitchYawRollDegrees(90, 0, 45)), + //rotation: Quat.fromPitchYawRollDegrees(0, 0, 90), + collisionsWillMove: false, + ignoreForCollisions: true, + script: CLOSE_BUTTON_SCRIPT_URL, + userData: JSON.stringify({ + grabbableKey: { + wantsTrigger: true + } + }) + } + + var button = Entities.addEntity(buttonProperties); + + closeButtons.push(button); + + if (ROTATE_CLOSE_BUTTON === true) { + Script.update.connect(rotateCloseButtons); + } +} + +function rotateCloseButtons() { + closeButtons.forEach(function(button) { + Entities.editEntity(button, { + angularVelocity: { + x: 0, + y: 0.5, + z: 0 + } + }) + + }) +} + +function subScribeToNewLights() { + Messages.subscribe('Hifi-Light-Mod-Receiver'); + Messages.messageReceived.connect(handleLightModMessages); +} + +function subscribeToSliderMessages() { + Messages.subscribe('Hifi-Slider-Value-Reciever'); + Messages.messageReceived.connect(handleValueMessages); +} + +function subscribeToLightOverlayRayCheckMessages() { + Messages.subscribe('Hifi-Light-Overlay-Ray-Check'); + Messages.messageReceived.connect(handleLightOverlayRayCheckMessages); +} + +function subscribeToCleanupMessages() { + Messages.subscribe('Hifi-Light-Modifier-Cleanup'); + Messages.messageReceived.connect(handleCleanupMessages); +} + + +function handleLightModMessages(channel, message, sender) { + if (channel !== 'Hifi-Light-Mod-Receiver') { + return; + } + if (sender !== MyAvatar.sessionUUID) { + return; + } + var parsedMessage = JSON.parse(message); + + makeSliders(parsedMessage.light); + light = parsedMessage.light.id + if (SHOW_LIGHT_VOLUME === true) { + selectionManager.setSelections([parsedMessage.light.id]); + } +} + +function handleValueMessages(channel, message, sender) { + + if (channel !== 'Hifi-Slider-Value-Reciever') { + return; + } + if (ONLY_I_CAN_EDIT === true && sender !== MyAvatar.sessionUUID) { + return; + } + var parsedMessage = JSON.parse(message); + + slidersRef[parsedMessage.sliderType].setValueFromMessage(parsedMessage); +} + +var currentLight; +var block; +var oldParent = null; +var hasParent = false; + +function handleLightOverlayRayCheckMessages(channel, message, sender) { + if (channel !== 'Hifi-Light-Overlay-Ray-Check') { + return; + } + if (ONLY_I_CAN_EDIT === true && sender !== MyAvatar.sessionUUID) { + return; + } + + var pickRay = JSON.parse(message); + + var doesIntersect = lightOverlayManager.findRayIntersection(pickRay); + // print('DOES INTERSECT A LIGHT WE HAVE???' + doesIntersect.intersects); + if (doesIntersect.intersects === true) { + // print('FULL MESSAGE:::' + JSON.stringify(doesIntersect)) + + var lightID = doesIntersect.entityID; + if (currentLight === lightID) { + // print('ALREADY HAVE A BLOCK, EXIT') + return; + } + + currentLight = lightID; + var lightProperties = Entities.getEntityProperties(lightID); + if (lightProperties.parentID !== DEFAULT_PARENT_ID) { + //this light has a parent already. so lets call our block the parent and then make sure not to delete it at the end; + oldParent = lightProperties.parentID; + hasParent = true; + block = lightProperties.parentID; + if (lightProperties.parentJointIndex !== -1) { + //should make sure to retain the parent too. but i don't actually know what the + } + } else { + block = createLightModel(lightProperties.position, lightProperties.rotation); + } + + var light = { + id: lightID, + type: 'spotlight', + initialProperties: lightProperties + } + + makeSliders(light); + + if (SHOW_LIGHT_VOLUME === true) { + selectionManager.setSelections([lightID]); + } + + Entities.editEntity(lightID, { + parentID: block, + parentJointIndex: -1 + }); + + } +} + +function handleCleanupMessages(channel, message, sender) { + + if (channel !== 'Hifi-Light-Modifier-Cleanup') { + return; + } + if (ONLY_I_CAN_EDIT === true && sender !== MyAvatar.sessionUUID) { + return; + } + if (message === 'callCleanup') { + cleanup(true); + } +} + +function updateSliderAxis() { + sliders.forEach(function(slider) { + + }) +} + +function cleanup(fromMessage) { + var i; + for (i = 0; i < sliders.length; i++) { + Entities.deleteEntity(sliders[i].axis); + Entities.deleteEntity(sliders[i].sliderIndicator); + Entities.deleteEntity(sliders[i].label); + } + + while (closeButtons.length > 0) { + Entities.deleteEntity(closeButtons.pop()); + } + + //if the light was already parented to something we will want to restore that. or come up with groups or something clever. + if (oldParent !== null) { + Entities.editEntity(currentLight, { + parentID: oldParent, + }); + } else { + Entities.editEntity(currentLight, { + parentID: null, + }); + } + + + if (fromMessage !== true) { + Messages.messageReceived.disconnect(handleLightModMessages); + Messages.messageReceived.disconnect(handleValueMessages); + Messages.messageReceived.disconnect(handleLightOverlayRayCheckMessages); + lightOverlayManager.setVisible(false); + } + + + Entities.deleteEntity(panel); + Entities.deleteEntity(visiblePanel); + + selectionManager.clearSelections(); + + if (ROTATE_CLOSE_BUTTON === true) { + Script.update.disconnect(rotateCloseButtons); + } + + if (hasParent === false) { + Entities.deleteEntity(block); + } + + oldParent = null; + hasParent = false; + currentLight = null; + sliders = []; + +} + +Script.scriptEnding.connect(cleanup); + +Script.scriptEnding.connect(function() { + lightOverlayManager.setVisible(false); +}) + + +subscribeToLightOverlayRayCheckMessages(); +subScribeToNewLights(); +subscribeToCleanupMessages(); + + + +//other light properties +// diffuseColor: { red: 255, green: 255, blue: 255 }, +// ambientColor: { red: 255, green: 255, blue: 255 }, +// specularColor: { red: 255, green: 255, blue: 255 }, +// constantAttenuation: 1, +// linearAttenuation: 0, +// quadraticAttenuation: 0, +// exponent: 0, +// cutoff: 180, // in degrees \ No newline at end of file diff --git a/examples/light_modifier/lightModifierTestScene.js b/examples/light_modifier/lightModifierTestScene.js new file mode 100644 index 0000000000..58956850f2 --- /dev/null +++ b/examples/light_modifier/lightModifierTestScene.js @@ -0,0 +1,73 @@ +// +// lightModifierTestScene.js +// +// Created by James Pollack @imgntn on 12/15/2015 +// Copyright 2015 High Fidelity, Inc. +// +// Given a selected light, instantiate some entities that represent various values you can dynamically adjust. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var PARENT_SCRIPT_URL = Script.resolvePath('lightParent.js?' + Math.random(0 - 100)); +var basePosition, avatarRot; +avatarRot = Quat.fromPitchYawRollDegrees(0, MyAvatar.bodyYaw, 0.0); +basePosition = Vec3.sum(MyAvatar.position, Vec3.multiply(0, Quat.getUp(avatarRot))); + +var light; + +function createLight() { + var position = basePosition; + position.y += 2; + var lightTransform = evalLightWorldTransform(position, avatarRot); + var lightProperties = { + name: 'Hifi-Spotlight', + type: "Light", + isSpotlight: true, + dimensions: { + x: 2, + y: 2, + z: 8 + }, + color: { + red: 255, + green: 0, + blue: 255 + }, + intensity: 0.035, + exponent: 1, + cutoff: 30, + lifetime: -1, + position: lightTransform.p, + rotation: lightTransform.q + }; + + light = Entities.addEntity(lightProperties); + +} + +function evalLightWorldTransform(modelPos, modelRot) { + var MODEL_LIGHT_POSITION = { + x: 0, + y: -0.3, + z: 0 + }; + var MODEL_LIGHT_ROTATION = Quat.angleAxis(-90, { + x: 1, + y: 0, + z: 0 + }); + return { + p: Vec3.sum(modelPos, Vec3.multiplyQbyV(modelRot, MODEL_LIGHT_POSITION)), + q: Quat.multiply(modelRot, MODEL_LIGHT_ROTATION) + }; +} + +function cleanup() { + Entities.deleteEntity(light); +} + +Script.scriptEnding.connect(cleanup); + +createLight(); \ No newline at end of file diff --git a/examples/light_modifier/lightParent.js b/examples/light_modifier/lightParent.js new file mode 100644 index 0000000000..2b53c05d0a --- /dev/null +++ b/examples/light_modifier/lightParent.js @@ -0,0 +1,40 @@ +// +// lightParent.js +// +// Created by James Pollack @imgntn on 12/15/2015 +// Copyright 2015 High Fidelity, Inc. +// +// Entity script that tells the light parent to update the selection tool when we move it. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + (function() { + + function LightParent() { + return this; + } + + LightParent.prototype = { + preload: function(entityID) { + this.entityID = entityID; + var entityProperties = Entities.getEntityProperties(this.entityID, "userData"); + this.initialProperties = entityProperties + this.userData = JSON.parse(entityProperties.userData); + }, + startNearGrab: function() {}, + startDistantGrab: function() { + + }, + continueNearGrab: function() { + this.continueDistantGrab(); + }, + continueDistantGrab: function() { + Messages.sendMessage('entityToolUpdates', 'callUpdate'); + }, + + }; + + return new LightParent(); + }); \ No newline at end of file diff --git a/examples/light_modifier/slider.js b/examples/light_modifier/slider.js new file mode 100644 index 0000000000..e1dfea4e87 --- /dev/null +++ b/examples/light_modifier/slider.js @@ -0,0 +1,105 @@ +// +// slider.js +// +// Created by James Pollack @imgntn on 12/15/2015 +// Copyright 2015 High Fidelity, Inc. +// +// Entity script that sends a scaled value to a light based on its distance from the start of its constraint axis. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { + + var AXIS_SCALE = 1; + var COLOR_MAX = 255; + var INTENSITY_MAX = 0.05; + var CUTOFF_MAX = 360; + var EXPONENT_MAX = 1; + + function Slider() { + return this; + } + + Slider.prototype = { + preload: function(entityID) { + this.entityID = entityID; + var entityProperties = Entities.getEntityProperties(this.entityID, "userData"); + var parsedUserData = JSON.parse(entityProperties.userData); + this.userData = parsedUserData.lightModifierKey; + }, + startNearGrab: function() { + this.setInitialProperties(); + }, + startDistantGrab: function() { + this.setInitialProperties(); + }, + setInitialProperties: function() { + this.initialProperties = Entities.getEntityProperties(this.entityID); + }, + continueNearGrab: function() { + // this.continueDistantGrab(); + }, + continueDistantGrab: function() { + this.setSliderValueBasedOnDistance(); + }, + setSliderValueBasedOnDistance: function() { + var currentPosition = Entities.getEntityProperties(this.entityID, "position").position; + + var distance = Vec3.distance(this.userData.axisStart, currentPosition); + + if (this.userData.sliderType === 'color_red' || this.userData.sliderType === 'color_green' || this.userData.sliderType === 'color_blue') { + this.sliderValue = this.scaleValueBasedOnDistanceFromStart(distance, 0, COLOR_MAX); + } + if (this.userData.sliderType === 'intensity') { + this.sliderValue = this.scaleValueBasedOnDistanceFromStart(distance, 0, INTENSITY_MAX); + } + if (this.userData.sliderType === 'cutoff') { + this.sliderValue = this.scaleValueBasedOnDistanceFromStart(distance, 0, CUTOFF_MAX); + } + if (this.userData.sliderType === 'exponent') { + this.sliderValue = this.scaleValueBasedOnDistanceFromStart(distance, 0, EXPONENT_MAX); + }; + + this.sendValueToSlider(); + }, + releaseGrab: function() { + Entities.editEntity(this.entityID, { + velocity: { + x: 0, + y: 0, + z: 0 + }, + angularVelocity: { + x: 0, + y: 0, + z: 0 + } + }) + + this.sendValueToSlider(); + }, + scaleValueBasedOnDistanceFromStart: function(value, min2, max2) { + var min1 = 0; + var max1 = AXIS_SCALE; + var min2 = min2; + var max2 = max2; + return min2 + (max2 - min2) * ((value - min1) / (max1 - min1)); + }, + sendValueToSlider: function() { + var _t = this; + var message = { + lightID: _t.userData.lightID, + sliderType: _t.userData.sliderType, + sliderValue: _t.sliderValue + } + Messages.sendMessage('Hifi-Slider-Value-Reciever', JSON.stringify(message)); + if (_t.userData.sliderType === 'cutoff') { + Messages.sendMessage('entityToolUpdates', 'callUpdate'); + } + } + }; + + return new Slider(); +}); \ No newline at end of file diff --git a/examples/light_modifier/visiblePanel.js b/examples/light_modifier/visiblePanel.js new file mode 100644 index 0000000000..cf6875fc59 --- /dev/null +++ b/examples/light_modifier/visiblePanel.js @@ -0,0 +1,40 @@ +// +// visiblePanel.js +// +// Created by James Pollack @imgntn on 12/15/2015 +// Copyright 2015 High Fidelity, Inc. +// +// Entity script that disables picking on this panel. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { + + function VisiblePanel() { + return this; + } + + VisiblePanel.prototype = { + preload: function(entityID) { + this.entityID = entityID; + + var data = { + action: 'add', + id: this.entityID + }; + Messages.sendMessage('Hifi-Hand-RayPick-Blacklist', JSON.stringify(data)) + }, + unload: function() { + var data = { + action: 'remove', + id: this.entityID + }; + Messages.sendMessage('Hifi-Hand-RayPick-Blacklist', JSON.stringify(data)) + } + + }; + + return new VisiblePanel(); +}); \ No newline at end of file diff --git a/examples/toybox/pistol/pistol.js b/examples/toybox/pistol/pistol.js index 8ef26b94c1..7fb05d992f 100644 --- a/examples/toybox/pistol/pistol.js +++ b/examples/toybox/pistol/pistol.js @@ -29,21 +29,14 @@ this.equipped = false; this.forceMultiplier = 1; this.laserLength = 100; - this.laserOffsets = { - y: .095 - }; - this.firingOffsets = { - z: 0.16 - } + this.fireSound = SoundCache.getSound("https://s3.amazonaws.com/hifi-public/sounds/Guns/GUN-SHOT2.raw"); this.ricochetSound = SoundCache.getSound("https://s3.amazonaws.com/hifi-public/sounds/Guns/Ricochet.L.wav"); this.playRichochetSoundChance = 0.1; this.fireVolume = 0.2; this.bulletForce = 10; - - - this.showLaser = false; + }; Pistol.prototype = { @@ -58,20 +51,36 @@ if (!this.equipped) { return; } - this.toggleWithTriggerPressure(); + this.updateProps(); if (this.showLaser) { this.updateLaser(); } + this.toggleWithTriggerPressure(); + + + }, + + updateProps: function() { + var gunProps = Entities.getEntityProperties(this.entityID, ['position', 'rotation']); + this.position = gunProps.position; + this.rotation = gunProps.rotation; + this.firingDirection = Quat.getFront(this.rotation); + var upVec = Quat.getUp(this.rotation); + this.barrelPoint = Vec3.sum(this.position, Vec3.multiply(upVec, this.laserOffsets.y)); + this.laserTip = Vec3.sum(this.barrelPoint, Vec3.multiply(this.firingDirection, this.laserLength)); + this.barrelPoint = Vec3.sum(this.barrelPoint, Vec3.multiply(this.firingDirection, this.firingOffsets.z)) + var pickRay = { + origin: this.barrelPoint, + direction: this.firingDirection + }; }, toggleWithTriggerPressure: function() { this.triggerValue = Controller.getValue(TRIGGER_CONTROLS[this.hand]); if (this.triggerValue < RELOAD_THRESHOLD) { - // print('RELOAD'); this.canShoot = true; } if (this.canShoot === true && this.triggerValue === 1) { - // print('SHOOT'); this.fire(); this.canShoot = false; } @@ -91,17 +100,10 @@ }, updateLaser: function() { - var gunProps = Entities.getEntityProperties(this.entityID, ['position', 'rotation']); - var position = gunProps.position; - var rotation = gunProps.rotation; - this.firingDirection = Quat.getFront(rotation); - var upVec = Quat.getUp(rotation); - this.barrelPoint = Vec3.sum(position, Vec3.multiply(upVec, this.laserOffsets.y)); - var laserTip = Vec3.sum(this.barrelPoint, Vec3.multiply(this.firingDirection, this.laserLength)); - this.barrelPoint = Vec3.sum(this.barrelPoint, Vec3.multiply(this.firingDirection, this.firingOffsets.z)) + Overlays.editOverlay(this.laser, { start: this.barrelPoint, - end: laserTip, + end: this.laserTip, alpha: 1 }); }, @@ -114,19 +116,6 @@ }); }, - preload: function(entityID) { - this.entityID = entityID; - // this.initControllerMapping(); - this.laser = Overlays.addOverlay("line3d", { - start: ZERO_VECTOR, - end: ZERO_VECTOR, - color: COLORS.RED, - alpha: 1, - visible: true, - lineWidth: 2 - }); - }, - triggerPress: function(hand, value) { if (this.hand === hand && value === 1) { //We are pulling trigger on the hand we have the gun in, so fire @@ -135,15 +124,16 @@ }, fire: function() { - var pickRay = { - origin: this.barrelPoint, - direction: this.firingDirection - }; + Audio.playSound(this.fireSound, { position: this.barrelPoint, volume: this.fireVolume }); + var pickRay = { + origin: this.barrelPoint, + direction: this.firingDirection + }; this.createGunFireEffect(this.barrelPoint) var intersection = Entities.findRayIntersectionBlocking(pickRay, true); if (intersection.intersects) { @@ -170,11 +160,11 @@ }, createEntityHitEffect: function(position) { - var flash = Entities.addEntity({ + var sparks = Entities.addEntity({ type: "ParticleEffect", position: position, lifetime: 4, - "name": "Flash Emitter", + "name": "Sparks Emitter", "color": { red: 228, green: 128, @@ -228,7 +218,7 @@ }); Script.setTimeout(function() { - Entities.editEntity(flash, { + Entities.editEntity(sparks, { isEmitting: false }); }, 100); @@ -261,11 +251,11 @@ "z": 0 }, "accelerationSpread": { - "x": .2, + "x": 0.2, "y": 0, - "z": .2 + "z": 0.2 }, - "radiusSpread": .04, + "radiusSpread": 0.04, "particleRadius": 0.07, "radiusStart": 0.07, "radiusFinish": 0.07, @@ -282,11 +272,46 @@ }); }, 100); - var flash = Entities.addEntity({ + Entities.editEntity(this.flash, { + isEmitting: true + }); + Script.setTimeout(function() { + Entities.editEntity(_this.flash, { + isEmitting: false + }); + }, 100) + + }, + + preload: function(entityID) { + this.entityID = entityID; + this.laser = Overlays.addOverlay("line3d", { + start: ZERO_VECTOR, + end: ZERO_VECTOR, + color: COLORS.RED, + alpha: 1, + visible: true, + lineWidth: 2 + }); + this.laserOffsets = { + y: 0.095 + }; + this.firingOffsets = { + z: 0.16 + } + var gunProps = Entities.getEntityProperties(this.entityID, ['position', 'rotation']); + var position = gunProps.position; + var rotation = Quat.fromPitchYawRollDegrees(0, 0, 0); + this.firingDirection = Quat.getFront(rotation); + var upVec = Quat.getUp(rotation); + this.barrelPoint = Vec3.sum(position, Vec3.multiply(upVec, this.laserOffsets.y)); + this.barrelPoint = Vec3.sum(this.barrelPoint, Vec3.multiply(this.firingDirection, this.firingOffsets.z)) + + this.flash = Entities.addEntity({ type: "ParticleEffect", - position: position, - lifetime: 4, + position: this.barrelPoint, "name": "Muzzle Flash", + isEmitting: false, "color": { red: 228, green: 128, @@ -339,16 +364,13 @@ "textures": "http://ericrius1.github.io/PartiArt/assets/star.png" }); - Script.setTimeout(function() { - Entities.editEntity(flash, { - isEmitting: false - }); - }, 100) - - } + Script.setTimeout(function() { + Entities.editEntity(_this.flash, {parentID: _this.entityID}); + }, 500) + }, }; // entity scripts always need to return a newly constructed object of our type return new Pistol(); -}); \ No newline at end of file +}); diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 9cd1222353..638f27f367 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -109,7 +109,9 @@ add_dependency_external_projects(sdl2) if (WIN32) add_dependency_external_projects(OpenVR) endif() - +if(WIN32 OR APPLE) + add_dependency_external_projects(neuron) +endif() # disable /OPT:REF and /OPT:ICF for the Debug builds # This will prevent the following linker warnings diff --git a/interface/resources/controllers/neuron.json b/interface/resources/controllers/neuron.json new file mode 100644 index 0000000000..2d61f80c35 --- /dev/null +++ b/interface/resources/controllers/neuron.json @@ -0,0 +1,7 @@ +{ + "name": "Neuron to Standard", + "channels": [ + { "from": "Hydra.LeftHand", "to": "Standard.LeftHand" }, + { "from": "Hydra.RightHand", "to": "Standard.RightHand" } + ] +} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d383ee3339..e81aa7ec52 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3486,78 +3486,86 @@ namespace render { // Background rendering decision auto skyStage = DependencyManager::get()->getSkyStage(); - if (skyStage->getBackgroundMode() == model::SunSkyStage::NO_BACKGROUND) { + auto backgroundMode = skyStage->getBackgroundMode(); + + if (backgroundMode == model::SunSkyStage::NO_BACKGROUND) { // this line intentionally left blank - } else if (skyStage->getBackgroundMode() == model::SunSkyStage::SKY_DOME) { - if (Menu::getInstance()->isOptionChecked(MenuOption::Stars)) { - PerformanceTimer perfTimer("stars"); - PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), - "Application::payloadRender() ... stars..."); - // should be the first rendering pass - w/o depth buffer / lighting - - // compute starfield alpha based on distance from atmosphere - float alpha = 1.0f; - bool hasStars = true; - - if (Menu::getInstance()->isOptionChecked(MenuOption::Atmosphere)) { - // TODO: handle this correctly for zones - const EnvironmentData& closestData = background->_environment->getClosestData(args->_viewFrustum->getPosition()); // was theCamera instead of _viewFrustum - - if (closestData.getHasStars()) { - const float APPROXIMATE_DISTANCE_FROM_HORIZON = 0.1f; - const float DOUBLE_APPROXIMATE_DISTANCE_FROM_HORIZON = 0.2f; - - glm::vec3 sunDirection = (args->_viewFrustum->getPosition()/*getAvatarPosition()*/ - closestData.getSunLocation()) - / closestData.getAtmosphereOuterRadius(); - float height = glm::distance(args->_viewFrustum->getPosition()/*theCamera.getPosition()*/, closestData.getAtmosphereCenter()); - if (height < closestData.getAtmosphereInnerRadius()) { - // If we're inside the atmosphere, then determine if our keyLight is below the horizon - alpha = 0.0f; - - if (sunDirection.y > -APPROXIMATE_DISTANCE_FROM_HORIZON) { - float directionY = glm::clamp(sunDirection.y, - -APPROXIMATE_DISTANCE_FROM_HORIZON, APPROXIMATE_DISTANCE_FROM_HORIZON) - + APPROXIMATE_DISTANCE_FROM_HORIZON; - alpha = (directionY / DOUBLE_APPROXIMATE_DISTANCE_FROM_HORIZON); - } - - - } else if (height < closestData.getAtmosphereOuterRadius()) { - alpha = (height - closestData.getAtmosphereInnerRadius()) / - (closestData.getAtmosphereOuterRadius() - closestData.getAtmosphereInnerRadius()); - - if (sunDirection.y > -APPROXIMATE_DISTANCE_FROM_HORIZON) { - float directionY = glm::clamp(sunDirection.y, - -APPROXIMATE_DISTANCE_FROM_HORIZON, APPROXIMATE_DISTANCE_FROM_HORIZON) - + APPROXIMATE_DISTANCE_FROM_HORIZON; - alpha = (directionY / DOUBLE_APPROXIMATE_DISTANCE_FROM_HORIZON); - } - } - } else { - hasStars = false; - } + } else { + if (backgroundMode == model::SunSkyStage::SKY_BOX) { + auto skybox = skyStage->getSkybox(); + if (skybox && skybox->getCubemap() && skybox->getCubemap()->isDefined()) { + PerformanceTimer perfTimer("skybox"); + skybox->render(batch, *(args->_viewFrustum)); + } else { + // If no skybox texture is available, render the SKY_DOME while it loads + backgroundMode = model::SunSkyStage::SKY_DOME; } - - // finally render the starfield - if (hasStars) { - background->_stars.render(args, alpha); - } - - // draw the sky dome - if (/*!selfAvatarOnly &&*/ Menu::getInstance()->isOptionChecked(MenuOption::Atmosphere)) { - PerformanceTimer perfTimer("atmosphere"); - PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), - "Application::displaySide() ... atmosphere..."); - - background->_environment->renderAtmospheres(batch, *(args->_viewFrustum)); - } - } - } else if (skyStage->getBackgroundMode() == model::SunSkyStage::SKY_BOX) { - PerformanceTimer perfTimer("skybox"); - auto skybox = skyStage->getSkybox(); - if (skybox) { - skybox->render(batch, *(args->_viewFrustum)); + if (backgroundMode == model::SunSkyStage::SKY_DOME) { + if (Menu::getInstance()->isOptionChecked(MenuOption::Stars)) { + PerformanceTimer perfTimer("stars"); + PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), + "Application::payloadRender() ... stars..."); + // should be the first rendering pass - w/o depth buffer / lighting + + // compute starfield alpha based on distance from atmosphere + float alpha = 1.0f; + bool hasStars = true; + + if (Menu::getInstance()->isOptionChecked(MenuOption::Atmosphere)) { + // TODO: handle this correctly for zones + const EnvironmentData& closestData = background->_environment->getClosestData(args->_viewFrustum->getPosition()); // was theCamera instead of _viewFrustum + + if (closestData.getHasStars()) { + const float APPROXIMATE_DISTANCE_FROM_HORIZON = 0.1f; + const float DOUBLE_APPROXIMATE_DISTANCE_FROM_HORIZON = 0.2f; + + glm::vec3 sunDirection = (args->_viewFrustum->getPosition()/*getAvatarPosition()*/ - closestData.getSunLocation()) + / closestData.getAtmosphereOuterRadius(); + float height = glm::distance(args->_viewFrustum->getPosition()/*theCamera.getPosition()*/, closestData.getAtmosphereCenter()); + if (height < closestData.getAtmosphereInnerRadius()) { + // If we're inside the atmosphere, then determine if our keyLight is below the horizon + alpha = 0.0f; + + if (sunDirection.y > -APPROXIMATE_DISTANCE_FROM_HORIZON) { + float directionY = glm::clamp(sunDirection.y, + -APPROXIMATE_DISTANCE_FROM_HORIZON, APPROXIMATE_DISTANCE_FROM_HORIZON) + + APPROXIMATE_DISTANCE_FROM_HORIZON; + alpha = (directionY / DOUBLE_APPROXIMATE_DISTANCE_FROM_HORIZON); + } + + + } else if (height < closestData.getAtmosphereOuterRadius()) { + alpha = (height - closestData.getAtmosphereInnerRadius()) / + (closestData.getAtmosphereOuterRadius() - closestData.getAtmosphereInnerRadius()); + + if (sunDirection.y > -APPROXIMATE_DISTANCE_FROM_HORIZON) { + float directionY = glm::clamp(sunDirection.y, + -APPROXIMATE_DISTANCE_FROM_HORIZON, APPROXIMATE_DISTANCE_FROM_HORIZON) + + APPROXIMATE_DISTANCE_FROM_HORIZON; + alpha = (directionY / DOUBLE_APPROXIMATE_DISTANCE_FROM_HORIZON); + } + } + } else { + hasStars = false; + } + } + + // finally render the starfield + if (hasStars) { + background->_stars.render(args, alpha); + } + + // draw the sky dome + if (/*!selfAvatarOnly &&*/ Menu::getInstance()->isOptionChecked(MenuOption::Atmosphere)) { + PerformanceTimer perfTimer("atmosphere"); + PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), + "Application::displaySide() ... atmosphere..."); + + background->_environment->renderAtmospheres(batch, *(args->_viewFrustum)); + } + + } } } } diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 312742e778..217cd28e61 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -310,8 +310,8 @@ QVector AvatarManager::getAvatarIdentifiers() { } AvatarData* AvatarManager::getAvatar(QUuid avatarID) { - QReadLocker locker(&_hashLock); - return _avatarHash[avatarID].get(); // Non-obvious: A bogus avatarID answers your own avatar. + // Null/Default-constructed QUuids will return MyAvatar + return getAvatarBySessionID(avatarID).get(); } diff --git a/libraries/animation/src/AnimExpression.cpp b/libraries/animation/src/AnimExpression.cpp new file mode 100644 index 0000000000..79004a72a6 --- /dev/null +++ b/libraries/animation/src/AnimExpression.cpp @@ -0,0 +1,676 @@ +// +// AnimExpression.cpp +// +// Created by Anthony J. Thibault on 11/1/15. +// Copyright (c) 2015 High Fidelity, Inc. All rights reserved. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include + +#include "AnimExpression.h" +#include "AnimationLogging.h" + +AnimExpression::AnimExpression(const QString& str) : + _expression(str) { + auto iter = str.begin(); + parseExpr(_expression, iter); + while(!_tokenStack.empty()) { + _tokenStack.pop(); + } +} + +// +// Tokenizer +// + +void AnimExpression::unconsumeToken(const Token& token) { + _tokenStack.push(token); +} + +AnimExpression::Token AnimExpression::consumeToken(const QString& str, QString::const_iterator& iter) const { + if (!_tokenStack.empty()) { + Token top = _tokenStack.top(); + _tokenStack.pop(); + return top; + } else { + while (iter != str.end()) { + if (iter->isSpace()) { + ++iter; + } else if (iter->isLetter()) { + return consumeIdentifier(str, iter); + } else if (iter->isDigit()) { + return consumeNumber(str, iter); + } else { + switch (iter->unicode()) { + case '&': return consumeAnd(str, iter); + case '|': return consumeOr(str, iter); + case '>': return consumeGreaterThan(str, iter); + case '<': return consumeLessThan(str, iter); + case '(': ++iter; return Token(Token::LeftParen); + case ')': ++iter; return Token(Token::RightParen); + case '!': return consumeNot(str, iter); + case '-': ++iter; return Token(Token::Minus); + case '+': ++iter; return Token(Token::Plus); + case '*': ++iter; return Token(Token::Multiply); + case '/': ++iter; return Token(Token::Divide); + case '%': ++iter; return Token(Token::Modulus); + case ',': ++iter; return Token(Token::Comma); + default: + qCCritical(animation) << "AnimExpression: unexpected char" << *iter << "at index " << (int)(iter - str.begin()); + return Token(Token::Error); + } + } + } + return Token(Token::End); + } +} + +AnimExpression::Token AnimExpression::consumeIdentifier(const QString& str, QString::const_iterator& iter) const { + assert(iter != str.end()); + assert(iter->isLetter()); + auto begin = iter; + while ((iter->isLetter() || iter->isDigit()) && iter != str.end()) { + ++iter; + } + int pos = (int)(begin - str.begin()); + int len = (int)(iter - begin); + + QStringRef stringRef(const_cast(&str), pos, len); + if (stringRef == "true") { + return Token(true); + } else if (stringRef == "false") { + return Token(false); + } else { + return Token(stringRef); + } +} + +// TODO: not very efficient or accruate, but it's close enough for now. +static float computeFractionalPart(int fractionalPart) +{ + float frac = (float)fractionalPart; + while (fractionalPart) { + fractionalPart /= 10; + frac /= 10.0f; + } + return frac; +} + +static float computeFloat(int whole, int fraction) { + return (float)whole + computeFractionalPart(fraction); +} + +AnimExpression::Token AnimExpression::consumeNumber(const QString& str, QString::const_iterator& iter) const { + assert(iter != str.end()); + assert(iter->isDigit()); + auto begin = iter; + while (iter->isDigit() && iter != str.end()) { + ++iter; + } + + // parse whole integer part + int pos = (int)(begin - str.begin()); + int len = (int)(iter - begin); + QString sub = QStringRef(const_cast(&str), pos, len).toString(); + int whole = sub.toInt(); + + // parse optional fractional part + if (iter->unicode() == '.') { + iter++; + auto begin = iter; + while (iter->isDigit() && iter != str.end()) { + ++iter; + } + + int pos = (int)(begin - str.begin()); + int len = (int)(iter - begin); + QString sub = QStringRef(const_cast(&str), pos, len).toString(); + int fraction = sub.toInt(); + + return Token(computeFloat(whole, fraction)); + + } else { + return Token(whole); + } +} + +AnimExpression::Token AnimExpression::consumeAnd(const QString& str, QString::const_iterator& iter) const { + assert(iter != str.end()); + assert(iter->unicode() == '&'); + iter++; + if (iter->unicode() == '&') { + iter++; + return Token(Token::And); + } else { + qCCritical(animation) << "AnimExpression: unexpected char" << *iter << "at index " << (int)(iter - str.begin()); + return Token(Token::Error); + } +} + +AnimExpression::Token AnimExpression::consumeOr(const QString& str, QString::const_iterator& iter) const { + assert(iter != str.end()); + assert(iter->unicode() == '|'); + iter++; + if (iter->unicode() == '|') { + iter++; + return Token(Token::Or); + } else { + qCCritical(animation) << "AnimExpression: unexpected char" << *iter << "at index " << (int)(iter - str.begin()); + return Token(Token::Error); + } +} + +AnimExpression::Token AnimExpression::consumeGreaterThan(const QString& str, QString::const_iterator& iter) const { + assert(iter != str.end()); + assert(iter->unicode() == '>'); + iter++; + if (iter->unicode() == '=') { + iter++; + return Token(Token::GreaterThanEqual); + } else { + return Token(Token::GreaterThan); + } +} + +AnimExpression::Token AnimExpression::consumeLessThan(const QString& str, QString::const_iterator& iter) const { + assert(iter != str.end()); + assert(iter->unicode() == '<'); + iter++; + if (iter->unicode() == '=') { + iter++; + return Token(Token::LessThanEqual); + } else { + return Token(Token::LessThan); + } +} + +AnimExpression::Token AnimExpression::consumeNot(const QString& str, QString::const_iterator& iter) const { + assert(iter != str.end()); + assert(iter->unicode() == '!'); + iter++; + if (iter->unicode() == '=') { + iter++; + return Token(Token::NotEqual); + } else { + return Token(Token::Not); + } +} + +// +// Parser +// + +/* +Expr → Term Expr' +Expr' → '||' Term Expr' + | ε +Term → Unary Term' +Term' → '&&' Unary Term' + | ε +Unary → '!' Unary + | Factor +Factor → INT + | BOOL + | FLOAT + | IDENTIFIER + | '(' Expr ')' +*/ + +// Expr → Term Expr' +bool AnimExpression::parseExpr(const QString& str, QString::const_iterator& iter) { + if (!parseTerm(str, iter)) { + return false; + } + if (!parseExprPrime(str, iter)) { + return false; + } + return true; +} + +// Expr' → '||' Term Expr' | ε +bool AnimExpression::parseExprPrime(const QString& str, QString::const_iterator& iter) { + auto token = consumeToken(str, iter); + if (token.type == Token::Or) { + if (!parseTerm(str, iter)) { + unconsumeToken(token); + return false; + } + if (!parseExprPrime(str, iter)) { + unconsumeToken(token); + return false; + } + _opCodes.push_back(OpCode {OpCode::Or}); + return true; + } else { + unconsumeToken(token); + return true; + } +} + +// Term → Unary Term' +bool AnimExpression::parseTerm(const QString& str, QString::const_iterator& iter) { + if (!parseUnary(str, iter)) { + return false; + } + if (!parseTermPrime(str, iter)) { + return false; + } + return true; +} + +// Term' → '&&' Unary Term' | ε +bool AnimExpression::parseTermPrime(const QString& str, QString::const_iterator& iter) { + auto token = consumeToken(str, iter); + if (token.type == Token::And) { + if (!parseUnary(str, iter)) { + unconsumeToken(token); + return false; + } + if (!parseTermPrime(str, iter)) { + unconsumeToken(token); + return false; + } + _opCodes.push_back(OpCode {OpCode::And}); + return true; + } else { + unconsumeToken(token); + return true; + } +} + +// Unary → '!' Unary | Factor +bool AnimExpression::parseUnary(const QString& str, QString::const_iterator& iter) { + + auto token = consumeToken(str, iter); + if (token.type == Token::Not) { + if (!parseUnary(str, iter)) { + unconsumeToken(token); + return false; + } + _opCodes.push_back(OpCode {OpCode::Not}); + return true; + } + unconsumeToken(token); + + return parseFactor(str, iter); +} + + +// Factor → INT | BOOL | FLOAT | IDENTIFIER | '(' Expr ')' +bool AnimExpression::parseFactor(const QString& str, QString::const_iterator& iter) { + auto token = consumeToken(str, iter); + if (token.type == Token::Int) { + _opCodes.push_back(OpCode {token.intVal}); + return true; + } else if (token.type == Token::Bool) { + _opCodes.push_back(OpCode {(bool)token.intVal}); + return true; + } else if (token.type == Token::Float) { + _opCodes.push_back(OpCode {token.floatVal}); + return true; + } else if (token.type == Token::Identifier) { + _opCodes.push_back(OpCode {token.strVal}); + return true; + } else if (token.type == Token::LeftParen) { + if (!parseExpr(str, iter)) { + unconsumeToken(token); + return false; + } + auto nextToken = consumeToken(str, iter); + if (nextToken.type != Token::RightParen) { + unconsumeToken(nextToken); + unconsumeToken(token); + return false; + } + return true; + } else { + unconsumeToken(token); + return false; + } +} + +// +// Evaluator +// + +AnimExpression::OpCode AnimExpression::evaluate(const AnimVariantMap& map) const { + std::stack stack; + for (auto& opCode : _opCodes) { + switch (opCode.type) { + case OpCode::Identifier: + case OpCode::Int: + case OpCode::Float: + case OpCode::Bool: + stack.push(opCode); + break; + case OpCode::And: evalAnd(map, stack); break; + case OpCode::Or: evalOr(map, stack); break; + case OpCode::GreaterThan: evalGreaterThan(map, stack); break; + case OpCode::GreaterThanEqual: evalGreaterThanEqual(map, stack); break; + case OpCode::LessThan: evalLessThan(map, stack); break; + case OpCode::LessThanEqual: evalLessThanEqual(map, stack); break; + case OpCode::Equal: evalEqual(map, stack); break; + case OpCode::NotEqual: evalNotEqual(map, stack); break; + case OpCode::Not: evalNot(map, stack); break; + case OpCode::Subtract: evalSubtract(map, stack); break; + case OpCode::Add: evalAdd(map, stack); break; + case OpCode::Multiply: evalMultiply(map, stack); break; + case OpCode::Divide: evalDivide(map, stack); break; + case OpCode::Modulus: evalModulus(map, stack); break; + case OpCode::UnaryMinus: evalUnaryMinus(map, stack); break; + } + } + return coerseToValue(map, stack.top()); +} + +#define POP_BOOL(NAME) \ + const OpCode& NAME##_temp = stack.top(); \ + bool NAME = NAME##_temp.coerceBool(map); \ + stack.pop() + +#define PUSH(EXPR) \ + stack.push(OpCode {(EXPR)}) + +void AnimExpression::evalAnd(const AnimVariantMap& map, std::stack& stack) const { + POP_BOOL(lhs); + POP_BOOL(rhs); + PUSH(lhs && rhs); +} + +void AnimExpression::evalOr(const AnimVariantMap& map, std::stack& stack) const { + POP_BOOL(lhs); + POP_BOOL(rhs); + PUSH(lhs || rhs); +} + +void AnimExpression::evalGreaterThan(const AnimVariantMap& map, std::stack& stack) const { + OpCode lhs = stack.top(); stack.pop(); + OpCode rhs = stack.top(); stack.pop(); + + // TODO: + PUSH(false); +} + +void AnimExpression::evalGreaterThanEqual(const AnimVariantMap& map, std::stack& stack) const { + OpCode lhs = stack.top(); stack.pop(); + OpCode rhs = stack.top(); stack.pop(); + + // TODO: + PUSH(false); +} + +void AnimExpression::evalLessThan(const AnimVariantMap& map, std::stack& stack) const { + OpCode lhs = stack.top(); stack.pop(); + OpCode rhs = stack.top(); stack.pop(); + + // TODO: + PUSH(false); +} + +void AnimExpression::evalLessThanEqual(const AnimVariantMap& map, std::stack& stack) const { + OpCode lhs = stack.top(); stack.pop(); + OpCode rhs = stack.top(); stack.pop(); + + // TODO: + PUSH(false); +} + +void AnimExpression::evalEqual(const AnimVariantMap& map, std::stack& stack) const { + OpCode lhs = stack.top(); stack.pop(); + OpCode rhs = stack.top(); stack.pop(); + + // TODO: + PUSH(false); +} + +void AnimExpression::evalNotEqual(const AnimVariantMap& map, std::stack& stack) const { + OpCode lhs = stack.top(); stack.pop(); + OpCode rhs = stack.top(); stack.pop(); + + // TODO: + PUSH(false); +} + +void AnimExpression::evalNot(const AnimVariantMap& map, std::stack& stack) const { + POP_BOOL(rhs); + PUSH(!rhs); +} + +void AnimExpression::evalSubtract(const AnimVariantMap& map, std::stack& stack) const { + OpCode lhs = stack.top(); stack.pop(); + OpCode rhs = stack.top(); stack.pop(); + + // TODO: + PUSH(0.0f); +} + +void AnimExpression::add(int lhs, const OpCode& rhs, std::stack& stack) const { + switch (rhs.type) { + case OpCode::Bool: + case OpCode::Int: + PUSH(lhs + rhs.intVal); + break; + case OpCode::Float: + PUSH((float)lhs + rhs.floatVal); + break; + default: + PUSH(lhs); + } +} + +void AnimExpression::add(float lhs, const OpCode& rhs, std::stack& stack) const { + switch (rhs.type) { + case OpCode::Bool: + case OpCode::Int: + PUSH(lhs + (float)rhs.intVal); + break; + case OpCode::Float: + PUSH(lhs + rhs.floatVal); + break; + default: + PUSH(lhs); + } +} + +void AnimExpression::evalAdd(const AnimVariantMap& map, std::stack& stack) const { + OpCode lhs = coerseToValue(map, stack.top()); + stack.pop(); + OpCode rhs = coerseToValue(map, stack.top()); + stack.pop(); + + switch (lhs.type) { + case OpCode::Bool: + add(lhs.intVal, rhs, stack); + break; + case OpCode::Int: + add(lhs.intVal, rhs, stack); + break; + case OpCode::Float: + add(lhs.floatVal, rhs, stack); + break; + default: + add(0, rhs, stack); + break; + } +} + +void AnimExpression::evalMultiply(const AnimVariantMap& map, std::stack& stack) const { + OpCode lhs = coerseToValue(map, stack.top()); + stack.pop(); + OpCode rhs = coerseToValue(map, stack.top()); + stack.pop(); + + switch(lhs.type) { + case OpCode::Bool: + mul(lhs.intVal, rhs, stack); + break; + case OpCode::Int: + mul(lhs.intVal, rhs, stack); + break; + case OpCode::Float: + mul(lhs.floatVal, rhs, stack); + break; + default: + mul(0, rhs, stack); + break; + } +} + +void AnimExpression::mul(int lhs, const OpCode& rhs, std::stack& stack) const { + switch (rhs.type) { + case OpCode::Bool: + case OpCode::Int: + PUSH(lhs * rhs.intVal); + break; + case OpCode::Float: + PUSH((float)lhs * rhs.floatVal); + break; + default: + PUSH(lhs); + } +} + +void AnimExpression::mul(float lhs, const OpCode& rhs, std::stack& stack) const { + switch (rhs.type) { + case OpCode::Bool: + case OpCode::Int: + PUSH(lhs * (float)rhs.intVal); + break; + case OpCode::Float: + PUSH(lhs * rhs.floatVal); + break; + default: + PUSH(lhs); + } +} + +void AnimExpression::evalDivide(const AnimVariantMap& map, std::stack& stack) const { + OpCode lhs = stack.top(); stack.pop(); + OpCode rhs = stack.top(); stack.pop(); + + // TODO: + PUSH(0.0f); +} + +void AnimExpression::evalModulus(const AnimVariantMap& map, std::stack& stack) const { + OpCode lhs = stack.top(); stack.pop(); + OpCode rhs = stack.top(); stack.pop(); + + // TODO: + PUSH((int)0); +} + +void AnimExpression::evalUnaryMinus(const AnimVariantMap& map, std::stack& stack) const { + OpCode rhs = stack.top(); stack.pop(); + + switch (rhs.type) { + case OpCode::Identifier: { + const AnimVariant& var = map.get(rhs.strVal); + switch (var.getType()) { + case AnimVariant::Type::Bool: + qCWarning(animation) << "AnimExpression: type missmatch for unary minus, expected a number not a bool"; + // interpret this as boolean not. + PUSH(!var.getBool()); + break; + case AnimVariant::Type::Int: + PUSH(-var.getInt()); + break; + case AnimVariant::Type::Float: + PUSH(-var.getFloat()); + break; + default: + // TODO: Vec3, Quat are unsupported + assert(false); + PUSH(false); + break; + } + } + case OpCode::Int: + PUSH(-rhs.intVal); + break; + case OpCode::Float: + PUSH(-rhs.floatVal); + break; + case OpCode::Bool: + qCWarning(animation) << "AnimExpression: type missmatch for unary minus, expected a number not a bool"; + // interpret this as boolean not. + PUSH(!rhs.coerceBool(map)); + break; + default: + qCCritical(animation) << "AnimExpression: ERRROR for unary minus, expected a number, type = " << rhs.type; + assert(false); + PUSH(false); + break; + } +} + +AnimExpression::OpCode AnimExpression::coerseToValue(const AnimVariantMap& map, const OpCode& opCode) const { + switch (opCode.type) { + case OpCode::Identifier: + { + const AnimVariant& var = map.get(opCode.strVal); + switch (var.getType()) { + case AnimVariant::Type::Bool: + return OpCode((bool)var.getBool()); + break; + case AnimVariant::Type::Int: + return OpCode(var.getInt()); + break; + case AnimVariant::Type::Float: + return OpCode(var.getFloat()); + break; + default: + // TODO: Vec3, Quat are unsupported + assert(false); + return OpCode(0); + break; + } + } + break; + case OpCode::Bool: + case OpCode::Int: + case OpCode::Float: + return opCode; + default: + qCCritical(animation) << "AnimExpression: ERROR expected a number, type = " << opCode.type; + assert(false); + return OpCode(0); + break; + } +} + +#ifndef NDEBUG +void AnimExpression::dumpOpCodes() const { + QString tmp; + for (auto& op : _opCodes) { + switch (op.type) { + case OpCode::Identifier: tmp += QString(" %1").arg(op.strVal); break; + case OpCode::Bool: tmp += QString(" %1").arg(op.intVal ? "true" : "false"); break; + case OpCode::Int: tmp += QString(" %1").arg(op.intVal); break; + case OpCode::Float: tmp += QString(" %1").arg(op.floatVal); break; + case OpCode::And: tmp += " &&"; break; + case OpCode::Or: tmp += " ||"; break; + case OpCode::GreaterThan: tmp += " >"; break; + case OpCode::GreaterThanEqual: tmp += " >="; break; + case OpCode::LessThan: tmp += " <"; break; + case OpCode::LessThanEqual: tmp += " <="; break; + case OpCode::Equal: tmp += " =="; break; + case OpCode::NotEqual: tmp += " !="; break; + case OpCode::Not: tmp += " !"; break; + case OpCode::Subtract: tmp += " -"; break; + case OpCode::Add: tmp += " +"; break; + case OpCode::Multiply: tmp += " *"; break; + case OpCode::Divide: tmp += " /"; break; + case OpCode::Modulus: tmp += " %"; break; + case OpCode::UnaryMinus: tmp += " unary-"; break; + default: tmp += " ???"; break; + } + } + qCDebug(animation).nospace().noquote() << "opCodes =" << tmp; + qCDebug(animation).resetFormat(); +} +#endif diff --git a/libraries/animation/src/AnimExpression.h b/libraries/animation/src/AnimExpression.h new file mode 100644 index 0000000000..468217f5b3 --- /dev/null +++ b/libraries/animation/src/AnimExpression.h @@ -0,0 +1,158 @@ +// +// AnimExpression.h +// +// Created by Anthony J. Thibault on 11/1/15. +// Copyright (c) 2015 High Fidelity, Inc. All rights reserved. +// +// 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_AnimExpression +#define hifi_AnimExpression + +#include +#include +#include +#include +#include +#include "AnimVariant.h" + +class AnimExpression { +public: + friend class AnimTests; + AnimExpression(const QString& str); +protected: + struct Token { + enum Type { + End = 0, + Identifier, + Bool, + Int, + Float, + And, + Or, + GreaterThan, + GreaterThanEqual, + LessThan, + LessThanEqual, + Equal, + NotEqual, + LeftParen, + RightParen, + Not, + Minus, + Plus, + Multiply, + Divide, + Modulus, + Comma, + Error + }; + Token(Type type) : type {type} {} + Token(const QStringRef& strRef) : type {Type::Identifier}, strVal {strRef.toString()} {} + explicit Token(int val) : type {Type::Int}, intVal {val} {} + explicit Token(bool val) : type {Type::Bool}, intVal {val} {} + explicit Token(float val) : type {Type::Float}, floatVal {val} {} + Type type {End}; + QString strVal; + int intVal {0}; + float floatVal {0.0f}; + }; + + struct OpCode { + enum Type { + Identifier, + Bool, + Int, + Float, + And, + Or, + GreaterThan, + GreaterThanEqual, + LessThan, + LessThanEqual, + Equal, + NotEqual, + Not, + Subtract, + Add, + Multiply, + Divide, + Modulus, + UnaryMinus + }; + OpCode(Type type) : type {type} {} + explicit OpCode(const QStringRef& strRef) : type {Type::Identifier}, strVal {strRef.toString()} {} + explicit OpCode(const QString& str) : type {Type::Identifier}, strVal {str} {} + explicit OpCode(int val) : type {Type::Int}, intVal {val} {} + explicit OpCode(bool val) : type {Type::Bool}, intVal {(int)val} {} + explicit OpCode(float val) : type {Type::Float}, floatVal {val} {} + + bool coerceBool(const AnimVariantMap& map) const { + if (type == Int || type == Bool) { + return intVal != 0; + } else if (type == Identifier) { + return map.lookup(strVal, false); + } else { + return true; + } + } + + Type type {Int}; + QString strVal; + int intVal {0}; + float floatVal {0.0f}; + }; + + void unconsumeToken(const Token& token); + Token consumeToken(const QString& str, QString::const_iterator& iter) const; + Token consumeIdentifier(const QString& str, QString::const_iterator& iter) const; + Token consumeNumber(const QString& str, QString::const_iterator& iter) const; + Token consumeAnd(const QString& str, QString::const_iterator& iter) const; + Token consumeOr(const QString& str, QString::const_iterator& iter) const; + Token consumeGreaterThan(const QString& str, QString::const_iterator& iter) const; + Token consumeLessThan(const QString& str, QString::const_iterator& iter) const; + Token consumeNot(const QString& str, QString::const_iterator& iter) const; + + bool parseExpr(const QString& str, QString::const_iterator& iter); + bool parseExprPrime(const QString& str, QString::const_iterator& iter); + bool parseTerm(const QString& str, QString::const_iterator& iter); + bool parseTermPrime(const QString& str, QString::const_iterator& iter); + bool parseUnary(const QString& str, QString::const_iterator& iter); + bool parseFactor(const QString& str, QString::const_iterator& iter); + + OpCode evaluate(const AnimVariantMap& map) const; + void evalAnd(const AnimVariantMap& map, std::stack& stack) const; + void evalOr(const AnimVariantMap& map, std::stack& stack) const; + void evalGreaterThan(const AnimVariantMap& map, std::stack& stack) const; + void evalGreaterThanEqual(const AnimVariantMap& map, std::stack& stack) const; + void evalLessThan(const AnimVariantMap& map, std::stack& stack) const; + void evalLessThanEqual(const AnimVariantMap& map, std::stack& stack) const; + void evalEqual(const AnimVariantMap& map, std::stack& stack) const; + void evalNotEqual(const AnimVariantMap& map, std::stack& stack) const; + void evalNot(const AnimVariantMap& map, std::stack& stack) const; + void evalSubtract(const AnimVariantMap& map, std::stack& stack) const; + void evalAdd(const AnimVariantMap& map, std::stack& stack) const; + void add(int lhs, const OpCode& rhs, std::stack& stack) const; + void add(float lhs, const OpCode& rhs, std::stack& stack) const; + void evalMultiply(const AnimVariantMap& map, std::stack& stack) const; + void mul(int lhs, const OpCode& rhs, std::stack& stack) const; + void mul(float lhs, const OpCode& rhs, std::stack& stack) const; + void evalDivide(const AnimVariantMap& map, std::stack& stack) const; + void evalModulus(const AnimVariantMap& map, std::stack& stack) const; + void evalUnaryMinus(const AnimVariantMap& map, std::stack& stack) const; + + OpCode coerseToValue(const AnimVariantMap& map, const OpCode& opCode) const; + + QString _expression; + mutable std::stack _tokenStack; // TODO: remove, only needed during parsing + std::vector _opCodes; + +#ifndef NDEBUG + void dumpOpCodes() const; +#endif +}; + +#endif + diff --git a/libraries/animation/src/AnimVariant.cpp b/libraries/animation/src/AnimVariant.cpp index d57d1b3c34..911899f5a9 100644 --- a/libraries/animation/src/AnimVariant.cpp +++ b/libraries/animation/src/AnimVariant.cpp @@ -15,6 +15,8 @@ #include #include "AnimVariant.h" // which has AnimVariant/AnimVariantMap +const AnimVariant AnimVariant::False = AnimVariant(); + QScriptValue AnimVariantMap::animVariantMapToScriptValue(QScriptEngine* engine, const QStringList& names, bool useNames) const { if (QThread::currentThread() != engine->thread()) { qCWarning(animation) << "Cannot create Javacript object from non-script thread" << QThread::currentThread(); diff --git a/libraries/animation/src/AnimVariant.h b/libraries/animation/src/AnimVariant.h index 0cd7fb29ac..531e2c4a2d 100644 --- a/libraries/animation/src/AnimVariant.h +++ b/libraries/animation/src/AnimVariant.h @@ -34,6 +34,8 @@ public: NumTypes }; + static const AnimVariant False; + AnimVariant() : _type(Type::Bool) { memset(&_val, 0, sizeof(_val)); } AnimVariant(bool value) : _type(Type::Bool) { _val.boolVal = value; } AnimVariant(int value) : _type(Type::Int) { _val.intVal = value; } @@ -57,13 +59,50 @@ public: void setQuat(const glm::quat& value) { assert(_type == Type::Quat); *reinterpret_cast(&_val) = value; } void setString(const QString& value) { assert(_type == Type::String); _stringVal = value; } - bool getBool() const { assert(_type == Type::Bool); return _val.boolVal; } - int getInt() const { assert(_type == Type::Int || _type == Type::Float); return _type == Type::Float ? (int)_val.floats[0] : _val.intVal; } - float getFloat() const { assert(_type == Type::Float || _type == Type::Int); return _type == Type::Int ? (float)_val.intVal : _val.floats[0]; } - - const glm::vec3& getVec3() const { assert(_type == Type::Vec3); return *reinterpret_cast(&_val); } - const glm::quat& getQuat() const { assert(_type == Type::Quat); return *reinterpret_cast(&_val); } - const QString& getString() const { assert(_type == Type::String); return _stringVal; } + bool getBool() const { + if (_type == Type::Bool) { + return _val.boolVal; + } else if (_type == Type::Int) { + return _val.intVal != 0; + } else { + return false; + } + } + int getInt() const { + if (_type == Type::Int) { + return _val.intVal; + } else if (_type == Type::Float) { + return (int)_val.floats[0]; + } else { + return 0; + } + } + float getFloat() const { + if (_type == Type::Float) { + return _val.floats[0]; + } else if (_type == Type::Int) { + return (float)_val.intVal; + } else { + return 0.0f; + } + } + const glm::vec3& getVec3() const { + if (_type == Type::Vec3) { + return *reinterpret_cast(&_val); + } else { + return Vectors::ZERO; + } + } + const glm::quat& getQuat() const { + if (_type == Type::Quat) { + return *reinterpret_cast(&_val); + } else { + return Quaternions::IDENTITY; + } + } + const QString& getString() const { + return _stringVal; + } protected: Type _type; @@ -71,7 +110,7 @@ protected: union { bool boolVal; int intVal; - float floats[16]; + float floats[4]; } _val; }; @@ -172,6 +211,15 @@ public: void clearMap() { _map.clear(); } bool hasKey(const QString& key) const { return _map.find(key) != _map.end(); } + const AnimVariant& get(const QString& key) const { + auto iter = _map.find(key); + if (iter != _map.end()) { + return iter->second; + } else { + return AnimVariant::False; + } + } + // Answer a Plain Old Javascript Object (for the given engine) all of our values set as properties. QScriptValue animVariantMapToScriptValue(QScriptEngine* engine, const QStringList& names, bool useNames) const; // Side-effect us with the value of object's own properties. (No inherited properties.) diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 68f382d2d9..4dd091f1d6 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -289,8 +289,10 @@ void Rig::clearJointState(int index) { void Rig::clearJointStates() { _internalPoseSet._overrideFlags.clear(); - _internalPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints()); - _internalPoseSet._overridePoses = _animSkeleton->getRelativeDefaultPoses(); + if (_animSkeleton) { + _internalPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints()); + _internalPoseSet._overridePoses = _animSkeleton->getRelativeDefaultPoses(); + } } void Rig::clearJointAnimationPriority(int index) { diff --git a/libraries/controllers/src/controllers/StandardControls.h b/libraries/controllers/src/controllers/StandardControls.h index bbd33c5cb3..2b0613321e 100644 --- a/libraries/controllers/src/controllers/StandardControls.h +++ b/libraries/controllers/src/controllers/StandardControls.h @@ -88,15 +88,73 @@ namespace controller { // No correlation to SDL enum StandardPoseChannel { - LEFT_HAND = 0, - RIGHT_HAND, + HIPS = 0, + RIGHT_UP_LEG, + RIGHT_LEG, + RIGHT_FOOT, + LEFT_UP_LEG, + LEFT_LEG, + LEFT_FOOT, + SPINE, + SPINE1, + SPINE2, + SPINE3, + NECK, HEAD, + RIGHT_SHOULDER, + RIGHT_ARM, + RIGHT_FORE_ARM, + RIGHT_HAND, + RIGHT_HAND_THUMB1, + RIGHT_HAND_THUMB2, + RIGHT_HAND_THUMB3, + RIGHT_HAND_THUMB4, + RIGHT_HAND_INDEX1, + RIGHT_HAND_INDEX2, + RIGHT_HAND_INDEX3, + RIGHT_HAND_INDEX4, + RIGHT_HAND_MIDDLE1, + RIGHT_HAND_MIDDLE2, + RIGHT_HAND_MIDDLE3, + RIGHT_HAND_MIDDLE4, + RIGHT_HAND_RING1, + RIGHT_HAND_RING2, + RIGHT_HAND_RING3, + RIGHT_HAND_RING4, + RIGHT_HAND_PINKY1, + RIGHT_HAND_PINKY2, + RIGHT_HAND_PINKY3, + RIGHT_HAND_PINKY4, + LEFT_SHOULDER, + LEFT_ARM, + LEFT_FORE_ARM, + LEFT_HAND, + LEFT_HAND_THUMB1, + LEFT_HAND_THUMB2, + LEFT_HAND_THUMB3, + LEFT_HAND_THUMB4, + LEFT_HAND_INDEX1, + LEFT_HAND_INDEX2, + LEFT_HAND_INDEX3, + LEFT_HAND_INDEX4, + LEFT_HAND_MIDDLE1, + LEFT_HAND_MIDDLE2, + LEFT_HAND_MIDDLE3, + LEFT_HAND_MIDDLE4, + LEFT_HAND_RING1, + LEFT_HAND_RING2, + LEFT_HAND_RING3, + LEFT_HAND_RING4, + LEFT_HAND_PINKY1, + LEFT_HAND_PINKY2, + LEFT_HAND_PINKY3, + LEFT_HAND_PINKY4, NUM_STANDARD_POSES }; enum StandardCounts { TRIGGERS = 2, ANALOG_STICKS = 2, - POSES = 2, // FIXME 3? if we want to expose the head? + POSES = NUM_STANDARD_POSES }; } diff --git a/libraries/model/src/model/Skybox.cpp b/libraries/model/src/model/Skybox.cpp index 8c37359638..476ac2fa08 100755 --- a/libraries/model/src/model/Skybox.cpp +++ b/libraries/model/src/model/Skybox.cpp @@ -96,7 +96,12 @@ void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Sky } }); + // Render + gpu::TexturePointer skymap = skybox.getCubemap(); + // FIXME: skymap->isDefined may not be threadsafe + assert(skymap && skymap->isDefined()); + glm::mat4 projMat; viewFrustum.evalProjectionMatrix(projMat); @@ -106,11 +111,6 @@ void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Sky batch.setViewTransform(viewTransform); batch.setModelTransform(Transform()); // only for Mac - gpu::TexturePointer skymap; - if (skybox.getCubemap() && skybox.getCubemap()->isDefined()) { - skymap = skybox.getCubemap(); - } - batch.setPipeline(thePipeline); batch.setUniformBuffer(SKYBOX_CONSTANTS_SLOT, skybox._dataBuffer); batch.setResourceTexture(SKYBOX_SKYMAP_SLOT, skymap); @@ -118,6 +118,5 @@ void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Sky batch.draw(gpu::TRIANGLE_STRIP, 4); batch.setResourceTexture(SKYBOX_SKYMAP_SLOT, nullptr); - } diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 5075d78597..5af2bbe7c7 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -234,6 +234,7 @@ void NodeList::addSetOfNodeTypesToNodeInterestSet(const NodeSet& setOfNodeTypes) void NodeList::sendDomainServerCheckIn() { if (_isShuttingDown) { qCDebug(networking) << "Refusing to send a domain-server check in while shutting down."; + return; } if (_publicSockAddr.isNull()) { diff --git a/libraries/procedural/src/procedural/ProceduralSkybox.cpp b/libraries/procedural/src/procedural/ProceduralSkybox.cpp index ce6f29c3d5..167d49cbaf 100644 --- a/libraries/procedural/src/procedural/ProceduralSkybox.cpp +++ b/libraries/procedural/src/procedural/ProceduralSkybox.cpp @@ -48,6 +48,10 @@ void ProceduralSkybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, } if (skybox._procedural && skybox._procedural->_enabled && skybox._procedural->ready()) { + gpu::TexturePointer skymap = skybox.getCubemap(); + // FIXME: skymap->isDefined may not be threadsafe + assert(skymap && skymap->isDefined()); + glm::mat4 projMat; viewFrustum.evalProjectionMatrix(projMat); @@ -56,10 +60,7 @@ void ProceduralSkybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, batch.setProjectionTransform(projMat); batch.setViewTransform(viewTransform); batch.setModelTransform(Transform()); // only for Mac - - if (skybox.getCubemap() && skybox.getCubemap()->isDefined()) { - batch.setResourceTexture(0, skybox.getCubemap()); - } + batch.setResourceTexture(0, skybox.getCubemap()); skybox._procedural->prepare(batch, glm::vec3(0), glm::vec3(1)); batch.draw(gpu::TRIANGLE_STRIP, 4); diff --git a/libraries/shared/src/GLMHelpers.h b/libraries/shared/src/GLMHelpers.h index 25ded54e93..55458e98a2 100644 --- a/libraries/shared/src/GLMHelpers.h +++ b/libraries/shared/src/GLMHelpers.h @@ -82,6 +82,7 @@ public: static const vec3& RIGHT; static const vec3& UP; static const vec3& FRONT; + static const vec3 ZERO4; }; // These pack/unpack functions are designed to start specific known types in as efficient a manner diff --git a/plugins/hifiNeuron/CMakeLists.txt b/plugins/hifiNeuron/CMakeLists.txt new file mode 100644 index 0000000000..9c512fc877 --- /dev/null +++ b/plugins/hifiNeuron/CMakeLists.txt @@ -0,0 +1,13 @@ +# +# Created by Anthony Thibault on 2015/12/18 +# 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 +# + +set(TARGET_NAME hifiNeuron) +setup_hifi_plugin(Script Qml Widgets) +link_hifi_libraries(shared controllers plugins input-plugins) +target_neuron() + diff --git a/plugins/hifiNeuron/src/NeuronPlugin.cpp b/plugins/hifiNeuron/src/NeuronPlugin.cpp new file mode 100644 index 0000000000..a175ce8e06 --- /dev/null +++ b/plugins/hifiNeuron/src/NeuronPlugin.cpp @@ -0,0 +1,557 @@ +// +// NeuronPlugin.cpp +// input-plugins/src/input-plugins +// +// Created by Anthony Thibault on 12/18/2015. +// 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 +// + +#include "NeuronPlugin.h" + +#include +#include +#include +#include +#include +#include +#include + +Q_DECLARE_LOGGING_CATEGORY(inputplugins) +Q_LOGGING_CATEGORY(inputplugins, "hifi.inputplugins") + +#define __OS_XUN__ 1 +#define BOOL int + +#ifdef HAVE_NEURON +#include +#endif + +const QString NeuronPlugin::NAME = "Neuron"; +const QString NeuronPlugin::NEURON_ID_STRING = "Perception Neuron"; + +// indices of joints of the Neuron standard skeleton. +// This is 'almost' the same as the High Fidelity standard skeleton. +// It is missing a thumb joint. +enum NeuronJointIndex { + Hips = 0, + RightUpLeg, + RightLeg, + RightFoot, + LeftUpLeg, + LeftLeg, + LeftFoot, + Spine, + Spine1, + Spine2, + Spine3, + Neck, + Head, + RightShoulder, + RightArm, + RightForeArm, + RightHand, + RightHandThumb1, + RightHandThumb2, + RightHandThumb3, + RightInHandIndex, + RightHandIndex1, + RightHandIndex2, + RightHandIndex3, + RightInHandMiddle, + RightHandMiddle1, + RightHandMiddle2, + RightHandMiddle3, + RightInHandRing, + RightHandRing1, + RightHandRing2, + RightHandRing3, + RightInHandPinky, + RightHandPinky1, + RightHandPinky2, + RightHandPinky3, + LeftShoulder, + LeftArm, + LeftForeArm, + LeftHand, + LeftHandThumb1, + LeftHandThumb2, + LeftHandThumb3, + LeftInHandIndex, + LeftHandIndex1, + LeftHandIndex2, + LeftHandIndex3, + LeftInHandMiddle, + LeftHandMiddle1, + LeftHandMiddle2, + LeftHandMiddle3, + LeftInHandRing, + LeftHandRing1, + LeftHandRing2, + LeftHandRing3, + LeftInHandPinky, + LeftHandPinky1, + LeftHandPinky2, + LeftHandPinky3, + Size +}; + +// Almost a direct mapping except for LEFT_HAND_THUMB1 and RIGHT_HAND_THUMB1, +// which are not present in the Neuron standard skeleton. +static controller::StandardPoseChannel neuronJointIndexToPoseIndexMap[NeuronJointIndex::Size] = { + controller::HIPS, + controller::RIGHT_UP_LEG, + controller::RIGHT_LEG, + controller::RIGHT_FOOT, + controller::LEFT_UP_LEG, + controller::LEFT_LEG, + controller::LEFT_FOOT, + controller::SPINE, + controller::SPINE1, + controller::SPINE2, + controller::SPINE3, + controller::NECK, + controller::HEAD, + controller::RIGHT_SHOULDER, + controller::RIGHT_ARM, + controller::RIGHT_FORE_ARM, + controller::RIGHT_HAND, + controller::RIGHT_HAND_THUMB2, + controller::RIGHT_HAND_THUMB3, + controller::RIGHT_HAND_THUMB4, + controller::RIGHT_HAND_INDEX1, + controller::RIGHT_HAND_INDEX2, + controller::RIGHT_HAND_INDEX3, + controller::RIGHT_HAND_INDEX4, + controller::RIGHT_HAND_MIDDLE1, + controller::RIGHT_HAND_MIDDLE2, + controller::RIGHT_HAND_MIDDLE3, + controller::RIGHT_HAND_MIDDLE4, + controller::RIGHT_HAND_RING1, + controller::RIGHT_HAND_RING2, + controller::RIGHT_HAND_RING3, + controller::RIGHT_HAND_RING4, + controller::RIGHT_HAND_PINKY1, + controller::RIGHT_HAND_PINKY2, + controller::RIGHT_HAND_PINKY3, + controller::RIGHT_HAND_PINKY4, + controller::LEFT_SHOULDER, + controller::LEFT_ARM, + controller::LEFT_FORE_ARM, + controller::LEFT_HAND, + controller::LEFT_HAND_THUMB2, + controller::LEFT_HAND_THUMB3, + controller::LEFT_HAND_THUMB4, + controller::LEFT_HAND_INDEX1, + controller::LEFT_HAND_INDEX2, + controller::LEFT_HAND_INDEX3, + controller::LEFT_HAND_INDEX4, + controller::LEFT_HAND_MIDDLE1, + controller::LEFT_HAND_MIDDLE2, + controller::LEFT_HAND_MIDDLE3, + controller::LEFT_HAND_MIDDLE4, + controller::LEFT_HAND_RING1, + controller::LEFT_HAND_RING2, + controller::LEFT_HAND_RING3, + controller::LEFT_HAND_RING4, + controller::LEFT_HAND_PINKY1, + controller::LEFT_HAND_PINKY2, + controller::LEFT_HAND_PINKY3, + controller::LEFT_HAND_PINKY4 +}; + +// in rig frame +static glm::vec3 rightHandThumb1DefaultAbsTranslation(-2.155500650405884, -0.7610001564025879, 2.685631036758423); +static glm::vec3 leftHandThumb1DefaultAbsTranslation(2.1555817127227783, -0.7603635787963867, 2.6856393814086914); + +// default translations (cm) +static glm::vec3 neuronJointTranslations[NeuronJointIndex::Size] = { + {131.901, 95.6602, -27.9815}, + {-9.55907, -1.58772, 0.0760284}, + {0.0144232, -41.4683, -0.105322}, + {1.59348, -41.5875, -0.557237}, + {9.72077, -1.68926, -0.280643}, + {0.0886684, -43.1586, -0.0111596}, + {-2.98473, -44.0517, 0.0694456}, + {0.110967, 16.3959, 0.140463}, + {0.0500451, 10.0238, 0.0731921}, + {0.061568, 10.4352, 0.0583075}, + {0.0500606, 10.0217, 0.0711083}, + {0.0317731, 10.7176, 0.0779325}, + {-0.0204253, 9.71067, 0.131734}, + {-3.24245, 7.13584, 0.185638}, + {-13.0885, -0.0877601, 0.176065}, + {-27.2674, 0.0688724, 0.0272146}, + {-26.7673, 0.0301916, 0.0102847}, + {-2.56017, 0.195537, 3.20968}, + {-3.78796, 0, 0}, + {-2.63141, 0, 0}, + {-3.31579, 0.522947, 2.03495}, + {-5.36589, -0.0939789, 1.02771}, + {-3.72278, 0, 0}, + {-2.11074, 0, 0}, + {-3.47874, 0.532042, 0.778358}, + {-5.32194, -0.0864, 0.322863}, + {-4.06232, 0, 0}, + {-2.54653, 0, 0}, + {-3.46131, 0.553263, -0.132632}, + {-4.76716, -0.0227368, -0.492632}, + {-3.54073, 0, 0}, + {-2.45634, 0, 0}, + {-3.25137, 0.482779, -1.23613}, + {-4.25937, -0.0227368, -1.12168}, + {-2.83528, 0, 0}, + {-1.79166, 0, 0}, + {3.25624, 7.13148, -0.131575}, + {13.149, -0.052598, -0.125076}, + {27.2903, 0.00282644, -0.0181535}, + {26.6602, 0.000969969, -0.0487599}, + {2.56017, 0.195537, 3.20968}, + {3.78796, 0, 0}, + {2.63141, 0, 0}, + {3.31579, 0.522947, 2.03495}, + {5.36589, -0.0939789, 1.02771}, + {3.72278, 0, 0}, + {2.11074, 0, 0}, + {3.47874, 0.532042, 0.778358}, + {5.32194, -0.0864, 0.322863}, + {4.06232, 0, 0}, + {2.54653, 0, 0}, + {3.46131, 0.553263, -0.132632}, + {4.76716, -0.0227368, -0.492632}, + {3.54073, 0, 0}, + {2.45634, 0, 0}, + {3.25137, 0.482779, -1.23613}, + {4.25937, -0.0227368, -1.12168}, + {2.83528, 0, 0}, + {1.79166, 0, 0} +}; + +static controller::StandardPoseChannel neuronJointIndexToPoseIndex(NeuronJointIndex i) { + assert(i >= 0 && i < NeuronJointIndex::Size); + if (i >= 0 && i < NeuronJointIndex::Size) { + return neuronJointIndexToPoseIndexMap[i]; + } else { + return (controller::StandardPoseChannel)0; // not sure what to do here, but don't crash! + } +} + +static const char* controllerJointName(controller::StandardPoseChannel i) { + switch (i) { + case controller::HIPS: return "Hips"; + case controller::RIGHT_UP_LEG: return "RightUpLeg"; + case controller::RIGHT_LEG: return "RightLeg"; + case controller::RIGHT_FOOT: return "RightFoot"; + case controller::LEFT_UP_LEG: return "LeftUpLeg"; + case controller::LEFT_LEG: return "LeftLeg"; + case controller::LEFT_FOOT: return "LeftFoot"; + case controller::SPINE: return "Spine"; + case controller::SPINE1: return "Spine1"; + case controller::SPINE2: return "Spine2"; + case controller::SPINE3: return "Spine3"; + case controller::NECK: return "Neck"; + case controller::HEAD: return "Head"; + case controller::RIGHT_SHOULDER: return "RightShoulder"; + case controller::RIGHT_ARM: return "RightArm"; + case controller::RIGHT_FORE_ARM: return "RightForeArm"; + case controller::RIGHT_HAND: return "RightHand"; + case controller::RIGHT_HAND_THUMB1: return "RightHandThumb1"; + case controller::RIGHT_HAND_THUMB2: return "RightHandThumb2"; + case controller::RIGHT_HAND_THUMB3: return "RightHandThumb3"; + case controller::RIGHT_HAND_THUMB4: return "RightHandThumb4"; + case controller::RIGHT_HAND_INDEX1: return "RightHandIndex1"; + case controller::RIGHT_HAND_INDEX2: return "RightHandIndex2"; + case controller::RIGHT_HAND_INDEX3: return "RightHandIndex3"; + case controller::RIGHT_HAND_INDEX4: return "RightHandIndex4"; + case controller::RIGHT_HAND_MIDDLE1: return "RightHandMiddle1"; + case controller::RIGHT_HAND_MIDDLE2: return "RightHandMiddle2"; + case controller::RIGHT_HAND_MIDDLE3: return "RightHandMiddle3"; + case controller::RIGHT_HAND_MIDDLE4: return "RightHandMiddle4"; + case controller::RIGHT_HAND_RING1: return "RightHandRing1"; + case controller::RIGHT_HAND_RING2: return "RightHandRing2"; + case controller::RIGHT_HAND_RING3: return "RightHandRing3"; + case controller::RIGHT_HAND_RING4: return "RightHandRing4"; + case controller::RIGHT_HAND_PINKY1: return "RightHandPinky1"; + case controller::RIGHT_HAND_PINKY2: return "RightHandPinky2"; + case controller::RIGHT_HAND_PINKY3: return "RightHandPinky3"; + case controller::RIGHT_HAND_PINKY4: return "RightHandPinky4"; + case controller::LEFT_SHOULDER: return "LeftShoulder"; + case controller::LEFT_ARM: return "LeftArm"; + case controller::LEFT_FORE_ARM: return "LeftForeArm"; + case controller::LEFT_HAND: return "LeftHand"; + case controller::LEFT_HAND_THUMB1: return "LeftHandThumb1"; + case controller::LEFT_HAND_THUMB2: return "LeftHandThumb2"; + case controller::LEFT_HAND_THUMB3: return "LeftHandThumb3"; + case controller::LEFT_HAND_THUMB4: return "LeftHandThumb4"; + case controller::LEFT_HAND_INDEX1: return "LeftHandIndex1"; + case controller::LEFT_HAND_INDEX2: return "LeftHandIndex2"; + case controller::LEFT_HAND_INDEX3: return "LeftHandIndex3"; + case controller::LEFT_HAND_INDEX4: return "LeftHandIndex4"; + case controller::LEFT_HAND_MIDDLE1: return "LeftHandMiddle1"; + case controller::LEFT_HAND_MIDDLE2: return "LeftHandMiddle2"; + case controller::LEFT_HAND_MIDDLE3: return "LeftHandMiddle3"; + case controller::LEFT_HAND_MIDDLE4: return "LeftHandMiddle4"; + case controller::LEFT_HAND_RING1: return "LeftHandRing1"; + case controller::LEFT_HAND_RING2: return "LeftHandRing2"; + case controller::LEFT_HAND_RING3: return "LeftHandRing3"; + case controller::LEFT_HAND_RING4: return "LeftHandRing4"; + case controller::LEFT_HAND_PINKY1: return "LeftHandPinky1"; + case controller::LEFT_HAND_PINKY2: return "LeftHandPinky2"; + case controller::LEFT_HAND_PINKY3: return "LeftHandPinky3"; + case controller::LEFT_HAND_PINKY4: return "LeftHandPinky4"; + default: return "???"; + } +} + +// convert between YXZ neuron euler angles in degrees to quaternion +// this is the default setting in the Axis Neuron server. +static quat eulerToQuat(vec3 euler) { + // euler.x and euler.y are swaped, WTF. + glm::vec3 e = glm::vec3(euler.y, euler.x, euler.z) * RADIANS_PER_DEGREE; + return (glm::angleAxis(e.y, Vectors::UNIT_Y) * + glm::angleAxis(e.x, Vectors::UNIT_X) * + glm::angleAxis(e.z, Vectors::UNIT_Z)); +} + +#ifdef HAVE_NEURON + +// +// neuronDataReader SDK callback functions +// + +// NOTE: must be thread-safe +void FrameDataReceivedCallback(void* context, SOCKET_REF sender, BvhDataHeaderEx* header, float* data) { + + auto neuronPlugin = reinterpret_cast(context); + + // version 1.0 + if (header->DataVersion.Major == 1 && header->DataVersion.Minor == 0) { + + // skip reference joint if present + if (header->WithReference && header->WithDisp) { + data += 6; + } else if (header->WithReference && !header->WithDisp) { + data += 3; + } + + if (header->WithDisp) { + // enter mutex + std::lock_guard guard(neuronPlugin->_jointsMutex); + + // + // Data is 6 floats per joint: 3 position values, 3 rotation euler angles (degrees) + // + + // resize vector if necessary + const size_t NUM_FLOATS_PER_JOINT = 6; + const size_t NUM_JOINTS = header->DataCount / NUM_FLOATS_PER_JOINT; + if (neuronPlugin->_joints.size() != NUM_JOINTS) { + neuronPlugin->_joints.resize(NUM_JOINTS, { { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f } }); + } + + assert(sizeof(NeuronPlugin::NeuronJoint) == (NUM_FLOATS_PER_JOINT * sizeof(float))); + + // copy the data + memcpy(&(neuronPlugin->_joints[0]), data, sizeof(NeuronPlugin::NeuronJoint) * NUM_JOINTS); + + } else { + qCWarning(inputplugins) << "NeuronPlugin: unsuported binary format, please enable displacements"; + + // enter mutex + std::lock_guard guard(neuronPlugin->_jointsMutex); + + if (neuronPlugin->_joints.size() != NeuronJointIndex::Size) { + neuronPlugin->_joints.resize(NeuronJointIndex::Size, { { 0.0f, 0.0f, 0.0f }, { 0.0f, 0.0f, 0.0f } }); + } + + for (int i = 0; i < NeuronJointIndex::Size; i++) { + neuronPlugin->_joints[i].euler = glm::vec3(); + neuronPlugin->_joints[i].pos = neuronJointTranslations[i]; + } + } + } else { + static bool ONCE = false; + if (!ONCE) { + qCCritical(inputplugins) << "NeuronPlugin: bad frame version number, expected 1.0"; + ONCE = true; + } + } +} + +// I can't even get the SDK to send me a callback. +// BRCommandFetchAvatarDataFromServer & BRRegisterAutoSyncParmeter [sic] don't seem to work. +// So this is totally untested. +// NOTE: must be thread-safe +static void CommandDataReceivedCallback(void* context, SOCKET_REF sender, CommandPack* pack, void* data) { + + DATA_VER version; + version._VersionMask = pack->DataVersion; + if (version.Major == 1 && version.Minor == 0) { + const char* str = "Unknown"; + switch (pack->CommandId) { + case Cmd_BoneSize: // Id can be used to request bone size from server or register avatar name command. + str = "BoneSize"; + break; + case Cmd_AvatarName: // Id can be used to request avatar name from server or register avatar name command. + str = "AvatarName"; + break; + case Cmd_FaceDirection: // Id used to request face direction from server + str = "FaceDirection"; + break; + case Cmd_DataFrequency: // Id can be used to request data frequency from server or register data frequency command. + str = "DataFrequency"; + break; + case Cmd_BvhInheritanceTxt: // Id can be used to request bvh header txt from server or register bvh header txt command. + str = "BvhInheritanceTxt"; + break; + case Cmd_AvatarCount: // Id can be used to request avatar count from server or register avatar count command. + str = "AvatarCount"; + break; + case Cmd_CombinationMode: // Id can be used to request combination mode from server or register combination mode command. + str = "CombinationMode"; + break; + case Cmd_RegisterEvent: // Id can be used to register event. + str = "RegisterEvent"; + break; + case Cmd_UnRegisterEvent: // Id can be used to unregister event. + str = "UnRegisterEvent"; + break; + } + qCDebug(inputplugins) << "NeuronPlugin: command data received CommandID = " << str; + } else { + static bool ONCE = false; + if (!ONCE) { + qCCritical(inputplugins) << "NeuronPlugin: bad command version number, expected 1.0"; + ONCE = true; + } + } +} + +// NOTE: must be thread-safe +static void SocketStatusChangedCallback(void* context, SOCKET_REF sender, SocketStatus status, char* message) { + // just dump to log, later we might want to pop up a connection lost dialog or attempt to reconnect. + qCDebug(inputplugins) << "NeuronPlugin: socket status = " << message; +} + +#endif // #ifdef HAVE_NEURON + +// +// NeuronPlugin +// + +bool NeuronPlugin::isSupported() const { +#ifdef HAVE_NEURON + // Because it's a client/server network architecture, we can't tell + // if the neuron is actually connected until we connect to the server. + return true; +#else + return false; +#endif +} + +void NeuronPlugin::activate() { +#ifdef HAVE_NEURON + InputPlugin::activate(); + + // register with userInputMapper + auto userInputMapper = DependencyManager::get(); + userInputMapper->registerDevice(_inputDevice); + + // register c-style callbacks + BRRegisterFrameDataCallback((void*)this, FrameDataReceivedCallback); + BRRegisterCommandDataCallback((void*)this, CommandDataReceivedCallback); + BRRegisterSocketStatusCallback((void*)this, SocketStatusChangedCallback); + + // TODO: Pull these from prefs dialog? + // localhost is fine for now. + _serverAddress = "localhost"; + _serverPort = 7001; // default port for TCP Axis Neuron server. + + _socketRef = BRConnectTo((char*)_serverAddress.c_str(), _serverPort); + if (!_socketRef) { + // error + qCCritical(inputplugins) << "NeuronPlugin: error connecting to " << _serverAddress.c_str() << ":" << _serverPort << ", error = " << BRGetLastErrorMessage(); + } else { + qCDebug(inputplugins) << "NeuronPlugin: success connecting to " << _serverAddress.c_str() << ":" << _serverPort; + + BRRegisterAutoSyncParmeter(_socketRef, Cmd_CombinationMode); + } +#endif +} + +void NeuronPlugin::deactivate() { +#ifdef HAVE_NEURON + // unregister from userInputMapper + if (_inputDevice->_deviceID != controller::Input::INVALID_DEVICE) { + auto userInputMapper = DependencyManager::get(); + userInputMapper->removeDevice(_inputDevice->_deviceID); + } + + if (_socketRef) { + BRUnregisterAutoSyncParmeter(_socketRef, Cmd_CombinationMode); + BRCloseSocket(_socketRef); + } + + InputPlugin::deactivate(); +#endif +} + +void NeuronPlugin::pluginUpdate(float deltaTime, bool jointsCaptured) { + std::vector joints; + { + // lock and copy + std::lock_guard guard(_jointsMutex); + joints = _joints; + } + _inputDevice->update(deltaTime, joints, _prevJoints); + _prevJoints = joints; +} + +void NeuronPlugin::saveSettings() const { + InputPlugin::saveSettings(); +} + +void NeuronPlugin::loadSettings() { + InputPlugin::loadSettings(); +} + +// +// InputDevice +// + +controller::Input::NamedVector NeuronPlugin::InputDevice::getAvailableInputs() const { + static controller::Input::NamedVector availableInputs; + if (availableInputs.size() == 0) { + for (int i = 0; i < controller::NUM_STANDARD_POSES; i++) { + auto channel = static_cast(i); + availableInputs.push_back(makePair(channel, controllerJointName(channel))); + } + }; + return availableInputs; +} + +QString NeuronPlugin::InputDevice::getDefaultMappingConfig() const { + static const QString MAPPING_JSON = PathUtils::resourcesPath() + "/controllers/neuron.json"; + return MAPPING_JSON; +} + +void NeuronPlugin::InputDevice::update(float deltaTime, const std::vector& joints, const std::vector& prevJoints) { + for (size_t i = 0; i < joints.size(); i++) { + glm::vec3 linearVel, angularVel; + glm::vec3 pos = joints[i].pos; + glm::quat rot = eulerToQuat(joints[i].euler); + if (i < prevJoints.size()) { + linearVel = (pos - (prevJoints[i].pos * METERS_PER_CENTIMETER)) / deltaTime; // m/s + // quat log imaginary part points along the axis of rotation, with length of one half the angle of rotation. + glm::quat d = glm::log(rot * glm::inverse(eulerToQuat(prevJoints[i].euler))); + angularVel = glm::vec3(d.x, d.y, d.z) / (0.5f * deltaTime); // radians/s + } + int poseIndex = neuronJointIndexToPoseIndex((NeuronJointIndex)i); + _poseStateMap[poseIndex] = controller::Pose(pos, rot, linearVel, angularVel); + } + + _poseStateMap[controller::RIGHT_HAND_THUMB1] = controller::Pose(rightHandThumb1DefaultAbsTranslation, glm::quat(), glm::vec3(), glm::vec3()); + _poseStateMap[controller::LEFT_HAND_THUMB1] = controller::Pose(leftHandThumb1DefaultAbsTranslation, glm::quat(), glm::vec3(), glm::vec3()); +} diff --git a/plugins/hifiNeuron/src/NeuronPlugin.h b/plugins/hifiNeuron/src/NeuronPlugin.h new file mode 100644 index 0000000000..c85a5dd383 --- /dev/null +++ b/plugins/hifiNeuron/src/NeuronPlugin.h @@ -0,0 +1,85 @@ +// +// NeuronPlugin.h +// input-plugins/src/input-plugins +// +// Created by Anthony Thibault on 12/18/2015. +// 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_NeuronPlugin_h +#define hifi_NeuronPlugin_h + +#include +#include +#include + +struct _BvhDataHeaderEx; +void FrameDataReceivedCallback(void* context, void* sender, _BvhDataHeaderEx* header, float* data); + +// Handles interaction with the Neuron SDK +class NeuronPlugin : public InputPlugin { + Q_OBJECT +public: + friend void FrameDataReceivedCallback(void* context, void* sender, _BvhDataHeaderEx* header, float* data); + + // Plugin functions + virtual bool isSupported() const override; + virtual bool isJointController() const override { return true; } + const QString& getName() const override { return NAME; } + const QString& getID() const override { return NEURON_ID_STRING; } + + virtual void activate() override; + virtual void deactivate() override; + + virtual void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); } + virtual void pluginUpdate(float deltaTime, bool jointsCaptured) override; + + virtual void saveSettings() const override; + virtual void loadSettings() override; + +protected: + + struct NeuronJoint { + glm::vec3 pos; + glm::vec3 euler; + }; + + class InputDevice : public controller::InputDevice { + public: + friend class NeuronPlugin; + + InputDevice() : controller::InputDevice("Neuron") {} + + // Device functions + virtual controller::Input::NamedVector getAvailableInputs() const override; + virtual QString getDefaultMappingConfig() const override; + virtual void update(float deltaTime, bool jointsCaptured) override {}; + virtual void focusOutEvent() override {}; + + void update(float deltaTime, const std::vector& joints, const std::vector& prevJoints); + }; + + std::shared_ptr _inputDevice { std::make_shared() }; + + static const QString NAME; + static const QString NEURON_ID_STRING; + + std::string _serverAddress; + int _serverPort; + void* _socketRef; + + // used to guard multi-threaded access to _joints + std::mutex _jointsMutex; + + // copy of data directly from the NeuronDataReader SDK + std::vector _joints; + + // one frame old copy of _joints, used to caluclate angular and linear velocity. + std::vector _prevJoints; +}; + +#endif // hifi_NeuronPlugin_h + diff --git a/plugins/hifiNeuron/src/NeuronProvider.cpp b/plugins/hifiNeuron/src/NeuronProvider.cpp new file mode 100644 index 0000000000..b171c5150d --- /dev/null +++ b/plugins/hifiNeuron/src/NeuronProvider.cpp @@ -0,0 +1,45 @@ +// +// Created by Anthony Thibault on 2015/12/18 +// 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 +// + +#include + +#include +#include +#include + +#include +#include + +#include "NeuronPlugin.h" + +class NeuronProvider : public QObject, public InputProvider +{ + Q_OBJECT + Q_PLUGIN_METADATA(IID InputProvider_iid FILE "plugin.json") + Q_INTERFACES(InputProvider) + +public: + NeuronProvider(QObject* parent = nullptr) : QObject(parent) {} + virtual ~NeuronProvider() {} + + virtual InputPluginList getInputPlugins() override { + static std::once_flag once; + std::call_once(once, [&] { + InputPluginPointer plugin(new NeuronPlugin()); + if (plugin->isSupported()) { + _inputPlugins.push_back(plugin); + } + }); + return _inputPlugins; + } + +private: + InputPluginList _inputPlugins; +}; + +#include "NeuronProvider.moc" diff --git a/plugins/hifiNeuron/src/plugin.json b/plugins/hifiNeuron/src/plugin.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/plugins/hifiNeuron/src/plugin.json @@ -0,0 +1 @@ +{} diff --git a/tests/QTestExtensions.h b/tests/QTestExtensions.h index 16e51b41ee..b7b9795a9a 100644 --- a/tests/QTestExtensions.h +++ b/tests/QTestExtensions.h @@ -274,7 +274,7 @@ struct ByteData { QTextStream & operator << (QTextStream& stream, const ByteData & wrapper) { // Print bytes as hex - stream << QByteArray::fromRawData(wrapper.data, wrapper.length).toHex(); + stream << QByteArray::fromRawData(wrapper.data, (int)wrapper.length).toHex(); return stream; } diff --git a/tests/animation/src/AnimTests.cpp b/tests/animation/src/AnimTests.cpp index 64db3f6154..6812bb63b6 100644 --- a/tests/animation/src/AnimTests.cpp +++ b/tests/animation/src/AnimTests.cpp @@ -8,12 +8,13 @@ // #include "AnimTests.h" -#include "AnimNodeLoader.h" -#include "AnimClip.h" -#include "AnimBlendLinear.h" -#include "AnimationLogging.h" -#include "AnimVariant.h" -#include "AnimUtil.h" +#include +#include +#include +#include +#include +#include +#include #include <../QTestExtensions.h> @@ -315,7 +316,6 @@ void AnimTests::testAccumulateTimeWithParameters(float startFrame, float endFram triggers.clear(); } - void AnimTests::testAnimPose() { const float PI = (float)M_PI; const glm::quat ROT_X_90 = glm::angleAxis(PI / 2.0f, glm::vec3(1.0f, 0.0f, 0.0f)); @@ -394,3 +394,234 @@ void AnimTests::testAnimPose() { } } } + +void AnimTests::testExpressionTokenizer() { + QString str = "(10 + x) >= 20.1 && (y != !z)"; + AnimExpression e("x"); + auto iter = str.cbegin(); + AnimExpression::Token token = e.consumeToken(str, iter); + QVERIFY(token.type == AnimExpression::Token::LeftParen); + token = e.consumeToken(str, iter); + QVERIFY(token.type == AnimExpression::Token::Int); + QVERIFY(token.intVal == 10); + token = e.consumeToken(str, iter); + QVERIFY(token.type == AnimExpression::Token::Plus); + token = e.consumeToken(str, iter); + QVERIFY(token.type == AnimExpression::Token::Identifier); + QVERIFY(token.strVal == "x"); + token = e.consumeToken(str, iter); + QVERIFY(token.type == AnimExpression::Token::RightParen); + token = e.consumeToken(str, iter); + QVERIFY(token.type == AnimExpression::Token::GreaterThanEqual); + token = e.consumeToken(str, iter); + QVERIFY(token.type == AnimExpression::Token::Float); + QVERIFY(fabsf(token.floatVal - 20.1f) < 0.0001f); + token = e.consumeToken(str, iter); + QVERIFY(token.type == AnimExpression::Token::And); + token = e.consumeToken(str, iter); + QVERIFY(token.type == AnimExpression::Token::LeftParen); + token = e.consumeToken(str, iter); + QVERIFY(token.type == AnimExpression::Token::Identifier); + QVERIFY(token.strVal == "y"); + token = e.consumeToken(str, iter); + QVERIFY(token.type == AnimExpression::Token::NotEqual); + token = e.consumeToken(str, iter); + QVERIFY(token.type == AnimExpression::Token::Not); + token = e.consumeToken(str, iter); + QVERIFY(token.type == AnimExpression::Token::Identifier); + QVERIFY(token.strVal == "z"); + token = e.consumeToken(str, iter); + QVERIFY(token.type == AnimExpression::Token::RightParen); + token = e.consumeToken(str, iter); + + str = "true"; + iter = str.cbegin(); + token = e.consumeToken(str, iter); + QVERIFY(token.type == AnimExpression::Token::Bool); + QVERIFY(token.intVal == (int)true); +} + +void AnimTests::testExpressionParser() { + + auto vars = AnimVariantMap(); + vars.set("f", false); + vars.set("t", true); + vars.set("ten", (int)10); + vars.set("twenty", (int)20); + vars.set("five", (float)5.0f); + vars.set("forty", (float)40.0f); + + AnimExpression e("10"); + QVERIFY(e._opCodes.size() == 1); + if (e._opCodes.size() == 1) { + QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Int); + QVERIFY(e._opCodes[0].intVal == 10); + } + + e = AnimExpression("(10)"); + QVERIFY(e._opCodes.size() == 1); + if (e._opCodes.size() == 1) { + QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Int); + QVERIFY(e._opCodes[0].intVal == 10); + } + + e = AnimExpression("((10))"); + QVERIFY(e._opCodes.size() == 1); + if (e._opCodes.size() == 1) { + QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Int); + QVERIFY(e._opCodes[0].intVal == 10); + } + + e = AnimExpression("12.5"); + QVERIFY(e._opCodes.size() == 1); + if (e._opCodes.size() == 1) { + QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Float); + QVERIFY(e._opCodes[0].floatVal == 12.5f); + } + + e = AnimExpression("twenty"); + QVERIFY(e._opCodes.size() == 1); + if (e._opCodes.size() == 1) { + QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Identifier); + QVERIFY(e._opCodes[0].strVal == "twenty"); + } + + e = AnimExpression("true || false"); + QVERIFY(e._opCodes.size() == 3); + if (e._opCodes.size() == 3) { + QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Bool); + QVERIFY(e._opCodes[0].intVal == (int)true); + QVERIFY(e._opCodes[1].type == AnimExpression::OpCode::Bool); + QVERIFY(e._opCodes[1].intVal == (int)false); + QVERIFY(e._opCodes[2].type == AnimExpression::OpCode::Or); + } + + e = AnimExpression("true || false && true"); + QVERIFY(e._opCodes.size() == 5); + if (e._opCodes.size() == 5) { + QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Bool); + QVERIFY(e._opCodes[0].intVal == (int)true); + QVERIFY(e._opCodes[1].type == AnimExpression::OpCode::Bool); + QVERIFY(e._opCodes[1].intVal == (int)false); + QVERIFY(e._opCodes[2].type == AnimExpression::OpCode::Bool); + QVERIFY(e._opCodes[2].intVal == (int)true); + QVERIFY(e._opCodes[3].type == AnimExpression::OpCode::And); + QVERIFY(e._opCodes[4].type == AnimExpression::OpCode::Or); + } + + e = AnimExpression("(true || false) && true"); + QVERIFY(e._opCodes.size() == 5); + if (e._opCodes.size() == 5) { + QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Bool); + QVERIFY(e._opCodes[0].intVal == (int)true); + QVERIFY(e._opCodes[1].type == AnimExpression::OpCode::Bool); + QVERIFY(e._opCodes[1].intVal == (int)false); + QVERIFY(e._opCodes[2].type == AnimExpression::OpCode::Or); + QVERIFY(e._opCodes[3].type == AnimExpression::OpCode::Bool); + QVERIFY(e._opCodes[3].intVal == (int)true); + QVERIFY(e._opCodes[4].type == AnimExpression::OpCode::And); + } + + e = AnimExpression("!(true || false) && true"); + QVERIFY(e._opCodes.size() == 6); + if (e._opCodes.size() == 6) { + QVERIFY(e._opCodes[0].type == AnimExpression::OpCode::Bool); + QVERIFY(e._opCodes[0].intVal == (int)true); + QVERIFY(e._opCodes[1].type == AnimExpression::OpCode::Bool); + QVERIFY(e._opCodes[1].intVal == (int)false); + QVERIFY(e._opCodes[2].type == AnimExpression::OpCode::Or); + QVERIFY(e._opCodes[3].type == AnimExpression::OpCode::Not); + QVERIFY(e._opCodes[4].type == AnimExpression::OpCode::Bool); + QVERIFY(e._opCodes[4].intVal == (int)true); + QVERIFY(e._opCodes[5].type == AnimExpression::OpCode::And); + } +} + +#define TEST_BOOL_EXPR(EXPR) \ + result = AnimExpression( #EXPR ).evaluate(vars); \ + QVERIFY(result.type == AnimExpression::OpCode::Bool); \ + QVERIFY(result.intVal == (int)(EXPR)) + +void AnimTests::testExpressionEvaluator() { + auto vars = AnimVariantMap(); + + bool f = false; + bool t = true; + int ten = 10; + int twenty = 20; + float five = 5.0f; + float fourty = 40.0f; + vars.set("f", f); + vars.set("t", t); + vars.set("ten", ten); + vars.set("twenty", twenty); + vars.set("five", five); + vars.set("forty", fourty); + + AnimExpression::OpCode result(AnimExpression::OpCode::Int); + + result = AnimExpression("10").evaluate(vars); + QVERIFY(result.type == AnimExpression::OpCode::Int); + QVERIFY(result.intVal == 10); + + result = AnimExpression("(10)").evaluate(vars); + QVERIFY(result.type == AnimExpression::OpCode::Int); + QVERIFY(result.intVal == 10); + + TEST_BOOL_EXPR(true); + TEST_BOOL_EXPR(false); + TEST_BOOL_EXPR(t); + TEST_BOOL_EXPR(f); + + TEST_BOOL_EXPR(true || false); + TEST_BOOL_EXPR(true || true); + TEST_BOOL_EXPR(false || false); + TEST_BOOL_EXPR(false || true); + + TEST_BOOL_EXPR(true && false); + TEST_BOOL_EXPR(true && true); + TEST_BOOL_EXPR(false && false); + TEST_BOOL_EXPR(false && true); + + TEST_BOOL_EXPR(true || false && true); + TEST_BOOL_EXPR(true || false && false); + TEST_BOOL_EXPR(true || true && true); + TEST_BOOL_EXPR(true || true && false); + TEST_BOOL_EXPR(false || false && true); + TEST_BOOL_EXPR(false || false && false); + TEST_BOOL_EXPR(false || true && true); + TEST_BOOL_EXPR(false || true && false); + + TEST_BOOL_EXPR(true && false || true); + TEST_BOOL_EXPR(true && false || false); + TEST_BOOL_EXPR(true && true || true); + TEST_BOOL_EXPR(true && true || false); + TEST_BOOL_EXPR(false && false || true); + TEST_BOOL_EXPR(false && false || false); + TEST_BOOL_EXPR(false && true || true); + TEST_BOOL_EXPR(false && true || false); + + TEST_BOOL_EXPR(t || false); + TEST_BOOL_EXPR(t || true); + TEST_BOOL_EXPR(f || false); + TEST_BOOL_EXPR(f || true); + + TEST_BOOL_EXPR(!true); + TEST_BOOL_EXPR(!false); + TEST_BOOL_EXPR(!true || true); + + TEST_BOOL_EXPR(!true && !false || !true); + TEST_BOOL_EXPR(!true && !false || true); + TEST_BOOL_EXPR(!true && false || !true); + TEST_BOOL_EXPR(!true && false || true); + TEST_BOOL_EXPR(true && !false || !true); + TEST_BOOL_EXPR(true && !false || true); + TEST_BOOL_EXPR(true && false || !true); + TEST_BOOL_EXPR(true && false || true); + + TEST_BOOL_EXPR(!(true && f) || !t); + TEST_BOOL_EXPR(!!!(t) && (!!f || true)); + TEST_BOOL_EXPR(!(true && f) && true); +} + + diff --git a/tests/animation/src/AnimTests.h b/tests/animation/src/AnimTests.h index a07217b91a..439793f21d 100644 --- a/tests/animation/src/AnimTests.h +++ b/tests/animation/src/AnimTests.h @@ -27,6 +27,9 @@ private slots: void testVariant(); void testAccumulateTime(); void testAnimPose(); + void testExpressionTokenizer(); + void testExpressionParser(); + void testExpressionEvaluator(); }; #endif // hifi_AnimTests_h diff --git a/tests/shared/src/GLMHelpersTests.cpp b/tests/shared/src/GLMHelpersTests.cpp new file mode 100644 index 0000000000..afb634ecbd --- /dev/null +++ b/tests/shared/src/GLMHelpersTests.cpp @@ -0,0 +1,57 @@ +// +// GLMHelpersTests.cpp +// tests/shared/src +// +// Created by Anthony Thibault on 2015.12.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 +// + +#include "GLMHelpersTests.h" + +#include +#include + +#include <../QTestExtensions.h> + + +QTEST_MAIN(GLMHelpersTests) + +void GLMHelpersTests::testEulerDecomposition() { + // quat to euler and back again.... + + const glm::quat ROT_X_90 = glm::angleAxis(PI / 2.0f, glm::vec3(1.0f, 0.0f, 0.0f)); + const glm::quat ROT_Y_180 = glm::angleAxis(PI, glm::vec3(0.0f, 1.0, 0.0f)); + const glm::quat ROT_Z_30 = glm::angleAxis(PI / 6.0f, glm::vec3(1.0f, 0.0f, 0.0f)); + + const float EPSILON = 0.00001f; + + std::vector quatVec = { + glm::quat(), + ROT_X_90, + ROT_Y_180, + ROT_Z_30, + ROT_X_90 * ROT_Y_180 * ROT_Z_30, + ROT_X_90 * ROT_Z_30 * ROT_Y_180, + ROT_Y_180 * ROT_Z_30 * ROT_X_90, + ROT_Y_180 * ROT_X_90 * ROT_Z_30, + ROT_Z_30 * ROT_X_90 * ROT_Y_180, + ROT_Z_30 * ROT_Y_180 * ROT_X_90, + }; + + for (auto& q : quatVec) { + glm::vec3 euler = safeEulerAngles(q); + glm::quat r(euler); + + // when the axis and angle are flipped. + if (glm::dot(q, r) < 0.0f) { + r = -r; + } + + QCOMPARE_WITH_ABS_ERROR(q, r, EPSILON); + } +} + + diff --git a/tests/shared/src/GLMHelpersTests.h b/tests/shared/src/GLMHelpersTests.h new file mode 100644 index 0000000000..5e880899e8 --- /dev/null +++ b/tests/shared/src/GLMHelpersTests.h @@ -0,0 +1,27 @@ +// +// GLMHelpersTests.h +// tests/shared/src +// +// Created by Anthony thibault on 2015.12.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 +// + +#ifndef hifi_GLMHelpersTests_h +#define hifi_GLMHelpersTests_h + +#include +#include + +class GLMHelpersTests : public QObject { + Q_OBJECT +private slots: + void testEulerDecomposition(); +}; + +float getErrorDifference(const float& a, const float& b); +float getErrorDifference(const glm::vec3& a, const glm::vec3& b); + +#endif // hifi_GLMHelpersTest_h diff --git a/tests/shared/src/GeometryUtilTests.cpp b/tests/shared/src/GeometryUtilTests.cpp index eaf1e7cd8a..7ba22ec13d 100644 --- a/tests/shared/src/GeometryUtilTests.cpp +++ b/tests/shared/src/GeometryUtilTests.cpp @@ -1,6 +1,6 @@ // // GeometryUtilTests.cpp -// tests/physics/src +// tests/shared/src // // Created by Andrew Meadows on 2015.07.27 // Copyright 2015 High Fidelity, Inc. diff --git a/tests/shared/src/GeometryUtilTests.h b/tests/shared/src/GeometryUtilTests.h index 6996c8bcea..daf740dcd3 100644 --- a/tests/shared/src/GeometryUtilTests.h +++ b/tests/shared/src/GeometryUtilTests.h @@ -1,6 +1,6 @@ // // GeometryUtilTests.h -// tests/physics/src +// tests/shared/src // // Created by Andrew Meadows on 2014.05.30 // Copyright 2014 High Fidelity, Inc. @@ -9,8 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifndef hifi_AngularConstraintTests_h -#define hifi_AngularConstraintTests_h +#ifndef hifi_GeometryUtilTests_h +#define hifi_GeometryUtilTests_h #include #include @@ -26,4 +26,4 @@ private slots: float getErrorDifference(const float& a, const float& b); float getErrorDifference(const glm::vec3& a, const glm::vec3& b); -#endif // hifi_AngularConstraintTests_h +#endif // hifi_GeometryUtilTests_h