From 4460e23b680a610df28bd58602f993d6503d1cd5 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Wed, 5 Nov 2014 23:08:00 +0100 Subject: [PATCH 01/20] Suppress repeated Hash mismatch debug messages --- libraries/networking/src/LimitedNodeList.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 043f0621bb..0c18c82962 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -209,8 +209,15 @@ bool LimitedNodeList::packetVersionAndHashMatch(const QByteArray& packet) { if (hashFromPacketHeader(packet) == hashForPacketAndConnectionUUID(packet, sendingNode->getConnectionSecret())) { return true; } else { - qDebug() << "Packet hash mismatch on" << checkType << "- Sender" + static QMultiMap hashDebugSuppressMap; + + QUuid senderUUID = uuidFromPacketHeader(packet); + if (!hashDebugSuppressMap.contains(senderUUID, checkType)) { + qDebug() << "Packet hash mismatch on" << checkType << "- Sender" << uuidFromPacketHeader(packet); + + hashDebugSuppressMap.insert(senderUUID, checkType); + } } } else { static QString repeatedMessage From ff197a2f64bfc2da98d9f98523293cc89bf086f5 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 6 Nov 2014 12:46:37 +0100 Subject: [PATCH 02/20] Base implementation for own reverb --- interface/src/Audio.cpp | 140 +++++++++++++++++++++++++--------------- interface/src/Audio.h | 3 + 2 files changed, 91 insertions(+), 52 deletions(-) diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 7d039387bb..2b38782231 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -510,6 +510,33 @@ void Audio::initGverb() { gverb_set_taillevel(_gverb, DB_CO(_reverbOptions->getTailLevel())); } +void Audio::updateGverbOptions() { + bool reverbChanged = false; + if (_receivedAudioStream.hasReverb()) { + + if (_zoneReverbOptions.getReverbTime() != _receivedAudioStream.getRevebTime()) { + _zoneReverbOptions.setReverbTime(_receivedAudioStream.getRevebTime()); + reverbChanged = true; + } + if (_zoneReverbOptions.getWetLevel() != _receivedAudioStream.getWetLevel()) { + _zoneReverbOptions.setWetLevel(_receivedAudioStream.getWetLevel()); + reverbChanged = true; + } + + if (_reverbOptions != &_zoneReverbOptions) { + _reverbOptions = &_zoneReverbOptions; + reverbChanged = true; + } + } else if (_reverbOptions != &_scriptReverbOptions) { + _reverbOptions = &_scriptReverbOptions; + reverbChanged = true; + } + + if (reverbChanged) { + initGverb(); + } +} + void Audio::setReverbOptions(const AudioEffectOptions* options) { // Save the new options _scriptReverbOptions.setMaxRoomSize(options->getMaxRoomSize()); @@ -557,6 +584,62 @@ void Audio::addReverb(int16_t* samplesData, int numSamples, QAudioFormat& audioF } } +void Audio::handleLocalEchoAndReverb(QByteArray inputByteArray) { + bool hasEcho = Menu::getInstance()->isOptionChecked(MenuOption::EchoLocalAudio); + bool hasLocalReverb = (_reverb || _receivedAudioStream.hasReverb()) && + !Menu::getInstance()->isOptionChecked(MenuOption::EchoServerAudio); + if (_muted || !_audioOutput || (!hasEcho && !hasLocalReverb)) { + //qDebug() << "No Echo/Reverb:" << hasEcho << _reverb << _receivedAudioStream.hasReverb(); + return; + } + + // if this person wants local loopback add that to the locally injected audio + // if there is reverb apply it to local audio and substract the origin samples + + if (!_loopbackOutputDevice && _loopbackAudioOutput) { + // we didn't have the loopback output device going so set that up now + _loopbackOutputDevice = _loopbackAudioOutput->start(); + } + + QByteArray loopBackByteArray(inputByteArray); + if (_inputFormat != _outputFormat) { + float loopbackOutputToInputRatio = (_outputFormat.sampleRate() / (float) _inputFormat.sampleRate()) * + (_outputFormat.channelCount() / _inputFormat.channelCount()); + loopBackByteArray.resize(inputByteArray.size() * loopbackOutputToInputRatio); + loopBackByteArray.fill(0); + linearResampling((int16_t*)inputByteArray.data(), (int16_t*)loopBackByteArray.data(), + inputByteArray.size() / sizeof(int16_t), loopBackByteArray.size() / sizeof(int16_t), + _inputFormat, _outputFormat); + } + + if (hasLocalReverb) { + //qDebug() << "Has Reverb"; + QByteArray loopbackCopy; + if (!hasEcho) { + loopbackCopy = loopBackByteArray; + } + + int16_t* loopbackSamples = (int16_t*) loopBackByteArray.data(); + int numLoopbackSamples = loopBackByteArray.size() / sizeof(int16_t); + updateGverbOptions(); + addReverb(loopbackSamples, numLoopbackSamples, _outputFormat); + + if (!hasEcho) { + int16_t* loopbackCopySamples = (int16_t*) loopbackCopy.data(); + + for (int i = 0; i < numLoopbackSamples; ++i) { + loopbackSamples[i] = glm::clamp((int)loopbackSamples[i] - loopbackCopySamples[i], + -32768, 32767); + } + } + } + + if (_loopbackOutputDevice) { + //qDebug() << "Writing"; + _loopbackOutputDevice->write(loopBackByteArray); + } +} + void Audio::handleAudioInput() { static char audioDataPacket[MAX_PACKET_SIZE]; @@ -601,34 +684,8 @@ void Audio::handleAudioInput() { _inputFrameBuffer.copyFrames(1, inputFrameCount, inputFrameData, true /*copy out*/); } - - if (Menu::getInstance()->isOptionChecked(MenuOption::EchoLocalAudio) && !_muted && _audioOutput) { - // if this person wants local loopback add that to the locally injected audio - - if (!_loopbackOutputDevice && _loopbackAudioOutput) { - // we didn't have the loopback output device going so set that up now - _loopbackOutputDevice = _loopbackAudioOutput->start(); - } - - if (_inputFormat == _outputFormat) { - if (_loopbackOutputDevice) { - _loopbackOutputDevice->write(inputByteArray); - } - } else { - float loopbackOutputToInputRatio = (_outputFormat.sampleRate() / (float) _inputFormat.sampleRate()) - * (_outputFormat.channelCount() / _inputFormat.channelCount()); - - QByteArray loopBackByteArray(inputByteArray.size() * loopbackOutputToInputRatio, 0); - - linearResampling((int16_t*) inputByteArray.data(), (int16_t*) loopBackByteArray.data(), - inputByteArray.size() / sizeof(int16_t), - loopBackByteArray.size() / sizeof(int16_t), _inputFormat, _outputFormat); - - if (_loopbackOutputDevice) { - _loopbackOutputDevice->write(loopBackByteArray); - } - } - } + + handleLocalEchoAndReverb(inputByteArray); _inputRingBuffer.writeData(inputByteArray.data(), inputByteArray.size()); @@ -951,34 +1008,13 @@ void Audio::processReceivedSamples(const QByteArray& inputBuffer, QByteArray& ou _desiredOutputFormat, _outputFormat); if(_reverb || _receivedAudioStream.hasReverb()) { - bool reverbChanged = false; - if (_receivedAudioStream.hasReverb()) { - - if (_zoneReverbOptions.getReverbTime() != _receivedAudioStream.getRevebTime()) { - _zoneReverbOptions.setReverbTime(_receivedAudioStream.getRevebTime()); - reverbChanged = true; - } - if (_zoneReverbOptions.getWetLevel() != _receivedAudioStream.getWetLevel()) { - _zoneReverbOptions.setWetLevel(_receivedAudioStream.getWetLevel()); - reverbChanged = true; - } - - if (_reverbOptions != &_zoneReverbOptions) { - _reverbOptions = &_zoneReverbOptions; - reverbChanged = true; - } - } else if (_reverbOptions != &_scriptReverbOptions) { - _reverbOptions = &_scriptReverbOptions; - reverbChanged = true; - } - - if (reverbChanged) { - initGverb(); - } + updateGverbOptions(); addReverb((int16_t*)outputBuffer.data(), numDeviceOutputSamples, _outputFormat); } } + + void Audio::addReceivedAudioToStream(const QByteArray& audioByteArray) { if (_audioOutput) { // Audio output must exist and be correctly set up if we're going to process received audio diff --git a/interface/src/Audio.h b/interface/src/Audio.h index fcbfb12761..13c7930478 100644 --- a/interface/src/Audio.h +++ b/interface/src/Audio.h @@ -267,8 +267,11 @@ private: // Adds Reverb void initGverb(); + void updateGverbOptions(); void addReverb(int16_t* samples, int numSamples, QAudioFormat& format); + void handleLocalEchoAndReverb(QByteArray inputByteArray); + // Add sounds that we want the user to not hear themselves, by adding on top of mic input signal void addProceduralSounds(int16_t* monoInput, int numSamples); From efc86b9f75128dc26642689ba2d55b48c526de61 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 6 Nov 2014 17:42:27 +0100 Subject: [PATCH 03/20] Remove debug --- interface/src/Audio.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 2b38782231..7a1bfbb1c7 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -589,7 +589,6 @@ void Audio::handleLocalEchoAndReverb(QByteArray inputByteArray) { bool hasLocalReverb = (_reverb || _receivedAudioStream.hasReverb()) && !Menu::getInstance()->isOptionChecked(MenuOption::EchoServerAudio); if (_muted || !_audioOutput || (!hasEcho && !hasLocalReverb)) { - //qDebug() << "No Echo/Reverb:" << hasEcho << _reverb << _receivedAudioStream.hasReverb(); return; } @@ -613,7 +612,6 @@ void Audio::handleLocalEchoAndReverb(QByteArray inputByteArray) { } if (hasLocalReverb) { - //qDebug() << "Has Reverb"; QByteArray loopbackCopy; if (!hasEcho) { loopbackCopy = loopBackByteArray; @@ -626,7 +624,6 @@ void Audio::handleLocalEchoAndReverb(QByteArray inputByteArray) { if (!hasEcho) { int16_t* loopbackCopySamples = (int16_t*) loopbackCopy.data(); - for (int i = 0; i < numLoopbackSamples; ++i) { loopbackSamples[i] = glm::clamp((int)loopbackSamples[i] - loopbackCopySamples[i], -32768, 32767); @@ -635,7 +632,6 @@ void Audio::handleLocalEchoAndReverb(QByteArray inputByteArray) { } if (_loopbackOutputDevice) { - //qDebug() << "Writing"; _loopbackOutputDevice->write(loopBackByteArray); } } From 44cb35778afa05c3a1ac6d27c6182ff95753aaf8 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Thu, 6 Nov 2014 17:42:57 +0100 Subject: [PATCH 04/20] Move AudioEnv packet send to own function Audio environment packet moved to own function and out of the if/else So it is now sent all the time, now matter if there are other people around you --- assignment-client/src/audio/AudioMixer.cpp | 113 +++++++++++---------- assignment-client/src/audio/AudioMixer.h | 3 + 2 files changed, 63 insertions(+), 53 deletions(-) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 6ca93a7b11..e217061826 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -463,6 +463,63 @@ int AudioMixer::prepareMixForListeningNode(Node* node) { return streamsMixed; } +void AudioMixer::sendAudioEnvironmentPacket(SharedNodePointer node) { + static char clientEnvBuffer[MAX_PACKET_SIZE]; + + // Send stream properties + bool hasReverb = false; + float reverbTime, wetLevel; + // find reverb properties + for (int i = 0; i < _zoneReverbSettings.size(); ++i) { + AudioMixerClientData* data = static_cast(node->getLinkedData()); + glm::vec3 streamPosition = data->getAvatarAudioStream()->getPosition(); + if (_audioZones[_zoneReverbSettings[i].zone].contains(streamPosition)) { + hasReverb = true; + reverbTime = _zoneReverbSettings[i].reverbTime; + wetLevel = _zoneReverbSettings[i].wetLevel; + break; + } + } + AudioMixerClientData* nodeData = static_cast(node->getLinkedData()); + AvatarAudioStream* stream = nodeData->getAvatarAudioStream(); + bool dataChanged = (stream->hasReverb() != hasReverb) || + (stream->hasReverb() && (stream->getRevebTime() != reverbTime || + stream->getWetLevel() != wetLevel)); + if (dataChanged) { + // Update stream + if (hasReverb) { + stream->setReverb(reverbTime, wetLevel); + } else { + stream->clearReverb(); + } + } + + // Send at change or every so often + float CHANCE_OF_SEND = 0.01f; + bool sendData = dataChanged || (randFloat() < CHANCE_OF_SEND); + + if (sendData) { + int numBytesEnvPacketHeader = populatePacketHeader(clientEnvBuffer, PacketTypeAudioEnvironment); + char* envDataAt = clientEnvBuffer + numBytesEnvPacketHeader; + + unsigned char bitset = 0; + if (hasReverb) { + setAtBit(bitset, HAS_REVERB_BIT); + } + + memcpy(envDataAt, &bitset, sizeof(unsigned char)); + envDataAt += sizeof(unsigned char); + + if (hasReverb) { + memcpy(envDataAt, &reverbTime, sizeof(float)); + envDataAt += sizeof(float); + memcpy(envDataAt, &wetLevel, sizeof(float)); + envDataAt += sizeof(float); + } + NodeList::getInstance()->writeDatagram(clientEnvBuffer, envDataAt - clientEnvBuffer, node); + } +} + void AudioMixer::readPendingDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr) { NodeList* nodeList = NodeList::getInstance(); @@ -640,7 +697,6 @@ void AudioMixer::run() { timer.start(); char clientMixBuffer[MAX_PACKET_SIZE]; - char clientEnvBuffer[MAX_PACKET_SIZE]; int usecToSleep = BUFFER_SEND_INTERVAL_USECS; @@ -734,58 +790,6 @@ void AudioMixer::run() { // pack mixed audio samples memcpy(mixDataAt, _mixSamples, NETWORK_BUFFER_LENGTH_BYTES_STEREO); mixDataAt += NETWORK_BUFFER_LENGTH_BYTES_STEREO; - - // Send stream properties - bool hasReverb = false; - float reverbTime, wetLevel; - // find reverb properties - for (int i = 0; i < _zoneReverbSettings.size(); ++i) { - AudioMixerClientData* data = static_cast(node->getLinkedData()); - glm::vec3 streamPosition = data->getAvatarAudioStream()->getPosition(); - if (_audioZones[_zoneReverbSettings[i].zone].contains(streamPosition)) { - hasReverb = true; - reverbTime = _zoneReverbSettings[i].reverbTime; - wetLevel = _zoneReverbSettings[i].wetLevel; - break; - } - } - AvatarAudioStream* stream = nodeData->getAvatarAudioStream(); - bool dataChanged = (stream->hasReverb() != hasReverb) || - (stream->hasReverb() && (stream->getRevebTime() != reverbTime || - stream->getWetLevel() != wetLevel)); - if (dataChanged) { - // Update stream - if (hasReverb) { - stream->setReverb(reverbTime, wetLevel); - } else { - stream->clearReverb(); - } - } - - // Send at change or every so often - float CHANCE_OF_SEND = 0.01f; - bool sendData = dataChanged || (randFloat() < CHANCE_OF_SEND); - - if (sendData) { - int numBytesEnvPacketHeader = populatePacketHeader(clientEnvBuffer, PacketTypeAudioEnvironment); - char* envDataAt = clientEnvBuffer + numBytesEnvPacketHeader; - - unsigned char bitset = 0; - if (hasReverb) { - setAtBit(bitset, HAS_REVERB_BIT); - } - - memcpy(envDataAt, &bitset, sizeof(unsigned char)); - envDataAt += sizeof(unsigned char); - - if (hasReverb) { - memcpy(envDataAt, &reverbTime, sizeof(float)); - envDataAt += sizeof(float); - memcpy(envDataAt, &wetLevel, sizeof(float)); - envDataAt += sizeof(float); - } - nodeList->writeDatagram(clientEnvBuffer, envDataAt - clientEnvBuffer, node); - } } else { // pack header int numBytesPacketHeader = populatePacketHeader(clientMixBuffer, PacketTypeSilentAudioFrame); @@ -801,6 +805,9 @@ void AudioMixer::run() { memcpy(mixDataAt, &numSilentSamples, sizeof(quint16)); mixDataAt += sizeof(quint16); } + + // Send audio environment + sendAudioEnvironmentPacket(node); // send mixed audio packet nodeList->writeDatagram(clientMixBuffer, mixDataAt - clientMixBuffer, node); diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h index ff976dec61..b4a9e2aa1c 100644 --- a/assignment-client/src/audio/AudioMixer.h +++ b/assignment-client/src/audio/AudioMixer.h @@ -49,6 +49,9 @@ private: /// prepares and sends a mix to one Node int prepareMixForListeningNode(Node* node); + + /// Send Audio Environment packet for a single node + void sendAudioEnvironmentPacket(SharedNodePointer node); // used on a per stream basis to run the filter on before mixing, large enough to handle the historical // data from a phase delay as well as an entire network buffer From 3e109ee770c19fa4151f55aa6093adcd804360b0 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 6 Nov 2014 14:57:04 -0800 Subject: [PATCH 05/20] Add WebWindow widget for scripts --- interface/src/Application.cpp | 3 ++ interface/src/scripting/WebWindow.cpp | 68 +++++++++++++++++++++++++++ interface/src/scripting/WebWindow.h | 53 +++++++++++++++++++++ 3 files changed, 124 insertions(+) create mode 100644 interface/src/scripting/WebWindow.cpp create mode 100644 interface/src/scripting/WebWindow.h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b773231b0f..98be839e25 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -91,6 +91,7 @@ #include "scripting/MenuScriptingInterface.h" #include "scripting/SettingsScriptingInterface.h" #include "scripting/WindowScriptingInterface.h" +#include "scripting/WebWindow.h" #include "ui/DataWebDialog.h" #include "ui/InfoView.h" @@ -3871,6 +3872,8 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri // register `location` on the global object. scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, LocationScriptingInterface::locationSetter); + + scriptEngine->registerFunction("WebWindow", WebWindow::constructor, 1); scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance()); diff --git a/interface/src/scripting/WebWindow.cpp b/interface/src/scripting/WebWindow.cpp new file mode 100644 index 0000000000..dfd3104f4b --- /dev/null +++ b/interface/src/scripting/WebWindow.cpp @@ -0,0 +1,68 @@ +// +// WebWindow.cpp +// interface/src/scripting +// +// Created by Ryan Huffman on 11/06/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include +#include + +#include "WindowScriptingInterface.h" +#include "WebWindow.h" + +ScriptEventBridge::ScriptEventBridge(QObject* parent) : QObject(parent) { +} + +void ScriptEventBridge::emitWebEvent(const QString& data) { + emit webEventReceived(data); +} + +void ScriptEventBridge::emitScriptEvent(const QString& data) { + emit scriptEventReceived(data); +} + +WebWindow::WebWindow(const QString& url, int width, int height) + : QObject(NULL), + _window(new QWidget(NULL, Qt::Tool)), + _eventBridge(new ScriptEventBridge(this)) { + + QWebView* webView = new QWebView(_window); + webView->page()->mainFrame()->addToJavaScriptWindowObject("EventBridge", _eventBridge); + webView->setUrl(url); + QVBoxLayout* layout = new QVBoxLayout(_window); + _window->setLayout(layout); + layout->addWidget(webView); + layout->setSpacing(0); + layout->setContentsMargins(0, 0, 0, 0); + _window->setGeometry(0, 0, width, height); + + connect(this, &WebWindow::destroyed, _window, &QWidget::deleteLater); +} + +WebWindow::~WebWindow() { +} + +void WebWindow::setVisible(bool visible) { + QMetaObject::invokeMethod(_window, "setVisible", Qt::BlockingQueuedConnection, Q_ARG(bool, visible)); +} + +QScriptValue WebWindow::constructor(QScriptContext* context, QScriptEngine* engine) { + WebWindow* retVal; + QString file = context->argument(0).toString(); + QMetaObject::invokeMethod(WindowScriptingInterface::getInstance(), "doCreateWebWindow", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(WebWindow*, retVal), + Q_ARG(const QString&, file), + Q_ARG(int, context->argument(1).toInteger()), + Q_ARG(int, context->argument(2).toInteger())); + + connect(engine, &QScriptEngine::destroyed, retVal, &WebWindow::deleteLater); + + return engine->newQObject(retVal);//, QScriptEngine::ScriptOwnership); +} diff --git a/interface/src/scripting/WebWindow.h b/interface/src/scripting/WebWindow.h new file mode 100644 index 0000000000..698e43fd6c --- /dev/null +++ b/interface/src/scripting/WebWindow.h @@ -0,0 +1,53 @@ +// +// WebWindow.h +// interface/src/scripting +// +// Created by Ryan Huffman on 11/06/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_WebWindow_h +#define hifi_WebWindow_h + +#include +#include + +// #include "ScriptWebView.h" + +class ScriptEventBridge : public QObject { + Q_OBJECT +public: + ScriptEventBridge(QObject* parent = NULL); + +public slots: + void emitWebEvent(const QString& data); + void emitScriptEvent(const QString& data); + +signals: + void webEventReceived(const QString& data); + void scriptEventReceived(const QString& data); + +}; + +class WebWindow : public QObject { + Q_OBJECT + Q_PROPERTY(QObject* eventBridge READ getEventBridge) +public: + WebWindow(const QString& url, int width, int height); + ~WebWindow(); + + static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); + +public slots: + void setVisible(bool visible); + ScriptEventBridge* getEventBridge() const { return _eventBridge; } + +private: + QWidget* _window; + ScriptEventBridge* _eventBridge; +}; + +#endif From feaf5678bbaeb499ed4ae21bf9889c4df2fbd1f4 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 6 Nov 2014 15:00:39 -0800 Subject: [PATCH 06/20] Add grid tools to entity edit tools --- examples/libraries/entitySelectionTool.js | 73 +++-- examples/libraries/gridTool.js | 266 +++++++++++++++++++ examples/newEditEntities.js | 27 +- gridControls.html | 206 ++++++++++++++ libraries/script-engine/src/ScriptEngine.cpp | 5 + libraries/script-engine/src/ScriptEngine.h | 1 + 6 files changed, 543 insertions(+), 35 deletions(-) create mode 100644 examples/libraries/gridTool.js create mode 100644 gridControls.html diff --git a/examples/libraries/entitySelectionTool.js b/examples/libraries/entitySelectionTool.js index 29bf1bdd79..980689d625 100644 --- a/examples/libraries/entitySelectionTool.js +++ b/examples/libraries/entitySelectionTool.js @@ -394,8 +394,7 @@ SelectionDisplay = (function () { var baseOverlayAngles = { x: 0, y: 0, z: 0 }; var baseOverlayRotation = Quat.fromVec3Degrees(baseOverlayAngles); var baseOfEntityProjectionOverlay = Overlays.addOverlay("rectangle3d", { - position: { x:0, y: 0, z: 0}, - size: 1, + position: { x: 1, y: 0, z: 0}, color: { red: 51, green: 152, blue: 203 }, alpha: 0.5, solid: true, @@ -570,6 +569,7 @@ SelectionDisplay = (function () { xRailOverlay, yRailOverlay, zRailOverlay, + baseOfEntityProjectionOverlay, ].concat(stretchHandles); overlayNames[highlightBox] = "highlightBox"; @@ -878,20 +878,24 @@ SelectionDisplay = (function () { translateHandlesVisible = false; } - var rotation = SelectionManager.worldRotation; - var dimensions = SelectionManager.worldDimensions; - var position = SelectionManager.worldPosition; + var rotation = selectionManager.worldRotation; + var dimensions = selectionManager.worldDimensions; + var position = selectionManager.worldPosition; Overlays.editOverlay(baseOfEntityProjectionOverlay, { - visible: true, - solid:true, - lineWidth: 2.0, - position: { x: position.x, - y: 0, - z: position.z }, - - dimensions: { x: dimensions.x, y: 0, z: dimensions.z }, + visible: mode != "ROTATE_YAW" && mode != "ROTATE_PITCH" && mode != "ROTATE_ROLL", + solid: true, + // lineWidth: 2.0, + position: { + x: position.x, + y: grid.getOrigin().y, + z: position.z + }, + dimensions: { + x: dimensions.x, + y: dimensions.z + }, rotation: rotation, }); @@ -1098,6 +1102,7 @@ SelectionDisplay = (function () { var initialXZPick = null; var isConstrained = false; + var constrainMajorOnly = false; var startPosition = null; var duplicatedEntityIDs = null; var translateXZTool = { @@ -1143,34 +1148,46 @@ SelectionDisplay = (function () { // If shifted, constrain to one axis if (event.isShifted) { - if (Math.abs(vector.x) > Math.abs(vector.z)) { - vector.z = 0; - } else { - vector.x = 0; - } + // if (Math.abs(vector.x) > Math.abs(vector.z)) { + // vector.z = 0; + // } else { + // vector.x = 0; + // } if (!isConstrained) { - Overlays.editOverlay(xRailOverlay, { visible: true }); - var xStart = Vec3.sum(startPosition, { x: -10000, y: 0, z: 0 }); - var xEnd = Vec3.sum(startPosition, { x: 10000, y: 0, z: 0 }); - var zStart = Vec3.sum(startPosition, { x: 0, y: 0, z: -10000 }); - var zEnd = Vec3.sum(startPosition, { x: 0, y: 0, z: 10000 }); - Overlays.editOverlay(xRailOverlay, { start: xStart, end: xEnd, visible: true }); - Overlays.editOverlay(zRailOverlay, { start: zStart, end: zEnd, visible: true }); + // Overlays.editOverlay(xRailOverlay, { visible: true }); + // var xStart = Vec3.sum(startPosition, { x: -10000, y: 0, z: 0 }); + // var xEnd = Vec3.sum(startPosition, { x: 10000, y: 0, z: 0 }); + // var zStart = Vec3.sum(startPosition, { x: 0, y: 0, z: -10000 }); + // var zEnd = Vec3.sum(startPosition, { x: 0, y: 0, z: 10000 }); + // Overlays.editOverlay(xRailOverlay, { start: xStart, end: xEnd, visible: true }); + // Overlays.editOverlay(zRailOverlay, { start: zStart, end: zEnd, visible: true }); isConstrained = true; } + // constrainMajorOnly = event.isControl; + // vector = Vec3.subtract( + // grid.snapToGrid(Vec3.sum(startPosition, vector), constrainMajorOnly), + // startPosition); } else { if (isConstrained) { - Overlays.editOverlay(xRailOverlay, { visible: false }); - Overlays.editOverlay(zRailOverlay, { visible: false }); + // Overlays.editOverlay(xRailOverlay, { visible: false }); + // Overlays.editOverlay(zRailOverlay, { visible: false }); + isConstrained = false; } } + constrainMajorOnly = event.isControl; + var cornerPosition = Vec3.sum(startPosition, Vec3.multiply(-0.5, selectionManager.worldDimensions)); + vector = Vec3.subtract( + grid.snapToGrid(Vec3.sum(cornerPosition, vector), constrainMajorOnly), + cornerPosition); + var wantDebug = false; for (var i = 0; i < SelectionManager.selections.length; i++) { var properties = SelectionManager.savedProperties[SelectionManager.selections[i].id]; + var newPosition = Vec3.sum(properties.position, { x: vector.x, y: 0, z: vector.z }); Entities.editEntity(SelectionManager.selections[i], { - position: Vec3.sum(properties.position, vector), + position: newPosition, }); if (wantDebug) { diff --git a/examples/libraries/gridTool.js b/examples/libraries/gridTool.js new file mode 100644 index 0000000000..76c0581b2c --- /dev/null +++ b/examples/libraries/gridTool.js @@ -0,0 +1,266 @@ +Grid = function(opts) { + var that = {}; + + var color = { red: 100, green: 152, blue: 203 }; + var gridColor = { red: 100, green: 152, blue: 203 }; + var gridAlpha = 0.9; + var origin = { x: 0, y: 0, z: 0 }; + var majorGridEvery = 5; + var minorGridSpacing = 0.2; + var halfSize = 40; + var yOffset = 0.001; + + var worldSize = 16384; + + var minorGridWidth = 0.5; + var majorGridWidth = 1.5; + + var gridOverlays = []; + + var snapToGrid = true; + + var gridPlane = Overlays.addOverlay("rectangle3d", { + position: origin, + color: color, + size: halfSize * 2 * minorGridSpacing * 1.05, + alpha: 0.2, + solid: true, + visible: false, + ignoreRayIntersection: true, + }); + + that.getMinorIncrement = function() { return minorGridSpacing; }; + that.getMajorIncrement = function() { return minorGridSpacing * majorGridEvery; }; + + that.visible = false; + + that.getOrigin = function() { + return origin; + } + + that.getSnapToGrid = function() { return snapToGrid; }; + + that.setVisible = function(visible, noUpdate) { + that.visible = visible; + updateGrid(); + // for (var i = 0; i < gridOverlays.length; i++) { + // Overlays.editOverlay(gridOverlays[i], { visible: visible }); + // } + // Overlays.editOverlay(gridPlane, { visible: visible }); + + if (!noUpdate) { + that.emitUpdate(); + } + } + + that.snapToGrid = function(position, majorOnly) { + if (!snapToGrid) { + return position; + } + + var spacing = majorOnly ? (minorGridSpacing * majorGridEvery) : minorGridSpacing; + + position = Vec3.subtract(position, origin); + + position.x = Math.round(position.x / spacing) * spacing; + position.y = Math.round(position.y / spacing) * spacing; + position.z = Math.round(position.z / spacing) * spacing; + + return Vec3.sum(position, origin); + } + + that.setPosition = function(newPosition, noUpdate) { + origin = Vec3.subtract(newPosition, { x: 0, y: yOffset, z: 0 }); + updateGrid(); + + print("updated grid"); + if (!noUpdate) { + that.emitUpdate(); + } + }; + + that.emitUpdate = function() { + if (that.onUpdate) { + that.onUpdate({ + origin: origin, + minorGridSpacing: minorGridSpacing, + majorGridEvery: majorGridEvery, + gridSize: halfSize, + visible: that.visible, + snapToGrid: snapToGrid, + gridColor: gridColor, + }); + } + }; + + that.update = function(data) { + print("Got update"); + if (data.snapToGrid !== undefined) { + snapToGrid = data.snapToGrid; + } + + if (data.origin) { + var pos = data.origin; + pos.x = pos.x === undefined ? origin.x : pos.x; + pos.y = pos.y === undefined ? origin.y : pos.y; + pos.z = pos.z === undefined ? origin.z : pos.z; + that.setPosition(pos, true); + } + + if (data.minorGridSpacing) { + minorGridSpacing = data.minorGridSpacing; + } + + if (data.majorGridEvery) { + majorGridEvery = data.majorGridEvery; + } + + if (data.gridColor) { + gridColor = data.gridColor; + } + + if (data.gridSize) { + halfSize = data.gridSize; + } + + if (data.visible !== undefined) { + that.setVisible(data.visible, true); + } + + updateGrid(); + } + + function updateGrid() { + // Delete overlays + var gridLinesRequired = (halfSize * 2 + 1) * 2; + if (gridLinesRequired > gridOverlays.length) { + for (var i = gridOverlays.length; i < gridLinesRequired; i++) { + gridOverlays.push(Overlays.addOverlay("line3d", {})); + } + } else if (gridLinesRequired < gridOverlays.length) { + var numberToRemove = gridOverlays.length - gridLinesRequired; + var removed = gridOverlays.splice(gridOverlays.length - numberToRemove, numberToRemove); + for (var i = 0; i < removed.length; i++) { + Overlays.deleteOverlay(removed[i]); + } + } + + Overlays.editOverlay(gridPlane, { + position: origin, + size: halfSize * 2 * minorGridSpacing * 1.05, + }); + + var startX = { + x: origin.x - (halfSize * minorGridSpacing), + y: origin.y, + z: origin.z, + }; + var endX = { + x: origin.x + (halfSize * minorGridSpacing), + y: origin.y, + z: origin.z, + }; + var startZ = { + x: origin.x, + y: origin.y, + z: origin.z - (halfSize * minorGridSpacing) + }; + var endZ = { + x: origin.x, + y: origin.y, + z: origin.z + (halfSize * minorGridSpacing) + }; + + var overlayIdx = 0; + for (var i = -halfSize; i <= halfSize; i++) { + // Offset for X-axis aligned grid line + var offsetX = { x: 0, y: 0, z: i * minorGridSpacing }; + + // Offset for Z-axis aligned grid line + var offsetZ = { x: i * minorGridSpacing, y: 0, z: 0 }; + + var position = Vec3.sum(origin, offsetX); + var size = i % majorGridEvery == 0 ? majorGridWidth : minorGridWidth; + + var gridLineX = gridOverlays[overlayIdx++]; + var gridLineZ = gridOverlays[overlayIdx++]; + + Overlays.editOverlay(gridLineX, { + start: Vec3.sum(startX, offsetX), + end: Vec3.sum(endX, offsetX), + lineWidth: size, + color: gridColor, + alpha: gridAlpha, + solid: true, + visible: that.visible, + ignoreRayIntersection: true, + }); + Overlays.editOverlay(gridLineZ, { + start: Vec3.sum(startZ, offsetZ), + end: Vec3.sum(endZ, offsetZ), + lineWidth: size, + color: gridColor, + alpha: gridAlpha, + solid: true, + visible: that.visible, + ignoreRayIntersection: true, + }); + } + } + + function cleanup() { + Overlays.deleteOverlay(gridPlane); + for (var i = 0; i < gridOverlays.length; i++) { + Overlays.deleteOverlay(gridOverlays[i]); + } + } + + that.addListener = function(callback) { + that.onUpdate = callback; + } + + Script.scriptEnding.connect(cleanup); + updateGrid(); + + that.onUpdate = null; + + return that; +}; + +GridTool = function(opts) { + var that = {}; + + var horizontalGrid = opts.horizontalGrid; + var verticalGrid = opts.verticalGrid; + var listeners = []; + + // var webView = Window.createWebView('http://localhost:8000/gridControls.html', 200, 280); + var webView = new WebWindow('http://localhost:8000/gridControls.html', 200, 280); + + horizontalGrid.addListener(function(data) { + webView.eventBridge.emitScriptEvent(JSON.stringify(data)); + }); + + webView.eventBridge.webEventReceived.connect(function(data) { + print('got event: ' + data); + data = JSON.parse(data); + if (data.type == "init") { + horizontalGrid.emitUpdate(); + } else if (data.type == "update") { + horizontalGrid.update(data); + for (var i = 0; i < listeners.length; i++) { + listeners[i](data); + } + } + }); + + that.addListener = function(callback) { + listeners.push(callback); + } + + that.setVisible = function(visible) { + webView.setVisible(visible); + } + + return that; +}; diff --git a/examples/newEditEntities.js b/examples/newEditEntities.js index ea1f69e15f..b8e7a6174e 100644 --- a/examples/newEditEntities.js +++ b/examples/newEditEntities.js @@ -35,6 +35,12 @@ var entityPropertyDialogBox = EntityPropertyDialogBox; Script.include("libraries/entityCameraTool.js"); var cameraManager = new CameraManager(); +Script.include("libraries/gridTool.js"); +var grid = Grid(); +gridTool = GridTool({ horizontalGrid: grid }); +gridTool.addListener(function(data) { +}); + selectionManager.setEventListener(selectionDisplay.updateHandles); var windowDimensions = Controller.getViewportDimensions(); @@ -258,6 +264,7 @@ var toolBar = (function () { if (activeButton === toolBar.clicked(clickedOverlay)) { isActive = !isActive; + gridTool.setVisible(isActive); if (!isActive) { selectionManager.clearSelections(); cameraManager.disable(); @@ -747,25 +754,32 @@ Controller.keyReleaseEvent.connect(function (event) { if (isActive) { cameraManager.enable(); } + } else if (event.text == 'g') { + if (isActive && selectionManager.hasSelection()) { + var newPosition = selectionManager.worldPosition; + newPosition = Vec3.subtract(newPosition, { x: 0, y: selectionManager.worldDimensions.y * 0.5, z: 0 }); + grid.setPosition(newPosition); + } } else if (isActive) { var delta = null; + var increment = event.isShifted ? grid.getMajorIncrement() : grid.getMinorIncrement(); if (event.text == 'UP') { if (event.isControl || event.isAlt) { - delta = { x: 0, y: 1, z: 0 }; + delta = { x: 0, y: increment, z: 0 }; } else { - delta = { x: 0, y: 0, z: -1 }; + delta = { x: 0, y: 0, z: -increment }; } } else if (event.text == 'DOWN') { if (event.isControl || event.isAlt) { - delta = { x: 0, y: -1, z: 0 }; + delta = { x: 0, y: -increment, z: 0 }; } else { - delta = { x: 0, y: 0, z: 1 }; + delta = { x: 0, y: 0, z: increment }; } } else if (event.text == 'LEFT') { - delta = { x: -1, y: 0, z: 0 }; + delta = { x: -increment, y: 0, z: 0 }; } else if (event.text == 'RIGHT') { - delta = { x: 1, y: 0, z: 0 }; + delta = { x: increment, y: 0, z: 0 }; } if (delta != null) { @@ -820,7 +834,6 @@ function applyEntityProperties(data) { var properties = data.createEntities[i].properties; var newEntityID = Entities.addEntity(properties); DELETED_ENTITY_MAP[entityID.id] = newEntityID; - print(newEntityID.isKnownID); if (data.selectCreated) { selectedEntityIDs.push(newEntityID); } diff --git a/gridControls.html b/gridControls.html new file mode 100644 index 0000000000..84d437a60d --- /dev/null +++ b/gridControls.html @@ -0,0 +1,206 @@ + + + + + + +
+ + +
+
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+
+
+
+ + diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index fb98124fc9..8f176de04f 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -322,6 +322,11 @@ QScriptValue ScriptEngine::registerGlobalObject(const QString& name, QObject* ob return QScriptValue::NullValue; } +void ScriptEngine::registerFunction(const QString& name, QScriptEngine::FunctionSignature fun, int numArguments) { + QScriptValue scriptFun = newFunction(fun, numArguments); + globalObject().setProperty(name, scriptFun); +} + void ScriptEngine::registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter, QScriptEngine::FunctionSignature setter, QScriptValue object) { QScriptValue setterFunction = newFunction(setter, 1); diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index d556475859..22617512a9 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -65,6 +65,7 @@ public: QScriptValue registerGlobalObject(const QString& name, QObject* object); /// registers a global object by name void registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter, QScriptEngine::FunctionSignature setter, QScriptValue object = QScriptValue::NullValue); + void registerFunction(const QString& name, QScriptEngine::FunctionSignature fun, int numArguments = -1); Q_INVOKABLE void setIsAvatar(bool isAvatar); bool isAvatar() const { return _isAvatar; } From 1e6cadc7c14da183069278632c2e31d8bec002d2 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Fri, 7 Nov 2014 00:06:57 +0100 Subject: [PATCH 07/20] Extra lines --- interface/src/Audio.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 7fc563411a..6129ee4ee0 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -1026,8 +1026,6 @@ void Audio::processReceivedSamples(const QByteArray& inputBuffer, QByteArray& ou } } - - void Audio::addReceivedAudioToStream(const QByteArray& audioByteArray) { if (_audioOutput) { // Audio output must exist and be correctly set up if we're going to process received audio From dcfeef471250e2dfb6d320a1a6646584c59d765b Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Fri, 7 Nov 2014 00:24:37 +0100 Subject: [PATCH 08/20] Reference and comments --- interface/src/Audio.cpp | 3 ++- interface/src/Audio.h | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 6129ee4ee0..79ebcdd043 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -587,8 +587,9 @@ void Audio::addReverb(int16_t* samplesData, int numSamples, QAudioFormat& audioF } } -void Audio::handleLocalEchoAndReverb(QByteArray inputByteArray) { +void Audio::handleLocalEchoAndReverb(QByteArray& inputByteArray) { bool hasEcho = Menu::getInstance()->isOptionChecked(MenuOption::EchoLocalAudio); + // If there is server echo, reverb will be applied to the recieved audio stream so no need to have it here. bool hasLocalReverb = (_reverb || _receivedAudioStream.hasReverb()) && !Menu::getInstance()->isOptionChecked(MenuOption::EchoServerAudio); if (_muted || !_audioOutput || (!hasEcho && !hasLocalReverb)) { diff --git a/interface/src/Audio.h b/interface/src/Audio.h index e58afa306f..be51511dcc 100644 --- a/interface/src/Audio.h +++ b/interface/src/Audio.h @@ -273,7 +273,7 @@ private: void updateGverbOptions(); void addReverb(int16_t* samples, int numSamples, QAudioFormat& format); - void handleLocalEchoAndReverb(QByteArray inputByteArray); + void handleLocalEchoAndReverb(QByteArray& inputByteArray); // Add sounds that we want the user to not hear themselves, by adding on top of mic input signal void addProceduralSounds(int16_t* monoInput, int numSamples); From 011e5319710fe8cc50366700ad039ff77e2e110a Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Fri, 7 Nov 2014 14:41:59 +0100 Subject: [PATCH 09/20] Magic numbers and casts --- interface/src/Audio.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 79ebcdd043..2d7b117f51 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -574,11 +574,11 @@ void Audio::addReverb(int16_t* samplesData, int numSamples, QAudioFormat& audioF for (unsigned int j = sample; j < sample + audioFormat.channelCount(); j++) { if (j == sample) { // left channel - int lResult = glm::clamp((int)(samplesData[j] * dryFraction + lValue * wetFraction), -32768, 32767); + int lResult = glm::clamp((int)(samplesData[j] * dryFraction + lValue * wetFraction), MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE); samplesData[j] = (int16_t)lResult; } else if (j == (sample + 1)) { // right channel - int rResult = glm::clamp((int)(samplesData[j] * dryFraction + rValue * wetFraction), -32768, 32767); + int rResult = glm::clamp((int)(samplesData[j] * dryFraction + rValue * wetFraction), MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE); samplesData[j] = (int16_t)rResult; } else { // ignore channels above 2 @@ -610,7 +610,8 @@ void Audio::handleLocalEchoAndReverb(QByteArray& inputByteArray) { (_outputFormat.channelCount() / _inputFormat.channelCount()); loopBackByteArray.resize(inputByteArray.size() * loopbackOutputToInputRatio); loopBackByteArray.fill(0); - linearResampling((int16_t*)inputByteArray.data(), (int16_t*)loopBackByteArray.data(), + linearResampling(reinterpret_cast(inputByteArray.data()), + reinterpret_cast(loopBackByteArray.data()), inputByteArray.size() / sizeof(int16_t), loopBackByteArray.size() / sizeof(int16_t), _inputFormat, _outputFormat); } @@ -621,16 +622,16 @@ void Audio::handleLocalEchoAndReverb(QByteArray& inputByteArray) { loopbackCopy = loopBackByteArray; } - int16_t* loopbackSamples = (int16_t*) loopBackByteArray.data(); + int16_t* loopbackSamples = reinterpret_cast(loopBackByteArray.data()); int numLoopbackSamples = loopBackByteArray.size() / sizeof(int16_t); updateGverbOptions(); addReverb(loopbackSamples, numLoopbackSamples, _outputFormat); if (!hasEcho) { - int16_t* loopbackCopySamples = (int16_t*) loopbackCopy.data(); + int16_t* loopbackCopySamples = reinterpret_cast(loopbackCopy.data()); for (int i = 0; i < numLoopbackSamples; ++i) { loopbackSamples[i] = glm::clamp((int)loopbackSamples[i] - loopbackCopySamples[i], - -32768, 32767); + MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE); } } } From 2ebca0a659c6ceb2ed6a3046cec8defb14d2b0eb Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 11 Nov 2014 09:50:25 -0800 Subject: [PATCH 10/20] Add Grid3DOverlay --- interface/src/ui/overlays/Grid3DOverlay.cpp | 118 ++++++++++++++++++++ interface/src/ui/overlays/Grid3DOverlay.h | 44 ++++++++ 2 files changed, 162 insertions(+) create mode 100644 interface/src/ui/overlays/Grid3DOverlay.cpp create mode 100644 interface/src/ui/overlays/Grid3DOverlay.h diff --git a/interface/src/ui/overlays/Grid3DOverlay.cpp b/interface/src/ui/overlays/Grid3DOverlay.cpp new file mode 100644 index 0000000000..c628199fe3 --- /dev/null +++ b/interface/src/ui/overlays/Grid3DOverlay.cpp @@ -0,0 +1,118 @@ +// +// Grid3DOverlay.cpp +// interface/src/ui/overlays +// +// Created by Ryan Huffman on 11/06/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "Grid3DOverlay.h" + +#include "Application.h" + +ProgramObject Grid3DOverlay::_gridProgram; + +Grid3DOverlay::Grid3DOverlay() : Base3DOverlay(), + _minorGridWidth(1.0), + _majorGridEvery(5) { +} + +Grid3DOverlay::~Grid3DOverlay() { +} + +void Grid3DOverlay::render(RenderArgs* args) { + if (!_visible) { + return; // do nothing if we're not visible + } + + if (!_gridProgram.isLinked()) { + if (!_gridProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + "shaders/grid.frag")) { + qDebug() << "Failed to compile: " + _gridProgram.log(); + return; + } + if (!_gridProgram.link()) { + qDebug() << "Failed to link: " + _gridProgram.log(); + return; + } + } + + // Render code largely taken from MetavoxelEditor::render() + glDisable(GL_LIGHTING); + + glDepthMask(GL_FALSE); + + glPushMatrix(); + + glm::quat rotation = getRotation(); + + glm::vec3 axis = glm::axis(rotation); + + glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); + + glLineWidth(1.5f); + + // center the grid around the camera position on the plane + glm::vec3 rotated = glm::inverse(rotation) * Application::getInstance()->getCamera()->getPosition(); + float spacing = _minorGridWidth; + + float alpha = getAlpha(); + xColor color = getColor(); + glm::vec3 position = getPosition(); + + const int GRID_DIVISIONS = 300; + const float MAX_COLOR = 255.0f; + float scale = GRID_DIVISIONS * spacing; + + glColor4f(color.red / MAX_COLOR, color.green / MAX_COLOR, color.blue / MAX_COLOR, alpha); + + _gridProgram.bind(); + + // Minor grid + glPushMatrix(); + { + glTranslatef(_minorGridWidth * (floorf(rotated.x / spacing) - GRID_DIVISIONS / 2), + spacing * (floorf(rotated.y / spacing) - GRID_DIVISIONS / 2), position.z); + + glScalef(scale, scale, scale); + + Application::getInstance()->getGeometryCache()->renderGrid(GRID_DIVISIONS, GRID_DIVISIONS); + } + glPopMatrix(); + + // Major grid + glPushMatrix(); + { + glLineWidth(4.0f); + spacing *= _majorGridEvery; + glTranslatef(spacing * (floorf(rotated.x / spacing) - GRID_DIVISIONS / 2), + spacing * (floorf(rotated.y / spacing) - GRID_DIVISIONS / 2), position.z); + + scale *= _majorGridEvery; + glScalef(scale, scale, scale); + + Application::getInstance()->getGeometryCache()->renderGrid(GRID_DIVISIONS, GRID_DIVISIONS); + } + glPopMatrix(); + + _gridProgram.release(); + + glPopMatrix(); + + glEnable(GL_LIGHTING); + glDepthMask(GL_TRUE); +} + +void Grid3DOverlay::setProperties(const QScriptValue& properties) { + Base3DOverlay::setProperties(properties); + + if (properties.property("minorGridWidth").isValid()) { + _minorGridWidth = properties.property("minorGridWidth").toVariant().toFloat(); + } + + if (properties.property("majorGridEvery").isValid()) { + _majorGridEvery = properties.property("majorGridEvery").toVariant().toInt(); + } +} diff --git a/interface/src/ui/overlays/Grid3DOverlay.h b/interface/src/ui/overlays/Grid3DOverlay.h new file mode 100644 index 0000000000..b1675f15d7 --- /dev/null +++ b/interface/src/ui/overlays/Grid3DOverlay.h @@ -0,0 +1,44 @@ +// +// Grid3DOverlay.h +// interface/src/ui/overlays +// +// Created by Ryan Huffman on 11/06/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_Grid3DOverlay_h +#define hifi_Grid3DOverlay_h + +// include this before QGLWidget, which includes an earlier version of OpenGL +#include "InterfaceConfig.h" + +#include + +#include +#include + +#include "Base3DOverlay.h" + +#include "renderer/ProgramObject.h" + +class Grid3DOverlay : public Base3DOverlay { + Q_OBJECT + +public: + Grid3DOverlay(); + ~Grid3DOverlay(); + + virtual void render(RenderArgs* args); + virtual void setProperties(const QScriptValue& properties); + +private: + float _minorGridWidth; + int _majorGridEvery; + + static ProgramObject _gridProgram; +}; + +#endif // hifi_Grid3DOverlay_h From b04fd89e4f6c7f3241a6570966c16b65aa89d780 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Tue, 11 Nov 2014 09:55:30 -0800 Subject: [PATCH 11/20] Update grid tool html location --- .../html/gridControls.html | 19 +-- examples/libraries/gridTool.js | 121 ++++-------------- libraries/script-engine/src/ScriptEngine.cpp | 6 +- libraries/script-engine/src/ScriptEngine.h | 2 +- 4 files changed, 29 insertions(+), 119 deletions(-) rename gridControls.html => examples/html/gridControls.html (89%) diff --git a/gridControls.html b/examples/html/gridControls.html similarity index 89% rename from gridControls.html rename to examples/html/gridControls.html index 84d437a60d..e7bf1cdf8c 100644 --- a/gridControls.html +++ b/examples/html/gridControls.html @@ -2,20 +2,13 @@