diff --git a/examples/headMove.js b/examples/headMove.js index 236130e23d..831e77a723 100644 --- a/examples/headMove.js +++ b/examples/headMove.js @@ -64,7 +64,6 @@ function activateWarp() { var TRIGGER_PULLBACK_DISTANCE = 0.04; var WATCH_AVATAR_DISTANCE = 1.5; -var MAX_WARP_YAW = 40.0; var MAX_PULLBACK_YAW = 5.0; var sound = new Sound("http://public.highfidelity.io/sounds/Footsteps/FootstepW2Right-12db.wav"); @@ -72,7 +71,7 @@ function playSound() { var options = new AudioInjectionOptions(); var position = MyAvatar.position; options.position = position; - options.volume = 0.5; + options.volume = 1.0; Audio.playSound(sound, options); } @@ -89,7 +88,7 @@ function updateWarp() { var deltaPitch = MyAvatar.getHeadFinalPitch() - headStartFinalPitch; deltaYaw = MyAvatar.getHeadFinalYaw() - headStartYaw; - willMove = (!watchAvatar && (Math.abs(deltaYaw) < MAX_WARP_YAW) && (keyDownTime > WARP_START_TIME)); + willMove = (!watchAvatar && (keyDownTime > WARP_START_TIME)); if (willMove) { //var distance = Math.pow((deltaPitch - WARP_PITCH_DEAD_ZONE) * WARP_SENSITIVITY, 2.0); diff --git a/examples/libraries/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js index b70c2187c2..f1a94dbdd7 100644 --- a/examples/libraries/entitySelectionTool.js +++ b/examples/libraries/entitySelectionTool.js @@ -78,6 +78,8 @@ SelectionManager = (function() { that.worldDimensions = null; that.worldPosition = null; } else if (that.selections.length == 1) { + SelectionDisplay.setSpaceMode(SPACE_LOCAL); + var properties = Entities.getEntityProperties(that.selections[0]); that.localDimensions = properties.dimensions; that.localPosition = properties.position; @@ -622,8 +624,8 @@ SelectionDisplay = (function () { } - var diagonal = (Vec3.length(properties.dimensions) / 2) * 1.1; - var halfDimensions = Vec3.multiply(properties.dimensions, 0.5); + var diagonal = (Vec3.length(selectionManager.worldDimensions) / 2) * 1.1; + var halfDimensions = Vec3.multiply(selectionManager.worldDimensions, 0.5); innerRadius = diagonal; outerRadius = diagonal * 1.15; var innerActive = false; @@ -843,7 +845,7 @@ SelectionDisplay = (function () { Overlays.editOverlay(grabberMoveUp, { visible: translateHandlesVisible, position: { x: boundsCenter.x, y: top + grabberMoveUpOffset, z: boundsCenter.z } }); - that.updateHandles(entityID); + that.updateHandles(); Overlays.editOverlay(baseOfEntityProjectionOverlay, @@ -924,18 +926,17 @@ SelectionDisplay = (function () { entitySelected = false; }; - that.updateHandles = function(entityID) { - if (!entitySelected) { + that.updateHandles = function() { + // print("Updating handles"); + if (SelectionManager.selections.length == 0) { that.setOverlaysVisible(false); return; } - var properties = Entities.getEntityProperties(entityID); - var rotation, dimensions, position; if (spaceMode == SPACE_LOCAL) { - rotation = properties.rotation; + rotation = SelectionManager.localRotation; dimensions = SelectionManager.localDimensions; position = SelectionManager.localPosition; } else { @@ -1095,6 +1096,44 @@ SelectionDisplay = (function () { entitySelected = false; }; + function applyEntityProperties(data) { + for (var i = 0; i < data.length; i++) { + var entityID = data[i].entityID; + var properties = data[i].properties; + Entities.editEntity(entityID, properties); + } + selectionManager._update(); + }; + + // For currently selected entities, push a command to the UndoStack that uses the current entity properties for the + // redo command, and the saved properties for the undo command. + function pushCommandForSelections() { + var undoData = []; + var redoData = []; + for (var i = 0; i < SelectionManager.selections.length; i++) { + var entityID = SelectionManager.selections[i]; + var initialProperties = SelectionManager.savedProperties[entityID.id]; + var currentProperties = Entities.getEntityProperties(entityID); + undoData.push({ + entityID: entityID, + properties: { + position: initialProperties.position, + rotation: initialProperties.rotation, + dimensions: initialProperties.dimensions, + }, + }); + redoData.push({ + entityID: entityID, + properties: { + position: currentProperties.position, + rotation: currentProperties.rotation, + dimensions: currentProperties.dimensions, + }, + }); + } + UndoStack.pushCommand(applyEntityProperties, undoData, applyEntityProperties, redoData); + } + var lastXZPick = null; var translateXZTool = { mode: 'TRANSLATE_XZ', @@ -1114,6 +1153,8 @@ SelectionDisplay = (function () { var initialProperties = SelectionManager.savedProperties[entityID.id]; Entities.editEntity(entityID, initialProperties); } + } else { + pushCommandForSelections(); } }, onMove: function(event) { @@ -1172,6 +1213,8 @@ SelectionDisplay = (function () { var initialProperties = SelectionManager.savedProperties[entityID.id]; Entities.editEntity(entityID, initialProperties); } + } else { + pushCommandForSelections(); } }, onMove: function(event) { @@ -1334,6 +1377,8 @@ SelectionDisplay = (function () { var initialProperties = SelectionManager.savedProperties[entityID.id]; Entities.editEntity(entityID, initialProperties); } + } else { + pushCommandForSelections(); } }; @@ -1496,6 +1541,8 @@ SelectionDisplay = (function () { var initialProperties = SelectionManager.savedProperties[entityID.id]; Entities.editEntity(entityID, initialProperties); } + } else { + pushCommandForSelections(); } }, onMove: function(event) { @@ -1602,6 +1649,8 @@ SelectionDisplay = (function () { var initialProperties = SelectionManager.savedProperties[entityID.id]; Entities.editEntity(entityID, initialProperties); } + } else { + pushCommandForSelections(); } }, onMove: function(event) { @@ -1706,6 +1755,8 @@ SelectionDisplay = (function () { var initialProperties = SelectionManager.savedProperties[entityID.id]; Entities.editEntity(entityID, initialProperties); } + } else { + pushCommandForSelections(); } }, onMove: function(event) { diff --git a/examples/newEditEntities.js b/examples/newEditEntities.js index 9b46cdaf3f..4d5abaf254 100644 --- a/examples/newEditEntities.js +++ b/examples/newEditEntities.js @@ -35,7 +35,7 @@ var entityPropertyDialogBox = EntityPropertyDialogBox; Script.include("libraries/entityCameraTool.js"); var entityCameraTool = new EntityCameraTool(); -selectionManager.setEventListener(selectionDisplay.updateHandles()); +selectionManager.setEventListener(selectionDisplay.updateHandles); var windowDimensions = Controller.getViewportDimensions(); var toolIconUrl = HIFI_PUBLIC_BUCKET + "images/tools/"; @@ -608,9 +608,12 @@ function handeMenuEvent(menuItem) { } else if (menuItem == "Delete") { if (entitySelected) { print(" Delete Entity.... selectedEntityID="+ selectedEntityID); - Entities.deleteEntity(selectedEntityID); + for (var i = 0; i < selectionManager.selections.length; i++) { + Entities.deleteEntity(selectionManager.selections[i]); + } selectionDisplay.unselect(selectedEntityID); entitySelected = false; + selectionManager.clearSelections(); } else { print(" Delete Entity.... not holding..."); } @@ -618,7 +621,7 @@ function handeMenuEvent(menuItem) { // good place to put the properties dialog editModelID = -1; - if (entitySelected) { + if (selectionManager.selections.length == 1) { print(" Edit Properties.... selectedEntityID="+ selectedEntityID); editModelID = selectedEntityID; } else { @@ -653,7 +656,7 @@ Controller.keyReleaseEvent.connect(function (event) { if (event.text == "`") { handeMenuEvent("Edit Properties..."); } - if (event.text == "BACKSPACE") { + if (event.text == "BACKSPACE" || event.text == "DELETE") { handeMenuEvent("Delete"); } else if (event.text == "TAB") { selectionDisplay.toggleSpaceMode(); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 145222cd3c..c379f701cf 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -172,6 +172,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _nodeBoundsDisplay(this), _previousScriptLocation(), _applicationOverlay(), + _undoStack(), + _undoStackScriptingInterface(&_undoStack), _runningScriptsWidget(NULL), _runningScriptsWidgetWasVisible(false), _trayIcon(new QSystemTrayIcon(_window)), @@ -3766,8 +3768,9 @@ ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUser // AvatarManager has some custom types AvatarManager::registerMetaTypes(scriptEngine); - // hook our avatar object into this script engine + // hook our avatar and avatar hash map object into this script engine scriptEngine->setAvatarData(_myAvatar, "MyAvatar"); // leave it as a MyAvatar class to expose thrust features + scriptEngine->setAvatarHashMap(&_avatarManager, "AvatarList"); CameraScriptableObject* cameraScriptable = new CameraScriptableObject(&_myCamera, &_viewFrustum); scriptEngine->registerGlobalObject("Camera", cameraScriptable); @@ -3810,6 +3813,8 @@ ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUser scriptEngine->registerGlobalObject("Joysticks", &JoystickScriptingInterface::getInstance()); qScriptRegisterMetaType(scriptEngine, joystickToScriptValue, joystickFromScriptValue); + scriptEngine->registerGlobalObject("UndoStack", &_undoStackScriptingInterface); + #ifdef HAVE_RTMIDI scriptEngine->registerGlobalObject("MIDI", &MIDIManager::getInstance()); #endif diff --git a/interface/src/Application.h b/interface/src/Application.h index 64c7032403..4779f9c810 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -91,6 +91,9 @@ #include "voxels/VoxelSystem.h" +#include "UndoStackScriptingInterface.h" + + class QAction; class QActionGroup; class QGLWidget; @@ -450,6 +453,7 @@ private: int _numChangedSettings; QUndoStack _undoStack; + UndoStackScriptingInterface _undoStackScriptingInterface; glm::vec3 _gravity; diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp index 62894510c2..709080e354 100644 --- a/interface/src/devices/OculusManager.cpp +++ b/interface/src/devices/OculusManager.cpp @@ -66,8 +66,6 @@ glm::vec3 OculusManager::_calibrationPosition; glm::quat OculusManager::_calibrationOrientation; quint64 OculusManager::_calibrationStartTime; int OculusManager::_calibrationMessage = NULL; -QString OculusManager::CALIBRATION_BILLBOARD_URL = "http://hifi-public.s3.amazonaws.com/images/hold-to-calibrate.svg"; -float OculusManager::CALIBRATION_BILLBOARD_SCALE = 2.f; #endif @@ -191,7 +189,7 @@ void OculusManager::disconnect() { } #ifdef HAVE_LIBOVR -void OculusManager::positionCalibrationBillboard(BillboardOverlay* billboard) { +void OculusManager::positionCalibrationBillboard(Text3DOverlay* billboard) { glm::quat headOrientation = Application::getInstance()->getAvatar()->getHeadOrientation(); headOrientation.x = 0; headOrientation.z = 0; @@ -204,8 +202,9 @@ void OculusManager::positionCalibrationBillboard(BillboardOverlay* billboard) { #ifdef HAVE_LIBOVR void OculusManager::calibrate(glm::vec3 position, glm::quat orientation) { + static QString instructionMessage = "Hold still to calibrate"; static QString progressMessage; - static BillboardOverlay* billboard; + static Text3DOverlay* billboard; switch (_calibrationState) { @@ -235,9 +234,13 @@ void OculusManager::calibrate(glm::vec3 position, glm::quat orientation) { if (!_calibrationMessage) { qDebug() << "Hold still to calibrate HMD"; - billboard = new BillboardOverlay(); - billboard->setURL(CALIBRATION_BILLBOARD_URL); - billboard->setScale(CALIBRATION_BILLBOARD_SCALE); + billboard = new Text3DOverlay(); + billboard->setDimensions(glm::vec2(2.0f, 1.25f)); + billboard->setTopMargin(0.35f); + billboard->setLeftMargin(0.28f); + billboard->setText(instructionMessage); + billboard->setAlpha(0.5f); + billboard->setLineHeight(0.1f); billboard->setIsFacingAvatar(false); positionCalibrationBillboard(billboard); @@ -275,7 +278,7 @@ void OculusManager::calibrate(glm::vec3 position, glm::quat orientation) { } else { progressMessage += "."; } - //qDebug() << progressMessage; // Progress message ready for 3D text overlays. + billboard->setText(instructionMessage + "\n\n" + progressMessage); } } } else { diff --git a/interface/src/devices/OculusManager.h b/interface/src/devices/OculusManager.h index dfe4a212b6..20e43d572c 100644 --- a/interface/src/devices/OculusManager.h +++ b/interface/src/devices/OculusManager.h @@ -18,7 +18,7 @@ #endif #include "renderer/ProgramObject.h" -#include "ui/overlays/BillboardOverlay.h" +#include "ui/overlays/Text3DOverlay.h" const float DEFAULT_OCULUS_UI_ANGULAR_SIZE = 72.0f; @@ -111,7 +111,7 @@ private: WAITING_FOR_ZERO_HELD, CALIBRATED }; - static void positionCalibrationBillboard(BillboardOverlay* billboard); + static void positionCalibrationBillboard(Text3DOverlay* message); static float CALIBRATION_DELTA_MINIMUM_LENGTH; static float CALIBRATION_DELTA_MINIMUM_ANGLE; static float CALIBRATION_ZERO_MAXIMUM_LENGTH; @@ -123,8 +123,6 @@ private: static glm::quat _calibrationOrientation; static quint64 _calibrationStartTime; static int _calibrationMessage; - static QString CALIBRATION_BILLBOARD_URL; - static float CALIBRATION_BILLBOARD_SCALE; #endif diff --git a/interface/src/ui/overlays/Text3DOverlay.h b/interface/src/ui/overlays/Text3DOverlay.h index 855890e493..c49116ee0c 100644 --- a/interface/src/ui/overlays/Text3DOverlay.h +++ b/interface/src/ui/overlays/Text3DOverlay.h @@ -32,6 +32,7 @@ public: float getTopMargin() const { return _topMargin; } float getRightMargin() const { return _rightMargin; } float getBottomMargin() const { return _bottomMargin; } + bool getIsFacingAvatar() const { return _isFacingAvatar; } xColor getBackgroundColor(); // setters @@ -41,6 +42,7 @@ public: void setTopMargin(float margin) { _topMargin = margin; } void setRightMargin(float margin) { _rightMargin = margin; } void setBottomMargin(float margin) { _bottomMargin = margin; } + void setIsFacingAvatar(bool isFacingAvatar) { _isFacingAvatar = isFacingAvatar; } virtual void setProperties(const QScriptValue& properties); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 1fd4052974..29682da286 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -381,6 +381,8 @@ private: AvatarData& operator= (const AvatarData&); }; +Q_DECLARE_METATYPE(AvatarData*) + class JointData { public: bool valid; diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index f996fc2bad..4f69eb2e3d 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -21,6 +21,7 @@ AvatarHashMap::AvatarHashMap() : connect(NodeList::getInstance(), &NodeList::uuidChanged, this, &AvatarHashMap::sessionUUIDChanged); } + AvatarHash::iterator AvatarHashMap::erase(const AvatarHash::iterator& iterator) { qDebug() << "Removing Avatar with UUID" << iterator.key() << "from AvatarHashMap."; return _avatarHash.erase(iterator); @@ -53,29 +54,26 @@ void AvatarHashMap::processAvatarMixerDatagram(const QByteArray& datagram, const } bool AvatarHashMap::containsAvatarWithDisplayName(const QString& displayName) { - - AvatarHash::iterator avatarIterator = _avatarHash.begin(); - while (avatarIterator != _avatarHash.end()) { - AvatarSharedPointer sharedAvatar = avatarIterator.value(); - if (avatarIterator.value()->getDisplayName() == displayName) { + return avatarWithDisplayName(displayName) == NULL ? false : true; +} + +AvatarData* AvatarHashMap::avatarWithDisplayName(const QString& displayName) { + foreach(const AvatarSharedPointer& sharedAvatar, _avatarHash) { + if (sharedAvatar->getDisplayName() == displayName) { // this is a match // check if this avatar should still be around if (!shouldKillAvatar(sharedAvatar)) { - // we have a match, return true - return true; + // we have a match, return the AvatarData + return sharedAvatar.data(); } else { - // we should remove this avatar, do that now - erase(avatarIterator); + // we should remove this avatar, but we might not be on a thread that is allowed + // so we just return NULL to the caller + return NULL; } - - break; - } else { - ++avatarIterator; } } - - // return false, no match - return false; + + return NULL; } AvatarSharedPointer AvatarHashMap::newSharedAvatar() { diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index d52c656bc1..03b0bf887c 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -21,6 +21,7 @@ #include "AvatarData.h" typedef QSharedPointer AvatarSharedPointer; +typedef QWeakPointer AvatarWeakPointer; typedef QHash AvatarHash; class AvatarHashMap : public QObject { @@ -34,6 +35,7 @@ public: public slots: void processAvatarMixerDatagram(const QByteArray& datagram, const QWeakPointer& mixerWeakPointer); bool containsAvatarWithDisplayName(const QString& displayName); + AvatarData* avatarWithDisplayName(const QString& displayname); private slots: void sessionUUIDChanged(const QUuid& sessionUUID, const QUuid& oldUUID); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index eb5dd07ffb..93da900055 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -63,7 +63,15 @@ static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine){ return QScriptValue(); } -QScriptValue injectorToScriptValue(QScriptEngine *engine, AudioInjector* const &in) { +QScriptValue avatarDataToScriptValue(QScriptEngine* engine, AvatarData* const &in) { + return engine->newQObject(in); +} + +void avatarDataFromScriptValue(const QScriptValue &object, AvatarData* &out) { + out = qobject_cast(object.toQObject()); +} + +QScriptValue injectorToScriptValue(QScriptEngine* engine, AudioInjector* const &in) { return engine->newQObject(in); } @@ -272,7 +280,7 @@ void ScriptEngine::init() { qScriptRegisterMetaType(this, injectorToScriptValue, injectorFromScriptValue); qScriptRegisterMetaType(this, inputControllerToScriptValue, inputControllerFromScriptValue); - + qScriptRegisterMetaType(this, avatarDataToScriptValue, avatarDataFromScriptValue); qScriptRegisterMetaType(this, animationDetailsToScriptValue, animationDetailsFromScriptValue); registerGlobalObject("Script", this); diff --git a/libraries/script-engine/src/UndoStackScriptingInterface.cpp b/libraries/script-engine/src/UndoStackScriptingInterface.cpp new file mode 100644 index 0000000000..ed0f4d563d --- /dev/null +++ b/libraries/script-engine/src/UndoStackScriptingInterface.cpp @@ -0,0 +1,58 @@ +// +// UndoStackScriptingInterface.cpp +// libraries/script-engine/src +// +// Created by Ryan Huffman on 10/22/14. +// Copyright 2014 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 +#include +#include +#include + +#include "UndoStackScriptingInterface.h" + +UndoStackScriptingInterface::UndoStackScriptingInterface(QUndoStack* undoStack) : _undoStack(undoStack) { +} + +void UndoStackScriptingInterface::pushCommand(QScriptValue undoFunction, QScriptValue undoData, + QScriptValue redoFunction, QScriptValue redoData) { + if (undoFunction.engine()) { + ScriptUndoCommand* undoCommand = new ScriptUndoCommand(undoFunction, undoData, redoFunction, redoData); + undoCommand->moveToThread(undoFunction.engine()->thread()); + _undoStack->push(undoCommand); + } +} + +ScriptUndoCommand::ScriptUndoCommand(QScriptValue undoFunction, QScriptValue undoData, + QScriptValue redoFunction, QScriptValue redoData) : + _undoFunction(undoFunction), + _undoData(undoData), + _redoFunction(redoFunction), + _redoData(redoData) { +} + +void ScriptUndoCommand::undo() { + QMetaObject::invokeMethod(this, "doUndo"); +} + +void ScriptUndoCommand::redo() { + QMetaObject::invokeMethod(this, "doRedo"); +} + +void ScriptUndoCommand::doUndo() { + QScriptValueList args; + args << _undoData; + _undoFunction.call(QScriptValue(), args); +} + + +void ScriptUndoCommand::doRedo() { + QScriptValueList args; + args << _redoData; + _redoFunction.call(QScriptValue(), args); +} diff --git a/libraries/script-engine/src/UndoStackScriptingInterface.h b/libraries/script-engine/src/UndoStackScriptingInterface.h new file mode 100644 index 0000000000..835e5dfff4 --- /dev/null +++ b/libraries/script-engine/src/UndoStackScriptingInterface.h @@ -0,0 +1,52 @@ +// +// UndoStackScriptingInterface.h +// libraries/script-engine/src +// +// Created by Ryan Huffman on 10/22/14. +// Copyright 2014 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 +// + +#ifndef hifi_UndoStackScriptingInterface_h +#define hifi_UndoStackScriptingInterface_h + +#include +#include +#include + +class UndoStackScriptingInterface : public QObject { + Q_OBJECT +public: + UndoStackScriptingInterface(QUndoStack* undoStack); + +public slots: + void pushCommand(QScriptValue undoFunction, QScriptValue undoData, QScriptValue redoFunction, QScriptValue redoData); + +private: + QUndoStack* _undoStack; +}; + +class ScriptUndoCommand : public QObject, public QUndoCommand { + Q_OBJECT +public: + ScriptUndoCommand(QScriptValue undoFunction, QScriptValue undoData, QScriptValue redoFunction, QScriptValue redoData); + + virtual void undo(); + virtual void redo(); + virtual bool mergeWith(const QUndoCommand* command) { return false; } + virtual int id() const { return -1; } + +public slots: + void doUndo(); + void doRedo(); + +private: + QScriptValue _undoFunction; + QScriptValue _undoData; + QScriptValue _redoFunction; + QScriptValue _redoData; +}; + +#endif // hifi_UndoStackScriptingInterface_h