Merge branch 'atp' of github.com:birarda/hifi into atp-more

This commit is contained in:
Ryan Huffman 2016-03-10 14:20:22 -08:00
commit e4b5c14a4c
94 changed files with 2674 additions and 689 deletions

View file

@ -482,7 +482,7 @@ void AssetServer::loadMappingsFromFile() {
}
}
qCritical() << "Failed to read mapping file at" << mapFilePath << "- assignment with not continue.";
qCritical() << "Failed to read mapping file at" << mapFilePath << "- assignment will not continue.";
setFinished(true);
} else {
qInfo() << "No existing mappings loaded from file since no file was found at" << mapFilePath;
@ -604,7 +604,7 @@ bool AssetServer::deleteMappings(const AssetPathList& paths) {
}
bool AssetServer::renameMapping(const AssetPath& oldPath, const AssetPath& newPath) {
if (oldPath[0] != '/' || newPath[0] != '/') {
if (!isValidPath(oldPath) || !isValidPath(newPath)) {
qWarning() << "Cannot perform rename with invalid paths - both should have leading forward slashes:"
<< oldPath << "=>" << newPath;

View file

@ -589,6 +589,9 @@ Section "-Core installation"
Delete "$INSTDIR\version"
Delete "$INSTDIR\xinput1_3.dll"
; Rename the incorrectly cased Raleway font
Rename "$INSTDIR\resources\qml\styles-uit\RalewaySemibold.qml" "$INSTDIR\resources\qml\styles-uit\RalewaySemiBold.qml"
; Remove the Old Interface directory and vcredist_x64.exe (from installs prior to Server Console)
RMDir /r "$INSTDIR\Interface"
Delete "$INSTDIR\vcredist_x64.exe"

View file

@ -10,9 +10,9 @@
//
var DEBUGGING = false;
var angularVelocityTrailingAverage = 0.0; // Global trailing average used to decide whether to move reticle at all
var angularVelocityTrailingAverage = 0.0; // Global trailing average used to decide whether to move reticle at all
var lastX = 0;
var lastY = 0;
var lastY = 0;
Math.clamp=function(a,b,c) {
return Math.max(b,Math.min(c,a));
@ -54,9 +54,11 @@ function debugPrint(message) {
var leftRightBias = 0.0;
var filteredRotatedLeft = Vec3.UNIT_NEG_Y;
var filteredRotatedRight = Vec3.UNIT_NEG_Y;
var lastAlpha = 0;
Script.update.connect(function(deltaTime) {
// avatar frame
var poseRight = Controller.getPoseValue(Controller.Standard.RightHand);
var poseLeft = Controller.getPoseValue(Controller.Standard.LeftHand);
@ -65,53 +67,59 @@ Script.update.connect(function(deltaTime) {
var screenSizeX = screenSize.x;
var screenSizeY = screenSize.y;
var rotatedRight = Vec3.multiplyQbyV(poseRight.rotation, Vec3.UNIT_NEG_Y);
var rotatedLeft = Vec3.multiplyQbyV(poseLeft.rotation, Vec3.UNIT_NEG_Y);
// transform hand facing vectors from avatar frame into sensor frame.
var worldToSensorMatrix = Mat4.inverse(MyAvatar.sensorToWorldMatrix);
var rotatedRight = Mat4.transformVector(worldToSensorMatrix, Vec3.multiplyQbyV(MyAvatar.orientation, Vec3.multiplyQbyV(poseRight.rotation, Vec3.UNIT_NEG_Y)));
var rotatedLeft = Mat4.transformVector(worldToSensorMatrix, Vec3.multiplyQbyV(MyAvatar.orientation, Vec3.multiplyQbyV(poseLeft.rotation, Vec3.UNIT_NEG_Y)));
lastRotatedRight = rotatedRight;
// Decide which hand should be controlling the pointer
// by comparing which one is moving more, and by
// tending to stay with the one moving more.
var BIAS_ADJUST_RATE = 0.5;
var BIAS_ADJUST_DEADZONE = 0.05;
leftRightBias += (Vec3.length(poseRight.angularVelocity) - Vec3.length(poseLeft.angularVelocity)) * BIAS_ADJUST_RATE;
if (leftRightBias < BIAS_ADJUST_DEADZONE) {
leftRightBias = 0.0;
} else if (leftRightBias > (1.0 - BIAS_ADJUST_DEADZONE)) {
leftRightBias = 1.0;
// by comparing which one is moving more, and by
// tending to stay with the one moving more.
if (deltaTime > 0.001) {
// leftRightBias is a running average of the difference in angular hand speed.
// a positive leftRightBias indicates the right hand is spinning faster then the left hand.
// a negative leftRightBias indicates the left hand is spnning faster.
var BIAS_ADJUST_PERIOD = 1.0;
var tau = Math.clamp(deltaTime / BIAS_ADJUST_PERIOD, 0, 1);
newLeftRightBias = Vec3.length(poseRight.angularVelocity) - Vec3.length(poseLeft.angularVelocity);
leftRightBias = (1 - tau) * leftRightBias + tau * newLeftRightBias;
}
// add a bit of hysteresis to prevent control flopping back and forth
// between hands when they are both mostly stationary.
var alpha;
var HYSTERESIS_OFFSET = 0.25;
if (lastAlpha > 0.5) {
// prefer right hand over left
alpha = leftRightBias > -HYSTERESIS_OFFSET ? 1 : 0;
} else {
alpha = leftRightBias > HYSTERESIS_OFFSET ? 1 : 0;
}
lastAlpha = alpha;
// Velocity filter the hand rotation used to position reticle so that it is easier to target small things with the hand controllers
var VELOCITY_FILTER_GAIN = 0.5;
filteredRotatedLeft = Vec3.mix(filteredRotatedLeft, rotatedLeft, Math.clamp(Vec3.length(poseLeft.angularVelocity) * VELOCITY_FILTER_GAIN, 0.0, 1.0));
filteredRotatedRight = Vec3.mix(filteredRotatedRight, rotatedRight, Math.clamp(Vec3.length(poseRight.angularVelocity) * VELOCITY_FILTER_GAIN, 0.0, 1.0));
var rotated = Vec3.mix(filteredRotatedLeft, filteredRotatedRight, leftRightBias);
var rotated = Vec3.mix(filteredRotatedLeft, filteredRotatedRight, alpha);
var absolutePitch = rotated.y; // from 1 down to -1 up ... but note: if you rotate down "too far" it starts to go up again...
var absoluteYaw = -rotated.x; // from -1 left to 1 right
var ROTATION_BOUND = 0.6;
var clampYaw = Math.clamp(absoluteYaw, -ROTATION_BOUND, ROTATION_BOUND);
var clampPitch = Math.clamp(absolutePitch, -ROTATION_BOUND, ROTATION_BOUND);
// using only from -ROTATION_BOUND to ROTATION_BOUND
var xRatio = (clampYaw + ROTATION_BOUND) / (2 * ROTATION_BOUND);
var yRatio = (clampPitch + ROTATION_BOUND) / (2 * ROTATION_BOUND);
var x = screenSizeX * xRatio;
var y = screenSizeY * yRatio;
var x = Math.clamp(screenSizeX * (absoluteYaw + 0.5), 0, screenSizeX);
var y = Math.clamp(screenSizeX * absolutePitch, 0, screenSizeY);
// don't move the reticle with the hand controllers unless the controllers are actually being moved
// take a time average of angular velocity, and don't move mouse at all if it's below threshold
var AVERAGING_INTERVAL = 0.95;
var MINIMUM_CONTROLLER_ANGULAR_VELOCITY = 0.03;
var angularVelocityMagnitude = Vec3.length(poseLeft.angularVelocity) * (1.0 - leftRightBias) + Vec3.length(poseRight.angularVelocity) * leftRightBias;
var angularVelocityMagnitude = Vec3.length(poseLeft.angularVelocity) * (1.0 - alpha) + Vec3.length(poseRight.angularVelocity) * alpha;
angularVelocityTrailingAverage = angularVelocityTrailingAverage * AVERAGING_INTERVAL + angularVelocityMagnitude * (1.0 - AVERAGING_INTERVAL);
if (!(xRatio == 0.5 && yRatio == 0) && (angularVelocityTrailingAverage > MINIMUM_CONTROLLER_ANGULAR_VELOCITY) && ((x != lastX) || (y != lastY))) {
if ((angularVelocityTrailingAverage > MINIMUM_CONTROLLER_ANGULAR_VELOCITY) && ((x != lastX) || (y != lastY))) {
moveReticleAbsolute(x, y);
lastX = x;
lastY = y;
@ -121,5 +129,3 @@ Script.update.connect(function(deltaTime) {
Script.scriptEnding.connect(function(){
mapping.disable();
});

View file

@ -0,0 +1,106 @@
//
// Created by Philip Rosedale on March 7, 2016
// Copyright 2016 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
//
// A firefly which is animated by passerbys. It's physical, no gravity, periodic forces applied.
// If a firefly is found to
//
(function () {
var entityID,
timeoutID = null,
properties,
shouldSimulate = false,
ACTIVE_CHECK_INTERVAL = 100, // milliseconds
INACTIVE_CHECK_INTERVAL = 1000, // milliseconds
MAX_DISTANCE_TO_SIMULATE = 20, // meters
LIGHT_LIFETIME = 1400, // milliseconds a firefly light will stay alive
BUMP_SPEED = 1.5, // average velocity given by a bump
BUMP_CHANCE = 0.33,
MIN_SPEED = 0.125, // below this speed, firefly gets a new bump
SPIN_SPEED = 3.5,
BRIGHTNESS = 0.25,
wantDebug = false
function randomVector(size) {
return { x: (Math.random() - 0.5) * size,
y: (Math.random() - 0.5) * size,
z: (Math.random() - 0.5) * size };
}
function printDebug(message) {
if (wantDebug) {
print(message);
}
}
function maybe() {
properties = Entities.getEntityProperties(entityID);
var speed = Vec3.length(properties.velocity);
var distance = Vec3.distance(MyAvatar.position, properties.position);
printDebug("maybe: speed: " + speed + ", distance: " + distance);
if (shouldSimulate) {
// We are simulating this firefly, so do stuff:
if (distance > MAX_DISTANCE_TO_SIMULATE) {
shouldSimulate = false;
} else if ((speed < MIN_SPEED) && (Math.random() < BUMP_CHANCE)) {
bump();
makeLight();
}
} else if (Vec3.length(properties.velocity) == 0.0) {
// We've found a firefly that is not being simulated, so maybe take it over
if (distance < MAX_DISTANCE_TO_SIMULATE) {
shouldSimulate = true;
}
}
timeoutID = Script.setTimeout(maybe, (shouldSimulate == true) ? ACTIVE_CHECK_INTERVAL : INACTIVE_CHECK_INTERVAL);
}
function bump() {
// Give the firefly a little brownian hop
printDebug("bump!");
var velocity = randomVector(BUMP_SPEED);
if (velocity.y < 0.0) { velocity.y *= -1.0 };
Entities.editEntity(entityID, { velocity: velocity,
angularVelocity: randomVector(SPIN_SPEED) });
}
function makeLight() {
printDebug("make light!");
// create a light attached to the firefly that lives for a while
Entities.addEntity({
type: "Light",
name: "firefly light",
intensity: 4.0 * BRIGHTNESS,
falloffRadius: 8.0 * BRIGHTNESS,
dimensions: {
x: 30 * BRIGHTNESS,
y: 30 * BRIGHTNESS,
z: 30 * BRIGHTNESS
},
position: Vec3.sum(properties.position, { x: 0, y: 0.2, z: 0 }),
parentID: entityID,
color: {
red: 150 + Math.random() * 100,
green: 100 + Math.random() * 50,
blue: 150 + Math.random() * 100
},
lifetime: LIGHT_LIFETIME / 1000
});
}
this.preload = function (givenEntityID) {
printDebug("preload firefly...");
entityID = givenEntityID;
timeoutID = Script.setTimeout(maybe, ACTIVE_CHECK_INTERVAL);
};
this.unload = function () {
printDebug("unload firefly...");
if (timeoutID !== undefined) {
Script.clearTimeout(timeoutID);
}
};
})

View file

@ -0,0 +1,77 @@
//
// Created by Philip Rosedale on March 7, 2016
// Copyright 2016 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
//
// Make some fireflies
//
var SIZE = 0.05;
//var ENTITY_URL = "file:///c:/users/dev/philip/examples/fireflies/firefly.js?"+Math.random()
var ENTITY_URL = "https://s3.amazonaws.com/hifi-public/scripts/fireflies/firefly.js"
var RATE_PER_SECOND = 50; // The entity server will drop data if we create things too fast.
var SCRIPT_INTERVAL = 100;
var LIFETIME = 120;
var NUMBER_TO_CREATE = 100;
var GRAVITY = { x: 0, y: -1.0, z: 0 };
var DAMPING = 0.5;
var ANGULAR_DAMPING = 0.5;
var collidable = true;
var gravity = true;
var RANGE = 10;
var HEIGHT = 3;
var HOW_FAR_IN_FRONT_OF_ME = 1.0;
var totalCreated = 0;
var center = Vec3.sum(MyAvatar.position, Vec3.multiply(HOW_FAR_IN_FRONT_OF_ME, Quat.getFront(Camera.orientation)));
function randomVector(range) {
return {
x: (Math.random() - 0.5) * range.x,
y: (Math.random() - 0.5) * range.y,
z: (Math.random() - 0.5) * range.z
}
}
Vec3.print("Center: ", center);
Script.setInterval(function () {
if (!Entities.serversExist() || !Entities.canRez() || (totalCreated > NUMBER_TO_CREATE)) {
return;
}
var numToCreate = RATE_PER_SECOND * (SCRIPT_INTERVAL / 1000.0);
for (var i = 0; (i < numToCreate) && (totalCreated < NUMBER_TO_CREATE); i++) {
var position = Vec3.sum(center, randomVector({ x: RANGE, y: HEIGHT, z: RANGE }));
position.y += HEIGHT / 2.0;
Entities.addEntity({
type: "Box",
name: "firefly",
position: position,
dimensions: { x: SIZE, y: SIZE, z: SIZE },
color: { red: 150 + Math.random() * 100, green: 100 + Math.random() * 50, blue: 0 },
damping: DAMPING,
angularDamping: ANGULAR_DAMPING,
gravity: (gravity ? GRAVITY : { x: 0, y: 0, z: 0}),
dynamic: collidable,
script: ENTITY_URL,
lifetime: LIFETIME
});
totalCreated++;
print("Firefly #" + totalCreated);
}
}, SCRIPT_INTERVAL);

View file

@ -4316,16 +4316,23 @@ SelectionDisplay = (function() {
return false;
};
that.updateHandleSizes = function() {
if (selectionManager.hasSelection()) {
var diff = Vec3.subtract(selectionManager.worldPosition, Camera.getPosition());
var grabberSize = Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO;
var dimensions = SelectionManager.worldDimensions;
var avgDimension = (dimensions.x + dimensions.y + dimensions.z) / 3;
grabberSize = Math.min(grabberSize, avgDimension / 10);
for (var i = 0; i < stretchHandles.length; i++) {
Overlays.editOverlay(stretchHandles[i], {
size: grabberSize,
});
}
var handleSize = Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 10;
var handleSize = Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 7;
handleSize = Math.min(handleSize, avgDimension / 3);
Overlays.editOverlay(yawHandle, {
scale: handleSize,
});
@ -4342,7 +4349,7 @@ SelectionDisplay = (function() {
});
Overlays.editOverlay(grabberMoveUp, {
position: pos,
scale: handleSize / 2,
scale: handleSize / 1.25,
});
}
}

165
examples/tests/mat4test.js Normal file
View file

@ -0,0 +1,165 @@
//
// mat4test.js
// examples/tests
//
// Created by Anthony Thibault on 2016/3/7
// Copyright 2016 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 IDENTITY = {r0c0: 1, r0c1: 0, r0c2: 0, r0c3: 0,
r1c0: 0, r1c1: 1, r1c2: 0, r1c3: 0,
r2c0: 0, r2c1: 0, r2c2: 1, r2c3: 0,
r3c0: 0, r3c1: 0, r3c2: 0, r3c3: 1};
var ROT_ZERO = {x: 0, y: 0, z: 0, w: 1};
var ROT_Y_180 = {x: 0, y: 1, z: 0, w: 0};
var ONE = {x: 1, y: 1, z: 1};
var ZERO = {x: 0, y: 0, z: 0};
var ONE_TWO_THREE = {x: 1, y: 2, z: 3};
var ONE_HALF = {x: 0.5, y: 0.5, z: 0.5};
var EPSILON = 0.000001;
function mat4FuzzyEqual(a, b) {
var r, c;
for (r = 0; r < 4; r++) {
for (c = 0; c < 4; c++) {
if (Math.abs(a["r" + r + "c" + c] - b["r" + r + "c" + c]) > EPSILON) {
return false;
}
}
}
return true;
}
function vec3FuzzyEqual(a, b) {
if (Math.abs(a.x - b.x) > EPSILON ||
Math.abs(a.y - b.y) > EPSILON ||
Math.abs(a.z - b.z) > EPSILON) {
return false;
}
return true;
}
function quatFuzzyEqual(a, b) {
if (Math.abs(a.x - b.x) > EPSILON ||
Math.abs(a.y - b.y) > EPSILON ||
Math.abs(a.z - b.z) > EPSILON ||
Math.abs(a.w - b.w) > EPSILON) {
return false;
}
return true;
}
var failureCount = 0;
var testCount = 0;
function assert(test) {
testCount++;
if (!test) {
print("MAT4 TEST " + testCount + " failed!");
failureCount++;
}
}
function testCreate() {
var test0 = Mat4.createFromScaleRotAndTrans(ONE, {x: 0, y: 0, z: 0, w: 1}, ZERO);
assert(mat4FuzzyEqual(test0, IDENTITY));
var test1 = Mat4.createFromRotAndTrans({x: 0, y: 0, z: 0, w: 1}, ZERO);
assert(mat4FuzzyEqual(test1, IDENTITY));
var test2 = Mat4.createFromRotAndTrans(ROT_Y_180, ONE_TWO_THREE);
assert(mat4FuzzyEqual(test2, {r0c0: -1, r0c1: 0, r0c2: 0, r0c3: 1,
r1c0: 0, r1c1: 1, r1c2: 0, r1c3: 2,
r2c0: 0, r2c1: 0, r2c2: -1, r2c3: 3,
r3c0: 0, r3c1: 0, r3c2: 0, r3c3: 1}));
var test3 = Mat4.createFromScaleRotAndTrans(ONE_HALF, ROT_Y_180, ONE_TWO_THREE);
assert(mat4FuzzyEqual(test3, {r0c0: -0.5, r0c1: 0, r0c2: 0, r0c3: 1,
r1c0: 0, r1c1: 0.5, r1c2: 0, r1c3: 2,
r2c0: 0, r2c1: 0, r2c2: -0.5, r2c3: 3,
r3c0: 0, r3c1: 0, r3c2: 0, r3c3: 1}));
}
function testExtractTranslation() {
var test0 = Mat4.extractTranslation(IDENTITY);
assert(vec3FuzzyEqual(ZERO, test0));
var test1 = Mat4.extractTranslation(Mat4.createFromRotAndTrans(ROT_Y_180, ONE_TWO_THREE));
assert(vec3FuzzyEqual(ONE_TWO_THREE, test1));
}
function testExtractRotation() {
var test0 = Mat4.extractRotation(IDENTITY);
assert(quatFuzzyEqual(ROT_ZERO, test0));
var test1 = Mat4.extractRotation(Mat4.createFromRotAndTrans(ROT_Y_180, ONE_TWO_THREE));
assert(quatFuzzyEqual(ROT_Y_180, test1));
}
function testExtractScale() {
var test0 = Mat4.extractScale(IDENTITY);
assert(vec3FuzzyEqual(ONE, test0));
var test1 = Mat4.extractScale(Mat4.createFromScaleRotAndTrans(ONE_HALF, ROT_Y_180, ONE_TWO_THREE));
assert(vec3FuzzyEqual(ONE_HALF, test1));
var test2 = Mat4.extractScale(Mat4.createFromScaleRotAndTrans(ONE_TWO_THREE, ROT_ZERO, ONE_TWO_THREE));
assert(vec3FuzzyEqual(ONE_TWO_THREE, test2));
}
function testTransformPoint() {
var test0 = Mat4.transformPoint(IDENTITY, ONE);
assert(vec3FuzzyEqual(ONE, test0));
var m = Mat4.createFromScaleRotAndTrans(ONE_HALF, ROT_Y_180, ONE_TWO_THREE);
var test1 = Mat4.transformPoint(m, ONE);
assert(vec3FuzzyEqual({x: 0.5, y: 2.5, z: 2.5}, test1));
}
function testTransformVector() {
var test0 = Mat4.transformVector(IDENTITY, ONE);
assert(vec3FuzzyEqual(ONE, test0));
var m = Mat4.createFromScaleRotAndTrans(ONE_HALF, ROT_Y_180, ONE_TWO_THREE);
var test1 = Mat4.transformVector(m, ONE);
assert(vec3FuzzyEqual({x: -0.5, y: 0.5, z: -0.5}, test1));
}
function testInverse() {
var test0 = IDENTITY;
assert(mat4FuzzyEqual(IDENTITY, Mat4.multiply(test0, Mat4.inverse(test0))));
var test1 = Mat4.createFromScaleRotAndTrans(ONE_HALF, ROT_Y_180, ONE_TWO_THREE);
assert(mat4FuzzyEqual(IDENTITY, Mat4.multiply(test1, Mat4.inverse(test1))));
var test2 = Mat4.extractScale(Mat4.createFromScaleRotAndTrans(ONE_TWO_THREE, ROT_ZERO, ONE_TWO_THREE));
assert(mat4FuzzyEqual(IDENTITY, Mat4.multiply(test2, Mat4.inverse(test2))));
}
function testFront() {
var test0 = IDENTITY;
assert(mat4FuzzyEqual({x: 0, y: 0, z: -1}, Mat4.getFront(test0)));
var test1 = Mat4.createFromScaleRotAndTrans(ONE_HALF, ROT_Y_180, ONE_TWO_THREE);
assert(mat4FuzzyEqual({x: 0, y: 0, z: 1}, Mat4.getFront(test1)));
}
function testMat4() {
testCreate();
testExtractTranslation();
testExtractRotation();
testExtractScale();
testTransformPoint();
testTransformVector();
testInverse();
testFront();
print("MAT4 TEST complete! (" + (testCount - failureCount) + "/" + testCount + ") tests passed!");
}
testMat4();

View file

@ -33,7 +33,8 @@ Window {
HifiConstants { id: hifi }
property var scripts: ScriptDiscoveryService;
property var assetMappingsModel: Assets.proxyModel;
property var assetProxyModel: Assets.proxyModel;
property var assetMappingsModel: Assets.mappingModel;
property var currentDirectory;
property alias currentFileUrl: fileUrlTextField.text;
@ -44,11 +45,22 @@ Window {
property alias directory: root.currentDirectory
}
Component.onCompleted: {
assetMappingsModel.errorGettingMappings.connect(handleGetMappingsError)
reload()
}
function doDeleteFile(path) {
console.log("Deleting " + path);
Assets.deleteMappings(path, function(err) {
print("Finished deleting path: ", path, err);
if (err) {
console.log("Error deleting path: ", path, err);
errorMessage("There was an error deleting:\n" + path + "\n\nPlease try again.");
} else {
console.log("Finished deleting path: ", path);
}
reload();
});
@ -62,7 +74,13 @@ Window {
console.log("Renaming " + oldPath + " to " + newPath);
Assets.renameMapping(oldPath, newPath, function(err) {
print("Finished rename: ", err);
if (err) {
console.log("Error renaming: ", oldPath, "=>", newPath, " - error ", err);
errorMessage("There was an error renaming:\n" + oldPath + " to " + newPath + "\n\nPlease try again.");
} else {
console.log("Finished rename: ", oldPath, "=>", newPath);
}
reload();
});
}
@ -89,7 +107,7 @@ Window {
function canAddToWorld() {
var supportedExtensions = [/\.fbx\b/i, /\.obj\b/i];
var path = assetMappingsModel.data(treeView.currentIndex, 0x100);
var path = assetProxyModel.data(treeView.currentIndex, 0x100);
return supportedExtensions.reduce(function(total, current) {
return total | new RegExp(current).test(path);
@ -100,6 +118,12 @@ Window {
print("reload");
Assets.mappingModel.refresh();
}
function handleGetMappingsError() {
errorMessage("There was a problem retreiving the list of assets from your Asset Server.\n"
+ "Please make sure you are connected to the Asset Server and try again. ");
}
function addToWorld() {
var url = assetMappingsModel.data(treeView.currentIndex, 0x102);
if (!url) {
@ -113,7 +137,7 @@ Window {
if (!index) {
index = treeView.currentIndex;
}
var path = assetMappingsModel.data(index, 0x103);
var path = assetProxyModel.data(index, 0x103);
if (!path) {
return;
}
@ -124,7 +148,7 @@ Window {
if (!index) {
index = treeView.currentIndex;
}
var path = assetMappingsModel.data(index, 0x100);
var path = assetProxyModel.data(index, 0x100);
if (!path) {
return;
}
@ -151,12 +175,12 @@ Window {
if (!index) {
index = treeView.currentIndex;
}
var path = assetMappingsModel.data(index, 0x100);
var path = assetProxyModel.data(index, 0x100);
if (!path) {
return;
}
var isFolder = assetMappingsModel.data(treeView.currentIndex, 0x101);
var isFolder = assetProxyModel.data(treeView.currentIndex, 0x101);
var typeString = isFolder ? 'folder' : 'file';
var object = desktop.messageBox({
@ -190,7 +214,7 @@ Window {
var fileUrl = fileUrlTextField.text
var addToWorld = addToWorldCheckBox.checked
var path = assetMappingsModel.data(treeView.currentIndex, 0x100);
var path = assetProxyModel.data(treeView.currentIndex, 0x100);
var directory = path ? path.slice(0, path.lastIndexOf('/') + 1) : "";
var filename = fileUrl.slice(fileUrl.lastIndexOf('/') + 1);
@ -210,6 +234,15 @@ Window {
});
}
function errorMessage(message) {
desktop.messageBox({
icon: OriginalDialogs.StandardIcon.Error,
buttons: OriginalDialogs.StandardButton.Ok,
text: "Error",
informativeText: message
});
}
Column {
width: pane.contentWidth
@ -301,7 +334,7 @@ Window {
HifiControls.Tree {
id: treeView
height: 400
treeModel: assetMappingsModel
treeModel: assetProxyModel
colorScheme: root.colorScheme
anchors.left: parent.left
anchors.right: parent.right

View file

@ -115,7 +115,7 @@ Item {
color: root.fontColor
font.pixelSize: root.fontSize
visible: root.expanded;
text: "Voxel max ping: " + 0
text: "Messages max ping: " + root.messagePing
}
}
}

View file

@ -239,17 +239,29 @@ class DeadlockWatchdogThread : public QThread {
public:
static const unsigned long HEARTBEAT_CHECK_INTERVAL_SECS = 1;
static const unsigned long HEARTBEAT_UPDATE_INTERVAL_SECS = 1;
#ifdef DEBUG
static const unsigned long MAX_HEARTBEAT_AGE_USECS = 600 * USECS_PER_SECOND;
#else
static const unsigned long MAX_HEARTBEAT_AGE_USECS = 10 * USECS_PER_SECOND;
#endif
// Set the heartbeat on launch
DeadlockWatchdogThread() {
setObjectName("Deadlock Watchdog");
QTimer* heartbeatTimer = new QTimer();
// Give the heartbeat an initial value
_heartbeat = usecTimestampNow();
updateHeartbeat();
connect(heartbeatTimer, &QTimer::timeout, [this] {
_heartbeat = usecTimestampNow();
updateHeartbeat();
});
heartbeatTimer->start(HEARTBEAT_UPDATE_INTERVAL_SECS * MSECS_PER_SECOND);
connect(qApp, &QCoreApplication::aboutToQuit, [this] {
_quit = true;
});
}
void updateHeartbeat() {
_heartbeat = usecTimestampNow();
}
void deadlockDetectionCrash() {
@ -258,7 +270,7 @@ public:
}
void run() override {
while (!qApp->isAboutToQuit()) {
while (!_quit) {
QThread::sleep(HEARTBEAT_UPDATE_INTERVAL_SECS);
auto now = usecTimestampNow();
auto lastHeartbeatAge = now - _heartbeat;
@ -269,6 +281,7 @@ public:
}
static std::atomic<uint64_t> _heartbeat;
bool _quit { false };
};
std::atomic<uint64_t> DeadlockWatchdogThread::_heartbeat;
@ -497,7 +510,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
auto nodeList = DependencyManager::get<NodeList>();
// Set up a watchdog thread to intentionally crash the application on deadlocks
(new DeadlockWatchdogThread())->start();
auto deadlockWatchdog = new DeadlockWatchdogThread();
deadlockWatchdog->start();
qCDebug(interfaceapp) << "[VERSION] Build sequence:" << qPrintable(applicationVersion());
@ -531,7 +545,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
audioThread->setObjectName("Audio Thread");
auto audioIO = DependencyManager::get<AudioClient>();
audioIO->setPositionGetter([this]{ return getMyAvatar()->getPositionForAudio(); });
audioIO->setOrientationGetter([this]{ return getMyAvatar()->getOrientationForAudio(); });
@ -549,7 +562,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
});
auto& audioScriptingInterface = AudioScriptingInterface::getInstance();
connect(audioThread, &QThread::started, audioIO.data(), &AudioClient::start);
connect(audioIO.data(), &AudioClient::destroyed, audioThread, &QThread::quit);
connect(audioThread, &QThread::finished, audioThread, &QThread::deleteLater);
@ -572,6 +584,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
audioThread->start();
ResourceManager::init();
// Make sure we don't time out during slow operations at startup
deadlockWatchdog->updateHeartbeat();
// Setup MessagesClient
auto messagesClient = DependencyManager::get<MessagesClient>();
@ -626,6 +640,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
// set the account manager's root URL and trigger a login request if we don't have the access token
accountManager.setIsAgent(true);
accountManager.setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL);
UserActivityLogger::getInstance().launch(applicationVersion());
// once the event loop has started, check and signal for an access token
@ -715,8 +730,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
_offscreenContext->create(_glWidget->context()->contextHandle());
_offscreenContext->makeCurrent();
initializeGL();
_offscreenContext->makeCurrent();
// Make sure we don't time out during slow operations at startup
deadlockWatchdog->updateHeartbeat();
// Tell our entity edit sender about our known jurisdictions
_entityEditSender.setServerJurisdictions(&_entityServerJurisdictions);
@ -727,6 +743,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
_entityEditSender.setPacketsPerSecond(3000); // super high!!
_overlays.init(); // do this before scripts load
// Make sure we don't time out during slow operations at startup
deadlockWatchdog->updateHeartbeat();
connect(this, SIGNAL(aboutToQuit()), this, SLOT(aboutToQuit()));
@ -878,8 +896,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
scriptEngines->setScriptsLocation(scriptEngines->getScriptsLocation());
// do this as late as possible so that all required subsystems are initialized
scriptEngines->loadScripts();
// Make sure we don't time out during slow operations at startup
deadlockWatchdog->updateHeartbeat();
loadSettings();
// Make sure we don't time out during slow operations at startup
deadlockWatchdog->updateHeartbeat();
int SAVE_SETTINGS_INTERVAL = 10 * MSECS_PER_SECOND; // Let's save every seconds for now
connect(&_settingsTimer, &QTimer::timeout, this, &Application::saveSettings);
connect(&_settingsThread, SIGNAL(started()), &_settingsTimer, SLOT(start()));
@ -992,6 +1015,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
}
});
// Make sure we don't time out during slow operations at startup
deadlockWatchdog->updateHeartbeat();
connect(this, &Application::applicationStateChanged, this, &Application::activeChanged);
qCDebug(interfaceapp, "Startup time: %4.2f seconds.", (double)startupTimer.elapsed() / 1000.0);
@ -1119,8 +1145,6 @@ Application::~Application() {
_octreeProcessor.terminate();
_entityEditSender.terminate();
Menu::getInstance()->deleteLater();
_physicsEngine->setCharacterController(NULL);
ModelEntityItem::cleanupLoadedAnimations();
@ -1165,6 +1189,10 @@ Application::~Application() {
#if 0
ConnexionClient::getInstance().destroy();
#endif
// The window takes ownership of the menu, so this has the side effect of destroying it.
_window->setMenuBar(nullptr);
_window->deleteLater();
qInstallMessageHandler(NULL); // NOTE: Do this as late as possible so we continue to get our log messages
}
@ -1332,7 +1360,7 @@ void Application::initializeUi() {
_keyboardMouseDevice = std::dynamic_pointer_cast<KeyboardMouseDevice>(inputPlugin);
}
}
Menu::setInstance();
_window->setMenuBar(new Menu());
updateInputModes();
auto compositorHelper = DependencyManager::get<CompositorHelper>();

View file

@ -45,15 +45,8 @@
#include "Menu.h"
// Fixme make static member of Menu
static const char* const MENU_PROPERTY_NAME = "com.highfidelity.Menu";
void Menu::setInstance() {
globalInstance<Menu>(MENU_PROPERTY_NAME);
}
Menu* Menu::getInstance() {
return static_cast<Menu*>(ui::Menu::getInstance());
return static_cast<Menu*>(qApp->getWindow()->menuBar());
}
Menu::Menu() {

View file

@ -20,7 +20,6 @@ class Menu : public ui::Menu {
Q_OBJECT
public:
static void setInstance();
static Menu* getInstance();
Menu();
Q_INVOKABLE void addMenuItem(const MenuItemProperties& properties);

View file

@ -23,124 +23,16 @@ PluginContainerProxy::PluginContainerProxy() {
PluginContainerProxy::~PluginContainerProxy() {
}
ui::Menu* PluginContainerProxy::getPrimaryMenu() {
auto appMenu = qApp->_window->menuBar();
auto uiMenu = dynamic_cast<ui::Menu*>(appMenu);
return uiMenu;
}
bool PluginContainerProxy::isForeground() {
return qApp->isForeground() && !qApp->getWindow()->isMinimized();
}
void PluginContainerProxy::addMenu(const QString& menuName) {
Menu::getInstance()->addMenu(menuName);
}
void PluginContainerProxy::removeMenu(const QString& menuName) {
Menu::getInstance()->removeMenu(menuName);
}
QAction* PluginContainerProxy::addMenuItem(PluginType type, const QString& path, const QString& name, std::function<void(bool)> onClicked, bool checkable, bool checked, const QString& groupName) {
auto menu = Menu::getInstance();
MenuWrapper* parentItem = menu->getMenu(path);
QAction* action = menu->addActionToQMenuAndActionHash(parentItem, name);
if (!groupName.isEmpty()) {
QActionGroup* group{ nullptr };
if (!_exclusiveGroups.count(groupName)) {
group = _exclusiveGroups[groupName] = new QActionGroup(menu);
group->setExclusive(true);
} else {
group = _exclusiveGroups[groupName];
}
group->addAction(action);
}
connect(action, &QAction::triggered, [=] {
onClicked(action->isChecked());
});
action->setCheckable(checkable);
action->setChecked(checked);
if (type == PluginType::DISPLAY_PLUGIN) {
_currentDisplayPluginActions.push_back({ path, name });
} else {
_currentInputPluginActions.push_back({ path, name });
}
return action;
}
void PluginContainerProxy::removeMenuItem(const QString& menuName, const QString& menuItem) {
Menu::getInstance()->removeMenuItem(menuName, menuItem);
}
bool PluginContainerProxy::isOptionChecked(const QString& name) {
return Menu::getInstance()->isOptionChecked(name);
}
void PluginContainerProxy::setIsOptionChecked(const QString& path, bool checked) {
Menu::getInstance()->setIsOptionChecked(path, checked);
}
// FIXME there is a bug in the fullscreen setting, where leaving
// fullscreen does not restore the window frame, making it difficult
// or impossible to move or size the window.
// Additionally, setting fullscreen isn't hiding the menu on windows
// make it useless for stereoscopic modes.
void PluginContainerProxy::setFullscreen(const QScreen* target, bool hideMenu) {
auto _window = qApp->getWindow();
if (!_window->isFullScreen()) {
_savedGeometry = _window->geometry();
}
if (nullptr == target) {
// FIXME target the screen where the window currently is
target = qApp->primaryScreen();
}
_window->setGeometry(target->availableGeometry());
_window->windowHandle()->setScreen((QScreen*)target);
_window->showFullScreen();
#ifndef Q_OS_MAC
// also hide the QMainWindow's menuBar
QMenuBar* menuBar = _window->menuBar();
if (menuBar && hideMenu) {
menuBar->setVisible(false);
}
#endif
}
void PluginContainerProxy::unsetFullscreen(const QScreen* avoid) {
auto _window = qApp->getWindow();
_window->showNormal();
QRect targetGeometry = _savedGeometry;
if (avoid != nullptr) {
QRect avoidGeometry = avoid->geometry();
if (avoidGeometry.contains(targetGeometry.topLeft())) {
QScreen* newTarget = qApp->primaryScreen();
if (newTarget == avoid) {
foreach(auto screen, qApp->screens()) {
if (screen != avoid) {
newTarget = screen;
break;
}
}
}
targetGeometry = newTarget->availableGeometry();
}
}
#ifdef Q_OS_MAC
QTimer* timer = new QTimer();
timer->singleShot(2000, [=] {
_window->setGeometry(targetGeometry);
timer->deleteLater();
});
#else
_window->setGeometry(targetGeometry);
#endif
#ifndef Q_OS_MAC
// also show the QMainWindow's menuBar
QMenuBar* menuBar = _window->menuBar();
if (menuBar) {
menuBar->setVisible(true);
}
#endif
}
void PluginContainerProxy::requestReset() {
// We could signal qApp to sequence this, but it turns out that requestReset is only used from within the main thread anyway.
qApp->resetSensors(true);
@ -154,8 +46,8 @@ GLWidget* PluginContainerProxy::getPrimaryWidget() {
return qApp->_glWidget;
}
QWindow* PluginContainerProxy::getPrimaryWindow() {
return qApp->_glWidget->windowHandle();
MainWindow* PluginContainerProxy::getPrimaryWindow() {
return qApp->getWindow();
}
QOpenGLContext* PluginContainerProxy::getPrimaryContext() {
@ -184,13 +76,3 @@ void PluginContainerProxy::releaseOverlayTexture(const gpu::TexturePointer& text
qApp->_applicationOverlay.releaseOverlay(texture);
}
/// settings interface
bool PluginContainerProxy::getBoolSetting(const QString& settingName, bool defaultValue) {
Setting::Handle<bool> settingValue(settingName, defaultValue);
return settingValue.get();
}
void PluginContainerProxy::setBoolSetting(const QString& settingName, bool value) {
Setting::Handle<bool> settingValue(settingName, value);
return settingValue.set(value);
}

View file

@ -14,34 +14,20 @@ class PluginContainerProxy : public QObject, PluginContainer {
Q_OBJECT
PluginContainerProxy();
virtual ~PluginContainerProxy();
virtual void addMenu(const QString& menuName) override;
virtual void removeMenu(const QString& menuName) override;
virtual QAction* addMenuItem(PluginType type, const QString& path, const QString& name, std::function<void(bool)> onClicked, bool checkable = false, bool checked = false, const QString& groupName = "") override;
virtual void removeMenuItem(const QString& menuName, const QString& menuItem) override;
virtual bool isOptionChecked(const QString& name) override;
virtual void setIsOptionChecked(const QString& path, bool checked) override;
virtual void setFullscreen(const QScreen* targetScreen, bool hideMenu = true) override;
virtual void unsetFullscreen(const QScreen* avoidScreen = nullptr) override;
virtual void showDisplayPluginsTools() override;
virtual void requestReset() override;
virtual bool makeRenderingContextCurrent() override;
virtual void releaseSceneTexture(const gpu::TexturePointer& texture) override;
virtual void releaseOverlayTexture(const gpu::TexturePointer& texture) override;
virtual GLWidget* getPrimaryWidget() override;
virtual QWindow* getPrimaryWindow() override;
virtual MainWindow* getPrimaryWindow() override;
virtual ui::Menu* getPrimaryMenu() override;
virtual QOpenGLContext* getPrimaryContext() override;
virtual bool isForeground() override;
virtual const DisplayPlugin* getActiveDisplayPlugin() const override;
/// settings interface
virtual bool getBoolSetting(const QString& settingName, bool defaultValue) override;
virtual void setBoolSetting(const QString& settingName, bool value) override;
QRect _savedGeometry{ 10, 120, 800, 600 };
std::map<QString, QActionGroup*> _exclusiveGroups;
friend class Application;
};
#endif
#endif

View file

@ -143,25 +143,17 @@ void ATPAssetMigrator::migrateResource(ResourceRequest* request) {
QFileInfo assetInfo { request->getUrl().fileName() };
auto upload = assetClient->createUpload(request->getData());
if (upload) {
// add this URL to our hash of AssetUpload to original URL
_originalURLs.insert(upload, request->getUrl());
qCDebug(asset_migrator) << "Starting upload of asset from" << request->getUrl();
// connect to the finished signal so we know when the AssetUpload is done
QObject::connect(upload, &AssetUpload::finished, this, &ATPAssetMigrator::assetUploadFinished);
// start the upload now
upload->start();
} else {
// show a QMessageBox to say that there is no local asset server
QString messageBoxText = QString("Could not upload \n\n%1\n\nbecause you are currently not connected" \
" to a local asset-server.").arg(assetInfo.fileName());
QMessageBox::information(_dialogParent, "Failed to Upload", messageBoxText);
}
// add this URL to our hash of AssetUpload to original URL
_originalURLs.insert(upload, request->getUrl());
qCDebug(asset_migrator) << "Starting upload of asset from" << request->getUrl();
// connect to the finished signal so we know when the AssetUpload is done
QObject::connect(upload, &AssetUpload::finished, this, &ATPAssetMigrator::assetUploadFinished);
// start the upload now
upload->start();
}
void ATPAssetMigrator::assetUploadFinished(AssetUpload *upload, const QString& hash) {

View file

@ -540,6 +540,7 @@ glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const {
}
void Avatar::fixupModelsInScene() {
_attachmentsToDelete.clear();
// check to see if when we added our models to the scene they were ready, if they were not ready, then
// fix them up in the scene
@ -560,9 +561,11 @@ void Avatar::fixupModelsInScene() {
attachmentModel->addToScene(scene, pendingChanges);
}
}
for (auto& attachmentModelToRemove : _attachmentsToRemove) {
attachmentModelToRemove->removeFromScene(scene, pendingChanges);
}
_attachmentsToDelete.insert(_attachmentsToDelete.end(), _attachmentsToRemove.begin(), _attachmentsToRemove.end());
_attachmentsToRemove.clear();
scene->enqueuePendingChanges(pendingChanges);
}

View file

@ -193,6 +193,7 @@ protected:
glm::vec3 _skeletonOffset;
std::vector<std::shared_ptr<Model>> _attachmentModels;
std::vector<std::shared_ptr<Model>> _attachmentsToRemove;
std::vector<std::shared_ptr<Model>> _attachmentsToDelete;
float _bodyYawDelta; // degrees/sec

View file

@ -416,8 +416,9 @@ void MyAvatar::simulate(float deltaTime) {
}
}
// thread-safe
glm::mat4 MyAvatar::getSensorToWorldMatrix() const {
return _sensorToWorldMatrix;
return _sensorToWorldMatrixCache.get();
}
// Pass a recent sample of the HMD to the avatar.
@ -442,6 +443,8 @@ void MyAvatar::updateSensorToWorldMatrix() {
_sensorToWorldMatrix = desiredMat * glm::inverse(_bodySensorMatrix);
lateUpdatePalms();
_sensorToWorldMatrixCache.set(_sensorToWorldMatrix);
}
// Update avatar head rotation with sensor data

View file

@ -22,7 +22,7 @@
#include "Avatar.h"
#include "AtRestDetector.h"
#include "MyCharacterController.h"
#include <ThreadSafeValueCache.h>
class ModelItemID;
@ -78,6 +78,9 @@ class MyAvatar : public Avatar {
Q_PROPERTY(controller::Pose rightHandPose READ getRightHandPose)
Q_PROPERTY(controller::Pose leftHandTipPose READ getLeftHandTipPose)
Q_PROPERTY(controller::Pose rightHandTipPose READ getRightHandTipPose)
Q_PROPERTY(glm::mat4 sensorToWorldMatrix READ getSensorToWorldMatrix)
Q_PROPERTY(float energy READ getEnergy WRITE setEnergy)
public:
@ -98,8 +101,9 @@ public:
const glm::vec3& getHMDSensorPosition() const { return _hmdSensorPosition; }
const glm::quat& getHMDSensorOrientation() const { return _hmdSensorOrientation; }
const glm::vec2& getHMDSensorFacingMovingAverage() const { return _hmdSensorFacingMovingAverage; }
glm::mat4 getSensorToWorldMatrix() const;
// thread safe
Q_INVOKABLE glm::mat4 getSensorToWorldMatrix() const;
// Pass a recent sample of the HMD to the avatar.
// This can also update the avatar's position to follow the HMD
@ -390,6 +394,7 @@ private:
// used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers.
glm::mat4 _sensorToWorldMatrix;
ThreadSafeValueCache<glm::mat4> _sensorToWorldMatrixCache { glm::mat4() };
struct FollowHelper {
FollowHelper();

View file

@ -44,20 +44,12 @@ void AssetUploadDialogFactory::showDialog() {
auto assetClient = DependencyManager::get<AssetClient>();
auto upload = assetClient->createUpload(filename);
if (upload) {
// connect to the finished signal so we know when the AssetUpload is done
QObject::connect(upload, &AssetUpload::finished, this, &AssetUploadDialogFactory::handleUploadFinished);
// start the upload now
upload->start();
} else {
// show a QMessageBox to say that there is no local asset server
QString messageBoxText = QString("Could not upload \n\n%1\n\nbecause you are currently not connected" \
" to a local asset-server.").arg(QFileInfo(filename).fileName());
QMessageBox::information(_dialogParent, "Failed to Upload", messageBoxText);
}
// connect to the finished signal so we know when the AssetUpload is done
QObject::connect(upload, &AssetUpload::finished, this, &AssetUploadDialogFactory::handleUploadFinished);
// start the upload now
upload->start();
}
} else {
// we don't have permission to upload to asset server in this domain - show the permission denied error

View file

@ -127,7 +127,7 @@ void LogDialog::resizeEvent(QResizeEvent*) {
void LogDialog::appendLogLine(QString logLine) {
if (isVisible()) {
if (logLine.contains(_searchTerm, Qt::CaseInsensitive)) {
_logTextBox->appendPlainText(logLine.simplified());
_logTextBox->appendPlainText(logLine.trimmed());
}
}
}

View file

@ -136,10 +136,12 @@ void Stats::updateStats(bool force) {
SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer);
SharedNodePointer avatarMixerNode = nodeList->soloNodeOfType(NodeType::AvatarMixer);
SharedNodePointer assetServerNode = nodeList->soloNodeOfType(NodeType::AssetServer);
SharedNodePointer messageMixerNode = nodeList->soloNodeOfType(NodeType::MessagesMixer);
STAT_UPDATE(audioPing, audioMixerNode ? audioMixerNode->getPingMs() : -1);
STAT_UPDATE(avatarPing, avatarMixerNode ? avatarMixerNode->getPingMs() : -1);
STAT_UPDATE(assetPing, assetServerNode ? assetServerNode->getPingMs() : -1);
STAT_UPDATE(messagePing, messageMixerNode ? messageMixerNode->getPingMs() : -1);
//// Now handle entity servers, since there could be more than one, we average their ping times
int totalPingOctree = 0;
int octreeServerCount = 0;

View file

@ -45,7 +45,8 @@ class Stats : public QQuickItem {
STATS_PROPERTY(int, avatarPing, 0)
STATS_PROPERTY(int, entitiesPing, 0)
STATS_PROPERTY(int, assetPing, 0)
STATS_PROPERTY(QVector3D, position, QVector3D(0, 0, 0) )
STATS_PROPERTY(int, messagePing, 0)
STATS_PROPERTY(QVector3D, position, QVector3D(0, 0, 0))
STATS_PROPERTY(float, speed, 0)
STATS_PROPERTY(float, yaw, 0)
STATS_PROPERTY(int, avatarMixerInKbps, 0)
@ -123,6 +124,7 @@ signals:
void avatarPingChanged();
void entitiesPingChanged();
void assetPingChanged();
void messagePingChanged();
void positionChanged();
void speedChanged();
void yawChanged();

View file

@ -132,6 +132,7 @@ void Animation::animationParseSuccess(FBXGeometry* geometry) {
void Animation::animationParseError(int error, QString str) {
qCCritical(animation) << "Animation failure parsing " << _url.toDisplayString() << "code =" << error << str;
emit failed(QNetworkReply::UnknownContentError);
finishedLoading(false);
}
AnimationDetails::AnimationDetails() :

View file

@ -80,6 +80,8 @@ void Sound::downloadFinished(const QByteArray& data) {
qCDebug(audio) << "Unknown sound file type";
}
finishedLoading(true);
_isReady = true;
emit ready();
}

View file

@ -126,10 +126,10 @@ namespace controller {
QVariantMap _actions;
QVariantMap _standard;
bool _mouseCaptured{ false };
bool _touchCaptured{ false };
bool _wheelCaptured{ false };
bool _actionsCaptured{ false };
std::atomic<bool> _mouseCaptured{ false };
std::atomic<bool> _touchCaptured { false };
std::atomic<bool> _wheelCaptured { false };
std::atomic<bool> _actionsCaptured { false };
};

View file

@ -286,32 +286,39 @@ glm::vec2 CompositorHelper::getReticleMaximumPosition() const {
return result;
}
void CompositorHelper::sendFakeMouseEvent() {
if (qApp->thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(this, "sendFakeMouseEvent", Qt::BlockingQueuedConnection);
return;
}
// in HMD mode we need to fake our mouse moves...
QPoint globalPos(_reticlePositionInHMD.x, _reticlePositionInHMD.y);
auto button = Qt::NoButton;
auto buttons = QApplication::mouseButtons();
auto modifiers = QApplication::keyboardModifiers();
static auto renderingWidget = PluginContainer::getInstance().getPrimaryWidget();
QMouseEvent event(QEvent::MouseMove, globalPos, button, buttons, modifiers);
_fakeMouseEvent = true;
qApp->sendEvent(renderingWidget, &event);
_fakeMouseEvent = false;
}
void CompositorHelper::setReticlePosition(const glm::vec2& position, bool sendFakeEvent) {
if (isHMD()) {
QMutexLocker locker(&_reticleLock);
glm::vec2 maxOverlayPosition = _currentDisplayPlugin->getRecommendedUiSize();
// FIXME don't allow negative mouseExtra
glm::vec2 mouseExtra = (MOUSE_EXTENTS_PIXELS - maxOverlayPosition) / 2.0f;
glm::vec2 minMouse = vec2(0) - mouseExtra;
glm::vec2 maxMouse = maxOverlayPosition + mouseExtra;
_reticlePositionInHMD = glm::clamp(position, minMouse, maxMouse);
{
QMutexLocker locker(&_reticleLock);
_reticlePositionInHMD = glm::clamp(position, minMouse, maxMouse);
}
if (sendFakeEvent) {
// in HMD mode we need to fake our mouse moves...
QPoint globalPos(_reticlePositionInHMD.x, _reticlePositionInHMD.y);
auto button = Qt::NoButton;
auto buttons = QApplication::mouseButtons();
auto modifiers = QApplication::keyboardModifiers();
static auto renderingWidget = PluginContainer::getInstance().getPrimaryWidget();
if (qApp->thread() == QThread::currentThread()) {
QMouseEvent event(QEvent::MouseMove, globalPos, button, buttons, modifiers);
_fakeMouseEvent = true;
qApp->sendEvent(renderingWidget, &event);
_fakeMouseEvent = false;
} else {
qApp->postEvent(renderingWidget, new QMouseEvent(QEvent::MouseMove, globalPos, button, buttons, modifiers));
}
sendFakeMouseEvent();
}
} else {
// NOTE: This is some debugging code we will leave in while debugging various reticle movement strategies,

View file

@ -116,6 +116,9 @@ public:
signals:
void allowMouseCaptureChanged();
protected slots:
void sendFakeMouseEvent();
private:
glm::mat4 getUiTransform() const;
void updateTooltips();

View file

@ -368,7 +368,8 @@ public:
void getAllTerseUpdateProperties(EntityItemProperties& properties) const;
void flagForOwnership() { _dirtyFlags |= Simulation::DIRTY_SIMULATOR_OWNERSHIP; }
void pokeSimulationOwnership() { _dirtyFlags |= Simulation::DIRTY_SIMULATION_OWNERSHIP_FOR_POKE; }
void grabSimulationOwnership() { _dirtyFlags |= Simulation::DIRTY_SIMULATION_OWNERSHIP_FOR_GRAB; }
void flagForMotionStateChange() { _dirtyFlags |= Simulation::DIRTY_MOTION_TYPE; }
bool addAction(EntitySimulation* simulation, EntityActionPointer action);

View file

@ -157,8 +157,8 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties
const QUuid myNodeID = nodeList->getSessionUUID();
// and make note of it now, so we can act on it right away.
propertiesWithSimID.setSimulationOwner(myNodeID, SCRIPT_EDIT_SIMULATION_PRIORITY);
entity->setSimulationOwner(myNodeID, SCRIPT_EDIT_SIMULATION_PRIORITY);
propertiesWithSimID.setSimulationOwner(myNodeID, SCRIPT_POKE_SIMULATION_PRIORITY);
entity->setSimulationOwner(myNodeID, SCRIPT_POKE_SIMULATION_PRIORITY);
}
entity->setLastBroadcast(usecTimestampNow());
@ -329,20 +329,19 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
// TODO: if we knew that ONLY TerseUpdate properties have changed in properties AND the object
// is dynamic AND it is active in the physics simulation then we could chose to NOT queue an update
// and instead let the physics simulation decide when to send a terse update. This would remove
// the "slide-no-rotate" glitch (and typical a double-update) that we see during the "poke rolling
// the "slide-no-rotate" glitch (and typical double-update) that we see during the "poke rolling
// balls" test. However, even if we solve this problem we still need to provide a "slerp the visible
// proxy toward the true physical position" feature to hide the final glitches in the remote watcher's
// simulation.
if (entity->getSimulationPriority() < SCRIPT_EDIT_SIMULATION_PRIORITY) {
if (entity->getSimulationPriority() < SCRIPT_POKE_SIMULATION_PRIORITY) {
// we re-assert our simulation ownership at a higher priority
properties.setSimulationOwner(myNodeID,
glm::max(entity->getSimulationPriority(), SCRIPT_EDIT_SIMULATION_PRIORITY));
properties.setSimulationOwner(myNodeID, SCRIPT_POKE_SIMULATION_PRIORITY);
}
} else {
// we make a bid for simulation ownership
properties.setSimulationOwner(myNodeID, SCRIPT_EDIT_SIMULATION_PRIORITY);
entity->flagForOwnership();
properties.setSimulationOwner(myNodeID, SCRIPT_POKE_SIMULATION_PRIORITY);
entity->pokeSimulationOwnership();
}
}
if (properties.parentRelatedPropertyChanged() && entity->computePuffedQueryAACube()) {
@ -814,11 +813,7 @@ QUuid EntityScriptingInterface::addAction(const QString& actionTypeString,
return false;
}
success = entity->addAction(simulation, action);
auto nodeList = DependencyManager::get<NodeList>();
const QUuid myNodeID = nodeList->getSessionUUID();
if (entity->getSimulatorID() != myNodeID) {
entity->flagForOwnership();
}
entity->grabSimulationOwnership();
return false; // Physics will cause a packet to be sent, so don't send from here.
});
if (success) {
@ -832,11 +827,7 @@ bool EntityScriptingInterface::updateAction(const QUuid& entityID, const QUuid&
return actionWorker(entityID, [&](EntitySimulation* simulation, EntityItemPointer entity) {
bool success = entity->updateAction(simulation, actionID, arguments);
if (success) {
auto nodeList = DependencyManager::get<NodeList>();
const QUuid myNodeID = nodeList->getSessionUUID();
if (entity->getSimulatorID() != myNodeID) {
entity->flagForOwnership();
}
entity->grabSimulationOwnership();
}
return success;
});
@ -846,6 +837,10 @@ bool EntityScriptingInterface::deleteAction(const QUuid& entityID, const QUuid&
bool success = false;
actionWorker(entityID, [&](EntitySimulation* simulation, EntityItemPointer entity) {
success = entity->removeAction(simulation, actionID);
if (success) {
// reduce from grab to poke
entity->pokeSimulationOwnership();
}
return false; // Physics will cause a packet to be sent, so don't send from here.
});
return success;

View file

@ -114,8 +114,8 @@ public:
// use this method if you have a pointer to the entity (avoid an extra entity lookup)
bool updateEntity(EntityItemPointer entity, const EntityItemProperties& properties, const SharedNodePointer& senderNode = SharedNodePointer(nullptr));
void deleteEntity(const EntityItemID& entityID, bool force = false, bool ignoreWarnings = false);
void deleteEntities(QSet<EntityItemID> entityIDs, bool force = false, bool ignoreWarnings = false);
void deleteEntity(const EntityItemID& entityID, bool force = false, bool ignoreWarnings = true);
void deleteEntities(QSet<EntityItemID> entityIDs, bool force = false, bool ignoreWarnings = true);
/// \param position point of query in world-frame (meters)
/// \param targetRadius radius of query (meters)

View file

@ -25,10 +25,13 @@ namespace Simulation {
const uint32_t DIRTY_UPDATEABLE = 0x0200;
const uint32_t DIRTY_MATERIAL = 0x00400;
const uint32_t DIRTY_PHYSICS_ACTIVATION = 0x0800; // should activate object in physics engine
const uint32_t DIRTY_SIMULATOR_OWNERSHIP = 0x1000; // should claim simulator ownership
const uint32_t DIRTY_SIMULATOR_ID = 0x2000; // the simulatorID has changed
const uint32_t DIRTY_SIMULATOR_ID = 0x1000; // the simulatorID has changed
const uint32_t DIRTY_SIMULATION_OWNERSHIP_FOR_POKE = 0x2000; // bid for simulation ownership at "poke"
const uint32_t DIRTY_SIMULATION_OWNERSHIP_FOR_GRAB = 0x4000; // bid for simulation ownership at "grab"
const uint32_t DIRTY_TRANSFORM = DIRTY_POSITION | DIRTY_ROTATION;
const uint32_t DIRTY_VELOCITIES = DIRTY_LINEAR_VELOCITY | DIRTY_ANGULAR_VELOCITY;
const uint32_t DIRTY_SIMULATION_OWNERSHIP_PRIORITY = DIRTY_SIMULATION_OWNERSHIP_FOR_POKE | DIRTY_SIMULATION_OWNERSHIP_FOR_GRAB;
};
#endif // hifi_SimulationFlags_h

View file

@ -70,13 +70,15 @@ bool SimulationOwner::setID(const QUuid& id) {
}
bool SimulationOwner::set(const QUuid& id, quint8 priority) {
uint8_t oldPriority = _priority;
setPriority(priority);
return setID(id);
return setID(id) || oldPriority != _priority;
}
bool SimulationOwner::set(const SimulationOwner& owner) {
uint8_t oldPriority = _priority;
setPriority(owner._priority);
return setID(owner._id);
return setID(owner._id) || oldPriority != _priority;
}
void SimulationOwner::updateExpiry() {

View file

@ -18,8 +18,6 @@
#include <SharedUtil.h>
#include <UUID.h>
const quint8 ZERO_SIMULATION_PRIORITY = 0x00;
// Simulation observers will bid to simulate unowned active objects at the lowest possible priority
// which is VOLUNTEER. If the server accepts a VOLUNTEER bid it will automatically bump it
// to RECRUIT priority so that other volunteers don't accidentally take over.
@ -27,11 +25,12 @@ const quint8 VOLUNTEER_SIMULATION_PRIORITY = 0x01;
const quint8 RECRUIT_SIMULATION_PRIORITY = VOLUNTEER_SIMULATION_PRIORITY + 1;
// When poking objects with scripts an observer will bid at SCRIPT_EDIT priority.
const quint8 SCRIPT_EDIT_SIMULATION_PRIORITY = 0x80;
const quint8 SCRIPT_GRAB_SIMULATION_PRIORITY = 0x80;
const quint8 SCRIPT_POKE_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY - 1;
// PERSONAL priority (needs a better name) is the level at which a simulation observer will bid for
// objects that collide its MyAvatar.
const quint8 PERSONAL_SIMULATION_PRIORITY = SCRIPT_EDIT_SIMULATION_PRIORITY - 1;
// PERSONAL priority (needs a better name) is the level at which a simulation observer owns its own avatar
// which really just means: things that collide with it will be bid at a priority level one lower
const quint8 PERSONAL_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY;
class SimulationOwner {

View file

@ -184,6 +184,9 @@ bool OBJReader::isValidTexture(const QByteArray &filename) {
}
QUrl candidateUrl = _url.resolved(QUrl(filename));
QNetworkReply *netReply = request(candidateUrl, true);
if (!netReply) {
return false;
}
bool isValid = netReply->isFinished() && (netReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200);
netReply->deleteLater();
return isValid;
@ -257,6 +260,9 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) {
}
QNetworkReply* OBJReader::request(QUrl& url, bool isTest) {
if (!qApp) {
return nullptr;
}
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest netRequest(url);
QNetworkReply* netReply = isTest ? networkAccessManager.head(netRequest) : networkAccessManager.get(netRequest);

View file

@ -326,6 +326,10 @@ OffscreenQmlSurface::~OffscreenQmlSurface() {
void OffscreenQmlSurface::onAboutToQuit() {
QObject::disconnect(&_updateTimer);
// Disconnecting the update timer is insufficient, since the renderer
// may attempting to render already, so we need to explicitly tell the renderer
// to stop
_renderer->aboutToQuit();
}
void OffscreenQmlSurface::create(QOpenGLContext* shareContext) {
@ -486,6 +490,9 @@ void OffscreenQmlSurface::updateQuick() {
QMutexLocker lock(&(_renderer->_mutex));
_renderer->post(RENDER);
while (!_renderer->_cond.wait(&(_renderer->_mutex), 100)) {
if (_renderer->_quit) {
return;
}
qApp->processEvents();
}
_render = false;

View file

@ -15,6 +15,7 @@ NetworkShader::NetworkShader(const QUrl& url, bool delayLoad)
void NetworkShader::downloadFinished(const QByteArray& data) {
_source = QString::fromUtf8(data);
finishedLoading(true);
}
ShaderCache& ShaderCache::instance() {

View file

@ -344,9 +344,7 @@ void NetworkTexture::setImage(const QImage& image, void* voidTexture, int origin
_width = _height = 0;
}
_isCacheable = true;
finishedLoading(true);
emit networkTextureCreated(qWeakPointerCast<NetworkTexture, Resource> (_self));
}

View file

@ -132,7 +132,7 @@ signals:
protected:
virtual bool isCacheable() const override { return _isCacheable; }
virtual bool isCacheable() const override { return _loaded; }
virtual void downloadFinished(const QByteArray& data) override;
@ -148,7 +148,6 @@ private:
int _originalHeight { 0 };
int _width { 0 };
int _height { 0 };
bool _isCacheable { false };
};
#endif // hifi_TextureCache_h

View file

@ -138,67 +138,66 @@ bool haveAssetServer() {
return true;
}
GetMappingRequest* AssetClient::createGetMappingRequest(const AssetPath& path) {
return new GetMappingRequest(path);
}
GetAllMappingsRequest* AssetClient::createGetAllMappingsRequest() {
return new GetAllMappingsRequest();
auto request = new GetAllMappingsRequest();
request->moveToThread(thread());
return request;
}
DeleteMappingsRequest* AssetClient::createDeleteMappingsRequest(const AssetPathList& paths) {
return new DeleteMappingsRequest(paths);
auto request = new DeleteMappingsRequest(paths);
request->moveToThread(thread());
return request;
}
SetMappingRequest* AssetClient::createSetMappingRequest(const AssetPath& path, const AssetHash& hash) {
return new SetMappingRequest(path, hash);
auto request = new SetMappingRequest(path, hash);
request->moveToThread(thread());
return request;
}
RenameMappingRequest* AssetClient::createRenameMappingRequest(const AssetPath& oldPath, const AssetPath& newPath) {
return new RenameMappingRequest(oldPath, newPath);
auto request = new RenameMappingRequest(oldPath, newPath);
request->moveToThread(thread());
return request;
}
AssetRequest* AssetClient::createRequest(const AssetHash& hash) {
if (hash.length() != SHA256_HASH_HEX_LENGTH) {
qCWarning(asset_client) << "Invalid hash size";
return nullptr;
}
auto request = new AssetRequest(hash);
if (haveAssetServer()) {
auto request = new AssetRequest(hash);
// Move to the AssetClient thread in case we are not currently on that thread (which will usually be the case)
request->moveToThread(thread());
return request;
} else {
return nullptr;
}
// Move to the AssetClient thread in case we are not currently on that thread (which will usually be the case)
request->moveToThread(thread());
return request;
}
AssetUpload* AssetClient::createUpload(const QString& filename) {
if (haveAssetServer()) {
auto upload = new AssetUpload(filename);
upload->moveToThread(thread());
return upload;
} else {
return nullptr;
}
auto upload = new AssetUpload(filename);
upload->moveToThread(thread());
return upload;
}
AssetUpload* AssetClient::createUpload(const QByteArray& data) {
if (haveAssetServer()) {
auto upload = new AssetUpload(data);
upload->moveToThread(thread());
return upload;
} else {
return nullptr;
}
auto upload = new AssetUpload(data);
upload->moveToThread(thread());
return upload;
}
bool AssetClient::getAsset(const QString& hash, DataOffset start, DataOffset end,
@ -232,9 +231,12 @@ bool AssetClient::getAsset(const QString& hash, DataOffset start, DataOffset end
_pendingRequests[assetServer][messageID] = { callback, progressCallback };
return true;
} else {
callback(false, AssetServerError::NoError, QByteArray());
return false;
}
return false;
}
bool AssetClient::getAssetInfo(const QString& hash, GetInfoCallback callback) {
@ -255,9 +257,10 @@ bool AssetClient::getAssetInfo(const QString& hash, GetInfoCallback callback) {
_pendingInfoRequests[assetServer][messageID] = callback;
return true;
} else {
callback(false, AssetServerError::NoError, { "", 0 });
return false;
}
return false;
}
void AssetClient::handleAssetGetInfoReply(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
@ -364,9 +367,10 @@ bool AssetClient::getAssetMapping(const AssetPath& path, MappingOperationCallbac
_pendingMappingRequests[assetServer][messageID] = callback;
return true;
} else {
callback(false, AssetServerError::NoError, QSharedPointer<ReceivedMessage>());
return false;
}
return false;
}
bool AssetClient::getAllAssetMappings(MappingOperationCallback callback) {
@ -386,9 +390,10 @@ bool AssetClient::getAllAssetMappings(MappingOperationCallback callback) {
_pendingMappingRequests[assetServer][messageID] = callback;
return true;
} else {
callback(false, AssetServerError::NoError, QSharedPointer<ReceivedMessage>());
return false;
}
return false;
}
bool AssetClient::deleteAssetMappings(const AssetPathList& paths, MappingOperationCallback callback) {
@ -414,9 +419,10 @@ bool AssetClient::deleteAssetMappings(const AssetPathList& paths, MappingOperati
_pendingMappingRequests[assetServer][messageID] = callback;
return true;
} else {
callback(false, AssetServerError::NoError, QSharedPointer<ReceivedMessage>());
return false;
}
return false;
}
bool AssetClient::setAssetMapping(const QString& path, const AssetHash& hash, MappingOperationCallback callback) {
@ -439,9 +445,10 @@ bool AssetClient::setAssetMapping(const QString& path, const AssetHash& hash, Ma
_pendingMappingRequests[assetServer][messageID] = callback;
return true;
} else {
callback(false, AssetServerError::NoError, QSharedPointer<ReceivedMessage>());
return false;
}
return false;
}
bool AssetClient::renameAssetMapping(const AssetPath& oldPath, const AssetPath& newPath, MappingOperationCallback callback) {
@ -465,9 +472,10 @@ bool AssetClient::renameAssetMapping(const AssetPath& oldPath, const AssetPath&
return true;
} else {
callback(false, AssetServerError::NoError, QSharedPointer<ReceivedMessage>());
return false;
}
return false;
}
bool AssetClient::uploadAsset(const QByteArray& data, UploadResultCallback callback) {
@ -489,8 +497,10 @@ bool AssetClient::uploadAsset(const QByteArray& data, UploadResultCallback callb
_pendingUploads[assetServer][messageID] = callback;
return true;
} else {
callback(false, AssetServerError::NoError, QString());
return false;
}
return false;
}
void AssetClient::handleAssetUploadReply(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {

View file

@ -35,6 +35,15 @@ void AssetRequest::start() {
qCWarning(asset_client) << "AssetRequest already started.";
return;
}
// in case we haven't parsed a valid hash, return an error now
if (!isValidHash(_hash)) {
_error = InvalidHash;
_state = Finished;
emit finished(this);
return;
}
// Try to load from cache
_data = loadFromCache(getUrl());
@ -53,7 +62,7 @@ void AssetRequest::start() {
auto assetClient = DependencyManager::get<AssetClient>();
assetClient->getAssetInfo(_hash, [this](bool responseReceived, AssetServerError serverError, AssetInfo info) {
_info = info;
if (!responseReceived) {
_error = NetworkError;
} else if (serverError != AssetServerError::NoError) {

View file

@ -34,6 +34,7 @@ public:
NoError,
NotFound,
InvalidByteRange,
InvalidHash,
HashVerificationFailed,
NetworkError,
UnknownError

View file

@ -58,15 +58,6 @@ void AssetResourceRequest::requestMappingForPath(const AssetPath& path) {
auto assetClient = DependencyManager::get<AssetClient>();
_assetMappingRequest = assetClient->createGetMappingRequest(path);
// if we get a nullptr for createGetMappingRequest assume that there is no currently available asset-server
if (!_assetMappingRequest) {
_result = ServerUnavailable;
_state = Finished;
emit finished();
return;
}
// make sure we'll hear about the result of the get mapping request
connect(_assetMappingRequest, &GetMappingRequest::finished, this, [this, path](GetMappingRequest* request){
Q_ASSERT(_state == InProgress);
@ -80,24 +71,27 @@ void AssetResourceRequest::requestMappingForPath(const AssetPath& path) {
requestHash(request->getHash());
break;
case MappingRequest::NotFound:
// no result for the mapping request, set error to not found
_result = NotFound;
default: {
switch (request->getError()) {
case MappingRequest::NotFound:
// no result for the mapping request, set error to not found
_result = NotFound;
break;
case MappingRequest::NetworkError:
// didn't hear back from the server, mark it unavailable
_result = ServerUnavailable;
break;
default:
_result = Error;
break;
}
// since we've failed we know we are finished
_state = Finished;
emit finished();
break;
default:
// these are unexpected errors for a GetMappingRequest object
_result = Error;
// since we've failed we know we are finished
_state = Finished;
emit finished();
break;
}
}
_assetMappingRequest->deleteLater();
@ -109,27 +103,10 @@ void AssetResourceRequest::requestMappingForPath(const AssetPath& path) {
void AssetResourceRequest::requestHash(const AssetHash& hash) {
// in case we haven't parsed a valid hash, return an error now
if (hash.length() != SHA256_HASH_HEX_LENGTH) {
_result = InvalidURL;
_state = Finished;
emit finished();
return;
}
// Make request to atp
auto assetClient = DependencyManager::get<AssetClient>();
_assetRequest = assetClient->createRequest(hash);
if (!_assetRequest) {
_result = ServerUnavailable;
_state = Finished;
emit finished();
return;
}
connect(_assetRequest, &AssetRequest::progress, this, &AssetResourceRequest::progress);
connect(_assetRequest, &AssetRequest::finished, this, [this](AssetRequest* req) {
Q_ASSERT(_state == InProgress);
@ -141,6 +118,9 @@ void AssetResourceRequest::requestHash(const AssetHash& hash) {
_data = req->getData();
_result = Success;
break;
case AssetRequest::InvalidHash:
_result = InvalidURL;
break;
case AssetRequest::Error::NotFound:
_result = NotFound;
break;

View file

@ -41,7 +41,7 @@ QString AssetUpload::getErrorString() const {
case AssetUpload::FileOpenError:
return "The file could not be opened. Please check your permissions and try again.";
case AssetUpload::NetworkError:
return "The file could not be opened. Please check your network connectivity.";
return "There was a problem reaching your Asset Server. Please check your network connectivity.";
default:
// not handled, do not show a message box
return QString();

View file

@ -30,6 +30,13 @@ GetMappingRequest::GetMappingRequest(const AssetPath& path) : _path(path) {
void GetMappingRequest::doStart() {
// short circuit the request if the path is invalid
if (!isValidPath(_path)) {
_error = MappingRequest::InvalidPath;
emit finished(this);
return;
}
auto assetClient = DependencyManager::get<AssetClient>();
assetClient->getAssetMapping(_path, [this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer<ReceivedMessage> message) {
@ -89,11 +96,26 @@ void GetAllMappingsRequest::doStart() {
});
};
SetMappingRequest::SetMappingRequest(const AssetPath& path, const AssetHash& hash) : _path(path), _hash(hash) {
SetMappingRequest::SetMappingRequest(const AssetPath& path, const AssetHash& hash) :
_path(path),
_hash(hash)
{
};
void SetMappingRequest::doStart() {
// short circuit the request if the hash or path are invalid
auto validPath = isValidPath(_path);
auto validHash = isValidHash(_hash);
if (!validPath || !validHash) {
_error = validPath ? MappingRequest::InvalidPath : MappingRequest::InvalidHash;
emit finished(this);
return;
}
auto assetClient = DependencyManager::get<AssetClient>();
assetClient->setAssetMapping(_path, _hash, [this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer<ReceivedMessage> message) {
if (!responseReceived) {
_error = NetworkError;
@ -119,7 +141,18 @@ DeleteMappingsRequest::DeleteMappingsRequest(const AssetPathList& paths) : _path
};
void DeleteMappingsRequest::doStart() {
// short circuit the request if any of the paths are invalid
for (auto& path : _paths) {
if (!isValidPath(path)) {
_error = MappingRequest::InvalidPath;
emit finished(this);
return;
}
}
auto assetClient = DependencyManager::get<AssetClient>();
assetClient->deleteAssetMappings(_paths, [this, assetClient](bool responseReceived, AssetServerError error, QSharedPointer<ReceivedMessage> message) {
if (!responseReceived) {
_error = NetworkError;
@ -142,14 +175,23 @@ void DeleteMappingsRequest::doStart() {
};
RenameMappingRequest::RenameMappingRequest(const AssetPath& oldPath, const AssetPath& newPath) :
_oldPath(oldPath),
_newPath(newPath)
_oldPath(oldPath),
_newPath(newPath)
{
}
void RenameMappingRequest::doStart() {
// short circuit the request if either of the paths are invalid
if (!isValidPath(_oldPath) || !isValidPath(_newPath)) {
_error = InvalidPath;
emit finished(this);
return;
}
auto assetClient = DependencyManager::get<AssetClient>();
assetClient->renameAssetMapping(_oldPath, _newPath, [this, assetClient](bool responseReceived,
AssetServerError error,
QSharedPointer<ReceivedMessage> message) {

View file

@ -26,6 +26,8 @@ public:
NotFound,
NetworkError,
PermissionDenied,
InvalidPath,
InvalidHash,
UnknownError
};

View file

@ -371,7 +371,6 @@ void Resource::handleReplyFinished() {
auto extraInfo = _url == _activeUrl ? "" : QString(", %1").arg(_activeUrl.toDisplayString());
qCDebug(networking).noquote() << QString("Request finished for %1%2").arg(_url.toDisplayString(), extraInfo);
finishedLoading(true);
emit loaded(_data);
downloadFinished(_data);
} else {
@ -409,10 +408,6 @@ void Resource::handleReplyFinished() {
_request = nullptr;
}
void Resource::downloadFinished(const QByteArray& data) {
}
uint qHash(const QPointer<QObject>& value, uint seed) {
return qHash(value.data(), seed);
}

View file

@ -177,13 +177,14 @@ public:
const QByteArray& getData() const { return _data; }
signals:
/// Fired when the resource has been loaded.
/// Fired when the resource has been downloaded.
/// This can be used instead of downloadFinished to access data before it is processed.
void loaded(const QByteArray& request);
/// Fired when resource failed to load.
/// Fired when the resource failed to load.
void failed(QNetworkReply::NetworkError error);
/// Fired when resource is refreshed.
/// Fired when the resource is refreshed.
void onRefresh();
protected slots:
@ -195,10 +196,12 @@ protected:
/// Checks whether the resource is cacheable.
virtual bool isCacheable() const { return true; }
/// Called when the download has finished
virtual void downloadFinished(const QByteArray& data);
/// Called when the download has finished.
/// This should be overridden by subclasses that need to process the data once it is downloaded.
virtual void downloadFinished(const QByteArray& data) { finishedLoading(true); }
/// Should be called by subclasses when all the loading that will be done has been done.
/// Called when the download is finished and processed.
/// This should be called by subclasses that override downloadFinished to mark the end of processing.
Q_INVOKABLE void finishedLoading(bool success);
/// Reinserts this resource into the cache.

View file

@ -65,7 +65,7 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer
_loopsWithoutOwner(0),
_accelerationNearlyGravityCount(0),
_numInactiveUpdates(1),
_outgoingPriority(ZERO_SIMULATION_PRIORITY)
_outgoingPriority(0)
{
_type = MOTIONSTATE_TYPE_ENTITY;
assert(_entity);
@ -103,33 +103,36 @@ bool EntityMotionState::handleEasyChanges(uint32_t& flags) {
if (_entity->getSimulatorID().isNull()) {
// simulation ownership has been removed by an external simulator
if (glm::length2(_entity->getVelocity()) == 0.0f) {
// this object is coming to rest --> clear the ACTIVATION flag and outgoing priority
// this object is coming to rest --> clear the ACTIVATION flag and _outgoingPriority
flags &= ~Simulation::DIRTY_PHYSICS_ACTIVATION;
_body->setActivationState(WANTS_DEACTIVATION);
_outgoingPriority = ZERO_SIMULATION_PRIORITY;
_loopsWithoutOwner = 0;
_outgoingPriority = 0;
} else {
// unowned object is still moving --> we should volunteer to own it
// disowned object is still moving --> start timer for ownership bid
// TODO? put a delay in here proportional to distance from object?
setOutgoingPriority(VOLUNTEER_SIMULATION_PRIORITY);
_loopsWithoutOwner = LOOPS_FOR_SIMULATION_ORPHAN;
_nextOwnershipBid = 0;
upgradeOutgoingPriority(VOLUNTEER_SIMULATION_PRIORITY);
_nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS;
}
_loopsWithoutOwner = 0;
} else if (_entity->getSimulatorID() == Physics::getSessionUUID()) {
// we just inherited ownership, make sure our desired priority matches what we have
upgradeOutgoingPriority(_entity->getSimulationPriority());
} else {
// this entity's simulation is owned by someone, so we push its ownership expiry into the future
_outgoingPriority = 0;
_nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS;
if (Physics::getSessionUUID() == _entity->getSimulatorID() || _entity->getSimulationPriority() >= _outgoingPriority) {
// either we already own the simulation or our old outgoing priority momentarily looses to current owner
// so we clear it
_outgoingPriority = ZERO_SIMULATION_PRIORITY;
}
}
}
if (flags & Simulation::DIRTY_SIMULATOR_OWNERSHIP) {
// The DIRTY_SIMULATOR_OWNERSHIP bit really means "we should bid for ownership at SCRIPT priority".
// Since that bit is set there must be a local script that is updating the physics properties of the objects
// therefore we upgrade _outgoingPriority to trigger a bid for ownership.
setOutgoingPriority(SCRIPT_EDIT_SIMULATION_PRIORITY);
if (flags & Simulation::DIRTY_SIMULATION_OWNERSHIP_PRIORITY) {
// The DIRTY_SIMULATOR_OWNERSHIP_PRIORITY bits really mean "we should bid for ownership because
// a local script has been changing physics properties, or we should adjust our own ownership priority".
// The desired priority is determined by which bits were set.
if (flags & Simulation::DIRTY_SIMULATION_OWNERSHIP_FOR_GRAB) {
_outgoingPriority = SCRIPT_GRAB_SIMULATION_PRIORITY;
} else {
_outgoingPriority = SCRIPT_POKE_SIMULATION_PRIORITY;
}
// reset bid expiry so that we bid ASAP
_nextOwnershipBid = 0;
}
if ((flags & Simulation::DIRTY_PHYSICS_ACTIVATION) && !_body->isActive()) {
_body->activate();
@ -209,7 +212,7 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) {
_loopsWithoutOwner++;
if (_loopsWithoutOwner > LOOPS_FOR_SIMULATION_ORPHAN && usecTimestampNow() > _nextOwnershipBid) {
setOutgoingPriority(VOLUNTEER_SIMULATION_PRIORITY);
upgradeOutgoingPriority(VOLUNTEER_SIMULATION_PRIORITY);
}
}
@ -243,7 +246,7 @@ bool EntityMotionState::isCandidateForOwnership(const QUuid& sessionID) const {
assert(_body);
assert(_entity);
assert(entityTreeIsLocked());
return _outgoingPriority != ZERO_SIMULATION_PRIORITY || sessionID == _entity->getSimulatorID() || _entity->actionDataNeedsTransmit();
return _outgoingPriority != 0 || sessionID == _entity->getSimulatorID() || _entity->actionDataNeedsTransmit();
}
bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
@ -298,7 +301,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
}
if (_entity->actionDataNeedsTransmit()) {
setOutgoingPriority(SCRIPT_EDIT_SIMULATION_PRIORITY);
_outgoingPriority = _entity->hasActions() ? SCRIPT_GRAB_SIMULATION_PRIORITY : SCRIPT_POKE_SIMULATION_PRIORITY;
return true;
}
@ -385,16 +388,15 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep, const QUuid& s
if (_entity->getSimulatorID() != sessionID) {
// we don't own the simulation
if (_outgoingPriority != ZERO_SIMULATION_PRIORITY) {
// but we would like to own it
if (_outgoingPriority < _entity->getSimulationPriority()) {
// but our priority loses to remote, so we don't bother trying
_outgoingPriority = ZERO_SIMULATION_PRIORITY;
return false;
}
return usecTimestampNow() > _nextOwnershipBid;
bool shouldBid = _outgoingPriority > 0 && // but we would like to own it and
usecTimestampNow() > _nextOwnershipBid; // it is time to bid again
if (shouldBid && _outgoingPriority < _entity->getSimulationPriority()) {
// we are insufficiently interested so clear our interest
// and reset the bid expiry
_outgoingPriority = 0;
_nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS;
}
return false;
return shouldBid;
}
return remoteSimulationOutOfSync(simulationStep);
@ -495,14 +497,24 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q
#endif //def WANT_DEBUG
if (_numInactiveUpdates > 0) {
// we own the simulation but the entity has stopped, so we tell the server that we're clearing simulatorID
// but we remember that we do still own it... and rely on the server to tell us that we don't
// we own the simulation but the entity has stopped so we tell the server we're clearing simulatorID
// but we remember we do still own it... and rely on the server to tell us we don't
properties.clearSimulationOwner();
_outgoingPriority = ZERO_SIMULATION_PRIORITY;
_outgoingPriority = 0;
} else if (sessionID != _entity->getSimulatorID()) {
// we don't own the simulation for this entity yet, but we're sending a bid for it
properties.setSimulationOwner(sessionID, glm::max<uint8_t>(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY));
_nextOwnershipBid = now + USECS_BETWEEN_OWNERSHIP_BIDS;
_outgoingPriority = 0; // reset outgoing priority whenever we bid
} else if (_outgoingPriority != _entity->getSimulationPriority()) {
// we own the simulation but our desired priority has changed
if (_outgoingPriority == 0) {
// we should release ownership
properties.clearSimulationOwner();
} else {
// we just need to change the priority
properties.setSimulationOwner(sessionID, _outgoingPriority);
}
}
EntityItemID id(_entity->getID());
@ -578,7 +590,8 @@ QUuid EntityMotionState::getSimulatorID() const {
}
void EntityMotionState::bump(uint8_t priority) {
setOutgoingPriority(glm::max(VOLUNTEER_SIMULATION_PRIORITY, --priority));
assert(priority != 0);
upgradeOutgoingPriority(glm::max(VOLUNTEER_SIMULATION_PRIORITY, --priority));
}
void EntityMotionState::resetMeasuredBodyAcceleration() {
@ -640,6 +653,6 @@ void EntityMotionState::computeCollisionGroupAndMask(int16_t& group, int16_t& ma
_entity->computeCollisionGroupAndFinalMask(group, mask);
}
void EntityMotionState::setOutgoingPriority(uint8_t priority) {
void EntityMotionState::upgradeOutgoingPriority(uint8_t priority) {
_outgoingPriority = glm::max<uint8_t>(_outgoingPriority, priority);
}

View file

@ -82,12 +82,12 @@ public:
virtual void computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const override;
// eternal logic can suggest a simuator priority bid for the next outgoing update
void setOutgoingPriority(uint8_t priority);
friend class PhysicalEntitySimulation;
protected:
// changes _outgoingPriority only if priority is larger
void upgradeOutgoingPriority(uint8_t priority);
#ifdef WANT_DEBUG_ENTITY_TREE_LOCKS
bool entityTreeIsLocked() const;
#endif
@ -125,7 +125,7 @@ protected:
uint8_t _loopsWithoutOwner;
uint8_t _accelerationNearlyGravityCount;
uint8_t _numInactiveUpdates { 1 };
uint8_t _outgoingPriority { ZERO_SIMULATION_PRIORITY };
uint8_t _outgoingPriority { 0 };
};
#endif // hifi_EntityMotionState_h

View file

@ -50,7 +50,7 @@ const uint32_t HARD_DIRTY_PHYSICS_FLAGS = (uint32_t)(Simulation::DIRTY_MOTION_TY
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_SIMULATOR_OWNERSHIP);
Simulation::DIRTY_SIMULATOR_ID | Simulation::DIRTY_SIMULATION_OWNERSHIP_PRIORITY);
// These are the set of incoming flags that the PhysicsEngine needs to hear about:
const uint32_t DIRTY_PHYSICS_FLAGS = (uint32_t)(HARD_DIRTY_PHYSICS_FLAGS | EASY_DIRTY_PHYSICS_FLAGS |

View file

@ -291,8 +291,8 @@ void PhysicsEngine::doOwnershipInfection(const btCollisionObject* objectA, const
// NOTE: we might own the simulation of a kinematic object (A)
// but we don't claim ownership of kinematic objects (B) based on collisions here.
if (!objectB->isStaticOrKinematicObject() && motionStateB->getSimulatorID() != _sessionID) {
quint8 priority = motionStateA ? motionStateA->getSimulationPriority() : PERSONAL_SIMULATION_PRIORITY;
motionStateB->bump(priority);
quint8 priorityA = motionStateA ? motionStateA->getSimulationPriority() : PERSONAL_SIMULATION_PRIORITY;
motionStateB->bump(priorityA);
}
} else if (motionStateA &&
((motionStateB && motionStateB->getSimulatorID() == _sessionID && !objectB->isStaticObject()) ||
@ -300,8 +300,8 @@ void PhysicsEngine::doOwnershipInfection(const btCollisionObject* objectA, const
// SIMILARLY: we might own the simulation of a kinematic object (B)
// but we don't claim ownership of kinematic objects (A) based on collisions here.
if (!objectA->isStaticOrKinematicObject() && motionStateA->getSimulatorID() != _sessionID) {
quint8 priority = motionStateB ? motionStateB->getSimulationPriority() : PERSONAL_SIMULATION_PRIORITY;
motionStateA->bump(priority);
quint8 priorityB = motionStateB ? motionStateB->getSimulationPriority() : PERSONAL_SIMULATION_PRIORITY;
motionStateA->bump(priorityB);
}
}
}

View file

@ -7,6 +7,14 @@
//
#include "PluginContainer.h"
#include <QtCore/QTimer>
#include <QtGui/QScreen>
#include <QtGui/QWindow>
#include <QtWidgets/QApplication>
#include <ui/Menu.h>
#include <MainWindow.h>
static PluginContainer* INSTANCE{ nullptr };
PluginContainer& PluginContainer::getInstance() {
@ -23,3 +31,131 @@ PluginContainer::~PluginContainer() {
Q_ASSERT(INSTANCE == this);
INSTANCE = nullptr;
};
void PluginContainer::addMenu(const QString& menuName) {
getPrimaryMenu()->addMenu(menuName);
}
void PluginContainer::removeMenu(const QString& menuName) {
getPrimaryMenu()->removeMenu(menuName);
}
QAction* PluginContainer::addMenuItem(PluginType type, const QString& path, const QString& name, std::function<void(bool)> onClicked, bool checkable, bool checked, const QString& groupName) {
auto menu = getPrimaryMenu();
MenuWrapper* parentItem = menu->getMenu(path);
QAction* action = menu->addActionToQMenuAndActionHash(parentItem, name);
if (!groupName.isEmpty()) {
QActionGroup* group { nullptr };
if (!_exclusiveGroups.count(groupName)) {
group = _exclusiveGroups[groupName] = new QActionGroup(menu);
group->setExclusive(true);
} else {
group = _exclusiveGroups[groupName];
}
group->addAction(action);
}
QObject::connect(action, &QAction::triggered, [=] {
onClicked(action->isChecked());
});
action->setCheckable(checkable);
action->setChecked(checked);
if (type == PluginType::DISPLAY_PLUGIN) {
_currentDisplayPluginActions.push_back({ path, name });
} else {
_currentInputPluginActions.push_back({ path, name });
}
return action;
}
void PluginContainer::removeMenuItem(const QString& menuName, const QString& menuItem) {
getPrimaryMenu()->removeMenuItem(menuName, menuItem);
}
bool PluginContainer::isOptionChecked(const QString& name) {
return getPrimaryMenu()->isOptionChecked(name);
}
void PluginContainer::setIsOptionChecked(const QString& path, bool checked) {
getPrimaryMenu()->setIsOptionChecked(path, checked);
}
// FIXME there is a bug in the fullscreen setting, where leaving
// fullscreen does not restore the window frame, making it difficult
// or impossible to move or size the window.
// Additionally, setting fullscreen isn't hiding the menu on windows
// make it useless for stereoscopic modes.
void PluginContainer::setFullscreen(const QScreen* target, bool hideMenu) {
auto _window = getPrimaryWindow();
if (!_window->isFullScreen()) {
_savedGeometry = _window->geometry();
}
if (nullptr == target) {
// FIXME target the screen where the window currently is
target = qApp->primaryScreen();
}
_window->setGeometry(target->availableGeometry());
_window->windowHandle()->setScreen((QScreen*)target);
_window->showFullScreen();
#ifndef Q_OS_MAC
// also hide the QMainWindow's menuBar
QMenuBar* menuBar = _window->menuBar();
if (menuBar && hideMenu) {
menuBar->setVisible(false);
}
#endif
}
void PluginContainer::unsetFullscreen(const QScreen* avoid) {
auto _window = getPrimaryWindow();
_window->showNormal();
QRect targetGeometry = _savedGeometry;
if (avoid != nullptr) {
QRect avoidGeometry = avoid->geometry();
if (avoidGeometry.contains(targetGeometry.topLeft())) {
QScreen* newTarget = qApp->primaryScreen();
if (newTarget == avoid) {
foreach(auto screen, qApp->screens()) {
if (screen != avoid) {
newTarget = screen;
break;
}
}
}
targetGeometry = newTarget->availableGeometry();
}
}
#ifdef Q_OS_MAC
QTimer* timer = new QTimer();
timer->singleShot(2000, [=] {
_window->setGeometry(targetGeometry);
timer->deleteLater();
});
#else
_window->setGeometry(targetGeometry);
#endif
#ifndef Q_OS_MAC
// also show the QMainWindow's menuBar
QMenuBar* menuBar = _window->menuBar();
if (menuBar) {
menuBar->setVisible(true);
}
#endif
}
/// settings interface
bool PluginContainer::getBoolSetting(const QString& settingName, bool defaultValue) {
Setting::Handle<bool> settingValue(settingName, defaultValue);
return settingValue.get();
}
void PluginContainer::setBoolSetting(const QString& settingName, bool value) {
Setting::Handle<bool> settingValue(settingName, value);
return settingValue.set(value);
}

View file

@ -8,10 +8,13 @@
#pragma once
#include <functional>
#include <map>
#include <stdint.h>
#include <QString>
#include <QtCore/QString>
#include <QtCore/QVector>
#include <QtCore/QPair>
#include <QtCore/QRect>
#include "Forward.h"
@ -28,33 +31,44 @@ namespace gpu {
using TexturePointer = std::shared_ptr<Texture>;
}
namespace ui {
class Menu;
}
class QActionGroup;
class MainWindow;
class PluginContainer {
public:
static PluginContainer& getInstance();
PluginContainer();
virtual ~PluginContainer();
virtual void addMenu(const QString& menuName) = 0;
virtual void removeMenu(const QString& menuName) = 0;
virtual QAction* addMenuItem(PluginType pluginType, const QString& path, const QString& name, std::function<void(bool)> onClicked, bool checkable = false, bool checked = false, const QString& groupName = "") = 0;
virtual void removeMenuItem(const QString& menuName, const QString& menuItem) = 0;
virtual bool isOptionChecked(const QString& name) = 0;
virtual void setIsOptionChecked(const QString& path, bool checked) = 0;
virtual void setFullscreen(const QScreen* targetScreen, bool hideMenu = false) = 0;
virtual void unsetFullscreen(const QScreen* avoidScreen = nullptr) = 0;
void addMenu(const QString& menuName);
void removeMenu(const QString& menuName);
QAction* addMenuItem(PluginType pluginType, const QString& path, const QString& name, std::function<void(bool)> onClicked, bool checkable = false, bool checked = false, const QString& groupName = "");
void removeMenuItem(const QString& menuName, const QString& menuItem);
bool isOptionChecked(const QString& name);
void setIsOptionChecked(const QString& path, bool checked);
void setFullscreen(const QScreen* targetScreen, bool hideMenu = false);
void unsetFullscreen(const QScreen* avoidScreen = nullptr);
virtual ui::Menu* getPrimaryMenu() = 0;
virtual void showDisplayPluginsTools() = 0;
virtual void requestReset() = 0;
virtual bool makeRenderingContextCurrent() = 0;
virtual void releaseSceneTexture(const gpu::TexturePointer& texture) = 0;
virtual void releaseOverlayTexture(const gpu::TexturePointer& texture) = 0;
virtual GLWidget* getPrimaryWidget() = 0;
virtual QWindow* getPrimaryWindow() = 0;
virtual MainWindow* getPrimaryWindow() = 0;
virtual QOpenGLContext* getPrimaryContext() = 0;
virtual bool isForeground() = 0;
virtual const DisplayPlugin* getActiveDisplayPlugin() const = 0;
/// settings interface
virtual bool getBoolSetting(const QString& settingName, bool defaultValue) = 0;
virtual void setBoolSetting(const QString& settingName, bool value) = 0;
bool getBoolSetting(const QString& settingName, bool defaultValue);
void setBoolSetting(const QString& settingName, bool value);
QVector<QPair<QString, QString>>& currentDisplayActions() {
return _currentDisplayPluginActions;
@ -67,5 +81,6 @@ public:
protected:
QVector<QPair<QString, QString>> _currentDisplayPluginActions;
QVector<QPair<QString, QString>> _currentInputPluginActions;
std::map<QString, QActionGroup*> _exclusiveGroups;
QRect _savedGeometry { 10, 120, 800, 600 };
};

View file

@ -23,6 +23,7 @@ void NetworkClip::init(const QByteArray& clipData) {
void NetworkClipLoader::downloadFinished(const QByteArray& data) {
_clip->init(data);
finishedLoading(true);
}
ClipCache& ClipCache::instance() {

View file

@ -26,10 +26,12 @@ out vec2 _texCoord0;
out vec3 _normal;
out vec3 _tangent;
out vec3 _color;
out float _alpha;
void main(void) {
// pass along the color
_color = colorToLinearRGB(inColor.xyz);
_color = colorToLinearRGB(inColor.rgb);
_alpha = inColor.a;
// and the texture coordinates
_texCoord0 = (texcoordMatrices[0] * vec4(inTexCoord0.xy, 0.0, 1.0)).st;

View file

@ -25,6 +25,7 @@ out vec4 _position;
out vec2 _texCoord0;
out vec3 _normal;
out vec3 _color;
out float _alpha;
void main(void) {
vec4 position = vec4(0.0, 0.0, 0.0, 0.0);
@ -34,6 +35,7 @@ void main(void) {
// pass along the color
_color = colorToLinearRGB(inColor.rgb);
_alpha = inColor.a;
// and the texture coordinates
_texCoord0 = (texcoordMatrices[0] * vec4(inTexCoord0.st, 0.0, 1.0)).st;

View file

@ -26,6 +26,7 @@ out vec2 _texCoord0;
out vec3 _normal;
out vec3 _tangent;
out vec3 _color;
out float _alpha;
void main(void) {
vec4 position = vec4(0.0, 0.0, 0.0, 0.0);
@ -36,6 +37,7 @@ void main(void) {
// pass along the color
_color = colorToLinearRGB(inColor.rgb);
_alpha = inColor.a;
// and the texture coordinates
_texCoord0 = (texcoordMatrices[0] * vec4(inTexCoord0.st, 0.0, 1.0)).st;

View file

@ -35,7 +35,7 @@ void AssetMappingsScriptingInterface::setMapping(QString path, QString hash, QJS
callback.call(args);
request->deleteLater();
});
request->start();
@ -51,7 +51,7 @@ void AssetMappingsScriptingInterface::getMapping(QString path, QJSValue callback
callback.call(args);
request->deleteLater();
});
request->start();
@ -67,7 +67,7 @@ void AssetMappingsScriptingInterface::deleteMappings(QStringList paths, QJSValue
callback.call(args);
request->deleteLater();
});
request->start();
@ -90,7 +90,7 @@ void AssetMappingsScriptingInterface::getAllMappings(QJSValue callback) {
callback.call(args);
request->deleteLater();
});
request->start();
@ -102,13 +102,13 @@ void AssetMappingsScriptingInterface::renameMapping(QString oldPath, QString new
connect(request, &RenameMappingRequest::finished, this, [this, callback](RenameMappingRequest* request) mutable {
QJSValueList args { uint8_t(request->getError()) };
callback.call(args);
request->deleteLater();
});
request->start();
}
@ -135,89 +135,93 @@ void AssetMappingModel::refresh() {
auto request = assetClient->createGetAllMappingsRequest();
connect(request, &GetAllMappingsRequest::finished, this, [this](GetAllMappingsRequest* request) mutable {
auto mappings = request->getMappings();
auto existingPaths = _pathToItemMap.keys();
for (auto& mapping : mappings) {
auto& path = mapping.first;
auto parts = path.split("/");
auto length = parts.length();
if (request->getError() == MappingRequest::NoError) {
auto mappings = request->getMappings();
auto existingPaths = _pathToItemMap.keys();
for (auto& mapping : mappings) {
auto& path = mapping.first;
auto parts = path.split("/");
auto length = parts.length();
existingPaths.removeOne(mapping.first);
existingPaths.removeOne(mapping.first);
QString fullPath = "/";
QString fullPath = "/";
QStandardItem* lastItem = nullptr;
QStandardItem* lastItem = nullptr;
// start index at 1 to avoid empty string from leading slash
for (int i = 1; i < length; ++i) {
fullPath += (i == 1 ? "" : "/") + parts[i];
// start index at 1 to avoid empty string from leading slash
for (int i = 1; i < length; ++i) {
fullPath += (i == 1 ? "" : "/") + parts[i];
auto it = _pathToItemMap.find(fullPath);
if (it == _pathToItemMap.end()) {
qDebug() << "prefix not found: " << fullPath;
auto item = new QStandardItem(parts[i]);
bool isFolder = i < length - 1;
item->setData(isFolder ? fullPath + "/" : fullPath, Qt::UserRole);
item->setData(isFolder, Qt::UserRole + 1);
item->setData(parts[i], Qt::UserRole + 2);
item->setData("atp:" + fullPath, Qt::UserRole + 3);
if (lastItem) {
lastItem->setChild(lastItem->rowCount(), 0, item);
} else {
appendRow(item);
auto it = _pathToItemMap.find(fullPath);
if (it == _pathToItemMap.end()) {
qDebug() << "prefix not found: " << fullPath;
auto item = new QStandardItem(parts[i]);
bool isFolder = i < length - 1;
item->setData(isFolder ? fullPath + "/" : fullPath, Qt::UserRole);
item->setData(isFolder, Qt::UserRole + 1);
item->setData(parts[i], Qt::UserRole + 2);
item->setData("atp:" + fullPath, Qt::UserRole + 3);
if (lastItem) {
lastItem->setChild(lastItem->rowCount(), 0, item);
} else {
appendRow(item);
}
lastItem = item;
_pathToItemMap[fullPath] = lastItem;
}
else {
lastItem = it.value();
}
}
lastItem = item;
_pathToItemMap[fullPath] = lastItem;
}
else {
lastItem = it.value();
}
Q_ASSERT(fullPath == path);
}
Q_ASSERT(fullPath == path);
}
// Remove folders from list
auto it = existingPaths.begin();
while (it != existingPaths.end()) {
auto item = _pathToItemMap[*it];
if (item->data(Qt::UserRole + 1).toBool()) {
it = existingPaths.erase(it);
} else {
++it;
}
}
for (auto& path : existingPaths) {
Q_ASSERT(_pathToItemMap.contains(path));
qDebug() << "removing existing: " << path;
auto item = _pathToItemMap[path];
while (item) {
// During each iteration, delete item
QStandardItem* nextItem = nullptr;
auto parent = item->parent();
if (parent) {
parent->removeRow(item->row());
if (parent->rowCount() > 0) {
// The parent still contains children, set the nextItem to null so we stop processing
nextItem = nullptr;
} else {
nextItem = parent;
}
// Remove folders from list
auto it = existingPaths.begin();
while (it != existingPaths.end()) {
auto item = _pathToItemMap[*it];
if (item->data(Qt::UserRole + 1).toBool()) {
it = existingPaths.erase(it);
} else {
removeRow(item->row());
++it;
}
_pathToItemMap.remove(path);
//delete item;
item = nextItem;
}
//removeitem->index();
for (auto& path : existingPaths) {
Q_ASSERT(_pathToItemMap.contains(path));
qDebug() << "removing existing: " << path;
auto item = _pathToItemMap[path];
while (item) {
// During each iteration, delete item
QStandardItem* nextItem = nullptr;
auto parent = item->parent();
if (parent) {
parent->removeRow(item->row());
if (parent->rowCount() > 0) {
// The parent still contains children, set the nextItem to null so we stop processing
nextItem = nullptr;
} else {
nextItem = parent;
}
} else {
removeRow(item->row());
}
_pathToItemMap.remove(path);
//delete item;
item = nextItem;
}
//removeitem->index();
}
} else {
emit errorGettingMappings(uint8_t(request->getError()));
}
});

View file

@ -42,6 +42,9 @@
bool isKnownMapping(QString path) const { return _pathToItemMap.contains(path); };
signals:
void errorGettingMappings(uint8_t error);
private:
QHash<QString, QStandardItem*> _pathToItemMap;
};
@ -65,6 +68,7 @@ public:
Q_INVOKABLE void deleteMapping(QString path, QJSValue callback) { deleteMappings(QStringList(path), callback); }
Q_INVOKABLE void getAllMappings(QJSValue callback);
Q_INVOKABLE void renameMapping(QString oldPath, QString newPath, QJSValue callback);
protected:
QSet<AssetRequest*> _pendingRequests;
AssetMappingModel _assetMappingModel;

View file

@ -27,14 +27,10 @@ AssetScriptingInterface::AssetScriptingInterface(QScriptEngine* engine) :
void AssetScriptingInterface::uploadData(QString data, QScriptValue callback) {
QByteArray dataByteArray = data.toUtf8();
auto upload = DependencyManager::get<AssetClient>()->createUpload(dataByteArray);
if (!upload) {
qCWarning(asset_client) << "Error uploading file to asset server";
return;
}
QObject::connect(upload, &AssetUpload::finished, this, [this, callback](AssetUpload* upload, const QString& hash) mutable {
if (callback.isFunction()) {
QString url = "atp://" + hash;
QString url = "atp:" + hash;
QScriptValueList args { url };
callback.call(_engine->currentContext()->thisObject(), args);
}
@ -43,7 +39,7 @@ void AssetScriptingInterface::uploadData(QString data, QScriptValue callback) {
}
void AssetScriptingInterface::downloadData(QString urlString, QScriptValue callback) {
const QString ATP_SCHEME { "atp://" };
const QString ATP_SCHEME { "atp:" };
if (!urlString.startsWith(ATP_SCHEME)) {
return;
@ -61,10 +57,6 @@ void AssetScriptingInterface::downloadData(QString urlString, QScriptValue callb
auto assetClient = DependencyManager::get<AssetClient>();
auto assetRequest = assetClient->createRequest(hash);
if (!assetRequest) {
return;
}
_pendingRequests << assetRequest;
connect(assetRequest, &AssetRequest::finished, this, [this, callback](AssetRequest* request) mutable {

View file

@ -0,0 +1,71 @@
//
// Mat4.cpp
// libraries/script-engine/src
//
// Created by Anthony Thibault on 3/7/16.
// Copyright 2016 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 <QDebug>
#include <GLMHelpers.h>
#include "ScriptEngineLogging.h"
#include "Mat4.h"
glm::mat4 Mat4::multiply(const glm::mat4& m1, const glm::mat4& m2) const {
return m1 * m2;
}
glm::mat4 Mat4::createFromRotAndTrans(const glm::quat& rot, const glm::vec3& trans) const {
return ::createMatFromQuatAndPos(rot, trans);
}
glm::mat4 Mat4::createFromScaleRotAndTrans(const glm::vec3& scale, const glm::quat& rot, const glm::vec3& trans) const {
return createMatFromScaleQuatAndPos(scale, rot, trans);
}
glm::vec3 Mat4::extractTranslation(const glm::mat4& m) const {
return ::extractTranslation(m);
}
glm::quat Mat4::extractRotation(const glm::mat4& m) const {
return ::glmExtractRotation(m);
}
glm::vec3 Mat4::extractScale(const glm::mat4& m) const {
return ::extractScale(m);
}
glm::vec3 Mat4::transformPoint(const glm::mat4& m, const glm::vec3& point) const {
return ::transformPoint(m, point);
}
glm::vec3 Mat4::transformVector(const glm::mat4& m, const glm::vec3& vector) const {
return ::transformVectorFast(m, vector);
}
glm::mat4 Mat4::inverse(const glm::mat4& m) const {
return glm::inverse(m);
}
glm::vec3 Mat4::getFront(const glm::mat4& m) const {
return glm::vec3(-m[0][2], -m[1][2], -m[2][2]);
}
glm::vec3 Mat4::getRight(const glm::mat4& m) const {
return glm::vec3(m[0][0], m[1][0], m[2][0]);
}
glm::vec3 Mat4::getUp(const glm::mat4& m) const {
return glm::vec3(m[0][1], m[1][1], m[2][1]);
}
void Mat4::print(const QString& label, const glm::mat4& m) const {
qCDebug(scriptengine) << qPrintable(label) <<
"row0 =" << m[0][0] << "," << m[1][0] << "," << m[2][0] << "," << m[3][0] <<
"row1 =" << m[0][1] << "," << m[1][1] << "," << m[2][1] << "," << m[3][1] <<
"row2 =" << m[0][2] << "," << m[1][2] << "," << m[2][2] << "," << m[3][2] <<
"row3 =" << m[0][3] << "," << m[1][3] << "," << m[2][3] << "," << m[3][3];
}

View file

@ -0,0 +1,45 @@
//
// Mat4.h
// libraries/script-engine/src
//
// Created by Anthony Thibault on 3/7/16.
// Copyright 2016 High Fidelity, Inc.
//
// Scriptable 4x4 Matrix class library.
//
// 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_Mat4_h
#define hifi_Mat4_h
#include <QObject>
#include <QString>
/// Scriptable Mat4 object. Used exclusively in the JavaScript API
class Mat4 : public QObject {
Q_OBJECT
public slots:
glm::mat4 multiply(const glm::mat4& m1, const glm::mat4& m2) const;
glm::mat4 createFromRotAndTrans(const glm::quat& rot, const glm::vec3& trans) const;
glm::mat4 createFromScaleRotAndTrans(const glm::vec3& scale, const glm::quat& rot, const glm::vec3& trans) const;
glm::vec3 extractTranslation(const glm::mat4& m) const;
glm::quat extractRotation(const glm::mat4& m) const;
glm::vec3 extractScale(const glm::mat4& m) const;
glm::vec3 transformPoint(const glm::mat4& m, const glm::vec3& point) const;
glm::vec3 transformVector(const glm::mat4& m, const glm::vec3& vector) const;
glm::mat4 inverse(const glm::mat4& m) const;
glm::vec3 getFront(const glm::mat4& m) const;
glm::vec3 getRight(const glm::mat4& m) const;
glm::vec3 getUp(const glm::mat4& m) const;
void print(const QString& label, const glm::mat4& m) const;
};
#endif // hifi_Mat4_h

View file

@ -28,21 +28,26 @@ ScriptCache::ScriptCache(QObject* parent) {
}
void ScriptCache::clearCache() {
Lock lock(_containerLock);
_scriptCache.clear();
}
QString ScriptCache::getScript(const QUrl& unnormalizedURL, ScriptUser* scriptUser, bool& isPending, bool reload) {
QUrl url = ResourceManager::normalizeURL(unnormalizedURL);
QString scriptContents;
Lock lock(_containerLock);
if (_scriptCache.contains(url) && !reload) {
qCDebug(scriptengine) << "Found script in cache:" << url.toString();
scriptContents = _scriptCache[url];
lock.unlock();
scriptUser->scriptContentsAvailable(url, scriptContents);
isPending = false;
} else {
isPending = true;
bool alreadyWaiting = _scriptUsers.contains(url);
_scriptUsers.insert(url, scriptUser);
lock.unlock();
if (alreadyWaiting) {
qCDebug(scriptengine) << "Already downloading script at:" << url.toString();
@ -58,6 +63,7 @@ QString ScriptCache::getScript(const QUrl& unnormalizedURL, ScriptUser* scriptUs
void ScriptCache::deleteScript(const QUrl& unnormalizedURL) {
QUrl url = ResourceManager::normalizeURL(unnormalizedURL);
Lock lock(_containerLock);
if (_scriptCache.contains(url)) {
qCDebug(scriptengine) << "Delete script from cache:" << url.toString();
_scriptCache.remove(url);
@ -67,17 +73,22 @@ void ScriptCache::deleteScript(const QUrl& unnormalizedURL) {
void ScriptCache::scriptDownloaded() {
ResourceRequest* req = qobject_cast<ResourceRequest*>(sender());
QUrl url = req->getUrl();
Lock lock(_containerLock);
QList<ScriptUser*> scriptUsers = _scriptUsers.values(url);
_scriptUsers.remove(url);
if (req->getResult() == ResourceRequest::Success) {
_scriptCache[url] = req->getData();
auto scriptContents = req->getData();
_scriptCache[url] = scriptContents;
lock.unlock();
qCDebug(scriptengine) << "Done downloading script at:" << url.toString();
foreach(ScriptUser* user, scriptUsers) {
user->scriptContentsAvailable(url, _scriptCache[url]);
user->scriptContentsAvailable(url, scriptContents);
}
} else {
lock.unlock();
qCWarning(scriptengine) << "Error loading script from URL " << url;
foreach(ScriptUser* user, scriptUsers) {
user->errorInLoadingScript(url);
@ -99,15 +110,19 @@ void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailable
return;
}
Lock lock(_containerLock);
if (_scriptCache.contains(url) && !forceDownload) {
auto scriptContent = _scriptCache[url];
lock.unlock();
qCDebug(scriptengine) << "Found script in cache:" << url.toString();
#if 1 // def THREAD_DEBUGGING
qCDebug(scriptengine) << "ScriptCache::getScriptContents() about to call contentAvailable() on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]";
#endif
contentAvailable(url.toString(), _scriptCache[url], true, true);
contentAvailable(url.toString(), scriptContent, true, true);
} else {
bool alreadyWaiting = _contentCallbacks.contains(url);
_contentCallbacks.insert(url, contentAvailable);
lock.unlock();
if (alreadyWaiting) {
qCDebug(scriptengine) << "Already downloading script at:" << url.toString();
@ -129,19 +144,29 @@ void ScriptCache::scriptContentAvailable() {
#endif
ResourceRequest* req = qobject_cast<ResourceRequest*>(sender());
QUrl url = req->getUrl();
QList<contentAvailableCallback> allCallbacks = _contentCallbacks.values(url);
_contentCallbacks.remove(url);
bool success = req->getResult() == ResourceRequest::Success;
QString scriptContent;
QList<contentAvailableCallback> allCallbacks;
bool success { false };
if (success) {
_scriptCache[url] = req->getData();
qCDebug(scriptengine) << "Done downloading script at:" << url.toString();
} else {
qCWarning(scriptengine) << "Error loading script from URL " << url;
{
Lock lock(_containerLock);
allCallbacks = _contentCallbacks.values(url);
_contentCallbacks.remove(url);
success = req->getResult() == ResourceRequest::Success;
if (success) {
_scriptCache[url] = scriptContent = req->getData();
qCDebug(scriptengine) << "Done downloading script at:" << url.toString();
} else {
// Dubious, but retained here because it matches the behavior before fixing the threading
scriptContent = _scriptCache[url];
qCWarning(scriptengine) << "Error loading script from URL " << url;
}
}
foreach(contentAvailableCallback thisCallback, allCallbacks) {
thisCallback(url.toString(), _scriptCache[url], true, success);
thisCallback(url.toString(), scriptContent, true, success);
}
req->deleteLater();
}

View file

@ -12,6 +12,7 @@
#ifndef hifi_ScriptCache_h
#define hifi_ScriptCache_h
#include <mutex>
#include <ResourceCache.h>
class ScriptUser {
@ -27,6 +28,9 @@ class ScriptCache : public QObject, public Dependency {
Q_OBJECT
SINGLETON_DEPENDENCY
using Mutex = std::mutex;
using Lock = std::unique_lock<Mutex>;
public:
void clearCache();
void getScriptContents(const QString& scriptOrURL, contentAvailableCallback contentAvailable, bool forceDownload = false);
@ -46,6 +50,7 @@ private slots:
private:
ScriptCache(QObject* parent = NULL);
Mutex _containerLock;
QMultiMap<QUrl, contentAvailableCallback> _contentCallbacks;
QHash<QUrl, QString> _scriptCache;

View file

@ -315,6 +315,7 @@ void ScriptEngine::init() {
registerGlobalObject("Entities", entityScriptingInterface.data());
registerGlobalObject("Quat", &_quatLibrary);
registerGlobalObject("Vec3", &_vec3Library);
registerGlobalObject("Mat4", &_mat4Library);
registerGlobalObject("Uuid", &_uuidLibrary);
registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCache>().data());
registerGlobalObject("Messages", DependencyManager::get<MessagesClient>().data());

View file

@ -34,6 +34,7 @@
#include "AssetScriptingInterface.h"
#include "AudioScriptingInterface.h"
#include "Quat.h"
#include "Mat4.h"
#include "ScriptCache.h"
#include "ScriptUUID.h"
#include "Vec3.h"
@ -199,6 +200,7 @@ protected:
QString _fileNameString;
Quat _quatLibrary;
Vec3 _vec3Library;
Mat4 _mat4Library;
ScriptUUID _uuidLibrary;
std::atomic<bool> _isUserLoaded { false };
bool _isReloading { false };

View file

@ -376,6 +376,15 @@ glm::mat4 createMatFromQuatAndPos(const glm::quat& q, const glm::vec3& p) {
return m;
}
// create matrix from a non-uniform scale, orientation and position
glm::mat4 createMatFromScaleQuatAndPos(const glm::vec3& scale, const glm::quat& rot, const glm::vec3& trans) {
glm::vec3 xAxis = rot * glm::vec3(scale.x, 0.0f, 0.0f);
glm::vec3 yAxis = rot * glm::vec3(0.0f, scale.y, 0.0f);
glm::vec3 zAxis = rot * glm::vec3(0.0f, 0.0f, scale.z);
return glm::mat4(glm::vec4(xAxis, 0.0f), glm::vec4(yAxis, 0.0f),
glm::vec4(zAxis, 0.0f), glm::vec4(trans, 1.0f));
}
// cancel out roll and pitch
glm::quat cancelOutRollAndPitch(const glm::quat& q) {
glm::vec3 zAxis = q * glm::vec3(0.0f, 0.0f, 1.0f);

View file

@ -212,6 +212,7 @@ glm::detail::tvec4<T, P> lerp(const glm::detail::tvec4<T, P>& x, const glm::deta
}
glm::mat4 createMatFromQuatAndPos(const glm::quat& q, const glm::vec3& p);
glm::mat4 createMatFromScaleQuatAndPos(const glm::vec3& scale, const glm::quat& rot, const glm::vec3& trans);
glm::quat cancelOutRollAndPitch(const glm::quat& q);
glm::mat4 cancelOutRollAndPitch(const glm::mat4& m);
glm::vec3 transformPoint(const glm::mat4& m, const glm::vec3& p);

View file

@ -34,6 +34,7 @@ static int collisionMetaTypeId = qRegisterMetaType<Collision>();
static int qMapURLStringMetaTypeId = qRegisterMetaType<QMap<QUrl,QString>>();
void registerMetaTypes(QScriptEngine* engine) {
qScriptRegisterMetaType(engine, mat4toScriptValue, mat4FromScriptValue);
qScriptRegisterMetaType(engine, vec4toScriptValue, vec4FromScriptValue);
qScriptRegisterMetaType(engine, vec3toScriptValue, vec3FromScriptValue);
qScriptRegisterMetaType(engine, qVectorVec3ToScriptValue, qVectorVec3FromScriptValue);
@ -53,6 +54,46 @@ void registerMetaTypes(QScriptEngine* engine) {
qScriptRegisterMetaType(engine, aaCubeToScriptValue, aaCubeFromScriptValue);
}
QScriptValue mat4toScriptValue(QScriptEngine* engine, const glm::mat4& mat4) {
QScriptValue obj = engine->newObject();
obj.setProperty("r0c0", mat4[0][0]);
obj.setProperty("r1c0", mat4[0][1]);
obj.setProperty("r2c0", mat4[0][2]);
obj.setProperty("r3c0", mat4[0][3]);
obj.setProperty("r0c1", mat4[1][0]);
obj.setProperty("r1c1", mat4[1][1]);
obj.setProperty("r2c1", mat4[1][2]);
obj.setProperty("r3c1", mat4[1][3]);
obj.setProperty("r0c2", mat4[2][0]);
obj.setProperty("r1c2", mat4[2][1]);
obj.setProperty("r2c2", mat4[2][2]);
obj.setProperty("r3c2", mat4[2][3]);
obj.setProperty("r0c3", mat4[3][0]);
obj.setProperty("r1c3", mat4[3][1]);
obj.setProperty("r2c3", mat4[3][2]);
obj.setProperty("r3c3", mat4[3][3]);
return obj;
}
void mat4FromScriptValue(const QScriptValue& object, glm::mat4& mat4) {
mat4[0][0] = object.property("r0c0").toVariant().toFloat();
mat4[0][1] = object.property("r1c0").toVariant().toFloat();
mat4[0][2] = object.property("r2c0").toVariant().toFloat();
mat4[0][3] = object.property("r3c0").toVariant().toFloat();
mat4[1][0] = object.property("r0c1").toVariant().toFloat();
mat4[1][1] = object.property("r1c1").toVariant().toFloat();
mat4[1][2] = object.property("r2c1").toVariant().toFloat();
mat4[1][3] = object.property("r3c1").toVariant().toFloat();
mat4[2][0] = object.property("r0c2").toVariant().toFloat();
mat4[2][1] = object.property("r1c2").toVariant().toFloat();
mat4[2][2] = object.property("r2c2").toVariant().toFloat();
mat4[2][3] = object.property("r3c2").toVariant().toFloat();
mat4[3][0] = object.property("r0c3").toVariant().toFloat();
mat4[3][1] = object.property("r1c3").toVariant().toFloat();
mat4[3][2] = object.property("r2c3").toVariant().toFloat();
mat4[3][3] = object.property("r3c3").toVariant().toFloat();
}
QScriptValue vec4toScriptValue(QScriptEngine* engine, const glm::vec4& vec4) {
QScriptValue obj = engine->newObject();
obj.setProperty("x", vec4.x);

View file

@ -28,6 +28,7 @@ Q_DECLARE_METATYPE(glm::vec4)
Q_DECLARE_METATYPE(glm::vec3)
Q_DECLARE_METATYPE(glm::vec2)
Q_DECLARE_METATYPE(glm::quat)
Q_DECLARE_METATYPE(glm::mat4)
Q_DECLARE_METATYPE(xColor)
Q_DECLARE_METATYPE(QVector<glm::vec3>)
Q_DECLARE_METATYPE(QVector<float>)
@ -35,6 +36,10 @@ Q_DECLARE_METATYPE(AACube)
void registerMetaTypes(QScriptEngine* engine);
// Mat4
QScriptValue mat4toScriptValue(QScriptEngine* engine, const glm::mat4& mat4);
void mat4FromScriptValue(const QScriptValue& object, glm::mat4& mat4);
// Vec4
QScriptValue vec4toScriptValue(QScriptEngine* engine, const glm::vec4& vec4);
void vec4FromScriptValue(const QScriptValue& object, glm::vec4& vec4);

View file

@ -9,6 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "SharedUtil.h"
#include <cassert>
#include <cstdlib>
#include <cstdio>
@ -33,7 +35,6 @@
#include "NumericalConstants.h"
#include "OctalCode.h"
#include "SharedLogging.h"
#include "SharedUtil.h"
static int usecTimestampNowAdjust = 0; // in usec
void usecTimestampNowForceClockSkew(int clockSkew) {

View file

@ -24,12 +24,33 @@
#include <QtCore/QDebug>
#include <QtCore/QCoreApplication>
// Access to the global instance pointer to enable setting / unsetting
template <typename T>
std::unique_ptr<T>& globalInstancePointer() {
static std::unique_ptr<T> instancePtr;
return instancePtr;
}
template <typename T>
void setGlobalInstance(const char* propertyName, T* instance) {
globalInstancePointer<T>().reset(instance);
}
template <typename T>
bool destroyGlobalInstance() {
std::unique_ptr<T>& instancePtr = globalInstancePointer<T>();
if (instancePtr.get()) {
instancePtr.reset();
return true;
}
return false;
}
// Provides efficient access to a named global type. By storing the value
// in the QApplication by name we can implement the singleton pattern and
// have the single instance function across DLL boundaries.
template <typename T, typename... Args>
T* globalInstance(const char* propertyName, Args&&... args) {
static std::unique_ptr<T> instancePtr;
static T* resultInstance { nullptr };
static std::mutex mutex;
if (!resultInstance) {
@ -37,10 +58,12 @@ T* globalInstance(const char* propertyName, Args&&... args) {
if (!resultInstance) {
auto variant = qApp->property(propertyName);
if (variant.isNull()) {
// Since we're building the object, store it in a shared_ptr so it's
// destroyed by the destructor of the static instancePtr
instancePtr = std::unique_ptr<T>(new T(std::forward<Args>(args)...));
std::unique_ptr<T>& instancePtr = globalInstancePointer<T>();
if (!instancePtr.get()) {
// Since we're building the object, store it in a shared_ptr so it's
// destroyed by the destructor of the static instancePtr
instancePtr = std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
void* voidInstance = &(*instancePtr);
variant = QVariant::fromValue(voidInstance);
qApp->setProperty(propertyName, variant);
@ -52,6 +75,7 @@ T* globalInstance(const char* propertyName, Args&&... args) {
return resultInstance;
}
const int BYTES_PER_COLOR = 3;
const int BYTES_PER_FLAGS = 1;
typedef unsigned char colorPart;

View file

@ -9,6 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "MainWindow.h"
#include <QApplication>
#include <QDesktopWidget>
#include <QEvent>
@ -20,10 +22,8 @@
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QMimeData>
#include <QDebug>
#include "MainWindow.h"
#include "Menu.h"
#include "Util.h"
MainWindow::MainWindow(QWidget* parent) :
QMainWindow(parent),
@ -33,6 +33,10 @@ MainWindow::MainWindow(QWidget* parent) :
setAcceptDrops(true);
}
MainWindow::~MainWindow() {
qDebug() << "Destroying main window";
}
void MainWindow::restoreGeometry() {
// Did not use setGeometry() on purpose,
// see http://doc.qt.io/qt-5/qsettings.html#restoring-the-state-of-a-gui-application

View file

@ -20,6 +20,7 @@ class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(QWidget* parent = NULL);
~MainWindow();
public slots:
void restoreGeometry();

View file

@ -326,8 +326,8 @@ QVariant OffscreenUi::inputDialog(const Icon icon, const QString& title, const Q
QVariant result;
QMetaObject::invokeMethod(this, "inputDialog", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QVariant, result),
Q_ARG(QString, title),
Q_ARG(Icon, icon),
Q_ARG(QString, title),
Q_ARG(QString, label),
Q_ARG(QVariant, current));
return result;

View file

@ -36,7 +36,16 @@ public:
const QUuid uuid{ QUuid::createUuid() };
static MenuUserData* forObject(QObject* object) {
return static_cast<MenuUserData*>(object->userData(USER_DATA_ID));
if (!object) {
qWarning() << "Attempted to fetch MenuUserData for null object";
return nullptr;
}
auto result = static_cast<MenuUserData*>(object->userData(USER_DATA_ID));
if (!result) {
qWarning() << "Unable to find MenuUserData for object " << object;
return nullptr;
}
return result;
}
private:
@ -105,6 +114,9 @@ void VrMenu::addMenu(QMenu* menu) {
QObject* qmlParent = nullptr;
if (dynamic_cast<QMenu*>(parent)) {
MenuUserData* userData = MenuUserData::forObject(parent);
if (!userData) {
return;
}
qmlParent = findMenuObject(userData->uuid.toString());
} else if (dynamic_cast<QMenuBar*>(parent)) {
qmlParent = _rootMenu;
@ -119,6 +131,10 @@ void VrMenu::addMenu(QMenu* menu) {
Q_UNUSED(invokeResult); // FIXME - apparently we haven't upgraded the Qt on our unix Jenkins environments to 5.5.x
QObject* result = returnedValue.value<QObject*>();
Q_ASSERT(result);
if (!result) {
qWarning() << "Unable to create QML menu for widget menu: " << menu->title();
return;
}
// Bind the QML and Widget together
new MenuUserData(menu, result);
@ -155,6 +171,9 @@ void VrMenu::addAction(QMenu* menu, QAction* action) {
Q_ASSERT(!MenuUserData::forObject(action));
Q_ASSERT(MenuUserData::forObject(menu));
MenuUserData* userData = MenuUserData::forObject(menu);
if (!userData) {
return;
}
QObject* menuQml = findMenuObject(userData->uuid.toString());
Q_ASSERT(menuQml);
QQuickMenuItem* returnedValue { nullptr };
@ -176,6 +195,9 @@ void VrMenu::insertAction(QAction* before, QAction* action) {
{
MenuUserData* beforeUserData = MenuUserData::forObject(before);
Q_ASSERT(beforeUserData);
if (!beforeUserData) {
return;
}
beforeQml = findMenuObject(beforeUserData->uuid.toString());
}
QObject* menu = beforeQml->parent();

View file

@ -18,15 +18,8 @@
#include "Logging.h"
using namespace ui;
static const char* const MENU_PROPERTY_NAME = "com.highfidelity.Menu";
Menu* Menu::getInstance() {
static Menu* instance = globalInstance<Menu>(MENU_PROPERTY_NAME);
return instance;
}
Menu::Menu() {
}
Menu::Menu() {}
void Menu::toggleAdvancedMenus() {
setGroupingIsVisible("Advanced", !getGroupingIsVisible("Advanced"));
@ -223,7 +216,7 @@ void Menu::removeAction(MenuWrapper* menu, const QString& actionName) {
void Menu::setIsOptionChecked(const QString& menuOption, bool isChecked) {
if (thread() != QThread::currentThread()) {
QMetaObject::invokeMethod(Menu::getInstance(), "setIsOptionChecked", Qt::BlockingQueuedConnection,
QMetaObject::invokeMethod(this, "setIsOptionChecked", Qt::BlockingQueuedConnection,
Q_ARG(const QString&, menuOption),
Q_ARG(bool, isChecked));
return;
@ -275,7 +268,7 @@ QAction* Menu::getActionFromName(const QString& menuName, MenuWrapper* menu) {
MenuWrapper* Menu::getSubMenuFromName(const QString& menuName, MenuWrapper* menu) {
QAction* action = getActionFromName(menuName, menu);
if (action) {
return MenuWrapper::fromMenu(action->menu());
return _backMap[action->menu()];
}
return NULL;
}
@ -320,7 +313,7 @@ QAction* Menu::getMenuAction(const QString& menuName) {
if (!action) {
break;
}
parent = MenuWrapper::fromMenu(action->menu());
parent = _backMap[action->menu()];
}
return action;
}
@ -357,7 +350,7 @@ MenuWrapper* Menu::addMenu(const QString& menuName, const QString& grouping) {
menu = getSubMenuFromName(menuTreePart.trimmed(), addTo);
if (!menu) {
if (!addTo) {
menu = new MenuWrapper(QMenuBar::addMenu(menuTreePart.trimmed()));
menu = new MenuWrapper(*this, QMenuBar::addMenu(menuTreePart.trimmed()));
} else {
menu = addTo->addMenu(menuTreePart.trimmed());
}
@ -498,11 +491,11 @@ void Menu::removeActionGroup(const QString& groupName) {
removeMenu(groupName);
}
MenuWrapper::MenuWrapper(QMenu* menu) : _realMenu(menu) {
MenuWrapper::MenuWrapper(ui::Menu& rootMenu, QMenu* menu) : _rootMenu(rootMenu), _realMenu(menu) {
VrMenu::executeOrQueue([=](VrMenu* vrMenu) {
vrMenu->addMenu(menu);
});
_backMap[menu] = this;
_rootMenu._backMap[menu] = this;
}
QList<QAction*> MenuWrapper::actions() {
@ -510,7 +503,7 @@ QList<QAction*> MenuWrapper::actions() {
}
MenuWrapper* MenuWrapper::addMenu(const QString& menuName) {
return new MenuWrapper(_realMenu->addMenu(menuName));
return new MenuWrapper(_rootMenu, _realMenu->addMenu(menuName));
}
void MenuWrapper::setEnabled(bool enabled) {
@ -558,4 +551,3 @@ void MenuWrapper::insertAction(QAction* before, QAction* action) {
});
}
QHash<QMenu*, MenuWrapper*> MenuWrapper::_backMap;

View file

@ -41,14 +41,9 @@ public:
}
private:
MenuWrapper(QMenu* menu);
static MenuWrapper* fromMenu(QMenu* menu) {
return _backMap[menu];
}
MenuWrapper(ui::Menu& rootMenu, QMenu* menu);
ui::Menu& _rootMenu;
QMenu* const _realMenu;
static QHash<QMenu*, MenuWrapper*> _backMap;
friend class ui::Menu;
};
@ -60,7 +55,6 @@ public:
static const int UNSPECIFIED_POSITION = -1;
Menu();
static Menu* getInstance();
void loadSettings();
void saveSettings();
@ -146,8 +140,10 @@ protected:
bool isValidGrouping(const QString& grouping) const { return grouping == "Advanced" || grouping == "Developer"; }
QHash<QString, bool> _groupingVisible;
QHash<QString, QSet<QAction*>> _groupingActions;
QHash<QMenu*, MenuWrapper*> _backMap;
static bool _isSomeSubmenuShown;
friend class ::MenuWrapper;
};
} // namespace ui

View file

@ -8,6 +8,6 @@
set(TARGET_NAME hifiNeuron)
setup_hifi_plugin(Script Qml Widgets)
link_hifi_libraries(shared controllers plugins input-plugins)
link_hifi_libraries(shared controllers ui plugins input-plugins)
target_neuron()

View file

@ -8,5 +8,5 @@
set(TARGET_NAME hifiSdl2)
setup_hifi_plugin(Script Qml Widgets)
link_hifi_libraries(shared controllers plugins input-plugins script-engine)
link_hifi_libraries(shared controllers ui plugins input-plugins script-engine)
target_sdl2()

View file

@ -8,5 +8,5 @@
set(TARGET_NAME hifiSixense)
setup_hifi_plugin(Script Qml Widgets)
link_hifi_libraries(shared controllers plugins input-plugins)
link_hifi_libraries(shared controllers ui plugins input-plugins)
target_sixense()

View file

@ -13,7 +13,7 @@ if (WIN32)
set(TARGET_NAME oculus)
setup_hifi_plugin()
link_hifi_libraries(shared gl gpu controllers plugins display-plugins input-plugins)
link_hifi_libraries(shared gl gpu controllers ui plugins display-plugins input-plugins)
include_hifi_library_headers(octree)

View file

@ -11,7 +11,7 @@ if (WIN32)
add_definitions(-DGLEW_STATIC)
set(TARGET_NAME openvr)
setup_hifi_plugin(OpenGL Script Qml Widgets)
link_hifi_libraries(shared gl networking controllers
link_hifi_libraries(shared gl networking controllers ui
plugins display-plugins input-plugins script-engine
render-utils model gpu render model-networking fbx)

View file

@ -81,26 +81,17 @@ class PluginContainerProxy : public QObject, PluginContainer {
Q_OBJECT
public:
virtual ~PluginContainerProxy() {}
virtual void addMenu(const QString& menuName) override {}
virtual void removeMenu(const QString& menuName) override {}
virtual QAction* addMenuItem(PluginType type, const QString& path, const QString& name, std::function<void(bool)> onClicked, bool checkable = false, bool checked = false, const QString& groupName = "") override { return nullptr; }
virtual void removeMenuItem(const QString& menuName, const QString& menuItem) override {}
virtual bool isOptionChecked(const QString& name) override { return false; }
virtual void setIsOptionChecked(const QString& path, bool checked) override {}
virtual void setFullscreen(const QScreen* targetScreen, bool hideMenu = true) override {}
virtual void unsetFullscreen(const QScreen* avoidScreen = nullptr) override {}
virtual void showDisplayPluginsTools() override {}
virtual void requestReset() override {}
virtual bool makeRenderingContextCurrent() override { return true; }
virtual void releaseSceneTexture(const gpu::TexturePointer& texture) override {}
virtual void releaseOverlayTexture(const gpu::TexturePointer& texture) override {}
virtual GLWidget* getPrimaryWidget() override { return nullptr; }
virtual QWindow* getPrimaryWindow() override { return nullptr; }
virtual MainWindow* getPrimaryWindow() override { return nullptr; }
virtual QOpenGLContext* getPrimaryContext() override { return nullptr; }
virtual bool isForeground() override { return true; }
virtual ui::Menu* getPrimaryMenu() { return nullptr; }
virtual bool isForeground() override { return true; }
virtual const DisplayPlugin* getActiveDisplayPlugin() const override { return nullptr; }
virtual bool getBoolSetting(const QString& settingName, bool defaultValue) override { return defaultValue; }
virtual void setBoolSetting(const QString& settingName, bool value) override { }
};
class MyControllerScriptingInterface : public controller::ScriptingInterface {

View file

@ -24,7 +24,7 @@
this.initialize = function(entityId) {
var properties = Entities.getEntityProperties(entityId);
if (properties.userData.length === 0 || properties.hasOwnProperty('userData') === false) {
if (properties.userData.length === 0 || properties.hasOwnProperty('userData') === false || properties.userData==="") {
self.initTimeout = Script.setTimeout(function() {
// print(' no user data yet, try again in one second')
self.initialize(entityId);

File diff suppressed because one or more lines are too long

View file

@ -8,7 +8,7 @@
var numDynein = 2;
var numKinesin = 2;
var percentOnMainMT = 100;
//print('RUNNING AC!!');
var baseLocation;
if (USE_LOCAL_HOST === true) {
baseLocation = "http://localhost:8080/";
@ -16,13 +16,32 @@ if (USE_LOCAL_HOST === true) {
baseLocation = "https://hifi-content.s3.amazonaws.com/DomainContent/CellScience/"
}
var WORLD_OFFSET = {
x: 0,
y: 0,
z: 0
}
var WORLD_SCALE_AMOUNT = 1.0;
function offsetVectorToWorld(vector) {
var newVector;
newVector = Vec3.sum(vector, WORLD_OFFSET);
print('JBP NEW VECTOR IS:: ' + JSON.stringify(newVector))
return newVector
}
var USE_LOCAL_HOST = false;
EntityViewer.setPosition({
var basePosition = offsetVectorToWorld({
x: 3000,
y: 13500,
z: 3000
});
EntityViewer.setPosition(basePosition);
EntityViewer.setKeyholeRadius(60000);
var octreeQueryInterval = Script.setInterval(function() {
EntityViewer.queryOctree();
@ -44,11 +63,12 @@ var scriptURL = this.baseLocation + "Scripts/clickToRideAndLook.js?" + Math.rand
var t = 0;
var tInc = 0.001;
var sceneOffset = {
var sceneOffset = offsetVectorToWorld({
x: 3000,
y: 13500,
z: 3000
};
});
var yOffset = {
dynein: 16,
kinesin: -28
@ -60,11 +80,11 @@ var terms;
var secondaryInit = false;
function deleteAllMotorProteins() {
var position = {
var position = offsetVectorToWorld({
x: 3280,
y: 13703,
z: 4405
};
});
if (secondaryInit === true) {
return;
@ -83,7 +103,7 @@ function deleteAllMotorProteins() {
results.forEach(function(r) {
var name = Entities.getEntityProperties(r, 'name').name;
if (name.indexOf('Hifi-Motor-Protein-Anchor') > -1) {
// print('Script.clearTimeout DELETING A MOTOR PROTEIN::' + r)
// print('Script.clearTimeout DELETING A MOTOR PROTEIN::' + r)
Entities.deleteEntity(r);
}
@ -95,7 +115,7 @@ function deleteAllMotorProteins() {
}
function makeAll() {
// print('CREATING MOTOR PROTEINS')
// print('CREATING MOTOR PROTEINS')
var segment;
var segments = shuffleSegments();
var lastSegment = [];
@ -195,11 +215,11 @@ function update(deltaTime) {
print("servers exist -- makeAll...");
Entities.setPacketsPerSecond(6000);
print("PPS:" + Entities.getPacketsPerSecond());
Script.setTimeout(function(){
Script.setTimeout(function() {
//print('SETTING TIMEOUT')
deleteAllMotorProteins()
},10000)
deleteAllMotorProteins()
}, 10000)
initialized = true;
}
return;

View file

@ -4,12 +4,29 @@
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var WORLD_OFFSET = {
x: 0,
y: 0,
z: 0
}
var basePosition = {
var WORLD_SCALE_AMOUNT = 1.0;
function offsetVectorToWorld(vector) {
var newVector;
newVector = Vec3.sum(vector, WORLD_OFFSET);
print('JBP NEW VECTOR IS:: ' + JSON.stringify(newVector))
return newVector
}
var basePosition = offsetVectorToWorld({
x: 3000,
y: 13500,
z: 3000
};
}, WORLD_OFFSET);
var initialized = false;

View file

@ -4,12 +4,29 @@
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var WORLD_OFFSET = {
x: 0,
y: 0,
z: 0
}
var basePosition = {
var WORLD_SCALE_AMOUNT = 1.0;
function offsetVectorToWorld(vector) {
var newVector;
newVector = Vec3.sum(vector, WORLD_OFFSET);
print('JBP NEW VECTOR IS:: ' + JSON.stringify(newVector))
return newVector
}
var basePosition = offsetVectorToWorld({
x: 3000,
y: 13500,
z: 3000
};
}, WORLD_OFFSET);
var initialized = false;

View file

@ -0,0 +1,452 @@
//
// createTank.js
//
//
// created by James b. Pollack @imgntn on 3/9/2016
// Copyright 2016 High Fidelity, Inc.
//
// Adds a fish tank and base, decorations, particle bubble systems, and a bubble sound. Attaches a script that does fish swimming.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var fishTank, tankBase, bubbleSystem, secondBubbleSystem, thirdBubbleSystem, innerContainer, bubbleInjector, lowerCorner, upperCorner, urchin, treasure, rocks;
var CLEANUP = true;
var TANK_DIMENSIONS = {
x: 0.8212,
y: 0.8116,
z: 2.1404
};
var INNER_TANK_SCALE = 0.7;
var INNER_TANK_DIMENSIONS = Vec3.multiply(INNER_TANK_SCALE, TANK_DIMENSIONS);
INNER_TANK_DIMENSIONS.y = INNER_TANK_DIMENSIONS.y - 0.4;
var TANK_WIDTH = TANK_DIMENSIONS.z;
var TANK_HEIGHT = TANK_DIMENSIONS.y;
var DEBUG_COLOR = {
red: 255,
green: 0,
blue: 255
}
var center = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), 1 * TANK_WIDTH));
var TANK_POSITION = center;
var TANK_SCRIPT = Script.resolvePath('tank.js?' + Math.random())
var TANK_MODEL_URL = "http://hifi-content.s3.amazonaws.com/DomainContent/Home/fishTank/aquariumTank.fbx";
var TANK_BASE_MODEL_URL = 'http://hifi-content.s3.amazonaws.com/DomainContent/Home/fishTank/aquariumBase.fbx';
var TANK_BASE_COLLISION_HULL = 'http://hifi-content.s3.amazonaws.com/DomainContent/Home/fishTank/aquariumBase.obj'
var TANK_BASE_DIMENSIONS = {
x: 0.8599,
y: 1.8450,
z: 2.1936
};
var BASE_VERTICAL_OFFSET = 0.42;
var BUBBLE_SYSTEM_FORWARD_OFFSET = TANK_DIMENSIONS.x + 0.06;
var BUBBLE_SYSTEM_LATERAL_OFFSET = 0.025;
var BUBBLE_SYSTEM_VERTICAL_OFFSET = -0.30;
var BUBBLE_SYSTEM_DIMENSIONS = {
x: TANK_DIMENSIONS.x / 8,
y: TANK_DIMENSIONS.y,
z: TANK_DIMENSIONS.z / 8
}
var BUBBLE_SOUND_URL = "http://hifi-content.s3.amazonaws.com/DomainContent/Home/Sounds/aquarium_small.L.wav";
var bubbleSound = SoundCache.getSound(BUBBLE_SOUND_URL);
var URCHIN_FORWARD_OFFSET = TANK_DIMENSIONS.x - 0.35;
var URCHIN_LATERAL_OFFSET = -0.05;
var URCHIN_VERTICAL_OFFSET = -0.12;
var URCHIN_MODEL_URL = 'http://hifi-content.s3.amazonaws.com/DomainContent/Home/fishTank/Urchin.fbx';
var URCHIN_DIMENSIONS = {
x: 0.4,
y: 0.4,
z: 0.4
}
var ROCKS_FORWARD_OFFSET = 0;
var ROCKS_LATERAL_OFFSET = 0.0;
var ROCKS_VERTICAL_OFFSET = (-TANK_DIMENSIONS.y / 2) + 0.25;
var ROCK_MODEL_URL = 'http://hifi-content.s3.amazonaws.com/DomainContent/Home/fishTank/Aquarium-Rocks-2.fbx';
var ROCK_DIMENSIONS = {
x: 0.707,
y: 0.33,
z: 1.64
}
var TREASURE_FORWARD_OFFSET = -TANK_DIMENSIONS.x;
var TREASURE_LATERAL_OFFSET = -0.15;
var TREASURE_VERTICAL_OFFSET = -0.23;
var TREASURE_MODEL_URL = 'http://hifi-content.s3.amazonaws.com/DomainContent/Home/fishTank/Treasure-Chest2-SM.fbx';
var TREASURE_DIMENSIONS = {
x: 0.1199,
y: 0.1105,
z: 0.1020
}
function createFishTank() {
var tankProperties = {
name: 'hifi-home-fishtank',
type: 'Model',
modelURL: TANK_MODEL_URL,
dimensions: TANK_DIMENSIONS,
position: TANK_POSITION,
color: DEBUG_COLOR,
collisionless: true,
script: TANK_SCRIPT,
visible: true
}
fishTank = Entities.addEntity(tankProperties);
}
function createBubbleSystems() {
var tankProperties = Entities.getEntityProperties(fishTank);
var bubbleProperties = {
"name": 'hifi-home-fishtank-bubbles',
"isEmitting": 1,
"maxParticles": 1880,
"lifespan": 1.6,
"emitRate": 10,
"emitSpeed": 0.025,
"speedSpread": 0.025,
"emitOrientation": {
"x": 0,
"y": 0.5,
"z": 0.5,
"w": 0
},
"emitDimensions": {
"x": -0.2,
"y": TANK_DIMENSIONS.y,
"z": 0
},
"polarStart": 0,
"polarFinish": 0,
"azimuthStart": 0.2,
"azimuthFinish": 0.1,
"emitAcceleration": {
"x": 0,
"y": 0.3,
"z": 0
},
"accelerationSpread": {
"x": 0.01,
"y": 0.01,
"z": 0.01
},
"particleRadius": 0.005,
"radiusSpread": 0,
"radiusStart": 0.01,
"radiusFinish": 0.01,
"alpha": 0.2,
"alphaSpread": 0,
"alphaStart": 0.3,
"alphaFinish": 0,
"emitterShouldTrail": 0,
"textures": "http://hifi-content.s3.amazonaws.com/DomainContent/Home/fishTank/bubble-white.png"
};
bubbleProperties.type = "ParticleEffect";
bubbleProperties.parentID = fishTank;
bubbleProperties.dimensions = BUBBLE_SYSTEM_DIMENSIONS;
var finalOffset = getOffsetFromTankCenter(BUBBLE_SYSTEM_VERTICAL_OFFSET, BUBBLE_SYSTEM_FORWARD_OFFSET, BUBBLE_SYSTEM_LATERAL_OFFSET);
bubbleProperties.position = finalOffset;
bubbleSystem = Entities.addEntity(bubbleProperties);
bubbleProperties.position.x += -0.076;
secondBubbleSystem = Entities.addEntity(bubbleProperties)
bubbleProperties.position.x += -0.076;
thirdBubbleSystem = Entities.addEntity(bubbleProperties)
createBubbleSound(finalOffset);
}
function getOffsetFromTankCenter(VERTICAL_OFFSET, FORWARD_OFFSET, LATERAL_OFFSET) {
var tankProperties = Entities.getEntityProperties(fishTank);
var upVector = Quat.getUp(tankProperties.rotation);
var frontVector = Quat.getFront(tankProperties.rotation);
var rightVector = Quat.getRight(tankProperties.rotation);
var upOffset = Vec3.multiply(upVector, VERTICAL_OFFSET);
var frontOffset = Vec3.multiply(frontVector, FORWARD_OFFSET);
var rightOffset = Vec3.multiply(rightVector, LATERAL_OFFSET);
var finalOffset = Vec3.sum(tankProperties.position, upOffset);
finalOffset = Vec3.sum(finalOffset, frontOffset);
finalOffset = Vec3.sum(finalOffset, rightOffset);
return finalOffset
}
function createBubbleSound(position) {
var audioProperties = {
volume: 0.05,
position: position,
loop: true
};
bubbleInjector = Audio.playSound(bubbleSound, audioProperties);
}
function createInnerContainer(position) {
var tankProperties = Entities.getEntityProperties(fishTank);
var containerProps = {
name: "hifi-home-fishtank-inner-container",
type: 'Box',
color: {
red: 0,
green: 0,
blue: 255
},
parentID: fishTank,
dimensions: INNER_TANK_DIMENSIONS,
position: tankProperties.position,
visible: false,
collisionless: true,
dynamic: false
};
innerContainer = Entities.addEntity(containerProps);
}
function createEntitiesAtCorners() {
var bounds = Entities.getEntityProperties(innerContainer, "boundingBox").boundingBox;
var lowerProps = {
name: 'hifi-home-fishtank-lower-corner',
type: "Box",
parentID: fishTank,
dimensions: {
x: 0.2,
y: 0.2,
z: 0.2
},
color: {
red: 255,
green: 0,
blue: 0
},
collisionless: true,
position: bounds.brn,
visible: false
}
var upperProps = {
name: 'hifi-home-fishtank-upper-corner',
type: "Box",
parentID: fishTank,
dimensions: {
x: 0.2,
y: 0.2,
z: 0.2
},
color: {
red: 0,
green: 255,
blue: 0
},
collisionless: true,
position: bounds.tfl,
visible: false
}
lowerCorner = Entities.addEntity(lowerProps);
upperCorner = Entities.addEntity(upperProps);
}
function createRocks() {
var finalPosition = getOffsetFromTankCenter(ROCKS_VERTICAL_OFFSET, ROCKS_FORWARD_OFFSET, ROCKS_LATERAL_OFFSET);
var properties = {
name: 'hifi-home-fishtank-rock',
type: 'Model',
parentID: fishTank,
modelURL: ROCK_MODEL_URL,
position: finalPosition,
dimensions: ROCK_DIMENSIONS
}
rocks = Entities.addEntity(properties);
}
function createUrchin() {
var finalPosition = getOffsetFromTankCenter(URCHIN_VERTICAL_OFFSET, URCHIN_FORWARD_OFFSET, URCHIN_LATERAL_OFFSET);
var properties = {
name: 'hifi-home-fishtank-urchin',
type: 'Model',
parentID: fishTank,
modelURL: URCHIN_MODEL_URL,
position: finalPosition,
shapeType: 'Sphere',
dimensions: URCHIN_DIMENSIONS
}
urchin = Entities.addEntity(properties);
}
function createTreasureChest() {
var finalPosition = getOffsetFromTankCenter(TREASURE_VERTICAL_OFFSET, TREASURE_FORWARD_OFFSET, TREASURE_LATERAL_OFFSET);
var properties = {
name: 'hifi-home-fishtank-treasure-chest',
type: 'Model',
parentID: fishTank,
modelURL: TREASURE_MODEL_URL,
position: finalPosition,
dimensions: TREASURE_DIMENSIONS,
rotation: Quat.fromPitchYawRollDegrees(10, -45, 10)
}
treasure = Entities.addEntity(properties);
}
function createTankBase() {
var properties = {
name: 'hifi-home-fishtank-base',
type: 'Model',
modelURL: TANK_BASE_MODEL_URL,
parentID: fishTank,
shapeType: 'compound',
compoundShapeURL: TANK_BASE_COLLISION_HULL,
position: {
x: TANK_POSITION.x,
y: TANK_POSITION.y - BASE_VERTICAL_OFFSET,
z: TANK_POSITION.z
},
dimensions: TANK_BASE_DIMENSIONS
}
tankBase = Entities.addEntity(properties);
}
createFishTank();
createInnerContainer();
createBubbleSystems();
createEntitiesAtCorners();
createUrchin();
createRocks();
createTankBase();
createTreasureChest();
var customKey = 'hifi-home-fishtank';
var data = {
fishLoaded: false,
bubbleSystem: bubbleSystem,
bubbleSound: bubbleSound,
corners: {
brn: lowerCorner,
tfl: upperCorner
},
innerContainer: innerContainer,
}
Script.setTimeout(function() {
setEntityCustomData(customKey, fishTank, data);
}, 2000)
function cleanup() {
Entities.deleteEntity(fishTank);
Entities.deleteEntity(tankBase);
Entities.deleteEntity(bubbleSystem);
Entities.deleteEntity(secondBubbleSystem);
Entities.deleteEntity(thirdBubbleSystem);
Entities.deleteEntity(innerContainer);
Entities.deleteEntity(lowerCorner);
Entities.deleteEntity(upperCorner);
Entities.deleteEntity(urchin);
Entities.deleteEntity(rocks);
bubbleInjector.stop();
bubbleInjector = null;
}
if (CLEANUP === true) {
Script.scriptEnding.connect(cleanup);
}
function setEntityUserData(id, data) {
var json = JSON.stringify(data)
Entities.editEntity(id, {
userData: json
});
}
function getEntityUserData(id) {
var results = null;
var properties = Entities.getEntityProperties(id, "userData");
if (properties.userData) {
try {
results = JSON.parse(properties.userData);
} catch (err) {
// print('error parsing json');
// print('properties are:'+ properties.userData);
}
}
return results ? results : {};
}
// Non-destructively modify the user data of an entity.
function setEntityCustomData(customKey, id, data) {
var userData = getEntityUserData(id);
if (data == null) {
delete userData[customKey];
} else {
userData[customKey] = data;
}
setEntityUserData(id, userData);
}
function getEntityCustomData(customKey, id, defaultValue) {
var userData = getEntityUserData(id);
if (undefined != userData[customKey]) {
return userData[customKey];
} else {
return defaultValue;
}
}

View file

@ -0,0 +1,709 @@
//
// tank.js
//
//
// created by James b. Pollack @imgntn on 3/9/2016
// Copyright 2016 High Fidelity, Inc.
//
// looks for fish to swim around, if there are none it makes some. adds an attractor where the person simulating it is looking.
//
// 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('../../../../examples/libraries/virtualBaton.js');
//only one person should simulate the tank at a time -- we pass around a virtual baton
var baton;
var iOwn = false;
var _entityID;
var _this;
var connected = false;
var TANK_SEARCH_RADIUS = 5;
var WANT_LOOK_DEBUG_LINE = false;
var WANT_LOOK_DEBUG_SPHERE = false;
var INTERSECT_COLOR = {
red: 255,
green: 0,
blue: 255
}
function FishTank() {
_this = this;
}
function startUpdate() {
//when the baton is claimed;
// print('trying to claim the object' + _entityID)
iOwn = true;
connected = true;
Script.update.connect(_this.update);
}
function stopUpdateAndReclaim() {
//when the baton is released;
// print('i released the object ' + _entityID)
iOwn = false;
if (connected === true) {
connected = false;
Script.update.disconnect(_this.update);
_this.clearLookAttractor();
}
//hook up callbacks to the baton
baton.claim(startUpdate, stopUpdateAndReclaim);
}
FishTank.prototype = {
fish: null,
tankLocked: false,
hasLookAttractor: false,
lookAttractor: null,
overlayLine: null,
overlayLineDistance: 3,
debugSphere: null,
findFishInTank: function() {
// print('looking for a fish in the tank')
var results = Entities.findEntities(_this.currentProperties.position, TANK_SEARCH_RADIUS);
var fishList = [];
results.forEach(function(fish) {
var properties = Entities.getEntityProperties(fish, 'name');
if (properties.name.indexOf('hifi-fishtank-fish' + _this.entityID) > -1) {
fishList.push(fish);
}
})
// print('fish? ' + fishList.length)
return fishList;
},
initialize: function(entityID) {
var properties = Entities.getEntityProperties(entityID)
if (properties.hasOwnProperty('userData') === false || properties.userData.length === 0) {
_this.initTimeout = Script.setTimeout(function() {
if (properties.hasOwnProperty('userData')) {
// print('has user data property')
}
if (properties.userData.length === 0) {
// print('user data length is zero')
}
// print('try again in one second')
_this.initialize(entityID);
}, 1000)
} else {
// print('userdata before parse attempt' + properties.userData)
_this.userData = null;
try {
_this.userData = JSON.parse(properties.userData);
} catch (err) {
// print('error parsing json');
// print('properties are:' + properties.userData);
return;
}
// print('after parse')
_this.currentProperties = Entities.getEntityProperties(entityID);
baton = virtualBaton({
batonName: 'io.highfidelity.fishtank:' + entityID, // One winner for each entity
});
stopUpdateAndReclaim();
}
},
preload: function(entityID) {
// print("preload");
this.entityID = entityID;
_entityID = entityID;
this.initialize(entityID);
this.initTimeout = null;
},
unload: function() {
// print(' UNLOAD')
if (connected === true) {
Script.update.disconnect(_this.update);
}
if (WANT_LOOK_DEBUG_LINE === true) {
_this.overlayLineOff();
}
if (baton) {
// print('BATON RELEASE ')
baton.release(function() {});
}
},
update: function(deltaTime) {
if (iOwn === false) {
// print('i dont own')
//exit if we're not supposed to be simulating the fish
return
}
// print('i am the owner!')
//do stuff
updateFish(deltaTime);
_this.seeIfOwnerIsLookingAtTheTank();
},
debugSphereOn: function(position) {
if (_this.debugSphere !== null) {
Entities.editEntity(_this.debugSphere, {
visible: true
})
return;
}
var sphereProperties = {
type: 'Sphere',
parentID: _this.entityID,
dimensions: {
x: 0.1,
y: 0.1,
z: 0.1,
},
color: INTERSECT_COLOR,
position: position,
collisionless: true
}
_this.debugSphere = Entities.addEntity(sphereProperties);
},
updateDebugSphere: function(position) {
Entities.editEntity(_this.debugSphere, {
visible: true,
position: position
})
},
debugSphereOff: function() {
Entities.editEntity(_this.debugSphere, {
visible: false
})
},
overlayLineOn: function(closePoint, farPoint, color) {
if (_this.overlayLine === null) {
var lineProperties = {
lineWidth: 5,
start: closePoint,
end: farPoint,
color: color,
ignoreRayIntersection: true, // always ignore this
visible: true,
alpha: 1
};
this.overlayLine = Overlays.addOverlay("line3d", lineProperties);
} else {
var success = Overlays.editOverlay(_this.overlayLine, {
lineWidth: 5,
start: closePoint,
end: farPoint,
color: color,
visible: true,
ignoreRayIntersection: true, // always ignore this
alpha: 1
});
}
},
overlayLineOff: function() {
if (_this.overlayLine !== null) {
Overlays.deleteOverlay(this.overlayLine);
}
_this.overlayLine = null;
},
seeIfOwnerIsLookingAtTheTank: function() {
var cameraPosition = Camera.getPosition();
var cameraOrientation = Camera.getOrientation();
var front = Quat.getFront(cameraOrientation);
var pickRay = {
origin: cameraPosition,
direction: front
};
if (WANT_LOOK_DEBUG_LINE === true) {
_this.overlayLineOn(pickRay.origin, Vec3.sum(pickRay.origin, Vec3.multiply(front, _this.overlayLineDistance)), INTERSECT_COLOR);
};
var brn = _this.userData['hifi-home-fishtank']['corners'].brn;
var tfl = _this.userData['hifi-home-fishtank']['corners'].tfl;
var innerContainer = _this.userData['hifi-home-fishtank'].innerContainer;
var intersection = Entities.findRayIntersection(pickRay, true, [innerContainer], [_this.entityID, brn, tfl]);
if (intersection.intersects && intersection.entityID === innerContainer) {
//print('intersecting a tank')
if (WANT_LOOK_DEBUG_SPHERE === true) {
if (_this.debugSphere === null) {
_this.debugSphereOn(intersection.intersection);
} else {
_this.updateDebugSphere(intersection.intersection);
}
}
if (intersection.distance > LOOK_ATTRACTOR_DISTANCE) {
if (WANT_LOOK_DEBUG_SPHERE === true) {
_this.debugSphereOff();
}
return
}
// print('intersection:: ' + JSON.stringify(intersection));
if (_this.hasLookAttractor === false) {
_this.createLookAttractor(intersection.intersection, intersection.distance);
} else if (_this.hasLookAttractor === true) {
_this.updateLookAttractor(intersection.intersection, intersection.distance);
}
} else {
if (_this.hasLookAttractor === true) {
_this.clearLookAttractor();
}
}
},
createLookAttractor: function(position, distance) {
_this.lookAttractor = {
position: position,
distance: distance
};
_this.hasLookAttractor = true;
},
updateLookAttractor: function(position, distance) {
_this.lookAttractor = {
position: position,
distance: distance
};
},
clearLookAttractor: function() {
_this.hasLookAttractor = false;
_this.lookAttractor = null;
},
createLookAttractorEntity: function() {
},
findLookAttractorEntities: function() {
},
seeIfAnyoneIsLookingAtTheTank: function() {
// get avatars
// get their positions
// get their gazes
// check for intersection
// add attractor for closest person (?)
}
};
//
// flockOfFish.js
// examples
//
// Philip Rosedale
// Copyright 2016 High Fidelity, Inc.
// Fish smimming around in a space in front of you
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
var FISHTANK_USERDATA_KEY = 'hifi-home-fishtank'
var LIFETIME = 300; // Fish live for 5 minutes
var NUM_FISH = 8;
var TANK_DIMENSIONS = {
x: 1.3393,
y: 1.3515,
z: 3.5914
};
var TANK_WIDTH = TANK_DIMENSIONS.z / 2;
var TANK_HEIGHT = TANK_DIMENSIONS.y / 2;
var FISH_DIMENSIONS = {
x: 0.0149,
y: 0.02546,
z: 0.0823
}
var MAX_SIGHT_DISTANCE = 1.5;
var MIN_SEPARATION = 0.15;
var AVOIDANCE_FORCE = 0.032;
var COHESION_FORCE = 0.025;
var ALIGNMENT_FORCE = 0.025;
var LOOK_ATTRACTOR_FORCE = 0.02;
var LOOK_ATTRACTOR_DISTANCE = 1.75;
var SWIMMING_FORCE = 0.025;
var SWIMMING_SPEED = 0.5;
var FISH_DAMPING = 0.55;
var FISH_ANGULAR_DAMPING = 0.55;
var THROTTLE = false;
var THROTTLE_RATE = 100;
var sinceLastUpdate = 0;
// var FISH_MODEL_URL = "http://hifi-content.s3.amazonaws.com/DomainContent/Home/fishTank/Fish-1.fbx";
// var FISH_MODEL_TWO_URL = "http://hifi-content.s3.amazonaws.com/DomainContent/Home/fishTank/Fish-2.fbx";
var FISH_MODEL_URL = "http://hifi-content.s3.amazonaws.com/DomainContent/Home/fishTank/goodfish5.fbx";
var FISH_MODEL_TWO_URL = "http://hifi-content.s3.amazonaws.com/DomainContent/Home/fishTank/goodfish5.fbx";
var fishLoaded = false;
function randomVector(scale) {
return {
x: Math.random() * scale - scale / 2.0,
y: Math.random() * scale - scale / 2.0,
z: Math.random() * scale - scale / 2.0
};
}
function updateFish(deltaTime) {
// print('update loop')
if (_this.tankLocked === true) {
return;
}
if (!Entities.serversExist() || !Entities.canRez()) {
return;
}
if (THROTTLE === true) {
sinceLastUpdate = sinceLastUpdate + deltaTime * 100;
if (sinceLastUpdate > THROTTLE_RATE) {
sinceLastUpdate = 0;
} else {
return;
}
}
// print('has userdata fish??' + _this.userData['hifi-home-fishtank'].fishLoaded)
if (_this.userData['hifi-home-fishtank'].fishLoaded === false) {
//no fish in the user data
_this.tankLocked = true;
print('NO FISH YET SO LOAD EM!!!')
loadFish(NUM_FISH);
var data = {
fishLoaded: true,
bubbleSystem: _this.userData['hifi-home-fishtank'].bubbleSystem,
bubbleSound: _this.userData['hifi-home-fishtank'].bubbleSound,
corners: {
brn: _this.userData['hifi-home-fishtank'].lowerCorner,
tfl: _this.userData['hifi-home-fishtank'].upperCorner
},
innerContainer: _this.userData['hifi-home-fishtank'].innerContainer,
}
setEntityCustomData(FISHTANK_USERDATA_KEY, _this.entityID, data);
_this.userData['hifi-home-fishtank'].fishLoaded = true;
Script.setTimeout(function() {
_this.fish = _this.findFishInTank();
}, 2000)
return;
} else {
//fish in userdata already
if (_this.fish === null) {
_this.fish = _this.findFishInTank();
}
}
var fish = _this.fish;
// print('how many fish do i find?' + fish.length)
if (fish.length === 0) {
// print('no fish...')
return
};
var averageVelocity = {
x: 0,
y: 0,
z: 0
};
var averagePosition = {
x: 0,
y: 0,
z: 0
};
var userData = JSON.parse(_this.currentProperties.userData);
var innerContainer = userData['hifi-home-fishtank']['innerContainer'];
var bounds = Entities.getEntityProperties(innerContainer, "boundingBox").boundingBox;
lowerCorner = bounds.brn;
upperCorner = bounds.tfl;
// First pre-load an array with properties on all the other fish so our per-fish loop
// isn't doing it.
var flockProperties = [];
for (var i = 0; i < fish.length; i++) {
var otherProps = Entities.getEntityProperties(fish[i], ["position", "velocity", "rotation"]);
flockProperties.push(otherProps);
}
for (var i = 0; i < fish.length; i++) {
if (fish[i]) {
// Get only the properties we need, because that is faster
var properties = flockProperties[i];
// If fish has been deleted, bail
if (properties.id != fish[i]) {
fish[i] = false;
return;
}
// Store old values so we can check if they have changed enough to update
var velocity = {
x: properties.velocity.x,
y: properties.velocity.y,
z: properties.velocity.z
};
var position = {
x: properties.position.x,
y: properties.position.y,
z: properties.position.z
};
averageVelocity = {
x: 0,
y: 0,
z: 0
};
averagePosition = {
x: 0,
y: 0,
z: 0
};
var othersCounted = 0;
for (var j = 0; j < fish.length; j++) {
if (i != j) {
// Get only the properties we need, because that is faster
var otherProps = flockProperties[j];
var separation = Vec3.distance(properties.position, otherProps.position);
if (separation < MAX_SIGHT_DISTANCE) {
averageVelocity = Vec3.sum(averageVelocity, otherProps.velocity);
averagePosition = Vec3.sum(averagePosition, otherProps.position);
othersCounted++;
}
if (separation < MIN_SEPARATION) {
var pushAway = Vec3.multiply(Vec3.normalize(Vec3.subtract(properties.position, otherProps.position)), AVOIDANCE_FORCE);
velocity = Vec3.sum(velocity, pushAway);
}
}
}
if (othersCounted > 0) {
averageVelocity = Vec3.multiply(averageVelocity, 1.0 / othersCounted);
averagePosition = Vec3.multiply(averagePosition, 1.0 / othersCounted);
// Alignment: Follow group's direction and speed
velocity = Vec3.mix(velocity, Vec3.multiply(Vec3.normalize(averageVelocity), Vec3.length(velocity)), ALIGNMENT_FORCE);
// Cohesion: Steer towards center of flock
var towardCenter = Vec3.subtract(averagePosition, position);
velocity = Vec3.mix(velocity, Vec3.multiply(Vec3.normalize(towardCenter), Vec3.length(velocity)), COHESION_FORCE);
//attractors
//[position, radius, force]
}
if (_this.hasLookAttractor === true) {
//print('has a look attractor, so use it')
var attractorPosition = _this.lookAttractor.position;
var towardAttractor = Vec3.subtract(attractorPosition, position);
velocity = Vec3.mix(velocity, Vec3.multiply(Vec3.normalize(towardAttractor), Vec3.length(velocity)), LOOK_ATTRACTOR_FORCE);
}
// Try to swim at a constant speed
velocity = Vec3.mix(velocity, Vec3.multiply(Vec3.normalize(velocity), SWIMMING_SPEED), SWIMMING_FORCE);
// Keep fish in their 'tank'
if (position.x < lowerCorner.x) {
position.x = lowerCorner.x;
velocity.x *= -0.15
} else if (position.x > upperCorner.x) {
position.x = upperCorner.x;
velocity.x *= -0.15
}
if (position.y < lowerCorner.y) {
position.y = lowerCorner.y;
velocity.y *= -0.15
} else if (position.y > upperCorner.y) {
position.y = upperCorner.y;
velocity.y *= -0.15
}
if (position.z < lowerCorner.z) {
position.z = lowerCorner.z;
velocity.z *= -0.15
} else if (position.z > upperCorner.z) {
position.z = upperCorner.z;
velocity.z *= -0.15
}
// Orient in direction of velocity
var rotation = Quat.rotationBetween(Vec3.UNIT_NEG_Z, velocity);
var mixedRotation = Quat.mix(properties.rotation, rotation, VELOCITY_FOLLOW_RATE);
var VELOCITY_FOLLOW_RATE = 0.30;
var slerpedRotation = Quat.slerp(properties.rotation, rotation, VELOCITY_FOLLOW_RATE);
var safeEuler = Quat.safeEulerAngles(rotation);
safeEuler.z = safeEuler.z *= 0.925;
//note: a we want the fish to both rotate toward its velocity and not roll over, and also not pitch more than 30 degrees positive or negative (not doing that last bit right quite yet)
var newQuat = Quat.fromPitchYawRollDegrees(safeEuler.x, safeEuler.y, safeEuler.z);
var finalQuat = Quat.slerp(slerpedRotation, newQuat, 0.5);
// Only update properties if they have changed, to save bandwidth
var MIN_POSITION_CHANGE_FOR_UPDATE = 0.001;
if (Vec3.distance(properties.position, position) < MIN_POSITION_CHANGE_FOR_UPDATE) {
Entities.editEntity(fish[i], {
velocity: velocity,
rotation: finalQuat
});
} else {
Entities.editEntity(fish[i], {
position: position,
velocity: velocity,
rotation: finalQuat
});
}
}
}
}
var STARTING_FRACTION = 0.25;
function loadFish(howMany) {
// print('LOADING FISH: ' + howMany)
var center = _this.currentProperties.position;
lowerCorner = {
x: center.x - (_this.currentProperties.dimensions.z / 2),
y: center.y,
z: center.z - (_this.currentProperties.dimensions.z / 2)
};
upperCorner = {
x: center.x + (_this.currentProperties.dimensions.z / 2),
y: center.y + _this.currentProperties.dimensions.y,
z: center.z + (_this.currentProperties.dimensions.z / 2)
};
var fish = [];
for (var i = 0; i < howMany; i++) {
var position = {
x: lowerCorner.x + (upperCorner.x - lowerCorner.x) / 2.0 + (Math.random() - 0.5) * (upperCorner.x - lowerCorner.x) * STARTING_FRACTION,
y: lowerCorner.y + (upperCorner.y - lowerCorner.y) / 2.0 + (Math.random() - 0.5) * (upperCorner.y - lowerCorner.y) * STARTING_FRACTION,
z: lowerCorner.z + (upperCorner.z - lowerCorner.z) / 2.0 + (Math.random() - 0.5) * (upperCorner.z - lowerCorner.z) * STARTING_FRACTION
};
fish.push(
Entities.addEntity({
name: 'hifi-fishtank-fish' + _this.entityID,
type: "Model",
modelURL: fish.length % 2 === 0 ? FISH_MODEL_URL : FISH_MODEL_TWO_URL,
position: position,
parentID: _this.entityID,
// rotation: {
// x: 0,
// y: 0,
// z: 0,
// w: 1
// },
// type: "Box",
dimensions: FISH_DIMENSIONS,
velocity: {
x: SWIMMING_SPEED,
y: SWIMMING_SPEED,
z: SWIMMING_SPEED
},
damping: FISH_DAMPING,
angularDamping: FISH_ANGULAR_DAMPING,
dynamic: false,
// lifetime: LIFETIME,
color: {
red: 0,
green: 255,
blue: 255
}
})
);
}
// print('initial fish::' + fish.length)
_this.tankLocked = false;
}
Script.scriptEnding.connect(function() {
Script.update.disconnect(_this.update);
})
function setEntityUserData(id, data) {
var json = JSON.stringify(data)
Entities.editEntity(id, {
userData: json
});
}
// FIXME do non-destructive modification of the existing user data
function getEntityUserData(id) {
var results = null;
var properties = Entities.getEntityProperties(id, "userData");
if (properties.userData) {
try {
results = JSON.parse(properties.userData);
} catch (err) {
// print('error parsing json');
// print('properties are:'+ properties.userData);
}
}
return results ? results : {};
}
// Non-destructively modify the user data of an entity.
function setEntityCustomData(customKey, id, data) {
var userData = getEntityUserData(id);
if (data == null) {
delete userData[customKey];
} else {
userData[customKey] = data;
}
setEntityUserData(id, userData);
}
function getEntityCustomData(customKey, id, defaultValue) {
var userData = getEntityUserData(id);
if (undefined != userData[customKey]) {
return userData[customKey];
} else {
return defaultValue;
}
}
return new FishTank();
});