diff --git a/CMakeLists.txt b/CMakeLists.txt index 47560576b2..79d8a0c0e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -46,6 +46,9 @@ if (WIN32) # Caveats: http://stackoverflow.com/questions/2288728/drawbacks-of-using-largeaddressaware-for-32-bit-windows-executables # TODO: Remove when building 64-bit. set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /LARGEADDRESSAWARE") + # always produce symbols as PDB files + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEBUG /OPT:REF /OPT:ICF") else () set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -fno-strict-aliasing -Wno-unused-parameter") if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 157154606f..d9109703cb 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -233,8 +233,8 @@ void Agent::setIsAvatar(bool isAvatar) { } if (_avatarBillboardTimer) { - _avatarIdentityTimer->stop(); - delete _avatarIdentityTimer; + _avatarBillboardTimer->stop(); + delete _avatarBillboardTimer; _avatarBillboardTimer = nullptr; } } @@ -364,10 +364,15 @@ void Agent::processAgentAvatarAndAudio(float deltaTime) { } void Agent::aboutToFinish() { - _scriptEngine->stop(); + setIsAvatar(false);// will stop timers for sending billboards and identity packets + if (_scriptEngine) { + _scriptEngine->stop(); + } - _pingTimer->stop(); - delete _pingTimer; + if (_pingTimer) { + _pingTimer->stop(); + delete _pingTimer; + } // our entity tree is going to go away so tell that to the EntityScriptingInterface DependencyManager::get()->setEntityTree(NULL); diff --git a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp index 4ae07042e8..cb94990037 100644 --- a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp +++ b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp @@ -238,7 +238,6 @@ int OctreeInboundPacketProcessor::sendNackPackets() { return 0; } - auto nackPacketList = NLPacketList::create(_myServer->getMyEditNackType()); auto nodeList = DependencyManager::get(); int packetsSent = 0; @@ -272,18 +271,19 @@ int OctreeInboundPacketProcessor::sendNackPackets() { auto it = missingSequenceNumbers.constBegin(); - while (it != missingSequenceNumbers.constEnd()) { - unsigned short int sequenceNumber = *it; - nackPacketList->writePrimitive(sequenceNumber); - ++it; - } - - - if (nackPacketList->getNumPackets()) { + if (it != missingSequenceNumbers.constEnd()) { + auto nackPacketList = NLPacketList::create(_myServer->getMyEditNackType()); + + while (it != missingSequenceNumbers.constEnd()) { + unsigned short int sequenceNumber = *it; + nackPacketList->writePrimitive(sequenceNumber); + ++it; + } + qDebug() << "NACK Sent back to editor/client... destinationNode=" << nodeUUID; - + packetsSent += nackPacketList->getNumPackets(); - + // send the list of nack packets nodeList->sendPacketList(std::move(nackPacketList), *destinationNode); } diff --git a/cmake/macros/CopyDllsBesideWindowsExecutable.cmake b/cmake/macros/CopyDllsBesideWindowsExecutable.cmake index e8d499bab8..b57c781eff 100644 --- a/cmake/macros/CopyDllsBesideWindowsExecutable.cmake +++ b/cmake/macros/CopyDllsBesideWindowsExecutable.cmake @@ -37,7 +37,7 @@ macro(COPY_DLLS_BESIDE_WINDOWS_EXECUTABLE) add_custom_command( TARGET ${TARGET_NAME} POST_BUILD - COMMAND CMD /C "SET PATH=%PATH%;${QT_DIR}/bin && ${WINDEPLOYQT_COMMAND} $" + COMMAND CMD /C "SET PATH=%PATH%;${QT_DIR}/bin && ${WINDEPLOYQT_COMMAND} $<$,$,$>:--release> $" ) endif () endmacro() \ No newline at end of file diff --git a/examples/acScripts/flickeringLight.js b/examples/acScripts/flickeringLight.js index edf8332eb5..ed60d56c86 100644 --- a/examples/acScripts/flickeringLight.js +++ b/examples/acScripts/flickeringLight.js @@ -39,11 +39,13 @@ var ZERO_VEC = { }; var totalTime = 0; +var lastUpdate = 0; +var UPDATE_INTERVAL = 1 / 30; // 30fps var MINIMUM_LIGHT_INTENSITY = 0.75; var MAXIMUM_LIGHT_INTENSITY = 2.75; var LIGHT_INTENSITY_RANDOMNESS = 0.3; -var EPHEMERAL_LIFETIME = 10; // ephemeral entities will live for 10 seconds after script stops running +var EPHEMERAL_LIFETIME = 60; // ephemeral entities will live for 60 seconds after script stops running var LightMaker = { light: null, @@ -74,10 +76,17 @@ function update(deltaTime) { LightMaker.spawnLight(); } else { totalTime += deltaTime; - var intensity = (MINIMUM_LIGHT_INTENSITY + (MAXIMUM_LIGHT_INTENSITY + (Math.sin(totalTime) * MAXIMUM_LIGHT_INTENSITY))); - intensity += randFloat(-LIGHT_INTENSITY_RANDOMNESS, LIGHT_INTENSITY_RANDOMNESS); - var properties = Entities.getEntityProperties(LightMaker.light, "age"); - Entities.editEntity(LightMaker.light, { intensity: intensity, lifetime: properties.age + EPHEMERAL_LIFETIME }); + + // We don't want to edit the entity EVERY update cycle, because that's just a lot + // of wasted bandwidth and extra effort on the server for very little visual gain + if (totalTime - lastUpdate > UPDATE_INTERVAL) { + var intensity = (MINIMUM_LIGHT_INTENSITY + (MAXIMUM_LIGHT_INTENSITY + (Math.sin(totalTime) * MAXIMUM_LIGHT_INTENSITY))); + intensity += randFloat(-LIGHT_INTENSITY_RANDOMNESS, LIGHT_INTENSITY_RANDOMNESS); + var properties = Entities.getEntityProperties(LightMaker.light, "age"); + var newLifetime = properties.age + EPHEMERAL_LIFETIME; + Entities.editEntity(LightMaker.light, { type: "Light", intensity: intensity, lifetime: newLifetime }); + lastUpdate = totalTime; + } } } diff --git a/examples/actionInspector.js b/examples/actionInspector.js new file mode 100644 index 0000000000..934120ddf6 --- /dev/null +++ b/examples/actionInspector.js @@ -0,0 +1,146 @@ +// +// actionInspector.js +// examples +// +// Created by Seth Alves on 2015-9-30. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +Script.include("libraries/utils.js"); + + +var INSPECT_RADIUS = 10; +var overlays = {}; + + +var toType = function(obj) { + return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase() +} + + +function actionArgumentsToString(actionArguments) { + var result = "type: " + actionArguments["type"] + "\n"; + for (var argumentName in actionArguments) { + if (actionArguments.hasOwnProperty(argumentName)) { + if (argumentName == "type") { + continue; + } + var arg = actionArguments[argumentName]; + var argType = toType(arg); + var argString = arg; + if (argType == "object") { + if (Object.keys(arg).length == 3) { + argString = vec3toStr(arg, 1); + } + } else if (argType == "number") { + argString = arg.toFixed(2); + } + result += argumentName + ": " + // + toType(arg) + " -- " + + argString + "\n"; + } + } + + return result; +} + + +function updateOverlay(entityID, actionText) { + var properties = Entities.getEntityProperties(entityID, ["position", "dimensions"]); + var position = Vec3.sum(properties.position, {x:0, y:properties.dimensions.y, z:0}); + // print("position: " + vec3toStr(position) + " " + actionText); + if (entityID in overlays) { + var overlay = overlays[entityID]; + Overlays.editOverlay(overlay, { + text: actionText, + position: position + }); + } else { + var lines = actionText.split(/\r\n|\r|\n/); + + var maxLineLength = lines[0].length; + for (var i = 1; i < lines.length; i++) { + if (lines[i].length > maxLineLength) { + maxLineLength = lines[i].length; + } + } + + var textWidth = maxLineLength * 0.034; // XXX how to know this? + var textHeight = .5; + var numberOfLines = lines.length; + var textMargin = 0.05; + var lineHeight = (textHeight - (2 * textMargin)) / numberOfLines; + + overlays[entityID] = Overlays.addOverlay("text3d", { + position: position, + dimensions: { x: textWidth, y: textHeight }, + backgroundColor: { red: 0, green: 0, blue: 0}, + color: { red: 255, green: 255, blue: 255}, + topMargin: textMargin, + leftMargin: textMargin, + bottomMargin: textMargin, + rightMargin: textMargin, + text: actionText, + lineHeight: lineHeight, + alpha: 0.9, + backgroundAlpha: 0.9, + ignoreRayIntersection: true, + visible: true, + isFacingAvatar: true + }); + } +} + + +function cleanup() { + for (var entityID in overlays) { + Overlays.deleteOverlay(overlays[entityID]); + } +} + + +Script.setInterval(function() { + var nearbyEntities = Entities.findEntities(MyAvatar.position, INSPECT_RADIUS); + for (var entityIndex = 0; entityIndex < nearbyEntities.length; entityIndex++) { + var entityID = nearbyEntities[entityIndex]; + var actionIDs = Entities.getActionIDs(entityID); + var actionText = "" + for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) { + var actionID = actionIDs[actionIndex]; + var actionArguments = Entities.getActionArguments(entityID, actionID); + var actionArgumentText = actionArgumentsToString(actionArguments); + if (actionArgumentText != "") { + actionText += "-----------------\n"; + actionText += actionArgumentText; + } + } + if (actionText != "") { + updateOverlay(entityID, actionText); + } + + // if an entity no longer has an action, remove its overlay + if (actionIDs.length == 0) { + if (entityID in overlays) { + Overlays.deleteOverlay(overlays[entityID]); + delete overlays[entityID]; + } + } + } + + + // if an entity is too far away, remove its overlay + for (var entityID in overlays) { + var position = Entities.getEntityProperties(entityID, ["position"]).position; + if (Vec3.distance(position, MyAvatar.position) > INSPECT_RADIUS) { + Overlays.deleteOverlay(overlays[entityID]); + delete overlays[entityID]; + } + } + +}, 100); + + +Script.scriptEnding.connect(cleanup); diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 41f36c19d5..5705bd4498 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -19,7 +19,8 @@ Script.include("../libraries/utils.js"); // var TRIGGER_SMOOTH_RATIO = 0.1; // 0.0 disables smoothing of trigger value -var TRIGGER_ON_VALUE = 0.2; +var TRIGGER_ON_VALUE = 0.4; +var TRIGGER_OFF_VALUE = 0.15; ///////////////////////////////////////////////////////////////// // @@ -54,27 +55,54 @@ var RELEASE_VELOCITY_MULTIPLIER = 1.5; // affects throwing things var RIGHT_HAND = 1; var LEFT_HAND = 0; -var ZERO_VEC = { x: 0, y: 0, z: 0}; +var ZERO_VEC = { + x: 0, + y: 0, + z: 0 +}; var NULL_ACTION_ID = "{00000000-0000-0000-000000000000}"; var MSEC_PER_SEC = 1000.0; // these control how long an abandoned pointer line will hang around var startTime = Date.now(); var LIFETIME = 10; +var ACTION_LIFETIME = 10; // seconds // states for the state machine -var STATE_SEARCHING = 0; -var STATE_DISTANCE_HOLDING = 1; -var STATE_CONTINUE_DISTANCE_HOLDING = 2; -var STATE_NEAR_GRABBING = 3; -var STATE_CONTINUE_NEAR_GRABBING = 4; -var STATE_NEAR_GRABBING_NON_COLLIDING = 5; -var STATE_CONTINUE_NEAR_GRABBING_NON_COLLIDING = 6; -var STATE_RELEASE = 7; +var STATE_OFF = 0; +var STATE_SEARCHING = 1; +var STATE_DISTANCE_HOLDING = 2; +var STATE_CONTINUE_DISTANCE_HOLDING = 3; +var STATE_NEAR_GRABBING = 4; +var STATE_CONTINUE_NEAR_GRABBING = 5; +var STATE_NEAR_GRABBING_NON_COLLIDING = 6; +var STATE_CONTINUE_NEAR_GRABBING_NON_COLLIDING = 7; +var STATE_RELEASE = 8; var GRAB_USER_DATA_KEY = "grabKey"; var GRABBABLE_DATA_KEY = "grabbableKey"; +function getTag() { + return "grab-" + MyAvatar.sessionUUID; +} + +function entityIsGrabbedByOther(entityID) { + var actionIDs = Entities.getActionIDs(entityID); + for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) { + var actionID = actionIDs[actionIndex]; + var actionArguments = Entities.getActionArguments(entityID, actionID); + var tag = actionArguments["tag"]; + if (tag == getTag()) { + continue; + } + if (tag.slice(0, 5) == "grab-") { + return true; + } + } + return false; +} + + function MyController(hand, triggerAction) { this.hand = hand; if (this.hand === RIGHT_HAND) { @@ -85,23 +113,32 @@ function MyController(hand, triggerAction) { this.getHandRotation = MyAvatar.getLeftPalmRotation; } + var SPATIAL_CONTROLLERS_PER_PALM = 2; + var TIP_CONTROLLER_OFFSET = 1; this.triggerAction = triggerAction; - this.palm = 2 * hand; - // this.tip = 2 * hand + 1; // unused, but I'm leaving this here for fear it will be needed + this.palm = SPATIAL_CONTROLLERS_PER_PALM * hand; + this.tip = SPATIAL_CONTROLLERS_PER_PALM * hand + TIP_CONTROLLER_OFFSET; this.actionID = null; // action this script created... this.grabbedEntity = null; // on this entity. this.grabbedVelocity = ZERO_VEC; // rolling average of held object's velocity - this.state = 0; + this.state = STATE_OFF; this.pointer = null; // entity-id of line object this.triggerValue = 0; // rolling average of trigger value + var _this = this; this.update = function() { + + this.updateSmoothedTrigger(); + switch (this.state) { + case STATE_OFF: + this.off(); + this.touchTest(); + break; case STATE_SEARCHING: this.search(); - this.touchTest(); break; case STATE_DISTANCE_HOLDING: this.distanceHolding(); @@ -158,21 +195,35 @@ function MyController(hand, triggerAction) { this.pointer = null; }; - this.triggerSmoothedSqueezed = function() { + this.updateSmoothedTrigger = function() { var triggerValue = Controller.getActionValue(this.triggerAction); // smooth out trigger value this.triggerValue = (this.triggerValue * TRIGGER_SMOOTH_RATIO) + (triggerValue * (1.0 - TRIGGER_SMOOTH_RATIO)); + } + + this.triggerSmoothedSqueezed = function() { return this.triggerValue > TRIGGER_ON_VALUE; }; + this.triggerSmoothedReleased = function() { + return this.triggerValue < TRIGGER_OFF_VALUE; + }; + this.triggerSqueezed = function() { var triggerValue = Controller.getActionValue(this.triggerAction); return triggerValue > TRIGGER_ON_VALUE; }; + this.off = function() { + if (this.triggerSmoothedSqueezed()) { + this.state = STATE_SEARCHING; + return; + } + } + this.search = function() { - if (!this.triggerSmoothedSqueezed()) { + if (this.triggerSmoothedReleased()) { this.state = STATE_RELEASE; return; } @@ -206,6 +257,10 @@ function MyController(hand, triggerAction) { this.state = STATE_NEAR_GRABBING; } else { + if (entityIsGrabbedByOther(intersection.entityID)) { + // don't allow two people to distance grab the same object + return; + } // the hand is far from the intersected object. go into distance-holding mode this.state = STATE_DISTANCE_HOLDING; this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR); @@ -244,6 +299,7 @@ function MyController(hand, triggerAction) { }; this.distanceHolding = function() { + var handControllerPosition = Controller.getSpatialControlPosition(this.palm); var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(this.palm)); var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["position", "rotation"]); @@ -255,11 +311,14 @@ function MyController(hand, triggerAction) { this.handPreviousPosition = handControllerPosition; this.handPreviousRotation = handRotation; + this.actionID = NULL_ACTION_ID; this.actionID = Entities.addAction("spring", this.grabbedEntity, { targetPosition: this.currentObjectPosition, linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, targetRotation: this.currentObjectRotation, - angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME + angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, + tag: getTag(), + lifetime: ACTION_LIFETIME }); if (this.actionID === NULL_ACTION_ID) { this.actionID = null; @@ -276,10 +335,13 @@ function MyController(hand, triggerAction) { Entities.callEntityMethod(this.grabbedEntity, "startDistantGrab"); } + this.currentAvatarPosition = MyAvatar.position; + this.currentAvatarOrientation = MyAvatar.orientation; + }; this.continueDistanceHolding = function() { - if (!this.triggerSmoothedSqueezed()) { + if (this.triggerSmoothedReleased()) { this.state = STATE_RELEASE; return; } @@ -294,11 +356,46 @@ function MyController(hand, triggerAction) { // the action was set up on a previous call. update the targets. var radius = Math.max(Vec3.distance(this.currentObjectPosition, handControllerPosition) * DISTANCE_HOLDING_RADIUS_FACTOR, DISTANCE_HOLDING_RADIUS_FACTOR); + // how far did avatar move this timestep? + var currentPosition = MyAvatar.position; + var avatarDeltaPosition = Vec3.subtract(currentPosition, this.currentAvatarPosition); + this.currentAvatarPosition = currentPosition; + + // How far did the avatar turn this timestep? + // Note: The following code is too long because we need a Quat.quatBetween() function + // that returns the minimum quaternion between two quaternions. + var currentOrientation = MyAvatar.orientation; + if (Quat.dot(currentOrientation, this.currentAvatarOrientation) < 0.0) { + var negativeCurrentOrientation = { + x: -currentOrientation.x, + y: -currentOrientation.y, + z: -currentOrientation.z, + w: -currentOrientation.w + }; + var avatarDeltaOrientation = Quat.multiply(negativeCurrentOrientation, Quat.inverse(this.currentAvatarOrientation)); + } else { + var avatarDeltaOrientation = Quat.multiply(currentOrientation, Quat.inverse(this.currentAvatarOrientation)); + } + var handToAvatar = Vec3.subtract(handControllerPosition, this.currentAvatarPosition); + var objectToAvatar = Vec3.subtract(this.currentObjectPosition, this.currentAvatarPosition); + var handMovementFromTurning = Vec3.subtract(Quat.multiply(avatarDeltaOrientation, handToAvatar), handToAvatar); + var objectMovementFromTurning = Vec3.subtract(Quat.multiply(avatarDeltaOrientation, objectToAvatar), objectToAvatar); + this.currentAvatarOrientation = currentOrientation; + + // how far did hand move this timestep? var handMoved = Vec3.subtract(handControllerPosition, this.handPreviousPosition); this.handPreviousPosition = handControllerPosition; + + // magnify the hand movement but not the change from avatar movement & rotation + handMoved = Vec3.subtract(handMoved, avatarDeltaPosition); + handMoved = Vec3.subtract(handMoved, handMovementFromTurning); var superHandMoved = Vec3.multiply(handMoved, radius); + // Move the object by the magnified amount and then by amount from avatar movement & rotation var newObjectPosition = Vec3.sum(this.currentObjectPosition, superHandMoved); + newObjectPosition = Vec3.sum(newObjectPosition, avatarDeltaPosition); + newObjectPosition = Vec3.sum(newObjectPosition, objectMovementFromTurning); + var deltaPosition = Vec3.subtract(newObjectPosition, this.currentObjectPosition); // meters var now = Date.now(); var deltaTime = (now - this.currentObjectTime) / MSEC_PER_SEC; // convert to seconds @@ -318,12 +415,14 @@ function MyController(hand, triggerAction) { targetPosition: this.currentObjectPosition, linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, targetRotation: this.currentObjectRotation, - angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME + angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME, + lifetime: ACTION_LIFETIME }); }; this.nearGrabbing = function() { - if (!this.triggerSmoothedSqueezed()) { + + if (this.triggerSmoothedReleased()) { this.state = STATE_RELEASE; return; } @@ -344,11 +443,13 @@ function MyController(hand, triggerAction) { var offset = Vec3.subtract(currentObjectPosition, handPosition); var offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, 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: offsetPosition, - relativeRotation: offsetRotation + relativeRotation: offsetRotation, + lifetime: ACTION_LIFETIME }); if (this.actionID === NULL_ACTION_ID) { this.actionID = null; @@ -363,40 +464,54 @@ function MyController(hand, triggerAction) { } - this.currentHandControllerPosition = Controller.getSpatialControlPosition(this.palm); + this.currentHandControllerTipPosition = Controller.getSpatialControlPosition(this.tip); + this.currentObjectTime = Date.now(); }; this.continueNearGrabbing = function() { - if (!this.triggerSmoothedSqueezed()) { + if (this.triggerSmoothedReleased()) { this.state = STATE_RELEASE; return; } - // keep track of the measured velocity of the held object - var handControllerPosition = Controller.getSpatialControlPosition(this.palm); + // Keep track of the fingertip velocity to impart when we release the object + // Note that the idea of using a constant 'tip' velocity regardless of the + // object's actual held offset is an idea intended to make it easier to throw things: + // Because we might catch something or transfer it between hands without a good idea + // of it's actual offset, let's try imparting a velocity which is at a fixed radius + // from the palm. + + var handControllerPosition = Controller.getSpatialControlPosition(this.tip); var now = Date.now(); - var deltaPosition = Vec3.subtract(handControllerPosition, this.currentHandControllerPosition); // meters + var deltaPosition = Vec3.subtract(handControllerPosition, this.currentHandControllerTipPosition); // meters var deltaTime = (now - this.currentObjectTime) / MSEC_PER_SEC; // convert to seconds this.computeReleaseVelocity(deltaPosition, deltaTime, true); - this.currentHandControllerPosition = handControllerPosition; + this.currentHandControllerTipPosition = handControllerPosition; this.currentObjectTime = now; Entities.callEntityMethod(this.grabbedEntity, "continueNearGrab"); + + Entities.updateAction(this.grabbedEntity, this.actionID, {lifetime: ACTION_LIFETIME}); }; this.nearGrabbingNonColliding = function() { - if (!this.triggerSmoothedSqueezed()) { + if (this.triggerSmoothedReleased()) { this.state = STATE_RELEASE; return; } + if (this.hand === RIGHT_HAND) { + Entities.callEntityMethod(this.grabbedEntity, "setRightHand"); + } else { + Entities.callEntityMethod(this.grabbedEntity, "setLeftHand"); + } Entities.callEntityMethod(this.grabbedEntity, "startNearGrabNonColliding"); this.state = STATE_CONTINUE_NEAR_GRABBING_NON_COLLIDING; }; this.continueNearGrabbingNonColliding = function() { - if (!this.triggerSmoothedSqueezed()) { + if (this.triggerSmoothedReleased()) { this.state = STATE_RELEASE; return; } @@ -459,17 +574,14 @@ function MyController(hand, triggerAction) { }; this.startTouch = function(entityID) { - // print('START TOUCH' + entityID); Entities.callEntityMethod(entityID, "startTouch"); }; this.continueTouch = function(entityID) { - // print('CONTINUE TOUCH' + entityID); Entities.callEntityMethod(entityID, "continueTouch"); }; this.stopTouch = function(entityID) { - // print('STOP TOUCH' + entityID); Entities.callEntityMethod(entityID, "stopTouch"); }; @@ -491,10 +603,13 @@ function MyController(hand, triggerAction) { }; this.release = function() { + this.lineOff(); - if (this.grabbedEntity !== null && this.actionID !== null) { - Entities.deleteAction(this.grabbedEntity, this.actionID); + if (this.grabbedEntity !== null) { + if(this.actionID !== null) { + Entities.deleteAction(this.grabbedEntity, this.actionID); + } Entities.callEntityMethod(this.grabbedEntity, "releaseGrab"); } @@ -508,7 +623,7 @@ function MyController(hand, triggerAction) { this.grabbedVelocity = ZERO_VEC; this.grabbedEntity = null; this.actionID = null; - this.state = STATE_SEARCHING; + this.state = STATE_OFF; }; this.cleanup = function() { diff --git a/examples/example/hmd/colorCube.fs b/examples/example/hmd/colorCube.fs new file mode 100644 index 0000000000..2687b70807 --- /dev/null +++ b/examples/example/hmd/colorCube.fs @@ -0,0 +1,10 @@ + +float getProceduralColors(inout vec3 diffuse, inout vec3 specular, inout float shininess) { + + specular = _modelNormal.rgb; + if (any(lessThan(specular, vec3(0.0)))) { + specular = vec3(1.0) + specular; + } + diffuse = vec3(1.0, 1.0, 1.0); + return 1.0; +} \ No newline at end of file diff --git a/examples/example/hmd/colorCube.js b/examples/example/hmd/colorCube.js new file mode 100644 index 0000000000..a138f38190 --- /dev/null +++ b/examples/example/hmd/colorCube.js @@ -0,0 +1,42 @@ +function avatarRelativePosition(position) { + return Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, position)); +} + +ColorCube = function() {}; +ColorCube.prototype.NAME = "ColorCube"; +ColorCube.prototype.POSITION = { x: 0, y: 0.5, z: -0.5 }; +ColorCube.prototype.USER_DATA = { ProceduralEntity: { + version: 2, shaderUrl: Script.resolvePath("colorCube.fs"), +} }; + +// Clear any previous entities within 50 meters +ColorCube.prototype.clear = function() { + var ids = Entities.findEntities(MyAvatar.position, 50); + var that = this; + ids.forEach(function(id) { + var properties = Entities.getEntityProperties(id); + if (properties.name == that.NAME) { + Entities.deleteEntity(id); + } + }, this); +} + +ColorCube.prototype.create = function() { + var that = this; + var size = HMD.ipd; + var id = Entities.addEntity({ + type: "Box", + position: avatarRelativePosition(that.POSITION), + name: that.NAME, + color: that.COLOR, + ignoreCollisions: true, + collisionsWillMove: false, + dimensions: { x: size, y: size, z: size }, + lifetime: 3600, + userData: JSON.stringify(that.USER_DATA) + }); +} + +var colorCube = new ColorCube(); +colorCube.clear(); +colorCube.create(); diff --git a/examples/grab.js b/examples/grab.js index fd67287645..03a227931d 100644 --- a/examples/grab.js +++ b/examples/grab.js @@ -9,7 +9,7 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/*global print, MouseMyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt, pointInExtents, vec3equal, setEntityCustomData, getEntityCustomData */ +/*global print, Mouse, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt, pointInExtents, vec3equal, setEntityCustomData, getEntityCustomData */ Script.include("libraries/utils.js"); // objects that appear smaller than this can't be grabbed @@ -32,6 +32,45 @@ var defaultGrabbableData = { grabbable: true }; + +var MAX_SOLID_ANGLE = 0.01; // objects that appear smaller than this can't be grabbed +var ZERO_VEC3 = { + x: 0, + y: 0, + z: 0 +}; +var IDENTITY_QUAT = { + x: 0, + y: 0, + z: 0, + w: 0 +}; +var ACTION_LIFETIME = 120; // 2 minutes + +function getTag() { + return "grab-" + MyAvatar.sessionUUID; +} + +function entityIsGrabbedByOther(entityID) { + var actionIDs = Entities.getActionIDs(entityID); + var actionIndex; + var actionID; + var actionArguments; + var tag; + for (actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) { + actionID = actionIDs[actionIndex]; + actionArguments = Entities.getActionArguments(entityID, actionID); + tag = actionArguments.tag; + if (tag === getTag()) { + continue; + } + if (tag.slice(0, 5) === "grab-") { + return true; + } + } + return false; +} + // helper function function mouseIntersectionWithPlane(pointOnPlane, planeNormal, event, maxDistance) { var cameraPosition = Camera.getPosition(); @@ -371,7 +410,10 @@ Grabber.prototype.moveEvent = function(event) { } this.currentPosition = entityProperties.position; - var actionArgs = {}; + var actionArgs = { + tag: getTag(), + lifetime: ACTION_LIFETIME + }; if (this.mode === "rotate") { var drag = mouse.getDrag(); @@ -386,10 +428,14 @@ Grabber.prototype.moveEvent = function(event) { // var qZero = entityProperties.rotation; //var qZero = this.lastRotation; this.lastRotation = Quat.multiply(deltaQ, this.lastRotation); + actionArgs = { targetRotation: this.lastRotation, - angularTimeScale: 0.1 + angularTimeScale: 0.1, + tag: getTag(), + lifetime: ACTION_LIFETIME }; + } else { var newPointOnPlane; if (this.mode === "verticalCylinder") { @@ -417,16 +463,22 @@ Grabber.prototype.moveEvent = function(event) { } } this.targetPosition = Vec3.subtract(newPointOnPlane, this.offset); + actionArgs = { targetPosition: this.targetPosition, - linearTimeScale: 0.1 + linearTimeScale: 0.1, + tag: getTag(), + lifetime: ACTION_LIFETIME }; + beacon.updatePosition(this.targetPosition); } if (!this.actionID) { - this.actionID = Entities.addAction("spring", this.entityID, actionArgs); + if (!entityIsGrabbedByOther(this.entityID)) { + this.actionID = Entities.addAction("spring", this.entityID, actionArgs); + } } else { Entities.updateAction(this.entityID, this.actionID, actionArgs); } diff --git a/examples/toys/basketball_hoop/createHoop.js b/examples/toys/basketball_hoop/createHoop.js new file mode 100644 index 0000000000..3887e0b421 --- /dev/null +++ b/examples/toys/basketball_hoop/createHoop.js @@ -0,0 +1,43 @@ +// +// createHoop.js +// examples/entityScripts +// +// Created by James B. Pollack on 9/29/2015 +// Copyright 2015 High Fidelity, Inc. +// +// This is a script that creates a persistent basketball hoop with a working collision hull. Feel free to move it. +// Run basketball.js to make a basketball. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +/*global MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */ + +var hoopURL = "http://hifi-public.s3.amazonaws.com/models/basketball_hoop/basketball_hoop.fbx"; +var hoopCollisionHullURL = "http://hifi-public.s3.amazonaws.com/models/basketball_hoop/basketball_hoop_collision_hull.obj"; + +var hoopStartPosition = + Vec3.sum(MyAvatar.position, + Vec3.multiplyQbyV(MyAvatar.orientation, { + x: 0, + y: 0.0, + z: -2 + })); + +var hoop = Entities.addEntity({ + type: "Model", + modelURL: hoopURL, + position: hoopStartPosition, + shapeType: 'compound', + gravity: { + x: 0, + y: -9.8, + z: 0 + }, + dimensions: { + x: 1.89, + y: 3.99, + z: 3.79 + }, + compoundShapeURL: hoopCollisionHullURL +}); + diff --git a/examples/toys/ping_pong_gun/createPingPongGun.js b/examples/toys/ping_pong_gun/createPingPongGun.js new file mode 100644 index 0000000000..4b7ed27643 --- /dev/null +++ b/examples/toys/ping_pong_gun/createPingPongGun.js @@ -0,0 +1,43 @@ +// createPingPongGun.js +// +// Script Type: Entity Spawner +// Created by James B. Pollack on 9/30/2015 +// Copyright 2015 High Fidelity, Inc. +// +// This script creates a gun that shoots ping pong balls when you pull the trigger on a hand controller. +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +/*global MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */ +Script.include("../../utilities.js"); + +var scriptURL = Script.resolvePath('pingPongGun.js'); + +var MODEL_URL = 'http://hifi-public.s3.amazonaws.com/models/ping_pong_gun/ping_pong_gun.fbx' +var COLLISION_HULL_URL = 'http://hifi-public.s3.amazonaws.com/models/ping_pong_gun/ping_pong_gun_collision_hull.obj'; + +var center = Vec3.sum(Vec3.sum(MyAvatar.position, { + x: 0, + y: 0.5, + z: 0 +}), Vec3.multiply(0.5, Quat.getFront(Camera.getOrientation()))); + +var pingPongGun = Entities.addEntity({ + type: "Model", + modelURL: MODEL_URL, + shapeType: 'compound', + compoundShapeURL: COLLISION_HULL_URL, + script: scriptURL, + position: center, + dimensions: { + x:0.67, + y: 0.14, + z: 0.09 + }, + collisionsWillMove: true, +}); + +function cleanUp() { + Entities.deleteEntity(pingPongGun); +} +Script.scriptEnding.connect(cleanUp); diff --git a/examples/toys/ping_pong_gun/pingPongGun.js b/examples/toys/ping_pong_gun/pingPongGun.js new file mode 100644 index 0000000000..a980fc1bd3 --- /dev/null +++ b/examples/toys/ping_pong_gun/pingPongGun.js @@ -0,0 +1,160 @@ +// pingPongGun.js +// +// Script Type: Entity +// Created by James B. Pollack @imgntn on 9/21/2015 +// Copyright 2015 High Fidelity, Inc. +// +// This script shoots a ping pong ball. +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */ +(function() { + + Script.include("../../libraries/utils.js"); + + var SHOOTING_SOUND_URL = 'http://hifi-public.s3.amazonaws.com/sounds/ping_pong_gun/pong_sound.wav'; + + function PingPongGun() { + return; + } + + //if the trigger value goes below this value, reload the gun. + var RELOAD_THRESHOLD = 0.95; + var GUN_TIP_FWD_OFFSET = 0.45; + var GUN_TIP_UP_OFFSET = 0.040; + var GUN_FORCE = 15; + var BALL_RESTITUTION = 0.6; + var BALL_LINEAR_DAMPING = 0.4; + var BALL_GRAVITY = { + x: 0, + y: -9.8, + z: 0 + }; + + var BALL_DIMENSIONS = { + x: 0.04, + y: 0.04, + z: 0.04 + } + + + var BALL_COLOR = { + red: 255, + green: 255, + blue: 255 + } + + PingPongGun.prototype = { + hand: null, + whichHand: null, + gunTipPosition: null, + canShoot: false, + canShootTimeout: null, + setRightHand: function() { + this.hand = 'RIGHT'; + }, + + setLeftHand: function() { + this.hand = 'LEFT'; + }, + + startNearGrab: function() { + this.setWhichHand(); + }, + + setWhichHand: function() { + this.whichHand = this.hand; + }, + + continueNearGrab: function() { + + if (this.whichHand === null) { + //only set the active hand once -- if we always read the current hand, our 'holding' hand will get overwritten + this.setWhichHand(); + } else { + if (this.canShootTimeout !== null) { + Script.clearTimeout(this.canShootTimeout); + } + this.checkTriggerPressure(this.whichHand); + } + }, + + releaseGrab: function() { + var _t = this; + this.canShootTimeout = Script.setTimeout(function() { + _t.canShoot = false; + }, 250) + }, + + checkTriggerPressure: function(gunHand) { + var handClickString = gunHand + "_HAND_CLICK"; + + var handClick = Controller.findAction(handClickString); + + this.triggerValue = Controller.getActionValue(handClick); + + if (this.triggerValue < RELOAD_THRESHOLD) { + // print('RELOAD'); + this.canShoot = true; + } else if (this.triggerValue >= RELOAD_THRESHOLD && this.canShoot === true) { + var gunProperties = Entities.getEntityProperties(this.entityID, ["position", "rotation"]); + this.shootBall(gunProperties); + this.canShoot = false; + } + return; + }, + + shootBall: function(gunProperties) { + var forwardVec = Quat.getFront(Quat.multiply(gunProperties.rotation, Quat.fromPitchYawRollDegrees(0, -90, 0))); + forwardVec = Vec3.normalize(forwardVec); + forwardVec = Vec3.multiply(forwardVec, GUN_FORCE); + var properties = { + type: 'Sphere', + color: BALL_COLOR, + dimensions: BALL_DIMENSIONS, + linearDamping: BALL_LINEAR_DAMPING, + gravity: BALL_GRAVITY, + restitution: BALL_RESTITUTION, + collisionsWillMove: true, + rotation: gunProperties.rotation, + position: this.getGunTipPosition(gunProperties), + velocity: forwardVec, + lifetime: 10 + }; + + Entities.addEntity(properties); + + this.playSoundAtCurrentPosition(gunProperties.position); + }, + + playSoundAtCurrentPosition: function(position) { + var audioProperties = { + volume: 0.1, + position: position + }; + + Audio.playSound(this.SHOOTING_SOUND, audioProperties); + }, + + getGunTipPosition: function(properties) { + //the tip of the gun is going to be in a different place than the center, so we move in space relative to the model to find that position + var frontVector = Quat.getRight(properties.rotation); + var frontOffset = Vec3.multiply(frontVector, GUN_TIP_FWD_OFFSET); + var upVector = Quat.getRight(properties.rotation); + var upOffset = Vec3.multiply(upVector, GUN_TIP_UP_OFFSET); + var gunTipPosition = Vec3.sum(properties.position, frontOffset); + gunTipPosition = Vec3.sum(gunTipPosition, upOffset); + return gunTipPosition; + }, + + preload: function(entityID) { + this.entityID = entityID; + this.SHOOTING_SOUND = SoundCache.getSound(SHOOTING_SOUND_URL); + } + + }; + + // entity scripts always need to return a newly constructed object of our type + return new PingPongGun(); +}); \ No newline at end of file diff --git a/examples/utilities/tools/currentAPI.js b/examples/utilities/tools/currentAPI.js index 30b24910f9..cb9f152794 100644 --- a/examples/utilities/tools/currentAPI.js +++ b/examples/utilities/tools/currentAPI.js @@ -10,22 +10,21 @@ // var array = []; -var buffer = "\n\n\n\n\n======= JS API list ======="; function listKeys(string, object) { - if (string == "listKeys" || string == "array" || string == "buffer" || string == "i") { + if (string === "listKeys" || string === "array" || string === "buffer" || string === "i") { return; } - if (typeof(object) != "object") { + if (typeof(object) !== "object" || object === null) { array.push(string + " " + typeof(object)); return; } var keys = Object.keys(object); for (var i = 0; i < keys.length; ++i) { - if (string == "") { + if (string === "") { listKeys(keys[i], object[keys[i]]); - } else { + } else if (keys[i] !== "parent") { listKeys(string + "." + keys[i], object[keys[i]]); } } @@ -34,9 +33,10 @@ function listKeys(string, object) { listKeys("", this); array.sort(); +var buffer = "\n======= JS API list ======="; for (var i = 0; i < array.length; ++i) { - buffer = buffer + "\n" + array[i]; + buffer += "\n" + array[i]; } -buffer = buffer + "\n========= API END =========\n\n\n\n\n"; +buffer += "\n========= API END =========\n"; print(buffer); diff --git a/interface/resources/meshes/defaultAvatar_full/avatar-animation.json b/interface/resources/meshes/defaultAvatar_full/avatar-animation.json index 72eefaf7e8..dceecddfe0 100644 --- a/interface/resources/meshes/defaultAvatar_full/avatar-animation.json +++ b/interface/resources/meshes/defaultAvatar_full/avatar-animation.json @@ -26,13 +26,14 @@ { "jointName": "Neck", "positionVar": "neckPosition", - "rotationVar": "neckRotation" + "rotationVar": "neckRotation", + "typeVar": "headAndNeckType" }, { "jointName": "Head", "positionVar": "headPosition", "rotationVar": "headRotation", - "typeVar": "headType" + "typeVar": "headAndNeckType" } ] }, diff --git a/interface/resources/qml/VrMenu.qml b/interface/resources/qml/VrMenu.qml index ef7ae852d4..14a4a449fd 100644 --- a/interface/resources/qml/VrMenu.qml +++ b/interface/resources/qml/VrMenu.qml @@ -196,7 +196,6 @@ Hifi.VrMenu { function insertItem(menu, beforeItem, newMenuItem) { for (var i = 0; i < menu.items.length; ++i) { - console.log(menu.items[i]); if (menu.items[i] === beforeItem) { return menu.insertItem(i, newMenuItem); } diff --git a/interface/src/InterfaceActionFactory.cpp b/interface/src/InterfaceActionFactory.cpp index dca1015ecc..2879c19eaa 100644 --- a/interface/src/InterfaceActionFactory.cpp +++ b/interface/src/InterfaceActionFactory.cpp @@ -43,6 +43,9 @@ EntityActionPointer InterfaceActionFactory::factory(EntityActionType type, if (action) { bool ok = action->updateArguments(arguments); if (ok) { + if (action->lifetimeIsOver()) { + return nullptr; + } return action; } } @@ -63,5 +66,9 @@ EntityActionPointer InterfaceActionFactory::factoryBA(EntityItemPointer ownerEnt if (action) { action->deserialize(data); } + if (action->lifetimeIsOver()) { + return nullptr; + } + return action; } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 5793510fb5..96f640b96e 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -446,9 +446,9 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::FixGaze, 0, false); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableAvatarUpdateThreading, 0, false, qApp, SLOT(setAvatarUpdateThreading(bool))); - addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableRigAnimations, 0, true, + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableRigAnimations, 0, false, avatar, SLOT(setEnableRigAnimations(bool))); - addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableAnimGraph, 0, false, + addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableAnimGraph, 0, true, avatar, SLOT(setEnableAnimGraph(bool))); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawBindPose, 0, false, avatar, SLOT(setEnableDebugDrawBindPose(bool))); diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index 4ecdb692ac..1fa50b79fd 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -84,6 +84,9 @@ void AvatarActionHold::updateActionWorker(float deltaTimeStep) { bool AvatarActionHold::updateArguments(QVariantMap arguments) { + if (!ObjectAction::updateArguments(arguments)) { + return false; + } bool ok = true; glm::vec3 relativePosition = EntityActionInterface::extractVec3Argument("hold", arguments, "relativePosition", ok, false); @@ -134,7 +137,7 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) { QVariantMap AvatarActionHold::getArguments() { - QVariantMap arguments; + QVariantMap arguments = ObjectAction::getArguments(); withReadLock([&]{ if (!_mine) { arguments = ObjectActionSpring::getArguments(); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 16964735da..a57740a691 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -153,7 +153,8 @@ void MyAvatar::reset() { // This should be simpler when we have only graph animations always on. bool isRig = _rig->getEnableRig(); - bool isGraph = _rig->getEnableAnimGraph(); + // seting rig animation to true, below, will clear the graph animation menu item, so grab it now. + bool isGraph = _rig->getEnableAnimGraph() || Menu::getInstance()->isOptionChecked(MenuOption::EnableAnimGraph); qApp->setRawAvatarUpdateThreading(false); _rig->disableHands = true; setEnableRigAnimations(true); @@ -270,6 +271,25 @@ glm::mat4 MyAvatar::getSensorToWorldMatrix() const { return _sensorToWorldMatrix; } +// returns true if pos is OUTSIDE of the vertical capsule +// where the middle cylinder length is defined by capsuleLen and the radius by capsuleRad. +static bool capsuleCheck(const glm::vec3& pos, float capsuleLen, float capsuleRad) { + const float halfCapsuleLen = capsuleLen / 2.0f; + if (fabs(pos.y) <= halfCapsuleLen) { + // cylinder check for middle capsule + glm::vec2 horizPos(pos.x, pos.z); + return glm::length(horizPos) > capsuleRad; + } else if (pos.y > halfCapsuleLen) { + glm::vec3 center(0.0f, halfCapsuleLen, 0.0f); + return glm::length(center - pos) > capsuleRad; + } else if (pos.y < halfCapsuleLen) { + glm::vec3 center(0.0f, -halfCapsuleLen, 0.0f); + return glm::length(center - pos) > capsuleRad; + } else { + return false; + } +} + // Pass a recent sample of the HMD to the avatar. // This can also update the avatar's position to follow the HMD // as it moves through the world. @@ -288,11 +308,14 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) { _hmdSensorOrientation = glm::quat_cast(hmdSensorMatrix); const float STRAIGHTING_LEAN_DURATION = 0.5f; // seconds - const float STRAIGHTING_LEAN_THRESHOLD = 0.2f; // meters + + // define a vertical capsule + const float STRAIGHTING_LEAN_CAPSULE_RADIUS = 0.2f; // meters + const float STRAIGHTING_LEAN_CAPSULE_LENGTH = 0.05f; // length of the cylinder part of the capsule in meters. auto newBodySensorMatrix = deriveBodyFromHMDSensor(); glm::vec3 diff = extractTranslation(newBodySensorMatrix) - extractTranslation(_bodySensorMatrix); - if (!_straightingLean && glm::length(diff) > STRAIGHTING_LEAN_THRESHOLD) { + if (!_straightingLean && capsuleCheck(diff, STRAIGHTING_LEAN_CAPSULE_LENGTH, STRAIGHTING_LEAN_CAPSULE_RADIUS)) { // begin homing toward derived body position. _straightingLean = true; @@ -1764,6 +1787,12 @@ void MyAvatar::goToLocation(const glm::vec3& newPosition, } void MyAvatar::updateMotionBehaviorFromMenu() { + + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "updateMotionBehaviorFromMenu"); + return; + } + Menu* menu = Menu::getInstance(); if (menu->isOptionChecked(MenuOption::KeyboardMotorControl)) { _motionBehaviors |= AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED; @@ -1847,13 +1876,39 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const { const glm::quat hmdOrientation = getHMDSensorOrientation(); const glm::quat hmdOrientationYawOnly = cancelOutRollAndPitch(hmdOrientation); - // In sensor space, figure out where the avatar body should be, - // by applying offsets from the avatar's neck & head joints. - vec3 localEyes = _skeletonModel.getDefaultEyeModelPosition(); - vec3 localNeck(0.0f, 0.48f, 0.0f); // start with some kind of guess if the skeletonModel is not loaded yet. - _skeletonModel.getLocalNeckPosition(localNeck); + const glm::vec3 DEFAULT_RIGHT_EYE_POS(-0.3f, 1.6f, 0.0f); + const glm::vec3 DEFAULT_LEFT_EYE_POS(0.3f, 1.6f, 0.0f); + const glm::vec3 DEFAULT_NECK_POS(0.0f, 1.5f, 0.0f); + const glm::vec3 DEFAULT_HIPS_POS(0.0f, 1.0f, 0.0f); + + vec3 localEyes, localNeck; + if (!_debugDrawSkeleton) { + const glm::quat rotY180 = glm::angleAxis((float)PI, glm::vec3(0.0f, 1.0f, 0.0f)); + localEyes = rotY180 * (((DEFAULT_RIGHT_EYE_POS + DEFAULT_LEFT_EYE_POS) / 2.0f) - DEFAULT_HIPS_POS); + localNeck = rotY180 * (DEFAULT_NECK_POS - DEFAULT_HIPS_POS); + } else { + // TODO: At the moment MyAvatar does not have access to the rig, which has the skeleton, which has the bind poses. + // for now use the _debugDrawSkeleton, which is initialized with the same FBX model as the rig. + + // TODO: cache these indices. + int rightEyeIndex = _debugDrawSkeleton->nameToJointIndex("RightEye"); + int leftEyeIndex = _debugDrawSkeleton->nameToJointIndex("LeftEye"); + int neckIndex = _debugDrawSkeleton->nameToJointIndex("Neck"); + int hipsIndex = _debugDrawSkeleton->nameToJointIndex("Hips"); + + glm::vec3 absRightEyePos = rightEyeIndex != -1 ? _debugDrawSkeleton->getAbsoluteBindPose(rightEyeIndex).trans : DEFAULT_RIGHT_EYE_POS; + glm::vec3 absLeftEyePos = leftEyeIndex != -1 ? _debugDrawSkeleton->getAbsoluteBindPose(leftEyeIndex).trans : DEFAULT_LEFT_EYE_POS; + glm::vec3 absNeckPos = neckIndex != -1 ? _debugDrawSkeleton->getAbsoluteBindPose(neckIndex).trans : DEFAULT_NECK_POS; + glm::vec3 absHipsPos = neckIndex != -1 ? _debugDrawSkeleton->getAbsoluteBindPose(hipsIndex).trans : DEFAULT_HIPS_POS; + + const glm::quat rotY180 = glm::angleAxis((float)PI, glm::vec3(0.0f, 1.0f, 0.0f)); + localEyes = rotY180 * (((absRightEyePos + absLeftEyePos) / 2.0f) - absHipsPos); + localNeck = rotY180 * (absNeckPos - absHipsPos); + } // apply simplistic head/neck model + // figure out where the avatar body should be by applying offsets from the avatar's neck & head joints. + // eyeToNeck offset is relative full HMD orientation. // while neckToRoot offset is only relative to HMDs yaw. glm::vec3 eyeToNeck = hmdOrientation * (localNeck - localEyes); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index bbc0667015..6efec3be13 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -193,7 +193,7 @@ public slots: glm::vec3 getThrust() { return _thrust; }; void setThrust(glm::vec3 newThrust) { _thrust = newThrust; } - void updateMotionBehaviorFromMenu(); + Q_INVOKABLE void updateMotionBehaviorFromMenu(); glm::vec3 getLeftPalmPosition(); glm::vec3 getLeftPalmVelocity(); diff --git a/interface/src/scripting/HMDScriptingInterface.cpp b/interface/src/scripting/HMDScriptingInterface.cpp index f65d638ccc..68ac511eaf 100644 --- a/interface/src/scripting/HMDScriptingInterface.cpp +++ b/interface/src/scripting/HMDScriptingInterface.cpp @@ -10,7 +10,7 @@ // #include "HMDScriptingInterface.h" - +#include "display-plugins/DisplayPlugin.h" #include HMDScriptingInterface& HMDScriptingInterface::getInstance() { @@ -53,3 +53,7 @@ QScriptValue HMDScriptingInterface::getHUDLookAtPosition3D(QScriptContext* conte } return QScriptValue::NullValue; } + +float HMDScriptingInterface::getIPD() const { + return Application::getInstance()->getActiveDisplayPlugin()->getIPD(); +} diff --git a/interface/src/scripting/HMDScriptingInterface.h b/interface/src/scripting/HMDScriptingInterface.h index 4bcced1fa2..82b444abaa 100644 --- a/interface/src/scripting/HMDScriptingInterface.h +++ b/interface/src/scripting/HMDScriptingInterface.h @@ -20,6 +20,7 @@ class HMDScriptingInterface : public QObject { Q_OBJECT Q_PROPERTY(bool magnifier READ getMagnifier) Q_PROPERTY(bool active READ isHMDMode) + Q_PROPERTY(float ipd READ getIPD) public: static HMDScriptingInterface& getInstance(); @@ -33,6 +34,7 @@ private: HMDScriptingInterface() {}; bool getMagnifier() const { return Application::getInstance()->getApplicationCompositor().hasMagnifier(); }; bool isHMDMode() const { return Application::getInstance()->isHMDMode(); } + float getIPD() const; bool getHUDLookAtPosition3D(glm::vec3& result) const; diff --git a/interface/src/ui/ApplicationCompositor.cpp b/interface/src/ui/ApplicationCompositor.cpp index 497a94bb86..98c2efc8f3 100644 --- a/interface/src/ui/ApplicationCompositor.cpp +++ b/interface/src/ui/ApplicationCompositor.cpp @@ -313,18 +313,21 @@ void ApplicationCompositor::displayOverlayTextureHmd(RenderArgs* renderArgs, int glm::mat4 overlayXfm; _modelTransform.getMatrix(overlayXfm); - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - for (int i = 0; i < (int)myAvatar->getHand()->getNumPalms(); i++) { - PalmData& palm = myAvatar->getHand()->getPalms()[i]; - if (palm.isActive()) { - glm::vec2 polar = getPolarCoordinates(palm); - // Convert to quaternion - mat4 pointerXfm = glm::mat4_cast(quat(vec3(polar.y, -polar.x, 0.0f))) * glm::translate(mat4(), vec3(0, 0, -1)); - mat4 reticleXfm = overlayXfm * pointerXfm; - reticleXfm = glm::scale(reticleXfm, reticleScale); - batch.setModelTransform(reticleXfm); - // Render reticle at location - geometryCache->renderUnitQuad(batch, glm::vec4(1), _reticleQuad); + // Only render the hand pointers if the HandMouseInput is enabled + if (Menu::getInstance()->isOptionChecked(MenuOption::HandMouseInput)) { + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + for (int i = 0; i < (int)myAvatar->getHand()->getNumPalms(); i++) { + PalmData& palm = myAvatar->getHand()->getPalms()[i]; + if (palm.isActive()) { + glm::vec2 polar = getPolarCoordinates(palm); + // Convert to quaternion + mat4 pointerXfm = glm::mat4_cast(quat(vec3(polar.y, -polar.x, 0.0f))) * glm::translate(mat4(), vec3(0, 0, -1)); + mat4 reticleXfm = overlayXfm * pointerXfm; + reticleXfm = glm::scale(reticleXfm, reticleScale); + batch.setModelTransform(reticleXfm); + // Render reticle at location + geometryCache->renderUnitQuad(batch, glm::vec4(1), _reticleQuad); + } } } diff --git a/libraries/animation/src/AnimInverseKinematics.cpp b/libraries/animation/src/AnimInverseKinematics.cpp index de226092f1..c0adba0ad6 100644 --- a/libraries/animation/src/AnimInverseKinematics.cpp +++ b/libraries/animation/src/AnimInverseKinematics.cpp @@ -89,7 +89,7 @@ static int findRootJointInSkeleton(AnimSkeleton::ConstPointer skeleton, int inde return rootIndex; } -void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std::vector& targets) { +void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std::vector& targets, const AnimPoseVec& underPoses) { // build a list of valid targets from _targetVarVec and animVars _maxTargetIndex = -1; bool removeUnfoundJoints = false; @@ -107,7 +107,7 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std:: } } else { IKTarget target; - AnimPose defaultPose = _skeleton->getAbsolutePose(targetVar.jointIndex, _relativePoses); + AnimPose defaultPose = _skeleton->getAbsolutePose(targetVar.jointIndex, underPoses); target.pose.trans = animVars.lookup(targetVar.positionVar, defaultPose.trans); target.pose.rot = animVars.lookup(targetVar.rotationVar, defaultPose.rot); target.setType(animVars.lookup(targetVar.typeVar, QString(""))); @@ -154,7 +154,6 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector& targets); + void computeTargets(const AnimVariantMap& animVars, std::vector& targets, const AnimPoseVec& underPoses); void solveWithCyclicCoordinateDescent(const std::vector& targets); virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton); diff --git a/libraries/animation/src/AnimStateMachine.cpp b/libraries/animation/src/AnimStateMachine.cpp index 758067343d..5304cefe46 100644 --- a/libraries/animation/src/AnimStateMachine.cpp +++ b/libraries/animation/src/AnimStateMachine.cpp @@ -93,9 +93,9 @@ void AnimStateMachine::switchState(const AnimVariantMap& animVars, State::Pointe const float dt = 0.0f; Triggers triggers; _nextPoses = nextStateNode->evaluate(animVars, dt, triggers); - +#if WANT_DEBUGa qCDebug(animation) << "AnimStateMachine::switchState:" << _currentState->getID() << "->" << desiredState->getID() << "duration =" << duration << "targetFrame =" << desiredState->_interpTarget; - +#endif _currentState = desiredState; } diff --git a/libraries/animation/src/AnimVariant.h b/libraries/animation/src/AnimVariant.h index 700a8b4121..cb886cd369 100644 --- a/libraries/animation/src/AnimVariant.h +++ b/libraries/animation/src/AnimVariant.h @@ -184,6 +184,8 @@ public: case AnimVariant::Type::String: qCDebug(animation) << " " << pair.first << "=" << pair.second.getString(); break; + default: + assert("AnimVariant::Type" == "valid"); } } } diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 0022749d51..5dc81fecfe 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -1026,12 +1026,21 @@ static void computeHeadNeckAnimVars(AnimSkeleton::ConstPointer skeleton, const A const glm::quat hmdOrientation = hmdPose.rot * rotY180; // rotY180 will make z forward not -z // TODO: cache jointIndices + int rightEyeIndex = skeleton->nameToJointIndex("RightEye"); + int leftEyeIndex = skeleton->nameToJointIndex("LeftEye"); + int headIndex = skeleton->nameToJointIndex("Head"); + int neckIndex = skeleton->nameToJointIndex("Neck"); + + const glm::vec3 DEFAULT_RIGHT_EYE_POS(-0.3f, 1.6f, 0.0f); + const glm::vec3 DEFAULT_LEFT_EYE_POS(0.3f, 1.6f, 0.0f); + const glm::vec3 DEFAULT_HEAD_POS(0.0f, 1.55f, 0.0f); + const glm::vec3 DEFAULT_NECK_POS(0.0f, 1.5f, 0.0f); // Use absolute bindPose positions just in case the relBindPose have rotations we don't expect. - glm::vec3 absRightEyePos = skeleton->getAbsoluteBindPose(skeleton->nameToJointIndex("RightEye")).trans; - glm::vec3 absLeftEyePos = skeleton->getAbsoluteBindPose(skeleton->nameToJointIndex("LeftEye")).trans; - glm::vec3 absHeadPos = skeleton->getAbsoluteBindPose(skeleton->nameToJointIndex("Head")).trans; - glm::vec3 absNeckPos = skeleton->getAbsoluteBindPose(skeleton->nameToJointIndex("Neck")).trans; + glm::vec3 absRightEyePos = rightEyeIndex != -1 ? skeleton->getAbsoluteBindPose(rightEyeIndex).trans : DEFAULT_RIGHT_EYE_POS; + glm::vec3 absLeftEyePos = leftEyeIndex != -1 ? skeleton->getAbsoluteBindPose(leftEyeIndex).trans : DEFAULT_LEFT_EYE_POS; + glm::vec3 absHeadPos = headIndex != -1 ? skeleton->getAbsoluteBindPose(headIndex).trans : DEFAULT_HEAD_POS; + glm::vec3 absNeckPos = neckIndex != -1 ? skeleton->getAbsoluteBindPose(neckIndex).trans : DEFAULT_NECK_POS; glm::vec3 absCenterEyePos = (absRightEyePos + absLeftEyePos) / 2.0f; glm::vec3 eyeOffset = absCenterEyePos - absHeadPos; @@ -1079,7 +1088,7 @@ void Rig::updateNeckJoint(int index, const HeadParameters& params) { _animVars.set("headPosition", headPos); _animVars.set("headRotation", headRot); - _animVars.set("headType", QString("RotationAndPosition")); + _animVars.set("headAndNeckType", QString("RotationAndPosition")); _animVars.set("neckPosition", neckPos); _animVars.set("neckRotation", neckRot); @@ -1092,7 +1101,7 @@ void Rig::updateNeckJoint(int index, const HeadParameters& params) { _animVars.unset("headPosition"); _animVars.set("headRotation", realLocalHeadOrientation); - _animVars.set("headType", QString("RotationOnly")); + _animVars.set("headAndNeckType", QString("RotationOnly")); _animVars.unset("neckPosition"); _animVars.unset("neckRotation"); } diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 104b592c1a..f64504d662 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -966,7 +966,8 @@ bool AvatarData::hasIdentityChangedAfterParsing(NLPacket& packet) { QByteArray AvatarData::identityByteArray() { QByteArray identityData; QDataStream identityStream(&identityData, QIODevice::Append); - const QUrl& urlToSend = (_skeletonModelURL == AvatarData::defaultFullAvatarModelUrl()) ? QUrl("") : _skeletonModelURL; + QUrl emptyURL(""); + const QUrl& urlToSend = (_skeletonModelURL == AvatarData::defaultFullAvatarModelUrl()) ? emptyURL : _skeletonModelURL; identityStream << QUuid() << _faceModelURL << urlToSend << _attachmentData << _displayName; @@ -989,8 +990,11 @@ void AvatarData::setFaceModelURL(const QUrl& faceModelURL) { } void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) { - _skeletonModelURL = skeletonModelURL.isEmpty() ? AvatarData::defaultFullAvatarModelUrl() : skeletonModelURL; - + const QUrl& expanded = skeletonModelURL.isEmpty() ? AvatarData::defaultFullAvatarModelUrl() : skeletonModelURL; + if (expanded == _skeletonModelURL) { + return; + } + _skeletonModelURL = expanded; qCDebug(avatars) << "Changing skeleton model for avatar to" << _skeletonModelURL.toString(); updateJointMappings(); diff --git a/libraries/display-plugins/src/display-plugins/DisplayPlugin.h b/libraries/display-plugins/src/display-plugins/DisplayPlugin.h index 86cfabe724..8b9d249bd4 100644 --- a/libraries/display-plugins/src/display-plugins/DisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/DisplayPlugin.h @@ -121,6 +121,8 @@ public: static const glm::mat4 pose; return pose; } + virtual float getIPD() const { return 0.0f; } + virtual void abandonCalibration() {} virtual void resetSensors() {} virtual float devicePixelRatio() { return 1.0; } diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.cpp index fa9d09e392..f2a7b06510 100644 --- a/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.cpp @@ -149,3 +149,11 @@ void OculusBaseDisplayPlugin::deactivate() { void OculusBaseDisplayPlugin::display(GLuint finalTexture, const glm::uvec2& sceneSize) { ++_frameIndex; } + +float OculusBaseDisplayPlugin::getIPD() const { + float result = 0.0f; +#if (OVR_MAJOR_VERSION >= 6) + result = ovr_GetFloat(_hmd, OVR_KEY_IPD, OVR_DEFAULT_IPD); +#endif + return result; +} \ No newline at end of file diff --git a/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.h index 12023db1ae..d879085b8f 100644 --- a/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/oculus/OculusBaseDisplayPlugin.h @@ -31,6 +31,7 @@ public: virtual void resetSensors() override final; virtual glm::mat4 getEyePose(Eye eye) const override final; virtual glm::mat4 getHeadPose() const override final; + virtual float getIPD() const override final; protected: virtual void preRender() override final; diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index c6b7b58538..572b24e99f 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -43,7 +43,7 @@ void RenderablePolyLineEntityItem::createPipeline() { static const int NORMAL_OFFSET = 12; static const int COLOR_OFFSET = 24; static const int TEXTURE_OFFSET = 28; - + auto textureCache = DependencyManager::get(); QString path = PathUtils::resourcesPath() + "images/paintStroke.png"; _texture = textureCache->getImageTexture(path); @@ -52,22 +52,22 @@ void RenderablePolyLineEntityItem::createPipeline() { _format->setAttribute(gpu::Stream::NORMAL, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), NORMAL_OFFSET); _format->setAttribute(gpu::Stream::COLOR, 0, gpu::Element::COLOR_RGBA_32, COLOR_OFFSET); _format->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), TEXTURE_OFFSET); - + auto VS = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(paintStroke_vert))); auto PS = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(paintStroke_frag))); gpu::ShaderPointer program = gpu::ShaderPointer(gpu::Shader::createProgram(VS, PS)); - + gpu::Shader::BindingSet slotBindings; PAINTSTROKE_GPU_SLOT = 0; slotBindings.insert(gpu::Shader::Binding(std::string("paintStrokeTextureBinding"), PAINTSTROKE_GPU_SLOT)); gpu::Shader::makeProgram(*program, slotBindings); - + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); state->setDepthTest(true, true, gpu::LESS_EQUAL); state->setBlendFunction(true, - gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, - gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + _pipeline = gpu::PipelinePointer(gpu::Pipeline::create(program, state)); } void RenderablePolyLineEntityItem::updateGeometry() { @@ -78,30 +78,30 @@ void RenderablePolyLineEntityItem::updateGeometry() { float tailStart = 0.0f; float tailEnd = 0.25f; float tailLength = tailEnd - tailStart; - + float headStart = 0.76f; float headEnd = 1.0f; float headLength = headEnd - headStart; float uCoord, vCoord; - - int numTailStrips = 5; + + int numTailStrips = 5; int numHeadStrips = 10; - int startHeadIndex = _normals.size() - numHeadStrips; - for (int i = 0; i < _normals.size(); i++) { + int startHeadIndex = _vertices.size() / 2 - numHeadStrips; + for (int i = 0; i < _vertices.size() / 2; i++) { uCoord = 0.26f; vCoord = 0.0f; //tail - if(i < numTailStrips) { - uCoord = float(i)/numTailStrips * tailLength + tailStart; + if (i < numTailStrips) { + uCoord = float(i) / numTailStrips * tailLength + tailStart; } - + //head - if( i > startHeadIndex) { - uCoord = float( (i+ 1) - startHeadIndex)/numHeadStrips * headLength + headStart; + if (i > startHeadIndex) { + uCoord = float((i + 1) - startHeadIndex) / numHeadStrips * headLength + headStart; } uv = vec2(uCoord, vCoord); - + _verticesBuffer->append(sizeof(glm::vec3), (const gpu::Byte*)&_vertices.at(vertexIndex)); _verticesBuffer->append(sizeof(glm::vec3), (const gpu::Byte*)&_normals.at(i)); _verticesBuffer->append(sizeof(int), (gpu::Byte*)&_color); @@ -114,32 +114,32 @@ void RenderablePolyLineEntityItem::updateGeometry() { _verticesBuffer->append(sizeof(int), (gpu::Byte*)_color); _verticesBuffer->append(sizeof(glm::vec2), (const gpu::Byte*)&uv); vertexIndex++; - - _numVertices +=2; + + _numVertices += 2; } _pointsChanged = false; - + } void RenderablePolyLineEntityItem::render(RenderArgs* args) { QWriteLocker lock(&_quadReadWriteLock); - if (_points.size() < 2 || _vertices.size() != _normals.size() * 2) { + if (_points.size() < 2 || _normals.size () < 2 || _vertices.size() < 2) { return; } - + if (!_pipeline) { createPipeline(); } - + PerformanceTimer perfTimer("RenderablePolyLineEntityItem::render"); Q_ASSERT(getType() == EntityTypes::PolyLine); - + Q_ASSERT(args->_batch); if (_pointsChanged) { updateGeometry(); } - + gpu::Batch& batch = *args->_batch; Transform transform = Transform(); transform.setTranslation(getPosition()); @@ -151,8 +151,8 @@ void RenderablePolyLineEntityItem::render(RenderArgs* args) { batch.setInputFormat(_format); batch.setInputBuffer(0, _verticesBuffer, 0, _format->getChannels().at(0)._stride); - + batch.draw(gpu::TRIANGLE_STRIP, _numVertices, 0); - + RenderableDebugableEntityItem::render(this, args); }; diff --git a/libraries/entities/src/EntityActionInterface.h b/libraries/entities/src/EntityActionInterface.h index a4f1c8ea15..e61019c98c 100644 --- a/libraries/entities/src/EntityActionInterface.h +++ b/libraries/entities/src/EntityActionInterface.h @@ -46,6 +46,8 @@ public: static EntityActionType actionTypeFromString(QString actionTypeString); static QString actionTypeToString(EntityActionType actionType); + virtual bool lifetimeIsOver() { return false; } + protected: virtual glm::vec3 getPosition() = 0; virtual void setPosition(glm::vec3 position) = 0; diff --git a/libraries/entities/src/PolyLineEntityItem.cpp b/libraries/entities/src/PolyLineEntityItem.cpp index 29a44547ff..a01c2ce17b 100644 --- a/libraries/entities/src/PolyLineEntityItem.cpp +++ b/libraries/entities/src/PolyLineEntityItem.cpp @@ -27,12 +27,12 @@ const int PolyLineEntityItem::MAX_POINTS_PER_LINE = 70; EntityItemPointer PolyLineEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - EntityItemPointer result { new PolyLineEntityItem(entityID, properties) }; + EntityItemPointer result{ new PolyLineEntityItem(entityID, properties) }; return result; } PolyLineEntityItem::PolyLineEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : -EntityItem(entityItemID) , +EntityItem(entityItemID), _lineWidth(DEFAULT_LINE_WIDTH), _pointsChanged(true), _points(QVector(0.0f)), @@ -48,16 +48,16 @@ _strokeWidths(QVector(0.0f)) EntityItemProperties PolyLineEntityItem::getProperties(EntityPropertyFlags desiredProperties) const { QWriteLocker lock(&_quadReadWriteLock); EntityItemProperties properties = EntityItem::getProperties(desiredProperties); // get the properties from our base class - - + + properties._color = getXColor(); properties._colorChanged = false; - + COPY_ENTITY_PROPERTY_TO_PROPERTIES(lineWidth, getLineWidth); COPY_ENTITY_PROPERTY_TO_PROPERTIES(linePoints, getLinePoints); COPY_ENTITY_PROPERTY_TO_PROPERTIES(normals, getNormals); COPY_ENTITY_PROPERTY_TO_PROPERTIES(strokeWidths, getStrokeWidths); - + properties._glowLevel = getGlowLevel(); properties._glowLevelChanged = false; return properties; @@ -67,20 +67,20 @@ bool PolyLineEntityItem::setProperties(const EntityItemProperties& properties) { QWriteLocker lock(&_quadReadWriteLock); bool somethingChanged = false; somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class - + SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); SET_ENTITY_PROPERTY_FROM_PROPERTIES(lineWidth, setLineWidth); SET_ENTITY_PROPERTY_FROM_PROPERTIES(linePoints, setLinePoints); SET_ENTITY_PROPERTY_FROM_PROPERTIES(normals, setNormals); SET_ENTITY_PROPERTY_FROM_PROPERTIES(strokeWidths, setStrokeWidths); - + if (somethingChanged) { bool wantDebug = false; if (wantDebug) { uint64_t now = usecTimestampNow(); int elapsed = now - getLastEdited(); qCDebug(entities) << "PolyLineEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << - "now=" << now << " getLastEdited()=" << getLastEdited(); + "now=" << now << " getLastEdited()=" << getLastEdited(); } setLastEdited(properties._lastEdited); } @@ -93,7 +93,7 @@ bool PolyLineEntityItem::appendPoint(const glm::vec3& point) { return false; } glm::vec3 halfBox = getDimensions() * 0.5f; - if ( (point.x < - halfBox.x || point.x > halfBox.x) || (point.y < -halfBox.y || point.y > halfBox.y) || (point.z < - halfBox.z || point.z > halfBox.z) ) { + if ((point.x < -halfBox.x || point.x > halfBox.x) || (point.y < -halfBox.y || point.y > halfBox.y) || (point.z < -halfBox.z || point.z > halfBox.z)) { qDebug() << "Point is outside entity's bounding box"; return false; } @@ -102,17 +102,17 @@ bool PolyLineEntityItem::appendPoint(const glm::vec3& point) { return true; } -bool PolyLineEntityItem::setStrokeWidths(const QVector& strokeWidths ) { +bool PolyLineEntityItem::setStrokeWidths(const QVector& strokeWidths) { _strokeWidths = strokeWidths; return true; } bool PolyLineEntityItem::setNormals(const QVector& normals) { _normals = normals; - if (_points.size () < 2 || _normals.size() < 2) { + if (_points.size() < 2 || _normals.size() < 2) { return false; } - + int minVectorSize = _normals.size(); if (_points.size() < minVectorSize) { minVectorSize = _points.size(); @@ -123,15 +123,15 @@ bool PolyLineEntityItem::setNormals(const QVector& normals) { _vertices.clear(); glm::vec3 v1, v2, tangent, binormal, point; - - for (int i = 0; i < minVectorSize-1; i++) { + + for (int i = 0; i < minVectorSize - 1; i++) { float width = _strokeWidths.at(i); point = _points.at(i); - - tangent = _points.at(i+1) - point; + + tangent = _points.at(i + 1) - point; glm::vec3 normal = normals.at(i); binormal = glm::normalize(glm::cross(tangent, normal)) * width; - + //This checks to make sure binormal is not a NAN assert(binormal.x == binormal.x); v1 = point + binormal; @@ -139,11 +139,11 @@ bool PolyLineEntityItem::setNormals(const QVector& normals) { _vertices << v1 << v2; } //for last point we can just assume binormals are same since it represents last two vertices of quad - point = _points.at(_points.size() - 1); + point = _points.at(minVectorSize - 1); v1 = point + binormal; v2 = point - binormal; _vertices << v1 << v2; - + return true; } @@ -157,7 +157,7 @@ bool PolyLineEntityItem::setLinePoints(const QVector& points) { //Check to see if points actually changed. If they haven't, return before doing anything else else if (points.size() == _points.size()) { //same number of points, so now compare every point - for (int i = 0; i < points.size(); i++ ) { + for (int i = 0; i < points.size(); i++) { if (points.at(i) != _points.at(i)){ _pointsChanged = true; break; @@ -171,9 +171,9 @@ bool PolyLineEntityItem::setLinePoints(const QVector& points) { for (int i = 0; i < points.size(); i++) { glm::vec3 point = points.at(i); glm::vec3 halfBox = getDimensions() * 0.5f; - if ((point.x < - halfBox.x || point.x > halfBox.x) || + if ((point.x < -halfBox.x || point.x > halfBox.x) || (point.y < -halfBox.y || point.y > halfBox.y) || - (point.z < - halfBox.z || point.z > halfBox.z)) { + (point.z < -halfBox.z || point.z > halfBox.z)) { qDebug() << "Point is outside entity's bounding box"; return false; } @@ -183,18 +183,18 @@ bool PolyLineEntityItem::setLinePoints(const QVector& points) { } int PolyLineEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, - ReadBitstreamToTreeParams& args, - EntityPropertyFlags& propertyFlags, bool overwriteLocalData) { - QWriteLocker lock(&_quadReadWriteLock); + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData) { + QWriteLocker lock(&_quadReadWriteLock); int bytesRead = 0; const unsigned char* dataAt = data; - + READ_ENTITY_PROPERTY(PROP_COLOR, rgbColor, setColor); READ_ENTITY_PROPERTY(PROP_LINE_WIDTH, float, setLineWidth); READ_ENTITY_PROPERTY(PROP_LINE_POINTS, QVector, setLinePoints); - READ_ENTITY_PROPERTY(PROP_NORMALS, QVector, setNormals); + READ_ENTITY_PROPERTY(PROP_NORMALS, QVector, setNormals); READ_ENTITY_PROPERTY(PROP_STROKE_WIDTHS, QVector, setStrokeWidths); - + return bytesRead; } @@ -211,16 +211,16 @@ EntityPropertyFlags PolyLineEntityItem::getEntityProperties(EncodeBitstreamParam } void PolyLineEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, - EntityPropertyFlags& requestedProperties, - EntityPropertyFlags& propertyFlags, - EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, - OctreeElement::AppendState& appendState) const { - + EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const { + QWriteLocker lock(&_quadReadWriteLock); bool successPropertyFits = true; - + APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); APPEND_ENTITY_PROPERTY(PROP_LINE_WIDTH, getLineWidth()); APPEND_ENTITY_PROPERTY(PROP_LINE_POINTS, getLinePoints()); diff --git a/libraries/networking/src/NLPacketList.cpp b/libraries/networking/src/NLPacketList.cpp index 3b115c558b..318fb037a1 100644 --- a/libraries/networking/src/NLPacketList.cpp +++ b/libraries/networking/src/NLPacketList.cpp @@ -23,7 +23,8 @@ std::unique_ptr NLPacketList::create(PacketType packetType, QByteA } std::unique_ptr NLPacketList::fromPacketList(std::unique_ptr packetList) { - auto nlPacketList = std::unique_ptr(new NLPacketList(std::move(*packetList.release()))); nlPacketList->open(ReadOnly); + auto nlPacketList = std::unique_ptr(new NLPacketList(std::move(*packetList.release()))); + nlPacketList->open(ReadOnly); return nlPacketList; } diff --git a/libraries/networking/src/SentPacketHistory.cpp b/libraries/networking/src/SentPacketHistory.cpp index c6eec8eb63..fbb7eff41a 100644 --- a/libraries/networking/src/SentPacketHistory.cpp +++ b/libraries/networking/src/SentPacketHistory.cpp @@ -35,6 +35,7 @@ void SentPacketHistory::packetSent(uint16_t sequenceNumber, const NLPacket& pack } _newestSequenceNumber = sequenceNumber; + QWriteLocker locker(&_packetsLock); _sentPackets.insert(NLPacket::createCopy(packet)); } @@ -48,6 +49,11 @@ const NLPacket* SentPacketHistory::getPacket(uint16_t sequenceNumber) const { if (seqDiff < 0) { seqDiff += UINT16_RANGE; } - - return _sentPackets.get(seqDiff)->get(); + + QReadLocker locker(&_packetsLock); + auto packet = _sentPackets.get(seqDiff); + if (packet) { + return packet->get(); + } + return nullptr; } diff --git a/libraries/networking/src/SentPacketHistory.h b/libraries/networking/src/SentPacketHistory.h index 1808e0020b..72150658e3 100644 --- a/libraries/networking/src/SentPacketHistory.h +++ b/libraries/networking/src/SentPacketHistory.h @@ -12,7 +12,9 @@ #define hifi_SentPacketHistory_h #include -#include + +#include +#include #include "NLPacket.h" #include "RingBufferHistory.h" @@ -29,6 +31,7 @@ public: const NLPacket* getPacket(uint16_t sequenceNumber) const; private: + mutable QReadWriteLock _packetsLock { QReadWriteLock::Recursive }; RingBufferHistory> _sentPackets; // circular buffer uint16_t _newestSequenceNumber; diff --git a/libraries/networking/src/ThreadedAssignment.cpp b/libraries/networking/src/ThreadedAssignment.cpp index 5f0db9412c..0422c03297 100644 --- a/libraries/networking/src/ThreadedAssignment.cpp +++ b/libraries/networking/src/ThreadedAssignment.cpp @@ -49,7 +49,8 @@ void ThreadedAssignment::setFinished(bool isFinished) { } if (_statsTimer) { - _statsTimer->stop(); + _statsTimer->deleteLater(); + _statsTimer = nullptr; } // call our virtual aboutToFinish method - this gives the ThreadedAssignment subclass a chance to cleanup @@ -105,7 +106,7 @@ void ThreadedAssignment::sendStatsPacket() { void ThreadedAssignment::startSendingStats() { // send the stats packet every 1s if (!_statsTimer) { - _statsTimer = new QTimer(); + _statsTimer = new QTimer; connect(_statsTimer, &QTimer::timeout, this, &ThreadedAssignment::sendStatsPacket); } diff --git a/libraries/networking/src/udt/CongestionControl.cpp b/libraries/networking/src/udt/CongestionControl.cpp index ea46d60acb..c1feae3911 100644 --- a/libraries/networking/src/udt/CongestionControl.cpp +++ b/libraries/networking/src/udt/CongestionControl.cpp @@ -161,13 +161,16 @@ void DefaultCC::onLoss(SequenceNumber rangeStart, SequenceNumber rangeEnd) { _lastDecreaseMaxSeq = _sendCurrSeqNum; - // avoid synchronous rate decrease across connections using randomization - std::random_device rd; - std::mt19937 generator(rd()); - std::uniform_int_distribution<> distribution(1, _avgNAKNum); - - _randomDecreaseThreshold = distribution(generator); - + if (_avgNAKNum < 1) { + _randomDecreaseThreshold = 1; + } else { + // avoid synchronous rate decrease across connections using randomization + std::random_device rd; + std::mt19937 generator(rd()); + std::uniform_int_distribution<> distribution(1, std::max(1, _avgNAKNum)); + + _randomDecreaseThreshold = distribution(generator); + } } else if ((_decreaseCount++ < MAX_DECREASES_PER_CONGESTION_EPOCH) && ((++_nakCount % _randomDecreaseThreshold) == 0)) { // there have been fewer than MAX_DECREASES_PER_CONGESTION_EPOCH AND this NAK matches the random count at which we // decided we would decrease the packet send period diff --git a/libraries/networking/src/udt/Connection.cpp b/libraries/networking/src/udt/Connection.cpp index 1bda840a6c..e8b22f6ab8 100644 --- a/libraries/networking/src/udt/Connection.cpp +++ b/libraries/networking/src/udt/Connection.cpp @@ -32,7 +32,7 @@ Connection::Connection(Socket* parentSocket, HifiSockAddr destination, std::uniq _destination(destination), _congestionControl(move(congestionControl)) { - Q_ASSERT_X(socket, "Connection::Connection", "Must be called with a valid Socket*"); + Q_ASSERT_X(parentSocket, "Connection::Connection", "Must be called with a valid Socket*"); Q_ASSERT_X(_congestionControl, "Connection::Connection", "Must be called with a valid CongestionControl object"); _congestionControl->init(); diff --git a/libraries/networking/src/udt/PacketList.h b/libraries/networking/src/udt/PacketList.h index ae783dabe3..7978e77ad7 100644 --- a/libraries/networking/src/udt/PacketList.h +++ b/libraries/networking/src/udt/PacketList.h @@ -28,7 +28,8 @@ class Packet; class PacketList : public QIODevice { Q_OBJECT public: - static std::unique_ptr create(PacketType packetType, QByteArray extendedHeader = QByteArray(), bool isReliable = false, bool isOrdered = false); + static std::unique_ptr create(PacketType packetType, QByteArray extendedHeader = QByteArray(), + bool isReliable = false, bool isOrdered = false); static std::unique_ptr fromReceivedPackets(std::list>&& packets); bool isReliable() const { return _isReliable; } diff --git a/libraries/octree/src/OctreeEditPacketSender.cpp b/libraries/octree/src/OctreeEditPacketSender.cpp index 1be271cbdd..495effc825 100644 --- a/libraries/octree/src/OctreeEditPacketSender.cpp +++ b/libraries/octree/src/OctreeEditPacketSender.cpp @@ -346,6 +346,7 @@ void OctreeEditPacketSender::processNackPacket(NLPacket& packet, SharedNodePoint if (_sentPacketHistories.count(sendingNode->getUUID()) == 0) { return; } + const SentPacketHistory& sentPacketHistory = _sentPacketHistories[sendingNode->getUUID()]; // read sequence numbers and queue packets for resend diff --git a/libraries/physics/src/ObjectAction.cpp b/libraries/physics/src/ObjectAction.cpp index 5205e08c62..82395c21cf 100644 --- a/libraries/physics/src/ObjectAction.cpp +++ b/libraries/physics/src/ObjectAction.cpp @@ -30,6 +30,18 @@ void ObjectAction::updateAction(btCollisionWorld* collisionWorld, btScalar delta dynamicsWorld->removeAction(this); return; } + + if (_expires > 0) { + quint64 now = usecTimestampNow(); + if (now > _expires) { + EntityItemPointer ownerEntity = _ownerEntity.lock(); + _active = false; + if (ownerEntity) { + ownerEntity->removeAction(nullptr, getID()); + } + } + } + if (!_active) { return; } @@ -37,6 +49,40 @@ void ObjectAction::updateAction(btCollisionWorld* collisionWorld, btScalar delta updateActionWorker(deltaTimeStep); } +bool ObjectAction::updateArguments(QVariantMap arguments) { + bool lifetimeSet = true; + float lifetime = EntityActionInterface::extractFloatArgument("action", arguments, "lifetime", lifetimeSet, false); + if (lifetimeSet) { + quint64 now = usecTimestampNow(); + _expires = now + (quint64)(lifetime * USECS_PER_SECOND); + } else { + _expires = 0; + } + + bool tagSet = true; + QString tag = EntityActionInterface::extractStringArgument("action", arguments, "tag", tagSet, false); + if (tagSet) { + _tag = tag; + } else { + tag = ""; + } + + return true; +} + +QVariantMap ObjectAction::getArguments() { + QVariantMap arguments; + if (_expires == 0) { + arguments["lifetime"] = 0.0f; + } else { + quint64 now = usecTimestampNow(); + arguments["lifetime"] = (float)(_expires - now) / (float)USECS_PER_SECOND; + } + arguments["tag"] = _tag; + return arguments; +} + + void ObjectAction::debugDraw(btIDebugDraw* debugDrawer) { } @@ -136,3 +182,14 @@ void ObjectAction::activateBody() { } } +bool ObjectAction::lifetimeIsOver() { + if (_expires == 0) { + return false; + } + + quint64 now = usecTimestampNow(); + if (now >= _expires) { + return true; + } + return false; +} diff --git a/libraries/physics/src/ObjectAction.h b/libraries/physics/src/ObjectAction.h index 4d91d0dbb1..5c29ac9892 100644 --- a/libraries/physics/src/ObjectAction.h +++ b/libraries/physics/src/ObjectAction.h @@ -33,8 +33,8 @@ public: virtual EntityItemWeakPointer getOwnerEntity() const { return _ownerEntity; } virtual void setOwnerEntity(const EntityItemPointer ownerEntity) { _ownerEntity = ownerEntity; } - virtual bool updateArguments(QVariantMap arguments) = 0; - virtual QVariantMap getArguments() = 0; + virtual bool updateArguments(QVariantMap arguments); + virtual QVariantMap getArguments(); // this is called from updateAction and should be overridden by subclasses virtual void updateActionWorker(float deltaTimeStep) = 0; @@ -46,6 +46,8 @@ public: virtual QByteArray serialize() const = 0; virtual void deserialize(QByteArray serializedArguments) = 0; + virtual bool lifetimeIsOver(); + protected: virtual btRigidBody* getRigidBody(); @@ -59,11 +61,11 @@ protected: virtual void setAngularVelocity(glm::vec3 angularVelocity); virtual void activateBody(); -private: - -protected: bool _active; EntityItemWeakPointer _ownerEntity; + + quint64 _expires; // in seconds since epoch + QString _tag; }; #endif // hifi_ObjectAction_h diff --git a/libraries/physics/src/ObjectActionOffset.cpp b/libraries/physics/src/ObjectActionOffset.cpp index 2c1b391ba5..448ec34689 100644 --- a/libraries/physics/src/ObjectActionOffset.cpp +++ b/libraries/physics/src/ObjectActionOffset.cpp @@ -80,6 +80,9 @@ void ObjectActionOffset::updateActionWorker(btScalar deltaTimeStep) { bool ObjectActionOffset::updateArguments(QVariantMap arguments) { + if (!ObjectAction::updateArguments(arguments)) { + return false; + } bool ok = true; glm::vec3 pointToOffsetFrom = EntityActionInterface::extractVec3Argument("offset action", arguments, "pointToOffsetFrom", ok, true); @@ -90,7 +93,7 @@ bool ObjectActionOffset::updateArguments(QVariantMap arguments) { ok = true; float linearTimeScale = EntityActionInterface::extractFloatArgument("offset action", arguments, "linearTimeScale", ok, false); - if (!ok) { + if (!ok) { linearTimeScale = _linearTimeScale; } @@ -119,7 +122,7 @@ bool ObjectActionOffset::updateArguments(QVariantMap arguments) { } QVariantMap ObjectActionOffset::getArguments() { - QVariantMap arguments; + QVariantMap arguments = ObjectAction::getArguments(); withReadLock([&] { arguments["pointToOffsetFrom"] = glmToQMap(_pointToOffsetFrom); arguments["linearTimeScale"] = _linearTimeScale; @@ -140,6 +143,9 @@ QByteArray ObjectActionOffset::serialize() const { dataStream << _linearTimeScale; dataStream << _positionalTargetSet; + dataStream << _expires; + dataStream << _tag; + return ba; } @@ -165,5 +171,8 @@ void ObjectActionOffset::deserialize(QByteArray serializedArguments) { dataStream >> _linearTimeScale; dataStream >> _positionalTargetSet; + dataStream >> _expires; + dataStream >> _tag; + _active = true; } diff --git a/libraries/physics/src/ObjectActionSpring.cpp b/libraries/physics/src/ObjectActionSpring.cpp index d163933299..7d0bab5143 100644 --- a/libraries/physics/src/ObjectActionSpring.cpp +++ b/libraries/physics/src/ObjectActionSpring.cpp @@ -109,6 +109,9 @@ void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) { const float MIN_TIMESCALE = 0.1f; bool ObjectActionSpring::updateArguments(QVariantMap arguments) { + if (!ObjectAction::updateArguments(arguments)) { + return false; + } // targets are required, spring-constants are optional bool ok = true; glm::vec3 positionalTarget = @@ -155,7 +158,7 @@ bool ObjectActionSpring::updateArguments(QVariantMap arguments) { } QVariantMap ObjectActionSpring::getArguments() { - QVariantMap arguments; + QVariantMap arguments = ObjectAction::getArguments(); withReadLock([&] { arguments["linearTimeScale"] = _linearTimeScale; arguments["targetPosition"] = glmToQMap(_positionalTarget); @@ -182,6 +185,9 @@ QByteArray ObjectActionSpring::serialize() const { dataStream << _angularTimeScale; dataStream << _rotationalTargetSet; + dataStream << _expires; + dataStream << _tag; + return serializedActionArguments; } @@ -210,5 +216,8 @@ void ObjectActionSpring::deserialize(QByteArray serializedArguments) { dataStream >> _angularTimeScale; dataStream >> _rotationalTargetSet; + dataStream >> _expires; + dataStream >> _tag; + _active = true; } diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index a9d83aa9d6..dbcbe3c05e 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -442,7 +442,7 @@ void DeferredLightingEffect::render(RenderArgs* args) { deferredTransforms[i].projection = projMats[i]; - auto sideViewMat = eyeViews[i] * monoViewMat; + auto sideViewMat = monoViewMat * glm::inverse(eyeViews[i]); viewTransforms[i].evalFromRawMatrix(sideViewMat); deferredTransforms[i].viewInverse = sideViewMat; @@ -574,10 +574,8 @@ void DeferredLightingEffect::render(RenderArgs* args) { for (auto lightID : _pointLights) { auto& light = _allocatedLights[lightID]; - // IN DEBUG: light->setShowContour(true); - if (_pointLightLocations->lightBufferUnit >= 0) { - batch.setUniformBuffer(_pointLightLocations->lightBufferUnit, light->getSchemaBuffer()); - } + // IN DEBUG: light->setShowContour(true); + batch.setUniformBuffer(_pointLightLocations->lightBufferUnit, light->getSchemaBuffer()); float expandedRadius = light->getMaximumRadius() * (1.0f + SCALE_EXPANSION); // TODO: We shouldn;t have to do that test and use a different volume geometry for when inside the vlight volume, @@ -612,8 +610,7 @@ void DeferredLightingEffect::render(RenderArgs* args) { for (auto lightID : _spotLights) { auto light = _allocatedLights[lightID]; - // IN DEBUG: light->setShowContour(true); - + // IN DEBUG: light->setShowContour(true); batch.setUniformBuffer(_spotLightLocations->lightBufferUnit, light->getSchemaBuffer()); auto eyeLightPos = eyePoint - light->getPosition(); diff --git a/libraries/render-utils/src/simple.slf b/libraries/render-utils/src/simple.slf index 5901a72838..576acf9340 100644 --- a/libraries/render-utils/src/simple.slf +++ b/libraries/render-utils/src/simple.slf @@ -17,6 +17,7 @@ // the interpolated normal in vec3 _normal; +in vec3 _modelNormal; in vec3 _color; in vec2 _texCoord0; in vec4 _position; diff --git a/libraries/render-utils/src/simple.slv b/libraries/render-utils/src/simple.slv index e7fed4a6b4..823654ec27 100644 --- a/libraries/render-utils/src/simple.slv +++ b/libraries/render-utils/src/simple.slv @@ -22,6 +22,7 @@ uniform bool Instanced = false; // the interpolated normal out vec3 _normal; +out vec3 _modelNormal; out vec3 _color; out vec2 _texCoord0; out vec4 _position; @@ -30,6 +31,7 @@ void main(void) { _color = inColor.rgb; _texCoord0 = inTexCoord0.st; _position = inPosition; + _modelNormal = inNormal.xyz; // standard transform TransformCamera cam = getTransformCamera();