From a1110bf28dda82eaff9454b9e7d947cfe504decc Mon Sep 17 00:00:00 2001 From: Daniela Date: Thu, 26 Oct 2017 16:52:58 +0100 Subject: [PATCH 01/47] Rotations now happen using the correct axis taking into account the avatar referential --- scripts/system/libraries/entitySelectionTool.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 3a422bcb8a..d5cc46586a 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -1346,6 +1346,11 @@ SelectionDisplay = (function() { } } + // place yaw, pitch and roll rotations on the avatar referential + yawHandleRotation = Quat.multiply(MyAvatar.orientation , yawHandleRotation); + pitchHandleRotation = Quat.multiply(MyAvatar.orientation , pitchHandleRotation); + rollHandleRotation = Quat.multiply(MyAvatar.orientation , rollHandleRotation); + var rotateHandlesVisible = true; var rotationOverlaysVisible = false; // note: Commented out as these are currently unused here; however, @@ -3498,6 +3503,8 @@ SelectionDisplay = (function() { initialPosition = SelectionManager.worldPosition; rotationNormal = { x: 0, y: 0, z: 0 }; rotationNormal[rotAroundAxis] = 1; + //get the correct axis according to the avatar referencial + rotationNormal = Vec3.multiplyQbyV(MyAvatar.orientation, rotationNormal); // Size the overlays to the current selection size var diagonal = (Vec3.length(SelectionManager.worldDimensions) / 2) * 1.1; @@ -3599,9 +3606,8 @@ SelectionDisplay = (function() { var snapAngle = snapToInner ? innerSnapAngle : 1.0; angleFromZero = Math.floor(angleFromZero / snapAngle) * snapAngle; - var vec3Degrees = { x: 0, y: 0, z: 0 }; - vec3Degrees[rotAroundAxis] = angleFromZero; - var rotChange = Quat.fromVec3Degrees(vec3Degrees); + + var rotChange = Quat.angleAxis(angleFromZero, rotationNormal); updateSelectionsRotation(rotChange); updateRotationDegreesOverlay(angleFromZero, handleRotation, rotCenter); From 2ceba76ba2cac92ad16293889dc86be8188abae1 Mon Sep 17 00:00:00 2001 From: Daniela Date: Fri, 27 Oct 2017 11:28:44 +0100 Subject: [PATCH 02/47] yaw tool --- .../system/libraries/entitySelectionTool.js | 41 ++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index d5cc46586a..3408b63e7a 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -1099,6 +1099,8 @@ SelectionDisplay = (function() { ------------------------------*/ var cameraPosition = Camera.getPosition(); + var look = Vec3.normalize(Vec3.subtract(cameraPosition, objectCenter)); + if (cameraPosition.x > objectCenter.x) { // must be BRF or BRN if (cameraPosition.z < objectCenter.z) { @@ -1350,6 +1352,38 @@ SelectionDisplay = (function() { yawHandleRotation = Quat.multiply(MyAvatar.orientation , yawHandleRotation); pitchHandleRotation = Quat.multiply(MyAvatar.orientation , pitchHandleRotation); rollHandleRotation = Quat.multiply(MyAvatar.orientation , rollHandleRotation); + var avatarReferential = Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({ + x: 0, + y: 180, + z: 0 + })); + var upVector = Quat.getUp(avatarReferential); + var rightVector = Quat.getRight(avatarReferential); + var frontVector = Quat.getFront(avatarReferential); + // Centers + var xSign = -1.0; + var zSign = -1.0; + if (Vec3.dot(look, rightVector) > 0) { + xSign = 1.0; + } + if (Vec3.dot(look, frontVector) > 0) { + zSign = 1.0; + } + + yawCenter = Vec3.sum(boundsCenter, Vec3.multiply(-(dimensions.y / 2), upVector)); + var myBotom = Vec3.multiply(-(dimensions.y / 2) - rotateHandleOffset, upVector); + var myRight = Vec3.multiply(xSign * (dimensions.x / 2) + xSign * rotateHandleOffset, rightVector); + var myFront = Vec3.multiply(zSign * (dimensions.z / 2) + zSign * rotateHandleOffset, frontVector); + yawCorner = Vec3.sum(boundsCenter, Vec3.sum(Vec3.sum(myBotom, myRight), myFront)); + + + yawHandleRotation = Quat.lookAt(yawCorner, Vec3.sum(yawCorner, upVector), Vec3.subtract(yawCenter,yawCorner)); + yawHandleRotation = Quat.multiply(Quat.angleAxis(45, upVector), yawHandleRotation); + + //Quat.fromPitchYawRollDegrees(0,270,0) + pitchCenter = Vec3.sum(boundsCenter, Vec3.multiply(xSign * (dimensions.x / 2), rightVector)); + rollCenter = Vec3.sum(boundsCenter, Vec3.multiply(zSign * (dimensions.z / 2), frontVector)); + var rotateHandlesVisible = true; var rotationOverlaysVisible = false; @@ -3504,7 +3538,12 @@ SelectionDisplay = (function() { rotationNormal = { x: 0, y: 0, z: 0 }; rotationNormal[rotAroundAxis] = 1; //get the correct axis according to the avatar referencial - rotationNormal = Vec3.multiplyQbyV(MyAvatar.orientation, rotationNormal); + var avatarReferential = Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({ + x: 0, + y: 180, + z: 0 + })); + rotationNormal = Vec3.multiplyQbyV(avatarReferential, rotationNormal); // Size the overlays to the current selection size var diagonal = (Vec3.length(SelectionManager.worldDimensions) / 2) * 1.1; From e38b0ab6b82b96cd2c6a85172634ee60feb68e9c Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 9 Nov 2017 17:09:45 -0800 Subject: [PATCH 03/47] make web overlays match web entities more, remove resolution --- interface/src/ui/overlays/Web3DOverlay.cpp | 66 ++++++++++----------- interface/src/ui/overlays/Web3DOverlay.h | 5 +- scripts/system/libraries/WebTablet.js | 24 +++++--- scripts/system/libraries/touchEventUtils.js | 30 ++-------- scripts/system/libraries/utils.js | 13 ++-- 5 files changed, 61 insertions(+), 77 deletions(-) diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index d418a79fbf..f096c50a03 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -56,14 +56,15 @@ #include "ui/Snapshot.h" #include "SoundCache.h" -static const float DPI = 30.47f; +static int MAX_WINDOW_SIZE = 4096; + static const float INCHES_TO_METERS = 1.0f / 39.3701f; static const float METERS_TO_INCHES = 39.3701f; static const float OPAQUE_ALPHA_THRESHOLD = 0.99f; const QString Web3DOverlay::TYPE = "web3d"; const QString Web3DOverlay::QML = "Web3DOverlay.qml"; -Web3DOverlay::Web3DOverlay() : _dpi(DPI) { +Web3DOverlay::Web3DOverlay() { _touchDevice.setCapabilities(QTouchDevice::Position); _touchDevice.setType(QTouchDevice::TouchScreen); _touchDevice.setName("RenderableWebEntityItemTouchDevice"); @@ -80,7 +81,6 @@ Web3DOverlay::Web3DOverlay(const Web3DOverlay* Web3DOverlay) : _url(Web3DOverlay->_url), _scriptURL(Web3DOverlay->_scriptURL), _dpi(Web3DOverlay->_dpi), - _resolution(Web3DOverlay->_resolution), _showKeyboardFocusHighlight(Web3DOverlay->_showKeyboardFocusHighlight) { _geometryId = DependencyManager::get()->allocateID(); @@ -152,7 +152,7 @@ void Web3DOverlay::buildWebSurface() { setupQmlSurface(); } _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(getPosition())); - _webSurface->resize(QSize(_resolution.x, _resolution.y)); + onResizeWebSurface(); _webSurface->resume(); }); @@ -249,8 +249,16 @@ void Web3DOverlay::setMaxFPS(uint8_t maxFPS) { } void Web3DOverlay::onResizeWebSurface() { - _mayNeedResize = false; - _webSurface->resize(QSize(_resolution.x, _resolution.y)); + glm::vec2 dims = glm::vec2(getDimensions()); + dims *= METERS_TO_INCHES * _dpi; + + // ensure no side is never larger then MAX_WINDOW_SIZE + float max = (dims.x > dims.y) ? dims.x : dims.y; + if (max > MAX_WINDOW_SIZE) { + dims *= MAX_WINDOW_SIZE / max; + } + + _webSurface->resize(QSize(dims.x, dims.y)); } const int INVALID_DEVICE_ID = -1; @@ -277,14 +285,14 @@ void Web3DOverlay::render(RenderArgs* args) { return; } - if (_currentMaxFPS != _desiredMaxFPS) { - setMaxFPS(_desiredMaxFPS); - } - if (_mayNeedResize) { emit resizeWebSurface(); } + if (_currentMaxFPS != _desiredMaxFPS) { + setMaxFPS(_desiredMaxFPS); + } + vec4 color(toGlm(getColor()), getAlpha()); if (!_texture) { @@ -321,7 +329,7 @@ void Web3DOverlay::render(RenderArgs* args) { Transform Web3DOverlay::evalRenderTransform() { Transform transform = Parent::evalRenderTransform(); transform.setScale(1.0f); - transform.postScale(glm::vec3(getSize(), 1.0f)); + transform.postScale(glm::vec3(getDimensions(), 1.0f)); return transform; } @@ -520,18 +528,10 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) { } } - auto resolution = properties["resolution"]; - if (resolution.isValid()) { - bool valid; - auto res = vec2FromVariant(resolution, valid); - if (valid) { - _resolution = res; - } - } - auto dpi = properties["dpi"]; if (dpi.isValid()) { _dpi = dpi.toFloat(); + _mayNeedResize = true; } auto maxFPS = properties["maxFPS"]; @@ -553,8 +553,6 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) { _inputMode = Touch; } } - - _mayNeedResize = true; } QVariant Web3DOverlay::getProperty(const QString& property) { @@ -564,9 +562,6 @@ QVariant Web3DOverlay::getProperty(const QString& property) { if (property == "scriptURL") { return _scriptURL; } - if (property == "resolution") { - return vec2toVariant(_resolution); - } if (property == "dpi") { return _dpi; } @@ -622,17 +617,18 @@ void Web3DOverlay::setScriptURL(const QString& scriptURL) { } } -glm::vec2 Web3DOverlay::getSize() const { - return _resolution / _dpi * INCHES_TO_METERS * getDimensions(); -}; - bool Web3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal) { - // FIXME - face and surfaceNormal not being returned + glm::vec2 dimensions = getDimensions(); + glm::quat rotation = getRotation(); + glm::vec3 position = getPosition(); - // Don't call applyTransformTo() or setTransform() here because this code runs too frequently. - - // Produce the dimensions of the overlay based on the image's aspect ratio and the overlay's scale. - return findRayRectangleIntersection(origin, direction, getRotation(), getPosition(), getSize(), distance); + if (findRayRectangleIntersection(origin, direction, rotation, position, dimensions, distance)) { + surfaceNormal = rotation * Vectors::UNIT_Z; + face = glm::dot(surfaceNormal, direction) > 0 ? MIN_Z_FACE : MAX_Z_FACE; + return true; + } else { + return false; + } } Web3DOverlay* Web3DOverlay::createClone() const { @@ -641,4 +637,4 @@ Web3DOverlay* Web3DOverlay::createClone() const { void Web3DOverlay::emitScriptEvent(const QVariant& message) { QMetaObject::invokeMethod(this, "scriptEventReceived", Q_ARG(QVariant, message)); -} +} \ No newline at end of file diff --git a/interface/src/ui/overlays/Web3DOverlay.h b/interface/src/ui/overlays/Web3DOverlay.h index 2fc63df76a..e138344877 100644 --- a/interface/src/ui/overlays/Web3DOverlay.h +++ b/interface/src/ui/overlays/Web3DOverlay.h @@ -51,8 +51,6 @@ public: void setProperties(const QVariantMap& properties) override; QVariant getProperty(const QString& property) override; - glm::vec2 getSize() const override; - virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal) override; @@ -92,8 +90,7 @@ private: gpu::TexturePointer _texture; QString _url; QString _scriptURL; - float _dpi; - vec2 _resolution{ 640, 480 }; + float _dpi { 30 }; int _geometryId { 0 }; bool _showKeyboardFocusHighlight{ true }; diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index c46cfaa073..4b521f531b 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -118,15 +118,16 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { Overlays.deleteOverlay(this.webOverlayID); } - var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2) * (1 / sensorScaleFactor); - var WEB_ENTITY_Y_OFFSET = 0.004 * (1 / sensorScaleFactor); - + var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.0) / sensorScaleFactor; + var WEB_ENTITY_Y_OFFSET = 0.004; + var screenWidth = 0.82 * tabletWidth; + var screenHeight = 0.81 * tabletHeight; this.webOverlayID = Overlays.addOverlay("web3d", { name: "WebTablet Web", url: url, localPosition: { x: 0, y: WEB_ENTITY_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, localRotation: Quat.angleAxis(180, Y_AXIS), - resolution: this.getTabletTextureResolution(), + dimensions: {x: screenWidth, y: screenHeight, z: 0.1}, dpi: tabletDpi, color: { red: 255, green: 255, blue: 255 }, alpha: 1.0, @@ -139,7 +140,7 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { var HOME_BUTTON_Y_OFFSET = ((tabletHeight / 2) - (tabletHeight / 20)) * (1 / sensorScaleFactor); this.homeButtonID = Overlays.addOverlay("sphere", { name: "homeButton", - localPosition: {x: -0.001, y: -HOME_BUTTON_Y_OFFSET, z: 0.0}, + localPosition: {x: 0.0, y: -HOME_BUTTON_Y_OFFSET, z: 0.0}, localRotation: {x: 0, y: 1, z: 0, w: 0}, dimensions: { x: 4 * tabletScaleFactor, y: 4 * tabletScaleFactor, z: 4 * tabletScaleFactor}, alpha: 0.0, @@ -266,11 +267,16 @@ WebTablet.prototype.setLandscape = function(newLandscapeValue) { this.landscape = newLandscapeValue; Overlays.editOverlay(this.tabletEntityID, - { rotation: this.landscape ? Quat.multiply(Camera.orientation, ROT_LANDSCAPE) : - Quat.multiply(Camera.orientation, ROT_Y_180) }); + { rotation: Quat.multiply(Camera.orientation, this.landscape ? ROT_LANDSCAPE : ROT_Y_180) }); + + var tabletWidth = getTabletWidthFromSettings() * MyAvatar.sensorToWorldScale; + var tabletScaleFactor = tabletWidth / TABLET_NATURAL_DIMENSIONS.x; + var tabletHeight = TABLET_NATURAL_DIMENSIONS.y * tabletScaleFactor; + var screenWidth = 0.82 * tabletWidth; + var screenHeight = 0.81 * tabletHeight; Overlays.editOverlay(this.webOverlayID, { - resolution: this.getTabletTextureResolution(), - rotation: Quat.multiply(Camera.orientation, ROT_LANDSCAPE_WINDOW) + rotation: Quat.multiply(Camera.orientation, ROT_LANDSCAPE_WINDOW), + dimensions: {x: this.landscape ? screenHeight : screenWidth, y: this.landscape ? screenWidth : screenHeight, z: 0.1} }); }; diff --git a/scripts/system/libraries/touchEventUtils.js b/scripts/system/libraries/touchEventUtils.js index 3c76c4c144..53ac4f49d1 100644 --- a/scripts/system/libraries/touchEventUtils.js +++ b/scripts/system/libraries/touchEventUtils.js @@ -169,31 +169,13 @@ function calculateTouchTargetFromOverlay(touchTip, overlayID) { // calclulate normalized position var invRot = Quat.inverse(overlayRotation); var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(position, overlayPosition)); - var dpi = Overlays.getProperty(overlayID, "dpi"); - var dimensions; - if (dpi) { - // Calculate physical dimensions for web3d overlay from resolution and dpi; "dimensions" property - // is used as a scale. - var resolution = Overlays.getProperty(overlayID, "resolution"); - if (resolution === undefined) { - return; - } - resolution.z = 1; // Circumvent divide-by-zero. - var scale = Overlays.getProperty(overlayID, "dimensions"); - if (scale === undefined) { - return; - } - scale.z = 0.01; // overlay dimensions are 2D, not 3D. - dimensions = Vec3.multiplyVbyV(Vec3.multiply(resolution, INCHES_TO_METERS / dpi), scale); - } else { - dimensions = Overlays.getProperty(overlayID, "dimensions"); - if (dimensions === undefined) { - return; - } - if (!dimensions.z) { - dimensions.z = 0.01; // sometimes overlay dimensions are 2D, not 3D. - } + var dimensions = Overlays.getProperty(overlayID, "dimensions"); + if (dimensions === undefined) { + return; + } + if (!dimensions.z) { + dimensions.z = 0.01; // sometimes overlay dimensions are 2D, not 3D. } var invDimensions = { x: 1 / dimensions.x, y: 1 / dimensions.y, z: 1 / dimensions.z }; var normalizedPosition = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), DEFAULT_REGISTRATION_POINT); diff --git a/scripts/system/libraries/utils.js b/scripts/system/libraries/utils.js index 76c248d880..f03f286988 100644 --- a/scripts/system/libraries/utils.js +++ b/scripts/system/libraries/utils.js @@ -185,7 +185,7 @@ logTrace = function(str) { // (the vector that would move the point outside the sphere) // otherwise returns false findSphereHit = function(point, sphereRadius) { - var EPSILON = 0.000001; //smallish positive number - used as margin of error for some computations + var EPSILON = 0.000001; //smallish positive number - used as margin of error for some computations var vectorLength = Vec3.length(point); if (vectorLength < EPSILON) { return true; @@ -400,22 +400,25 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride) }); // update webOverlay - var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2) * sensorScaleOffsetOverride; - var WEB_ENTITY_Y_OFFSET = 0.004 * sensorScaleOffsetOverride; + var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.0) * sensorScaleOffsetOverride; + var WEB_ENTITY_Y_OFFSET = 0.004 * sensorScaleFactor * sensorScaleOffsetOverride; + var screenWidth = 0.82 * tabletWidth; + var screenHeight = 0.81 * tabletHeight; Overlays.editOverlay(HMD.tabletScreenID, { localPosition: { x: 0, y: WEB_ENTITY_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, + dimensions: {x: screenWidth, y: screenHeight, z: 0.1}, dpi: tabletDpi }); // update homeButton var HOME_BUTTON_Y_OFFSET = ((tabletHeight / 2) - (tabletHeight / 20)) * sensorScaleOffsetOverride; Overlays.editOverlay(HMD.homeButtonID, { - localPosition: {x: -0.001, y: -HOME_BUTTON_Y_OFFSET, z: 0.0}, + localPosition: {x: 0, y: -HOME_BUTTON_Y_OFFSET, z: 0 }, dimensions: { x: 4 * tabletScaleFactor, y: 4 * tabletScaleFactor, z: 4 * tabletScaleFactor} }); Overlays.editOverlay(HMD.homeButtonHighlightID, { - localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET + 0.003, z: -0.0158 }, + localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET + 0.003 * sensorScaleFactor * sensorScaleOffsetOverride, z: -0.0158 * sensorScaleFactor * sensorScaleOffsetOverride }, dimensions: { x: 4 * tabletScaleFactor, y: 4 * tabletScaleFactor, z: 4 * tabletScaleFactor }, outerRadius: 25 * tabletScaleFactor, innerRadius: 20 * tabletScaleFactor From 393ad0079c4af2f09048aa5e18013129d4ba38de Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 17 Nov 2017 14:00:01 -0800 Subject: [PATCH 04/47] home button is circle instead of sphere, fixed positioning, fix lasers on tablet --- interface/src/ui/overlays/Web3DOverlay.cpp | 1 - scripts/system/libraries/WebTablet.js | 19 +++++++++++-------- .../libraries/controllerDispatcherUtils.js | 18 ++---------------- scripts/system/libraries/touchEventUtils.js | 4 +--- scripts/system/libraries/utils.js | 19 +++++++++---------- scripts/system/tablet-ui/tabletUI.js | 2 +- 6 files changed, 24 insertions(+), 39 deletions(-) diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index f096c50a03..f8e20bcd32 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -58,7 +58,6 @@ static int MAX_WINDOW_SIZE = 4096; -static const float INCHES_TO_METERS = 1.0f / 39.3701f; static const float METERS_TO_INCHES = 39.3701f; static const float OPAQUE_ALPHA_THRESHOLD = 0.99f; diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index cdc25fede3..01fdfb1845 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -137,12 +137,15 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { visible: visible }); - var HOME_BUTTON_Y_OFFSET = ((tabletHeight / 2) - (tabletHeight / 20)) * (1 / sensorScaleFactor); - this.homeButtonID = Overlays.addOverlay("sphere", { + var HOME_BUTTON_Y_OFFSET = ((tabletHeight / 2) - (tabletHeight / 20)) * (1 / sensorScaleFactor) - 0.003; + // FIXME: Circle3D overlays currently at the wrong dimensions, so we need to account for that here + var homeButtonDim = 4.0 * tabletScaleFactor / 3.0; + this.homeButtonID = Overlays.addOverlay("circle3d", { name: "homeButton", - localPosition: {x: 0.0, y: -HOME_BUTTON_Y_OFFSET, z: 0.0}, - localRotation: {x: 0, y: 1, z: 0, w: 0}, - dimensions: { x: 4 * tabletScaleFactor, y: 4 * tabletScaleFactor, z: 4 * tabletScaleFactor}, + localPosition: { x: 0.0, y: -HOME_BUTTON_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, + localRotation: { x: 0, y: 1, z: 0, w: 0}, + dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim }, + solid: true, alpha: 0.0, visible: visible, drawInFront: false, @@ -152,14 +155,14 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) { this.homeButtonHighlightID = Overlays.addOverlay("circle3d", { name: "homeButtonHighlight", - localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET + 0.003, z: -0.0158 }, + localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, localRotation: { x: 0, y: 1, z: 0, w: 0 }, - dimensions: { x: 4 * tabletScaleFactor, y: 4 * tabletScaleFactor, z: 4 * tabletScaleFactor }, + dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim }, + color: { red: 255, green: 255, blue: 255 }, solid: true, innerRadius: 0.9, ignoreIntersection: true, alpha: 1.0, - color: { red: 255, green: 255, blue: 255 }, visible: visible, drawInFront: false, parentID: this.tabletEntityID, diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index fb6de0e683..33472d6581 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -255,22 +255,8 @@ projectOntoEntityXYPlane = function (entityID, worldPos, props) { projectOntoOverlayXYPlane = function projectOntoOverlayXYPlane(overlayID, worldPos) { var position = Overlays.getProperty(overlayID, "position"); var rotation = Overlays.getProperty(overlayID, "rotation"); - var dimensions; - - var dpi = Overlays.getProperty(overlayID, "dpi"); - if (dpi) { - // Calculate physical dimensions for web3d overlay from resolution and dpi; "dimensions" property is used as a scale. - var resolution = Overlays.getProperty(overlayID, "resolution"); - resolution.z = 1; // Circumvent divide-by-zero. - var scale = Overlays.getProperty(overlayID, "dimensions"); - scale.z = 0.01; // overlay dimensions are 2D, not 3D. - dimensions = Vec3.multiplyVbyV(Vec3.multiply(resolution, INCHES_TO_METERS / dpi), scale); - } else { - dimensions = Overlays.getProperty(overlayID, "dimensions"); - if (dimensions.z) { - dimensions.z = 0.01; // overlay dimensions are 2D, not 3D. - } - } + var dimensions = Overlays.getProperty(overlayID, "dimensions"); + dimensions.z = 0.01; // we are projecting onto the XY plane of the overlay, so ignore the z dimension return projectOntoXYPlane(worldPos, position, rotation, dimensions, DEFAULT_REGISTRATION_POINT); }; diff --git a/scripts/system/libraries/touchEventUtils.js b/scripts/system/libraries/touchEventUtils.js index 53ac4f49d1..7bd2a10dc9 100644 --- a/scripts/system/libraries/touchEventUtils.js +++ b/scripts/system/libraries/touchEventUtils.js @@ -174,9 +174,7 @@ function calculateTouchTargetFromOverlay(touchTip, overlayID) { if (dimensions === undefined) { return; } - if (!dimensions.z) { - dimensions.z = 0.01; // sometimes overlay dimensions are 2D, not 3D. - } + dimensions.z = 0.01; // we are projecting onto the XY plane of the overlay, so ignore the z dimension var invDimensions = { x: 1 / dimensions.x, y: 1 / dimensions.y, z: 1 / dimensions.z }; var normalizedPosition = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), DEFAULT_REGISTRATION_POINT); diff --git a/scripts/system/libraries/utils.js b/scripts/system/libraries/utils.js index a6d09e564d..3e9a548798 100644 --- a/scripts/system/libraries/utils.js +++ b/scripts/system/libraries/utils.js @@ -185,7 +185,7 @@ logTrace = function(str) { // (the vector that would move the point outside the sphere) // otherwise returns false findSphereHit = function(point, sphereRadius) { - var EPSILON = 0.000001; //smallish positive number - used as margin of error for some computations + var EPSILON = 0.000001; //smallish positive number - used as margin of error for some computations var vectorLength = Vec3.length(point); if (vectorLength < EPSILON) { return true; @@ -406,22 +406,21 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride) var screenHeight = 0.81 * tabletHeight; Overlays.editOverlay(HMD.tabletScreenID, { localPosition: { x: 0, y: WEB_ENTITY_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, - dimensions: {x: screenWidth, y: screenHeight, z: 0.1}, + dimensions: { x: screenWidth, y: screenHeight, z: 0.1 }, dpi: tabletDpi }); // update homeButton - var HOME_BUTTON_Y_OFFSET = ((tabletHeight / 2) - (tabletHeight / 20)) * sensorScaleOffsetOverride; - var homeButtonDim = 4 * tabletScaleFactor; + var HOME_BUTTON_Y_OFFSET = ((tabletHeight / 2) - (tabletHeight / 20) - 0.003 * sensorScaleFactor) * sensorScaleOffsetOverride; + // FIXME: Circle3D overlays currently at the wrong dimensions, so we need to account for that here + var homeButtonDim = 4.0 * tabletScaleFactor / 3.0; Overlays.editOverlay(HMD.homeButtonID, { - localPosition: {x: 0, y: -HOME_BUTTON_Y_OFFSET, z: 0 }, - dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim} + localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, + dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim } }); - // Circle3D overlays render at 1.5x their proper dimensions - var highlightDim = homeButtonDim / 3.0; Overlays.editOverlay(HMD.homeButtonHighlightID, { - localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET + 0.003 * sensorScaleFactor * sensorScaleOffsetOverride, z: -0.0158 * sensorScaleFactor * sensorScaleOffsetOverride }, - dimensions: { x: highlightDim, y: highlightDim, z: highlightDim } + localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET }, + dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim } }); }; diff --git a/scripts/system/tablet-ui/tabletUI.js b/scripts/system/tablet-ui/tabletUI.js index 17821c737e..36a1cbcdd9 100644 --- a/scripts/system/tablet-ui/tabletUI.js +++ b/scripts/system/tablet-ui/tabletUI.js @@ -47,7 +47,7 @@ } return false; } - if (Overlays.getProperty(HMD.homeButtonID, "type") != "sphere" || + if (Overlays.getProperty(HMD.homeButtonID, "type") != "circle3d" || Overlays.getProperty(HMD.tabletScreenID, "type") != "web3d") { if (debugTablet) { print("TABLET is invalid due to other"); From 9f54ce55f301745badddb54047f62359d3492fc3 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Mon, 20 Nov 2017 14:16:56 -0800 Subject: [PATCH 05/47] Change domain setting from min/max avatar scale to min/max avatar height * Domain settings version has been bumped from version 2.0 to 2.1 * Old domain settings for avatar scale will be auto-converted to avatar height * Avatar code has been changed so that limitDomainScale() works with the new height limits * Avatar getUnscaledEyeHeight() was added to C++. * MyAvatar.getHeight() was added to JS. --- assignment-client/src/avatars/AvatarMixer.cpp | 24 ++--- assignment-client/src/avatars/AvatarMixer.h | 4 +- .../resources/describe-settings.json | 22 ++--- .../src/DomainServerSettingsManager.cpp | 20 ++++ interface/src/Application.cpp | 4 +- interface/src/avatar/MyAvatar.cpp | 97 ++++++------------- libraries/animation/src/Rig.h | 3 + .../src/avatars-renderer/Avatar.cpp | 83 +++++++++++----- .../src/avatars-renderer/Avatar.h | 19 ++-- libraries/avatars/src/AvatarData.cpp | 31 ++++++ libraries/avatars/src/AvatarData.h | 40 ++++++-- .../networking/src/udt/PacketHeaders.cpp | 2 + libraries/render-utils/src/Model.cpp | 2 +- libraries/render-utils/src/Model.h | 6 +- libraries/shared/src/AvatarConstants.h | 7 ++ 15 files changed, 226 insertions(+), 138 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index c67e998dd4..3ca924b007 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -870,8 +870,8 @@ AvatarMixerClientData* AvatarMixer::getOrCreateClientData(SharedNodePointer node node->setLinkedData(std::unique_ptr { new AvatarMixerClientData(node->getUUID()) }); clientData = dynamic_cast(node->getLinkedData()); auto& avatar = clientData->getAvatar(); - avatar.setDomainMinimumScale(_domainMinimumScale); - avatar.setDomainMaximumScale(_domainMaximumScale); + avatar.setDomainMinimumHeight(_domainMinimumHeight); + avatar.setDomainMaximumHeight(_domainMaximumHeight); } return clientData; @@ -939,21 +939,21 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) { const QString AVATARS_SETTINGS_KEY = "avatars"; - static const QString MIN_SCALE_OPTION = "min_avatar_scale"; - float settingMinScale = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MIN_SCALE_OPTION].toDouble(MIN_AVATAR_SCALE); - _domainMinimumScale = glm::clamp(settingMinScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); + static const QString MIN_HEIGHT_OPTION = "min_avatar_height"; + float settingMinHeight = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MIN_HEIGHT_OPTION].toDouble(MIN_AVATAR_HEIGHT); + _domainMinimumHeight = glm::clamp(settingMinHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT); - static const QString MAX_SCALE_OPTION = "max_avatar_scale"; - float settingMaxScale = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MAX_SCALE_OPTION].toDouble(MAX_AVATAR_SCALE); - _domainMaximumScale = glm::clamp(settingMaxScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); + static const QString MAX_HEIGHT_OPTION = "max_avatar_height"; + float settingMaxHeight = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MAX_HEIGHT_OPTION].toDouble(MAX_AVATAR_HEIGHT); + _domainMaximumHeight = glm::clamp(settingMaxHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT); // make sure that the domain owner didn't flip min and max - if (_domainMinimumScale > _domainMaximumScale) { - std::swap(_domainMinimumScale, _domainMaximumScale); + if (_domainMinimumHeight > _domainMaximumHeight) { + std::swap(_domainMinimumHeight, _domainMaximumHeight); } - qCDebug(avatars) << "This domain requires a minimum avatar scale of" << _domainMinimumScale - << "and a maximum avatar scale of" << _domainMaximumScale; + qCDebug(avatars) << "This domain requires a minimum avatar height of" << _domainMinimumHeight + << "and a maximum avatar height of" << _domainMaximumHeight; const QString AVATAR_WHITELIST_DEFAULT{ "" }; static const QString AVATAR_WHITELIST_OPTION = "avatar_whitelist"; diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index 610da8ad57..cb5f536faa 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -90,8 +90,8 @@ private: float _maxKbpsPerNode = 0.0f; - float _domainMinimumScale { MIN_AVATAR_SCALE }; - float _domainMaximumScale { MAX_AVATAR_SCALE }; + float _domainMinimumHeight { MIN_AVATAR_HEIGHT }; + float _domainMaximumHeight { MAX_AVATAR_HEIGHT }; RateCounter<> _broadcastRate; p_high_resolution_clock::time_point _lastDebugMessage; diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index d55da6c848..cacd95fba5 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1,5 +1,5 @@ { - "version": 2.0, + "version": 2.1, "settings": [ { "name": "label", @@ -1007,20 +1007,20 @@ "assignment-types": [ 1, 2 ], "settings": [ { - "name": "min_avatar_scale", + "name": "min_avatar_height", "type": "double", - "label": "Minimum Avatar Scale", - "help": "Limits the scale of avatars in your domain. Must be at least 0.005.", - "placeholder": 0.25, - "default": 0.25 + "label": "Minimum Avatar Height (meters)", + "help": "Limits the height of avatars in your domain. Must be at least 0.009.", + "placeholder": 0.4, + "default": 0.4 }, { - "name": "max_avatar_scale", + "name": "max_avatar_height", "type": "double", - "label": "Maximum Avatar Scale", - "help": "Limits the scale of avatars in your domain. Cannot be greater than 1000.", - "placeholder": 3.0, - "default": 3.0 + "label": "Maximum Avatar Height (meters)", + "help": "Limits the scale of avatars in your domain. Cannot be greater than 1755.", + "placeholder": 5.2, + "default": 5.2 }, { "name": "avatar_whitelist", diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 6c50e5245d..674f3a18d1 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -304,6 +304,26 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList *wizardCompletedOnce = QVariant(true); } + if (oldVersion < 2.1) { + // convert old avatar scale settings into avatar height. + + const QString AVATAR_MIN_SCALE_KEYPATH = "avatars.min_avatar_scale"; + const QString AVATAR_MAX_SCALE_KEYPATH = "avatars.max_avatar_scale"; + const QString AVATAR_MIN_HEIGHT_KEYPATH = "avatars.min_avatar_height"; + const QString AVATAR_MAX_HEIGHT_KEYPATH = "avatars.max_avatar_height"; + + QVariant* avatarMinScale = _configMap.valueForKeyPath(AVATAR_MIN_SCALE_KEYPATH); + if (avatarMinScale) { + float scale = avatarMinScale->toFloat(); + QVariant* avatarMinHeight = _configMap.valueForKeyPath(AVATAR_MIN_HEIGHT_KEYPATH, scale * DEFAULT_AVATAR_HEIGHT); + } + + QVariant* avatarMaxScale = _configMap.valueForKeyPath(AVATAR_MAX_SCALE_KEYPATH); + if (avatarMaxScale) { + float scale = avatarMaxScale->toFloat(); + QVariant* avatarMinHeight = _configMap.valueForKeyPath(AVATAR_MAX_HEIGHT_KEYPATH, scale * DEFAULT_AVATAR_HEIGHT); + } + } // write the current description version to our settings *versionVariant = _descriptionVersion; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e7e1fbe2e4..d9c889aa42 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2792,10 +2792,10 @@ static int getEventQueueSize(QThread* thread) { static void dumpEventQueue(QThread* thread) { auto threadData = QThreadData::get2(thread); QMutexLocker locker(&threadData->postEventList.mutex); - qDebug() << "AJT: event list, size =" << threadData->postEventList.size(); + qDebug() << "Event list, size =" << threadData->postEventList.size(); for (auto& postEvent : threadData->postEventList) { QEvent::Type type = (postEvent.event ? postEvent.event->type() : QEvent::None); - qDebug() << "AJT: " << type; + qDebug() << " " << type; } } #endif // DEBUG_EVENT_QUEUE diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 6b2795fc90..d77bfbe09d 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1799,6 +1799,7 @@ void MyAvatar::postUpdate(float deltaTime, const render::ScenePointer& scene) { _skeletonModel->setCauterizeBoneSet(_headBoneSet); _fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl(); initAnimGraph(); + _isAnimatingScale = true; } if (_enableDebugDrawDefaultPose || _enableDebugDrawAnimPose) { @@ -2161,39 +2162,14 @@ bool findAvatarAvatarPenetration(const glm::vec3 positionA, float radiusA, float // target scale to match the new scale they have chosen. When they leave the domain they will not return to the scale they were // before they entered the limiting domain. -void MyAvatar::clampTargetScaleToDomainLimits() { - // when we're about to change the target scale because the user has asked to increase or decrease their scale, - // we first make sure that we're starting from a target scale that is allowed by the current domain - - auto clampedTargetScale = glm::clamp(_targetScale, _domainMinimumScale, _domainMaximumScale); - - if (clampedTargetScale != _targetScale) { - qCDebug(interfaceapp, "Clamped scale to %f since original target scale %f was not allowed by domain", - (double)clampedTargetScale, (double)_targetScale); - - setTargetScale(clampedTargetScale); - } -} - -void MyAvatar::clampScaleChangeToDomainLimits(float desiredScale) { - auto clampedTargetScale = glm::clamp(desiredScale, _domainMinimumScale, _domainMaximumScale); - - if (clampedTargetScale != desiredScale) { - qCDebug(interfaceapp, "Forcing scale to %f since %f is not allowed by domain", - clampedTargetScale, desiredScale); - } - - setTargetScale(clampedTargetScale); - qCDebug(interfaceapp, "Changed scale to %f", (double)_targetScale); - emit(scaleChanged()); -} - float MyAvatar::getDomainMinScale() { - return _domainMinimumScale; + const float unscaledHeight = getUnscaledEyeHeight(); + return _domainMinimumHeight / unscaledHeight; } float MyAvatar::getDomainMaxScale() { - return _domainMaximumScale; + const float unscaledHeight = getUnscaledEyeHeight(); + return _domainMaximumHeight / unscaledHeight; } void MyAvatar::setGravity(float gravity) { @@ -2205,70 +2181,54 @@ float MyAvatar::getGravity() { } void MyAvatar::increaseSize() { - // make sure we're starting from an allowable scale - clampTargetScaleToDomainLimits(); + float minScale = getDomainMinScale(); + float maxScale = getDomainMaxScale(); - // calculate what our new scale should be - float updatedTargetScale = _targetScale * (1.0f + SCALING_RATIO); - - // attempt to change to desired scale (clamped to the domain limits) - clampScaleChangeToDomainLimits(updatedTargetScale); + float newTargetScale = glm::clamp(_targetScale * (1.0f + SCALING_RATIO), minScale, maxScale); + setTargetScale(newTargetScale); } void MyAvatar::decreaseSize() { - // make sure we're starting from an allowable scale - clampTargetScaleToDomainLimits(); + float minScale = getDomainMinScale(); + float maxScale = getDomainMaxScale(); - // calculate what our new scale should be - float updatedTargetScale = _targetScale * (1.0f - SCALING_RATIO); - - // attempt to change to desired scale (clamped to the domain limits) - clampScaleChangeToDomainLimits(updatedTargetScale); + float newTargetScale = glm::clamp(_targetScale * (1.0f - SCALING_RATIO), minScale, maxScale); + setTargetScale(newTargetScale); } void MyAvatar::resetSize() { // attempt to reset avatar size to the default (clamped to domain limits) const float DEFAULT_AVATAR_SCALE = 1.0f; - - clampScaleChangeToDomainLimits(DEFAULT_AVATAR_SCALE); + setTargetScale(DEFAULT_AVATAR_SCALE); } void MyAvatar::restrictScaleFromDomainSettings(const QJsonObject& domainSettingsObject) { - // pull out the minimum and maximum scale and set them to restrict our scale + // pull out the minimum and maximum height and set them to restrict our scale static const QString AVATAR_SETTINGS_KEY = "avatars"; auto avatarsObject = domainSettingsObject[AVATAR_SETTINGS_KEY].toObject(); - static const QString MIN_SCALE_OPTION = "min_avatar_scale"; - float settingMinScale = avatarsObject[MIN_SCALE_OPTION].toDouble(MIN_AVATAR_SCALE); - setDomainMinimumScale(settingMinScale); + static const QString MIN_HEIGHT_OPTION = "min_avatar_height"; + float settingMinHeight = avatarsObject[MIN_HEIGHT_OPTION].toDouble(MIN_AVATAR_HEIGHT); + setDomainMinimumHeight(settingMinHeight); - static const QString MAX_SCALE_OPTION = "max_avatar_scale"; - float settingMaxScale = avatarsObject[MAX_SCALE_OPTION].toDouble(MAX_AVATAR_SCALE); - setDomainMaximumScale(settingMaxScale); + static const QString MAX_HEIGHT_OPTION = "max_avatar_height"; + float settingMaxHeight = avatarsObject[MAX_HEIGHT_OPTION].toDouble(MAX_AVATAR_HEIGHT); + setDomainMaximumHeight(settingMaxHeight); // make sure that the domain owner didn't flip min and max - if (_domainMinimumScale > _domainMaximumScale) { - std::swap(_domainMinimumScale, _domainMaximumScale); + if (_domainMinimumHeight > _domainMaximumHeight) { + std::swap(_domainMinimumHeight, _domainMaximumHeight); } // Set avatar current scale Settings settings; settings.beginGroup("Avatar"); _targetScale = loadSetting(settings, "scale", 1.0f); - qCDebug(interfaceapp) << "This domain requires a minimum avatar scale of " << _domainMinimumScale - << " and a maximum avatar scale of " << _domainMaximumScale - << ". Current avatar scale is " << _targetScale; + qCDebug(interfaceapp) << "This domain requires a minimum avatar scale of " << _domainMinimumHeight + << " and a maximum avatar scale of " << _domainMaximumHeight; - // debug to log if this avatar's scale in this domain will be clamped - float clampedScale = glm::clamp(_targetScale, _domainMinimumScale, _domainMaximumScale); - - if (_targetScale != clampedScale) { - qCDebug(interfaceapp) << "Current avatar scale is clamped to " << clampedScale - << " because " << _targetScale << " is not allowed by current domain"; - // The current scale of avatar should not be more than domain's max_avatar_scale and not less than domain's min_avatar_scale . - _targetScale = clampedScale; - } + _isAnimatingScale = true; setModelScale(_targetScale); rebuildCollisionShape(); @@ -2288,8 +2248,8 @@ void MyAvatar::saveAvatarScale() { } void MyAvatar::clearScaleRestriction() { - _domainMinimumScale = MIN_AVATAR_SCALE; - _domainMaximumScale = MAX_AVATAR_SCALE; + _domainMinimumHeight = MIN_AVATAR_HEIGHT; + _domainMaximumHeight = MAX_AVATAR_HEIGHT; } void MyAvatar::goToLocation(const QVariant& propertiesVar) { @@ -3248,6 +3208,7 @@ void MyAvatar::setModelScale(float scale) { if (changed) { float sensorToWorldScale = getEyeHeight() / getUserEyeHeight(); emit sensorToWorldScaleChanged(sensorToWorldScale); + emit scaleChanged(); } } diff --git a/libraries/animation/src/Rig.h b/libraries/animation/src/Rig.h index e9cc444bd4..e738ad1c19 100644 --- a/libraries/animation/src/Rig.h +++ b/libraries/animation/src/Rig.h @@ -231,6 +231,9 @@ public: const glm::mat4& getGeometryToRigTransform() const { return _geometryToRigTransform; } + const AnimPose& getModelOffsetPose() const { return _modelOffset; } + const AnimPose& getGeometryOffsetPose() const { return _geometryOffset; } + void setEnableDebugDrawIKTargets(bool enableDebugDrawIKTargets) { _enableDebugDrawIKTargets = enableDebugDrawIKTargets; } void setEnableDebugDrawIKConstraints(bool enableDebugDrawIKConstraints) { _enableDebugDrawIKConstraints = enableDebugDrawIKConstraints; } void setEnableDebugDrawIKChains(bool enableDebugDrawIKChains) { _enableDebugDrawIKChains = enableDebugDrawIKChains; } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index d00af4dd1e..819ad764a6 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -162,6 +162,7 @@ AABox Avatar::getBounds() const { } void Avatar::animateScaleChanges(float deltaTime) { + if (_isAnimatingScale) { float currentScale = getModelScale(); float desiredScale = getDomainLimitedScale(); @@ -172,7 +173,7 @@ void Avatar::animateScaleChanges(float deltaTime) { float animatedScale = (1.0f - blendFactor) * currentScale + blendFactor * desiredScale; // snap to the end when we get close enough - const float MIN_RELATIVE_ERROR = 0.03f; + const float MIN_RELATIVE_ERROR = 0.001f; float relativeError = fabsf(desiredScale - currentScale) / desiredScale; if (relativeError < MIN_RELATIVE_ERROR) { animatedScale = desiredScale; @@ -698,6 +699,7 @@ void Avatar::fixupModelsInScene(const render::ScenePointer& scene) { _skeletonModel->removeFromScene(scene, transaction); _skeletonModel->addToScene(scene, transaction); canTryFade = true; + _isAnimatingScale = true; } for (auto attachmentModel : _attachmentModels) { if (attachmentModel->isRenderable() && attachmentModel->needsFixupInScene()) { @@ -1195,6 +1197,8 @@ void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { void Avatar::setModelURLFinished(bool success) { invalidateJointIndicesCache(); + _isAnimatingScale = true; + if (!success && _skeletonModelURL != AvatarData::defaultFullAvatarModelUrl()) { const int MAX_SKELETON_DOWNLOAD_ATTEMPTS = 4; // NOTE: we don't want to be as generous as ResourceCache is, we only want 4 attempts if (_skeletonModel->getResourceDownloadAttemptsRemaining() <= 0 || @@ -1588,45 +1592,80 @@ float Avatar::getEyeHeight() const { return result; } + return getModelScale() * getUnscaledEyeHeight(); +} + +float Avatar::getUnscaledEyeHeight() const { + float skeletonHeight = getUnscaledEyeHeightFromSkeleton(); + + // Sanity check by looking at the model extents. + Extents meshExtents = _skeletonModel->getUnscaledMeshExtents(); + float meshHeight = meshExtents.size().y; + + // if we determine the mesh is much larger then the skeleton, then we use the mesh height instead. + // This helps prevent absurdly large avatars from exceeding the domain height limit. + const float MESH_SLOP_RATIO = 1.5; + if (meshHeight > skeletonHeight * MESH_SLOP_RATIO) { + return meshHeight; + } else { + return skeletonHeight; + } +} + +float Avatar::getUnscaledEyeHeightFromSkeleton() const { + // TODO: if performance becomes a concern we can cache this value rather then computing it everytime. - // Makes assumption that the y = 0 plane in geometry is the ground plane. - // We also make that assumption in Rig::computeAvatarBoundingCapsule() - float avatarScale = getModelScale(); + if (_skeletonModel) { auto& rig = _skeletonModel->getRig(); + + // Normally the model offset transform will contain the avatar scale factor, we explicitly remove it here. + AnimPose modelOffsetWithoutAvatarScale(glm::vec3(1.0f), rig.getModelOffsetPose().rot(), rig.getModelOffsetPose().trans()); + AnimPose geomToRigWithoutAvatarScale = modelOffsetWithoutAvatarScale * rig.getGeometryOffsetPose(); + + // This factor can be used to scale distances in the geometry frame into the unscaled rig frame. + // Typically it will be the unit conversion from cm to m. + float scaleFactor = geomToRigWithoutAvatarScale.scale().x; // in practice this always a uniform scale factor. + int headTopJoint = rig.indexOfJoint("HeadTop_End"); int headJoint = rig.indexOfJoint("Head"); int eyeJoint = rig.indexOfJoint("LeftEye") != -1 ? rig.indexOfJoint("LeftEye") : rig.indexOfJoint("RightEye"); int toeJoint = rig.indexOfJoint("LeftToeBase") != -1 ? rig.indexOfJoint("LeftToeBase") : rig.indexOfJoint("RightToeBase"); + + // Makes assumption that the y = 0 plane in geometry is the ground plane. + // We also make that assumption in Rig::computeAvatarBoundingCapsule() + const float GROUND_Y = 0.0f; + + // Values from the skeleton are in the geometry coordinate frame. + auto skeleton = rig.getAnimSkeleton(); if (eyeJoint >= 0 && toeJoint >= 0) { - // measure from eyes to toes. - float eyeHeight = rig.getAbsoluteDefaultPose(eyeJoint).trans().y - rig.getAbsoluteDefaultPose(toeJoint).trans().y; - return eyeHeight; + // Measure from eyes to toes. + float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y; + return scaleFactor * eyeHeight; } else if (eyeJoint >= 0) { - // measure eyes to y = 0 plane. - float groundHeight = transformPoint(rig.getGeometryToRigTransform(), glm::vec3(0.0f)).y; - float eyeHeight = rig.getAbsoluteDefaultPose(eyeJoint).trans().y - groundHeight; - return eyeHeight; + // Measure Eye joint to y = 0 plane. + float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - GROUND_Y; + return scaleFactor * eyeHeight; } else if (headTopJoint >= 0 && toeJoint >= 0) { - // measure toe to top of head. Note: default poses already include avatar scale factor + // Measure from ToeBase joint to HeadTop_End joint, then remove forehead distance. const float ratio = DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD / DEFAULT_AVATAR_HEIGHT; - float height = rig.getAbsoluteDefaultPose(headTopJoint).trans().y - rig.getAbsoluteDefaultPose(toeJoint).trans().y; - return height - height * ratio; + float height = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y; + return scaleFactor * (height - height * ratio); } else if (headTopJoint >= 0) { + // Measure from HeadTop_End joint to the ground, then remove forehead distance. const float ratio = DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD / DEFAULT_AVATAR_HEIGHT; - float groundHeight = transformPoint(rig.getGeometryToRigTransform(), glm::vec3(0.0f)).y; - float headHeight = rig.getAbsoluteDefaultPose(headTopJoint).trans().y - groundHeight; - return headHeight - headHeight * ratio; + float headHeight = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - GROUND_Y; + return scaleFactor * (headHeight - headHeight * ratio); } else if (headJoint >= 0) { - float groundHeight = transformPoint(rig.getGeometryToRigTransform(), glm::vec3(0.0f)).y; + // Measure Head joint to the ground, then add in distance from neck to eye. const float DEFAULT_AVATAR_NECK_TO_EYE = DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD - DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; const float ratio = DEFAULT_AVATAR_NECK_TO_EYE / DEFAULT_AVATAR_NECK_HEIGHT; - float neckHeight = rig.getAbsoluteDefaultPose(headJoint).trans().y - groundHeight; - return neckHeight + neckHeight * ratio; + float neckHeight = skeleton->getAbsoluteDefaultPose(headJoint).trans().y - GROUND_Y; + return scaleFactor * (neckHeight + neckHeight * ratio); } else { - return avatarScale * DEFAULT_AVATAR_EYE_HEIGHT; + return DEFAULT_AVATAR_EYE_HEIGHT; } } else { - return avatarScale * DEFAULT_AVATAR_EYE_HEIGHT; + return DEFAULT_AVATAR_EYE_HEIGHT; } } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 8069c6b604..76ed381d3f 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -255,12 +255,16 @@ public: bool isFading() const { return _isFading; } void updateFadingStatus(render::ScenePointer scene); - /**jsdoc - * Provides read only access to the current eye height of the avatar. - * @function Avatar.getEyeHeight - * @returns {number} eye height of avatar in meters - */ - Q_INVOKABLE float getEyeHeight() const; + Q_INVOKABLE virtual float getEyeHeight() const override; + + // returns eye height of avatar in meters, ignoreing avatar scale. + // if _targetScale is 1 then this will be identical to getEyeHeight; + virtual float getUnscaledEyeHeight() const override; + + // returns true, if an acurate eye height estimage can be obtained by inspecting the avatar model skeleton and geometry, + // not all subclasses of AvatarData have access to this data. + virtual bool canMeasureEyeHeight() const override { return true; } + virtual float getModelScale() const { return _modelScale; } virtual void setModelScale(float scale) { _modelScale = scale; } @@ -279,6 +283,7 @@ public slots: void setModelURLFinished(bool success); protected: + float Avatar::getUnscaledEyeHeightFromSkeleton() const; virtual const QString& getSessionDisplayNameForTransport() const override { return _empty; } // Save a tiny bit of bandwidth. Mixer won't look at what we send. QString _empty{}; virtual void maybeUpdateSessionDisplayNameFromTransport(const QString& sessionDisplayName) override { _sessionDisplayName = sessionDisplayName; } // don't use no-op setter! @@ -349,7 +354,7 @@ protected: RateCounter<> _skeletonModelSimulationRate; RateCounter<> _jointDataSimulationRate; -private: +protected: class AvatarEntityDataHash { public: AvatarEntityDataHash(uint32_t h) : hash(h) {}; diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index bd313ac133..7117fd01aa 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -117,6 +117,37 @@ void AvatarData::setTargetScale(float targetScale) { } } +float AvatarData::getDomainLimitedScale() const { + if (canMeasureEyeHeight()) { + const float unscaledEyeHeight = getUnscaledEyeHeight(); + + // Add in an estimate of forehead height. + const float ratio = unscaledEyeHeight / DEFAULT_AVATAR_HEIGHT; + const float unscaledHeight = unscaledEyeHeight + ratio * DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; + + const float minScale = _domainMinimumHeight / unscaledHeight; + const float maxScale = _domainMaximumHeight / unscaledHeight; + return glm::clamp(_targetScale, minScale, maxScale); + } else { + // We can't make a good estimate. + return _targetScale; + } +} + +void AvatarData::setDomainMinimumHeight(float domainMinimumHeight) { + _domainMinimumHeight = glm::clamp(domainMinimumHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT); +} + +void AvatarData::setDomainMaximumHeight(float domainMaximumHeight) { + _domainMaximumHeight = glm::clamp(domainMaximumHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT); +} + +float AvatarData::getHeight() const { + const float eyeHeight = getEyeHeight(); + const float ratio = eyeHeight / DEFAULT_AVATAR_HEIGHT; + return eyeHeight + ratio * DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; +} + glm::vec3 AvatarData::getHandPosition() const { return getOrientation() * _handPosition + getPosition(); } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index bf3bb20ef9..e228fb42d5 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -35,6 +35,7 @@ #include #include +#include #include #include #include @@ -257,9 +258,6 @@ namespace AvatarDataPacket { size_t maxJointDataSize(size_t numJoints); } -static const float MAX_AVATAR_SCALE = 1000.0f; -static const float MIN_AVATAR_SCALE = .005f; - const float MAX_AUDIO_LOUDNESS = 1000.0f; // close enough for mouth animation const int AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS = 1000; @@ -484,12 +482,34 @@ public: // Scale virtual void setTargetScale(float targetScale); - float getDomainLimitedScale() const { return glm::clamp(_targetScale, _domainMinimumScale, _domainMaximumScale); } + float getDomainLimitedScale() const; - void setDomainMinimumScale(float domainMinimumScale) - { _domainMinimumScale = glm::clamp(domainMinimumScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); _scaleChanged = usecTimestampNow(); } - void setDomainMaximumScale(float domainMaximumScale) - { _domainMaximumScale = glm::clamp(domainMaximumScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); _scaleChanged = usecTimestampNow(); } + // returns eye height of avatar in meters, ignoreing avatar scale. + // if _targetScale is 1 then this will be identical to getEyeHeight; + virtual float getUnscaledEyeHeight() const { return DEFAULT_AVATAR_EYE_HEIGHT; } + + // returns true, if an acurate eye height estimage can be obtained by inspecting the avatar model skeleton and geometry, + // not all subclasses of AvatarData have access to this data. + virtual bool canMeasureEyeHeight() const { return false; } + + /**jsdoc + * Provides read only access to the current eye height of the avatar. + * This height is only an estimate and might be incorrect for avatars that are missing standard joints. + * @function AvatarData.getEyeHeight + * @returns {number} eye height of avatar in meters + */ + Q_INVOKABLE virtual float getEyeHeight() const { return _targetScale * getUnscaledEyeHeight(); } + + /**jsdoc + * Provides read only access to the current height of the avatar. + * This height is only an estimate and might be incorrect for avatars that are missing standard joints. + * @function AvatarData.getHeight + * @returns {number} height of avatar in meters + */ + Q_INVOKABLE virtual float getHeight() const; + + void setDomainMinimumHeight(float domainMinimumHeight); + void setDomainMaximumHeight(float domainMaximumHeight); // Hand State Q_INVOKABLE void setHandState(char s) { _handState = s; } @@ -706,8 +726,8 @@ protected: // Body scale float _targetScale; - float _domainMinimumScale { MIN_AVATAR_SCALE }; - float _domainMaximumScale { MAX_AVATAR_SCALE }; + float _domainMinimumHeight { MIN_AVATAR_HEIGHT }; + float _domainMaximumHeight { MAX_AVATAR_HEIGHT }; // Hand state (are we grabbing something or not) char _handState; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index c2c1d75726..2b9b96bed9 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -74,6 +74,8 @@ PacketVersion versionForPacketType(PacketType packetType) { return static_cast(AudioVersion::HighDynamicRangeVolume); case PacketType::ICEPing: return static_cast(IcePingVersion::SendICEPeerID); + case PacketType::DomainSettings: + return 18; // replace min_avatar_scale and max_avatar_scale with min_avatar_height and max_avatar_height default: return 17; } diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 428fcc7a54..199cb29f53 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -163,7 +163,7 @@ void Model::setScale(const glm::vec3& scale) { _scaledToFit = false; } -const float SCALE_CHANGE_EPSILON = 0.01f; +const float SCALE_CHANGE_EPSILON = 0.001f; void Model::setScaleInternal(const glm::vec3& scale) { if (glm::distance(_scale, scale) > SCALE_CHANGE_EPSILON) { diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index c537a928b3..fadd44b745 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -204,6 +204,9 @@ public: /// Returns the extents of the model's mesh Extents getMeshExtents() const; + /// Returns the unscaled extents of the model's mesh + Extents getUnscaledMeshExtents() const; + void setTranslation(const glm::vec3& translation); void setRotation(const glm::quat& rotation); void setTransformNoUpdateRenderItems(const Transform& transform); // temporary HACK @@ -276,9 +279,6 @@ protected: void setBlendshapeCoefficients(const QVector& coefficients) { _blendshapeCoefficients = coefficients; } const QVector& getBlendshapeCoefficients() const { return _blendshapeCoefficients; } - /// Returns the unscaled extents of the model's mesh - Extents getUnscaledMeshExtents() const; - /// Clear the joint states void clearJointState(int index); diff --git a/libraries/shared/src/AvatarConstants.h b/libraries/shared/src/AvatarConstants.h index a7a80471be..4942c63e27 100644 --- a/libraries/shared/src/AvatarConstants.h +++ b/libraries/shared/src/AvatarConstants.h @@ -12,6 +12,8 @@ #ifndef hifi_AvatarConstants_h #define hifi_AvatarConstants_h +#include "GLMHelpers.h" + // 50th Percentile Man const float DEFAULT_AVATAR_HEIGHT = 1.755f; // meters const float DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD = 0.11f; // meters @@ -52,5 +54,10 @@ const float DEFAULT_AVATAR_JUMP_HEIGHT = (DEFAULT_AVATAR_JUMP_SPEED * DEFAULT_AV const float DEFAULT_AVATAR_FALL_HEIGHT = 20.0f; // meters const float DEFAULT_AVATAR_MIN_HOVER_HEIGHT = 2.5f; // meters +static const float MAX_AVATAR_SCALE = 1000.0f; +static const float MIN_AVATAR_SCALE = 0.005f; + +static const float MAX_AVATAR_HEIGHT = 1000.0f * DEFAULT_AVATAR_HEIGHT; // meters +static const float MIN_AVATAR_HEIGHT = 0.005f * DEFAULT_AVATAR_HEIGHT; // meters #endif // hifi_AvatarConstants_h From b3896f664d8439b460e3eeacf236da71dc397719 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 22 Nov 2017 14:45:52 -0800 Subject: [PATCH 06/47] Made increaseScale and decreaseScale more responsive at limits --- interface/src/avatar/MyAvatar.cpp | 18 ++++++------------ interface/src/avatar/MyAvatar.h | 2 -- libraries/avatars/src/AvatarData.cpp | 26 ++++++++++++++++++-------- libraries/avatars/src/AvatarData.h | 4 ++++ 4 files changed, 28 insertions(+), 22 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index d77bfbe09d..5da7e37fec 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2162,16 +2162,6 @@ bool findAvatarAvatarPenetration(const glm::vec3 positionA, float radiusA, float // target scale to match the new scale they have chosen. When they leave the domain they will not return to the scale they were // before they entered the limiting domain. -float MyAvatar::getDomainMinScale() { - const float unscaledHeight = getUnscaledEyeHeight(); - return _domainMinimumHeight / unscaledHeight; -} - -float MyAvatar::getDomainMaxScale() { - const float unscaledHeight = getUnscaledEyeHeight(); - return _domainMaximumHeight / unscaledHeight; -} - void MyAvatar::setGravity(float gravity) { _characterController.setGravity(gravity); } @@ -2184,7 +2174,9 @@ void MyAvatar::increaseSize() { float minScale = getDomainMinScale(); float maxScale = getDomainMaxScale(); - float newTargetScale = glm::clamp(_targetScale * (1.0f + SCALING_RATIO), minScale, maxScale); + float clampedTargetScale = glm::clamp(_targetScale, minScale, maxScale); + float newTargetScale = glm::clamp(clampedTargetScale * (1.0f + SCALING_RATIO), minScale, maxScale); + setTargetScale(newTargetScale); } @@ -2192,7 +2184,9 @@ void MyAvatar::decreaseSize() { float minScale = getDomainMinScale(); float maxScale = getDomainMaxScale(); - float newTargetScale = glm::clamp(_targetScale * (1.0f - SCALING_RATIO), minScale, maxScale); + float clampedTargetScale = glm::clamp(_targetScale, minScale, maxScale); + float newTargetScale = glm::clamp(clampedTargetScale * (1.0f - SCALING_RATIO), minScale, maxScale); + setTargetScale(newTargetScale); } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index e4e8f8d02c..3b5157fdeb 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -558,8 +558,6 @@ public slots: void increaseSize(); void decreaseSize(); void resetSize(); - float getDomainMinScale(); - float getDomainMaxScale(); void setGravity(float gravity); float getGravity(); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 7117fd01aa..c6b78de07c 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -119,14 +119,8 @@ void AvatarData::setTargetScale(float targetScale) { float AvatarData::getDomainLimitedScale() const { if (canMeasureEyeHeight()) { - const float unscaledEyeHeight = getUnscaledEyeHeight(); - - // Add in an estimate of forehead height. - const float ratio = unscaledEyeHeight / DEFAULT_AVATAR_HEIGHT; - const float unscaledHeight = unscaledEyeHeight + ratio * DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; - - const float minScale = _domainMinimumHeight / unscaledHeight; - const float maxScale = _domainMaximumHeight / unscaledHeight; + const float minScale = getDomainMinScale(); + const float maxScale = getDomainMaxScale(); return glm::clamp(_targetScale, minScale, maxScale); } else { // We can't make a good estimate. @@ -142,6 +136,22 @@ void AvatarData::setDomainMaximumHeight(float domainMaximumHeight) { _domainMaximumHeight = glm::clamp(domainMaximumHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT); } +float AvatarData::getDomainMinScale() const { + const float unscaledHeight = getUnscaledHeight(); + return _domainMinimumHeight / unscaledHeight; +} + +float AvatarData::getDomainMaxScale() const { + const float unscaledHeight = getUnscaledHeight(); + return _domainMaximumHeight / unscaledHeight; +} + +float AvatarData::getUnscaledHeight() const { + const float eyeHeight = getUnscaledEyeHeight(); + const float ratio = eyeHeight / DEFAULT_AVATAR_HEIGHT; + return eyeHeight + ratio * DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD; +} + float AvatarData::getHeight() const { const float eyeHeight = getEyeHeight(); const float ratio = eyeHeight / DEFAULT_AVATAR_HEIGHT; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index e228fb42d5..50704b98e3 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -483,6 +483,8 @@ public: virtual void setTargetScale(float targetScale); float getDomainLimitedScale() const; + float getDomainMinScale() const; + float getDomainMaxScale() const; // returns eye height of avatar in meters, ignoreing avatar scale. // if _targetScale is 1 then this will be identical to getEyeHeight; @@ -508,6 +510,8 @@ public: */ Q_INVOKABLE virtual float getHeight() const; + float getUnscaledHeight() const; + void setDomainMinimumHeight(float domainMinimumHeight); void setDomainMaximumHeight(float domainMaximumHeight); From b481a6060e867e44f6ac550414cc7388da8320ad Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Wed, 22 Nov 2017 18:15:42 -0700 Subject: [PATCH 07/47] improve audio scope --- interface/src/audio/AudioScope.cpp | 126 ++++++++++++------------ interface/src/audio/AudioScope.h | 55 ++++++++++- interface/src/ui/ApplicationOverlay.cpp | 20 ---- interface/src/ui/ApplicationOverlay.h | 1 - 4 files changed, 112 insertions(+), 90 deletions(-) diff --git a/interface/src/audio/AudioScope.cpp b/interface/src/audio/AudioScope.cpp index cf9984e32b..5fbb5cdaf6 100644 --- a/interface/src/audio/AudioScope.cpp +++ b/interface/src/audio/AudioScope.cpp @@ -9,6 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include #include #include @@ -28,6 +29,8 @@ static const unsigned int SCOPE_HEIGHT = 2 * 15 * MULTIPLIER_SCOPE_HEIGHT; AudioScope::AudioScope() : _isEnabled(false), _isPaused(false), + _isTriggered(false), + _autoTrigger(false), _scopeInputOffset(0), _scopeOutputOffset(0), _framesPerScope(DEFAULT_FRAMES_PER_SCOPE), @@ -108,63 +111,19 @@ void AudioScope::freeScope() { } } -void AudioScope::render(RenderArgs* renderArgs, int width, int height) { - - if (!_isEnabled) { - return; - } - - static const glm::vec4 backgroundColor = { 0.4f, 0.4f, 0.4f, 0.6f }; - static const glm::vec4 gridColor = { 0.7f, 0.7f, 0.7f, 1.0f }; - static const glm::vec4 inputColor = { 0.3f, 1.0f, 0.3f, 1.0f }; - static const glm::vec4 outputLeftColor = { 1.0f, 0.3f, 0.3f, 1.0f }; - static const glm::vec4 outputRightColor = { 0.3f, 0.3f, 1.0f, 1.0f }; - static const int gridCols = 2; - int gridRows = _framesPerScope; - - int x = (width - (int)SCOPE_WIDTH) / 2; - int y = (height - (int)SCOPE_HEIGHT) / 2; - int w = (int)SCOPE_WIDTH; - int h = (int)SCOPE_HEIGHT; - - gpu::Batch& batch = *renderArgs->_batch; - auto geometryCache = DependencyManager::get(); - - // Grid uses its own pipeline, so draw it before setting another - const float GRID_EDGE = 0.005f; - geometryCache->renderGrid(batch, glm::vec2(x, y), glm::vec2(x + w, y + h), - gridRows, gridCols, GRID_EDGE, gridColor, true, _audioScopeGrid); - - geometryCache->useSimpleDrawPipeline(batch); - auto textureCache = DependencyManager::get(); - batch.setResourceTexture(0, textureCache->getWhiteTexture()); - - // FIXME - do we really need to reset this here? we know that we're called inside of ApplicationOverlay::renderOverlays - // which already set up our batch for us to have these settings - mat4 legacyProjection = glm::ortho(0, width, height, 0, -1000, 1000); - batch.setProjectionTransform(legacyProjection); - batch.setModelTransform(Transform()); - batch.resetViewTransform(); - - geometryCache->renderQuad(batch, x, y, w, h, backgroundColor, _audioScopeBackground); - renderLineStrip(batch, _inputID, inputColor, x, y, _samplesPerScope, _scopeInputOffset, _scopeInput); - renderLineStrip(batch, _outputLeftID, outputLeftColor, x, y, _samplesPerScope, _scopeOutputOffset, _scopeOutputLeft); - renderLineStrip(batch, _outputRightD, outputRightColor, x, y, _samplesPerScope, _scopeOutputOffset, _scopeOutputRight); +void AudioScope::setTriggerValues(float x, float y) { + _triggerValues.x = x; + _triggerValues.y = y; } -void AudioScope::renderLineStrip(gpu::Batch& batch, int id, const glm::vec4& color, int x, int y, int n, int offset, const QByteArray* byteArray) { - +QVector AudioScope::getScopeVector(const QByteArray* byteArray, int offset) { int16_t sample; - int16_t* samples = ((int16_t*) byteArray->data()) + offset; + QVector points; + if (!_isEnabled || byteArray == NULL) return points; + int16_t* samples = ((int16_t*)byteArray->data()) + offset; int numSamplesToAverage = _framesPerScope / DEFAULT_FRAMES_PER_SCOPE; - int count = (n - offset) / numSamplesToAverage; - int remainder = (n - offset) % numSamplesToAverage; - y += SCOPE_HEIGHT / 2; - - auto geometryCache = DependencyManager::get(); - - QVector points; - + int count = (_samplesPerScope - offset) / numSamplesToAverage; + int remainder = (_samplesPerScope - offset) % numSamplesToAverage; // Compute and draw the sample averages from the offset position for (int i = count; --i >= 0; ) { @@ -173,7 +132,7 @@ void AudioScope::renderLineStrip(gpu::Batch& batch, int id, const glm::vec4& col sample += *samples++; } sample /= numSamplesToAverage; - points << glm::vec2(x++, y - sample); + points << -sample; } // Compute and draw the sample average across the wrap boundary @@ -182,16 +141,17 @@ void AudioScope::renderLineStrip(gpu::Batch& batch, int id, const glm::vec4& col for (int j = remainder; --j >= 0; ) { sample += *samples++; } - - samples = (int16_t*) byteArray->data(); + + samples = (int16_t*)byteArray->data(); for (int j = numSamplesToAverage - remainder; --j >= 0; ) { sample += *samples++; } sample /= numSamplesToAverage; - points << glm::vec2(x++, y - sample); - } else { - samples = (int16_t*) byteArray->data(); + points << -sample; + } + else { + samples = (int16_t*)byteArray->data(); } // Compute and draw the sample average from the beginning to the offset @@ -202,12 +162,46 @@ void AudioScope::renderLineStrip(gpu::Batch& batch, int id, const glm::vec4& col sample += *samples++; } sample /= numSamplesToAverage; - points << glm::vec2(x++, y - sample); + + points << -sample; + } + return points; +} + +bool AudioScope::shouldTrigger(const QVector& scope) { + int threshold = 4; + if (_autoTrigger && _triggerValues.x < scope.size()) { + for (int i = -2*threshold; i < +2*threshold*2; i++) { + int idx = _triggerValues.x + i; + idx = (idx < 0) ? 0 : (idx < scope.size() ? idx : scope.size() - 1); + int dif = abs(_triggerValues.y - scope[idx]); + if (dif < threshold) { + return true; + } + } + } + return false; +} + +void AudioScope::computeInputData() { + _scopeInputData = getScopeVector(_scopeInput, _scopeInputOffset); + if (shouldTrigger(_scopeInputData)) { + _triggerInputData = _scopeInputData; + _isTriggered = true; + } +} + +void AudioScope::computeOutputData() { + _scopeOutputLeftData = getScopeVector(_scopeOutputLeft, _scopeOutputOffset); + if (shouldTrigger(_scopeOutputLeftData)) { + _triggerOutputLeftData = _scopeOutputLeftData; + _isTriggered = true; + } + _scopeOutputRightData = getScopeVector(_scopeOutputRight, _scopeOutputOffset); + if (shouldTrigger(_scopeOutputRightData)) { + _triggerOutputRightData = _scopeOutputRightData; + _isTriggered = true; } - - - geometryCache->updateVertices(id, points, color); - geometryCache->renderVertices(batch, gpu::LINE_STRIP, id); } int AudioScope::addBufferToScope(QByteArray* byteArray, int frameOffset, const int16_t* source, int sourceSamplesPerChannel, @@ -231,7 +225,7 @@ int AudioScope::addBufferToScope(QByteArray* byteArray, int frameOffset, const i } int AudioScope::addSilenceToScope(QByteArray* byteArray, int frameOffset, int silentSamples) { - + // Short int pointer to mapped samples in byte array int16_t* destination = (int16_t*)byteArray->data(); @@ -271,6 +265,7 @@ void AudioScope::addStereoSamplesToScope(const QByteArray& samples) { _scopeOutputOffset = addBufferToScope(_scopeOutputRight, _scopeOutputOffset, samplesData, samplesPerChannel, 1, AudioConstants::STEREO); _scopeLastFrame = samples.right(AudioConstants::NETWORK_FRAME_BYTES_STEREO); + computeOutputData(); } void AudioScope::addLastFrameRepeatedWithFadeToScope(int samplesPerChannel) { @@ -302,4 +297,5 @@ void AudioScope::addInputToScope(const QByteArray& inputSamples) { _scopeInputOffset = addBufferToScope(_scopeInput, _scopeInputOffset, reinterpret_cast(inputSamples.data()), inputSamples.size() / sizeof(int16_t), INPUT_AUDIO_CHANNEL, NUM_INPUT_CHANNELS); + computeInputData(); } diff --git a/interface/src/audio/AudioScope.h b/interface/src/audio/AudioScope.h index e0c8840bb2..dfc198279f 100644 --- a/interface/src/audio/AudioScope.h +++ b/interface/src/audio/AudioScope.h @@ -24,14 +24,21 @@ class AudioScope : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY + + Q_PROPERTY(QVector scopeInput READ getScopeInput) + Q_PROPERTY(QVector scopeOutputLeft READ getScopeOutputLeft) + Q_PROPERTY(QVector scopeOutputRight READ getScopeOutputRight) + + Q_PROPERTY(QVector triggerInput READ getTriggerInput) + Q_PROPERTY(QVector triggerOutputLeft READ getTriggerOutputLeft) + Q_PROPERTY(QVector triggerOutputRight READ getTriggerOutputRight) + public: // Audio scope methods for allocation/deallocation void allocateScope(); void freeScope(); void reallocateScope(int frames); - void render(RenderArgs* renderArgs, int width, int height); - public slots: void toggle() { setVisible(!_isEnabled); } void setVisible(bool visible); @@ -41,9 +48,33 @@ public slots: void setPause(bool paused) { _isPaused = paused; } bool getPause() { return _isPaused; } + void toggleTrigger() { _autoTrigger = !_autoTrigger; } + bool getAutoTrigger() { return _autoTrigger; } + void setAutoTrigger(bool autoTrigger) { + _isTriggered = false; + _autoTrigger = autoTrigger; + } + + void setTriggered(bool triggered) { _isTriggered = triggered; } + bool getTriggered() { return _isTriggered; } + void selectAudioScopeFiveFrames(); void selectAudioScopeTwentyFrames(); void selectAudioScopeFiftyFrames(); + + void setEnabled(bool enabled) { _isEnabled = enabled; } + bool getEnabled() { return _isEnabled; } + const int getAudioScopeBackground() { return _audioScopeBackground; } + + QVector getScopeInput() { return _scopeInputData; }; + QVector getScopeOutputLeft() { return _scopeOutputLeftData; }; + QVector getScopeOutputRight() { return _scopeOutputRightData; }; + + QVector getTriggerInput() { return _triggerInputData; }; + QVector getTriggerOutputLeft() { return _triggerOutputLeftData; }; + QVector getTriggerOutputRight() { return _triggerOutputRightData; }; + + void setTriggerValues(float x, float y); protected: AudioScope(); @@ -55,16 +86,21 @@ private slots: void addInputToScope(const QByteArray& inputSamples); private: - // Audio scope methods for rendering - void renderLineStrip(gpu::Batch& batch, int id, const glm::vec4& color, int x, int y, int n, int offset, const QByteArray* byteArray); // Audio scope methods for data acquisition int addBufferToScope(QByteArray* byteArray, int frameOffset, const int16_t* source, int sourceSamples, unsigned int sourceChannel, unsigned int sourceNumberOfChannels, float fade = 1.0f); int addSilenceToScope(QByteArray* byteArray, int frameOffset, int silentSamples); + QVector getScopeVector(const QByteArray* scope, int offset); + + bool shouldTrigger(const QVector& scope); + void computeInputData(); + void computeOutputData(); + bool _isEnabled; bool _isPaused; + bool _isTriggered; int _scopeInputOffset; int _scopeOutputOffset; int _framesPerScope; @@ -73,6 +109,17 @@ private: QByteArray* _scopeOutputLeft; QByteArray* _scopeOutputRight; QByteArray _scopeLastFrame; + + QVector _scopeInputData; + QVector _scopeOutputLeftData; + QVector _scopeOutputRightData; + + QVector _triggerInputData; + QVector _triggerOutputLeftData; + QVector _triggerOutputRightData; + + bool _autoTrigger; + glm::vec2 _triggerValues; int _audioScopeBackground; int _audioScopeGrid; diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index a99fe002ee..52b53a3298 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -82,7 +82,6 @@ void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) { // Now render the overlay components together into a single texture renderDomainConnectionStatusBorder(renderArgs); // renders the connected domain line - renderAudioScope(renderArgs); // audio scope in the very back - NOTE: this is the debug audio scope, not the VU meter renderOverlays(renderArgs); // renders Scripts Overlay and AudioScope renderQmlUi(renderArgs); // renders a unit quad with the QML UI texture, and the text overlays from scripts }); @@ -118,25 +117,6 @@ void ApplicationOverlay::renderQmlUi(RenderArgs* renderArgs) { geometryCache->renderUnitQuad(batch, glm::vec4(1), _qmlGeometryId); } -void ApplicationOverlay::renderAudioScope(RenderArgs* renderArgs) { - PROFILE_RANGE(app, __FUNCTION__); - - gpu::Batch& batch = *renderArgs->_batch; - auto geometryCache = DependencyManager::get(); - geometryCache->useSimpleDrawPipeline(batch); - auto textureCache = DependencyManager::get(); - batch.setResourceTexture(0, textureCache->getWhiteTexture()); - int width = renderArgs->_viewport.z; - int height = renderArgs->_viewport.w; - mat4 legacyProjection = glm::ortho(0, width, height, 0, ORTHO_NEAR_CLIP, ORTHO_FAR_CLIP); - batch.setProjectionTransform(legacyProjection); - batch.setModelTransform(Transform()); - batch.resetViewTransform(); - - // Render the audio scope - DependencyManager::get()->render(renderArgs, width, height); -} - void ApplicationOverlay::renderOverlays(RenderArgs* renderArgs) { PROFILE_RANGE(app, __FUNCTION__); diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index af4d8779d4..0d30123c61 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -32,7 +32,6 @@ private: void renderStatsAndLogs(RenderArgs* renderArgs); void renderDomainConnectionStatusBorder(RenderArgs* renderArgs); void renderQmlUi(RenderArgs* renderArgs); - void renderAudioScope(RenderArgs* renderArgs); void renderOverlays(RenderArgs* renderArgs); void buildFramebufferObject(); From 5bf83671263fae9b98bab3283f797e38e594ac2c Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Wed, 22 Nov 2017 18:17:50 -0700 Subject: [PATCH 08/47] add resources --- interface/resources/qml/AudioScope.qml | 268 ++++++++++++++++++ .../developer/utilities/audio/audioScope.js | 10 + 2 files changed, 278 insertions(+) create mode 100644 interface/resources/qml/AudioScope.qml create mode 100644 scripts/developer/utilities/audio/audioScope.js diff --git a/interface/resources/qml/AudioScope.qml b/interface/resources/qml/AudioScope.qml new file mode 100644 index 0000000000..8956a20643 --- /dev/null +++ b/interface/resources/qml/AudioScope.qml @@ -0,0 +1,268 @@ +// +// AudioScope.qml +// +// Created by Luis Cuenca on 11/22/2017 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "styles-uit" +import "controls-uit" as HifiControlsUit + +Item { + id: root + width: parent.width + height: parent.height + + property var _scopedata + property var _triggerdata + property var _triggerValues + property var _triggered + property var _steps + + Component.onCompleted: { + // createValues(); + _triggerValues = { x: width/2, y: height/3 } + _triggered = false + _steps = 5 + } + + + function pullFreshValues() { + if (!AudioScope.getPause()){ + _scopedata = AudioScope.scopeInput + if (AudioScope.getTriggered()) { + _triggered = true + _triggerdata = AudioScope.triggerInput + } + } + mycanvas.requestPaint() + } + + Timer { + interval: 10; running: true; repeat: true + onTriggered: pullFreshValues() + } + + Canvas { + id: mycanvas + anchors.fill:parent + + onPaint: { + var lineHeight = 12; + + function displayTrigger(ctx) { + var size = 3; + ctx.lineWidth="3"; + ctx.strokeStyle="#EFB400"; + ctx.beginPath(); + ctx.moveTo(_triggerValues.x - (size + 2), _triggerValues.y); + ctx.lineTo(_triggerValues.x - 2, _triggerValues.y); + ctx.moveTo(_triggerValues.x + 2, _triggerValues.y); + ctx.lineTo(_triggerValues.x + (size + 2), _triggerValues.y); + + ctx.moveTo(_triggerValues.x, _triggerValues.y - (size + 2)); + ctx.lineTo(_triggerValues.x, _triggerValues.y - 2); + ctx.moveTo(_triggerValues.x, _triggerValues.y + 2); + ctx.lineTo(_triggerValues.x, _triggerValues.y + (size + 2)); + + ctx.stroke(); + } + + function displayBackground(ctx, datawidth, steps, color) { + ctx.fillStyle = Qt.rgba(0, 0, 0, 1); + ctx.fillRect(0, 0, width, height); + + ctx.strokeStyle= color; + ctx.lineWidth="1"; + + ctx.moveTo(0, height/2); + ctx.lineTo(datawidth, height/2); + + var gap = datawidth/steps; + for (var i = 0; i < steps; i++) { + ctx.moveTo(i*gap + 1, 100); + ctx.lineTo(i*gap + 1, height-100); + } + ctx.moveTo(datawidth-1, 100); + ctx.lineTo(datawidth-1, height-100); + ctx.stroke(); + + } + + function drawScope(ctx, data, width, color) { + ctx.beginPath(); + ctx.strokeStyle = color; // Green path + ctx.lineWidth=width; + var x = 0; + for (var i = 0; i < data.length-1; i++) { + ctx.moveTo(x, data[i] + height/2); + ctx.lineTo(++x, data[i+1] + height/2); + } + ctx.stroke(); + } + + + + var ctx = getContext("2d"); + + displayBackground(ctx, _scopedata.length, _steps, "#555555"); + + drawScope(ctx, _scopedata, "2", "#00B4EF"); + if (_triggered) { + drawScope(ctx, _triggerdata, "1", "#EF0000"); + } + if (AudioScope.getAutoTrigger()) { + displayTrigger(ctx); + } + } + } + + MouseArea { + id: hitbox + anchors.fill: mycanvas + hoverEnabled: true + onClicked: { + _triggerValues.x = mouseX + _triggerValues.y = mouseY + AudioScope.setTriggerValues(mouseX, mouseY-height/2); + } + } + + HifiControlsUit.CheckBox { + id: activated + boxSize: 20 + anchors.top: parent.top; + anchors.left: parent.left; + anchors.topMargin: 20; + anchors.leftMargin: 20; + checked: AudioScope.getEnabled(); + onCheckedChanged: { + AudioScope.setEnabled(checked); + activelabel.text = AudioScope.getEnabled() ? "On" : "Off" + } + } + + HifiControlsUit.Label { + id: activelabel + text: AudioScope.getEnabled() ? "On" : "Off" + anchors.top: activated.top; + anchors.left: activated.right; + } + + HifiControlsUit.Button { + id: pauseButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.right: parent.right; + anchors.bottom: parent.bottom; + anchors.rightMargin: 30; + anchors.bottomMargin: 8; + height: 26; + text: "Pause Scope"; + onClicked: { + AudioScope.togglePause(); + pauseButton.text = AudioScope.getPause() ? "Continue Scope" : "Pause Scope"; + } + } + + + + HifiControlsUit.CheckBox { + id: twentyFrames + boxSize: 20 + anchors.horizontalCenter: parent.horizontalCenter; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 8; + onCheckedChanged: { + if (checked){ + fiftyFrames.checked = false; + fiveFrames.checked = false; + AudioScope.selectAudioScopeTwentyFrames(); + _steps = 20; + _triggered = false; + AudioScope.setTriggered(false); + } + } + } + HifiControlsUit.Label { + text: "20 Frames"; + anchors.horizontalCenter: twentyFrames.horizontalCenter; + anchors.bottom: twentyFrames.top; + } + + HifiControlsUit.CheckBox { + id: fiveFrames + boxSize: 20 + anchors.bottom: twentyFrames.bottom; + anchors.right: twentyFrames.left; + anchors.rightMargin: 80; + checked: true; + onCheckedChanged: { + if (checked) { + fiftyFrames.checked = false; + twentyFrames.checked = false; + AudioScope.selectAudioScopeFiveFrames(); + _steps = 5; + _triggered = false; + AudioScope.setTriggered(false); + } + } + } + + HifiControlsUit.Label { + text: "5 Frames"; + anchors.horizontalCenter: fiveFrames.horizontalCenter; + anchors.bottom: fiveFrames.top; + } + + HifiControlsUit.CheckBox { + id: fiftyFrames + boxSize: 20 + anchors.bottom: twentyFrames.bottom; + anchors.left: twentyFrames.right; + anchors.leftMargin: 80; + onCheckedChanged: { + if (checked) { + twentyFrames.checked = false; + fiveFrames.checked = false; + AudioScope.selectAudioScopeFiftyFrames(); + _steps = 50; + _triggered = false; + AudioScope.setTriggered(false); + } + } + } + + HifiControlsUit.Label { + text: "50 Frames"; + anchors.horizontalCenter: fiftyFrames.horizontalCenter; + anchors.bottom: fiftyFrames.top; + } + + HifiControlsUit.Switch { + id: triggerSwitch; + height: 26; + anchors.left: parent.left; + anchors.bottom: parent.bottom; + anchors.leftMargin: 75; + anchors.bottomMargin: 8; + labelTextOff: "Off"; + labelTextOn: "On"; + onCheckedChanged: { + if (!checked) AudioScope.setPause(false); + _triggered = false; + AudioScope.setTriggered(false); + AudioScope.setAutoTrigger(checked); + } + } + HifiControlsUit.Label { + text: "Trigger"; + anchors.left: triggerSwitch.left; + anchors.leftMargin: -15; + anchors.bottom: triggerSwitch.top; + } +} diff --git a/scripts/developer/utilities/audio/audioScope.js b/scripts/developer/utilities/audio/audioScope.js new file mode 100644 index 0000000000..3331482fbd --- /dev/null +++ b/scripts/developer/utilities/audio/audioScope.js @@ -0,0 +1,10 @@ +var qml = Script.resourcesPath() + '/qml/AudioScope.qml'; +var viewdim = Controller.getViewportDimensions(); +var window = new OverlayWindow({ + title: 'Audio Scope', + source: qml, + width: 1200, + height: 500 +}); +//window.setPosition(0.1*viewdim, 0.2*viewdim); +window.closed.connect(function () { Script.stop(); }); \ No newline at end of file From 69299bdd68353c6ec9881986dfd8d39c4cfd5c6e Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Wed, 22 Nov 2017 19:25:24 -0700 Subject: [PATCH 09/47] try to fix errors --- interface/src/audio/AudioScope.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/interface/src/audio/AudioScope.h b/interface/src/audio/AudioScope.h index dfc198279f..a0f1a620cb 100644 --- a/interface/src/audio/AudioScope.h +++ b/interface/src/audio/AudioScope.h @@ -64,7 +64,6 @@ public slots: void setEnabled(bool enabled) { _isEnabled = enabled; } bool getEnabled() { return _isEnabled; } - const int getAudioScopeBackground() { return _audioScopeBackground; } QVector getScopeInput() { return _scopeInputData; }; QVector getScopeOutputLeft() { return _scopeOutputLeftData; }; @@ -101,10 +100,12 @@ private: bool _isEnabled; bool _isPaused; bool _isTriggered; + bool _autoTrigger; int _scopeInputOffset; int _scopeOutputOffset; int _framesPerScope; int _samplesPerScope; + QByteArray* _scopeInput; QByteArray* _scopeOutputLeft; QByteArray* _scopeOutputRight; @@ -118,7 +119,7 @@ private: QVector _triggerOutputLeftData; QVector _triggerOutputRightData; - bool _autoTrigger; + glm::vec2 _triggerValues; int _audioScopeBackground; From 027d2d323c18289b1f8b9aeb5fa08c26ecf241df Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Sun, 26 Nov 2017 22:28:34 -0700 Subject: [PATCH 10/47] Measure tool and corrections --- interface/resources/qml/AudioScope.qml | 403 ++++++++++++++++++----- interface/src/Menu.cpp | 11 +- interface/src/audio/AudioScope.cpp | 35 +- interface/src/audio/AudioScope.h | 27 +- libraries/audio-client/src/AudioClient.h | 3 + 5 files changed, 372 insertions(+), 107 deletions(-) diff --git a/interface/resources/qml/AudioScope.qml b/interface/resources/qml/AudioScope.qml index 8956a20643..6f78a2d820 100644 --- a/interface/resources/qml/AudioScope.qml +++ b/interface/resources/qml/AudioScope.qml @@ -17,33 +17,92 @@ Item { width: parent.width height: parent.height - property var _scopedata - property var _triggerdata - property var _triggerValues - property var _triggered + property var _scopeInputData + property var _scopeOutputLeftData + property var _scopeOutputRightData + + property var _triggerInputData + property var _triggerOutputLeftData + property var _triggerOutputRightData + + property var _triggerValues: QtObject{ + property int x: parent.width/2 + property int y: parent.height/3 + } + + property var _triggered: false property var _steps + property var _refreshMs: 10 + property var _framesPerSecond: AudioScope.getFramesPerSecond() + property var _isFrameUnits: true - Component.onCompleted: { - // createValues(); - _triggerValues = { x: width/2, y: height/3 } - _triggered = false - _steps = 5 + property var _holdStart: QtObject{ + property int x: 0 + property int y: 0 } + property var _holdEnd: QtObject{ + property int x: 0 + property int y: 0 + } + property var _timeBeforeHold: 100; + property var _pressedTime: 0; + property var _isPressed: false; + + function isHolding() { + return (_pressedTime > _timeBeforeHold); + } + + function updateMeasureUnits() { + timeButton.text = _isFrameUnits ? "Display Frames" : "Milliseconds"; + fiveLabel.text = _isFrameUnits ? "5" : "" + (Math.round(1000 * 5.0/_framesPerSecond)); + twentyLabel.text = _isFrameUnits ? "20" : "" + (Math.round(1000 * 20.0/_framesPerSecond)); + fiftyLabel.text = _isFrameUnits ? "50" : "" + (Math.round(1000 * 50.0/_framesPerSecond)); + } + + function collectScopeData() { + if (inputCh.checked) { + _scopeInputData = AudioScope.scopeInput; + } + if (outputLeftCh.checked) { + _scopeOutputLeftData = AudioScope.scopeOutputLeft; + } + if (outputRightCh.checked) { + _scopeOutputRightData = AudioScope.scopeOutputRight; + } + } + + function collectTriggerData() { + if (inputCh.checked) { + _triggerInputData = AudioScope.triggerInput; + } + if (outputLeftCh.checked) { + _triggerOutputLeftData = AudioScope.triggerOutputLeft; + } + if (outputRightCh.checked) { + _triggerOutputRightData = AudioScope.triggerOutputRight; + } + } + function pullFreshValues() { - if (!AudioScope.getPause()){ - _scopedata = AudioScope.scopeInput + if (!AudioScope.getPause()){ if (AudioScope.getTriggered()) { - _triggered = true - _triggerdata = AudioScope.triggerInput - } + _triggered = true; + collectTriggerData(); + } else { + collectScopeData(); + } } - mycanvas.requestPaint() + if (inputCh.checked || outputLeftCh.checked || outputRightCh.checked) { + mycanvas.requestPaint(); + } } + + Timer { - interval: 10; running: true; repeat: true + interval: _refreshMs; running: true; repeat: true onTriggered: pullFreshValues() } @@ -52,51 +111,74 @@ Item { anchors.fill:parent onPaint: { - var lineHeight = 12; - - function displayTrigger(ctx) { - var size = 3; - ctx.lineWidth="3"; - ctx.strokeStyle="#EFB400"; - ctx.beginPath(); - ctx.moveTo(_triggerValues.x - (size + 2), _triggerValues.y); - ctx.lineTo(_triggerValues.x - 2, _triggerValues.y); - ctx.moveTo(_triggerValues.x + 2, _triggerValues.y); - ctx.lineTo(_triggerValues.x + (size + 2), _triggerValues.y); + + function displayMeasureArea(ctx) { - ctx.moveTo(_triggerValues.x, _triggerValues.y - (size + 2)); - ctx.lineTo(_triggerValues.x, _triggerValues.y - 2); - ctx.moveTo(_triggerValues.x, _triggerValues.y + 2); - ctx.lineTo(_triggerValues.x, _triggerValues.y + (size + 2)); + ctx.fillStyle = Qt.rgba(0.1, 0.1, 0.1, 1); + ctx.fillRect(_holdStart.x, 0, _holdEnd.x - _holdStart.x, height); + + ctx.lineWidth = "2"; + ctx.strokeStyle = "#555555"; + + ctx.beginPath(); + ctx.moveTo(_holdStart.x, 0); + ctx.lineTo(_holdStart.x, height); + ctx.moveTo(_holdEnd.x, 0); + ctx.lineTo(_holdEnd.x, height); + + ctx.moveTo(_holdStart.x, _holdStart.y); + ctx.lineTo(_holdEnd.x, _holdStart.y); + ctx.moveTo(_holdEnd.x, _holdEnd.y); + ctx.lineTo(_holdStart.x, _holdEnd.y); + + ctx.stroke(); + } + + function displayTrigger(ctx, lineWidth, color) { + var crossSize = 3; + var holeSize = 2; + + ctx.lineWidth = lineWidth; + ctx.strokeStyle = color; + + ctx.beginPath(); + ctx.moveTo(_triggerValues.x - (crossSize + holeSize), _triggerValues.y); + ctx.lineTo(_triggerValues.x - holeSize, _triggerValues.y); + ctx.moveTo(_triggerValues.x + holeSize, _triggerValues.y); + ctx.lineTo(_triggerValues.x + (crossSize + holeSize), _triggerValues.y); + + ctx.moveTo(_triggerValues.x, _triggerValues.y - (crossSize + holeSize)); + ctx.lineTo(_triggerValues.x, _triggerValues.y - holeSize); + ctx.moveTo(_triggerValues.x, _triggerValues.y + holeSize); + ctx.lineTo(_triggerValues.x, _triggerValues.y + (crossSize + holeSize)); ctx.stroke(); } - function displayBackground(ctx, datawidth, steps, color) { - ctx.fillStyle = Qt.rgba(0, 0, 0, 1); - ctx.fillRect(0, 0, width, height); + function displayBackground(ctx, datawidth, steps, lineWidth, color) { + var verticalPadding = 100; - ctx.strokeStyle= color; - ctx.lineWidth="1"; + ctx.strokeStyle = color; + ctx.lineWidth = lineWidth; ctx.moveTo(0, height/2); ctx.lineTo(datawidth, height/2); var gap = datawidth/steps; for (var i = 0; i < steps; i++) { - ctx.moveTo(i*gap + 1, 100); - ctx.lineTo(i*gap + 1, height-100); + ctx.moveTo(i*gap + 1, verticalPadding); + ctx.lineTo(i*gap + 1, height-verticalPadding); } - ctx.moveTo(datawidth-1, 100); - ctx.lineTo(datawidth-1, height-100); - ctx.stroke(); + ctx.moveTo(datawidth-1, verticalPadding); + ctx.lineTo(datawidth-1, height-verticalPadding); + ctx.stroke(); } function drawScope(ctx, data, width, color) { ctx.beginPath(); - ctx.strokeStyle = color; // Green path - ctx.lineWidth=width; + ctx.strokeStyle = color; + ctx.lineWidth = width; var x = 0; for (var i = 0; i < data.length-1; i++) { ctx.moveTo(x, data[i] + height/2); @@ -105,18 +187,91 @@ Item { ctx.stroke(); } + function getMeasurementText(dist) { + var datasize = _scopeInputData.length; + var value = 0; + if (fiveFrames.checked) { + value = (_isFrameUnits) ? 5.0*dist/datasize : (Math.round(1000 * 5.0/_framesPerSecond))*dist/datasize; + } else if (twentyFrames.checked) { + value = (_isFrameUnits) ? 20.0*dist/datasize : (Math.round(1000 * 20.0/_framesPerSecond))*dist/datasize; + } else if (fiftyFrames.checked) { + value = (_isFrameUnits) ? 50.0*dist/datasize : (Math.round(1000 * 50.0/_framesPerSecond))*dist/datasize; + } + value = Math.abs(Math.round(value*100)/100); + var measureText = "" + value + (_isFrameUnits ? " frames" : " milliseconds"); + return measureText; + } + function drawMeasurements(ctx, color) { + ctx.fillStyle = color; + ctx.font = "normal 16px sans-serif"; + var fontwidth = 8; + var measureText = getMeasurementText(_holdEnd.x - _holdStart.x); + if (_holdStart.x < _holdEnd.x) { + ctx.fillText("" + height/2 - _holdStart.y, _holdStart.x-40, _holdStart.y); + ctx.fillText("" + height/2 - _holdEnd.y, _holdStart.x-40, _holdEnd.y); + ctx.fillText(measureText, _holdEnd.x+10, _holdEnd.y); + } else { + ctx.fillText("" + height/2 - _holdStart.y, _holdStart.x+10, _holdStart.y); + ctx.fillText("" + height/2 - _holdEnd.y, _holdStart.x+10, _holdEnd.y); + ctx.fillText(measureText, _holdEnd.x-fontwidth*measureText.length, _holdEnd.y); + } + } var ctx = getContext("2d"); - - displayBackground(ctx, _scopedata.length, _steps, "#555555"); + + ctx.fillStyle = Qt.rgba(0, 0, 0, 1); + ctx.fillRect(0, 0, width, height); - drawScope(ctx, _scopedata, "2", "#00B4EF"); - if (_triggered) { - drawScope(ctx, _triggerdata, "1", "#EF0000"); + if (isHolding()) { + displayMeasureArea(ctx); } + + var guideLinesColor = "#555555" + var guideLinesWidth = "1" + + displayBackground(ctx, _scopeInputData.length, _steps, guideLinesWidth, guideLinesColor); + + var triggerWidth = "3" + var triggerColor = "#EFB400" + if (AudioScope.getAutoTrigger()) { - displayTrigger(ctx); + displayTrigger(ctx, triggerWidth, triggerColor); + } + + var scopeWidth = "2" + var scopeInputColor = "#00B4EF" + var scopeOutputLeftColor = "#BB0000" + var scopeOutputRightColor = "#00BB00" + + if (!_triggered) { + if (inputCh.checked) { + drawScope(ctx, _scopeInputData, scopeWidth, scopeInputColor); + } + if (outputLeftCh.checked) { + drawScope(ctx, _scopeOutputLeftData, scopeWidth, scopeOutputLeftColor); + } + if (outputRightCh.checked) { + drawScope(ctx, _scopeOutputRightData, scopeWidth, scopeOutputRightColor); + } + } else { + if (inputCh.checked) { + drawScope(ctx, _triggerInputData, scopeWidth, scopeInputColor); + } + if (outputLeftCh.checked) { + drawScope(ctx, _triggerOutputLeftData, scopeWidth, scopeOutputLeftColor); + } + if (outputRightCh.checked) { + drawScope(ctx, _triggerOutputRightData, scopeWidth, scopeOutputRightColor); + } + } + + if (isHolding()) { + drawMeasurements(ctx, "#eeeeee"); + } + + if (_isPressed) { + _pressedTime += _refreshMs; } } } @@ -125,10 +280,24 @@ Item { id: hitbox anchors.fill: mycanvas hoverEnabled: true - onClicked: { - _triggerValues.x = mouseX - _triggerValues.y = mouseY - AudioScope.setTriggerValues(mouseX, mouseY-height/2); + onPressed: { + _isPressed = true; + _pressedTime = 0; + _holdStart.x = mouseX; + _holdStart.y = mouseY; + } + onPositionChanged: { + _holdEnd.x = mouseX; + _holdEnd.y = mouseY; + } + onReleased: { + if (!isHolding() && AudioScope.getAutoTrigger()) { + _triggerValues.x = mouseX + _triggerValues.y = mouseY + AudioScope.setTriggerValues(mouseX, mouseY-height/2); + } + _isPressed = false; + _pressedTime = 0; } } @@ -139,18 +308,60 @@ Item { anchors.left: parent.left; anchors.topMargin: 20; anchors.leftMargin: 20; - checked: AudioScope.getEnabled(); + checked: AudioScope.getVisible(); + onCheckedChanged: { + AudioScope.setVisible(!AudioScope.getVisible()); + activelabel.text = AudioScope.getVisible() ? "On" : "Off" + } + } + + HifiControlsUit.Label { + id: activelabel + text: AudioScope.getVisible() ? "On" : "Off" + anchors.top: activated.top; + anchors.left: activated.right; + } + + HifiControlsUit.CheckBox { + id: outputLeftCh + boxSize: 20 + text: "Output L" + anchors.horizontalCenter: parent.horizontalCenter; + anchors.top: parent.top; + anchors.topMargin: 8; + onCheckedChanged: { + AudioScope.setServerEcho(outputLeftCh.checked || outputRightCh.checked); + } + } + HifiControlsUit.Label { + text: "Channels"; + anchors.horizontalCenter: outputLeftCh.horizontalCenter; + anchors.bottom: outputLeftCh.top; + anchors.bottomMargin: 8; + } + + HifiControlsUit.CheckBox { + id: inputCh + boxSize: 20 + text: "Input Mono" + anchors.bottom: outputLeftCh.bottom; + anchors.right: outputLeftCh.left; + anchors.rightMargin: 80; + checked: true; onCheckedChanged: { - AudioScope.setEnabled(checked); - activelabel.text = AudioScope.getEnabled() ? "On" : "Off" } } - HifiControlsUit.Label { - id: activelabel - text: AudioScope.getEnabled() ? "On" : "Off" - anchors.top: activated.top; - anchors.left: activated.right; + HifiControlsUit.CheckBox { + id: outputRightCh + boxSize: 20 + text: "Output R" + anchors.bottom: outputLeftCh.bottom; + anchors.left: outputLeftCh.right; + anchors.leftMargin: 80; + onCheckedChanged: { + AudioScope.setServerEcho(outputLeftCh.checked || outputRightCh.checked); + } } HifiControlsUit.Button { @@ -162,19 +373,16 @@ Item { anchors.rightMargin: 30; anchors.bottomMargin: 8; height: 26; - text: "Pause Scope"; + text: " Pause "; onClicked: { AudioScope.togglePause(); - pauseButton.text = AudioScope.getPause() ? "Continue Scope" : "Pause Scope"; } } - - HifiControlsUit.CheckBox { id: twentyFrames boxSize: 20 - anchors.horizontalCenter: parent.horizontalCenter; + anchors.left: parent.horizontalCenter; anchors.bottom: parent.bottom; anchors.bottomMargin: 8; onCheckedChanged: { @@ -188,18 +396,35 @@ Item { } } } + HifiControlsUit.Label { - text: "20 Frames"; + id:twentyLabel + anchors.left: twentyFrames.right; + anchors.verticalCenter: twentyFrames.verticalCenter; + } + + HifiControlsUit.Button { + id: timeButton; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + text: "Display Frames"; anchors.horizontalCenter: twentyFrames.horizontalCenter; anchors.bottom: twentyFrames.top; + anchors.bottomMargin: 8; + height: 26; + onClicked: { + _isFrameUnits = !_isFrameUnits; + updateMeasureUnits(); + } } HifiControlsUit.CheckBox { id: fiveFrames boxSize: 20 - anchors.bottom: twentyFrames.bottom; - anchors.right: twentyFrames.left; - anchors.rightMargin: 80; + anchors.horizontalCenter: parent.horizontalCenter; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 8; + anchors.horizontalCenterOffset: -50; checked: true; onCheckedChanged: { if (checked) { @@ -214,17 +439,18 @@ Item { } HifiControlsUit.Label { - text: "5 Frames"; - anchors.horizontalCenter: fiveFrames.horizontalCenter; - anchors.bottom: fiveFrames.top; + id:fiveLabel + anchors.left: fiveFrames.right; + anchors.verticalCenter: fiveFrames.verticalCenter; } HifiControlsUit.CheckBox { id: fiftyFrames boxSize: 20 - anchors.bottom: twentyFrames.bottom; - anchors.left: twentyFrames.right; - anchors.leftMargin: 80; + anchors.horizontalCenter: parent.horizontalCenter; + anchors.bottom: parent.bottom; + anchors.bottomMargin: 8; + anchors.horizontalCenterOffset: 70; onCheckedChanged: { if (checked) { twentyFrames.checked = false; @@ -238,9 +464,9 @@ Item { } HifiControlsUit.Label { - text: "50 Frames"; - anchors.horizontalCenter: fiftyFrames.horizontalCenter; - anchors.bottom: fiftyFrames.top; + id:fiftyLabel + anchors.left: fiftyFrames.right; + anchors.verticalCenter: fiftyFrames.verticalCenter; } HifiControlsUit.Switch { @@ -259,10 +485,29 @@ Item { AudioScope.setAutoTrigger(checked); } } + HifiControlsUit.Label { text: "Trigger"; anchors.left: triggerSwitch.left; anchors.leftMargin: -15; anchors.bottom: triggerSwitch.top; } + + Component.onCompleted: { + _steps = AudioScope.getFramesPerScope(); + AudioScope.setTriggerValues(_triggerValues.x, _triggerValues.y-root.height/2); + activated.checked = true; + updateMeasureUnits(); + } + + Component.onDestruction: { + AudioScope.setVisible(false); + } + + Connections { + target: AudioScope + onPauseChanged: { + pauseButton.text = AudioScope.getPause() ? "Continue" : " Pause "; + } + } } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 9ec5cc6034..88ae59a803 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -688,8 +688,15 @@ Menu::Menu() { auto scope = DependencyManager::get(); MenuWrapper* audioScopeMenu = audioDebugMenu->addMenu("Audio Scope"); - addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScope, Qt::CTRL | Qt::Key_F2, false, - scope.data(), SLOT(toggle())); + + action = addActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScope, Qt::CTRL | Qt::Key_F2, false); + connect(action, &QAction::triggered, [] { + auto scriptEngines = DependencyManager::get(); + QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation(); + defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/utilities/audio/audioScope.js"); + scriptEngines->loadScript(defaultScriptsLoc.toString()); + }); + addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopePause, Qt::CTRL | Qt::SHIFT | Qt::Key_F2, false, scope.data(), SLOT(togglePause())); diff --git a/interface/src/audio/AudioScope.cpp b/interface/src/audio/AudioScope.cpp index 5fbb5cdaf6..44f25ae5a9 100644 --- a/interface/src/audio/AudioScope.cpp +++ b/interface/src/audio/AudioScope.cpp @@ -22,7 +22,6 @@ #include "AudioScope.h" static const unsigned int DEFAULT_FRAMES_PER_SCOPE = 5; -static const unsigned int SCOPE_WIDTH = AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL * DEFAULT_FRAMES_PER_SCOPE; static const unsigned int MULTIPLIER_SCOPE_HEIGHT = 20; static const unsigned int SCOPE_HEIGHT = 2 * 15 * MULTIPLIER_SCOPE_HEIGHT; @@ -46,6 +45,7 @@ AudioScope::AudioScope() : _outputRightD(DependencyManager::get()->allocateID()) { auto audioIO = DependencyManager::get(); + connect(&audioIO->getReceivedAudioStream(), &MixedProcessedAudioStream::addedSilence, this, &AudioScope::addStereoSilenceToScope); connect(&audioIO->getReceivedAudioStream(), &MixedProcessedAudioStream::addedLastFrameRepeatedWithFade, @@ -78,6 +78,14 @@ void AudioScope::selectAudioScopeFiftyFrames() { reallocateScope(50); } +void AudioScope::setServerEcho(bool serverEcho) { + DependencyManager::get()->setServerEcho(serverEcho); +} + +float AudioScope::getFramesPerSecond(){ + return AudioConstants::NETWORK_FRAMES_PER_SEC; +} + void AudioScope::allocateScope() { _scopeInputOffset = 0; _scopeOutputOffset = 0; @@ -111,11 +119,6 @@ void AudioScope::freeScope() { } } -void AudioScope::setTriggerValues(float x, float y) { - _triggerValues.x = x; - _triggerValues.y = y; -} - QVector AudioScope::getScopeVector(const QByteArray* byteArray, int offset) { int16_t sample; QVector points; @@ -171,7 +174,7 @@ QVector AudioScope::getScopeVector(const QByteArray* byteArray, int offset) bool AudioScope::shouldTrigger(const QVector& scope) { int threshold = 4; if (_autoTrigger && _triggerValues.x < scope.size()) { - for (int i = -2*threshold; i < +2*threshold*2; i++) { + for (int i = -4*threshold; i < +4*threshold; i++) { int idx = _triggerValues.x + i; idx = (idx < 0) ? 0 : (idx < scope.size() ? idx : scope.size() - 1); int dif = abs(_triggerValues.y - scope[idx]); @@ -183,24 +186,28 @@ bool AudioScope::shouldTrigger(const QVector& scope) { return false; } +void AudioScope::storeTriggerValues() { + _triggerInputData = _scopeInputData; + _triggerOutputLeftData = _scopeOutputLeftData; + _triggerOutputRightData = _scopeOutputRightData; + _isTriggered = true; +} + void AudioScope::computeInputData() { _scopeInputData = getScopeVector(_scopeInput, _scopeInputOffset); if (shouldTrigger(_scopeInputData)) { - _triggerInputData = _scopeInputData; - _isTriggered = true; + storeTriggerValues(); } } void AudioScope::computeOutputData() { _scopeOutputLeftData = getScopeVector(_scopeOutputLeft, _scopeOutputOffset); if (shouldTrigger(_scopeOutputLeftData)) { - _triggerOutputLeftData = _scopeOutputLeftData; - _isTriggered = true; + storeTriggerValues(); } _scopeOutputRightData = getScopeVector(_scopeOutputRight, _scopeOutputOffset); if (shouldTrigger(_scopeOutputRightData)) { - _triggerOutputRightData = _scopeOutputRightData; - _isTriggered = true; + storeTriggerValues(); } } @@ -225,7 +232,7 @@ int AudioScope::addBufferToScope(QByteArray* byteArray, int frameOffset, const i } int AudioScope::addSilenceToScope(QByteArray* byteArray, int frameOffset, int silentSamples) { - + // Short int pointer to mapped samples in byte array int16_t* destination = (int16_t*)byteArray->data(); diff --git a/interface/src/audio/AudioScope.h b/interface/src/audio/AudioScope.h index a0f1a620cb..33d88f8fe0 100644 --- a/interface/src/audio/AudioScope.h +++ b/interface/src/audio/AudioScope.h @@ -44,27 +44,25 @@ public slots: void setVisible(bool visible); bool getVisible() const { return _isEnabled; } - void togglePause() { _isPaused = !_isPaused; } - void setPause(bool paused) { _isPaused = paused; } + void togglePause() { setPause(!_isPaused); } + void setPause(bool paused) { _isPaused = paused; emit pauseChanged(); } bool getPause() { return _isPaused; } void toggleTrigger() { _autoTrigger = !_autoTrigger; } bool getAutoTrigger() { return _autoTrigger; } - void setAutoTrigger(bool autoTrigger) { - _isTriggered = false; - _autoTrigger = autoTrigger; - } + void setAutoTrigger(bool autoTrigger) { _isTriggered = false; _autoTrigger = autoTrigger; } + void setTriggerValues(int x, int y) { _triggerValues.x = x; _triggerValues.y = y; } void setTriggered(bool triggered) { _isTriggered = triggered; } bool getTriggered() { return _isTriggered; } + float getFramesPerSecond(); + int getFramesPerScope() { return _framesPerScope; } + void selectAudioScopeFiveFrames(); void selectAudioScopeTwentyFrames(); void selectAudioScopeFiftyFrames(); - void setEnabled(bool enabled) { _isEnabled = enabled; } - bool getEnabled() { return _isEnabled; } - QVector getScopeInput() { return _scopeInputData; }; QVector getScopeOutputLeft() { return _scopeOutputLeftData; }; QVector getScopeOutputRight() { return _scopeOutputRightData; }; @@ -73,8 +71,11 @@ public slots: QVector getTriggerOutputLeft() { return _triggerOutputLeftData; }; QVector getTriggerOutputRight() { return _triggerOutputRightData; }; - void setTriggerValues(float x, float y); - + void setServerEcho(bool serverEcho); + +signals: + void pauseChanged(); + protected: AudioScope(); @@ -97,6 +98,8 @@ private: void computeInputData(); void computeOutputData(); + void storeTriggerValues(); + bool _isEnabled; bool _isPaused; bool _isTriggered; @@ -120,7 +123,7 @@ private: QVector _triggerOutputRightData; - glm::vec2 _triggerValues; + glm::ivec2 _triggerValues; int _audioScopeBackground; int _audioScopeGrid; diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 01a487455c..4dd7436251 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -191,6 +191,9 @@ public slots: bool isNoiseReductionEnabled() const { return _isNoiseGateEnabled; } void toggleLocalEcho() { _shouldEchoLocally = !_shouldEchoLocally; } + + bool getServerEcho() { return _shouldEchoToServer; } + void setServerEcho(bool serverEcho) { _shouldEchoToServer = serverEcho; } void toggleServerEcho() { _shouldEchoToServer = !_shouldEchoToServer; } void processReceivedSamples(const QByteArray& inputBuffer, QByteArray& outputBuffer); From ad02118588ce445c82ebe981cc4f7d5d90b58e91 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Sun, 26 Nov 2017 23:43:13 -0700 Subject: [PATCH 11/47] fixed menu error --- interface/src/Menu.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 88ae59a803..7292594b53 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -689,7 +689,7 @@ Menu::Menu() { auto scope = DependencyManager::get(); MenuWrapper* audioScopeMenu = audioDebugMenu->addMenu("Audio Scope"); - action = addActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScope, Qt::CTRL | Qt::Key_F2, false); + action = addActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScope); connect(action, &QAction::triggered, [] { auto scriptEngines = DependencyManager::get(); QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation(); From d4a4c8902ae069f97fe2f5baafe8cfa733d1929b Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 27 Nov 2017 17:46:19 -0800 Subject: [PATCH 12/47] fix reticle depth at different scales and x button click on mouse press --- .../src/display-plugins/CompositorHelper.cpp | 6 ++-- scripts/system/libraries/WebTablet.js | 34 ++++++------------- 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp index 7b639e8308..f3f81c0b2e 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp @@ -437,9 +437,11 @@ glm::mat4 CompositorHelper::getReticleTransform(const glm::mat4& eyePose, const } else { d = glm::normalize(overlaySurfacePoint); } - reticlePosition = headPosition + (d * getReticleDepth()); + // Our sensor to world matrix always has uniform scale + float sensorSpaceReticleDepth = getReticleDepth() / extractScale(_sensorToWorldMatrix).x; + reticlePosition = headPosition + (d * sensorSpaceReticleDepth); quat reticleOrientation = cancelOutRoll(glm::quat_cast(_currentDisplayPlugin->getHeadPose())); - vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize * getReticleDepth()); + vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize * sensorSpaceReticleDepth); return glm::inverse(eyePose) * createMatFromScaleQuatAndPos(reticleScale, reticleOrientation, reticlePosition); } else { static const float CURSOR_PIXEL_SIZE = 32.0f; diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index 01fdfb1845..4217ec503e 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -514,31 +514,17 @@ WebTablet.prototype.getPosition = function () { }; WebTablet.prototype.mousePressEvent = function (event) { - var pickRay = Camera.computePickRay(event.x, event.y); - var entityPickResults; - entityPickResults = Overlays.findRayIntersection(pickRay, true, [this.tabletEntityID]); - if (entityPickResults.intersects && (entityPickResults.entityID === this.tabletEntityID || - entityPickResults.overlayID === this.tabletEntityID)) { - var overlayPickResults = Overlays.findRayIntersection(pickRay, true, [this.webOverlayID, this.homeButtonID], []); - if (overlayPickResults.intersects && overlayPickResults.overlayID === this.homeButtonID) { - var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - var onHomeScreen = tablet.onHomeScreen(); - var isMessageOpen = tablet.isMessageDialogOpen(); - if (onHomeScreen) { - if (isMessageOpen === false) { - HMD.closeTablet(); - } - } else { - if (isMessageOpen === false) { - tablet.gotoHomeScreen(); - this.setHomeButtonTexture(); - } + if (!HMD.active) { + var pickRay = Camera.computePickRay(event.x, event.y); + var tabletBackPickResults = Overlays.findRayIntersection(pickRay, true, [this.tabletEntityID]); + if (tabletBackPickResults.intersects) { + var overlayPickResults = Overlays.findRayIntersection(pickRay, true, [this.webOverlayID, this.homeButtonID]); + if (!overlayPickResults.intersects) { + this.dragging = true; + var invCameraXform = new Xform(Camera.orientation, Camera.position).inv(); + this.initialLocalIntersectionPoint = invCameraXform.xformPoint(tabletBackPickResults.intersection); + this.initialLocalPosition = Overlays.getProperty(this.tabletEntityID, "localPosition"); } - } else if (!HMD.active && (!overlayPickResults.intersects || overlayPickResults.overlayID !== this.webOverlayID)) { - this.dragging = true; - var invCameraXform = new Xform(Camera.orientation, Camera.position).inv(); - this.initialLocalIntersectionPoint = invCameraXform.xformPoint(entityPickResults.intersection); - this.initialLocalPosition = Overlays.getProperty(this.tabletEntityID, "localPosition"); } } }; From 7327b79ce37076acf4c69567a887dfb9c7ae1931 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 28 Nov 2017 11:37:58 -0800 Subject: [PATCH 13/47] WIP --- interface/src/avatar/MyAvatar.cpp | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index df2089223b..563bf30f76 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1956,11 +1956,14 @@ void MyAvatar::updateOrientation(float deltaTime) { // Use head/HMD roll to turn while flying, but not when standing still. if (qApp->isHMDMode() && getCharacterController()->getState() == CharacterController::State::Hover && _hmdRollControlEnabled && hasDriveInput()) { + // Turn with head roll. const float MIN_CONTROL_SPEED = 0.01f; float speed = glm::length(getWorldVelocity()); if (speed >= MIN_CONTROL_SPEED) { // Feather turn when stopping moving. + + /* AJT hack float speedFactor; if (getDriveKey(TRANSLATE_Z) != 0.0f || _lastDrivenSpeed == 0.0f) { _lastDrivenSpeed = speed; @@ -1968,15 +1971,28 @@ void MyAvatar::updateOrientation(float deltaTime) { } else { speedFactor = glm::min(speed / _lastDrivenSpeed, 1.0f); } + */ float direction = glm::dot(getWorldVelocity(), getWorldOrientation() * Vectors::UNIT_NEG_Z) > 0.0f ? 1.0f : -1.0f; - float rollAngle = glm::degrees(asinf(glm::dot(IDENTITY_UP, _hmdSensorOrientation * IDENTITY_RIGHT))); + float rollAngle = asinf(glm::dot(IDENTITY_UP, _hmdSensorOrientation * IDENTITY_RIGHT)); float rollSign = rollAngle < 0.0f ? -1.0f : 1.0f; rollAngle = fabsf(rollAngle); - rollAngle = rollAngle > _hmdRollControlDeadZone ? rollSign * (rollAngle - _hmdRollControlDeadZone) : 0.0f; + //rollAngle = rollAngle > _hmdRollControlDeadZone ? rollSign * (rollAngle - _hmdRollControlDeadZone) : 0.0f; - totalBodyYaw += speedFactor * direction * rollAngle * deltaTime * _hmdRollControlRate; + const float MAX_ROLL_ANGLE = PI / 2.0f; + const float MAX_ANGULAR_SPEED = 1.5f; // radians per sec + + // apply a quadratic ease in curve. giving less roll and shallow angles and more roll at extreme angles. + float t = glm::clamp(rollAngle, 0.0f, MAX_ROLL_ANGLE) / MAX_ROLL_ANGLE; + float newT = t;//t < 0.5f ? 2.0f * t * t : -1 + (4.0f - 2.0f * t) * t; + float angularSpeed = rollSign * newT * MAX_ANGULAR_SPEED; + + qDebug() << "AJT: rollAngle =" << rollAngle << ", t =" << t << ", newT =" << newT << ", angularSpeed =" << angularSpeed; + + // AJT: remove _hmdRollControlDeadZone, _hmdRollControlRate, _lastDrivenSpeed + + totalBodyYaw += direction * glm::degrees(angularSpeed) * deltaTime; } } From eb0d9250917155ad3953900780c6796f3b9797d4 Mon Sep 17 00:00:00 2001 From: Daniela Date: Tue, 28 Nov 2017 22:55:09 +0000 Subject: [PATCH 14/47] YAW ROLL PITCH tool gizmos + functionality --- .../system/libraries/entitySelectionTool.js | 414 +++++++----------- 1 file changed, 154 insertions(+), 260 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index a66f897b0b..f0f87d6aa2 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -1031,6 +1031,40 @@ SelectionDisplay = (function() { that.updateHandles(); }; + // Function: Calculate New Bound Extremes + // uses dot product to discover new top and bottom on the new referential (max and min) + that.calculateNewBoundExtremes = function(boundPointList, referenceVector) { + + if (boundPointList.length < 2) { + return [null, null]; + } + + var refMax = boundPointList[0]; + var refMin = boundPointList[1]; + + var dotMax = Vec3.dot(boundPointList[0], referenceVector); + var dotMin = Vec3.dot(boundPointList[1], referenceVector); + + if (dotMin > dotMax) { + dotMax = dotMin; + dotMin = Vec3.dot(boundPointList[0], referenceVector); + refMax = boundPointList[1]; + refMin = boundPointList[0]; + } + + for (var i = 2; i < boundPointList.length ; i++) { + var dotAux = Vec3.dot(boundPointList[i], referenceVector); + if (dotAux > dotMax) { + dotMax = dotAux; + refMax = boundPointList[i]; + } else if (dotAux < dotMin) { + dotMin = dotAux; + refMin = boundPointList[i]; + } + } + return [refMin, refMax]; + } + // FUNCTION: UPDATE ROTATION HANDLES that.updateRotationHandles = function() { var diagonal = (Vec3.length(SelectionManager.worldDimensions) / 2) * 1.1; @@ -1043,7 +1077,7 @@ SelectionDisplay = (function() { } else { outerAlpha = 0.5; } - + // prev 0.05 var rotateHandleOffset = 0.05; var top, far, left, bottom, near, right, boundsCenter, objectCenter, BLN, BRN, BLF, TLN, TRN, TLF, TRF; @@ -1088,253 +1122,6 @@ SelectionDisplay = (function() { var cameraPosition = Camera.getPosition(); var look = Vec3.normalize(Vec3.subtract(cameraPosition, objectCenter)); - 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 - }); - - yawCorner = { - x: left + rotateHandleOffset, - y: bottom - rotateHandleOffset, - z: near - rotateHandleOffset - }; - - pitchCorner = { - x: right - rotateHandleOffset, - y: top + rotateHandleOffset, - z: near - 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 - }; - - - 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 - }); - - yawCorner = { - x: left + rotateHandleOffset, - y: bottom - rotateHandleOffset, - z: far + rotateHandleOffset - }; - - pitchCorner = { - x: right - rotateHandleOffset, - y: top + rotateHandleOffset, - z: far + 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 - }; - - 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 - }); - - yawCorner = { - x: right - rotateHandleOffset, - y: bottom - rotateHandleOffset, - z: near - rotateHandleOffset - }; - - pitchCorner = { - x: left + rotateHandleOffset, - y: top + rotateHandleOffset, - z: near - rotateHandleOffset - }; - - rollCorner = { - x: right - rotateHandleOffset, - y: top + rotateHandleOffset, - z: far + 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 - }; - - 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 - }); - - yawCorner = { - x: right - rotateHandleOffset, - y: bottom - rotateHandleOffset, - z: far + rotateHandleOffset - }; - - rollCorner = { - x: right - rotateHandleOffset, - y: top + rotateHandleOffset, - z: near - rotateHandleOffset - }; - - pitchCorner = { - x: left + rotateHandleOffset, - y: top + rotateHandleOffset, - z: far + 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 - }; - - Overlays.editOverlay(pitchHandle, { - url: ROTATE_ARROW_WEST_NORTH_URL - }); - Overlays.editOverlay(rollHandle, { - url: ROTATE_ARROW_WEST_NORTH_URL - }); - - } - } - // place yaw, pitch and roll rotations on the avatar referential yawHandleRotation = Quat.multiply(MyAvatar.orientation , yawHandleRotation); pitchHandleRotation = Quat.multiply(MyAvatar.orientation , pitchHandleRotation); @@ -1345,7 +1132,7 @@ SelectionDisplay = (function() { z: 0 })); var upVector = Quat.getUp(avatarReferential); - var rightVector = Quat.getRight(avatarReferential); + var rightVector = Vec3.multiply(-1, Quat.getRight(avatarReferential)); var frontVector = Quat.getFront(avatarReferential); // Centers var xSign = -1.0; @@ -1357,20 +1144,127 @@ SelectionDisplay = (function() { zSign = 1.0; } - yawCenter = Vec3.sum(boundsCenter, Vec3.multiply(-(dimensions.y / 2), upVector)); - var myBotom = Vec3.multiply(-(dimensions.y / 2) - rotateHandleOffset, upVector); - var myRight = Vec3.multiply(xSign * (dimensions.x / 2) + xSign * rotateHandleOffset, rightVector); - var myFront = Vec3.multiply(zSign * (dimensions.z / 2) + zSign * rotateHandleOffset, frontVector); - yawCorner = Vec3.sum(boundsCenter, Vec3.sum(Vec3.sum(myBotom, myRight), myFront)); + + + + //project all 8 bounding box points (assumes center 0,0,0) onto the new avatar referential + + var projT_UP = Vec3.dot(Vec3.multiply((dimensions.y / 2) + rotateHandleOffset, Vec3.UNIT_Y), upVector); + projT_UP = Vec3.multiply(projT_UP, upVector); + + + var projB_UP = Vec3.dot(Vec3.multiply(-(dimensions.y / 2) - rotateHandleOffset, Vec3.UNIT_Y), upVector); + projB_UP = Vec3.multiply(projB_UP, upVector); + + + var projL_UP = Vec3.dot(Vec3.multiply(xSign * (dimensions.x / 2) + xSign * rotateHandleOffset, Vec3.UNIT_X), upVector); + projL_UP = Vec3.multiply(projL_UP, upVector); + + var projR_UP = Vec3.dot(Vec3.multiply(-xSign * (dimensions.x / 2) - xSign * rotateHandleOffset, Vec3.UNIT_X), upVector); + projR_UP = Vec3.multiply(projR_UP, upVector); + + + var projN_UP = Vec3.dot(Vec3.multiply(zSign * (dimensions.z / 2) + zSign * rotateHandleOffset, Vec3.FRONT), upVector); + projN_UP = Vec3.multiply(projN_UP, upVector); + + var projF_UP = Vec3.dot(Vec3.multiply(-zSign * (dimensions.z / 2) - zSign * rotateHandleOffset, Vec3.FRONT), upVector); + projF_UP = Vec3.multiply(projF_UP, upVector); + + var projUPList = [projT_UP, projB_UP, projL_UP, projR_UP, projN_UP, projF_UP]; + + var projUP = that.calculateNewBoundExtremes(projUPList, upVector); + + //// RIGHT + + var projT_RIGHT = Vec3.dot(Vec3.multiply((dimensions.y / 2) + rotateHandleOffset, Vec3.UNIT_Y), rightVector); + projT_RIGHT = Vec3.multiply(projT_RIGHT, rightVector); + + + var projB_RIGHT = Vec3.dot(Vec3.multiply(-(dimensions.y / 2) - rotateHandleOffset, Vec3.UNIT_Y), rightVector); + projB_RIGHT = Vec3.multiply(projB_RIGHT, rightVector); + + + var projL_RIGHT = Vec3.dot(Vec3.multiply(xSign * (dimensions.x / 2) + xSign * rotateHandleOffset, Vec3.UNIT_X), rightVector); + projL_RIGHT = Vec3.multiply(projL_RIGHT, rightVector); + + var projR_RIGHT = Vec3.dot(Vec3.multiply(-xSign * (dimensions.x / 2) - xSign * rotateHandleOffset, Vec3.UNIT_X), rightVector); + projR_RIGHT = Vec3.multiply(projR_RIGHT, rightVector); + + + var projN_RIGHT = Vec3.dot(Vec3.multiply(zSign * (dimensions.z / 2) + zSign * rotateHandleOffset, Vec3.FRONT), rightVector); + projN_RIGHT = Vec3.multiply(projN_RIGHT, rightVector); + + var projF_RIGHT = Vec3.dot(Vec3.multiply(-zSign * (dimensions.z / 2) - zSign * rotateHandleOffset, Vec3.FRONT), rightVector); + projF_RIGHT = Vec3.multiply(projF_RIGHT, rightVector); + + + var projRIGHTList = [projT_RIGHT, projB_RIGHT, projL_RIGHT, projR_RIGHT, projN_RIGHT, projF_RIGHT]; + + var projRIGHT = that.calculateNewBoundExtremes(projRIGHTList, rightVector); + + //FRONT + + var projT_FRONT = Vec3.dot(Vec3.multiply((dimensions.y / 2) + rotateHandleOffset, Vec3.UNIT_Y), frontVector); + projT_FRONT = Vec3.multiply(projT_FRONT, frontVector); + + + var projB_FRONT = Vec3.dot(Vec3.multiply(-(dimensions.y / 2) - rotateHandleOffset, Vec3.UNIT_Y), frontVector); + projB_FRONT = Vec3.multiply(projB_FRONT, frontVector); + + + var projL_FRONT = Vec3.dot(Vec3.multiply(xSign * (dimensions.x / 2) + xSign * rotateHandleOffset, Vec3.UNIT_X), frontVector); + projL_FRONT = Vec3.multiply(projL_FRONT, frontVector); + + var projR_FRONT = Vec3.dot(Vec3.multiply(-xSign * (dimensions.x / 2) - xSign * rotateHandleOffset, Vec3.UNIT_X), frontVector); + projR_FRONT = Vec3.multiply(projR_FRONT, frontVector); + + + var projN_FRONT = Vec3.dot(Vec3.multiply(zSign * (dimensions.z / 2) + zSign * rotateHandleOffset, Vec3.FRONT), frontVector); + projN_FRONT = Vec3.multiply(projN_FRONT, frontVector); + + var projF_FRONT = Vec3.dot(Vec3.multiply(-zSign * (dimensions.z / 2) - zSign * rotateHandleOffset, Vec3.FRONT), frontVector); + projF_FRONT = Vec3.multiply(projF_FRONT, frontVector); + + var projFRONTList = [projT_FRONT, projB_FRONT, projL_FRONT, projR_FRONT, projN_FRONT, projF_FRONT]; + + var projFRONT = that.calculateNewBoundExtremes(projFRONTList, frontVector); + + + ///// + + Overlays.editOverlay(pitchHandle, { + url: ROTATE_ARROW_WEST_NORTH_URL + }); + Overlays.editOverlay(rollHandle, { + url: ROTATE_ARROW_WEST_NORTH_URL + }); + + yawCenter = Vec3.sum(boundsCenter, projUP[0]); + yawCorner = Vec3.sum(boundsCenter, Vec3.sum(Vec3.sum(projUP[0], projRIGHT[1]), projFRONT[1])); yawHandleRotation = Quat.lookAt(yawCorner, Vec3.sum(yawCorner, upVector), Vec3.subtract(yawCenter,yawCorner)); yawHandleRotation = Quat.multiply(Quat.angleAxis(45, upVector), yawHandleRotation); - - //Quat.fromPitchYawRollDegrees(0,270,0) - pitchCenter = Vec3.sum(boundsCenter, Vec3.multiply(xSign * (dimensions.x / 2), rightVector)); - rollCenter = Vec3.sum(boundsCenter, Vec3.multiply(zSign * (dimensions.z / 2), frontVector)); - + + + yawCorner = Vec3.sum(boundsCenter, Vec3.sum(Vec3.sum(projUP[0], projRIGHT[1]), projFRONT[1])); + + pitchCorner = Vec3.sum(boundsCenter, Vec3.sum(Vec3.sum(projUP[1], projRIGHT[0]), projFRONT[1])); + + pitchCenter = Vec3.sum(boundsCenter, projRIGHT[0]); + + pitchHandleRotation = Quat.lookAt(pitchCorner, Vec3.sum(pitchCorner, rightVector), Vec3.subtract(pitchCenter,pitchCorner)); + pitchHandleRotation = Quat.multiply(Quat.angleAxis(45, rightVector), pitchHandleRotation); + + + rollCorner = Vec3.sum(boundsCenter, Vec3.sum(Vec3.sum(projUP[1], projRIGHT[1]), projFRONT[0])); + rollCenter = Vec3.sum(boundsCenter, projFRONT[0]); + + rollHandleRotation = Quat.lookAt(rollCorner, Vec3.sum(rollCorner, frontVector), Vec3.subtract(rollCenter,rollCorner)); + + rollHandleRotation = Quat.multiply(Quat.angleAxis(45, frontVector), rollHandleRotation); + + /////////// var rotateHandlesVisible = true; var rotationOverlaysVisible = false; @@ -3433,7 +3327,7 @@ SelectionDisplay = (function() { // FUNCTION: UPDATE ROTATION DEGREES OVERLAY function updateRotationDegreesOverlay(angleFromZero, handleRotation, centerPosition) { - var wantDebug = false; + var wantDebug = true; if (wantDebug) { print("---> updateRotationDegreesOverlay ---"); print(" AngleFromZero: " + angleFromZero); From 68d5d600b747e90d2af5aa100324c8ddb7bbf5ec Mon Sep 17 00:00:00 2001 From: Daniela Date: Tue, 28 Nov 2017 23:16:34 +0000 Subject: [PATCH 15/47] YAW ROLL PITCH tool gizmos + functionality --- scripts/system/libraries/entitySelectionTool.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index f0f87d6aa2..f4f5af8d5b 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -1123,9 +1123,7 @@ SelectionDisplay = (function() { var look = Vec3.normalize(Vec3.subtract(cameraPosition, objectCenter)); // place yaw, pitch and roll rotations on the avatar referential - yawHandleRotation = Quat.multiply(MyAvatar.orientation , yawHandleRotation); - pitchHandleRotation = Quat.multiply(MyAvatar.orientation , pitchHandleRotation); - rollHandleRotation = Quat.multiply(MyAvatar.orientation , rollHandleRotation); + var avatarReferential = Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({ x: 0, y: 180, @@ -1246,7 +1244,6 @@ SelectionDisplay = (function() { yawHandleRotation = Quat.lookAt(yawCorner, Vec3.sum(yawCorner, upVector), Vec3.subtract(yawCenter,yawCorner)); yawHandleRotation = Quat.multiply(Quat.angleAxis(45, upVector), yawHandleRotation); - yawCorner = Vec3.sum(boundsCenter, Vec3.sum(Vec3.sum(projUP[0], projRIGHT[1]), projFRONT[1])); pitchCorner = Vec3.sum(boundsCenter, Vec3.sum(Vec3.sum(projUP[1], projRIGHT[0]), projFRONT[1])); @@ -1315,6 +1312,8 @@ SelectionDisplay = (function() { position: rollCorner, rotation: rollHandleRotation }); + + }; // FUNCTION: UPDATE HANDLE SIZES From dff49cafaa547a4f864a4cad44c438b067df9b77 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 28 Nov 2017 17:11:44 -0800 Subject: [PATCH 16/47] Embiggen the stick deadspots for oculus touch controllers This effectively splits the controller into directional zones. This should make driving/flying navigation more predictable and less prone to drifting in unintentional directions, which could induce nausea. --- interface/resources/controllers/oculus_touch.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/interface/resources/controllers/oculus_touch.json b/interface/resources/controllers/oculus_touch.json index 03fc1cbefb..b818d371e3 100644 --- a/interface/resources/controllers/oculus_touch.json +++ b/interface/resources/controllers/oculus_touch.json @@ -13,11 +13,11 @@ { "from": "OculusTouch.LY", "to": "Standard.LY", "filters": [ - { "type": "deadZone", "min": 0.3 }, + { "type": "deadZone", "min": 0.7 }, "invert" ] }, - { "from": "OculusTouch.LX", "filters": { "type": "deadZone", "min": 0.3 }, "to": "Standard.LX" }, + { "from": "OculusTouch.LX", "filters": { "type": "deadZone", "min": 0.7 }, "to": "Standard.LX" }, { "from": "OculusTouch.LT", "to": "Standard.LTClick", "peek": true, "filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ] @@ -29,11 +29,11 @@ { "from": "OculusTouch.RY", "to": "Standard.RY", "filters": [ - { "type": "deadZone", "min": 0.3 }, + { "type": "deadZone", "min": 0.7 }, "invert" ] }, - { "from": "OculusTouch.RX", "filters": { "type": "deadZone", "min": 0.3 }, "to": "Standard.RX" }, + { "from": "OculusTouch.RX", "filters": { "type": "deadZone", "min": 0.7 }, "to": "Standard.RX" }, { "from": "OculusTouch.RT", "to": "Standard.RTClick", "peek": true, "filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ] From a4d70e89df86953545816c25440990130dba7136 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 28 Nov 2017 17:20:26 -0800 Subject: [PATCH 17/47] hmd turn roll tuning * Tighten action motor timescale while flying. This should cause less drift as the user changes direction and should lead to more comfort and control. * Hmd head roll is only enabled if you are forward or backward faster then 2 meters per second. This should prevent un-intentional turning/rolling when hovering or moving at slow speeds. * Documented hmdRoll tuning parameters accessible from JavaScript. * Removed roll feathering code, because it is non-linear and can perhaps induce nausea. * Tuned default dead spot and turning speeds to match Eagle Flight's defaults. --- interface/src/avatar/MyAvatar.cpp | 64 ++++++++++++++++--------------- interface/src/avatar/MyAvatar.h | 12 ++++-- 2 files changed, 41 insertions(+), 35 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 563bf30f76..2f608aca15 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1516,9 +1516,19 @@ void MyAvatar::updateMotors() { _characterController.clearMotors(); glm::quat motorRotation; if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) { + + const float FLYING_MOTOR_TIMESCALE = 0.05f; + const float WALKING_MOTOR_TIMESCALE = 0.2f; + const float INVALID_MOTOR_TIMESCALE = 1.0e6f; + + float horizontalMotorTimescale; + float verticalMotorTimescale; + if (_characterController.getState() == CharacterController::State::Hover || _characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) { motorRotation = getMyHead()->getHeadOrientation(); + horizontalMotorTimescale = FLYING_MOTOR_TIMESCALE; + verticalMotorTimescale = FLYING_MOTOR_TIMESCALE; } else { // non-hovering = walking: follow camera twist about vertical but not lift // we decompose camera's rotation and store the twist part in motorRotation @@ -1529,11 +1539,12 @@ void MyAvatar::updateMotors() { glm::quat liftRotation; swingTwistDecomposition(headOrientation, Vectors::UNIT_Y, liftRotation, motorRotation); motorRotation = orientation * motorRotation; + horizontalMotorTimescale = WALKING_MOTOR_TIMESCALE; + verticalMotorTimescale = INVALID_MOTOR_TIMESCALE; } - const float DEFAULT_MOTOR_TIMESCALE = 0.2f; - const float INVALID_MOTOR_TIMESCALE = 1.0e6f; + if (_isPushing || _isBraking || !_isBeingPushed) { - _characterController.addMotor(_actionMotorVelocity, motorRotation, DEFAULT_MOTOR_TIMESCALE, INVALID_MOTOR_TIMESCALE); + _characterController.addMotor(_actionMotorVelocity, motorRotation, horizontalMotorTimescale, verticalMotorTimescale); } else { // _isBeingPushed must be true --> disable action motor by giving it a long timescale, // otherwise it's attempt to "stand in in place" could defeat scripted motor/thrusts @@ -1958,41 +1969,31 @@ void MyAvatar::updateOrientation(float deltaTime) { if (qApp->isHMDMode() && getCharacterController()->getState() == CharacterController::State::Hover && _hmdRollControlEnabled && hasDriveInput()) { // Turn with head roll. - const float MIN_CONTROL_SPEED = 0.01f; - float speed = glm::length(getWorldVelocity()); - if (speed >= MIN_CONTROL_SPEED) { - // Feather turn when stopping moving. + const float MIN_CONTROL_SPEED = 2.0f; // meters / sec + const glm::vec3 characterForward = getWorldOrientation() * Vectors::UNIT_NEG_Z; + float forwardSpeed = glm::dot(characterForward, getWorldVelocity()); - /* AJT hack - float speedFactor; - if (getDriveKey(TRANSLATE_Z) != 0.0f || _lastDrivenSpeed == 0.0f) { - _lastDrivenSpeed = speed; - speedFactor = 1.0f; - } else { - speedFactor = glm::min(speed / _lastDrivenSpeed, 1.0f); - } - */ + // only enable roll-turns if we are moving forward or backward at greater then MIN_CONTROL_SPEED + if (fabsf(forwardSpeed) >= MIN_CONTROL_SPEED) { - float direction = glm::dot(getWorldVelocity(), getWorldOrientation() * Vectors::UNIT_NEG_Z) > 0.0f ? 1.0f : -1.0f; - - float rollAngle = asinf(glm::dot(IDENTITY_UP, _hmdSensorOrientation * IDENTITY_RIGHT)); + float direction = forwardSpeed > 0.0f ? 1.0f : -1.0f; + float rollAngle = glm::degrees(asinf(glm::dot(IDENTITY_UP, _hmdSensorOrientation * IDENTITY_RIGHT))); float rollSign = rollAngle < 0.0f ? -1.0f : 1.0f; rollAngle = fabsf(rollAngle); - //rollAngle = rollAngle > _hmdRollControlDeadZone ? rollSign * (rollAngle - _hmdRollControlDeadZone) : 0.0f; - const float MAX_ROLL_ANGLE = PI / 2.0f; - const float MAX_ANGULAR_SPEED = 1.5f; // radians per sec + const float MIN_ROLL_ANGLE = _hmdRollControlDeadZone; + const float MAX_ROLL_ANGLE = 90.0f; // degrees - // apply a quadratic ease in curve. giving less roll and shallow angles and more roll at extreme angles. - float t = glm::clamp(rollAngle, 0.0f, MAX_ROLL_ANGLE) / MAX_ROLL_ANGLE; - float newT = t;//t < 0.5f ? 2.0f * t * t : -1 + (4.0f - 2.0f * t) * t; - float angularSpeed = rollSign * newT * MAX_ANGULAR_SPEED; + if (rollAngle > MIN_ROLL_ANGLE) { + // rate of turning is linearly proportional to rolAngle + rollAngle = glm::clamp(rollAngle, MIN_ROLL_ANGLE, MAX_ROLL_ANGLE); - qDebug() << "AJT: rollAngle =" << rollAngle << ", t =" << t << ", newT =" << newT << ", angularSpeed =" << angularSpeed; + // scale rollAngle into a value from zero to one. + float t = (rollAngle - MIN_ROLL_ANGLE) / (MAX_ROLL_ANGLE - MIN_ROLL_ANGLE); - // AJT: remove _hmdRollControlDeadZone, _hmdRollControlRate, _lastDrivenSpeed - - totalBodyYaw += direction * glm::degrees(angularSpeed) * deltaTime; + float angularSpeed = rollSign * t * _hmdRollControlRate; + totalBodyYaw += direction * angularSpeed * deltaTime; + } } } @@ -2038,12 +2039,13 @@ void MyAvatar::updateActionMotor(float deltaTime) { _isBraking = _wasPushing || (_isBraking && speed > MIN_ACTION_BRAKE_SPEED); } + CharacterController::State state = _characterController.getState(); + // compute action input glm::vec3 forward = (getDriveKey(TRANSLATE_Z)) * IDENTITY_FORWARD; glm::vec3 right = (getDriveKey(TRANSLATE_X)) * IDENTITY_RIGHT; glm::vec3 direction = forward + right; - CharacterController::State state = _characterController.getState(); if (state == CharacterController::State::Hover || _characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) { // we can fly --> support vertical motion diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 563fb7fccd..16cb0edfad 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -110,6 +110,10 @@ class MyAvatar : public Avatar { * @property userEyeHeight {number} Estimated height of the users eyes in sensor space. (meters) * @property SELF_ID {string} READ-ONLY. UUID representing "my avatar". Only use for local-only entities and overlays in situations where MyAvatar.sessionUUID is not available (e.g., if not connected to a domain). * Note: Likely to be deprecated. + * @property hmdRollControlEnabled {bool} When enabled the roll angle of your HMD will turn your avatar while flying. + * @property hmdRollControlDeadZone {number} If hmdRollControlEnabled is true, this value can be used to tune what roll angle is required to begin turning. + * This angle is specified in degrees. + * @property hmdRollControlRate {number} If hmdRollControlEnabled is true, this value determines the maximum turn rate of your avatar when rolling your HMD in degrees per second. */ // FIXME: `glm::vec3 position` is not accessible from QML, so this exposes position in a QML-native type @@ -158,7 +162,7 @@ class MyAvatar : public Avatar { Q_PROPERTY(float userEyeHeight READ getUserEyeHeight) Q_PROPERTY(QUuid SELF_ID READ getSelfID CONSTANT) - + const QString DOMINANT_LEFT_HAND = "left"; const QString DOMINANT_RIGHT_HAND = "right"; @@ -737,12 +741,12 @@ private: bool _clearOverlayWhenMoving { true }; QString _dominantHand { DOMINANT_RIGHT_HAND }; - const float ROLL_CONTROL_DEAD_ZONE_DEFAULT = 8.0f; // deg - const float ROLL_CONTROL_RATE_DEFAULT = 2.5f; // deg/sec/deg + const float ROLL_CONTROL_DEAD_ZONE_DEFAULT = 8.0; // degrees + const float ROLL_CONTROL_RATE_DEFAULT = 114.0f; // degrees / sec + bool _hmdRollControlEnabled { true }; float _hmdRollControlDeadZone { ROLL_CONTROL_DEAD_ZONE_DEFAULT }; float _hmdRollControlRate { ROLL_CONTROL_RATE_DEFAULT }; - float _lastDrivenSpeed { 0.0f }; // working copy -- see AvatarData for thread-safe _sensorToWorldMatrixCache, used for outward facing access glm::mat4 _sensorToWorldMatrix { glm::mat4() }; From 0ec72f25591617cc3e4b43af1667de7e3cfa6613 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 28 Nov 2017 17:45:13 -0800 Subject: [PATCH 18/47] warning fix --- libraries/avatars-renderer/src/avatars-renderer/Avatar.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 76ed381d3f..85b48ce423 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -283,7 +283,7 @@ public slots: void setModelURLFinished(bool success); protected: - float Avatar::getUnscaledEyeHeightFromSkeleton() const; + float getUnscaledEyeHeightFromSkeleton() const; virtual const QString& getSessionDisplayNameForTransport() const override { return _empty; } // Save a tiny bit of bandwidth. Mixer won't look at what we send. QString _empty{}; virtual void maybeUpdateSessionDisplayNameFromTransport(const QString& sessionDisplayName) override { _sessionDisplayName = sessionDisplayName; } // don't use no-op setter! From d1609fb9646005dfbabd6de3d4a018207be6e898 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 28 Nov 2017 17:48:34 -0800 Subject: [PATCH 19/47] domain-server warning fix --- domain-server/src/DomainServerSettingsManager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 674f3a18d1..0b644d57d8 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -315,13 +315,13 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList QVariant* avatarMinScale = _configMap.valueForKeyPath(AVATAR_MIN_SCALE_KEYPATH); if (avatarMinScale) { float scale = avatarMinScale->toFloat(); - QVariant* avatarMinHeight = _configMap.valueForKeyPath(AVATAR_MIN_HEIGHT_KEYPATH, scale * DEFAULT_AVATAR_HEIGHT); + _configMap.valueForKeyPath(AVATAR_MIN_HEIGHT_KEYPATH, scale * DEFAULT_AVATAR_HEIGHT); } QVariant* avatarMaxScale = _configMap.valueForKeyPath(AVATAR_MAX_SCALE_KEYPATH); if (avatarMaxScale) { float scale = avatarMaxScale->toFloat(); - QVariant* avatarMinHeight = _configMap.valueForKeyPath(AVATAR_MAX_HEIGHT_KEYPATH, scale * DEFAULT_AVATAR_HEIGHT); + _configMap.valueForKeyPath(AVATAR_MAX_HEIGHT_KEYPATH, scale * DEFAULT_AVATAR_HEIGHT); } } From 8985acd8b68a37e1e802446989aeb00dcc1c197d Mon Sep 17 00:00:00 2001 From: Daniela Date: Wed, 29 Nov 2017 13:00:36 +0000 Subject: [PATCH 20/47] YAW ROLL PITCH tool now represents angles in a usefull way. Refactoring. --- .../system/libraries/entitySelectionTool.js | 290 +++++++----------- 1 file changed, 104 insertions(+), 186 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index f4f5af8d5b..fb79dbab0a 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -1031,40 +1031,75 @@ SelectionDisplay = (function() { that.updateHandles(); }; - // Function: Calculate New Bound Extremes - // uses dot product to discover new top and bottom on the new referential (max and min) - that.calculateNewBoundExtremes = function(boundPointList, referenceVector) { - - if (boundPointList.length < 2) { - return [null, null]; - } - - var refMax = boundPointList[0]; - var refMin = boundPointList[1]; - - var dotMax = Vec3.dot(boundPointList[0], referenceVector); - var dotMin = Vec3.dot(boundPointList[1], referenceVector); - - if (dotMin > dotMax) { - dotMax = dotMin; - dotMin = Vec3.dot(boundPointList[0], referenceVector); - refMax = boundPointList[1]; - refMin = boundPointList[0]; - } - - for (var i = 2; i < boundPointList.length ; i++) { - var dotAux = Vec3.dot(boundPointList[i], referenceVector); - if (dotAux > dotMax) { - dotMax = dotAux; - refMax = boundPointList[i]; - } else if (dotAux < dotMin) { - dotMin = dotAux; - refMin = boundPointList[i]; - } - } - return [refMin, refMax]; - } - + // Function: Calculate New Bound Extremes + // uses dot product to discover new top and bottom on the new referential (max and min) + that.calculateNewBoundExtremes = function(boundPointList, referenceVector) { + + if (boundPointList.length < 2) { + return [null, null]; + } + + var refMax = boundPointList[0]; + var refMin = boundPointList[1]; + + var dotMax = Vec3.dot(boundPointList[0], referenceVector); + var dotMin = Vec3.dot(boundPointList[1], referenceVector); + + if (dotMin > dotMax) { + dotMax = dotMin; + dotMin = Vec3.dot(boundPointList[0], referenceVector); + refMax = boundPointList[1]; + refMin = boundPointList[0]; + } + + for (var i = 2; i < boundPointList.length ; i++) { + var dotAux = Vec3.dot(boundPointList[i], referenceVector); + if (dotAux > dotMax) { + dotMax = dotAux; + refMax = boundPointList[i]; + } else if (dotAux < dotMin) { + dotMin = dotAux; + refMin = boundPointList[i]; + } + } + return [refMin, refMax]; + } + + // Projects all 6 bounding box points: Top, Bottom, Left, Right, Near, Far (assumes center 0,0,0) onto + // one of the basis of the new avatar referencial + // dimensions - dimensions of the AABB (axis aligned bounding box) on the standard basis + // [1, 0, 0], [0, 1, 0], [0, 0, 1] + // v - projection vector + // rotateHandleOffset - offset for the rotation handle gizmo position + that.projectBoundingBoxPoints = function(dimensions, v, rotateHandleOffset){ + //project all 6 bounding box points: Top, Bottom, Left, Right, Near, Far (assumes center 0,0,0) onto the new avatar referential + + var projT_v = Vec3.dot(Vec3.multiply((dimensions.y / 2) + rotateHandleOffset, Vec3.UNIT_Y), v); + projT_v = Vec3.multiply(projT_v, v); + + + var projB_v = Vec3.dot(Vec3.multiply(-(dimensions.y / 2) - rotateHandleOffset, Vec3.UNIT_Y), v); + projB_v = Vec3.multiply(projB_v, v); + + + var projL_v = Vec3.dot(Vec3.multiply((dimensions.x / 2) + rotateHandleOffset, Vec3.UNIT_X), v); + projL_v = Vec3.multiply(projL_v, v); + + var projR_v = Vec3.dot(Vec3.multiply(-1.0 * (dimensions.x / 2) - 1.0 * rotateHandleOffset, Vec3.UNIT_X), v); + projR_v = Vec3.multiply(projR_v, v); + + + var projN_v = Vec3.dot(Vec3.multiply((dimensions.z / 2) + rotateHandleOffset, Vec3.FRONT), v); + projN_v = Vec3.multiply(projN_v, v); + + var projF_v = Vec3.dot(Vec3.multiply(-1.0 * (dimensions.z / 2) - 1.0 * rotateHandleOffset, Vec3.FRONT), v); + projF_v = Vec3.multiply(projF_v, v); + + var projList = [projT_v, projB_v, projL_v, projR_v, projN_v, projF_v]; + + return that.calculateNewBoundExtremes(projList, v); + }; + // FUNCTION: UPDATE ROTATION HANDLES that.updateRotationHandles = function() { var diagonal = (Vec3.length(SelectionManager.worldDimensions) / 2) * 1.1; @@ -1080,7 +1115,7 @@ SelectionDisplay = (function() { // prev 0.05 var rotateHandleOffset = 0.05; - var top, far, left, bottom, near, right, boundsCenter, objectCenter, BLN, BRN, BLF, TLN, TRN, TLF, TRF; + var boundsCenter, objectCenter; var dimensions, rotation; if (spaceMode === SPACE_LOCAL) { @@ -1092,33 +1127,12 @@ SelectionDisplay = (function() { dimensions = SelectionManager.worldDimensions; var position = objectCenter; - top = objectCenter.y + (dimensions.y / 2); - far = objectCenter.z + (dimensions.z / 2); - left = objectCenter.x + (dimensions.x / 2); - - bottom = objectCenter.y - (dimensions.y / 2); - near = objectCenter.z - (dimensions.z / 2); - right = objectCenter.x - (dimensions.x / 2); - boundsCenter = objectCenter; var yawCorner; var pitchCorner; var rollCorner; - // determine which bottom corner we are closest to - /*------------------------------ - example: - - BRF +--------+ BLF - | | - | | - BRN +--------+ BLN - - * - - ------------------------------*/ - var cameraPosition = Camera.getPosition(); var look = Vec3.normalize(Vec3.subtract(cameraPosition, objectCenter)); @@ -1132,136 +1146,39 @@ SelectionDisplay = (function() { var upVector = Quat.getUp(avatarReferential); var rightVector = Vec3.multiply(-1, Quat.getRight(avatarReferential)); var frontVector = Quat.getFront(avatarReferential); - // Centers - var xSign = -1.0; - var zSign = -1.0; - if (Vec3.dot(look, rightVector) > 0) { - xSign = 1.0; - } - if (Vec3.dot(look, frontVector) > 0) { - zSign = 1.0; - } - - - - //project all 8 bounding box points (assumes center 0,0,0) onto the new avatar referential - - var projT_UP = Vec3.dot(Vec3.multiply((dimensions.y / 2) + rotateHandleOffset, Vec3.UNIT_Y), upVector); - projT_UP = Vec3.multiply(projT_UP, upVector); - - - var projB_UP = Vec3.dot(Vec3.multiply(-(dimensions.y / 2) - rotateHandleOffset, Vec3.UNIT_Y), upVector); - projB_UP = Vec3.multiply(projB_UP, upVector); - - - var projL_UP = Vec3.dot(Vec3.multiply(xSign * (dimensions.x / 2) + xSign * rotateHandleOffset, Vec3.UNIT_X), upVector); - projL_UP = Vec3.multiply(projL_UP, upVector); - - var projR_UP = Vec3.dot(Vec3.multiply(-xSign * (dimensions.x / 2) - xSign * rotateHandleOffset, Vec3.UNIT_X), upVector); - projR_UP = Vec3.multiply(projR_UP, upVector); - - - var projN_UP = Vec3.dot(Vec3.multiply(zSign * (dimensions.z / 2) + zSign * rotateHandleOffset, Vec3.FRONT), upVector); - projN_UP = Vec3.multiply(projN_UP, upVector); - - var projF_UP = Vec3.dot(Vec3.multiply(-zSign * (dimensions.z / 2) - zSign * rotateHandleOffset, Vec3.FRONT), upVector); - projF_UP = Vec3.multiply(projF_UP, upVector); - - var projUPList = [projT_UP, projB_UP, projL_UP, projR_UP, projN_UP, projF_UP]; - - var projUP = that.calculateNewBoundExtremes(projUPList, upVector); - - //// RIGHT - - var projT_RIGHT = Vec3.dot(Vec3.multiply((dimensions.y / 2) + rotateHandleOffset, Vec3.UNIT_Y), rightVector); - projT_RIGHT = Vec3.multiply(projT_RIGHT, rightVector); - - - var projB_RIGHT = Vec3.dot(Vec3.multiply(-(dimensions.y / 2) - rotateHandleOffset, Vec3.UNIT_Y), rightVector); - projB_RIGHT = Vec3.multiply(projB_RIGHT, rightVector); - - - var projL_RIGHT = Vec3.dot(Vec3.multiply(xSign * (dimensions.x / 2) + xSign * rotateHandleOffset, Vec3.UNIT_X), rightVector); - projL_RIGHT = Vec3.multiply(projL_RIGHT, rightVector); - - var projR_RIGHT = Vec3.dot(Vec3.multiply(-xSign * (dimensions.x / 2) - xSign * rotateHandleOffset, Vec3.UNIT_X), rightVector); - projR_RIGHT = Vec3.multiply(projR_RIGHT, rightVector); - - - var projN_RIGHT = Vec3.dot(Vec3.multiply(zSign * (dimensions.z / 2) + zSign * rotateHandleOffset, Vec3.FRONT), rightVector); - projN_RIGHT = Vec3.multiply(projN_RIGHT, rightVector); - - var projF_RIGHT = Vec3.dot(Vec3.multiply(-zSign * (dimensions.z / 2) - zSign * rotateHandleOffset, Vec3.FRONT), rightVector); - projF_RIGHT = Vec3.multiply(projF_RIGHT, rightVector); - - - var projRIGHTList = [projT_RIGHT, projB_RIGHT, projL_RIGHT, projR_RIGHT, projN_RIGHT, projF_RIGHT]; - - var projRIGHT = that.calculateNewBoundExtremes(projRIGHTList, rightVector); - - //FRONT - - var projT_FRONT = Vec3.dot(Vec3.multiply((dimensions.y / 2) + rotateHandleOffset, Vec3.UNIT_Y), frontVector); - projT_FRONT = Vec3.multiply(projT_FRONT, frontVector); - - - var projB_FRONT = Vec3.dot(Vec3.multiply(-(dimensions.y / 2) - rotateHandleOffset, Vec3.UNIT_Y), frontVector); - projB_FRONT = Vec3.multiply(projB_FRONT, frontVector); - - - var projL_FRONT = Vec3.dot(Vec3.multiply(xSign * (dimensions.x / 2) + xSign * rotateHandleOffset, Vec3.UNIT_X), frontVector); - projL_FRONT = Vec3.multiply(projL_FRONT, frontVector); - - var projR_FRONT = Vec3.dot(Vec3.multiply(-xSign * (dimensions.x / 2) - xSign * rotateHandleOffset, Vec3.UNIT_X), frontVector); - projR_FRONT = Vec3.multiply(projR_FRONT, frontVector); - - - var projN_FRONT = Vec3.dot(Vec3.multiply(zSign * (dimensions.z / 2) + zSign * rotateHandleOffset, Vec3.FRONT), frontVector); - projN_FRONT = Vec3.multiply(projN_FRONT, frontVector); - - var projF_FRONT = Vec3.dot(Vec3.multiply(-zSign * (dimensions.z / 2) - zSign * rotateHandleOffset, Vec3.FRONT), frontVector); - projF_FRONT = Vec3.multiply(projF_FRONT, frontVector); - - var projFRONTList = [projT_FRONT, projB_FRONT, projL_FRONT, projR_FRONT, projN_FRONT, projF_FRONT]; - - var projFRONT = that.calculateNewBoundExtremes(projFRONTList, frontVector); - - - ///// - - Overlays.editOverlay(pitchHandle, { - url: ROTATE_ARROW_WEST_NORTH_URL - }); - Overlays.editOverlay(rollHandle, { - url: ROTATE_ARROW_WEST_NORTH_URL - }); - - yawCenter = Vec3.sum(boundsCenter, projUP[0]); + //project all 6 bounding box points: Top, Bottom, Left, Right, Near, Far (assumes center 0,0,0) onto the new avatar referential + // UP + var projUP = that.projectBoundingBoxPoints(dimensions, upVector, rotateHandleOffset); + // RIGHT + var projRIGHT = that.projectBoundingBoxPoints(dimensions, rightVector, rotateHandleOffset); + // FRONT + var projFRONT = that.projectBoundingBoxPoints(dimensions, frontVector, rotateHandleOffset); + + ///// + + // YAW + yawCenter = Vec3.sum(boundsCenter, projUP[0]); yawCorner = Vec3.sum(boundsCenter, Vec3.sum(Vec3.sum(projUP[0], projRIGHT[1]), projFRONT[1])); - yawHandleRotation = Quat.lookAt(yawCorner, Vec3.sum(yawCorner, upVector), Vec3.subtract(yawCenter,yawCorner)); yawHandleRotation = Quat.multiply(Quat.angleAxis(45, upVector), yawHandleRotation); - - yawCorner = Vec3.sum(boundsCenter, Vec3.sum(Vec3.sum(projUP[0], projRIGHT[1]), projFRONT[1])); - - pitchCorner = Vec3.sum(boundsCenter, Vec3.sum(Vec3.sum(projUP[1], projRIGHT[0]), projFRONT[1])); - - pitchCenter = Vec3.sum(boundsCenter, projRIGHT[0]); - - pitchHandleRotation = Quat.lookAt(pitchCorner, Vec3.sum(pitchCorner, rightVector), Vec3.subtract(pitchCenter,pitchCorner)); + + // PTCH + pitchCorner = Vec3.sum(boundsCenter, Vec3.sum(Vec3.sum(projUP[1], projRIGHT[0]), projFRONT[1])); + pitchCenter = Vec3.sum(boundsCenter, projRIGHT[0]); + + pitchHandleRotation = Quat.lookAt(pitchCorner, Vec3.sum(pitchCorner, rightVector), Vec3.subtract(pitchCenter,pitchCorner)); pitchHandleRotation = Quat.multiply(Quat.angleAxis(45, rightVector), pitchHandleRotation); - - - rollCorner = Vec3.sum(boundsCenter, Vec3.sum(Vec3.sum(projUP[1], projRIGHT[1]), projFRONT[0])); - rollCenter = Vec3.sum(boundsCenter, projFRONT[0]); - - rollHandleRotation = Quat.lookAt(rollCorner, Vec3.sum(rollCorner, frontVector), Vec3.subtract(rollCenter,rollCorner)); - - rollHandleRotation = Quat.multiply(Quat.angleAxis(45, frontVector), rollHandleRotation); - - /////////// + + // ROLL + rollCorner = Vec3.sum(boundsCenter, Vec3.sum(Vec3.sum(projUP[1], projRIGHT[1]), projFRONT[0])); + rollCenter = Vec3.sum(boundsCenter, projFRONT[0]); + + rollHandleRotation = Quat.lookAt(rollCorner, Vec3.sum(rollCorner, frontVector), Vec3.subtract(rollCenter,rollCorner)); + rollHandleRotation = Quat.multiply(Quat.angleAxis(45, frontVector), rollHandleRotation); + + /////////// var rotateHandlesVisible = true; var rotationOverlaysVisible = false; @@ -1312,8 +1229,8 @@ SelectionDisplay = (function() { position: rollCorner, rotation: rollHandleRotation }); - - + + }; // FUNCTION: UPDATE HANDLE SIZES @@ -3326,7 +3243,7 @@ SelectionDisplay = (function() { // FUNCTION: UPDATE ROTATION DEGREES OVERLAY function updateRotationDegreesOverlay(angleFromZero, handleRotation, centerPosition) { - var wantDebug = true; + var wantDebug = false; if (wantDebug) { print("---> updateRotationDegreesOverlay ---"); print(" AngleFromZero: " + angleFromZero); @@ -3354,7 +3271,7 @@ SelectionDisplay = (function() { y: innerRadius * ROTATION_DISPLAY_SIZE_Y_MULTIPLIER }, lineHeight: innerRadius * ROTATION_DISPLAY_LINE_HEIGHT_MULTIPLIER, - text: normalizeDegrees(angleFromZero) + "°" + text: normalizeDegrees(-angleFromZero) + "°" }; if (wantDebug) { print(" TranslatedPos: " + position.x + ", " + position.y + ", " + position.z); @@ -3418,7 +3335,7 @@ SelectionDisplay = (function() { //get the correct axis according to the avatar referencial var avatarReferential = Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees({ x: 0, - y: 180, + y: 0, z: 0 })); rotationNormal = Vec3.multiplyQbyV(avatarReferential, rotationNormal); @@ -3526,7 +3443,8 @@ SelectionDisplay = (function() { var rotChange = Quat.angleAxis(angleFromZero, rotationNormal); updateSelectionsRotation(rotChange); - + //present angle in avatar referencial + angleFromZero = -angleFromZero; updateRotationDegreesOverlay(angleFromZero, handleRotation, rotCenter); // update the rotation display accordingly... From ef49aa0b71d1436867deaab7097292259004b951 Mon Sep 17 00:00:00 2001 From: Daniela Date: Wed, 29 Nov 2017 18:54:08 +0000 Subject: [PATCH 21/47] Refactor to comply with code standard. --- .../system/libraries/entitySelectionTool.js | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index fb79dbab0a..b8ba146757 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -1065,29 +1065,25 @@ SelectionDisplay = (function() { return [refMin, refMax]; } + // Function: Project Bounding Box Points // Projects all 6 bounding box points: Top, Bottom, Left, Right, Near, Far (assumes center 0,0,0) onto // one of the basis of the new avatar referencial // dimensions - dimensions of the AABB (axis aligned bounding box) on the standard basis // [1, 0, 0], [0, 1, 0], [0, 0, 1] // v - projection vector // rotateHandleOffset - offset for the rotation handle gizmo position - that.projectBoundingBoxPoints = function(dimensions, v, rotateHandleOffset){ - //project all 6 bounding box points: Top, Bottom, Left, Right, Near, Far (assumes center 0,0,0) onto the new avatar referential - + that.projectBoundingBoxPoints = function(dimensions, v, rotateHandleOffset) { var projT_v = Vec3.dot(Vec3.multiply((dimensions.y / 2) + rotateHandleOffset, Vec3.UNIT_Y), v); projT_v = Vec3.multiply(projT_v, v); - var projB_v = Vec3.dot(Vec3.multiply(-(dimensions.y / 2) - rotateHandleOffset, Vec3.UNIT_Y), v); projB_v = Vec3.multiply(projB_v, v); - var projL_v = Vec3.dot(Vec3.multiply((dimensions.x / 2) + rotateHandleOffset, Vec3.UNIT_X), v); projL_v = Vec3.multiply(projL_v, v); var projR_v = Vec3.dot(Vec3.multiply(-1.0 * (dimensions.x / 2) - 1.0 * rotateHandleOffset, Vec3.UNIT_X), v); projR_v = Vec3.multiply(projR_v, v); - var projN_v = Vec3.dot(Vec3.multiply((dimensions.z / 2) + rotateHandleOffset, Vec3.FRONT), v); projN_v = Vec3.multiply(projN_v, v); @@ -1147,7 +1143,9 @@ SelectionDisplay = (function() { var rightVector = Vec3.multiply(-1, Quat.getRight(avatarReferential)); var frontVector = Quat.getFront(avatarReferential); - //project all 6 bounding box points: Top, Bottom, Left, Right, Near, Far (assumes center 0,0,0) onto the new avatar referential + // project all 6 bounding box points: Top, Bottom, Left, Right, Near, Far (assumes center 0,0,0) + // onto the new avatar referential + // UP var projUP = that.projectBoundingBoxPoints(dimensions, upVector, rotateHandleOffset); // RIGHT @@ -1155,30 +1153,36 @@ SelectionDisplay = (function() { // FRONT var projFRONT = that.projectBoundingBoxPoints(dimensions, frontVector, rotateHandleOffset); - ///// - // YAW yawCenter = Vec3.sum(boundsCenter, projUP[0]); yawCorner = Vec3.sum(boundsCenter, Vec3.sum(Vec3.sum(projUP[0], projRIGHT[1]), projFRONT[1])); - yawHandleRotation = Quat.lookAt(yawCorner, Vec3.sum(yawCorner, upVector), Vec3.subtract(yawCenter,yawCorner)); + yawHandleRotation = Quat.lookAt( + yawCorner, + Vec3.sum(yawCorner, upVector), + Vec3.subtract(yawCenter,yawCorner)); yawHandleRotation = Quat.multiply(Quat.angleAxis(45, upVector), yawHandleRotation); // PTCH pitchCorner = Vec3.sum(boundsCenter, Vec3.sum(Vec3.sum(projUP[1], projRIGHT[0]), projFRONT[1])); pitchCenter = Vec3.sum(boundsCenter, projRIGHT[0]); - pitchHandleRotation = Quat.lookAt(pitchCorner, Vec3.sum(pitchCorner, rightVector), Vec3.subtract(pitchCenter,pitchCorner)); + pitchHandleRotation = Quat.lookAt( + pitchCorner, + Vec3.sum(pitchCorner, rightVector), + Vec3.subtract(pitchCenter,pitchCorner)); pitchHandleRotation = Quat.multiply(Quat.angleAxis(45, rightVector), pitchHandleRotation); // ROLL rollCorner = Vec3.sum(boundsCenter, Vec3.sum(Vec3.sum(projUP[1], projRIGHT[1]), projFRONT[0])); rollCenter = Vec3.sum(boundsCenter, projFRONT[0]); - rollHandleRotation = Quat.lookAt(rollCorner, Vec3.sum(rollCorner, frontVector), Vec3.subtract(rollCenter,rollCorner)); + rollHandleRotation = Quat.lookAt( + rollCorner, + Vec3.sum(rollCorner, frontVector), + Vec3.subtract(rollCenter,rollCorner)); rollHandleRotation = Quat.multiply(Quat.angleAxis(45, frontVector), rollHandleRotation); - /////////// var rotateHandlesVisible = true; var rotationOverlaysVisible = false; From a1d90b5dd92d5b114ebad6301090f6b03ed05229 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 1 Dec 2017 15:41:13 -0800 Subject: [PATCH 22/47] added extra bit --- libraries/entities/src/EntityItem.cpp | 81 ++++++++++++++--------- libraries/entities/src/EntityItem.h | 1 + libraries/entities/src/SimulationFlags.h | 1 + libraries/physics/src/ObjectMotionState.h | 2 +- 4 files changed, 51 insertions(+), 34 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 4f2b290635..330e1fa854 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1832,39 +1832,8 @@ void EntityItem::computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask } } - if (userMask & USER_COLLISION_GROUP_MY_AVATAR) { - bool iAmHoldingThis = false; - // if this entity is a descendant of MyAvatar, don't collide with MyAvatar. This avoids the - // "bootstrapping" problem where you can shoot yourself across the room by grabbing something - // and holding it against your own avatar. - if (isChildOfMyAvatar()) { - iAmHoldingThis = true; - } - // also, don't bootstrap our own avatar with a hold action - QList holdActions = getActionsOfType(DYNAMIC_TYPE_HOLD); - QList::const_iterator i = holdActions.begin(); - while (i != holdActions.end()) { - EntityDynamicPointer action = *i; - if (action->isMine()) { - iAmHoldingThis = true; - break; - } - i++; - } - QList farGrabActions = getActionsOfType(DYNAMIC_TYPE_FAR_GRAB); - i = farGrabActions.begin(); - while (i != farGrabActions.end()) { - EntityDynamicPointer action = *i; - if (action->isMine()) { - iAmHoldingThis = true; - break; - } - i++; - } - - if (iAmHoldingThis) { - userMask &= ~USER_COLLISION_GROUP_MY_AVATAR; - } + if (_dirtyFlags & Simulation::DIRTY_IGNORE_MY_AVATAR) { + userMask &= ~USER_COLLISION_GROUP_MY_AVATAR; } mask = Physics::getDefaultCollisionMask(group) & (int16_t)(userMask); } @@ -1960,6 +1929,17 @@ bool EntityItem::addActionInternal(EntitySimulationPointer simulation, EntityDyn _allActionsDataCache = newDataCache; _dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION; _dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar + + auto actionType = action->getType(); + if (actionType == DYNAMIC_TYPE_HOLD || actionType == DYNAMIC_TYPE_FAR_GRAB) { + _dirtyFlags |= Simulation::DIRTY_IGNORE_MY_AVATAR; + forEachDescendant([&](SpatiallyNestablePointer child) { + if (child->getNestableType() == NestableType::Entity) { + EntityItemPointer entity = std::static_pointer_cast(child); + entity->markDirtyFlags(Simulation::DIRTY_IGNORE_MY_AVATAR); + } + }); + } } else { qCDebug(entities) << "EntityItem::addActionInternal -- serializeActions failed"; } @@ -2000,6 +1980,32 @@ bool EntityItem::removeAction(EntitySimulationPointer simulation, const QUuid& a return success; } +bool EntityItem::stillHasGrabActions() { + bool stillHasGrabAction = false; + QList holdActions = getActionsOfType(DYNAMIC_TYPE_HOLD); + QList::const_iterator i = holdActions.begin(); + while (i != holdActions.end()) { + EntityDynamicPointer action = *i; + if (action->isMine()) { + stillHasGrabAction = true; + break; + } + i++; + } + QList farGrabActions = getActionsOfType(DYNAMIC_TYPE_FAR_GRAB); + i = farGrabActions.begin(); + while (i != farGrabActions.end()) { + EntityDynamicPointer action = *i; + if (action->isMine()) { + stillHasGrabAction = true; + break; + } + i++; + } + + return stillHasGrabAction; +} + bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPointer simulation) { _previouslyDeletedActions.insert(actionID, usecTimestampNow()); if (_objectActions.contains(actionID)) { @@ -2023,6 +2029,15 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi serializeActions(success, _allActionsDataCache); _dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION; _dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar + if (stillHasGrabActions()) { + _dirtyFlags |= Simulation::DIRTY_IGNORE_MY_AVATAR; + forEachDescendant([&](SpatiallyNestablePointer child) { + if (child->getNestableType() == NestableType::Entity) { + EntityItemPointer entity = std::static_pointer_cast(child); + entity->markDirtyFlags(Simulation::DIRTY_IGNORE_MY_AVATAR); + } + }); + } setDynamicDataNeedsTransmit(true); return success; } diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 882b8e6812..ab6df1ab33 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -437,6 +437,7 @@ public: // if this entity is client-only, which avatar is it associated with? QUuid getOwningAvatarID() const { return _owningAvatarID; } void setOwningAvatarID(const QUuid& owningAvatarID) { _owningAvatarID = owningAvatarID; } + bool stillHasGrabActions(); virtual bool wantsHandControllerPointerEvents() const { return false; } virtual bool wantsKeyboardFocus() const { return false; } diff --git a/libraries/entities/src/SimulationFlags.h b/libraries/entities/src/SimulationFlags.h index e2b2224b4a..aeee1ae01b 100644 --- a/libraries/entities/src/SimulationFlags.h +++ b/libraries/entities/src/SimulationFlags.h @@ -27,6 +27,7 @@ namespace Simulation { const uint32_t DIRTY_PHYSICS_ACTIVATION = 0x0800; // should activate object in physics engine const uint32_t DIRTY_SIMULATOR_ID = 0x1000; // the simulatorID has changed const uint32_t DIRTY_SIMULATION_OWNERSHIP_PRIORITY = 0x2000; // our own bid priority has changed + const uint32_t DIRTY_IGNORE_MY_AVATAR = 0x4000; const uint32_t DIRTY_TRANSFORM = DIRTY_POSITION | DIRTY_ROTATION; const uint32_t DIRTY_VELOCITIES = DIRTY_LINEAR_VELOCITY | DIRTY_ANGULAR_VELOCITY; diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index 0b91ede574..ae5496b076 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -47,7 +47,7 @@ enum MotionStateType { // The update flags trigger two varieties of updates: "hard" which require the body to be pulled // and re-added to the physics engine and "easy" which just updates the body properties. const uint32_t HARD_DIRTY_PHYSICS_FLAGS = (uint32_t)(Simulation::DIRTY_MOTION_TYPE | Simulation::DIRTY_SHAPE | - Simulation::DIRTY_COLLISION_GROUP); + Simulation::DIRTY_COLLISION_GROUP | Simulation::DIRTY_IGNORE_MY_AVATAR); const uint32_t EASY_DIRTY_PHYSICS_FLAGS = (uint32_t)(Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES | Simulation::DIRTY_MASS | Simulation::DIRTY_MATERIAL | Simulation::DIRTY_SIMULATOR_ID | Simulation::DIRTY_SIMULATION_OWNERSHIP_PRIORITY | From 1e6b5c0c75d269f37e10eada11c663210acdf72c Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 1 Dec 2017 17:09:00 -0800 Subject: [PATCH 23/47] make requested changes --- libraries/entities/src/EntityItem.cpp | 32 ++++++++++++--------- libraries/entities/src/EntityItem.h | 2 +- libraries/entities/src/SimulationFlags.h | 2 +- libraries/physics/src/EntityMotionState.cpp | 2 +- libraries/physics/src/ObjectMotionState.h | 2 +- 5 files changed, 23 insertions(+), 17 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 330e1fa854..e764cfff90 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1832,7 +1832,7 @@ void EntityItem::computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask } } - if (_dirtyFlags & Simulation::DIRTY_IGNORE_MY_AVATAR) { + if (_dirtyFlags & Simulation::NO_BOOTSTRAPPING) { userMask &= ~USER_COLLISION_GROUP_MY_AVATAR; } mask = Physics::getDefaultCollisionMask(group) & (int16_t)(userMask); @@ -1932,11 +1932,11 @@ bool EntityItem::addActionInternal(EntitySimulationPointer simulation, EntityDyn auto actionType = action->getType(); if (actionType == DYNAMIC_TYPE_HOLD || actionType == DYNAMIC_TYPE_FAR_GRAB) { - _dirtyFlags |= Simulation::DIRTY_IGNORE_MY_AVATAR; + _dirtyFlags |= Simulation::NO_BOOTSTRAPPING; forEachDescendant([&](SpatiallyNestablePointer child) { if (child->getNestableType() == NestableType::Entity) { EntityItemPointer entity = std::static_pointer_cast(child); - entity->markDirtyFlags(Simulation::DIRTY_IGNORE_MY_AVATAR); + entity->markDirtyFlags(Simulation::NO_BOOTSTRAPPING | Simulation::DIRTY_COLLISION_GROUP); } }); } @@ -1980,15 +1980,13 @@ bool EntityItem::removeAction(EntitySimulationPointer simulation, const QUuid& a return success; } -bool EntityItem::stillHasGrabActions() { - bool stillHasGrabAction = false; +bool EntityItem::stillHasGrabActions() const { QList holdActions = getActionsOfType(DYNAMIC_TYPE_HOLD); QList::const_iterator i = holdActions.begin(); while (i != holdActions.end()) { EntityDynamicPointer action = *i; if (action->isMine()) { - stillHasGrabAction = true; - break; + return true; } i++; } @@ -1997,13 +1995,12 @@ bool EntityItem::stillHasGrabActions() { while (i != farGrabActions.end()) { EntityDynamicPointer action = *i; if (action->isMine()) { - stillHasGrabAction = true; - break; + return true; } i++; } - return stillHasGrabAction; + return false; } bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPointer simulation) { @@ -2029,12 +2026,21 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi serializeActions(success, _allActionsDataCache); _dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION; _dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar - if (stillHasGrabActions()) { - _dirtyFlags |= Simulation::DIRTY_IGNORE_MY_AVATAR; + if (stillHasGrabActions() && !(_dirtyFlags & Simulation::NO_BOOTSTRAPPING)) { + _dirtyFlags |= Simulation::NO_BOOTSTRAPPING; forEachDescendant([&](SpatiallyNestablePointer child) { if (child->getNestableType() == NestableType::Entity) { EntityItemPointer entity = std::static_pointer_cast(child); - entity->markDirtyFlags(Simulation::DIRTY_IGNORE_MY_AVATAR); + entity->markDirtyFlags(Simulation::NO_BOOTSTRAPPING | Simulation::DIRTY_COLLISION_GROUP); + } + }); + } else if (_dirtyFlags & Simulation::NO_BOOTSTRAPPING) { + _dirtyFlags &= ~Simulation::NO_BOOTSTRAPPING; + forEachDescendant([&](SpatiallyNestablePointer child) { + if (child->getNestableType() == NestableType::Entity) { + EntityItemPointer entity = std::static_pointer_cast(child); + entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP); + entity->clearDirtyFlags(Simulation::NO_BOOTSTRAPPING); } }); } diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index ab6df1ab33..4c7f37bd6a 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -437,7 +437,6 @@ public: // if this entity is client-only, which avatar is it associated with? QUuid getOwningAvatarID() const { return _owningAvatarID; } void setOwningAvatarID(const QUuid& owningAvatarID) { _owningAvatarID = owningAvatarID; } - bool stillHasGrabActions(); virtual bool wantsHandControllerPointerEvents() const { return false; } virtual bool wantsKeyboardFocus() const { return false; } @@ -471,6 +470,7 @@ protected: void setSimulated(bool simulated) { _simulated = simulated; } const QByteArray getDynamicDataInternal() const; + bool stillHasGrabActions() const; void setDynamicDataInternal(QByteArray dynamicData); virtual void dimensionsChanged() override; diff --git a/libraries/entities/src/SimulationFlags.h b/libraries/entities/src/SimulationFlags.h index aeee1ae01b..aaa92000e7 100644 --- a/libraries/entities/src/SimulationFlags.h +++ b/libraries/entities/src/SimulationFlags.h @@ -27,7 +27,7 @@ namespace Simulation { const uint32_t DIRTY_PHYSICS_ACTIVATION = 0x0800; // should activate object in physics engine const uint32_t DIRTY_SIMULATOR_ID = 0x1000; // the simulatorID has changed const uint32_t DIRTY_SIMULATION_OWNERSHIP_PRIORITY = 0x2000; // our own bid priority has changed - const uint32_t DIRTY_IGNORE_MY_AVATAR = 0x4000; + const uint32_t NO_BOOTSTRAPPING = 0x4000; const uint32_t DIRTY_TRANSFORM = DIRTY_POSITION | DIRTY_ROTATION; const uint32_t DIRTY_VELOCITIES = DIRTY_LINEAR_VELOCITY | DIRTY_ANGULAR_VELOCITY; diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 8ebce9f811..7e8b431ceb 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -700,7 +700,7 @@ uint32_t EntityMotionState::getIncomingDirtyFlags() { void EntityMotionState::clearIncomingDirtyFlags() { assert(entityTreeIsLocked()); if (_body && _entity) { - _entity->clearDirtyFlags(); + _entity->clearDirtyFlags(DIRTY_PHYSICS_FLAGS); } } diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index ae5496b076..0b91ede574 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -47,7 +47,7 @@ enum MotionStateType { // The update flags trigger two varieties of updates: "hard" which require the body to be pulled // and re-added to the physics engine and "easy" which just updates the body properties. const uint32_t HARD_DIRTY_PHYSICS_FLAGS = (uint32_t)(Simulation::DIRTY_MOTION_TYPE | Simulation::DIRTY_SHAPE | - Simulation::DIRTY_COLLISION_GROUP | Simulation::DIRTY_IGNORE_MY_AVATAR); + Simulation::DIRTY_COLLISION_GROUP); const uint32_t EASY_DIRTY_PHYSICS_FLAGS = (uint32_t)(Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES | Simulation::DIRTY_MASS | Simulation::DIRTY_MATERIAL | Simulation::DIRTY_SIMULATOR_ID | Simulation::DIRTY_SIMULATION_OWNERSHIP_PRIORITY | From 4030688e7a694af7fc2c2aed6433c582a3f9d436 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Sun, 3 Dec 2017 11:54:37 -0700 Subject: [PATCH 24/47] added record wav files --- interface/resources/qml/AudioScope.qml | 178 +++++++++++++++++--- interface/src/audio/AudioScope.cpp | 5 + interface/src/audio/AudioScope.h | 2 + interface/src/scripting/Audio.cpp | 26 +++ interface/src/scripting/Audio.h | 9 +- libraries/audio-client/src/AudioClient.cpp | 9 +- libraries/audio-client/src/AudioClient.h | 15 +- libraries/audio-client/src/AudioFileWav.cpp | 69 ++++++++ libraries/audio-client/src/AudioFileWav.h | 34 ++++ 9 files changed, 312 insertions(+), 35 deletions(-) create mode 100644 libraries/audio-client/src/AudioFileWav.cpp create mode 100644 libraries/audio-client/src/AudioFileWav.h diff --git a/interface/resources/qml/AudioScope.qml b/interface/resources/qml/AudioScope.qml index 6f78a2d820..1f67b3f090 100644 --- a/interface/resources/qml/AudioScope.qml +++ b/interface/resources/qml/AudioScope.qml @@ -7,6 +7,7 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html // + import QtQuick 2.5 import QtQuick.Controls 1.4 import "styles-uit" @@ -32,7 +33,7 @@ Item { property var _triggered: false property var _steps - property var _refreshMs: 10 + property var _refreshMs: 32 property var _framesPerSecond: AudioScope.getFramesPerSecond() property var _isFrameUnits: true @@ -46,9 +47,17 @@ Item { property int y: 0 } - property var _timeBeforeHold: 100; - property var _pressedTime: 0; - property var _isPressed: false; + property var _timeBeforeHold: 300 + property var _pressedTime: 0 + property var _isPressed: false + + property var _recOpacity : 0.0 + property var _recSign : 0.05 + + property var _outputLeftState: false + property var _outputRightState: false + + property var _wavFilePath: "" function isHolding() { return (_pressedTime > _timeBeforeHold); @@ -84,13 +93,29 @@ Item { _triggerOutputRightData = AudioScope.triggerOutputRight; } } + + function setRecordingLabelOpacity(opacity) { + _recOpacity = opacity; + recCircle.opacity = _recOpacity; + recText.opacity = _recOpacity; + } + + function updateRecordingLabel() { + _recOpacity += _recSign; + if (_recOpacity > 1.0 || _recOpacity < 0.0) { + _recOpacity = _recOpacity > 1.0 ? 1.0 : 0.0; + _recSign *= -1; + } + setRecordingLabelOpacity(_recOpacity); + } function pullFreshValues() { - if (!AudioScope.getPause()){ - if (AudioScope.getTriggered()) { - _triggered = true; - collectTriggerData(); - } else { + if (Audio.getRecording()) { + updateRecordingLabel(); + } + + if (!AudioScope.getPause()) { + if (!_triggered) { collectScopeData(); } } @@ -99,8 +124,49 @@ Item { } } - + function startRecording() { + _wavFilePath = (new Date()).toISOString(); // yyyy-mm-ddThh:mm:ss.sssZ + _wavFilePath = _wavFilePath.replace(/[\-:]|\.\d*Z$/g, "").replace("T", "-") + ".wav"; + // Using controller recording default directory + _wavFilePath = Recording.getDefaultRecordingSaveDirectory() + _wavFilePath; + if (!Audio.startRecording(_wavFilePath)) { + Messages.sendMessage("Hifi-Notifications", JSON.stringify({message:"Error creating: "+_wavFilePath})); + updateRecordingUI(false); + } + } + function stopRecording() { + Audio.stopRecording(); + setRecordingLabelOpacity(0.0); + Messages.sendMessage("Hifi-Notifications", JSON.stringify({message:"Saved: "+_wavFilePath})); + } + + function updateRecordingUI(isRecording) { + if (!isRecording) { + recordButton.text = "Record"; + recordButton.color = hifi.buttons.black; + outputLeftCh.checked = _outputLeftState; + outputRightCh.checked = _outputRightState; + } else { + recordButton.text = "Stop"; + recordButton.color = hifi.buttons.red; + _outputLeftState = outputLeftCh.checked; + _outputRightState = outputRightCh.checked; + outputLeftCh.checked = true; + outputRightCh.checked = true; + } + } + + function toggleRecording() { + if (Audio.getRecording()) { + updateRecordingUI(false); + stopRecording(); + } else { + updateRecordingUI(true); + startRecording(); + } + } + Timer { interval: _refreshMs; running: true; repeat: true onTriggered: pullFreshValues() @@ -306,7 +372,7 @@ Item { boxSize: 20 anchors.top: parent.top; anchors.left: parent.left; - anchors.topMargin: 20; + anchors.topMargin: 8; anchors.leftMargin: 20; checked: AudioScope.getVisible(); onCheckedChanged: { @@ -333,6 +399,7 @@ Item { AudioScope.setServerEcho(outputLeftCh.checked || outputRightCh.checked); } } + HifiControlsUit.Label { text: "Channels"; anchors.horizontalCenter: outputLeftCh.horizontalCenter; @@ -346,9 +413,9 @@ Item { text: "Input Mono" anchors.bottom: outputLeftCh.bottom; anchors.right: outputLeftCh.left; - anchors.rightMargin: 80; - checked: true; + anchors.rightMargin: 40; onCheckedChanged: { + AudioScope.setLocalEcho(checked); } } @@ -358,21 +425,38 @@ Item { text: "Output R" anchors.bottom: outputLeftCh.bottom; anchors.left: outputLeftCh.right; - anchors.leftMargin: 80; + anchors.leftMargin: 40; onCheckedChanged: { AudioScope.setServerEcho(outputLeftCh.checked || outputRightCh.checked); } } + HifiControlsUit.Button { + id: recordButton; + text: "Record"; + color: hifi.buttons.black; + colorScheme: hifi.colorSchemes.dark; + anchors.right: parent.right; + anchors.bottom: parent.bottom; + anchors.rightMargin: 30; + anchors.bottomMargin: 8; + width: 95; + height: 55; + onClicked: { + toggleRecording(); + } + } + HifiControlsUit.Button { id: pauseButton; color: hifi.buttons.black; colorScheme: hifi.colorSchemes.dark; - anchors.right: parent.right; + anchors.right: recordButton.left; anchors.bottom: parent.bottom; anchors.rightMargin: 30; anchors.bottomMargin: 8; - height: 26; + height: 55; + width: 95; text: " Pause "; onClicked: { AudioScope.togglePause(); @@ -391,8 +475,7 @@ Item { fiveFrames.checked = false; AudioScope.selectAudioScopeTwentyFrames(); _steps = 20; - _triggered = false; - AudioScope.setTriggered(false); + AudioScope.setPause(false); } } } @@ -432,8 +515,7 @@ Item { twentyFrames.checked = false; AudioScope.selectAudioScopeFiveFrames(); _steps = 5; - _triggered = false; - AudioScope.setTriggered(false); + AudioScope.setPause(false); } } } @@ -457,8 +539,7 @@ Item { fiveFrames.checked = false; AudioScope.selectAudioScopeFiftyFrames(); _steps = 50; - _triggered = false; - AudioScope.setTriggered(false); + AudioScope.setPause(false); } } } @@ -480,9 +561,9 @@ Item { labelTextOn: "On"; onCheckedChanged: { if (!checked) AudioScope.setPause(false); - _triggered = false; - AudioScope.setTriggered(false); + AudioScope.setPause(false); AudioScope.setAutoTrigger(checked); + AudioScope.setTriggerValues(_triggerValues.x, _triggerValues.y-root.height/2); } } @@ -493,21 +574,68 @@ Item { anchors.bottom: triggerSwitch.top; } + Rectangle { + id: recordIcon; + width:110; + height:40; + anchors.right: parent.right; + anchors.top: parent.top; + anchors.topMargin: 8; + color: "transparent" + + Text { + id: recText + text: "REC" + color: "red" + font.pixelSize: 30; + anchors.left: recCircle.right; + anchors.leftMargin: 10; + opacity: _recOpacity; + y: -8; + } + + Rectangle { + id: recCircle; + width: 25; + height: 25; + radius: width*0.5 + opacity: _recOpacity; + color: "red"; + } + } + Component.onCompleted: { _steps = AudioScope.getFramesPerScope(); AudioScope.setTriggerValues(_triggerValues.x, _triggerValues.y-root.height/2); activated.checked = true; + inputCh.checked = true; updateMeasureUnits(); } Component.onDestruction: { + if (Audio.getRecording()) { + stopRecording(); + } AudioScope.setVisible(false); } Connections { target: AudioScope onPauseChanged: { - pauseButton.text = AudioScope.getPause() ? "Continue" : " Pause "; + if (!AudioScope.getPause()) { + pauseButton.text = "Pause"; + pauseButton.color = hifi.buttons.black; + AudioScope.setTriggered(false); + _triggered = false; + } else { + pauseButton.text = "Continue"; + pauseButton.color = hifi.buttons.blue; + } + } + onTriggered: { + _triggered = true; + collectTriggerData(); + AudioScope.setPause(true); } } } diff --git a/interface/src/audio/AudioScope.cpp b/interface/src/audio/AudioScope.cpp index 44f25ae5a9..1a2e867d51 100644 --- a/interface/src/audio/AudioScope.cpp +++ b/interface/src/audio/AudioScope.cpp @@ -78,6 +78,10 @@ void AudioScope::selectAudioScopeFiftyFrames() { reallocateScope(50); } +void AudioScope::setLocalEcho(bool localEcho) { + DependencyManager::get()->setLocalEcho(localEcho); +} + void AudioScope::setServerEcho(bool serverEcho) { DependencyManager::get()->setServerEcho(serverEcho); } @@ -191,6 +195,7 @@ void AudioScope::storeTriggerValues() { _triggerOutputLeftData = _scopeOutputLeftData; _triggerOutputRightData = _scopeOutputRightData; _isTriggered = true; + emit triggered(); } void AudioScope::computeInputData() { diff --git a/interface/src/audio/AudioScope.h b/interface/src/audio/AudioScope.h index 33d88f8fe0..e99b8378e3 100644 --- a/interface/src/audio/AudioScope.h +++ b/interface/src/audio/AudioScope.h @@ -71,10 +71,12 @@ public slots: QVector getTriggerOutputLeft() { return _triggerOutputLeftData; }; QVector getTriggerOutputRight() { return _triggerOutputRightData; }; + void setLocalEcho(bool serverEcho); void setServerEcho(bool serverEcho); signals: void pauseChanged(); + void triggered(); protected: AudioScope(); diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index f9c1a95fb5..7ed5c2ead9 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -58,6 +58,32 @@ Audio::Audio() : _devices(_contextIsHMD) { enableNoiseReduction(enableNoiseReductionSetting.get()); } +void Audio::onOutputBufferReceived(const QByteArray outputBuffer) { + if (_isRecording) { + _audioFileWav.addRawAudioChunk((char*)outputBuffer.data(), outputBuffer.size()); + } +} + +bool Audio::startRecording(const QString& filepath) { + auto client = DependencyManager::get().data(); + if (!_audioFileWav.create(client->getOutputFormat(), filepath)) { + qDebug() << "Error creating audio file: "+filepath; + return false; + } + connect(client, &AudioClient::outputBufferReceived, this, &Audio::onOutputBufferReceived); + _isRecording = true; + return true; +} + +void Audio::stopRecording() { + auto client = DependencyManager::get().data(); + disconnect(client, &AudioClient::outputBufferReceived, this, 0); + if (_isRecording) { + _isRecording = false; + _audioFileWav.close(); + } +} + void Audio::setMuted(bool isMuted) { if (_isMuted != isMuted) { auto client = DependencyManager::get().data(); diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index abd2312cf0..7e10761970 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -16,6 +16,7 @@ #include "AudioDevices.h" #include "AudioEffectOptions.h" #include "SettingHandle.h" +#include "AudioFileWav.h" namespace scripting { @@ -55,6 +56,10 @@ public: Q_INVOKABLE void setReverb(bool enable); Q_INVOKABLE void setReverbOptions(const AudioEffectOptions* options); + Q_INVOKABLE bool startRecording(const QString& filename); + Q_INVOKABLE void stopRecording(); + Q_INVOKABLE bool getRecording() { return _isRecording; }; + signals: void nop(); void mutedChanged(bool isMuted); @@ -71,6 +76,7 @@ private slots: void onNoiseReductionChanged(); void onInputVolumeChanged(float volume); void onInputLoudnessChanged(float loudness); + void onOutputBufferReceived(const QByteArray outputBuffer); protected: // Audio must live on a separate thread from AudioClient to avoid deadlocks @@ -83,9 +89,10 @@ private: bool _isMuted { false }; bool _enableNoiseReduction { true }; // Match default value of AudioClient::_isNoiseGateEnabled. bool _contextIsHMD { false }; - + bool _isRecording { false }; AudioDevices* getDevices() { return &_devices; } AudioDevices _devices; + AudioFileWav _audioFileWav; }; }; diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 78475f5b68..96b96d2bb1 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -222,8 +222,7 @@ AudioClient::AudioClient() : // initialize wasapi; if getAvailableDevices is called from the CheckDevicesThread before this, it will crash getAvailableDevices(QAudio::AudioInput); getAvailableDevices(QAudio::AudioOutput); - - + // start a thread to detect any device changes _checkDevicesTimer = new QTimer(this); connect(_checkDevicesTimer, &QTimer::timeout, [this] { @@ -1845,11 +1844,9 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) { qCDebug(audiostream, "Read %d samples from buffer (%d available, %d requested)", networkSamplesPopped, _receivedAudioStream.getSamplesAvailable(), samplesRequested); AudioRingBuffer::ConstIterator lastPopOutput = _receivedAudioStream.getLastPopOutput(); lastPopOutput.readSamples(scratchBuffer, networkSamplesPopped); - for (int i = 0; i < networkSamplesPopped; i++) { mixBuffer[i] = convertToFloat(scratchBuffer[i]); } - samplesRequested = networkSamplesPopped; } @@ -1911,6 +1908,10 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) { bytesWritten = maxSize; } + // send output buffer for recording + QByteArray outputBuffer(reinterpret_cast(scratchBuffer), bytesWritten); + emit _audio->outputBufferReceived(outputBuffer); + int bytesAudioOutputUnplayed = _audio->_audioOutput->bufferSize() - _audio->_audioOutput->bytesFree(); float msecsAudioOutputUnplayed = bytesAudioOutputUnplayed / (float)_audio->_outputFormat.bytesForDuration(USECS_PER_MSEC); _audio->_stats.updateOutputMsUnplayed(msecsAudioOutputUnplayed); diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 4dd7436251..eb9de29411 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -47,11 +47,13 @@ #include #include + #include #include #include "AudioIOStats.h" +#include "AudioFileWav.h" #ifdef _WIN32 #pragma warning( push ) @@ -67,7 +69,6 @@ class QAudioInput; class QAudioOutput; class QIODevice; - class Transform; class NLPacket; @@ -118,6 +119,8 @@ public: const MixedProcessedAudioStream& getReceivedAudioStream() const { return _receivedAudioStream; } MixedProcessedAudioStream& getReceivedAudioStream() { return _receivedAudioStream; } + const QAudioFormat& getOutputFormat() const { return _outputFormat; } + float getLastInputLoudness() const { return _lastInputLoudness; } // TODO: relative to noise floor? float getTimeSinceLastClip() const { return _timeSinceLastClip; } @@ -142,7 +145,7 @@ public: void setIsPlayingBackRecording(bool isPlayingBackRecording) { _isPlayingBackRecording = isPlayingBackRecording; } Q_INVOKABLE void setAvatarBoundingBoxParameters(glm::vec3 corner, glm::vec3 scale); - + bool outputLocalInjector(const AudioInjectorPointer& injector) override; QAudioDeviceInfo getActiveAudioDevice(QAudio::Mode mode) const; @@ -184,12 +187,13 @@ public slots: void toggleMute(); bool isMuted() { return _muted; } - virtual void setIsStereoInput(bool stereo) override; void setNoiseReduction(bool isNoiseGateEnabled); bool isNoiseReductionEnabled() const { return _isNoiseGateEnabled; } + bool getLocalEcho() { return _shouldEchoLocally; } + void setLocalEcho(bool localEcho) { _shouldEchoLocally = localEcho; } void toggleLocalEcho() { _shouldEchoLocally = !_shouldEchoLocally; } bool getServerEcho() { return _shouldEchoToServer; } @@ -242,6 +246,8 @@ signals: void muteEnvironmentRequested(glm::vec3 position, float radius); + void outputBufferReceived(const QByteArray _outputBuffer); + protected: AudioClient(); ~AudioClient(); @@ -357,9 +363,8 @@ private: int16_t _localScratchBuffer[AudioConstants::NETWORK_FRAME_SAMPLES_AMBISONIC]; float* _localOutputMixBuffer { NULL }; Mutex _localAudioMutex; - AudioLimiter _audioLimiter; - + // Adds Reverb void configureReverb(); void updateReverbOptions(); diff --git a/libraries/audio-client/src/AudioFileWav.cpp b/libraries/audio-client/src/AudioFileWav.cpp new file mode 100644 index 0000000000..613628883c --- /dev/null +++ b/libraries/audio-client/src/AudioFileWav.cpp @@ -0,0 +1,69 @@ +// +// AudioWavFile.h +// libraries/audio-client/src +// +// Created by Luis Cuenca on 12/1/2017. +// Copyright 2017 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 "AudioFileWav.h" + +bool AudioFileWav::create(const QAudioFormat& audioFormat, const QString& filepath) { + if (_file.isOpen()) { + _file.close(); + } + _file.setFileName(filepath); + if (!_file.open(QIODevice::WriteOnly)) { + return false; + } + addHeader(audioFormat); + return true; +} + +bool AudioFileWav::addRawAudioChunk(char* chunk, int size) { + if (_file.isOpen()) { + QDataStream stream(&_file); + stream.writeRawData(chunk, size); + return true; + } + return false; +} + +void AudioFileWav::close() { + QDataStream stream(&_file); + stream.setByteOrder(QDataStream::LittleEndian); + + // fill RIFF and size data on header + _file.seek(4); + stream << quint32(_file.size() - 8); + _file.seek(40); + stream << quint32(_file.size() - 44); + _file.close(); +} + +void AudioFileWav::addHeader(const QAudioFormat& audioFormat) { + QDataStream stream(&_file); + + stream.setByteOrder(QDataStream::LittleEndian); + + // RIFF + stream.writeRawData("RIFF", 4); + stream << quint32(0); + stream.writeRawData("WAVE", 4); + + // Format description PCM = 16 + stream.writeRawData("fmt ", 4); + stream << quint32(16); + stream << quint16(1); + stream << quint16(audioFormat.channelCount()); + stream << quint32(audioFormat.sampleRate()); + stream << quint32(audioFormat.sampleRate() * audioFormat.channelCount() * audioFormat.sampleSize() / 8); // bytes per second + stream << quint16(audioFormat.channelCount() * audioFormat.sampleSize() / 8); // block align + stream << quint16(audioFormat.sampleSize()); // bits Per Sample + // Init data chunck + stream.writeRawData("data", 4); + stream << quint32(0); +} diff --git a/libraries/audio-client/src/AudioFileWav.h b/libraries/audio-client/src/AudioFileWav.h new file mode 100644 index 0000000000..7e9c83a23b --- /dev/null +++ b/libraries/audio-client/src/AudioFileWav.h @@ -0,0 +1,34 @@ +// +// AudioWavFile.h +// libraries/audio-client/src +// +// Created by Luis Cuenca on 12/1/2017. +// Copyright 2017 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_AudioFileWav_h +#define hifi_AudioFileWav_h + +#include +#include +#include +#include +#include + +class AudioFileWav : public QObject { + Q_OBJECT +public: + AudioFileWav() {} + bool create(const QAudioFormat& audioFormat, const QString& filepath); + bool addRawAudioChunk(char* chunk, int size); + void close(); + +private: + void addHeader(const QAudioFormat& audioFormat); + QFile _file; +}; + +#endif // hifi_AudioFileWav_h \ No newline at end of file From d60f0827966a9082ce19129b866edd35912690fd Mon Sep 17 00:00:00 2001 From: Alexander Ivash Date: Sun, 3 Dec 2017 23:24:34 +0300 Subject: [PATCH 25/47] 9706 Editing Attachments Results In Attachments Window Scrolling To Top --- .../dialogs/content/AttachmentsContent.qml | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/interface/resources/qml/hifi/dialogs/content/AttachmentsContent.qml b/interface/resources/qml/hifi/dialogs/content/AttachmentsContent.qml index 5c9d6822c8..0e0786975e 100644 --- a/interface/resources/qml/hifi/dialogs/content/AttachmentsContent.qml +++ b/interface/resources/qml/hifi/dialogs/content/AttachmentsContent.qml @@ -26,6 +26,7 @@ Item { } Connections { + id: onAttachmentsChangedConnection target: MyAvatar onAttachmentsChanged: reload() } @@ -34,6 +35,12 @@ Item { reload() } + function setAttachmentsVariant(attachments) { + onAttachmentsChangedConnection.enabled = false; + MyAvatar.setAttachmentsVariant(attachments); + onAttachmentsChangedConnection.enabled = true; + } + Column { width: pane.width @@ -92,11 +99,15 @@ Item { attachments.splice(index, 1); listView.model.remove(index, 1); } - onUpdateAttachment: MyAvatar.setAttachmentsVariant(attachments); + onUpdateAttachment: { + setAttachmentsVariant(attachments); + } } } - onCountChanged: MyAvatar.setAttachmentsVariant(attachments); + onCountChanged: { + setAttachmentsVariant(attachments); + } /* // DEBUG @@ -220,7 +231,7 @@ Item { }; attachments.push(template); listView.model.append({}); - MyAvatar.setAttachmentsVariant(attachments); + setAttachmentsVariant(attachments); } } @@ -250,7 +261,7 @@ Item { id: cancelAction text: "Cancel" onTriggered: { - MyAvatar.setAttachmentsVariant(originalAttachments); + setAttachmentsVariant(originalAttachments); closeDialog(); } } @@ -263,7 +274,7 @@ Item { console.log("Attachment " + i + ": " + attachments[i]); } - MyAvatar.setAttachmentsVariant(attachments); + setAttachmentsVariant(attachments); closeDialog(); } } From a1bf54ff005df24d296da280e4d2970e44c6cb5a Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Mon, 4 Dec 2017 11:16:41 -0800 Subject: [PATCH 26/47] fix issue of no_bootstrapping not being set correctly --- libraries/entities/src/EntityItem.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 6ab4b3ccbe..29b104b421 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1834,7 +1834,7 @@ void EntityItem::computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask } } - if (_dirtyFlags & Simulation::NO_BOOTSTRAPPING) { + if ((bool)(_dirtyFlags & Simulation::NO_BOOTSTRAPPING)) { userMask &= ~USER_COLLISION_GROUP_MY_AVATAR; } mask = Physics::getDefaultCollisionMask(group) & (int16_t)(userMask); @@ -2028,7 +2028,7 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi serializeActions(success, _allActionsDataCache); _dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION; _dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar - if (stillHasGrabActions() && !(_dirtyFlags & Simulation::NO_BOOTSTRAPPING)) { + if (stillHasGrabActions()) { _dirtyFlags |= Simulation::NO_BOOTSTRAPPING; forEachDescendant([&](SpatiallyNestablePointer child) { if (child->getNestableType() == NestableType::Entity) { @@ -2036,7 +2036,7 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi entity->markDirtyFlags(Simulation::NO_BOOTSTRAPPING | Simulation::DIRTY_COLLISION_GROUP); } }); - } else if (_dirtyFlags & Simulation::NO_BOOTSTRAPPING) { + } else if ((bool)(_dirtyFlags & Simulation::NO_BOOTSTRAPPING)) { _dirtyFlags &= ~Simulation::NO_BOOTSTRAPPING; forEachDescendant([&](SpatiallyNestablePointer child) { if (child->getNestableType() == NestableType::Entity) { From 390c59b45c57591ff87655b28ca5602836f4520a Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Mon, 4 Dec 2017 12:11:17 -0800 Subject: [PATCH 27/47] Change menu and minor fixes --- interface/resources/qml/AudioScope.qml | 6 +++- interface/src/Menu.cpp | 29 +------------------ .../developer/utilities/audio/audioScope.js | 13 +++++++-- 3 files changed, 16 insertions(+), 32 deletions(-) diff --git a/interface/resources/qml/AudioScope.qml b/interface/resources/qml/AudioScope.qml index 1f67b3f090..677567c5c2 100644 --- a/interface/resources/qml/AudioScope.qml +++ b/interface/resources/qml/AudioScope.qml @@ -376,7 +376,7 @@ Item { anchors.leftMargin: 20; checked: AudioScope.getVisible(); onCheckedChanged: { - AudioScope.setVisible(!AudioScope.getVisible()); + AudioScope.setVisible(checked); activelabel.text = AudioScope.getVisible() ? "On" : "Off" } } @@ -617,6 +617,10 @@ Item { stopRecording(); } AudioScope.setVisible(false); + AudioScope.setLocalEcho(false); + AudioScope.setServerEcho(false); + AudioScope.selectAudioScopeFiveFrames(); + console.log("Component Destroyed"); } Connections { diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 7292594b53..9bbb72357b 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -679,43 +679,16 @@ Menu::Menu() { }); auto audioIO = DependencyManager::get(); - addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::EchoServerAudio, 0, false, - audioIO.data(), SLOT(toggleServerEcho())); - addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::EchoLocalAudio, 0, false, - audioIO.data(), SLOT(toggleLocalEcho())); addActionToQMenuAndActionHash(audioDebugMenu, MenuOption::MuteEnvironment, 0, audioIO.data(), SLOT(sendMuteEnvironmentPacket())); - auto scope = DependencyManager::get(); - MenuWrapper* audioScopeMenu = audioDebugMenu->addMenu("Audio Scope"); - - action = addActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScope); + action = addActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioScope); connect(action, &QAction::triggered, [] { auto scriptEngines = DependencyManager::get(); QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation(); defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/utilities/audio/audioScope.js"); scriptEngines->loadScript(defaultScriptsLoc.toString()); }); - - addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopePause, Qt::CTRL | Qt::SHIFT | Qt::Key_F2, false, - scope.data(), SLOT(togglePause())); - - addDisabledActionAndSeparator(audioScopeMenu, "Display Frames"); - { - QAction* fiveFrames = addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopeFiveFrames, - 0, true, scope.data(), SLOT(selectAudioScopeFiveFrames())); - - QAction* twentyFrames = addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopeTwentyFrames, - 0, false, scope.data(), SLOT(selectAudioScopeTwentyFrames())); - - QAction* fiftyFrames = addCheckableActionToQMenuAndActionHash(audioScopeMenu, MenuOption::AudioScopeFiftyFrames, - 0, false, scope.data(), SLOT(selectAudioScopeFiftyFrames())); - - QActionGroup* audioScopeFramesGroup = new QActionGroup(audioScopeMenu); - audioScopeFramesGroup->addAction(fiveFrames); - audioScopeFramesGroup->addAction(twentyFrames); - audioScopeFramesGroup->addAction(fiftyFrames); - } // Developer > Physics >>> MenuWrapper* physicsOptionsMenu = developerMenu->addMenu("Physics"); diff --git a/scripts/developer/utilities/audio/audioScope.js b/scripts/developer/utilities/audio/audioScope.js index 3331482fbd..00c9e4b725 100644 --- a/scripts/developer/utilities/audio/audioScope.js +++ b/scripts/developer/utilities/audio/audioScope.js @@ -1,10 +1,17 @@ var qml = Script.resourcesPath() + '/qml/AudioScope.qml'; -var viewdim = Controller.getViewportDimensions(); var window = new OverlayWindow({ title: 'Audio Scope', source: qml, width: 1200, height: 500 }); -//window.setPosition(0.1*viewdim, 0.2*viewdim); -window.closed.connect(function () { Script.stop(); }); \ No newline at end of file +window.closed.connect(function () { + if (Audio.getRecording()) { + Audio.stopRecording(); + } + AudioScope.setVisible(false); + AudioScope.setLocalEcho(false); + AudioScope.setServerEcho(false); + AudioScope.selectAudioScopeFiveFrames(); + Script.stop(); +}); \ No newline at end of file From d70a3685b8bdd570f116461a655da721e980fd00 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Mon, 4 Dec 2017 14:41:27 -0800 Subject: [PATCH 28/47] Clean up --- interface/resources/qml/AudioScope.qml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/interface/resources/qml/AudioScope.qml b/interface/resources/qml/AudioScope.qml index 677567c5c2..aea1473c3d 100644 --- a/interface/resources/qml/AudioScope.qml +++ b/interface/resources/qml/AudioScope.qml @@ -611,17 +611,6 @@ Item { inputCh.checked = true; updateMeasureUnits(); } - - Component.onDestruction: { - if (Audio.getRecording()) { - stopRecording(); - } - AudioScope.setVisible(false); - AudioScope.setLocalEcho(false); - AudioScope.setServerEcho(false); - AudioScope.selectAudioScopeFiveFrames(); - console.log("Component Destroyed"); - } Connections { target: AudioScope From 486cdf14d2a71824b079af5ba0b427787b373a91 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Mon, 4 Dec 2017 15:34:53 -0800 Subject: [PATCH 29/47] make sure that NO_BOOTSTRAPPING is disbaled correctly if parent is changed --- libraries/entities/src/EntityItem.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 29b104b421..82e364486b 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1605,6 +1605,25 @@ void EntityItem::setParentID(const QUuid& value) { if (tree && !oldParentID.isNull()) { tree->removeFromChildrenOfAvatars(getThisPointer()); } + + bool enableNoBootStrapping = false; + if (!value.isNull() && tree) { + EntityItemPointer entity = tree->findEntityByEntityItemID(value); + if (entity) { + enableNoBootStrapping = (bool)(entity->getDirtyFlags() & Simulation::NO_BOOTSTRAPPING); + } + } + + enableNoBootStrapping ? markDirtyFlags(Simulation::NO_BOOTSTRAPPING) : clearDirtyFlags(Simulation::NO_BOOTSTRAPPING); + forEachDescendant([&](SpatiallyNestablePointer object) { + if (object->getNestableType() == NestableType::Entity) { + EntityItemPointer entity = std::static_pointer_cast(object); + entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP); + enableNoBootStrapping ? entity->markDirtyFlags(Simulation::NO_BOOTSTRAPPING) : + entity->clearDirtyFlags(Simulation::NO_BOOTSTRAPPING); + } + }); + SpatiallyNestable::setParentID(value); // children are forced to be kinematic // may need to not collide with own avatar From c58286d371ef8145df2f91f832f1a3698afe19fb Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 5 Dec 2017 09:31:35 -0800 Subject: [PATCH 30/47] dont do work if not needed --- libraries/entities/src/EntityItem.cpp | 39 ++++++++++++++++++++------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 82e364486b..453e78b305 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1606,23 +1606,42 @@ void EntityItem::setParentID(const QUuid& value) { tree->removeFromChildrenOfAvatars(getThisPointer()); } - bool enableNoBootStrapping = false; + uint32_t oldParentNoBootstrapping = 0; + uint32_t newParentNoBootstrapping = 0; if (!value.isNull() && tree) { EntityItemPointer entity = tree->findEntityByEntityItemID(value); if (entity) { - enableNoBootStrapping = (bool)(entity->getDirtyFlags() & Simulation::NO_BOOTSTRAPPING); + newParentNoBootstrapping = entity->getDirtyFlags() & Simulation::NO_BOOTSTRAPPING; } } - enableNoBootStrapping ? markDirtyFlags(Simulation::NO_BOOTSTRAPPING) : clearDirtyFlags(Simulation::NO_BOOTSTRAPPING); - forEachDescendant([&](SpatiallyNestablePointer object) { - if (object->getNestableType() == NestableType::Entity) { - EntityItemPointer entity = std::static_pointer_cast(object); - entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP); - enableNoBootStrapping ? entity->markDirtyFlags(Simulation::NO_BOOTSTRAPPING) : - entity->clearDirtyFlags(Simulation::NO_BOOTSTRAPPING); + if (!oldParentID.isNull() && tree) { + EntityItemPointer entity = tree->findEntityByEntityItemID(oldParentID); + if (entity) { + oldParentNoBootstrapping = entity->getDirtyFlags() & Simulation::NO_BOOTSTRAPPING; } - }); + } + + if ((bool)(oldParentNoBootstrapping ^ newParentNoBootstrapping)) { + if ((bool)(newParentNoBootstrapping & Simulation::NO_BOOTSTRAPPING)) { + markDirtyFlags(Simulation::NO_BOOTSTRAPPING); + forEachDescendant([&](SpatiallyNestablePointer object) { + if (object->getNestableType() == NestableType::Entity) { + EntityItemPointer entity = std::static_pointer_cast(object); + entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP | Simulation::NO_BOOTSTRAPPING); + } + }); + } else { + clearDirtyFlags(Simulation::NO_BOOTSTRAPPING); + forEachDescendant([&](SpatiallyNestablePointer object) { + if (object->getNestableType() == NestableType::Entity) { + EntityItemPointer entity = std::static_pointer_cast(object); + entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP); + entity->clearDirtyFlags(Simulation::NO_BOOTSTRAPPING); + } + }); + } + } SpatiallyNestable::setParentID(value); // children are forced to be kinematic From bbc022da2d0cc24ab0c147be7256959051950c87 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 5 Dec 2017 14:09:46 -0800 Subject: [PATCH 31/47] fix-far-to-equip --- scripts/system/controllers/controllerModules/equipEntity.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index b8c20d5bd6..7edeae0a53 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -10,7 +10,7 @@ getControllerJointIndex, enableDispatcherModule, disableDispatcherModule, Messages, makeDispatcherModuleParameters, makeRunningValues, Settings, entityHasActions, Vec3, Overlays, flatten, Xform, getControllerWorldLocation, ensureDynamic, entityIsCloneable, - cloneEntity, DISPATCHER_PROPERTIES + cloneEntity, DISPATCHER_PROPERTIES, TEAR_AWAY_DISTANCE */ Script.include("/~/system/libraries/Xform.js"); @@ -162,7 +162,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var ATTACH_POINT_SETTINGS = "io.highfidelity.attachPoints"; - var EQUIP_RADIUS = 0.2; // radius used for palm vs equip-hotspot for equipping. + var EQUIP_RADIUS = 0.25; // radius used for palm vs equip-hotspot for equipping. var HAPTIC_PULSE_STRENGTH = 1.0; var HAPTIC_PULSE_DURATION = 13.0; From c007c6d00f5317c0d9fc6afb7d1ff4da9eceeef6 Mon Sep 17 00:00:00 2001 From: Alexander Ivash Date: Wed, 6 Dec 2017 02:47:19 +0300 Subject: [PATCH 32/47] 10108 marketplace category cannot scroll --- scripts/system/html/js/marketplacesInject.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 1346bcd750..878c3b51f1 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -327,6 +327,19 @@ }); } + // fix for 10108 - marketplace category cannot scroll + function injectAddScrollbarToCategories() { + $('#categories-dropdown').on('show.bs.dropdown', function () { + $('body > div.container').css('display', 'none') + $('#categories-dropdown > ul.dropdown-menu').css({ 'overflow': 'auto', 'height': 'calc(100vh - 110px)' }) + }); + + $('#categories-dropdown').on('hide.bs.dropdown', function () { + $('body > div.container').css('display', '') + $('#categories-dropdown > ul.dropdown-menu').css({ 'overflow': '', 'height': '' }) + }); + } + function injectHiFiCode() { if (commerceMode) { maybeAddLogInButton(); @@ -358,6 +371,7 @@ } injectUnfocusOnSearch(); + injectAddScrollbarToCategories(); } function injectHiFiItemPageCode() { From 8af1d8d8d02c9590d5b0602956c7233d4b4eb545 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Tue, 5 Dec 2017 15:48:09 -0800 Subject: [PATCH 33/47] dont run code if not nesscassry --- libraries/entities/src/EntityItem.cpp | 37 +++++++++++++-------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 453e78b305..55b345cff5 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1968,17 +1968,19 @@ bool EntityItem::addActionInternal(EntitySimulationPointer simulation, EntityDyn if (success) { _allActionsDataCache = newDataCache; _dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION; - _dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar auto actionType = action->getType(); if (actionType == DYNAMIC_TYPE_HOLD || actionType == DYNAMIC_TYPE_FAR_GRAB) { - _dirtyFlags |= Simulation::NO_BOOTSTRAPPING; - forEachDescendant([&](SpatiallyNestablePointer child) { - if (child->getNestableType() == NestableType::Entity) { - EntityItemPointer entity = std::static_pointer_cast(child); - entity->markDirtyFlags(Simulation::NO_BOOTSTRAPPING | Simulation::DIRTY_COLLISION_GROUP); - } - }); + if (!(bool)(_dirtyFlags & Simulation::NO_BOOTSTRAPPING)) { + _dirtyFlags |= Simulation::NO_BOOTSTRAPPING; + _dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar + forEachDescendant([&](SpatiallyNestablePointer child) { + if (child->getNestableType() == NestableType::Entity) { + EntityItemPointer entity = std::static_pointer_cast(child); + entity->markDirtyFlags(Simulation::NO_BOOTSTRAPPING | Simulation::DIRTY_COLLISION_GROUP); + } + }); + } } } else { qCDebug(entities) << "EntityItem::addActionInternal -- serializeActions failed"; @@ -2056,7 +2058,6 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi action->setOwnerEntity(nullptr); action->setIsMine(false); - _objectActions.remove(actionID); if (simulation) { action->removeFromSimulation(simulation); @@ -2065,17 +2066,10 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi bool success = true; serializeActions(success, _allActionsDataCache); _dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION; - _dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar - if (stillHasGrabActions()) { - _dirtyFlags |= Simulation::NO_BOOTSTRAPPING; - forEachDescendant([&](SpatiallyNestablePointer child) { - if (child->getNestableType() == NestableType::Entity) { - EntityItemPointer entity = std::static_pointer_cast(child); - entity->markDirtyFlags(Simulation::NO_BOOTSTRAPPING | Simulation::DIRTY_COLLISION_GROUP); - } - }); - } else if ((bool)(_dirtyFlags & Simulation::NO_BOOTSTRAPPING)) { + auto removedActionType = action->getType(); + if ((removedActionType == DYNAMIC_TYPE_HOLD || removedActionType == DYNAMIC_TYPE_FAR_GRAB) && !stillHasGrabActions()) { _dirtyFlags &= ~Simulation::NO_BOOTSTRAPPING; + _dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar forEachDescendant([&](SpatiallyNestablePointer child) { if (child->getNestableType() == NestableType::Entity) { EntityItemPointer entity = std::static_pointer_cast(child); @@ -2083,7 +2077,12 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi entity->clearDirtyFlags(Simulation::NO_BOOTSTRAPPING); } }); + } else { + // NO-OP: we assume NO_BOOTSTRAPPING bits and collision group are correct + // because they should have been set correctly when the action was added + // and/or when children were linked } + _objectActions.remove(actionID); setDynamicDataNeedsTransmit(true); return success; } From 20d6615417172663a45c0ee8468e340b60cddfd4 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Tue, 5 Dec 2017 16:13:44 -0800 Subject: [PATCH 34/47] emit only when recording --- interface/src/scripting/Audio.cpp | 16 ++++++++++------ interface/src/scripting/Audio.h | 3 +-- libraries/audio-client/src/AudioClient.cpp | 7 +++++-- libraries/audio-client/src/AudioClient.h | 5 +++++ 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 7ed5c2ead9..0d0a845a7a 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -59,9 +59,7 @@ Audio::Audio() : _devices(_contextIsHMD) { } void Audio::onOutputBufferReceived(const QByteArray outputBuffer) { - if (_isRecording) { - _audioFileWav.addRawAudioChunk((char*)outputBuffer.data(), outputBuffer.size()); - } + _audioFileWav.addRawAudioChunk((char*)outputBuffer.data(), outputBuffer.size()); } bool Audio::startRecording(const QString& filepath) { @@ -71,15 +69,21 @@ bool Audio::startRecording(const QString& filepath) { return false; } connect(client, &AudioClient::outputBufferReceived, this, &Audio::onOutputBufferReceived); - _isRecording = true; + client->setRecording(true); return true; } +bool Audio::getRecording() { + auto client = DependencyManager::get().data(); + return client->getRecording(); +} + void Audio::stopRecording() { auto client = DependencyManager::get().data(); disconnect(client, &AudioClient::outputBufferReceived, this, 0); - if (_isRecording) { - _isRecording = false; + + if (client->getRecording()) { + client->setRecording(false); _audioFileWav.close(); } } diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index 7e10761970..dbedb99a0d 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -58,7 +58,7 @@ public: Q_INVOKABLE bool startRecording(const QString& filename); Q_INVOKABLE void stopRecording(); - Q_INVOKABLE bool getRecording() { return _isRecording; }; + Q_INVOKABLE bool getRecording(); signals: void nop(); @@ -89,7 +89,6 @@ private: bool _isMuted { false }; bool _enableNoiseReduction { true }; // Match default value of AudioClient::_isNoiseGateEnabled. bool _contextIsHMD { false }; - bool _isRecording { false }; AudioDevices* getDevices() { return &_devices; } AudioDevices _devices; AudioFileWav _audioFileWav; diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 96b96d2bb1..7524641f37 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1909,8 +1909,11 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) { } // send output buffer for recording - QByteArray outputBuffer(reinterpret_cast(scratchBuffer), bytesWritten); - emit _audio->outputBufferReceived(outputBuffer); + if (_audio->_isRecording) { + QByteArray outputBuffer(reinterpret_cast(scratchBuffer), bytesWritten); + emit _audio->outputBufferReceived(outputBuffer); + } + int bytesAudioOutputUnplayed = _audio->_audioOutput->bufferSize() - _audio->_audioOutput->bytesFree(); float msecsAudioOutputUnplayed = bytesAudioOutputUnplayed / (float)_audio->_outputFormat.bytesForDuration(USECS_PER_MSEC); diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index eb9de29411..5ec85aab0f 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -158,6 +158,9 @@ public: bool getNamedAudioDeviceForModeExists(QAudio::Mode mode, const QString& deviceName); + void setRecording(bool isRecording) { _isRecording = isRecording; }; + bool getRecording() { return _isRecording; }; + #ifdef Q_OS_WIN static QString getWinDeviceName(wchar_t* guid); #endif @@ -420,6 +423,8 @@ private: QTimer* _checkDevicesTimer { nullptr }; QTimer* _checkPeakValuesTimer { nullptr }; + + bool _isRecording { false }; }; From 8abe8b987944a18c2ef71c9db12b3caf6bd527d5 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Tue, 5 Dec 2017 18:22:14 -0800 Subject: [PATCH 35/47] move save logic to audioclient --- interface/src/scripting/Audio.cpp | 19 ++----------------- interface/src/scripting/Audio.h | 2 -- libraries/audio-client/src/AudioClient.cpp | 21 +++++++++++++++++++-- libraries/audio-client/src/AudioClient.h | 6 ++++++ 4 files changed, 27 insertions(+), 21 deletions(-) diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 0d0a845a7a..be9b4280f7 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -58,19 +58,9 @@ Audio::Audio() : _devices(_contextIsHMD) { enableNoiseReduction(enableNoiseReductionSetting.get()); } -void Audio::onOutputBufferReceived(const QByteArray outputBuffer) { - _audioFileWav.addRawAudioChunk((char*)outputBuffer.data(), outputBuffer.size()); -} - bool Audio::startRecording(const QString& filepath) { auto client = DependencyManager::get().data(); - if (!_audioFileWav.create(client->getOutputFormat(), filepath)) { - qDebug() << "Error creating audio file: "+filepath; - return false; - } - connect(client, &AudioClient::outputBufferReceived, this, &Audio::onOutputBufferReceived); - client->setRecording(true); - return true; + return client->startRecording(filepath); } bool Audio::getRecording() { @@ -80,12 +70,7 @@ bool Audio::getRecording() { void Audio::stopRecording() { auto client = DependencyManager::get().data(); - disconnect(client, &AudioClient::outputBufferReceived, this, 0); - - if (client->getRecording()) { - client->setRecording(false); - _audioFileWav.close(); - } + client->stopRecording(); } void Audio::setMuted(bool isMuted) { diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index dbedb99a0d..0f0043510c 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -76,7 +76,6 @@ private slots: void onNoiseReductionChanged(); void onInputVolumeChanged(float volume); void onInputLoudnessChanged(float loudness); - void onOutputBufferReceived(const QByteArray outputBuffer); protected: // Audio must live on a separate thread from AudioClient to avoid deadlocks @@ -91,7 +90,6 @@ private: bool _contextIsHMD { false }; AudioDevices* getDevices() { return &_devices; } AudioDevices _devices; - AudioFileWav _audioFileWav; }; }; diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 7524641f37..af86499101 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -79,6 +79,7 @@ Setting::Handle staticJitterBufferFrames("staticJitterBufferFrames", using Mutex = std::mutex; using Lock = std::unique_lock; Mutex _deviceMutex; +Mutex _recordMutex; // thread-safe QList getAvailableDevices(QAudio::Mode mode) { @@ -1910,8 +1911,8 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) { // send output buffer for recording if (_audio->_isRecording) { - QByteArray outputBuffer(reinterpret_cast(scratchBuffer), bytesWritten); - emit _audio->outputBufferReceived(outputBuffer); + Lock lock(_recordMutex); + _audio->_audioFileWav.addRawAudioChunk(reinterpret_cast(scratchBuffer), bytesWritten); } @@ -1926,6 +1927,22 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) { return bytesWritten; } +bool AudioClient::startRecording(const QString& filepath) { + if (!_audioFileWav.create(_outputFormat, filepath)) { + qDebug() << "Error creating audio file: " + filepath; + return false; + } + _isRecording = true; + return true; +} + +void AudioClient::stopRecording() { + if (_isRecording) { + _isRecording = false; + _audioFileWav.close(); + } +} + void AudioClient::loadSettings() { _receivedAudioStream.setDynamicJitterBufferEnabled(dynamicJitterBufferEnabled.get()); _receivedAudioStream.setStaticJitterBufferFrames(staticJitterBufferFrames.get()); diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 5ec85aab0f..0ceb9c4dc3 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -161,6 +161,10 @@ public: void setRecording(bool isRecording) { _isRecording = isRecording; }; bool getRecording() { return _isRecording; }; + bool startRecording(const QString& filename); + void stopRecording(); + + #ifdef Q_OS_WIN static QString getWinDeviceName(wchar_t* guid); #endif @@ -402,6 +406,8 @@ private: QList _inputDevices; QList _outputDevices; + AudioFileWav _audioFileWav; + bool _hasReceivedFirstPacket { false }; QVector _activeLocalAudioInjectors; From 0f3a70553c624769bb825afa54b3b526eb50af96 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 6 Dec 2017 11:18:15 -0800 Subject: [PATCH 36/47] fix dynamic entities that are parented to your avatar --- libraries/entities/src/EntityItem.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 55b345cff5..48370b02fd 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1622,6 +1622,10 @@ void EntityItem::setParentID(const QUuid& value) { } } + if (!value.isNull() && (value == Physics::getSessionUUID() || value == AVATAR_SELF_ID)) { + newParentNoBootstrapping |= Simulation::NO_BOOTSTRAPPING; + } + if ((bool)(oldParentNoBootstrapping ^ newParentNoBootstrapping)) { if ((bool)(newParentNoBootstrapping & Simulation::NO_BOOTSTRAPPING)) { markDirtyFlags(Simulation::NO_BOOTSTRAPPING); From 32fca16c8579badef6036ceb633547360848a384 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Wed, 6 Dec 2017 14:16:32 -0800 Subject: [PATCH 37/47] fix radius equiping --- .../controllers/controllerModules/equipEntity.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index 7edeae0a53..a250f77b2e 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -138,9 +138,9 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var dimensions; if (overlayInfoSet.type === "sphere") { - dimensions = overlayInfoSet.hotspot.radius * 2 * overlayInfoSet.currentSize * EQUIP_SPHERE_SCALE_FACTOR; + dimensions = (overlayInfoSet.hotspot.radius / 2) * overlayInfoSet.currentSize * EQUIP_SPHERE_SCALE_FACTOR; } else { - dimensions = overlayInfoSet.hotspot.radius * 2 * overlayInfoSet.currentSize; + dimensions = (overlayInfoSet.hotspot.radius / 2) * overlayInfoSet.currentSize; } overlayInfoSet.overlays.forEach(function(overlay) { @@ -162,7 +162,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var ATTACH_POINT_SETTINGS = "io.highfidelity.attachPoints"; - var EQUIP_RADIUS = 0.25; // radius used for palm vs equip-hotspot for equipping. + var EQUIP_RADIUS = 1.0; // radius used for palm vs equip-hotspot for equipping. var HAPTIC_PULSE_STRENGTH = 1.0; var HAPTIC_PULSE_DURATION = 13.0; @@ -322,7 +322,9 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa } } else { var wearableProps = getWearableData(props); + var sensorToScaleFactor = MyAvatar.sensorToWorldScale; if (wearableProps && wearableProps.joints) { + result.push({ key: entityID.toString() + "0", entityID: entityID, @@ -332,7 +334,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa z: 0 }, worldPosition: entityXform.pos, - radius: EQUIP_RADIUS, + radius: EQUIP_RADIUS * sensorToScaleFactor, joints: wearableProps.joints, modelURL: null, modelScale: null From ed76ca8018df6eeee3e96482f594cdda3e357cdb Mon Sep 17 00:00:00 2001 From: H Q Date: Wed, 6 Dec 2017 15:37:19 -0800 Subject: [PATCH 38/47] return to main mktplace page from rez page --- scripts/system/marketplaces/marketplaces.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 646e5452df..16787e1bfc 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -421,7 +421,8 @@ break; case 'checkout_itemLinkClicked': case 'checkout_continueShopping': - tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); + //tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); + tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); //tablet.popFromStack(); break; case 'purchases_itemInfoClicked': From 99c52f86021fc1f6a919d8aea6de53d8633f56a8 Mon Sep 17 00:00:00 2001 From: Sam Gondelman Date: Thu, 7 Dec 2017 09:38:14 -0800 Subject: [PATCH 39/47] coding standards --- interface/src/ui/overlays/Web3DOverlay.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/ui/overlays/Web3DOverlay.h b/interface/src/ui/overlays/Web3DOverlay.h index 33ab849e25..4098e98488 100644 --- a/interface/src/ui/overlays/Web3DOverlay.h +++ b/interface/src/ui/overlays/Web3DOverlay.h @@ -91,9 +91,9 @@ private: gpu::TexturePointer _texture; QString _url; QString _scriptURL; - float _dpi { 30 }; + float _dpi { 30.0f }; int _geometryId { 0 }; - bool _showKeyboardFocusHighlight{ true }; + bool _showKeyboardFocusHighlight { true }; QTouchDevice _touchDevice; From 21077da41ad47664c353cf0866246fd142b69384 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 7 Dec 2017 09:48:06 -0800 Subject: [PATCH 40/47] Don't send requests to backend if cached public keys is empty --- interface/src/commerce/QmlCommerce.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 435d15d161..69089df9c2 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -83,19 +83,28 @@ void QmlCommerce::buy(const QString& assetId, int cost, const bool controlledFai void QmlCommerce::balance() { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); - ledger->balance(wallet->listPublicKeys()); + QStringList cachedPublicKeys = wallet->listPublicKeys(); + if (!cachedPublicKeys.isEmpty()) { + ledger->balance(cachedPublicKeys); + } } void QmlCommerce::inventory() { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); - ledger->inventory(wallet->listPublicKeys()); + QStringList cachedPublicKeys = wallet->listPublicKeys(); + if (!cachedPublicKeys.isEmpty()) { + ledger->inventory(cachedPublicKeys); + } } void QmlCommerce::history() { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); - ledger->history(wallet->listPublicKeys()); + QStringList cachedPublicKeys = wallet->listPublicKeys(); + if (!cachedPublicKeys.isEmpty()) { + ledger->history(cachedPublicKeys); + } } void QmlCommerce::changePassphrase(const QString& oldPassphrase, const QString& newPassphrase) { From 095bedcd3fb062e3182e9a2d0c4171fef34fd833 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 7 Dec 2017 10:08:18 -0800 Subject: [PATCH 41/47] code review feedback --- .../avatars-renderer/src/avatars-renderer/Avatar.cpp | 2 +- libraries/avatars/src/AvatarData.cpp | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 44e75e5919..437ac623e3 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1604,7 +1604,7 @@ float Avatar::getUnscaledEyeHeight() const { // if we determine the mesh is much larger then the skeleton, then we use the mesh height instead. // This helps prevent absurdly large avatars from exceeding the domain height limit. - const float MESH_SLOP_RATIO = 1.5; + const float MESH_SLOP_RATIO = 1.5f; if (meshHeight > skeletonHeight * MESH_SLOP_RATIO) { return meshHeight; } else { diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 06fe8b597b..410c57c343 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -138,11 +138,19 @@ void AvatarData::setDomainMaximumHeight(float domainMaximumHeight) { float AvatarData::getDomainMinScale() const { const float unscaledHeight = getUnscaledHeight(); + const float EPSILON = 1.0e-4f; + if (unscaledHeight <= EPSILON) { + unscaledHeight = DEFAULT_AVATAR_HEIGHT; + } return _domainMinimumHeight / unscaledHeight; } float AvatarData::getDomainMaxScale() const { const float unscaledHeight = getUnscaledHeight(); + const float EPSILON = 1.0e-4f; + if (unscaledHeight <= EPSILON) { + unscaledHeight = DEFAULT_AVATAR_HEIGHT; + } return _domainMaximumHeight / unscaledHeight; } From b5ffda69116e7b53de6117a74439759810b4455b Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 7 Dec 2017 10:09:09 -0800 Subject: [PATCH 42/47] const fix --- libraries/avatars/src/AvatarData.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 410c57c343..f2053e29d7 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -137,7 +137,7 @@ void AvatarData::setDomainMaximumHeight(float domainMaximumHeight) { } float AvatarData::getDomainMinScale() const { - const float unscaledHeight = getUnscaledHeight(); + float unscaledHeight = getUnscaledHeight(); const float EPSILON = 1.0e-4f; if (unscaledHeight <= EPSILON) { unscaledHeight = DEFAULT_AVATAR_HEIGHT; @@ -146,7 +146,7 @@ float AvatarData::getDomainMinScale() const { } float AvatarData::getDomainMaxScale() const { - const float unscaledHeight = getUnscaledHeight(); + float unscaledHeight = getUnscaledHeight(); const float EPSILON = 1.0e-4f; if (unscaledHeight <= EPSILON) { unscaledHeight = DEFAULT_AVATAR_HEIGHT; From 47b52238d91bab462be296e85971bd0bb0229fea Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Thu, 7 Dec 2017 10:49:23 -0800 Subject: [PATCH 43/47] code review feedback --- interface/src/avatar/MyAvatar.cpp | 8 ++++---- interface/src/avatar/MyAvatar.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 2f608aca15..6d5a202128 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1969,7 +1969,7 @@ void MyAvatar::updateOrientation(float deltaTime) { if (qApp->isHMDMode() && getCharacterController()->getState() == CharacterController::State::Hover && _hmdRollControlEnabled && hasDriveInput()) { // Turn with head roll. - const float MIN_CONTROL_SPEED = 2.0f; // meters / sec + const float MIN_CONTROL_SPEED = 2.0f * getSensorToWorldScale(); // meters / sec const glm::vec3 characterForward = getWorldOrientation() * Vectors::UNIT_NEG_Z; float forwardSpeed = glm::dot(characterForward, getWorldVelocity()); @@ -1985,13 +1985,13 @@ void MyAvatar::updateOrientation(float deltaTime) { const float MAX_ROLL_ANGLE = 90.0f; // degrees if (rollAngle > MIN_ROLL_ANGLE) { - // rate of turning is linearly proportional to rolAngle + // rate of turning is linearly proportional to rollAngle rollAngle = glm::clamp(rollAngle, MIN_ROLL_ANGLE, MAX_ROLL_ANGLE); // scale rollAngle into a value from zero to one. - float t = (rollAngle - MIN_ROLL_ANGLE) / (MAX_ROLL_ANGLE - MIN_ROLL_ANGLE); + float rollFactor = (rollAngle - MIN_ROLL_ANGLE) / (MAX_ROLL_ANGLE - MIN_ROLL_ANGLE); - float angularSpeed = rollSign * t * _hmdRollControlRate; + float angularSpeed = rollSign * rollFactor * _hmdRollControlRate; totalBodyYaw += direction * angularSpeed * deltaTime; } } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 16cb0edfad..0902865f9e 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -741,7 +741,7 @@ private: bool _clearOverlayWhenMoving { true }; QString _dominantHand { DOMINANT_RIGHT_HAND }; - const float ROLL_CONTROL_DEAD_ZONE_DEFAULT = 8.0; // degrees + const float ROLL_CONTROL_DEAD_ZONE_DEFAULT = 8.0f; // degrees const float ROLL_CONTROL_RATE_DEFAULT = 114.0f; // degrees / sec bool _hmdRollControlEnabled { true }; From 72ed3b077df4328d4b28fbbe7c3956787acb8c57 Mon Sep 17 00:00:00 2001 From: H Q Date: Thu, 7 Dec 2017 11:28:40 -0800 Subject: [PATCH 44/47] Addressed PR change requests --- scripts/system/marketplaces/marketplaces.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 16787e1bfc..7a85462395 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -420,8 +420,9 @@ tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH); break; case 'checkout_itemLinkClicked': + tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); + break; case 'checkout_continueShopping': - //tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL); tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL); //tablet.popFromStack(); break; From fd38b89f35383d1847be5174a08b4625bae07f0f Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 8 Dec 2017 10:58:52 +1300 Subject: [PATCH 45/47] Move mouse and hover slots out of Overlays API --- interface/src/ui/overlays/Overlays.h | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index a8f504bbc5..e1996e6bfc 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -305,13 +305,6 @@ public slots: OverlayID getKeyboardFocusOverlay(); void setKeyboardFocusOverlay(const OverlayID& id); - void mousePressPointerEvent(const OverlayID& overlayID, const PointerEvent& event); - void mouseMovePointerEvent(const OverlayID& overlayID, const PointerEvent& event); - void mouseReleasePointerEvent(const OverlayID& overlayID, const PointerEvent& event); - void hoverEnterPointerEvent(const OverlayID& overlayID, const PointerEvent& event); - void hoverOverPointerEvent(const OverlayID& overlayID, const PointerEvent& event); - void hoverLeavePointerEvent(const OverlayID& overlayID, const PointerEvent& event); - signals: /**jsdoc * Emitted when an overlay is deleted @@ -358,6 +351,14 @@ private: OverlayID _currentHoverOverOverlayID { UNKNOWN_OVERLAY_ID }; RayToOverlayIntersectionResult findRayIntersectionForMouseEvent(PickRay ray); + +private slots: + void mousePressPointerEvent(const OverlayID& overlayID, const PointerEvent& event); + void mouseMovePointerEvent(const OverlayID& overlayID, const PointerEvent& event); + void mouseReleasePointerEvent(const OverlayID& overlayID, const PointerEvent& event); + void hoverEnterPointerEvent(const OverlayID& overlayID, const PointerEvent& event); + void hoverOverPointerEvent(const OverlayID& overlayID, const PointerEvent& event); + void hoverLeavePointerEvent(const OverlayID& overlayID, const PointerEvent& event); }; #endif // hifi_Overlays_h From 542af47e9edd7e5416643de01893a4d75138b1a6 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Wed, 29 Nov 2017 17:20:54 -0800 Subject: [PATCH 46/47] Fix for hand controller avatar scaling. * Added getDomainMaxScale() and getDomainMinScale() to JS api. * Updated scaleAvatar controller module to use this to prevent scaling past the limits. * Made sure that getDomainMaxScale() getDomainMinScale() and getUnscaledEyeHeight are thread safe, so that they can be invoked on the script thread. * Added signals to Model class that can be used to let observers know when the Rig has finished initializing it's skeleton. and also when the skeleton is no longer valid. These hooks are used to cache the unscaled eye height of the avatar. --- interface/src/avatar/MyAvatar.cpp | 3 +- .../src/avatars-renderer/Avatar.cpp | 30 +++++++++++++------ .../src/avatars-renderer/Avatar.h | 13 ++++++-- .../src/avatars-renderer/OtherAvatar.cpp | 2 ++ libraries/avatars/src/AvatarData.h | 18 +++++++++-- libraries/render-utils/src/Model.cpp | 2 ++ libraries/render-utils/src/Model.h | 2 ++ .../controllerModules/scaleAvatar.js | 6 +++- 8 files changed, 61 insertions(+), 15 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 1623fa3213..c874205611 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -114,7 +114,8 @@ MyAvatar::MyAvatar(QThread* thread) : _skeletonModel = std::make_shared(this, nullptr); connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished); - + connect(_skeletonModel.get(), &Model::rigReady, this, &Avatar::rigReady); + connect(_skeletonModel.get(), &Model::rigReset, this, &Avatar::rigReset); using namespace recording; _skeletonModel->flagAsCauterized(); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index 437ac623e3..bb7f141cd9 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -1217,6 +1217,15 @@ void Avatar::setModelURLFinished(bool success) { } } +// rig is ready +void Avatar::rigReady() { + buildUnscaledEyeHeightCache(); +} + +// rig has been reset. +void Avatar::rigReset() { + clearUnscaledEyeHeightCache(); +} // create new model, can return an instance of a SoftAttachmentModel rather then Model static std::shared_ptr allocateAttachmentModel(bool isSoft, const Rig& rigOverride, bool isCauterized) { @@ -1584,18 +1593,17 @@ void Avatar::ensureInScene(AvatarSharedPointer self, const render::ScenePointer& } } +// thread-safe float Avatar::getEyeHeight() const { - - if (QThread::currentThread() != thread()) { - float result = DEFAULT_AVATAR_EYE_HEIGHT; - BLOCKING_INVOKE_METHOD(const_cast(this), "getEyeHeight", Q_RETURN_ARG(float, result)); - return result; - } - return getModelScale() * getUnscaledEyeHeight(); } +// thread-safe float Avatar::getUnscaledEyeHeight() const { + return _unscaledEyeHeightCache.get(); +} + +void Avatar::buildUnscaledEyeHeightCache() { float skeletonHeight = getUnscaledEyeHeightFromSkeleton(); // Sanity check by looking at the model extents. @@ -1606,12 +1614,16 @@ float Avatar::getUnscaledEyeHeight() const { // This helps prevent absurdly large avatars from exceeding the domain height limit. const float MESH_SLOP_RATIO = 1.5f; if (meshHeight > skeletonHeight * MESH_SLOP_RATIO) { - return meshHeight; + _unscaledEyeHeightCache.set(meshHeight); } else { - return skeletonHeight; + _unscaledEyeHeightCache.set(skeletonHeight); } } +void Avatar::clearUnscaledEyeHeightCache() { + _unscaledEyeHeightCache.set(DEFAULT_AVATAR_EYE_HEIGHT); +} + float Avatar::getUnscaledEyeHeightFromSkeleton() const { // TODO: if performance becomes a concern we can cache this value rather then computing it everytime. diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 44d4ef4c51..c75b54fdc4 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -257,8 +257,8 @@ public: Q_INVOKABLE virtual float getEyeHeight() const override; - // returns eye height of avatar in meters, ignoreing avatar scale. - // if _targetScale is 1 then this will be identical to getEyeHeight; + // returns eye height of avatar in meters, ignoring avatar scale. + // if _targetScale is 1 then this will be identical to getEyeHeight. virtual float getUnscaledEyeHeight() const override; // returns true, if an acurate eye height estimage can be obtained by inspecting the avatar model skeleton and geometry, @@ -280,10 +280,17 @@ public slots: glm::vec3 getRightPalmPosition() const; glm::quat getRightPalmRotation() const; + // hooked up to Model::setURLFinished signal void setModelURLFinished(bool success); + // hooked up to Model::rigReady & rigReset signals + void rigReady(); + void rigReset(); + protected: float getUnscaledEyeHeightFromSkeleton() const; + void buildUnscaledEyeHeightCache(); + void clearUnscaledEyeHeightCache(); virtual const QString& getSessionDisplayNameForTransport() const override { return _empty; } // Save a tiny bit of bandwidth. Mixer won't look at what we send. QString _empty{}; virtual void maybeUpdateSessionDisplayNameFromTransport(const QString& sessionDisplayName) override { _sessionDisplayName = sessionDisplayName; } // don't use no-op setter! @@ -384,6 +391,8 @@ protected: float _displayNameTargetAlpha { 1.0f }; float _displayNameAlpha { 1.0f }; + + ThreadSafeValueCache _unscaledEyeHeightCache { DEFAULT_AVATAR_EYE_HEIGHT }; }; #endif // hifi_Avatar_h diff --git a/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.cpp index e870e2de12..4382216575 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/OtherAvatar.cpp @@ -13,4 +13,6 @@ OtherAvatar::OtherAvatar(QThread* thread) : Avatar(thread) { _headData = new Head(this); _skeletonModel = std::make_shared(this, nullptr); connect(_skeletonModel.get(), &Model::setURLFinished, this, &Avatar::setModelURLFinished); + connect(_skeletonModel.get(), &Model::rigReady, this, &Avatar::rigReady); + connect(_skeletonModel.get(), &Model::rigReset, this, &Avatar::rigReset); } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index de98675733..d7dd2837cb 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -483,8 +483,22 @@ public: virtual void setTargetScale(float targetScale); float getDomainLimitedScale() const; - float getDomainMinScale() const; - float getDomainMaxScale() const; + + /**jsdoc + * returns the minimum scale allowed for this avatar in the current domain. + * This value can change as the user changes avatars or when changing domains. + * @function AvatarData.getDomainMinScale + * @returns {number} minimum scale allowed for this avatar in the current domain. + */ + Q_INVOKABLE float getDomainMinScale() const; + + /**jsdoc + * returns the maximum scale allowed for this avatar in the current domain. + * This value can change as the user changes avatars or when changing domains. + * @function AvatarData.getDomainMaxScale + * @returns {number} maximum scale allowed for this avatar in the current domain. + */ + Q_INVOKABLE float getDomainMaxScale() const; // returns eye height of avatar in meters, ignoreing avatar scale. // if _targetScale is 1 then this will be identical to getEyeHeight; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 199cb29f53..c4bc435691 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -286,6 +286,7 @@ void Model::reset() { if (isLoaded()) { const FBXGeometry& geometry = getFBXGeometry(); _rig.reset(geometry); + emit rigReset(); } } @@ -322,6 +323,7 @@ bool Model::updateGeometry() { _blendedVertexBuffers.push_back(buffer); } needFullUpdate = true; + emit rigReady(); } return needFullUpdate; } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index fadd44b745..7568a17342 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -273,6 +273,8 @@ signals: void setURLFinished(bool success); void setCollisionModelURLFinished(bool success); void requestRenderUpdate(); + void rigReady(); + void rigReset(); protected: diff --git a/scripts/system/controllers/controllerModules/scaleAvatar.js b/scripts/system/controllers/controllerModules/scaleAvatar.js index e4ae3654e1..1868b0228a 100644 --- a/scripts/system/controllers/controllerModules/scaleAvatar.js +++ b/scripts/system/controllers/controllerModules/scaleAvatar.js @@ -12,6 +12,10 @@ (function () { var dispatcherUtils = Script.require("/~/system/libraries/controllerDispatcherUtils.js"); + function clamp(val, min, max) { + return Math.max(min, Math.min(max, val)); + } + function ScaleAvatar(hand) { this.hand = hand; this.scalingStartAvatarScale = 0; @@ -61,7 +65,7 @@ controllerData.controllerLocations[this.otherHand()].position)); var newAvatarScale = (scalingCurrentDistance / this.scalingStartDistance) * this.scalingStartAvatarScale; - MyAvatar.scale = newAvatarScale; + MyAvatar.scale = clamp(newAvatarScale, MyAvatar.getDomainMinScale(), MyAvatar.getDomainMaxScale()); MyAvatar.scaleChanged(); } return dispatcherUtils.makeRunningValues(true, [], []); From 42b509639b136c5b2c09fab5657997fe6ffe26ca Mon Sep 17 00:00:00 2001 From: David Kelly Date: Thu, 7 Dec 2017 17:22:00 -0800 Subject: [PATCH 47/47] Fix certificate hash --- libraries/entities/src/EntityItemProperties.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 5ab4bdee01..9f7ba1cc80 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -2486,7 +2486,7 @@ QByteArray EntityItemProperties::getStaticCertificateJSON() const { ADD_STRING_PROPERTY(collisionSoundURL, CollisionSoundURL); ADD_STRING_PROPERTY(compoundShapeURL, CompoundShapeURL); ADD_INT_PROPERTY(editionNumber, EditionNumber); - ADD_INT_PROPERTY(instanceNumber, EntityInstanceNumber); + ADD_INT_PROPERTY(entityInstanceNumber, EntityInstanceNumber); ADD_STRING_PROPERTY(itemArtist, ItemArtist); ADD_STRING_PROPERTY(itemCategories, ItemCategories); ADD_STRING_PROPERTY(itemDescription, ItemDescription);