diff --git a/interface/resources/qml/hifi/tablet/Tablet.qml b/interface/resources/qml/hifi/tablet/Tablet.qml index 8f0b591da8..0d563ed5bb 100644 --- a/interface/resources/qml/hifi/tablet/Tablet.qml +++ b/interface/resources/qml/hifi/tablet/Tablet.qml @@ -30,6 +30,24 @@ Item { return -1; } + function sortButtons() { + var children = []; + for (var i = 0; i < flowMain.children.length; i++) { + children[i] = flowMain.children[i]; + } + + children.sort(function (a, b) { + if (a.sortOrder === b.sortOrder) { + // subsort by stableOrder, because JS sort is not stable in qml. + return a.stableOrder - b.stableOrder; + } else { + return a.sortOrder - b.sortOrder; + } + }); + + flowMain.children = children; + } + // called by C++ code when a button should be added to the tablet function addButtonProxy(properties) { var component = Qt.createComponent("TabletButton.qml"); @@ -42,6 +60,9 @@ Item { // pass a reference to the tabletRoot object to the button. button.tabletRoot = parent.parent; + + sortButtons(); + return button; } @@ -221,6 +242,7 @@ Item { flowMain.children[index].state = state; } } + function nextItem() { setCurrentItemState("base state"); var nextColumnIndex = (columnIndex + 3 + 1) % 3; diff --git a/interface/resources/qml/hifi/tablet/TabletButton.qml b/interface/resources/qml/hifi/tablet/TabletButton.qml index c6c810d25e..945d2769dd 100644 --- a/interface/resources/qml/hifi/tablet/TabletButton.qml +++ b/interface/resources/qml/hifi/tablet/TabletButton.qml @@ -11,6 +11,8 @@ Item { property bool isActive: false property bool inDebugMode: false property bool isEntered: false + property double sortOrder: 100 + property int stableOrder: 0 property var tabletRoot; width: 129 height: 129 diff --git a/libraries/script-engine/src/TabletScriptingInterface.cpp b/libraries/script-engine/src/TabletScriptingInterface.cpp index d73cb980f6..2414ec469f 100644 --- a/libraries/script-engine/src/TabletScriptingInterface.cpp +++ b/libraries/script-engine/src/TabletScriptingInterface.cpp @@ -388,10 +388,13 @@ QQuickItem* TabletProxy::getQmlMenu() const { // const QString UUID_KEY = "uuid"; +const QString STABLE_ORDER_KEY = "stableOrder"; +static int s_stableOrder = 1; -TabletButtonProxy::TabletButtonProxy(const QVariantMap& properties) : _uuid(QUuid::createUuid()), _properties(properties) { +TabletButtonProxy::TabletButtonProxy(const QVariantMap& properties) : _uuid(QUuid::createUuid()), _stableOrder(++s_stableOrder), _properties(properties) { // this is used to uniquely identify this button. _properties[UUID_KEY] = _uuid; + _properties[STABLE_ORDER_KEY] = _stableOrder; } void TabletButtonProxy::setQmlButton(QQuickItem* qmlButton) { diff --git a/libraries/script-engine/src/TabletScriptingInterface.h b/libraries/script-engine/src/TabletScriptingInterface.h index 0b7829c7fb..4fe2c44c10 100644 --- a/libraries/script-engine/src/TabletScriptingInterface.h +++ b/libraries/script-engine/src/TabletScriptingInterface.h @@ -194,6 +194,7 @@ signals: protected: QUuid _uuid; + int _stableOrder; mutable std::mutex _mutex; QQuickItem* _qmlButton { nullptr }; QVariantMap _properties; @@ -206,6 +207,7 @@ protected: * @property {string} activeText - button caption when button is active * @property {string} activeIcon - url to button icon used when button is active. (50 x 50) * @property {string} isActive - true when button is active. + * @property {number} sortOrder - determines sort order on tablet. lower numbers will appear before larger numbers. default is 100 */ #endif // hifi_TabletScriptingInterface_h diff --git a/plugins/hifiKinect/src/KinectPlugin.cpp b/plugins/hifiKinect/src/KinectPlugin.cpp index 2c837d83d3..0bff69ed57 100644 --- a/plugins/hifiKinect/src/KinectPlugin.cpp +++ b/plugins/hifiKinect/src/KinectPlugin.cpp @@ -227,15 +227,26 @@ void KinectPlugin::init() { static const QString KINECT_PLUGIN { "Kinect" }; { auto getter = [this]()->bool { return _enabled; }; - auto setter = [this](bool value) { _enabled = value; saveSettings(); }; + auto setter = [this](bool value) { + _enabled = value; saveSettings(); + if (!_enabled) { + auto userInputMapper = DependencyManager::get(); + userInputMapper->withLock([&, this]() { + _inputDevice->clearState(); + }); + } + }; auto preference = new CheckPreference(KINECT_PLUGIN, "Enabled", getter, setter); preferences->addPreference(preference); } } bool KinectPlugin::isSupported() const { - // FIXME -- check to see if there's a camera or not... - return true; + bool supported = false; +#ifdef HAVE_KINECT + supported = initializeDefaultSensor(); +#endif + return supported; } bool KinectPlugin::activate() { @@ -250,13 +261,29 @@ bool KinectPlugin::activate() { userInputMapper->registerDevice(_inputDevice); return initializeDefaultSensor(); - } else { - return false; } + return false; } -bool KinectPlugin::initializeDefaultSensor() { +bool KinectPlugin::isHandController() const { + bool sensorAvailable = false; #ifdef HAVE_KINECT + if (_kinectSensor) { + BOOLEAN sensorIsAvailable = FALSE; + HRESULT hr = _kinectSensor->get_IsAvailable(&sensorIsAvailable); + sensorAvailable = SUCCEEDED(hr) && (sensorIsAvailable == TRUE); + } +#endif + return _enabled && _initialized && sensorAvailable; +} + + +bool KinectPlugin::initializeDefaultSensor() const { +#ifdef HAVE_KINECT + if (_initialized) { + return true; + } + HRESULT hr; hr = GetDefaultKinectSensor(&_kinectSensor); @@ -289,6 +316,7 @@ bool KinectPlugin::initializeDefaultSensor() { return false; } + _initialized = true; return true; #else return false; @@ -527,3 +555,9 @@ void KinectPlugin::InputDevice::update(float deltaTime, const controller::InputC } } +void KinectPlugin::InputDevice::clearState() { + for (size_t i = 0; i < KinectJointIndex::Size; i++) { + int poseIndex = KinectJointIndexToPoseIndex((KinectJointIndex)i); + _poseStateMap[poseIndex] = controller::Pose(); + } +} diff --git a/plugins/hifiKinect/src/KinectPlugin.h b/plugins/hifiKinect/src/KinectPlugin.h index c66d746427..b10698fa31 100644 --- a/plugins/hifiKinect/src/KinectPlugin.h +++ b/plugins/hifiKinect/src/KinectPlugin.h @@ -41,7 +41,7 @@ template inline void SafeRelease(Interface *& pInterfaceToRelea class KinectPlugin : public InputPlugin { Q_OBJECT public: - bool isHandController() const override { return true; } + bool isHandController() const override; // Plugin functions virtual void init() override; @@ -79,6 +79,8 @@ protected: void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, const std::vector& joints, const std::vector& prevJoints); + + void clearState(); }; std::shared_ptr _inputDevice { std::make_shared() }; @@ -86,7 +88,8 @@ protected: static const char* NAME; static const char* KINECT_ID_STRING; - bool _enabled; + bool _enabled { false }; + mutable bool _initialized { false }; // copy of data directly from the KinectDataReader SDK std::vector _joints; @@ -97,18 +100,18 @@ protected: // Kinect SDK related items... - bool KinectPlugin::initializeDefaultSensor(); + bool KinectPlugin::initializeDefaultSensor() const; void updateBody(); #ifdef HAVE_KINECT void ProcessBody(INT64 time, int bodyCount, IBody** bodies); // Current Kinect - IKinectSensor* _kinectSensor { nullptr }; - ICoordinateMapper* _coordinateMapper { nullptr }; + mutable IKinectSensor* _kinectSensor { nullptr }; + mutable ICoordinateMapper* _coordinateMapper { nullptr }; // Body reader - IBodyFrameReader* _bodyFrameReader { nullptr }; + mutable IBodyFrameReader* _bodyFrameReader { nullptr }; #endif }; diff --git a/scripts/system/bubble.js b/scripts/system/bubble.js index b5134e096b..87043ccc8a 100644 --- a/scripts/system/bubble.js +++ b/scripts/system/bubble.js @@ -177,7 +177,8 @@ var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); button = tablet.addButton({ icon: "icons/tablet-icons/bubble-i.svg", - text: buttonName + text: buttonName, + sortOrder: 4 }); } onBubbleToggled(); diff --git a/scripts/system/edit.js b/scripts/system/edit.js index d2db83de6e..f8cce6a544 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -253,7 +253,8 @@ var toolBar = (function () { tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); activeButton = tablet.addButton({ icon: "icons/tablet-icons/edit-i.svg", - text: "EDIT" + text: "EDIT", + sortOrder: 10 }); } diff --git a/scripts/system/goto.js b/scripts/system/goto.js index 95bd05ae73..092abd0369 100644 --- a/scripts/system/goto.js +++ b/scripts/system/goto.js @@ -39,7 +39,8 @@ if (Settings.getValue("HUDUIEnabled")) { tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); button = tablet.addButton({ icon: "icons/tablet-icons/goto-i.svg", - text: buttonName + text: buttonName, + sortOrder: 8 }); } diff --git a/scripts/system/help.js b/scripts/system/help.js index 7813780da3..19c4b04363 100644 --- a/scripts/system/help.js +++ b/scripts/system/help.js @@ -30,7 +30,8 @@ tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); button = tablet.addButton({ icon: "icons/tablet-icons/help-i.svg", - text: buttonName + text: buttonName, + sortOrder: 6 }); } var enabled = false; diff --git a/scripts/system/hmd.js b/scripts/system/hmd.js index 61667d6ecc..c755454fbb 100644 --- a/scripts/system/hmd.js +++ b/scripts/system/hmd.js @@ -59,7 +59,8 @@ function onHmdChanged(isHmd) { } else { button.editProperties({ icon: "icons/tablet-icons/switch-i.svg", - text: "VR" + text: "VR", + sortOrder: 2 }); } desktopOnlyViews.forEach(function (view) { @@ -82,7 +83,8 @@ if (headset) { } else { button = tablet.addButton({ icon: "icons/tablet-icons/switch-a.svg", - text: "SWITCH" + text: "SWITCH", + sortOrder: 2 }); } onHmdChanged(HMD.active); diff --git a/scripts/system/libraries/WebTablet.js b/scripts/system/libraries/WebTablet.js index b6e0b8b0e1..0cd7d26854 100644 --- a/scripts/system/libraries/WebTablet.js +++ b/scripts/system/libraries/WebTablet.js @@ -25,13 +25,17 @@ var ROT_Y_180 = {x: 0, y: 1, z: 0, w: 0}; var TABLET_TEXTURE_RESOLUTION = { x: 480, y: 706 }; var INCHES_TO_METERS = 1 / 39.3701; var AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}"; -var TABLET_URL = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx"; + var NO_HANDS = -1; // will need to be recaclulated if dimensions of fbx model change. var TABLET_NATURAL_DIMENSIONS = {x: 33.797, y: 50.129, z: 2.269}; -var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-close.png"; -var TABLET_MODEL_PATH = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx"; + +var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-close.png"; +// var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-close.png"; +var TABLET_MODEL_PATH = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx"; +// var TABLET_MODEL_PATH = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx"; + // returns object with two fields: // * position - position in front of the user // * rotation - rotation of entity so it faces the user. diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index a58615673b..0803f753c7 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -19,7 +19,9 @@ var MARKETPLACE_URL = "https://metaverse.highfidelity.com/marketplace"; var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page. var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html"); var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); -var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; + +var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; +// var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; // Event bridge messages. var CLARA_IO_DOWNLOAD = "CLARA.IO DOWNLOAD"; @@ -132,7 +134,8 @@ if (Settings.getValue("HUDUIEnabled")) { tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); marketplaceButton = tablet.addButton({ icon: "icons/tablet-icons/market-i.svg", - text: "MARKET" + text: "MARKET", + sortOrder: 9 }); } diff --git a/scripts/system/menu.js b/scripts/system/menu.js index 9858b69476..13c6ce1e0d 100644 --- a/scripts/system/menu.js +++ b/scripts/system/menu.js @@ -9,15 +9,17 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; +var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; +//var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; + (function() { var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var button = tablet.addButton({ icon: "icons/tablet-icons/menu-i.svg", - text: "MENU" + text: "MENU", + sortOrder: 3 }); - function onClicked() { var entity = HMD.tabletID; Entities.editEntity(entity, {textures: JSON.stringify({"tex.close": HOME_BUTTON_TEXTURE})}); @@ -29,5 +31,5 @@ var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-butt Script.scriptEnding.connect(function () { button.clicked.disconnect(onClicked); tablet.removeButton(button); - }) + }); }()); diff --git a/scripts/system/mute.js b/scripts/system/mute.js index fff40eb883..f28a2eb9a5 100644 --- a/scripts/system/mute.js +++ b/scripts/system/mute.js @@ -40,7 +40,8 @@ if (Settings.getValue("HUDUIEnabled")) { icon: "icons/tablet-icons/mic-a.svg", text: buttonName, activeIcon: "icons/tablet-icons/mic-i.svg", - activeText: "UNMUTE" + activeText: "UNMUTE", + sortOrder: 1 }); } onMuteToggled(); diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 8c53ccd59d..9e9c49b1a0 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -493,7 +493,8 @@ if (Settings.getValue("HUDUIEnabled")) { tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); button = tablet.addButton({ text: buttonName, - icon: "icons/tablet-icons/people-i.svg" + icon: "icons/tablet-icons/people-i.svg", + sortOrder: 7 }); } var isWired = false; diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index ed22b60242..c9462bbe7f 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -36,7 +36,8 @@ if (Settings.getValue("HUDUIEnabled")) { tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); button = tablet.addButton({ icon: "icons/tablet-icons/snap-i.svg", - text: buttonName + text: buttonName, + sortOrder: 5 }); } diff --git a/scripts/system/tablet-users.js b/scripts/system/tablet-users.js index 0fad0c56f3..f832fa304a 100644 --- a/scripts/system/tablet-users.js +++ b/scripts/system/tablet-users.js @@ -35,7 +35,8 @@ var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); var button = tablet.addButton({ icon: "icons/tablet-icons/people-i.svg", - text: "Users" + text: "USERS", + sortOrder: 11 }); function onClicked() { diff --git a/scripts/system/users.js b/scripts/system/users.js index 009c446ff3..480b9f07a2 100644 --- a/scripts/system/users.js +++ b/scripts/system/users.js @@ -33,7 +33,8 @@ if (Settings.getValue("HUDUIEnabled")) { button = tablet.addButton({ icon: "icons/tablet-icons/users-i.svg", text: "USERS", - isActive: Menu.isOptionChecked(MENU_ITEM) + isActive: Menu.isOptionChecked(MENU_ITEM), + sortOrder: 11 }); } diff --git a/unpublishedScripts/marketplace/stopwatch/chime.wav b/unpublishedScripts/marketplace/stopwatch/chime.wav new file mode 100644 index 0000000000..cd7143eeeb Binary files /dev/null and b/unpublishedScripts/marketplace/stopwatch/chime.wav differ diff --git a/unpublishedScripts/marketplace/stopwatch/spawnStopwatch.js b/unpublishedScripts/marketplace/stopwatch/spawnStopwatch.js new file mode 100644 index 0000000000..4203db37fa --- /dev/null +++ b/unpublishedScripts/marketplace/stopwatch/spawnStopwatch.js @@ -0,0 +1,48 @@ +// +// spawnStopwatch.js +// +// Created by Ryan Huffman on 1/20/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var positionToSpawn = Vec3.sum(MyAvatar.position, Quat.getFront(MyAvatar.rotation)); + +var stopwatchID = Entities.addEntity({ + type: "Model", + name: "stopwatch/base", + position: positionToSpawn, + modelURL: "http://hifi-content.s3.amazonaws.com/alan/dev/Stopwatch.fbx", + dimensions: {"x":4.129462242126465,"y":1.058512806892395,"z":5.773681640625} +}); + +var secondHandID = Entities.addEntity({ + type: "Model", + name: "stopwatch/seconds", + parentID: stopwatchID, + localPosition: {"x":-0.004985813982784748,"y":0.39391064643859863,"z":0.8312804698944092}, + dimensions: {"x":0.14095762372016907,"y":0.02546107769012451,"z":1.6077008247375488}, + registrationPoint: {"x":0.5,"y":0.5,"z":1}, + modelURL: "http://hifi-content.s3.amazonaws.com/alan/dev/Stopwatch-sec-hand.fbx", +}); + +var minuteHandID = Entities.addEntity({ + type: "Model", + name: "stopwatch/minutes", + parentID: stopwatchID, + localPosition: {"x":-0.0023056098725646734,"y":0.3308190703392029,"z":0.21810021996498108}, + dimensions: {"x":0.045471154153347015,"y":0.015412690117955208,"z":0.22930574417114258}, + registrationPoint: {"x":0.5,"y":0.5,"z":1}, + modelURL: "http://hifi-content.s3.amazonaws.com/alan/dev/Stopwatch-min-hand.fbx", +}); + +Entities.editEntity(stopwatchID, { + userData: JSON.stringify({ + secondHandID: secondHandID, + minuteHandID: minuteHandID, + }), + script: Script.resolvePath("stopwatchClient.js"), + serverScripts: Script.resolvePath("stopwatchServer.js") +}); diff --git a/unpublishedScripts/marketplace/stopwatch/stopwatchClient.js b/unpublishedScripts/marketplace/stopwatch/stopwatchClient.js new file mode 100644 index 0000000000..6284b86102 --- /dev/null +++ b/unpublishedScripts/marketplace/stopwatch/stopwatchClient.js @@ -0,0 +1,22 @@ +// +// stopwatchServer.js +// +// Created by Ryan Huffman on 1/20/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { + var messageChannel; + this.preload = function(entityID) { + this.messageChannel = "STOPWATCH-" + entityID; + }; + function click() { + Messages.sendMessage(this.messageChannel, 'click'); + } + this.startNearTrigger = click; + this.startFarTrigger = click; + this.clickDownOnEntity = click; +}); diff --git a/unpublishedScripts/marketplace/stopwatch/stopwatchServer.js b/unpublishedScripts/marketplace/stopwatch/stopwatchServer.js new file mode 100644 index 0000000000..b923d6af88 --- /dev/null +++ b/unpublishedScripts/marketplace/stopwatch/stopwatchServer.js @@ -0,0 +1,119 @@ +// +// stopwatchServer.js +// +// Created by Ryan Huffman on 1/20/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { + var self = this; + + self.equipped = false; + self.isActive = false; + + self.secondHandID = null; + self.minuteHandID = null; + + self.tickSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/huffman/tick.wav"); + self.tickInjector = null; + self.tickIntervalID = null; + + self.chimeSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/huffman/chime.wav"); + + self.preload = function(entityID) { + print("Preloading stopwatch: ", entityID); + self.entityID = entityID; + self.messageChannel = "STOPWATCH-" + entityID; + + var userData = Entities.getEntityProperties(self.entityID, 'userData').userData; + var data = JSON.parse(userData); + self.secondHandID = data.secondHandID; + self.minuteHandID = data.minuteHandID; + + self.resetTimer(); + + Messages.subscribe(self.messageChannel); + Messages.messageReceived.connect(this, self.messageReceived); + }; + self.unload = function() { + print("Unloading stopwatch:", self.entityID); + self.resetTimer(); + Messages.unsubscribe(self.messageChannel); + Messages.messageReceived.disconnect(this, self.messageReceived); + }; + self.messageReceived = function(channel, message, sender) { + print("Message received", channel, sender, message); + if (channel === self.messageChannel && message === 'click') { + if (self.isActive) { + self.resetTimer(); + } else { + self.startTimer(); + } + } + }; + self.getStopwatchPosition = function() { + return Entities.getEntityProperties(self.entityID, "position").position; + }; + self.resetTimer = function() { + print("Stopping stopwatch"); + if (self.tickInjector) { + self.tickInjector.stop(); + } + if (self.tickIntervalID !== null) { + Script.clearInterval(self.tickIntervalID); + self.tickIntervalID = null; + } + Entities.editEntity(self.secondHandID, { + rotation: Quat.fromPitchYawRollDegrees(0, 0, 0), + angularVelocity: { x: 0, y: 0, z: 0 }, + }); + Entities.editEntity(self.minuteHandID, { + rotation: Quat.fromPitchYawRollDegrees(0, 0, 0), + angularVelocity: { x: 0, y: 0, z: 0 }, + }); + self.isActive = false; + }; + self.startTimer = function() { + print("Starting stopwatch"); + if (!self.tickInjector) { + self.tickInjector = Audio.playSound(self.tickSound, { + position: self.getStopwatchPosition(), + volume: 0.7, + loop: true + }); + } else { + self.tickInjector.restart(); + } + + var seconds = 0; + self.tickIntervalID = Script.setInterval(function() { + if (self.tickInjector) { + self.tickInjector.setOptions({ + position: self.getStopwatchPosition(), + volume: 0.7, + loop: true + }); + } + seconds++; + const degreesPerTick = -360 / 60; + Entities.editEntity(self.secondHandID, { + rotation: Quat.fromPitchYawRollDegrees(0, seconds * degreesPerTick, 0), + }); + if (seconds % 60 == 0) { + Entities.editEntity(self.minuteHandID, { + rotation: Quat.fromPitchYawRollDegrees(0, (seconds / 60) * degreesPerTick, 0), + }); + Audio.playSound(self.chimeSound, { + position: self.getStopwatchPosition(), + volume: 1.0, + loop: false + }); + } + }, 1000); + + self.isActive = true; + }; +}); diff --git a/unpublishedScripts/marketplace/stopwatch/tick.wav b/unpublishedScripts/marketplace/stopwatch/tick.wav new file mode 100644 index 0000000000..21781f8ce4 Binary files /dev/null and b/unpublishedScripts/marketplace/stopwatch/tick.wav differ