diff --git a/examples/acScripts/rain.js b/examples/acScripts/rain.js new file mode 100644 index 0000000000..ee8a1e19a6 --- /dev/null +++ b/examples/acScripts/rain.js @@ -0,0 +1,163 @@ +// +// rain.js +// examples +// +// Created by David Rowe on 9 Apr 2015. +// Copyright 2015 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 RainSquall = function (properties) { + var // Properties + squallOrigin, + squallRadius, + dropsPerMinute = 60, + dropSize = { x: 0.1, y: 0.1, z: 0.1 }, + dropFallSpeed = 1, // m/s + dropLifetime = 60, // Seconds + dropSpinMax = 0, // Maximum angular velocity per axis; deg/s + debug = false, // Display origin circle; don't use running on Stack Manager + // Other + squallCircle, + SQUALL_CIRCLE_COLOR = { red: 255, green: 0, blue: 0 }, + SQUALL_CIRCLE_ALPHA = 0.5, + SQUALL_CIRCLE_ROTATION = Quat.fromPitchYawRollDegrees(90, 0, 0), + raindropProperties, + RAINDROP_MODEL_URL = "https://s3.amazonaws.com/hifi-public/ozan/Polyworld/Sets/sky/tetrahedron_v1-Faceted2.fbx", + raindropTimer, + raindrops = [], // HACK: Work around raindrops not always getting velocities + raindropVelocities = [], // HACK: Work around raindrops not always getting velocities + DEGREES_TO_RADIANS = Math.PI / 180; + + function processProperties() { + if (!properties.hasOwnProperty("origin")) { + print("ERROR: Rain squall origin must be specified"); + return; + } + squallOrigin = properties.origin; + + if (!properties.hasOwnProperty("radius")) { + print("ERROR: Rain squall radius must be specified"); + return; + } + squallRadius = properties.radius; + + if (properties.hasOwnProperty("dropsPerMinute")) { + dropsPerMinute = properties.dropsPerMinute; + } + + if (properties.hasOwnProperty("dropSize")) { + dropSize = properties.dropSize; + } + + if (properties.hasOwnProperty("dropFallSpeed")) { + dropFallSpeed = properties.dropFallSpeed; + } + + if (properties.hasOwnProperty("dropLifetime")) { + dropLifetime = properties.dropLifetime; + } + + if (properties.hasOwnProperty("dropSpinMax")) { + dropSpinMax = properties.dropSpinMax; + } + + if (properties.hasOwnProperty("debug")) { + debug = properties.debug; + } + + raindropProperties = { + type: "Model", + modelURL: RAINDROP_MODEL_URL, + lifetime: dropLifetime, + dimensions: dropSize, + velocity: { x: 0, y: -dropFallSpeed, z: 0 }, + damping: 0, + angularDamping: 0, + ignoreForCollisions: true + }; + } + + function createRaindrop() { + var angle, + radius, + offset, + drop, + spin = { x: 0, y: 0, z: 0 }, + maxSpinRadians = properties.dropSpinMax * DEGREES_TO_RADIANS, + i; + + // HACK: Work around rain drops not always getting velocities set at creation + for (i = 0; i < raindrops.length; i += 1) { + Entities.editEntity(raindrops[i], raindropVelocities[i]); + } + + angle = Math.random() * 360; + radius = Math.random() * squallRadius; + offset = Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, angle, 0), { x: 0, y: -0.1, z: radius }); + raindropProperties.position = Vec3.sum(squallOrigin, offset); + if (properties.dropSpinMax > 0) { + spin = { + x: Math.random() * maxSpinRadians, + y: Math.random() * maxSpinRadians, + z: Math.random() * maxSpinRadians + }; + raindropProperties.angularVelocity = spin; + } + drop = Entities.addEntity(raindropProperties); + + // HACK: Work around rain drops not always getting velocities set at creation + raindrops.push(drop); + raindropVelocities.push({ + velocity: raindropProperties.velocity, + angularVelocity: spin + }); + if (raindrops.length > 5) { + raindrops.shift(); + raindropVelocities.shift(); + } + } + + function setUp() { + if (debug) { + squallCircle = Overlays.addOverlay("circle3d", { + size: { x: 2 * squallRadius, y: 2 * squallRadius }, + color: SQUALL_CIRCLE_COLOR, + alpha: SQUALL_CIRCLE_ALPHA, + solid: true, + visible: debug, + position: squallOrigin, + rotation: SQUALL_CIRCLE_ROTATION + }); + } + + raindropTimer = Script.setInterval(createRaindrop, 60000 / dropsPerMinute); + } + + function tearDown() { + Script.clearInterval(raindropTimer); + if (debug) { + Overlays.deleteOverlay(squallCircle); + } + } + + processProperties(); + setUp(); + Script.scriptEnding.connect(tearDown); + + return { + }; +}; + +var rainSquall1 = new RainSquall({ + origin: { x: 1195, y: 1223, z: 1020 }, + radius: 25, + dropsPerMinute: 120, + dropSize: { x: 0.1, y: 0.1, z: 0.1 }, + dropFallSpeed: 3, + dropLifetime: 30, + dropSpinMax: 180, + debug: false +}); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index dc24127054..26892ea981 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3867,6 +3867,9 @@ void Application::stopAllScripts(bool restart) { // stops all current running scripts for (QHash::const_iterator it = _scriptEnginesHash.constBegin(); it != _scriptEnginesHash.constEnd(); it++) { + if (it.value()->isFinished()) { + continue; + } if (restart && it.value()->isUserLoaded()) { connect(it.value(), SIGNAL(finished(const QString&)), SLOT(loadScript(const QString&))); } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index d50cb48118..bde0745c4d 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -235,33 +235,33 @@ Menu::Menu() { QMenu* viewMenu = addMenu("View"); + addCheckableActionToQMenuAndActionHash(viewMenu, + MenuOption::Fullscreen, #ifdef Q_OS_MAC - addCheckableActionToQMenuAndActionHash(viewMenu, - MenuOption::Fullscreen, Qt::CTRL | Qt::META | Qt::Key_F, - false, - qApp, - SLOT(setFullscreen(bool))); #else - addCheckableActionToQMenuAndActionHash(viewMenu, - MenuOption::Fullscreen, Qt::CTRL | Qt::Key_F, +#endif false, qApp, SLOT(setFullscreen(bool))); -#endif + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FirstPerson, Qt::Key_P, true, qApp,SLOT(cameraMenuChanged())); addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Mirror, Qt::SHIFT | Qt::Key_H, true); addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FullscreenMirror, Qt::Key_H, false, qApp, SLOT(cameraMenuChanged())); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::HMDTools, Qt::CTRL | Qt::Key_H, + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::HMDTools, +#ifdef Q_OS_MAC + Qt::META | Qt::Key_H, +#else + Qt::CTRL | Qt::Key_H, +#endif false, dialogsManager.data(), SLOT(hmdTools(bool))); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::EnableVRMode, 0, false, qApp, diff --git a/interface/src/devices/DdeFaceTracker.cpp b/interface/src/devices/DdeFaceTracker.cpp index a701160198..c7a8b7d166 100644 --- a/interface/src/devices/DdeFaceTracker.cpp +++ b/interface/src/devices/DdeFaceTracker.cpp @@ -182,9 +182,10 @@ void DdeFaceTracker::setEnabled(bool enabled) { _udpSocket.bind(_host, _serverPort); } + const char* DDE_EXIT_COMMAND = "exit"; + if (enabled && !_ddeProcess) { // Terminate any existing DDE process, perhaps left running after an Interface crash - const char* DDE_EXIT_COMMAND = "exit"; _udpSocket.writeDatagram(DDE_EXIT_COMMAND, DDE_SERVER_ADDR, _controlPort); qDebug() << "[Info] DDE Face Tracker Starting"; @@ -194,7 +195,8 @@ void DdeFaceTracker::setEnabled(bool enabled) { } if (!enabled && _ddeProcess) { - _ddeProcess->kill(); // More robust than trying to send an "exit" command to DDE + _udpSocket.writeDatagram(DDE_EXIT_COMMAND, DDE_SERVER_ADDR, _controlPort); + delete _ddeProcess; _ddeProcess = NULL; qDebug() << "[Info] DDE Face Tracker Stopped"; } diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 2f9427a63d..ac2c212001 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -647,8 +647,10 @@ void ScriptEngine::stopAllTimers() { } void ScriptEngine::stop() { - _isFinished = true; - emit runningStateChanged(); + if (!_isFinished) { + _isFinished = true; + emit runningStateChanged(); + } } void ScriptEngine::timerFired() {