Merge branch 'master' into tony/wrong-way-overlay

This commit is contained in:
Anthony J. Thibault 2016-03-09 16:50:18 -08:00
commit 886457939d
44 changed files with 1502 additions and 163 deletions

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);

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

@ -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

@ -236,6 +236,48 @@ const QHash<QString, Application::AcceptURLMethod> Application::_acceptedExtensi
{ AVA_JSON_EXTENSION, &Application::askToWearAvatarAttachmentUrl }
};
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() {
QTimer* heartbeatTimer = new QTimer();
// Give the heartbeat an initial value
_heartbeat = usecTimestampNow();
connect(heartbeatTimer, &QTimer::timeout, [this] {
_heartbeat = usecTimestampNow();
});
heartbeatTimer->start(HEARTBEAT_UPDATE_INTERVAL_SECS * MSECS_PER_SECOND);
}
void deadlockDetectionCrash() {
uint32_t* crashTrigger = nullptr;
*crashTrigger = 0xDEAD10CC;
}
void run() override {
while (!qApp->isAboutToQuit()) {
QThread::sleep(HEARTBEAT_UPDATE_INTERVAL_SECS);
auto now = usecTimestampNow();
auto lastHeartbeatAge = now - _heartbeat;
if (lastHeartbeatAge > MAX_HEARTBEAT_AGE_USECS) {
deadlockDetectionCrash();
}
}
}
static std::atomic<uint64_t> _heartbeat;
};
std::atomic<uint64_t> DeadlockWatchdogThread::_heartbeat;
#ifdef Q_OS_WIN
class MyNativeEventFilter : public QAbstractNativeEventFilter {
public:
@ -458,6 +500,9 @@ 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();
qCDebug(interfaceapp) << "[VERSION] Build sequence:" << qPrintable(applicationVersion());
_bookmarks = new Bookmarks(); // Before setting up the menu
@ -4961,6 +5006,15 @@ void Application::crashApplication() {
Q_UNUSED(value);
}
void Application::deadlockApplication() {
qCDebug(interfaceapp) << "Intentionally deadlocked Interface";
// Using a loop that will *technically* eventually exit (in ~600 billion years)
// to avoid compiler warnings about a loop that will never exit
for (uint64_t i = 1; i != 0; ++i) {
QThread::sleep(1);
}
}
void Application::setActiveDisplayPlugin(const QString& pluginName) {
auto menu = Menu::getInstance();
foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getDisplayPlugins()) {

View file

@ -276,6 +276,7 @@ public slots:
void reloadResourceCaches();
void crashApplication();
void deadlockApplication();
void rotationModeChanged();

View file

@ -592,6 +592,8 @@ Menu::Menu() {
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::DisplayCrashOptions, 0, true);
// Developer > Crash Application
addActionToQMenuAndActionHash(developerMenu, MenuOption::CrashInterface, 0, qApp, SLOT(crashApplication()));
// Developer > Deadlock Application
addActionToQMenuAndActionHash(developerMenu, MenuOption::DeadlockInterface, 0, qApp, SLOT(deadlockApplication()));
// Developer > Log...
addActionToQMenuAndActionHash(developerMenu, MenuOption::Log, Qt::CTRL | Qt::SHIFT | Qt::Key_L,

View file

@ -66,6 +66,7 @@ namespace MenuOption {
const QString CopyPath = "Copy Path to Clipboard";
const QString CoupleEyelids = "Couple Eyelids";
const QString CrashInterface = "Crash Interface";
const QString DeadlockInterface = "Deadlock Interface";
const QString DecreaseAvatarSize = "Decrease Avatar Size";
const QString DeleteBookmark = "Delete Bookmark...";
const QString DisableActivityLogger = "Disable Activity Logger";

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.
@ -446,6 +447,8 @@ void MyAvatar::updateSensorToWorldMatrix() {
if (_enableDebugDrawSensorToWorldMatrix) {
DebugDraw::getInstance().addMarker("sensorToWorldMatrix", glmExtractRotation(_sensorToWorldMatrix), extractTranslation(_sensorToWorldMatrix), glm::vec4(1));
}
_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
@ -391,6 +395,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

@ -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

@ -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

@ -867,11 +867,10 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
}
} else if (object.name == "Material") {
FBXMaterial material;
if (object.properties.at(1).toByteArray().contains("StingrayPBS")) {
material.isPBSMaterial = true;
}
material.name = (object.properties.at(1).toString());
foreach (const FBXNode& subobject, object.children) {
bool properties = false;
QByteArray propertyName;
int index;
if (subobject.name == "Properties60") {
@ -884,25 +883,36 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
propertyName = "P";
index = 4;
}
if (!material.isPBSMaterial && properties) {
foreach (const FBXNode& property, subobject.children) {
if (properties) {
std::vector<std::string> unknowns;
foreach(const FBXNode& property, subobject.children) {
if (property.name == propertyName) {
if (property.properties.at(0) == "DiffuseColor") {
material.diffuseColor = getVec3(property.properties, index);
} else if (property.properties.at(0) == "Diffuse") {
material.diffuseColor = getVec3(property.properties, index);
} else if (property.properties.at(0) == "DiffuseFactor") {
material.diffuseFactor = property.properties.at(index).value<double>();
} else if (property.properties.at(0) == "Diffuse") {
// NOTE: this is uneeded but keep it for now for debug
// material.diffuseColor = getVec3(property.properties, index);
// material.diffuseFactor = 1.0;
} else if (property.properties.at(0) == "SpecularColor") {
material.specularColor = getVec3(property.properties, index);
} else if (property.properties.at(0) == "Specular") {
material.specularColor = getVec3(property.properties, index);
} else if (property.properties.at(0) == "SpecularFactor") {
material.specularFactor = property.properties.at(index).value<double>();
} else if (property.properties.at(0) == "Specular") {
// NOTE: this is uneeded but keep it for now for debug
// material.specularColor = getVec3(property.properties, index);
// material.specularFactor = 1.0;
} else if (property.properties.at(0) == "Emissive") {
} else if (property.properties.at(0) == "EmissiveColor") {
material.emissiveColor = getVec3(property.properties, index);
} else if (property.properties.at(0) == "EmissiveFactor") {
material.emissiveFactor = property.properties.at(index).value<double>();
} else if (property.properties.at(0) == "Emissive") {
// NOTE: this is uneeded but keep it for now for debug
// material.emissiveColor = getVec3(property.properties, index);
// material.emissiveFactor = 1.0;
} else if (property.properties.at(0) == "Shininess") {
material.shininess = property.properties.at(index).value<double>();
@ -910,45 +920,50 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
} else if (property.properties.at(0) == "Opacity") {
material.opacity = property.properties.at(index).value<double>();
}
#if defined(DEBUG_FBXREADER)
else {
const QString propname = property.properties.at(0).toString();
if (propname == "EmissiveFactor") {
}
}
#endif
}
}
} else if (material.isPBSMaterial && properties) {
std::vector<std::string> unknowns;
foreach(const FBXNode& property, subobject.children) {
if (property.name == propertyName) {
if (property.properties.at(0) == "Maya|use_normal_map") {
// Sting Ray Material Properties!!!!
else if (property.properties.at(0) == "Maya|use_normal_map") {
material.isPBSMaterial = true;
material.useNormalMap = (bool)property.properties.at(index).value<double>();
} else if (property.properties.at(0) == "Maya|base_color") {
material.isPBSMaterial = true;
material.diffuseColor = getVec3(property.properties, index);
} else if (property.properties.at(0) == "Maya|use_color_map") {
material.isPBSMaterial = true;
material.useAlbedoMap = (bool) property.properties.at(index).value<double>();
} else if (property.properties.at(0) == "Maya|roughness") {
material.isPBSMaterial = true;
material.roughness = property.properties.at(index).value<double>();
} else if (property.properties.at(0) == "Maya|use_roughness_map") {
material.isPBSMaterial = true;
material.useRoughnessMap = (bool)property.properties.at(index).value<double>();
} else if (property.properties.at(0) == "Maya|metallic") {
material.isPBSMaterial = true;
material.metallic = property.properties.at(index).value<double>();
} else if (property.properties.at(0) == "Maya|use_metallic_map") {
material.isPBSMaterial = true;
material.useMetallicMap = (bool)property.properties.at(index).value<double>();
} else if (property.properties.at(0) == "Maya|emissive") {
material.isPBSMaterial = true;
material.emissiveColor = getVec3(property.properties, index);
} else if (property.properties.at(0) == "Maya|emissive_intensity") {
material.isPBSMaterial = true;
material.emissiveIntensity = property.properties.at(index).value<double>();
} else if (property.properties.at(0) == "Maya|use_emissive_map") {
material.isPBSMaterial = true;
material.useEmissiveMap = (bool)property.properties.at(index).value<double>();
} else if (property.properties.at(0) == "Maya|use_ao_map") {
material.isPBSMaterial = true;
material.useOcclusionMap = (bool)property.properties.at(index).value<double>();
} else {
@ -1073,7 +1088,9 @@ FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QS
if (connection.properties.at(0) == "OP") {
int counter = 0;
QByteArray type = connection.properties.at(3).toByteArray().toLower();
if ((type.contains("diffuse") && !type.contains("tex_global_diffuse"))) {
if (type.contains("DiffuseFactor")) {
diffuseFactorTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
} else if ((type.contains("diffuse") && !type.contains("tex_global_diffuse"))) {
diffuseTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
} else if (type.contains("tex_color_map")) {
diffuseTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));

View file

@ -138,19 +138,22 @@ public:
opacity(opacity) {}
glm::vec3 diffuseColor{ 1.0f };
float diffuseFactor = 1.0f;
float diffuseFactor{ 1.0f };
glm::vec3 specularColor{ 0.02f };
float specularFactor = 1.0f;
float specularFactor{ 1.0f };
glm::vec3 emissiveColor{ 0.0f };
float shininess = 23.0f;
float opacity = 1.0f;
float emissiveFactor{ 0.0f };
float shininess{ 23.0f };
float opacity{ 1.0f };
float metallic{ 0.0f };
float roughness{ 1.0f };
float emissiveIntensity{ 1.0f };
QString materialID;
QString name;
model::MaterialPointer _material;
FBXTexture normalTexture;
@ -421,6 +424,7 @@ public:
QHash<QString, QString> diffuseTextures;
QHash<QString, QString> diffuseFactorTextures;
QHash<QString, QString> transparentTextures;
QHash<QString, QString> bumpTextures;
QHash<QString, QString> normalTextures;

View file

@ -75,12 +75,24 @@ void FBXReader::consolidateFBXMaterials() {
// the pure material associated with this part
bool detectDifferentUVs = false;
FBXTexture diffuseTexture;
FBXTexture diffuseFactorTexture;
QString diffuseTextureID = diffuseTextures.value(material.materialID);
if (!diffuseTextureID.isNull()) {
QString diffuseFactorTextureID = diffuseFactorTextures.value(material.materialID);
// If both factor and color textures are specified, the texture bound to DiffuseColor wins
if (!diffuseFactorTextureID.isNull() || !diffuseTextureID.isNull()) {
if (!diffuseFactorTextureID.isNull() && diffuseTextureID.isNull()) {
diffuseTextureID = diffuseFactorTextureID;
// If the diffuseTextureID comes from the Texture bound to DiffuseFactor, we know it s exported from maya
// And the DiffuseFactor is forced to 0.5 by Maya which is bad
// So we need to force it to 1.0
material.diffuseFactor = 1.0;
}
diffuseTexture = getTexture(diffuseTextureID);
// FBX files generated by 3DSMax have an intermediate texture parent, apparently
foreach (const QString& childTextureID, _connectionChildMap.values(diffuseTextureID)) {
foreach(const QString& childTextureID, _connectionChildMap.values(diffuseTextureID)) {
if (_textureFilenames.contains(childTextureID)) {
diffuseTexture = getTexture(diffuseTextureID);
}
@ -180,11 +192,13 @@ void FBXReader::consolidateFBXMaterials() {
// Finally create the true material representation
material._material = std::make_shared<model::Material>();
material._material->setEmissive(material.emissiveColor);
auto diffuse = material.diffuseColor;
// FIXME: Do not use the Diffuse Factor yet as some FBX models have it set to 0
// diffuse *= material.diffuseFactor;
// Emissive color is the mix of emissiveColor with emissiveFactor
auto emissive = material.emissiveColor * material.emissiveFactor;
material._material->setEmissive(emissive);
// Final diffuse color is the mix of diffuseColor with diffuseFactor
auto diffuse = material.diffuseColor * material.diffuseFactor;
material._material->setAlbedo(diffuse);
if (material.isPBSMaterial) {

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

@ -348,4 +348,3 @@ void NetworkTexture::setImage(const QImage& image, void* voidTexture, int origin
emit networkTextureCreated(qWeakPointerCast<NetworkTexture, Resource> (_self));
}

View file

@ -132,6 +132,8 @@ signals:
protected:
virtual bool isCacheable() const override { return _loaded; }
virtual void downloadFinished(const QByteArray& data) override;
Q_INVOKABLE void loadContent(const QByteArray& content);

View file

@ -278,20 +278,20 @@ void Resource::refresh() {
}
void Resource::allReferencesCleared() {
if (_cache) {
if (_cache && isCacheable()) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "allReferencesCleared");
return;
}
// create and reinsert new shared pointer
QSharedPointer<Resource> self(this, &Resource::allReferencesCleared);
setSelf(self);
reinsert();
// add to the unused list
_cache->addUnusedResource(self);
} else {
delete this;
}
@ -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:
@ -192,10 +193,15 @@ protected slots:
protected:
virtual void init();
/// Called when the download has finished
virtual void downloadFinished(const QByteArray& data);
/// Checks whether the resource is cacheable.
virtual bool isCacheable() const { return true; }
/// Should be called by subclasses when all the loading that will be done has been done.
/// 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); }
/// 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

@ -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

@ -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

@ -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 "ArrayBufferClass.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 };
@ -219,4 +221,4 @@ protected:
static std::atomic<bool> _stoppingAllScripts;
};
#endif // hifi_ScriptEngine_h
#endif // hifi_ScriptEngine_h

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

@ -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

@ -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,94 @@
//
// flowers.fs
// examples/homeContent/plant
//
// Created by Eric Levin on 3/7/16.
// Copyright 2016 High Fidelity, Inc.
//
// This fragment shader is designed to shader a sphere to create the effect of a blooming flower
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
#define TWO_PI 6.28318530718
uniform float iBloomPct = 0.2;
uniform vec3 iHSLColor = vec3(0.7, 0.5, 0.5);
float hue2rgb(float f1, float f2, float hue) {
if (hue < 0.0)
hue += 1.0;
else if (hue > 1.0)
hue -= 1.0;
float res;
if ((6.0 * hue) < 1.0)
res = f1 + (f2 - f1) * 6.0 * hue;
else if ((2.0 * hue) < 1.0)
res = f2;
else if ((3.0 * hue) < 2.0)
res = f1 + (f2 - f1) * ((2.0 / 3.0) - hue) * 6.0;
else
res = f1;
return res;
}
vec3 hsl2rgb(vec3 hsl) {
vec3 rgb;
if (hsl.y == 0.0) {
rgb = vec3(hsl.z); // Luminance
} else {
float f2;
if (hsl.z < 0.5)
f2 = hsl.z * (1.0 + hsl.y);
else
f2 = hsl.z + hsl.y - hsl.y * hsl.z;
float f1 = 2.0 * hsl.z - f2;
rgb.r = hue2rgb(f1, f2, hsl.x + (1.0/3.0));
rgb.g = hue2rgb(f1, f2, hsl.x);
rgb.b = hue2rgb(f1, f2, hsl.x - (1.0/3.0));
}
return rgb;
}
vec3 hsl2rgb(float h, float s, float l) {
return hsl2rgb(vec3(h, s, l));
}
void mainImage( out vec4 fragColor, in vec2 fragCoord ) {
vec2 st = fragCoord.xy/iWorldScale.xz;
vec3 color = vec3(0.0, 0.0, 0.0);
vec2 toCenter = vec2(0.5) - st;
float angle = atan(toCenter.y, toCenter.x);
float radius = length(toCenter) * 2.0;
// Second check is so we discard the top half of the sphere
if ( iBloomPct < radius || _position.y > 0) {
discard;
}
// simulate ambient occlusion
float brightness = pow(radius, 0.8);
vec3 hslColor = iHSLColor + (abs(angle) * 0.02);
hslColor.z = 0.15 + pow(radius, 2.);
vec3 rgbColor = hsl2rgb(hslColor);
fragColor = vec4(rgbColor, 1.0);
}
vec4 getProceduralColor() {
vec4 result;
vec2 position = _position.xz;
position += 0.5;
mainImage(result, position * iWorldScale.xz);
return result;
}

View file

@ -0,0 +1,154 @@
//
// growingPlantEntityScript.js
// examples/homeContent/plant
//
// Created by Eric Levin on 2/10/16.
// Copyright 2016 High Fidelity, Inc.
//
// This entity script handles the logic for growing a plant when it has water poured on it
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
(function() {
Script.include('../../../../libraries/utils.js');
var _this;
GrowingPlant = function() {
_this = this;
_this.flowers = [];
// _this.STARTING_FLOWER_DIMENSIONS = {x: 0.1, y: 0.001, z: 0.1}
_this.STARTING_FLOWER_DIMENSIONS = {
x: 0.001,
y: 0.001,
z: 0.001
}
_this.MAX_FLOWERS = 50;
_this.MIN_FLOWER_TO_FLOWER_DISTANCE = 0.03;
_this.debounceRange = {
min: 500,
max: 1000
};
_this.canCreateFlower = true;
_this.SHADER_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/shaders/flower.fs";
// _this.SHADER_URL = "file:///C:/Users/Eric/hifi/unpublishedScripts/DomainContent/Home/plant/flower.fs";
_this.flowerHSLColors = [{
hue: 19 / 360,
saturation: 0.92,
light: 0.31
}, {
hue: 161 / 360,
saturation: 0.28,
light: 0.62
}];
};
GrowingPlant.prototype = {
continueWatering: function(entityID, data) {
// we're being watered- every now and then spawn a new flower to add to our growing list
// If we don't have any flowers yet, immediately grow one
var data = JSON.parse(data[0]);
if (_this.canCreateFlower && _this.flowers.length < _this.MAX_FLOWERS) {
_this.createFlower(data.position, data.surfaceNormal);
_this.canCreateFlower = false;
Script.setTimeout(function() {
_this.canCreateFlower = true;
}, randFloat(_this.debounceRange.min, this.debounceRange.max));
}
_this.flowers.forEach(function(flower) {
flower.grow();
});
},
createFlower: function(position, surfaceNormal) {
if (_this.previousFlowerPosition && Vec3.distance(position, _this.previousFlowerPosition) < _this.MIN_FLOWER_TO_FLOWER_DISTANCE) {
// Reduces flower overlap
return;
}
var xzGrowthRate = randFloat(0.00006, 0.00016);
var growthRate = {x: xzGrowthRate, y: randFloat(0.001, 0.003), z: xzGrowthRate};
var flower = {
dimensions: {
x: _this.STARTING_FLOWER_DIMENSIONS.x,
y: _this.STARTING_FLOWER_DIMENSIONS.y,
z: _this.STARTING_FLOWER_DIMENSIONS.z
},
startingPosition: position,
rotation: Quat.rotationBetween(Vec3.UNIT_Y, surfaceNormal),
maxYDimension: randFloat(0.4, 1.1),
// startingHSLColor: {
// hue: 80 / 360,
// saturation: 0.47,
// light: 0.48
// },
// endingHSLColor: {
// hue: 19 / 260,
// saturation: 0.92,
// light: 0.41
// },
hslColor: Math.random() < 0.5 ? _this.flowerHSLColors[0] : _this.flowerHSLColors[1],
growthRate: growthRate
};
flower.userData = {
ProceduralEntity: {
shaderUrl: _this.SHADER_URL,
uniforms: {
iBloomPct: randFloat(0.4, 0.8),
iHSLColor: [flower.hslColor.hue, flower.hslColor.saturation, flower.hslColor.light]
}
}
};
flower.id = Entities.addEntity({
type: "Sphere",
name: "flower",
lifetime: 3600,
position: position,
collisionless: true,
rotation: flower.rotation,
dimensions: _this.STARTING_FLOWER_DIMENSIONS,
userData: JSON.stringify(flower.userData)
});
flower.grow = function() {
// grow flower a bit
if (flower.dimensions.y > flower.maxYDimension) {
return;
}
flower.dimensions = Vec3.sum(flower.dimensions, flower.growthRate);
flower.position = Vec3.sum(flower.startingPosition, Vec3.multiply(Quat.getUp(flower.rotation), flower.dimensions.y / 2));
//As we grow we must also move ourselves in direction we grow!
//TODO: Add this color changing back once we fix bug https://app.asana.com/0/inbox/31759584831096/96943843100173/98022172055918
// var newHue = map(flower.dimensions.y, _this.STARTING_FLOWER_DIMENSIONS.y, flower.maxYDimension, flower.startingHSLColor.hue, flower.endingHSLColor.hue);
// var newSaturation = map(flower.dimensions.y, _this.STARTING_FLOWER_DIMENSIONS.y, flower.maxYDimension, flower.startingHSLColor.saturation, flower.endingHSLColor.saturation);
// var newLight = map(flower.dimensions.y, _this.STARTING_FLOWER_DIMENSIONS.y, flower.maxYDimension, flower.startingHSLColor.light, flower.endingHSLColor.light);
// flower.userData.PrsoceduralEntity.uniforms.iHSLColor = [newHue, newSaturation, newLight];
Entities.editEntity(flower.id, {
dimensions: flower.dimensions,
position: flower.position,
});
}
_this.flowers.push(flower);
_this.previousFlowerPosition = position;
},
preload: function(entityID) {
_this.entityID = entityID;
},
};
// entity scripts always need to return a newly constructed object of our type
return new GrowingPlant();
});

View file

@ -0,0 +1,139 @@
//
// growingPlantSpawner.js
// examples/homeContent/plant
//
// Created by Eric Levin on 2/10/16.
// Copyright 2016 High Fidelity, Inc.
//
// This entity script handles the logic for growing a plant when it has water poured on it
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
var orientation = Camera.getOrientation();
orientation = Quat.safeEulerAngles(orientation);
orientation.x = 0;
orientation = Quat.fromVec3Degrees(orientation);
var bowlPosition = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getFront(orientation)));
var BOWL_MODEL_URL = "http://hifi-content.s3.amazonaws.com/alan/dev/Flowers--Bowl.fbx";
var bowlDimensions = {
x: 0.518,
y: 0.1938,
z: 0.5518
};
var bowl = Entities.addEntity({
type: "Model",
modelURL: BOWL_MODEL_URL,
dimensions: bowlDimensions,
name: "plant bowl",
position: bowlPosition
});
var PLANT_MODEL_URL = "http://hifi-content.s3.amazonaws.com/alan/dev/Flowers--Moss-Rock.fbx";
var PLANT_SCRIPT_URL = Script.resolvePath("growingPlantEntityScript.js?v1" + Math.random().toFixed(2));
var plantDimensions = {
x: 0.52,
y: 0.2600,
z: 0.52
};
var plantPosition = Vec3.sum(bowlPosition, {
x: 0,
y: plantDimensions.y / 2,
z: 0
});
var plant = Entities.addEntity({
type: "Model",
modelURL: PLANT_MODEL_URL,
name: "hifi-growable-plant",
dimensions: plantDimensions,
position: plantPosition,
script: PLANT_SCRIPT_URL,
parentID: bowl
});
var WATER_CAN_MODEL_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/models/waterCan.fbx?v1" + Math.random();
var WATER_CAN_SCRIPT_URL = Script.resolvePath("waterCanEntityScript.js?v2" + Math.random().toFixed());
var waterCanPosition = Vec3.sum(plantPosition, Vec3.multiply(0.6, Quat.getRight(orientation)));
var waterCanRotation = orientation;
var waterCan = Entities.addEntity({
type: "Model",
shapeType: 'box',
name: "hifi-water-can",
modelURL: WATER_CAN_MODEL_URL,
script: WATER_CAN_SCRIPT_URL,
dimensions: {
x: 0.1859,
y: 0.2762,
z: 0.4115
},
position: waterCanPosition,
angularDamping: 1,
dynamic: true,
gravity: {
x: 0.0,
y: -2.0,
z: 0
},
rotation: waterCanRotation,
userData: JSON.stringify({
wearable: {
joints: {
RightHand: [{
x: 0.024,
y: 0.173,
z: 0.152
}, {
x: 0.374,
y: 0.636,
z: -0.638,
w: -0.215
}],
LeftHand: [{
x: -0.0348,
y: 0.201,
z: 0.166
}, {
x: 0.4095,
y: -0.625,
z: 0.616,
w: -0.247
}]
}
}
})
});
var waterSpoutPosition = Vec3.sum(waterCanPosition, Vec3.multiply(0.2, Quat.getFront(orientation)))
var waterSpoutRotation = Quat.multiply(waterCanRotation, Quat.fromPitchYawRollDegrees(10, 0, 0));
var waterSpout = Entities.addEntity({
type: "Box",
name: "hifi-water-spout",
dimensions: {
x: 0.02,
y: 0.02,
z: 0.07
},
color: {
red: 200,
green: 10,
blue: 200
},
position: waterSpoutPosition,
rotation: waterSpoutRotation,
parentID: waterCan,
visible: false
});
function cleanup() {
// Entities.deleteEntity(plant);
// Entities.deleteEntity(bowl);
// Entities.deleteEntity(waterCan);
// Entities.deleteEntity(waterSpout);
}
Script.scriptEnding.connect(cleanup);

View file

@ -0,0 +1,242 @@
//
// waterCanEntityScript.js
// examples/homeContent/plant
//
// Created by Eric Levin on 2/15/16.
// Copyright 2016 High Fidelity, Inc.
//
// This entity script handles the logic for pouring water when a user tilts the entity the script is attached too.
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
(function() {
Script.include('../../../../libraries/utils.js');
var _this;
WaterSpout = function() {
_this = this;
_this.waterSound = SoundCache.getSound("https://s3-us-west-1.amazonaws.com/hifi-content/eric/Sounds/shower.wav");
_this.POUR_ANGLE_THRESHOLD = 0;
_this.waterPouring = false;
_this.WATER_SPOUT_NAME = "hifi-water-spout";
_this.GROWABLE_ENTITIES_SEARCH_RANGE = 100;
};
WaterSpout.prototype = {
startNearGrab: function() {
_this.startHold();
},
startEquip: function() {
_this.startHold();
},
startHold: function() {
_this.findGrowableEntities();
},
releaseEquip: function() {
_this.releaseHold();
},
releaseGrab: function() {
_this.releaseHold();
},
releaseHold: function() {
_this.stopPouring();
},
stopPouring: function() {
Entities.editEntity(_this.waterEffect, {
isEmitting: false
});
_this.waterPouring = false;
//water no longer pouring...
if (_this.waterInjector) {
_this.waterInjector.stop();
}
Entities.callEntityMethod(_this.mostRecentIntersectedGrowableEntity, 'stopWatering');
},
continueEquip: function() {
_this.continueHolding();
},
continueNearGrab: function() {
_this.continueHolding();
},
continueHolding: function() {
if (!_this.waterSpout) {
return;
}
// Check rotation of water can along it's z axis. If it's beyond a threshold, then start spraying water
_this.castRay();
var rotation = Entities.getEntityProperties(_this.entityID, "rotation").rotation;
var pitch = Quat.safeEulerAngles(rotation).x;
if (pitch < _this.POUR_ANGLE_THRESHOLD) {
// Water is pouring
var spoutProps = Entities.getEntityProperties(_this.waterSpout, ["rotation", "position"]);
if (!_this.waterPouring) {
Entities.editEntity(_this.waterEffect, {
isEmitting: true
});
_this.waterPouring = true;
if (!_this.waterInjector) {
_this.waterInjector = Audio.playSound(_this.waterSound, {
position: spoutProps.position,
loop: true
});
} else {
_this.waterInjector.restart();
}
}
_this.waterSpoutRotation = spoutProps.rotation;
var waterEmitOrientation = Quat.multiply(_this.waterSpoutRotation, Quat.fromPitchYawRollDegrees(0, 180, 0));
Entities.editEntity(_this.waterEffect, {
emitOrientation: waterEmitOrientation
});
} else if (pitch > _this.POUR_ANGLE_THRESHOLD && _this.waterPouring) {
_this.stopPouring();
}
},
castRay: function() {
var spoutProps = Entities.getEntityProperties(_this.waterSpout, ["position, rotation"]);
var direction = Quat.getFront(spoutProps.rotation)
var end = Vec3.sum(spoutProps.position, Vec3.multiply(5, direction));
var pickRay = {
origin: spoutProps.position,
direction: direction
};
var intersection = Entities.findRayIntersection(pickRay, true, _this.growableEntities);
if (intersection.intersects) {
//We've intersected with a waterable object
var data = JSON.stringify({
position: intersection.intersection,
surfaceNormal: intersection.surfaceNormal
});
_this.mostRecentIntersectedGrowableEntity = intersection.entityID;
Entities.callEntityMethod(intersection.entityID, 'continueWatering', [data]);
}
},
createWaterEffect: function() {
var waterEffectPosition = Vec3.sum(_this.waterSpoutPosition, Vec3.multiply(Quat.getFront(_this.waterSpoutRotation), -0.04));
_this.waterEffect = Entities.addEntity({
type: "ParticleEffect",
name: "water particle effect",
position: waterEffectPosition,
isEmitting: false,
parentID: _this.waterSpout,
colorStart: {
red: 90,
green: 90,
blue: 110
},
color: {
red: 70,
green: 70,
blue: 130
},
colorFinish: {
red: 23,
green: 195,
blue: 206
},
maxParticles: 20000,
lifespan: 2,
emitRate: 2000,
emitSpeed: .3,
speedSpread: 0.1,
emitDimensions: {
x: 0.0,
y: 0.0,
z: 0.0
},
emitAcceleration: {
x: 0.0,
y: 0,
z: 0
},
polarStart: 0.0,
polarFinish: 0.1,
accelerationSpread: {
x: 0.01,
y: 0.0,
z: 0.01
},
emitOrientation: Quat.fromPitchYawRollDegrees(0, 0, 0),
radiusSpread: 0.0001,
radiusStart: 0.005,
particleRadius: 0.003,
radiusFinish: 0.001,
alphaSpread: 0,
alphaStart: 0.1,
alpha: 1.0,
alphaFinish: 1.0,
emitterShouldTrail: true,
textures: "https://s3-us-west-1.amazonaws.com/hifi-content/eric/images/raindrop.png",
});
},
findGrowableEntities: function() {
_this.growableEntities = [];
var entities = Entities.findEntities(_this.position, _this.GROWABLE_ENTITIES_SEARCH_RANGE);
entities.forEach(function(entity) {
var name = Entities.getEntityProperties(entity, "name").name;
if (name.length > 0 && name.indexOf("growable") !== -1) {
_this.growableEntities.push(entity);
}
});
},
preload: function(entityID) {
_this.entityID = entityID;
_this.position = Entities.getEntityProperties(_this.entityID, "position").position;
// Wait a a bit for spout to spawn for case where preload is initial spawn, then save it
Script.setTimeout(function() {
var entities = Entities.findEntities(_this.position, 1);
entities.forEach(function(entity) {
var name = Entities.getEntityProperties(entity, "name").name;
if (name === _this.WATER_SPOUT_NAME) {
_this.waterSpout = entity;
}
});
if (_this.waterSpout) {
_this.waterSpoutPosition = Entities.getEntityProperties(_this.waterSpout, "position").position;
_this.waterSpoutRotation = Entities.getEntityProperties(_this.waterSpout, "rotation").rotation;
_this.createWaterEffect();
}
}, 3000);
},
unload: function() {
Entities.deleteEntity(_this.waterEffect);
if (_this.waterInjector) {
_this.waterInjector.stop();
delete _this.waterInjector;
}
}
};
// entity scripts always need to return a newly constructed object of our type
return new WaterSpout();
});