From a83616a7dcb911204bcde7decf3c44db4d384db3 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 30 Apr 2015 10:32:19 -0700 Subject: [PATCH 01/41] Don't prevent building on Visual Studio 2015 --- CMakeLists.txt | 2 +- cmake/macros/SetupExternalsBinaryDir.cmake | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b271664c35..24deac8393 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,7 +51,7 @@ elseif (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -fno-strict-aliasing -Wno-unused-parameter -ggdb") endif(WIN32) -if (NOT MSVC12) +if ((NOT MSVC12) AND (NOT MSVC14)) include(CheckCXXCompilerFlag) CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11) CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X) diff --git a/cmake/macros/SetupExternalsBinaryDir.cmake b/cmake/macros/SetupExternalsBinaryDir.cmake index a118dcf543..118df9c8fa 100644 --- a/cmake/macros/SetupExternalsBinaryDir.cmake +++ b/cmake/macros/SetupExternalsBinaryDir.cmake @@ -14,12 +14,12 @@ macro(SETUP_EXTERNALS_BINARY_DIR) # get a short name for the generator to use in the path STRING(REGEX REPLACE " " "-" CMAKE_GENERATOR_FOLDER_NAME ${CMAKE_GENERATOR}) - if (MSVC12) + if (MSVC12) set(CMAKE_GENERATOR_FOLDER_NAME "vc12") - else () - if (CMAKE_GENERATOR_FOLDER_NAME STREQUAL "Unix-Makefiles") - set(CMAKE_GENERATOR_FOLDER_NAME "makefiles") - endif () + elseif (MSVC14) + set(CMAKE_GENERATOR_FOLDER_NAME "vc14") + elseif(CMAKE_GENERATOR_FOLDER_NAME STREQUAL "Unix-Makefiles") + set(CMAKE_GENERATOR_FOLDER_NAME "makefiles") endif () set(EXTERNALS_BINARY_ROOT_DIR "${CMAKE_CURRENT_BINARY_DIR}/ext") From 984d449bf9a391d6385ec9fd239b002e4cf5b8ad Mon Sep 17 00:00:00 2001 From: Sam Gondelman Date: Thu, 4 Jun 2015 16:29:58 -0700 Subject: [PATCH 02/41] exposed input key bindings to js to enable key remapping --- interface/src/Application.h | 1 + interface/src/devices/KeyboardMouseDevice.cpp | 18 ++- .../ControllerScriptingInterface.cpp | 117 +++++++++++++++++- .../scripting/ControllerScriptingInterface.h | 14 ++- interface/src/ui/UserInputMapper.cpp | 96 +++++++++++++- interface/src/ui/UserInputMapper.h | 56 +++++++-- .../AbstractControllerScriptingInterface.h | 2 + libraries/script-engine/src/ScriptEngine.cpp | 1 + 8 files changed, 289 insertions(+), 16 deletions(-) diff --git a/interface/src/Application.h b/interface/src/Application.h index b6efb6420b..9b815cb51a 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -148,6 +148,7 @@ public: static glm::quat getOrientationForPath() { return getInstance()->_myAvatar->getOrientation(); } static glm::vec3 getPositionForAudio() { return getInstance()->_myAvatar->getHead()->getPosition(); } static glm::quat getOrientationForAudio() { return getInstance()->_myAvatar->getHead()->getFinalOrientationInWorldFrame(); } + static UserInputMapper* getUserInputMapper() { return &getInstance()->_userInputMapper; } static void initPlugins(); static void shutdownPlugins(); diff --git a/interface/src/devices/KeyboardMouseDevice.cpp b/interface/src/devices/KeyboardMouseDevice.cpp index 7b0f8c056c..a7e85d28e1 100755 --- a/interface/src/devices/KeyboardMouseDevice.cpp +++ b/interface/src/devices/KeyboardMouseDevice.cpp @@ -159,9 +159,25 @@ void KeyboardMouseDevice::registerToUserInputMapper(UserInputMapper& mapper) { // Grab the current free device ID _deviceID = mapper.getFreeDeviceID(); - auto proxy = UserInputMapper::DeviceProxy::Pointer(new UserInputMapper::DeviceProxy()); + auto proxy = UserInputMapper::DeviceProxy::Pointer(new UserInputMapper::DeviceProxy("Keyboard")); proxy->getButton = [this] (const UserInputMapper::Input& input, int timestamp) -> bool { return this->getButton(input._channel); }; proxy->getAxis = [this] (const UserInputMapper::Input& input, int timestamp) -> float { return this->getAxis(input._channel); }; + proxy->getAvailabeInputs = [this] () -> QVector { + QVector availableInputs; + for (int i = 0; i <= 9; i++) { + availableInputs.append(UserInputMapper::InputPair(makeInput(Qt::Key(48 + i)), QKeySequence(Qt::Key(48 + i)).toString())); + } + for (int i = 0; i < 26; i++) { + availableInputs.append(UserInputMapper::InputPair(makeInput(Qt::Key(65 + i)), QKeySequence(Qt::Key(65 + i)).toString())); + } + availableInputs.append(UserInputMapper::InputPair(makeInput(Qt::Key_Space), QKeySequence(Qt::Key_Space).toString())); + return availableInputs; + }; + proxy->resetDeviceBindings = [this, &_mapper = mapper, &device = _deviceID] () -> bool { + _mapper.removeAllInputChannelsForDevice(device); + this->assignDefaultInputMapping(_mapper); + return true; + }; mapper.registerDevice(_deviceID, proxy); } diff --git a/interface/src/scripting/ControllerScriptingInterface.cpp b/interface/src/scripting/ControllerScriptingInterface.cpp index 5f12d73b37..fe2eda12df 100644 --- a/interface/src/scripting/ControllerScriptingInterface.cpp +++ b/interface/src/scripting/ControllerScriptingInterface.cpp @@ -28,6 +28,90 @@ ControllerScriptingInterface::ControllerScriptingInterface() : { } +static int actionMetaTypeId = qRegisterMetaType(); +static int inputChannelMetaTypeId = qRegisterMetaType(); +static int inputMetaTypeId = qRegisterMetaType(); +static int inputPairMetaTypeId = qRegisterMetaType(); + +QScriptValue inputToScriptValue(QScriptEngine* engine, const UserInputMapper::Input& input); +void inputFromScriptValue(const QScriptValue& object, UserInputMapper::Input& input); +QScriptValue inputChannelToScriptValue(QScriptEngine* engine, const UserInputMapper::InputChannel& inputChannel); +void inputChannelFromScriptValue(const QScriptValue& object, UserInputMapper::InputChannel& inputChannel); +QScriptValue actionToScriptValue(QScriptEngine* engine, const UserInputMapper::Action& action); +void actionFromScriptValue(const QScriptValue& object, UserInputMapper::Action& action); +QScriptValue inputPairToScriptValue(QScriptEngine* engine, const UserInputMapper::InputPair& inputPair); +void inputPairFromScriptValue(const QScriptValue& object, UserInputMapper::InputPair& inputPair); + +QScriptValue inputToScriptValue(QScriptEngine* engine, const UserInputMapper::Input& input) { + QScriptValue obj = engine->newObject(); + obj.setProperty("device", input.getDevice()); + obj.setProperty("channel", input.getChannel()); + obj.setProperty("type", input._type); + obj.setProperty("id", input.getID()); + return obj; +} + +void inputFromScriptValue(const QScriptValue& object, UserInputMapper::Input& input) { + input._device = object.property("device").toUInt16(); + input._channel = object.property("channel").toUInt16(); + input._type = object.property("type").toUInt16(); + input._id = object.property("id").toInt32(); +} + +QScriptValue inputChannelToScriptValue(QScriptEngine* engine, const UserInputMapper::InputChannel& inputChannel) { + QScriptValue obj = engine->newObject(); + obj.setProperty("input", inputToScriptValue(engine, inputChannel._input)); + obj.setProperty("modifier", inputToScriptValue(engine, inputChannel._modifier)); + obj.setProperty("action", inputChannel._action); + obj.setProperty("scale", inputChannel._scale); + return obj; +} + +void inputChannelFromScriptValue(const QScriptValue& object, UserInputMapper::InputChannel& inputChannel) { + inputFromScriptValue(object.property("input"), inputChannel._input); + inputFromScriptValue(object.property("modifier"), inputChannel._modifier); + inputChannel._action = UserInputMapper::Action(object.property("action").toVariant().toInt()); + inputChannel._scale = object.property("scale").toVariant().toFloat(); +} + +QScriptValue actionToScriptValue(QScriptEngine* engine, const UserInputMapper::Action& action) { + QScriptValue obj = engine->newObject(); + QVector inputChannels = Application::getUserInputMapper()->getInputChannelsForAction(action); + QScriptValue _inputChannels = engine->newArray(inputChannels.size()); + for (int i = 0; i < inputChannels.size(); i++) { + _inputChannels.setProperty(i, inputChannelToScriptValue(engine, inputChannels[i])); + } + obj.setProperty("action", (int) action); + obj.setProperty("actionName", Application::getUserInputMapper()->getActionName(action)); + obj.setProperty("inputChannels", _inputChannels); + return obj; +} + +void actionFromScriptValue(const QScriptValue& object, UserInputMapper::Action& action) { + action = UserInputMapper::Action(object.property("action").toVariant().toInt()); +} + +QScriptValue inputPairToScriptValue(QScriptEngine* engine, const UserInputMapper::InputPair& inputPair) { + QScriptValue obj = engine->newObject(); + obj.setProperty("input", inputToScriptValue(engine, inputPair.first)); + obj.setProperty("inputName", inputPair.second); + return obj; +} + +void inputPairFromScriptValue(const QScriptValue& object, UserInputMapper::InputPair& inputPair) { + inputFromScriptValue(object.property("input"), inputPair.first); + inputPair.second = QString(object.property("inputName").toVariant().toString()); +} + +void ControllerScriptingInterface::registerControllerTypes(QScriptEngine* engine) { + qScriptRegisterSequenceMetaType >(engine); + qScriptRegisterSequenceMetaType >(engine); + qScriptRegisterSequenceMetaType >(engine); + qScriptRegisterMetaType(engine, actionToScriptValue, actionFromScriptValue); + qScriptRegisterMetaType(engine, inputChannelToScriptValue, inputChannelFromScriptValue); + qScriptRegisterMetaType(engine, inputToScriptValue, inputFromScriptValue); + qScriptRegisterMetaType(engine, inputPairToScriptValue, inputPairFromScriptValue); +} void ControllerScriptingInterface::handleMetaEvent(HFMetaEvent* event) { if (event->type() == HFActionEvent::startType()) { @@ -337,6 +421,37 @@ void ControllerScriptingInterface::updateInputControllers() { } } +QVector ControllerScriptingInterface::getAllActions() { + return Application::getUserInputMapper()->getAllActions(); +} + +QVector ControllerScriptingInterface::getInputChannelsForAction(UserInputMapper::Action action) { + return Application::getUserInputMapper()->getInputChannelsForAction(action); +} + +QString ControllerScriptingInterface::getDeviceName(unsigned int device) { + return Application::getUserInputMapper()->getDeviceName((unsigned short) device); +} + +QVector ControllerScriptingInterface::getAllInputsForDevice(unsigned int device) { + return Application::getUserInputMapper()->getAllInputsForDevice(device); +} + +bool ControllerScriptingInterface::addInputChannel(UserInputMapper::InputChannel inputChannel) { + return Application::getUserInputMapper()->addInputChannel(inputChannel._action, inputChannel._input, inputChannel._modifier, inputChannel._scale); +} + +bool ControllerScriptingInterface::removeInputChannel(UserInputMapper::InputChannel inputChannel) { + return Application::getUserInputMapper()->removeInputChannel(inputChannel); +} + +QVector ControllerScriptingInterface::getAvailableInputs(unsigned int device) { + return Application::getUserInputMapper()->getAvailableInputs((unsigned short) device); +} + +void ControllerScriptingInterface::resetAllDeviceBindings() { + Application::getUserInputMapper()->resetAllDeviceBindings(); +} InputController::InputController(int deviceTrackerId, int subTrackerId, QObject* parent) : AbstractInputController(), @@ -373,4 +488,4 @@ const unsigned int INPUTCONTROLLER_KEY_DEVICE_MASK = 16; InputController::Key InputController::getKey() const { return (((_deviceTrackerId & INPUTCONTROLLER_KEY_DEVICE_MASK) << INPUTCONTROLLER_KEY_DEVICE_OFFSET) | _subTrackerId); -} +} \ No newline at end of file diff --git a/interface/src/scripting/ControllerScriptingInterface.h b/interface/src/scripting/ControllerScriptingInterface.h index c088dd6c9a..9414dc887b 100644 --- a/interface/src/scripting/ControllerScriptingInterface.h +++ b/interface/src/scripting/ControllerScriptingInterface.h @@ -14,10 +14,11 @@ #include +#include "ui/UserInputMapper.h" + #include class PalmData; - class InputController : public AbstractInputController { Q_OBJECT @@ -54,6 +55,9 @@ class ControllerScriptingInterface : public AbstractControllerScriptingInterface public: ControllerScriptingInterface(); + + virtual void registerControllerTypes(QScriptEngine* engine); + void emitKeyPressEvent(QKeyEvent* event) { emit keyPressEvent(KeyEvent(*event)); } void emitKeyReleaseEvent(QKeyEvent* event) { emit keyReleaseEvent(KeyEvent(*event)); } @@ -79,6 +83,14 @@ public: void updateInputControllers(); public slots: + Q_INVOKABLE virtual QVector getAllActions(); + Q_INVOKABLE virtual QVector getInputChannelsForAction(UserInputMapper::Action action); + Q_INVOKABLE virtual QString getDeviceName(unsigned int device); + Q_INVOKABLE virtual QVector getAllInputsForDevice(unsigned int device); + Q_INVOKABLE virtual bool addInputChannel(UserInputMapper::InputChannel inputChannel); + Q_INVOKABLE virtual bool removeInputChannel(UserInputMapper::InputChannel inputChannel); + Q_INVOKABLE virtual QVector getAvailableInputs(unsigned int device); + Q_INVOKABLE virtual void resetAllDeviceBindings(); virtual bool isPrimaryButtonPressed() const; virtual glm::vec2 getPrimaryJoystickPosition() const; diff --git a/interface/src/ui/UserInputMapper.cpp b/interface/src/ui/UserInputMapper.cpp index 892ab6a9b6..e994b3cf30 100755 --- a/interface/src/ui/UserInputMapper.cpp +++ b/interface/src/ui/UserInputMapper.cpp @@ -13,7 +13,15 @@ // UserInputMapper Class + +// Default contruct allocate the poutput size with the current hardcoded action channels +UserInputMapper::UserInputMapper() { + assignDefaulActionScales(); + createActionNames(); +} + bool UserInputMapper::registerDevice(uint16 deviceID, const DeviceProxy::Pointer& proxy){ + proxy->_name += " (" + QString::number(deviceID) + ")"; _registeredDevices[deviceID] = proxy; return true; } @@ -27,6 +35,12 @@ UserInputMapper::DeviceProxy::Pointer UserInputMapper::getDeviceProxy(const Inpu } } +void UserInputMapper::resetAllDeviceBindings() { + for (auto device : _registeredDevices) { + device.second->resetDeviceBindings(); + } +} + bool UserInputMapper::addInputChannel(Action action, const Input& input, float scale) { return addInputChannel(action, input, Input(), scale); } @@ -37,7 +51,7 @@ bool UserInputMapper::addInputChannel(Action action, const Input& input, const I qDebug() << "UserInputMapper::addInputChannel: The input comes from a device #" << input.getDevice() << "is unknown. no inputChannel mapped."; return false; } - + auto inputChannel = InputChannel(input, modifier, action, scale); // Insert or replace the input to modifiers @@ -61,6 +75,37 @@ int UserInputMapper::addInputChannels(const InputChannels& channels) { return nbAdded; } +bool UserInputMapper::removeInputChannel(InputChannel inputChannel) { + // Remove from Input to Modifiers map + if (inputChannel.hasModifier()) { + _inputToModifiersMap.erase(inputChannel._input.getID()); + } + + // Remove from Action to Inputs map + std::pair ret; + ret = _actionToInputsMap.equal_range(inputChannel._action); + for (ActionToInputsMap::iterator it=ret.first; it!=ret.second; ++it) { + if (it->second == inputChannel) { + _actionToInputsMap.erase(it); + return true; + } + } + + return false; +} + +void UserInputMapper::removeAllInputChannels() { + _inputToModifiersMap.clear(); + _actionToInputsMap.clear(); +} + +void UserInputMapper::removeAllInputChannelsForDevice(uint16 device) { + QVector channels = getAllInputsForDevice(device); + for (auto& channel : channels) { + removeInputChannel(channel); + } +} + int UserInputMapper::getInputChannels(InputChannels& channels) const { for (auto& channel : _actionToInputsMap) { channels.push_back(channel.second); @@ -69,6 +114,20 @@ int UserInputMapper::getInputChannels(InputChannels& channels) const { return _actionToInputsMap.size(); } +QVector UserInputMapper::getAllInputsForDevice(uint16 device) { + InputChannels allChannels; + getInputChannels(allChannels); + + QVector channels; + for (InputChannel inputChannel : allChannels) { + if (inputChannel._input._device == device) { + channels.push_back(inputChannel); + } + } + + return channels; +} + void UserInputMapper::update(float deltaTime) { // Reset the axis state for next loop @@ -130,6 +189,24 @@ void UserInputMapper::update(float deltaTime) { } } +QVector UserInputMapper::getAllActions() { + QVector actions; + for (auto i = 0; i < NUM_ACTIONS; i++) { + actions.append(Action(i)); + } + return actions; +} + +QVector UserInputMapper::getInputChannelsForAction(UserInputMapper::Action action) { + QVector inputChannels; + std::pair ret; + ret = _actionToInputsMap.equal_range(action); + for (ActionToInputsMap::iterator it=ret.first; it!=ret.second; ++it) { + inputChannels.append(it->second); + } + return inputChannels; +} + void UserInputMapper::assignDefaulActionScales() { _actionScales[LONGITUDINAL_BACKWARD] = 1.0f; // 1m per unit _actionScales[LONGITUDINAL_FORWARD] = 1.0f; // 1m per unit @@ -144,3 +221,20 @@ void UserInputMapper::assignDefaulActionScales() { _actionScales[BOOM_IN] = 1.0f; // 1m per unit _actionScales[BOOM_OUT] = 1.0f; // 1m per unit } + +// This is only necessary as long as the actions are hardcoded +// Eventually you can just add the string when you add the action +void UserInputMapper::createActionNames() { + _actionNames[LONGITUDINAL_BACKWARD] = "LONGITUDINAL_BACKWARD"; + _actionNames[LONGITUDINAL_FORWARD] = "LONGITUDINAL_FORWARD"; + _actionNames[LATERAL_LEFT] = "LATERAL_LEFT"; + _actionNames[LATERAL_RIGHT] = "LATERAL_RIGHT"; + _actionNames[VERTICAL_DOWN] = "VERTICAL_DOWN"; + _actionNames[VERTICAL_UP] = "VERTICAL_UP"; + _actionNames[YAW_LEFT] = "YAW_LEFT"; + _actionNames[YAW_RIGHT] = "YAW_RIGHT"; + _actionNames[PITCH_DOWN] = "PITCH_DOWN"; + _actionNames[PITCH_UP] = "PITCH_UP"; + _actionNames[BOOM_IN] = "BOOM_IN"; + _actionNames[BOOM_OUT] = "BOOM_OUT"; +} \ No newline at end of file diff --git a/interface/src/ui/UserInputMapper.h b/interface/src/ui/UserInputMapper.h index ab63bdbef7..34188ae3f5 100755 --- a/interface/src/ui/UserInputMapper.h +++ b/interface/src/ui/UserInputMapper.h @@ -21,6 +21,7 @@ class UserInputMapper : public QObject { Q_OBJECT + Q_ENUMS(Action) public: typedef unsigned short uint16; typedef unsigned int uint32; @@ -64,6 +65,7 @@ public: explicit Input(uint16 device, uint16 channel, ChannelType type) : _device(device), _channel(channel), _type(uint16(type)) {} Input(const Input& src) : _id(src._id) {} Input& operator = (const Input& src) { _id = src._id; return (*this); } + bool operator ==(const Input& right) const { return _id == right._id; } bool operator < (const Input& src) const { return _id < src._id; } }; @@ -83,22 +85,32 @@ public: typedef std::function ButtonGetter; typedef std::function AxisGetter; typedef std::function JointGetter; + typedef QPair InputPair; + typedef std::function ()> AvailableInputGetter; + typedef std::function ResetBindings; + + typedef QVector AvailableInput; class DeviceProxy { public: - DeviceProxy() {} - - ButtonGetter getButton = [] (const Input& input, int timestamp) -> bool { return false; }; - AxisGetter getAxis = [] (const Input& input, int timestamp) -> bool { return 0.0f; }; - JointGetter getJoint = [] (const Input& input, int timestamp) -> JointValue { return JointValue(); }; - - typedef std::shared_ptr Pointer; + DeviceProxy(QString name) { _name = name; } + + QString _name; + ButtonGetter getButton = [] (const Input& input, int timestamp) -> bool { return false; }; + AxisGetter getAxis = [] (const Input& input, int timestamp) -> bool { return 0.0f; }; + JointGetter getJoint = [] (const Input& input, int timestamp) -> JointValue { return JointValue(); }; + AvailableInputGetter getAvailabeInputs = [] () -> AvailableInput { return QVector(); }; + ResetBindings resetDeviceBindings = [] () -> bool { return true; }; + + typedef std::shared_ptr Pointer; }; // GetFreeDeviceID should be called before registering a device to use an ID not used by a different device. uint16 getFreeDeviceID() { return _nextFreeDeviceID++; } bool registerDevice(uint16 deviceID, const DeviceProxy::Pointer& device); DeviceProxy::Pointer getDeviceProxy(const Input& input); - + QString getDeviceName(uint16 deviceID) { return _registeredDevices[deviceID]->_name; } + QVector getAvailableInputs(uint16 deviceID) { return _registeredDevices[deviceID]->getAvailabeInputs(); } + void resetAllDeviceBindings(); // Actions are the output channels of the Mapper, that's what the InputChannel map to // For now the Actions are hardcoded, this is bad, but we will fix that in the near future @@ -123,8 +135,14 @@ public: NUM_ACTIONS, }; + + std::vector _actionNames = std::vector(NUM_ACTIONS); + void createActionNames(); + QVector getAllActions(); + QString getActionName(Action action) { return UserInputMapper::_actionNames[(int) action]; } float getActionState(Action action) const { return _actionStates[action]; } +// QVector void assignDefaulActionScales(); // Add input channel to the mapper and check that all the used channels are registered. @@ -146,21 +164,27 @@ public: _input(input), _modifier(modifier), _action(action), _scale(scale) {} InputChannel(const InputChannel& src) : InputChannel(src._input, src._modifier, src._action, src._scale) {} InputChannel& operator = (const InputChannel& src) { _input = src._input; _modifier = src._modifier; _action = src._action; _scale = src._scale; return (*this); } - + bool operator ==(const InputChannel& right) const { return _input == right._input && _modifier == right._modifier && _action == right._action && _scale == right._scale; } bool hasModifier() { return _modifier.isValid(); } }; typedef std::vector< InputChannel > InputChannels; // Add a bunch of input channels, return the true number of channels that successfully were added int addInputChannels(const InputChannels& channels); + // Remove the first found instance of the input channel from the input mapper, true if found + bool removeInputChannel(InputChannel channel); + void removeAllInputChannels(); + void removeAllInputChannelsForDevice(uint16 device); //Grab all the input channels currently in use, return the number int getInputChannels(InputChannels& channels) const; + QVector getAllInputsForDevice(uint16 device); + QVector getInputChannelsForAction(UserInputMapper::Action action); + std::multimap getActionToInputsMap() { return _actionToInputsMap; } // Update means go grab all the device input channels and update the output channel values void update(float deltaTime); - - // Default contruct allocate the poutput size with the current hardcoded action channels - UserInputMapper() { assignDefaulActionScales(); } + + UserInputMapper(); protected: typedef std::map DevicesMap; @@ -177,4 +201,12 @@ protected: std::vector _actionScales = std::vector(NUM_ACTIONS, 1.0f); }; +Q_DECLARE_METATYPE(UserInputMapper::InputPair) +Q_DECLARE_METATYPE(QVector) +Q_DECLARE_METATYPE(UserInputMapper::Input) +Q_DECLARE_METATYPE(UserInputMapper::InputChannel) +Q_DECLARE_METATYPE(QVector) +Q_DECLARE_METATYPE(UserInputMapper::Action) +Q_DECLARE_METATYPE(QVector) + #endif // hifi_UserInputMapper_h diff --git a/libraries/script-engine/src/AbstractControllerScriptingInterface.h b/libraries/script-engine/src/AbstractControllerScriptingInterface.h index 11755add8e..43076039a9 100644 --- a/libraries/script-engine/src/AbstractControllerScriptingInterface.h +++ b/libraries/script-engine/src/AbstractControllerScriptingInterface.h @@ -52,6 +52,8 @@ class AbstractControllerScriptingInterface : public QObject { Q_OBJECT public slots: + virtual void registerControllerTypes(QScriptEngine* engine) = 0; + virtual bool isPrimaryButtonPressed() const = 0; virtual glm::vec2 getPrimaryJoystickPosition() const = 0; diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 30c3fbe8e4..182a0aea8d 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -317,6 +317,7 @@ void ScriptEngine::init() { registerAnimationTypes(this); registerAvatarTypes(this); registerAudioMetaTypes(this); + _controllerScriptingInterface->registerControllerTypes(this); qScriptRegisterMetaType(this, EntityItemPropertiesToScriptValue, EntityItemPropertiesFromScriptValueHonorReadOnly); qScriptRegisterMetaType(this, EntityItemIDtoScriptValue, EntityItemIDfromScriptValue); From dd12e4740e036895929ddd42e63eab9caab6fab0 Mon Sep 17 00:00:00 2001 From: Sam Gondelman Date: Fri, 5 Jun 2015 10:14:13 -0700 Subject: [PATCH 03/41] example script for controller scripting, input mapping --- .../scripts/controllerScriptingExamples.js | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 examples/example/scripts/controllerScriptingExamples.js diff --git a/examples/example/scripts/controllerScriptingExamples.js b/examples/example/scripts/controllerScriptingExamples.js new file mode 100644 index 0000000000..3e693c42ea --- /dev/null +++ b/examples/example/scripts/controllerScriptingExamples.js @@ -0,0 +1,94 @@ +// +// controllerScriptingExamples.js +// examples +// +// Created by Sam Gondelman on 6/2/15 +// 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 +// + +// Resets every device to its default key bindings: +Controller.resetAllDeviceBindings() + +// Query all actions +print("All Actions: \n" + Controller.getAllActions()) + +// Each action stores: +// action: int representation of enum +print("Action 5 int: \n" + Controller.getAllActions()[5].action) + +// actionName: string representation of enum +print("Action 5 name: \n" + Controller.getAllActions()[5].actionName) + +// inputChannels: list of all inputchannels that control that action +print("Action 5 input channels: \n" + Controller.getAllActions()[5].inputChannels + "\n") + + +// Each input channel stores: +// action: Action that this InputChannel maps to +print("Input channel action: \n" + Controller.getAllActions()[5].inputChannels[0].action) + +// scale: sensitivity of input +print("Input channel scale: \n" + Controller.getAllActions()[5].inputChannels[0].scale) + +// input and modifier: Inputs +print("Input channel input and modifier: \n" + Controller.getAllActions()[5].inputChannels[0].input + "\n" + Controller.getAllActions()[5].inputChannels[0].modifier + "\n") + + +// Each Input stores: +// device: device of input +print("Input device: \n" + Controller.getAllActions()[5].inputChannels[0].input.device) + +// channel: channel of input +print("Input channel: \n" + Controller.getAllActions()[5].inputChannels[0].input.channel) + +// type: type of input (Unknown, Button, Axis, Joint) +print("Input type: \n" + Controller.getAllActions()[5].inputChannels[0].input.type) + +// id: id of input +print("Input id: \n" + Controller.getAllActions()[5].inputChannels[0].input.id + "\n") + + +// You can get the name of a device from its id +print("Device 1 name: \n" + Controller.getDeviceName(Controller.getAllActions()[5].inputChannels[0].input.id)) + +// You can also get all of a devices input channels +print("Device 1's input channels: \n" + Controller.getAllInputsForDevice(1) + "\n") + + +// Modifying properties: +// The following code will switch the "w" and "s" key functionality and adjust their scales +var s = Controller.getAllActions()[0].inputChannels[0] +var w = Controller.getAllActions()[1].inputChannels[0] + +// You must remove an input controller before modifying it so the old input controller isn't registered anymore +// removeInputChannel and addInputChannel return true if successful, false otherwise +Controller.removeInputChannel(s) +Controller.removeInputChannel(w) +print(s.scale) +s.action = 1 +s.scale = .01 + +w.action = 0 +w.scale = 10000 +Controller.addInputChannel(s) +Controller.addInputChannel(w) +print(s.scale) + +// You can get all the available inputs for any device +// Each AvailableInput has: +// input: the Input itself +// inputName: string representing the input +var availableInputs = Controller.getAvailableInputs(1) +for (i = 0; i < availableInputs.length; i++) { + print(availableInputs[i].inputName); +} + +// You can modify key bindings by using these avaiable inputs +// This will replace e (up) with 6 +var e = Controller.getAllActions()[5].inputChannels[0] +Controller.removeInputChannel(e) +e.input = availableInputs[6].input +Controller.addInputChannel(e) \ No newline at end of file From 1e858d8bc5bbc3e135c5463dc3b38bb69ffb9c18 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 8 Jun 2015 14:16:03 -0700 Subject: [PATCH 04/41] start on spring action --- .../entities/src/EntityActionInterface.cpp | 62 +++++++++- .../entities/src/EntityActionInterface.h | 4 +- libraries/physics/src/ObjectActionSpring.cpp | 107 ++++++++++++++++++ libraries/physics/src/ObjectActionSpring.h | 37 ++++++ .../physics/src/PhysicalEntitySimulation.cpp | 4 + 5 files changed, 208 insertions(+), 6 deletions(-) create mode 100644 libraries/physics/src/ObjectActionSpring.cpp create mode 100644 libraries/physics/src/ObjectActionSpring.h diff --git a/libraries/entities/src/EntityActionInterface.cpp b/libraries/entities/src/EntityActionInterface.cpp index f26dd006ff..d92771fce1 100644 --- a/libraries/entities/src/EntityActionInterface.cpp +++ b/libraries/entities/src/EntityActionInterface.cpp @@ -22,6 +22,9 @@ EntityActionType EntityActionInterface::actionTypeFromString(QString actionTypeS if (normalizedActionTypeString == "pulltopoint") { return ACTION_TYPE_PULL_TO_POINT; } + if (normalizedActionTypeString == "spring") { + return ACTION_TYPE_SPRING; + } qDebug() << "Warning -- EntityActionInterface::actionTypeFromString got unknown action-type name" << actionTypeString; return ACTION_TYPE_NONE; @@ -33,6 +36,8 @@ QString EntityActionInterface::actionTypeToString(EntityActionType actionType) { return "none"; case ACTION_TYPE_PULL_TO_POINT: return "pullToPoint"; + case ACTION_TYPE_SPRING: + return "spring"; } assert(false); return "none"; @@ -43,21 +48,21 @@ glm::vec3 EntityActionInterface::extractVec3Argument(QString objectName, QVarian if (!arguments.contains(argumentName)) { qDebug() << objectName << "requires argument:" << argumentName; ok = false; - return vec3(); + return glm::vec3(); } QVariant resultV = arguments[argumentName]; if (resultV.type() != (QVariant::Type) QMetaType::QVariantMap) { qDebug() << objectName << "argument" << argumentName << "must be a map"; ok = false; - return vec3(); + return glm::vec3(); } QVariantMap resultVM = resultV.toMap(); if (!resultVM.contains("x") || !resultVM.contains("y") || !resultVM.contains("z")) { qDebug() << objectName << "argument" << argumentName << "must be a map with keys of x, y, z"; ok = false; - return vec3(); + return glm::vec3(); } QVariant xV = resultVM["x"]; @@ -73,13 +78,60 @@ glm::vec3 EntityActionInterface::extractVec3Argument(QString objectName, QVarian if (!xOk || !yOk || !zOk) { qDebug() << objectName << "argument" << argumentName << "must be a map with keys of x, y, z and values of type float."; ok = false; - return vec3(); + return glm::vec3(); } - return vec3(x, y, z); + return glm::vec3(x, y, z); } +glm::quat EntityActionInterface::extractQuatArgument(QString objectName, QVariantMap arguments, + QString argumentName, bool& ok) { + if (!arguments.contains(argumentName)) { + qDebug() << objectName << "requires argument:" << argumentName; + ok = false; + return glm::quat(); + } + + QVariant resultV = arguments[argumentName]; + if (resultV.type() != (QVariant::Type) QMetaType::QVariantMap) { + qDebug() << objectName << "argument" << argumentName << "must be a map"; + ok = false; + return glm::quat(); + } + + QVariantMap resultVM = resultV.toMap(); + if (!resultVM.contains("x") || !resultVM.contains("y") || !resultVM.contains("z")) { + qDebug() << objectName << "argument" << argumentName << "must be a map with keys of x, y, z"; + ok = false; + return glm::quat(); + } + + QVariant xV = resultVM["x"]; + QVariant yV = resultVM["y"]; + QVariant zV = resultVM["z"]; + QVariant wV = resultVM["w"]; + + bool xOk = true; + bool yOk = true; + bool zOk = true; + bool wOk = true; + float x = xV.toFloat(&xOk); + float y = yV.toFloat(&yOk); + float z = zV.toFloat(&zOk); + float w = wV.toFloat(&wOk); + if (!xOk || !yOk || !zOk || !wOk) { + qDebug() << objectName << "argument" << argumentName + << "must be a map with keys of x, y, z, w and values of type float."; + ok = false; + return glm::quat(); + } + + return glm::quat(x, y, z, w); +} + + + float EntityActionInterface::extractFloatArgument(QString objectName, QVariantMap arguments, QString argumentName, bool& ok) { if (!arguments.contains(argumentName)) { diff --git a/libraries/entities/src/EntityActionInterface.h b/libraries/entities/src/EntityActionInterface.h index 74efae3239..3baee06c3e 100644 --- a/libraries/entities/src/EntityActionInterface.h +++ b/libraries/entities/src/EntityActionInterface.h @@ -19,7 +19,8 @@ class EntitySimulation; enum EntityActionType { // keep these synchronized with actionTypeFromString and actionTypeToString ACTION_TYPE_NONE, - ACTION_TYPE_PULL_TO_POINT + ACTION_TYPE_PULL_TO_POINT, + ACTION_TYPE_SPRING }; @@ -41,6 +42,7 @@ public: protected: static glm::vec3 extractVec3Argument(QString objectName, QVariantMap arguments, QString argumentName, bool& ok); + static glm::quat extractQuatArgument(QString objectName, QVariantMap arguments, QString argumentName, bool& ok); static float extractFloatArgument(QString objectName, QVariantMap arguments, QString argumentName, bool& ok); }; diff --git a/libraries/physics/src/ObjectActionSpring.cpp b/libraries/physics/src/ObjectActionSpring.cpp new file mode 100644 index 0000000000..8a436096a8 --- /dev/null +++ b/libraries/physics/src/ObjectActionSpring.cpp @@ -0,0 +1,107 @@ +// +// ObjectActionSpring.cpp +// libraries/physics/src +// +// Created by Seth Alves 2015-6-5 +// 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 +// + +#include "ObjectMotionState.h" +#include "BulletUtil.h" + +#include "ObjectActionSpring.h" + +ObjectActionSpring::ObjectActionSpring(QUuid id, EntityItemPointer ownerEntity) : + ObjectAction(id, ownerEntity) { + #if WANT_DEBUG + qDebug() << "ObjectActionSpring::ObjectActionSpring"; + #endif +} + +ObjectActionSpring::~ObjectActionSpring() { + #if WANT_DEBUG + qDebug() << "ObjectActionSpring::~ObjectActionSpring"; + #endif +} + +void ObjectActionSpring::updateAction(btCollisionWorld* collisionWorld, btScalar deltaTimeStep) { + if (!tryLockForRead()) { + // don't risk hanging the thread running the physics simulation + return; + } + void* physicsInfo = _ownerEntity->getPhysicsInfo(); + + if (_active && physicsInfo) { + ObjectMotionState* motionState = static_cast(physicsInfo); + btRigidBody* rigidBody = motionState->getRigidBody(); + if (rigidBody) { + glm::vec3 offset = _positionalTarget - bulletToGLM(rigidBody->getCenterOfMassPosition()); + + + // btQuaternion getOrientation() const; + // const btTransform& getCenterOfMassTransform() const; + + float offsetLength = glm::length(offset); + float speed = offsetLength; // XXX use _positionalSpringConstant + + float interpolation_value = 0.5; // XXX + const glm::quat slerped_quat = glm::slerp(bulletToGLM(rigidBody->getOrientation()), + _rotationalTarget, + interpolation_value); + + if (offsetLength > IGNORE_POSITION_DELTA) { + glm::vec3 newVelocity = glm::normalize(offset) * speed; + rigidBody->setLinearVelocity(glmToBullet(newVelocity)); + // void setAngularVelocity (const btVector3 &ang_vel); + rigidBody->activate(); + } else { + rigidBody->setLinearVelocity(glmToBullet(glm::vec3())); + } + } + } + unlock(); +} + + +bool ObjectActionSpring::updateArguments(QVariantMap arguments) { + // targets are required, spring-constants are optional + bool ok = true; + glm::vec3 positionalTarget = + EntityActionInterface::extractVec3Argument("spring action", arguments, "positionalTarget", ok); + bool pscOK = true; + float positionalSpringConstant = + EntityActionInterface::extractFloatArgument("spring action", arguments, "positionalSpringConstant", pscOK); + + glm::quat rotationalTarget = + EntityActionInterface::extractQuatArgument("spring action", arguments, "rotationalTarget", ok); + bool rscOK = true; + float rotationalSpringConstant = + EntityActionInterface::extractFloatArgument("spring action", arguments, "rotationalSpringConstant", rscOK); + + if (!ok) { + return false; + } + + lockForWrite(); + + _positionalTarget = positionalTarget; + if (pscOK) { + _positionalSpringConstant = positionalSpringConstant; + } else { + _positionalSpringConstant = 0.5; // XXX pick a good default; + } + + _rotationalTarget = rotationalTarget; + if (rscOK) { + _rotationalSpringConstant = rotationalSpringConstant; + } else { + _rotationalSpringConstant = 0.5; // XXX pick a good default; + } + + _active = true; + unlock(); + return true; +} diff --git a/libraries/physics/src/ObjectActionSpring.h b/libraries/physics/src/ObjectActionSpring.h new file mode 100644 index 0000000000..77502e8544 --- /dev/null +++ b/libraries/physics/src/ObjectActionSpring.h @@ -0,0 +1,37 @@ +// +// ObjectActionSpring.h +// libraries/physics/src +// +// Created by Seth Alves 2015-6-5 +// 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 +// + +#ifndef hifi_ObjectActionSpring_h +#define hifi_ObjectActionSpring_h + +#include + +#include +#include "ObjectAction.h" + +class ObjectActionSpring : public ObjectAction { +public: + ObjectActionSpring(QUuid id, EntityItemPointer ownerEntity); + virtual ~ObjectActionSpring(); + + virtual bool updateArguments(QVariantMap arguments); + virtual void updateAction(btCollisionWorld* collisionWorld, btScalar deltaTimeStep); + +private: + + glm::vec3 _positionalTarget; + float _positionalSpringConstant; + + glm::quat _rotationalTarget; + float _rotationalSpringConstant; +}; + +#endif // hifi_ObjectActionSpring_h diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index 711c5e49da..56d497f8a1 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -13,6 +13,7 @@ #include "PhysicsLogging.h" #include "ShapeManager.h" #include "ObjectActionPullToPoint.h" +#include "ObjectActionSpring.h" #include "PhysicalEntitySimulation.h" @@ -245,6 +246,9 @@ EntityActionPointer PhysicalEntitySimulation::actionFactory(EntityActionType typ case ACTION_TYPE_PULL_TO_POINT: action = (EntityActionPointer) new ObjectActionPullToPoint(id, ownerEntity); break; + case ACTION_TYPE_SPRING: + action = (EntityActionPointer) new ObjectActionSpring(id, ownerEntity); + break; } bool ok = action->updateArguments(arguments); From b3bc9c3ef05d871b5b6dfc0cc0b2378b2192636e Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 8 Jun 2015 17:10:13 -0700 Subject: [PATCH 05/41] first attempt at getting spring-action to handle rotation --- .../entities/src/EntityActionInterface.cpp | 20 ++-- .../entities/src/EntityActionInterface.h | 9 +- libraries/physics/src/ObjectActionSpring.cpp | 96 +++++++++++-------- libraries/physics/src/ObjectActionSpring.h | 6 +- 4 files changed, 80 insertions(+), 51 deletions(-) diff --git a/libraries/entities/src/EntityActionInterface.cpp b/libraries/entities/src/EntityActionInterface.cpp index d92771fce1..b3e774df96 100644 --- a/libraries/entities/src/EntityActionInterface.cpp +++ b/libraries/entities/src/EntityActionInterface.cpp @@ -44,9 +44,11 @@ QString EntityActionInterface::actionTypeToString(EntityActionType actionType) { } glm::vec3 EntityActionInterface::extractVec3Argument(QString objectName, QVariantMap arguments, - QString argumentName, bool& ok) { + QString argumentName, bool& ok, bool required) { if (!arguments.contains(argumentName)) { - qDebug() << objectName << "requires argument:" << argumentName; + if (required) { + qDebug() << objectName << "requires argument:" << argumentName; + } ok = false; return glm::vec3(); } @@ -86,16 +88,18 @@ glm::vec3 EntityActionInterface::extractVec3Argument(QString objectName, QVarian glm::quat EntityActionInterface::extractQuatArgument(QString objectName, QVariantMap arguments, - QString argumentName, bool& ok) { + QString argumentName, bool& ok, bool required) { if (!arguments.contains(argumentName)) { - qDebug() << objectName << "requires argument:" << argumentName; + if (required) { + qDebug() << objectName << "requires argument:" << argumentName; + } ok = false; return glm::quat(); } QVariant resultV = arguments[argumentName]; if (resultV.type() != (QVariant::Type) QMetaType::QVariantMap) { - qDebug() << objectName << "argument" << argumentName << "must be a map"; + qDebug() << objectName << "argument" << argumentName << "must be a map, not" << resultV.typeName(); ok = false; return glm::quat(); } @@ -133,9 +137,11 @@ glm::quat EntityActionInterface::extractQuatArgument(QString objectName, QVarian float EntityActionInterface::extractFloatArgument(QString objectName, QVariantMap arguments, - QString argumentName, bool& ok) { + QString argumentName, bool& ok, bool required) { if (!arguments.contains(argumentName)) { - qDebug() << objectName << "requires argument:" << argumentName; + if (required) { + qDebug() << objectName << "requires argument:" << argumentName; + } ok = false; return 0.0f; } diff --git a/libraries/entities/src/EntityActionInterface.h b/libraries/entities/src/EntityActionInterface.h index 3baee06c3e..a0a3db9b68 100644 --- a/libraries/entities/src/EntityActionInterface.h +++ b/libraries/entities/src/EntityActionInterface.h @@ -41,9 +41,12 @@ public: protected: - static glm::vec3 extractVec3Argument(QString objectName, QVariantMap arguments, QString argumentName, bool& ok); - static glm::quat extractQuatArgument(QString objectName, QVariantMap arguments, QString argumentName, bool& ok); - static float extractFloatArgument(QString objectName, QVariantMap arguments, QString argumentName, bool& ok); + static glm::vec3 extractVec3Argument (QString objectName, QVariantMap arguments, + QString argumentName, bool& ok, bool required = true); + static glm::quat extractQuatArgument (QString objectName, QVariantMap arguments, + QString argumentName, bool& ok, bool required = true); + static float extractFloatArgument(QString objectName, QVariantMap arguments, + QString argumentName, bool& ok, bool required = true); }; typedef std::shared_ptr EntityActionPointer; diff --git a/libraries/physics/src/ObjectActionSpring.cpp b/libraries/physics/src/ObjectActionSpring.cpp index 8a436096a8..665ec352fe 100644 --- a/libraries/physics/src/ObjectActionSpring.cpp +++ b/libraries/physics/src/ObjectActionSpring.cpp @@ -38,27 +38,30 @@ void ObjectActionSpring::updateAction(btCollisionWorld* collisionWorld, btScalar ObjectMotionState* motionState = static_cast(physicsInfo); btRigidBody* rigidBody = motionState->getRigidBody(); if (rigidBody) { - glm::vec3 offset = _positionalTarget - bulletToGLM(rigidBody->getCenterOfMassPosition()); + // handle the linear part + if (_positionalTargetSet) { + glm::vec3 offset = _positionalTarget - bulletToGLM(rigidBody->getCenterOfMassPosition()); + float offsetLength = glm::length(offset); + float speed = offsetLength / _linearTimeScale; + if (offsetLength > IGNORE_POSITION_DELTA) { + glm::vec3 newVelocity = glm::normalize(offset) * speed; + rigidBody->setLinearVelocity(glmToBullet(newVelocity)); + // void setAngularVelocity (const btVector3 &ang_vel); + rigidBody->activate(); + } else { + rigidBody->setLinearVelocity(glmToBullet(glm::vec3())); + } + } - // btQuaternion getOrientation() const; - // const btTransform& getCenterOfMassTransform() const; - - float offsetLength = glm::length(offset); - float speed = offsetLength; // XXX use _positionalSpringConstant - - float interpolation_value = 0.5; // XXX - const glm::quat slerped_quat = glm::slerp(bulletToGLM(rigidBody->getOrientation()), - _rotationalTarget, - interpolation_value); - - if (offsetLength > IGNORE_POSITION_DELTA) { - glm::vec3 newVelocity = glm::normalize(offset) * speed; - rigidBody->setLinearVelocity(glmToBullet(newVelocity)); - // void setAngularVelocity (const btVector3 &ang_vel); - rigidBody->activate(); - } else { - rigidBody->setLinearVelocity(glmToBullet(glm::vec3())); + // handle rotation + if (_rotationalTargetSet) { + glm::quat qZeroInverse = glm::inverse(bulletToGLM(rigidBody->getOrientation())); + glm::quat deltaQ = _rotationalTarget * qZeroInverse; + glm::vec3 axis = glm::axis(deltaQ); + float angle = glm::angle(deltaQ); + glm::vec3 newAngularVelocity = (-angle / _angularTimeScale) * glm::normalize(axis); + rigidBody->setAngularVelocity(glmToBullet(newAngularVelocity)); } } } @@ -68,37 +71,52 @@ void ObjectActionSpring::updateAction(btCollisionWorld* collisionWorld, btScalar bool ObjectActionSpring::updateArguments(QVariantMap arguments) { // targets are required, spring-constants are optional - bool ok = true; + bool ptOk = true; glm::vec3 positionalTarget = - EntityActionInterface::extractVec3Argument("spring action", arguments, "positionalTarget", ok); - bool pscOK = true; - float positionalSpringConstant = - EntityActionInterface::extractFloatArgument("spring action", arguments, "positionalSpringConstant", pscOK); + EntityActionInterface::extractVec3Argument("spring action", arguments, "targetPosition", ptOk, false); + bool pscOk = true; + float linearTimeScale = + EntityActionInterface::extractFloatArgument("spring action", arguments, "linearTimeScale", pscOk, false); + if (ptOk && pscOk && linearTimeScale <= 0.0f) { + qDebug() << "spring action -- linearTimeScale must be greater than zero."; + return false; + } + bool rtOk = true; glm::quat rotationalTarget = - EntityActionInterface::extractQuatArgument("spring action", arguments, "rotationalTarget", ok); - bool rscOK = true; - float rotationalSpringConstant = - EntityActionInterface::extractFloatArgument("spring action", arguments, "rotationalSpringConstant", rscOK); + EntityActionInterface::extractQuatArgument("spring action", arguments, "targetRotation", rtOk, false); + bool rscOk = true; + float angularTimeScale = + EntityActionInterface::extractFloatArgument("spring action", arguments, "angularTimeScale", rscOk, false); - if (!ok) { + if (!ptOk && !rtOk) { + qDebug() << "spring action requires either targetPosition or targetRotation argument"; return false; } lockForWrite(); - _positionalTarget = positionalTarget; - if (pscOK) { - _positionalSpringConstant = positionalSpringConstant; - } else { - _positionalSpringConstant = 0.5; // XXX pick a good default; + _positionalTargetSet = _rotationalTargetSet = false; + + if (ptOk) { + _positionalTarget = positionalTarget; + _positionalTargetSet = true; + + if (pscOk) { + _linearTimeScale = linearTimeScale; + } else { + _linearTimeScale = 0.1; + } } - _rotationalTarget = rotationalTarget; - if (rscOK) { - _rotationalSpringConstant = rotationalSpringConstant; - } else { - _rotationalSpringConstant = 0.5; // XXX pick a good default; + if (rtOk) { + _rotationalTarget = rotationalTarget; + + if (rscOk) { + _angularTimeScale = angularTimeScale; + } else { + _angularTimeScale = 0.1; + } } _active = true; diff --git a/libraries/physics/src/ObjectActionSpring.h b/libraries/physics/src/ObjectActionSpring.h index 77502e8544..b211259866 100644 --- a/libraries/physics/src/ObjectActionSpring.h +++ b/libraries/physics/src/ObjectActionSpring.h @@ -28,10 +28,12 @@ public: private: glm::vec3 _positionalTarget; - float _positionalSpringConstant; + float _linearTimeScale; + bool _positionalTargetSet; glm::quat _rotationalTarget; - float _rotationalSpringConstant; + float _angularTimeScale; + bool _rotationalTargetSet; }; #endif // hifi_ObjectActionSpring_h From 14a45e83492e411b66726d31fd8287b46e68b1b9 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 8 Jun 2015 18:25:58 -0700 Subject: [PATCH 06/41] convert grab.js to use spring action. rotation doesn't work right, yet --- examples/grab.js | 95 +++++++++++-------- .../physics/src/ObjectActionPullToPoint.cpp | 4 + libraries/physics/src/ObjectActionSpring.cpp | 5 + 3 files changed, 65 insertions(+), 39 deletions(-) diff --git a/examples/grab.js b/examples/grab.js index 306af86c68..d04e46b17b 100644 --- a/examples/grab.js +++ b/examples/grab.js @@ -4,7 +4,7 @@ // Created by Eric Levin on May 1, 2015 // Copyright 2015 High Fidelity, Inc. // -// Grab's physically moveable entities with the mouse, by applying a spring force. +// Grab's physically moveable entities with the mouse, by applying a spring force. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -20,6 +20,7 @@ var ANGULAR_DAMPING_RATE = 0.40; // NOTE: to improve readability global variable names start with 'g' var gIsGrabbing = false; var gGrabbedEntity = null; +var gActionID = null; var gPrevMouse = {x: 0, y: 0}; var gEntityProperties; var gStartPosition; @@ -31,20 +32,20 @@ var gPlaneNormal = ZERO_VEC3; // gMaxGrabDistance is a function of the size of the object. var gMaxGrabDistance; -// gGrabMode defines the degrees of freedom of the grab target positions -// relative to gGrabStartPosition options include: +// gGrabMode defines the degrees of freedom of the grab target positions +// relative to gGrabStartPosition options include: // xzPlane (default) // verticalCylinder (SHIFT) // rotate (CONTROL) // Modes to eventually support?: -// xyPlane -// yzPlane +// xyPlane +// yzPlane // polar // elevationAzimuth -var gGrabMode = "xzplane"; +var gGrabMode = "xzplane"; -// gGrabOffset allows the user to grab an object off-center. It points from ray's intersection -// with the move-plane to object center (at the moment the grab is initiated). Future target positions +// gGrabOffset allows the user to grab an object off-center. It points from ray's intersection +// with the move-plane to object center (at the moment the grab is initiated). Future target positions // are relative to the ray's intersection by the same offset. var gGrabOffset = { x: 0, y: 0, z: 0 }; @@ -162,7 +163,7 @@ function computeNewGrabPlane() { var xzOffset = Vec3.subtract(gPointOnPlane, Camera.getPosition()); xzOffset.y = 0; gXzDistanceToGrab = Vec3.length(xzOffset); - + if (gGrabMode !== "rotate" && maybeResetMousePosition) { // we reset the mouse position whenever we stop rotating Window.setCursorPosition(gMouseAtRotateStart.x, gMouseAtRotateStart.y); @@ -193,6 +194,7 @@ function mousePressEvent(event) { var cameraPosition = Camera.getPosition(); gBeaconHeight = Vec3.length(entityProperties.dimensions); + print("gBeaconHeight = " + gBeaconHeight); gMaxGrabDistance = gBeaconHeight / MAX_SOLID_ANGLE; if (Vec3.distance(objectPosition, cameraPosition) > gMaxGrabDistance) { // don't allow grabs of things far away @@ -231,6 +233,8 @@ function mouseReleaseEvent() { } gIsGrabbing = false + Entities.deleteAction(grabbedEntity, gActionID); + gActionID = null; Overlays.editOverlay(gBeacon, { visible: false }); @@ -250,6 +254,8 @@ function mouseMoveEvent(event) { gOriginalGravity = entityProperties.gravity; } + var actionArgs; + if (gGrabMode === "rotate") { var deltaMouse = { x: 0, y: 0 }; var dx = event.x - gPreviousMouse.x; @@ -259,9 +265,12 @@ function mouseMoveEvent(event) { var dragOffset = Vec3.multiply(dx, Quat.getRight(orientation)); dragOffset = Vec3.sum(dragOffset, Vec3.multiply(-dy, Quat.getUp(orientation))); var axis = Vec3.cross(dragOffset, Quat.getFront(orientation)); - var axis = Vec3.normalize(axis); - var ROTATE_STRENGTH = 8.0; // magic number tuned by hand - gAngularVelocity = Vec3.multiply(ROTATE_STRENGTH, axis); + // var axis = Vec3.normalize(axis); + // var ROTATE_STRENGTH = 8.0; // magic number tuned by hand + // gAngularVelocity = Vec3.multiply(ROTATE_STRENGTH, axis); + + var targetRotation = Quat.angleAxis(Vec3.length(axis), Vec3.normalize(axis)); + actionArgs = {targetRotation: targetRotation, angularTimeScale: 1.0}; } else { var newTargetPosition; if (gGrabMode === "verticalCylinder") { @@ -284,9 +293,18 @@ function mouseMoveEvent(event) { } } gTargetPosition = Vec3.sum(newTargetPosition, gGrabOffset); + actionArgs = {targetPosition: gTargetPosition, linearTimeScale: 0.1}; } gPreviousMouse = { x: event.x, y: event.y }; gMouseCursorLocation = { x: Window.getCursorPositionX(), y: Window.getCursorPositionY() }; + + if (!gActionID) { + gActionID = Entities.addAction("spring", gGrabbedEntity, actionArgs); + } else { + Entities.updateAction(gGrabbedEntity, gActionID, actionArgs); + } + + updateDropLine(gTargetPosition); } function keyReleaseEvent(event) { @@ -309,38 +327,37 @@ function keyPressEvent(event) { computeNewGrabPlane(); } -function update(deltaTime) { - if (!gIsGrabbing) { - return; - } +// function update(deltaTime) { +// if (!gIsGrabbing) { +// return; +// } - var entityProperties = Entities.getEntityProperties(gGrabbedEntity); - gCurrentPosition = entityProperties.position; - if (gGrabMode === "rotate") { - gAngularVelocity = Vec3.subtract(gAngularVelocity, Vec3.multiply(gAngularVelocity, ANGULAR_DAMPING_RATE)); - Entities.editEntity(gGrabbedEntity, { angularVelocity: gAngularVelocity, }); - } +// var entityProperties = Entities.getEntityProperties(gGrabbedEntity); +// gCurrentPosition = entityProperties.position; +// if (gGrabMode === "rotate") { +// gAngularVelocity = Vec3.subtract(gAngularVelocity, Vec3.multiply(gAngularVelocity, ANGULAR_DAMPING_RATE)); +// Entities.editEntity(gGrabbedEntity, { angularVelocity: gAngularVelocity, }); +// } - // always push toward linear grab position, even when rotating - var newVelocity = ZERO_VEC3; - var dPosition = Vec3.subtract(gTargetPosition, gCurrentPosition); - var delta = Vec3.length(dPosition); - if (delta > CLOSE_ENOUGH) { - var MAX_POSITION_DELTA = 4.0; - if (delta > MAX_POSITION_DELTA) { - dPosition = Vec3.multiply(dPosition, MAX_POSITION_DELTA / delta); - } - // desired speed is proportional to displacement by the inverse of timescale - // (for critically damped motion) - newVelocity = Vec3.multiply(dPosition, INV_MOVE_TIMESCALE); - } - Entities.editEntity(gGrabbedEntity, { velocity: newVelocity, }); - updateDropLine(gTargetPosition); -} +// // always push toward linear grab position, even when rotating +// var newVelocity = ZERO_VEC3; +// var dPosition = Vec3.subtract(gTargetPosition, gCurrentPosition); +// var delta = Vec3.length(dPosition); +// if (delta > CLOSE_ENOUGH) { +// var MAX_POSITION_DELTA = 4.0; +// if (delta > MAX_POSITION_DELTA) { +// dPosition = Vec3.multiply(dPosition, MAX_POSITION_DELTA / delta); +// } +// // desired speed is proportional to displacement by the inverse of timescale +// // (for critically damped motion) +// newVelocity = Vec3.multiply(dPosition, INV_MOVE_TIMESCALE); +// } +// Entities.editEntity(gGrabbedEntity, { velocity: newVelocity, }); +// } Controller.mouseMoveEvent.connect(mouseMoveEvent); Controller.mousePressEvent.connect(mousePressEvent); Controller.mouseReleaseEvent.connect(mouseReleaseEvent); Controller.keyPressEvent.connect(keyPressEvent); Controller.keyReleaseEvent.connect(keyReleaseEvent); -Script.update.connect(update); +// Script.update.connect(update); diff --git a/libraries/physics/src/ObjectActionPullToPoint.cpp b/libraries/physics/src/ObjectActionPullToPoint.cpp index 78f202a24f..28c0e08bd9 100644 --- a/libraries/physics/src/ObjectActionPullToPoint.cpp +++ b/libraries/physics/src/ObjectActionPullToPoint.cpp @@ -28,6 +28,10 @@ ObjectActionPullToPoint::~ObjectActionPullToPoint() { } void ObjectActionPullToPoint::updateAction(btCollisionWorld* collisionWorld, btScalar deltaTimeStep) { + if (!_ownerEntity) { + qDebug() << "ObjectActionPullToPoint::updateAction no owner entity"; + return; + } if (!tryLockForRead()) { // don't risk hanging the thread running the physics simulation return; diff --git a/libraries/physics/src/ObjectActionSpring.cpp b/libraries/physics/src/ObjectActionSpring.cpp index 665ec352fe..f8b8d92ad5 100644 --- a/libraries/physics/src/ObjectActionSpring.cpp +++ b/libraries/physics/src/ObjectActionSpring.cpp @@ -28,6 +28,10 @@ ObjectActionSpring::~ObjectActionSpring() { } void ObjectActionSpring::updateAction(btCollisionWorld* collisionWorld, btScalar deltaTimeStep) { + if (!_ownerEntity) { + qDebug() << "ObjectActionSpring::updateAction no owner entity"; + return; + } if (!tryLockForRead()) { // don't risk hanging the thread running the physics simulation return; @@ -111,6 +115,7 @@ bool ObjectActionSpring::updateArguments(QVariantMap arguments) { if (rtOk) { _rotationalTarget = rotationalTarget; + _rotationalTargetSet = true; if (rscOk) { _angularTimeScale = angularTimeScale; From 2fb64aad91cab4a1ec3089a88cc6abed90be79b0 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 8 Jun 2015 20:31:35 -0700 Subject: [PATCH 07/41] fix variable name --- examples/grab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/grab.js b/examples/grab.js index d04e46b17b..f2c74bb252 100644 --- a/examples/grab.js +++ b/examples/grab.js @@ -233,7 +233,7 @@ function mouseReleaseEvent() { } gIsGrabbing = false - Entities.deleteAction(grabbedEntity, gActionID); + Entities.deleteAction(gGrabbedEntity, gActionID); gActionID = null; Overlays.editOverlay(gBeacon, { visible: false }); From 873f73ffb4929c5fa44a7574e9777d0c629e9abf Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 9 Jun 2015 00:39:49 -0700 Subject: [PATCH 08/41] Working on cursor manager --- libraries/ui/src/CursorManager.cpp | 32 ++++++++++++++++++++++++++++++ libraries/ui/src/CursorManager.h | 32 ++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 libraries/ui/src/CursorManager.cpp create mode 100644 libraries/ui/src/CursorManager.h diff --git a/libraries/ui/src/CursorManager.cpp b/libraries/ui/src/CursorManager.cpp new file mode 100644 index 0000000000..8b318d549a --- /dev/null +++ b/libraries/ui/src/CursorManager.cpp @@ -0,0 +1,32 @@ +// +// Created by Bradley Austin Davis on 2015/06/08 +// 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 +// + +#include "CursorManager.h" + +namespace Cursor { + enum class Source { + MOUSE, + LEFT_HAND, + RIGHT_HAND, + UNKNOWN, + }; + + class Instance { + Source type; + }; + + class Manager { + public: + static Manager& instance(); + + uint8_t getCount(); + Instance + }; +} + + diff --git a/libraries/ui/src/CursorManager.h b/libraries/ui/src/CursorManager.h new file mode 100644 index 0000000000..a2d26efc58 --- /dev/null +++ b/libraries/ui/src/CursorManager.h @@ -0,0 +1,32 @@ +// +// Created by Bradley Austin Davis on 2015/06/08 +// 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 +// + +#pragma once + +namespace Cursor { + enum class Source { + MOUSE, + LEFT_HAND, + RIGHT_HAND, + UNKNOWN, + }; + + class Instance { + Source type; + }; + + class Manager { + public: + static Manager& instance(); + + uint8_t getCount(); + Instance + }; +} + + From b1b2d1f85cf54f9d0bf65a6674bb4c4c3fdc0fb6 Mon Sep 17 00:00:00 2001 From: Sam Gondelman Date: Tue, 9 Jun 2015 09:45:19 -0700 Subject: [PATCH 09/41] ;) (added semicolons to js example) --- .../scripts/controllerScriptingExamples.js | 64 ++++++++++--------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/examples/example/scripts/controllerScriptingExamples.js b/examples/example/scripts/controllerScriptingExamples.js index 3e693c42ea..26a1999bbb 100644 --- a/examples/example/scripts/controllerScriptingExamples.js +++ b/examples/example/scripts/controllerScriptingExamples.js @@ -9,86 +9,88 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +// Assumes you only have the default keyboard connected + // Resets every device to its default key bindings: -Controller.resetAllDeviceBindings() +Controller.resetAllDeviceBindings(); // Query all actions -print("All Actions: \n" + Controller.getAllActions()) +print("All Actions: \n" + Controller.getAllActions()); // Each action stores: // action: int representation of enum -print("Action 5 int: \n" + Controller.getAllActions()[5].action) +print("Action 5 int: \n" + Controller.getAllActions()[5].action); // actionName: string representation of enum -print("Action 5 name: \n" + Controller.getAllActions()[5].actionName) +print("Action 5 name: \n" + Controller.getAllActions()[5].actionName); // inputChannels: list of all inputchannels that control that action -print("Action 5 input channels: \n" + Controller.getAllActions()[5].inputChannels + "\n") +print("Action 5 input channels: \n" + Controller.getAllActions()[5].inputChannels + "\n"); // Each input channel stores: // action: Action that this InputChannel maps to -print("Input channel action: \n" + Controller.getAllActions()[5].inputChannels[0].action) +print("Input channel action: \n" + Controller.getAllActions()[5].inputChannels[0].action); // scale: sensitivity of input -print("Input channel scale: \n" + Controller.getAllActions()[5].inputChannels[0].scale) +print("Input channel scale: \n" + Controller.getAllActions()[5].inputChannels[0].scale); // input and modifier: Inputs -print("Input channel input and modifier: \n" + Controller.getAllActions()[5].inputChannels[0].input + "\n" + Controller.getAllActions()[5].inputChannels[0].modifier + "\n") +print("Input channel input and modifier: \n" + Controller.getAllActions()[5].inputChannels[0].input + "\n" + Controller.getAllActions()[5].inputChannels[0].modifier + "\n"); // Each Input stores: // device: device of input -print("Input device: \n" + Controller.getAllActions()[5].inputChannels[0].input.device) +print("Input device: \n" + Controller.getAllActions()[5].inputChannels[0].input.device); // channel: channel of input -print("Input channel: \n" + Controller.getAllActions()[5].inputChannels[0].input.channel) +print("Input channel: \n" + Controller.getAllActions()[5].inputChannels[0].input.channel); // type: type of input (Unknown, Button, Axis, Joint) -print("Input type: \n" + Controller.getAllActions()[5].inputChannels[0].input.type) +print("Input type: \n" + Controller.getAllActions()[5].inputChannels[0].input.type); // id: id of input -print("Input id: \n" + Controller.getAllActions()[5].inputChannels[0].input.id + "\n") +print("Input id: \n" + Controller.getAllActions()[5].inputChannels[0].input.id + "\n"); // You can get the name of a device from its id -print("Device 1 name: \n" + Controller.getDeviceName(Controller.getAllActions()[5].inputChannels[0].input.id)) +print("Device 1 name: \n" + Controller.getDeviceName(Controller.getAllActions()[5].inputChannels[0].input.id)); // You can also get all of a devices input channels -print("Device 1's input channels: \n" + Controller.getAllInputsForDevice(1) + "\n") +print("Device 1's input channels: \n" + Controller.getAllInputsForDevice(1) + "\n"); // Modifying properties: // The following code will switch the "w" and "s" key functionality and adjust their scales -var s = Controller.getAllActions()[0].inputChannels[0] -var w = Controller.getAllActions()[1].inputChannels[0] +var s = Controller.getAllActions()[0].inputChannels[0]; +var w = Controller.getAllActions()[1].inputChannels[0]; // You must remove an input controller before modifying it so the old input controller isn't registered anymore // removeInputChannel and addInputChannel return true if successful, false otherwise -Controller.removeInputChannel(s) -Controller.removeInputChannel(w) -print(s.scale) -s.action = 1 -s.scale = .01 +Controller.removeInputChannel(s); +Controller.removeInputChannel(w); +print(s.scale); +s.action = 1; +s.scale = .01; -w.action = 0 -w.scale = 10000 -Controller.addInputChannel(s) -Controller.addInputChannel(w) -print(s.scale) +w.action = 0; +w.scale = 10000; +Controller.addInputChannel(s); +Controller.addInputChannel(w); +print(s.scale); // You can get all the available inputs for any device // Each AvailableInput has: // input: the Input itself // inputName: string representing the input -var availableInputs = Controller.getAvailableInputs(1) +var availableInputs = Controller.getAvailableInputs(1); for (i = 0; i < availableInputs.length; i++) { print(availableInputs[i].inputName); } // You can modify key bindings by using these avaiable inputs // This will replace e (up) with 6 -var e = Controller.getAllActions()[5].inputChannels[0] -Controller.removeInputChannel(e) -e.input = availableInputs[6].input -Controller.addInputChannel(e) \ No newline at end of file +var e = Controller.getAllActions()[5].inputChannels[0]; +Controller.removeInputChannel(e); +e.input = availableInputs[6].input; +Controller.addInputChannel(e); \ No newline at end of file From d3a4eec5c5a935bfacd56221672829f246c18e2f Mon Sep 17 00:00:00 2001 From: Sam Gondelman Date: Tue, 9 Jun 2015 09:56:40 -0700 Subject: [PATCH 10/41] removed dead code, magic numbers --- interface/src/devices/KeyboardMouseDevice.cpp | 8 ++++---- interface/src/ui/UserInputMapper.h | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/interface/src/devices/KeyboardMouseDevice.cpp b/interface/src/devices/KeyboardMouseDevice.cpp index a7e85d28e1..d0f04e5636 100755 --- a/interface/src/devices/KeyboardMouseDevice.cpp +++ b/interface/src/devices/KeyboardMouseDevice.cpp @@ -164,11 +164,11 @@ void KeyboardMouseDevice::registerToUserInputMapper(UserInputMapper& mapper) { proxy->getAxis = [this] (const UserInputMapper::Input& input, int timestamp) -> float { return this->getAxis(input._channel); }; proxy->getAvailabeInputs = [this] () -> QVector { QVector availableInputs; - for (int i = 0; i <= 9; i++) { - availableInputs.append(UserInputMapper::InputPair(makeInput(Qt::Key(48 + i)), QKeySequence(Qt::Key(48 + i)).toString())); + for (int i = (int) Qt::Key_0; i <= (int) Qt::Key_9; i++) { + availableInputs.append(UserInputMapper::InputPair(makeInput(Qt::Key(i)), QKeySequence(Qt::Key(i)).toString())); } - for (int i = 0; i < 26; i++) { - availableInputs.append(UserInputMapper::InputPair(makeInput(Qt::Key(65 + i)), QKeySequence(Qt::Key(65 + i)).toString())); + for (int i = (int) Qt::Key_A; i <= (int) Qt::Key_Z; i++) { + availableInputs.append(UserInputMapper::InputPair(makeInput(Qt::Key(i)), QKeySequence(Qt::Key(i)).toString())); } availableInputs.append(UserInputMapper::InputPair(makeInput(Qt::Key_Space), QKeySequence(Qt::Key_Space).toString())); return availableInputs; diff --git a/interface/src/ui/UserInputMapper.h b/interface/src/ui/UserInputMapper.h index 34188ae3f5..9019a1de48 100755 --- a/interface/src/ui/UserInputMapper.h +++ b/interface/src/ui/UserInputMapper.h @@ -142,7 +142,6 @@ public: QVector getAllActions(); QString getActionName(Action action) { return UserInputMapper::_actionNames[(int) action]; } float getActionState(Action action) const { return _actionStates[action]; } -// QVector void assignDefaulActionScales(); // Add input channel to the mapper and check that all the used channels are registered. From 0769593ca89c74a0b84f38e8066e2b43cfa5e42f Mon Sep 17 00:00:00 2001 From: Sam Gondelman Date: Tue, 9 Jun 2015 10:20:36 -0700 Subject: [PATCH 11/41] added getters/setters for Inputs and InputChannels --- .../ControllerScriptingInterface.cpp | 30 +++++++++++-------- interface/src/ui/UserInputMapper.h | 17 ++++++++++- 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/interface/src/scripting/ControllerScriptingInterface.cpp b/interface/src/scripting/ControllerScriptingInterface.cpp index fe2eda12df..2c747195a7 100644 --- a/interface/src/scripting/ControllerScriptingInterface.cpp +++ b/interface/src/scripting/ControllerScriptingInterface.cpp @@ -46,32 +46,36 @@ QScriptValue inputToScriptValue(QScriptEngine* engine, const UserInputMapper::In QScriptValue obj = engine->newObject(); obj.setProperty("device", input.getDevice()); obj.setProperty("channel", input.getChannel()); - obj.setProperty("type", input._type); + obj.setProperty("type", (unsigned short) input.getType()); obj.setProperty("id", input.getID()); return obj; } void inputFromScriptValue(const QScriptValue& object, UserInputMapper::Input& input) { - input._device = object.property("device").toUInt16(); - input._channel = object.property("channel").toUInt16(); - input._type = object.property("type").toUInt16(); - input._id = object.property("id").toInt32(); + input.setDevice(object.property("device").toUInt16()); + input.setChannel(object.property("channel").toUInt16()); + input.setType(object.property("type").toUInt16()); + input.setID(object.property("id").toInt32()); } QScriptValue inputChannelToScriptValue(QScriptEngine* engine, const UserInputMapper::InputChannel& inputChannel) { QScriptValue obj = engine->newObject(); - obj.setProperty("input", inputToScriptValue(engine, inputChannel._input)); - obj.setProperty("modifier", inputToScriptValue(engine, inputChannel._modifier)); - obj.setProperty("action", inputChannel._action); - obj.setProperty("scale", inputChannel._scale); + obj.setProperty("input", inputToScriptValue(engine, inputChannel.getInput())); + obj.setProperty("modifier", inputToScriptValue(engine, inputChannel.getModifier())); + obj.setProperty("action", inputChannel.getAction()); + obj.setProperty("scale", inputChannel.getScale()); return obj; } void inputChannelFromScriptValue(const QScriptValue& object, UserInputMapper::InputChannel& inputChannel) { - inputFromScriptValue(object.property("input"), inputChannel._input); - inputFromScriptValue(object.property("modifier"), inputChannel._modifier); - inputChannel._action = UserInputMapper::Action(object.property("action").toVariant().toInt()); - inputChannel._scale = object.property("scale").toVariant().toFloat(); + UserInputMapper::Input input; + UserInputMapper::Input modifier; + inputFromScriptValue(object.property("input"), input); + inputChannel.setInput(input); + inputFromScriptValue(object.property("modifier"), modifier); + inputChannel.setModifier(modifier); + inputChannel.setAction(UserInputMapper::Action(object.property("action").toVariant().toInt())); + inputChannel.setScale(object.property("scale").toVariant().toFloat()); } QScriptValue actionToScriptValue(QScriptEngine* engine, const UserInputMapper::Action& action) { diff --git a/interface/src/ui/UserInputMapper.h b/interface/src/ui/UserInputMapper.h index 9019a1de48..0a08e277db 100755 --- a/interface/src/ui/UserInputMapper.h +++ b/interface/src/ui/UserInputMapper.h @@ -52,8 +52,13 @@ public: uint16 getDevice() const { return _device; } uint16 getChannel() const { return _channel; } uint32 getID() const { return _id; } - ChannelType getType() const { return (ChannelType) _type; } + + void setDevice(uint16 device) { _device = device; } + void setChannel(uint16 channel) { _channel = channel; } + void setType(uint16 type) { _type = type; } + void setID(uint32 ID) { _id = ID; } + bool isButton() const { return getType() == ChannelType::BUTTON; } bool isAxis() const { return getType() == ChannelType::AXIS; } bool isJoint() const { return getType() == ChannelType::JOINT; } @@ -157,6 +162,16 @@ public: Input _modifier = Input(); // make it invalid by default, meaning no modifier Action _action = LONGITUDINAL_BACKWARD; float _scale = 0.0f; + + Input getInput() const { return _input; } + Input getModifier() const { return _modifier; } + Action getAction() const { return _action; } + float getScale() const { return _scale; } + + void setInput(Input input) { _input = input; } + void setModifier(Input modifier) { _modifier = modifier; } + void setAction(Action action) { _action = action; } + void setScale(float scale) { _scale = scale; } InputChannel() {} InputChannel(const Input& input, const Input& modifier, Action action, float scale = 1.0f) : From ccb2f17b33f48d46cfa3d1c51325ab04402ff2ec Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 9 Jun 2015 11:21:13 -0700 Subject: [PATCH 12/41] grab.js rotation sort-of works now --- examples/grab.js | 18 +++++++++--------- .../entities/src/EntityActionInterface.cpp | 2 +- libraries/physics/src/ObjectActionSpring.cpp | 3 ++- .../physics/src/PhysicalEntitySimulation.cpp | 6 +++--- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/examples/grab.js b/examples/grab.js index f2c74bb252..8f733e9c9d 100644 --- a/examples/grab.js +++ b/examples/grab.js @@ -60,7 +60,7 @@ var gMouseAtRotateStart = { x: 0, y: 0 }; var gBeaconHeight = 0.10; -var gAngularVelocity = ZERO_VEC3; +// var gAngularVelocity = ZERO_VEC3; // TODO: play sounds again when we aren't leaking AudioInjector threads // var grabSound = SoundCache.getSound("https://hifi-public.s3.amazonaws.com/eric/sounds/CloseClamp.wav"); @@ -194,7 +194,6 @@ function mousePressEvent(event) { var cameraPosition = Camera.getPosition(); gBeaconHeight = Vec3.length(entityProperties.dimensions); - print("gBeaconHeight = " + gBeaconHeight); gMaxGrabDistance = gBeaconHeight / MAX_SOLID_ANGLE; if (Vec3.distance(objectPosition, cameraPosition) > gMaxGrabDistance) { // don't allow grabs of things far away @@ -254,7 +253,7 @@ function mouseMoveEvent(event) { gOriginalGravity = entityProperties.gravity; } - var actionArgs; + var actionArgs = {}; if (gGrabMode === "rotate") { var deltaMouse = { x: 0, y: 0 }; @@ -265,12 +264,13 @@ function mouseMoveEvent(event) { var dragOffset = Vec3.multiply(dx, Quat.getRight(orientation)); dragOffset = Vec3.sum(dragOffset, Vec3.multiply(-dy, Quat.getUp(orientation))); var axis = Vec3.cross(dragOffset, Quat.getFront(orientation)); - // var axis = Vec3.normalize(axis); - // var ROTATE_STRENGTH = 8.0; // magic number tuned by hand - // gAngularVelocity = Vec3.multiply(ROTATE_STRENGTH, axis); - - var targetRotation = Quat.angleAxis(Vec3.length(axis), Vec3.normalize(axis)); - actionArgs = {targetRotation: targetRotation, angularTimeScale: 1.0}; + axis = Vec3.normalize(axis); + var ROTATE_STRENGTH = 8.0; // magic number tuned by hand + var angle = ROTATE_STRENGTH * Math.sqrt((dx * dx) + (dy * dy)); + var deltaQ = Quat.angleAxis(angle, axis); + var qZero = entityProperties.rotation; + var qOne = Quat.multiply(deltaQ, qZero); + actionArgs = {targetRotation: qOne, angularTimeScale: 0.1}; } else { var newTargetPosition; if (gGrabMode === "verticalCylinder") { diff --git a/libraries/entities/src/EntityActionInterface.cpp b/libraries/entities/src/EntityActionInterface.cpp index b3e774df96..399c529aa2 100644 --- a/libraries/entities/src/EntityActionInterface.cpp +++ b/libraries/entities/src/EntityActionInterface.cpp @@ -131,7 +131,7 @@ glm::quat EntityActionInterface::extractQuatArgument(QString objectName, QVarian return glm::quat(); } - return glm::quat(x, y, z, w); + return glm::quat(w, x, y, z); } diff --git a/libraries/physics/src/ObjectActionSpring.cpp b/libraries/physics/src/ObjectActionSpring.cpp index f8b8d92ad5..5badc91f6a 100644 --- a/libraries/physics/src/ObjectActionSpring.cpp +++ b/libraries/physics/src/ObjectActionSpring.cpp @@ -64,8 +64,9 @@ void ObjectActionSpring::updateAction(btCollisionWorld* collisionWorld, btScalar glm::quat deltaQ = _rotationalTarget * qZeroInverse; glm::vec3 axis = glm::axis(deltaQ); float angle = glm::angle(deltaQ); - glm::vec3 newAngularVelocity = (-angle / _angularTimeScale) * glm::normalize(axis); + glm::vec3 newAngularVelocity = (angle / _angularTimeScale) * glm::normalize(axis); rigidBody->setAngularVelocity(glmToBullet(newAngularVelocity)); + rigidBody->activate(); } } } diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index 56d497f8a1..a4d2113be6 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -236,9 +236,9 @@ void PhysicalEntitySimulation::handleCollisionEvents(CollisionEvents& collisionE } EntityActionPointer PhysicalEntitySimulation::actionFactory(EntityActionType type, - QUuid id, - EntityItemPointer ownerEntity, - QVariantMap arguments) { + QUuid id, + EntityItemPointer ownerEntity, + QVariantMap arguments) { EntityActionPointer action = nullptr; switch (type) { case ACTION_TYPE_NONE: From 23dab530f911396cea8896f927dbf74d77b5c52b Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 9 Jun 2015 14:11:01 -0700 Subject: [PATCH 13/41] fix some glitchy behavior during rotation --- examples/grab.js | 33 +------------------- libraries/physics/src/ObjectActionSpring.cpp | 33 +++++++++++++++----- 2 files changed, 27 insertions(+), 39 deletions(-) diff --git a/examples/grab.js b/examples/grab.js index 8f733e9c9d..3355ad65c0 100644 --- a/examples/grab.js +++ b/examples/grab.js @@ -21,7 +21,6 @@ var ANGULAR_DAMPING_RATE = 0.40; var gIsGrabbing = false; var gGrabbedEntity = null; var gActionID = null; -var gPrevMouse = {x: 0, y: 0}; var gEntityProperties; var gStartPosition; var gStartRotation; @@ -259,13 +258,12 @@ function mouseMoveEvent(event) { var deltaMouse = { x: 0, y: 0 }; var dx = event.x - gPreviousMouse.x; var dy = event.y - gPreviousMouse.y; - var orientation = Camera.getOrientation(); var dragOffset = Vec3.multiply(dx, Quat.getRight(orientation)); dragOffset = Vec3.sum(dragOffset, Vec3.multiply(-dy, Quat.getUp(orientation))); var axis = Vec3.cross(dragOffset, Quat.getFront(orientation)); axis = Vec3.normalize(axis); - var ROTATE_STRENGTH = 8.0; // magic number tuned by hand + var ROTATE_STRENGTH = 16.0; // magic number tuned by hand var angle = ROTATE_STRENGTH * Math.sqrt((dx * dx) + (dy * dy)); var deltaQ = Quat.angleAxis(angle, axis); var qZero = entityProperties.rotation; @@ -327,37 +325,8 @@ function keyPressEvent(event) { computeNewGrabPlane(); } -// function update(deltaTime) { -// if (!gIsGrabbing) { -// return; -// } - -// var entityProperties = Entities.getEntityProperties(gGrabbedEntity); -// gCurrentPosition = entityProperties.position; -// if (gGrabMode === "rotate") { -// gAngularVelocity = Vec3.subtract(gAngularVelocity, Vec3.multiply(gAngularVelocity, ANGULAR_DAMPING_RATE)); -// Entities.editEntity(gGrabbedEntity, { angularVelocity: gAngularVelocity, }); -// } - -// // always push toward linear grab position, even when rotating -// var newVelocity = ZERO_VEC3; -// var dPosition = Vec3.subtract(gTargetPosition, gCurrentPosition); -// var delta = Vec3.length(dPosition); -// if (delta > CLOSE_ENOUGH) { -// var MAX_POSITION_DELTA = 4.0; -// if (delta > MAX_POSITION_DELTA) { -// dPosition = Vec3.multiply(dPosition, MAX_POSITION_DELTA / delta); -// } -// // desired speed is proportional to displacement by the inverse of timescale -// // (for critically damped motion) -// newVelocity = Vec3.multiply(dPosition, INV_MOVE_TIMESCALE); -// } -// Entities.editEntity(gGrabbedEntity, { velocity: newVelocity, }); -// } - Controller.mouseMoveEvent.connect(mouseMoveEvent); Controller.mousePressEvent.connect(mousePressEvent); Controller.mouseReleaseEvent.connect(mouseReleaseEvent); Controller.keyPressEvent.connect(keyPressEvent); Controller.keyReleaseEvent.connect(keyReleaseEvent); -// Script.update.connect(update); diff --git a/libraries/physics/src/ObjectActionSpring.cpp b/libraries/physics/src/ObjectActionSpring.cpp index 5badc91f6a..cf9a226c89 100644 --- a/libraries/physics/src/ObjectActionSpring.cpp +++ b/libraries/physics/src/ObjectActionSpring.cpp @@ -60,13 +60,32 @@ void ObjectActionSpring::updateAction(btCollisionWorld* collisionWorld, btScalar // handle rotation if (_rotationalTargetSet) { - glm::quat qZeroInverse = glm::inverse(bulletToGLM(rigidBody->getOrientation())); - glm::quat deltaQ = _rotationalTarget * qZeroInverse; - glm::vec3 axis = glm::axis(deltaQ); - float angle = glm::angle(deltaQ); - glm::vec3 newAngularVelocity = (angle / _angularTimeScale) * glm::normalize(axis); - rigidBody->setAngularVelocity(glmToBullet(newAngularVelocity)); - rigidBody->activate(); + glm::quat bodyRotation = bulletToGLM(rigidBody->getOrientation()); + // if qZero and qOne are too close to each other, we can get NaN for angle. + auto alignmentDot = glm::dot(bodyRotation, _rotationalTarget); + const float almostOne = 0.99999; + if (glm::abs(alignmentDot) < almostOne) { + glm::quat target = _rotationalTarget; + if (alignmentDot < 0) { + target = -target; + } + glm::quat qZeroInverse = glm::inverse(bodyRotation); + glm::quat deltaQ = target * qZeroInverse; + glm::vec3 axis = glm::axis(deltaQ); + float angle = glm::angle(deltaQ); + if (isNaN(angle)) { + qDebug() << "ObjectActionSpring::updateAction angle =" << angle + << "body-rotation =" << bodyRotation.x << bodyRotation.y << bodyRotation.z << bodyRotation.w + << "target-rotation =" + << target.x << target.y << target.z<< target.w; + } + assert(!isNaN(angle)); + glm::vec3 newAngularVelocity = (angle / _angularTimeScale) * glm::normalize(axis); + rigidBody->setAngularVelocity(glmToBullet(newAngularVelocity)); + rigidBody->activate(); + } else { + rigidBody->setAngularVelocity(glmToBullet(glm::vec3(0.0f))); + } } } } From 6cce8459847567f6508d4cbd0235710418b67ea2 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 9 Jun 2015 14:26:44 -0700 Subject: [PATCH 14/41] rotate based on initial mouse-position and initial entity rotation rather than doing small deltas as the mouse moves --- examples/grab.js | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/examples/grab.js b/examples/grab.js index 3355ad65c0..6a27e1b73b 100644 --- a/examples/grab.js +++ b/examples/grab.js @@ -53,6 +53,7 @@ var gTargetRotation; var gLiftKey = false; // SHIFT var gRotateKey = false; // CONTROL +var gInitialMouse = { x: 0, y: 0 }; var gPreviousMouse = { x: 0, y: 0 }; var gMouseCursorLocation = { x: 0, y: 0 }; var gMouseAtRotateStart = { x: 0, y: 0 }; @@ -173,6 +174,7 @@ function mousePressEvent(event) { if (!event.isLeftButton) { return; } + gInitialMouse = {x: event.x, y: event.y }; gPreviousMouse = {x: event.x, y: event.y }; var pickRay = Camera.computePickRay(event.x, event.y); @@ -189,12 +191,13 @@ function mousePressEvent(event) { var clickedEntity = pickResults.entityID; var entityProperties = Entities.getEntityProperties(clickedEntity) - var objectPosition = entityProperties.position; + gStartPosition = entityProperties.position; + gStartRotation = entityProperties.rotation; var cameraPosition = Camera.getPosition(); gBeaconHeight = Vec3.length(entityProperties.dimensions); gMaxGrabDistance = gBeaconHeight / MAX_SOLID_ANGLE; - if (Vec3.distance(objectPosition, cameraPosition) > gMaxGrabDistance) { + if (Vec3.distance(gStartPosition, cameraPosition) > gMaxGrabDistance) { // don't allow grabs of things far away return; } @@ -205,20 +208,20 @@ function mousePressEvent(event) { gGrabbedEntity = clickedEntity; gCurrentPosition = entityProperties.position; gOriginalGravity = entityProperties.gravity; - gTargetPosition = objectPosition; + gTargetPosition = gStartPosition; // compute the grab point - var nearestPoint = Vec3.subtract(objectPosition, cameraPosition); + var nearestPoint = Vec3.subtract(gStartPosition, cameraPosition); var distanceToGrab = Vec3.dot(nearestPoint, pickRay.direction); nearestPoint = Vec3.multiply(distanceToGrab, pickRay.direction); gPointOnPlane = Vec3.sum(cameraPosition, nearestPoint); // compute the grab offset - gGrabOffset = Vec3.subtract(objectPosition, gPointOnPlane); + gGrabOffset = Vec3.subtract(gStartPosition, gPointOnPlane); computeNewGrabPlane(); - updateDropLine(objectPosition); + updateDropLine(gStartPosition); // TODO: play sounds again when we aren't leaking AudioInjector threads //Audio.playSound(grabSound, { position: entityProperties.position, volume: VOLUME }); @@ -256,17 +259,18 @@ function mouseMoveEvent(event) { if (gGrabMode === "rotate") { var deltaMouse = { x: 0, y: 0 }; - var dx = event.x - gPreviousMouse.x; - var dy = event.y - gPreviousMouse.y; + var dx = event.x - gInitialMouse.x; + var dy = event.y - gInitialMouse.y; var orientation = Camera.getOrientation(); var dragOffset = Vec3.multiply(dx, Quat.getRight(orientation)); dragOffset = Vec3.sum(dragOffset, Vec3.multiply(-dy, Quat.getUp(orientation))); var axis = Vec3.cross(dragOffset, Quat.getFront(orientation)); axis = Vec3.normalize(axis); - var ROTATE_STRENGTH = 16.0; // magic number tuned by hand + var ROTATE_STRENGTH = 0.4; // magic number tuned by hand var angle = ROTATE_STRENGTH * Math.sqrt((dx * dx) + (dy * dy)); var deltaQ = Quat.angleAxis(angle, axis); - var qZero = entityProperties.rotation; + // var qZero = entityProperties.rotation; + var qZero = gStartRotation; var qOne = Quat.multiply(deltaQ, qZero); actionArgs = {targetRotation: qOne, angularTimeScale: 0.1}; } else { From b1a209b9db9e6cdad15ef47c37a47a7cc2208ee7 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 9 Jun 2015 16:17:48 -0700 Subject: [PATCH 15/41] pull some common code out of subclasses and into ObjectAction --- .../src/EntityTreeRenderer.cpp | 4 +- libraries/physics/src/ObjectAction.cpp | 22 +++- libraries/physics/src/ObjectAction.h | 11 +- .../physics/src/ObjectActionPullToPoint.cpp | 39 ++----- .../physics/src/ObjectActionPullToPoint.h | 3 +- libraries/physics/src/ObjectActionSpring.cpp | 106 +++++++----------- libraries/physics/src/ObjectActionSpring.h | 3 +- 7 files changed, 90 insertions(+), 98 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 98cbc1f845..3e83c6f559 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -1012,7 +1012,9 @@ void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) { void EntityTreeRenderer::addingEntity(const EntityItemID& entityID) { checkAndCallPreload(entityID); auto entity = static_cast(_tree)->findEntityByID(entityID); - addEntityToScene(entity); + if (entity) { + addEntityToScene(entity); + } } void EntityTreeRenderer::addEntityToScene(EntityItemPointer entity) { diff --git a/libraries/physics/src/ObjectAction.cpp b/libraries/physics/src/ObjectAction.cpp index 6ff4098ba8..5f67db4355 100644 --- a/libraries/physics/src/ObjectAction.cpp +++ b/libraries/physics/src/ObjectAction.cpp @@ -24,7 +24,27 @@ ObjectAction::~ObjectAction() { } void ObjectAction::updateAction(btCollisionWorld* collisionWorld, btScalar deltaTimeStep) { - qDebug() << "ObjectAction::updateAction called"; + if (!_active) { + return; + } + if (!_ownerEntity) { + qDebug() << "ObjectActionPullToPoint::updateAction no owner entity"; + return; + } + if (!tryLockForRead()) { + // don't risk hanging the thread running the physics simulation + return; + } + void* physicsInfo = _ownerEntity->getPhysicsInfo(); + + if (physicsInfo) { + ObjectMotionState* motionState = static_cast(physicsInfo); + btRigidBody* rigidBody = motionState->getRigidBody(); + if (rigidBody) { + updateActionWorker(collisionWorld, deltaTimeStep, motionState, rigidBody); + } + } + unlock(); } void ObjectAction::debugDraw(btIDebugDraw* debugDrawer) { diff --git a/libraries/physics/src/ObjectAction.h b/libraries/physics/src/ObjectAction.h index 10b086c07d..7ff11b8ba0 100644 --- a/libraries/physics/src/ObjectAction.h +++ b/libraries/physics/src/ObjectAction.h @@ -13,11 +13,14 @@ #ifndef hifi_ObjectAction_h #define hifi_ObjectAction_h -#include - #include +#include + #include +#include "ObjectMotionState.h" +#include "BulletUtil.h" + class ObjectAction : public btActionInterface, public EntityActionInterface { public: @@ -30,6 +33,10 @@ public: virtual void setOwnerEntity(const EntityItemPointer ownerEntity) { _ownerEntity = ownerEntity; } virtual bool updateArguments(QVariantMap arguments) { return false; } + // this is called from updateAction and should be overridden by subclasses + virtual void updateActionWorker(btCollisionWorld* collisionWorld, btScalar deltaTimeStep, + ObjectMotionState* motionState, btRigidBody* rigidBody) {} + // these are from btActionInterface virtual void updateAction(btCollisionWorld* collisionWorld, btScalar deltaTimeStep); virtual void debugDraw(btIDebugDraw* debugDrawer); diff --git a/libraries/physics/src/ObjectActionPullToPoint.cpp b/libraries/physics/src/ObjectActionPullToPoint.cpp index 28c0e08bd9..2e66b948f5 100644 --- a/libraries/physics/src/ObjectActionPullToPoint.cpp +++ b/libraries/physics/src/ObjectActionPullToPoint.cpp @@ -9,9 +9,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "ObjectMotionState.h" -#include "BulletUtil.h" - #include "ObjectActionPullToPoint.h" ObjectActionPullToPoint::ObjectActionPullToPoint(QUuid id, EntityItemPointer ownerEntity) : @@ -27,33 +24,17 @@ ObjectActionPullToPoint::~ObjectActionPullToPoint() { #endif } -void ObjectActionPullToPoint::updateAction(btCollisionWorld* collisionWorld, btScalar deltaTimeStep) { - if (!_ownerEntity) { - qDebug() << "ObjectActionPullToPoint::updateAction no owner entity"; - return; +void ObjectActionPullToPoint::updateActionWorker(btCollisionWorld* collisionWorld, btScalar deltaTimeStep, + ObjectMotionState* motionState, btRigidBody* rigidBody) { + glm::vec3 offset = _target - bulletToGLM(rigidBody->getCenterOfMassPosition()); + float offsetLength = glm::length(offset); + if (offsetLength > IGNORE_POSITION_DELTA) { + glm::vec3 newVelocity = glm::normalize(offset) * _speed; + rigidBody->setLinearVelocity(glmToBullet(newVelocity)); + rigidBody->activate(); + } else { + rigidBody->setLinearVelocity(glmToBullet(glm::vec3())); } - if (!tryLockForRead()) { - // don't risk hanging the thread running the physics simulation - return; - } - void* physicsInfo = _ownerEntity->getPhysicsInfo(); - - if (_active && physicsInfo) { - ObjectMotionState* motionState = static_cast(physicsInfo); - btRigidBody* rigidBody = motionState->getRigidBody(); - if (rigidBody) { - glm::vec3 offset = _target - bulletToGLM(rigidBody->getCenterOfMassPosition()); - float offsetLength = glm::length(offset); - if (offsetLength > IGNORE_POSITION_DELTA) { - glm::vec3 newVelocity = glm::normalize(offset) * _speed; - rigidBody->setLinearVelocity(glmToBullet(newVelocity)); - rigidBody->activate(); - } else { - rigidBody->setLinearVelocity(glmToBullet(glm::vec3())); - } - } - } - unlock(); } diff --git a/libraries/physics/src/ObjectActionPullToPoint.h b/libraries/physics/src/ObjectActionPullToPoint.h index 3aca70d640..55fd748921 100644 --- a/libraries/physics/src/ObjectActionPullToPoint.h +++ b/libraries/physics/src/ObjectActionPullToPoint.h @@ -23,7 +23,8 @@ public: virtual ~ObjectActionPullToPoint(); virtual bool updateArguments(QVariantMap arguments); - virtual void updateAction(btCollisionWorld* collisionWorld, btScalar deltaTimeStep); + virtual void updateActionWorker(btCollisionWorld* collisionWorld, btScalar deltaTimeStep, + ObjectMotionState* motionState, btRigidBody* rigidBody); private: diff --git a/libraries/physics/src/ObjectActionSpring.cpp b/libraries/physics/src/ObjectActionSpring.cpp index cf9a226c89..101af665f7 100644 --- a/libraries/physics/src/ObjectActionSpring.cpp +++ b/libraries/physics/src/ObjectActionSpring.cpp @@ -9,9 +9,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "ObjectMotionState.h" -#include "BulletUtil.h" - #include "ObjectActionSpring.h" ObjectActionSpring::ObjectActionSpring(QUuid id, EntityItemPointer ownerEntity) : @@ -27,69 +24,52 @@ ObjectActionSpring::~ObjectActionSpring() { #endif } -void ObjectActionSpring::updateAction(btCollisionWorld* collisionWorld, btScalar deltaTimeStep) { - if (!_ownerEntity) { - qDebug() << "ObjectActionSpring::updateAction no owner entity"; - return; - } - if (!tryLockForRead()) { - // don't risk hanging the thread running the physics simulation - return; - } - void* physicsInfo = _ownerEntity->getPhysicsInfo(); +void ObjectActionSpring::updateActionWorker(btCollisionWorld* collisionWorld, btScalar deltaTimeStep, + ObjectMotionState* motionState, btRigidBody* rigidBody) { + // handle the linear part + if (_positionalTargetSet) { + glm::vec3 offset = _positionalTarget - bulletToGLM(rigidBody->getCenterOfMassPosition()); + float offsetLength = glm::length(offset); + float speed = offsetLength / _linearTimeScale; - if (_active && physicsInfo) { - ObjectMotionState* motionState = static_cast(physicsInfo); - btRigidBody* rigidBody = motionState->getRigidBody(); - if (rigidBody) { - // handle the linear part - if (_positionalTargetSet) { - glm::vec3 offset = _positionalTarget - bulletToGLM(rigidBody->getCenterOfMassPosition()); - float offsetLength = glm::length(offset); - float speed = offsetLength / _linearTimeScale; - - if (offsetLength > IGNORE_POSITION_DELTA) { - glm::vec3 newVelocity = glm::normalize(offset) * speed; - rigidBody->setLinearVelocity(glmToBullet(newVelocity)); - // void setAngularVelocity (const btVector3 &ang_vel); - rigidBody->activate(); - } else { - rigidBody->setLinearVelocity(glmToBullet(glm::vec3())); - } - } - - // handle rotation - if (_rotationalTargetSet) { - glm::quat bodyRotation = bulletToGLM(rigidBody->getOrientation()); - // if qZero and qOne are too close to each other, we can get NaN for angle. - auto alignmentDot = glm::dot(bodyRotation, _rotationalTarget); - const float almostOne = 0.99999; - if (glm::abs(alignmentDot) < almostOne) { - glm::quat target = _rotationalTarget; - if (alignmentDot < 0) { - target = -target; - } - glm::quat qZeroInverse = glm::inverse(bodyRotation); - glm::quat deltaQ = target * qZeroInverse; - glm::vec3 axis = glm::axis(deltaQ); - float angle = glm::angle(deltaQ); - if (isNaN(angle)) { - qDebug() << "ObjectActionSpring::updateAction angle =" << angle - << "body-rotation =" << bodyRotation.x << bodyRotation.y << bodyRotation.z << bodyRotation.w - << "target-rotation =" - << target.x << target.y << target.z<< target.w; - } - assert(!isNaN(angle)); - glm::vec3 newAngularVelocity = (angle / _angularTimeScale) * glm::normalize(axis); - rigidBody->setAngularVelocity(glmToBullet(newAngularVelocity)); - rigidBody->activate(); - } else { - rigidBody->setAngularVelocity(glmToBullet(glm::vec3(0.0f))); - } - } + if (offsetLength > IGNORE_POSITION_DELTA) { + glm::vec3 newVelocity = glm::normalize(offset) * speed; + rigidBody->setLinearVelocity(glmToBullet(newVelocity)); + rigidBody->activate(); + } else { + rigidBody->setLinearVelocity(glmToBullet(glm::vec3(0.0f))); + } + } + + // handle rotation + if (_rotationalTargetSet) { + glm::quat bodyRotation = bulletToGLM(rigidBody->getOrientation()); + // if qZero and qOne are too close to each other, we can get NaN for angle. + auto alignmentDot = glm::dot(bodyRotation, _rotationalTarget); + const float almostOne = 0.99999; + if (glm::abs(alignmentDot) < almostOne) { + glm::quat target = _rotationalTarget; + if (alignmentDot < 0) { + target = -target; + } + glm::quat qZeroInverse = glm::inverse(bodyRotation); + glm::quat deltaQ = target * qZeroInverse; + glm::vec3 axis = glm::axis(deltaQ); + float angle = glm::angle(deltaQ); + if (isNaN(angle)) { + qDebug() << "ObjectActionSpring::updateAction angle =" << angle + << "body-rotation =" << bodyRotation.x << bodyRotation.y << bodyRotation.z << bodyRotation.w + << "target-rotation =" + << target.x << target.y << target.z<< target.w; + } + assert(!isNaN(angle)); + glm::vec3 newAngularVelocity = (angle / _angularTimeScale) * glm::normalize(axis); + rigidBody->setAngularVelocity(glmToBullet(newAngularVelocity)); + rigidBody->activate(); + } else { + rigidBody->setAngularVelocity(glmToBullet(glm::vec3(0.0f))); } } - unlock(); } diff --git a/libraries/physics/src/ObjectActionSpring.h b/libraries/physics/src/ObjectActionSpring.h index b211259866..0ce06ab43e 100644 --- a/libraries/physics/src/ObjectActionSpring.h +++ b/libraries/physics/src/ObjectActionSpring.h @@ -23,7 +23,8 @@ public: virtual ~ObjectActionSpring(); virtual bool updateArguments(QVariantMap arguments); - virtual void updateAction(btCollisionWorld* collisionWorld, btScalar deltaTimeStep); + virtual void updateActionWorker(btCollisionWorld* collisionWorld, btScalar deltaTimeStep, + ObjectMotionState* motionState, btRigidBody* rigidBody); private: From 111284158643fd79bd9b85f5b38a4d9ce98772e1 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 9 Jun 2015 19:01:03 -0700 Subject: [PATCH 16/41] fix indentation in AddressManager --- libraries/networking/src/AddressManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 642ca4748d..51575bfde5 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -218,7 +218,7 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const const QString DOMAIN_NETWORK_PORT_KEY = "network_port"; const QString DOMAIN_ICE_SERVER_ADDRESS_KEY = "ice_server_address"; - DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::HandleAddress); + DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::HandleAddress); const QString DOMAIN_ID_KEY = "id"; QString domainIDString = domainObject[DOMAIN_ID_KEY].toString(); From 94c414e4e86ed32a5ebc68f673d3d48c41b9d117 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 10 Jun 2015 08:12:58 -0700 Subject: [PATCH 17/41] Add unit quad, commonly used in compositing the overlays --- libraries/render-utils/src/GeometryCache.cpp | 15 +++++++++++++++ libraries/render-utils/src/GeometryCache.h | 3 +++ 2 files changed, 18 insertions(+) diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 303d63bef8..3066fd4890 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -1179,6 +1179,21 @@ void GeometryCache::renderQuad(gpu::Batch& batch, const glm::vec2& minCorner, co batch.draw(gpu::QUADS, 4, 0); } +void GeometryCache::renderUnitQuad(const glm::vec4& color, int id) { + gpu::Batch batch; + renderUnitQuad(batch, color, id); + gpu::GLBackend::renderBatch(batch); +} + +void GeometryCache::renderUnitQuad(gpu::Batch& batch, const glm::vec4& color, int id) { + static const glm::vec2 topLeft(-1, 1); + static const glm::vec2 bottomRight(1, -1); + static const glm::vec2 texCoordTopLeft(0.0f, 1.0f); + static const glm::vec2 texCoordBottomRight(1.0f, 0.0f); + renderQuad(batch, topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight, color, id); +} + + void GeometryCache::renderQuad(const glm::vec2& minCorner, const glm::vec2& maxCorner, const glm::vec2& texCoordMinCorner, const glm::vec2& texCoordMaxCorner, const glm::vec4& color, int id) { diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index b438eb2d3b..76e03f8669 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -155,6 +155,9 @@ public: void renderBevelCornersRect(int x, int y, int width, int height, int bevelDistance, const glm::vec4& color, int id = UNKNOWN_ID); void renderBevelCornersRect(gpu::Batch& batch, int x, int y, int width, int height, int bevelDistance, const glm::vec4& color, int id = UNKNOWN_ID); + void renderUnitQuad(const glm::vec4& color = glm::vec4(1), int id = UNKNOWN_ID); + void renderUnitQuad(gpu::Batch& batch, const glm::vec4& color = glm::vec4(1), int id = UNKNOWN_ID); + void renderQuad(int x, int y, int width, int height, const glm::vec4& color, int id = UNKNOWN_ID) { renderQuad(glm::vec2(x,y), glm::vec2(x + width, y + height), color, id); } void renderQuad(gpu::Batch& batch, int x, int y, int width, int height, const glm::vec4& color, int id = UNKNOWN_ID) From 7374fb84e896a046361aee080f23459e25bd9921 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 10 Jun 2015 09:36:14 -0700 Subject: [PATCH 18/41] Working on fixing overlays with team-teaching merge --- interface/src/Application.cpp | 2 +- interface/src/devices/OculusManager.cpp | 29 ++- interface/src/devices/TV3DManager.cpp | 2 +- interface/src/ui/ApplicationOverlay.cpp | 313 ++++++++++++------------ interface/src/ui/ApplicationOverlay.h | 21 +- 5 files changed, 198 insertions(+), 169 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1763623fa6..9d5c43b7cf 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -958,7 +958,7 @@ void Application::paintGL() { GL_COLOR_BUFFER_BIT, GL_NEAREST); glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - _applicationOverlay.displayOverlayTexture(); + _applicationOverlay.displayOverlayTexture(&renderArgs); } if (!OculusManager::isConnected() || OculusManager::allowSwap()) { diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp index a7383ae4bb..77263935f6 100644 --- a/interface/src/devices/OculusManager.cpp +++ b/interface/src/devices/OculusManager.cpp @@ -615,12 +615,9 @@ void OculusManager::display(QGLWidget * glCanvas, RenderArgs* renderArgs, const renderArgs->_renderSide = RenderArgs::MONO; qApp->displaySide(renderArgs, *_camera, false); - qApp->getApplicationOverlay().displayOverlayTextureHmd(*_camera); }); _activeEye = ovrEye_Count; - glPopMatrix(); - gpu::FramebufferPointer finalFbo; //Bind the output texture from the glow shader. If glow effect is disabled, we just grab the texture if (Menu::getInstance()->isOptionChecked(MenuOption::EnableGlowEffect)) { @@ -632,9 +629,35 @@ void OculusManager::display(QGLWidget * glCanvas, RenderArgs* renderArgs, const glBindFramebuffer(GL_FRAMEBUFFER, 0); } + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gpu::GLBackend::getFramebufferID(finalFbo)); + //Render each eye into an fbo + for_each_eye(_ovrHmd, [&](ovrEyeType eye) { + _activeEye = eye; + // Update our camera to what the application camera is doing + _camera->setRotation(toGlm(eyeRenderPose[eye].Orientation)); + _camera->setPosition(toGlm(eyeRenderPose[eye].Position)); + configureCamera(*_camera); + glMatrixMode(GL_PROJECTION); + glLoadMatrixf(glm::value_ptr(_camera->getProjection())); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + ovrRecti & vp = _eyeTextures[eye].Header.RenderViewport; + vp.Size.h = _recommendedTexSize.h * _offscreenRenderScale; + vp.Size.w = _recommendedTexSize.w * _offscreenRenderScale; + + glViewport(vp.Pos.x, vp.Pos.y, vp.Size.w, vp.Size.h); + qApp->getApplicationOverlay().displayOverlayTextureHmd(renderArgs, *_camera); + }); + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + + glPopMatrix(); + glMatrixMode(GL_PROJECTION); glPopMatrix(); + // restore our normal viewport glViewport(0, 0, deviceSize.width(), deviceSize.height()); diff --git a/interface/src/devices/TV3DManager.cpp b/interface/src/devices/TV3DManager.cpp index 09edb03e5a..c14e589389 100644 --- a/interface/src/devices/TV3DManager.cpp +++ b/interface/src/devices/TV3DManager.cpp @@ -120,7 +120,7 @@ void TV3DManager::display(RenderArgs* renderArgs, Camera& whichCamera) { glLoadIdentity(); renderArgs->_renderSide = RenderArgs::MONO; qApp->displaySide(renderArgs, eyeCamera, false); - qApp->getApplicationOverlay().displayOverlayTextureStereo(whichCamera, _aspect, fov); + qApp->getApplicationOverlay().displayOverlayTextureStereo(renderArgs, whichCamera, _aspect, fov); _activeEye = NULL; }, [&]{ // render right side view diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index bc8047fc98..79ec39b506 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -14,13 +14,16 @@ #include #include +#include + #include +#include #include -#include #include #include -#include #include +#include +#include #include "AudioClient.h" #include "audio/AudioIOStatsRenderer.h" @@ -149,7 +152,8 @@ ApplicationOverlay::ApplicationOverlay() : _previousMagnifierBottomLeft(), _previousMagnifierBottomRight(), _previousMagnifierTopLeft(), - _previousMagnifierTopRight() + _previousMagnifierTopRight(), + _framebufferObject(nullptr) { memset(_reticleActive, 0, sizeof(_reticleActive)); memset(_magActive, 0, sizeof(_reticleActive)); @@ -196,16 +200,17 @@ void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) { //Handle fading and deactivation/activation of UI // Render 2D overlay - glMatrixMode(GL_PROJECTION); glDisable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - _overlays.buildFramebufferObject(); - _overlays.bind(); + buildFramebufferObject(); + + _framebufferObject->bind(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glViewport(0, 0, size.x, size.y); + glMatrixMode(GL_PROJECTION); glPushMatrix(); { const float NEAR_CLIP = -10000; const float FAR_CLIP = 10000; @@ -227,6 +232,22 @@ void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) { renderPointers(); renderDomainConnectionStatusBorder(); + if (_newUiTexture) { + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBindTexture(GL_TEXTURE_2D, _newUiTexture); + DependencyManager::get()->renderUnitQuad(); + glBindTexture(GL_TEXTURE_2D, 0); + glDisable(GL_TEXTURE_2D); + glDisable(GL_BLEND); + } + glLoadIdentity(); + glMatrixMode(GL_PROJECTION); } glPopMatrix(); @@ -236,166 +257,159 @@ void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) { glEnable(GL_LIGHTING); glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); - _overlays.release(); + _framebufferObject->release(); } // A quick and dirty solution for compositing the old overlay // texture with the new one -template -void with_each_texture(GLuint firstPassTexture, GLuint secondPassTexture, F f) { - glEnable(GL_TEXTURE_2D); - glActiveTexture(GL_TEXTURE0); - if (firstPassTexture) { - glBindTexture(GL_TEXTURE_2D, firstPassTexture); - f(); - } - if (secondPassTexture) { - glBindTexture(GL_TEXTURE_2D, secondPassTexture); - f(); - } - glBindTexture(GL_TEXTURE_2D, 0); - glDisable(GL_TEXTURE_2D); -} +//template +//void with_each_texture(GLuint firstPassTexture, GLuint secondPassTexture, F f) { +// glEnable(GL_TEXTURE_2D); +// glActiveTexture(GL_TEXTURE0); +// if (firstPassTexture) { +// glBindTexture(GL_TEXTURE_2D, firstPassTexture); +// f(); +// } +// //if (secondPassTexture) { +// // glBindTexture(GL_TEXTURE_2D, secondPassTexture); +// // f(); +// //} +// glBindTexture(GL_TEXTURE_2D, 0); +// glDisable(GL_TEXTURE_2D); +//} // Draws the FBO texture for the screen -void ApplicationOverlay::displayOverlayTexture() { +void ApplicationOverlay::displayOverlayTexture(RenderArgs* renderArgs) { + if (_alpha == 0.0f) { return; } + + if (!_crosshairTexture) { + _crosshairTexture = TextureCache::getImageTexture( + PathUtils::resourcesPath() + "images/sixense-reticle.png"); + } + + /* + FIXME - doesn't work + renderArgs->_context->syncCache(); + gpu::Batch batch; + DependencyManager::get()->bindSimpleProgram(batch, true); + batch.setModelTransform(Transform()); + batch.setProjectionTransform(mat4()); + batch.setViewTransform(Transform()); + batch.setUniformTexture(0, _crosshairTexture); + DependencyManager::get()->renderUnitQuad(batch, vec4(vec3(1), _alpha)); + renderArgs->_context->render(batch); + return; + */ + + + glDisable(GL_DEPTH_TEST); + glDisable(GL_LIGHTING); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_TEXTURE_2D); + glViewport(0, 0, qApp->getDeviceSize().width(), qApp->getDeviceSize().height()); + glMatrixMode(GL_PROJECTION); - glPushMatrix(); { - glLoadIdentity(); - glDisable(GL_DEPTH_TEST); - glDisable(GL_LIGHTING); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glViewport(0, 0, qApp->getDeviceSize().width(), qApp->getDeviceSize().height()); - - static const glm::vec2 topLeft(-1, 1); - static const glm::vec2 bottomRight(1, -1); - static const glm::vec2 texCoordTopLeft(0.0f, 1.0f); - static const glm::vec2 texCoordBottomRight(1.0f, 0.0f); - with_each_texture(_overlays.getTexture(), _newUiTexture, [&] { - DependencyManager::get()->renderQuad( - topLeft, bottomRight, - texCoordTopLeft, texCoordBottomRight, - glm::vec4(1.0f, 1.0f, 1.0f, _alpha)); - }); - - if (!_crosshairTexture) { - _crosshairTexture = DependencyManager::get()-> - getImageTexture(PathUtils::resourcesPath() + "images/sixense-reticle.png"); - } - + glPushMatrix(); + glLoadIdentity(); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + { + glBindTexture(GL_TEXTURE_2D, _framebufferObject->texture()); + DependencyManager::get()->renderUnitQuad(vec4(vec3(1), _alpha)); //draw the mouse pointer glm::vec2 canvasSize = qApp->getCanvasSize(); - glm::vec2 mouseSize = 32.0f / canvasSize; - auto mouseTopLeft = topLeft * mouseSize; - auto mouseBottomRight = bottomRight * mouseSize; + + // Get the mouse coordinates and convert to NDC [-1, 1] vec2 mousePosition = vec2(qApp->getMouseX(), qApp->getMouseY()); mousePosition /= canvasSize; mousePosition *= 2.0f; mousePosition -= 1.0f; mousePosition.y *= -1.0f; + mat4 mouseMv = glm::translate(mat4(), vec3(mousePosition, 0)); - glEnable(GL_TEXTURE_2D); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + // Scale the mouse based on the canvasSize (NOT the device size, + // we don't want a smaller mouse on retina displays) + glm::vec2 mouseSize = 32.0f / canvasSize; + mouseMv = glm::scale(mouseMv, vec3(mouseSize, 1.0f)); + + // Push the resulting matrix into modelview + glLoadMatrixf(glm::value_ptr(mouseMv)); glBindTexture(GL_TEXTURE_2D, gpu::GLBackend::getTextureID(_crosshairTexture)); glm::vec4 reticleColor = { RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2], 1.0f }; - DependencyManager::get()->renderQuad( - mouseTopLeft + mousePosition, mouseBottomRight + mousePosition, - texCoordTopLeft, texCoordBottomRight, - reticleColor); - glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); - glDisable(GL_TEXTURE_2D); - } glPopMatrix(); + DependencyManager::get()->renderUnitQuad(reticleColor); + } + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); } // Draws the FBO texture for Oculus rift. -void ApplicationOverlay::displayOverlayTextureHmd(Camera& whichCamera) { +void ApplicationOverlay::displayOverlayTextureHmd(RenderArgs* renderArgs, Camera& whichCamera) { if (_alpha == 0.0f) { return; } - glEnable(GL_BLEND); - glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); - glEnable(GL_DEPTH_TEST); - glDepthMask(GL_TRUE); + _overlays.buildVBO(_textureFov, _textureAspectRatio, 80, 80); + glDisable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); - glEnable(GL_ALPHA_TEST); - glAlphaFunc(GL_GREATER, 0.01f); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glEnable(GL_TEXTURE_2D); + glActiveTexture(GL_TEXTURE0); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + // The camera here contains only the head pose relative to the avatar position + vec3 pos = whichCamera.getPosition(); + quat rot = whichCamera.getOrientation(); + mat4 overlayXfm = glm::translate(glm::mat4(), pos) * glm::mat4_cast(rot); + glLoadMatrixf(glm::value_ptr(glm::inverse(overlayXfm))); + glBindTexture(GL_TEXTURE_2D, _framebufferObject->texture()); + _overlays.render(); //Update and draw the magnifiers + /* + // FIXME Mangifiers need to be re-thought MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - const glm::quat& orientation = myAvatar->getOrientation(); - // Always display the HMD overlay relative to the camera position but - // remove the HMD pose offset. This results in an overlay that sticks with you - // even in third person mode, but isn't drawn at a fixed distance. - glm::vec3 position = whichCamera.getPosition(); - position -= qApp->getCamera()->getHmdPosition(); const float scale = myAvatar->getScale() * _oculusUIRadius; - -// glm::vec3 eyeOffset = setEyeOffsetPosition; - glMatrixMode(GL_MODELVIEW); - glPushMatrix(); { - glTranslatef(position.x, position.y, position.z); - glm::mat4 rotation = glm::toMat4(orientation); - glMultMatrixf(&rotation[0][0]); - glScalef(scale, scale, scale); - for (int i = 0; i < NUMBER_OF_RETICLES; i++) { - - if (_magActive[i]) { - _magSizeMult[i] += MAG_SPEED; - if (_magSizeMult[i] > 1.0f) { - _magSizeMult[i] = 1.0f; - } - } else { - _magSizeMult[i] -= MAG_SPEED; - if (_magSizeMult[i] < 0.0f) { - _magSizeMult[i] = 0.0f; - } + overlayXfm = glm::scale(overlayXfm, vec3(scale)); + for (int i = 0; i < NUMBER_OF_RETICLES; i++) { + if (_magActive[i]) { + _magSizeMult[i] += MAG_SPEED; + if (_magSizeMult[i] > 1.0f) { + _magSizeMult[i] = 1.0f; } - - if (_magSizeMult[i] > 0.0f) { - //Render magnifier, but dont show border for mouse magnifier - glm::vec2 projection = screenToOverlay(glm::vec2(_reticlePosition[MOUSE].x(), - _reticlePosition[MOUSE].y())); - with_each_texture(_overlays.getTexture(), 0, [&] { - renderMagnifier(projection, _magSizeMult[i], i != MOUSE); - }); + } else { + _magSizeMult[i] -= MAG_SPEED; + if (_magSizeMult[i] < 0.0f) { + _magSizeMult[i] = 0.0f; } } - - glDepthMask(GL_FALSE); - glDisable(GL_ALPHA_TEST); - - static float textureFOV = 0.0f, textureAspectRatio = 1.0f; - if (textureFOV != _textureFov || - textureAspectRatio != _textureAspectRatio) { - textureFOV = _textureFov; - textureAspectRatio = _textureAspectRatio; - _overlays.buildVBO(_textureFov, _textureAspectRatio, 80, 80); + if (_magSizeMult[i] > 0.0f) { + //Render magnifier, but dont show border for mouse magnifier + glm::vec2 projection = screenToOverlay(glm::vec2(_reticlePosition[MOUSE].x(), + _reticlePosition[MOUSE].y())); + with_each_texture(_overlays.getTexture(), 0, [&] { + renderMagnifier(projection, _magSizeMult[i], i != MOUSE); + }); } + } + */ - with_each_texture(_overlays.getTexture(), _newUiTexture, [&] { - _overlays.render(); - }); - - if (!Application::getInstance()->isMouseHidden()) { - renderPointersOculus(); - } - glDepthMask(GL_TRUE); - glDisable(GL_TEXTURE_2D); - - glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); - glEnable(GL_LIGHTING); - } glPopMatrix(); + if (!Application::getInstance()->isMouseHidden()) { + renderPointersOculus(); + } } // Draws the FBO texture for 3DTV. -void ApplicationOverlay::displayOverlayTextureStereo(Camera& whichCamera, float aspectRatio, float fov) { +void ApplicationOverlay::displayOverlayTextureStereo(RenderArgs* renderArgs, Camera& whichCamera, float aspectRatio, float fov) { if (_alpha == 0.0f) { return; } @@ -440,15 +454,15 @@ void ApplicationOverlay::displayOverlayTextureStereo(Camera& whichCamera, float GLfloat y = -halfQuadHeight; glDisable(GL_DEPTH_TEST); - with_each_texture(_overlays.getTexture(), _newUiTexture, [&] { - DependencyManager::get()->renderQuad(glm::vec3(x, y + quadHeight, -distance), - glm::vec3(x + quadWidth, y + quadHeight, -distance), - glm::vec3(x + quadWidth, y, -distance), - glm::vec3(x, y, -distance), - glm::vec2(0.0f, 1.0f), glm::vec2(1.0f, 1.0f), - glm::vec2(1.0f, 0.0f), glm::vec2(0.0f, 0.0f), - overlayColor); - }); + //with_each_texture(_framebufferObject->texture(), _newUiTexture, [&] { + // DependencyManager::get()->renderQuad(glm::vec3(x, y + quadHeight, -distance), + // glm::vec3(x + quadWidth, y + quadHeight, -distance), + // glm::vec3(x + quadWidth, y, -distance), + // glm::vec3(x, y, -distance), + // glm::vec2(0.0f, 1.0f), glm::vec2(1.0f, 1.0f), + // glm::vec2(1.0f, 0.0f), glm::vec2(0.0f, 0.0f), + // overlayColor); + //}); if (!_crosshairTexture) { _crosshairTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + @@ -1071,29 +1085,24 @@ void ApplicationOverlay::renderDomainConnectionStatusBorder() { ApplicationOverlay::TexturedHemisphere::TexturedHemisphere() : _vertices(0), _indices(0), - _framebufferObject(NULL), _vbo(0, 0) { } ApplicationOverlay::TexturedHemisphere::~TexturedHemisphere() { cleanupVBO(); - if (_framebufferObject != NULL) { - delete _framebufferObject; - } -} - -void ApplicationOverlay::TexturedHemisphere::bind() { - _framebufferObject->bind(); -} - -void ApplicationOverlay::TexturedHemisphere::release() { - _framebufferObject->release(); } void ApplicationOverlay::TexturedHemisphere::buildVBO(const float fov, const float aspectRatio, const int slices, const int stacks) { + static float textureFOV = 0.0f, textureAspectRatio = 1.0f; + if (textureFOV == fov && textureAspectRatio == aspectRatio) { + return; + } + textureFOV = fov; + textureAspectRatio = aspectRatio; + if (fov >= PI) { qDebug() << "TexturedHemisphere::buildVBO(): FOV greater or equal than Pi will create issues"; } @@ -1176,7 +1185,11 @@ void ApplicationOverlay::TexturedHemisphere::cleanupVBO() { } } -void ApplicationOverlay::TexturedHemisphere::buildFramebufferObject() { +GLuint ApplicationOverlay::getOverlayTexture() { + return _framebufferObject->texture(); +} + +void ApplicationOverlay::buildFramebufferObject() { auto canvasSize = qApp->getCanvasSize(); QSize fboSize = QSize(canvasSize.x, canvasSize.y); if (_framebufferObject != NULL && fboSize == _framebufferObject->size()) { @@ -1189,7 +1202,7 @@ void ApplicationOverlay::TexturedHemisphere::buildFramebufferObject() { } _framebufferObject = new QOpenGLFramebufferObject(fboSize, QOpenGLFramebufferObject::Depth); - glBindTexture(GL_TEXTURE_2D, getTexture()); + glBindTexture(GL_TEXTURE_2D, getOverlayTexture()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); @@ -1201,7 +1214,7 @@ void ApplicationOverlay::TexturedHemisphere::buildFramebufferObject() { //Renders a hemisphere with texture coordinates. void ApplicationOverlay::TexturedHemisphere::render() { - if (_framebufferObject == NULL || _vbo.first == 0 || _vbo.second == 0) { + if (_vbo.first == 0 || _vbo.second == 0) { qDebug() << "TexturedHemisphere::render(): Incorrect initialisation"; return; } @@ -1227,10 +1240,6 @@ void ApplicationOverlay::TexturedHemisphere::render() { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } -GLuint ApplicationOverlay::TexturedHemisphere::getTexture() { - return _framebufferObject->texture(); -} - glm::vec2 ApplicationOverlay::directionToSpherical(const glm::vec3& direction) { glm::vec2 result; // Compute yaw diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index ee82c9274a..36161dd29d 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -33,9 +33,9 @@ public: ~ApplicationOverlay(); void renderOverlay(RenderArgs* renderArgs); - void displayOverlayTexture(); - void displayOverlayTextureStereo(Camera& whichCamera, float aspectRatio, float fov); - void displayOverlayTextureHmd(Camera& whichCamera); + void displayOverlayTexture(RenderArgs* renderArgs); + void displayOverlayTextureStereo(RenderArgs* renderArgs, Camera& whichCamera, float aspectRatio, float fov); + void displayOverlayTextureHmd(RenderArgs* renderArgs, Camera& whichCamera); QPoint getPalmClickLocation(const PalmData *palm) const; bool calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction, glm::vec3& result) const; @@ -59,6 +59,7 @@ public: glm::vec2 screenToOverlay(const glm::vec2 & screenPos) const; glm::vec2 overlayToScreen(const glm::vec2 & overlayPos) const; void computeHmdPickRay(glm::vec2 cursorPos, glm::vec3& origin, glm::vec3& direction) const; + GLuint getOverlayTexture(); static glm::vec2 directionToSpherical(const glm::vec3 & direction); static glm::vec3 sphericalToDirection(const glm::vec2 & sphericalPos); @@ -77,12 +78,6 @@ private: public: TexturedHemisphere(); ~TexturedHemisphere(); - - void bind(); - void release(); - GLuint getTexture(); - - void buildFramebufferObject(); void buildVBO(const float fov, const float aspectRatio, const int slices, const int stacks); void render(); @@ -91,14 +86,14 @@ private: GLuint _vertices; GLuint _indices; - QOpenGLFramebufferObject* _framebufferObject; VerticesIndices _vbo; }; float _hmdUIAngularSize = DEFAULT_HMD_UI_ANGULAR_SIZE; - + QOpenGLFramebufferObject* _framebufferObject; + void renderReticle(glm::quat orientation, float alpha); - void renderPointers();; + void renderPointers(); void renderMagnifier(glm::vec2 magPos, float sizeMult, bool showBorder); void renderControllerPointers(); @@ -109,6 +104,8 @@ private: void renderStatsAndLogs(); void renderDomainConnectionStatusBorder(); + void buildFramebufferObject(); + TexturedHemisphere _overlays; float _textureFov; From 3dcc6c9b8ce28722928c891feae1d21009842e21 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 10 Jun 2015 12:04:44 -0700 Subject: [PATCH 19/41] make action-factory interface and subclass it in Interface. this allows an action to use avatar data. the login the AvatarActionHold is still bogus. --- interface/src/Application.cpp | 5 +- interface/src/InterfaceActionFactory.cpp | 49 ++++++++ interface/src/InterfaceActionFactory.h | 28 +++++ interface/src/avatar/AvatarActionHold.cpp | 111 ++++++++++++++++++ interface/src/avatar/AvatarActionHold.h | 39 ++++++ .../src/EntityActionFactoryInterface.h | 33 ++++++ .../entities/src/EntityActionInterface.cpp | 2 + .../entities/src/EntityActionInterface.h | 14 ++- libraries/entities/src/EntityItem.h | 5 +- .../entities/src/EntityScriptingInterface.cpp | 5 +- libraries/entities/src/EntitySimulation.h | 5 +- libraries/physics/src/ObjectAction.cpp | 94 +++++++++++++-- libraries/physics/src/ObjectAction.h | 15 ++- .../physics/src/ObjectActionPullToPoint.cpp | 13 +- .../physics/src/ObjectActionPullToPoint.h | 3 +- libraries/physics/src/ObjectActionSpring.cpp | 13 +- libraries/physics/src/ObjectActionSpring.h | 3 +- .../physics/src/PhysicalEntitySimulation.cpp | 30 +---- .../physics/src/PhysicalEntitySimulation.h | 4 - 19 files changed, 413 insertions(+), 58 deletions(-) create mode 100644 interface/src/InterfaceActionFactory.cpp create mode 100644 interface/src/InterfaceActionFactory.h create mode 100644 interface/src/avatar/AvatarActionHold.cpp create mode 100644 interface/src/avatar/AvatarActionHold.h create mode 100644 libraries/entities/src/EntityActionFactoryInterface.h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 188ae6eaf9..0c90fdb9bd 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -100,6 +100,7 @@ #include "ModelPackager.h" #include "Util.h" #include "InterfaceLogging.h" +#include "InterfaceActionFactory.h" #include "avatar/AvatarManager.h" @@ -257,6 +258,7 @@ bool setupEssentials(int& argc, char** argv) { DependencyManager::registerInheritance(); DependencyManager::registerInheritance(); + DependencyManager::registerInheritance(); Setting::init(); @@ -293,7 +295,8 @@ bool setupEssentials(int& argc, char** argv) { auto discoverabilityManager = DependencyManager::set(); auto sceneScriptingInterface = DependencyManager::set(); auto offscreenUi = DependencyManager::set(); - auto pathUtils = DependencyManager::set(); + auto pathUtils = DependencyManager::set(); + auto actionFactory = DependencyManager::set(); return true; } diff --git a/interface/src/InterfaceActionFactory.cpp b/interface/src/InterfaceActionFactory.cpp new file mode 100644 index 0000000000..08afc3581c --- /dev/null +++ b/interface/src/InterfaceActionFactory.cpp @@ -0,0 +1,49 @@ +// +// InterfaceActionFactory.cpp +// libraries/entities/src +// +// Created by Seth Alves on 2015-6-2 +// 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 +// + + + +#include +#include +#include + +#include "InterfaceActionFactory.h" + + +EntityActionPointer InterfaceActionFactory::factory(EntitySimulation* simulation, + EntityActionType type, + QUuid id, + EntityItemPointer ownerEntity, + QVariantMap arguments) { + EntityActionPointer action = nullptr; + switch (type) { + case ACTION_TYPE_NONE: + return nullptr; + case ACTION_TYPE_PULL_TO_POINT: + action = (EntityActionPointer) new ObjectActionPullToPoint(id, ownerEntity); + break; + case ACTION_TYPE_SPRING: + action = (EntityActionPointer) new ObjectActionSpring(id, ownerEntity); + break; + case ACTION_TYPE_HOLD: + action = (EntityActionPointer) new AvatarActionHold(id, ownerEntity); + break; + } + + bool ok = action->updateArguments(arguments); + if (ok) { + ownerEntity->addAction(simulation, action); + return action; + } + + action = nullptr; + return action; +} diff --git a/interface/src/InterfaceActionFactory.h b/interface/src/InterfaceActionFactory.h new file mode 100644 index 0000000000..5848df4635 --- /dev/null +++ b/interface/src/InterfaceActionFactory.h @@ -0,0 +1,28 @@ +// +// InterfaceActionFactory.cpp +// interface/src/ +// +// Created by Seth Alves on 2015-6-10 +// 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 +// + +#ifndef hifi_InterfaceActionFactory_h +#define hifi_InterfaceActionFactory_h + +#include "EntityActionFactoryInterface.h" + +class InterfaceActionFactory : public EntityActionFactoryInterface { +public: + InterfaceActionFactory() : EntityActionFactoryInterface() { } + virtual ~InterfaceActionFactory() { } + virtual EntityActionPointer factory(EntitySimulation* simulation, + EntityActionType type, + QUuid id, + EntityItemPointer ownerEntity, + QVariantMap arguments); +}; + +#endif // hifi_InterfaceActionFactory_h diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp new file mode 100644 index 0000000000..6d4164367a --- /dev/null +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -0,0 +1,111 @@ +// +// AvatarActionHold.cpp +// interface/src/avatar/ +// +// Created by Seth Alves 2015-6-9 +// 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 +// + +#include "avatar/MyAvatar.h" +#include "avatar/AvatarManager.h" + +#include "AvatarActionHold.h" + +AvatarActionHold::AvatarActionHold(QUuid id, EntityItemPointer ownerEntity) : + ObjectAction(id, ownerEntity) { + #if WANT_DEBUG + qDebug() << "AvatarActionHold::AvatarActionHold"; + #endif +} + +AvatarActionHold::~AvatarActionHold() { + #if WANT_DEBUG + qDebug() << "AvatarActionHold::~AvatarActionHold"; + #endif +} + +void AvatarActionHold::updateActionWorker(float deltaTimeStep) { + // handle the linear part + if (_linearOffsetSet) { + } + + auto myAvatar = DependencyManager::get()->getMyAvatar(); + + glm::vec3 palmPosition = myAvatar->getRightPalmPosition(); + glm::vec3 position = getPosition(); + glm::vec3 positionalTarget = palmPosition + _linearOffset; + glm::vec3 offset = positionalTarget - position; + + float offsetLength = glm::length(offset); + float speed = offsetLength / _timeScale; + + if (offsetLength > IGNORE_POSITION_DELTA) { + glm::vec3 newVelocity = glm::normalize(offset) * speed; + setLinearVelocity(newVelocity); + } else { + setLinearVelocity(glm::vec3(0.0f)); + } + + + // handle rotation + if (_angularOffsetSet) { + glm::quat bodyRotation = getRotation(); + // if qZero and qOne are too close to each other, we can get NaN for angle. + auto alignmentDot = glm::dot(bodyRotation, _angularOffset); + const float almostOne = 0.99999; + if (glm::abs(alignmentDot) < almostOne) { + glm::quat target = _angularOffset; + if (alignmentDot < 0) { + target = -target; + } + glm::quat qZeroInverse = glm::inverse(bodyRotation); + glm::quat deltaQ = target * qZeroInverse; + glm::vec3 axis = glm::axis(deltaQ); + float angle = glm::angle(deltaQ); + if (isNaN(angle)) { + qDebug() << "AvatarActionHold::updateAction angle =" << angle + << "body-rotation =" << bodyRotation.x << bodyRotation.y << bodyRotation.z << bodyRotation.w + << "target-rotation =" + << target.x << target.y << target.z<< target.w; + } + assert(!isNaN(angle)); + glm::vec3 newAngularVelocity = (angle / _timeScale) * glm::normalize(axis); + setAngularVelocity(newAngularVelocity); + } else { + setAngularVelocity(glm::vec3(0.0f)); + } + } +} + + +bool AvatarActionHold::updateArguments(QVariantMap arguments) { + // targets are required, spring-constants are optional + bool ptOk = true; + glm::vec3 linearOffset = + EntityActionInterface::extractVec3Argument("spring action", arguments, "targetPosition", ptOk, false); + + bool rtOk = true; + glm::quat angularOffset = + EntityActionInterface::extractQuatArgument("spring action", arguments, "targetRotation", rtOk, false); + + lockForWrite(); + + _linearOffsetSet = _angularOffsetSet = false; + + if (ptOk) { + _linearOffset = linearOffset; + _linearOffsetSet = true; + } + + if (rtOk) { + _angularOffset = angularOffset; + _angularOffsetSet = true; + } + + _active = true; + unlock(); + return true; +} diff --git a/interface/src/avatar/AvatarActionHold.h b/interface/src/avatar/AvatarActionHold.h new file mode 100644 index 0000000000..19e6e8df55 --- /dev/null +++ b/interface/src/avatar/AvatarActionHold.h @@ -0,0 +1,39 @@ +// +// AvatarActionHold.h +// interface/src/avatar/ +// +// Created by Seth Alves 2015-6-9 +// 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 +// + +#ifndef hifi_AvatarActionHold_h +#define hifi_AvatarActionHold_h + +#include + +#include +#include + +class AvatarActionHold : public ObjectAction { +public: + AvatarActionHold(QUuid id, EntityItemPointer ownerEntity); + virtual ~AvatarActionHold(); + + virtual bool updateArguments(QVariantMap arguments); + virtual void updateActionWorker(float deltaTimeStep); + +private: + + glm::vec3 _linearOffset; + bool _linearOffsetSet; + + glm::quat _angularOffset; + bool _angularOffsetSet; + + float _timeScale = 0.01; +}; + +#endif // hifi_AvatarActionHold_h diff --git a/libraries/entities/src/EntityActionFactoryInterface.h b/libraries/entities/src/EntityActionFactoryInterface.h new file mode 100644 index 0000000000..2347eff525 --- /dev/null +++ b/libraries/entities/src/EntityActionFactoryInterface.h @@ -0,0 +1,33 @@ +// +// EntityActionFactoryInterface.cpp +// libraries/entities/src +// +// Created by Seth Alves on 2015-6-2 +// 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 +// + +#ifndef hifi_EntityActionFactoryInterface_h +#define hifi_EntityActionFactoryInterface_h + +#include + +#include "EntityActionInterface.h" + +class EntityActionFactoryInterface : public QObject, public Dependency { + Q_OBJECT + SINGLETON_DEPENDENCY + + public: + EntityActionFactoryInterface() { } + virtual ~EntityActionFactoryInterface() { } + virtual EntityActionPointer factory(EntitySimulation* simulation, + EntityActionType type, + QUuid id, + EntityItemPointer ownerEntity, + QVariantMap arguments) { assert(false); } +}; + +#endif // hifi_EntityActionFactoryInterface_h diff --git a/libraries/entities/src/EntityActionInterface.cpp b/libraries/entities/src/EntityActionInterface.cpp index 399c529aa2..09bb933488 100644 --- a/libraries/entities/src/EntityActionInterface.cpp +++ b/libraries/entities/src/EntityActionInterface.cpp @@ -38,6 +38,8 @@ QString EntityActionInterface::actionTypeToString(EntityActionType actionType) { return "pullToPoint"; case ACTION_TYPE_SPRING: return "spring"; + case ACTION_TYPE_HOLD: + return "hold"; } assert(false); return "none"; diff --git a/libraries/entities/src/EntityActionInterface.h b/libraries/entities/src/EntityActionInterface.h index a0a3db9b68..50f862c535 100644 --- a/libraries/entities/src/EntityActionInterface.h +++ b/libraries/entities/src/EntityActionInterface.h @@ -14,13 +14,16 @@ #include +#include "EntityItem.h" + class EntitySimulation; enum EntityActionType { // keep these synchronized with actionTypeFromString and actionTypeToString ACTION_TYPE_NONE, ACTION_TYPE_PULL_TO_POINT, - ACTION_TYPE_SPRING + ACTION_TYPE_SPRING, + ACTION_TYPE_HOLD }; @@ -40,6 +43,14 @@ public: static QString actionTypeToString(EntityActionType actionType); protected: + virtual glm::vec3 getPosition() = 0; + virtual void setPosition(glm::vec3 position) = 0; + virtual glm::quat getRotation() = 0; + virtual void setRotation(glm::quat rotation) = 0; + virtual glm::vec3 getLinearVelocity() = 0; + virtual void setLinearVelocity(glm::vec3 linearVelocity) = 0; + virtual glm::vec3 getAngularVelocity() = 0; + virtual void setAngularVelocity(glm::vec3 angularVelocity) = 0; static glm::vec3 extractVec3Argument (QString objectName, QVariantMap arguments, QString argumentName, bool& ok, bool required = true); @@ -49,6 +60,7 @@ protected: QString argumentName, bool& ok, bool required = true); }; + typedef std::shared_ptr EntityActionPointer; #endif // hifi_EntityActionInterface_h diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 77a6627853..7e56fb8ec4 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -28,13 +28,16 @@ #include "EntityItemID.h" #include "EntityItemProperties.h" #include "EntityItemPropertiesDefaults.h" -#include "EntityActionInterface.h" #include "EntityTypes.h" class EntitySimulation; class EntityTreeElement; class EntityTreeElementExtraEncodeData; +class EntityActionInterface; +typedef std::shared_ptr EntityActionPointer; + + namespace render { class Scene; class PendingChanges; diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 836e7f5225..6f6633ce0f 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -17,6 +17,8 @@ #include "ZoneEntityItem.h" #include "EntitiesLogging.h" #include "EntitySimulation.h" +#include "EntityActionInterface.h" +#include "EntityActionFactoryInterface.h" #include "EntityScriptingInterface.h" @@ -491,12 +493,13 @@ QUuid EntityScriptingInterface::addAction(const QString& actionTypeString, const QUuid& entityID, const QVariantMap& arguments) { QUuid actionID = QUuid::createUuid(); + auto actionFactory = DependencyManager::get(); bool success = actionWorker(entityID, [&](EntitySimulation* simulation, EntityItemPointer entity) { EntityActionType actionType = EntityActionInterface::actionTypeFromString(actionTypeString); if (actionType == ACTION_TYPE_NONE) { return false; } - if (simulation->actionFactory(actionType, actionID, entity, arguments)) { + if (actionFactory->factory(simulation, actionType, actionID, entity, arguments)) { return true; } return false; diff --git a/libraries/entities/src/EntitySimulation.h b/libraries/entities/src/EntitySimulation.h index 0c9b3efee6..7d244086e5 100644 --- a/libraries/entities/src/EntitySimulation.h +++ b/libraries/entities/src/EntitySimulation.h @@ -18,6 +18,7 @@ #include +#include "EntityActionInterface.h" #include "EntityItem.h" #include "EntityTree.h" @@ -56,10 +57,6 @@ public: friend class EntityTree; - virtual EntityActionPointer actionFactory(EntityActionType type, - QUuid id, - EntityItemPointer ownerEntity, - QVariantMap arguments) { return nullptr; } virtual void addAction(EntityActionPointer action) { _actionsToAdd += action; } virtual void removeAction(const QUuid actionID) { _actionsToRemove += actionID; } virtual void removeActions(QList actionIDsToRemove) { _actionsToRemove += actionIDsToRemove; } diff --git a/libraries/physics/src/ObjectAction.cpp b/libraries/physics/src/ObjectAction.cpp index 5f67db4355..8f01a90410 100644 --- a/libraries/physics/src/ObjectAction.cpp +++ b/libraries/physics/src/ObjectAction.cpp @@ -35,15 +35,9 @@ void ObjectAction::updateAction(btCollisionWorld* collisionWorld, btScalar delta // don't risk hanging the thread running the physics simulation return; } - void* physicsInfo = _ownerEntity->getPhysicsInfo(); - if (physicsInfo) { - ObjectMotionState* motionState = static_cast(physicsInfo); - btRigidBody* rigidBody = motionState->getRigidBody(); - if (rigidBody) { - updateActionWorker(collisionWorld, deltaTimeStep, motionState, rigidBody); - } - } + updateActionWorker(deltaTimeStep); + unlock(); } @@ -53,3 +47,87 @@ void ObjectAction::debugDraw(btIDebugDraw* debugDrawer) { void ObjectAction::removeFromSimulation(EntitySimulation* simulation) const { simulation->removeAction(_id); } + +btRigidBody* ObjectAction::getRigidBody() { + if (!_ownerEntity) { + return nullptr; + } + void* physicsInfo = _ownerEntity->getPhysicsInfo(); + if (!physicsInfo) { + return nullptr; + } + ObjectMotionState* motionState = static_cast(physicsInfo); + return motionState->getRigidBody(); +} + +glm::vec3 ObjectAction::getPosition() { + auto rigidBody = getRigidBody(); + if (!rigidBody) { + return glm::vec3(0.0f); + } + return bulletToGLM(rigidBody->getCenterOfMassPosition()); +} + +void ObjectAction::setPosition(glm::vec3 position) { + auto rigidBody = getRigidBody(); + if (!rigidBody) { + return; + } + // XXX + // void setWorldTransform (const btTransform &worldTrans) + assert(false); + rigidBody->activate(); +} + +glm::quat ObjectAction::getRotation() { + auto rigidBody = getRigidBody(); + if (!rigidBody) { + return glm::quat(0.0f, 0.0f, 0.0f, 1.0f); + } + return bulletToGLM(rigidBody->getOrientation()); +} + +void ObjectAction::setRotation(glm::quat rotation) { + auto rigidBody = getRigidBody(); + if (!rigidBody) { + return; + } + // XXX + // void setWorldTransform (const btTransform &worldTrans) + assert(false); + rigidBody->activate(); +} + +glm::vec3 ObjectAction::getLinearVelocity() { + auto rigidBody = getRigidBody(); + if (!rigidBody) { + return glm::vec3(0.0f); + } + return bulletToGLM(rigidBody->getLinearVelocity()); +} + +void ObjectAction::setLinearVelocity(glm::vec3 linearVelocity) { + auto rigidBody = getRigidBody(); + if (!rigidBody) { + return; + } + rigidBody->setLinearVelocity(glmToBullet(glm::vec3(0.0f))); + rigidBody->activate(); +} + +glm::vec3 ObjectAction::getAngularVelocity() { + auto rigidBody = getRigidBody(); + if (!rigidBody) { + return glm::vec3(0.0f); + } + return bulletToGLM(rigidBody->getAngularVelocity()); +} + +void ObjectAction::setAngularVelocity(glm::vec3 angularVelocity) { + auto rigidBody = getRigidBody(); + if (!rigidBody) { + return; + } + rigidBody->setAngularVelocity(glmToBullet(angularVelocity)); + rigidBody->activate(); +} diff --git a/libraries/physics/src/ObjectAction.h b/libraries/physics/src/ObjectAction.h index 7ff11b8ba0..a834a54864 100644 --- a/libraries/physics/src/ObjectAction.h +++ b/libraries/physics/src/ObjectAction.h @@ -18,8 +18,10 @@ #include #include + #include "ObjectMotionState.h" #include "BulletUtil.h" +#include "EntityActionInterface.h" class ObjectAction : public btActionInterface, public EntityActionInterface { @@ -34,8 +36,7 @@ public: virtual bool updateArguments(QVariantMap arguments) { return false; } // this is called from updateAction and should be overridden by subclasses - virtual void updateActionWorker(btCollisionWorld* collisionWorld, btScalar deltaTimeStep, - ObjectMotionState* motionState, btRigidBody* rigidBody) {} + virtual void updateActionWorker(float deltaTimeStep) {} // these are from btActionInterface virtual void updateAction(btCollisionWorld* collisionWorld, btScalar deltaTimeStep); @@ -46,6 +47,16 @@ private: QReadWriteLock _lock; protected: + btRigidBody* getRigidBody(); + virtual glm::vec3 getPosition(); + virtual void setPosition(glm::vec3 position); + virtual glm::quat getRotation(); + virtual void setRotation(glm::quat rotation); + virtual glm::vec3 getLinearVelocity(); + virtual void setLinearVelocity(glm::vec3 linearVelocity); + virtual glm::vec3 getAngularVelocity(); + virtual void setAngularVelocity(glm::vec3 angularVelocity); + bool tryLockForRead() { return _lock.tryLockForRead(); } void lockForWrite() { _lock.lockForWrite(); } void unlock() { _lock.unlock(); } diff --git a/libraries/physics/src/ObjectActionPullToPoint.cpp b/libraries/physics/src/ObjectActionPullToPoint.cpp index 2e66b948f5..1069a2b9cf 100644 --- a/libraries/physics/src/ObjectActionPullToPoint.cpp +++ b/libraries/physics/src/ObjectActionPullToPoint.cpp @@ -24,8 +24,17 @@ ObjectActionPullToPoint::~ObjectActionPullToPoint() { #endif } -void ObjectActionPullToPoint::updateActionWorker(btCollisionWorld* collisionWorld, btScalar deltaTimeStep, - ObjectMotionState* motionState, btRigidBody* rigidBody) { +void ObjectActionPullToPoint::updateActionWorker(btScalar deltaTimeStep) { + void* physicsInfo = _ownerEntity->getPhysicsInfo(); + if (!physicsInfo) { + return; + } + ObjectMotionState* motionState = static_cast(physicsInfo); + btRigidBody* rigidBody = motionState->getRigidBody(); + if (!rigidBody) { + return; + } + glm::vec3 offset = _target - bulletToGLM(rigidBody->getCenterOfMassPosition()); float offsetLength = glm::length(offset); if (offsetLength > IGNORE_POSITION_DELTA) { diff --git a/libraries/physics/src/ObjectActionPullToPoint.h b/libraries/physics/src/ObjectActionPullToPoint.h index 55fd748921..2596f13515 100644 --- a/libraries/physics/src/ObjectActionPullToPoint.h +++ b/libraries/physics/src/ObjectActionPullToPoint.h @@ -23,8 +23,7 @@ public: virtual ~ObjectActionPullToPoint(); virtual bool updateArguments(QVariantMap arguments); - virtual void updateActionWorker(btCollisionWorld* collisionWorld, btScalar deltaTimeStep, - ObjectMotionState* motionState, btRigidBody* rigidBody); + virtual void updateActionWorker(float deltaTimeStep); private: diff --git a/libraries/physics/src/ObjectActionSpring.cpp b/libraries/physics/src/ObjectActionSpring.cpp index 101af665f7..aea4dafbb8 100644 --- a/libraries/physics/src/ObjectActionSpring.cpp +++ b/libraries/physics/src/ObjectActionSpring.cpp @@ -24,8 +24,17 @@ ObjectActionSpring::~ObjectActionSpring() { #endif } -void ObjectActionSpring::updateActionWorker(btCollisionWorld* collisionWorld, btScalar deltaTimeStep, - ObjectMotionState* motionState, btRigidBody* rigidBody) { +void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) { + void* physicsInfo = _ownerEntity->getPhysicsInfo(); + if (!physicsInfo) { + return; + } + ObjectMotionState* motionState = static_cast(physicsInfo); + btRigidBody* rigidBody = motionState->getRigidBody(); + if (!rigidBody) { + return; + } + // handle the linear part if (_positionalTargetSet) { glm::vec3 offset = _positionalTarget - bulletToGLM(rigidBody->getCenterOfMassPosition()); diff --git a/libraries/physics/src/ObjectActionSpring.h b/libraries/physics/src/ObjectActionSpring.h index 0ce06ab43e..c5cbbe6126 100644 --- a/libraries/physics/src/ObjectActionSpring.h +++ b/libraries/physics/src/ObjectActionSpring.h @@ -23,8 +23,7 @@ public: virtual ~ObjectActionSpring(); virtual bool updateArguments(QVariantMap arguments); - virtual void updateActionWorker(btCollisionWorld* collisionWorld, btScalar deltaTimeStep, - ObjectMotionState* motionState, btRigidBody* rigidBody); + virtual void updateActionWorker(float deltaTimeStep); private: diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index a4d2113be6..c68b993fe2 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -9,11 +9,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // + + #include "PhysicsHelpers.h" #include "PhysicsLogging.h" #include "ShapeManager.h" -#include "ObjectActionPullToPoint.h" -#include "ObjectActionSpring.h" #include "PhysicalEntitySimulation.h" @@ -235,32 +235,6 @@ void PhysicalEntitySimulation::handleCollisionEvents(CollisionEvents& collisionE } } -EntityActionPointer PhysicalEntitySimulation::actionFactory(EntityActionType type, - QUuid id, - EntityItemPointer ownerEntity, - QVariantMap arguments) { - EntityActionPointer action = nullptr; - switch (type) { - case ACTION_TYPE_NONE: - return nullptr; - case ACTION_TYPE_PULL_TO_POINT: - action = (EntityActionPointer) new ObjectActionPullToPoint(id, ownerEntity); - break; - case ACTION_TYPE_SPRING: - action = (EntityActionPointer) new ObjectActionSpring(id, ownerEntity); - break; - } - - bool ok = action->updateArguments(arguments); - if (ok) { - ownerEntity->addAction(this, action); - return action; - } - - action = nullptr; - return action; -} - void PhysicalEntitySimulation::applyActionChanges() { if (_physicsEngine) { foreach (EntityActionPointer actionToAdd, _actionsToAdd) { diff --git a/libraries/physics/src/PhysicalEntitySimulation.h b/libraries/physics/src/PhysicalEntitySimulation.h index 82b0f8ad51..81ab9f5cce 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.h +++ b/libraries/physics/src/PhysicalEntitySimulation.h @@ -32,10 +32,6 @@ public: void init(EntityTree* tree, PhysicsEngine* engine, EntityEditPacketSender* packetSender); - virtual EntityActionPointer actionFactory(EntityActionType type, - QUuid id, - EntityItemPointer ownerEntity, - QVariantMap arguments); virtual void applyActionChanges(); protected: // only called by EntitySimulation From ca1af777637bfaa2d3298822ed1da08acb7f23e0 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 10 Jun 2015 13:24:47 -0700 Subject: [PATCH 20/41] Working on cursor manager --- interface/resources/images/reticleLink.png | Bin 0 -> 6381 bytes interface/resources/images/reticleWhite.png | Bin 0 -> 4749 bytes interface/src/Application.cpp | 39 +++++++-- interface/src/ui/ApplicationOverlay.cpp | 42 ++++----- interface/src/ui/ApplicationOverlay.h | 4 +- libraries/ui/src/CursorManager.cpp | 90 ++++++++++++++++---- libraries/ui/src/CursorManager.h | 35 +++++++- 7 files changed, 164 insertions(+), 46 deletions(-) create mode 100644 interface/resources/images/reticleLink.png create mode 100644 interface/resources/images/reticleWhite.png diff --git a/interface/resources/images/reticleLink.png b/interface/resources/images/reticleLink.png new file mode 100644 index 0000000000000000000000000000000000000000..b36ee79d00b261d2c7d422df39302a46eb19daa4 GIT binary patch literal 6381 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 T{C0ae007unI$FFq>7V#N&}Y{u literal 0 HcmV?d00001 diff --git a/interface/resources/images/reticleWhite.png b/interface/resources/images/reticleWhite.png new file mode 100644 index 0000000000000000000000000000000000000000..4c35a186b2e7aa8939937e909586726ef3a10d09 GIT binary patch literal 4749 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 Har^%O!X>IH literal 0 HcmV?d00001 diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1763623fa6..5f17fd0510 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -57,6 +57,7 @@ #include #include +#include #include #include #include @@ -1229,9 +1230,20 @@ void Application::keyPressEvent(QKeyEvent* event) { } break; - case Qt::Key_Apostrophe: - resetSensors(); + case Qt::Key_Apostrophe: { + if (isMeta) { + auto cursor = Cursor::Manager::instance().getCursor(); + auto curIcon = cursor->getIcon(); + if (curIcon == Cursor::Icon::DEFAULT) { + cursor->setIcon(Cursor::Icon::LINK); + } else { + cursor->setIcon(Cursor::Icon::DEFAULT); + } + } else { + resetSensors(); + } break; + } case Qt::Key_A: if (isShifted) { @@ -1355,12 +1367,27 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_Slash: Menu::getInstance()->triggerOption(MenuOption::Stats); break; - case Qt::Key_Plus: - _myAvatar->increaseSize(); + + case Qt::Key_Plus: { + if (isMeta && event->modifiers().testFlag(Qt::KeypadModifier)) { + auto& cursorManager = Cursor::Manager::instance(); + cursorManager.setScale(cursorManager.getScale() * 1.1f); + } else { + _myAvatar->increaseSize(); + } break; - case Qt::Key_Minus: - _myAvatar->decreaseSize(); + } + + case Qt::Key_Minus: { + if (isMeta && event->modifiers().testFlag(Qt::KeypadModifier)) { + auto& cursorManager = Cursor::Manager::instance(); + cursorManager.setScale(cursorManager.getScale() / 1.1f); + } else { + _myAvatar->decreaseSize(); + } break; + } + case Qt::Key_Equal: _myAvatar->resetSize(); break; diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index bc8047fc98..7895232f8a 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include "AudioClient.h" #include "audio/AudioIOStatsRenderer.h" @@ -143,7 +144,6 @@ ApplicationOverlay::ApplicationOverlay() : _alpha(1.0f), _oculusUIRadius(1.0f), _trailingAudioLoudness(0.0f), - _crosshairTexture(0), _previousBorderWidth(-1), _previousBorderHeight(-1), _previousMagnifierBottomLeft(), @@ -257,6 +257,18 @@ void with_each_texture(GLuint firstPassTexture, GLuint secondPassTexture, F f) { glDisable(GL_TEXTURE_2D); } +void ApplicationOverlay::bindCursorTexture(uint8_t cursorIndex) { + auto& cursorManager = Cursor::Manager::instance(); + auto cursor = cursorManager.getCursor(cursorIndex); + auto iconId = cursor->getIcon(); + if (!_cursors.count(iconId)) { + auto iconPath = cursorManager.getIconImage(cursor->getIcon()); + _cursors[iconId] = DependencyManager::get()-> + getImageTexture(iconPath); + } + glBindTexture(GL_TEXTURE_2D, gpu::GLBackend::getTextureID(_cursors[iconId])); +} + // Draws the FBO texture for the screen void ApplicationOverlay::displayOverlayTexture() { if (_alpha == 0.0f) { @@ -282,14 +294,10 @@ void ApplicationOverlay::displayOverlayTexture() { glm::vec4(1.0f, 1.0f, 1.0f, _alpha)); }); - if (!_crosshairTexture) { - _crosshairTexture = DependencyManager::get()-> - getImageTexture(PathUtils::resourcesPath() + "images/sixense-reticle.png"); - } - + //draw the mouse pointer glm::vec2 canvasSize = qApp->getCanvasSize(); - glm::vec2 mouseSize = 32.0f / canvasSize; + glm::vec2 mouseSize = 32.0f / canvasSize * Cursor::Manager::instance().getScale(); auto mouseTopLeft = topLeft * mouseSize; auto mouseBottomRight = bottomRight * mouseSize; vec2 mousePosition = vec2(qApp->getMouseX(), qApp->getMouseY()); @@ -300,7 +308,7 @@ void ApplicationOverlay::displayOverlayTexture() { glEnable(GL_TEXTURE_2D); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glBindTexture(GL_TEXTURE_2D, gpu::GLBackend::getTextureID(_crosshairTexture)); + bindCursorTexture(); glm::vec4 reticleColor = { RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2], 1.0f }; DependencyManager::get()->renderQuad( mouseTopLeft + mousePosition, mouseBottomRight + mousePosition, @@ -450,18 +458,14 @@ void ApplicationOverlay::displayOverlayTextureStereo(Camera& whichCamera, float overlayColor); }); - if (!_crosshairTexture) { - _crosshairTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + - "images/sixense-reticle.png"); - } - //draw the mouse pointer glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, gpu::GLBackend::getTextureID(_crosshairTexture)); + bindCursorTexture(); glm::vec2 canvasSize = qApp->getCanvasSize(); - const float reticleSize = 40.0f / canvasSize.x * quadWidth; + const float reticleSize = 40.0f / canvasSize.x * quadWidth * + Cursor::Manager::instance().getScale(); x -= reticleSize / 2.0f; y += reticleSize / 2.0f; const float mouseX = (qApp->getMouseX() / (float)canvasSize.x) * quadWidth; @@ -583,16 +587,12 @@ bool ApplicationOverlay::calculateRayUICollisionPoint(const glm::vec3& position, //Renders optional pointers void ApplicationOverlay::renderPointers() { - //lazily load crosshair texture - if (_crosshairTexture == 0) { - _crosshairTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/sixense-reticle.png"); - } glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, gpu::GLBackend::getTextureID(_crosshairTexture)); + bindCursorTexture(); if (qApp->isHMDMode() && !qApp->getLastMouseMoveWasSimulated() && !qApp->isMouseHidden()) { //If we are in oculus, render reticle later @@ -757,7 +757,7 @@ void ApplicationOverlay::renderPointersOculus() { glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, gpu::GLBackend::getTextureID(_crosshairTexture)); + bindCursorTexture(); glDisable(GL_DEPTH_TEST); glMatrixMode(GL_MODELVIEW); diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index ee82c9274a..0e8206547a 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -108,6 +108,7 @@ private: void renderCameraToggle(); void renderStatsAndLogs(); void renderDomainConnectionStatusBorder(); + void bindCursorTexture(uint8_t cursorId = 0); TexturedHemisphere _overlays; @@ -127,7 +128,8 @@ private: float _trailingAudioLoudness; - gpu::TexturePointer _crosshairTexture; + QMap _cursors; + GLuint _newUiTexture{ 0 }; int _reticleQuad; diff --git a/libraries/ui/src/CursorManager.cpp b/libraries/ui/src/CursorManager.cpp index 8b318d549a..efadd09142 100644 --- a/libraries/ui/src/CursorManager.cpp +++ b/libraries/ui/src/CursorManager.cpp @@ -8,25 +8,85 @@ #include "CursorManager.h" +#include +#include +#include + +#include + namespace Cursor { - enum class Source { - MOUSE, - LEFT_HAND, - RIGHT_HAND, - UNKNOWN, + + void Instance::setIcon(uint16_t icon) { + _icon = icon; + } + + uint16_t Instance::getIcon() const { + return _icon; + } + + + class MouseInstance : public Instance { + Source getType() const { + return Source::MOUSE; + } + + ivec2 getScreenPosition() const { + return toGlm(QCursor::pos()); + } + + ivec2 getWindowPosition(QWidget* widget) const { + return toGlm(widget->mapFromGlobal(QCursor::pos())); + } + + vec2 getRelativePosition(QWidget* widget) const { + vec2 pos = getWindowPosition(widget); + pos /= vec2(toGlm(widget->size())); + return pos; + } }; - class Instance { - Source type; - }; + static QMap ICONS; + static uint16_t _customIconId = Icon::USER_BASE; - class Manager { - public: - static Manager& instance(); + Manager::Manager() { + ICONS[Icon::DEFAULT] = PathUtils::resourcesPath() + "images/sixense-reticle.png"; + ICONS[Icon::LINK] = PathUtils::resourcesPath() + "images/reticleLink.png"; + } - uint8_t getCount(); - Instance - }; -} + Manager& Manager::instance() { + static Manager instance; + return instance; + } + uint8_t Manager::getCount() { + return 1; + } + Instance* Manager::getCursor(uint8_t index) { + Q_ASSERT(index < getCount()); + static MouseInstance mouseInstance; + if (index == 0) { + return &mouseInstance; + } + return nullptr; + } + + uint16_t Manager::registerIcon(const QString& path) { + ICONS[_customIconId] = path; + return _customIconId++; + } + + const QString& Manager::getIconImage(uint16_t icon) { + Q_ASSERT(ICONS.count(icon)); + return ICONS[icon]; + } + + float Manager::getScale() { + return _scale; + } + + void Manager::setScale(float scale) { + _scale = scale; + } + +} \ No newline at end of file diff --git a/libraries/ui/src/CursorManager.h b/libraries/ui/src/CursorManager.h index a2d26efc58..c5810caf58 100644 --- a/libraries/ui/src/CursorManager.h +++ b/libraries/ui/src/CursorManager.h @@ -7,6 +7,9 @@ // #pragma once +#include + +#include namespace Cursor { enum class Source { @@ -16,16 +19,42 @@ namespace Cursor { UNKNOWN, }; + enum Icon { + DEFAULT, + LINK, + GRAB, + + // Add new system cursors here + + // User cursors will have ids over this value + USER_BASE = 0xFF, + }; + class Instance { - Source type; + public: + virtual Source getType() const = 0; + virtual ivec2 getWindowPosition(QWidget* widget) const = 0; + virtual vec2 getRelativePosition(QWidget* widget) const = 0; + virtual ivec2 getScreenPosition() const = 0; + virtual void setIcon(uint16_t icon); + virtual uint16_t getIcon() const; + private: + uint16_t _icon; }; class Manager { + Manager(); + Manager(const Manager& other) = delete; public: static Manager& instance(); - uint8_t getCount(); - Instance + float getScale(); + void setScale(float scale); + Instance* getCursor(uint8_t index = 0); + uint16_t registerIcon(const QString& path); + const QString& getIconImage(uint16_t icon); + private: + float _scale{ 1.0f }; }; } From 1f62fb4b6fbe33f090dc072dd3b0e15efc5b6d2d Mon Sep 17 00:00:00 2001 From: Sam Gateau Date: Wed, 10 Jun 2015 15:24:29 -0700 Subject: [PATCH 21/41] Adding standard vertex and pixel shaders for drawing texture in applicationOverlay --- interface/src/ui/ApplicationOverlay.cpp | 30 ++++++++++++++--- interface/src/ui/ApplicationOverlay.h | 4 +++ .../render-utils/src/standardDrawTexture.slf | 24 ++++++++++++++ .../src/standardTransformPNTC.slv | 33 +++++++++++++++++++ 4 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 libraries/render-utils/src/standardDrawTexture.slf create mode 100644 libraries/render-utils/src/standardTransformPNTC.slv diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 79ec39b506..f57c10b64e 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -36,6 +36,9 @@ #include "Util.h" #include "ui/Stats.h" +#include "../../libraries/render-utils/standardTransformPNTC_vert.h" +#include "../../libraries/render-utils/standardDrawTexture_frag.h" + // Used to animate the magnification windows const float MAG_SPEED = 0.08f; @@ -278,6 +281,24 @@ void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) { // glDisable(GL_TEXTURE_2D); //} +gpu::PipelinePointer ApplicationOverlay::getDrawPipeline() { + if (!_standardDrawPipeline) { + auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(standardTransformPNTC_vert))); + auto ps = gpu::ShaderPointer(gpu::Shader::createPixel(std::string(standardDrawTexture_frag))); + auto program = gpu::ShaderPointer(gpu::Shader::createProgram(vs, ps)); + gpu::Shader::makeProgram((*program)); + + auto state = gpu::StatePointer(new gpu::State()); + + // enable decal blend + state->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); + + _standardDrawPipeline.reset(gpu::Pipeline::create(program, state)); + } + + return _standardDrawPipeline; +} + // Draws the FBO texture for the screen void ApplicationOverlay::displayOverlayTexture(RenderArgs* renderArgs) { @@ -290,11 +311,12 @@ void ApplicationOverlay::displayOverlayTexture(RenderArgs* renderArgs) { PathUtils::resourcesPath() + "images/sixense-reticle.png"); } - /* - FIXME - doesn't work + + //FIXME - doesn't work renderArgs->_context->syncCache(); gpu::Batch batch; - DependencyManager::get()->bindSimpleProgram(batch, true); + //DependencyManager::get()->bindSimpleProgram(batch, true); + batch.setPipeline(getDrawPipeline()); batch.setModelTransform(Transform()); batch.setProjectionTransform(mat4()); batch.setViewTransform(Transform()); @@ -302,7 +324,7 @@ void ApplicationOverlay::displayOverlayTexture(RenderArgs* renderArgs) { DependencyManager::get()->renderUnitQuad(batch, vec4(vec3(1), _alpha)); renderArgs->_context->render(batch); return; - */ + glDisable(GL_DEPTH_TEST); diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index 36161dd29d..63ef48bd92 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -143,6 +143,10 @@ private: glm::vec3 _previousMagnifierTopLeft; glm::vec3 _previousMagnifierTopRight; + gpu::PipelinePointer _standardDrawPipeline; + + gpu::PipelinePointer getDrawPipeline(); + }; #endif // hifi_ApplicationOverlay_h diff --git a/libraries/render-utils/src/standardDrawTexture.slf b/libraries/render-utils/src/standardDrawTexture.slf new file mode 100644 index 0000000000..4fbeb6eb7f --- /dev/null +++ b/libraries/render-utils/src/standardDrawTexture.slf @@ -0,0 +1,24 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// standardDrawTexture.frag +// fragment shader +// +// Created by Sam Gateau on 6/10/15. +// 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 +// + +// the texture +uniform sampler2D colorMap; + +varying vec2 varTexcoord; +varying vec4 varColor; + + +void main(void) { + vec4 color = texture2D(colorMap, varTexcoord); + gl_FragColor = color * varColor; +} diff --git a/libraries/render-utils/src/standardTransformPNTC.slv b/libraries/render-utils/src/standardTransformPNTC.slv new file mode 100644 index 0000000000..fd2c28049f --- /dev/null +++ b/libraries/render-utils/src/standardTransformPNTC.slv @@ -0,0 +1,33 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// standardTransformPNTC.slv +// vertex shader +// +// Created by Sam Gateau on 6/10/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 +// + +<@include gpu/Transform.slh@> + +<$declareStandardTransform()$> + +varying vec3 varNormal; +varying vec2 varTexcoord; +varying vec4 varColor; + +void main(void) { + varTexcoord = gl_MultiTexCoord0.xy; + varColor = gl_Color; + + // standard transform + TransformCamera cam = getTransformCamera(); + TransformObject obj = getTransformObject(); + <$transformModelToClipPos(cam, obj, gl_Vertex, gl_Position)$> + <$transformModelToEyeDir(cam, obj, gl_Normal, varNormal)$> + varNormal = normalize(varNormal); +} \ No newline at end of file From eccf4eb8a8eb8ab048d205a18f46429f8f2545da Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 10 Jun 2015 17:05:49 -0700 Subject: [PATCH 22/41] hold action works --- interface/src/avatar/AvatarActionHold.cpp | 100 +++++++----------- interface/src/avatar/AvatarActionHold.h | 14 +-- .../entities/src/EntityActionInterface.cpp | 3 + libraries/physics/src/ObjectAction.cpp | 7 -- libraries/physics/src/ObjectAction.h | 2 +- .../physics/src/ObjectActionPullToPoint.cpp | 9 ++ libraries/physics/src/ObjectActionSpring.cpp | 12 +++ libraries/physics/src/ObjectActionSpring.h | 2 +- 8 files changed, 67 insertions(+), 82 deletions(-) diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index 6d4164367a..74a583df58 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -15,7 +15,7 @@ #include "AvatarActionHold.h" AvatarActionHold::AvatarActionHold(QUuid id, EntityItemPointer ownerEntity) : - ObjectAction(id, ownerEntity) { + ObjectActionSpring(id, ownerEntity) { #if WANT_DEBUG qDebug() << "AvatarActionHold::AvatarActionHold"; #endif @@ -28,83 +28,57 @@ AvatarActionHold::~AvatarActionHold() { } void AvatarActionHold::updateActionWorker(float deltaTimeStep) { - // handle the linear part - if (_linearOffsetSet) { - } - auto myAvatar = DependencyManager::get()->getMyAvatar(); - glm::vec3 palmPosition = myAvatar->getRightPalmPosition(); - glm::vec3 position = getPosition(); - glm::vec3 positionalTarget = palmPosition + _linearOffset; - glm::vec3 offset = positionalTarget - position; - float offsetLength = glm::length(offset); - float speed = offsetLength / _timeScale; + auto rotation = myAvatar->getWorldAlignedOrientation(); + auto offset = rotation * _relativePosition; + auto position = palmPosition + offset; + rotation *= _relativeRotation; - if (offsetLength > IGNORE_POSITION_DELTA) { - glm::vec3 newVelocity = glm::normalize(offset) * speed; - setLinearVelocity(newVelocity); - } else { - setLinearVelocity(glm::vec3(0.0f)); - } + lockForWrite(); + _positionalTarget = position; + _rotationalTarget = rotation; + unlock(); - - // handle rotation - if (_angularOffsetSet) { - glm::quat bodyRotation = getRotation(); - // if qZero and qOne are too close to each other, we can get NaN for angle. - auto alignmentDot = glm::dot(bodyRotation, _angularOffset); - const float almostOne = 0.99999; - if (glm::abs(alignmentDot) < almostOne) { - glm::quat target = _angularOffset; - if (alignmentDot < 0) { - target = -target; - } - glm::quat qZeroInverse = glm::inverse(bodyRotation); - glm::quat deltaQ = target * qZeroInverse; - glm::vec3 axis = glm::axis(deltaQ); - float angle = glm::angle(deltaQ); - if (isNaN(angle)) { - qDebug() << "AvatarActionHold::updateAction angle =" << angle - << "body-rotation =" << bodyRotation.x << bodyRotation.y << bodyRotation.z << bodyRotation.w - << "target-rotation =" - << target.x << target.y << target.z<< target.w; - } - assert(!isNaN(angle)); - glm::vec3 newAngularVelocity = (angle / _timeScale) * glm::normalize(axis); - setAngularVelocity(newAngularVelocity); - } else { - setAngularVelocity(glm::vec3(0.0f)); - } - } + ObjectActionSpring::updateActionWorker(deltaTimeStep); } bool AvatarActionHold::updateArguments(QVariantMap arguments) { - // targets are required, spring-constants are optional - bool ptOk = true; - glm::vec3 linearOffset = - EntityActionInterface::extractVec3Argument("spring action", arguments, "targetPosition", ptOk, false); - - bool rtOk = true; - glm::quat angularOffset = - EntityActionInterface::extractQuatArgument("spring action", arguments, "targetRotation", rtOk, false); + bool rPOk = true; + glm::vec3 relativePosition = + EntityActionInterface::extractVec3Argument("hold", arguments, "relativePosition", rPOk, false); + bool rROk = true; + glm::quat relativeRotation = + EntityActionInterface::extractQuatArgument("hold", arguments, "relativeRotation", rROk, false); + bool tSOk = true; + float timeScale = + EntityActionInterface::extractFloatArgument("hold", arguments, "timeScale", tSOk, false); lockForWrite(); - - _linearOffsetSet = _angularOffsetSet = false; - - if (ptOk) { - _linearOffset = linearOffset; - _linearOffsetSet = true; + if (rPOk) { + _relativePosition = relativePosition; + } else { + _relativePosition = glm::vec3(0.0f, 0.0f, 1.0f); } - if (rtOk) { - _angularOffset = angularOffset; - _angularOffsetSet = true; + if (rROk) { + _relativeRotation = relativeRotation; + } else { + _relativeRotation = glm::quat(0.0f, 0.0f, 0.0f, 1.0f); } + if (tSOk) { + _linearTimeScale = timeScale; + _angularTimeScale = timeScale; + } else { + _linearTimeScale = 0.2; + _angularTimeScale = 0.2; + } + + _positionalTargetSet = true; + _rotationalTargetSet = true; _active = true; unlock(); return true; diff --git a/interface/src/avatar/AvatarActionHold.h b/interface/src/avatar/AvatarActionHold.h index 19e6e8df55..f92ea94aaa 100644 --- a/interface/src/avatar/AvatarActionHold.h +++ b/interface/src/avatar/AvatarActionHold.h @@ -15,9 +15,9 @@ #include #include -#include +#include -class AvatarActionHold : public ObjectAction { +class AvatarActionHold : public ObjectActionSpring { public: AvatarActionHold(QUuid id, EntityItemPointer ownerEntity); virtual ~AvatarActionHold(); @@ -26,14 +26,8 @@ public: virtual void updateActionWorker(float deltaTimeStep); private: - - glm::vec3 _linearOffset; - bool _linearOffsetSet; - - glm::quat _angularOffset; - bool _angularOffsetSet; - - float _timeScale = 0.01; + glm::vec3 _relativePosition; + glm::quat _relativeRotation; }; #endif // hifi_AvatarActionHold_h diff --git a/libraries/entities/src/EntityActionInterface.cpp b/libraries/entities/src/EntityActionInterface.cpp index 09bb933488..b293d609c2 100644 --- a/libraries/entities/src/EntityActionInterface.cpp +++ b/libraries/entities/src/EntityActionInterface.cpp @@ -25,6 +25,9 @@ EntityActionType EntityActionInterface::actionTypeFromString(QString actionTypeS if (normalizedActionTypeString == "spring") { return ACTION_TYPE_SPRING; } + if (normalizedActionTypeString == "hold") { + return ACTION_TYPE_HOLD; + } qDebug() << "Warning -- EntityActionInterface::actionTypeFromString got unknown action-type name" << actionTypeString; return ACTION_TYPE_NONE; diff --git a/libraries/physics/src/ObjectAction.cpp b/libraries/physics/src/ObjectAction.cpp index 8f01a90410..ee7ba9ce7c 100644 --- a/libraries/physics/src/ObjectAction.cpp +++ b/libraries/physics/src/ObjectAction.cpp @@ -31,14 +31,7 @@ void ObjectAction::updateAction(btCollisionWorld* collisionWorld, btScalar delta qDebug() << "ObjectActionPullToPoint::updateAction no owner entity"; return; } - if (!tryLockForRead()) { - // don't risk hanging the thread running the physics simulation - return; - } - updateActionWorker(deltaTimeStep); - - unlock(); } void ObjectAction::debugDraw(btIDebugDraw* debugDrawer) { diff --git a/libraries/physics/src/ObjectAction.h b/libraries/physics/src/ObjectAction.h index a834a54864..0fd7383e6f 100644 --- a/libraries/physics/src/ObjectAction.h +++ b/libraries/physics/src/ObjectAction.h @@ -47,7 +47,7 @@ private: QReadWriteLock _lock; protected: - btRigidBody* getRigidBody(); + virtual btRigidBody* getRigidBody(); virtual glm::vec3 getPosition(); virtual void setPosition(glm::vec3 position); virtual glm::quat getRotation(); diff --git a/libraries/physics/src/ObjectActionPullToPoint.cpp b/libraries/physics/src/ObjectActionPullToPoint.cpp index 1069a2b9cf..053bef6a03 100644 --- a/libraries/physics/src/ObjectActionPullToPoint.cpp +++ b/libraries/physics/src/ObjectActionPullToPoint.cpp @@ -25,13 +25,20 @@ ObjectActionPullToPoint::~ObjectActionPullToPoint() { } void ObjectActionPullToPoint::updateActionWorker(btScalar deltaTimeStep) { + if (!tryLockForRead()) { + // don't risk hanging the thread running the physics simulation + return; + } + void* physicsInfo = _ownerEntity->getPhysicsInfo(); if (!physicsInfo) { + unlock(); return; } ObjectMotionState* motionState = static_cast(physicsInfo); btRigidBody* rigidBody = motionState->getRigidBody(); if (!rigidBody) { + unlock(); return; } @@ -44,6 +51,8 @@ void ObjectActionPullToPoint::updateActionWorker(btScalar deltaTimeStep) { } else { rigidBody->setLinearVelocity(glmToBullet(glm::vec3())); } + + unlock(); } diff --git a/libraries/physics/src/ObjectActionSpring.cpp b/libraries/physics/src/ObjectActionSpring.cpp index aea4dafbb8..6883e73766 100644 --- a/libraries/physics/src/ObjectActionSpring.cpp +++ b/libraries/physics/src/ObjectActionSpring.cpp @@ -25,13 +25,23 @@ ObjectActionSpring::~ObjectActionSpring() { } void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) { + if (!tryLockForRead()) { + // don't risk hanging the thread running the physics simulation + qDebug() << "ObjectActionSpring::updateActionWorker lock failed"; + return; + } + void* physicsInfo = _ownerEntity->getPhysicsInfo(); if (!physicsInfo) { + unlock(); + qDebug() << "ObjectActionSpring::updateActionWorker no physicsInfo"; return; } ObjectMotionState* motionState = static_cast(physicsInfo); btRigidBody* rigidBody = motionState->getRigidBody(); if (!rigidBody) { + unlock(); + qDebug() << "ObjectActionSpring::updateActionWorker no rigidBody"; return; } @@ -79,6 +89,8 @@ void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) { rigidBody->setAngularVelocity(glmToBullet(glm::vec3(0.0f))); } } + + unlock(); } diff --git a/libraries/physics/src/ObjectActionSpring.h b/libraries/physics/src/ObjectActionSpring.h index c5cbbe6126..9f3df0fdf8 100644 --- a/libraries/physics/src/ObjectActionSpring.h +++ b/libraries/physics/src/ObjectActionSpring.h @@ -25,7 +25,7 @@ public: virtual bool updateArguments(QVariantMap arguments); virtual void updateActionWorker(float deltaTimeStep); -private: +protected: glm::vec3 _positionalTarget; float _linearTimeScale; From 45c7cd4929681f09f3209a8a7f5dc074b0ed9b1f Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 10 Jun 2015 18:48:51 -0700 Subject: [PATCH 23/41] respond to code review, add a simple hold-a-stick script --- examples/stick.js | 18 ++++++++++++++++++ libraries/entities/src/EntityActionInterface.h | 5 +++-- .../entities/src/EntityScriptingInterface.cpp | 6 ++++++ libraries/physics/src/ObjectActionSpring.cpp | 13 +++---------- libraries/render/src/render/Scene.h | 2 +- 5 files changed, 31 insertions(+), 13 deletions(-) create mode 100644 examples/stick.js diff --git a/examples/stick.js b/examples/stick.js new file mode 100644 index 0000000000..02101f59e1 --- /dev/null +++ b/examples/stick.js @@ -0,0 +1,18 @@ +var stickID = null; +// sometimes if this is run immediately the stick doesn't get created? use a timer. +Script.setTimeout(function() { + var stickID = Entities.addEntity({ + type: "Model", + modelURL: "https://hifi-public.s3.amazonaws.com/eric/models/stick.fbx", + compoundShapeURL: "https://hifi-public.s3.amazonaws.com/eric/models/stick.obj", + dimensions: {x: .11, y: .11, z: .59}, + position: MyAvatar.getRightPalmPosition(), + rotation: MyAvatar.orientation, + damping: .1, + collisionsWillMove: true + }); + Entities.addAction("hold", stickID, {relativePosition: {x: 0.0, y: 0.0, z: -0.9}, timeScale: 0.15}); +}, 3000); + +function cleanup() { Entities.deleteEntity(stickID); } +Script.scriptEnding.connect(cleanup); diff --git a/libraries/entities/src/EntityActionInterface.h b/libraries/entities/src/EntityActionInterface.h index 50f862c535..457db8f5c3 100644 --- a/libraries/entities/src/EntityActionInterface.h +++ b/libraries/entities/src/EntityActionInterface.h @@ -36,8 +36,6 @@ public: virtual const EntityItemPointer& getOwnerEntity() const = 0; virtual void setOwnerEntity(const EntityItemPointer ownerEntity) = 0; virtual bool updateArguments(QVariantMap arguments) = 0; - // virtual QByteArray serialize() = 0; - // static EntityActionPointer deserialize(EntityItemPointer ownerEntity, QByteArray data); static EntityActionType actionTypeFromString(QString actionTypeString); static QString actionTypeToString(EntityActionType actionType); @@ -52,6 +50,9 @@ protected: virtual glm::vec3 getAngularVelocity() = 0; virtual void setAngularVelocity(glm::vec3 angularVelocity) = 0; + // these look in the arguments map for a named argument. if it's not found or isn't well formed, + // ok will be set to false (note that it's never set to true -- set it to true before calling these). + // if required is true, failure to extract an argument will cause a warning to be printed. static glm::vec3 extractVec3Argument (QString objectName, QVariantMap arguments, QString argumentName, bool& ok, bool required = true); static glm::quat extractQuatArgument (QString objectName, QVariantMap arguments, diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 6f6633ce0f..a091c786b7 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -495,6 +495,12 @@ QUuid EntityScriptingInterface::addAction(const QString& actionTypeString, QUuid actionID = QUuid::createUuid(); auto actionFactory = DependencyManager::get(); bool success = actionWorker(entityID, [&](EntitySimulation* simulation, EntityItemPointer entity) { + // create this action even if the entity doesn't have physics info. it will often be the + // case that a script adds an action immediately after an object is created, and the physicsInfo + // is computed asynchronously. + // if (!entity->getPhysicsInfo()) { + // return false; + // } EntityActionType actionType = EntityActionInterface::actionTypeFromString(actionTypeString); if (actionType == ACTION_TYPE_NONE) { return false; diff --git a/libraries/physics/src/ObjectActionSpring.cpp b/libraries/physics/src/ObjectActionSpring.cpp index 6883e73766..8eb4f7f652 100644 --- a/libraries/physics/src/ObjectActionSpring.cpp +++ b/libraries/physics/src/ObjectActionSpring.cpp @@ -34,7 +34,6 @@ void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) { void* physicsInfo = _ownerEntity->getPhysicsInfo(); if (!physicsInfo) { unlock(); - qDebug() << "ObjectActionSpring::updateActionWorker no physicsInfo"; return; } ObjectMotionState* motionState = static_cast(physicsInfo); @@ -65,7 +64,7 @@ void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) { glm::quat bodyRotation = bulletToGLM(rigidBody->getOrientation()); // if qZero and qOne are too close to each other, we can get NaN for angle. auto alignmentDot = glm::dot(bodyRotation, _rotationalTarget); - const float almostOne = 0.99999; + const float almostOne = 0.99999f; if (glm::abs(alignmentDot) < almostOne) { glm::quat target = _rotationalTarget; if (alignmentDot < 0) { @@ -75,12 +74,6 @@ void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) { glm::quat deltaQ = target * qZeroInverse; glm::vec3 axis = glm::axis(deltaQ); float angle = glm::angle(deltaQ); - if (isNaN(angle)) { - qDebug() << "ObjectActionSpring::updateAction angle =" << angle - << "body-rotation =" << bodyRotation.x << bodyRotation.y << bodyRotation.z << bodyRotation.w - << "target-rotation =" - << target.x << target.y << target.z<< target.w; - } assert(!isNaN(angle)); glm::vec3 newAngularVelocity = (angle / _angularTimeScale) * glm::normalize(axis); rigidBody->setAngularVelocity(glmToBullet(newAngularVelocity)); @@ -130,7 +123,7 @@ bool ObjectActionSpring::updateArguments(QVariantMap arguments) { if (pscOk) { _linearTimeScale = linearTimeScale; } else { - _linearTimeScale = 0.1; + _linearTimeScale = 0.1f; } } @@ -141,7 +134,7 @@ bool ObjectActionSpring::updateArguments(QVariantMap arguments) { if (rscOk) { _angularTimeScale = angularTimeScale; } else { - _angularTimeScale = 0.1; + _angularTimeScale = 0.1f; } } diff --git a/libraries/render/src/render/Scene.h b/libraries/render/src/render/Scene.h index 8cb29609ba..b9481d367e 100644 --- a/libraries/render/src/render/Scene.h +++ b/libraries/render/src/render/Scene.h @@ -245,7 +245,7 @@ public: void update(const UpdateFunctorPointer& updateFunctor) { _payload->update(updateFunctor); } // Shape Type Interface - const model::MaterialKey& getMaterialKey() const { return _payload->getMaterialKey(); } + const model::MaterialKey getMaterialKey() const { return _payload->getMaterialKey(); } protected: PayloadPointer _payload; From 93c0bd63e962bc6673371e00bc9a1555570617e6 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Wed, 10 Jun 2015 18:54:07 -0700 Subject: [PATCH 24/41] fix debug rendering transforms --- .../entities-renderer/src/RenderableBoxEntityItem.cpp | 2 +- .../src/RenderableDebugableEntityItem.cpp | 9 +++------ .../entities-renderer/src/RenderableLightEntityItem.cpp | 2 +- .../entities-renderer/src/RenderableSphereEntityItem.cpp | 2 +- 4 files changed, 6 insertions(+), 9 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp b/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp index 6ddf44b82d..066aaa8b31 100644 --- a/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp @@ -33,7 +33,7 @@ void RenderableBoxEntityItem::render(RenderArgs* args) { Q_ASSERT(args->_batch); gpu::Batch& batch = *args->_batch; - batch.setModelTransform(getTransformToCenter()); + batch.setModelTransform(getTransform()); // we want to include the scale as well DependencyManager::get()->renderSolidCube(batch, 1.0f, cubeColor); RenderableDebugableEntityItem::render(this, args); diff --git a/libraries/entities-renderer/src/RenderableDebugableEntityItem.cpp b/libraries/entities-renderer/src/RenderableDebugableEntityItem.cpp index fecc574af2..32e30c7e96 100644 --- a/libraries/entities-renderer/src/RenderableDebugableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableDebugableEntityItem.cpp @@ -24,9 +24,7 @@ void RenderableDebugableEntityItem::renderBoundingBox(EntityItem* entity, Render float puffedOut, glm::vec4& color) { Q_ASSERT(args->_batch); gpu::Batch& batch = *args->_batch; - Transform transform = entity->getTransformToCenter(); - //transform.postScale(entity->getDimensions()); - batch.setModelTransform(transform); + batch.setModelTransform(entity->getTransform()); // we want to include the scale as well DependencyManager::get()->renderWireCube(batch, 1.0f + puffedOut, color); } @@ -34,10 +32,9 @@ void RenderableDebugableEntityItem::render(EntityItem* entity, RenderArgs* args) if (args->_debugFlags & RenderArgs::RENDER_DEBUG_SIMULATION_OWNERSHIP) { Q_ASSERT(args->_batch); gpu::Batch& batch = *args->_batch; - Transform transform = entity->getTransformToCenter(); - transform.postScale(entity->getDimensions()); - batch.setModelTransform(transform); + batch.setModelTransform(entity->getTransform()); // we want to include the scale as well + auto nodeList = DependencyManager::get(); const QUuid& myNodeID = nodeList->getSessionUUID(); bool highlightSimulationOwnership = (entity->getSimulatorID() == myNodeID); diff --git a/libraries/entities-renderer/src/RenderableLightEntityItem.cpp b/libraries/entities-renderer/src/RenderableLightEntityItem.cpp index 819989d5ec..8a84c167c5 100644 --- a/libraries/entities-renderer/src/RenderableLightEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableLightEntityItem.cpp @@ -49,7 +49,7 @@ void RenderableLightEntityItem::render(RenderArgs* args) { #ifdef WANT_DEBUG Q_ASSERT(args->_batch); gpu::Batch& batch = *args->_batch; - batch.setModelTransform(getTransformToCenter()); + batch.setModelTransform(getTransform()); DependencyManager::get()->renderWireSphere(batch, 0.5f, 15, 15, glm::vec4(color, 1.0f)); #endif }; diff --git a/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp b/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp index b0aaebb2c8..5ba9003d43 100644 --- a/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp @@ -39,7 +39,7 @@ void RenderableSphereEntityItem::render(RenderArgs* args) { Q_ASSERT(args->_batch); gpu::Batch& batch = *args->_batch; - batch.setModelTransform(getTransformToCenter()); + batch.setModelTransform(getTransform()); // use a transform with scale, rotation, registration point and translation DependencyManager::get()->renderSolidSphere(batch, 0.5f, SLICES, STACKS, sphereColor); RenderableDebugableEntityItem::render(this, args); From ba4fd7adf6b0b3238b2f82e043ddbf7eb49e5d30 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 10 Jun 2015 19:23:01 -0700 Subject: [PATCH 25/41] windows didn't like EntityActionFactoryInterface::factory not returning an error --- libraries/entities/src/EntityActionFactoryInterface.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityActionFactoryInterface.h b/libraries/entities/src/EntityActionFactoryInterface.h index 2347eff525..9820313aae 100644 --- a/libraries/entities/src/EntityActionFactoryInterface.h +++ b/libraries/entities/src/EntityActionFactoryInterface.h @@ -27,7 +27,7 @@ class EntityActionFactoryInterface : public QObject, public Dependency { EntityActionType type, QUuid id, EntityItemPointer ownerEntity, - QVariantMap arguments) { assert(false); } + QVariantMap arguments) { assert(false); return nullptr; } }; #endif // hifi_EntityActionFactoryInterface_h From 8bd80c511ee8df21053f7ab47c93105484317dca Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 10 Jun 2015 21:08:00 -0700 Subject: [PATCH 26/41] make stick follow mouse motion --- examples/stick.js | 46 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/examples/stick.js b/examples/stick.js index 02101f59e1..5631f3aa3a 100644 --- a/examples/stick.js +++ b/examples/stick.js @@ -1,7 +1,20 @@ +// stick.js +// examples +// +// Created by Seth Alves on 2015-6-10 +// Copyright 2015 High Fidelity, Inc. +// +// Allow avatar to hold a stick +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + var stickID = null; +var actionID = "00000000-0000-0000-0000-000000000000"; // sometimes if this is run immediately the stick doesn't get created? use a timer. Script.setTimeout(function() { - var stickID = Entities.addEntity({ + stickID = Entities.addEntity({ type: "Model", modelURL: "https://hifi-public.s3.amazonaws.com/eric/models/stick.fbx", compoundShapeURL: "https://hifi-public.s3.amazonaws.com/eric/models/stick.obj", @@ -11,8 +24,33 @@ Script.setTimeout(function() { damping: .1, collisionsWillMove: true }); - Entities.addAction("hold", stickID, {relativePosition: {x: 0.0, y: 0.0, z: -0.9}, timeScale: 0.15}); + actionID = Entities.addAction("hold", stickID, {relativePosition: {x: 0.0, y: 0.0, z: -0.9}, timeScale: 0.15}); }, 3000); -function cleanup() { Entities.deleteEntity(stickID); } -Script.scriptEnding.connect(cleanup); + +function cleanUp() { + Entities.deleteEntity(stickID); +} + +function mouseMoveEvent(event) { + if (!stickID || actionID == "00000000-0000-0000-0000-000000000000") { + return; + } + var windowCenterX = Window.innerWidth/ 2; + var windowCenterY = Window.innerHeight / 2; + var mouseXCenterOffset = event.x - windowCenterX; + var mouseYCenterOffset = event.y - windowCenterY; + var mouseXRatio = mouseXCenterOffset / windowCenterX; + var mouseYRatio = mouseYCenterOffset / windowCenterY; + + var stickOrientation = Quat.fromPitchYawRollDegrees(mouseYRatio * -90, mouseXRatio * -90, 0); + var baseOffset = {x: 0.0, y: 0.0, z: -0.9}; + var offset = Vec3.multiplyQbyV(stickOrientation, baseOffset); + + Entities.updateAction(stickID, actionID, {relativePosition: offset, + relativeRotation: stickOrientation, + timeScale: 0.15}); +} + +Script.scriptEnding.connect(cleanUp); +Controller.mouseMoveEvent.connect(mouseMoveEvent); From ac0fc5d97405ec304bab0f15e18773dab2556d7c Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 11 Jun 2015 00:50:24 -0700 Subject: [PATCH 27/41] Working on oculus overlay code --- interface/src/ui/ApplicationOverlay.cpp | 235 ++++++++++++------------ interface/src/ui/ApplicationOverlay.h | 28 +-- 2 files changed, 117 insertions(+), 146 deletions(-) diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index f57c10b64e..4be47e342a 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -301,7 +301,6 @@ gpu::PipelinePointer ApplicationOverlay::getDrawPipeline() { // Draws the FBO texture for the screen void ApplicationOverlay::displayOverlayTexture(RenderArgs* renderArgs) { - if (_alpha == 0.0f) { return; } @@ -312,80 +311,77 @@ void ApplicationOverlay::displayOverlayTexture(RenderArgs* renderArgs) { } - //FIXME - doesn't work renderArgs->_context->syncCache(); + glViewport(0, 0, qApp->getDeviceSize().width(), qApp->getDeviceSize().height()); + gpu::Batch batch; + Transform model; //DependencyManager::get()->bindSimpleProgram(batch, true); batch.setPipeline(getDrawPipeline()); batch.setModelTransform(Transform()); batch.setProjectionTransform(mat4()); - batch.setViewTransform(Transform()); - batch.setUniformTexture(0, _crosshairTexture); + batch.setViewTransform(model); + batch._glBindTexture(GL_TEXTURE_2D, _framebufferObject->texture()); + batch._glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + batch._glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); DependencyManager::get()->renderUnitQuad(batch, vec4(vec3(1), _alpha)); + + //draw the mouse pointer + glm::vec2 canvasSize = qApp->getCanvasSize(); + + // Get the mouse coordinates and convert to NDC [-1, 1] + vec2 mousePosition = vec2(qApp->getMouseX(), qApp->getMouseY()); + mousePosition /= canvasSize; + mousePosition *= 2.0f; + mousePosition -= 1.0f; + mousePosition.y *= -1.0f; + model.setTranslation(vec3(mousePosition, 0)); + glm::vec2 mouseSize = 32.0f / canvasSize; + model.setScale(vec3(mouseSize, 1.0f)); + batch.setModelTransform(model); + batch.setUniformTexture(0, _crosshairTexture); + glm::vec4 reticleColor = { RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2], 1.0f }; + DependencyManager::get()->renderUnitQuad(batch, vec4(1)); renderArgs->_context->render(batch); - return; - - - - glDisable(GL_DEPTH_TEST); - glDisable(GL_LIGHTING); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glEnable(GL_TEXTURE_2D); - glViewport(0, 0, qApp->getDeviceSize().width(), qApp->getDeviceSize().height()); - - glMatrixMode(GL_PROJECTION); - glPushMatrix(); - glLoadIdentity(); - glMatrixMode(GL_MODELVIEW); - glPushMatrix(); - glLoadIdentity(); - { - glBindTexture(GL_TEXTURE_2D, _framebufferObject->texture()); - DependencyManager::get()->renderUnitQuad(vec4(vec3(1), _alpha)); - //draw the mouse pointer - glm::vec2 canvasSize = qApp->getCanvasSize(); - - // Get the mouse coordinates and convert to NDC [-1, 1] - vec2 mousePosition = vec2(qApp->getMouseX(), qApp->getMouseY()); - mousePosition /= canvasSize; - mousePosition *= 2.0f; - mousePosition -= 1.0f; - mousePosition.y *= -1.0f; - mat4 mouseMv = glm::translate(mat4(), vec3(mousePosition, 0)); - - // Scale the mouse based on the canvasSize (NOT the device size, - // we don't want a smaller mouse on retina displays) - glm::vec2 mouseSize = 32.0f / canvasSize; - mouseMv = glm::scale(mouseMv, vec3(mouseSize, 1.0f)); - - // Push the resulting matrix into modelview - glLoadMatrixf(glm::value_ptr(mouseMv)); - glBindTexture(GL_TEXTURE_2D, gpu::GLBackend::getTextureID(_crosshairTexture)); - glm::vec4 reticleColor = { RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2], 1.0f }; - DependencyManager::get()->renderUnitQuad(reticleColor); - } - glMatrixMode(GL_PROJECTION); - glPopMatrix(); - glMatrixMode(GL_MODELVIEW); - glPopMatrix(); } + +static gpu::BufferPointer _hemiVertices; +static gpu::BufferPointer _hemiIndices; +static int _hemiIndexCount{ 0 }; + // Draws the FBO texture for Oculus rift. void ApplicationOverlay::displayOverlayTextureHmd(RenderArgs* renderArgs, Camera& whichCamera) { if (_alpha == 0.0f) { return; } - _overlays.buildVBO(_textureFov, _textureAspectRatio, 80, 80); - glDisable(GL_DEPTH_TEST); - glDisable(GL_LIGHTING); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glEnable(GL_TEXTURE_2D); - glActiveTexture(GL_TEXTURE0); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); + auto geometryCache = DependencyManager::get(); + gpu::Batch batch; + //DependencyManager::get()->bindSimpleProgram(batch, true); + batch.setPipeline(getDrawPipeline()); + batch._glDisable(GL_DEPTH_TEST); + batch._glBindTexture(GL_TEXTURE_2D, _framebufferObject->texture()); + batch._glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + batch._glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + batch.setProjectionTransform(whichCamera.getProjection()); + batch.setViewTransform(Transform()); + + Transform model; + model.setTranslation(vec3(0.0f, 0.0f, -2.0f)); + batch.setModelTransform(model); + + // FIXME doesn't work + drawSphereSection(batch); + + // works... + geometryCache->renderUnitQuad(batch, vec4(vec3(1), _alpha)); + + renderArgs->_context->render(batch); + // batch.setUniformTexture(0, gpu::TexturePointer()); + // geometryCache->renderSolidCube(batch, 0.5f, vec4(1)); + + /* // The camera here contains only the head pose relative to the avatar position vec3 pos = whichCamera.getPosition(); quat rot = whichCamera.getOrientation(); @@ -394,6 +390,7 @@ void ApplicationOverlay::displayOverlayTextureHmd(RenderArgs* renderArgs, Camera glLoadMatrixf(glm::value_ptr(glm::inverse(overlayXfm))); glBindTexture(GL_TEXTURE_2D, _framebufferObject->texture()); _overlays.render(); + */ //Update and draw the magnifiers /* @@ -423,11 +420,11 @@ void ApplicationOverlay::displayOverlayTextureHmd(RenderArgs* renderArgs, Camera }); } } - */ if (!Application::getInstance()->isMouseHidden()) { renderPointersOculus(); } + */ } // Draws the FBO texture for 3DTV. @@ -1104,109 +1101,102 @@ void ApplicationOverlay::renderDomainConnectionStatusBorder() { } } -ApplicationOverlay::TexturedHemisphere::TexturedHemisphere() : - _vertices(0), - _indices(0), - _vbo(0, 0) { -} -ApplicationOverlay::TexturedHemisphere::~TexturedHemisphere() { - cleanupVBO(); -} - -void ApplicationOverlay::TexturedHemisphere::buildVBO(const float fov, - const float aspectRatio, - const int slices, - const int stacks) { +void ApplicationOverlay::buildHemiVertices( + const float fov, const float aspectRatio, const int slices, const int stacks) { static float textureFOV = 0.0f, textureAspectRatio = 1.0f; - if (textureFOV == fov && textureAspectRatio == aspectRatio) { + if (_hemiVerticesID != GeometryCache::UNKNOWN_ID && textureFOV == fov && textureAspectRatio == aspectRatio) { return; } + textureFOV = fov; textureAspectRatio = aspectRatio; + auto geometryCache = DependencyManager::get(); + if (_hemiVerticesID == GeometryCache::UNKNOWN_ID) { + _hemiVerticesID = geometryCache->allocateID(); + } + + _hemiVertices = gpu::BufferPointer(new gpu::Buffer()); + _hemiIndices = gpu::BufferPointer(new gpu::Buffer()); + + if (fov >= PI) { qDebug() << "TexturedHemisphere::buildVBO(): FOV greater or equal than Pi will create issues"; } - // Cleanup old VBO if necessary - cleanupVBO(); - + //UV mapping source: http://www.mvps.org/directx/articles/spheremap.htm - // Compute number of vertices needed - _vertices = slices * stacks; - + vec3 pos; // Compute vertices positions and texture UV coordinate - TextureVertex* vertexData = new TextureVertex[_vertices]; - TextureVertex* vertexPtr = &vertexData[0]; + // Create and write to buffer + //_hemiVertices->(sizeof(vec3) + sizeof(vec2) + sizeof(vec4)) * stacks * slices); for (int i = 0; i < stacks; i++) { float stacksRatio = (float)i / (float)(stacks - 1); // First stack is 0.0f, last stack is 1.0f // abs(theta) <= fov / 2.0f float pitch = -fov * (stacksRatio - 0.5f); - for (int j = 0; j < slices; j++) { float slicesRatio = (float)j / (float)(slices - 1); // First slice is 0.0f, last slice is 1.0f // abs(phi) <= fov * aspectRatio / 2.0f float yaw = -fov * aspectRatio * (slicesRatio - 0.5f); - - vertexPtr->position = getPoint(yaw, pitch); - vertexPtr->uv.x = slicesRatio; - vertexPtr->uv.y = stacksRatio; - vertexPtr++; + pos = getPoint(yaw, pitch); + _hemiVertices->append(sizeof(pos), (gpu::Byte*)&pos); + _hemiVertices->append(sizeof(vec2), (gpu::Byte*)&vec2(slicesRatio, stacksRatio)); + _hemiVertices->append(sizeof(vec4), (gpu::Byte*)&vec4(1)); } } - // Create and write to buffer - glGenBuffers(1, &_vbo.first); - glBindBuffer(GL_ARRAY_BUFFER, _vbo.first); - static const int BYTES_PER_VERTEX = sizeof(TextureVertex); - glBufferData(GL_ARRAY_BUFFER, _vertices * BYTES_PER_VERTEX, vertexData, GL_STATIC_DRAW); - delete[] vertexData; - // Compute number of indices needed static const int VERTEX_PER_TRANGLE = 3; static const int TRIANGLE_PER_RECTANGLE = 2; int numberOfRectangles = (slices - 1) * (stacks - 1); - _indices = numberOfRectangles * TRIANGLE_PER_RECTANGLE * VERTEX_PER_TRANGLE; + _hemiIndexCount = numberOfRectangles * TRIANGLE_PER_RECTANGLE * VERTEX_PER_TRANGLE; // Compute indices order - GLushort* indexData = new GLushort[_indices]; - GLushort* indexPtr = indexData; + std::vector indices; for (int i = 0; i < stacks - 1; i++) { for (int j = 0; j < slices - 1; j++) { GLushort bottomLeftIndex = i * slices + j; GLushort bottomRightIndex = bottomLeftIndex + 1; GLushort topLeftIndex = bottomLeftIndex + slices; GLushort topRightIndex = topLeftIndex + 1; - - *(indexPtr++) = topLeftIndex; - *(indexPtr++) = bottomLeftIndex; - *(indexPtr++) = topRightIndex; - - *(indexPtr++) = topRightIndex; - *(indexPtr++) = bottomLeftIndex; - *(indexPtr++) = bottomRightIndex; + // FIXME make a z-order curve for better vertex cache locality + indices.push_back(topLeftIndex); + indices.push_back(bottomLeftIndex); + indices.push_back(topRightIndex); + + indices.push_back(topRightIndex); + indices.push_back(bottomLeftIndex); + indices.push_back(bottomRightIndex); } } - // Create and write to buffer - glGenBuffers(1, &_vbo.second); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _vbo.second); - static const int BYTES_PER_INDEX = sizeof(GLushort); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, _indices * BYTES_PER_INDEX, indexData, GL_STATIC_DRAW); - delete[] indexData; + _hemiIndices->append(sizeof(GLushort) * indices.size(), (gpu::Byte*)&indices[0]); } -void ApplicationOverlay::TexturedHemisphere::cleanupVBO() { - if (_vbo.first != 0) { - glDeleteBuffers(1, &_vbo.first); - _vbo.first = 0; - } - if (_vbo.second != 0) { - glDeleteBuffers(1, &_vbo.second); - _vbo.second = 0; - } + +void ApplicationOverlay::drawSphereSection(gpu::Batch& batch) { + buildHemiVertices(_textureFov, _textureAspectRatio, 80, 80); + static const int VERTEX_DATA_SLOT = 0; + static const int TEXTURE_DATA_SLOT = 1; + static const int COLOR_DATA_SLOT = 2; + gpu::Stream::FormatPointer streamFormat(new gpu::Stream::Format()); // 1 for everyone + streamFormat->setAttribute(gpu::Stream::POSITION, VERTEX_DATA_SLOT, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); + streamFormat->setAttribute(gpu::Stream::TEXCOORD, TEXTURE_DATA_SLOT, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), sizeof(vec3)); + streamFormat->setAttribute(gpu::Stream::COLOR, COLOR_DATA_SLOT, gpu::Element(gpu::VEC4, gpu::FLOAT, gpu::RGBA), sizeof(vec3) + sizeof(vec2)); + batch.setInputFormat(streamFormat); + + static const int VERTEX_STRIDE = sizeof(vec3) + sizeof(vec2) + sizeof(vec4); + gpu::BufferView posView(_hemiVertices, 0, _hemiVertices->getSize(), VERTEX_STRIDE, streamFormat->getAttributes().at(gpu::Stream::POSITION)._element); + gpu::BufferView uvView(_hemiVertices, sizeof(vec3), _hemiVertices->getSize(), VERTEX_STRIDE, streamFormat->getAttributes().at(gpu::Stream::TEXCOORD)._element); + gpu::BufferView colView(_hemiVertices, sizeof(vec3) + sizeof(vec2), _hemiVertices->getSize(), VERTEX_STRIDE, streamFormat->getAttributes().at(gpu::Stream::COLOR)._element); + batch.setInputBuffer(VERTEX_DATA_SLOT, posView); + batch.setInputBuffer(TEXTURE_DATA_SLOT, uvView); + batch.setInputBuffer(COLOR_DATA_SLOT, colView); + batch.setIndexBuffer(gpu::UINT16, _hemiIndices, 0); + batch.drawIndexed(gpu::TRIANGLES, _hemiIndexCount); } + GLuint ApplicationOverlay::getOverlayTexture() { return _framebufferObject->texture(); } @@ -1234,6 +1224,7 @@ void ApplicationOverlay::buildFramebufferObject() { glBindTexture(GL_TEXTURE_2D, 0); } +/* //Renders a hemisphere with texture coordinates. void ApplicationOverlay::TexturedHemisphere::render() { if (_vbo.first == 0 || _vbo.second == 0) { @@ -1261,7 +1252,7 @@ void ApplicationOverlay::TexturedHemisphere::render() { glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } - +*/ glm::vec2 ApplicationOverlay::directionToSpherical(const glm::vec3& direction) { glm::vec2 result; // Compute yaw diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index 63ef48bd92..d78ad4bc9f 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -67,28 +67,8 @@ public: static glm::vec2 sphericalToScreen(const glm::vec2 & sphericalPos); private: - // Interleaved vertex data - struct TextureVertex { - glm::vec3 position; - glm::vec2 uv; - }; - - typedef QPair VerticesIndices; - class TexturedHemisphere { - public: - TexturedHemisphere(); - ~TexturedHemisphere(); - void buildVBO(const float fov, const float aspectRatio, const int slices, const int stacks); - void render(); - - private: - void cleanupVBO(); - - GLuint _vertices; - GLuint _indices; - VerticesIndices _vbo; - }; - + void buildHemiVertices(const float fov, const float aspectRatio, const int slices, const int stacks); + void drawSphereSection(gpu::Batch& batch); float _hmdUIAngularSize = DEFAULT_HMD_UI_ANGULAR_SIZE; QOpenGLFramebufferObject* _framebufferObject; @@ -105,11 +85,11 @@ private: void renderDomainConnectionStatusBorder(); void buildFramebufferObject(); - - TexturedHemisphere _overlays; float _textureFov; float _textureAspectRatio; + int _hemiVerticesID{ GeometryCache::UNKNOWN_ID }; + enum Reticles { MOUSE, LEFT_CONTROLLER, RIGHT_CONTROLLER, NUMBER_OF_RETICLES }; bool _reticleActive[NUMBER_OF_RETICLES]; From ef520353008fb10205bcd8034b8d14d745966934 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 11 Jun 2015 02:20:51 -0700 Subject: [PATCH 28/41] Working on functional overlays --- interface/src/Application.cpp | 3 +- interface/src/devices/OculusManager.cpp | 38 +++++++++++----------- interface/src/devices/TV3DManager.cpp | 18 +++++------ interface/src/ui/ApplicationOverlay.cpp | 42 ++++++++++++++----------- 4 files changed, 53 insertions(+), 48 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 9d5c43b7cf..a93b0186aa 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -941,6 +941,7 @@ void Application::paintGL() { glPushMatrix(); glLoadIdentity(); displaySide(&renderArgs, _myCamera); + _applicationOverlay.displayOverlayTexture(&renderArgs); glPopMatrix(); if (Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) { @@ -957,8 +958,6 @@ void Application::paintGL() { 0, 0, _glWidget->getDeviceSize().width(), _glWidget->getDeviceSize().height(), GL_COLOR_BUFFER_BIT, GL_NEAREST); glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); - - _applicationOverlay.displayOverlayTexture(&renderArgs); } if (!OculusManager::isConnected() || OculusManager::allowSwap()) { diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp index 77263935f6..2ee62c85f3 100644 --- a/interface/src/devices/OculusManager.cpp +++ b/interface/src/devices/OculusManager.cpp @@ -615,6 +615,7 @@ void OculusManager::display(QGLWidget * glCanvas, RenderArgs* renderArgs, const renderArgs->_renderSide = RenderArgs::MONO; qApp->displaySide(renderArgs, *_camera, false); + qApp->getApplicationOverlay().displayOverlayTextureHmd(renderArgs, *_camera); }); _activeEye = ovrEye_Count; @@ -629,28 +630,27 @@ void OculusManager::display(QGLWidget * glCanvas, RenderArgs* renderArgs, const glBindFramebuffer(GL_FRAMEBUFFER, 0); } - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gpu::GLBackend::getFramebufferID(finalFbo)); - //Render each eye into an fbo - for_each_eye(_ovrHmd, [&](ovrEyeType eye) { - _activeEye = eye; - // Update our camera to what the application camera is doing - _camera->setRotation(toGlm(eyeRenderPose[eye].Orientation)); - _camera->setPosition(toGlm(eyeRenderPose[eye].Position)); - configureCamera(*_camera); - glMatrixMode(GL_PROJECTION); - glLoadMatrixf(glm::value_ptr(_camera->getProjection())); + //glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gpu::GLBackend::getFramebufferID(finalFbo)); + ////Render each eye into an fbo + //for_each_eye(_ovrHmd, [&](ovrEyeType eye) { + // _activeEye = eye; + // // Update our camera to what the application camera is doing + // _camera->setRotation(toGlm(eyeRenderPose[eye].Orientation)); + // _camera->setPosition(toGlm(eyeRenderPose[eye].Position)); + // configureCamera(*_camera); + // glMatrixMode(GL_PROJECTION); + // glLoadMatrixf(glm::value_ptr(_camera->getProjection())); - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); + // glMatrixMode(GL_MODELVIEW); + // glLoadIdentity(); - ovrRecti & vp = _eyeTextures[eye].Header.RenderViewport; - vp.Size.h = _recommendedTexSize.h * _offscreenRenderScale; - vp.Size.w = _recommendedTexSize.w * _offscreenRenderScale; + // ovrRecti & vp = _eyeTextures[eye].Header.RenderViewport; + // vp.Size.h = _recommendedTexSize.h * _offscreenRenderScale; + // vp.Size.w = _recommendedTexSize.w * _offscreenRenderScale; - glViewport(vp.Pos.x, vp.Pos.y, vp.Size.w, vp.Size.h); - qApp->getApplicationOverlay().displayOverlayTextureHmd(renderArgs, *_camera); - }); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + // glViewport(vp.Pos.x, vp.Pos.y, vp.Size.w, vp.Size.h); + //}); + //glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); glPopMatrix(); diff --git a/interface/src/devices/TV3DManager.cpp b/interface/src/devices/TV3DManager.cpp index c14e589389..b0a2ff7e3b 100644 --- a/interface/src/devices/TV3DManager.cpp +++ b/interface/src/devices/TV3DManager.cpp @@ -12,6 +12,7 @@ #include "InterfaceConfig.h" #include +#include #include #include "gpu/GLBackend.h" @@ -106,21 +107,20 @@ void TV3DManager::display(RenderArgs* renderArgs, Camera& whichCamera) { _activeEye = &eye; glViewport(portalX, portalY, portalW, portalH); glScissor(portalX, portalY, portalW, portalH); + + glm::mat4 projection = glm::frustum(eye.left, eye.right, eye.bottom, eye.top, nearZ, farZ); + float fov = atan(1.0f / projection[1][1]); + projection = glm::translate(projection, vec3(eye.modelTranslation, 0, 0)); + eyeCamera.setProjection(projection); + glMatrixMode(GL_PROJECTION); glLoadIdentity(); // reset projection matrix - glFrustum(eye.left, eye.right, eye.bottom, eye.top, nearZ, farZ); // set left view frustum - GLfloat p[4][4]; - // Really? - glGetFloatv(GL_PROJECTION_MATRIX, &(p[0][0])); - float cotangent = p[1][1]; - GLfloat fov = atan(1.0f / cotangent); - glTranslatef(eye.modelTranslation, 0.0, 0.0); // translate to cancel parallax - + glLoadMatrixf(glm::value_ptr(projection)); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); renderArgs->_renderSide = RenderArgs::MONO; qApp->displaySide(renderArgs, eyeCamera, false); - qApp->getApplicationOverlay().displayOverlayTextureStereo(renderArgs, whichCamera, _aspect, fov); + qApp->getApplicationOverlay().displayOverlayTexture(renderArgs); _activeEye = NULL; }, [&]{ // render right side view diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 4be47e342a..b4824e96a3 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -312,7 +312,6 @@ void ApplicationOverlay::displayOverlayTexture(RenderArgs* renderArgs) { renderArgs->_context->syncCache(); - glViewport(0, 0, qApp->getDeviceSize().width(), qApp->getDeviceSize().height()); gpu::Batch batch; Transform model; @@ -356,9 +355,10 @@ void ApplicationOverlay::displayOverlayTextureHmd(RenderArgs* renderArgs, Camera return; } + renderArgs->_context->syncCache(); + auto geometryCache = DependencyManager::get(); gpu::Batch batch; - //DependencyManager::get()->bindSimpleProgram(batch, true); batch.setPipeline(getDrawPipeline()); batch._glDisable(GL_DEPTH_TEST); batch._glBindTexture(GL_TEXTURE_2D, _framebufferObject->texture()); @@ -367,19 +367,23 @@ void ApplicationOverlay::displayOverlayTextureHmd(RenderArgs* renderArgs, Camera batch.setProjectionTransform(whichCamera.getProjection()); batch.setViewTransform(Transform()); - Transform model; - model.setTranslation(vec3(0.0f, 0.0f, -2.0f)); - batch.setModelTransform(model); + Transform mv; + mv.setTranslation(vec3(0.0f, 0.0f, -1.0f)); + mv.preRotate(glm::inverse(qApp->getCamera()->getHmdRotation())); + mv.setScale(vec3(1.0f, 1.0f / aspect(qApp->getCanvasSize()), 1.0f)); + batch.setModelTransform(mv); + // FIXME doesn't work - drawSphereSection(batch); + // drawSphereSection(batch); // works... geometryCache->renderUnitQuad(batch, vec4(vec3(1), _alpha)); + // sort of works, renders a semi-transparent red quad + // geometryCache->renderSolidCube(batch, 1.0f, vec4(1)); + renderArgs->_context->render(batch); - // batch.setUniformTexture(0, gpu::TexturePointer()); - // geometryCache->renderSolidCube(batch, 0.5f, vec4(1)); /* // The camera here contains only the head pose relative to the avatar position @@ -427,8 +431,9 @@ void ApplicationOverlay::displayOverlayTextureHmd(RenderArgs* renderArgs, Camera */ } +/* // Draws the FBO texture for 3DTV. -void ApplicationOverlay::displayOverlayTextureStereo(RenderArgs* renderArgs, Camera& whichCamera, float aspectRatio, float fov) { +void ApplicationOverlay::displayOverlayTextureStereo(Camera& whichCamera, float aspectRatio, float fov) { if (_alpha == 0.0f) { return; } @@ -473,15 +478,15 @@ void ApplicationOverlay::displayOverlayTextureStereo(RenderArgs* renderArgs, Cam GLfloat y = -halfQuadHeight; glDisable(GL_DEPTH_TEST); - //with_each_texture(_framebufferObject->texture(), _newUiTexture, [&] { - // DependencyManager::get()->renderQuad(glm::vec3(x, y + quadHeight, -distance), - // glm::vec3(x + quadWidth, y + quadHeight, -distance), - // glm::vec3(x + quadWidth, y, -distance), - // glm::vec3(x, y, -distance), - // glm::vec2(0.0f, 1.0f), glm::vec2(1.0f, 1.0f), - // glm::vec2(1.0f, 0.0f), glm::vec2(0.0f, 0.0f), - // overlayColor); - //}); + with_each_texture(_overlays.getTexture(), _newUiTexture, [&] { + DependencyManager::get()->renderQuad(glm::vec3(x, y + quadHeight, -distance), + glm::vec3(x + quadWidth, y + quadHeight, -distance), + glm::vec3(x + quadWidth, y, -distance), + glm::vec3(x, y, -distance), + glm::vec2(0.0f, 1.0f), glm::vec2(1.0f, 1.0f), + glm::vec2(1.0f, 0.0f), glm::vec2(0.0f, 0.0f), + overlayColor); + }); if (!_crosshairTexture) { _crosshairTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + @@ -521,6 +526,7 @@ void ApplicationOverlay::displayOverlayTextureStereo(RenderArgs* renderArgs, Cam glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); glEnable(GL_LIGHTING); } +*/ void ApplicationOverlay::computeHmdPickRay(glm::vec2 cursorPos, glm::vec3& origin, glm::vec3& direction) const { cursorPos *= qApp->getCanvasSize(); From 7d0000c5377febd3fc3307b840f5fe020f3d7bfa Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 11 Jun 2015 08:38:03 -0700 Subject: [PATCH 29/41] fix warning about returning ref to temp variable --- libraries/render/src/render/Scene.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/render/src/render/Scene.h b/libraries/render/src/render/Scene.h index 8cb29609ba..b9481d367e 100644 --- a/libraries/render/src/render/Scene.h +++ b/libraries/render/src/render/Scene.h @@ -245,7 +245,7 @@ public: void update(const UpdateFunctorPointer& updateFunctor) { _payload->update(updateFunctor); } // Shape Type Interface - const model::MaterialKey& getMaterialKey() const { return _payload->getMaterialKey(); } + const model::MaterialKey getMaterialKey() const { return _payload->getMaterialKey(); } protected: PayloadPointer _payload; From f77f3f1e1e46eea210f9bbefad2befe182177359 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 11 Jun 2015 08:43:19 -0700 Subject: [PATCH 30/41] fix bug where grab.js moves mouse to screen corner when CTRL is pressed --- examples/grab.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/grab.js b/examples/grab.js index 306af86c68..c6b1b884b7 100644 --- a/examples/grab.js +++ b/examples/grab.js @@ -140,6 +140,10 @@ function mouseIntersectionWithPlane(pointOnPlane, planeNormal, event) { } function computeNewGrabPlane() { + if (!gIsGrabbing) { + return; + } + var maybeResetMousePosition = false; if (gGrabMode !== "rotate") { gMouseAtRotateStart = gMouseCursorLocation; From f4f5f167588206987836c3e1b1a2757351a2a9cb Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 11 Jun 2015 18:34:39 +0200 Subject: [PATCH 31/41] Fix zones wireframe rendering --- .../src/RenderableZoneEntityItem.cpp | 73 ++++++++++++++++++- .../src/RenderableZoneEntityItem.h | 5 ++ 2 files changed, 74 insertions(+), 4 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 9c4f8ae0bb..8c147cac05 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -100,10 +101,17 @@ void RenderableZoneEntityItem::render(RenderArgs* args) { case SHAPE_TYPE_COMPOUND: { PerformanceTimer perfTimer("zone->renderCompound"); updateGeometry(); - - if (_model && _model->isActive()) { - // FIX ME: this is no longer available... we need to switch to payloads - //_model->renderInScene(getLocalRenderAlpha(), args); + if (_model && _model->needsFixupInScene()) { + // check to see if when we added our models to the scene they were ready, if they were not ready, then + // fix them up in the scene + render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); + render::PendingChanges pendingChanges; + _model->removeFromScene(scene, pendingChanges); + _model->addToScene(scene, pendingChanges); + + scene->enqueuePendingChanges(pendingChanges); + + _model->setVisibleInScene(getVisible(), scene); } break; } @@ -131,6 +139,15 @@ void RenderableZoneEntityItem::render(RenderArgs* args) { break; } } + + if ((!_drawZoneBoundaries || getShapeType() != SHAPE_TYPE_COMPOUND) && + _model && !_model->needsFixupInScene()) { + // If the model is in the scene but doesn't need to be, remove it. + render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); + render::PendingChanges pendingChanges; + _model->removeFromScene(scene, pendingChanges); + scene->enqueuePendingChanges(pendingChanges); + } } bool RenderableZoneEntityItem::contains(const glm::vec3& point) const { @@ -145,3 +162,51 @@ bool RenderableZoneEntityItem::contains(const glm::vec3& point) const { return false; } + +class RenderableZoneEntityItemMeta { +public: + RenderableZoneEntityItemMeta(EntityItemPointer entity) : entity(entity){ } + typedef render::Payload Payload; + typedef Payload::DataPointer Pointer; + + EntityItemPointer entity; +}; + +namespace render { + template <> const ItemKey payloadGetKey(const RenderableZoneEntityItemMeta::Pointer& payload) { + return ItemKey::Builder::opaqueShape(); + } + + template <> const Item::Bound payloadGetBound(const RenderableZoneEntityItemMeta::Pointer& payload) { + if (payload && payload->entity) { + return payload->entity->getAABox(); + } + return render::Item::Bound(); + } + template <> void payloadRender(const RenderableZoneEntityItemMeta::Pointer& payload, RenderArgs* args) { + if (args) { + if (payload && payload->entity) { + payload->entity->render(args); + } + } + } +} + +bool RenderableZoneEntityItem::addToScene(EntityItemPointer self, std::shared_ptr scene, + render::PendingChanges& pendingChanges) { + _myMetaItem = scene->allocateID(); + + auto renderData = RenderableZoneEntityItemMeta::Pointer(new RenderableZoneEntityItemMeta(self)); + auto renderPayload = render::PayloadPointer(new RenderableZoneEntityItemMeta::Payload(renderData)); + + pendingChanges.resetItem(_myMetaItem, renderPayload); + return true; +} + +void RenderableZoneEntityItem::removeFromScene(EntityItemPointer self, std::shared_ptr scene, + render::PendingChanges& pendingChanges) { + pendingChanges.removeItem(_myMetaItem); + if (_model) { + _model->removeFromScene(scene, pendingChanges); + } +} diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.h b/libraries/entities-renderer/src/RenderableZoneEntityItem.h index b2a9791d44..f455ea34de 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.h +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.h @@ -35,6 +35,9 @@ public: virtual void render(RenderArgs* args); virtual bool contains(const glm::vec3& point) const; + virtual bool addToScene(EntityItemPointer self, std::shared_ptr scene, render::PendingChanges& pendingChanges); + virtual void removeFromScene(EntityItemPointer self, std::shared_ptr scene, render::PendingChanges& pendingChanges); + private: Model* getModel(); void initialSimulation(); @@ -45,6 +48,8 @@ private: Model* _model; bool _needsInitialSimulation; + + render::ItemID _myMetaItem; }; #endif // hifi_RenderableZoneEntityItem_h From 47888b46716d6808645eeb1ae3a3a96735ff82f7 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 11 Jun 2015 09:34:56 -0700 Subject: [PATCH 32/41] fix transforms for items with non-default registration point --- libraries/entities-renderer/src/RenderableBoxEntityItem.cpp | 2 +- .../entities-renderer/src/RenderableDebugableEntityItem.cpp | 4 ++-- .../entities-renderer/src/RenderableSphereEntityItem.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp b/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp index 066aaa8b31..23b93250bc 100644 --- a/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp @@ -33,7 +33,7 @@ void RenderableBoxEntityItem::render(RenderArgs* args) { Q_ASSERT(args->_batch); gpu::Batch& batch = *args->_batch; - batch.setModelTransform(getTransform()); // we want to include the scale as well + batch.setModelTransform(getTransformToCenter()); // we want to include the scale as well DependencyManager::get()->renderSolidCube(batch, 1.0f, cubeColor); RenderableDebugableEntityItem::render(this, args); diff --git a/libraries/entities-renderer/src/RenderableDebugableEntityItem.cpp b/libraries/entities-renderer/src/RenderableDebugableEntityItem.cpp index 32e30c7e96..6a511e0d30 100644 --- a/libraries/entities-renderer/src/RenderableDebugableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableDebugableEntityItem.cpp @@ -24,7 +24,7 @@ void RenderableDebugableEntityItem::renderBoundingBox(EntityItem* entity, Render float puffedOut, glm::vec4& color) { Q_ASSERT(args->_batch); gpu::Batch& batch = *args->_batch; - batch.setModelTransform(entity->getTransform()); // we want to include the scale as well + batch.setModelTransform(entity->getTransformToCenter()); // we want to include the scale as well DependencyManager::get()->renderWireCube(batch, 1.0f + puffedOut, color); } @@ -33,7 +33,7 @@ void RenderableDebugableEntityItem::render(EntityItem* entity, RenderArgs* args) Q_ASSERT(args->_batch); gpu::Batch& batch = *args->_batch; - batch.setModelTransform(entity->getTransform()); // we want to include the scale as well + batch.setModelTransform(entity->getTransformToCenter()); // we want to include the scale as well auto nodeList = DependencyManager::get(); const QUuid& myNodeID = nodeList->getSessionUUID(); diff --git a/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp b/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp index 5ba9003d43..6d9cb525d6 100644 --- a/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp @@ -39,7 +39,7 @@ void RenderableSphereEntityItem::render(RenderArgs* args) { Q_ASSERT(args->_batch); gpu::Batch& batch = *args->_batch; - batch.setModelTransform(getTransform()); // use a transform with scale, rotation, registration point and translation + batch.setModelTransform(getTransformToCenter()); // use a transform with scale, rotation, registration point and translation DependencyManager::get()->renderSolidSphere(batch, 0.5f, SLICES, STACKS, sphereColor); RenderableDebugableEntityItem::render(this, args); From a878559e0c4a88126c924abc805f236f0f7bb765 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 11 Jun 2015 09:38:45 -0700 Subject: [PATCH 33/41] fix transforms for items with non-default registration point --- libraries/entities-renderer/src/RenderableLightEntityItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities-renderer/src/RenderableLightEntityItem.cpp b/libraries/entities-renderer/src/RenderableLightEntityItem.cpp index 8a84c167c5..819989d5ec 100644 --- a/libraries/entities-renderer/src/RenderableLightEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableLightEntityItem.cpp @@ -49,7 +49,7 @@ void RenderableLightEntityItem::render(RenderArgs* args) { #ifdef WANT_DEBUG Q_ASSERT(args->_batch); gpu::Batch& batch = *args->_batch; - batch.setModelTransform(getTransform()); + batch.setModelTransform(getTransformToCenter()); DependencyManager::get()->renderWireSphere(batch, 0.5f, 15, 15, glm::vec4(color, 1.0f)); #endif }; From 0e8ec81837f56f4a211fc843c3a51c4aa168308d Mon Sep 17 00:00:00 2001 From: Sam Gondelman Date: Thu, 11 Jun 2015 09:41:59 -0700 Subject: [PATCH 34/41] fixed lambda capture --- interface/src/devices/KeyboardMouseDevice.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/devices/KeyboardMouseDevice.cpp b/interface/src/devices/KeyboardMouseDevice.cpp index d0f04e5636..8a336064e5 100755 --- a/interface/src/devices/KeyboardMouseDevice.cpp +++ b/interface/src/devices/KeyboardMouseDevice.cpp @@ -173,9 +173,9 @@ void KeyboardMouseDevice::registerToUserInputMapper(UserInputMapper& mapper) { availableInputs.append(UserInputMapper::InputPair(makeInput(Qt::Key_Space), QKeySequence(Qt::Key_Space).toString())); return availableInputs; }; - proxy->resetDeviceBindings = [this, &_mapper = mapper, &device = _deviceID] () -> bool { - _mapper.removeAllInputChannelsForDevice(device); - this->assignDefaultInputMapping(_mapper); + proxy->resetDeviceBindings = [this, &mapper] () -> bool { + mapper.removeAllInputChannelsForDevice(_deviceID); + this->assignDefaultInputMapping(mapper); return true; }; mapper.registerDevice(_deviceID, proxy); From 8ed9a3ca02e94bb064a42cb216b9b5c863384cc2 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 11 Jun 2015 11:04:19 -0700 Subject: [PATCH 35/41] add handedness setting to avatar-hold action. update stick.js to allow use of hydra --- examples/stick.js | 58 +++++++++++++++---- interface/src/avatar/AvatarActionHold.cpp | 31 ++++++++-- interface/src/avatar/AvatarActionHold.h | 2 + .../entities/src/EntityActionInterface.cpp | 18 +++++- .../entities/src/EntityActionInterface.h | 3 + 5 files changed, 94 insertions(+), 18 deletions(-) diff --git a/examples/stick.js b/examples/stick.js index 5631f3aa3a..ea1f3439a9 100644 --- a/examples/stick.js +++ b/examples/stick.js @@ -10,8 +10,12 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +var hand = "left"; +var nullActionID = "00000000-0000-0000-0000-000000000000"; +var controllerID; +var controllerActive; var stickID = null; -var actionID = "00000000-0000-0000-0000-000000000000"; +var actionID = nullActionID; // sometimes if this is run immediately the stick doesn't get created? use a timer. Script.setTimeout(function() { stickID = Entities.addEntity({ @@ -19,12 +23,14 @@ Script.setTimeout(function() { modelURL: "https://hifi-public.s3.amazonaws.com/eric/models/stick.fbx", compoundShapeURL: "https://hifi-public.s3.amazonaws.com/eric/models/stick.obj", dimensions: {x: .11, y: .11, z: .59}, - position: MyAvatar.getRightPalmPosition(), + position: MyAvatar.getRightPalmPosition(), // initial position doesn't matter, as long as it's close rotation: MyAvatar.orientation, damping: .1, collisionsWillMove: true }); - actionID = Entities.addAction("hold", stickID, {relativePosition: {x: 0.0, y: 0.0, z: -0.9}, timeScale: 0.15}); + actionID = Entities.addAction("hold", stickID, {relativePosition: {x: 0.0, y: 0.0, z: -0.9}, + hand: hand, + timeScale: 0.15}); }, 3000); @@ -32,11 +38,20 @@ function cleanUp() { Entities.deleteEntity(stickID); } + +function positionStick(stickOrientation) { + var baseOffset = {x: 0.0, y: 0.0, z: -0.9}; + var offset = Vec3.multiplyQbyV(stickOrientation, baseOffset); + Entities.updateAction(stickID, actionID, {relativePosition: offset, + relativeRotation: stickOrientation}); +} + + function mouseMoveEvent(event) { - if (!stickID || actionID == "00000000-0000-0000-0000-000000000000") { + if (!stickID || actionID == nullActionID) { return; } - var windowCenterX = Window.innerWidth/ 2; + var windowCenterX = Window.innerWidth / 2; var windowCenterY = Window.innerHeight / 2; var mouseXCenterOffset = event.x - windowCenterX; var mouseYCenterOffset = event.y - windowCenterY; @@ -44,13 +59,34 @@ function mouseMoveEvent(event) { var mouseYRatio = mouseYCenterOffset / windowCenterY; var stickOrientation = Quat.fromPitchYawRollDegrees(mouseYRatio * -90, mouseXRatio * -90, 0); - var baseOffset = {x: 0.0, y: 0.0, z: -0.9}; - var offset = Vec3.multiplyQbyV(stickOrientation, baseOffset); - - Entities.updateAction(stickID, actionID, {relativePosition: offset, - relativeRotation: stickOrientation, - timeScale: 0.15}); + positionStick(stickOrientation); } + +function initControls(){ + if (hand == "right") { + controllerID = 3; // right handed + } else { + controllerID = 4; // left handed + } +} + + +function update(deltaTime){ + var palmPosition = Controller.getSpatialControlPosition(controllerID); + controllerActive = (Vec3.length(palmPosition) > 0); + if(!controllerActive){ + return; + } + + stickOrientation = Controller.getSpatialControlRawRotation(controllerID); + var adjustment = Quat.fromPitchYawRollDegrees(180, 0, 0); + stickOrientation = Quat.multiply(stickOrientation, adjustment); + + positionStick(stickOrientation); +} + + Script.scriptEnding.connect(cleanUp); Controller.mouseMoveEvent.connect(mouseMoveEvent); +Script.update.connect(update); diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index 74a583df58..1fbb01beb3 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -29,7 +29,12 @@ AvatarActionHold::~AvatarActionHold() { void AvatarActionHold::updateActionWorker(float deltaTimeStep) { auto myAvatar = DependencyManager::get()->getMyAvatar(); - glm::vec3 palmPosition = myAvatar->getRightPalmPosition(); + glm::vec3 palmPosition; + if (_hand == "right") { + palmPosition = myAvatar->getRightPalmPosition(); + } else { + palmPosition = myAvatar->getLeftPalmPosition(); + } auto rotation = myAvatar->getWorldAlignedOrientation(); auto offset = rotation * _relativePosition; @@ -55,28 +60,46 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) { bool tSOk = true; float timeScale = EntityActionInterface::extractFloatArgument("hold", arguments, "timeScale", tSOk, false); + bool hOk = true; + QString hand = + EntityActionInterface::extractStringArgument("hold", arguments, "hand", hOk, false); lockForWrite(); if (rPOk) { _relativePosition = relativePosition; - } else { + } else if (!_parametersSet) { _relativePosition = glm::vec3(0.0f, 0.0f, 1.0f); } if (rROk) { _relativeRotation = relativeRotation; - } else { + } else if (!_parametersSet) { _relativeRotation = glm::quat(0.0f, 0.0f, 0.0f, 1.0f); } if (tSOk) { _linearTimeScale = timeScale; _angularTimeScale = timeScale; - } else { + } else if (!_parametersSet) { _linearTimeScale = 0.2; _angularTimeScale = 0.2; } + if (hOk) { + hand = hand.toLower(); + if (hand == "left") { + _hand = "left"; + } else if (hand == "right") { + _hand = "right"; + } else { + qDebug() << "hold action -- invalid hand argument:" << hand; + _hand = "right"; + } + } else if (!_parametersSet) { + _hand = "right"; + } + + _parametersSet = true; _positionalTargetSet = true; _rotationalTargetSet = true; _active = true; diff --git a/interface/src/avatar/AvatarActionHold.h b/interface/src/avatar/AvatarActionHold.h index f92ea94aaa..705c751029 100644 --- a/interface/src/avatar/AvatarActionHold.h +++ b/interface/src/avatar/AvatarActionHold.h @@ -28,6 +28,8 @@ public: private: glm::vec3 _relativePosition; glm::quat _relativeRotation; + QString _hand; + bool _parametersSet = false; }; #endif // hifi_AvatarActionHold_h diff --git a/libraries/entities/src/EntityActionInterface.cpp b/libraries/entities/src/EntityActionInterface.cpp index b293d609c2..e97669686c 100644 --- a/libraries/entities/src/EntityActionInterface.cpp +++ b/libraries/entities/src/EntityActionInterface.cpp @@ -91,7 +91,6 @@ glm::vec3 EntityActionInterface::extractVec3Argument(QString objectName, QVarian return glm::vec3(x, y, z); } - glm::quat EntityActionInterface::extractQuatArgument(QString objectName, QVariantMap arguments, QString argumentName, bool& ok, bool required) { if (!arguments.contains(argumentName)) { @@ -139,8 +138,6 @@ glm::quat EntityActionInterface::extractQuatArgument(QString objectName, QVarian return glm::quat(w, x, y, z); } - - float EntityActionInterface::extractFloatArgument(QString objectName, QVariantMap arguments, QString argumentName, bool& ok, bool required) { if (!arguments.contains(argumentName)) { @@ -162,3 +159,18 @@ float EntityActionInterface::extractFloatArgument(QString objectName, QVariantMa return v; } + +QString EntityActionInterface::extractStringArgument(QString objectName, QVariantMap arguments, + QString argumentName, bool& ok, bool required) { + if (!arguments.contains(argumentName)) { + if (required) { + qDebug() << objectName << "requires argument:" << argumentName; + } + ok = false; + return ""; + } + + QVariant vV = arguments[argumentName]; + QString v = vV.toString(); + return v; +} diff --git a/libraries/entities/src/EntityActionInterface.h b/libraries/entities/src/EntityActionInterface.h index 457db8f5c3..486d3f5948 100644 --- a/libraries/entities/src/EntityActionInterface.h +++ b/libraries/entities/src/EntityActionInterface.h @@ -59,6 +59,9 @@ protected: QString argumentName, bool& ok, bool required = true); static float extractFloatArgument(QString objectName, QVariantMap arguments, QString argumentName, bool& ok, bool required = true); + static QString extractStringArgument(QString objectName, QVariantMap arguments, + QString argumentName, bool& ok, bool required = true); + }; From bc206633f5b1916a009fad216ee56332065349d3 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 11 Jun 2015 14:56:21 -0700 Subject: [PATCH 36/41] Fixing overlay rendering in HMD --- interface/resources/images/arrow.png | Bin 0 -> 3562 bytes interface/src/ui/ApplicationOverlay.cpp | 342 ++++++------------------ interface/src/ui/ApplicationOverlay.h | 2 +- 3 files changed, 78 insertions(+), 266 deletions(-) create mode 100644 interface/resources/images/arrow.png diff --git a/interface/resources/images/arrow.png b/interface/resources/images/arrow.png new file mode 100644 index 0000000000000000000000000000000000000000..408881b5856eb47a30875e3e56d5864721161587 GIT binary patch literal 3562 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 R^nCyTaI$x`E7(oB_+R=!c8UN1 literal 0 HcmV?d00001 diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index b4824e96a3..e5f701e807 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -120,27 +120,6 @@ bool raySphereIntersect(const glm::vec3 &dir, const glm::vec3 &origin, float r, } } -void ApplicationOverlay::renderReticle(glm::quat orientation, float alpha) { - glPushMatrix(); { - glm::vec3 axis = glm::axis(orientation); - glRotatef(glm::degrees(glm::angle(orientation)), axis.x, axis.y, axis.z); - glm::vec3 topLeft = getPoint(reticleSize / 2.0f, -reticleSize / 2.0f); - glm::vec3 topRight = getPoint(-reticleSize / 2.0f, -reticleSize / 2.0f); - glm::vec3 bottomLeft = getPoint(reticleSize / 2.0f, reticleSize / 2.0f); - glm::vec3 bottomRight = getPoint(-reticleSize / 2.0f, reticleSize / 2.0f); - - // TODO: this version of renderQuad() needs to take a color - glm::vec4 reticleColor = { RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2], alpha }; - - - - DependencyManager::get()->renderQuad(topLeft, bottomLeft, bottomRight, topRight, - glm::vec2(0.0f, 0.0f), glm::vec2(1.0f, 0.0f), - glm::vec2(1.0f, 1.0f), glm::vec2(0.0f, 1.0f), - reticleColor, _reticleQuad); - } glPopMatrix(); -} - ApplicationOverlay::ApplicationOverlay() : _textureFov(glm::radians(DEFAULT_HMD_UI_ANGULAR_SIZE)), _textureAspectRatio(1.0f), @@ -307,7 +286,7 @@ void ApplicationOverlay::displayOverlayTexture(RenderArgs* renderArgs) { if (!_crosshairTexture) { _crosshairTexture = TextureCache::getImageTexture( - PathUtils::resourcesPath() + "images/sixense-reticle.png"); + PathUtils::resourcesPath() + "images/arrow.png"); } @@ -349,6 +328,40 @@ static gpu::BufferPointer _hemiVertices; static gpu::BufferPointer _hemiIndices; static int _hemiIndexCount{ 0 }; +glm::vec2 getPolarCoordinates(const PalmData& palm) { + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + auto avatarOrientation = myAvatar->getOrientation(); + auto eyePos = myAvatar->getDefaultEyePosition(); + glm::vec3 tip = myAvatar->getLaserPointerTipPosition(&palm); + // Direction of the tip relative to the eye + glm::vec3 tipDirection = tip - eyePos; + // orient into avatar space + tipDirection = glm::inverse(avatarOrientation) * tipDirection; + // Normalize for trig functions + tipDirection = glm::normalize(tipDirection); + // Convert to polar coordinates + glm::vec2 polar(glm::atan(tipDirection.x, -tipDirection.z), glm::asin(tipDirection.y)); + return polar; +} + +void ApplicationOverlay::renderReticle(gpu::Batch& batch, glm::quat orientation, float alpha) { + if (!_crosshairTexture) { + _crosshairTexture = TextureCache::getImageTexture( + PathUtils::resourcesPath() + "images/arrow.png"); + } + + batch.setUniformTexture(0, _crosshairTexture); + glm::vec4 reticleColor = { RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2], alpha }; + + vec3 dist = getPoint(0, 0); + mat4 reticleXfm = glm::translate(mat4(), vec3(0, 0, -1)); + reticleXfm = glm::mat4_cast(orientation) * reticleXfm; + reticleXfm = glm::scale(reticleXfm, vec3(reticleSize, reticleSize, 1.0f)); + batch.setModelTransform(Transform(reticleXfm)); + DependencyManager::get()->renderUnitQuad(batch, glm::vec4(1), _reticleQuad); +} + + // Draws the FBO texture for Oculus rift. void ApplicationOverlay::displayOverlayTextureHmd(RenderArgs* renderArgs, Camera& whichCamera) { if (_alpha == 0.0f) { @@ -357,176 +370,60 @@ void ApplicationOverlay::displayOverlayTextureHmd(RenderArgs* renderArgs, Camera renderArgs->_context->syncCache(); - auto geometryCache = DependencyManager::get(); gpu::Batch batch; batch.setPipeline(getDrawPipeline()); batch._glDisable(GL_DEPTH_TEST); + batch._glDisable(GL_CULL_FACE); batch._glBindTexture(GL_TEXTURE_2D, _framebufferObject->texture()); batch._glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); batch._glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); batch.setProjectionTransform(whichCamera.getProjection()); batch.setViewTransform(Transform()); - Transform mv; - mv.setTranslation(vec3(0.0f, 0.0f, -1.0f)); - mv.preRotate(glm::inverse(qApp->getCamera()->getHmdRotation())); - mv.setScale(vec3(1.0f, 1.0f / aspect(qApp->getCanvasSize()), 1.0f)); - batch.setModelTransform(mv); + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + const quat& avatarOrientation = myAvatar->getOrientation(); + quat hmdOrientation = qApp->getCamera()->getHmdRotation(); + vec3 hmdPosition = glm::inverse(avatarOrientation) * qApp->getCamera()->getHmdPosition(); + mat4 overlayXfm = glm::mat4_cast(glm::inverse(hmdOrientation)) * glm::translate(mat4(), -hmdPosition); + batch.setModelTransform(Transform(overlayXfm)); + drawSphereSection(batch); - // FIXME doesn't work - // drawSphereSection(batch); + if (!_crosshairTexture) { + _crosshairTexture = TextureCache::getImageTexture( + PathUtils::resourcesPath() + "images/arrow.png"); + } + batch.setUniformTexture(0, _crosshairTexture); + auto geometryCache = DependencyManager::get(); + //Controller Pointers + for (int i = 0; i < (int)myAvatar->getHand()->getNumPalms(); i++) { + PalmData& palm = myAvatar->getHand()->getPalms()[i]; + if (palm.isActive()) { + glm::vec2 polar = getPolarCoordinates(palm); + // Convert to quaternion + mat4 pointerXfm = glm::mat4_cast(quat(vec3(polar.y, -polar.x, 0.0f))) * glm::translate(mat4(), vec3(0, 0, -1)); + mat4 reticleXfm = overlayXfm * pointerXfm; + reticleXfm = glm::scale(reticleXfm, vec3(reticleSize, reticleSize, 1.0f)); + batch.setModelTransform(reticleXfm); + // Render reticle at location + geometryCache->renderUnitQuad(batch, glm::vec4(1), _reticleQuad); + } + } - // works... - geometryCache->renderUnitQuad(batch, vec4(vec3(1), _alpha)); - - // sort of works, renders a semi-transparent red quad - // geometryCache->renderSolidCube(batch, 1.0f, vec4(1)); + //Mouse Pointer + if (_reticleActive[MOUSE]) { + glm::vec2 projection = screenToSpherical(glm::vec2(_reticlePosition[MOUSE].x(), + _reticlePosition[MOUSE].y())); + mat4 pointerXfm = glm::mat4_cast(quat(vec3(-projection.y, projection.x, 0.0f))) * glm::translate(mat4(), vec3(0, 0, -1)); + mat4 reticleXfm = overlayXfm * pointerXfm; + reticleXfm = glm::scale(reticleXfm, vec3(reticleSize, reticleSize, 1.0f)); + batch.setModelTransform(reticleXfm); + geometryCache->renderUnitQuad(batch, glm::vec4(1), _reticleQuad); + } renderArgs->_context->render(batch); - - /* - // The camera here contains only the head pose relative to the avatar position - vec3 pos = whichCamera.getPosition(); - quat rot = whichCamera.getOrientation(); - mat4 overlayXfm = glm::translate(glm::mat4(), pos) * glm::mat4_cast(rot); - - glLoadMatrixf(glm::value_ptr(glm::inverse(overlayXfm))); - glBindTexture(GL_TEXTURE_2D, _framebufferObject->texture()); - _overlays.render(); - */ - - //Update and draw the magnifiers - /* - // FIXME Mangifiers need to be re-thought - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - const float scale = myAvatar->getScale() * _oculusUIRadius; - overlayXfm = glm::scale(overlayXfm, vec3(scale)); - for (int i = 0; i < NUMBER_OF_RETICLES; i++) { - if (_magActive[i]) { - _magSizeMult[i] += MAG_SPEED; - if (_magSizeMult[i] > 1.0f) { - _magSizeMult[i] = 1.0f; - } - } else { - _magSizeMult[i] -= MAG_SPEED; - if (_magSizeMult[i] < 0.0f) { - _magSizeMult[i] = 0.0f; - } - } - - if (_magSizeMult[i] > 0.0f) { - //Render magnifier, but dont show border for mouse magnifier - glm::vec2 projection = screenToOverlay(glm::vec2(_reticlePosition[MOUSE].x(), - _reticlePosition[MOUSE].y())); - with_each_texture(_overlays.getTexture(), 0, [&] { - renderMagnifier(projection, _magSizeMult[i], i != MOUSE); - }); - } - } - - if (!Application::getInstance()->isMouseHidden()) { - renderPointersOculus(); - } - */ } -/* -// Draws the FBO texture for 3DTV. -void ApplicationOverlay::displayOverlayTextureStereo(Camera& whichCamera, float aspectRatio, float fov) { - if (_alpha == 0.0f) { - return; - } - - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - const glm::vec3& viewMatrixTranslation = qApp->getViewMatrixTranslation(); - - glEnable(GL_BLEND); - glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); - glEnable(GL_DEPTH_TEST); - glDisable(GL_LIGHTING); - - glMatrixMode(GL_MODELVIEW); - - glPushMatrix(); - glLoadIdentity(); - // Transform to world space - glm::quat rotation = whichCamera.getRotation(); - glm::vec3 axis2 = glm::axis(rotation); - glRotatef(-glm::degrees(glm::angle(rotation)), axis2.x, axis2.y, axis2.z); - glTranslatef(viewMatrixTranslation.x, viewMatrixTranslation.y, viewMatrixTranslation.z); - - // Translate to the front of the camera - glm::vec3 pos = whichCamera.getPosition(); - glm::quat rot = myAvatar->getOrientation(); - glm::vec3 axis = glm::axis(rot); - - glTranslatef(pos.x, pos.y, pos.z); - glRotatef(glm::degrees(glm::angle(rot)), axis.x, axis.y, axis.z); - - glm::vec4 overlayColor = {1.0f, 1.0f, 1.0f, _alpha}; - - //Render - const GLfloat distance = 1.0f; - - const GLfloat halfQuadHeight = distance * tan(fov); - const GLfloat halfQuadWidth = halfQuadHeight * aspectRatio; - const GLfloat quadWidth = halfQuadWidth * 2.0f; - const GLfloat quadHeight = halfQuadHeight * 2.0f; - - GLfloat x = -halfQuadWidth; - GLfloat y = -halfQuadHeight; - glDisable(GL_DEPTH_TEST); - - with_each_texture(_overlays.getTexture(), _newUiTexture, [&] { - DependencyManager::get()->renderQuad(glm::vec3(x, y + quadHeight, -distance), - glm::vec3(x + quadWidth, y + quadHeight, -distance), - glm::vec3(x + quadWidth, y, -distance), - glm::vec3(x, y, -distance), - glm::vec2(0.0f, 1.0f), glm::vec2(1.0f, 1.0f), - glm::vec2(1.0f, 0.0f), glm::vec2(0.0f, 0.0f), - overlayColor); - }); - - if (!_crosshairTexture) { - _crosshairTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + - "images/sixense-reticle.png"); - } - - //draw the mouse pointer - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, gpu::GLBackend::getTextureID(_crosshairTexture)); - glm::vec2 canvasSize = qApp->getCanvasSize(); - const float reticleSize = 40.0f / canvasSize.x * quadWidth; - x -= reticleSize / 2.0f; - y += reticleSize / 2.0f; - const float mouseX = (qApp->getMouseX() / (float)canvasSize.x) * quadWidth; - const float mouseY = (1.0 - (qApp->getMouseY() / (float)canvasSize.y)) * quadHeight; - - glm::vec4 reticleColor = { RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2], 1.0f }; - - DependencyManager::get()->renderQuad(glm::vec3(x + mouseX, y + mouseY, -distance), - glm::vec3(x + mouseX + reticleSize, y + mouseY, -distance), - glm::vec3(x + mouseX + reticleSize, y + mouseY - reticleSize, -distance), - glm::vec3(x + mouseX, y + mouseY - reticleSize, -distance), - glm::vec2(0.0f, 0.0f), glm::vec2(1.0f, 0.0f), - glm::vec2(1.0f, 1.0f), glm::vec2(0.0f, 1.0f), - reticleColor, _reticleQuad); - - glEnable(GL_DEPTH_TEST); - - glPopMatrix(); - - glDepthMask(GL_TRUE); - glBindTexture(GL_TEXTURE_2D, 0); - glDisable(GL_TEXTURE_2D); - - glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); - glEnable(GL_LIGHTING); -} -*/ void ApplicationOverlay::computeHmdPickRay(glm::vec2 cursorPos, glm::vec3& origin, glm::vec3& direction) const { cursorPos *= qApp->getCanvasSize(); @@ -556,22 +453,6 @@ void ApplicationOverlay::computeHmdPickRay(glm::vec2 cursorPos, glm::vec3& origi direction = glm::normalize(intersectionWithUi - origin); } -glm::vec2 getPolarCoordinates(const PalmData& palm) { - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - auto avatarOrientation = myAvatar->getOrientation(); - auto eyePos = myAvatar->getDefaultEyePosition(); - glm::vec3 tip = myAvatar->getLaserPointerTipPosition(&palm); - // Direction of the tip relative to the eye - glm::vec3 tipDirection = tip - eyePos; - // orient into avatar space - tipDirection = glm::inverse(avatarOrientation) * tipDirection; - // Normalize for trig functions - tipDirection = glm::normalize(tipDirection); - // Convert to polar coordinates - glm::vec2 polar(glm::atan(tipDirection.x, -tipDirection.z), glm::asin(tipDirection.y)); - return polar; -} - //Caculate the click location using one of the sixense controllers. Scale is not applied QPoint ApplicationOverlay::getPalmClickLocation(const PalmData *palm) const { QPoint rv; @@ -624,7 +505,7 @@ bool ApplicationOverlay::calculateRayUICollisionPoint(const glm::vec3& position, void ApplicationOverlay::renderPointers() { //lazily load crosshair texture if (_crosshairTexture == 0) { - _crosshairTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/sixense-reticle.png"); + _crosshairTexture = TextureCache::getImageTexture(PathUtils::resourcesPath() + "images/arrow.png"); } glEnable(GL_TEXTURE_2D); glEnable(GL_BLEND); @@ -790,43 +671,6 @@ void ApplicationOverlay::renderControllerPointers() { } } -void ApplicationOverlay::renderPointersOculus() { - - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glEnable(GL_TEXTURE_2D); - - glBindTexture(GL_TEXTURE_2D, gpu::GLBackend::getTextureID(_crosshairTexture)); - glDisable(GL_DEPTH_TEST); - - glMatrixMode(GL_MODELVIEW); - - //Controller Pointers - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - for (int i = 0; i < (int)myAvatar->getHand()->getNumPalms(); i++) { - PalmData& palm = myAvatar->getHand()->getPalms()[i]; - if (palm.isActive()) { - glm::vec2 polar = getPolarCoordinates(palm); - // Convert to quaternion - glm::quat orientation = glm::quat(glm::vec3(polar.y, -polar.x, 0.0f)); - // Render reticle at location - renderReticle(orientation, _alpha); - } - } - - //Mouse Pointer - if (_reticleActive[MOUSE]) { - glm::vec2 projection = screenToSpherical(glm::vec2(_reticlePosition[MOUSE].x(), - _reticlePosition[MOUSE].y())); - glm::quat orientation(glm::vec3(-projection.y, projection.x, 0.0f)); - renderReticle(orientation, _alpha); - } - - glEnable(GL_DEPTH_TEST); - glDisable(GL_TEXTURE_2D); - glDisable(GL_BLEND); -} - //Renders a small magnification of the currently bound texture at the coordinates void ApplicationOverlay::renderMagnifier(glm::vec2 magPos, float sizeMult, bool showBorder) { if (!_magnifier) { @@ -1111,7 +955,7 @@ void ApplicationOverlay::renderDomainConnectionStatusBorder() { void ApplicationOverlay::buildHemiVertices( const float fov, const float aspectRatio, const int slices, const int stacks) { static float textureFOV = 0.0f, textureAspectRatio = 1.0f; - if (_hemiVerticesID != GeometryCache::UNKNOWN_ID && textureFOV == fov && textureAspectRatio == aspectRatio) { + if (textureFOV == fov && textureAspectRatio == aspectRatio) { return; } @@ -1119,9 +963,6 @@ void ApplicationOverlay::buildHemiVertices( textureAspectRatio = aspectRatio; auto geometryCache = DependencyManager::get(); - if (_hemiVerticesID == GeometryCache::UNKNOWN_ID) { - _hemiVerticesID = geometryCache->allocateID(); - } _hemiVertices = gpu::BufferPointer(new gpu::Buffer()); _hemiIndices = gpu::BufferPointer(new gpu::Buffer()); @@ -1187,8 +1028,8 @@ void ApplicationOverlay::drawSphereSection(gpu::Batch& batch) { static const int COLOR_DATA_SLOT = 2; gpu::Stream::FormatPointer streamFormat(new gpu::Stream::Format()); // 1 for everyone streamFormat->setAttribute(gpu::Stream::POSITION, VERTEX_DATA_SLOT, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0); - streamFormat->setAttribute(gpu::Stream::TEXCOORD, TEXTURE_DATA_SLOT, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), sizeof(vec3)); - streamFormat->setAttribute(gpu::Stream::COLOR, COLOR_DATA_SLOT, gpu::Element(gpu::VEC4, gpu::FLOAT, gpu::RGBA), sizeof(vec3) + sizeof(vec2)); + streamFormat->setAttribute(gpu::Stream::TEXCOORD, TEXTURE_DATA_SLOT, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV)); + streamFormat->setAttribute(gpu::Stream::COLOR, COLOR_DATA_SLOT, gpu::Element(gpu::VEC4, gpu::FLOAT, gpu::RGBA)); batch.setInputFormat(streamFormat); static const int VERTEX_STRIDE = sizeof(vec3) + sizeof(vec2) + sizeof(vec4); @@ -1230,35 +1071,6 @@ void ApplicationOverlay::buildFramebufferObject() { glBindTexture(GL_TEXTURE_2D, 0); } -/* -//Renders a hemisphere with texture coordinates. -void ApplicationOverlay::TexturedHemisphere::render() { - if (_vbo.first == 0 || _vbo.second == 0) { - qDebug() << "TexturedHemisphere::render(): Incorrect initialisation"; - return; - } - - glBindBuffer(GL_ARRAY_BUFFER, _vbo.first); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _vbo.second); - - glEnableClientState(GL_VERTEX_ARRAY); - glEnableClientState(GL_TEXTURE_COORD_ARRAY); - - static const int STRIDE = sizeof(TextureVertex); - static const void* VERTEX_POINTER = 0; - static const void* TEX_COORD_POINTER = (void*)sizeof(glm::vec3); - glVertexPointer(3, GL_FLOAT, STRIDE, VERTEX_POINTER); - glTexCoordPointer(2, GL_FLOAT, STRIDE, TEX_COORD_POINTER); - - glDrawRangeElements(GL_TRIANGLES, 0, _vertices - 1, _indices, GL_UNSIGNED_SHORT, 0); - - glDisableClientState(GL_VERTEX_ARRAY); - glDisableClientState(GL_TEXTURE_COORD_ARRAY); - - glBindBuffer(GL_ARRAY_BUFFER, 0); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); -} -*/ glm::vec2 ApplicationOverlay::directionToSpherical(const glm::vec3& direction) { glm::vec2 result; // Compute yaw diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index d78ad4bc9f..609bef745d 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -72,7 +72,7 @@ private: float _hmdUIAngularSize = DEFAULT_HMD_UI_ANGULAR_SIZE; QOpenGLFramebufferObject* _framebufferObject; - void renderReticle(glm::quat orientation, float alpha); + void renderReticle(gpu::Batch& batch, glm::quat orientation, float alpha); void renderPointers(); void renderMagnifier(glm::vec2 magPos, float sizeMult, bool showBorder); From d3cdbc389a4c7d3fda287ebe4ac440b0ccb4e051 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 11 Jun 2015 15:05:08 -0700 Subject: [PATCH 37/41] Code cleanup --- interface/src/devices/OculusManager.cpp | 24 ------------------------ interface/src/ui/ApplicationOverlay.cpp | 21 +-------------------- 2 files changed, 1 insertion(+), 44 deletions(-) diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp index 2ee62c85f3..414c7f6199 100644 --- a/interface/src/devices/OculusManager.cpp +++ b/interface/src/devices/OculusManager.cpp @@ -629,35 +629,11 @@ void OculusManager::display(QGLWidget * glCanvas, RenderArgs* renderArgs, const finalFbo = DependencyManager::get()->getPrimaryFramebuffer(); glBindFramebuffer(GL_FRAMEBUFFER, 0); } - - //glBindFramebuffer(GL_DRAW_FRAMEBUFFER, gpu::GLBackend::getFramebufferID(finalFbo)); - ////Render each eye into an fbo - //for_each_eye(_ovrHmd, [&](ovrEyeType eye) { - // _activeEye = eye; - // // Update our camera to what the application camera is doing - // _camera->setRotation(toGlm(eyeRenderPose[eye].Orientation)); - // _camera->setPosition(toGlm(eyeRenderPose[eye].Position)); - // configureCamera(*_camera); - // glMatrixMode(GL_PROJECTION); - // glLoadMatrixf(glm::value_ptr(_camera->getProjection())); - - // glMatrixMode(GL_MODELVIEW); - // glLoadIdentity(); - - // ovrRecti & vp = _eyeTextures[eye].Header.RenderViewport; - // vp.Size.h = _recommendedTexSize.h * _offscreenRenderScale; - // vp.Size.w = _recommendedTexSize.w * _offscreenRenderScale; - - // glViewport(vp.Pos.x, vp.Pos.y, vp.Size.w, vp.Size.h); - //}); - //glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); - // restore our normal viewport glViewport(0, 0, deviceSize.width(), deviceSize.height()); diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index e5f701e807..887af89cd0 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -242,24 +242,6 @@ void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) { _framebufferObject->release(); } -// A quick and dirty solution for compositing the old overlay -// texture with the new one -//template -//void with_each_texture(GLuint firstPassTexture, GLuint secondPassTexture, F f) { -// glEnable(GL_TEXTURE_2D); -// glActiveTexture(GL_TEXTURE0); -// if (firstPassTexture) { -// glBindTexture(GL_TEXTURE_2D, firstPassTexture); -// f(); -// } -// //if (secondPassTexture) { -// // glBindTexture(GL_TEXTURE_2D, secondPassTexture); -// // f(); -// //} -// glBindTexture(GL_TEXTURE_2D, 0); -// glDisable(GL_TEXTURE_2D); -//} - gpu::PipelinePointer ApplicationOverlay::getDrawPipeline() { if (!_standardDrawPipeline) { auto vs = gpu::ShaderPointer(gpu::Shader::createVertex(std::string(standardTransformPNTC_vert))); @@ -977,7 +959,6 @@ void ApplicationOverlay::buildHemiVertices( vec3 pos; // Compute vertices positions and texture UV coordinate // Create and write to buffer - //_hemiVertices->(sizeof(vec3) + sizeof(vec2) + sizeof(vec4)) * stacks * slices); for (int i = 0; i < stacks; i++) { float stacksRatio = (float)i / (float)(stacks - 1); // First stack is 0.0f, last stack is 1.0f // abs(theta) <= fov / 2.0f @@ -1052,7 +1033,7 @@ void ApplicationOverlay::buildFramebufferObject() { auto canvasSize = qApp->getCanvasSize(); QSize fboSize = QSize(canvasSize.x, canvasSize.y); if (_framebufferObject != NULL && fboSize == _framebufferObject->size()) { - // Already build + // Already built return; } From 92f22bc6d52bde359f7b6fc8baa0678e139ba311 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 11 Jun 2015 15:40:16 -0700 Subject: [PATCH 38/41] Magic number and build errors --- interface/src/ui/ApplicationOverlay.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 887af89cd0..f6dd4f5773 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -260,6 +260,8 @@ gpu::PipelinePointer ApplicationOverlay::getDrawPipeline() { return _standardDrawPipeline; } +#define CURSOR_PIXEL_SIZE 32.0f + // Draws the FBO texture for the screen void ApplicationOverlay::displayOverlayTexture(RenderArgs* renderArgs) { if (_alpha == 0.0f) { @@ -296,7 +298,7 @@ void ApplicationOverlay::displayOverlayTexture(RenderArgs* renderArgs) { mousePosition -= 1.0f; mousePosition.y *= -1.0f; model.setTranslation(vec3(mousePosition, 0)); - glm::vec2 mouseSize = 32.0f / canvasSize; + glm::vec2 mouseSize = CURSOR_PIXEL_SIZE / canvasSize; model.setScale(vec3(mouseSize, 1.0f)); batch.setModelTransform(model); batch.setUniformTexture(0, _crosshairTexture); @@ -957,20 +959,22 @@ void ApplicationOverlay::buildHemiVertices( //UV mapping source: http://www.mvps.org/directx/articles/spheremap.htm vec3 pos; + vec2 uv; // Compute vertices positions and texture UV coordinate // Create and write to buffer for (int i = 0; i < stacks; i++) { - float stacksRatio = (float)i / (float)(stacks - 1); // First stack is 0.0f, last stack is 1.0f + uv.y = (float)i / (float)(stacks - 1); // First stack is 0.0f, last stack is 1.0f // abs(theta) <= fov / 2.0f - float pitch = -fov * (stacksRatio - 0.5f); + float pitch = -fov * (uv.y - 0.5f); for (int j = 0; j < slices; j++) { - float slicesRatio = (float)j / (float)(slices - 1); // First slice is 0.0f, last slice is 1.0f + uv.x = (float)j / (float)(slices - 1); // First slice is 0.0f, last slice is 1.0f // abs(phi) <= fov * aspectRatio / 2.0f - float yaw = -fov * aspectRatio * (slicesRatio - 0.5f); + float yaw = -fov * aspectRatio * (uv.x - 0.5f); pos = getPoint(yaw, pitch); + static const vec4 color(1); _hemiVertices->append(sizeof(pos), (gpu::Byte*)&pos); - _hemiVertices->append(sizeof(vec2), (gpu::Byte*)&vec2(slicesRatio, stacksRatio)); - _hemiVertices->append(sizeof(vec4), (gpu::Byte*)&vec4(1)); + _hemiVertices->append(sizeof(vec2), (gpu::Byte*)&uv); + _hemiVertices->append(sizeof(vec4), (gpu::Byte*)&color); } } From 09cfe004a18ab8c9b5622eb6d33dc22a41b7cf36 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 11 Jun 2015 16:55:23 -0700 Subject: [PATCH 39/41] Combining with new cursor render code --- interface/src/ui/ApplicationOverlay.cpp | 16 ++++++++-------- libraries/ui/src/CursorManager.cpp | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 971958ae61..ba0d3a60a9 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -210,7 +210,7 @@ void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) { overlays.renderHUD(renderArgs); - //renderPointers(); + renderPointers(); renderDomainConnectionStatusBorder(); if (_newUiTexture) { @@ -268,7 +268,7 @@ void ApplicationOverlay::bindCursorTexture(gpu::Batch& batch, uint8_t cursorInde _cursors[iconId] = DependencyManager::get()-> getImageTexture(iconPath); } - glBindTexture(GL_TEXTURE_2D, gpu::GLBackend::getTextureID(_cursors[iconId])); + batch.setUniformTexture(0, _cursors[iconId]); } #define CURSOR_PIXEL_SIZE 32.0f @@ -471,11 +471,11 @@ bool ApplicationOverlay::calculateRayUICollisionPoint(const glm::vec3& position, //Renders optional pointers void ApplicationOverlay::renderPointers() { - glEnable(GL_TEXTURE_2D); - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + //glEnable(GL_TEXTURE_2D); + //glEnable(GL_BLEND); + //glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glActiveTexture(GL_TEXTURE0); + //glActiveTexture(GL_TEXTURE0); //bindCursorTexture(); if (qApp->isHMDMode() && !qApp->getLastMouseMoveWasSimulated() && !qApp->isMouseHidden()) { @@ -521,8 +521,8 @@ void ApplicationOverlay::renderPointers() { _magActive[MOUSE] = false; renderControllerPointers(); } - glBindTexture(GL_TEXTURE_2D, 0); - glDisable(GL_TEXTURE_2D); + //glBindTexture(GL_TEXTURE_2D, 0); + //glDisable(GL_TEXTURE_2D); } void ApplicationOverlay::renderControllerPointers() { diff --git a/libraries/ui/src/CursorManager.cpp b/libraries/ui/src/CursorManager.cpp index efadd09142..38c746994d 100644 --- a/libraries/ui/src/CursorManager.cpp +++ b/libraries/ui/src/CursorManager.cpp @@ -49,7 +49,7 @@ namespace Cursor { static uint16_t _customIconId = Icon::USER_BASE; Manager::Manager() { - ICONS[Icon::DEFAULT] = PathUtils::resourcesPath() + "images/sixense-reticle.png"; + ICONS[Icon::DEFAULT] = PathUtils::resourcesPath() + "images/arrow.png"; ICONS[Icon::LINK] = PathUtils::resourcesPath() + "images/reticleLink.png"; } From 81460e48f46115ff1f3fb3b22c711e2a2126cb6c Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 11 Jun 2015 17:25:25 -0700 Subject: [PATCH 40/41] fix for sticking windows audio-mixer --- assignment-client/src/AssignmentClient.cpp | 3 +++ .../src/AssignmentClientMonitor.cpp | 24 +++++++++---------- assignment-client/src/audio/AudioMixer.cpp | 2 ++ 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index cfee18941c..e125a44783 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -66,6 +66,9 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri // set the logging target to the the CHILD_TARGET_NAME LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_TARGET_NAME); + // make sure we output process IDs for a child AC otherwise it's insane to parse + LogHandler::getInstance().setShouldOutputPID(true); + // setup our _requestAssignment member variable from the passed arguments _requestAssignment = Assignment(Assignment::RequestCommand, requestAssignmentType, assignmentPool); diff --git a/assignment-client/src/AssignmentClientMonitor.cpp b/assignment-client/src/AssignmentClientMonitor.cpp index d19eb90df6..8c6478b59f 100644 --- a/assignment-client/src/AssignmentClientMonitor.cpp +++ b/assignment-client/src/AssignmentClientMonitor.cpp @@ -39,9 +39,9 @@ AssignmentClientMonitor::AssignmentClientMonitor(const unsigned int numAssignmen _walletUUID(walletUUID), _assignmentServerHostname(assignmentServerHostname), _assignmentServerPort(assignmentServerPort) -{ +{ qDebug() << "_requestAssignmentType =" << _requestAssignmentType; - + // start the Logging class with the parent's target name LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_MONITOR_TARGET_NAME); @@ -77,13 +77,13 @@ void AssignmentClientMonitor::simultaneousWaitOnChildren(int waitMsecs) { while(_childProcesses.size() > 0 && !waitTimer.hasExpired(waitMsecs)) { // continue processing events so we can handle a process finishing up QCoreApplication::processEvents(); - } + } } void AssignmentClientMonitor::childProcessFinished() { QProcess* childProcess = qobject_cast(sender()); qint64 processID = _childProcesses.key(childProcess); - + if (processID > 0) { qDebug() << "Child process" << processID << "has finished. Removing from internal map."; _childProcesses.remove(processID); @@ -98,17 +98,17 @@ void AssignmentClientMonitor::stopChildProcesses() { qDebug() << "Attempting to terminate child process" << childProcess->processId(); childProcess->terminate(); } - + simultaneousWaitOnChildren(WAIT_FOR_CHILD_MSECS); - + if (_childProcesses.size() > 0) { // ask even more firmly foreach(QProcess* childProcess, _childProcesses) { qDebug() << "Attempting to kill child process" << childProcess->processId(); childProcess->kill(); } - - simultaneousWaitOnChildren(WAIT_FOR_CHILD_MSECS); + + simultaneousWaitOnChildren(WAIT_FOR_CHILD_MSECS); } } @@ -122,7 +122,7 @@ void AssignmentClientMonitor::aboutToQuit() { void AssignmentClientMonitor::spawnChildClient() { QProcess* assignmentClient = new QProcess(this); - + // unparse the parts of the command-line that the child cares about QStringList _childArguments; if (_assignmentPool != "") { @@ -153,7 +153,7 @@ void AssignmentClientMonitor::spawnChildClient() { // make sure that the output from the child process appears in our output assignmentClient->setProcessChannelMode(QProcess::ForwardedChannels); - + assignmentClient->start(QCoreApplication::applicationFilePath(), _childArguments); // make sure we hear that this process has finished when it does @@ -194,7 +194,7 @@ void AssignmentClientMonitor::checkSpares() { qDebug() << "asking child" << aSpareId << "to exit."; SharedNodePointer childNode = nodeList->nodeWithUUID(aSpareId); childNode->activateLocalSocket(); - + QByteArray diePacket = nodeList->byteArrayWithPopulatedHeader(PacketTypeStopNode); nodeList->writeUnverifiedDatagram(diePacket, childNode); } @@ -239,7 +239,7 @@ void AssignmentClientMonitor::readPendingDatagrams() { // update our records about how to reach this child matchingNode->setLocalSocket(senderSockAddr); - QVariantMap packetVariantMap = + QVariantMap packetVariantMap = JSONBreakableMarshal::fromStringBuffer(receivedPacket.mid(numBytesForPacketHeader(receivedPacket))); QJsonObject unpackedStatsJSON = QJsonObject::fromVariantMap(packetVariantMap); diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index ea7ac0648b..c3ca6796bc 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -850,7 +850,9 @@ void AudioMixer::run() { ++_numStatFrames; + // since we're a while loop we need to help Qt's event processing QCoreApplication::processEvents(); + QCoreApplication::sendPostedEvents(this, 0); if (_isFinished) { break; From 27f40ea8817e7f274c2b97a56cac71823fc4c711 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 11 Jun 2015 20:33:30 -0700 Subject: [PATCH 41/41] Signal Audio.disconnected, and use new hello/goodbye sounds in resources. --- examples/dialTone.js | 10 ++++++++-- interface/resources/sounds/goodbye.wav | Bin 0 -> 62044 bytes interface/resources/sounds/hello.wav | Bin 0 -> 61324 bytes interface/src/Application.cpp | 2 ++ libraries/audio-client/src/AudioClient.cpp | 1 + libraries/audio-client/src/AudioClient.h | 1 + .../script-engine/src/AudioScriptingInterface.h | 1 + 7 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 interface/resources/sounds/goodbye.wav create mode 100644 interface/resources/sounds/hello.wav diff --git a/examples/dialTone.js b/examples/dialTone.js index 135acb17f4..b55e654f25 100644 --- a/examples/dialTone.js +++ b/examples/dialTone.js @@ -3,6 +3,7 @@ // examples // // Created by Stephen Birarda on 06/08/15. +// Added disconnect HRS 6/11/15. // Copyright 2015 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. @@ -10,14 +11,19 @@ // // setup the local sound we're going to use -var connectSound = SoundCache.getSound("file://" + Paths.resources + "sounds/short1.wav"); +var connectSound = SoundCache.getSound("file://" + Paths.resources + "sounds/hello.wav"); +var disconnectSound = SoundCache.getSound("file://" + Paths.resources + "sounds/goodbye.wav"); // setup the options needed for that sound var connectSoundOptions = { localOnly: true -} +}; // play the sound locally once we get the first audio packet from a mixer Audio.receivedFirstPacket.connect(function(){ Audio.playSound(connectSound, connectSoundOptions); }); + +Audio.disconnected.connect(function(){ + Audio.playSound(disconnectSound, connectSoundOptions); +}); diff --git a/interface/resources/sounds/goodbye.wav b/interface/resources/sounds/goodbye.wav new file mode 100644 index 0000000000000000000000000000000000000000..bd6c51a5c0b6de4d407528d6a24ca8da558c11e6 GIT binary patch literal 62044 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 Qqk(kI~7|322#O~g^->3kcv5x zlo=6^Y4IK@@dkFSHGhv>A`I7WcFY540S&v;em? zA2&7YBQctS>za%h{fw&`jTnu=6%9v>hT*Craa}_Ytsiks18`G=5v~5XrGdDm-iXma z+*e=RQ6Jn>e>_lcJXU`^RbM>UK)g^Nyw;G9HBLkDUIPFO0r4XeU>M?K7?NT*l4BH7 zVN8I7?TEwLS~upJ$- z2iRkLB!{2B;(&m%V=cf z4dmo=6yOsS<9n2193q&I5lq6GOwIbt%ErvbRxHSNEW)lV#a=AWK@8^ze!(%U$;qtC z8T^`a*_2Dzj4SvZ*Ru^b@(1o=C;q`sJkG8>%p01?ILf2=le@8#8?cc}v4XR(gp)Cok(kPU7|HhdiOtZH zbUcqN5_wQr+;i+M=EsqmpW&qAH@eN}`zZBDb<2r;;L*5+JqSD&>DJINef0T~`8~ zQJ`a@_Q`3Npx@=RPSj$>(;|JMUlpS1N~9@Drin_U2}-BY%KniNDypF>qERZRNR`(p z)zV1S)o3-*SbeXF>ZHjUpkFjZvoucgG*e47U#qlM>$OFjwO@O5O8fPXPJMiP{O3rH z(?cc3Go{A6|NeKN7!solvY;w*p%x0GDJq~XBG45LF#wG*9PKdyJum}&hw(Q~;tH-H8rN_a&+q`x@EnGhaD0uVe2>(OLuSSyH^KY_i;yf$ zupGgP^r%9zD&L_N4Yl|$>hm?~GZqc`2w(9r8Zib9c?*qr0gZVc4R{=1@h{ZjKGfk> ze90a7l1ovED-q5aD92w>lH*a7kto0)k(*tSl^v0eEs&Ir5yDyktKls^#S4_gUF5|r zWW{-;LKH%90K^}7uix=pYj8*N5UpSFwvlE`doVzp=}D&MwQoU zmC-Vl&^(pW|5R90Ra}!*Kx36p!<1i<%B=y)r~b;WUdpYm%AqdGskX|ccFL*N%AuCZ zspiV6rplv6%BIH3s|L!cuarl1lt*aK6pO|8{e zEi_2q>nDA$k?N@NYOhJ^r5Wm~x$3WZ>Z9cvqE#BIO&XzX8mGOQpaYt$6Pm6w`k&5e zkz%w&H?>mFv{uixQ6IEjrvJ>)`;-*Nl@7<18RwM;S5yemDuM?J!+ll2OI5=g)r6@L z;-MiD<2xin8)QT$WJXWqMsF0tU=+kp_za^^4r5Ujlkf$mpblo>Ys^9u%s~sxMH?(e zJ1jzH{Dz(%S&2bdhCx_~NG!upti~8D$7n3W1pI~xSd2+ngvnThDVT+6n1@N2jA@vP zsThw*7=dXRiisGEpV1rR(H-N^9>dWRqtF}!Q3r!j6Mazu-B1o)P#E7K7g`|`nji`4 zApvURUqv8R6%eChxT*p;uAJDfbl9%sSSezGyr$}rM(dt_)IaK`v-&}Qs*$#%8UD3Cpa*mR#HI6K%3rw%T6UQoCaR z`^X{t*$&zy+h`+ft&Oq;HrVFc5SwHJY>f4>Nb6_)th@EFe%8Ty*mu^?T3Rpr%KBSl z>u)t|u+_95t+EZZsy4>LZJbrKsaD=*Sb3Xk4JI)>)YSVd1vNBJ3}#VrQ+2 zUAJ0x$7)%uHL$nV!~!+5lxlAo)ZX%{hn3O*E3Y9|Q{%0kCRuC!Y8|!M`e}s?(>9x+ zoiW^6U#Z&dc z8}-LK^@LYn1n35bo=Aj_h=;C7g0@J6?~xd-kqC{D1kI2bb&weU^=efRAOax>gQ++i zis6Iu;9q6O8)d>{CBYLV#%)PA^iEgxTz~7aPV1(Q>7w@Qtaj+AwramtYP*)}cg@vG z&D26o)Ete`Bn{Sh4bV_^)L^w&Pc>5)HBxKU)VHdn#tK&*6;pK;QDtRU1!dCz0R#La ACIA2c literal 0 HcmV?d00001 diff --git a/interface/resources/sounds/hello.wav b/interface/resources/sounds/hello.wav new file mode 100644 index 0000000000000000000000000000000000000000..6269dab5db21a869759dfe96cde18449729ebc87 GIT binary patch literal 61324 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≀E{LgyR2Os(I_{2}fkA60O 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 literal 0 HcmV?d00001 diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 79a03164ea..56fe4188e9 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -425,6 +425,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : connect(audioIO.data(), &AudioClient::muteToggled, this, &Application::audioMuteToggled); connect(audioIO.data(), &AudioClient::receivedFirstPacket, &AudioScriptingInterface::getInstance(), &AudioScriptingInterface::receivedFirstPacket); + connect(audioIO.data(), &AudioClient::disconnected, + &AudioScriptingInterface::getInstance(), &AudioScriptingInterface::disconnected); audioThread->start(); diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 340ca9374d..6450f25208 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -156,6 +156,7 @@ void AudioClient::audioMixerKilled() { _hasReceivedFirstPacket = false; _outgoingAvatarAudioSequenceNumber = 0; _stats.reset(); + emit disconnected(); } diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 3b2c1c1ae6..642edde84a 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -186,6 +186,7 @@ signals: void deviceChanged(); void receivedFirstPacket(); + void disconnected(); protected: AudioClient(); diff --git a/libraries/script-engine/src/AudioScriptingInterface.h b/libraries/script-engine/src/AudioScriptingInterface.h index d74e1ed1e0..470d038196 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.h +++ b/libraries/script-engine/src/AudioScriptingInterface.h @@ -39,6 +39,7 @@ signals: void mutedByMixer(); void environmentMuted(); void receivedFirstPacket(); + void disconnected(); private: AudioScriptingInterface();