Merge branch 'master' into 20812

This commit is contained in:
David Rowe 2016-03-02 09:55:17 +13:00
commit 881302df02
26 changed files with 1039 additions and 155 deletions

View file

@ -55,13 +55,7 @@ Agent::Agent(ReceivedMessage& message) :
{ {
DependencyManager::get<EntityScriptingInterface>()->setPacketSender(&_entityEditSender); DependencyManager::get<EntityScriptingInterface>()->setPacketSender(&_entityEditSender);
auto assetClient = DependencyManager::set<AssetClient>(); ResourceManager::init();
QThread* assetThread = new QThread;
assetThread->setObjectName("Asset Thread");
assetClient->moveToThread(assetThread);
connect(assetThread, &QThread::started, assetClient.data(), &AssetClient::init);
assetThread->start();
DependencyManager::registerInheritance<SpatialParentFinder, AssignmentParentFinder>(); DependencyManager::registerInheritance<SpatialParentFinder, AssignmentParentFinder>();
@ -471,11 +465,7 @@ void Agent::aboutToFinish() {
// our entity tree is going to go away so tell that to the EntityScriptingInterface // our entity tree is going to go away so tell that to the EntityScriptingInterface
DependencyManager::get<EntityScriptingInterface>()->setEntityTree(nullptr); DependencyManager::get<EntityScriptingInterface>()->setEntityTree(nullptr);
// cleanup the AssetClient thread ResourceManager::cleanup();
QThread* assetThread = DependencyManager::get<AssetClient>()->thread();
DependencyManager::destroy<AssetClient>();
assetThread->quit();
assetThread->wait();
// cleanup the AudioInjectorManager (and any still running injectors) // cleanup the AudioInjectorManager (and any still running injectors)
DependencyManager::destroy<AudioInjectorManager>(); DependencyManager::destroy<AudioInjectorManager>();

View file

@ -13,13 +13,30 @@
// //
// Goes into "paused" when the '.' key (and automatically when started in HMD), and normal when pressing any key. // Goes into "paused" when the '.' key (and automatically when started in HMD), and normal when pressing any key.
// See MAIN CONTROL, below, for what "paused" actually does. // See MAIN CONTROL, below, for what "paused" actually does.
var OVERLAY_RATIO = 1920 / 1080; var OVERLAY_WIDTH = 1920;
var OVERLAY_HEIGHT = 1080;
var OVERLAY_RATIO = OVERLAY_WIDTH / OVERLAY_HEIGHT;
var OVERLAY_DATA = { var OVERLAY_DATA = {
width: OVERLAY_WIDTH,
height: OVERLAY_HEIGHT,
imageURL: "http://hifi-content.s3.amazonaws.com/alan/production/images/images/Overlay-Viz-blank.png", imageURL: "http://hifi-content.s3.amazonaws.com/alan/production/images/images/Overlay-Viz-blank.png",
color: {red: 255, green: 255, blue: 255}, color: {red: 255, green: 255, blue: 255},
alpha: 1 alpha: 1
}; };
var lastOverlayPosition = { x: 0, y: 0, z: 0};
var OVERLAY_DATA_HMD = {
position: lastOverlayPosition,
width: OVERLAY_WIDTH,
height: OVERLAY_HEIGHT,
url: "http://hifi-content.s3.amazonaws.com/alan/production/images/images/Overlay-Viz-blank.png",
color: {red: 255, green: 255, blue: 255},
alpha: 1,
scale: 2,
isFacingAvatar: true,
drawInFront: true
};
// ANIMATION // ANIMATION
// We currently don't have play/stopAnimation integrated with the animation graph, but we can get the same effect // We currently don't have play/stopAnimation integrated with the animation graph, but we can get the same effect
// using an animation graph with a state that we turn on and off through the animation var defined with that state. // using an animation graph with a state that we turn on and off through the animation var defined with that state.
@ -64,29 +81,74 @@ function stopAwayAnimation() {
// OVERLAY // OVERLAY
var overlay = Overlays.addOverlay("image", OVERLAY_DATA); var overlay = Overlays.addOverlay("image", OVERLAY_DATA);
var overlayHMD = Overlays.addOverlay("image3d", OVERLAY_DATA_HMD);
function moveCloserToCamera(positionAtHUD) {
// we don't actually want to render at the slerped look at... instead, we want to render
// slightly closer to the camera than that.
var MOVE_CLOSER_TO_CAMERA_BY = -0.25;
var cameraFront = Quat.getFront(Camera.orientation);
var closerToCamera = Vec3.multiply(cameraFront, MOVE_CLOSER_TO_CAMERA_BY); // slightly closer to camera
var slightlyCloserPosition = Vec3.sum(positionAtHUD, closerToCamera);
return slightlyCloserPosition;
}
function showOverlay() { function showOverlay() {
var properties = {visible: true}, var properties = {visible: true};
// Update for current screen size, keeping overlay proportions constant.
screen = Controller.getViewportDimensions(), if (HMD.active) {
screenRatio = screen.x / screen.y; // make sure desktop version is hidden
if (screenRatio < OVERLAY_RATIO) { Overlays.editOverlay(overlay, { visible: false });
properties.width = screen.x;
properties.height = screen.x / OVERLAY_RATIO; lastOverlayPosition = HMD.getHUDLookAtPosition3D();
properties.x = 0; var actualOverlayPositon = moveCloserToCamera(lastOverlayPosition);
properties.y = (screen.y - properties.height) / 2; Overlays.editOverlay(overlayHMD, { visible: true, position: actualOverlayPositon });
} else { } else {
properties.height = screen.y; // make sure HMD is hidden
properties.width = screen.y * OVERLAY_RATIO; Overlays.editOverlay(overlayHMD, { visible: false });
properties.y = 0;
properties.x = (screen.x - properties.width) / 2; // Update for current screen size, keeping overlay proportions constant.
var screen = Controller.getViewportDimensions();
// keep the overlay it's natural size and always center it...
Overlays.editOverlay(overlay, { visible: true,
x: ((screen.x - OVERLAY_WIDTH) / 2),
y: ((screen.y - OVERLAY_HEIGHT) / 2) });
} }
Overlays.editOverlay(overlay, properties);
} }
function hideOverlay() { function hideOverlay() {
Overlays.editOverlay(overlay, {visible: false}); Overlays.editOverlay(overlay, {visible: false});
Overlays.editOverlay(overlayHMD, {visible: false});
} }
hideOverlay(); hideOverlay();
function maybeMoveOverlay() {
if (isAway) {
// if we switched from HMD to Desktop, make sure to hide our HUD overlay and show the
// desktop overlay
if (!HMD.active) {
showOverlay(); // this will also recenter appropriately
}
if (HMD.active) {
// Note: instead of moving it directly to the lookAt, we will move it slightly toward the
// new look at. This will result in a more subtle slerp toward the look at and reduce jerkiness
var EASE_BY_RATIO = 0.1;
var lookAt = HMD.getHUDLookAtPosition3D();
var lookAtChange = Vec3.subtract(lookAt, lastOverlayPosition);
var halfWayBetweenOldAndLookAt = Vec3.multiply(lookAtChange, EASE_BY_RATIO);
var newOverlayPosition = Vec3.sum(lastOverlayPosition, halfWayBetweenOldAndLookAt);
lastOverlayPosition = newOverlayPosition;
var actualOverlayPositon = moveCloserToCamera(lastOverlayPosition);
Overlays.editOverlay(overlayHMD, { visible: true, position: actualOverlayPositon });
// make sure desktop version is hidden
Overlays.editOverlay(overlay, { visible: false });
}
}
}
// MAIN CONTROL // MAIN CONTROL
var wasMuted, isAway; var wasMuted, isAway;
@ -106,6 +168,12 @@ function goAway() {
MyAvatar.setEnableMeshVisible(false); // just for our own display, without changing point of view MyAvatar.setEnableMeshVisible(false); // just for our own display, without changing point of view
playAwayAnimation(); // animation is still seen by others playAwayAnimation(); // animation is still seen by others
showOverlay(); showOverlay();
// tell the Reticle, we want to stop capturing the mouse until we come back
Reticle.allowMouseCapture = false;
if (HMD.active) {
Reticle.visible = false;
}
} }
function goActive() { function goActive() {
if (!isAway) { if (!isAway) {
@ -119,13 +187,20 @@ function goActive() {
MyAvatar.setEnableMeshVisible(true); // IWBNI we respected Developer->Avatar->Draw Mesh setting. MyAvatar.setEnableMeshVisible(true); // IWBNI we respected Developer->Avatar->Draw Mesh setting.
stopAwayAnimation(); stopAwayAnimation();
hideOverlay(); hideOverlay();
// tell the Reticle, we are ready to capture the mouse again and it should be visible
Reticle.allowMouseCapture = true;
Reticle.visible = true;
if (HMD.active) {
Reticle.position = HMD.getHUDLookAtPosition2D();
}
} }
function maybeGoActive(event) { function maybeGoActive(event) {
if (event.isAutoRepeat) { // isAutoRepeat is true when held down (or when Windows feels like it) if (event.isAutoRepeat) { // isAutoRepeat is true when held down (or when Windows feels like it)
return; return;
} }
if (!isAway && (event.text === '.')) { if (!isAway && (event.text == 'ESC')) {
goAway(); goAway();
} else { } else {
goActive(); goActive();
@ -141,10 +216,8 @@ function maybeGoAway() {
} }
} }
// If the mouse has gone from captured, to non-captured state, // If the mouse has gone from captured, to non-captured state, then it likely means the person is still in the HMD, but
// then it likely means the person is still in the HMD, but has // tabbed away from the application (meaning they don't have mouse control) and they likely want to go into an away state
// tabbed away from the application (meaning they don't have mouse
// control) and they likely want to go into an away state
if (Reticle.mouseCaptured !== wasMouseCaptured) { if (Reticle.mouseCaptured !== wasMouseCaptured) {
wasMouseCaptured = !wasMouseCaptured; wasMouseCaptured = !wasMouseCaptured;
if (!wasMouseCaptured) { if (!wasMouseCaptured) {
@ -153,6 +226,8 @@ function maybeGoAway() {
} }
} }
Script.update.connect(maybeMoveOverlay);
Script.update.connect(maybeGoAway); Script.update.connect(maybeGoAway);
Controller.mousePressEvent.connect(goActive); Controller.mousePressEvent.connect(goActive);
Controller.keyPressEvent.connect(maybeGoActive); Controller.keyPressEvent.connect(maybeGoActive);

View file

@ -22,3 +22,4 @@ Script.load("grab.js");
Script.load("directory.js"); Script.load("directory.js");
Script.load("dialTone.js"); Script.load("dialTone.js");
Script.load("attachedEntitiesManager.js"); Script.load("attachedEntitiesManager.js");
Script.load("depthReticle.js");

View file

@ -5,6 +5,8 @@
// Copyright 2016 High Fidelity, Inc. // Copyright 2016 High Fidelity, Inc.
// //
// When used in HMD, this script will make the reticle depth track to any clickable item in view. // When used in HMD, this script will make the reticle depth track to any clickable item in view.
// This script also handles auto-hiding the reticle after inactivity, as well as having the reticle
// seek the look at position upon waking up.
// //
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -17,8 +19,89 @@ var desiredDepth = APPARENT_2D_OVERLAY_DEPTH;
var TIME_BETWEEN_DEPTH_CHECKS = 100; var TIME_BETWEEN_DEPTH_CHECKS = 100;
var MINIMUM_DEPTH_ADJUST = 0.01; var MINIMUM_DEPTH_ADJUST = 0.01;
var NON_LINEAR_DIVISOR = 2; var NON_LINEAR_DIVISOR = 2;
var MINIMUM_SEEK_DISTANCE = 0.01;
Script.update.connect(function(deltaTime) { var lastMouseMove = Date.now();
var lastMouseX = Reticle.position.x;
var lastMouseY = Reticle.position.y;
var HIDE_STATIC_MOUSE_AFTER = 3000; // 3 seconds
var shouldSeekToLookAt = false;
var fastMouseMoves = 0;
var averageMouseVelocity = 0;
var WEIGHTING = 1/20; // simple moving average over last 20 samples
var ONE_MINUS_WEIGHTING = 1 - WEIGHTING;
var AVERAGE_MOUSE_VELOCITY_FOR_SEEK_TO = 50;
Controller.mouseMoveEvent.connect(function(mouseEvent) {
var now = Date.now();
// if the reticle is hidden, and we're not in away mode...
if (!Reticle.visible && Reticle.allowMouseCapture) {
Reticle.visible = true;
if (HMD.active) {
shouldSeekToLookAt = true;
}
} else {
// even if the reticle is visible, if we're in HMD mode, and the person is moving their mouse quickly (shaking it)
// then they are probably looking for it, and we should move into seekToLookAt mode
if (HMD.active && !shouldSeekToLookAt && Reticle.allowMouseCapture) {
var dx = Reticle.position.x - lastMouseX;
var dy = Reticle.position.y - lastMouseY;
var dt = Math.max(1, (now - lastMouseMove)); // mSecs since last mouse move
var mouseMoveDistance = Math.sqrt((dx*dx) + (dy*dy));
var mouseVelocity = mouseMoveDistance / dt;
averageMouseVelocity = (ONE_MINUS_WEIGHTING * averageMouseVelocity) + (WEIGHTING * mouseVelocity);
if (averageMouseVelocity > AVERAGE_MOUSE_VELOCITY_FOR_SEEK_TO) {
shouldSeekToLookAt = true;
}
}
}
lastMouseMove = now;
lastMouseX = mouseEvent.x;
lastMouseY = mouseEvent.y;
});
function seekToLookAt() {
// if we're currently seeking the lookAt move the mouse toward the lookat
if (shouldSeekToLookAt) {
averageMouseVelocity = 0; // reset this, these never count for movement...
var lookAt2D = HMD.getHUDLookAtPosition2D();
var currentReticlePosition = Reticle.position;
var distanceBetweenX = lookAt2D.x - Reticle.position.x;
var distanceBetweenY = lookAt2D.y - Reticle.position.y;
var moveX = distanceBetweenX / NON_LINEAR_DIVISOR;
var moveY = distanceBetweenY / NON_LINEAR_DIVISOR;
var newPosition = { x: Reticle.position.x + moveX, y: Reticle.position.y + moveY };
var closeEnoughX = false;
var closeEnoughY = false;
if (moveX < MINIMUM_SEEK_DISTANCE) {
newPosition.x = lookAt2D.x;
closeEnoughX = true;
}
if (moveY < MINIMUM_SEEK_DISTANCE) {
newPosition.y = lookAt2D.y;
closeEnoughY = true;
}
Reticle.position = newPosition;
if (closeEnoughX && closeEnoughY) {
shouldSeekToLookAt = false;
}
}
}
function autoHideReticle() {
// if we haven't moved in a long period of time, and we're not pointing at some
// system overlay (like a window), then hide the reticle
if (Reticle.visible && !Reticle.pointingAtSystemOverlay) {
var now = Date.now();
var timeSinceLastMouseMove = now - lastMouseMove;
if (timeSinceLastMouseMove > HIDE_STATIC_MOUSE_AFTER) {
Reticle.visible = false;
}
}
}
function checkReticleDepth() {
var now = Date.now(); var now = Date.now();
var timeSinceLastDepthCheck = now - lastDepthCheckTime; var timeSinceLastDepthCheck = now - lastDepthCheckTime;
if (timeSinceLastDepthCheck > TIME_BETWEEN_DEPTH_CHECKS) { if (timeSinceLastDepthCheck > TIME_BETWEEN_DEPTH_CHECKS) {
@ -56,6 +139,9 @@ Script.update.connect(function(deltaTime) {
} }
} }
}
function moveToDesiredDepth() {
// move the reticle toward the desired depth // move the reticle toward the desired depth
if (desiredDepth != Reticle.depth) { if (desiredDepth != Reticle.depth) {
@ -69,4 +155,13 @@ Script.update.connect(function(deltaTime) {
Reticle.setDepth(newDepth); Reticle.setDepth(newDepth);
} }
}
Script.update.connect(function(deltaTime) {
autoHideReticle(); // auto hide reticle for desktop or HMD mode
if (HMD.active) {
seekToLookAt(); // handle moving the reticle toward the look at
checkReticleDepth(); // make sure reticle is at correct depth
moveToDesiredDepth(); // move the fade the reticle to the desired depth
}
}); });

View file

@ -0,0 +1,109 @@
//
// eraserEntityScript.js
// examples/homeContent/eraserEntityScript
//
// Created by Eric Levin on 2/17/15.
// Copyright 2016 High Fidelity, Inc.
//
// This entity script provides logic for an object with attached script to erase nearby marker strokes
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
(function() {
Script.include("../../libraries/utils.js");
var TRIGGER_CONTROLS = [
Controller.Standard.LT,
Controller.Standard.RT,
];
var _this;
Eraser = function() {
_this = this;
_this.ERASER_TRIGGER_THRESHOLD = 0.2;
_this.STROKE_NAME = "hifi-marker-stroke";
_this.ERASER_TO_STROKE_SEARCH_RADIUS = 0.7;
_this.ERASER_RESET_WAIT_TIME = 3000;
};
Eraser.prototype = {
startEquip: function(id, params) {
_this.equipped = true;
_this.hand = params[0] == "left" ? 0 : 1;
// We really only need to grab position of marker strokes once, and then just check to see if eraser comes near enough to those strokes
Overlays.editOverlay(_this.searchSphere, {
visible: true
});
},
continueEquip: function() {
_this.eraserPosition = Entities.getEntityProperties(_this.entityID, "position").position;
Overlays.editOverlay(_this.searchSphere, {
position: _this.eraserPosition
});
this.triggerValue = Controller.getValue(TRIGGER_CONTROLS[_this.hand]);
if (_this.triggerValue > _this.ERASER_TRIGGER_THRESHOLD) {
_this.continueHolding();
}
},
continueHolding: function() {
var strokeIDs = Entities.findEntities(_this.eraserPosition, _this.ERASER_TO_STROKE_SEARCH_RADIUS);
// Create a map of stroke entities and their positions
strokeIDs.forEach(function(strokeID) {
var strokeProps = Entities.getEntityProperties(strokeID, ["position", "name"]);
if (strokeProps.name === _this.STROKE_NAME && Vec3.distance(_this.eraserPosition, strokeProps.position) < _this.ERASER_TO_STROKE_SEARCH_RADIUS) {
Entities.deleteEntity(strokeID);
}
});
},
releaseEquip: function() {
Overlays.editOverlay(_this.searchSphere, {
visible: false
});
// Once user releases eraser, wait a bit then put marker back to its original position and rotation
Script.setTimeout(function() {
var userData = getEntityUserData(_this.entityID);
Entities.editEntity(_this.entityID, {
position: userData.originalPosition,
rotation: userData.originalRotation,
velocity: {
x: 0,
y: -0.01,
z: 0
}
});
}, _this.ERASER_RESET_WAIT_TIME);
},
preload: function(entityID) {
_this.entityID = entityID;
_this.searchSphere = Overlays.addOverlay('sphere', {
size: _this.ERASER_TO_STROKE_SEARCH_RADIUS,
color: {
red: 200,
green: 10,
blue: 10
},
alpha: 0.2,
solid: true,
visible: false
})
},
unload: function() {
Overlays.deleteOverlay(_this.searchSphere);
}
};
// entity scripts always need to return a newly constructed object of our type
return new Eraser();
});

View file

@ -0,0 +1,219 @@
//
// markerTipEntityScript.js
// examples/homeContent/markerTipEntityScript
//
// Created by Eric Levin on 2/17/15.
// Copyright 2016 High Fidelity, Inc.
//
// This script provides the logic for an object to draw marker strokes on its associated whiteboard
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
(function() {
Script.include("../../libraries/utils.js");
var TRIGGER_CONTROLS = [
Controller.Standard.LT,
Controller.Standard.RT,
];
var MAX_POINTS_PER_STROKE = 40;
var _this;
MarkerTip = function() {
_this = this;
_this.MARKER_TEXTURE_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/textures/markerStroke.png";
_this.strokeForwardOffset = 0.0001;
_this.STROKE_WIDTH_RANGE = {
min: 0.002,
max: 0.01
};
_this.MAX_MARKER_TO_BOARD_DISTANCE = 1.4;
_this.MIN_DISTANCE_BETWEEN_POINTS = 0.002;
_this.MAX_DISTANCE_BETWEEN_POINTS = 0.1;
_this.strokes = [];
_this.PAINTING_TRIGGER_THRESHOLD = 0.2;
_this.STROKE_NAME = "hifi-marker-stroke";
_this.WHITEBOARD_SURFACE_NAME = "hifi-whiteboardDrawingSurface";
_this.MARKER_RESET_WAIT_TIME = 3000;
};
MarkerTip.prototype = {
startEquip: function(id, params) {
_this.whiteboards = [];
_this.equipped = true;
_this.hand = params[0] == "left" ? 0 : 1;
_this.markerColor = getEntityUserData(_this.entityID).markerColor;
// search for whiteboards
var markerPosition = Entities.getEntityProperties(_this.entityID, "position").position;
var entities = Entities.findEntities(markerPosition, 10);
entities.forEach(function(entity) {
var entityName = Entities.getEntityProperties(entity, "name").name;
if (entityName === _this.WHITEBOARD_SURFACE_NAME) {
_this.whiteboards.push(entity);
}
});
print("intersectable entities " + JSON.stringify(_this.whiteboards))
},
releaseEquip: function() {
_this.resetStroke();
Overlays.editOverlay(_this.laserPointer, {
visible: false
});
// Once user releases marker, wait a bit then put marker back to its original position and rotation
Script.setTimeout(function() {
var userData = getEntityUserData(_this.entityID);
Entities.editEntity(_this.entityID, {
position: userData.originalPosition,
rotation: userData.originalRotation,
velocity: {
x: 0,
y: -0.01,
z: 0
}
});
}, _this.MARKER_RESET_WAIT_TIME);
},
continueEquip: function() {
// cast a ray from marker and see if it hits anything
var markerProps = Entities.getEntityProperties(_this.entityID, ["position", "rotation"]);
var pickRay = {
origin: markerProps.position,
direction: Quat.getFront(markerProps.rotation)
}
var intersection = Entities.findRayIntersectionBlocking(pickRay, true, _this.whiteboards);
if (intersection.intersects && Vec3.distance(intersection.intersection, markerProps.position) < _this.MAX_MARKER_TO_BOARD_DISTANCE) {
_this.currentWhiteboard = intersection.entityID;
var whiteboardRotation = Entities.getEntityProperties(_this.currentWhiteboard, "rotation").rotation;
_this.whiteboardNormal = Quat.getFront(whiteboardRotation);
Overlays.editOverlay(_this.laserPointer, {
visible: true,
position: intersection.intersection,
rotation: whiteboardRotation
})
_this.triggerValue = Controller.getValue(TRIGGER_CONTROLS[_this.hand]);
if (_this.triggerValue > _this.PAINTING_TRIGGER_THRESHOLD) {
_this.paint(intersection.intersection)
} else {
_this.resetStroke();
}
} else {
if (_this.currentStroke) {
_this.resetStroke();
}
Overlays.editOverlay(_this.laserPointer, {
visible: false
});
}
},
newStroke: function(position) {
_this.strokeBasePosition = position;
_this.currentStroke = Entities.addEntity({
type: "PolyLine",
name: _this.STROKE_NAME,
dimensions: {
x: 10,
y: 10,
z: 10
},
position: position,
textures: _this.MARKER_TEXTURE_URL,
color: _this.markerColor,
lifetime: 5000,
});
_this.linePoints = [];
_this.normals = [];
_this.strokes.push(_this.currentStroke);
},
paint: function(position) {
var basePosition = position;
if (!_this.currentStroke) {
if (_this.oldPosition) {
basePosition = _this.oldPosition;
}
_this.newStroke(basePosition);
}
var localPoint = Vec3.subtract(basePosition, _this.strokeBasePosition);
localPoint = Vec3.sum(localPoint, Vec3.multiply(_this.whiteboardNormal, _this.strokeForwardOffset));
if (_this.linePoints.length > 0) {
var distance = Vec3.distance(localPoint, _this.linePoints[_this.linePoints.length - 1]);
if (distance < _this.MIN_DISTANCE_BETWEEN_POINTS) {
return;
}
}
_this.linePoints.push(localPoint);
_this.normals.push(_this.whiteboardNormal);
var strokeWidths = [];
for (var i = 0; i < _this.linePoints.length; i++) {
// Create a temp array of stroke widths for calligraphy effect - start and end should be less wide
var pointsFromCenter = Math.abs(_this.linePoints.length / 2 - i);
var pointWidth = map(pointsFromCenter, 0, this.linePoints.length / 2, _this.STROKE_WIDTH_RANGE.max, this.STROKE_WIDTH_RANGE.min);
strokeWidths.push(pointWidth);
}
Entities.editEntity(_this.currentStroke, {
linePoints: _this.linePoints,
normals: _this.normals,
strokeWidths: strokeWidths
});
if (_this.linePoints.length > MAX_POINTS_PER_STROKE) {
Entities.editEntity(_this.currentStroke, {
parentID: _this.currentWhiteboard
});
_this.currentStroke = null;
_this.oldPosition = position;
}
},
resetStroke: function() {
Entities.editEntity(_this.currentStroke, {
parentID: _this.currentWhiteboard
});
_this.currentStroke = null;
_this.oldPosition = null;
},
preload: function(entityID) {
this.entityID = entityID;
_this.laserPointer = Overlays.addOverlay("circle3d", {
color: {
red: 220,
green: 35,
blue: 53
},
solid: true,
size: 0.01,
});
},
unload: function() {
Overlays.deleteOverlay(_this.laserPointer);
_this.strokes.forEach(function(stroke) {
Entities.deleteEntity(stroke);
});
}
};
// entity scripts always need to return a newly constructed object of our type
return new MarkerTip();
});

View file

@ -0,0 +1,254 @@
//
// whiteboardSpawner.js
// examples/homeContent/whiteboardV2
//
// Created by Eric Levina on 2/17/16
// Copyright 2016 High Fidelity, Inc.
//
// Run this script to spawn a whiteboard, markers, and an eraser.
// To draw on the whiteboard, equip a marker and hold down trigger with marker tip pointed at whiteboard
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
Script.include("../../libraries/utils.js")
var orientation = MyAvatar.orientation;
orientation = Quat.safeEulerAngles(orientation);
var markerRotation = Quat.fromVec3Degrees({
x: orientation.x + 10,
y: orientation.y - 90,
z: orientation.z
})
orientation.x = 0;
var whiteboardRotation = Quat.fromVec3Degrees({
x: 0,
y: orientation.y,
z: 0
});
orientation = Quat.fromVec3Degrees(orientation);
var markers = [];
var whiteboardPosition = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getFront(orientation)));
var WHITEBOARD_MODEL_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/models/Whiteboard-4.fbx";
var WHITEBOARD_COLLISION_HULL_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/models/whiteboardCollisionHull.obj";
var whiteboard = Entities.addEntity({
type: "Model",
name: "whiteboard",
modelURL: WHITEBOARD_MODEL_URL,
position: whiteboardPosition,
rotation: whiteboardRotation,
shapeType: 'compound',
compoundShapeURL: WHITEBOARD_COLLISION_HULL_URL,
dimensions: {
x: 1.86,
y: 2.7,
z: 0.4636
},
});
var whiteboardSurfacePosition = Vec3.sum(whiteboardPosition, {
x: 0.0,
y: 0.45,
z: 0.0
});
whiteboardSurfacePosition = Vec3.sum(whiteboardSurfacePosition, Vec3.multiply(-0.02, Quat.getRight(whiteboardRotation)));
var moveForwardDistance = 0.02;
whiteboardFrontSurfacePosition = Vec3.sum(whiteboardSurfacePosition, Vec3.multiply(-moveForwardDistance, Quat.getFront(whiteboardRotation)));
var whiteboardSurfaceSettings = {
type: "Box",
name: "hifi-whiteboardDrawingSurface",
dimensions: {
x: 1.82,
y: 1.8,
z: 0.01
},
color: {
red: 200,
green: 10,
blue: 200
},
position: whiteboardFrontSurfacePosition,
rotation: whiteboardRotation,
visible: false,
parentID: whiteboard
}
var whiteboardFrontDrawingSurface = Entities.addEntity(whiteboardSurfaceSettings);
whiteboardBackSurfacePosition = Vec3.sum(whiteboardSurfacePosition, Vec3.multiply(moveForwardDistance, Quat.getFront(whiteboardRotation)));
whiteboardSurfaceSettings.position = whiteboardBackSurfacePosition;
var whiteboardBackDrawingSurface = Entities.addEntity(whiteboardSurfaceSettings);
var WHITEBOARD_RACK_DEPTH = 1.9;
var ERASER_MODEL_URL = "http://hifi-content.s3.amazonaws.com/alan/dev/eraser-2.fbx";
var ERASER_SCRIPT_URL = Script.resolvePath("eraserEntityScript.js?v43");
var eraserPosition = Vec3.sum(MyAvatar.position, Vec3.multiply(WHITEBOARD_RACK_DEPTH, Quat.getFront(whiteboardRotation)));
eraserPosition = Vec3.sum(eraserPosition, Vec3.multiply(-0.5, Quat.getRight(whiteboardRotation)));
var eraserRotation = markerRotation;
var eraser = Entities.addEntity({
type: "Model",
modelURL: ERASER_MODEL_URL,
position: eraserPosition,
script: ERASER_SCRIPT_URL,
shapeType: "box",
dimensions: {
x: 0.0858,
y: 0.0393,
z: 0.2083
},
rotation: eraserRotation,
dynamic: true,
gravity: {
x: 0,
y: -1,
z: 0
},
velocity: {
x: 0,
y: -0.1,
z: 0
},
userData: JSON.stringify({
originalPosition: eraserPosition,
originalRotation: eraserRotation,
wearable: {
joints: {
RightHand: [{
x: 0.020,
y: 0.120,
z: 0.049
}, {
x: 0.1004,
y: 0.6424,
z: 0.717,
w: 0.250
}],
LeftHand: [{
x: -0.005,
y: 0.1101,
z: 0.053
}, {
x: 0.723,
y: 0.289,
z: 0.142,
w: 0.610
}]
}
}
})
});
createMarkers();
function createMarkers() {
var modelURLS = [
"https://s3-us-west-1.amazonaws.com/hifi-content/eric/models/marker-blue.fbx",
"https://s3-us-west-1.amazonaws.com/hifi-content/eric/models/marker-red.fbx",
"https://s3-us-west-1.amazonaws.com/hifi-content/eric/models/marker-black.fbx",
];
var markerPosition = Vec3.sum(MyAvatar.position, Vec3.multiply(WHITEBOARD_RACK_DEPTH, Quat.getFront(orientation)));
createMarker(modelURLS[0], markerPosition, {
red: 10,
green: 10,
blue: 200
});
markerPosition = Vec3.sum(markerPosition, Vec3.multiply(-0.2, Quat.getFront(markerRotation)));
createMarker(modelURLS[1], markerPosition, {
red: 200,
green: 10,
blue: 10
});
markerPosition = Vec3.sum(markerPosition, Vec3.multiply(0.4, Quat.getFront(markerRotation)));
createMarker(modelURLS[2], markerPosition, {
red: 10,
green: 10,
blue: 10
});
}
function createMarker(modelURL, markerPosition, markerColor) {
var MARKER_SCRIPT_URL = Script.resolvePath("markerEntityScript.js?v1" + Math.random());
var marker = Entities.addEntity({
type: "Model",
modelURL: modelURL,
rotation: markerRotation,
shapeType: "box",
name: "marker",
dynamic: true,
gravity: {
x: 0,
y: -1,
z: 0
},
velocity: {
x: 0,
y: -0.1,
z: 0
},
position: markerPosition,
dimensions: {
x: 0.027,
y: 0.027,
z: 0.164
},
name: "marker",
script: MARKER_SCRIPT_URL,
userData: JSON.stringify({
originalPosition: markerPosition,
originalRotation: markerRotation,
markerColor: markerColor,
wearable: {
joints: {
RightHand: [{
x: 0.001,
y: 0.139,
z: 0.050
}, {
x: -0.73,
y: -0.043,
z: -0.108,
w: -0.666
}],
LeftHand: [{
x: 0.007,
y: 0.151,
z: 0.061
}, {
x: -0.417,
y: 0.631,
z: -0.389,
w: -0.525
}]
}
}
})
});
markers.push(marker);
}
function cleanup() {
Entities.deleteEntity(whiteboard);
Entities.deleteEntity(whiteboardFrontDrawingSurface);
Entities.deleteEntity(whiteboardBackDrawingSurface);
Entities.deleteEntity(eraser);
markers.forEach(function(marker) {
Entities.deleteEntity(marker);
});
}
Script.scriptEnding.connect(cleanup);

View file

@ -133,13 +133,14 @@ TableView {
HiFiGlyphs { HiFiGlyphs {
id: reloadButton id: reloadButton
text: hifi.glyphs.reloadSmall text: hifi.glyphs.reloadSmall
color: parent.color color: reloadButtonArea.pressed ? hifi.colors.white : parent.color
anchors { anchors {
top: parent.top top: parent.top
right: stopButton.left right: stopButton.left
verticalCenter: parent.verticalCenter verticalCenter: parent.verticalCenter
} }
MouseArea { MouseArea {
id: reloadButtonArea
anchors { fill: parent; margins: -2 } anchors { fill: parent; margins: -2 }
onClicked: reloadScript(model.url) onClicked: reloadScript(model.url)
} }
@ -149,13 +150,14 @@ TableView {
HiFiGlyphs { HiFiGlyphs {
id: stopButton id: stopButton
text: hifi.glyphs.closeSmall text: hifi.glyphs.closeSmall
color: parent.color color: stopButtonArea.pressed ? hifi.colors.white : parent.color
anchors { anchors {
top: parent.top top: parent.top
right: parent.right right: parent.right
verticalCenter: parent.verticalCenter verticalCenter: parent.verticalCenter
} }
MouseArea { MouseArea {
id: stopButtonArea
anchors { fill: parent; margins: -2 } anchors { fill: parent; margins: -2 }
onClicked: stopScript(model.url) onClicked: stopScript(model.url)
} }

View file

@ -56,14 +56,23 @@ TreeView {
branchDelegate: HiFiGlyphs { branchDelegate: HiFiGlyphs {
text: styleData.isExpanded ? hifi.glyphs.disclosureCollapse : hifi.glyphs.disclosureExpand text: styleData.isExpanded ? hifi.glyphs.disclosureCollapse : hifi.glyphs.disclosureExpand
size: hifi.fontSizes.tableText * 2.5 // tableText is in points; proportionately scale to pixels size: hifi.fontSizes.tableText * 2.5
color: colorScheme == hifi.colorSchemes.light color: colorScheme == hifi.colorSchemes.light
? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight) ? (styleData.selected
: (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText) ? hifi.colors.black
: (iconArea.pressed ? hifi.colors.white : hifi.colors.baseGrayHighlight))
: (styleData.selected
? hifi.colors.black
: (iconArea.pressed ? hifi.colors.white : hifi.colors.lightGrayText))
anchors { anchors {
left: parent ? parent.left : undefined left: parent ? parent.left : undefined
leftMargin: hifi.dimensions.tablePadding / 2 leftMargin: hifi.dimensions.tablePadding / 2
} }
MouseArea {
id: iconArea
anchors.fill: parent
propagateComposedEvents: true
}
} }
handle: Item { handle: Item {

View file

@ -370,7 +370,6 @@ bool setupEssentials(int& argc, char** argv) {
DependencyManager::set<AutoUpdater>(); DependencyManager::set<AutoUpdater>();
DependencyManager::set<PathUtils>(); DependencyManager::set<PathUtils>();
DependencyManager::set<InterfaceActionFactory>(); DependencyManager::set<InterfaceActionFactory>();
DependencyManager::set<AssetClient>();
DependencyManager::set<AudioInjectorManager>(); DependencyManager::set<AudioInjectorManager>();
DependencyManager::set<MessagesClient>(); DependencyManager::set<MessagesClient>();
DependencyManager::set<UserInputMapper>(); DependencyManager::set<UserInputMapper>();
@ -528,13 +527,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
audioThread->start(); audioThread->start();
// Setup AssetClient ResourceManager::init();
auto assetClient = DependencyManager::get<AssetClient>();
QThread* assetThread = new QThread;
assetThread->setObjectName("Asset Thread");
assetClient->moveToThread(assetThread);
connect(assetThread, &QThread::started, assetClient.data(), &AssetClient::init);
assetThread->start();
// Setup MessagesClient // Setup MessagesClient
auto messagesClient = DependencyManager::get<MessagesClient>(); auto messagesClient = DependencyManager::get<MessagesClient>();
@ -644,13 +637,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
connect(&identityPacketTimer, &QTimer::timeout, getMyAvatar(), &MyAvatar::sendIdentityPacket); connect(&identityPacketTimer, &QTimer::timeout, getMyAvatar(), &MyAvatar::sendIdentityPacket);
identityPacketTimer.start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS); identityPacketTimer.start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS);
QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkDiskCache* cache = new QNetworkDiskCache();
cache->setMaximumCacheSize(MAXIMUM_CACHE_SIZE);
cache->setCacheDirectory(!cachePath.isEmpty() ? cachePath : "interfaceCache");
networkAccessManager.setCache(cache);
ResourceCache::setRequestLimit(3); ResourceCache::setRequestLimit(3);
_glWidget = new GLCanvas(); _glWidget = new GLCanvas();
@ -661,15 +647,15 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
_glWidget->setFocusPolicy(Qt::StrongFocus); _glWidget->setFocusPolicy(Qt::StrongFocus);
_glWidget->setFocus(); _glWidget->setFocus();
#ifdef Q_OS_MAC #ifdef Q_OS_MAC
// OSX doesn't seem to provide for hiding the cursor only on the GL widget auto cursorTarget = _window; // OSX doesn't seem to provide for hiding the cursor only on the GL widget
_window->setCursor(Qt::BlankCursor);
#else #else
// On windows and linux, hiding the top level cursor also means it's invisible // On windows and linux, hiding the top level cursor also means it's invisible when hovering over the
// when hovering over the window menu, which is a pain, so only hide it for // window menu, which is a pain, so only hide it for the GL surface
// the GL surface auto cursorTarget = _glWidget;
_glWidget->setCursor(Qt::BlankCursor);
#endif #endif
cursorTarget->setCursor(Qt::BlankCursor);
// enable mouse tracking; otherwise, we only get drag events // enable mouse tracking; otherwise, we only get drag events
_glWidget->setMouseTracking(true); _glWidget->setMouseTracking(true);
@ -981,6 +967,29 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
_idleTimer->start(0); _idleTimer->start(0);
} }
void Application::checkChangeCursor() {
QMutexLocker locker(&_changeCursorLock);
if (_cursorNeedsChanging) {
#ifdef Q_OS_MAC
auto cursorTarget = _window; // OSX doesn't seem to provide for hiding the cursor only on the GL widget
#else
// On windows and linux, hiding the top level cursor also means it's invisible when hovering over the
// window menu, which is a pain, so only hide it for the GL surface
auto cursorTarget = _glWidget;
#endif
cursorTarget->setCursor(_desiredCursor);
_cursorNeedsChanging = false;
}
}
void Application::showCursor(const QCursor& cursor) {
QMutexLocker locker(&_changeCursorLock);
_desiredCursor = cursor;
_cursorNeedsChanging = true;
}
void Application::aboutToQuit() { void Application::aboutToQuit() {
emit beforeAboutToQuit(); emit beforeAboutToQuit();
@ -1062,13 +1071,6 @@ void Application::cleanupBeforeQuit() {
DependencyManager::destroy<OffscreenUi>(); DependencyManager::destroy<OffscreenUi>();
} }
void Application::emptyLocalCache() {
if (auto cache = NetworkAccessManager::getInstance().cache()) {
qDebug() << "DiskCacheEditor::clear(): Clearing disk cache.";
cache->clear();
}
}
Application::~Application() { Application::~Application() {
EntityTreePointer tree = getEntities()->getTree(); EntityTreePointer tree = getEntities()->getTree();
tree->setSimulation(NULL); tree->setSimulation(NULL);
@ -1106,11 +1108,7 @@ Application::~Application() {
DependencyManager::destroy<ScriptCache>(); DependencyManager::destroy<ScriptCache>();
DependencyManager::destroy<SoundCache>(); DependencyManager::destroy<SoundCache>();
// cleanup the AssetClient thread ResourceManager::cleanup();
QThread* assetThread = DependencyManager::get<AssetClient>()->thread();
DependencyManager::destroy<AssetClient>();
assetThread->quit();
assetThread->wait();
QThread* nodeThread = DependencyManager::get<NodeList>()->thread(); QThread* nodeThread = DependencyManager::get<NodeList>()->thread();
@ -2431,6 +2429,9 @@ void Application::idle(uint64_t now) {
return; // bail early, nothing to do here. return; // bail early, nothing to do here.
} }
checkChangeCursor();
Stats::getInstance()->updateStats(); Stats::getInstance()->updateStats();
AvatarInputs::getInstance()->update(); AvatarInputs::getInstance()->update();
@ -3055,7 +3056,7 @@ void Application::reloadResourceCaches() {
_viewFrustum.setOrientation(glm::quat()); _viewFrustum.setOrientation(glm::quat());
queryOctree(NodeType::EntityServer, PacketType::EntityQuery, _entityServerJurisdictions); queryOctree(NodeType::EntityServer, PacketType::EntityQuery, _entityServerJurisdictions);
emptyLocalCache(); DependencyManager::get<AssetClient>()->clearCache();
DependencyManager::get<AnimationCache>()->refreshAll(); DependencyManager::get<AnimationCache>()->refreshAll();
DependencyManager::get<ModelCache>()->refreshAll(); DependencyManager::get<ModelCache>()->refreshAll();

View file

@ -120,6 +120,8 @@ public:
QSize getDeviceSize() const; QSize getDeviceSize() const;
bool hasFocus() const; bool hasFocus() const;
void showCursor(const QCursor& cursor);
bool isThrottleRendering() const; bool isThrottleRendering() const;
Camera* getCamera() { return &_myCamera; } Camera* getCamera() { return &_myCamera; }
@ -328,8 +330,6 @@ private:
void cleanupBeforeQuit(); void cleanupBeforeQuit();
void emptyLocalCache();
void update(float deltaTime); void update(float deltaTime);
void setPalmData(Hand* hand, const controller::Pose& pose, float deltaTime, HandData::Hand whichHand, float triggerValue); void setPalmData(Hand* hand, const controller::Pose& pose, float deltaTime, HandData::Hand whichHand, float triggerValue);
@ -515,6 +515,11 @@ private:
QTimer* _idleTimer { nullptr }; QTimer* _idleTimer { nullptr };
bool _fakedMouseEvent { false }; bool _fakedMouseEvent { false };
void checkChangeCursor();
mutable QMutex _changeCursorLock { QMutex::Recursive };
QCursor _desiredCursor{ Qt::BlankCursor };
bool _cursorNeedsChanging { false };
}; };
#endif // hifi_Application_h #endif // hifi_Application_h

View file

@ -212,19 +212,21 @@ void ApplicationCompositor::displayOverlayTexture(RenderArgs* renderArgs) {
geometryCache->renderUnitQuad(batch, vec4(vec3(1), _alpha)); geometryCache->renderUnitQuad(batch, vec4(vec3(1), _alpha));
//draw the mouse pointer //draw the mouse pointer
// Get the mouse coordinates and convert to NDC [-1, 1] if (getReticleVisible()) {
vec2 canvasSize = qApp->getCanvasSize(); // desktop, use actual canvas... // Get the mouse coordinates and convert to NDC [-1, 1]
vec2 mousePosition = toNormalizedDeviceScale(vec2(qApp->getMouse()), canvasSize); vec2 canvasSize = qApp->getCanvasSize(); // desktop, use actual canvas...
// Invert the Y axis vec2 mousePosition = toNormalizedDeviceScale(vec2(qApp->getMouse()), canvasSize);
mousePosition.y *= -1.0f; // Invert the Y axis
mousePosition.y *= -1.0f;
Transform model; Transform model;
model.setTranslation(vec3(mousePosition, 0)); model.setTranslation(vec3(mousePosition, 0));
vec2 mouseSize = CURSOR_PIXEL_SIZE / canvasSize; vec2 mouseSize = CURSOR_PIXEL_SIZE / canvasSize;
model.setScale(vec3(mouseSize, 1.0f)); model.setScale(vec3(mouseSize, 1.0f));
batch.setModelTransform(model); batch.setModelTransform(model);
bindCursorTexture(batch); bindCursorTexture(batch);
geometryCache->renderUnitQuad(batch, vec4(1)); geometryCache->renderUnitQuad(batch, vec4(1));
}
}); });
} }
@ -335,9 +337,21 @@ QPointF ApplicationCompositor::getMouseEventPosition(QMouseEvent* event) {
bool ApplicationCompositor::shouldCaptureMouse() const { bool ApplicationCompositor::shouldCaptureMouse() const {
// if we're in HMD mode, and some window of ours is active, but we're not currently showing a popup menu // if we're in HMD mode, and some window of ours is active, but we're not currently showing a popup menu
return qApp->isHMDMode() && QApplication::activeWindow() && !Menu::isSomeSubmenuShown(); return _allowMouseCapture && qApp->isHMDMode() && QApplication::activeWindow() && !Menu::isSomeSubmenuShown();
} }
void ApplicationCompositor::setAllowMouseCapture(bool capture) {
if (qApp->isHMDMode()) {
if (capture) {
qApp->showCursor(Qt::BlankCursor);
} else {
qApp->showCursor(Qt::ArrowCursor);
}
}
_allowMouseCapture = capture;
}
void ApplicationCompositor::handleLeaveEvent() { void ApplicationCompositor::handleLeaveEvent() {
if (shouldCaptureMouse()) { if (shouldCaptureMouse()) {

View file

@ -106,6 +106,9 @@ public:
bool shouldCaptureMouse() const; bool shouldCaptureMouse() const;
bool getAllowMouseCapture() const { return _allowMouseCapture; }
void setAllowMouseCapture(bool capture);
/// if the reticle is pointing to a system overlay (a dialog box for example) then the function returns true otherwise false /// if the reticle is pointing to a system overlay (a dialog box for example) then the function returns true otherwise false
bool getReticleOverDesktop() const; bool getReticleOverDesktop() const;
void setReticleOverDesktop(bool value) { _isOverDesktop = value; } void setReticleOverDesktop(bool value) { _isOverDesktop = value; }
@ -162,6 +165,8 @@ private:
bool _reticleOverQml { false }; bool _reticleOverQml { false };
bool _allowMouseCapture { true };
ReticleInterface* _reticleInterface; ReticleInterface* _reticleInterface;
}; };
@ -173,12 +178,17 @@ class ReticleInterface : public QObject {
Q_PROPERTY(float depth READ getDepth WRITE setDepth) Q_PROPERTY(float depth READ getDepth WRITE setDepth)
Q_PROPERTY(glm::vec2 maximumPosition READ getMaximumPosition) Q_PROPERTY(glm::vec2 maximumPosition READ getMaximumPosition)
Q_PROPERTY(bool mouseCaptured READ isMouseCaptured) Q_PROPERTY(bool mouseCaptured READ isMouseCaptured)
Q_PROPERTY(bool allowMouseCapture READ getAllowMouseCapture WRITE setAllowMouseCapture)
Q_PROPERTY(bool pointingAtSystemOverlay READ isPointingAtSystemOverlay) Q_PROPERTY(bool pointingAtSystemOverlay READ isPointingAtSystemOverlay)
public: public:
ReticleInterface(ApplicationCompositor* outer) : QObject(outer), _compositor(outer) {} ReticleInterface(ApplicationCompositor* outer) : QObject(outer), _compositor(outer) {}
Q_INVOKABLE bool isMouseCaptured() { return _compositor->shouldCaptureMouse(); } Q_INVOKABLE bool isMouseCaptured() { return _compositor->shouldCaptureMouse(); }
Q_INVOKABLE bool getAllowMouseCapture() { return _compositor->getAllowMouseCapture(); }
Q_INVOKABLE void setAllowMouseCapture(bool value) { return _compositor->setAllowMouseCapture(value); }
Q_INVOKABLE bool isPointingAtSystemOverlay() { return !_compositor->getReticleOverDesktop(); } Q_INVOKABLE bool isPointingAtSystemOverlay() { return !_compositor->getReticleOverDesktop(); }
Q_INVOKABLE bool getVisible() { return _compositor->getReticleVisible(); } Q_INVOKABLE bool getVisible() { return _compositor->getReticleVisible(); }

View file

@ -9,23 +9,21 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
#include <functional> #include "DiskCacheEditor.h"
#include <QDebug> #include <QDebug>
#include <QDialog> #include <QDialog>
#include <QGridLayout> #include <QGridLayout>
#include <QPushButton> #include <QPushButton>
#include <QLabel> #include <QLabel>
#include <QNetworkDiskCache> #include <QTimer>
#include <QMessageBox> #include <QMessageBox>
#include <NetworkAccessManager.h> #include <AssetClient.h>
#include "DiskCacheEditor.h"
#include "OffscreenUi.h" #include "OffscreenUi.h"
DiskCacheEditor::DiskCacheEditor(QWidget* parent) : QObject(parent) { DiskCacheEditor::DiskCacheEditor(QWidget* parent) : QObject(parent) {
} }
QWindow* DiskCacheEditor::windowHandle() { QWindow* DiskCacheEditor::windowHandle() {
@ -33,7 +31,6 @@ QWindow* DiskCacheEditor::windowHandle() {
} }
void DiskCacheEditor::toggle() { void DiskCacheEditor::toggle() {
qDebug() << "DiskCacheEditor::toggle()";
if (!_dialog) { if (!_dialog) {
makeDialog(); makeDialog();
} }
@ -88,17 +85,17 @@ void DiskCacheEditor::makeDialog() {
Q_CHECK_PTR(_maxSize); Q_CHECK_PTR(_maxSize);
_maxSize->setAlignment(Qt::AlignLeft); _maxSize->setAlignment(Qt::AlignLeft);
layout->addWidget(_maxSize, 2, 1, 1, 3); layout->addWidget(_maxSize, 2, 1, 1, 3);
refresh(); refresh();
QPushButton* refreshCacheButton = new QPushButton(_dialog); static const int REFRESH_INTERVAL = 100; // msec
Q_CHECK_PTR(refreshCacheButton); _refreshTimer = new QTimer(_dialog);
refreshCacheButton->setText("Refresh"); _refreshTimer->setInterval(REFRESH_INTERVAL);
refreshCacheButton->setToolTip("Reload the cache stats."); _refreshTimer->setSingleShot(false);
connect(refreshCacheButton, SIGNAL(clicked()), SLOT(refresh())); QObject::connect(_refreshTimer.data(), &QTimer::timeout, this, &DiskCacheEditor::refresh);
layout->addWidget(refreshCacheButton, 3, 2); _refreshTimer->start();
QPushButton* clearCacheButton = new QPushButton(_dialog); QPushButton* clearCacheButton = new QPushButton(_dialog);
Q_CHECK_PTR(clearCacheButton); Q_CHECK_PTR(clearCacheButton);
clearCacheButton->setText("Clear"); clearCacheButton->setText("Clear");
@ -108,7 +105,11 @@ void DiskCacheEditor::makeDialog() {
} }
void DiskCacheEditor::refresh() { void DiskCacheEditor::refresh() {
static const std::function<QString(qint64)> stringify = [](qint64 number) { DependencyManager::get<AssetClient>()->cacheInfoRequest(this, "cacheInfoCallback");
}
void DiskCacheEditor::cacheInfoCallback(QString cacheDirectory, qint64 cacheSize, qint64 maximumCacheSize) {
static const auto stringify = [](qint64 number) {
static const QStringList UNITS = QStringList() << "B" << "KB" << "MB" << "GB"; static const QStringList UNITS = QStringList() << "B" << "KB" << "MB" << "GB";
static const qint64 CHUNK = 1024; static const qint64 CHUNK = 1024;
QString unit; QString unit;
@ -122,30 +123,24 @@ void DiskCacheEditor::refresh() {
} }
return QString("%0 %1").arg(number).arg(UNITS[i]); return QString("%0 %1").arg(number).arg(UNITS[i]);
}; };
QNetworkDiskCache* cache = qobject_cast<QNetworkDiskCache*>(NetworkAccessManager::getInstance().cache());
if (_path) { if (_path) {
_path->setText(cache->cacheDirectory()); _path->setText(cacheDirectory);
} }
if (_size) { if (_size) {
_size->setText(stringify(cache->cacheSize())); _size->setText(stringify(cacheSize));
} }
if (_maxSize) { if (_maxSize) {
_maxSize->setText(stringify(cache->maximumCacheSize())); _maxSize->setText(stringify(maximumCacheSize));
} }
} }
void DiskCacheEditor::clear() { void DiskCacheEditor::clear() {
QMessageBox::StandardButton buttonClicked = auto buttonClicked = OffscreenUi::question(_dialog, "Clearing disk cache",
OffscreenUi::question(_dialog, "Clearing disk cache", "You are about to erase all the content of the disk cache, "
"You are about to erase all the content of the disk cache, " "are you sure you want to do that?",
"are you sure you want to do that?", QMessageBox::Ok | QMessageBox::Cancel);
QMessageBox::Ok | QMessageBox::Cancel);
if (buttonClicked == QMessageBox::Ok) { if (buttonClicked == QMessageBox::Ok) {
if (auto cache = NetworkAccessManager::getInstance().cache()) { DependencyManager::get<AssetClient>()->clearCache();
qDebug() << "DiskCacheEditor::clear(): Clearing disk cache.";
cache->clear();
}
} }
refresh();
} }

View file

@ -18,6 +18,7 @@
class QDialog; class QDialog;
class QLabel; class QLabel;
class QWindow; class QWindow;
class QTimer;
class DiskCacheEditor : public QObject { class DiskCacheEditor : public QObject {
Q_OBJECT Q_OBJECT
@ -32,8 +33,9 @@ public slots:
private slots: private slots:
void refresh(); void refresh();
void cacheInfoCallback(QString cacheDirectory, qint64 cacheSize, qint64 maximumCacheSize);
void clear(); void clear();
private: private:
void makeDialog(); void makeDialog();
@ -41,6 +43,7 @@ private:
QPointer<QLabel> _path; QPointer<QLabel> _path;
QPointer<QLabel> _size; QPointer<QLabel> _size;
QPointer<QLabel> _maxSize; QPointer<QLabel> _maxSize;
QPointer<QTimer> _refreshTimer;
}; };
#endif // hifi_DiskCacheEditor_h #endif // hifi_DiskCacheEditor_h

View file

@ -47,22 +47,53 @@ AssetClient::AssetClient() {
} }
void AssetClient::init() { void AssetClient::init() {
if (QThread::currentThread() != thread()) { Q_ASSERT(QThread::currentThread() == thread());
QMetaObject::invokeMethod(this, "init", Qt::BlockingQueuedConnection);
}
// Setup disk cache if not already // Setup disk cache if not already
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); auto& networkAccessManager = NetworkAccessManager::getInstance();
if (!networkAccessManager.cache()) { if (!networkAccessManager.cache()) {
QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation); QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
cachePath = !cachePath.isEmpty() ? cachePath : "interfaceCache"; cachePath = !cachePath.isEmpty() ? cachePath : "interfaceCache";
QNetworkDiskCache* cache = new QNetworkDiskCache(); QNetworkDiskCache* cache = new QNetworkDiskCache();
cache->setMaximumCacheSize(MAXIMUM_CACHE_SIZE); cache->setMaximumCacheSize(MAXIMUM_CACHE_SIZE);
cache->setCacheDirectory(cachePath); cache->setCacheDirectory(cachePath);
networkAccessManager.setCache(cache); networkAccessManager.setCache(cache);
qCDebug(asset_client) << "AssetClient disk cache setup at" << cachePath qDebug() << "ResourceManager disk cache setup at" << cachePath
<< "(size:" << MAXIMUM_CACHE_SIZE / BYTES_PER_GIGABYTES << "GB)"; << "(size:" << MAXIMUM_CACHE_SIZE / BYTES_PER_GIGABYTES << "GB)";
}
}
void AssetClient::cacheInfoRequest(QObject* reciever, QString slot) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "cacheInfoRequest", Qt::QueuedConnection,
Q_ARG(QObject*, reciever), Q_ARG(QString, slot));
return;
}
if (auto* cache = qobject_cast<QNetworkDiskCache*>(NetworkAccessManager::getInstance().cache())) {
QMetaObject::invokeMethod(reciever, slot.toStdString().data(), Qt::QueuedConnection,
Q_ARG(QString, cache->cacheDirectory()),
Q_ARG(qint64, cache->cacheSize()),
Q_ARG(qint64, cache->maximumCacheSize()));
} else {
qCWarning(asset_client) << "No disk cache to get info from.";
}
}
void AssetClient::clearCache() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "clearCache", Qt::QueuedConnection);
return;
}
if (auto cache = NetworkAccessManager::getInstance().cache()) {
qDebug() << "AssetClient::clearCache(): Clearing disk cache.";
cache->clear();
} else {
qCWarning(asset_client) << "No disk cache to clear.";
} }
} }

View file

@ -43,13 +43,17 @@ class AssetClient : public QObject, public Dependency {
Q_OBJECT Q_OBJECT
public: public:
AssetClient(); AssetClient();
Q_INVOKABLE void init();
Q_INVOKABLE AssetRequest* createRequest(const QString& hash, const QString& extension); Q_INVOKABLE AssetRequest* createRequest(const QString& hash, const QString& extension);
Q_INVOKABLE AssetUpload* createUpload(const QString& filename); Q_INVOKABLE AssetUpload* createUpload(const QString& filename);
Q_INVOKABLE AssetUpload* createUpload(const QByteArray& data, const QString& extension); Q_INVOKABLE AssetUpload* createUpload(const QByteArray& data, const QString& extension);
public slots:
void init();
void cacheInfoRequest(QObject* reciever, QString slot);
void clearCache();
private slots: private slots:
void handleAssetGetInfoReply(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode); void handleAssetGetInfoReply(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void handleAssetGetReply(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode); void handleAssetGetReply(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);

View file

@ -20,7 +20,7 @@
class AssetResourceRequest : public ResourceRequest { class AssetResourceRequest : public ResourceRequest {
Q_OBJECT Q_OBJECT
public: public:
AssetResourceRequest(QObject* parent, const QUrl& url) : ResourceRequest(parent, url) { } AssetResourceRequest(const QUrl& url) : ResourceRequest(url) { }
~AssetResourceRequest(); ~AssetResourceRequest();
protected: protected:

View file

@ -19,7 +19,7 @@
class FileResourceRequest : public ResourceRequest { class FileResourceRequest : public ResourceRequest {
Q_OBJECT Q_OBJECT
public: public:
FileResourceRequest(QObject* parent, const QUrl& url) : ResourceRequest(parent, url) { } FileResourceRequest(const QUrl& url) : ResourceRequest(url) { }
protected: protected:
virtual void doSend() override; virtual void doSend() override;

View file

@ -28,6 +28,25 @@ HTTPResourceRequest::~HTTPResourceRequest() {
} }
} }
void HTTPResourceRequest::setupTimer() {
Q_ASSERT(!_sendTimer);
static const int TIMEOUT_MS = 10000;
_sendTimer = new QTimer();
connect(this, &QObject::destroyed, _sendTimer, &QTimer::deleteLater);
connect(_sendTimer, &QTimer::timeout, this, &HTTPResourceRequest::onTimeout);
_sendTimer->setSingleShot(true);
_sendTimer->start(TIMEOUT_MS);
}
void HTTPResourceRequest::cleanupTimer() {
Q_ASSERT(_sendTimer);
_sendTimer->disconnect(this);
_sendTimer->deleteLater();
_sendTimer = nullptr;
}
void HTTPResourceRequest::doSend() { void HTTPResourceRequest::doSend() {
QNetworkRequest networkRequest(_url); QNetworkRequest networkRequest(_url);
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
@ -42,18 +61,15 @@ void HTTPResourceRequest::doSend() {
connect(_reply, &QNetworkReply::finished, this, &HTTPResourceRequest::onRequestFinished); connect(_reply, &QNetworkReply::finished, this, &HTTPResourceRequest::onRequestFinished);
connect(_reply, &QNetworkReply::downloadProgress, this, &HTTPResourceRequest::onDownloadProgress); connect(_reply, &QNetworkReply::downloadProgress, this, &HTTPResourceRequest::onDownloadProgress);
connect(&_sendTimer, &QTimer::timeout, this, &HTTPResourceRequest::onTimeout);
static const int TIMEOUT_MS = 10000; setupTimer();
_sendTimer.setSingleShot(true);
_sendTimer.start(TIMEOUT_MS);
} }
void HTTPResourceRequest::onRequestFinished() { void HTTPResourceRequest::onRequestFinished() {
Q_ASSERT(_state == InProgress); Q_ASSERT(_state == InProgress);
Q_ASSERT(_reply); Q_ASSERT(_reply);
_sendTimer.stop(); cleanupTimer();
switch(_reply->error()) { switch(_reply->error()) {
case QNetworkReply::NoError: case QNetworkReply::NoError:
@ -80,7 +96,7 @@ void HTTPResourceRequest::onDownloadProgress(qint64 bytesReceived, qint64 bytesT
Q_ASSERT(_state == InProgress); Q_ASSERT(_state == InProgress);
// We've received data, so reset the timer // We've received data, so reset the timer
_sendTimer.start(); _sendTimer->start();
emit progress(bytesReceived, bytesTotal); emit progress(bytesReceived, bytesTotal);
} }
@ -91,6 +107,8 @@ void HTTPResourceRequest::onTimeout() {
_reply->abort(); _reply->abort();
_reply->deleteLater(); _reply->deleteLater();
_reply = nullptr; _reply = nullptr;
cleanupTimer();
_result = Timeout; _result = Timeout;
_state = Finished; _state = Finished;

View file

@ -21,7 +21,7 @@
class HTTPResourceRequest : public ResourceRequest { class HTTPResourceRequest : public ResourceRequest {
Q_OBJECT Q_OBJECT
public: public:
HTTPResourceRequest(QObject* parent, const QUrl& url) : ResourceRequest(parent, url) { } HTTPResourceRequest(const QUrl& url) : ResourceRequest(url) { }
~HTTPResourceRequest(); ~HTTPResourceRequest();
protected: protected:
@ -33,7 +33,10 @@ private slots:
void onRequestFinished(); void onRequestFinished();
private: private:
QTimer _sendTimer; void setupTimer();
void cleanupTimer();
QTimer* _sendTimer { nullptr };
QNetworkReply* _reply { nullptr }; QNetworkReply* _reply { nullptr };
}; };

View file

@ -11,12 +11,20 @@
#include "ResourceManager.h" #include "ResourceManager.h"
#include "AssetResourceRequest.h" #include <QNetworkDiskCache>
#include "FileResourceRequest.h" #include <QStandardPaths>
#include "HTTPResourceRequest.h" #include <QThread>
#include <SharedUtil.h> #include <SharedUtil.h>
#include "AssetResourceRequest.h"
#include "FileResourceRequest.h"
#include "HTTPResourceRequest.h"
#include "NetworkAccessManager.h"
QThread ResourceManager::_thread;
ResourceManager::PrefixMap ResourceManager::_prefixMap; ResourceManager::PrefixMap ResourceManager::_prefixMap;
QMutex ResourceManager::_prefixMapLock; QMutex ResourceManager::_prefixMapLock;
@ -67,18 +75,41 @@ QUrl ResourceManager::normalizeURL(const QUrl& originalUrl) {
return url; return url;
} }
void ResourceManager::init() {
_thread.setObjectName("Ressource Manager Thread");
auto assetClient = DependencyManager::set<AssetClient>();
assetClient->moveToThread(&_thread);
QObject::connect(&_thread, &QThread::started, assetClient.data(), &AssetClient::init);
_thread.start();
}
void ResourceManager::cleanup() {
// cleanup the AssetClient thread
DependencyManager::destroy<AssetClient>();
_thread.quit();
_thread.wait();
}
ResourceRequest* ResourceManager::createResourceRequest(QObject* parent, const QUrl& url) { ResourceRequest* ResourceManager::createResourceRequest(QObject* parent, const QUrl& url) {
auto normalizedURL = normalizeURL(url); auto normalizedURL = normalizeURL(url);
auto scheme = normalizedURL.scheme(); auto scheme = normalizedURL.scheme();
ResourceRequest* request = nullptr;
if (scheme == URL_SCHEME_FILE) { if (scheme == URL_SCHEME_FILE) {
return new FileResourceRequest(parent, normalizedURL); request = new FileResourceRequest(normalizedURL);
} else if (scheme == URL_SCHEME_HTTP || scheme == URL_SCHEME_HTTPS || scheme == URL_SCHEME_FTP) { } else if (scheme == URL_SCHEME_HTTP || scheme == URL_SCHEME_HTTPS || scheme == URL_SCHEME_FTP) {
return new HTTPResourceRequest(parent, normalizedURL); request = new HTTPResourceRequest(normalizedURL);
} else if (scheme == URL_SCHEME_ATP) { } else if (scheme == URL_SCHEME_ATP) {
return new AssetResourceRequest(parent, normalizedURL); request = new AssetResourceRequest(normalizedURL);
} else {
qDebug() << "Unknown scheme (" << scheme << ") for URL: " << url.url();
return nullptr;
} }
Q_ASSERT(request);
qDebug() << "Unknown scheme (" << scheme << ") for URL: " << url.url(); request->moveToThread(&_thread);
return request;
return nullptr;
} }

View file

@ -29,8 +29,15 @@ public:
static void setUrlPrefixOverride(const QString& prefix, const QString& replacement); static void setUrlPrefixOverride(const QString& prefix, const QString& replacement);
static QString normalizeURL(const QString& urlString); static QString normalizeURL(const QString& urlString);
static QUrl normalizeURL(const QUrl& url); static QUrl normalizeURL(const QUrl& url);
static ResourceRequest* createResourceRequest(QObject* parent, const QUrl& url); static ResourceRequest* createResourceRequest(QObject* parent, const QUrl& url);
static void init();
static void cleanup();
private: private:
static QThread _thread;
using PrefixMap = std::map<QString, QString>; using PrefixMap = std::map<QString, QString>;
static PrefixMap _prefixMap; static PrefixMap _prefixMap;

View file

@ -11,12 +11,15 @@
#include "ResourceRequest.h" #include "ResourceRequest.h"
ResourceRequest::ResourceRequest(QObject* parent, const QUrl& url) : #include <QtCore/QThread>
QObject(parent),
_url(url) { ResourceRequest::ResourceRequest(const QUrl& url) : _url(url) { }
}
void ResourceRequest::send() { void ResourceRequest::send() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "send", Qt::QueuedConnection);
return;
}
Q_ASSERT(_state == NotStarted); Q_ASSERT(_state == NotStarted);
_state = InProgress; _state = InProgress;

View file

@ -20,7 +20,7 @@
class ResourceRequest : public QObject { class ResourceRequest : public QObject {
Q_OBJECT Q_OBJECT
public: public:
ResourceRequest(QObject* parent, const QUrl& url); ResourceRequest(const QUrl& url);
enum State { enum State {
NotStarted = 0, NotStarted = 0,
@ -38,7 +38,6 @@ public:
NotFound NotFound
}; };
void send();
QByteArray getData() { return _data; } QByteArray getData() { return _data; }
State getState() const { return _state; } State getState() const { return _state; }
Result getResult() const { return _result; } Result getResult() const { return _result; }
@ -47,8 +46,11 @@ public:
void setCacheEnabled(bool value) { _cacheEnabled = value; } void setCacheEnabled(bool value) { _cacheEnabled = value; }
public slots:
void send();
signals: signals:
void progress(uint64_t bytesReceived, uint64_t bytesTotal); void progress(qint64 bytesReceived, qint64 bytesTotal);
void finished(); void finished();
protected: protected:

View file

@ -96,6 +96,9 @@
if (this.ballLocked === true) { if (this.ballLocked === true) {
return; return;
} }
if(this.ball!==null){
Entities.deleteEntity(this.ball);
}
var properties = { var properties = {
name: 'Hifi Tilt Maze Ball', name: 'Hifi Tilt Maze Ball',