diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 2ece07e86d..c6a42c2f3a 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -452,6 +452,13 @@ void EntityTree::deleteEntity(const EntityItemID& entityID, bool force, bool ign // NOTE: callers must lock the tree before using this method DeleteEntityOperator theOperator(getThisPointer(), entityID); + + existingEntity->forEachDescendant([&](SpatiallyNestablePointer descendant) { + auto descendantID = descendant->getID(); + theOperator.addEntityIDToDeleteList(descendantID); + emit deletingEntity(descendantID); + }); + recurseTreeWithOperator(&theOperator); processRemovedEntities(theOperator); _isDirty = true; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 766a584b85..e09afa3f31 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -376,7 +376,7 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g glm::vec3 meshFrameOrigin = glm::vec3(worldToMeshMatrix * glm::vec4(origin, 1.0f)); glm::vec3 meshFrameDirection = glm::vec3(worldToMeshMatrix * glm::vec4(direction, 0.0f)); - for (const auto& triangleSet : _modelSpaceMeshTriangleSets) { + for (auto& triangleSet : _modelSpaceMeshTriangleSets) { float triangleSetDistance = 0.0f; BoxFace triangleSetFace; glm::vec3 triangleSetNormal; @@ -1052,7 +1052,6 @@ void Model::updateRig(float deltaTime, glm::mat4 parentTransform) { void Model::computeMeshPartLocalBounds() { for (auto& part : _modelMeshRenderItems) { - assert(part->_meshIndex < _modelMeshRenderItems.size()); const Model::MeshState& state = _meshStates.at(part->_meshIndex); part->computeAdjustedLocalBound(state.clusterMatrices); } diff --git a/libraries/shared/src/AABox.cpp b/libraries/shared/src/AABox.cpp index 3f3146cc04..cea0a83d52 100644 --- a/libraries/shared/src/AABox.cpp +++ b/libraries/shared/src/AABox.cpp @@ -114,6 +114,10 @@ static bool isWithin(float value, float corner, float size) { return value >= corner && value <= corner + size; } +bool AABox::contains(const Triangle& triangle) const { + return contains(triangle.v0) && contains(triangle.v1) && contains(triangle.v2); +} + bool AABox::contains(const glm::vec3& point) const { return isWithin(point.x, _corner.x, _scale.x) && isWithin(point.y, _corner.y, _scale.y) && @@ -622,3 +626,40 @@ void AABox::transform(const glm::mat4& matrix) { _corner = newCenter - newDir; _scale = newDir * 2.0f; } + +AABox AABox::getOctreeChild(OctreeChild child) const { + AABox result(*this); // self + switch (child) { + case topLeftNear: + result._corner.y += _scale.y / 2.0f; + break; + case topLeftFar: + result._corner.y += _scale.y / 2.0f; + result._corner.z += _scale.z / 2.0f; + break; + case topRightNear: + result._corner.y += _scale.y / 2.0f; + result._corner.x += _scale.x / 2.0f; + break; + case topRightFar: + result._corner.y += _scale.y / 2.0f; + result._corner.x += _scale.x / 2.0f; + result._corner.z += _scale.z / 2.0f; + break; + case bottomLeftNear: + // _corner = same as parent + break; + case bottomLeftFar: + result._corner.z += _scale.z / 2.0f; + break; + case bottomRightNear: + result._corner.x += _scale.x / 2.0f; + break; + case bottomRightFar: + result._corner.x += _scale.x / 2.0f; + result._corner.z += _scale.z / 2.0f; + break; + } + result._scale /= 2.0f; // everything is half the scale + return result; +} diff --git a/libraries/shared/src/AABox.h b/libraries/shared/src/AABox.h index a53cc26163..eef83974ea 100644 --- a/libraries/shared/src/AABox.h +++ b/libraries/shared/src/AABox.h @@ -20,6 +20,7 @@ #include #include "BoxBase.h" +#include "GeometryUtil.h" #include "StreamUtils.h" class AACube; @@ -58,6 +59,7 @@ public: const glm::vec3& getMinimumPoint() const { return _corner; } glm::vec3 getMaximumPoint() const { return calcTopFarLeft(); } + bool contains(const Triangle& triangle) const; bool contains(const glm::vec3& point) const; bool contains(const AABox& otherBox) const; bool touches(const AABox& otherBox) const; @@ -112,6 +114,19 @@ public: void clear() { _corner = INFINITY_VECTOR; _scale = glm::vec3(0.0f); } + typedef enum { + topLeftNear, + topLeftFar, + topRightNear, + topRightFar, + bottomLeftNear, + bottomLeftFar, + bottomRightNear, + bottomRightFar + } OctreeChild; + + AABox getOctreeChild(OctreeChild child) const; // returns the AABox of the would be octree child of this AABox + private: glm::vec3 getClosestPointOnFace(const glm::vec3& point, BoxFace face) const; glm::vec3 getClosestPointOnFace(const glm::vec4& origin, const glm::vec4& direction, BoxFace face) const; diff --git a/libraries/shared/src/TriangleSet.cpp b/libraries/shared/src/TriangleSet.cpp index cdb3fd6b2c..aa21aa5cc0 100644 --- a/libraries/shared/src/TriangleSet.cpp +++ b/libraries/shared/src/TriangleSet.cpp @@ -12,9 +12,11 @@ #include "GLMHelpers.h" #include "TriangleSet.h" -void TriangleSet::insert(const Triangle& t) { - _triangles.push_back(t); +void TriangleSet::insert(const Triangle& t) { + _isBalanced = false; + + _triangles.push_back(t); _bounds += t.v0; _bounds += t.v1; _bounds += t.v2; @@ -23,39 +25,31 @@ void TriangleSet::insert(const Triangle& t) { void TriangleSet::clear() { _triangles.clear(); _bounds.clear(); + _isBalanced = false; + + _triangleOctree.clear(); } -// Determine of the given ray (origin/direction) in model space intersects with any triangles -// in the set. If an intersection occurs, the distance and surface normal will be provided. bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision) const { + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision) { - bool intersectedSomething = false; - float boxDistance = std::numeric_limits::max(); - float bestDistance = std::numeric_limits::max(); + // reset our distance to be the max possible, lower level tests will store best distance here + distance = std::numeric_limits::max(); - if (_bounds.findRayIntersection(origin, direction, boxDistance, face, surfaceNormal)) { - if (precision) { - for (const auto& triangle : _triangles) { - float thisTriangleDistance; - if (findRayTriangleIntersection(origin, direction, triangle, thisTriangleDistance)) { - if (thisTriangleDistance < bestDistance) { - bestDistance = thisTriangleDistance; - intersectedSomething = true; - surfaceNormal = triangle.getNormal(); - distance = bestDistance; - } - } - } - } else { - intersectedSomething = true; - distance = boxDistance; - } + if (!_isBalanced) { + balanceOctree(); } - return intersectedSomething; -} + int trianglesTouched = 0; + auto result = _triangleOctree.findRayIntersection(origin, direction, distance, face, surfaceNormal, precision, trianglesTouched); + #if WANT_DEBUGGING + if (precision) { + qDebug() << "trianglesTouched :" << trianglesTouched << "out of:" << _triangleOctree._population << "_triangles.size:" << _triangles.size(); + } + #endif + return result; +} bool TriangleSet::convexHullContains(const glm::vec3& point) const { if (!_bounds.contains(point)) { @@ -74,3 +68,198 @@ bool TriangleSet::convexHullContains(const glm::vec3& point) const { return insideMesh; } +void TriangleSet::debugDump() { + qDebug() << __FUNCTION__; + qDebug() << "bounds:" << getBounds(); + qDebug() << "triangles:" << size() << "at top level...."; + qDebug() << "----- _triangleOctree -----"; + _triangleOctree.debugDump(); +} + +void TriangleSet::balanceOctree() { + _triangleOctree.reset(_bounds, 0); + + // insert all the triangles + for (size_t i = 0; i < _triangles.size(); i++) { + _triangleOctree.insert(i); + } + + _isBalanced = true; + + #if WANT_DEBUGGING + debugDump(); + #endif +} + + +// Determine of the given ray (origin/direction) in model space intersects with any triangles +// in the set. If an intersection occurs, the distance and surface normal will be provided. +bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction, + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched) { + + bool intersectedSomething = false; + float boxDistance = distance; + float bestDistance = distance; + + if (_bounds.findRayIntersection(origin, direction, boxDistance, face, surfaceNormal)) { + + // if our bounding box intersects at a distance greater than the current known + // best distance, than we can safely not check any of our triangles + if (boxDistance > bestDistance) { + return false; + } + + if (precision) { + for (const auto& triangleIndex : _triangleIndices) { + const auto& triangle = _allTriangles[triangleIndex]; + float thisTriangleDistance; + trianglesTouched++; + if (findRayTriangleIntersection(origin, direction, triangle, thisTriangleDistance)) { + if (thisTriangleDistance < bestDistance) { + bestDistance = thisTriangleDistance; + intersectedSomething = true; + surfaceNormal = triangle.getNormal(); + distance = bestDistance; + } + } + } + } else { + intersectedSomething = true; + distance = boxDistance; + } + } + + return intersectedSomething; +} + +static const int MAX_DEPTH = 4; // for now +static const int MAX_CHILDREN = 8; + +TriangleSet::TriangleOctreeCell::TriangleOctreeCell(std::vector& allTriangles, const AABox& bounds, int depth) : + _allTriangles(allTriangles) +{ + reset(bounds, depth); +} + +void TriangleSet::TriangleOctreeCell::clear() { + _population = 0; + _triangleIndices.clear(); + _bounds.clear(); + _children.clear(); +} + +void TriangleSet::TriangleOctreeCell::reset(const AABox& bounds, int depth) { + clear(); + _bounds = bounds; + _depth = depth; +} + +void TriangleSet::TriangleOctreeCell::debugDump() { + qDebug() << __FUNCTION__; + qDebug() << "bounds:" << getBounds(); + qDebug() << "depth:" << _depth; + qDebug() << "population:" << _population << "this level or below" + << " ---- triangleIndices:" << _triangleIndices.size() << "in this cell"; + + qDebug() << "child cells:" << _children.size(); + if (_depth < MAX_DEPTH) { + int childNum = 0; + for (auto& child : _children) { + qDebug() << "child:" << childNum; + child.second.debugDump(); + childNum++; + } + } +} + +void TriangleSet::TriangleOctreeCell::insert(size_t triangleIndex) { + const Triangle& triangle = _allTriangles[triangleIndex]; + _population++; + // if we're not yet at the max depth, then check which child the triangle fits in + if (_depth < MAX_DEPTH) { + + for (int child = 0; child < MAX_CHILDREN; child++) { + AABox childBounds = getBounds().getOctreeChild((AABox::OctreeChild)child); + + + // if the child AABox would contain the triangle... + if (childBounds.contains(triangle)) { + // if the child cell doesn't yet exist, create it... + if (_children.find((AABox::OctreeChild)child) == _children.end()) { + _children.insert( + std::pair + ((AABox::OctreeChild)child, TriangleOctreeCell(_allTriangles, childBounds, _depth + 1))); + } + + // insert the triangleIndex in the child cell + _children.at((AABox::OctreeChild)child).insert(triangleIndex); + return; + } + } + } + // either we're at max depth, or the triangle doesn't fit in one of our + // children and so we want to just record it here + _triangleIndices.push_back(triangleIndex); +} + +bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched) { + + if (_population < 1) { + return false; // no triangles below here, so we can't intersect + } + + float bestLocalDistance = distance; + BoxFace bestLocalFace; + glm::vec3 bestLocalNormal; + bool intersects = false; + + // if the ray intersects our bounding box, then continue + if (getBounds().findRayIntersection(origin, direction, bestLocalDistance, bestLocalFace, bestLocalNormal)) { + + // if the intersection with our bounding box, is greater than the current best distance (the distance passed in) + // then we know that none of our triangles can represent a better intersection and we can return + + if (bestLocalDistance > distance) { + return false; + } + + bestLocalDistance = distance; + + float childDistance = distance; + BoxFace childFace; + glm::vec3 childNormal; + + // if we're not yet at the max depth, then check which child the triangle fits in + if (_depth < MAX_DEPTH) { + for (auto& child : _children) { + // check each child, if there's an intersection, it will return some distance that we need + // to compare against the other results, because there might be multiple intersections and + // we will always choose the best (shortest) intersection + if (child.second.findRayIntersection(origin, direction, childDistance, childFace, childNormal, precision, trianglesTouched)) { + if (childDistance < bestLocalDistance) { + bestLocalDistance = childDistance; + bestLocalFace = childFace; + bestLocalNormal = childNormal; + intersects = true; + } + } + } + } + // also check our local triangle set + if (findRayIntersectionInternal(origin, direction, childDistance, childFace, childNormal, precision, trianglesTouched)) { + if (childDistance < bestLocalDistance) { + bestLocalDistance = childDistance; + bestLocalFace = childFace; + bestLocalNormal = childNormal; + intersects = true; + } + } + } + if (intersects) { + distance = bestLocalDistance; + face = bestLocalFace; + surfaceNormal = bestLocalNormal; + } + return intersects; +} diff --git a/libraries/shared/src/TriangleSet.h b/libraries/shared/src/TriangleSet.h index b54f1a642a..6cedc4da7e 100644 --- a/libraries/shared/src/TriangleSet.h +++ b/libraries/shared/src/TriangleSet.h @@ -15,19 +15,64 @@ #include "GeometryUtil.h" class TriangleSet { -public: - void reserve(size_t size) { _triangles.reserve(size); } // reserve space in the datastructure for size number of triangles - size_t size() const { return _triangles.size(); } - const Triangle& getTriangle(size_t t) const { return _triangles[t]; } + class TriangleOctreeCell { + public: + TriangleOctreeCell(std::vector& allTriangles) : + _allTriangles(allTriangles) + { } + + void insert(size_t triangleIndex); + void reset(const AABox& bounds, int depth = 0); + void clear(); + + bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched); + + const AABox& getBounds() const { return _bounds; } + + void debugDump(); + + protected: + TriangleOctreeCell(std::vector& allTriangles, const AABox& bounds, int depth); + + // checks our internal list of triangles + bool findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction, + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched); + + std::vector& _allTriangles; + std::map _children; + int _depth{ 0 }; + int _population{ 0 }; + AABox _bounds; + std::vector _triangleIndices; + + friend class TriangleSet; + }; + +public: + TriangleSet() : + _triangleOctree(_triangles) + {} + + void debugDump(); void insert(const Triangle& t); + + bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision); + + void balanceOctree(); + + void reserve(size_t size) { _triangles.reserve(size); } // reserve space in the datastructure for size number of triangles + size_t size() const { return _triangles.size(); } void clear(); // Determine if the given ray (origin/direction) in model space intersects with any triangles in the set. If an // intersection occurs, the distance and surface normal will be provided. - bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision) const; + // note: this might side-effect internal structures + bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched); // Determine if a point is "inside" all the triangles of a convex hull. It is the responsibility of the caller to // determine that the triangle set is indeed a convex hull. If the triangles added to this set are not in fact a @@ -35,7 +80,10 @@ public: bool convexHullContains(const glm::vec3& point) const; const AABox& getBounds() const { return _bounds; } -private: +protected: + + bool _isBalanced{ false }; + TriangleOctreeCell _triangleOctree; std::vector _triangles; AABox _bounds; }; diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 5e22272dd6..3bda481243 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -40,7 +40,7 @@ void releaseOpenVrSystem(); static const char* CONTROLLER_MODEL_STRING = "vr_controller_05_wireless_b"; -const quint64 CALIBRATION_TIMELAPSE = 2 * USECS_PER_SECOND; +const quint64 CALIBRATION_TIMELAPSE = 1 * USECS_PER_SECOND; static const char* MENU_PARENT = "Avatar"; static const char* MENU_NAME = "Vive Controllers"; diff --git a/scripts/developer/tests/performance/rayPickPerformance.js b/scripts/developer/tests/performance/rayPickPerformance.js new file mode 100644 index 0000000000..b4faf4c1be --- /dev/null +++ b/scripts/developer/tests/performance/rayPickPerformance.js @@ -0,0 +1,131 @@ +// +// rayPickingPerformance.js +// examples +// +// Created by Brad Hefta-Gaub on 5/13/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 +// + + + +var MIN_RANGE = -3; +var MAX_RANGE = 3; +var RANGE_DELTA = 0.5; +var OUTER_LOOPS = 10; + +// NOTE: These expected results depend completely on the model, and the range settings above +var EXPECTED_TESTS = 1385 * OUTER_LOOPS; +var EXPECTED_INTERSECTIONS = 1286 * OUTER_LOOPS; + + +var center = Vec3.sum(MyAvatar.position, Vec3.multiply(3, Quat.getFront(Camera.getOrientation()))); +var model_url = "http://hifi-content.s3.amazonaws.com/caitlyn/production/Scansite/buddhaReduced.fbx"; + +var rayPickOverlays = Array(); + +var modelEntity = Entities.addEntity({ + type: "Model", + modelURL: model_url, + dimensions: { + x: 0.671, + y: 1.21, + z: 0.938 + }, + position: center +}); + +function rayCastTest() { + var tests = 0; + var intersections = 0; + + var testStart = Date.now(); + for (var t = 0; t < OUTER_LOOPS; t++) { + print("beginning loop:" + t); + for (var x = MIN_RANGE; x < MAX_RANGE; x += RANGE_DELTA) { + for (var y = MIN_RANGE; y < MAX_RANGE; y += RANGE_DELTA) { + for (var z = MIN_RANGE; z < MAX_RANGE; z += RANGE_DELTA) { + if ((x <= -2 || x >= 2) || + (y <= -2 || y >= 2) || + (z <= -2 || z >= 2)) { + + tests++; + + var origin = { x: center.x + x, + y: center.y + y, + z: center.z + z }; + var direction = Vec3.subtract(center, origin); + + var pickRay = { + origin: origin, + direction: direction + }; + + var pickResults = Entities.findRayIntersection(pickRay, true); + + var color; + var visible; + + if (pickResults.intersects && pickResults.entityID == modelEntity) { + intersections++; + color = { + red: 0, + green: 255, + blue: 0 + }; + visible = false; + + } else { + /* + print("NO INTERSECTION?"); + Vec3.print("origin:", origin); + Vec3.print("direction:", direction); + */ + + color = { + red: 255, + green: 0, + blue: 0 + }; + visible = true; + } + + var overlayID = Overlays.addOverlay("line3d", { + color: color, + alpha: 1, + visible: visible, + lineWidth: 2, + start: origin, + end: Vec3.sum(origin,Vec3.multiply(5,direction)) + }); + + rayPickOverlays.push(overlayID); + + } + } + } + } + print("ending loop:" + t); + } + var testEnd = Date.now(); + var testElapsed = testEnd - testStart; + + + print("EXPECTED tests:" + EXPECTED_TESTS + " intersections:" + EXPECTED_INTERSECTIONS); + print("ACTUAL tests:" + tests + " intersections:" + intersections); + print("ELAPSED TIME:" + testElapsed + " ms"); + +} + +function cleanup() { + Entities.deleteEntity(modelEntity); + rayPickOverlays.forEach(function(item){ + Overlays.deleteOverlay(item); + }); +} + +Script.scriptEnding.connect(cleanup); + +rayCastTest(); // run ray cast test immediately \ No newline at end of file diff --git a/scripts/system/html/js/SnapshotReview.js b/scripts/system/html/js/SnapshotReview.js index 946e04beef..f962c7b624 100644 --- a/scripts/system/html/js/SnapshotReview.js +++ b/scripts/system/html/js/SnapshotReview.js @@ -359,7 +359,7 @@ function showUploadingMessage(selectedID, destination) { shareBarHelp.classList.add("uploading"); shareBarHelp.setAttribute("data-destination", destination); } -function hideUploadingMessageAndShare(selectedID, storyID) { +function hideUploadingMessageAndMaybeShare(selectedID, storyID) { if (selectedID.id) { selectedID = selectedID.id; // sometimes (?), `containerID` is passed as an HTML object to these functions; we just want the ID } @@ -382,21 +382,28 @@ function hideUploadingMessageAndShare(selectedID, storyID) { var facebookButton = document.getElementById(selectedID + "facebookButton"); window.open(facebookButton.getAttribute("href"), "_blank"); shareBarHelp.innerHTML = facebookShareText; + // This emitWebEvent() call isn't necessary in the "hifi" and "blast" cases + // because the "removeFromStoryIDsToMaybeDelete()" call happens + // in snapshot.js when sharing with that method. + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "removeFromStoryIDsToMaybeDelete", + story_id: storyID + })); break; case 'twitter': var twitterButton = document.getElementById(selectedID + "twitterButton"); window.open(twitterButton.getAttribute("href"), "_blank"); shareBarHelp.innerHTML = twitterShareText; + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "removeFromStoryIDsToMaybeDelete", + story_id: storyID + })); break; } shareBarHelp.setAttribute("data-destination", ""); - - EventBridge.emitWebEvent(JSON.stringify({ - type: "snapshot", - action: "removeFromStoryIDsToMaybeDelete", - story_id: storyID - })); } } function updateShareInfo(containerID, storyID) { @@ -417,7 +424,7 @@ function updateShareInfo(containerID, storyID) { twitterButton.setAttribute("target", "_blank"); twitterButton.setAttribute("href", 'https://twitter.com/intent/tweet?text=I%20just%20took%20a%20snapshot!&url=' + shareURL + '&via=highfidelityinc&hashtags=VR,HiFi'); - hideUploadingMessageAndShare(containerID, storyID); + hideUploadingMessageAndMaybeShare(containerID, storyID); } function blastToConnections(selectedID, isGif) { if (selectedID.id) { @@ -552,6 +559,12 @@ function shareButtonClicked(destination, selectedID) { if (!storyID) { showUploadingMessage(selectedID, destination); + } else { + EventBridge.emitWebEvent(JSON.stringify({ + type: "snapshot", + action: "removeFromStoryIDsToMaybeDelete", + story_id: storyID + })); } } diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 2f109597d7..e000e14aec 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -1020,7 +1020,7 @@ function loaded() { elTextText.value = properties.text; elTextLineHeight.value = properties.lineHeight.toFixed(4); - elTextFaceCamera = properties.faceCamera; + elTextFaceCamera.checked = properties.faceCamera; elTextTextColor.style.backgroundColor = "rgb(" + properties.textColor.red + "," + properties.textColor.green + "," + properties.textColor.blue + ")"; elTextTextColorRed.value = properties.textColor.red; elTextTextColorGreen.value = properties.textColor.green; diff --git a/scripts/system/record.js b/scripts/system/record.js index 3db82696ef..acc81f2526 100644 --- a/scripts/system/record.js +++ b/scripts/system/record.js @@ -37,6 +37,12 @@ Window.alert(message); } + function logDetails() { + return { + current_domain: location.placename + }; + } + RecordingIndicator = (function () { // Displays "recording" overlay. @@ -181,7 +187,7 @@ recordingState = IDLE; log("Finish recording"); - UserActivityLogger.logAction("record_finish_recording"); + UserActivityLogger.logAction("record_finish_recording", logDetails()); playSound(finishRecordingSound); Recording.stopRecording(); RecordingIndicator.hide(); @@ -519,7 +525,7 @@ value: Player.numberOfPlayers() })); updateRecordingStatus(!Recorder.isIdle()); - UserActivityLogger.logAction("record_open_dialog"); + UserActivityLogger.logAction("record_open_dialog", logDetails()); break; case STOP_PLAYING_RECORDING_ACTION: // Stop the specified player. @@ -530,7 +536,7 @@ recording = Window.browseAssets("Select Recording to Play", "recordings", "*.hfr"); if (recording) { log("Load recording " + recording); - UserActivityLogger.logAction("record_load_recording"); + UserActivityLogger.logAction("record_load_recording", logDetails()); Player.playRecording("atp:" + recording, MyAvatar.position, MyAvatar.orientation); } break; @@ -660,7 +666,7 @@ isConnected = Window.location.isConnected; Script.update.connect(onUpdate); - UserActivityLogger.logAction("record_run_script"); + UserActivityLogger.logAction("record_run_script", logDetails()); } function tearDown() { diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index 2a2e816d45..8b5ae3c9a7 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -51,6 +51,11 @@ function openLoginWindow() { } } +function removeFromStoryIDsToMaybeDelete(story_id) { + storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(story_id), 1); + print('storyIDsToMaybeDelete[] now:', JSON.stringify(storyIDsToMaybeDelete)); +} + function onMessage(message) { // Receives message from the html dialog via the qwebchannel EventBridge. This is complicated by the following: // 1. Although we can send POJOs, we cannot receive a toplevel object. (Arrays of POJOs are fine, though.) @@ -191,6 +196,7 @@ function onMessage(message) { return; } else { print("SUCCESS uploading announcement story! Story ID:", response.user_story.id); + removeFromStoryIDsToMaybeDelete(message.story_id); // Don't delete original "for_url" story } }); } @@ -230,13 +236,13 @@ function onMessage(message) { return; } else { print("SUCCESS changing audience" + (message.isAnnouncement ? " and posting announcement!" : "!")); + removeFromStoryIDsToMaybeDelete(message.story_id); } }); } break; case 'removeFromStoryIDsToMaybeDelete': - storyIDsToMaybeDelete.splice(storyIDsToMaybeDelete.indexOf(message.story_id), 1); - print('storyIDsToMaybeDelete[] now:', JSON.stringify(storyIDsToMaybeDelete)); + removeFromStoryIDsToMaybeDelete(message.story_id); break; default: print('Unknown message action received by snapshot.js!');