From a83616a7dcb911204bcde7decf3c44db4d384db3 Mon Sep 17 00:00:00 2001 From: Brad Davis <bdavis@saintandreas.org> Date: Thu, 30 Apr 2015 10:32:19 -0700 Subject: [PATCH 01/88] 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 <samuel_gondelman@brown.edu> Date: Thu, 4 Jun 2015 16:29:58 -0700 Subject: [PATCH 02/88] 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<UserInputMapper::InputPair> { + QVector<UserInputMapper::InputPair> 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<UserInputMapper::Action>(); +static int inputChannelMetaTypeId = qRegisterMetaType<UserInputMapper::InputChannel>(); +static int inputMetaTypeId = qRegisterMetaType<UserInputMapper::Input>(); +static int inputPairMetaTypeId = qRegisterMetaType<UserInputMapper::InputPair>(); + +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<UserInputMapper::InputChannel> 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<QVector<UserInputMapper::Action> >(engine); + qScriptRegisterSequenceMetaType<QVector<UserInputMapper::InputChannel> >(engine); + qScriptRegisterSequenceMetaType<QVector<UserInputMapper::InputPair> >(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<UserInputMapper::Action> ControllerScriptingInterface::getAllActions() { + return Application::getUserInputMapper()->getAllActions(); +} + +QVector<UserInputMapper::InputChannel> ControllerScriptingInterface::getInputChannelsForAction(UserInputMapper::Action action) { + return Application::getUserInputMapper()->getInputChannelsForAction(action); +} + +QString ControllerScriptingInterface::getDeviceName(unsigned int device) { + return Application::getUserInputMapper()->getDeviceName((unsigned short) device); +} + +QVector<UserInputMapper::InputChannel> 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<UserInputMapper::InputPair> 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 <QtCore/QObject> +#include "ui/UserInputMapper.h" + #include <AbstractControllerScriptingInterface.h> 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<UserInputMapper::Action> getAllActions(); + Q_INVOKABLE virtual QVector<UserInputMapper::InputChannel> getInputChannelsForAction(UserInputMapper::Action action); + Q_INVOKABLE virtual QString getDeviceName(unsigned int device); + Q_INVOKABLE virtual QVector<UserInputMapper::InputChannel> getAllInputsForDevice(unsigned int device); + Q_INVOKABLE virtual bool addInputChannel(UserInputMapper::InputChannel inputChannel); + Q_INVOKABLE virtual bool removeInputChannel(UserInputMapper::InputChannel inputChannel); + Q_INVOKABLE virtual QVector<UserInputMapper::InputPair> 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<ActionToInputsMap::iterator, ActionToInputsMap::iterator> 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<InputChannel> 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::InputChannel> UserInputMapper::getAllInputsForDevice(uint16 device) { + InputChannels allChannels; + getInputChannels(allChannels); + + QVector<InputChannel> 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::Action> UserInputMapper::getAllActions() { + QVector<Action> actions; + for (auto i = 0; i < NUM_ACTIONS; i++) { + actions.append(Action(i)); + } + return actions; +} + +QVector<UserInputMapper::InputChannel> UserInputMapper::getInputChannelsForAction(UserInputMapper::Action action) { + QVector<InputChannel> inputChannels; + std::pair <ActionToInputsMap::iterator, ActionToInputsMap::iterator> 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<bool (const Input& input, int timestamp)> ButtonGetter; typedef std::function<float (const Input& input, int timestamp)> AxisGetter; typedef std::function<JointValue (const Input& input, int timestamp)> JointGetter; + typedef QPair<Input, QString> InputPair; + typedef std::function<QVector<InputPair> ()> AvailableInputGetter; + typedef std::function<bool ()> ResetBindings; + + typedef QVector<InputPair> 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<DeviceProxy> 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<InputPair>(); }; + ResetBindings resetDeviceBindings = [] () -> bool { return true; }; + + typedef std::shared_ptr<DeviceProxy> 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<InputPair> 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<QString> _actionNames = std::vector<QString>(NUM_ACTIONS); + void createActionNames(); + QVector<Action> getAllActions(); + QString getActionName(Action action) { return UserInputMapper::_actionNames[(int) action]; } float getActionState(Action action) const { return _actionStates[action]; } +// QVector<InputChannel> 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<InputChannel> getAllInputsForDevice(uint16 device); + QVector<InputChannel> getInputChannelsForAction(UserInputMapper::Action action); + std::multimap<Action, InputChannel> 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<int, DeviceProxy::Pointer> DevicesMap; @@ -177,4 +201,12 @@ protected: std::vector<float> _actionScales = std::vector<float>(NUM_ACTIONS, 1.0f); }; +Q_DECLARE_METATYPE(UserInputMapper::InputPair) +Q_DECLARE_METATYPE(QVector<UserInputMapper::InputPair>) +Q_DECLARE_METATYPE(UserInputMapper::Input) +Q_DECLARE_METATYPE(UserInputMapper::InputChannel) +Q_DECLARE_METATYPE(QVector<UserInputMapper::InputChannel>) +Q_DECLARE_METATYPE(UserInputMapper::Action) +Q_DECLARE_METATYPE(QVector<UserInputMapper::Action>) + #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 <samuel_gondelman@brown.edu> Date: Fri, 5 Jun 2015 10:14:13 -0700 Subject: [PATCH 03/88] 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 f51feca37d54ddbd0202738090925da412ee0457 Mon Sep 17 00:00:00 2001 From: David Rowe <david@ctrlaltstudio.com> Date: Mon, 8 Jun 2015 11:29:35 -0700 Subject: [PATCH 04/88] Add directory button --- examples/defaultScripts.js | 1 + examples/directory.js | 86 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 examples/directory.js diff --git a/examples/defaultScripts.js b/examples/defaultScripts.js index 8f32b80bba..61bed8d9b1 100644 --- a/examples/defaultScripts.js +++ b/examples/defaultScripts.js @@ -18,3 +18,4 @@ Script.load("notifications.js"); Script.load("users.js"); Script.load("grab.js"); Script.load("pointer.js"); +Script.load("directory.js"); diff --git a/examples/directory.js b/examples/directory.js new file mode 100644 index 0000000000..965abf1453 --- /dev/null +++ b/examples/directory.js @@ -0,0 +1,86 @@ +// +// directory.js +// examples +// +// Created by David Rowe on 8 Jun 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 +// + +Script.include("libraries/globals.js"); + +var directory = (function () { + + var DIRECTORY_URL = "https://metaverse.highfidelity.com/directory", + directoryWindow, + DIRECTORY_BUTTON_URL = HIFI_PUBLIC_BUCKET + "images/tools/directory.svg", + BUTTON_WIDTH = 50, + BUTTON_HEIGHT = 50, + BUTTON_ALPHA = 0.9, + BUTTON_MARGIN = 8, + directoryButton, + viewport; + + function updateButtonPosition() { + Overlays.editOverlay(directoryButton, { + x: viewport.x - BUTTON_WIDTH - BUTTON_MARGIN, + y: BUTTON_MARGIN + }); + } + + function onMousePressEvent(event) { + var clickedOverlay; + + clickedOverlay = Overlays.getOverlayAtPoint({ x: event.x, y: event.y }); + + if (clickedOverlay === directoryButton) { + if (directoryWindow.url !== DIRECTORY_URL) { + directoryWindow.setURL(DIRECTORY_URL); + } + directoryWindow.setVisible(true); + directoryWindow.raise(); + } + } + + function onScriptUpdate() { + var oldViewport = viewport; + + viewport = Controller.getViewportDimensions(); + + if (viewport.x !== oldViewport.x || viewport.y !== oldViewport.y) { + updateButtonPosition(); + } + } + + function setUp() { + viewport = Controller.getViewportDimensions(); + + directoryWindow = new WebWindow('Directory', DIRECTORY_URL, 900, 700, false); + directoryWindow.setVisible(false); + + directoryButton = Overlays.addOverlay("image", { + imageURL: DIRECTORY_BUTTON_URL, + width: BUTTON_WIDTH, + height: BUTTON_HEIGHT, + x: viewport.x - BUTTON_WIDTH - BUTTON_MARGIN, + y: BUTTON_MARGIN, + alpha: BUTTON_ALPHA, + visible: true + }); + + updateButtonPosition(); + + Controller.mousePressEvent.connect(onMousePressEvent); + + Script.update.connect(onScriptUpdate); + } + + function tearDown() { + Overlays.deleteOverlay(directoryButton); + } + + setUp(); + Script.scriptEnding.connect(tearDown); +}()); \ No newline at end of file From cd931282365b27bf31d878f1a81140c32f721a1a Mon Sep 17 00:00:00 2001 From: Niraj Venkat <venkatn93@gmail.com> Date: Mon, 8 Jun 2015 13:29:54 -0700 Subject: [PATCH 05/88] [ERRORS] Starting on hyperlink entity and properties --- libraries/entities/src/EntityItemProperties.h | 6 + libraries/entities/src/Hyperlink.h | 5 + .../entities/src/HyperlinkEntityItem.cpp | 174 ++++++++++++++++++ libraries/entities/src/HyperlinkEntityItem.h | 95 ++++++++++ 4 files changed, 280 insertions(+) create mode 100644 libraries/entities/src/Hyperlink.h create mode 100644 libraries/entities/src/HyperlinkEntityItem.cpp create mode 100644 libraries/entities/src/HyperlinkEntityItem.h diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 3c8133af8f..42d4555e2e 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -55,6 +55,7 @@ class EntityItemProperties { friend class WebEntityItem; // TODO: consider removing this friend relationship and use public methods friend class LineEntityItem; // TODO: consider removing this friend relationship and use public methods friend class PolyVoxEntityItem; // TODO: consider removing this friend relationship and use public methods + friend class HyperlinkEntityItem; // TODO: consider removing this friend relationship and use public methods public: EntityItemProperties(); virtual ~EntityItemProperties(); @@ -148,6 +149,9 @@ public: DEFINE_PROPERTY_REF(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString); DEFINE_PROPERTY(PROP_LINE_WIDTH, LineWidth, lineWidth, float); DEFINE_PROPERTY_REF(LINE_POINTS, LinePoints, linePoints, QVector<glm::vec3>); + DEFINE_PROPERTY_REF(PROP_HREF, Href, href, QString); + DEFINE_PROPERTY_REF(PROP_DESCRIPTION, Description, description, QString); + static QString getBackgroundModeString(BackgroundMode mode); @@ -295,6 +299,8 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, VoxelVolumeSize, voxelVolumeSize, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, VoxelData, voxelData, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, VoxelSurfaceStyle, voxelSurfaceStyle, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, Href, href, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, Description, description, ""); properties.getStage().debugDump(); properties.getAtmosphere().debugDump(); diff --git a/libraries/entities/src/Hyperlink.h b/libraries/entities/src/Hyperlink.h new file mode 100644 index 0000000000..7e2e2261e7 --- /dev/null +++ b/libraries/entities/src/Hyperlink.h @@ -0,0 +1,5 @@ + + + + +entityItem \ No newline at end of file diff --git a/libraries/entities/src/HyperlinkEntityItem.cpp b/libraries/entities/src/HyperlinkEntityItem.cpp new file mode 100644 index 0000000000..af9a4003b1 --- /dev/null +++ b/libraries/entities/src/HyperlinkEntityItem.cpp @@ -0,0 +1,174 @@ +// +// HyperlinkEntityItem.cpp +// libraries/entities/src +// +// Created by Niraj Venkat on 6/8/15. +// Copyright 2013 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 <glm/gtx/transform.hpp> + +#include <QDebug> + +#include <ByteCountCoding.h> +#include <PlaneShape.h> + +#include "EntityTree.h" +#include "EntityTreeElement.h" +#include "EntitiesLogging.h" +#include "HyperlinkEntityItem.h" + + +const QString HyperlinkEntityItem::DEFAULT_HREF(""); +const QString HyperlinkEntityItem::DEFAULT_DESCRIPTION(""); + +const float HyperlinkEntityItem::DEFAULT_LINE_HEIGHT = 0.1f; +const xColor HyperlinkEntityItem::DEFAULT_TEXT_COLOR = { 255, 255, 255 }; +const xColor HyperlinkEntityItem::DEFAULT_BACKGROUND_COLOR = { 0, 0, 0 }; + +EntityItemPointer HyperlinkEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { + return EntityItemPointer(new HyperlinkEntityItem(entityID, properties)); +} + +HyperlinkEntityItem::HyperlinkEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : +EntityItem(entityItemID) +{ + _type = EntityTypes::Text; + _created = properties.getCreated(); + setProperties(properties); +} + +const float TEXT_ENTITY_ITEM_FIXED_DEPTH = 0.01f; + +void HyperlinkEntityItem::setDimensions(const glm::vec3& value) { + // NOTE: Text Entities always have a "depth" of 1cm. + _dimensions = glm::vec3(value.x, value.y, TEXT_ENTITY_ITEM_FIXED_DEPTH); +} + +EntityItemProperties HyperlinkEntityItem::getProperties() const { + EntityItemProperties properties = EntityItem::getProperties(); // get the properties from our base class + + COPY_ENTITY_PROPERTY_TO_PROPERTIES(href, getHref); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(description, getDescription); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(lineHeight, getLineHeight); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(textColor, getTextColorX); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(backgroundColor, getBackgroundColorX); + return properties; +} + +bool HyperlinkEntityItem::setProperties(const EntityItemProperties& properties) { + bool somethingChanged = false; + somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class + + SET_ENTITY_PROPERTY_FROM_PROPERTIES(href, setHref); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(description, setDescription); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(lineHeight, setLineHeight); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(textColor, setTextColor); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(backgroundColor, setBackgroundColor); + + if (somethingChanged) { + bool wantDebug = false; + if (wantDebug) { + uint64_t now = usecTimestampNow(); + int elapsed = now - getLastEdited(); + qCDebug(entities) << "HyperlinkEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << + "now=" << now << " getLastEdited()=" << getLastEdited(); + } + setLastEdited(properties._lastEdited); + } + + return somethingChanged; +} + +int HyperlinkEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData) { + + int bytesRead = 0; + const unsigned char* dataAt = data; + + READ_ENTITY_PROPERTY(PROP_HREF, QString, setHref); + READ_ENTITY_PROPERTY(PROP_DESCRIPTION, QString, setDescription); + READ_ENTITY_PROPERTY(PROP_LINE_HEIGHT, float, setLineHeight); + READ_ENTITY_PROPERTY(PROP_TEXT_COLOR, rgbColor, setTextColor); + READ_ENTITY_PROPERTY(PROP_BACKGROUND_COLOR, rgbColor, setBackgroundColor); + + return bytesRead; +} + + +// TODO: eventually only include properties changed since the params.lastViewFrustumSent time +EntityPropertyFlags HyperlinkEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { + EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); + requestedProperties += PROP_HREF; + requestedProperties += PROP_DESCRIPTION; + requestedProperties += PROP_LINE_HEIGHT; + requestedProperties += PROP_TEXT_COLOR; + requestedProperties += PROP_BACKGROUND_COLOR; + return requestedProperties; +} + +void HyperlinkEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const { + + bool successPropertyFits = true; + + APPEND_ENTITY_PROPERTY(PROP_HREF, getHref()); + APPEND_ENTITY_PROPERTY(PROP_DESCRIPTION, getDescription()); + APPEND_ENTITY_PROPERTY(PROP_LINE_HEIGHT, getLineHeight()); + APPEND_ENTITY_PROPERTY(PROP_TEXT_COLOR, getTextColor()); + APPEND_ENTITY_PROPERTY(PROP_BACKGROUND_COLOR, getBackgroundColor()); +} + + +bool HyperlinkEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, + void** intersectedObject, bool precisionPicking) const { + + RayIntersectionInfo rayInfo; + rayInfo._rayStart = origin; + rayInfo._rayDirection = direction; + rayInfo._rayLength = std::numeric_limits<float>::max(); + + PlaneShape plane; + + const glm::vec3 UNROTATED_NORMAL(0.0f, 0.0f, -1.0f); + glm::vec3 normal = _rotation * UNROTATED_NORMAL; + plane.setNormal(normal); + plane.setPoint(getPosition()); // the position is definitely a point on our plane + + bool intersects = plane.findRayIntersection(rayInfo); + + if (intersects) { + glm::vec3 hitAt = origin + (direction * rayInfo._hitDistance); + // now we know the point the ray hit our plane + + glm::mat4 rotation = glm::mat4_cast(getRotation()); + glm::mat4 translation = glm::translate(getPosition()); + glm::mat4 entityToWorldMatrix = translation * rotation; + glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); + + glm::vec3 dimensions = getDimensions(); + glm::vec3 registrationPoint = getRegistrationPoint(); + glm::vec3 corner = -(dimensions * registrationPoint); + AABox entityFrameBox(corner, dimensions); + + glm::vec3 entityFrameHitAt = glm::vec3(worldToEntityMatrix * glm::vec4(hitAt, 1.0f)); + + intersects = entityFrameBox.contains(entityFrameHitAt); + } + + if (intersects) { + distance = rayInfo._hitDistance; + } + return intersects; +} diff --git a/libraries/entities/src/HyperlinkEntityItem.h b/libraries/entities/src/HyperlinkEntityItem.h new file mode 100644 index 0000000000..2d522d6cfb --- /dev/null +++ b/libraries/entities/src/HyperlinkEntityItem.h @@ -0,0 +1,95 @@ +// +// HyperlinkEntityItem.h +// libraries/entities/src +// +// Created by Niraj Venkat on 6/8/15. +// Copyright 2013 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_HyperlinkEntityItem_h +#define hifi_HyperlinkEntityItem_h + +#include "EntityItem.h" + +class HyperlinkEntityItem : public EntityItem { +public: + static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); + + HyperlinkEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); + + ALLOW_INSTANTIATION // This class can be instantiated + + /// set dimensions in domain scale units (0.0 - 1.0) this will also reset radius appropriately + virtual void setDimensions(const glm::vec3& value); + virtual ShapeType getShapeType() const { return SHAPE_TYPE_BOX; } + + // methods for getting/setting all properties of an entity + virtual EntityItemProperties getProperties() const; + virtual bool setProperties(const EntityItemProperties& properties); + + // TODO: eventually only include properties changed since the params.lastViewFrustumSent time + virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const; + + virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const; + + virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData); + + virtual bool supportsDetailedRayIntersection() const { return true; } + virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, + void** intersectedObject, bool precisionPicking) const; + + static const QString DEFAULT_HREF; + void setHref(const QString& value) { _href = value; } + const QString& getHref() const { return _href; } + + static const QString DEFAULT_DESCRIPTION; + void setDescription(const QString& value) { _description = value; } + const QString& getDescription() const { return _description; } + + static const float DEFAULT_LINE_HEIGHT; + void setLineHeight(float value) { _lineHeight = value; } + float getLineHeight() const { return _lineHeight; } + + static const xColor DEFAULT_TEXT_COLOR; + const rgbColor& getTextColor() const { return _textColor; } + xColor getTextColorX() const { xColor color = { _textColor[RED_INDEX], _textColor[GREEN_INDEX], _textColor[BLUE_INDEX] }; return color; } + + void setTextColor(const rgbColor& value) { memcpy(_textColor, value, sizeof(_textColor)); } + void setTextColor(const xColor& value) { + _textColor[RED_INDEX] = value.red; + _textColor[GREEN_INDEX] = value.green; + _textColor[BLUE_INDEX] = value.blue; + } + + static const xColor DEFAULT_BACKGROUND_COLOR; + const rgbColor& getBackgroundColor() const { return _backgroundColor; } + xColor getBackgroundColorX() const { xColor color = { _backgroundColor[RED_INDEX], _backgroundColor[GREEN_INDEX], _backgroundColor[BLUE_INDEX] }; return color; } + + void setBackgroundColor(const rgbColor& value) { memcpy(_backgroundColor, value, sizeof(_backgroundColor)); } + void setBackgroundColor(const xColor& value) { + _backgroundColor[RED_INDEX] = value.red; + _backgroundColor[GREEN_INDEX] = value.green; + _backgroundColor[BLUE_INDEX] = value.blue; + } + +protected: + QString _href; + QString _description; + float _lineHeight; + rgbColor _textColor; + rgbColor _backgroundColor; +}; + +#endif // hifi_HyperlinkEntityItem_h From 1e858d8bc5bbc3e135c5463dc3b38bb69ffb9c18 Mon Sep 17 00:00:00 2001 From: Seth Alves <seth.alves@gmail.com> Date: Mon, 8 Jun 2015 14:16:03 -0700 Subject: [PATCH 06/88] 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<ObjectMotionState*>(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 <QUuid> + +#include <EntityItem.h> +#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 63665d720a8c83f3b9ce35de6407b415c30a1005 Mon Sep 17 00:00:00 2001 From: Niraj Venkat <venkatn93@gmail.com> Date: Mon, 8 Jun 2015 15:32:21 -0700 Subject: [PATCH 07/88] Fixed bug by adding hyperlink props to flags. Safe to merge --- libraries/entities/src/EntityPropertyFlags.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 8eb09fece0..f1ebdb8a1f 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -117,6 +117,10 @@ enum EntityPropertyList { //for lines PROP_LINE_WIDTH, PROP_LINE_POINTS, + + // used by hyperlinks + PROP_HREF, + PROP_DESCRIPTION, //////////////////////////////////////////////////////////////////////////////////////////////////// // ATTENTION: add new properties ABOVE this line From dbb447c9b5a4ea60263f40955aa782034c0b567a Mon Sep 17 00:00:00 2001 From: Niraj Venkat <venkatn93@gmail.com> Date: Mon, 8 Jun 2015 16:09:06 -0700 Subject: [PATCH 08/88] Exposing Hyperlink type to javascript --- libraries/entities/src/EntityTypes.cpp | 2 ++ libraries/entities/src/EntityTypes.h | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityTypes.cpp b/libraries/entities/src/EntityTypes.cpp index a41cc22d2e..5689cfd11a 100644 --- a/libraries/entities/src/EntityTypes.cpp +++ b/libraries/entities/src/EntityTypes.cpp @@ -28,6 +28,7 @@ #include "ZoneEntityItem.h" #include "LineEntityItem.h" #include "PolyVoxEntityItem.h" +#include "HyperlinkEntityItem.h" QMap<EntityTypes::EntityType, QString> EntityTypes::_typeToNameMap; QMap<QString, EntityTypes::EntityType> EntityTypes::_nameToTypeMap; @@ -47,6 +48,7 @@ REGISTER_ENTITY_TYPE(ParticleEffect) REGISTER_ENTITY_TYPE(Zone) REGISTER_ENTITY_TYPE(Line) REGISTER_ENTITY_TYPE(PolyVox) +REGISTER_ENTITY_TYPE(Hyperlink) const QString& EntityTypes::getEntityTypeName(EntityType entityType) { QMap<EntityType, QString>::iterator matchedTypeName = _typeToNameMap.find(entityType); diff --git a/libraries/entities/src/EntityTypes.h b/libraries/entities/src/EntityTypes.h index 323a4eb92b..89bd57b253 100644 --- a/libraries/entities/src/EntityTypes.h +++ b/libraries/entities/src/EntityTypes.h @@ -46,7 +46,8 @@ public: Web, Line, PolyVox, - LAST = PolyVox + Hyperlink, + LAST = Hyperlink } EntityType; static const QString& getEntityTypeName(EntityType entityType); From b3bc9c3ef05d871b5b6dfc0cc0b2378b2192636e Mon Sep 17 00:00:00 2001 From: Seth Alves <seth.alves@gmail.com> Date: Mon, 8 Jun 2015 17:10:13 -0700 Subject: [PATCH 09/88] 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<EntityActionInterface> 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<ObjectMotionState*>(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 <seth.alves@gmail.com> Date: Mon, 8 Jun 2015 18:25:58 -0700 Subject: [PATCH 10/88] 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 <seth.alves@gmail.com> Date: Mon, 8 Jun 2015 20:31:35 -0700 Subject: [PATCH 11/88] 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 <bdavis@saintandreas.org> Date: Tue, 9 Jun 2015 00:39:49 -0700 Subject: [PATCH 12/88] 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 9e3ce344fbdd9b5419d14404bb3945c379aba40d Mon Sep 17 00:00:00 2001 From: David Rowe <david@ctrlaltstudio.com> Date: Tue, 9 Jun 2015 07:26:18 -0700 Subject: [PATCH 13/88] Display directory button just above edit toobar --- examples/directory.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/directory.js b/examples/directory.js index 965abf1453..03d352e024 100644 --- a/examples/directory.js +++ b/examples/directory.js @@ -21,12 +21,13 @@ var directory = (function () { BUTTON_ALPHA = 0.9, BUTTON_MARGIN = 8, directoryButton, + EDIT_TOOLBAR_BUTTONS = 10, // Number of buttons in edit.js toolbar viewport; function updateButtonPosition() { Overlays.editOverlay(directoryButton, { x: viewport.x - BUTTON_WIDTH - BUTTON_MARGIN, - y: BUTTON_MARGIN + y: (viewport.y - (EDIT_TOOLBAR_BUTTONS + 1) * (BUTTON_HEIGHT + BUTTON_MARGIN) - BUTTON_MARGIN) / 2 - 1 }); } From b1b2d1f85cf54f9d0bf65a6674bb4c4c3fdc0fb6 Mon Sep 17 00:00:00 2001 From: Sam Gondelman <samuel_gondelman@brown.edu> Date: Tue, 9 Jun 2015 09:45:19 -0700 Subject: [PATCH 14/88] ;) (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 <samuel_gondelman@brown.edu> Date: Tue, 9 Jun 2015 09:56:40 -0700 Subject: [PATCH 15/88] 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<UserInputMapper::InputPair> { QVector<UserInputMapper::InputPair> 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<Action> getAllActions(); QString getActionName(Action action) { return UserInputMapper::_actionNames[(int) action]; } float getActionState(Action action) const { return _actionStates[action]; } -// QVector<InputChannel> 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 <samuel_gondelman@brown.edu> Date: Tue, 9 Jun 2015 10:20:36 -0700 Subject: [PATCH 16/88] 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 0004b1a81cb808744f595508f31ebd18f13973d4 Mon Sep 17 00:00:00 2001 From: Niraj Venkat <venkatn93@gmail.com> Date: Tue, 9 Jun 2015 10:32:25 -0700 Subject: [PATCH 17/88] Reversing previous entity work and creation of property group --- libraries/entities/src/EntityItemProperties.h | 6 +- libraries/entities/src/EntityTypes.cpp | 1 - libraries/entities/src/EntityTypes.h | 3 +- .../entities/src/HyperlinkEntityItem.cpp | 2 +- .../entities/src/HyperlinkPropertyGroup.h | 88 +++++++++++++++++++ 5 files changed, 92 insertions(+), 8 deletions(-) create mode 100644 libraries/entities/src/HyperlinkPropertyGroup.h diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 42d4555e2e..e7d9a7ad18 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -36,6 +36,7 @@ #include "EntityPropertyFlags.h" #include "SkyboxPropertyGroup.h" #include "StagePropertyGroup.h" +#include "HyperlinkPropertyGroup.h" const quint64 UNKNOWN_CREATED_TIME = 0; @@ -149,8 +150,7 @@ public: DEFINE_PROPERTY_REF(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString); DEFINE_PROPERTY(PROP_LINE_WIDTH, LineWidth, lineWidth, float); DEFINE_PROPERTY_REF(LINE_POINTS, LinePoints, linePoints, QVector<glm::vec3>); - DEFINE_PROPERTY_REF(PROP_HREF, Href, href, QString); - DEFINE_PROPERTY_REF(PROP_DESCRIPTION, Description, description, QString); + DEFINE_PROPERTY_GROUP(Hyperlink, hyperlink, HyperlinkPropertyGroup) static QString getBackgroundModeString(BackgroundMode mode); @@ -299,8 +299,6 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, VoxelVolumeSize, voxelVolumeSize, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, VoxelData, voxelData, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, VoxelSurfaceStyle, voxelSurfaceStyle, ""); - DEBUG_PROPERTY_IF_CHANGED(debug, properties, Href, href, ""); - DEBUG_PROPERTY_IF_CHANGED(debug, properties, Description, description, ""); properties.getStage().debugDump(); properties.getAtmosphere().debugDump(); diff --git a/libraries/entities/src/EntityTypes.cpp b/libraries/entities/src/EntityTypes.cpp index 5689cfd11a..b20ee28e5a 100644 --- a/libraries/entities/src/EntityTypes.cpp +++ b/libraries/entities/src/EntityTypes.cpp @@ -48,7 +48,6 @@ REGISTER_ENTITY_TYPE(ParticleEffect) REGISTER_ENTITY_TYPE(Zone) REGISTER_ENTITY_TYPE(Line) REGISTER_ENTITY_TYPE(PolyVox) -REGISTER_ENTITY_TYPE(Hyperlink) const QString& EntityTypes::getEntityTypeName(EntityType entityType) { QMap<EntityType, QString>::iterator matchedTypeName = _typeToNameMap.find(entityType); diff --git a/libraries/entities/src/EntityTypes.h b/libraries/entities/src/EntityTypes.h index 89bd57b253..323a4eb92b 100644 --- a/libraries/entities/src/EntityTypes.h +++ b/libraries/entities/src/EntityTypes.h @@ -46,8 +46,7 @@ public: Web, Line, PolyVox, - Hyperlink, - LAST = Hyperlink + LAST = PolyVox } EntityType; static const QString& getEntityTypeName(EntityType entityType); diff --git a/libraries/entities/src/HyperlinkEntityItem.cpp b/libraries/entities/src/HyperlinkEntityItem.cpp index af9a4003b1..4dee124dc1 100644 --- a/libraries/entities/src/HyperlinkEntityItem.cpp +++ b/libraries/entities/src/HyperlinkEntityItem.cpp @@ -37,7 +37,7 @@ EntityItemPointer HyperlinkEntityItem::factory(const EntityItemID& entityID, con HyperlinkEntityItem::HyperlinkEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : EntityItem(entityItemID) { - _type = EntityTypes::Text; + _type = EntityTypes::Hyperlink; _created = properties.getCreated(); setProperties(properties); } diff --git a/libraries/entities/src/HyperlinkPropertyGroup.h b/libraries/entities/src/HyperlinkPropertyGroup.h new file mode 100644 index 0000000000..b6bd40c91f --- /dev/null +++ b/libraries/entities/src/HyperlinkPropertyGroup.h @@ -0,0 +1,88 @@ +// +// HyperlinkPropertyGroup.h +// libraries/entities/src +// +// Created by Niraj Venkat on 6/9/15. +// Copyright 2013 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_HyperlinkPropertyGroup_h +#define hifi_HyperlinkPropertyGroup_h + +#include <QtScript/QScriptEngine> + +#include "PropertyGroup.h" +#include "EntityItemPropertiesMacros.h" + +class EntityItemProperties; +class EncodeBitstreamParams; +class OctreePacketData; +class EntityTreeElementExtraEncodeData; +class ReadBitstreamToTreeParams; + +#include <stdint.h> +#include <glm/glm.hpp> + + + +class HyperlinkPropertyGroup : public PropertyGroup { +public: + HyperlinkPropertyGroup(); + virtual ~HyperlinkPropertyGroup() {} + + // EntityItemProperty related helpers + virtual void copyToScriptValue(QScriptValue& properties, QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const; + virtual void copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings); + virtual void debugDump() const; + + virtual bool appentToEditPacket(OctreePacketData* packetData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const; + + virtual bool decodeFromEditPacket(EntityPropertyFlags& propertyFlags, const unsigned char*& dataAt, int& processedBytes); + virtual void markAllChanged(); + virtual EntityPropertyFlags getChangedProperties() const; + + // EntityItem related helpers + // methods for getting/setting all properties of an entity + virtual void getProperties(EntityItemProperties& propertiesOut) const; + + /// returns true if something changed + virtual bool setProperties(const EntityItemProperties& properties); + + virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const; + + virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const; + + virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData); + + /* + DEFINE_PROPERTY_REF(PROP_Hyperlink_CENTER, Center, center, glm::vec3); + DEFINE_PROPERTY(PROP_Hyperlink_INNER_RADIUS, InnerRadius, innerRadius, float); + DEFINE_PROPERTY(PROP_Hyperlink_OUTER_RADIUS, OuterRadius, outerRadius, float); + DEFINE_PROPERTY(PROP_Hyperlink_MIE_SCATTERING, MieScattering, mieScattering, float); + DEFINE_PROPERTY(PROP_Hyperlink_RAYLEIGH_SCATTERING, RayleighScattering, rayleighScattering, float); + DEFINE_PROPERTY_REF(PROP_Hyperlink_SCATTERING_WAVELENGTHS, ScatteringWavelengths, scatteringWavelengths, glm::vec3); + DEFINE_PROPERTY(PROP_Hyperlink_HAS_STARS, HasStars, hasStars, bool); + */ + + DEFINE_PROPERTY_REF(PROP_HREF, Href, href, QString); + DEFINE_PROPERTY_REF(PROP_DESCRIPTION, Description, description, QString); + +}; + +#endif // hifi_HyperlinkPropertyGroup_h From 1bf0b15bb0d76f3f72637e935f3fd4d8aecdbb36 Mon Sep 17 00:00:00 2001 From: Niraj Venkat <venkatn93@gmail.com> Date: Tue, 9 Jun 2015 10:33:05 -0700 Subject: [PATCH 18/88] Removing unnecessary files --- libraries/entities/src/Hyperlink.h | 5 - .../entities/src/HyperlinkPropertyGroup.cpp | 136 ++++++++++++++++++ 2 files changed, 136 insertions(+), 5 deletions(-) delete mode 100644 libraries/entities/src/Hyperlink.h create mode 100644 libraries/entities/src/HyperlinkPropertyGroup.cpp diff --git a/libraries/entities/src/Hyperlink.h b/libraries/entities/src/Hyperlink.h deleted file mode 100644 index 7e2e2261e7..0000000000 --- a/libraries/entities/src/Hyperlink.h +++ /dev/null @@ -1,5 +0,0 @@ - - - - -entityItem \ No newline at end of file diff --git a/libraries/entities/src/HyperlinkPropertyGroup.cpp b/libraries/entities/src/HyperlinkPropertyGroup.cpp new file mode 100644 index 0000000000..bc94a8d5d3 --- /dev/null +++ b/libraries/entities/src/HyperlinkPropertyGroup.cpp @@ -0,0 +1,136 @@ +// +// HyperlinkPropertyGroup.cpp +// libraries/entities/src +// +// Created by Niraj Venkat on 6/9/15. +// Copyright 2013 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 <OctreePacketData.h> + +#include "HyperlinkPropertyGroup.h" +#include "EntityItemProperties.h" +#include "EntityItemPropertiesMacros.h" + +HyperlinkPropertyGroup::HyperlinkPropertyGroup() { + const QString DEFAULT_HREF = QString(""); + const QString DEFAULT_DESCRIPTION = QString(""); + + _href = DEFAULT_HREF; + _description = DEFAULT_DESCRIPTION; +} + +void HyperlinkPropertyGroup::copyToScriptValue(QScriptValue& properties, QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { + COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(Hyperlink, hyperlink, Href, href); + COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(Hyperlink, hyperlink, Description, description); +} + +void HyperlinkPropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) { + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(Hyperlink, href, QString, setHref); + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(Hyperlink, description, QString, setDescription); +} + +void HyperlinkPropertyGroup::debugDump() const { + qDebug() << " HyperlinkPropertyGroup: ---------------------------------------------"; + qDebug() << " Href:" << getHref() << " has changed:" << hrefChanged(); + qDebug() << " Description:" << getDescription() << " has changed:" << descriptionChanged(); +} + +bool HyperlinkPropertyGroup::appentToEditPacket(OctreePacketData* packetData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const { + + bool successPropertyFits = true; + + APPEND_ENTITY_PROPERTY(PROP_HREF, getHref()); + APPEND_ENTITY_PROPERTY(PROP_DESCRIPTION, getDescription()); + + return true; +} + + +bool HyperlinkPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFlags, const unsigned char*& dataAt, int& processedBytes) { + + int bytesRead = 0; + bool overwriteLocalData = true; + + READ_ENTITY_PROPERTY(PROP_HREF, QString, setHref); + READ_ENTITY_PROPERTY(PROP_DESCRIPTION, QString, setDescription); + + DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_HREF, Href); + DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_DESCRIPTION, Description); + + processedBytes += bytesRead; + + return true; +} + +void HyperlinkPropertyGroup::markAllChanged() { + _hrefChanged = true; + _descriptionChanged = true; +} + +EntityPropertyFlags HyperlinkPropertyGroup::getChangedProperties() const { + EntityPropertyFlags changedProperties; + + CHECK_PROPERTY_CHANGE(PROP_HREF, href); + CHECK_PROPERTY_CHANGE(PROP_DESCRIPTION, description); + + return changedProperties; +} + +void HyperlinkPropertyGroup::getProperties(EntityItemProperties& properties) const { + COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Hyperlink, Href, getHref); + COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Hyperlink, Description, getDescription); +} + +bool HyperlinkPropertyGroup::setProperties(const EntityItemProperties& properties) { + bool somethingChanged = false; + + SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Hyperlink, Href, href, setHref); + SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Hyperlink, Description, description, setDescription); + + return somethingChanged; +} + +EntityPropertyFlags HyperlinkPropertyGroup::getEntityProperties(EncodeBitstreamParams& params) const { + EntityPropertyFlags requestedProperties; + + requestedProperties += PROP_HREF; + requestedProperties += PROP_DESCRIPTION; + + return requestedProperties; +} + +void HyperlinkPropertyGroup::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const { + + bool successPropertyFits = true; + + APPEND_ENTITY_PROPERTY(PROP_HREF, getHref()); + APPEND_ENTITY_PROPERTY(PROP_DESCRIPTION, getDescription()); +} + +int HyperlinkPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData) { + + int bytesRead = 0; + const unsigned char* dataAt = data; + + READ_ENTITY_PROPERTY(PROP_DESCRIPTION, QString, setHref); + READ_ENTITY_PROPERTY(PROP_DESCRIPTION, QString, setDescription); + + return bytesRead; +} From ccb2f17b33f48d46cfa3d1c51325ab04402ff2ec Mon Sep 17 00:00:00 2001 From: Seth Alves <seth.alves@gmail.com> Date: Tue, 9 Jun 2015 11:21:13 -0700 Subject: [PATCH 19/88] 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 a577d7955c56cb07a31c8df6c8146f47b078346d Mon Sep 17 00:00:00 2001 From: Niraj Venkat <venkatn93@gmail.com> Date: Tue, 9 Jun 2015 11:27:57 -0700 Subject: [PATCH 20/88] Removed classes and the "hyperlink entity" --- libraries/entities/src/EntityTypes.cpp | 1 - .../entities/src/HyperlinkEntityItem.cpp | 174 ------------------ libraries/entities/src/HyperlinkEntityItem.h | 95 ---------- 3 files changed, 270 deletions(-) delete mode 100644 libraries/entities/src/HyperlinkEntityItem.cpp delete mode 100644 libraries/entities/src/HyperlinkEntityItem.h diff --git a/libraries/entities/src/EntityTypes.cpp b/libraries/entities/src/EntityTypes.cpp index b20ee28e5a..a41cc22d2e 100644 --- a/libraries/entities/src/EntityTypes.cpp +++ b/libraries/entities/src/EntityTypes.cpp @@ -28,7 +28,6 @@ #include "ZoneEntityItem.h" #include "LineEntityItem.h" #include "PolyVoxEntityItem.h" -#include "HyperlinkEntityItem.h" QMap<EntityTypes::EntityType, QString> EntityTypes::_typeToNameMap; QMap<QString, EntityTypes::EntityType> EntityTypes::_nameToTypeMap; diff --git a/libraries/entities/src/HyperlinkEntityItem.cpp b/libraries/entities/src/HyperlinkEntityItem.cpp deleted file mode 100644 index 4dee124dc1..0000000000 --- a/libraries/entities/src/HyperlinkEntityItem.cpp +++ /dev/null @@ -1,174 +0,0 @@ -// -// HyperlinkEntityItem.cpp -// libraries/entities/src -// -// Created by Niraj Venkat on 6/8/15. -// Copyright 2013 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 <glm/gtx/transform.hpp> - -#include <QDebug> - -#include <ByteCountCoding.h> -#include <PlaneShape.h> - -#include "EntityTree.h" -#include "EntityTreeElement.h" -#include "EntitiesLogging.h" -#include "HyperlinkEntityItem.h" - - -const QString HyperlinkEntityItem::DEFAULT_HREF(""); -const QString HyperlinkEntityItem::DEFAULT_DESCRIPTION(""); - -const float HyperlinkEntityItem::DEFAULT_LINE_HEIGHT = 0.1f; -const xColor HyperlinkEntityItem::DEFAULT_TEXT_COLOR = { 255, 255, 255 }; -const xColor HyperlinkEntityItem::DEFAULT_BACKGROUND_COLOR = { 0, 0, 0 }; - -EntityItemPointer HyperlinkEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { - return EntityItemPointer(new HyperlinkEntityItem(entityID, properties)); -} - -HyperlinkEntityItem::HyperlinkEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : -EntityItem(entityItemID) -{ - _type = EntityTypes::Hyperlink; - _created = properties.getCreated(); - setProperties(properties); -} - -const float TEXT_ENTITY_ITEM_FIXED_DEPTH = 0.01f; - -void HyperlinkEntityItem::setDimensions(const glm::vec3& value) { - // NOTE: Text Entities always have a "depth" of 1cm. - _dimensions = glm::vec3(value.x, value.y, TEXT_ENTITY_ITEM_FIXED_DEPTH); -} - -EntityItemProperties HyperlinkEntityItem::getProperties() const { - EntityItemProperties properties = EntityItem::getProperties(); // get the properties from our base class - - COPY_ENTITY_PROPERTY_TO_PROPERTIES(href, getHref); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(description, getDescription); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(lineHeight, getLineHeight); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(textColor, getTextColorX); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(backgroundColor, getBackgroundColorX); - return properties; -} - -bool HyperlinkEntityItem::setProperties(const EntityItemProperties& properties) { - bool somethingChanged = false; - somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class - - SET_ENTITY_PROPERTY_FROM_PROPERTIES(href, setHref); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(description, setDescription); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(lineHeight, setLineHeight); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(textColor, setTextColor); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(backgroundColor, setBackgroundColor); - - if (somethingChanged) { - bool wantDebug = false; - if (wantDebug) { - uint64_t now = usecTimestampNow(); - int elapsed = now - getLastEdited(); - qCDebug(entities) << "HyperlinkEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << - "now=" << now << " getLastEdited()=" << getLastEdited(); - } - setLastEdited(properties._lastEdited); - } - - return somethingChanged; -} - -int HyperlinkEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, - ReadBitstreamToTreeParams& args, - EntityPropertyFlags& propertyFlags, bool overwriteLocalData) { - - int bytesRead = 0; - const unsigned char* dataAt = data; - - READ_ENTITY_PROPERTY(PROP_HREF, QString, setHref); - READ_ENTITY_PROPERTY(PROP_DESCRIPTION, QString, setDescription); - READ_ENTITY_PROPERTY(PROP_LINE_HEIGHT, float, setLineHeight); - READ_ENTITY_PROPERTY(PROP_TEXT_COLOR, rgbColor, setTextColor); - READ_ENTITY_PROPERTY(PROP_BACKGROUND_COLOR, rgbColor, setBackgroundColor); - - return bytesRead; -} - - -// TODO: eventually only include properties changed since the params.lastViewFrustumSent time -EntityPropertyFlags HyperlinkEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { - EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); - requestedProperties += PROP_HREF; - requestedProperties += PROP_DESCRIPTION; - requestedProperties += PROP_LINE_HEIGHT; - requestedProperties += PROP_TEXT_COLOR; - requestedProperties += PROP_BACKGROUND_COLOR; - return requestedProperties; -} - -void HyperlinkEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, - EntityPropertyFlags& requestedProperties, - EntityPropertyFlags& propertyFlags, - EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, - OctreeElement::AppendState& appendState) const { - - bool successPropertyFits = true; - - APPEND_ENTITY_PROPERTY(PROP_HREF, getHref()); - APPEND_ENTITY_PROPERTY(PROP_DESCRIPTION, getDescription()); - APPEND_ENTITY_PROPERTY(PROP_LINE_HEIGHT, getLineHeight()); - APPEND_ENTITY_PROPERTY(PROP_TEXT_COLOR, getTextColor()); - APPEND_ENTITY_PROPERTY(PROP_BACKGROUND_COLOR, getBackgroundColor()); -} - - -bool HyperlinkEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, - void** intersectedObject, bool precisionPicking) const { - - RayIntersectionInfo rayInfo; - rayInfo._rayStart = origin; - rayInfo._rayDirection = direction; - rayInfo._rayLength = std::numeric_limits<float>::max(); - - PlaneShape plane; - - const glm::vec3 UNROTATED_NORMAL(0.0f, 0.0f, -1.0f); - glm::vec3 normal = _rotation * UNROTATED_NORMAL; - plane.setNormal(normal); - plane.setPoint(getPosition()); // the position is definitely a point on our plane - - bool intersects = plane.findRayIntersection(rayInfo); - - if (intersects) { - glm::vec3 hitAt = origin + (direction * rayInfo._hitDistance); - // now we know the point the ray hit our plane - - glm::mat4 rotation = glm::mat4_cast(getRotation()); - glm::mat4 translation = glm::translate(getPosition()); - glm::mat4 entityToWorldMatrix = translation * rotation; - glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); - - glm::vec3 dimensions = getDimensions(); - glm::vec3 registrationPoint = getRegistrationPoint(); - glm::vec3 corner = -(dimensions * registrationPoint); - AABox entityFrameBox(corner, dimensions); - - glm::vec3 entityFrameHitAt = glm::vec3(worldToEntityMatrix * glm::vec4(hitAt, 1.0f)); - - intersects = entityFrameBox.contains(entityFrameHitAt); - } - - if (intersects) { - distance = rayInfo._hitDistance; - } - return intersects; -} diff --git a/libraries/entities/src/HyperlinkEntityItem.h b/libraries/entities/src/HyperlinkEntityItem.h deleted file mode 100644 index 2d522d6cfb..0000000000 --- a/libraries/entities/src/HyperlinkEntityItem.h +++ /dev/null @@ -1,95 +0,0 @@ -// -// HyperlinkEntityItem.h -// libraries/entities/src -// -// Created by Niraj Venkat on 6/8/15. -// Copyright 2013 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_HyperlinkEntityItem_h -#define hifi_HyperlinkEntityItem_h - -#include "EntityItem.h" - -class HyperlinkEntityItem : public EntityItem { -public: - static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); - - HyperlinkEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); - - ALLOW_INSTANTIATION // This class can be instantiated - - /// set dimensions in domain scale units (0.0 - 1.0) this will also reset radius appropriately - virtual void setDimensions(const glm::vec3& value); - virtual ShapeType getShapeType() const { return SHAPE_TYPE_BOX; } - - // methods for getting/setting all properties of an entity - virtual EntityItemProperties getProperties() const; - virtual bool setProperties(const EntityItemProperties& properties); - - // TODO: eventually only include properties changed since the params.lastViewFrustumSent time - virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const; - - virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, - EntityPropertyFlags& requestedProperties, - EntityPropertyFlags& propertyFlags, - EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, - OctreeElement::AppendState& appendState) const; - - virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, - ReadBitstreamToTreeParams& args, - EntityPropertyFlags& propertyFlags, bool overwriteLocalData); - - virtual bool supportsDetailedRayIntersection() const { return true; } - virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, - void** intersectedObject, bool precisionPicking) const; - - static const QString DEFAULT_HREF; - void setHref(const QString& value) { _href = value; } - const QString& getHref() const { return _href; } - - static const QString DEFAULT_DESCRIPTION; - void setDescription(const QString& value) { _description = value; } - const QString& getDescription() const { return _description; } - - static const float DEFAULT_LINE_HEIGHT; - void setLineHeight(float value) { _lineHeight = value; } - float getLineHeight() const { return _lineHeight; } - - static const xColor DEFAULT_TEXT_COLOR; - const rgbColor& getTextColor() const { return _textColor; } - xColor getTextColorX() const { xColor color = { _textColor[RED_INDEX], _textColor[GREEN_INDEX], _textColor[BLUE_INDEX] }; return color; } - - void setTextColor(const rgbColor& value) { memcpy(_textColor, value, sizeof(_textColor)); } - void setTextColor(const xColor& value) { - _textColor[RED_INDEX] = value.red; - _textColor[GREEN_INDEX] = value.green; - _textColor[BLUE_INDEX] = value.blue; - } - - static const xColor DEFAULT_BACKGROUND_COLOR; - const rgbColor& getBackgroundColor() const { return _backgroundColor; } - xColor getBackgroundColorX() const { xColor color = { _backgroundColor[RED_INDEX], _backgroundColor[GREEN_INDEX], _backgroundColor[BLUE_INDEX] }; return color; } - - void setBackgroundColor(const rgbColor& value) { memcpy(_backgroundColor, value, sizeof(_backgroundColor)); } - void setBackgroundColor(const xColor& value) { - _backgroundColor[RED_INDEX] = value.red; - _backgroundColor[GREEN_INDEX] = value.green; - _backgroundColor[BLUE_INDEX] = value.blue; - } - -protected: - QString _href; - QString _description; - float _lineHeight; - rgbColor _textColor; - rgbColor _backgroundColor; -}; - -#endif // hifi_HyperlinkEntityItem_h From ba0cecb3561c6b47be665354d082a6918b3df034 Mon Sep 17 00:00:00 2001 From: ZappoMan <bradh@konamoxt.com> Date: Tue, 9 Jun 2015 12:32:37 -0700 Subject: [PATCH 21/88] remove attachments from Model - only supported at avatar layer --- libraries/fbx/src/FBXReader.cpp | 28 ------------- libraries/fbx/src/FBXReader.h | 15 +------ libraries/fbx/src/FSTReader.cpp | 4 +- libraries/fbx/src/OBJReader.cpp | 1 - libraries/render-utils/src/Model.cpp | 60 ---------------------------- libraries/render-utils/src/Model.h | 2 - 6 files changed, 4 insertions(+), 106 deletions(-) diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 464deb1059..76108730e8 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -2646,34 +2646,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping, } } geometry.palmDirection = parseVec3(mapping.value("palmDirection", "0, -1, 0").toString()); - - // process attachments - QVariantHash attachments = mapping.value("attach").toHash(); - for (QVariantHash::const_iterator it = attachments.constBegin(); it != attachments.constEnd(); it++) { - FBXAttachment attachment; - attachment.jointIndex = modelIDs.indexOf(processID(it.key())); - attachment.scale = glm::vec3(1.0f, 1.0f, 1.0f); - - QVariantList properties = it->toList(); - if (properties.isEmpty()) { - attachment.url = it->toString(); - } else { - attachment.url = properties.at(0).toString(); - - if (properties.size() >= 2) { - attachment.translation = parseVec3(properties.at(1).toString()); - - if (properties.size() >= 3) { - attachment.rotation = glm::quat(glm::radians(parseVec3(properties.at(2).toString()))); - - if (properties.size() >= 4) { - attachment.scale = parseVec3(properties.at(3).toString()); - } - } - } - } - geometry.attachments.append(attachment); - } // Add sitting points QVariantHash sittingPoints = mapping.value("sit").toHash(); diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index 08ac0e308c..200cd4a121 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -189,17 +189,6 @@ public: Q_DECLARE_METATYPE(FBXAnimationFrame) Q_DECLARE_METATYPE(QVector<FBXAnimationFrame>) -/// An attachment to an FBX document. -class FBXAttachment { -public: - - int jointIndex; - QUrl url; - glm::vec3 translation; - glm::quat rotation; - glm::vec3 scale; -}; - /// A point where an avatar can sit class SittingPoint { public: @@ -256,9 +245,7 @@ public: Extents meshExtents; QVector<FBXAnimationFrame> animationFrames; - - QVector<FBXAttachment> attachments; - + int getJointIndex(const QString& name) const { return jointIndices.value(name) - 1; } QStringList getJointNames() const; diff --git a/libraries/fbx/src/FSTReader.cpp b/libraries/fbx/src/FSTReader.cpp index 32be82b392..a62c0fcea2 100644 --- a/libraries/fbx/src/FSTReader.cpp +++ b/libraries/fbx/src/FSTReader.cpp @@ -124,7 +124,9 @@ FSTReader::ModelType FSTReader::getTypeFromName(const QString& name) { _namesToTypes["head"] = HEAD_MODEL ; _namesToTypes["body"] = BODY_ONLY_MODEL; _namesToTypes["body+head"] = HEAD_AND_BODY_MODEL; - _namesToTypes["attachment"] = ATTACHMENT_MODEL; + + // NOTE: this is not yet implemented, but will be used to allow you to attach fully independent models to your avatar + _namesToTypes["attachment"] = ATTACHMENT_MODEL; } return _namesToTypes[name]; } diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 4a8a2fc53d..f0d3ecf517 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -544,7 +544,6 @@ void fbxDebugDump(const FBXGeometry& fbxgeo) { qCDebug(modelformat) << "---------------- fbxGeometry ----------------"; qCDebug(modelformat) << " hasSkeletonJoints =" << fbxgeo.hasSkeletonJoints; qCDebug(modelformat) << " offset =" << fbxgeo.offset; - qCDebug(modelformat) << " attachments.count() = " << fbxgeo.attachments.count(); qCDebug(modelformat) << " meshes.count() =" << fbxgeo.meshes.count(); foreach (FBXMesh mesh, fbxgeo.meshes) { qCDebug(modelformat) << " vertices.count() =" << mesh.vertices.count(); diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index c578015b9f..824f100d46 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -405,9 +405,6 @@ void Model::reset() { if (_jointStates.isEmpty()) { return; } - foreach (Model* attachment, _attachments) { - attachment->reset(); - } const FBXGeometry& geometry = _geometry->getFBXGeometry(); for (int i = 0; i < _jointStates.size(); i++) { _jointStates[i].setRotationInConstrainedFrame(geometry.joints.at(i).rotation, 0.0f); @@ -419,14 +416,7 @@ void Model::reset() { } bool Model::updateGeometry() { - // NOTE: this is a recursive call that walks all attachments, and their attachments bool needFullUpdate = false; - for (int i = 0; i < _attachments.size(); i++) { - Model* model = _attachments.at(i); - if (model->updateGeometry()) { - needFullUpdate = true; - } - } bool needToRebuild = false; if (_nextGeometry) { @@ -499,12 +489,6 @@ bool Model::updateGeometry() { } _blendedVertexBuffers.push_back(buffer); } - foreach (const FBXAttachment& attachment, fbxGeometry.attachments) { - Model* model = new Model(this); - model->init(); - model->setURL(attachment.url); - _attachments.append(model); - } needFullUpdate = true; } return needFullUpdate; @@ -913,12 +897,6 @@ bool Model::addToScene(std::shared_ptr<render::Scene> scene, render::PendingChan bool somethingAdded = false; - // allow the attachments to add to scene - foreach (Model* attachment, _attachments) { - bool attachementSomethingAdded = attachment->addToScene(scene, pendingChanges); - somethingAdded = somethingAdded || attachementSomethingAdded; - } - foreach (auto renderItem, _transparentRenderItems) { auto item = scene->allocateID(); auto renderData = TransparentMeshPart::Pointer(renderItem); @@ -942,11 +920,6 @@ bool Model::addToScene(std::shared_ptr<render::Scene> scene, render::PendingChan } void Model::removeFromScene(std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) { - // allow the attachments to remove to scene - foreach (Model* attachment, _attachments) { - attachment->removeFromScene(scene, pendingChanges); - } - foreach (auto item, _renderItems.keys()) { pendingChanges.removeItem(item); } @@ -958,10 +931,6 @@ bool Model::render(RenderArgs* renderArgs, float alpha) { return true; // PROFILE_RANGE(__FUNCTION__); - // render the attachments - foreach (Model* attachment, _attachments) { - attachment->render(renderArgs, alpha); - } if (_meshStates.isEmpty()) { return false; } @@ -1623,7 +1592,6 @@ void Model::simulate(float deltaTime, bool fullUpdate) { } void Model::simulateInternal(float deltaTime) { - // NOTE: this is a recursive call that walks all attachments, and their attachments // update the world space transforms for all joints // update animations @@ -1640,31 +1608,7 @@ void Model::simulateInternal(float deltaTime) { _shapesAreDirty = !_shapes.isEmpty(); - // update the attachment transforms and simulate them const FBXGeometry& geometry = _geometry->getFBXGeometry(); - for (int i = 0; i < _attachments.size(); i++) { - const FBXAttachment& attachment = geometry.attachments.at(i); - Model* model = _attachments.at(i); - - glm::vec3 jointTranslation = _translation; - glm::quat jointRotation = _rotation; - if (_showTrueJointTransforms) { - getJointPositionInWorldFrame(attachment.jointIndex, jointTranslation); - getJointRotationInWorldFrame(attachment.jointIndex, jointRotation); - } else { - getVisibleJointPositionInWorldFrame(attachment.jointIndex, jointTranslation); - getVisibleJointRotationInWorldFrame(attachment.jointIndex, jointRotation); - } - - model->setTranslation(jointTranslation + jointRotation * attachment.translation * _scale); - model->setRotation(jointRotation * attachment.rotation); - model->setScale(_scale * attachment.scale); - - if (model->isActive()) { - model->simulateInternal(deltaTime); - } - } - glm::mat4 modelToWorld = glm::mat4_cast(_rotation); for (int i = 0; i < _meshStates.size(); i++) { MeshState& state = _meshStates[i]; @@ -2002,10 +1946,6 @@ void Model::applyNextGeometry() { } void Model::deleteGeometry() { - foreach (Model* attachment, _attachments) { - delete attachment; - } - _attachments.clear(); _blendedVertexBuffers.clear(); _jointStates.clear(); _meshStates.clear(); diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 6f751a5f8d..043b7e659b 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -350,8 +350,6 @@ private: QVector<QVector<QSharedPointer<Texture> > > _dilatedTextures; - QVector<Model*> _attachments; - QSet<WeakAnimationHandlePointer> _animationHandles; QList<AnimationHandlePointer> _runningAnimations; From 01b1e9c4ed29d1c92aebb8330ade9592984814d1 Mon Sep 17 00:00:00 2001 From: David Rowe <david@ctrlaltstudio.com> Date: Tue, 9 Jun 2015 12:33:14 -0700 Subject: [PATCH 22/88] Signal domain changes for those handled by ICE as well as not --- interface/src/scripting/WindowScriptingInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 3aae6a4d4a..6be67a7261 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -31,7 +31,7 @@ WindowScriptingInterface::WindowScriptingInterface() : _formResult(QDialog::Rejected) { const DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler(); - connect(&domainHandler, &DomainHandler::hostnameChanged, this, &WindowScriptingInterface::domainChanged); + connect(&domainHandler, &DomainHandler::connectedToDomain, this, &WindowScriptingInterface::domainChanged); connect(Application::getInstance(), &Application::svoImportRequested, this, &WindowScriptingInterface::svoImportRequested); connect(Application::getInstance(), &Application::domainConnectionRefused, this, &WindowScriptingInterface::domainConnectionRefused); } From a4885ff38ce896f2d71ee57b33009856c5045f02 Mon Sep 17 00:00:00 2001 From: David Rowe <david@ctrlaltstudio.com> Date: Tue, 9 Jun 2015 12:33:30 -0700 Subject: [PATCH 23/88] Close directory window after domain change --- examples/directory.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/directory.js b/examples/directory.js index 03d352e024..b1fac19e8b 100644 --- a/examples/directory.js +++ b/examples/directory.js @@ -45,6 +45,10 @@ var directory = (function () { } } + function onDomainChanged() { + directoryWindow.setVisible(false); + } + function onScriptUpdate() { var oldViewport = viewport; @@ -74,6 +78,7 @@ var directory = (function () { updateButtonPosition(); Controller.mousePressEvent.connect(onMousePressEvent); + Window.domainChanged.connect(onDomainChanged); Script.update.connect(onScriptUpdate); } From e0adb8e38ac1d899509f39f21721f650281e21f4 Mon Sep 17 00:00:00 2001 From: ZappoMan <bradh@konamoxt.com> Date: Tue, 9 Jun 2015 12:57:04 -0700 Subject: [PATCH 24/88] render avatar attachments as model payload items --- interface/src/avatar/Avatar.cpp | 31 ++++++++++++++------------- interface/src/avatar/Avatar.h | 1 - interface/src/avatar/MyAvatar.cpp | 35 ++++++------------------------- interface/src/avatar/MyAvatar.h | 3 --- 4 files changed, 23 insertions(+), 47 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 8708a2b2b0..022c0262e0 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -298,12 +298,22 @@ bool Avatar::addToScene(AvatarSharedPointer self, std::shared_ptr<render::Scene> pendingChanges.resetItem(_renderItemID, avatarPayloadPointer); _skeletonModel.addToScene(scene, pendingChanges); getHead()->getFaceModel().addToScene(scene, pendingChanges); + + for (auto attachmentModel : _attachmentModels) { + attachmentModel->addToScene(scene, pendingChanges); + } + return true; } void Avatar::removeFromScene(AvatarSharedPointer self, std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges) { pendingChanges.removeItem(_renderItemID); _skeletonModel.removeFromScene(scene, pendingChanges); + getHead()->getFaceModel().removeFromScene(scene, pendingChanges); + for (auto attachmentModel : _attachmentModels) { + attachmentModel->removeFromScene(scene, pendingChanges); + } + } void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition, bool postLighting) { @@ -529,6 +539,12 @@ void Avatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, bool getHead()->getFaceModel().removeFromScene(scene, pendingChanges); getHead()->getFaceModel().addToScene(scene, pendingChanges); } + for (auto attachmentModel : _attachmentModels) { + if (attachmentModel->needsFixupInScene()) { + attachmentModel->removeFromScene(scene, pendingChanges); + attachmentModel->addToScene(scene, pendingChanges); + } + } scene->enqueuePendingChanges(pendingChanges); { @@ -544,10 +560,6 @@ void Avatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, bool if (postLighting) { getHand()->render(renderArgs, false); - } else { - // NOTE: we no longer call this here, because we've added all the model parts as renderable items in the scene - //_skeletonModel.render(renderArgs, 1.0f); - renderAttachments(renderArgs); } } getHead()->render(renderArgs, 1.0f, renderFrustum, postLighting); @@ -577,16 +589,6 @@ void Avatar::simulateAttachments(float deltaTime) { } } -void Avatar::renderAttachments(RenderArgs* args) { - // RenderArgs::RenderMode modelRenderMode = (renderMode == RenderArgs::SHADOW_RENDER_MODE) ? - // RenderArgs::SHADOW_RENDER_MODE : RenderArgs::DEFAULT_RENDER_MODE; - /* - foreach (Model* model, _attachmentModels) { - model->render(args, 1.0f); - } - */ -} - void Avatar::updateJointMappings() { // no-op; joint mappings come from skeleton model } @@ -949,6 +951,7 @@ void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) { _attachmentModels.append(model); } while (_attachmentModels.size() > attachmentData.size()) { + // NOTE: what's really going to happen here? This seems dangerous... has the model been removed from the scene? delete _attachmentModels.takeLast(); } diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index dbc59f7d9c..1113496080 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -236,7 +236,6 @@ protected: virtual bool shouldRenderHead(const RenderArgs* renderArgs, const glm::vec3& cameraPosition) const; void simulateAttachments(float deltaTime); - virtual void renderAttachments(RenderArgs* args); virtual void updateJointMappings(); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 8a49d69129..afe0311a29 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1188,6 +1188,12 @@ void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, bo getHead()->getFaceModel().removeFromScene(scene, pendingChanges); getHead()->getFaceModel().addToScene(scene, pendingChanges); } + for (auto attachmentModel : _attachmentModels) { + if (attachmentModel->needsFixupInScene()) { + attachmentModel->removeFromScene(scene, pendingChanges); + attachmentModel->addToScene(scene, pendingChanges); + } + } scene->enqueuePendingChanges(pendingChanges); Camera *camera = Application::getInstance()->getCamera(); @@ -1208,14 +1214,6 @@ void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, bo } }*/ - // Render the body's voxels and head - if (!postLighting) { - - // NOTE: we no longer call this here, because we've added all the model parts as renderable items in the scene - //_skeletonModel.render(renderArgs, 1.0f); - renderAttachments(renderArgs); - } - // Render head so long as the camera isn't inside it if (shouldRenderHead(renderArgs, cameraPos)) { getHead()->render(renderArgs, 1.0f, renderFrustum, postLighting); @@ -1571,27 +1569,6 @@ void MyAvatar::updateMotionBehavior() { _feetTouchFloor = menu->isOptionChecked(MenuOption::ShiftHipsForIdleAnimations); } -void MyAvatar::renderAttachments(RenderArgs* args) { - if (Application::getInstance()->getCamera()->getMode() != CAMERA_MODE_FIRST_PERSON || args->_renderMode == RenderArgs::MIRROR_RENDER_MODE) { - Avatar::renderAttachments(args); - return; - } - const FBXGeometry& geometry = _skeletonModel.getGeometry()->getFBXGeometry(); - QString headJointName = (geometry.headJointIndex == -1) ? QString() : geometry.joints.at(geometry.headJointIndex).name; - // RenderArgs::RenderMode modelRenderMode = (renderMode == RenderArgs::SHADOW_RENDER_MODE) ? - // RenderArgs::SHADOW_RENDER_MODE : RenderArgs::DEFAULT_RENDER_MODE; - - // FIX ME - attachments need to be added to scene too... - /* - for (int i = 0; i < _attachmentData.size(); i++) { - const QString& jointName = _attachmentData.at(i).jointName; - if (jointName != headJointName && jointName != "Head") { - _attachmentModels.at(i)->render(args, 1.0f); - } - } - */ -} - //Renders sixense laser pointers for UI selection with controllers void MyAvatar::renderLaserPointers() { const float PALM_TIP_ROD_RADIUS = 0.002f; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index c8d16e8cb0..a3dc34e6e0 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -197,9 +197,6 @@ public slots: signals: void transformChanged(); -protected: - virtual void renderAttachments(RenderArgs* args); - private: // These are made private for MyAvatar so that you will use the "use" methods instead From 2c6ebcb06a90b8f4e9d82a9ad94c2c60ec7785f7 Mon Sep 17 00:00:00 2001 From: ZappoMan <bradh@konamoxt.com> Date: Tue, 9 Jun 2015 12:57:14 -0700 Subject: [PATCH 25/88] remove a bunch of cruft from Model --- libraries/render-utils/src/Model.cpp | 422 --------------------------- libraries/render-utils/src/Model.h | 19 +- 2 files changed, 1 insertion(+), 440 deletions(-) diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 824f100d46..3026efceb2 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -927,206 +927,6 @@ void Model::removeFromScene(std::shared_ptr<render::Scene> scene, render::Pendin _readyWhenAdded = false; } -bool Model::render(RenderArgs* renderArgs, float alpha) { - return true; // - PROFILE_RANGE(__FUNCTION__); - - if (_meshStates.isEmpty()) { - return false; - } - - renderSetup(renderArgs); - return renderCore(renderArgs, alpha); -} - -bool Model::renderCore(RenderArgs* args, float alpha) { - return true; - - PROFILE_RANGE(__FUNCTION__); - if (!_viewState) { - return false; - } - - auto mode = args->_renderMode; - - // Let's introduce a gpu::Batch to capture all the calls to the graphics api - _renderBatch.clear(); - gpu::Batch& batch = _renderBatch; - - // Setup the projection matrix - if (args && args->_viewFrustum) { - glm::mat4 proj; - // If for easier debug depending on the pass - if (mode == RenderArgs::SHADOW_RENDER_MODE) { - args->_viewFrustum->evalProjectionMatrix(proj); - } else { - args->_viewFrustum->evalProjectionMatrix(proj); - } - batch.setProjectionTransform(proj); - } - - // Capture the view matrix once for the rendering of this model - if (_transforms.empty()) { - _transforms.push_back(Transform()); - } - - _transforms[0] = _viewState->getViewTransform(); - - // apply entity translation offset to the viewTransform in one go (it's a preTranslate because viewTransform goes from world to eye space) - _transforms[0].preTranslate(-_translation); - - batch.setViewTransform(_transforms[0]); - - /*DependencyManager::get<TextureCache>()->setPrimaryDrawBuffers( - mode == RenderArgs::DEFAULT_RENDER_MODE || mode == RenderArgs::DIFFUSE_RENDER_MODE, - mode == RenderArgs::DEFAULT_RENDER_MODE || mode == RenderArgs::NORMAL_RENDER_MODE, - mode == RenderArgs::DEFAULT_RENDER_MODE); - */ - /*if (mode != RenderArgs::SHADOW_RENDER_MODE)*/ { - GLenum buffers[3]; - int bufferCount = 0; - - // if (mode == RenderArgs::DEFAULT_RENDER_MODE || mode == RenderArgs::DIFFUSE_RENDER_MODE) { - if (mode != RenderArgs::SHADOW_RENDER_MODE) { - buffers[bufferCount++] = GL_COLOR_ATTACHMENT0; - } - // if (mode == RenderArgs::DEFAULT_RENDER_MODE || mode == RenderArgs::NORMAL_RENDER_MODE) { - if (mode != RenderArgs::SHADOW_RENDER_MODE) { - buffers[bufferCount++] = GL_COLOR_ATTACHMENT1; - } - // if (mode == RenderArgs::DEFAULT_RENDER_MODE) { - if (mode != RenderArgs::SHADOW_RENDER_MODE) { - buffers[bufferCount++] = GL_COLOR_ATTACHMENT2; - } - GLBATCH(glDrawBuffers)(bufferCount, buffers); - // batch.setFramebuffer(DependencyManager::get<TextureCache>()->getPrimaryOpaqueFramebuffer()); - } - - const float DEFAULT_ALPHA_THRESHOLD = 0.5f; - - - //renderMeshes(batch, mode, translucent, alphaThreshold, hasTangents, hasSpecular, isSkinned, args, forceRenderMeshes); - int opaqueMeshPartsRendered = 0; - opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, false, false, false, args, true); - opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, false, true, false, args, true); - opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, true, false, false, args, true); - opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, false, true, true, false, args, true); - opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, false, false, false, args, true); - opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, false, true, false, args, true); - opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, true, false, false, args, true); - opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, false, true, true, true, false, args, true); - - opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, false, false, false, false, args, true); - opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, false, true, false, false, args, true); - opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, true, false, false, false, args, true); - opaqueMeshPartsRendered += renderMeshes(batch, mode, false, DEFAULT_ALPHA_THRESHOLD, true, true, true, false, false, args, true); - - // render translucent meshes afterwards - //DependencyManager::get<TextureCache>()->setPrimaryDrawBuffers(false, true, true); - { - GLenum buffers[2]; - int bufferCount = 0; - buffers[bufferCount++] = GL_COLOR_ATTACHMENT1; - buffers[bufferCount++] = GL_COLOR_ATTACHMENT2; - GLBATCH(glDrawBuffers)(bufferCount, buffers); - } - - int translucentMeshPartsRendered = 0; - const float MOSTLY_OPAQUE_THRESHOLD = 0.75f; - translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, false, false, false, args, true); - translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, false, true, false, args, true); - translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, true, false, false, args, true); - translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, false, true, true, false, args, true); - translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, false, false, false, args, true); - translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, false, true, false, args, true); - translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, true, false, false, args, true); - translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_OPAQUE_THRESHOLD, false, true, true, true, false, args, true); - - { - GLenum buffers[1]; - int bufferCount = 0; - buffers[bufferCount++] = GL_COLOR_ATTACHMENT0; - GLBATCH(glDrawBuffers)(bufferCount, buffers); - } - - // if (mode == RenderArgs::DEFAULT_RENDER_MODE || mode == RenderArgs::DIFFUSE_RENDER_MODE) { - if (mode != RenderArgs::SHADOW_RENDER_MODE) { - // batch.setFramebuffer(DependencyManager::get<TextureCache>()->getPrimaryTransparentFramebuffer()); - - const float MOSTLY_TRANSPARENT_THRESHOLD = 0.0f; - translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, false, false, false, args, true); - translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, false, true, false, args, true); - translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, true, false, false, args, true); - translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, false, true, true, false, args, true); - translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, false, false, false, args, true); - translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, false, true, false, args, true); - translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, true, false, false, args, true); - translucentMeshPartsRendered += renderMeshes(batch, mode, true, MOSTLY_TRANSPARENT_THRESHOLD, false, true, true, true, false, args, true); - - // batch.setFramebuffer(DependencyManager::get<TextureCache>()->getPrimaryOpaqueFramebuffer()); - } - - GLBATCH(glDepthMask)(true); - GLBATCH(glDepthFunc)(GL_LESS); - GLBATCH(glDisable)(GL_CULL_FACE); - - if (mode == RenderArgs::SHADOW_RENDER_MODE) { - GLBATCH(glCullFace)(GL_BACK); - } - - GLBATCH(glActiveTexture)(GL_TEXTURE0 + 1); - GLBATCH(glBindTexture)(GL_TEXTURE_2D, 0); - GLBATCH(glActiveTexture)(GL_TEXTURE0 + 2); - GLBATCH(glBindTexture)(GL_TEXTURE_2D, 0); - GLBATCH(glActiveTexture)(GL_TEXTURE0 + 3); - GLBATCH(glBindTexture)(GL_TEXTURE_2D, 0); - GLBATCH(glActiveTexture)(GL_TEXTURE0); - GLBATCH(glBindTexture)(GL_TEXTURE_2D, 0); - - // deactivate vertex arrays after drawing - GLBATCH(glDisableClientState)(GL_NORMAL_ARRAY); - GLBATCH(glDisableClientState)(GL_VERTEX_ARRAY); - GLBATCH(glDisableClientState)(GL_TEXTURE_COORD_ARRAY); - GLBATCH(glDisableClientState)(GL_COLOR_ARRAY); - GLBATCH(glDisableVertexAttribArray)(gpu::Stream::TANGENT); - GLBATCH(glDisableVertexAttribArray)(gpu::Stream::SKIN_CLUSTER_INDEX); - GLBATCH(glDisableVertexAttribArray)(gpu::Stream::SKIN_CLUSTER_WEIGHT); - - // bind with 0 to switch back to normal operation - GLBATCH(glBindBuffer)(GL_ARRAY_BUFFER, 0); - GLBATCH(glBindBuffer)(GL_ELEMENT_ARRAY_BUFFER, 0); - GLBATCH(glBindTexture)(GL_TEXTURE_2D, 0); - - // Back to no program - GLBATCH(glUseProgram)(0); - - // Render! - { - PROFILE_RANGE("render Batch"); - - #if defined(ANDROID) - #else - glPushMatrix(); - #endif - - ::gpu::GLBackend::renderBatch(batch, true); // force sync with gl state here - - #if defined(ANDROID) - #else - glPopMatrix(); - #endif - } - - // restore all the default material settings - _viewState->setupWorldLight(); - - #ifdef WANT_DEBUG_MESHBOXES - renderDebugMeshBoxes(); - #endif - - return true; -} - void Model::renderDebugMeshBoxes() { int colorNdx = 0; _mutex.lock(); @@ -2295,22 +2095,6 @@ void Model::segregateMeshGroups() { _meshGroupsKnown = true; } -QVector<int>* Model::pickMeshList(bool translucent, float alphaThreshold, bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, bool isWireframe) { - PROFILE_RANGE(__FUNCTION__); - - // depending on which parameters we were called with, pick the correct mesh group to render - QVector<int>* whichList = NULL; - - RenderKey key(translucent, hasLightmap, hasTangents, hasSpecular, isSkinned, isWireframe); - - auto bucket = _renderBuckets.find(key.getRaw()); - if (bucket != _renderBuckets.end()) { - whichList = &(*bucket).second._meshes; - } - - return whichList; -} - void Model::pickPrograms(gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold, bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, bool isWireframe, RenderArgs* args, Locations*& locations) { @@ -2339,212 +2123,6 @@ void Model::pickPrograms(gpu::Batch& batch, RenderMode mode, bool translucent, f } } -int Model::renderMeshes(gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold, - bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, bool isWireframe, RenderArgs* args, - bool forceRenderSomeMeshes) { - - PROFILE_RANGE(__FUNCTION__); - int meshPartsRendered = 0; - - //Pick the mesh list with the requested render flags - QVector<int>* whichList = pickMeshList(translucent, alphaThreshold, hasLightmap, hasTangents, hasSpecular, isSkinned, isWireframe); - if (!whichList) { - return 0; - } - QVector<int>& list = *whichList; - - // If this list has nothing to render, then don't bother proceeding. This saves us on binding to programs - if (list.empty()) { - return 0; - } - - Locations* locations = nullptr; - pickPrograms(batch, mode, translucent, alphaThreshold, hasLightmap, hasTangents, hasSpecular, isSkinned, isWireframe, - args, locations); - meshPartsRendered = renderMeshesFromList(list, batch, mode, translucent, alphaThreshold, - args, locations, forceRenderSomeMeshes); - - return meshPartsRendered; -} - - -int Model::renderMeshesFromList(QVector<int>& list, gpu::Batch& batch, RenderMode mode, bool translucent, float alphaThreshold, RenderArgs* args, - Locations* locations, bool forceRenderMeshes) { - PROFILE_RANGE(__FUNCTION__); - - auto textureCache = DependencyManager::get<TextureCache>(); - - QString lastMaterialID; - int meshPartsRendered = 0; - updateVisibleJointStates(); - const FBXGeometry& geometry = _geometry->getFBXGeometry(); - const QVector<NetworkMesh>& networkMeshes = _geometry->getMeshes(); - - // i is the "index" from the original networkMeshes QVector... - foreach (int i, list) { - - // if our index is ever out of range for either meshes or networkMeshes, then skip it, and set our _meshGroupsKnown - // to false to rebuild out mesh groups. - - if (i < 0 || i >= networkMeshes.size() || i > geometry.meshes.size()) { - _meshGroupsKnown = false; // regenerate these lists next time around. - _readyWhenAdded = false; // in case any of our users are using scenes - invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid - continue; - } - - // exit early if the translucency doesn't match what we're drawing - const NetworkMesh& networkMesh = networkMeshes.at(i); - const FBXMesh& mesh = geometry.meshes.at(i); - - batch.setIndexBuffer(gpu::UINT32, (networkMesh._indexBuffer), 0); - int vertexCount = mesh.vertices.size(); - if (vertexCount == 0) { - // sanity check - continue; - } - - // if we got here, then check to see if this mesh is in view - if (args) { - bool shouldRender = true; - if (args->_viewFrustum) { - - shouldRender = forceRenderMeshes || - args->_viewFrustum->boxInFrustum(_calculatedMeshBoxes.at(i)) != ViewFrustum::OUTSIDE; - - if (shouldRender && !forceRenderMeshes) { - float distance = args->_viewFrustum->distanceToCamera(_calculatedMeshBoxes.at(i).calcCenter()); - shouldRender = !_viewState ? false : _viewState->shouldRenderMesh(_calculatedMeshBoxes.at(i).getLargestDimension(), - distance); - } - } - - if (!shouldRender) { - continue; // skip this mesh - } - } - - const MeshState& state = _meshStates.at(i); - if (state.clusterMatrices.size() > 1) { - GLBATCH(glUniformMatrix4fv)(locations->clusterMatrices, state.clusterMatrices.size(), false, - (const float*)state.clusterMatrices.constData()); - batch.setModelTransform(Transform()); - } else { - batch.setModelTransform(Transform(state.clusterMatrices[0])); - } - - if (mesh.blendshapes.isEmpty()) { - batch.setInputFormat(networkMesh._vertexFormat); - batch.setInputStream(0, *networkMesh._vertexStream); - } else { - batch.setInputFormat(networkMesh._vertexFormat); - batch.setInputBuffer(0, _blendedVertexBuffers[i], 0, sizeof(glm::vec3)); - batch.setInputBuffer(1, _blendedVertexBuffers[i], vertexCount * sizeof(glm::vec3), sizeof(glm::vec3)); - batch.setInputStream(2, *networkMesh._vertexStream); - } - - if (mesh.colors.isEmpty()) { - GLBATCH(glColor4f)(1.0f, 1.0f, 1.0f, 1.0f); - } - - qint64 offset = 0; - for (int j = 0; j < networkMesh.parts.size(); j++) { - const NetworkMeshPart& networkPart = networkMesh.parts.at(j); - const FBXMeshPart& part = mesh.parts.at(j); - model::MaterialPointer material = part._material; - if ((networkPart.isTranslucent() || part.opacity != 1.0f) != translucent) { - offset += (part.quadIndices.size() + part.triangleIndices.size()) * sizeof(int); - continue; - } - - // apply material properties - if (mode == RenderArgs::SHADOW_RENDER_MODE) { - /// GLBATCH(glBindTexture)(GL_TEXTURE_2D, 0); - - } else { - if (lastMaterialID != part.materialID) { - const bool wantDebug = false; - if (wantDebug) { - qCDebug(renderutils) << "Material Changed ---------------------------------------------"; - qCDebug(renderutils) << "part INDEX:" << j; - qCDebug(renderutils) << "NEW part.materialID:" << part.materialID; - } - - if (locations->materialBufferUnit >= 0) { - batch.setUniformBuffer(locations->materialBufferUnit, material->getSchemaBuffer()); - } - - Texture* diffuseMap = networkPart.diffuseTexture.data(); - if (mesh.isEye && diffuseMap) { - diffuseMap = (_dilatedTextures[i][j] = - static_cast<DilatableNetworkTexture*>(diffuseMap)->getDilatedTexture(_pupilDilation)).data(); - } - static bool showDiffuse = true; - if (showDiffuse && diffuseMap) { - batch.setUniformTexture(0, diffuseMap->getGPUTexture()); - - } else { - batch.setUniformTexture(0, textureCache->getWhiteTexture()); - } - - if (locations->texcoordMatrices >= 0) { - glm::mat4 texcoordTransform[2]; - if (!part.diffuseTexture.transform.isIdentity()) { - part.diffuseTexture.transform.getMatrix(texcoordTransform[0]); - } - if (!part.emissiveTexture.transform.isIdentity()) { - part.emissiveTexture.transform.getMatrix(texcoordTransform[1]); - } - GLBATCH(glUniformMatrix4fv)(locations->texcoordMatrices, 2, false, (const float*) &texcoordTransform); - } - - if (!mesh.tangents.isEmpty()) { - Texture* normalMap = networkPart.normalTexture.data(); - batch.setUniformTexture(1, !normalMap ? - textureCache->getBlueTexture() : normalMap->getGPUTexture()); - - } - - if (locations->specularTextureUnit >= 0) { - Texture* specularMap = networkPart.specularTexture.data(); - batch.setUniformTexture(locations->specularTextureUnit, !specularMap ? - textureCache->getWhiteTexture() : specularMap->getGPUTexture()); - } - } - - // HACK: For unkwon reason (yet!) this code that should be assigned only if the material changes need to be called for every - // drawcall with an emissive, so let's do it for now. - if (locations->emissiveTextureUnit >= 0) { - // assert(locations->emissiveParams >= 0); // we should have the emissiveParams defined in the shader - float emissiveOffset = part.emissiveParams.x; - float emissiveScale = part.emissiveParams.y; - GLBATCH(glUniform2f)(locations->emissiveParams, emissiveOffset, emissiveScale); - - Texture* emissiveMap = networkPart.emissiveTexture.data(); - batch.setUniformTexture(locations->emissiveTextureUnit, !emissiveMap ? - textureCache->getWhiteTexture() : emissiveMap->getGPUTexture()); - } - - lastMaterialID = part.materialID; - } - - meshPartsRendered++; - - if (part.quadIndices.size() > 0) { - batch.drawIndexed(gpu::QUADS, part.quadIndices.size(), offset); - offset += part.quadIndices.size() * sizeof(int); - } - - if (part.triangleIndices.size() > 0) { - batch.drawIndexed(gpu::TRIANGLES, part.triangleIndices.size(), offset); - offset += part.triangleIndices.size() * sizeof(int); - } - - } - } - - return meshPartsRendered; -} ModelBlender::ModelBlender() : _pendingBlenders(0) { diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 043b7e659b..c96e006aa8 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -399,18 +399,8 @@ private: int _debugMeshBoxesID = GeometryCache::UNKNOWN_ID; // helper functions used by render() or renderInScene() - bool renderCore(RenderArgs* args, float alpha); - int renderMeshes(gpu::Batch& batch, RenderArgs::RenderMode mode, bool translucent, float alphaThreshold, - bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, bool isWireframe, RenderArgs* args = NULL, - bool forceRenderMeshes = false); - + void setupBatchTransform(gpu::Batch& batch, RenderArgs* args); - QVector<int>* pickMeshList(bool translucent, float alphaThreshold, bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, bool isWireframe); - - int renderMeshesFromList(QVector<int>& list, gpu::Batch& batch, RenderArgs::RenderMode mode, bool translucent, float alphaThreshold, - RenderArgs* args, Locations* locations, - bool forceRenderSomeMeshes = false); - static void pickPrograms(gpu::Batch& batch, RenderArgs::RenderMode mode, bool translucent, float alphaThreshold, bool hasLightmap, bool hasTangents, bool hasSpecular, bool isSkinned, bool isWireframe, RenderArgs* args, Locations*& locations); @@ -543,13 +533,6 @@ private: bool _readyWhenAdded = false; bool _needsReload = true; - -private: - // FIX ME - We want to get rid of this interface for rendering... - // right now the only remaining user are Avatar attachments. - // that usage has been temporarily disabled... - bool render(RenderArgs* renderArgs, float alpha = 1.0f); - }; Q_DECLARE_METATYPE(QPointer<Model>) From 23dab530f911396cea8896f927dbf74d77b5c52b Mon Sep 17 00:00:00 2001 From: Seth Alves <seth.alves@gmail.com> Date: Tue, 9 Jun 2015 14:11:01 -0700 Subject: [PATCH 26/88] 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 <seth.alves@gmail.com> Date: Tue, 9 Jun 2015 14:26:44 -0700 Subject: [PATCH 27/88] 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 d90e43c5aa36f0e88dcb2d71bba00e00f50d4af4 Mon Sep 17 00:00:00 2001 From: Niraj Venkat <venkatn93@gmail.com> Date: Tue, 9 Jun 2015 16:15:47 -0700 Subject: [PATCH 28/88] Adding text boxes for hyperlink href and description --- examples/html/entityProperties.html | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/examples/html/entityProperties.html b/examples/html/entityProperties.html index d9cad0feff..bebbb1d80e 100644 --- a/examples/html/entityProperties.html +++ b/examples/html/entityProperties.html @@ -360,6 +360,10 @@ var elVoxelVolumeSizeZ = document.getElementById("property-voxel-volume-size-z"); var elVoxelSurfaceStyle = document.getElementById("property-voxel-surface-style"); + var elHyperlinkHref = document.getElementById("property-hyperlink-href"); + var elHyperlinkDescription = document.getElementById("property-hyperlink-description"); + + if (window.EventBridge !== undefined) { EventBridge.scriptEventReceived.connect(function(data) { @@ -850,6 +854,10 @@ elVoxelVolumeSizeZ.addEventListener('change', voxelVolumeSizeChangeFunction); elVoxelSurfaceStyle.addEventListener('change', createEmitTextPropertyUpdateFunction('voxelSurfaceStyle')); + var hyperlinkChangeFunction = createEmitGroupTextPropertyUpdateFunction('hyperlink','href'); + + var hyperlinkChangeFunction = createEmitGroupTextPropertyUpdateFunction('hyperlink','description'); + elMoveSelectionToGrid.addEventListener("click", function() { EventBridge.emitWebEvent(JSON.stringify({ @@ -937,6 +945,18 @@ <input type="text" id="property-name"></input> </div> </div> + + <div class="property"> + <div class="label">Hyperlink</div> + <div class="input-area">Href<br></div> + <div class="value"> + <input id="property-hyperlink-href" class="url"></input> + </div> + <div class="input-area">Description<br></div> <div class="value"> + <input id="property-hyperlink-description" class="url"></input> + </div> + </div> + <div class="property"> <span class="label">Locked</span> <span class="value"> From b1a209b9db9e6cdad15ef47c37a47a7cc2208ee7 Mon Sep 17 00:00:00 2001 From: Seth Alves <seth.alves@gmail.com> Date: Tue, 9 Jun 2015 16:17:48 -0700 Subject: [PATCH 29/88] 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<EntityTree*>(_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<ObjectMotionState*>(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 <btBulletDynamicsCommon.h> - #include <QUuid> +#include <btBulletDynamicsCommon.h> + #include <EntityItem.h> +#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<ObjectMotionState*>(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<ObjectMotionState*>(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 4df87ec4e833a1414d21a15304ab922c51b52b05 Mon Sep 17 00:00:00 2001 From: Andrew Meadows <andrew@highfidelity.io> Date: Tue, 9 Jun 2015 17:23:37 -0700 Subject: [PATCH 30/88] fix rendering of simulation ownership debug info --- .../src/RenderableBoxEntityItem.cpp | 51 ++++++++++++++----- .../src/RenderableDebugableEntityItem.cpp | 44 ++++++++-------- .../src/RenderableDebugableEntityItem.h | 1 - .../src/RenderableModelEntityItem.cpp | 13 ----- 4 files changed, 59 insertions(+), 50 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp b/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp index b2400b797e..2d72897faf 100644 --- a/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp @@ -15,6 +15,7 @@ #include <gpu/Batch.h> #include <DeferredLightingEffect.h> +#include <ObjectMotionState.h> #include <PerfStat.h> #include "RenderableBoxEntityItem.h" @@ -27,23 +28,45 @@ void RenderableBoxEntityItem::render(RenderArgs* args) { PerformanceTimer perfTimer("RenderableBoxEntityItem::render"); Q_ASSERT(getType() == EntityTypes::Box); glm::vec4 cubeColor(toGlm(getXColor()), getLocalRenderAlpha()); - - bool debugSimulationOwnership = args->_debugFlags & RenderArgs::RENDER_DEBUG_SIMULATION_OWNERSHIP; - bool highlightSimulationOwnership = false; - if (debugSimulationOwnership) { - auto nodeList = DependencyManager::get<NodeList>(); - const QUuid& myNodeID = nodeList->getSessionUUID(); - highlightSimulationOwnership = (getSimulatorID() == myNodeID); - } Q_ASSERT(args->_batch); gpu::Batch& batch = *args->_batch; batch.setModelTransform(getTransformToCenter()); - if (highlightSimulationOwnership) { - DependencyManager::get<DeferredLightingEffect>()->renderWireCube(batch, 1.0f, cubeColor); - } else { - DependencyManager::get<DeferredLightingEffect>()->renderSolidCube(batch, 1.0f, cubeColor); - } + DependencyManager::get<DeferredLightingEffect>()->renderSolidCube(batch, 1.0f, cubeColor); - RenderableDebugableEntityItem::render(this, args); + // TODO: use RenderableDebugableEntityItem::render (instead of the hack below) + // when we fix the scaling bug that breaks RenderableDebugableEntityItem for boxes. + if (args->_debugFlags & RenderArgs::RENDER_DEBUG_SIMULATION_OWNERSHIP) { + Q_ASSERT(args->_batch); + gpu::Batch& batch = *args->_batch; + Transform transform = getTransformToCenter(); + //transform.postScale(entity->getDimensions()); // HACK: this line breaks for BoxEntityItem + batch.setModelTransform(transform); + + auto nodeList = DependencyManager::get<NodeList>(); + const QUuid& myNodeID = nodeList->getSessionUUID(); + bool highlightSimulationOwnership = (getSimulatorID() == myNodeID); + if (highlightSimulationOwnership) { + glm::vec4 greenColor(0.0f, 1.0f, 0.2f, 1.0f); + DependencyManager::get<DeferredLightingEffect>()->renderWireCube(batch, 1.08f, greenColor); + } + + quint64 now = usecTimestampNow(); + if (now - getLastEditedFromRemote() < 0.1f * USECS_PER_SECOND) { + glm::vec4 redColor(1.0f, 0.0f, 0.0f, 1.0f); + DependencyManager::get<DeferredLightingEffect>()->renderWireCube(batch, 1.16f, redColor); + } + + if (now - getLastBroadcast() < 0.2f * USECS_PER_SECOND) { + glm::vec4 yellowColor(1.0f, 1.0f, 0.2f, 1.0f); + DependencyManager::get<DeferredLightingEffect>()->renderWireCube(batch, 1.24f, yellowColor); + } + + ObjectMotionState* motionState = static_cast<ObjectMotionState*>(getPhysicsInfo()); + if (motionState && motionState->isActive()) { + glm::vec4 blueColor(0.0f, 0.0f, 1.0f, 1.0f); + DependencyManager::get<DeferredLightingEffect>()->renderWireCube(batch, 1.32f, blueColor); + } + } + //RenderableDebugableEntityItem::render(this, args); // TODO: use this instead of the hack above }; diff --git a/libraries/entities-renderer/src/RenderableDebugableEntityItem.cpp b/libraries/entities-renderer/src/RenderableDebugableEntityItem.cpp index ca81ae4f2b..53213eae51 100644 --- a/libraries/entities-renderer/src/RenderableDebugableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableDebugableEntityItem.cpp @@ -15,9 +15,8 @@ #include <gpu/GPUConfig.h> #include <gpu/Batch.h> - #include <DeferredLightingEffect.h> -#include <PhysicsEngine.h> +#include <ObjectMotionState.h> #include "RenderableDebugableEntityItem.h" @@ -26,42 +25,43 @@ void RenderableDebugableEntityItem::renderBoundingBox(EntityItem* entity, Render float puffedOut, glm::vec4& color) { Q_ASSERT(args->_batch); gpu::Batch& batch = *args->_batch; - batch.setModelTransform(entity->getTransformToCenter()); + Transform transform = entity->getTransformToCenter(); + transform.postScale(entity->getDimensions()); + batch.setModelTransform(transform); DependencyManager::get<DeferredLightingEffect>()->renderWireCube(batch, 1.0f + puffedOut, color); } -void RenderableDebugableEntityItem::renderHoverDot(EntityItem* entity, RenderArgs* args) { - const int SLICES = 8, STACKS = 8; - float radius = 0.05f; - glm::vec4 blueColor(0.0f, 0.0f, 1.0f, 1.0f); - - Q_ASSERT(args->_batch); - gpu::Batch& batch = *args->_batch; - Transform transform = entity->getTransformToCenter(); - // Cancel true dimensions and set scale to 2 * radius (diameter) - transform.postScale(2.0f * glm::vec3(radius, radius, radius) / entity->getDimensions()); - batch.setModelTransform(transform); - DependencyManager::get<DeferredLightingEffect>()->renderSolidSphere(batch, 0.5f, SLICES, STACKS, blueColor); -} - void RenderableDebugableEntityItem::render(EntityItem* entity, RenderArgs* args) { - bool debugSimulationOwnership = args->_debugFlags & RenderArgs::RENDER_DEBUG_SIMULATION_OWNERSHIP; + 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); + + auto nodeList = DependencyManager::get<NodeList>(); + const QUuid& myNodeID = nodeList->getSessionUUID(); + bool highlightSimulationOwnership = (entity->getSimulatorID() == myNodeID); + if (highlightSimulationOwnership) { + glm::vec4 greenColor(0.0f, 1.0f, 0.2f, 1.0f); + DependencyManager::get<DeferredLightingEffect>()->renderWireCube(batch, 1.08f, greenColor); + } - if (debugSimulationOwnership) { quint64 now = usecTimestampNow(); if (now - entity->getLastEditedFromRemote() < 0.1f * USECS_PER_SECOND) { glm::vec4 redColor(1.0f, 0.0f, 0.0f, 1.0f); - renderBoundingBox(entity, args, 0.2f, redColor); + DependencyManager::get<DeferredLightingEffect>()->renderWireCube(batch, 1.16f, redColor); } if (now - entity->getLastBroadcast() < 0.2f * USECS_PER_SECOND) { glm::vec4 yellowColor(1.0f, 1.0f, 0.2f, 1.0f); - renderBoundingBox(entity, args, 0.3f, yellowColor); + DependencyManager::get<DeferredLightingEffect>()->renderWireCube(batch, 1.24f, yellowColor); } ObjectMotionState* motionState = static_cast<ObjectMotionState*>(entity->getPhysicsInfo()); if (motionState && motionState->isActive()) { - renderHoverDot(entity, args); + glm::vec4 blueColor(0.0f, 0.0f, 1.0f, 1.0f); + DependencyManager::get<DeferredLightingEffect>()->renderWireCube(batch, 1.32f, blueColor); } } } diff --git a/libraries/entities-renderer/src/RenderableDebugableEntityItem.h b/libraries/entities-renderer/src/RenderableDebugableEntityItem.h index 758bac353b..2680d882f5 100644 --- a/libraries/entities-renderer/src/RenderableDebugableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableDebugableEntityItem.h @@ -17,7 +17,6 @@ class RenderableDebugableEntityItem { public: static void renderBoundingBox(EntityItem* entity, RenderArgs* args, float puffedOut, glm::vec4& color); - static void renderHoverDot(EntityItem* entity, RenderArgs* args); static void render(EntityItem* entity, RenderArgs* args); }; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index e0bc493a5c..14a64d289e 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -201,14 +201,6 @@ void RenderableModelEntityItem::render(RenderArgs* args) { glm::vec3 position = getPosition(); glm::vec3 dimensions = getDimensions(); - bool debugSimulationOwnership = args->_debugFlags & RenderArgs::RENDER_DEBUG_SIMULATION_OWNERSHIP; - bool highlightSimulationOwnership = false; - if (debugSimulationOwnership) { - auto nodeList = DependencyManager::get<NodeList>(); - const QUuid& myNodeID = nodeList->getSessionUUID(); - highlightSimulationOwnership = (getSimulatorID() == myNodeID); - } - if (hasModel()) { if (_model) { if (QUrl(getModelURL()) != _model->getURL()) { @@ -274,11 +266,6 @@ void RenderableModelEntityItem::render(RenderArgs* args) { } } } - - if (highlightSimulationOwnership) { - glm::vec4 greenColor(0.0f, 1.0f, 0.0f, 1.0f); - RenderableDebugableEntityItem::renderBoundingBox(this, args, 0.0f, greenColor); - } } else { glm::vec4 greenColor(0.0f, 1.0f, 0.0f, 1.0f); RenderableDebugableEntityItem::renderBoundingBox(this, args, 0.0f, greenColor); From 111284158643fd79bd9b85f5b38a4d9ce98772e1 Mon Sep 17 00:00:00 2001 From: Stephen Birarda <commit@birarda.com> Date: Tue, 9 Jun 2015 19:01:03 -0700 Subject: [PATCH 31/88] 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<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::HandleAddress); + DependencyManager::get<NodeList>()->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 <bdavis@saintandreas.org> Date: Wed, 10 Jun 2015 08:12:58 -0700 Subject: [PATCH 32/88] 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 <bdavis@saintandreas.org> Date: Wed, 10 Jun 2015 09:36:14 -0700 Subject: [PATCH 33/88] 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 <QOpenGLFramebufferObject> #include <QOpenGLTexture> +#include <glm/gtc/type_ptr.hpp> + #include <avatar/AvatarManager.h> +#include <DeferredLightingEffect.h> #include <GLMHelpers.h> -#include <PathUtils.h> #include <gpu/GLBackend.h> #include <GLMHelpers.h> -#include <PerfStat.h> #include <OffscreenUi.h> +#include <PathUtils.h> +#include <PerfStat.h> #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<GeometryCache>()->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 <typename F> -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 <typename F> +//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<DeferredLightingEffect>()->bindSimpleProgram(batch, true); + batch.setModelTransform(Transform()); + batch.setProjectionTransform(mat4()); + batch.setViewTransform(Transform()); + batch.setUniformTexture(0, _crosshairTexture); + DependencyManager::get<GeometryCache>()->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<GeometryCache>()->renderQuad( - topLeft, bottomRight, - texCoordTopLeft, texCoordBottomRight, - glm::vec4(1.0f, 1.0f, 1.0f, _alpha)); - }); - - if (!_crosshairTexture) { - _crosshairTexture = DependencyManager::get<TextureCache>()-> - getImageTexture(PathUtils::resourcesPath() + "images/sixense-reticle.png"); - } - + glPushMatrix(); + glLoadIdentity(); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + { + glBindTexture(GL_TEXTURE_2D, _framebufferObject->texture()); + DependencyManager::get<GeometryCache>()->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<GeometryCache>()->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<GeometryCache>()->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<AvatarManager>()->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<GeometryCache>()->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<GeometryCache>()->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 4617b7331c9614a687c71a4645e1707df0ce571a Mon Sep 17 00:00:00 2001 From: Niraj Venkat <venkatn93@gmail.com> Date: Wed, 10 Jun 2015 09:50:47 -0700 Subject: [PATCH 34/88] Working on creating functions to serialize and deserialize properties --- examples/html/entityProperties.html | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/html/entityProperties.html b/examples/html/entityProperties.html index bebbb1d80e..a2387a51bf 100644 --- a/examples/html/entityProperties.html +++ b/examples/html/entityProperties.html @@ -855,7 +855,6 @@ elVoxelSurfaceStyle.addEventListener('change', createEmitTextPropertyUpdateFunction('voxelSurfaceStyle')); var hyperlinkChangeFunction = createEmitGroupTextPropertyUpdateFunction('hyperlink','href'); - var hyperlinkChangeFunction = createEmitGroupTextPropertyUpdateFunction('hyperlink','description'); From 3dcc6c9b8ce28722928c891feae1d21009842e21 Mon Sep 17 00:00:00 2001 From: Seth Alves <seth.alves@gmail.com> Date: Wed, 10 Jun 2015 12:04:44 -0700 Subject: [PATCH 35/88] 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<LimitedNodeList, NodeList>(); DependencyManager::registerInheritance<AvatarHashMap, AvatarManager>(); + DependencyManager::registerInheritance<EntityActionFactoryInterface, InterfaceActionFactory>(); Setting::init(); @@ -293,7 +295,8 @@ bool setupEssentials(int& argc, char** argv) { auto discoverabilityManager = DependencyManager::set<DiscoverabilityManager>(); auto sceneScriptingInterface = DependencyManager::set<SceneScriptingInterface>(); auto offscreenUi = DependencyManager::set<OffscreenUi>(); - auto pathUtils = DependencyManager::set<PathUtils>(); + auto pathUtils = DependencyManager::set<PathUtils>(); + auto actionFactory = DependencyManager::set<InterfaceActionFactory>(); 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 <avatar/AvatarActionHold.h> +#include <ObjectActionPullToPoint.h> +#include <ObjectActionSpring.h> + +#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<AvatarManager>()->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 <QUuid> + +#include <EntityItem.h> +#include <ObjectAction.h> + +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 <DependencyManager.h> + +#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 <QUuid> +#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<EntityActionInterface> 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<EntityActionInterface> 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<EntityActionFactoryInterface>(); 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 <PerfStat.h> +#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<QUuid> 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<ObjectMotionState*>(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<ObjectMotionState*>(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 <btBulletDynamicsCommon.h> #include <EntityItem.h> + #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<ObjectMotionState*>(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<ObjectMotionState*>(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 8d41960cc395b6e26ba9e8fd94218a1d2609be4e Mon Sep 17 00:00:00 2001 From: Andrew Meadows <andrew@highfidelity.io> Date: Wed, 10 Jun 2015 12:40:14 -0700 Subject: [PATCH 36/88] reset simulation bid counters on object activation --- libraries/physics/src/EntityMotionState.cpp | 8 ++++---- libraries/physics/src/ObjectMotionState.h | 2 -- libraries/physics/src/PhysicsEngine.cpp | 5 ++--- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 23237cab32..186ff40f60 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -180,10 +180,6 @@ btCollisionShape* EntityMotionState::computeNewShape() { return nullptr; } -// RELIABLE_SEND_HACK: until we have truly reliable resends of non-moving updates -// we alwasy resend packets for objects that have stopped moving up to some max limit. -const int MAX_NUM_NON_MOVING_UPDATES = 5; - bool EntityMotionState::isCandidateForOwnership(const QUuid& sessionID) const { if (!_body || !_entity) { return false; @@ -495,6 +491,10 @@ void EntityMotionState::measureBodyAcceleration() { glm::vec3 velocity = bulletToGLM(_body->getLinearVelocity()); _measuredAcceleration = (velocity / powf(1.0f - _body->getLinearDamping(), dt) - _lastVelocity) * invDt; _lastVelocity = velocity; + if (numSubsteps > PHYSICS_ENGINE_MAX_NUM_SUBSTEPS && !_candidateForOwnership) { + _loopsSinceOwnershipBid = 0; + _loopsWithoutOwner = 0; + } } } glm::vec3 EntityMotionState::getObjectLinearVelocityChange() const { diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index b17dc67cff..561ce02d62 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -53,8 +53,6 @@ const uint32_t OUTGOING_DIRTY_PHYSICS_FLAGS = EntityItem::DIRTY_TRANSFORM | Enti class OctreeEditPacketSender; class PhysicsEngine; -extern const int MAX_NUM_NON_MOVING_UPDATES; - class ObjectMotionState : public btMotionState { public: // These poroperties of the PhysicsEngine are "global" within the context of all ObjectMotionStates diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index e5c974dfbc..55fc5e6295 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -227,8 +227,7 @@ void PhysicsEngine::stepSimulation() { // (3) synchronize outgoing motion states // (4) send outgoing packets - const int MAX_NUM_SUBSTEPS = 4; - const float MAX_TIMESTEP = (float)MAX_NUM_SUBSTEPS * PHYSICS_ENGINE_FIXED_SUBSTEP; + const float MAX_TIMESTEP = (float)PHYSICS_ENGINE_MAX_NUM_SUBSTEPS * PHYSICS_ENGINE_FIXED_SUBSTEP; float dt = 1.0e-6f * (float)(_clock.getTimeMicroseconds()); _clock.reset(); float timeStep = btMin(dt, MAX_TIMESTEP); @@ -245,7 +244,7 @@ void PhysicsEngine::stepSimulation() { _characterController->preSimulation(timeStep); } - int numSubsteps = _dynamicsWorld->stepSimulation(timeStep, MAX_NUM_SUBSTEPS, PHYSICS_ENGINE_FIXED_SUBSTEP); + int numSubsteps = _dynamicsWorld->stepSimulation(timeStep, PHYSICS_ENGINE_MAX_NUM_SUBSTEPS, PHYSICS_ENGINE_FIXED_SUBSTEP); if (numSubsteps > 0) { BT_PROFILE("postSimulation"); _numSubsteps += (uint32_t)numSubsteps; From 0e12cdc39e1d0932035ef0cc36b0e82de0e8b2ed Mon Sep 17 00:00:00 2001 From: Andrew Meadows <andrew@highfidelity.io> Date: Wed, 10 Jun 2015 12:41:15 -0700 Subject: [PATCH 37/88] woops, forgot to include this in last commit --- libraries/shared/src/PhysicsHelpers.h | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/shared/src/PhysicsHelpers.h b/libraries/shared/src/PhysicsHelpers.h index bef7275067..0e58ae99f0 100644 --- a/libraries/shared/src/PhysicsHelpers.h +++ b/libraries/shared/src/PhysicsHelpers.h @@ -15,6 +15,7 @@ #include <glm/glm.hpp> #include <glm/gtc/quaternion.hpp> +const int PHYSICS_ENGINE_MAX_NUM_SUBSTEPS = 4; const float PHYSICS_ENGINE_FIXED_SUBSTEP = 1.0f / 60.0f; // return incremental rotation (Bullet-style) caused by angularVelocity over timeStep From 182a3e918cc6680f4eaa9612f26d2e22fd764e37 Mon Sep 17 00:00:00 2001 From: Niraj Venkat <venkatn93@gmail.com> Date: Wed, 10 Jun 2015 13:12:18 -0700 Subject: [PATCH 38/88] Hyperlink properties now propagate locally and over network --- examples/html/entityProperties.html | 5 + libraries/entities/src/EntityItem.cpp | 16 ++- libraries/entities/src/EntityItem.h | 11 +- .../entities/src/EntityItemProperties.cpp | 17 ++- libraries/entities/src/EntityItemProperties.h | 8 +- .../entities/src/HyperlinkPropertyGroup.cpp | 136 ------------------ .../entities/src/HyperlinkPropertyGroup.h | 88 ------------ 7 files changed, 50 insertions(+), 231 deletions(-) delete mode 100644 libraries/entities/src/HyperlinkPropertyGroup.cpp delete mode 100644 libraries/entities/src/HyperlinkPropertyGroup.h diff --git a/examples/html/entityProperties.html b/examples/html/entityProperties.html index a2387a51bf..37f2d0085f 100644 --- a/examples/html/entityProperties.html +++ b/examples/html/entityProperties.html @@ -471,6 +471,9 @@ elScriptURL.value = properties.script; elUserData.value = properties.userData; + elHyperlinkHref.value = properties.href; + elHyperlinkDescription.value = properties.description; + for (var i = 0; i < allSections.length; i++) { for (var j = 0; j < allSections[i].length; j++) { allSections[i][j].style.display = 'none'; @@ -616,6 +619,8 @@ elLocked.addEventListener('change', createEmitCheckedPropertyUpdateFunction('locked')); elName.addEventListener('change', createEmitTextPropertyUpdateFunction('name')); + elHyperlinkHref.addEventListener('change', createEmitTextPropertyUpdateFunction('href')); + elHyperlinkDescription.addEventListener('change', createEmitTextPropertyUpdateFunction('description')); elVisible.addEventListener('change', createEmitCheckedPropertyUpdateFunction('visible')); var positionChangeFunction = createEmitVec3PropertyUpdateFunction( diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 4a10aad95d..f64dad0ef4 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -70,7 +70,9 @@ EntityItem::EntityItem(const EntityItemID& entityItemID) : _dirtyFlags(0), _element(nullptr), _physicsInfo(nullptr), - _simulated(false) + _simulated(false), + _href(""), + _description("") { quint64 now = usecTimestampNow(); _lastSimulated = now; @@ -117,6 +119,8 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_MARKETPLACE_ID; requestedProperties += PROP_NAME; requestedProperties += PROP_SIMULATOR_ID; + requestedProperties += PROP_HREF; + requestedProperties += PROP_DESCRIPTION; return requestedProperties; } @@ -246,6 +250,9 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet APPEND_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, getMarketplaceID()); APPEND_ENTITY_PROPERTY(PROP_NAME, getName()); APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, getCollisionSoundURL()); + APPEND_ENTITY_PROPERTY(PROP_HREF, getHref()); + APPEND_ENTITY_PROPERTY(PROP_DESCRIPTION, getDescription()); + appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, @@ -573,6 +580,9 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY(PROP_NAME, QString, setName); READ_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, QString, setCollisionSoundURL); + READ_ENTITY_PROPERTY(PROP_HREF, QString, setHref); + READ_ENTITY_PROPERTY(PROP_DESCRIPTION, QString, setDescription); + bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData); //////////////////////////////////// @@ -905,6 +915,8 @@ EntityItemProperties EntityItem::getProperties() const { COPY_ENTITY_PROPERTY_TO_PROPERTIES(simulatorID, getSimulatorID); COPY_ENTITY_PROPERTY_TO_PROPERTIES(marketplaceID, getMarketplaceID); COPY_ENTITY_PROPERTY_TO_PROPERTIES(name, getName); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(href, getHref); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(description, getDescription); properties._defaultSettings = false; @@ -963,6 +975,8 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(userData, setUserData); SET_ENTITY_PROPERTY_FROM_PROPERTIES(marketplaceID, setMarketplaceID); SET_ENTITY_PROPERTY_FROM_PROPERTIES(name, setName); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(href, setHref); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(description, setDescription); if (somethingChanged) { uint64_t now = usecTimestampNow(); diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 77a6627853..a145c4c236 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -203,7 +203,14 @@ public: inline const glm::quat& getRotation() const { return _transform.getRotation(); } inline void setRotation(const glm::quat& rotation) { _transform.setRotation(rotation); } - + + // Hyperlink related getters and setters + QString getHref() const { return _href; } + void setHref(QString value) { _href = value; } + + QString getDescription() const { return _description; } + void setDescription(QString value) { _description = value; } + /// Dimensions in meters (0.0 - TREE_SCALE) inline const glm::vec3& getDimensions() const { return _transform.getScale(); } virtual void setDimensions(const glm::vec3& value); @@ -415,6 +422,8 @@ protected: quint64 _simulatorIDChangedTime; // when was _simulatorID last updated? QString _marketplaceID; QString _name; + QString _href; //Hyperlink href + QString _description; //Hyperlink description // NOTE: Damping is applied like this: v *= pow(1 - damping, dt) // diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 90f2b22698..cbb3b1dc31 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -347,6 +347,9 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_VOXEL_SURFACE_STYLE, voxelSurfaceStyle); CHECK_PROPERTY_CHANGE(PROP_LINE_WIDTH, lineWidth); CHECK_PROPERTY_CHANGE(PROP_LINE_POINTS, linePoints); + CHECK_PROPERTY_CHANGE(PROP_HREF, href); + CHECK_PROPERTY_CHANGE(PROP_DESCRIPTION, description); + changedProperties += _stage.getChangedProperties(); changedProperties += _atmosphere.getChangedProperties(); changedProperties += _skybox.getChangedProperties(); @@ -439,7 +442,9 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(voxelSurfaceStyle); COPY_PROPERTY_TO_QSCRIPTVALUE(lineWidth); COPY_PROPERTY_TO_QSCRIPTVALUE(linePoints); - + COPY_PROPERTY_TO_QSCRIPTVALUE(href); + COPY_PROPERTY_TO_QSCRIPTVALUE(description); + // Sitting properties support if (!skipDefaults) { QScriptValue sittingPoints = engine->newObject(); @@ -548,6 +553,9 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelSurfaceStyle, uint16_t, setVoxelSurfaceStyle); COPY_PROPERTY_FROM_QSCRIPTVALUE(lineWidth, float, setLineWidth); COPY_PROPERTY_FROM_QSCRIPTVALUE(linePoints, qVectorVec3, setLinePoints); + COPY_PROPERTY_FROM_QSCRIPTVALUE(href, QString, setHref); + COPY_PROPERTY_FROM_QSCRIPTVALUE(description, QString, setDescription); + if (!honorReadOnly) { // this is used by the json reader to set things that we don't want javascript to able to affect. @@ -712,6 +720,8 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem APPEND_ENTITY_PROPERTY(PROP_LOCKED, properties.getLocked()); APPEND_ENTITY_PROPERTY(PROP_USER_DATA, properties.getUserData()); APPEND_ENTITY_PROPERTY(PROP_SIMULATOR_ID, properties.getSimulatorID()); + APPEND_ENTITY_PROPERTY(PROP_HREF, properties.getHref()); + APPEND_ENTITY_PROPERTY(PROP_DESCRIPTION, properties.getDescription()); if (properties.getType() == EntityTypes::Web) { APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, properties.getSourceUrl()); @@ -962,6 +972,8 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LOCKED, bool, setLocked); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_USER_DATA, QString, setUserData); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SIMULATOR_ID, QUuid, setSimulatorID); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_HREF, QString, setHref); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DESCRIPTION, QString, setDescription); if (properties.getType() == EntityTypes::Web) { READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SOURCE_URL, QString, setSourceUrl); @@ -1147,6 +1159,9 @@ void EntityItemProperties::markAllChanged() { _lineWidthChanged = true; _linePointsChanged = true; + _hrefChanged = true; + _descriptionChanged = true; + } /// The maximum bounding cube for the entity, independent of it's rotation. diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index e7d9a7ad18..068bc98f7e 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -36,7 +36,6 @@ #include "EntityPropertyFlags.h" #include "SkyboxPropertyGroup.h" #include "StagePropertyGroup.h" -#include "HyperlinkPropertyGroup.h" const quint64 UNKNOWN_CREATED_TIME = 0; @@ -56,7 +55,6 @@ class EntityItemProperties { friend class WebEntityItem; // TODO: consider removing this friend relationship and use public methods friend class LineEntityItem; // TODO: consider removing this friend relationship and use public methods friend class PolyVoxEntityItem; // TODO: consider removing this friend relationship and use public methods - friend class HyperlinkEntityItem; // TODO: consider removing this friend relationship and use public methods public: EntityItemProperties(); virtual ~EntityItemProperties(); @@ -150,8 +148,8 @@ public: DEFINE_PROPERTY_REF(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString); DEFINE_PROPERTY(PROP_LINE_WIDTH, LineWidth, lineWidth, float); DEFINE_PROPERTY_REF(LINE_POINTS, LinePoints, linePoints, QVector<glm::vec3>); - DEFINE_PROPERTY_GROUP(Hyperlink, hyperlink, HyperlinkPropertyGroup) - + DEFINE_PROPERTY_REF(PROP_HREF, Href, href, QString); + DEFINE_PROPERTY_REF(PROP_DESCRIPTION, Description, description, QString); static QString getBackgroundModeString(BackgroundMode mode); @@ -299,6 +297,8 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, VoxelVolumeSize, voxelVolumeSize, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, VoxelData, voxelData, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, VoxelSurfaceStyle, voxelSurfaceStyle, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, Href, href, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, Description, description, ""); properties.getStage().debugDump(); properties.getAtmosphere().debugDump(); diff --git a/libraries/entities/src/HyperlinkPropertyGroup.cpp b/libraries/entities/src/HyperlinkPropertyGroup.cpp deleted file mode 100644 index bc94a8d5d3..0000000000 --- a/libraries/entities/src/HyperlinkPropertyGroup.cpp +++ /dev/null @@ -1,136 +0,0 @@ -// -// HyperlinkPropertyGroup.cpp -// libraries/entities/src -// -// Created by Niraj Venkat on 6/9/15. -// Copyright 2013 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 <OctreePacketData.h> - -#include "HyperlinkPropertyGroup.h" -#include "EntityItemProperties.h" -#include "EntityItemPropertiesMacros.h" - -HyperlinkPropertyGroup::HyperlinkPropertyGroup() { - const QString DEFAULT_HREF = QString(""); - const QString DEFAULT_DESCRIPTION = QString(""); - - _href = DEFAULT_HREF; - _description = DEFAULT_DESCRIPTION; -} - -void HyperlinkPropertyGroup::copyToScriptValue(QScriptValue& properties, QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const { - COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(Hyperlink, hyperlink, Href, href); - COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(Hyperlink, hyperlink, Description, description); -} - -void HyperlinkPropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings) { - COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(Hyperlink, href, QString, setHref); - COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(Hyperlink, description, QString, setDescription); -} - -void HyperlinkPropertyGroup::debugDump() const { - qDebug() << " HyperlinkPropertyGroup: ---------------------------------------------"; - qDebug() << " Href:" << getHref() << " has changed:" << hrefChanged(); - qDebug() << " Description:" << getDescription() << " has changed:" << descriptionChanged(); -} - -bool HyperlinkPropertyGroup::appentToEditPacket(OctreePacketData* packetData, - EntityPropertyFlags& requestedProperties, - EntityPropertyFlags& propertyFlags, - EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, - OctreeElement::AppendState& appendState) const { - - bool successPropertyFits = true; - - APPEND_ENTITY_PROPERTY(PROP_HREF, getHref()); - APPEND_ENTITY_PROPERTY(PROP_DESCRIPTION, getDescription()); - - return true; -} - - -bool HyperlinkPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFlags, const unsigned char*& dataAt, int& processedBytes) { - - int bytesRead = 0; - bool overwriteLocalData = true; - - READ_ENTITY_PROPERTY(PROP_HREF, QString, setHref); - READ_ENTITY_PROPERTY(PROP_DESCRIPTION, QString, setDescription); - - DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_HREF, Href); - DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_DESCRIPTION, Description); - - processedBytes += bytesRead; - - return true; -} - -void HyperlinkPropertyGroup::markAllChanged() { - _hrefChanged = true; - _descriptionChanged = true; -} - -EntityPropertyFlags HyperlinkPropertyGroup::getChangedProperties() const { - EntityPropertyFlags changedProperties; - - CHECK_PROPERTY_CHANGE(PROP_HREF, href); - CHECK_PROPERTY_CHANGE(PROP_DESCRIPTION, description); - - return changedProperties; -} - -void HyperlinkPropertyGroup::getProperties(EntityItemProperties& properties) const { - COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Hyperlink, Href, getHref); - COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Hyperlink, Description, getDescription); -} - -bool HyperlinkPropertyGroup::setProperties(const EntityItemProperties& properties) { - bool somethingChanged = false; - - SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Hyperlink, Href, href, setHref); - SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Hyperlink, Description, description, setDescription); - - return somethingChanged; -} - -EntityPropertyFlags HyperlinkPropertyGroup::getEntityProperties(EncodeBitstreamParams& params) const { - EntityPropertyFlags requestedProperties; - - requestedProperties += PROP_HREF; - requestedProperties += PROP_DESCRIPTION; - - return requestedProperties; -} - -void HyperlinkPropertyGroup::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData, - EntityPropertyFlags& requestedProperties, - EntityPropertyFlags& propertyFlags, - EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, - OctreeElement::AppendState& appendState) const { - - bool successPropertyFits = true; - - APPEND_ENTITY_PROPERTY(PROP_HREF, getHref()); - APPEND_ENTITY_PROPERTY(PROP_DESCRIPTION, getDescription()); -} - -int HyperlinkPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, - ReadBitstreamToTreeParams& args, - EntityPropertyFlags& propertyFlags, bool overwriteLocalData) { - - int bytesRead = 0; - const unsigned char* dataAt = data; - - READ_ENTITY_PROPERTY(PROP_DESCRIPTION, QString, setHref); - READ_ENTITY_PROPERTY(PROP_DESCRIPTION, QString, setDescription); - - return bytesRead; -} diff --git a/libraries/entities/src/HyperlinkPropertyGroup.h b/libraries/entities/src/HyperlinkPropertyGroup.h deleted file mode 100644 index b6bd40c91f..0000000000 --- a/libraries/entities/src/HyperlinkPropertyGroup.h +++ /dev/null @@ -1,88 +0,0 @@ -// -// HyperlinkPropertyGroup.h -// libraries/entities/src -// -// Created by Niraj Venkat on 6/9/15. -// Copyright 2013 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_HyperlinkPropertyGroup_h -#define hifi_HyperlinkPropertyGroup_h - -#include <QtScript/QScriptEngine> - -#include "PropertyGroup.h" -#include "EntityItemPropertiesMacros.h" - -class EntityItemProperties; -class EncodeBitstreamParams; -class OctreePacketData; -class EntityTreeElementExtraEncodeData; -class ReadBitstreamToTreeParams; - -#include <stdint.h> -#include <glm/glm.hpp> - - - -class HyperlinkPropertyGroup : public PropertyGroup { -public: - HyperlinkPropertyGroup(); - virtual ~HyperlinkPropertyGroup() {} - - // EntityItemProperty related helpers - virtual void copyToScriptValue(QScriptValue& properties, QScriptEngine* engine, bool skipDefaults, EntityItemProperties& defaultEntityProperties) const; - virtual void copyFromScriptValue(const QScriptValue& object, bool& _defaultSettings); - virtual void debugDump() const; - - virtual bool appentToEditPacket(OctreePacketData* packetData, - EntityPropertyFlags& requestedProperties, - EntityPropertyFlags& propertyFlags, - EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, - OctreeElement::AppendState& appendState) const; - - virtual bool decodeFromEditPacket(EntityPropertyFlags& propertyFlags, const unsigned char*& dataAt, int& processedBytes); - virtual void markAllChanged(); - virtual EntityPropertyFlags getChangedProperties() const; - - // EntityItem related helpers - // methods for getting/setting all properties of an entity - virtual void getProperties(EntityItemProperties& propertiesOut) const; - - /// returns true if something changed - virtual bool setProperties(const EntityItemProperties& properties); - - virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const; - - virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, - EntityTreeElementExtraEncodeData* entityTreeElementExtraEncodeData, - EntityPropertyFlags& requestedProperties, - EntityPropertyFlags& propertyFlags, - EntityPropertyFlags& propertiesDidntFit, - int& propertyCount, - OctreeElement::AppendState& appendState) const; - - virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, - ReadBitstreamToTreeParams& args, - EntityPropertyFlags& propertyFlags, bool overwriteLocalData); - - /* - DEFINE_PROPERTY_REF(PROP_Hyperlink_CENTER, Center, center, glm::vec3); - DEFINE_PROPERTY(PROP_Hyperlink_INNER_RADIUS, InnerRadius, innerRadius, float); - DEFINE_PROPERTY(PROP_Hyperlink_OUTER_RADIUS, OuterRadius, outerRadius, float); - DEFINE_PROPERTY(PROP_Hyperlink_MIE_SCATTERING, MieScattering, mieScattering, float); - DEFINE_PROPERTY(PROP_Hyperlink_RAYLEIGH_SCATTERING, RayleighScattering, rayleighScattering, float); - DEFINE_PROPERTY_REF(PROP_Hyperlink_SCATTERING_WAVELENGTHS, ScatteringWavelengths, scatteringWavelengths, glm::vec3); - DEFINE_PROPERTY(PROP_Hyperlink_HAS_STARS, HasStars, hasStars, bool); - */ - - DEFINE_PROPERTY_REF(PROP_HREF, Href, href, QString); - DEFINE_PROPERTY_REF(PROP_DESCRIPTION, Description, description, QString); - -}; - -#endif // hifi_HyperlinkPropertyGroup_h From ca1af777637bfaa2d3298822ed1da08acb7f23e0 Mon Sep 17 00:00:00 2001 From: Brad Davis <bdavis@saintandreas.org> Date: Wed, 10 Jun 2015 13:24:47 -0700 Subject: [PATCH 39/88] 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 <AccountManager.h> #include <AddressManager.h> +#include <CursorManager.h> #include <AmbientOcclusionEffect.h> #include <AudioInjector.h> #include <DeferredLightingEffect.h> @@ -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 <GLMHelpers.h> #include <PerfStat.h> #include <OffscreenUi.h> +#include <CursorManager.h> #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<TextureCache>()-> + 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<TextureCache>()-> - 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<GeometryCache>()->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<uint16_t, gpu::TexturePointer> _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 <QCursor> +#include <QWidget> +#include <QUrl> + +#include <PathUtils.h> + 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<uint16_t, QString> 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 <stdint.h> + +#include <GLMHelpers.h> 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 1dfc9c89eb487906f940411c5434a6a9d1ead68d Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis <bdavis@saintandreas.org> Date: Wed, 10 Jun 2015 13:57:48 -0700 Subject: [PATCH 40/88] Fix doubled cursor on OSX --- interface/src/Application.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1763623fa6..3e993450c3 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -524,8 +524,16 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _window->setVisible(true); _glWidget->setFocusPolicy(Qt::StrongFocus); _glWidget->setFocus(); +#ifdef Q_OS_MAC + // OSX doesn't seem to provide for hiding the cursor only on the GL widget + _window->setCursor(Qt::BlankCursor); +#else + // On windows and linux, hiding the top level cursor also means it's invisible + // when hovering over the window menu, which is a pain, so only hide it for + // the GL surface _glWidget->setCursor(Qt::BlankCursor); - +#endif + // enable mouse tracking; otherwise, we only get drag events _glWidget->setMouseTracking(true); From 6497ac6c8270a7528cd7efa536e4195d1488089b Mon Sep 17 00:00:00 2001 From: Andrew Meadows <andrew@highfidelity.io> Date: Wed, 10 Jun 2015 14:24:00 -0700 Subject: [PATCH 41/88] renderBoundingBox() for sim-ownership debug --- .../src/RenderableBoxEntityItem.cpp | 40 ++----------------- .../src/RenderableBoxEntityItem.h | 1 - .../src/RenderableDebugableEntityItem.cpp | 13 +++--- .../src/RenderableSphereEntityItem.cpp | 6 ++- 4 files changed, 15 insertions(+), 45 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp b/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp index 2d72897faf..6ddf44b82d 100644 --- a/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableBoxEntityItem.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "RenderableBoxEntityItem.h" + #include <glm/gtx/quaternion.hpp> #include <gpu/GPUConfig.h> @@ -18,7 +20,7 @@ #include <ObjectMotionState.h> #include <PerfStat.h> -#include "RenderableBoxEntityItem.h" +#include "RenderableDebugableEntityItem.h" EntityItemPointer RenderableBoxEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { return EntityItemPointer(new RenderableBoxEntityItem(entityID, properties)); @@ -34,39 +36,5 @@ void RenderableBoxEntityItem::render(RenderArgs* args) { batch.setModelTransform(getTransformToCenter()); DependencyManager::get<DeferredLightingEffect>()->renderSolidCube(batch, 1.0f, cubeColor); - // TODO: use RenderableDebugableEntityItem::render (instead of the hack below) - // when we fix the scaling bug that breaks RenderableDebugableEntityItem for boxes. - if (args->_debugFlags & RenderArgs::RENDER_DEBUG_SIMULATION_OWNERSHIP) { - Q_ASSERT(args->_batch); - gpu::Batch& batch = *args->_batch; - Transform transform = getTransformToCenter(); - //transform.postScale(entity->getDimensions()); // HACK: this line breaks for BoxEntityItem - batch.setModelTransform(transform); - - auto nodeList = DependencyManager::get<NodeList>(); - const QUuid& myNodeID = nodeList->getSessionUUID(); - bool highlightSimulationOwnership = (getSimulatorID() == myNodeID); - if (highlightSimulationOwnership) { - glm::vec4 greenColor(0.0f, 1.0f, 0.2f, 1.0f); - DependencyManager::get<DeferredLightingEffect>()->renderWireCube(batch, 1.08f, greenColor); - } - - quint64 now = usecTimestampNow(); - if (now - getLastEditedFromRemote() < 0.1f * USECS_PER_SECOND) { - glm::vec4 redColor(1.0f, 0.0f, 0.0f, 1.0f); - DependencyManager::get<DeferredLightingEffect>()->renderWireCube(batch, 1.16f, redColor); - } - - if (now - getLastBroadcast() < 0.2f * USECS_PER_SECOND) { - glm::vec4 yellowColor(1.0f, 1.0f, 0.2f, 1.0f); - DependencyManager::get<DeferredLightingEffect>()->renderWireCube(batch, 1.24f, yellowColor); - } - - ObjectMotionState* motionState = static_cast<ObjectMotionState*>(getPhysicsInfo()); - if (motionState && motionState->isActive()) { - glm::vec4 blueColor(0.0f, 0.0f, 1.0f, 1.0f); - DependencyManager::get<DeferredLightingEffect>()->renderWireCube(batch, 1.32f, blueColor); - } - } - //RenderableDebugableEntityItem::render(this, args); // TODO: use this instead of the hack above + RenderableDebugableEntityItem::render(this, args); }; diff --git a/libraries/entities-renderer/src/RenderableBoxEntityItem.h b/libraries/entities-renderer/src/RenderableBoxEntityItem.h index 06a62706b9..b14da9ee22 100644 --- a/libraries/entities-renderer/src/RenderableBoxEntityItem.h +++ b/libraries/entities-renderer/src/RenderableBoxEntityItem.h @@ -13,7 +13,6 @@ #define hifi_RenderableBoxEntityItem_h #include <BoxEntityItem.h> -#include "RenderableDebugableEntityItem.h" #include "RenderableEntityItem.h" class RenderableBoxEntityItem : public BoxEntityItem { diff --git a/libraries/entities-renderer/src/RenderableDebugableEntityItem.cpp b/libraries/entities-renderer/src/RenderableDebugableEntityItem.cpp index 53213eae51..fecc574af2 100644 --- a/libraries/entities-renderer/src/RenderableDebugableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableDebugableEntityItem.cpp @@ -10,6 +10,7 @@ // +#include "RenderableDebugableEntityItem.h" #include <glm/gtx/quaternion.hpp> @@ -18,15 +19,13 @@ #include <DeferredLightingEffect.h> #include <ObjectMotionState.h> -#include "RenderableDebugableEntityItem.h" - void RenderableDebugableEntityItem::renderBoundingBox(EntityItem* entity, RenderArgs* args, float puffedOut, glm::vec4& color) { Q_ASSERT(args->_batch); gpu::Batch& batch = *args->_batch; Transform transform = entity->getTransformToCenter(); - transform.postScale(entity->getDimensions()); + //transform.postScale(entity->getDimensions()); batch.setModelTransform(transform); DependencyManager::get<DeferredLightingEffect>()->renderWireCube(batch, 1.0f + puffedOut, color); } @@ -44,24 +43,24 @@ void RenderableDebugableEntityItem::render(EntityItem* entity, RenderArgs* args) bool highlightSimulationOwnership = (entity->getSimulatorID() == myNodeID); if (highlightSimulationOwnership) { glm::vec4 greenColor(0.0f, 1.0f, 0.2f, 1.0f); - DependencyManager::get<DeferredLightingEffect>()->renderWireCube(batch, 1.08f, greenColor); + renderBoundingBox(entity, args, 0.08f, greenColor); } quint64 now = usecTimestampNow(); if (now - entity->getLastEditedFromRemote() < 0.1f * USECS_PER_SECOND) { glm::vec4 redColor(1.0f, 0.0f, 0.0f, 1.0f); - DependencyManager::get<DeferredLightingEffect>()->renderWireCube(batch, 1.16f, redColor); + renderBoundingBox(entity, args, 0.16f, redColor); } if (now - entity->getLastBroadcast() < 0.2f * USECS_PER_SECOND) { glm::vec4 yellowColor(1.0f, 1.0f, 0.2f, 1.0f); - DependencyManager::get<DeferredLightingEffect>()->renderWireCube(batch, 1.24f, yellowColor); + renderBoundingBox(entity, args, 0.24f, yellowColor); } ObjectMotionState* motionState = static_cast<ObjectMotionState*>(entity->getPhysicsInfo()); if (motionState && motionState->isActive()) { glm::vec4 blueColor(0.0f, 0.0f, 1.0f, 1.0f); - DependencyManager::get<DeferredLightingEffect>()->renderWireCube(batch, 1.32f, blueColor); + renderBoundingBox(entity, args, 0.32f, blueColor); } } } diff --git a/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp b/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp index d5cb7d11b8..b0aaebb2c8 100644 --- a/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableSphereEntityItem.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "RenderableSphereEntityItem.h" + #include <glm/gtx/quaternion.hpp> #include <gpu/GPUConfig.h> @@ -18,7 +20,7 @@ #include <DeferredLightingEffect.h> #include <PerfStat.h> -#include "RenderableSphereEntityItem.h" +#include "RenderableDebugableEntityItem.h" EntityItemPointer RenderableSphereEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { return EntityItemPointer(new RenderableSphereEntityItem(entityID, properties)); @@ -39,4 +41,6 @@ void RenderableSphereEntityItem::render(RenderArgs* args) { gpu::Batch& batch = *args->_batch; batch.setModelTransform(getTransformToCenter()); DependencyManager::get<DeferredLightingEffect>()->renderSolidSphere(batch, 0.5f, SLICES, STACKS, sphereColor); + + RenderableDebugableEntityItem::render(this, args); }; From 1f62fb4b6fbe33f090dc072dd3b0e15efc5b6d2d Mon Sep 17 00:00:00 2001 From: Sam Gateau <sam@highfidelity.io> Date: Wed, 10 Jun 2015 15:24:29 -0700 Subject: [PATCH 42/88] 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<DeferredLightingEffect>()->bindSimpleProgram(batch, true); + //DependencyManager::get<DeferredLightingEffect>()->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<GeometryCache>()->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 9ed8daa97c4aca572fbe3de7afcb0b861b80da1b Mon Sep 17 00:00:00 2001 From: Niraj Venkat <venkatn93@gmail.com> Date: Wed, 10 Jun 2015 15:33:07 -0700 Subject: [PATCH 43/88] Removing unnecessary functions --- examples/html/entityProperties.html | 4 ---- 1 file changed, 4 deletions(-) diff --git a/examples/html/entityProperties.html b/examples/html/entityProperties.html index 37f2d0085f..f029088b1a 100644 --- a/examples/html/entityProperties.html +++ b/examples/html/entityProperties.html @@ -859,10 +859,6 @@ elVoxelVolumeSizeZ.addEventListener('change', voxelVolumeSizeChangeFunction); elVoxelSurfaceStyle.addEventListener('change', createEmitTextPropertyUpdateFunction('voxelSurfaceStyle')); - var hyperlinkChangeFunction = createEmitGroupTextPropertyUpdateFunction('hyperlink','href'); - var hyperlinkChangeFunction = createEmitGroupTextPropertyUpdateFunction('hyperlink','description'); - - elMoveSelectionToGrid.addEventListener("click", function() { EventBridge.emitWebEvent(JSON.stringify({ type: "action", From e06422825a54a0ee186b579760261dbf4813a8c9 Mon Sep 17 00:00:00 2001 From: ZappoMan <bradh@konamoxt.com> Date: Wed, 10 Jun 2015 15:46:54 -0700 Subject: [PATCH 44/88] mostly getting attachments working again --- interface/src/avatar/Avatar.cpp | 4 +- interface/src/avatar/MyAvatar.cpp | 18 +--- libraries/render-utils/src/Model.cpp | 124 ++++++++------------------- libraries/render-utils/src/Model.h | 29 ++----- 4 files changed, 43 insertions(+), 132 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 627c42ffa8..f644968ff8 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -582,7 +582,9 @@ void Avatar::simulateAttachments(float deltaTime) { _skeletonModel.getJointCombinedRotation(jointIndex, jointRotation)) { model->setTranslation(jointPosition + jointRotation * attachment.translation * _scale); model->setRotation(jointRotation * attachment.rotation); - model->setScaleToFit(true, _scale * attachment.scale); + model->setScaleToFit(true, _scale * attachment.scale, true); // hack to force rescale + model->setSnapModelToCenter(false); // hack to force resnap + model->setSnapModelToCenter(true); model->simulate(deltaTime); } } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index afe0311a29..65b927c2c0 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1196,23 +1196,7 @@ void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, bo } scene->enqueuePendingChanges(pendingChanges); - Camera *camera = Application::getInstance()->getCamera(); - const glm::vec3 cameraPos = camera->getPosition(); - - - // HACK: comment this block which possibly change the near and break the rendering 5/6/2015 - // Only tweak the frustum near far if it's not shadow - /* if (renderMode != RenderArgs::SHADOW_RENDER_MODE) { - // Set near clip distance according to skeleton model dimensions if first person and there is no separate head model. - if (shouldRenderHead(cameraPos, renderMode) || !getHead()->getFaceModel().getURL().isEmpty()) { - renderFrustum->setNearClip(DEFAULT_NEAR_CLIP); - } else { - float clipDistance = _skeletonModel.getHeadClipDistance(); - clipDistance = glm::length(getEyePosition() - + camera->getOrientation() * glm::vec3(0.0f, 0.0f, -clipDistance) - cameraPos); - renderFrustum->setNearClip(clipDistance); - } - }*/ + const glm::vec3 cameraPos = Application::getInstance()->getCamera()->getPosition(); // Render head so long as the camera isn't inside it if (shouldRenderHead(renderArgs, cameraPos)) { diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 3cea5c9777..8d234cdef5 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -435,6 +435,7 @@ bool Model::updateGeometry() { QSharedPointer<NetworkGeometry> geometry = _geometry->getLODOrFallback(_lodDistance, _lodHysteresis); if (_geometry != geometry) { + // NOTE: it is theoretically impossible to reach here after passing through the applyNextGeometry() call above. // Which means we don't need to worry about calling deleteGeometry() below immediately after creating new geometry. @@ -811,71 +812,41 @@ void Model::renderSetup(RenderArgs* args) { } -class TransparentMeshPart { +class MeshPartPayload { public: - TransparentMeshPart(Model* model, int meshIndex, int partIndex) : model(model), meshIndex(meshIndex), partIndex(partIndex) { } - typedef render::Payload<TransparentMeshPart> Payload; + MeshPartPayload(bool transparent, Model* model, int meshIndex, int partIndex) : + transparent(transparent), model(model), url(model->getURL()), meshIndex(meshIndex), partIndex(partIndex) { } + typedef render::Payload<MeshPartPayload> Payload; typedef Payload::DataPointer Pointer; - Model* model; + bool transparent; + Model* model; + QUrl url; int meshIndex; int partIndex; }; namespace render { - template <> const ItemKey payloadGetKey(const TransparentMeshPart::Pointer& payload) { + template <> const ItemKey payloadGetKey(const MeshPartPayload::Pointer& payload) { if (!payload->model->isVisible()) { return ItemKey::Builder().withInvisible().build(); } - return ItemKey::Builder::transparentShape(); + return payload->transparent ? ItemKey::Builder::transparentShape() : ItemKey::Builder::opaqueShape(); } - template <> const Item::Bound payloadGetBound(const TransparentMeshPart::Pointer& payload) { + template <> const Item::Bound payloadGetBound(const MeshPartPayload::Pointer& payload) { if (payload) { return payload->model->getPartBounds(payload->meshIndex, payload->partIndex); } return render::Item::Bound(); } - template <> void payloadRender(const TransparentMeshPart::Pointer& payload, RenderArgs* args) { + template <> void payloadRender(const MeshPartPayload::Pointer& payload, RenderArgs* args) { if (args) { - return payload->model->renderPart(args, payload->meshIndex, payload->partIndex, true); + return payload->model->renderPart(args, payload->meshIndex, payload->partIndex, payload->transparent); } } -} -class OpaqueMeshPart { -public: - OpaqueMeshPart(Model* model, int meshIndex, int partIndex) : model(model), meshIndex(meshIndex), partIndex(partIndex) { } - typedef render::Payload<OpaqueMeshPart> Payload; - typedef Payload::DataPointer Pointer; - - Model* model; - int meshIndex; - int partIndex; -}; - -namespace render { - template <> const ItemKey payloadGetKey(const OpaqueMeshPart::Pointer& payload) { - if (!payload->model->isVisible()) { - return ItemKey::Builder().withInvisible().build(); - } - return ItemKey::Builder::opaqueShape(); - } - - template <> const Item::Bound payloadGetBound(const OpaqueMeshPart::Pointer& payload) { - if (payload) { - Item::Bound result = payload->model->getPartBounds(payload->meshIndex, payload->partIndex); - //qDebug() << "payloadGetBound(OpaqueMeshPart) " << result; - return result; - } - return render::Item::Bound(); - } - template <> void payloadRender(const OpaqueMeshPart::Pointer& payload, RenderArgs* args) { - if (args) { - return payload->model->renderPart(args, payload->meshIndex, payload->partIndex, false); - } - } - /* template <> const model::MaterialKey& shapeGetMaterialKey(const OpaqueMeshPart::Pointer& payload) { + /* template <> const model::MaterialKey& shapeGetMaterialKey(const MeshPartPayload::Pointer& payload) { return payload->model->getPartMaterial(payload->meshIndex, payload->partIndex); }*/ } @@ -902,16 +873,17 @@ bool Model::addToScene(std::shared_ptr<render::Scene> scene, render::PendingChan foreach (auto renderItem, _transparentRenderItems) { auto item = scene->allocateID(); - auto renderData = TransparentMeshPart::Pointer(renderItem); - auto renderPayload = render::PayloadPointer(new TransparentMeshPart::Payload(renderData)); + auto renderData = MeshPartPayload::Pointer(renderItem); + auto renderPayload = render::PayloadPointer(new MeshPartPayload::Payload(renderData)); pendingChanges.resetItem(item, renderPayload); _renderItems.insert(item, renderPayload); somethingAdded = true; } + foreach (auto renderItem, _opaqueRenderItems) { auto item = scene->allocateID(); - auto renderData = OpaqueMeshPart::Pointer(renderItem); - auto renderPayload = render::PayloadPointer(new OpaqueMeshPart::Payload(renderData)); + auto renderData = MeshPartPayload::Pointer(renderItem); + auto renderPayload = render::PayloadPointer(new MeshPartPayload::Payload(renderData)); pendingChanges.resetItem(item, renderPayload); _renderItems.insert(item, renderPayload); somethingAdded = true; @@ -1036,12 +1008,12 @@ Extents Model::calculateScaledOffsetExtents(const Extents& extents) const { Extents translatedExtents = { rotatedExtents.minimum + _translation, rotatedExtents.maximum + _translation }; + return translatedExtents; } /// Returns the world space equivalent of some box in model space. AABox Model::calculateScaledOffsetAABox(const AABox& box) const { - return AABox(calculateScaledOffsetExtents(Extents(box))); } @@ -1110,9 +1082,10 @@ void Model::setURL(const QUrl& url, const QUrl& fallback, bool retainCurrent, bo if (_url == url && _geometry && _geometry->getURL() == url) { return; } - + _readyWhenAdded = false; // reset out render items. _needsReload = true; + invalidCalculatedMeshBoxes(); _url = url; @@ -1301,7 +1274,7 @@ void Model::setScaleToFit(bool scaleToFit, const glm::vec3& dimensions) { } } -void Model::setScaleToFit(bool scaleToFit, float largestDimension) { +void Model::setScaleToFit(bool scaleToFit, float largestDimension, bool forceRescale) { // NOTE: if the model is not active, then it means we don't actually know the true/natural dimensions of the // mesh, and so we can't do the needed calculations for scaling to fit to a single largest dimension. In this // case we will record that we do want to do this, but we will stick our desired single dimension into the @@ -1314,7 +1287,7 @@ void Model::setScaleToFit(bool scaleToFit, float largestDimension) { return; } - if (_scaleToFit != scaleToFit || glm::length(_scaleToFitDimensions) != largestDimension) { + if (forceRescale || _scaleToFit != scaleToFit || glm::length(_scaleToFitDimensions) != largestDimension) { _scaleToFit = scaleToFit; // we only need to do this work if we're "turning on" scale to fit. @@ -1324,7 +1297,7 @@ void Model::setScaleToFit(bool scaleToFit, float largestDimension) { float maxScale = largestDimension / maxDimension; glm::vec3 modelMeshDimensions = modelMeshExtents.maximum - modelMeshExtents.minimum; glm::vec3 dimensions = modelMeshDimensions * maxScale; - + _scaleToFitDimensions = dimensions; _scaledToFit = false; // force rescaling } @@ -1822,7 +1795,6 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran glm::mat4 scale = glm::scale(partBounds.getDimensions()); glm::mat4 modelToWorldMatrix = translation * scale; batch.setModelTransform(modelToWorldMatrix); - //qDebug() << "partBounds:" << partBounds; DependencyManager::get<DeferredLightingEffect>()->renderWireCube(batch, 1.0f, cubeColor); } #endif //def DEBUG_BOUNDING_PARTS @@ -1912,16 +1884,18 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran // guard against partially loaded meshes if (partIndex >= networkMesh.parts.size() || partIndex >= mesh.parts.size()) { - return; + return; } const NetworkMeshPart& networkPart = networkMesh.parts.at(partIndex); const FBXMeshPart& part = mesh.parts.at(partIndex); model::MaterialPointer material = part._material; + #ifdef WANT_DEBUG if (material == nullptr) { - // qCDebug(renderutils) << "WARNING: material == nullptr!!!"; + qCDebug(renderutils) << "WARNING: material == nullptr!!!"; } + #endif if (material != nullptr) { @@ -2023,8 +1997,6 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran } void Model::segregateMeshGroups() { - _renderBuckets.clear(); - const FBXGeometry& geometry = _geometry->getFBXGeometry(); const QVector<NetworkMesh>& networkMeshes = _geometry->getMeshes(); @@ -2034,6 +2006,9 @@ void Model::segregateMeshGroups() { qDebug() << "WARNING!!!! Mesh Sizes don't match! We will not segregate mesh groups yet."; return; } + + _transparentRenderItems.clear(); + _opaqueRenderItems.clear(); // Run through all of the meshes, and place them into their segregated, but unsorted buckets for (int i = 0; i < networkMeshes.size(); i++) { @@ -2058,43 +2033,12 @@ void Model::segregateMeshGroups() { for (int partIndex = 0; partIndex < totalParts; partIndex++) { // this is a good place to create our renderPayloads if (translucentMesh) { - _transparentRenderItems << std::shared_ptr<TransparentMeshPart>(new TransparentMeshPart(this, i, partIndex)); + _transparentRenderItems << std::shared_ptr<MeshPartPayload>(new MeshPartPayload(true, this, i, partIndex)); } else { - _opaqueRenderItems << std::shared_ptr<OpaqueMeshPart>(new OpaqueMeshPart(this, i, partIndex)); + _opaqueRenderItems << std::shared_ptr<MeshPartPayload>(new MeshPartPayload(false, this, i, partIndex)); } } - - - QString materialID; - - // create a material name from all the parts. If there's one part, this will be a single material and its - // true name. If however the mesh has multiple parts the name will be all the part's materials mashed together - // which will result in those parts being sorted away from single material parts. - QString lastPartMaterialID; - foreach(FBXMeshPart part, mesh.parts) { - if (part.materialID != lastPartMaterialID) { - materialID += part.materialID; - } - lastPartMaterialID = part.materialID; - } - const bool wantDebug = false; - if (wantDebug) { - qCDebug(renderutils) << "materialID:" << materialID << "parts:" << mesh.parts.size(); - } - - RenderKey key(translucentMesh, hasLightmap, hasTangents, hasSpecular, isSkinned, wireframe); - - // reuse or create the bucket corresponding to that key and insert the mesh as unsorted - _renderBuckets[key.getRaw()]._unsortedMeshes.insertMulti(materialID, i); } - - for(auto& b : _renderBuckets) { - foreach(auto i, b.second._unsortedMeshes) { - b.second._meshes.append(i); - } - b.second._unsortedMeshes.clear(); - } - _meshGroupsKnown = true; } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index c96e006aa8..7c1572418e 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -51,17 +51,11 @@ namespace render { class PendingChanges; typedef unsigned int ItemID; } -class OpaqueMeshPart; -class TransparentMeshPart; +class MeshPartPayload; -inline uint qHash(const std::shared_ptr<TransparentMeshPart>& a, uint seed) { +inline uint qHash(const std::shared_ptr<MeshPartPayload>& a, uint seed) { return qHash(a.get(), seed); } -inline uint qHash(const std::shared_ptr<OpaqueMeshPart>& a, uint seed) { - return qHash(a.get(), seed); -} - - /// A generic 3D model displaying geometry loaded from a URL. class Model : public QObject, public PhysicsEntity { @@ -77,7 +71,7 @@ public: virtual ~Model(); /// enables/disables scale to fit behavior, the model will be automatically scaled to the specified largest dimension - void setScaleToFit(bool scaleToFit, float largestDimension = 0.0f); + void setScaleToFit(bool scaleToFit, float largestDimension = 0.0f, bool forceRescale = false); bool getScaleToFit() const { return _scaleToFit; } /// is scale to fit enabled bool getIsScaledToFit() const { return _scaledToFit; } /// is model scaled to fit const glm::vec3& getScaleToFitDimensions() const { return _scaleToFitDimensions; } /// the dimensions model is scaled to @@ -511,24 +505,11 @@ private: }; static RenderPipelineLib _renderPipelineLib; - - class RenderBucket { - public: - QVector<int> _meshes; - QMap<QString, int> _unsortedMeshes; - }; - typedef std::unordered_map<int, RenderBucket> BaseRenderBucketMap; - class RenderBucketMap : public BaseRenderBucketMap { - public: - typedef RenderKey Key; - }; - RenderBucketMap _renderBuckets; - bool _renderCollisionHull; - QSet<std::shared_ptr<TransparentMeshPart>> _transparentRenderItems; - QSet<std::shared_ptr<OpaqueMeshPart>> _opaqueRenderItems; + QSet<std::shared_ptr<MeshPartPayload>> _transparentRenderItems; + QSet<std::shared_ptr<MeshPartPayload>> _opaqueRenderItems; QMap<render::ItemID, render::PayloadPointer> _renderItems; bool _readyWhenAdded = false; bool _needsReload = true; From 139855f48792594c5e6b5ea2a51557b0b0a5f515 Mon Sep 17 00:00:00 2001 From: ZappoMan <bradh@konamoxt.com> Date: Wed, 10 Jun 2015 16:13:17 -0700 Subject: [PATCH 45/88] handle remove attachment case --- interface/src/avatar/Avatar.cpp | 26 ++++++++++++++++++++++---- interface/src/avatar/Avatar.h | 3 +++ interface/src/avatar/MyAvatar.cpp | 18 +----------------- 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index f644968ff8..dbe7d52a3f 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -107,6 +107,9 @@ Avatar::Avatar() : } Avatar::~Avatar() { + for(auto attachment : _unusedAttachments) { + delete attachment; + } } const float BILLBOARD_LOD_DISTANCE = 40.0f; @@ -525,7 +528,7 @@ glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const { return glm::angleAxis(angle * proportion, axis); } -void Avatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, bool postLighting, float glowLevel) { +void Avatar::fixupModelsInScene() { // 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 = Application::getInstance()->getMain3DScene(); @@ -544,8 +547,18 @@ void Avatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, bool attachmentModel->addToScene(scene, pendingChanges); } } + for (auto attachmentModelToRemove : _attachmentsToRemove) { + attachmentModelToRemove->removeFromScene(scene, pendingChanges); + _unusedAttachments << attachmentModelToRemove; + } + _attachmentsToRemove.clear(); scene->enqueuePendingChanges(pendingChanges); +} +void Avatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, bool postLighting, float glowLevel) { + + fixupModelsInScene(); + { Glower glower(renderArgs, glowLevel); @@ -947,13 +960,18 @@ void Avatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) { } // make sure we have as many models as attachments while (_attachmentModels.size() < attachmentData.size()) { - Model* model = new Model(this); + Model* model = nullptr; + if (_unusedAttachments.size() > 0) { + model = _unusedAttachments.takeFirst(); + } else { + model = new Model(this); + } model->init(); _attachmentModels.append(model); } while (_attachmentModels.size() > attachmentData.size()) { - // NOTE: what's really going to happen here? This seems dangerous... has the model been removed from the scene? - delete _attachmentModels.takeLast(); + auto attachmentModel = _attachmentModels.takeLast(); + _attachmentsToRemove << attachmentModel; } // update the urls diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 1113496080..b198d12a6e 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -195,6 +195,8 @@ protected: SkeletonModel _skeletonModel; glm::vec3 _skeletonOffset; QVector<Model*> _attachmentModels; + QVector<Model*> _attachmentsToRemove; + QVector<Model*> _unusedAttachments; float _bodyYawDelta; // These position histories and derivatives are in the world-frame. @@ -234,6 +236,7 @@ protected: void renderDisplayName(RenderArgs* renderArgs); virtual void renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, bool postLighting, float glowLevel = 0.0f); virtual bool shouldRenderHead(const RenderArgs* renderArgs, const glm::vec3& cameraPosition) const; + virtual void fixupModelsInScene(); void simulateAttachments(float deltaTime); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 65b927c2c0..c4e08b5dba 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1178,23 +1178,7 @@ void MyAvatar::renderBody(RenderArgs* renderArgs, ViewFrustum* renderFrustum, bo // 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 = Application::getInstance()->getMain3DScene(); - render::PendingChanges pendingChanges; - if (_skeletonModel.needsFixupInScene()) { - _skeletonModel.removeFromScene(scene, pendingChanges); - _skeletonModel.addToScene(scene, pendingChanges); - } - if (getHead()->getFaceModel().needsFixupInScene()) { - getHead()->getFaceModel().removeFromScene(scene, pendingChanges); - getHead()->getFaceModel().addToScene(scene, pendingChanges); - } - for (auto attachmentModel : _attachmentModels) { - if (attachmentModel->needsFixupInScene()) { - attachmentModel->removeFromScene(scene, pendingChanges); - attachmentModel->addToScene(scene, pendingChanges); - } - } - scene->enqueuePendingChanges(pendingChanges); + fixupModelsInScene(); const glm::vec3 cameraPos = Application::getInstance()->getCamera()->getPosition(); From eccf4eb8a8eb8ab048d205a18f46429f8f2545da Mon Sep 17 00:00:00 2001 From: Seth Alves <seth.alves@gmail.com> Date: Wed, 10 Jun 2015 17:05:49 -0700 Subject: [PATCH 46/88] 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<AvatarManager>()->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 <QUuid> #include <EntityItem.h> -#include <ObjectAction.h> +#include <ObjectActionSpring.h> -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<ObjectMotionState*>(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<ObjectMotionState*>(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 <seth.alves@gmail.com> Date: Wed, 10 Jun 2015 18:48:51 -0700 Subject: [PATCH 47/88] 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<EntityActionFactoryInterface>(); 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<ObjectMotionState*>(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 <bradh@konamoxt.com> Date: Wed, 10 Jun 2015 18:54:07 -0700 Subject: [PATCH 48/88] 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<DeferredLightingEffect>()->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<DeferredLightingEffect>()->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<NodeList>(); 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<DeferredLightingEffect>()->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<DeferredLightingEffect>()->renderSolidSphere(batch, 0.5f, SLICES, STACKS, sphereColor); RenderableDebugableEntityItem::render(this, args); From ba4fd7adf6b0b3238b2f82e043ddbf7eb49e5d30 Mon Sep 17 00:00:00 2001 From: Seth Alves <seth.alves@gmail.com> Date: Wed, 10 Jun 2015 19:23:01 -0700 Subject: [PATCH 49/88] 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 <seth.alves@gmail.com> Date: Wed, 10 Jun 2015 21:08:00 -0700 Subject: [PATCH 50/88] 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 <bdavis@saintandreas.org> Date: Thu, 11 Jun 2015 00:50:24 -0700 Subject: [PATCH 51/88] 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<DeferredLightingEffect>()->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<GeometryCache>()->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<GeometryCache>()->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<GeometryCache>()->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<GeometryCache>()->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<GeometryCache>(); + gpu::Batch batch; + //DependencyManager::get<DeferredLightingEffect>()->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<GeometryCache>(); + 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<GLushort> 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<GLuint, GLuint> 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 <bdavis@saintandreas.org> Date: Thu, 11 Jun 2015 02:20:51 -0700 Subject: [PATCH 52/88] 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 <glm/glm.hpp> +#include <glm/gtc/type_ptr.hpp> #include <GlowEffect.h> #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<float>(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<GeometryCache>(); gpu::Batch batch; - //DependencyManager::get<DeferredLightingEffect>()->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<GeometryCache>()->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<GeometryCache>()->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 7685fe2229ff876fa0a3cfe296f45a38f1d1d704 Mon Sep 17 00:00:00 2001 From: Sam Gateau <sam@highfidelity.io> Date: Thu, 11 Jun 2015 06:40:21 -0700 Subject: [PATCH 53/88] Clean up on the item interface and introduction of the Layered concept, fixing the highliting box of the edit tool --- examples/libraries/entitySelectionTool.js | 3 +- interface/src/ui/overlays/Overlay.h | 1 + interface/src/ui/overlays/OverlaysPayload.cpp | 13 +- .../render-utils/src/RenderDeferredTask.cpp | 5 +- libraries/render/src/render/DrawTask.cpp | 43 ++++++- libraries/render/src/render/DrawTask.h | 8 ++ libraries/render/src/render/Scene.cpp | 35 +----- libraries/render/src/render/Scene.h | 113 +++++++----------- 8 files changed, 115 insertions(+), 106 deletions(-) diff --git a/examples/libraries/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js index d12c9dae3c..d5e2b24f36 100644 --- a/examples/libraries/entitySelectionTool.js +++ b/examples/libraries/entitySelectionTool.js @@ -304,7 +304,8 @@ SelectionDisplay = (function () { visible: false, dashed: true, lineWidth: 2.0, - ignoreRayIntersection: true // this never ray intersects + ignoreRayIntersection: true, // this never ray intersects + drawInFront: true }); var selectionBox = Overlays.addOverlay("cube", { diff --git a/interface/src/ui/overlays/Overlay.h b/interface/src/ui/overlays/Overlay.h index 1a808bc15c..0264c6e3c0 100644 --- a/interface/src/ui/overlays/Overlay.h +++ b/interface/src/ui/overlays/Overlay.h @@ -123,6 +123,7 @@ protected: namespace render { template <> const ItemKey payloadGetKey(const Overlay::Pointer& overlay); template <> const Item::Bound payloadGetBound(const Overlay::Pointer& overlay); + template <> int payloadGetLayer(const Overlay::Pointer& overlay); template <> void payloadRender(const Overlay::Pointer& overlay, RenderArgs* args); } diff --git a/interface/src/ui/overlays/OverlaysPayload.cpp b/interface/src/ui/overlays/OverlaysPayload.cpp index cf3262d05c..d5e4b34f6b 100644 --- a/interface/src/ui/overlays/OverlaysPayload.cpp +++ b/interface/src/ui/overlays/OverlaysPayload.cpp @@ -37,7 +37,7 @@ namespace render { template <> const ItemKey payloadGetKey(const Overlay::Pointer& overlay) { if (overlay->is3D() && !static_cast<Base3DOverlay*>(overlay.get())->getDrawOnHUD()) { if (static_cast<Base3DOverlay*>(overlay.get())->getDrawInFront()) { - return ItemKey::Builder().withTypeShape().withNoDepthSort().build(); + return ItemKey::Builder().withTypeShape().withLayered().build(); } else { return ItemKey::Builder::opaqueShape(); } @@ -53,6 +53,17 @@ namespace render { return AABox(glm::vec3(bounds.x(), bounds.y(), 0.0f), glm::vec3(bounds.width(), bounds.height(), 0.1f)); } } + template <> int payloadGetLayer(const Overlay::Pointer& overlay) { + // MAgic number while we are defining the layering mechanism: + const int LAYER_2D = 2; + const int LAYER_3D_FRONT = 1; + const int LAYER_3D = 0; + if (overlay->is3D()) { + return (static_cast<Base3DOverlay*>(overlay.get())->getDrawInFront() ? LAYER_3D_FRONT : LAYER_3D); + } else { + return LAYER_2D; + } + } template <> void payloadRender(const Overlay::Pointer& overlay, RenderArgs* args) { if (args) { glPushMatrix(); diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index 777d9466a5..3fd7d05666 100755 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -50,6 +50,7 @@ RenderDeferredTask::RenderDeferredTask() : Task() { _jobs.push_back(Job(RenderDeferred())); _jobs.push_back(Job(ResolveDeferred())); _jobs.push_back(Job(DrawTransparentDeferred())); + _jobs.push_back(Job(DrawPostLayered())); _jobs.push_back(Job(ResetGLState())); } @@ -85,7 +86,7 @@ template <> void render::jobRun(const DrawOpaqueDeferred& job, const SceneContex // render opaques auto& scene = sceneContext->_scene; - auto& items = scene->getMasterBucket().at(ItemFilter::Builder::opaqueShape()); + auto& items = scene->getMasterBucket().at(ItemFilter::Builder::opaqueShape().withoutLayered()); auto& renderDetails = renderContext->args->_details; ItemIDsBounds inItems; @@ -163,7 +164,7 @@ template <> void render::jobRun(const DrawTransparentDeferred& job, const SceneC // render transparents auto& scene = sceneContext->_scene; - auto& items = scene->getMasterBucket().at(ItemFilter::Builder::transparentShape()); + auto& items = scene->getMasterBucket().at(ItemFilter::Builder::transparentShape().withoutLayered()); auto& renderDetails = renderContext->args->_details; ItemIDsBounds inItems; diff --git a/libraries/render/src/render/DrawTask.cpp b/libraries/render/src/render/DrawTask.cpp index bfc888ea8a..53964ac1db 100755 --- a/libraries/render/src/render/DrawTask.cpp +++ b/libraries/render/src/render/DrawTask.cpp @@ -192,7 +192,6 @@ void render::renderItems(const SceneContextPointer& sceneContext, const RenderCo } } - void addClearStateCommands(gpu::Batch& batch) { batch._glDepthMask(true); batch._glDepthFunc(GL_LESS); @@ -446,6 +445,48 @@ template <> void render::jobRun(const DrawBackground& job, const SceneContextPoi args->_context->syncCache(); } +template <> void render::jobRun(const DrawPostLayered& job, const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext) { + PerformanceTimer perfTimer("DrawPostLayered"); + assert(renderContext->args); + assert(renderContext->args->_viewFrustum); + + // render backgrounds + auto& scene = sceneContext->_scene; + auto& items = scene->getMasterBucket().at(ItemFilter::Builder::opaqueShape().withLayered()); + + + ItemIDsBounds inItems; + inItems.reserve(items.size()); + for (auto id : items) { + auto& item = scene->getItem(id); + if (item.getKey().isVisible() && (item.getLayer() > 0)) { + inItems.emplace_back(id); + } + } + if (inItems.empty()) { + return; + } + + RenderArgs* args = renderContext->args; + gpu::Batch batch; + args->_batch = &batch; + + glm::mat4 projMat; + Transform viewMat; + args->_viewFrustum->evalProjectionMatrix(projMat); + args->_viewFrustum->evalViewTransform(viewMat); + batch.setProjectionTransform(projMat); + batch.setViewTransform(viewMat); + + batch.clearFramebuffer(gpu::Framebuffer::BUFFER_DEPTH, glm::vec4(), 1.f, 0); + + renderItems(sceneContext, renderContext, inItems); + args->_context->render((*args->_batch)); + args->_batch = nullptr; + + // Force the context sync + args->_context->syncCache(); +} void ItemMaterialBucketMap::insert(const ItemID& id, const model::MaterialKey& key) { diff --git a/libraries/render/src/render/DrawTask.h b/libraries/render/src/render/DrawTask.h index 1f260583f2..0052c314c0 100755 --- a/libraries/render/src/render/DrawTask.h +++ b/libraries/render/src/render/DrawTask.h @@ -87,6 +87,14 @@ public: }; template <> void jobRun(const DrawBackground& job, const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext); + +class DrawPostLayered { +public: +}; +template <> void jobRun(const DrawPostLayered& job, const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext); + + + class ResetGLState { public: }; diff --git a/libraries/render/src/render/Scene.cpp b/libraries/render/src/render/Scene.cpp index 8615f7cf7a..1d2e54541b 100644 --- a/libraries/render/src/render/Scene.cpp +++ b/libraries/render/src/render/Scene.cpp @@ -45,10 +45,12 @@ void ItemBucketMap::reset(const ItemID& id, const ItemKey& oldKey, const ItemKey } void ItemBucketMap::allocateStandardOpaqueTranparentBuckets() { - (*this)[ItemFilter::Builder::opaqueShape()]; - (*this)[ItemFilter::Builder::transparentShape()]; + (*this)[ItemFilter::Builder::opaqueShape().withoutLayered()]; + (*this)[ItemFilter::Builder::transparentShape().withoutLayered()]; (*this)[ItemFilter::Builder::light()]; (*this)[ItemFilter::Builder::background()]; + (*this)[ItemFilter::Builder::opaqueShape().withLayered()]; + (*this)[ItemFilter::Builder::transparentShape().withLayered()]; } @@ -61,11 +63,6 @@ void Item::resetPayload(const PayloadPointer& payload) { } } -void Item::kill() { - _payload.reset(); - _key._flags.reset(); -} - void PendingChanges::resetItem(ItemID id, const PayloadPointer& payload) { _resetItems.push_back(id); _resetPayloads.push_back(payload); @@ -164,27 +161,3 @@ void Scene::updateItems(const ItemIDs& ids, UpdateFunctors& functors) { _items[(*updateID)].update((*updateFunctor)); } } - -void Scene::registerObserver(ObserverPointer& observer) { - // make sure it's a valid observer - if (observer && (observer->getScene() == nullptr)) { - // Then register the observer - _observers.push_back(observer); - - // And let it do what it wants to do - observer->registerScene(this); - } -} - -void Scene::unregisterObserver(ObserverPointer& observer) { - // make sure it's a valid observer currently registered - if (observer && (observer->getScene() == this)) { - // let it do what it wants to do - observer->unregisterScene(); - - // Then unregister the observer - auto it = std::find(_observers.begin(), _observers.end(), observer); - _observers.erase(it); - } -} - diff --git a/libraries/render/src/render/Scene.h b/libraries/render/src/render/Scene.h index 8cb29609ba..5568312dfe 100644 --- a/libraries/render/src/render/Scene.h +++ b/libraries/render/src/render/Scene.h @@ -36,7 +36,6 @@ public: enum FlagBit { TYPE_SHAPE = 0, // Item is a Shape TYPE_LIGHT, // Item is a Light - TYPE_BACKGROUND, // Item is a Background TRANSLUCENT, // Transparent and not opaque, for some odd reason TRANSPARENCY doesn't work... VIEW_SPACE, // Transformed in view space, and not in world space DYNAMIC, // Dynamic and bound will change unlike static item @@ -44,7 +43,7 @@ public: INVISIBLE, // Visible or not? could be just here to cast shadow SHADOW_CASTER, // Item cast shadows PICKABLE, // Item can be picked/selected - NO_DEPTH_SORT, // Item should not be depth sorted + LAYERED, // Item belongs to one of the layers different from the default layer NUM_FLAGS, // Not a valid flag }; @@ -57,6 +56,7 @@ public: ItemKey(const Flags& flags) : _flags(flags) {} class Builder { + friend class ItemKey; Flags _flags{ 0 }; public: Builder() {} @@ -65,7 +65,6 @@ public: Builder& withTypeShape() { _flags.set(TYPE_SHAPE); return (*this); } Builder& withTypeLight() { _flags.set(TYPE_LIGHT); return (*this); } - Builder& withTypeBackground() { _flags.set(TYPE_BACKGROUND); return (*this); } Builder& withTransparent() { _flags.set(TRANSLUCENT); return (*this); } Builder& withViewSpace() { _flags.set(VIEW_SPACE); return (*this); } Builder& withDynamic() { _flags.set(DYNAMIC); return (*this); } @@ -73,14 +72,15 @@ public: Builder& withInvisible() { _flags.set(INVISIBLE); return (*this); } Builder& withShadowCaster() { _flags.set(SHADOW_CASTER); return (*this); } Builder& withPickable() { _flags.set(PICKABLE); return (*this); } - Builder& withNoDepthSort() { _flags.set(NO_DEPTH_SORT); return (*this); } + Builder& withLayered() { _flags.set(LAYERED); return (*this); } // Convenient standard keys that we will keep on using all over the place - static ItemKey opaqueShape() { return Builder().withTypeShape().build(); } - static ItemKey transparentShape() { return Builder().withTypeShape().withTransparent().build(); } - static ItemKey light() { return Builder().withTypeLight().build(); } - static ItemKey background() { return Builder().withTypeBackground().build(); } + static Builder opaqueShape() { return Builder().withTypeShape(); } + static Builder transparentShape() { return Builder().withTypeShape().withTransparent(); } + static Builder light() { return Builder().withTypeLight(); } + static Builder background() { return Builder().withViewSpace().withLayered(); } }; + ItemKey(const Builder& builder) : ItemKey(builder._flags) {} bool isOpaque() const { return !_flags[TRANSLUCENT]; } bool isTransparent() const { return _flags[TRANSLUCENT]; } @@ -101,8 +101,7 @@ public: bool isPickable() const { return _flags[PICKABLE]; } - bool isDepthSort() const { return !_flags[NO_DEPTH_SORT]; } - bool isNoDepthSort() const { return _flags[NO_DEPTH_SORT]; } + bool isLayered() const { return _flags[LAYERED]; } }; inline QDebug operator<<(QDebug debug, const ItemKey& itemKey) { @@ -122,6 +121,7 @@ public: ItemFilter(const ItemKey::Flags& value = ItemKey::Flags(0), const ItemKey::Flags& mask = ItemKey::Flags(0)) : _value(value), _mask(mask) {} class Builder { + friend class ItemFilter; ItemKey::Flags _value{ 0 }; ItemKey::Flags _mask{ 0 }; public: @@ -131,7 +131,6 @@ public: Builder& withTypeShape() { _value.set(ItemKey::TYPE_SHAPE); _mask.set(ItemKey::TYPE_SHAPE); return (*this); } Builder& withTypeLight() { _value.set(ItemKey::TYPE_LIGHT); _mask.set(ItemKey::TYPE_LIGHT); return (*this); } - Builder& withTypeBackground() { _value.set(ItemKey::TYPE_BACKGROUND); _mask.set(ItemKey::TYPE_BACKGROUND); return (*this); } Builder& withOpaque() { _value.reset(ItemKey::TRANSLUCENT); _mask.set(ItemKey::TRANSLUCENT); return (*this); } Builder& withTransparent() { _value.set(ItemKey::TRANSLUCENT); _mask.set(ItemKey::TRANSLUCENT); return (*this); } @@ -153,16 +152,18 @@ public: Builder& withPickable() { _value.set(ItemKey::PICKABLE); _mask.set(ItemKey::PICKABLE); return (*this); } - Builder& withDepthSort() { _value.reset(ItemKey::NO_DEPTH_SORT); _mask.set(ItemKey::NO_DEPTH_SORT); return (*this); } - Builder& withNotDepthSort() { _value.set(ItemKey::NO_DEPTH_SORT); _mask.set(ItemKey::NO_DEPTH_SORT); return (*this); } + Builder& withoutLayered() { _value.reset(ItemKey::LAYERED); _mask.set(ItemKey::LAYERED); return (*this); } + Builder& withLayered() { _value.set(ItemKey::LAYERED); _mask.set(ItemKey::LAYERED); return (*this); } // Convenient standard keys that we will keep on using all over the place - static ItemFilter opaqueShape() { return Builder().withTypeShape().withOpaque().withWorldSpace().build(); } - static ItemFilter transparentShape() { return Builder().withTypeShape().withTransparent().withWorldSpace().build(); } - static ItemFilter light() { return Builder().withTypeLight().build(); } - static ItemFilter background() { return Builder().withTypeBackground().build(); } + static Builder opaqueShape() { return Builder().withTypeShape().withOpaque().withWorldSpace(); } + static Builder transparentShape() { return Builder().withTypeShape().withTransparent().withWorldSpace(); } + static Builder light() { return Builder().withTypeLight(); } + static Builder background() { return Builder().withViewSpace().withLayered(); } }; + ItemFilter(const Builder& builder) : ItemFilter(builder._value, builder._mask) {} + // Item Filter operator testing if a key pass the filter bool test(const ItemKey& key) const { return (key._flags & _mask) == (_value & _mask); } @@ -179,7 +180,7 @@ public: }; inline QDebug operator<<(QDebug debug, const ItemFilter& me) { - debug << "[ItemFilter: opaqueShape:" << me.test(ItemKey::Builder::opaqueShape()) + debug << "[ItemFilter: opaqueShape:" << me.test(ItemKey::Builder::opaqueShape().build()) << "]"; return debug; } @@ -214,35 +215,40 @@ public: public: virtual const ItemKey getKey() const = 0; virtual const Bound getBound() const = 0; - virtual void render(RenderArgs* args) = 0; + virtual int getLayer() const = 0; - virtual void update(const UpdateFunctorPointer& functor) = 0; + virtual void render(RenderArgs* args) = 0; virtual const model::MaterialKey getMaterialKey() const = 0; ~PayloadInterface() {} protected: + friend class Item; + virtual void update(const UpdateFunctorPointer& functor) = 0; }; - - - typedef std::shared_ptr<PayloadInterface> PayloadPointer; - - Item() {} ~Item() {} + // Main scene / item managment interface Reset/Update/Kill void resetPayload(const PayloadPointer& payload); - void kill(); + void update(const UpdateFunctorPointer& updateFunctor) { _payload->update(updateFunctor); } // Communicate update to the payload + void kill() { _payload.reset(); _key._flags.reset(); } // Kill means forget the payload and key // Check heuristic key const ItemKey& getKey() const { return _key; } // Payload Interface + + // Get the bound of the item expressed in world space (or eye space depending on the key.isWorldSpace()) const Bound getBound() const { return _payload->getBound(); } + + // Get the layer where the item belongs. 0 by default meaning NOT LAYERED + int getLayer() const { return _payload->getLayer(); } + + // Render call for the item void render(RenderArgs* args) { _payload->render(args); } - void update(const UpdateFunctorPointer& updateFunctor) { _payload->update(updateFunctor); } // Shape Type Interface const model::MaterialKey& getMaterialKey() const { return _payload->getMaterialKey(); } @@ -280,6 +286,7 @@ inline QDebug operator<<(QDebug debug, const Item& item) { // of the Payload interface template <class T> const ItemKey payloadGetKey(const std::shared_ptr<T>& payloadData) { return ItemKey(); } template <class T> const Item::Bound payloadGetBound(const std::shared_ptr<T>& payloadData) { return Item::Bound(); } +template <class T> int payloadGetLayer(const std::shared_ptr<T>& payloadData) { return 0; } template <class T> void payloadRender(const std::shared_ptr<T>& payloadData, RenderArgs* args) { } // Shape type interface @@ -290,19 +297,25 @@ public: typedef std::shared_ptr<T> DataPointer; typedef UpdateFunctor<T> Updater; - virtual void update(const UpdateFunctorPointer& functor) { static_cast<Updater*>(functor.get())->_func((*_data)); } + Payload(const DataPointer& data) : _data(data) {} // Payload general interface virtual const ItemKey getKey() const { return payloadGetKey<T>(_data); } virtual const Item::Bound getBound() const { return payloadGetBound<T>(_data); } + virtual int getLayer() const { return payloadGetLayer<T>(_data); } + + virtual void render(RenderArgs* args) { payloadRender<T>(_data, args); } // Shape Type interface virtual const model::MaterialKey getMaterialKey() const { return shapeGetMaterialKey<T>(_data); } - Payload(const DataPointer& data) : _data(data) {} protected: DataPointer _data; + + // Update mechanics + virtual void update(const UpdateFunctorPointer& functor) { static_cast<Updater*>(functor.get())->_func((*_data)); } + friend class Item; }; // Let's show how to make a simple FooPayload example: @@ -358,6 +371,7 @@ public: typedef std::vector< ItemIDAndBounds > ItemIDsBounds; + // A map of ItemIDSets allowing to create bucket lists of items which are filtering correctly class ItemBucketMap : public std::map<ItemFilter, ItemIDSet, ItemFilter::Less> { public: @@ -374,8 +388,6 @@ public: }; class Engine; -class Observer; - class PendingChanges { public: @@ -411,36 +423,6 @@ typedef std::queue<PendingChanges> PendingChangesQueue; // Items are notified accordingly on any update message happening class Scene { public: - - class Observer { - public: - Observer(Scene* scene) { - - } - ~Observer() {} - - const Scene* getScene() const { return _scene; } - Scene* editScene() { return _scene; } - - protected: - Scene* _scene = nullptr; - virtual void onRegisterScene() {} - virtual void onUnregisterScene() {} - - friend class Scene; - void registerScene(Scene* scene) { - _scene = scene; - onRegisterScene(); - } - - void unregisterScene() { - onUnregisterScene(); - _scene = 0; - } - }; - typedef std::shared_ptr< Observer > ObserverPointer; - typedef std::vector< ObserverPointer > Observers; - Scene(); ~Scene() {} @@ -450,11 +432,6 @@ public: /// Enqueue change batch to the scene void enqueuePendingChanges(const PendingChanges& pendingChanges); - /// Scene Observer listen to any change and get notified - void registerObserver(ObserverPointer& observer); - void unregisterObserver(ObserverPointer& observer); - - /// Access the main bucketmap of items const ItemBucketMap& getMasterBucket() const { return _masterBucketMap; } @@ -483,10 +460,6 @@ protected: void removeItems(const ItemIDs& ids); void updateItems(const ItemIDs& ids, UpdateFunctors& functors); - - // The scene context listening for any change to the database - Observers _observers; - friend class Engine; }; From 7d0000c5377febd3fc3307b840f5fe020f3d7bfa Mon Sep 17 00:00:00 2001 From: Andrew Meadows <andrew@highfidelity.io> Date: Thu, 11 Jun 2015 08:38:03 -0700 Subject: [PATCH 54/88] 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 <andrew@highfidelity.io> Date: Thu, 11 Jun 2015 08:43:19 -0700 Subject: [PATCH 55/88] 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 <clement.brisset@gmail.com> Date: Thu, 11 Jun 2015 18:34:39 +0200 Subject: [PATCH 56/88] 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 <gpu/GPUConfig.h> #include <gpu/Batch.h> +#include <AbstractViewStateInterface.h> #include <DeferredLightingEffect.h> #include <DependencyManager.h> #include <GeometryCache.h> @@ -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<RenderableZoneEntityItemMeta> 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<render::Scene> 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<render::Scene> 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<render::Scene> scene, render::PendingChanges& pendingChanges); + virtual void removeFromScene(EntityItemPointer self, std::shared_ptr<render::Scene> 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 <bradh@konamoxt.com> Date: Thu, 11 Jun 2015 09:34:56 -0700 Subject: [PATCH 57/88] 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<DeferredLightingEffect>()->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<DeferredLightingEffect>()->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<NodeList>(); 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<DeferredLightingEffect>()->renderSolidSphere(batch, 0.5f, SLICES, STACKS, sphereColor); RenderableDebugableEntityItem::render(this, args); From a878559e0c4a88126c924abc805f236f0f7bb765 Mon Sep 17 00:00:00 2001 From: ZappoMan <bradh@konamoxt.com> Date: Thu, 11 Jun 2015 09:38:45 -0700 Subject: [PATCH 58/88] 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<DeferredLightingEffect>()->renderWireSphere(batch, 0.5f, 15, 15, glm::vec4(color, 1.0f)); #endif }; From 0e8ec81837f56f4a211fc843c3a51c4aa168308d Mon Sep 17 00:00:00 2001 From: Sam Gondelman <samuel_gondelman@brown.edu> Date: Thu, 11 Jun 2015 09:41:59 -0700 Subject: [PATCH 59/88] 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 <seth.alves@gmail.com> Date: Thu, 11 Jun 2015 11:04:19 -0700 Subject: [PATCH 60/88] 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<AvatarManager>()->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 <bdavis@saintandreas.org> Date: Thu, 11 Jun 2015 14:56:21 -0700 Subject: [PATCH 61/88] 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<GeometryCache>()->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<AvatarManager>()->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<GeometryCache>()->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<GeometryCache>(); 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<AvatarManager>()->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<GeometryCache>(); + //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<AvatarManager>()->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<AvatarManager>()->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<GeometryCache>()->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<GeometryCache>()->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<AvatarManager>()->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<AvatarManager>()->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<GeometryCache>(); - 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 <bdavis@saintandreas.org> Date: Thu, 11 Jun 2015 15:05:08 -0700 Subject: [PATCH 62/88] 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<TextureCache>()->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 <typename F> -//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 <bdavis@saintandreas.org> Date: Thu, 11 Jun 2015 15:40:16 -0700 Subject: [PATCH 63/88] 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 e6bafb9bf1a202415540f955fcea739abc108738 Mon Sep 17 00:00:00 2001 From: Howard Stearns <howard@highfidelity.io> Date: Thu, 11 Jun 2015 16:19:54 -0700 Subject: [PATCH 64/88] Draggable toolBar.js, with persistence, and update some scripts for it. --- examples/controllers/hydra/gun.js | 23 ++++++---- examples/dice.js | 24 +++++++---- examples/edit.js | 11 +++++ examples/libraries/toolBars.js | 70 +++++++++++++++++++++++++++++-- examples/pointer.js | 16 +++++-- 5 files changed, 119 insertions(+), 25 deletions(-) diff --git a/examples/controllers/hydra/gun.js b/examples/controllers/hydra/gun.js index 146f9daca3..7d024e2fd3 100644 --- a/examples/controllers/hydra/gun.js +++ b/examples/controllers/hydra/gun.js @@ -99,6 +99,13 @@ var NUM_BUTTONS = 3; var screenSize = Controller.getViewportDimensions(); var startX = screenSize.x / 2 - (NUM_BUTTONS * (BUTTON_SIZE + PADDING)) / 2; +Script.include(["../../libraries/toolBars.js"]); +const persistKey = "highfidelity.gun.toolbar.position"; +var toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL); +toolBar.save = function () { + Settings.setValue(persistKey, JSON.stringify([toolBar.x, toolBar.y])); +}; +var old = JSON.parse(Settings.getValue(persistKey) || '0'); var reticle = Overlays.addOverlay("image", { x: screenSize.x / 2 - (BUTTON_SIZE / 2), y: screenSize.y / 2 - (BUTTON_SIZE / 2), @@ -108,9 +115,9 @@ var reticle = Overlays.addOverlay("image", { alpha: 1 }); -var offButton = Overlays.addOverlay("image", { - x: startX, - y: screenSize.y - (BUTTON_SIZE + PADDING), +var offButton = toolBar.addOverlay("image", { + x: old ? old[0] : startX, + y: old ? old[1] : (screenSize.y - (BUTTON_SIZE + PADDING)), width: BUTTON_SIZE, height: BUTTON_SIZE, imageURL: HIFI_PUBLIC_BUCKET + "images/gun/close.svg", @@ -118,7 +125,7 @@ var offButton = Overlays.addOverlay("image", { }); startX += BUTTON_SIZE + PADDING; -var platformButton = Overlays.addOverlay("image", { +var platformButton = toolBar.addOverlay("image", { x: startX, y: screenSize.y - (BUTTON_SIZE + PADDING), width: BUTTON_SIZE, @@ -128,7 +135,7 @@ var platformButton = Overlays.addOverlay("image", { }); startX += BUTTON_SIZE + PADDING; -var gridButton = Overlays.addOverlay("image", { +var gridButton = toolBar.addOverlay("image", { x: startX, y: screenSize.y - (BUTTON_SIZE + PADDING), width: BUTTON_SIZE, @@ -493,10 +500,8 @@ function mousePressEvent(event) { } function scriptEnding() { - Overlays.deleteOverlay(reticle); - Overlays.deleteOverlay(offButton); - Overlays.deleteOverlay(platformButton); - Overlays.deleteOverlay(gridButton); + Overlays.deleteOverlay(reticle); + toolBar.cleanup(); Overlays.deleteOverlay(pointer[0]); Overlays.deleteOverlay(pointer[1]); Overlays.deleteOverlay(text); diff --git a/examples/dice.js b/examples/dice.js index 515019e740..ac5d1b7426 100644 --- a/examples/dice.js +++ b/examples/dice.js @@ -3,6 +3,7 @@ // examples // // Created by Philip Rosedale on February 2, 2015 +// Persist toolbar by HRS 6/11/15. // Copyright 2015 High Fidelity, Inc. // // Press the dice button to throw some dice from the center of the screen. @@ -31,9 +32,16 @@ var screenSize = Controller.getViewportDimensions(); var BUTTON_SIZE = 32; var PADDING = 3; -var offButton = Overlays.addOverlay("image", { - x: screenSize.x / 2 - BUTTON_SIZE * 2 + PADDING, - y: screenSize.y - (BUTTON_SIZE + PADDING), +Script.include(["libraries/toolBars.js"]); +var toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL); +const persistKey = "highfidelity.dice.toolbar.position"; +toolBar.save = function () { + Settings.setValue(persistKey, JSON.stringify([toolBar.x, toolBar.y])); +}; +var old = JSON.parse(Settings.getValue(persistKey) || '0'); +var offButton = toolBar.addOverlay("image", { + x: old ? old[0] : (screenSize.x / 2 - BUTTON_SIZE * 2 + PADDING), + y: old ? old[1] : (screenSize.y - (BUTTON_SIZE + PADDING)), width: BUTTON_SIZE, height: BUTTON_SIZE, imageURL: HIFI_PUBLIC_BUCKET + "images/close.png", @@ -45,7 +53,7 @@ var offButton = Overlays.addOverlay("image", { alpha: 1 }); -var deleteButton = Overlays.addOverlay("image", { +var deleteButton = toolBar.addOverlay("image", { x: screenSize.x / 2 - BUTTON_SIZE, y: screenSize.y - (BUTTON_SIZE + PADDING), width: BUTTON_SIZE, @@ -59,7 +67,7 @@ var deleteButton = Overlays.addOverlay("image", { alpha: 1 }); -var diceButton = Overlays.addOverlay("image", { +var diceButton = toolBar.addOverlay("image", { x: screenSize.x / 2 + PADDING, y: screenSize.y - (BUTTON_SIZE + PADDING), width: BUTTON_SIZE, @@ -140,10 +148,8 @@ function mousePressEvent(event) { } function scriptEnding() { - Overlays.deleteOverlay(offButton); - Overlays.deleteOverlay(diceButton); - Overlays.deleteOverlay(deleteButton); + toolBar.cleanup(); } Controller.mousePressEvent.connect(mousePressEvent); -Script.scriptEnding.connect(scriptEnding); \ No newline at end of file +Script.scriptEnding.connect(scriptEnding); diff --git a/examples/edit.js b/examples/edit.js index 6055400289..2974397cde 100644 --- a/examples/edit.js +++ b/examples/edit.js @@ -2,6 +2,7 @@ // examples // // Created by Brad Hefta-Gaub on 10/2/14. +// Persist toolbar by HRS 6/11/15. // Copyright 2014 High Fidelity, Inc. // // This script allows you to edit entities with a new UI/UX for mouse and trackpad based editing @@ -320,6 +321,7 @@ var toolBar = (function () { } } + const persistKey = "highfidelity.edit.toolbar.position"; that.move = function () { var newViewPort, toolsX, @@ -330,6 +332,15 @@ var toolBar = (function () { if (toolBar === undefined) { initialize(); + toolBar.save = function () { + Settings.setValue(persistKey, JSON.stringify([toolBar.x, toolBar.y])); + }; + var old = JSON.parse(Settings.getValue(persistKey) || '0'); + if (old) { + windowDimensions = newViewPort; + toolBar.move(old[0], old[1]); + return; + } } else if (windowDimensions.x === newViewPort.x && windowDimensions.y === newViewPort.y) { return; diff --git a/examples/libraries/toolBars.js b/examples/libraries/toolBars.js index 670a69dec7..94bc1c8af0 100644 --- a/examples/libraries/toolBars.js +++ b/examples/libraries/toolBars.js @@ -236,6 +236,7 @@ ToolBar = function(x, y, direction) { y: y - ToolBar.SPACING }); } + this.save(); } this.setAlpha = function(alpha, tool) { @@ -313,9 +314,8 @@ ToolBar = function(x, y, direction) { this.cleanup = function() { for(var tool in this.tools) { this.tools[tool].cleanup(); - delete this.tools[tool]; } - + if (this.back != null) { Overlays.deleteOverlay(this.back); this.back = null; @@ -327,7 +327,71 @@ ToolBar = function(x, y, direction) { this.width = 0; this.height = 0; } + + var that = this; + this.contains = function (xOrPoint, optionalY) { + var x = (optionalY === undefined) ? xOrPoint.x : xOrPoint, + y = (optionalY === undefined) ? xOrPoint.y : optionalY; + return (that.x <= x) && (x <= (that.x + that.width)) && + (that.y <= y) && (y <= (that.y + that.height)); + } + that.hover = function (enable) { + that.isHovering = enable; + if (that.back) { + if (enable) { + that.oldAlpha = Overlays.getProperty(that.back, 'backgroundAlpha'); + } + Overlays.editOverlay(this.back, { + visible: enable, + backgroundAlpha: enable ? 0.5 : that.oldAlpha + }); + } + }; + // These are currently only doing that which is necessary for toolbar hover and toolbar drag. + // They have not yet been extended to tool hover/click/release, etc. + this.mousePressEvent = function (event) { + if (!that.contains(event)) { + that.mightBeDragging = false; + return; + } + that.mightBeDragging = true; + that.dragOffsetX = that.x - event.x; + that.dragOffsetY = that.y - event.y; + }; + this.mouseMove = function (event) { + if (!that.mightBeDragging || !event.isLeftButton) { + that.mightBeDragging = false; + if (!that.contains(event)) { + if (that.isHovering) { + that.hover(false); + } + return; + } + if (!that.isHovering) { + that.hover(true); + } + return; + } + that.move(that.dragOffsetX + event.x, that.dragOffsetY + event.y); + }; + Controller.mousePressEvent.connect(this.mousePressEvent); + Controller.mouseMoveEvent.connect(this.mouseMove); + // Called on move. A different approach would be to have all this on the prototype, + // and let apps extend where needed. Ex. app defines its toolbar.move() to call this.__proto__.move and then save. + this.save = function () { }; + // This compatability hack breaks the model, but makes converting existing scripts easier: + this.addOverlay = function (ignored, oldSchoolProperties) { + var properties = JSON.parse(JSON.stringify(oldSchoolProperties)); // a copy + if (that.numberOfTools() === 0) { + that.move(properties.x, properties.y); + } + delete properties.x; + delete properties.y; + var index = that.addTool(properties); + var id = that.tools[index].overlay(); + return id; + } } ToolBar.SPACING = 4; ToolBar.VERTICAL = 0; -ToolBar.HORIZONTAL = 1; \ No newline at end of file +ToolBar.HORIZONTAL = 1; diff --git a/examples/pointer.js b/examples/pointer.js index cca46709ee..ea6b0c233f 100644 --- a/examples/pointer.js +++ b/examples/pointer.js @@ -3,6 +3,7 @@ // // Created by Seth Alves on May 15th // Modified by Eric Levin on June 4 +// Persist toolbar by HRS 6/11/15. // Copyright 2015 High Fidelity, Inc. // // Provides a pointer with option to draw on surfaces @@ -31,9 +32,16 @@ HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; var screenSize = Controller.getViewportDimensions(); var userCanPoint = false; -var pointerButton = Overlays.addOverlay("image", { - x: screenSize.x / 2 - BUTTON_SIZE * 2 + PADDING, - y: screenSize.y - (BUTTON_SIZE + PADDING), +Script.include(["libraries/toolBars.js"]); +const persistKey = "highfidelity.pointer.toolbar.position"; +var toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL); +toolBar.save = function () { + Settings.setValue(persistKey, JSON.stringify([toolBar.x, toolBar.y])); +}; +var old = JSON.parse(Settings.getValue(persistKey) || '0'); +var pointerButton = toolBar.addOverlay("image", { + x: old ? old[0] : screenSize.x / 2 - BUTTON_SIZE * 2 + PADDING, + y: old ? old[1] : screenSize.y - (BUTTON_SIZE + PADDING), width: BUTTON_SIZE, height: BUTTON_SIZE, imageURL: HIFI_PUBLIC_BUCKET + "images/laser.png", @@ -150,4 +158,4 @@ Script.scriptEnding.connect(cleanup); Controller.mouseMoveEvent.connect(mouseMoveEvent); Controller.mousePressEvent.connect(mousePressEvent); -Controller.mouseReleaseEvent.connect(mouseReleaseEvent); \ No newline at end of file +Controller.mouseReleaseEvent.connect(mouseReleaseEvent); From 3cb4ee183e615a2fcf9192c5a1802cac70cbc100 Mon Sep 17 00:00:00 2001 From: Howard Stearns <howard@highfidelity.io> Date: Thu, 11 Jun 2015 16:24:22 -0700 Subject: [PATCH 65/88] Update header. --- examples/libraries/toolBars.js | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/libraries/toolBars.js b/examples/libraries/toolBars.js index 94bc1c8af0..6e72795ea5 100644 --- a/examples/libraries/toolBars.js +++ b/examples/libraries/toolBars.js @@ -3,6 +3,7 @@ // examples // // Created by Clément Brisset on 5/7/14. +// Persistable drag position by HRS 6/11/15. // Copyright 2014 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. From 09cfe004a18ab8c9b5622eb6d33dc22a41b7cf36 Mon Sep 17 00:00:00 2001 From: Brad Davis <bdavis@saintandreas.org> Date: Thu, 11 Jun 2015 16:55:23 -0700 Subject: [PATCH 66/88] 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<TextureCache>()-> 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 <commit@birarda.com> Date: Thu, 11 Jun 2015 17:25:25 -0700 Subject: [PATCH 67/88] 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<QProcess*>(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 <howard@highfidelity.io> Date: Thu, 11 Jun 2015 20:33:30 -0700 Subject: [PATCH 68/88] 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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 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(); From feb3f68a00bef6b95fc12852dcdbb913f5390401 Mon Sep 17 00:00:00 2001 From: ZappoMan <bradh@konamoxt.com> Date: Thu, 11 Jun 2015 20:49:19 -0700 Subject: [PATCH 69/88] hack to fix culling bugs for some subMesh parts in the windmill scene --- libraries/render-utils/src/Model.cpp | 62 +++++++++++++++++++++------- 1 file changed, 46 insertions(+), 16 deletions(-) diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 8d234cdef5..94a856073e 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1019,7 +1019,7 @@ AABox Model::calculateScaledOffsetAABox(const AABox& box) const { glm::vec3 Model::calculateScaledOffsetPoint(const glm::vec3& point) const { // we need to include any fst scaling, translation, and rotation, which is captured in the offset matrix - glm::vec3 offsetPoint = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(point, 1.0f)); + glm::vec3 offsetPoint = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(point, 1.0f)); /// should this be point??? glm::vec3 scaledPoint = ((offsetPoint + _offset) * _scale); glm::vec3 rotatedPoint = _rotation * scaledPoint; glm::vec3 translatedPoint = rotatedPoint + _translation; @@ -1764,13 +1764,33 @@ AABox Model::getPartBounds(int meshIndex, int partIndex) { if (!_calculatedMeshPartBoxesValid) { recalculateMeshBoxes(true); } + + if (meshIndex < _meshStates.size()) { + const MeshState& state = _meshStates.at(meshIndex); + bool isSkinned = state.clusterMatrices.size() > 1; + if (isSkinned) { + // if we're skinned return the entire mesh extents because we can't know for sure our clusters don't move us + return calculateScaledOffsetAABox(_geometry->getFBXGeometry().meshExtents); + } + } + if (_calculatedMeshPartBoxesValid && _calculatedMeshPartBoxes.contains(QPair<int,int>(meshIndex, partIndex))) { - return calculateScaledOffsetAABox(_calculatedMeshPartBoxes[QPair<int,int>(meshIndex, partIndex)]); + + // FIX ME! - This is currently a hack because for some mesh parts our efforts to calculate the bounding + // box of the mesh part fails. It seems to create boxes that are not consistent with where the + // geometry actually renders. If instead we make all the parts share the bounds of the entire subMesh + // things will render properly. + // + // return calculateScaledOffsetAABox(_calculatedMeshPartBoxes[QPair<int,int>(meshIndex, partIndex)]); + // + // If we not skinned use the bounds of the subMesh for all it's parts + return _calculatedMeshBoxes[meshIndex]; } return AABox(); } void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool translucent) { + if (!_readyWhenAdded) { return; // bail asap } @@ -1785,19 +1805,6 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran gpu::Batch& batch = *(args->_batch); auto mode = args->_renderMode; - // render the part bounding box - #ifdef DEBUG_BOUNDING_PARTS - { - glm::vec4 cubeColor(1.0f,0.0f,0.0f,1.0f); - AABox partBounds = getPartBounds(meshIndex, partIndex); - - glm::mat4 translation = glm::translate(partBounds.calcCenter()); - glm::mat4 scale = glm::scale(partBounds.getDimensions()); - glm::mat4 modelToWorldMatrix = translation * scale; - batch.setModelTransform(modelToWorldMatrix); - DependencyManager::get<DeferredLightingEffect>()->renderWireCube(batch, 1.0f, cubeColor); - } - #endif //def DEBUG_BOUNDING_PARTS // Capture the view matrix once for the rendering of this model if (_transforms.empty()) { @@ -1824,6 +1831,29 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran bool hasLightmap = mesh.hasEmissiveTexture(); bool isSkinned = state.clusterMatrices.size() > 1; bool wireframe = isWireframe(); + + AABox partBounds = getPartBounds(meshIndex, partIndex); + bool inView = args->_viewFrustum->boxInFrustum(partBounds) != ViewFrustum::OUTSIDE; + + // render the part bounding box + #ifdef DEBUG_BOUNDING_PARTS + { + glm::vec4 cubeColor; + if (isSkinned) { + cubeColor = glm::vec4(0.0f,1.0f,1.0f,1.0f); + } else if (inView) { + cubeColor = glm::vec4(1.0f,0.0f,1.0f,1.0f); + } else { + cubeColor = glm::vec4(1.0f,1.0f,0.0f,1.0f); + } + + Transform transform; + transform.setTranslation(partBounds.calcCenter()); + transform.setScale(partBounds.getDimensions()); + batch.setModelTransform(transform); + DependencyManager::get<DeferredLightingEffect>()->renderWireCube(batch, 1.0f, cubeColor); + } + #endif //def DEBUG_BOUNDING_PARTS if (wireframe) { translucentMesh = hasTangents = hasSpecular = hasLightmap = isSkinned = false; @@ -1977,7 +2007,7 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran } qint64 offset = _calculatedMeshPartOffet[QPair<int,int>(meshIndex, partIndex)]; - + if (part.quadIndices.size() > 0) { batch.drawIndexed(gpu::QUADS, part.quadIndices.size(), offset); offset += part.quadIndices.size() * sizeof(int); From 2fc0233096d1ff65c301dae50ef8a6c539a2aaea Mon Sep 17 00:00:00 2001 From: ZappoMan <bradh@konamoxt.com> Date: Thu, 11 Jun 2015 20:50:31 -0700 Subject: [PATCH 70/88] hack to fix culling bugs for some subMesh parts in the windmill scene --- libraries/render-utils/src/Model.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 94a856073e..d313ef9b79 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1832,12 +1832,12 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran bool isSkinned = state.clusterMatrices.size() > 1; bool wireframe = isWireframe(); - AABox partBounds = getPartBounds(meshIndex, partIndex); - bool inView = args->_viewFrustum->boxInFrustum(partBounds) != ViewFrustum::OUTSIDE; - // render the part bounding box #ifdef DEBUG_BOUNDING_PARTS { + AABox partBounds = getPartBounds(meshIndex, partIndex); + bool inView = args->_viewFrustum->boxInFrustum(partBounds) != ViewFrustum::OUTSIDE; + glm::vec4 cubeColor; if (isSkinned) { cubeColor = glm::vec4(0.0f,1.0f,1.0f,1.0f); From 565bf8bcb269f2c17c5694f50da71a2a1734fc35 Mon Sep 17 00:00:00 2001 From: Atlante45 <clement.brisset@gmail.com> Date: Fri, 12 Jun 2015 15:41:37 +0200 Subject: [PATCH 71/88] Fix text entities wrapping --- .../src/RenderableTextEntityItem.cpp | 5 +++-- libraries/render-utils/src/TextRenderer3D.cpp | 21 ++++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index 8a67bb99f2..f61ed6f2c5 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -51,8 +51,9 @@ void RenderableTextEntityItem::render(RenderArgs* args) { transformToTopLeft.setScale(scale); // Scale to have the correct line height batch.setModelTransform(transformToTopLeft); - float leftMargin = 0.5f * _lineHeight, topMargin = 0.5f * _lineHeight; - glm::vec2 bounds = glm::vec2(dimensions.x - 2.0f * leftMargin, dimensions.y - 2.0f * topMargin); + float leftMargin = 0.1f * _lineHeight, topMargin = 0.1f * _lineHeight; + glm::vec2 bounds = glm::vec2(dimensions.x - 2.0f * leftMargin, + dimensions.y - 2.0f * topMargin); _textRenderer->draw(batch, leftMargin / scale, -topMargin / scale, _text, textColor, bounds / scale); } diff --git a/libraries/render-utils/src/TextRenderer3D.cpp b/libraries/render-utils/src/TextRenderer3D.cpp index d081c0480a..7dbb7ea4fb 100644 --- a/libraries/render-utils/src/TextRenderer3D.cpp +++ b/libraries/render-utils/src/TextRenderer3D.cpp @@ -101,7 +101,7 @@ struct QuadBuilder { texMin); } QuadBuilder(const Glyph3D& glyph, const glm::vec2& offset) : - QuadBuilder(offset + glyph.offset - glm::vec2(0.0f, glyph.size.y), glyph.size, + QuadBuilder(offset + glm::vec2(glyph.offset.x, glyph.offset.y - glyph.size.y), glyph.size, glyph.texOffset, glyph.texSize) {} }; @@ -113,7 +113,7 @@ public: void read(QIODevice& path); glm::vec2 computeExtent(const QString& str) const; - float getRowHeight() const { return _rowHeight; } + float getRowHeight() const { return _fontSize; } // Render string to batch void drawString(gpu::Batch& batch, float x, float y, const QString& str, @@ -251,13 +251,14 @@ glm::vec2 Font3D::computeTokenExtent(const QString& token) const { glm::vec2 Font3D::computeExtent(const QString& str) const { glm::vec2 extent = glm::vec2(0.0f, 0.0f); - QStringList tokens = splitLines(str); - foreach(const QString& token, tokens) { - glm::vec2 tokenExtent = computeTokenExtent(token); - extent.x = std::max(tokenExtent.x, extent.x); + QStringList lines{ splitLines(str) }; + if (!lines.empty()) { + for(const auto& line : lines) { + glm::vec2 tokenExtent = computeTokenExtent(line); + extent.x = std::max(tokenExtent.x, extent.x); + } + extent.y = lines.count() * _fontSize; } - extent.y = tokens.count() * _rowHeight; - return extent; } @@ -393,7 +394,7 @@ void Font3D::drawString(gpu::Batch& batch, float x, float y, const QString& str, } if (isNewLine || forceNewLine) { // Character return, move the advance to a new line - advance = glm::vec2(x, advance.y - _rowHeight); + advance = glm::vec2(x, advance.y - _fontSize); if (isNewLine) { // No need to draw anything, go directly to next token @@ -413,7 +414,7 @@ void Font3D::drawString(gpu::Batch& batch, float x, float y, const QString& str, for (auto c : token) { auto glyph = _glyphs[c]; - QuadBuilder qd(glyph, advance - glm::vec2(0.0f, _fontSize)); + QuadBuilder qd(glyph, advance - glm::vec2(0.0f, _ascent)); _verticesBuffer->append(sizeof(QuadBuilder), (const gpu::Byte*)&qd); _numVertices += 4; From 228f8c2e61cd37dc3912b4dd41c62d9be18d6b6b Mon Sep 17 00:00:00 2001 From: Atlante45 <clement.brisset@gmail.com> Date: Fri, 12 Jun 2015 17:34:16 +0200 Subject: [PATCH 72/88] Fix a few items rendering without pipeline --- interface/src/avatar/Avatar.cpp | 7 ++++++- .../entities-renderer/src/RenderableLineEntityItem.cpp | 1 + .../src/RenderableParticleEffectEntityItem.cpp | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index dbe7d52a3f..71f4621205 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -328,6 +328,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition, boo if (postLighting && glm::distance(DependencyManager::get<AvatarManager>()->getMyAvatar()->getPosition(), _position) < 10.0f) { auto geometryCache = DependencyManager::get<GeometryCache>(); + auto deferredLighting = DependencyManager::get<DeferredLightingEffect>(); // render pointing lasers glm::vec3 laserColor = glm::vec3(1.0f, 0.0f, 1.0f); @@ -354,6 +355,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition, boo pointerTransform.setTranslation(position); pointerTransform.setRotation(rotation); batch->setModelTransform(pointerTransform); + deferredLighting->bindSimpleProgram(*batch); geometryCache->renderLine(*batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, laserLength, 0.0f), laserColor); } } @@ -376,6 +378,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition, boo pointerTransform.setTranslation(position); pointerTransform.setRotation(rotation); batch->setModelTransform(pointerTransform); + deferredLighting->bindSimpleProgram(*batch); geometryCache->renderLine(*batch, glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, laserLength, 0.0f), laserColor); } } @@ -462,7 +465,8 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition, boo Transform transform; transform.setTranslation(position); batch->setModelTransform(transform); - DependencyManager::get<GeometryCache>()->renderSphere(*batch, LOOK_AT_INDICATOR_RADIUS, 15, 15, LOOK_AT_INDICATOR_COLOR); + DependencyManager::get<DeferredLightingEffect>()->renderSolidSphere(*batch, LOOK_AT_INDICATOR_RADIUS + , 15, 15, LOOK_AT_INDICATOR_COLOR); } } @@ -494,6 +498,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition, boo _voiceSphereID = DependencyManager::get<GeometryCache>()->allocateID(); } + DependencyManager::get<DeferredLightingEffect>()->bindSimpleProgram(*batch); DependencyManager::get<GeometryCache>()->renderSphere(*batch, sphereRadius, 15, 15, glm::vec4(SPHERE_COLOR[0], SPHERE_COLOR[1], SPHERE_COLOR[2], 1.0f - angle / MAX_SPHERE_ANGLE), true, _voiceSphereID); diff --git a/libraries/entities-renderer/src/RenderableLineEntityItem.cpp b/libraries/entities-renderer/src/RenderableLineEntityItem.cpp index 4e5de331bb..65407c74e7 100644 --- a/libraries/entities-renderer/src/RenderableLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableLineEntityItem.cpp @@ -49,6 +49,7 @@ void RenderableLineEntityItem::render(RenderArgs* args) { batch._glLineWidth(getLineWidth()); if (getLinePoints().size() > 1) { + DependencyManager::get<DeferredLightingEffect>()->bindSimpleProgram(batch); DependencyManager::get<GeometryCache>()->renderVertices(batch, gpu::LINE_STRIP, _lineVerticesID); } batch._glLineWidth(1.0f); diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index 6a50cbf1cb..91c89bb183 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -53,6 +53,7 @@ void RenderableParticleEffectEntityItem::render(RenderArgs* args) { batch.setUniformTexture(0, _texture->getGPUTexture()); } batch.setModelTransform(getTransformToCenter()); + DependencyManager::get<DeferredLightingEffect>()->bindSimpleProgram(batch); DependencyManager::get<GeometryCache>()->renderVertices(batch, gpu::QUADS, _cacheID); }; From c17ae593f0b8520c274a36b3f77c30d2c41c7f3d Mon Sep 17 00:00:00 2001 From: ZappoMan <bradh@konamoxt.com> Date: Fri, 12 Jun 2015 09:04:16 -0700 Subject: [PATCH 73/88] CR feedback --- libraries/render-utils/src/Model.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index d313ef9b79..2379058b92 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1019,7 +1019,7 @@ AABox Model::calculateScaledOffsetAABox(const AABox& box) const { glm::vec3 Model::calculateScaledOffsetPoint(const glm::vec3& point) const { // we need to include any fst scaling, translation, and rotation, which is captured in the offset matrix - glm::vec3 offsetPoint = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(point, 1.0f)); /// should this be point??? + glm::vec3 offsetPoint = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(point, 1.0f)); glm::vec3 scaledPoint = ((offsetPoint + _offset) * _scale); glm::vec3 rotatedPoint = _rotation * scaledPoint; glm::vec3 translatedPoint = rotatedPoint + _translation; @@ -1840,11 +1840,11 @@ void Model::renderPart(RenderArgs* args, int meshIndex, int partIndex, bool tran glm::vec4 cubeColor; if (isSkinned) { - cubeColor = glm::vec4(0.0f,1.0f,1.0f,1.0f); + cubeColor = glm::vec4(0.0f, 1.0f, 1.0f, 1.0f); } else if (inView) { - cubeColor = glm::vec4(1.0f,0.0f,1.0f,1.0f); + cubeColor = glm::vec4(1.0f, 0.0f, 1.0f, 1.0f); } else { - cubeColor = glm::vec4(1.0f,1.0f,0.0f,1.0f); + cubeColor = glm::vec4(1.0f, 1.0f, 0.0f, 1.0f); } Transform transform; From 128b086e82adbc45875e5d928474643c58a99430 Mon Sep 17 00:00:00 2001 From: Stephen Birarda <commit@birarda.com> Date: Fri, 12 Jun 2015 10:25:34 -0700 Subject: [PATCH 74/88] don't build the polyvox examples --- cmake/externals/polyvox/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/externals/polyvox/CMakeLists.txt b/cmake/externals/polyvox/CMakeLists.txt index 28aec6dab7..c28a8e1319 100644 --- a/cmake/externals/polyvox/CMakeLists.txt +++ b/cmake/externals/polyvox/CMakeLists.txt @@ -5,7 +5,7 @@ ExternalProject_Add( ${EXTERNAL_NAME} URL http://hifi-public.s3.amazonaws.com/dependencies/polyvox.zip URL_MD5 904b840328278c9b36fa7a14be730c34 - CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> + CMAKE_ARGS -DENABLE_EXAMPLES=OFF -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build LOG_DOWNLOAD 1 LOG_CONFIGURE 1 From 926283956412d1e35fc1ba319f2c513803da2c77 Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis <bdavis@saintandreas.org> Date: Fri, 12 Jun 2015 10:33:52 -0700 Subject: [PATCH 75/88] Fix broken identity transforms on OSX --- libraries/gpu/src/gpu/GLBackendTransform.cpp | 25 +++++++------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/libraries/gpu/src/gpu/GLBackendTransform.cpp b/libraries/gpu/src/gpu/GLBackendTransform.cpp index 3f760e4cc8..4e524ec24a 100755 --- a/libraries/gpu/src/gpu/GLBackendTransform.cpp +++ b/libraries/gpu/src/gpu/GLBackendTransform.cpp @@ -133,11 +133,11 @@ void GLBackend::updateTransform() { } if (_transform._invalidModel || _transform._invalidView) { + if (_transform._lastMode != GL_MODELVIEW) { + glMatrixMode(GL_MODELVIEW); + _transform._lastMode = GL_MODELVIEW; + } if (!_transform._model.isIdentity()) { - if (_transform._lastMode != GL_MODELVIEW) { - glMatrixMode(GL_MODELVIEW); - _transform._lastMode = GL_MODELVIEW; - } Transform::Mat4 modelView; if (!_transform._view.isIdentity()) { Transform mvx; @@ -147,19 +147,12 @@ void GLBackend::updateTransform() { _transform._model.getMatrix(modelView); } glLoadMatrixf(reinterpret_cast< const GLfloat* >(&modelView)); + } else if (!_transform._view.isIdentity()) { + Transform::Mat4 modelView; + _transform._view.getInverseMatrix(modelView); + glLoadMatrixf(reinterpret_cast< const GLfloat* >(&modelView)); } else { - if (!_transform._view.isIdentity()) { - if (_transform._lastMode != GL_MODELVIEW) { - glMatrixMode(GL_MODELVIEW); - _transform._lastMode = GL_MODELVIEW; - } - Transform::Mat4 modelView; - _transform._view.getInverseMatrix(modelView); - glLoadMatrixf(reinterpret_cast< const GLfloat* >(&modelView)); - } else { - // TODO: eventually do something about the matrix when neither view nor model is specified? - // glLoadIdentity(); - } + glLoadIdentity(); } (void) CHECK_GL_ERROR(); } From f3d3bd7bec9ab12c83da8d9013f9a510886f3699 Mon Sep 17 00:00:00 2001 From: Atlante45 <clement.brisset@gmail.com> Date: Fri, 12 Jun 2015 19:44:15 +0200 Subject: [PATCH 76/88] Remove _rowHeight --- libraries/render-utils/src/TextRenderer3D.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libraries/render-utils/src/TextRenderer3D.cpp b/libraries/render-utils/src/TextRenderer3D.cpp index 7dbb7ea4fb..5a6ec89c4f 100644 --- a/libraries/render-utils/src/TextRenderer3D.cpp +++ b/libraries/render-utils/src/TextRenderer3D.cpp @@ -139,7 +139,6 @@ private: // Font characteristics QString _family; float _fontSize = 0.0f; - float _rowHeight = 0.0f; float _leading = 0.0f; float _ascent = 0.0f; float _descent = 0.0f; @@ -289,8 +288,7 @@ void Font3D::read(QIODevice& in) { readStream(in, _descent); readStream(in, _spaceWidth); _fontSize = _ascent + _descent; - _rowHeight = _fontSize + _leading; - + // Read character count uint16_t count; readStream(in, count); From 26dd0679827a59bfe8cc25a3a22c692c9fe07ba6 Mon Sep 17 00:00:00 2001 From: Atlante45 <clement.brisset@gmail.com> Date: Fri, 12 Jun 2015 19:44:40 +0200 Subject: [PATCH 77/88] Advance with _leading on y --- libraries/render-utils/src/TextRenderer3D.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/render-utils/src/TextRenderer3D.cpp b/libraries/render-utils/src/TextRenderer3D.cpp index 5a6ec89c4f..4b59877582 100644 --- a/libraries/render-utils/src/TextRenderer3D.cpp +++ b/libraries/render-utils/src/TextRenderer3D.cpp @@ -392,7 +392,7 @@ void Font3D::drawString(gpu::Batch& batch, float x, float y, const QString& str, } if (isNewLine || forceNewLine) { // Character return, move the advance to a new line - advance = glm::vec2(x, advance.y - _fontSize); + advance = glm::vec2(x, advance.y - _leading); if (isNewLine) { // No need to draw anything, go directly to next token From 30ae78e3b6a256bf77ad5b6a1cdd240fa2df6e74 Mon Sep 17 00:00:00 2001 From: Atlante45 <clement.brisset@gmail.com> Date: Fri, 12 Jun 2015 19:50:32 +0200 Subject: [PATCH 78/88] Rename getRowHeight --- .../entities-renderer/src/RenderableTextEntityItem.cpp | 2 +- libraries/render-utils/src/TextRenderer3D.cpp | 6 +++--- libraries/render-utils/src/TextRenderer3D.h | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index f61ed6f2c5..d06ffb9400 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -47,7 +47,7 @@ void RenderableTextEntityItem::render(RenderArgs* args) { glm::vec3 maxCorner = glm::vec3(dimensions.x, 0.0f, SLIGHTLY_BEHIND); DependencyManager::get<DeferredLightingEffect>()->renderQuad(batch, minCorner, maxCorner, backgroundColor); - float scale = _lineHeight / _textRenderer->getRowHeight(); + float scale = _lineHeight / _textRenderer->getFontSize(); transformToTopLeft.setScale(scale); // Scale to have the correct line height batch.setModelTransform(transformToTopLeft); diff --git a/libraries/render-utils/src/TextRenderer3D.cpp b/libraries/render-utils/src/TextRenderer3D.cpp index 4b59877582..0eb560bf72 100644 --- a/libraries/render-utils/src/TextRenderer3D.cpp +++ b/libraries/render-utils/src/TextRenderer3D.cpp @@ -113,7 +113,7 @@ public: void read(QIODevice& path); glm::vec2 computeExtent(const QString& str) const; - float getRowHeight() const { return _fontSize; } + float getFontSize() const { return _fontSize; } // Render string to batch void drawString(gpu::Batch& batch, float x, float y, const QString& str, @@ -476,9 +476,9 @@ glm::vec2 TextRenderer3D::computeExtent(const QString& str) const { return glm::vec2(0.0f, 0.0f); } -float TextRenderer3D::getRowHeight() const { +float TextRenderer3D::getFontSize() const { if (_font) { - return _font->getRowHeight(); + return _font->getFontSize(); } return 0.0f; } diff --git a/libraries/render-utils/src/TextRenderer3D.h b/libraries/render-utils/src/TextRenderer3D.h index 8f55d0c977..e61203b06f 100644 --- a/libraries/render-utils/src/TextRenderer3D.h +++ b/libraries/render-utils/src/TextRenderer3D.h @@ -50,7 +50,7 @@ public: ~TextRenderer3D(); glm::vec2 computeExtent(const QString& str) const; - float getRowHeight() const; + float getFontSize() const; void draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color = glm::vec4(-1.0f), const glm::vec2& bounds = glm::vec2(-1.0f)); From 4ed147418a540c77025dd1abb5fcbedfbc935dab Mon Sep 17 00:00:00 2001 From: ZappoMan <bradh@konamoxt.com> Date: Fri, 12 Jun 2015 11:19:30 -0700 Subject: [PATCH 79/88] quick hack to fix stats texture bleed through --- interface/src/ui/Stats.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 32df75c46d..8d098b4dc8 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -161,6 +161,10 @@ void Stats::drawBackground(unsigned int rgba, int x, int y, int width, int heigh ((rgba >> 8) & 0xff) / 255.0f, (rgba & 0xff) / 255.0f); + // FIX ME: is this correct? It seems to work to fix textures bleeding into us... + glBindTexture(GL_TEXTURE_2D, 0); + glDisable(GL_TEXTURE_2D); + DependencyManager::get<GeometryCache>()->renderQuad(x, y, width, height, color); } From 87951ff7b3bfd19f8d824fda7dfa11553aa49b49 Mon Sep 17 00:00:00 2001 From: ZappoMan <bradh@konamoxt.com> Date: Fri, 12 Jun 2015 11:19:30 -0700 Subject: [PATCH 80/88] quick hack to fix stats texture bleed through --- interface/src/ui/Stats.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 32df75c46d..8d098b4dc8 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -161,6 +161,10 @@ void Stats::drawBackground(unsigned int rgba, int x, int y, int width, int heigh ((rgba >> 8) & 0xff) / 255.0f, (rgba & 0xff) / 255.0f); + // FIX ME: is this correct? It seems to work to fix textures bleeding into us... + glBindTexture(GL_TEXTURE_2D, 0); + glDisable(GL_TEXTURE_2D); + DependencyManager::get<GeometryCache>()->renderQuad(x, y, width, height, color); } From 7d7db65fd1550c0944c962c9a0313a69ea153b49 Mon Sep 17 00:00:00 2001 From: ZappoMan <bradh@konamoxt.com> Date: Fri, 12 Jun 2015 11:42:38 -0700 Subject: [PATCH 81/88] fix avatar mesh boxes not staying in sync with avatar position --- libraries/render-utils/src/Model.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 2379058b92..865c225445 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1783,8 +1783,13 @@ AABox Model::getPartBounds(int meshIndex, int partIndex) { // // return calculateScaledOffsetAABox(_calculatedMeshPartBoxes[QPair<int,int>(meshIndex, partIndex)]); // + // NOTE: we also don't want to use the _calculatedMeshBoxes[] because they don't handle avatar moving correctly + // without recalculating them... + // return _calculatedMeshBoxes[meshIndex]; + // // If we not skinned use the bounds of the subMesh for all it's parts - return _calculatedMeshBoxes[meshIndex]; + const FBXMesh& mesh = _geometry->getFBXGeometry().meshes.at(meshIndex); + return calculateScaledOffsetExtents(mesh.meshExtents); } return AABox(); } From 7ee609396c5045ae976b8b72dee5fddbacdb974c Mon Sep 17 00:00:00 2001 From: Eric Levin <ericrius1@gmail.com> Date: Fri, 12 Jun 2015 11:58:43 -0700 Subject: [PATCH 82/88] added lifetime to painted lines to prevent huge model.json files when lots of people draw in one domain. Got rid of gaps in lines when switching line entities --- examples/paint.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/examples/paint.js b/examples/paint.js index 8bba4e2571..c0cc93afc7 100644 --- a/examples/paint.js +++ b/examples/paint.js @@ -12,8 +12,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // Script.include('lineRider.js') -var MAX_POINTS_PER_LINE = 80; - +var MAX_POINTS_PER_LINE = 30; +var LINE_LIFETIME = 60 * 5 //5 minute lifetime var colorPalette = [{ red: 236, @@ -120,10 +120,12 @@ function MousePaint() { y: 10, z: 10 }, - lineWidth: LINE_WIDTH + lineWidth: LINE_WIDTH, + lifetime: LINE_LIFETIME }); points = []; if (point) { + points.push(point); path.push(point); } @@ -133,22 +135,22 @@ function MousePaint() { function mouseMoveEvent(event) { + if (!isDrawing) { + return; + } var pickRay = Camera.computePickRay(event.x, event.y); var addVector = Vec3.multiply(Vec3.normalize(pickRay.direction), DRAWING_DISTANCE); var point = Vec3.sum(Camera.getPosition(), addVector); + points.push(point); + path.push(point); Entities.editEntity(line, { linePoints: points }); Entities.editEntity(brush, { position: point }); - if (!isDrawing) { - return; - } - points.push(point); - path.push(point); if (points.length === MAX_POINTS_PER_LINE) { //We need to start a new line! @@ -253,7 +255,6 @@ function HydraPaint() { var maxLineWidth = 10; var currentLineWidth = minLineWidth; var MIN_PAINT_TRIGGER_THRESHOLD = .01; - var LINE_LIFETIME = 20; var COLOR_CHANGE_TIME_FACTOR = 0.1; var RIGHT_BUTTON_1 = 7 @@ -330,7 +331,7 @@ function HydraPaint() { z: 10 }, lineWidth: 5, - // lifetime: LINE_LIFETIME + lifetime: LINE_LIFETIME }); this.points = []; if (point) { From 0cdc2b53fe7815bba21a98ecbdfd91bcbb2898f5 Mon Sep 17 00:00:00 2001 From: ZappoMan <bradh@konamoxt.com> Date: Fri, 12 Jun 2015 12:57:24 -0700 Subject: [PATCH 83/88] Revert "Merge pull request #5106 from jherico/render_cursor" This reverts commit 4d18bd7cec710b5188d1f6ad5c58010117d7a77d, reversing changes made to 24fda9a7336e4eff1d1310dd3359dd6cbbb6acb5. --- interface/resources/images/arrow.png | Bin 3562 -> 0 bytes interface/src/Application.cpp | 3 +- interface/src/devices/OculusManager.cpp | 5 +- interface/src/devices/TV3DManager.cpp | 18 +- interface/src/ui/ApplicationOverlay.cpp | 666 +++++++++++------- interface/src/ui/ApplicationOverlay.h | 53 +- libraries/render-utils/src/GeometryCache.cpp | 15 - libraries/render-utils/src/GeometryCache.h | 3 - .../render-utils/src/standardDrawTexture.slf | 24 - .../src/standardTransformPNTC.slv | 33 - 10 files changed, 481 insertions(+), 339 deletions(-) delete mode 100644 interface/resources/images/arrow.png delete mode 100644 libraries/render-utils/src/standardDrawTexture.slf delete mode 100644 libraries/render-utils/src/standardTransformPNTC.slv diff --git a/interface/resources/images/arrow.png b/interface/resources/images/arrow.png deleted file mode 100644 index 408881b5856eb47a30875e3e56d5864721161587..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3562 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 R^nCyTaI$x`E7(oB_+R=!c8UN1 diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 56fe4188e9..0f0abb3996 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -955,7 +955,6 @@ void Application::paintGL() { glPushMatrix(); glLoadIdentity(); displaySide(&renderArgs, _myCamera); - _applicationOverlay.displayOverlayTexture(&renderArgs); glPopMatrix(); if (Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) { @@ -972,6 +971,8 @@ void Application::paintGL() { 0, 0, _glWidget->getDeviceSize().width(), _glWidget->getDeviceSize().height(), GL_COLOR_BUFFER_BIT, GL_NEAREST); glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); + + _applicationOverlay.displayOverlayTexture(); } if (!OculusManager::isConnected() || OculusManager::allowSwap()) { diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp index 414c7f6199..a7383ae4bb 100644 --- a/interface/src/devices/OculusManager.cpp +++ b/interface/src/devices/OculusManager.cpp @@ -615,10 +615,12 @@ void OculusManager::display(QGLWidget * glCanvas, RenderArgs* renderArgs, const renderArgs->_renderSide = RenderArgs::MONO; qApp->displaySide(renderArgs, *_camera, false); - qApp->getApplicationOverlay().displayOverlayTextureHmd(renderArgs, *_camera); + 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)) { @@ -629,7 +631,6 @@ void OculusManager::display(QGLWidget * glCanvas, RenderArgs* renderArgs, const finalFbo = DependencyManager::get<TextureCache>()->getPrimaryFramebuffer(); glBindFramebuffer(GL_FRAMEBUFFER, 0); } - glPopMatrix(); glMatrixMode(GL_PROJECTION); glPopMatrix(); diff --git a/interface/src/devices/TV3DManager.cpp b/interface/src/devices/TV3DManager.cpp index b0a2ff7e3b..09edb03e5a 100644 --- a/interface/src/devices/TV3DManager.cpp +++ b/interface/src/devices/TV3DManager.cpp @@ -12,7 +12,6 @@ #include "InterfaceConfig.h" #include <glm/glm.hpp> -#include <glm/gtc/type_ptr.hpp> #include <GlowEffect.h> #include "gpu/GLBackend.h" @@ -107,20 +106,21 @@ void TV3DManager::display(RenderArgs* renderArgs, Camera& whichCamera) { _activeEye = &eye; glViewport(portalX, portalY, portalW, portalH); glScissor(portalX, portalY, portalW, portalH); - - glm::mat4 projection = glm::frustum<float>(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 - glLoadMatrixf(glm::value_ptr(projection)); + 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 + glMatrixMode(GL_MODELVIEW); glLoadIdentity(); renderArgs->_renderSide = RenderArgs::MONO; qApp->displaySide(renderArgs, eyeCamera, false); - qApp->getApplicationOverlay().displayOverlayTexture(renderArgs); + qApp->getApplicationOverlay().displayOverlayTextureStereo(whichCamera, _aspect, fov); _activeEye = NULL; }, [&]{ // render right side view diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index ba0d3a60a9..0c1783f8ac 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -14,16 +14,13 @@ #include <QOpenGLFramebufferObject> #include <QOpenGLTexture> -#include <glm/gtc/type_ptr.hpp> - #include <avatar/AvatarManager.h> -#include <DeferredLightingEffect.h> #include <GLMHelpers.h> +#include <PathUtils.h> #include <gpu/GLBackend.h> #include <GLMHelpers.h> -#include <OffscreenUi.h> -#include <CursorManager.h> #include <PerfStat.h> +#include <OffscreenUi.h> #include "AudioClient.h" #include "audio/AudioIOStatsRenderer.h" @@ -36,9 +33,6 @@ #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; @@ -120,6 +114,27 @@ 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<GeometryCache>()->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), @@ -133,8 +148,7 @@ ApplicationOverlay::ApplicationOverlay() : _previousMagnifierBottomLeft(), _previousMagnifierBottomRight(), _previousMagnifierTopLeft(), - _previousMagnifierTopRight(), - _framebufferObject(nullptr) + _previousMagnifierTopRight() { memset(_reticleActive, 0, sizeof(_reticleActive)); memset(_magActive, 0, sizeof(_reticleActive)); @@ -181,17 +195,16 @@ 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); - buildFramebufferObject(); - - _framebufferObject->bind(); + _overlays.buildFramebufferObject(); + _overlays.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; @@ -213,22 +226,6 @@ 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<GeometryCache>()->renderUnitQuad(); - glBindTexture(GL_TEXTURE_2D, 0); - glDisable(GL_TEXTURE_2D); - glDisable(GL_BLEND); - } - glLoadIdentity(); - glMatrixMode(GL_PROJECTION); } glPopMatrix(); @@ -238,161 +235,259 @@ void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) { glEnable(GL_LIGHTING); glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); - _framebufferObject->release(); + _overlays.release(); } -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)); +// A quick and dirty solution for compositing the old overlay +// texture with the new one +template <typename F> +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(); } - - return _standardDrawPipeline; -} - -void ApplicationOverlay::bindCursorTexture(gpu::Batch& batch, 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<TextureCache>()-> - getImageTexture(iconPath); + if (secondPassTexture) { + glBindTexture(GL_TEXTURE_2D, secondPassTexture); + f(); } - batch.setUniformTexture(0, _cursors[iconId]); + glBindTexture(GL_TEXTURE_2D, 0); + glDisable(GL_TEXTURE_2D); } -#define CURSOR_PIXEL_SIZE 32.0f - // Draws the FBO texture for the screen -void ApplicationOverlay::displayOverlayTexture(RenderArgs* renderArgs) { +void ApplicationOverlay::displayOverlayTexture() { + if (_alpha == 0.0f) { + return; + } + 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<GeometryCache>()->renderQuad( + topLeft, bottomRight, + texCoordTopLeft, texCoordBottomRight, + glm::vec4(1.0f, 1.0f, 1.0f, _alpha)); + }); + + if (!_crosshairTexture) { + _crosshairTexture = DependencyManager::get<TextureCache>()-> + getImageTexture(PathUtils::resourcesPath() + "images/sixense-reticle.png"); + } + + //draw the mouse pointer + glm::vec2 canvasSize = qApp->getCanvasSize(); + glm::vec2 mouseSize = 32.0f / canvasSize; + auto mouseTopLeft = topLeft * mouseSize; + auto mouseBottomRight = bottomRight * mouseSize; + vec2 mousePosition = vec2(qApp->getMouseX(), qApp->getMouseY()); + mousePosition /= canvasSize; + mousePosition *= 2.0f; + mousePosition -= 1.0f; + mousePosition.y *= -1.0f; + + glEnable(GL_TEXTURE_2D); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBindTexture(GL_TEXTURE_2D, gpu::GLBackend::getTextureID(_crosshairTexture)); + glm::vec4 reticleColor = { RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2], 1.0f }; + DependencyManager::get<GeometryCache>()->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(); +} + +// Draws the FBO texture for Oculus rift. +void ApplicationOverlay::displayOverlayTextureHmd(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); + glDisable(GL_LIGHTING); + glEnable(GL_ALPHA_TEST); + glAlphaFunc(GL_GREATER, 0.01f); + + + //Update and draw the magnifiers + MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->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; + } + } + + 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); + }); + } + } + + 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); + } + + 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(); +} + +// Draws the FBO texture for 3DTV. +void ApplicationOverlay::displayOverlayTextureStereo(Camera& whichCamera, float aspectRatio, float fov) { if (_alpha == 0.0f) { return; } - renderArgs->_context->syncCache(); - - gpu::Batch batch; - Transform model; - //DependencyManager::get<DeferredLightingEffect>()->bindSimpleProgram(batch, true); - batch.setPipeline(getDrawPipeline()); - batch.setModelTransform(Transform()); - batch.setProjectionTransform(mat4()); - 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<GeometryCache>()->renderUnitQuad(batch, vec4(vec3(1), _alpha)); + MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->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<GeometryCache>()->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(); - - // 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 = CURSOR_PIXEL_SIZE / canvasSize; - model.setScale(vec3(mouseSize, 1.0f)); - batch.setModelTransform(model); - bindCursorTexture(batch); + 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<GeometryCache>()->renderUnitQuad(batch, vec4(1)); - renderArgs->_context->render(batch); + + DependencyManager::get<GeometryCache>()->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); } - -static gpu::BufferPointer _hemiVertices; -static gpu::BufferPointer _hemiIndices; -static int _hemiIndexCount{ 0 }; - -glm::vec2 getPolarCoordinates(const PalmData& palm) { - MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->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; -} - -// Draws the FBO texture for Oculus rift. -void ApplicationOverlay::displayOverlayTextureHmd(RenderArgs* renderArgs, Camera& whichCamera) { - if (_alpha == 0.0f) { - return; - } - - renderArgs->_context->syncCache(); - - 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()); - - MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->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); - - - bindCursorTexture(batch); - auto geometryCache = DependencyManager::get<GeometryCache>(); - vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize); - //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, reticleScale); - batch.setModelTransform(reticleXfm); - // Render reticle at location - geometryCache->renderUnitQuad(batch, glm::vec4(1), _reticleQuad); - } - } - - //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, reticleScale); - batch.setModelTransform(reticleXfm); - geometryCache->renderUnitQuad(batch, glm::vec4(1), _reticleQuad); - } - - renderArgs->_context->render(batch); -} - - void ApplicationOverlay::computeHmdPickRay(glm::vec2 cursorPos, glm::vec3& origin, glm::vec3& direction) const { cursorPos *= qApp->getCanvasSize(); const glm::vec2 projection = screenToSpherical(cursorPos); @@ -421,6 +516,22 @@ void ApplicationOverlay::computeHmdPickRay(glm::vec2 cursorPos, glm::vec3& origi direction = glm::normalize(intersectionWithUi - origin); } +glm::vec2 getPolarCoordinates(const PalmData& palm) { + MyAvatar* myAvatar = DependencyManager::get<AvatarManager>()->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; @@ -471,9 +582,13 @@ 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); + //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); //bindCursorTexture(); @@ -635,6 +750,43 @@ 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<AvatarManager>()->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) { @@ -915,109 +1067,119 @@ void ApplicationOverlay::renderDomainConnectionStatusBorder() { } } +ApplicationOverlay::TexturedHemisphere::TexturedHemisphere() : + _vertices(0), + _indices(0), + _framebufferObject(NULL), + _vbo(0, 0) { +} -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) { - return; +ApplicationOverlay::TexturedHemisphere::~TexturedHemisphere() { + cleanupVBO(); + if (_framebufferObject != NULL) { + delete _framebufferObject; } +} - textureFOV = fov; - textureAspectRatio = aspectRatio; - - auto geometryCache = DependencyManager::get<GeometryCache>(); - - _hemiVertices = gpu::BufferPointer(new gpu::Buffer()); - _hemiIndices = gpu::BufferPointer(new gpu::Buffer()); +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) { 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 - vec3 pos; - vec2 uv; + // Compute number of vertices needed + _vertices = slices * stacks; + // Compute vertices positions and texture UV coordinate - // Create and write to buffer + TextureVertex* vertexData = new TextureVertex[_vertices]; + TextureVertex* vertexPtr = &vertexData[0]; for (int i = 0; i < stacks; i++) { - uv.y = (float)i / (float)(stacks - 1); // First stack is 0.0f, last stack is 1.0f + 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 * (uv.y - 0.5f); + float pitch = -fov * (stacksRatio - 0.5f); + for (int j = 0; j < slices; j++) { - uv.x = (float)j / (float)(slices - 1); // First slice is 0.0f, last slice is 1.0f + 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 * (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*)&uv); - _hemiVertices->append(sizeof(vec4), (gpu::Byte*)&color); + float yaw = -fov * aspectRatio * (slicesRatio - 0.5f); + + vertexPtr->position = getPoint(yaw, pitch); + vertexPtr->uv.x = slicesRatio; + vertexPtr->uv.y = stacksRatio; + vertexPtr++; } } + // 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); - _hemiIndexCount = numberOfRectangles * TRIANGLE_PER_RECTANGLE * VERTEX_PER_TRANGLE; + _indices = numberOfRectangles * TRIANGLE_PER_RECTANGLE * VERTEX_PER_TRANGLE; // Compute indices order - std::vector<GLushort> indices; + GLushort* indexData = new GLushort[_indices]; + GLushort* indexPtr = indexData; 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; - // 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); + + *(indexPtr++) = topLeftIndex; + *(indexPtr++) = bottomLeftIndex; + *(indexPtr++) = topRightIndex; + + *(indexPtr++) = topRightIndex; + *(indexPtr++) = bottomLeftIndex; + *(indexPtr++) = bottomRightIndex; } } - _hemiIndices->append(sizeof(GLushort) * indices.size(), (gpu::Byte*)&indices[0]); + // 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; } - -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)); - 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); - 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); +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; + } } - -GLuint ApplicationOverlay::getOverlayTexture() { - return _framebufferObject->texture(); -} - -void ApplicationOverlay::buildFramebufferObject() { +void ApplicationOverlay::TexturedHemisphere::buildFramebufferObject() { auto canvasSize = qApp->getCanvasSize(); QSize fboSize = QSize(canvasSize.x, canvasSize.y); if (_framebufferObject != NULL && fboSize == _framebufferObject->size()) { - // Already built + // Already build return; } @@ -1026,7 +1188,7 @@ void ApplicationOverlay::buildFramebufferObject() { } _framebufferObject = new QOpenGLFramebufferObject(fboSize, QOpenGLFramebufferObject::Depth); - glBindTexture(GL_TEXTURE_2D, getOverlayTexture()); + glBindTexture(GL_TEXTURE_2D, getTexture()); 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); @@ -1036,6 +1198,38 @@ void ApplicationOverlay::buildFramebufferObject() { glBindTexture(GL_TEXTURE_2D, 0); } +//Renders a hemisphere with texture coordinates. +void ApplicationOverlay::TexturedHemisphere::render() { + if (_framebufferObject == NULL || _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); +} + +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 e8b5a77b1f..eb397fe3c6 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(RenderArgs* renderArgs); - void displayOverlayTextureStereo(RenderArgs* renderArgs, Camera& whichCamera, float aspectRatio, float fov); - void displayOverlayTextureHmd(RenderArgs* renderArgs, Camera& whichCamera); + void displayOverlayTexture(); + void displayOverlayTextureStereo(Camera& whichCamera, float aspectRatio, float fov); + void displayOverlayTextureHmd(Camera& whichCamera); QPoint getPalmClickLocation(const PalmData *palm) const; bool calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction, glm::vec3& result) const; @@ -59,7 +59,6 @@ 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); @@ -67,12 +66,38 @@ public: static glm::vec2 sphericalToScreen(const glm::vec2 & sphericalPos); private: - 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; + // Interleaved vertex data + struct TextureVertex { + glm::vec3 position; + glm::vec2 uv; + }; - void renderPointers(); + typedef QPair<GLuint, GLuint> VerticesIndices; + class TexturedHemisphere { + 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(); + + private: + void cleanupVBO(); + + GLuint _vertices; + GLuint _indices; + QOpenGLFramebufferObject* _framebufferObject; + VerticesIndices _vbo; + }; + + float _hmdUIAngularSize = DEFAULT_HMD_UI_ANGULAR_SIZE; + void renderReticle(glm::quat orientation, float alpha); + void renderPointers();; void renderMagnifier(glm::vec2 magPos, float sizeMult, bool showBorder); void renderControllerPointers(); @@ -84,12 +109,10 @@ private: void renderDomainConnectionStatusBorder(); void bindCursorTexture(gpu::Batch& batch, uint8_t cursorId = 0); - 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]; @@ -102,6 +125,8 @@ private: float _alpha = 1.0f; float _oculusUIRadius; float _trailingAudioLoudness; + + gpu::TexturePointer _crosshairTexture; QMap<uint16_t, gpu::TexturePointer> _cursors; @@ -124,10 +149,6 @@ private: glm::vec3 _previousMagnifierTopLeft; glm::vec3 _previousMagnifierTopRight; - gpu::PipelinePointer _standardDrawPipeline; - - gpu::PipelinePointer getDrawPipeline(); - }; #endif // hifi_ApplicationOverlay_h diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 3066fd4890..303d63bef8 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -1179,21 +1179,6 @@ 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 76e03f8669..b438eb2d3b 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -155,9 +155,6 @@ 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) diff --git a/libraries/render-utils/src/standardDrawTexture.slf b/libraries/render-utils/src/standardDrawTexture.slf deleted file mode 100644 index 4fbeb6eb7f..0000000000 --- a/libraries/render-utils/src/standardDrawTexture.slf +++ /dev/null @@ -1,24 +0,0 @@ -<@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 deleted file mode 100644 index fd2c28049f..0000000000 --- a/libraries/render-utils/src/standardTransformPNTC.slv +++ /dev/null @@ -1,33 +0,0 @@ -<@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 1feceec0c7b5b3c6d2225c26ddbb56a605bd99a4 Mon Sep 17 00:00:00 2001 From: ZappoMan <bradh@konamoxt.com> Date: Fri, 12 Jun 2015 12:57:37 -0700 Subject: [PATCH 84/88] Revert "Fix broken identity transforms on OSX" This reverts commit 926283956412d1e35fc1ba319f2c513803da2c77. --- libraries/gpu/src/gpu/GLBackendTransform.cpp | 25 +++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/libraries/gpu/src/gpu/GLBackendTransform.cpp b/libraries/gpu/src/gpu/GLBackendTransform.cpp index 4e524ec24a..3f760e4cc8 100755 --- a/libraries/gpu/src/gpu/GLBackendTransform.cpp +++ b/libraries/gpu/src/gpu/GLBackendTransform.cpp @@ -133,11 +133,11 @@ void GLBackend::updateTransform() { } if (_transform._invalidModel || _transform._invalidView) { - if (_transform._lastMode != GL_MODELVIEW) { - glMatrixMode(GL_MODELVIEW); - _transform._lastMode = GL_MODELVIEW; - } if (!_transform._model.isIdentity()) { + if (_transform._lastMode != GL_MODELVIEW) { + glMatrixMode(GL_MODELVIEW); + _transform._lastMode = GL_MODELVIEW; + } Transform::Mat4 modelView; if (!_transform._view.isIdentity()) { Transform mvx; @@ -147,12 +147,19 @@ void GLBackend::updateTransform() { _transform._model.getMatrix(modelView); } glLoadMatrixf(reinterpret_cast< const GLfloat* >(&modelView)); - } else if (!_transform._view.isIdentity()) { - Transform::Mat4 modelView; - _transform._view.getInverseMatrix(modelView); - glLoadMatrixf(reinterpret_cast< const GLfloat* >(&modelView)); } else { - glLoadIdentity(); + if (!_transform._view.isIdentity()) { + if (_transform._lastMode != GL_MODELVIEW) { + glMatrixMode(GL_MODELVIEW); + _transform._lastMode = GL_MODELVIEW; + } + Transform::Mat4 modelView; + _transform._view.getInverseMatrix(modelView); + glLoadMatrixf(reinterpret_cast< const GLfloat* >(&modelView)); + } else { + // TODO: eventually do something about the matrix when neither view nor model is specified? + // glLoadIdentity(); + } } (void) CHECK_GL_ERROR(); } From ad773747325e4d80996c6279333211445a6d5f13 Mon Sep 17 00:00:00 2001 From: Ryan Huffman <ryanhuffman@gmail.com> Date: Fri, 12 Jun 2015 13:03:33 -0700 Subject: [PATCH 85/88] Fix setting of _renderMode --- interface/src/Application.cpp | 8 +++----- libraries/render-utils/src/RenderDeferredTask.cpp | 3 --- libraries/render/src/render/DrawTask.cpp | 4 ---- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 56fe4188e9..0757e6790f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -958,12 +958,15 @@ void Application::paintGL() { _applicationOverlay.displayOverlayTexture(&renderArgs); glPopMatrix(); + renderArgs._renderMode = RenderArgs::MIRROR_RENDER_MODE; if (Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) { _rearMirrorTools->render(&renderArgs, true, _glWidget->mapFromGlobal(QCursor::pos())); } else if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) { renderRearViewMirror(&renderArgs, _mirrorViewRect); } + renderArgs._renderMode = RenderArgs::NORMAL_RENDER_MODE; + auto finalFbo = DependencyManager::get<GlowEffect>()->render(&renderArgs); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); @@ -3439,7 +3442,6 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se "Application::displaySide() ... entities..."); RenderArgs::DebugFlags renderDebugFlags = RenderArgs::RENDER_DEBUG_NONE; - RenderArgs::RenderMode renderMode = RenderArgs::DEFAULT_RENDER_MODE; if (Menu::getInstance()->isOptionChecked(MenuOption::PhysicsShowHulls)) { renderDebugFlags = (RenderArgs::DebugFlags) (renderDebugFlags | (int) RenderArgs::RENDER_DEBUG_HULLS); @@ -3448,10 +3450,6 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se renderDebugFlags = (RenderArgs::DebugFlags) (renderDebugFlags | (int) RenderArgs::RENDER_DEBUG_SIMULATION_OWNERSHIP); } - if (theCamera.getMode() == CAMERA_MODE_MIRROR) { - renderMode = RenderArgs::MIRROR_RENDER_MODE; - } - renderArgs->_renderMode = renderMode; renderArgs->_debugFlags = renderDebugFlags; _entities.render(renderArgs); } diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index 3fd7d05666..96a197335a 100755 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -139,7 +139,6 @@ template <> void render::jobRun(const DrawOpaqueDeferred& job, const SceneContex batch.setProjectionTransform(projMat); batch.setViewTransform(viewMat); - renderContext->args->_renderMode = RenderArgs::NORMAL_RENDER_MODE; { GLenum buffers[3]; int bufferCount = 0; @@ -207,8 +206,6 @@ template <> void render::jobRun(const DrawTransparentDeferred& job, const SceneC batch.setProjectionTransform(projMat); batch.setViewTransform(viewMat); - args->_renderMode = RenderArgs::NORMAL_RENDER_MODE; - const float TRANSPARENT_ALPHA_THRESHOLD = 0.0f; { diff --git a/libraries/render/src/render/DrawTask.cpp b/libraries/render/src/render/DrawTask.cpp index 53964ac1db..96be8d63b0 100755 --- a/libraries/render/src/render/DrawTask.cpp +++ b/libraries/render/src/render/DrawTask.cpp @@ -263,7 +263,6 @@ template <> void render::jobRun(const DrawOpaque& job, const SceneContextPointer renderContext->_numDrawnOpaqueItems = renderedItems.size(); - ItemIDsBounds sortedItems; sortedItems.reserve(culledItems.size()); if (renderContext->_sortOpaque) { @@ -283,7 +282,6 @@ template <> void render::jobRun(const DrawOpaque& job, const SceneContextPointer batch.setProjectionTransform(projMat); batch.setViewTransform(viewMat); - renderContext->args->_renderMode = RenderArgs::NORMAL_RENDER_MODE; { GLenum buffers[3]; int bufferCount = 0; @@ -350,8 +348,6 @@ template <> void render::jobRun(const DrawTransparent& job, const SceneContextPo batch.setProjectionTransform(projMat); batch.setViewTransform(viewMat); - args->_renderMode = RenderArgs::NORMAL_RENDER_MODE; - const float MOSTLY_OPAQUE_THRESHOLD = 0.75f; const float TRANSPARENT_ALPHA_THRESHOLD = 0.0f; From 66a65e367511259c699ca6ea382040fccde5c7d1 Mon Sep 17 00:00:00 2001 From: Ryan Huffman <ryanhuffman@gmail.com> Date: Fri, 12 Jun 2015 13:04:02 -0700 Subject: [PATCH 86/88] Update model to select clockwise backface culling program when in mirror mode --- libraries/render-utils/src/Model.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 8d234cdef5..636bb4bcbe 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -2047,6 +2047,9 @@ void Model::pickPrograms(gpu::Batch& batch, RenderMode mode, bool translucent, f Locations*& locations) { RenderKey key(mode, translucent, alphaThreshold, hasLightmap, hasTangents, hasSpecular, isSkinned, isWireframe); + if (mode == RenderArgs::MIRROR_RENDER_MODE) { + key = RenderKey(key.getRaw() | RenderKey::IS_MIRROR); + } auto pipeline = _renderPipelineLib.find(key.getRaw()); if (pipeline == _renderPipelineLib.end()) { qDebug() << "No good, couldn't find a pipeline from the key ?" << key.getRaw(); From 81d003bdb8e34ca5709bb7e326f7169de55c1e8a Mon Sep 17 00:00:00 2001 From: Ryan Huffman <ryanhuffman@gmail.com> Date: Fri, 12 Jun 2015 13:05:10 -0700 Subject: [PATCH 87/88] Add post-scale to view matrix when in mirror mode --- libraries/render-utils/src/RenderDeferredTask.cpp | 6 ++++++ libraries/render/src/render/DrawTask.cpp | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index 96a197335a..d9dda279e0 100755 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -136,6 +136,9 @@ template <> void render::jobRun(const DrawOpaqueDeferred& job, const SceneContex Transform viewMat; args->_viewFrustum->evalProjectionMatrix(projMat); args->_viewFrustum->evalViewTransform(viewMat); + if (args->_renderMode == RenderArgs::MIRROR_RENDER_MODE) { + viewMat.postScale(glm::vec3(-1.0f, 1.0f, 1.0f)); + } batch.setProjectionTransform(projMat); batch.setViewTransform(viewMat); @@ -203,6 +206,9 @@ template <> void render::jobRun(const DrawTransparentDeferred& job, const SceneC Transform viewMat; args->_viewFrustum->evalProjectionMatrix(projMat); args->_viewFrustum->evalViewTransform(viewMat); + if (args->_renderMode == RenderArgs::MIRROR_RENDER_MODE) { + viewMat.postScale(glm::vec3(-1.0f, 1.0f, 1.0f)); + } batch.setProjectionTransform(projMat); batch.setViewTransform(viewMat); diff --git a/libraries/render/src/render/DrawTask.cpp b/libraries/render/src/render/DrawTask.cpp index 96be8d63b0..bb4ba00cee 100755 --- a/libraries/render/src/render/DrawTask.cpp +++ b/libraries/render/src/render/DrawTask.cpp @@ -279,6 +279,9 @@ template <> void render::jobRun(const DrawOpaque& job, const SceneContextPointer Transform viewMat; args->_viewFrustum->evalProjectionMatrix(projMat); args->_viewFrustum->evalViewTransform(viewMat); + if (args->_renderMode == RenderArgs::MIRROR_RENDER_MODE) { + viewMat.postScale(glm::vec3(-1.0f, 1.0f, 1.0f)); + } batch.setProjectionTransform(projMat); batch.setViewTransform(viewMat); @@ -345,6 +348,9 @@ template <> void render::jobRun(const DrawTransparent& job, const SceneContextPo Transform viewMat; args->_viewFrustum->evalProjectionMatrix(projMat); args->_viewFrustum->evalViewTransform(viewMat); + if (args->_renderMode == RenderArgs::MIRROR_RENDER_MODE) { + viewMat.postScale(glm::vec3(-1.0f, 1.0f, 1.0f)); + } batch.setProjectionTransform(projMat); batch.setViewTransform(viewMat); @@ -430,6 +436,9 @@ template <> void render::jobRun(const DrawBackground& job, const SceneContextPoi Transform viewMat; args->_viewFrustum->evalProjectionMatrix(projMat); args->_viewFrustum->evalViewTransform(viewMat); + if (args->_renderMode == RenderArgs::MIRROR_RENDER_MODE) { + viewMat.postScale(glm::vec3(-1.0f, 1.0f, 1.0f)); + } batch.setProjectionTransform(projMat); batch.setViewTransform(viewMat); @@ -471,6 +480,9 @@ template <> void render::jobRun(const DrawPostLayered& job, const SceneContextPo Transform viewMat; args->_viewFrustum->evalProjectionMatrix(projMat); args->_viewFrustum->evalViewTransform(viewMat); + if (args->_renderMode == RenderArgs::MIRROR_RENDER_MODE) { + viewMat.postScale(glm::vec3(-1.0f, 1.0f, 1.0f)); + } batch.setProjectionTransform(projMat); batch.setViewTransform(viewMat); From 648f9bebdf8676ea31c5aa2d00ead97958c0eca3 Mon Sep 17 00:00:00 2001 From: ZappoMan <bradh@konamoxt.com> Date: Fri, 12 Jun 2015 15:58:26 -0700 Subject: [PATCH 88/88] add LOD stats to the default stats --- interface/src/ui/Stats.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 8d098b4dc8..8359480b03 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -464,7 +464,7 @@ void Stats::display( verticalOffset = STATS_PELS_INITIALOFFSET; horizontalOffset = _lastHorizontalOffset + _generalStatsWidth + _pingStatsWidth + _geoStatsWidth + 3; - lines = _expanded ? 10 : 2; + lines = _expanded ? 10 : 3; drawBackground(backgroundColor, horizontalOffset, 0, canvasSize.x - horizontalOffset, (lines + 1) * STATS_PELS_PER_LINE); @@ -612,12 +612,10 @@ void Stats::display( } // LOD Details - if (_expanded) { - octreeStats.str(""); - QString displayLODDetails = DependencyManager::get<LODManager>()->getLODFeedbackText(); - octreeStats << "LOD: You can see " << qPrintable(displayLODDetails.trimmed()); - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)octreeStats.str().c_str(), color); - } + octreeStats.str(""); + QString displayLODDetails = DependencyManager::get<LODManager>()->getLODFeedbackText(); + octreeStats << "LOD: You can see " << qPrintable(displayLODDetails.trimmed()); + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)octreeStats.str().c_str(), color); }