diff --git a/assignment-client/src/assets/AssetServer.cpp b/assignment-client/src/assets/AssetServer.cpp index 7f43b86328..905cc6fd30 100644 --- a/assignment-client/src/assets/AssetServer.cpp +++ b/assignment-client/src/assets/AssetServer.cpp @@ -298,7 +298,8 @@ void AssetServer::handleAssetGetInfo(QSharedPointer message, Sh message->readPrimitive(&messageID); assetHash = message->readWithoutCopy(SHA256_HASH_LENGTH); - auto replyPacket = NLPacket::create(PacketType::AssetGetInfoReply); + auto size = qint64(sizeof(MessageID) + SHA256_HASH_LENGTH + sizeof(AssetServerError) + sizeof(qint64)); + auto replyPacket = NLPacket::create(PacketType::AssetGetInfoReply, size, true); QByteArray hexHash = assetHash.toHex(); @@ -347,7 +348,7 @@ void AssetServer::handleAssetUpload(QSharedPointer message, Sha // for now this also means it isn't allowed to add assets // so return a packet with error that indicates that - auto permissionErrorPacket = NLPacket::create(PacketType::AssetUploadReply, sizeof(MessageID) + sizeof(AssetServerError)); + auto permissionErrorPacket = NLPacket::create(PacketType::AssetUploadReply, sizeof(MessageID) + sizeof(AssetServerError), true); MessageID messageID; message->readPrimitive(&messageID); diff --git a/assignment-client/src/assets/UploadAssetTask.cpp b/assignment-client/src/assets/UploadAssetTask.cpp index 5ca9b5bbf1..e09619a3cc 100644 --- a/assignment-client/src/assets/UploadAssetTask.cpp +++ b/assignment-client/src/assets/UploadAssetTask.cpp @@ -43,7 +43,7 @@ void UploadAssetTask::run() { qDebug() << "UploadAssetTask reading a file of " << fileSize << "bytes from" << uuidStringWithoutCurlyBraces(_senderNode->getUUID()); - auto replyPacket = NLPacket::create(PacketType::AssetUploadReply); + auto replyPacket = NLPacket::create(PacketType::AssetUploadReply, -1, true); replyPacket->writePrimitive(messageID); if (fileSize > MAX_UPLOAD_SIZE) { diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 08e6e029a0..40e22f855a 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -62,7 +62,7 @@ #include "AudioMixer.h" const float LOUDNESS_TO_DISTANCE_RATIO = 0.00001f; -const float DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE = 0.18f; +const float DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE = 0.5f; // attenuation = -6dB * log2(distance) const float DEFAULT_NOISE_MUTING_THRESHOLD = 0.003f; const QString AUDIO_MIXER_LOGGING_TARGET_NAME = "audio-mixer"; const QString AUDIO_ENV_GROUP_KEY = "audio_env"; @@ -141,13 +141,14 @@ float AudioMixer::gainForSource(const PositionalAudioStream& streamToAdd, } if (distanceBetween >= ATTENUATION_BEGINS_AT_DISTANCE) { - // calculate the distance coefficient using the distance to this node - float distanceCoefficient = 1.0f - (logf(distanceBetween / ATTENUATION_BEGINS_AT_DISTANCE) / logf(2.0f) - * attenuationPerDoublingInDistance); - if (distanceCoefficient < 0) { - distanceCoefficient = 0; - } + // translate the zone setting to gain per log2(distance) + float g = 1.0f - attenuationPerDoublingInDistance; + g = (g < EPSILON) ? EPSILON : g; + g = (g > 1.0f) ? 1.0f : g; + + // calculate the distance coefficient using the distance to this node + float distanceCoefficient = exp2f(log2f(g) * log2f(distanceBetween/ATTENUATION_BEGINS_AT_DISTANCE)); // multiply the current attenuation coefficient by the distance coefficient gain *= distanceCoefficient; diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 948c6ddc18..e1334ee46f 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -589,8 +589,8 @@ "name": "attenuation_per_doubling_in_distance", "label": "Default Domain Attenuation", "help": "Factor between 0 and 1.0 (0: No attenuation, 1.0: extreme attenuation)", - "placeholder": "0.18", - "default": "0.18", + "placeholder": "0.5", + "default": "0.5", "advanced": false }, { @@ -686,7 +686,7 @@ "name": "coefficient", "label": "Attenuation coefficient", "can_set": true, - "placeholder": "0.18" + "placeholder": "0.5" } ] }, diff --git a/interface/resources/controllers/hydra.json b/interface/resources/controllers/hydra.json index 066676140c..716d283a86 100644 --- a/interface/resources/controllers/hydra.json +++ b/interface/resources/controllers/hydra.json @@ -3,9 +3,17 @@ "channels": [ { "from": "Hydra.LY", "filters": "invert", "to": "Standard.LY" }, { "from": "Hydra.LX", "to": "Standard.LX" }, + { "from": "Hydra.LT", "to": "Standard.LTClick", + "peek": true, + "filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ] + }, { "from": "Hydra.LT", "to": "Standard.LT" }, { "from": "Hydra.RY", "filters": "invert", "to": "Standard.RY" }, { "from": "Hydra.RX", "to": "Standard.RX" }, + { "from": "Hydra.RT", "to": "Standard.RTClick", + "peek": true, + "filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ] + }, { "from": "Hydra.RT", "to": "Standard.RT" }, { "from": "Hydra.LB", "to": "Standard.LB" }, diff --git a/interface/resources/controllers/oculus_touch.json b/interface/resources/controllers/oculus_touch.json index 3def439221..001d5b8716 100644 --- a/interface/resources/controllers/oculus_touch.json +++ b/interface/resources/controllers/oculus_touch.json @@ -8,6 +8,10 @@ { "from": "OculusTouch.LY", "filters": "invert", "to": "Standard.LY" }, { "from": "OculusTouch.LX", "to": "Standard.LX" }, + { "from": "OculusTouch.LT", "to": "Standard.LTClick", + "peek": true, + "filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ] + }, { "from": "OculusTouch.LT", "to": "Standard.LT" }, { "from": "OculusTouch.LS", "to": "Standard.LS" }, { "from": "OculusTouch.LeftGrip", "to": "Standard.LeftGrip" }, @@ -15,6 +19,10 @@ { "from": "OculusTouch.RY", "filters": "invert", "to": "Standard.RY" }, { "from": "OculusTouch.RX", "to": "Standard.RX" }, + { "from": "OculusTouch.RT", "to": "Standard.RTClick", + "peek": true, + "filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ] + }, { "from": "OculusTouch.RT", "to": "Standard.RT" }, { "from": "OculusTouch.RS", "to": "Standard.RS" }, { "from": "OculusTouch.RightGrip", "to": "Standard.RightGrip" }, diff --git a/interface/resources/controllers/standard_navigation.json b/interface/resources/controllers/standard_navigation.json index c9dc90cbe6..81096a7230 100644 --- a/interface/resources/controllers/standard_navigation.json +++ b/interface/resources/controllers/standard_navigation.json @@ -10,14 +10,7 @@ { "from": "Standard.RB", "to": "Actions.UiNavGroup" }, { "from": [ "Standard.A", "Standard.X" ], "to": "Actions.UiNavSelect" }, { "from": [ "Standard.B", "Standard.Y" ], "to": "Actions.UiNavBack" }, - { - "from": [ "Standard.RT", "Standard.LT" ], - "to": "Actions.UiNavSelect", - "filters": [ - { "type": "deadZone", "min": 0.5 }, - "constrainToInteger" - ] - }, + { "from": [ "Standard.RTClick", "Standard.LTClick" ], "to": "Actions.UiNavSelect" }, { "from": "Standard.LX", "to": "Actions.UiNavLateral", "filters": [ diff --git a/interface/resources/controllers/vive.json b/interface/resources/controllers/vive.json index 6be672900a..79114b8141 100644 --- a/interface/resources/controllers/vive.json +++ b/interface/resources/controllers/vive.json @@ -9,6 +9,8 @@ { "type": "deadZone", "min": 0.05 } ] }, + { "from": "Vive.LTClick", "to": "Standard.LTClick" }, + { "from": "Vive.LeftGrip", "to": "Standard.LeftGrip" }, { "from": "Vive.LS", "to": "Standard.LS" }, { "from": "Vive.LSTouch", "to": "Standard.LSTouch" }, @@ -21,6 +23,8 @@ { "type": "deadZone", "min": 0.05 } ] }, + { "from": "Vive.RTClick", "to": "Standard.RTClick" }, + { "from": "Vive.RightGrip", "to": "Standard.RightGrip" }, { "from": "Vive.RS", "to": "Standard.RS" }, { "from": "Vive.RSTouch", "to": "Standard.RSTouch" }, diff --git a/interface/resources/qml/hifi/toolbars/Toolbar.qml b/interface/resources/qml/hifi/toolbars/Toolbar.qml index c5c15a6406..30989be688 100644 --- a/interface/resources/qml/hifi/toolbars/Toolbar.qml +++ b/interface/resources/qml/hifi/toolbars/Toolbar.qml @@ -19,26 +19,13 @@ Window { shown: true width: content.width height: content.height - visible: true // Disable this window from being able to call 'desktop.raise() and desktop.showDesktop' - activator: MouseArea { - width: frame.decoration ? frame.decoration.width : window.width - height: frame.decoration ? frame.decoration.height : window.height - x: frame.decoration ? frame.decoration.anchors.leftMargin : 0 - y: frame.decoration ? frame.decoration.anchors.topMargin : 0 - propagateComposedEvents: true - acceptedButtons: Qt.AllButtons - enabled: window.visible - hoverEnabled: true - onPressed: mouse.accepted = false; - onEntered: window.mouseEntered(); - onExited: window.mouseExited(); - } + activator: Item {} property bool horizontal: true property real buttonSize: 50; property var buttons: [] property var container: horizontal ? row : column - + Settings { category: "toolbar/" + window.objectName property alias x: window.x diff --git a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml index 4356b18253..aed90cd433 100644 --- a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml +++ b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml @@ -4,48 +4,20 @@ import QtQuick.Controls 1.4 Item { id: button property alias imageURL: image.source - property alias alpha: button.opacity + property alias alpha: image.opacity property var subImage; property int yOffset: 0 property int buttonState: 0 - property int hoverOffset: 0 + property int hoverState: -1 + property int defaultState: -1 property var toolbar; property real size: 50 // toolbar ? toolbar.buttonSize : 50 width: size; height: size property bool pinned: false clip: true - Behavior on opacity { - NumberAnimation { - duration: 150 - easing.type: Easing.InOutCubic - } - } - - property alias fadeTargetProperty: button.opacity - - onFadeTargetPropertyChanged: { - visible = (fadeTargetProperty !== 0.0); - } - - onVisibleChanged: { - if ((!visible && fadeTargetProperty != 0.0) || (visible && fadeTargetProperty == 0.0)) { - var target = visible; - visible = !visible; - fadeTargetProperty = target ? 1.0 : 0.0; - return; - } - } - - function updateOffset() { - yOffset = size * (buttonState + hoverOffset); - } onButtonStateChanged: { - hoverOffset = 0; // subtle: show the new state without hover. don't wait for mouse to be moved away - // The above is per UX design, but ALSO avoid a subtle issue that would be a problem because - // the hand controllers don't move the mouse when not triggered, so releasing the trigger would - // never show unhovered. - updateOffset(); + yOffset = size * buttonState; } Component.onCompleted: { @@ -64,18 +36,28 @@ Item { width: parent.width } + Timer { + id: asyncClickSender + interval: 10 + repeat: false + running: false + onTriggered: button.clicked(); + } + MouseArea { id: mouseArea hoverEnabled: true anchors.fill: parent - onClicked: button.clicked(); + onClicked: asyncClickSender.start(); onEntered: { - hoverOffset = 2; - updateOffset(); + if (hoverState >= 0) { + buttonState = hoverState; + } } onExited: { - hoverOffset = 0; - updateOffset(); + if (defaultState >= 0) { + buttonState = defaultState; + } } } } diff --git a/interface/resources/qml/menus/VrMenuView.qml b/interface/resources/qml/menus/VrMenuView.qml index d8cc0e6667..5db13fc160 100644 --- a/interface/resources/qml/menus/VrMenuView.qml +++ b/interface/resources/qml/menus/VrMenuView.qml @@ -45,6 +45,7 @@ FocusScope { onVisibleChanged: recalcSize(); onCountChanged: recalcSize(); focus: true + highlightMoveDuration: 0 highlight: Rectangle { anchors { diff --git a/interface/resources/qml/windows/Decoration.qml b/interface/resources/qml/windows/Decoration.qml index edfb369c0f..843ae25596 100644 --- a/interface/resources/qml/windows/Decoration.qml +++ b/interface/resources/qml/windows/Decoration.qml @@ -43,23 +43,45 @@ Rectangle { // Enable dragging of the window, // detect mouseover of the window (including decoration) MouseArea { + id: decorationMouseArea anchors.fill: parent drag.target: window + hoverEnabled: true + onEntered: window.mouseEntered(); + onExited: { + if (!containsMouseGlobal()) { + window.mouseExited(); + } + } + + function containsMouseGlobal() { + var reticlePos = Reticle.position; + var globalPosition = decorationMouseArea.mapToItem(desktop, 0, 0); + var localPosition = { + x: reticlePos.x - globalPosition.x, + y: reticlePos.y - globalPosition.y, + }; + return localPosition.x >= 0 && localPosition.x <= width && + localPosition.y >= 0 && localPosition.y <= height; + } + } Connections { target: window onMouseEntered: { if (desktop.hmdHandMouseActive) { root.inflateDecorations() - } + } + } + onMouseExited: { + root.deflateDecorations(); } - onMouseExited: root.deflateDecorations(); } Connections { target: desktop onHmdHandMouseActiveChanged: { if (desktop.hmdHandMouseActive) { - if (window.activator.containsMouse) { + if (decorationMouseArea.containsMouse) { root.inflateDecorations(); } } else { diff --git a/interface/resources/qml/windows/Window.qml b/interface/resources/qml/windows/Window.qml index ca37c55f4d..c873872692 100644 --- a/interface/resources/qml/windows/Window.qml +++ b/interface/resources/qml/windows/Window.qml @@ -115,14 +115,10 @@ Fadable { propagateComposedEvents: true acceptedButtons: Qt.AllButtons enabled: window.visible - hoverEnabled: true onPressed: { - //console.log("Pressed on activator area"); window.raise(); mouse.accepted = false; } - onEntered: window.mouseEntered(); - onExited: window.mouseExited(); } // This mouse area serves to swallow mouse events while the mouse is over the window diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a6180141e5..bc65977beb 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4704,6 +4704,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri qScriptRegisterMetaType(scriptEngine, RayToOverlayIntersectionResultToScriptValue, RayToOverlayIntersectionResultFromScriptValue); + scriptEngine->registerGlobalObject("OffscreenFlags", DependencyManager::get()->getFlags()); scriptEngine->registerGlobalObject("Desktop", DependencyManager::get().data()); scriptEngine->registerGlobalObject("Toolbars", DependencyManager::get().data()); diff --git a/interface/src/FileLogger.cpp b/interface/src/FileLogger.cpp index 50f7d15d6b..754fa7f474 100644 --- a/interface/src/FileLogger.cpp +++ b/interface/src/FileLogger.cpp @@ -24,9 +24,14 @@ static const QString FILENAME_FORMAT = "hifi-log_%1_%2.txt"; static const QString DATETIME_FORMAT = "yyyy-MM-dd_hh.mm.ss"; static const QString LOGS_DIRECTORY = "Logs"; +static const QString IPADDR_WILDCARD = "[0-9]*.[0-9]*.[0-9]*.[0-9]*"; +static const QString DATETIME_WILDCARD = "20[0-9][0-9]-[0,1][0-9]-[0-3][0-9]_[0-2][0-9].[0-6][0-9].[0-6][0-9]"; +static const QString FILENAME_WILDCARD = "hifi-log_" + IPADDR_WILDCARD + "_" + DATETIME_WILDCARD + ".txt"; // Max log size is 512 KB. We send log files to our crash reporter, so we want to keep this relatively // small so it doesn't go over the 2MB zipped limit for all of the files we send. static const qint64 MAX_LOG_SIZE = 512 * 1024; +// Max log files found in the log directory is 100. +static const qint64 MAX_LOG_DIR_SIZE = 512 * 1024 * 100; // Max log age is 1 hour static const uint64_t MAX_LOG_AGE_USECS = USECS_PER_SECOND * 3600; @@ -71,6 +76,22 @@ void FilePersistThread::rollFileIfNecessary(QFile& file, bool notifyListenersIfR _lastRollTime = now; } + QStringList nameFilters; + nameFilters << FILENAME_WILDCARD; + + QDir logQDir(FileUtils::standardPath(LOGS_DIRECTORY)); + logQDir.setNameFilters(nameFilters); + logQDir.setSorting(QDir::Time); + QFileInfoList filesInDir = logQDir.entryInfoList(); + qint64 totalSizeOfDir = 0; + foreach(QFileInfo dirItm, filesInDir){ + if (totalSizeOfDir < MAX_LOG_DIR_SIZE){ + totalSizeOfDir += dirItm.size(); + } else { + QFile file(dirItm.filePath()); + file.remove(); + } + } } } diff --git a/interface/src/scripting/ToolbarScriptingInterface.cpp b/interface/src/scripting/ToolbarScriptingInterface.cpp index 175a7fd539..0cb314615a 100644 --- a/interface/src/scripting/ToolbarScriptingInterface.cpp +++ b/interface/src/scripting/ToolbarScriptingInterface.cpp @@ -8,6 +8,8 @@ #include "ToolbarScriptingInterface.h" +#include + #include class QmlWrapper : public QObject { @@ -79,7 +81,11 @@ public: Q_INVOKABLE QObject* addButton(const QVariant& properties) { QVariant resultVar; - bool invokeResult = QMetaObject::invokeMethod(_qmlObject, "addButton", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, properties)); + Qt::ConnectionType connectionType = Qt::AutoConnection; + if (QThread::currentThread() != _qmlObject->thread()) { + connectionType = Qt::BlockingQueuedConnection; + } + bool invokeResult = QMetaObject::invokeMethod(_qmlObject, "addButton", connectionType, Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, properties)); if (!invokeResult) { return nullptr; } @@ -101,8 +107,12 @@ public: QObject* ToolbarScriptingInterface::getToolbar(const QString& toolbarId) { auto offscreenUi = DependencyManager::get(); auto desktop = offscreenUi->getDesktop(); + Qt::ConnectionType connectionType = Qt::AutoConnection; + if (QThread::currentThread() != desktop->thread()) { + connectionType = Qt::BlockingQueuedConnection; + } QVariant resultVar; - bool invokeResult = QMetaObject::invokeMethod(desktop, "getToolbar", Qt::BlockingQueuedConnection, Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, toolbarId)); + bool invokeResult = QMetaObject::invokeMethod(desktop, "getToolbar", connectionType, Q_RETURN_ARG(QVariant, resultVar), Q_ARG(QVariant, toolbarId)); if (!invokeResult) { return nullptr; } diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 7a24832ea0..b5a7c8c0cf 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1308,7 +1308,7 @@ float AudioClient::gainForSource(float distance, float volume) { // attenuate based on distance if (distance >= ATTENUATION_BEGINS_AT_DISTANCE) { - gain /= distance; // attenuation = -6dB * log2(distance) + gain /= (distance/ATTENUATION_BEGINS_AT_DISTANCE); // attenuation = -6dB * log2(distance) } return gain; diff --git a/libraries/controllers/src/controllers/StandardController.cpp b/libraries/controllers/src/controllers/StandardController.cpp index a9d317d407..02ae5706b7 100644 --- a/libraries/controllers/src/controllers/StandardController.cpp +++ b/libraries/controllers/src/controllers/StandardController.cpp @@ -65,6 +65,8 @@ Input::NamedVector StandardController::getAvailableInputs() const { // Triggers makePair(LT, "LT"), makePair(RT, "RT"), + makePair(LT_CLICK, "LTClick"), + makePair(RT_CLICK, "RTClick"), // Finger abstractions makePair(LEFT_PRIMARY_THUMB, "LeftPrimaryThumb"), diff --git a/libraries/controllers/src/controllers/StandardControls.h b/libraries/controllers/src/controllers/StandardControls.h index 501f97f04b..c21d8a2f6e 100644 --- a/libraries/controllers/src/controllers/StandardControls.h +++ b/libraries/controllers/src/controllers/StandardControls.h @@ -46,6 +46,7 @@ namespace controller { LS_CENTER, LS_X, LS_Y, + LT_CLICK, RIGHT_PRIMARY_THUMB, RIGHT_SECONDARY_THUMB, @@ -56,6 +57,7 @@ namespace controller { RS_CENTER, RS_X, RS_Y, + RT_CLICK, LEFT_PRIMARY_INDEX, LEFT_SECONDARY_INDEX, diff --git a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp index 5ae52893e0..7dedfda3cb 100644 --- a/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp +++ b/libraries/controllers/src/controllers/impl/RouteBuilderProxy.cpp @@ -26,6 +26,7 @@ #include "filters/InvertFilter.h" #include "filters/PulseFilter.h" #include "filters/ScaleFilter.h" +#include "conditionals/AndConditional.h" using namespace controller; @@ -58,12 +59,22 @@ QObject* RouteBuilderProxy::peek(bool enable) { } QObject* RouteBuilderProxy::when(const QScriptValue& expression) { - _route->conditional = _parent.conditionalFor(expression); + auto newConditional = _parent.conditionalFor(expression); + if (_route->conditional) { + _route->conditional = ConditionalPointer(new AndConditional(_route->conditional, newConditional)); + } else { + _route->conditional = newConditional; + } return this; } QObject* RouteBuilderProxy::whenQml(const QJSValue& expression) { - _route->conditional = _parent.conditionalFor(expression); + auto newConditional = _parent.conditionalFor(expression); + if (_route->conditional) { + _route->conditional = ConditionalPointer(new AndConditional(_route->conditional, newConditional)); + } else { + _route->conditional = newConditional; + } return this; } diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp index 89ff2e0c8d..56be8e1cf9 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.cpp @@ -442,3 +442,12 @@ glm::mat4 CompositorHelper::getReticleTransform(const glm::mat4& eyePose, const } return result; } + + +QVariant ReticleInterface::getPosition() const { + return vec2toVariant(_compositor->getReticlePosition()); +} + +void ReticleInterface::setPosition(QVariant position) { + _compositor->setReticlePosition(vec2FromVariant(position)); +} diff --git a/libraries/display-plugins/src/display-plugins/CompositorHelper.h b/libraries/display-plugins/src/display-plugins/CompositorHelper.h index 2a3dd0c852..b0b96d86be 100644 --- a/libraries/display-plugins/src/display-plugins/CompositorHelper.h +++ b/libraries/display-plugins/src/display-plugins/CompositorHelper.h @@ -174,7 +174,7 @@ private: // Scripting interface available to control the Reticle class ReticleInterface : public QObject { Q_OBJECT - Q_PROPERTY(glm::vec2 position READ getPosition WRITE setPosition) + Q_PROPERTY(QVariant position READ getPosition WRITE setPosition) Q_PROPERTY(bool visible READ getVisible WRITE setVisible) Q_PROPERTY(float depth READ getDepth WRITE setDepth) Q_PROPERTY(glm::vec2 maximumPosition READ getMaximumPosition) @@ -198,8 +198,8 @@ public: Q_INVOKABLE float getDepth() { return _compositor->getReticleDepth(); } Q_INVOKABLE void setDepth(float depth) { _compositor->setReticleDepth(depth); } - Q_INVOKABLE glm::vec2 getPosition() { return _compositor->getReticlePosition(); } - Q_INVOKABLE void setPosition(glm::vec2 position) { _compositor->setReticlePosition(position); } + Q_INVOKABLE QVariant getPosition() const; + Q_INVOKABLE void setPosition(QVariant position); Q_INVOKABLE glm::vec2 getMaximumPosition() { return _compositor->getReticleMaximumPosition(); } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index d63361538a..bef790299c 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -709,9 +709,9 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { // compute meshPart local transforms QVector localTransforms; const FBXGeometry& fbxGeometry = _model->getFBXGeometry(); - int32_t numMeshes = (int32_t)fbxGeometry.meshes.size(); - int32_t totalNumVertices = 0; - for (int32_t i = 0; i < numMeshes; i++) { + int numFbxMeshes = fbxGeometry.meshes.size(); + int totalNumVertices = 0; + for (int i = 0; i < numFbxMeshes; i++) { const FBXMesh& mesh = fbxGeometry.meshes.at(i); if (mesh.clusters.size() > 0) { const FBXCluster& cluster = mesh.clusters.at(0); @@ -730,10 +730,8 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { return; } - auto& meshes = _model->getGeometry()->getGeometry()->getMeshes(); - - // the render geometry's mesh count should match that of the FBXGeometry - assert(numMeshes == (int32_t)(meshes.size())); + auto& meshes = _model->getGeometry()->getMeshes(); + int32_t numMeshes = (int32_t)(meshes.size()); ShapeInfo::PointCollection& pointCollection = info.getPointCollection(); pointCollection.clear(); diff --git a/libraries/model-networking/src/model-networking/ModelCache.cpp b/libraries/model-networking/src/model-networking/ModelCache.cpp index 971c5f927b..4885186e47 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.cpp +++ b/libraries/model-networking/src/model-networking/ModelCache.cpp @@ -70,7 +70,7 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) { auto modelCache = DependencyManager::get(); GeometryExtra extra{ mapping, _textureBaseUrl }; - // Get the raw GeometryResource, not the wrapped NetworkGeometry + // Get the raw GeometryResource _geometryResource = modelCache->getResource(url, QUrl(), &extra).staticCast(); // Avoid caching nested resources - their references will be held by the parent _geometryResource->_isCacheable = false; @@ -90,8 +90,8 @@ void GeometryMappingResource::downloadFinished(const QByteArray& data) { void GeometryMappingResource::onGeometryMappingLoaded(bool success) { if (success && _geometryResource) { - _geometry = _geometryResource->_geometry; - _shapes = _geometryResource->_shapes; + _fbxGeometry = _geometryResource->_fbxGeometry; + _meshParts = _geometryResource->_meshParts; _meshes = _geometryResource->_meshes; _materials = _geometryResource->_materials; @@ -200,31 +200,31 @@ void GeometryDefinitionResource::downloadFinished(const QByteArray& data) { void GeometryDefinitionResource::setGeometryDefinition(FBXGeometry::Pointer fbxGeometry) { // Assume ownership of the geometry pointer - _geometry = fbxGeometry; + _fbxGeometry = fbxGeometry; // Copy materials QHash materialIDAtlas; - for (const FBXMaterial& material : _geometry->materials) { + for (const FBXMaterial& material : _fbxGeometry->materials) { materialIDAtlas[material.materialID] = _materials.size(); _materials.push_back(std::make_shared(material, _textureBaseUrl)); } - std::shared_ptr meshes = std::make_shared(); - std::shared_ptr shapes = std::make_shared(); + std::shared_ptr meshes = std::make_shared(); + std::shared_ptr parts = std::make_shared(); int meshID = 0; - for (const FBXMesh& mesh : _geometry->meshes) { + for (const FBXMesh& mesh : _fbxGeometry->meshes) { // Copy mesh pointers meshes->emplace_back(mesh._mesh); int partID = 0; for (const FBXMeshPart& part : mesh.parts) { - // Construct local shapes - shapes->push_back(std::make_shared(meshID, partID, (int)materialIDAtlas[part.materialID])); + // Construct local parts + parts->push_back(std::make_shared(meshID, partID, (int)materialIDAtlas[part.materialID])); partID++; } meshID++; } _meshes = meshes; - _shapes = shapes; + _meshParts = parts; finishedLoading(true); } @@ -250,17 +250,15 @@ QSharedPointer ModelCache::createResource(const QUrl& url, const QShar return QSharedPointer(resource, &Resource::deleter); } -std::shared_ptr ModelCache::getGeometry(const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl) { +GeometryResource::Pointer ModelCache::getGeometryResource(const QUrl& url, const QVariantHash& mapping, const QUrl& textureBaseUrl) { GeometryExtra geometryExtra = { mapping, textureBaseUrl }; GeometryResource::Pointer resource = getResource(url, QUrl(), &geometryExtra).staticCast(); if (resource) { if (resource->isLoaded() && resource->shouldSetTextures()) { resource->setTextures(); } - return std::make_shared(resource); - } else { - return NetworkGeometry::Pointer(); } + return resource; } const QVariantMap Geometry::getTextures() const { @@ -278,9 +276,9 @@ const QVariantMap Geometry::getTextures() const { // FIXME: The materials should only be copied when modified, but the Model currently caches the original Geometry::Geometry(const Geometry& geometry) { - _geometry = geometry._geometry; + _fbxGeometry = geometry._fbxGeometry; _meshes = geometry._meshes; - _shapes = geometry._shapes; + _meshParts = geometry._meshParts; _materials.reserve(geometry._materials.size()); for (const auto& material : geometry._materials) { @@ -336,9 +334,9 @@ bool Geometry::areTexturesLoaded() const { return true; } -const std::shared_ptr Geometry::getShapeMaterial(int shapeID) const { - if ((shapeID >= 0) && (shapeID < (int)_shapes->size())) { - int materialID = _shapes->at(shapeID)->materialID; +const std::shared_ptr Geometry::getShapeMaterial(int partID) const { + if ((partID >= 0) && (partID < (int)_meshParts->size())) { + int materialID = _meshParts->at(partID)->materialID; if ((materialID >= 0) && (materialID < (int)_materials.size())) { return _materials[materialID]; } @@ -352,7 +350,7 @@ void GeometryResource::deleter() { } void GeometryResource::setTextures() { - for (const FBXMaterial& material : _geometry->materials) { + for (const FBXMaterial& material : _fbxGeometry->materials) { _materials.push_back(std::make_shared(material, _textureBaseUrl)); } } @@ -361,26 +359,40 @@ void GeometryResource::resetTextures() { _materials.clear(); } -NetworkGeometry::NetworkGeometry(const GeometryResource::Pointer& networkGeometry) : _resource(networkGeometry) { - connect(_resource.data(), &Resource::finished, this, &NetworkGeometry::resourceFinished); - connect(_resource.data(), &Resource::onRefresh, this, &NetworkGeometry::resourceRefreshed); +void GeometryResourceWatcher::startWatching() { + connect(_resource.data(), &Resource::finished, this, &GeometryResourceWatcher::resourceFinished); + connect(_resource.data(), &Resource::onRefresh, this, &GeometryResourceWatcher::resourceRefreshed); if (_resource->isLoaded()) { resourceFinished(!_resource->getURL().isEmpty()); } } -void NetworkGeometry::resourceFinished(bool success) { - // FIXME: Model is not set up to handle a refresh - if (_instance) { - return; - } - if (success) { - _instance = std::make_shared(*_resource); - } - emit finished(success); +void GeometryResourceWatcher::stopWatching() { + disconnect(_resource.data(), &Resource::finished, this, &GeometryResourceWatcher::resourceFinished); + disconnect(_resource.data(), &Resource::onRefresh, this, &GeometryResourceWatcher::resourceRefreshed); } -void NetworkGeometry::resourceRefreshed() { +void GeometryResourceWatcher::setResource(GeometryResource::Pointer resource) { + if (_resource) { + stopWatching(); + } + _resource = resource; + if (_resource) { + if (_resource->isLoaded()) { + _geometryRef = std::make_shared(*_resource); + } else { + startWatching(); + } + } +} + +void GeometryResourceWatcher::resourceFinished(bool success) { + if (success) { + _geometryRef = std::make_shared(*_resource); + } +} + +void GeometryResourceWatcher::resourceRefreshed() { // FIXME: Model is not set up to handle a refresh // _instance.reset(); } diff --git a/libraries/model-networking/src/model-networking/ModelCache.h b/libraries/model-networking/src/model-networking/ModelCache.h index f15e1106e2..aa3ea78db3 100644 --- a/libraries/model-networking/src/model-networking/ModelCache.h +++ b/libraries/model-networking/src/model-networking/ModelCache.h @@ -22,52 +22,30 @@ #include "TextureCache.h" // Alias instead of derive to avoid copying -using NetworkMesh = model::Mesh; class NetworkTexture; class NetworkMaterial; -class NetworkShape; -class NetworkGeometry; +class MeshPart; class GeometryMappingResource; -/// Stores cached model geometries. -class ModelCache : public ResourceCache, public Dependency { - Q_OBJECT - SINGLETON_DEPENDENCY - -public: - /// Loads a model geometry from the specified URL. - std::shared_ptr getGeometry(const QUrl& url, - const QVariantHash& mapping = QVariantHash(), const QUrl& textureBaseUrl = QUrl()); - -protected: - friend class GeometryMappingResource; - - virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, - const void* extra); - -private: - ModelCache(); - virtual ~ModelCache() = default; -}; - class Geometry { public: using Pointer = std::shared_ptr; + using WeakPointer = std::weak_ptr; Geometry() = default; Geometry(const Geometry& geometry); // Immutable over lifetime - using NetworkMeshes = std::vector>; - using NetworkShapes = std::vector>; + using GeometryMeshes = std::vector>; + using GeometryMeshParts = std::vector>; // Mutable, but must retain structure of vector using NetworkMaterials = std::vector>; - const FBXGeometry& getGeometry() const { return *_geometry; } - const NetworkMeshes& getMeshes() const { return *_meshes; } + const FBXGeometry& getFBXGeometry() const { return *_fbxGeometry; } + const GeometryMeshes& getMeshes() const { return *_meshes; } const std::shared_ptr getShapeMaterial(int shapeID) const; const QVariantMap getTextures() const; @@ -79,9 +57,9 @@ protected: friend class GeometryMappingResource; // Shared across all geometries, constant throughout lifetime - std::shared_ptr _geometry; - std::shared_ptr _meshes; - std::shared_ptr _shapes; + std::shared_ptr _fbxGeometry; + std::shared_ptr _meshes; + std::shared_ptr _meshParts; // Copied to each geometry, mutable throughout lifetime via setTextures NetworkMaterials _materials; @@ -108,7 +86,7 @@ protected: // Geometries may not hold onto textures while cached - that is for the texture cache // Instead, these methods clear and reset textures from the geometry when caching/loading - bool shouldSetTextures() const { return _geometry && _materials.empty(); } + bool shouldSetTextures() const { return _fbxGeometry && _materials.empty(); } void setTextures(); void resetTextures(); @@ -118,22 +96,21 @@ protected: bool _isCacheable { true }; }; -class NetworkGeometry : public QObject { +class GeometryResourceWatcher : public QObject { Q_OBJECT public: - using Pointer = std::shared_ptr; + using Pointer = std::shared_ptr; - NetworkGeometry() = delete; - NetworkGeometry(const GeometryResource::Pointer& networkGeometry); + GeometryResourceWatcher() = delete; + GeometryResourceWatcher(Geometry::Pointer& geometryPtr) : _geometryRef(geometryPtr) {} - const QUrl& getURL() { return _resource->getURL(); } + void setResource(GeometryResource::Pointer resource); - /// Returns the geometry, if it is loaded (must be checked!) - const Geometry::Pointer& getGeometry() { return _instance; } + QUrl getURL() const { return (bool)_resource ? _resource->getURL() : QUrl(); } -signals: - /// Emitted when the NetworkGeometry loads (or fails to) - void finished(bool success); +private: + void startWatching(); + void stopWatching(); private slots: void resourceFinished(bool success); @@ -141,7 +118,27 @@ private slots: private: GeometryResource::Pointer _resource; - Geometry::Pointer _instance { nullptr }; + Geometry::Pointer& _geometryRef; +}; + +/// Stores cached model geometries. +class ModelCache : public ResourceCache, public Dependency { + Q_OBJECT + SINGLETON_DEPENDENCY + +public: + GeometryResource::Pointer getGeometryResource(const QUrl& url, + const QVariantHash& mapping = QVariantHash(), const QUrl& textureBaseUrl = QUrl()); + +protected: + friend class GeometryMappingResource; + + virtual QSharedPointer createResource(const QUrl& url, const QSharedPointer& fallback, + const void* extra); + +private: + ModelCache(); + virtual ~ModelCache() = default; }; class NetworkMaterial : public model::Material { @@ -185,9 +182,9 @@ private: bool _isOriginal { true }; }; -class NetworkShape { +class MeshPart { public: - NetworkShape(int mesh, int part, int material) : meshID { mesh }, partID { part }, materialID { material } {} + MeshPart(int mesh, int part, int material) : meshID { mesh }, partID { part }, materialID { material } {} int meshID { -1 }; int partID { -1 }; int materialID { -1 }; diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index 1bfdc1798e..385f2c6fa4 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -402,8 +402,6 @@ private: QHash _gridBuffers; QHash _registeredGridBuffers; - QHash > _networkGeometry; - gpu::ShaderPointer _simpleShader; gpu::ShaderPointer _unlitShader; static render::ShapePipelinePointer _simplePipeline; diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index f554e1aa39..cb6c73f414 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -324,7 +324,7 @@ ModelMeshPartPayload::ModelMeshPartPayload(Model* model, int _meshIndex, int par _shapeID(shapeIndex) { assert(_model && _model->isLoaded()); - auto& modelMesh = _model->getGeometry()->getGeometry()->getMeshes().at(_meshIndex); + auto& modelMesh = _model->getGeometry()->getMeshes().at(_meshIndex); updateMeshPart(modelMesh, partIndex); updateTransform(transform, offsetTransform); @@ -345,7 +345,7 @@ void ModelMeshPartPayload::initCache() { _isBlendShaped = !mesh.blendshapes.isEmpty(); } - auto networkMaterial = _model->getGeometry()->getGeometry()->getShapeMaterial(_shapeID); + auto networkMaterial = _model->getGeometry()->getShapeMaterial(_shapeID); if (networkMaterial) { _drawMaterial = networkMaterial; }; @@ -398,7 +398,7 @@ ItemKey ModelMeshPartPayload::getKey() const { ShapeKey ModelMeshPartPayload::getShapeKey() const { assert(_model->isLoaded()); const FBXGeometry& geometry = _model->getFBXGeometry(); - const auto& networkMeshes = _model->getGeometry()->getGeometry()->getMeshes(); + const auto& networkMeshes = _model->getGeometry()->getMeshes(); // guard against partially loaded meshes if (_meshIndex >= (int)networkMeshes.size() || _meshIndex >= (int)geometry.meshes.size() || _meshIndex >= (int)_model->_meshStates.size()) { diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 43eced3107..e2363d0cca 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -31,7 +31,7 @@ using namespace std; int nakedModelPointerTypeId = qRegisterMetaType(); -int weakNetworkGeometryPointerTypeId = qRegisterMetaType >(); +int weakGeometryResourceBridgePointerTypeId = qRegisterMetaType(); int vec3VectorTypeId = qRegisterMetaType >(); float Model::FAKE_DIMENSION_PLACEHOLDER = -1.0f; #define HTTP_INVALID_COM "http://invalid.com" @@ -79,6 +79,10 @@ void initCollisionHullMaterials() { Model::Model(RigPointer rig, QObject* parent) : QObject(parent), + _renderGeometry(), + _collisionGeometry(), + _renderWatcher(_renderGeometry), + _collisionWatcher(_collisionGeometry), _translation(0.0f), _rotation(), _scale(1.0f, 1.0f, 1.0f), @@ -98,7 +102,6 @@ Model::Model(RigPointer rig, QObject* parent) : _calculatedMeshTrianglesValid(false), _meshGroupsKnown(false), _isWireframe(false), - _renderCollisionHull(false), _rig(rig) { // we may have been created in the network thread, but we live in the main thread if (_viewState) { @@ -116,7 +119,7 @@ AbstractViewStateInterface* Model::_viewState = NULL; bool Model::needsFixupInScene() const { if (readyToAddToScene()) { - if (_needsUpdateTextures && _geometry->getGeometry()->areTexturesLoaded()) { + if (_needsUpdateTextures && _renderGeometry->areTexturesLoaded()) { _needsUpdateTextures = false; return true; } @@ -793,13 +796,13 @@ int Model::getLastFreeJointIndex(int jointIndex) const { void Model::setTextures(const QVariantMap& textures) { if (isLoaded()) { _needsUpdateTextures = true; - _geometry->getGeometry()->setTextures(textures); + _renderGeometry->setTextures(textures); } } void Model::setURL(const QUrl& url) { // don't recreate the geometry if it's the same URL - if (_url == url && _geometry && _geometry->getURL() == url) { + if (_url == url && _renderWatcher.getURL() == url) { return; } @@ -818,16 +821,16 @@ void Model::setURL(const QUrl& url) { invalidCalculatedMeshBoxes(); deleteGeometry(); - _geometry = DependencyManager::get()->getGeometry(url); + _renderWatcher.setResource(DependencyManager::get()->getGeometryResource(url)); onInvalidate(); } void Model::setCollisionModelURL(const QUrl& url) { - if (_collisionUrl == url) { + if (_collisionUrl == url && _collisionWatcher.getURL() == url) { return; } _collisionUrl = url; - _collisionGeometry = DependencyManager::get()->getGeometry(url); + _collisionWatcher.setResource(DependencyManager::get()->getGeometryResource(url)); } bool Model::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const { @@ -883,7 +886,7 @@ QStringList Model::getJointNames() const { class Blender : public QRunnable { public: - Blender(ModelPointer model, int blendNumber, const std::weak_ptr& geometry, + Blender(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry, const QVector& meshes, const QVector& blendshapeCoefficients); virtual void run(); @@ -892,12 +895,12 @@ private: ModelPointer _model; int _blendNumber; - std::weak_ptr _geometry; + Geometry::WeakPointer _geometry; QVector _meshes; QVector _blendshapeCoefficients; }; -Blender::Blender(ModelPointer model, int blendNumber, const std::weak_ptr& geometry, +Blender::Blender(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry, const QVector& meshes, const QVector& blendshapeCoefficients) : _model(model), _blendNumber(blendNumber), @@ -940,7 +943,7 @@ void Blender::run() { // post the result to the geometry cache, which will dispatch to the model if still alive QMetaObject::invokeMethod(DependencyManager::get().data(), "setBlendedVertices", Q_ARG(ModelPointer, _model), Q_ARG(int, _blendNumber), - Q_ARG(const std::weak_ptr&, _geometry), Q_ARG(const QVector&, vertices), + Q_ARG(const Geometry::WeakPointer&, _geometry), Q_ARG(const QVector&, vertices), Q_ARG(const QVector&, normals)); } @@ -1151,7 +1154,7 @@ bool Model::maybeStartBlender() { if (isLoaded()) { const FBXGeometry& fbxGeometry = getFBXGeometry(); if (fbxGeometry.hasBlendedMeshes()) { - QThreadPool::globalInstance()->start(new Blender(getThisPointer(), ++_blendNumber, _geometry, + QThreadPool::globalInstance()->start(new Blender(getThisPointer(), ++_blendNumber, _renderGeometry, fbxGeometry.meshes, _blendshapeCoefficients)); return true; } @@ -1159,10 +1162,10 @@ bool Model::maybeStartBlender() { return false; } -void Model::setBlendedVertices(int blendNumber, const std::weak_ptr& geometry, +void Model::setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geometry, const QVector& vertices, const QVector& normals) { auto geometryRef = geometry.lock(); - if (!geometryRef || _geometry != geometryRef || _blendedVertexBuffers.empty() || blendNumber < _appliedBlendNumber) { + if (!geometryRef || _renderGeometry != geometryRef || _blendedVertexBuffers.empty() || blendNumber < _appliedBlendNumber) { return; } _appliedBlendNumber = blendNumber; @@ -1205,27 +1208,23 @@ AABox Model::getRenderableMeshBound() const { } void Model::segregateMeshGroups() { - NetworkGeometry::Pointer networkGeometry; + Geometry::Pointer geometry; bool showingCollisionHull = false; if (_showCollisionHull && _collisionGeometry) { if (isCollisionLoaded()) { - networkGeometry = _collisionGeometry; + geometry = _collisionGeometry; showingCollisionHull = true; } else { return; } } else { assert(isLoaded()); - networkGeometry = _geometry; + geometry = _renderGeometry; } - const FBXGeometry& geometry = networkGeometry->getGeometry()->getGeometry(); - const auto& networkMeshes = networkGeometry->getGeometry()->getMeshes(); + const auto& meshes = geometry->getMeshes(); // all of our mesh vectors must match in size - auto geoMeshesSize = geometry.meshes.size(); - if ((int)networkMeshes.size() != geoMeshesSize || - // geometry.meshes.size() != _meshStates.size()) { - geoMeshesSize > _meshStates.size()) { + if ((int)meshes.size() != _meshStates.size()) { qDebug() << "WARNING!!!! Mesh Sizes don't match! We will not segregate mesh groups yet."; return; } @@ -1249,23 +1248,25 @@ void Model::segregateMeshGroups() { // Run through all of the meshes, and place them into their segregated, but unsorted buckets int shapeID = 0; - for (int i = 0; i < (int)networkMeshes.size(); i++) { - const FBXMesh& mesh = geometry.meshes.at(i); - const auto& networkMesh = networkMeshes.at(i); + uint32_t numMeshes = (uint32_t)meshes.size(); + for (uint32_t i = 0; i < numMeshes; i++) { + const auto& mesh = meshes.at(i); + if (mesh) { - // Create the render payloads - int totalParts = mesh.parts.size(); - for (int partIndex = 0; partIndex < totalParts; partIndex++) { - if (showingCollisionHull) { - if (_collisionHullMaterials.empty()) { - initCollisionHullMaterials(); + // Create the render payloads + int numParts = (int)mesh->getNumParts(); + for (int partIndex = 0; partIndex < numParts; partIndex++) { + if (showingCollisionHull) { + if (_collisionHullMaterials.empty()) { + initCollisionHullMaterials(); + } + _collisionRenderItemsSet << std::make_shared(mesh, partIndex, _collisionHullMaterials[partIndex % NUM_COLLISION_HULL_COLORS], transform, offset); + } else { + _modelMeshRenderItemsSet << std::make_shared(this, i, partIndex, shapeID, transform, offset); } - _collisionRenderItemsSet << std::make_shared(networkMesh, partIndex, _collisionHullMaterials[partIndex % NUM_COLLISION_HULL_COLORS], transform, offset); - } else { - _modelMeshRenderItemsSet << std::make_shared(this, i, partIndex, shapeID, transform, offset); - } - shapeID++; + shapeID++; + } } } _meshGroupsKnown = true; @@ -1328,7 +1329,7 @@ void ModelBlender::noteRequiresBlend(ModelPointer model) { } void ModelBlender::setBlendedVertices(ModelPointer model, int blendNumber, - const std::weak_ptr& geometry, const QVector& vertices, const QVector& normals) { + const Geometry::WeakPointer& geometry, const QVector& vertices, const QVector& normals) { if (model) { model->setBlendedVertices(blendNumber, geometry, vertices, normals); } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 6a7c9ec560..aa0c49f720 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -97,7 +97,7 @@ public: bool showCollisionHull = false); void removeFromScene(std::shared_ptr scene, render::PendingChanges& pendingChanges); void renderSetup(RenderArgs* args); - bool isRenderable() const { return !_meshStates.isEmpty() || (isActive() && getGeometry()->getGeometry()->getMeshes().empty()); } + bool isRenderable() const { return !_meshStates.isEmpty() || (isActive() && _renderGeometry->getMeshes().empty()); } bool isVisible() const { return _isVisible; } @@ -107,11 +107,11 @@ public: bool maybeStartBlender(); /// Sets blended vertices computed in a separate thread. - void setBlendedVertices(int blendNumber, const std::weak_ptr& geometry, + void setBlendedVertices(int blendNumber, const Geometry::WeakPointer& geometry, const QVector& vertices, const QVector& normals); - bool isLoaded() const { return _geometry && _geometry->getGeometry(); } - bool isCollisionLoaded() const { return _collisionGeometry && _collisionGeometry->getGeometry(); } + bool isLoaded() const { return (bool)_renderGeometry; } + bool isCollisionLoaded() const { return (bool)_collisionGeometry; } void setIsWireframe(bool isWireframe) { _isWireframe = isWireframe; } bool isWireframe() const { return _isWireframe; } @@ -128,18 +128,18 @@ public: virtual void updateClusterMatrices(glm::vec3 modelPosition, glm::quat modelOrientation); /// Returns a reference to the shared geometry. - const NetworkGeometry::Pointer& getGeometry() const { return _geometry; } + const Geometry::Pointer& getGeometry() const { return _renderGeometry; } /// Returns a reference to the shared collision geometry. - const NetworkGeometry::Pointer& getCollisionGeometry() const { return _collisionGeometry; } + const Geometry::Pointer& getCollisionGeometry() const { return _collisionGeometry; } - const QVariantMap getTextures() const { assert(isLoaded()); return _geometry->getGeometry()->getTextures(); } + const QVariantMap getTextures() const { assert(isLoaded()); return _renderGeometry->getTextures(); } void setTextures(const QVariantMap& textures); /// Provided as a convenience, will crash if !isLoaded() // And so that getGeometry() isn't chained everywhere - const FBXGeometry& getFBXGeometry() const { assert(isLoaded()); return getGeometry()->getGeometry()->getGeometry(); } + const FBXGeometry& getFBXGeometry() const { assert(isLoaded()); return _renderGeometry->getFBXGeometry(); } /// Provided as a convenience, will crash if !isCollisionLoaded() - const FBXGeometry& getCollisionFBXGeometry() const { assert(isCollisionLoaded()); return getCollisionGeometry()->getGeometry()->getGeometry(); } + const FBXGeometry& getCollisionFBXGeometry() const { assert(isCollisionLoaded()); return _collisionGeometry->getFBXGeometry(); } // Set the model to use for collisions. // Should only be called from the model's rendering thread to avoid access violations of changed geometry. @@ -263,7 +263,11 @@ protected: /// \return true if joint exists bool getJointPosition(int jointIndex, glm::vec3& position) const; - NetworkGeometry::Pointer _geometry; + Geometry::Pointer _renderGeometry; // only ever set by its watcher + Geometry::Pointer _collisionGeometry; // only ever set by its watcher + + GeometryResourceWatcher _renderWatcher; + GeometryResourceWatcher _collisionWatcher; glm::vec3 _translation; glm::quat _rotation; @@ -330,8 +334,6 @@ protected: void deleteGeometry(); void initJointTransforms(); - NetworkGeometry::Pointer _collisionGeometry; - float _pupilDilation; QVector _blendshapeCoefficients; @@ -373,9 +375,6 @@ protected: static AbstractViewStateInterface* _viewState; - bool _renderCollisionHull; - - QSet> _collisionRenderItemsSet; QMap _collisionRenderItems; @@ -395,7 +394,7 @@ protected: }; Q_DECLARE_METATYPE(ModelPointer) -Q_DECLARE_METATYPE(std::weak_ptr) +Q_DECLARE_METATYPE(Geometry::WeakPointer) /// Handle management of pending models that need blending class ModelBlender : public QObject, public Dependency { @@ -408,7 +407,7 @@ public: void noteRequiresBlend(ModelPointer model); public slots: - void setBlendedVertices(ModelPointer model, int blendNumber, const std::weak_ptr& geometry, + void setBlendedVertices(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry, const QVector& vertices, const QVector& normals); private: diff --git a/libraries/render-utils/src/glowLine.slg b/libraries/render-utils/src/glowLine.slg index 429cb8ee37..9af8eaa4d0 100644 --- a/libraries/render-utils/src/glowLine.slg +++ b/libraries/render-utils/src/glowLine.slg @@ -71,28 +71,24 @@ void main() { lineOrthogonal *= 0.02; gl_Position = gl_PositionIn[0]; - gl_Position.xy -= lineNormal; gl_Position.xy -= lineOrthogonal; outColor = inColor[0]; outLineDistance = vec3(-1.02, -1, gl_Position.z); EmitVertex(); gl_Position = gl_PositionIn[0]; - gl_Position.xy -= lineNormal; gl_Position.xy += lineOrthogonal; outColor = inColor[0]; outLineDistance = vec3(-1.02, 1, gl_Position.z); EmitVertex(); gl_Position = gl_PositionIn[1]; - gl_Position.xy += lineNormal; gl_Position.xy -= lineOrthogonal; outColor = inColor[1]; outLineDistance = vec3(1.02, -1, gl_Position.z); EmitVertex(); gl_Position = gl_PositionIn[1]; - gl_Position.xy += lineNormal; gl_Position.xy += lineOrthogonal; outColor = inColor[1]; outLineDistance = vec3(1.02, 1, gl_Position.z); diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 1a7d4b2328..28aba4a365 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -29,6 +29,9 @@ class OffscreenFlags : public QObject { Q_OBJECT Q_PROPERTY(bool navigationFocused READ isNavigationFocused WRITE setNavigationFocused NOTIFY navigationFocusedChanged) + // Allow scripts that are doing their own navigation support to disable navigation focus (i.e. handControllerPointer.js) + Q_PROPERTY(bool navigationFocusDisabled READ isNavigationFocusDisabled WRITE setNavigationFocusDisabled NOTIFY navigationFocusDisabledChanged) + public: OffscreenFlags(QObject* parent = nullptr) : QObject(parent) {} @@ -40,11 +43,21 @@ public: } } + bool isNavigationFocusDisabled() const { return _navigationFocusDisabled; } + void setNavigationFocusDisabled(bool disabled) { + if (_navigationFocusDisabled != disabled) { + _navigationFocusDisabled = disabled; + emit navigationFocusDisabledChanged(); + } + } + signals: void navigationFocusedChanged(); + void navigationFocusDisabledChanged(); private: bool _navigationFocused { false }; + bool _navigationFocusDisabled{ false }; }; QString fixupHifiUrl(const QString& urlString) { @@ -103,6 +116,10 @@ bool OffscreenUi::shouldSwallowShortcut(QEvent* event) { OffscreenUi::OffscreenUi() { } +QObject* OffscreenUi::getFlags() { + return offscreenFlags; +} + void OffscreenUi::create(QOpenGLContext* context) { OffscreenQmlSurface::create(context); auto rootContext = getRootContext(); @@ -392,7 +409,7 @@ QVariant OffscreenUi::waitForInputDialogResult(QQuickItem* inputDialog) { } bool OffscreenUi::navigationFocused() { - return offscreenFlags->isNavigationFocused(); + return !offscreenFlags->isNavigationFocusDisabled() && offscreenFlags->isNavigationFocused(); } void OffscreenUi::setNavigationFocused(bool focused) { diff --git a/libraries/ui/src/OffscreenUi.h b/libraries/ui/src/OffscreenUi.h index e1d552c978..2bd00bf612 100644 --- a/libraries/ui/src/OffscreenUi.h +++ b/libraries/ui/src/OffscreenUi.h @@ -55,7 +55,7 @@ public: bool eventFilter(QObject* originalDestination, QEvent* event) override; void addMenuInitializer(std::function f); - + QObject* getFlags(); QQuickItem* getDesktop(); QQuickItem* getToolWindow(); diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 4a515978c3..85feebda11 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -22,6 +22,7 @@ #include #include + #include #include @@ -284,7 +285,6 @@ void ViveControllerManager::InputDevice::handleHandController(float deltaTime, u vr::VRControllerState_t controllerState = vr::VRControllerState_t(); if (_system->GetControllerState(deviceIndex, &controllerState)) { - // process each button for (uint32_t i = 0; i < vr::k_EButton_Max; ++i) { auto mask = vr::ButtonMaskFromId((vr::EVRButtonId)i); @@ -342,6 +342,11 @@ void ViveControllerManager::InputDevice::handleAxisEvent(float deltaTime, uint32 _axisStateMap[isLeftHand ? LY : RY] = stick.y; } else if (axis == vr::k_EButton_SteamVR_Trigger) { _axisStateMap[isLeftHand ? LT : RT] = x; + // The click feeling on the Vive controller trigger represents a value of *precisely* 1.0, + // so we can expose that as an additional button + if (x >= 1.0f) { + _buttonPressedMap.insert(isLeftHand ? LT_CLICK : RT_CLICK); + } } } @@ -463,10 +468,15 @@ controller::Input::NamedVector ViveControllerManager::InputDevice::getAvailableI makePair(RS_X, "RSX"), makePair(RS_Y, "RSY"), + // triggers makePair(LT, "LT"), makePair(RT, "RT"), + // Trigger clicks + makePair(LT_CLICK, "LTClick"), + makePair(RT_CLICK, "RTClick"), + // low profile side grip button. makePair(LEFT_GRIP, "LeftGrip"), makePair(RIGHT_GRIP, "RightGrip"), diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index b66fca1448..66c9e10795 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -29,9 +29,8 @@ var WANT_DEBUG_SEARCH_NAME = null; var SPARK_MODEL_SCALE_FACTOR = 0.75; var TRIGGER_SMOOTH_RATIO = 0.1; // Time averaging of trigger - 0.0 disables smoothing -var TRIGGER_ON_VALUE = 0.4; // Squeezed just enough to activate search or near grab -var TRIGGER_GRAB_VALUE = 0.85; // Squeezed far enough to complete distant grab -var TRIGGER_OFF_VALUE = 0.15; +var TRIGGER_OFF_VALUE = 0.1; +var TRIGGER_ON_VALUE = TRIGGER_OFF_VALUE + 0.05; // Squeezed just enough to activate search or near grab var COLLIDE_WITH_AV_AFTER_RELEASE_DELAY = 0.25; // seconds @@ -43,10 +42,6 @@ var HAND_HEAD_MIX_RATIO = 0.0; // 0 = only use hands for search/move. 1 = only var PICK_WITH_HAND_RAY = true; -var DRAW_GRAB_BOXES = false; -var DRAW_HAND_SPHERES = false; -var DROP_WITHOUT_SHAKE = false; - var EQUIP_SPHERE_COLOR = { red: 179, green: 120, blue: 211 }; var EQUIP_SPHERE_ALPHA = 0.15; var EQUIP_SPHERE_SCALE_FACTOR = 0.65; @@ -80,8 +75,6 @@ var LINE_ENTITY_DIMENSIONS = { var LINE_LENGTH = 500; var PICK_MAX_DISTANCE = 500; // max length of pick-ray -var EQUIP_RADIUS_EMBIGGEN_FACTOR = 1.1; - // // near grabbing // @@ -209,7 +202,8 @@ CONTROLLER_STATE_MACHINE[STATE_NEAR_GRABBING] = { CONTROLLER_STATE_MACHINE[STATE_HOLD] = { name: "hold", enterMethod: "nearGrabbingEnter", - updateMethod: "nearGrabbing" + updateMethod: "nearGrabbing", + exitMethod: "holdExit" }; CONTROLLER_STATE_MACHINE[STATE_NEAR_TRIGGER] = { name: "trigger", @@ -230,6 +224,14 @@ function getTag() { return "grab-" + MyAvatar.sessionUUID; } +function colorPow(color, power) { + return { + red: Math.pow(color.red / 255.0, power) * 255, + green: Math.pow(color.green / 255.0, power) * 255, + blue: Math.pow(color.blue / 255.0, power) * 255, + }; +} + function entityHasActions(entityID) { return Entities.getActionIDs(entityID).length > 0; } @@ -269,6 +271,51 @@ function propsArePhysical(props) { return isPhysical; } +// currently disabled. +var USE_ATTACH_POINT_SETTINGS = false; + +var ATTACH_POINT_SETTINGS = "io.highfidelity.attachPoints"; +function getAttachPointSettings() { + try { + var str = Settings.getValue(ATTACH_POINT_SETTINGS); + print("getAttachPointSettings = " + str); + if (str === "false") { + return {}; + } else { + return JSON.parse(str); + } + } catch (err) { + print("Error parsing attachPointSettings: " + err); + return {}; + } +} +function setAttachPointSettings(attachPointSettings) { + var str = JSON.stringify(attachPointSettings); + print("setAttachPointSettings = " + str); + Settings.setValue(ATTACH_POINT_SETTINGS, str); +} +function getAttachPointForHotspotFromSettings(hotspot, hand) { + var attachPointSettings = getAttachPointSettings(); + var jointName = (hand === RIGHT_HAND) ? "RightHand" : "LeftHand"; + var joints = attachPointSettings[hotspot.key]; + if (joints) { + return joints[jointName]; + } else { + return undefined; + } +} +function storeAttachPointForHotspotInSettings(hotspot, hand, offsetPosition, offsetRotation) { + var attachPointSettings = getAttachPointSettings(); + var jointName = (hand === RIGHT_HAND) ? "RightHand" : "LeftHand"; + var joints = attachPointSettings[hotspot.key]; + if (!joints) { + joints = {}; + attachPointSettings[hotspot.key] = joints; + } + joints[jointName] = [offsetPosition, offsetRotation]; + setAttachPointSettings(attachPointSettings); +} + function removeMyAvatarFromCollidesWith(origCollidesWith) { var collidesWithSplit = origCollidesWith.split(","); // remove myAvatar from the array @@ -303,20 +350,32 @@ function restore2DMode() { // EntityPropertiesCache is a helper class that contains a cache of entity properties. // the hope is to prevent excess calls to Entity.getEntityProperties() +// +// usage: +// call EntityPropertiesCache.addEntities with all the entities that you are interested in. +// This will fetch their properties. Then call EntityPropertiesCache.getProps to receive an object +// containing a cache of all the properties previously fetched. function EntityPropertiesCache() { this.cache = {}; } EntityPropertiesCache.prototype.clear = function () { this.cache = {}; }; -EntityPropertiesCache.prototype.findEntities = function (position, radius) { - var entities = Entities.findEntities(position, radius); +EntityPropertiesCache.prototype.addEntity = function (entityID) { + var cacheEntry = this.cache[entityID]; + if (cacheEntry && cacheEntry.refCount) { + cacheEntry.refCount += 1; + } else { + this._updateCacheEntry(entityID); + } +}; +EntityPropertiesCache.prototype.addEntities = function (entities) { var _this = this; - entities.forEach(function (x) { - _this.updateEntity(x); + entities.forEach(function (entityID) { + _this.addEntity(entityID); }); }; -EntityPropertiesCache.prototype.updateEntity = function (entityID) { +EntityPropertiesCache.prototype._updateCacheEntry = function (entityID) { var props = Entities.getEntityProperties(entityID, GRABBABLE_PROPERTIES); // convert props.userData from a string to an object. @@ -329,11 +388,21 @@ EntityPropertiesCache.prototype.updateEntity = function (entityID) { } } props.userData = userData; + props.refCount = 1; this.cache[entityID] = props; }; -EntityPropertiesCache.prototype.getEntities = function () { - return Object.keys(this.cache); +EntityPropertiesCache.prototype.update = function () { + // delete any cacheEntries with zero refCounts. + var entities = Object.keys(this.cache); + for (var i = 0; i < entities.length; i++) { + var props = this.cache[entities[i]]; + if (props.refCount === 0) { + delete this.cache[entities[i]]; + } else { + props.refCount = 0; + } + } }; EntityPropertiesCache.prototype.getProps = function (entityID) { var obj = this.cache[entityID]; @@ -372,6 +441,158 @@ EntityPropertiesCache.prototype.getEquipHotspotsProps = function (entityID) { } }; +// global cache +var entityPropertiesCache = new EntityPropertiesCache(); + +// Each overlayInfoSet describes a single equip hotspot. +// It is an object with the following keys: +// timestamp - last time this object was updated, used to delete stale hotspot overlays. +// entityID - entity assosicated with this hotspot +// localPosition - position relative to the entity +// hotspot - hotspot object +// overlays - array of overlay objects created by Overlay.addOverlay() +// currentSize - current animated scale value +// targetSize - the target of our scale animations +// type - "sphere" or "model". +function EquipHotspotBuddy() { + // holds map from {string} hotspot.key to {object} overlayInfoSet. + this.map = {}; + + // array of all hotspots that are highlighed. + this.highlightedHotspots = []; +} +EquipHotspotBuddy.prototype.clear = function () { + var keys = Object.keys(this.map); + for (var i = 0; i < keys.length; i++) { + var overlayInfoSet = this.map[keys[i]]; + this.deleteOverlayInfoSet(overlayInfoSet); + } + this.map = {}; + this.highlightedHotspots = []; +}; +EquipHotspotBuddy.prototype.highlightHotspot = function (hotspot) { + this.highlightedHotspots.push(hotspot.key); +}; +EquipHotspotBuddy.prototype.updateHotspot = function (hotspot, timestamp) { + var overlayInfoSet = this.map[hotspot.key]; + if (!overlayInfoSet) { + // create a new overlayInfoSet + overlayInfoSet = { + timestamp: timestamp, + entityID: hotspot.entityID, + localPosition: hotspot.localPosition, + hotspot: hotspot, + currentSize: 0, + targetSize: 1, + overlays: [] + }; + + var diameter = hotspot.radius * 2; + + if (hotspot.modelURL) { + // override default sphere with a user specified model + overlayInfoSet.overlays.push(Overlays.addOverlay("model", { + url: hotspot.modelURL, + position: hotspot.worldPosition, + rotation: {x: 0, y: 0, z: 0, w: 1}, + dimensions: diameter * EQUIP_SPHERE_SCALE_FACTOR, + scale: hotspot.modelScale, + ignoreRayIntersection: true + })); + overlayInfoSet.type = "model"; + } else { + // default sphere overlay + overlayInfoSet.overlays.push(Overlays.addOverlay("sphere", { + position: hotspot.worldPosition, + rotation: {x: 0, y: 0, z: 0, w: 1}, + dimensions: diameter * EQUIP_SPHERE_SCALE_FACTOR, + color: EQUIP_SPHERE_COLOR, + alpha: EQUIP_SPHERE_ALPHA, + solid: true, + visible: true, + ignoreRayIntersection: true, + drawInFront: false + })); + overlayInfoSet.type = "sphere"; + } + + this.map[hotspot.key] = overlayInfoSet; + } else { + overlayInfoSet.timestamp = timestamp; + } +}; +EquipHotspotBuddy.prototype.updateHotspots = function (hotspots, timestamp) { + var _this = this; + hotspots.forEach(function (hotspot) { + _this.updateHotspot(hotspot, timestamp); + }); + this.highlightedHotspots = []; +}; +EquipHotspotBuddy.prototype.update = function (deltaTime, timestamp) { + + var HIGHLIGHT_SIZE = 1.1; + var NORMAL_SIZE = 1.0; + + var keys = Object.keys(this.map); + for (var i = 0; i < keys.length; i++) { + var overlayInfoSet = this.map[keys[i]]; + + // this overlayInfo is highlighted. + if (this.highlightedHotspots.indexOf(keys[i]) != -1) { + overlayInfoSet.targetSize = HIGHLIGHT_SIZE; + } else { + overlayInfoSet.targetSize = NORMAL_SIZE; + } + + // start to fade out this hotspot. + if (overlayInfoSet.timestamp != timestamp) { + // because this item timestamp has expired, it might not be in the cache anymore.... + entityPropertiesCache.addEntity(overlayInfoSet.entityID); + overlayInfoSet.targetSize = 0; + } + + // animate the size. + var SIZE_TIMESCALE = 0.1; + var tau = deltaTime / SIZE_TIMESCALE; + if (tau > 1.0) { + tau = 1.0; + } + overlayInfoSet.currentSize += (overlayInfoSet.targetSize - overlayInfoSet.currentSize) * tau; + + if (overlayInfoSet.timestamp != timestamp && overlayInfoSet.currentSize <= 0.05) { + // this is an old overlay, that has finished fading out, delete it! + overlayInfoSet.overlays.forEach(function (overlay) { + Overlays.deleteOverlay(overlay); + }); + delete this.map[keys[i]]; + } else { + // update overlay position, rotation to follow the object it's attached to. + + var props = entityPropertiesCache.getProps(overlayInfoSet.entityID); + var entityXform = new Xform(props.rotation, props.position); + var position = entityXform.xformPoint(overlayInfoSet.localPosition); + + var dimensions; + if (overlayInfoSet.type == "sphere") { + dimensions = overlayInfoSet.hotspot.radius * 2 * overlayInfoSet.currentSize * EQUIP_SPHERE_SCALE_FACTOR; + } else { + dimensions = overlayInfoSet.hotspot.radius * 2 * overlayInfoSet.currentSize; + } + + overlayInfoSet.overlays.forEach(function (overlay) { + Overlays.editOverlay(overlay, { + position: position, + rotation: props.rotation, + dimensions: dimensions + }); + }); + } + } +}; + +// global EquipHotspotBuddy instance +var equipHotspotBuddy = new EquipHotspotBuddy(); + function MyController(hand) { this.hand = hand; if (this.hand === RIGHT_HAND) { @@ -393,6 +614,7 @@ function MyController(hand) { this.entityActivated = false; this.triggerValue = 0; // rolling average of trigger value + this.triggerClicked = false; this.rawTriggerValue = 0; this.rawSecondaryValue = 0; this.rawThumbValue = 0; @@ -421,8 +643,6 @@ function MyController(hand) { this.lastPickTime = 0; this.lastUnequipCheckTime = 0; - this.entityPropertyCache = new EntityPropertiesCache(); - this.equipOverlayInfoSetMap = {}; var _this = this; @@ -434,7 +654,7 @@ function MyController(hand) { return (-1 !== suppressedIn2D.indexOf(this.state)) && isIn2DMode(); }; - this.update = function (deltaTime) { + this.update = function (deltaTime, timestamp) { this.updateSmoothedTrigger(); @@ -447,7 +667,7 @@ function MyController(hand) { var updateMethodName = CONTROLLER_STATE_MACHINE[this.state].updateMethod; var updateMethod = this[updateMethodName]; if (updateMethod) { - updateMethod.call(this, deltaTime); + updateMethod.call(this, deltaTime, timestamp); } else { print("WARNING: could not find updateMethod for state " + stateToName(this.state)); } @@ -547,23 +767,33 @@ function MyController(hand) { var SEARCH_SPHERE_ALPHA = 0.5; this.searchSphereOn = function (location, size, color) { + + var rotation = Quat.lookAt(location, Camera.getPosition(), Vec3.UP); + var brightColor = colorPow(color, 0.06); if (this.searchSphere === null) { var sphereProperties = { position: location, - size: size, - color: color, - alpha: SEARCH_SPHERE_ALPHA, + rotation: rotation, + outerRadius: size * 1.2, + innerColor: brightColor, + outerColor: color, + innerAlpha: 0.9, + outerAlpha: 0.0, solid: true, ignoreRayIntersection: true, drawInFront: true, // Even when burried inside of something, show it. visible: true }; - this.searchSphere = Overlays.addOverlay("sphere", sphereProperties); + this.searchSphere = Overlays.addOverlay("circle3d", sphereProperties); } else { Overlays.editOverlay(this.searchSphere, { position: location, - size: size, - color: color, + rotation: rotation, + innerColor: brightColor, + outerColor: color, + innerAlpha: 1.0, + outerAlpha: 0.0, + outerRadius: size * 1.2, visible: true }); } @@ -852,6 +1082,10 @@ function MyController(hand) { _this.rawTriggerValue = value; }; + this.triggerClick = function (value) { + _this.triggerClicked = value; + }; + this.secondaryPress = function (value) { _this.rawSecondaryValue = value; }; @@ -864,7 +1098,7 @@ function MyController(hand) { }; this.triggerSmoothedGrab = function () { - return this.triggerValue > TRIGGER_GRAB_VALUE; + return this.triggerClicked; }; this.triggerSmoothedSqueezed = function () { @@ -903,7 +1137,7 @@ function MyController(hand) { return _this.rawThumbValue < THUMB_ON_VALUE; }; - this.off = function () { + this.off = function (deltaTime, timestamp) { if (this.triggerSmoothedReleased()) { this.waitForTriggerRelease = false; } @@ -917,17 +1151,18 @@ function MyController(hand) { } } - this.entityPropertyCache.clear(); - this.entityPropertyCache.findEntities(this.getHandPosition(), EQUIP_HOTSPOT_RENDER_RADIUS); - var candidateEntities = this.entityPropertyCache.getEntities(); - + var candidateEntities = Entities.findEntities(this.getHandPosition(), EQUIP_HOTSPOT_RENDER_RADIUS); + entityPropertiesCache.addEntities(candidateEntities); var potentialEquipHotspot = this.chooseBestEquipHotspot(candidateEntities); if (!this.waitForTriggerRelease) { this.updateEquipHaptics(potentialEquipHotspot); } var nearEquipHotspots = this.chooseNearEquipHotspots(candidateEntities, EQUIP_HOTSPOT_RENDER_RADIUS); - this.updateEquipHotspotRendering(nearEquipHotspots, potentialEquipHotspot); + equipHotspotBuddy.updateHotspots(nearEquipHotspots, timestamp); + if (potentialEquipHotspot) { + equipHotspotBuddy.highlightHotspot(potentialEquipHotspot); + } }; this.clearEquipHaptics = function () { @@ -942,95 +1177,6 @@ function MyController(hand) { this.prevPotentialEquipHotspot = potentialEquipHotspot; }; - this.clearEquipHotspotRendering = function () { - var keys = Object.keys(this.equipOverlayInfoSetMap); - for (var i = 0; i < keys.length; i++) { - var overlayInfoSet = this.equipOverlayInfoSetMap[keys[i]]; - this.deleteOverlayInfoSet(overlayInfoSet); - } - this.equipOverlayInfoSetMap = {}; - }; - - this.createOverlayInfoSet = function (hotspot, timestamp) { - var overlayInfoSet = { - timestamp: timestamp, - entityID: hotspot.entityID, - localPosition: hotspot.localPosition, - hotspot: hotspot, - overlays: [] - }; - - var diameter = hotspot.radius * 2; - - overlayInfoSet.overlays.push(Overlays.addOverlay("sphere", { - position: hotspot.worldPosition, - rotation: {x: 0, y: 0, z: 0, w: 1}, - dimensions: diameter * EQUIP_SPHERE_SCALE_FACTOR, - color: EQUIP_SPHERE_COLOR, - alpha: EQUIP_SPHERE_ALPHA, - solid: true, - visible: true, - ignoreRayIntersection: true, - drawInFront: false - })); - - return overlayInfoSet; - }; - - this.updateOverlayInfoSet = function (overlayInfoSet, timestamp, potentialEquipHotspot) { - overlayInfoSet.timestamp = timestamp; - - var diameter = overlayInfoSet.hotspot.radius * 2; - - // embiggen the overlays if it maches the potentialEquipHotspot - if (potentialEquipHotspot && overlayInfoSet.entityID == potentialEquipHotspot.entityID && - Vec3.equal(overlayInfoSet.localPosition, potentialEquipHotspot.localPosition)) { - diameter = diameter * EQUIP_RADIUS_EMBIGGEN_FACTOR; - } - - var props = _this.entityPropertyCache.getProps(overlayInfoSet.entityID); - var entityXform = new Xform(props.rotation, props.position); - var position = entityXform.xformPoint(overlayInfoSet.localPosition); - - overlayInfoSet.overlays.forEach(function (overlay) { - Overlays.editOverlay(overlay, { - position: position, - rotation: props.rotation, - dimensions: diameter * EQUIP_SPHERE_SCALE_FACTOR - }); - }); - }; - - this.deleteOverlayInfoSet = function (overlayInfoSet) { - overlayInfoSet.overlays.forEach(function (overlay) { - Overlays.deleteOverlay(overlay); - }); - }; - - this.updateEquipHotspotRendering = function (hotspots, potentialEquipHotspot) { - var now = Date.now(); - var _this = this; - - hotspots.forEach(function (hotspot) { - var overlayInfoSet = _this.equipOverlayInfoSetMap[hotspot.key]; - if (overlayInfoSet) { - _this.updateOverlayInfoSet(overlayInfoSet, now, potentialEquipHotspot); - } else { - _this.equipOverlayInfoSetMap[hotspot.key] = _this.createOverlayInfoSet(hotspot, now); - } - }); - - // delete sets with old timestamps. - var keys = Object.keys(this.equipOverlayInfoSetMap); - for (var i = 0; i < keys.length; i++) { - var overlayInfoSet = this.equipOverlayInfoSetMap[keys[i]]; - if (overlayInfoSet.timestamp !== now) { - this.deleteOverlayInfoSet(overlayInfoSet); - delete this.equipOverlayInfoSetMap[keys[i]]; - } - } - }; - // Performs ray pick test from the hand controller into the world // @param {number} which hand to use, RIGHT_HAND or LEFT_HAND // @returns {object} returns object with two keys entityID and distance @@ -1089,7 +1235,7 @@ function MyController(hand) { }; this.entityWantsTrigger = function (entityID) { - var grabbableProps = this.entityPropertyCache.getGrabbableProps(entityID); + var grabbableProps = entityPropertiesCache.getGrabbableProps(entityID); return grabbableProps && grabbableProps.wantsTrigger; }; @@ -1103,11 +1249,13 @@ function MyController(hand) { // * radius {number} radius of equip hotspot // * joints {Object} keys are joint names values are arrays of two elements: // offset position {Vec3} and offset rotation {Quat}, both are in the coordinate system of the joint. + // * modelURL {string} url for model to use instead of default sphere. + // * modelScale {Vec3} scale factor for model this.collectEquipHotspots = function (entityID) { var result = []; - var props = this.entityPropertyCache.getProps(entityID); + var props = entityPropertiesCache.getProps(entityID); var entityXform = new Xform(props.rotation, props.position); - var equipHotspotsProps = this.entityPropertyCache.getEquipHotspotsProps(entityID); + var equipHotspotsProps = entityPropertiesCache.getEquipHotspotsProps(entityID); if (equipHotspotsProps && equipHotspotsProps.length > 0) { var i, length = equipHotspotsProps.length; for (i = 0; i < length; i++) { @@ -1119,12 +1267,14 @@ function MyController(hand) { localPosition: hotspot.position, worldPosition: entityXform.xformPoint(hotspot.position), radius: hotspot.radius, - joints: hotspot.joints + joints: hotspot.joints, + modelURL: hotspot.modelURL, + modelScale: hotspot.modelScale }); } } } else { - var wearableProps = this.entityPropertyCache.getWearableProps(entityID); + var wearableProps = entityPropertiesCache.getWearableProps(entityID); if (wearableProps && wearableProps.joints) { result.push({ key: entityID.toString() + "0", @@ -1132,7 +1282,9 @@ function MyController(hand) { localPosition: {x: 0, y: 0, z: 0}, worldPosition: entityXform.pos, radius: EQUIP_RADIUS, - joints: wearableProps.joints + joints: wearableProps.joints, + modelURL: null, + modelScale: null }); } } @@ -1140,8 +1292,8 @@ function MyController(hand) { }; this.hotspotIsEquippable = function (hotspot) { - var props = this.entityPropertyCache.getProps(hotspot.entityID); - var grabProps = this.entityPropertyCache.getGrabProps(hotspot.entityID); + var props = entityPropertiesCache.getProps(hotspot.entityID); + var grabProps = entityPropertiesCache.getGrabProps(hotspot.entityID); var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); var refCount = ("refCount" in grabProps) ? grabProps.refCount : 0; @@ -1159,9 +1311,9 @@ function MyController(hand) { }; this.entityIsGrabbable = function (entityID) { - var grabbableProps = this.entityPropertyCache.getGrabbableProps(entityID); - var grabProps = this.entityPropertyCache.getGrabProps(entityID); - var props = this.entityPropertyCache.getProps(entityID); + var grabbableProps = entityPropertiesCache.getGrabbableProps(entityID); + var grabProps = entityPropertiesCache.getGrabProps(entityID); + var props = entityPropertiesCache.getProps(entityID); var physical = propsArePhysical(props); var grabbable = false; var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); @@ -1215,7 +1367,7 @@ function MyController(hand) { return false; } - var props = this.entityPropertyCache.getProps(entityID); + var props = entityPropertiesCache.getProps(entityID); var distance = Vec3.distance(props.position, handPosition); var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); @@ -1253,7 +1405,7 @@ function MyController(hand) { return false; } - var props = this.entityPropertyCache.getProps(entityID); + var props = entityPropertiesCache.getProps(entityID); var distance = Vec3.distance(props.position, handPosition); var debug = (WANT_DEBUG_SEARCH_NAME && props.name === WANT_DEBUG_SEARCH_NAME); @@ -1294,7 +1446,7 @@ function MyController(hand) { } }; - this.search = function () { + this.search = function (deltaTime, timestamp) { var _this = this; var name; @@ -1311,16 +1463,15 @@ function MyController(hand) { var handPosition = this.getHandPosition(); - this.entityPropertyCache.clear(); - this.entityPropertyCache.findEntities(handPosition, NEAR_GRAB_RADIUS); - var candidateEntities = this.entityPropertyCache.getEntities(); + var candidateEntities = Entities.findEntities(handPosition, NEAR_GRAB_RADIUS); + entityPropertiesCache.addEntities(candidateEntities); var potentialEquipHotspot = this.chooseBestEquipHotspot(candidateEntities); if (potentialEquipHotspot) { if (this.triggerSmoothedGrab()) { this.grabbedHotspot = potentialEquipHotspot; this.grabbedEntity = potentialEquipHotspot.entityID; - this.setState(STATE_HOLD, "eqipping '" + this.entityPropertyCache.getProps(this.grabbedEntity).name + "'"); + this.setState(STATE_HOLD, "eqipping '" + entityPropertiesCache.getProps(this.grabbedEntity).name + "'"); return; } } @@ -1332,7 +1483,7 @@ function MyController(hand) { var rayPickInfo = this.calcRayPickInfo(this.hand); if (rayPickInfo.entityID) { this.intersectionDistance = rayPickInfo.distance; - this.entityPropertyCache.updateEntity(rayPickInfo.entityID); + entityPropertiesCache.addEntity(rayPickInfo.entityID); if (this.entityIsGrabbable(rayPickInfo.entityID) && rayPickInfo.distance < NEAR_GRAB_PICK_RADIUS) { grabbableEntities.push(rayPickInfo.entityID); } @@ -1346,12 +1497,12 @@ function MyController(hand) { if (grabbableEntities.length > 0) { // sort by distance grabbableEntities.sort(function (a, b) { - var aDistance = Vec3.distance(_this.entityPropertyCache.getProps(a).position, handPosition); - var bDistance = Vec3.distance(_this.entityPropertyCache.getProps(b).position, handPosition); + var aDistance = Vec3.distance(entityPropertiesCache.getProps(a).position, handPosition); + var bDistance = Vec3.distance(entityPropertiesCache.getProps(b).position, handPosition); return aDistance - bDistance; }); entity = grabbableEntities[0]; - name = this.entityPropertyCache.getProps(entity).name; + name = entityPropertiesCache.getProps(entity).name; this.grabbedEntity = entity; if (this.entityWantsTrigger(entity)) { if (this.triggerSmoothedGrab()) { @@ -1362,8 +1513,8 @@ function MyController(hand) { } } else { if (this.triggerSmoothedGrab()) { - var props = this.entityPropertyCache.getProps(entity); - var grabProps = this.entityPropertyCache.getGrabProps(entity); + var props = entityPropertiesCache.getProps(entity); + var grabProps = entityPropertiesCache.getGrabProps(entity); var refCount = grabProps.refCount ? grabProps.refCount : 0; if (refCount >= 1) { // if another person is holding the object, remember to restore the @@ -1383,7 +1534,7 @@ function MyController(hand) { if (rayPickInfo.entityID) { entity = rayPickInfo.entityID; - name = this.entityPropertyCache.getProps(entity).name; + name = entityPropertiesCache.getProps(entity).name; if (this.entityWantsTrigger(entity)) { if (this.triggerSmoothedGrab()) { this.grabbedEntity = entity; @@ -1406,7 +1557,10 @@ function MyController(hand) { this.updateEquipHaptics(potentialEquipHotspot); var nearEquipHotspots = this.chooseNearEquipHotspots(candidateEntities, EQUIP_HOTSPOT_RENDER_RADIUS); - this.updateEquipHotspotRendering(nearEquipHotspots, potentialEquipHotspot); + equipHotspotBuddy.updateHotspots(nearEquipHotspots, timestamp); + if (potentialEquipHotspot) { + equipHotspotBuddy.highlightHotspot(potentialEquipHotspot); + } // search line visualizations if (USE_ENTITY_LINES_FOR_SEARCHING === true) { @@ -1435,7 +1589,6 @@ function MyController(hand) { this.distanceHoldingEnter = function () { - this.clearEquipHotspotRendering(); this.clearEquipHaptics(); // controller pose is in avatar frame @@ -1495,7 +1648,7 @@ function MyController(hand) { this.previousControllerRotation = controllerRotation; }; - this.distanceHolding = function () { + this.distanceHolding = function (deltaTime, timestamp) { if (this.triggerSmoothedReleased()) { this.callEntityMethodOnGrabbed("releaseGrab"); this.setState(STATE_OFF, "trigger released"); @@ -1664,47 +1817,35 @@ function MyController(hand) { }; this.dropGestureReset = function () { - this.fastHandMoveDetected = false; - this.fastHandMoveTimer = 0; + this.prevHandIsUpsideDown = false; }; this.dropGestureProcess = function (deltaTime) { var standardControllerValue = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; var pose = Controller.getPoseValue(standardControllerValue); - var worldHandVelocity = Vec3.multiplyQbyV(MyAvatar.orientation, pose.velocity); var worldHandRotation = Quat.multiply(MyAvatar.orientation, pose.rotation); - if (this.fastHandMoveDetected) { - this.fastHandMoveTimer -= deltaTime; - } - if (this.fastHandMoveTimer < 0) { - this.fastHandMoveDetected = false; - } - var FAST_HAND_SPEED_REST_TIME = 1; // sec - var FAST_HAND_SPEED_THRESHOLD = 0.4; // m/sec - if (Vec3.length(worldHandVelocity) > FAST_HAND_SPEED_THRESHOLD) { - this.fastHandMoveDetected = true; - this.fastHandMoveTimer = FAST_HAND_SPEED_REST_TIME; - } - var localHandUpAxis = this.hand === RIGHT_HAND ? {x: 1, y: 0, z: 0} : {x: -1, y: 0, z: 0}; var worldHandUpAxis = Vec3.multiplyQbyV(worldHandRotation, localHandUpAxis); var DOWN = {x: 0, y: -1, z: 0}; - var ROTATION_THRESHOLD = Math.cos(Math.PI / 8); + + var DROP_ANGLE = Math.PI / 7; + var HYSTERESIS_FACTOR = 1.1; + var ROTATION_ENTER_THRESHOLD = Math.cos(DROP_ANGLE); + var ROTATION_EXIT_THRESHOLD = Math.cos(DROP_ANGLE * HYSTERESIS_FACTOR); + var rotationThreshold = this.prevHandIsUpsideDown ? ROTATION_EXIT_THRESHOLD : ROTATION_ENTER_THRESHOLD; var handIsUpsideDown = false; - if (Vec3.dot(worldHandUpAxis, DOWN) > ROTATION_THRESHOLD) { + if (Vec3.dot(worldHandUpAxis, DOWN) > rotationThreshold) { handIsUpsideDown = true; } - var WANT_DEBUG = false; - if (WANT_DEBUG) { - print("zAxis = " + worldHandUpAxis.x + ", " + worldHandUpAxis.y + ", " + worldHandUpAxis.z); - print("dot = " + Vec3.dot(worldHandUpAxis, DOWN) + ", ROTATION_THRESHOLD = " + ROTATION_THRESHOLD); - print("handMove = " + this.fastHandMoveDetected + ", handIsUpsideDown = " + handIsUpsideDown); + if (handIsUpsideDown != this.prevHandIsUpsideDown) { + this.prevHandIsUpsideDown = handIsUpsideDown; + Controller.triggerShortHapticPulse(0.5, this.hand); } - return (DROP_WITHOUT_SHAKE || this.fastHandMoveDetected) && handIsUpsideDown; + return handIsUpsideDown; }; this.nearGrabbingEnter = function () { @@ -1714,7 +1855,6 @@ function MyController(hand) { this.dropGestureReset(); this.clearEquipHaptics(); - this.clearEquipHotspotRendering(); Controller.triggerShortHapticPulse(1.0, this.hand); @@ -1743,11 +1883,18 @@ function MyController(hand) { // if an object is "equipped" and has a predefined offset, use it. this.ignoreIK = grabbableData.ignoreIK ? grabbableData.ignoreIK : false; - var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"; - if (this.grabbedHotspot.joints[handJointName]) { - this.offsetPosition = this.grabbedHotspot.joints[handJointName][0]; - this.offsetRotation = this.grabbedHotspot.joints[handJointName][1]; + var offsets = USE_ATTACH_POINT_SETTINGS && getAttachPointForHotspotFromSettings(this.grabbedHotspot, this.hand); + if (offsets) { + this.offsetPosition = offsets[0]; + this.offsetRotation = offsets[1]; hasPresetPosition = true; + } else { + var handJointName = this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"; + if (this.grabbedHotspot.joints[handJointName]) { + this.offsetPosition = this.grabbedHotspot.joints[handJointName][0]; + this.offsetRotation = this.grabbedHotspot.joints[handJointName][1]; + hasPresetPosition = true; + } } } else { this.ignoreIK = false; @@ -1816,7 +1963,7 @@ function MyController(hand) { this.currentAngularVelocity = ZERO_VEC; }; - this.nearGrabbing = function (deltaTime) { + this.nearGrabbing = function (deltaTime, timestamp) { var dropDetected = this.dropGestureProcess(deltaTime); @@ -1827,6 +1974,14 @@ function MyController(hand) { } if (this.state == STATE_HOLD) { + + // highlight the grabbed hotspot when the dropGesture is detected. + if (dropDetected) { + entityPropertiesCache.addEntity(this.grabbedHotspot.entityID); + equipHotspotBuddy.updateHotspot(this.grabbedHotspot, timestamp); + equipHotspotBuddy.highlightHotspot(this.grabbedHotspot); + } + if (dropDetected && this.triggerSmoothedGrab()) { this.callEntityMethodOnGrabbed("releaseEquip"); this.setState(STATE_OFF, "drop gesture detected"); @@ -1933,9 +2088,24 @@ function MyController(hand) { } }; + this.holdExit = function () { + // store the offset attach points into preferences. + if (USE_ATTACH_POINT_SETTINGS && this.grabbedHotspot && this.grabbedEntity) { + entityPropertiesCache.addEntity(this.grabbedEntity); + var props = entityPropertiesCache.getProps(this.grabbedEntity); + var entityXform = new Xform(props.rotation, props.position); + var avatarXform = new Xform(MyAvatar.orientation, MyAvatar.position); + var handRot = (this.hand === RIGHT_HAND) ? MyAvatar.getRightPalmRotation() : MyAvatar.getLeftPalmRotation(); + var avatarHandPos = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition; + var palmXform = new Xform(handRot, avatarXform.xformPoint(avatarHandPos)); + var offsetXform = Xform.mul(palmXform.inv(), entityXform); + + storeAttachPointForHotspotInSettings(this.grabbedHotspot, this.hand, offsetXform.pos, offsetXform.rot); + } + }; + this.nearTriggerEnter = function () { - this.clearEquipHotspotRendering(); this.clearEquipHaptics(); Controller.triggerShortHapticPulse(1.0, this.hand); @@ -1943,13 +2113,12 @@ function MyController(hand) { }; this.farTriggerEnter = function () { - this.clearEquipHotspotRendering(); this.clearEquipHaptics(); this.callEntityMethodOnGrabbed("startFarTrigger"); }; - this.nearTrigger = function () { + this.nearTrigger = function (deltaTime, timestamp) { if (this.triggerSmoothedReleased()) { this.callEntityMethodOnGrabbed("stopNearTrigger"); this.setState(STATE_OFF, "trigger released"); @@ -1958,7 +2127,7 @@ function MyController(hand) { this.callEntityMethodOnGrabbed("continueNearTrigger"); }; - this.farTrigger = function () { + this.farTrigger = function (deltaTime, timestamp) { if (this.triggerSmoothedReleased()) { this.callEntityMethodOnGrabbed("stopFarTrigger"); this.setState(STATE_OFF, "trigger released"); @@ -2006,7 +2175,7 @@ function MyController(hand) { // and rotation of the held thing to help content creators set the userData. var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, {}); if (grabData.refCount > 1) { - grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "localRotation"]); + var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "localRotation"]); if (grabbedProperties && grabbedProperties.localPosition && grabbedProperties.localRotation) { print((this.hand === RIGHT_HAND ? '"LeftHand"' : '"RightHand"') + ":" + '[{"x":' + grabbedProperties.localPosition.x + ', "y":' + grabbedProperties.localPosition.y + @@ -2210,7 +2379,7 @@ function MyController(hand) { delayedDeactivateTimeout = null; _this.delayedDeactivateEntity(delayedEntityID, delayedCollidesWith); return delayedCollidesWith; - } + }; delayedDeactivateTimeout = Script.setTimeout(delayedDeactivateFunc, COLLIDE_WITH_AV_AFTER_RELEASE_DELAY * MSECS_PER_SEC); delayedDeactivateEntityID = entityID; @@ -2293,7 +2462,10 @@ var MAPPING_NAME = "com.highfidelity.handControllerGrab"; var mapping = Controller.newMapping(MAPPING_NAME); mapping.from([Controller.Standard.RT]).peek().to(rightController.triggerPress); +mapping.from([Controller.Standard.RTClick]).peek().to(rightController.triggerClick); + mapping.from([Controller.Standard.LT]).peek().to(leftController.triggerPress); +mapping.from([Controller.Standard.LTClick]).peek().to(leftController.triggerClick); mapping.from([Controller.Standard.RB]).peek().to(rightController.secondaryPress); mapping.from([Controller.Standard.LB]).peek().to(leftController.secondaryPress); @@ -2310,12 +2482,16 @@ Controller.enableMapping(MAPPING_NAME); var handToDisable = 'none'; function update(deltaTime) { + var timestamp = Date.now(); + if (handToDisable !== LEFT_HAND && handToDisable !== 'both') { - leftController.update(deltaTime); + leftController.update(deltaTime, timestamp); } if (handToDisable !== RIGHT_HAND && handToDisable !== 'both') { - rightController.update(deltaTime); + rightController.update(deltaTime, timestamp); } + equipHotspotBuddy.update(deltaTime, timestamp); + entityPropertiesCache.update(); } Messages.subscribe('Hifi-Hand-Disabler'); @@ -2385,38 +2561,7 @@ function cleanup() { leftController.cleanup(); Controller.disableMapping(MAPPING_NAME); Reticle.setVisible(true); - Menu.removeMenuItem("Developer > Hands", "Drop Without Shake"); } Script.scriptEnding.connect(cleanup); Script.update.connect(update); - -if (!Menu.menuExists("Developer > Grab Script")) { - Menu.addMenu("Developer > Grab Script"); -} - -Menu.addMenuItem({ - menuName: "Developer > Grab Script", - menuItemName: "Drop Without Shake", - isCheckable: true, - isChecked: DROP_WITHOUT_SHAKE -}); - -Menu.addMenuItem({ - menuName: "Developer > Grab Script", - menuItemName: "Draw Grab Boxes", - isCheckable: true, - isChecked: DRAW_GRAB_BOXES -}); - -function handleMenuItemEvent(menuItem) { - if (menuItem === "Drop Without Shake") { - DROP_WITHOUT_SHAKE = Menu.isOptionChecked("Drop Without Shake"); - } - if (menuItem === "Draw Grab Boxes") { - DRAW_GRAB_BOXES = Menu.isOptionChecked("Draw Grab Boxes"); - DRAW_HAND_SPHERES = DRAW_GRAB_BOXES; - } -} - -Menu.menuItemEvent.connect(handleMenuItemEvent); diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index cb35414cf8..dab6438efa 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -32,6 +32,7 @@ function setupHandler(event, handler) { event.disconnect(handler); }); } + // If some capability is not available until expiration milliseconds after the last update. function TimeLock(expiration) { var last = 0; @@ -42,6 +43,7 @@ function TimeLock(expiration) { return ((optionalNow || Date.now()) - last) > expiration; }; } + var handControllerLockOut = new TimeLock(2000); function Trigger(label) { @@ -49,23 +51,23 @@ function Trigger(label) { var that = this; that.label = label; that.TRIGGER_SMOOTH_RATIO = 0.1; // Time averaging of trigger - 0.0 disables smoothing - that.TRIGGER_ON_VALUE = 0.4; // Squeezed just enough to activate search or near grab - that.TRIGGER_GRAB_VALUE = 0.85; // Squeezed far enough to complete distant grab - that.TRIGGER_OFF_VALUE = 0.15; + that.TRIGGER_OFF_VALUE = 0.10; + that.TRIGGER_ON_VALUE = that.TRIGGER_OFF_VALUE + 0.05; // Squeezed just enough to activate search or near grab that.rawTriggerValue = 0; that.triggerValue = 0; // rolling average of trigger value - that.triggerPress = function (value) { - that.rawTriggerValue = value; - }; + that.triggerClicked = false; + that.triggerClick = function (value) { that.triggerClicked = value; }; + that.triggerPress = function (value) { that.rawTriggerValue = value; }; that.updateSmoothedTrigger = function () { // e.g., call once/update for effect var triggerValue = that.rawTriggerValue; // smooth out trigger value that.triggerValue = (that.triggerValue * that.TRIGGER_SMOOTH_RATIO) + (triggerValue * (1.0 - that.TRIGGER_SMOOTH_RATIO)); + OffscreenFlags.navigationFocusDisabled = that.triggerValue != 0.0; }; // Current smoothed state, without hysteresis. Answering booleans. - that.triggerSmoothedGrab = function () { - return that.triggerValue > that.TRIGGER_GRAB_VALUE; + that.triggerSmoothedClick = function () { + return that.triggerClicked; }; that.triggerSmoothedSqueezed = function () { return that.triggerValue > that.TRIGGER_ON_VALUE; @@ -81,7 +83,7 @@ function Trigger(label) { that.updateSmoothedTrigger(); // The first two are independent of previous state: - if (that.triggerSmoothedGrab()) { + if (that.triggerSmoothedClick()) { state = 'full'; } else if (that.triggerSmoothedReleased()) { state = null; @@ -118,6 +120,10 @@ function ignoreMouseActivity() { if (!Reticle.allowMouseCapture) { return true; } + var pos = Reticle.position; + if (pos.x == -1 && pos.y == -1) { + return true; + } // Only we know if we moved it, which is why this script has to replace depthReticle.js if (!weMovedReticle) { return false; @@ -365,6 +371,8 @@ Script.scriptEnding.connect(clickMapping.disable); // Gather the trigger data for smoothing. clickMapping.from(Controller.Standard.RT).peek().to(rightTrigger.triggerPress); clickMapping.from(Controller.Standard.LT).peek().to(leftTrigger.triggerPress); +clickMapping.from(Controller.Standard.RTClick).peek().to(rightTrigger.triggerClick); +clickMapping.from(Controller.Standard.LTClick).peek().to(leftTrigger.triggerClick); // Full smoothed trigger is a click. function isPointingAtOverlayStartedNonFullTrigger(trigger) { // true if isPointingAtOverlay AND we were NOT full triggered when we became so. @@ -431,11 +439,12 @@ function clearSystemLaser() { } HMD.disableHandLasers(BOTH_HUD_LASERS); systemLaserOn = false; + weMovedReticle = true; + Reticle.position = { x: -1, y: -1 }; } function setColoredLaser() { // answer trigger state if lasers supported, else falsey. var color = (activeTrigger.state === 'full') ? LASER_TRIGGER_COLOR_XYZW : LASER_SEARCH_COLOR_XYZW; return HMD.setHandLasers(activeHudLaser, true, color, SYSTEM_LASER_DIRECTION) && activeTrigger.state; - } // MAIN OPERATIONS ----------- @@ -493,7 +502,10 @@ function checkSettings() { updateRecommendedArea(); } checkSettings(); + var settingsChecker = Script.setInterval(checkSettings, SETTINGS_CHANGE_RECHECK_INTERVAL); Script.scriptEnding.connect(function () { Script.clearInterval(settingsChecker); + OffscreenFlags.navigationFocusDisabled = false; }); + diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 4e87fcbf71..f7e44933d7 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -12,11 +12,13 @@ // HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; - +var EDIT_TOGGLE_BUTTON = "com.highfidelity.interface.system.editButton"; +var SYSTEM_TOOLBAR = "com.highfidelity.interface.toolbar.system"; +var EDIT_TOOLBAR = "com.highfidelity.interface.toolbar.edit"; + Script.include([ "libraries/stringHelpers.js", "libraries/dataViewHelpers.js", - "libraries/toolBars.js", "libraries/progressDialog.js", "libraries/entitySelectionTool.js", @@ -50,11 +52,6 @@ selectionManager.addEventListener(function() { lightOverlayManager.updatePositions(); }); -var toolIconUrl = Script.resolvePath("assets/images/tools/"); -var toolHeight = 50; -var toolWidth = 50; -var TOOLBAR_MARGIN_Y = 0; - var DEGREES_TO_RADIANS = Math.PI / 180.0; var RADIANS_TO_DEGREES = 180.0 / Math.PI; var epsilon = 0.001; @@ -98,12 +95,11 @@ var INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG = "You do not have the necessary p var mode = 0; var isActive = false; -var placingEntityID = null; - IMPORTING_SVO_OVERLAY_WIDTH = 144; IMPORTING_SVO_OVERLAY_HEIGHT = 30; IMPORTING_SVO_OVERLAY_MARGIN = 5; IMPORTING_SVO_OVERLAY_LEFT_MARGIN = 34; + var importingSVOImageOverlay = Overlays.addOverlay("image", { imageURL: Script.resolvePath("assets") + "/images/hourglass.svg", width: 20, @@ -168,241 +164,15 @@ function toggleMarketplace() { } var toolBar = (function() { + var EDIT_SETTING = "io.highfidelity.isEditting"; // for communication with other scripts + var TOOL_ICON_URL = Script.resolvePath("assets/images/tools/"); var that = {}, toolBar, - activeButton, - newModelButton, - newCubeButton, - newSphereButton, - newLightButton, - newTextButton, - newWebButton, - newZoneButton, - newParticleButton - - function initialize() { - toolBar = new ToolBar(0, 0, ToolBar.HORIZONTAL, "highfidelity.edit.toolbar", function(windowDimensions, toolbar) { - return { - x: (windowDimensions.x / 2) + (Tool.IMAGE_WIDTH * 2), - y: windowDimensions.y - }; - }, { - x: toolWidth, - y: -TOOLBAR_MARGIN_Y - toolHeight - }); - - activeButton = toolBar.addTool({ - imageURL: toolIconUrl + "edit.svg", - subImage: { - x: 0, - y: Tool.IMAGE_WIDTH, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT - }, - width: toolWidth, - height: toolHeight, - alpha: 0.9, - visible: true - }, true, false); - - newModelButton = toolBar.addTool({ - imageURL: toolIconUrl + "model-01.svg", - subImage: { - x: 0, - y: Tool.IMAGE_WIDTH, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT - }, - width: toolWidth, - height: toolHeight, - alpha: 0.9, - showButtonDown: true, - visible: false - }); - - newCubeButton = toolBar.addTool({ - imageURL: toolIconUrl + "cube-01.svg", - subImage: { - x: 0, - y: Tool.IMAGE_WIDTH, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT - }, - width: toolWidth, - height: toolHeight, - alpha: 0.9, - showButtonDown: true, - visible: false - }); - - newSphereButton = toolBar.addTool({ - imageURL: toolIconUrl + "sphere-01.svg", - subImage: { - x: 0, - y: Tool.IMAGE_WIDTH, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT - }, - width: toolWidth, - height: toolHeight, - alpha: 0.9, - showButtonDown: true, - visible: false - }); - - newLightButton = toolBar.addTool({ - imageURL: toolIconUrl + "light-01.svg", - subImage: { - x: 0, - y: Tool.IMAGE_WIDTH, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT - }, - width: toolWidth, - height: toolHeight, - alpha: 0.9, - showButtonDown: true, - visible: false - }); - - newTextButton = toolBar.addTool({ - imageURL: toolIconUrl + "text-01.svg", - subImage: { - x: 0, - y: Tool.IMAGE_WIDTH, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT - }, - width: toolWidth, - height: toolHeight, - alpha: 0.9, - showButtonDown: true, - visible: false - }); - - newWebButton = toolBar.addTool({ - imageURL: toolIconUrl + "web-01.svg", - subImage: { - x: 0, - y: Tool.IMAGE_WIDTH, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT - }, - width: toolWidth, - height: toolHeight, - alpha: 0.9, - showButtonDown: true, - visible: false - }); - - newZoneButton = toolBar.addTool({ - imageURL: toolIconUrl + "zone-01.svg", - subImage: { - x: 0, - y: Tool.IMAGE_WIDTH, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT - }, - width: toolWidth, - height: toolHeight, - alpha: 0.9, - showButtonDown: true, - visible: false - }); - - newParticleButton = toolBar.addTool({ - imageURL: toolIconUrl + "particle-01.svg", - subImage: { - x: 0, - y: Tool.IMAGE_WIDTH, - width: Tool.IMAGE_WIDTH, - height: Tool.IMAGE_HEIGHT - }, - width: toolWidth, - height: toolHeight, - alpha: 0.9, - showButtonDown: true, - visible: false - }); - - that.setActive(false); - } - - that.clearEntityList = function() { - entityListTool.clearEntityList(); - }; - - var EDIT_SETTING = "io.highfidelity.isEditting"; // for communication with other scripts - Settings.setValue(EDIT_SETTING, false); - that.setActive = function(active) { - if (active != isActive) { - if (active && !Entities.canRez() && !Entities.canRezTmp()) { - Window.alert(INSUFFICIENT_PERMISSIONS_ERROR_MSG); - } else { - Messages.sendLocalMessage("edit-events", JSON.stringify({ - enabled: active - })); - isActive = active; - Settings.setValue(EDIT_SETTING, active); - if (!isActive) { - entityListTool.setVisible(false); - gridTool.setVisible(false); - grid.setEnabled(false); - propertiesTool.setVisible(false); - selectionManager.clearSelections(); - cameraManager.disable(); - selectionDisplay.triggerMapping.disable(); - } else { - UserActivityLogger.enabledEdit(); - hasShownPropertiesTool = false; - entityListTool.setVisible(true); - gridTool.setVisible(true); - grid.setEnabled(true); - propertiesTool.setVisible(true); - // Not sure what the following was meant to accomplish, but it currently causes - selectionDisplay.triggerMapping.enable(); - // everybody else to think that Interface has lost focus overall. fogbugzid:558 - // Window.setFocus(); - } - that.showTools(isActive); - } - } - toolBar.selectTool(activeButton, isActive); - lightOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_IN_EDIT_MODE)); - Entities.setDrawZoneBoundaries(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); - }; - - // Sets visibility of tool buttons, excluding the power button - that.showTools = function(doShow) { - toolBar.showTool(newModelButton, doShow); - toolBar.showTool(newCubeButton, doShow); - toolBar.showTool(newSphereButton, doShow); - toolBar.showTool(newLightButton, doShow); - toolBar.showTool(newTextButton, doShow); - toolBar.showTool(newWebButton, doShow); - toolBar.showTool(newZoneButton, doShow); - toolBar.showTool(newParticleButton, doShow); - }; - - var RESIZE_INTERVAL = 50; - var RESIZE_TIMEOUT = 120000; // 2 minutes - var RESIZE_MAX_CHECKS = RESIZE_TIMEOUT / RESIZE_INTERVAL; - - function addModel(url) { - var entityID = createNewEntity({ - type: "Model", - modelURL: url - }, false); - - if (entityID) { - print("Model added: " + url); - selectionManager.setSelections([entityID]); - } - } - - function createNewEntity(properties, dragOnCreate) { - // Default to true if not passed in - dragOnCreate = dragOnCreate == undefined ? true : dragOnCreate; + systemToolbar, + activeButton; + + function createNewEntity(properties) { + Settings.setValue(EDIT_SETTING, false); var dimensions = properties.dimensions ? properties.dimensions : DEFAULT_DIMENSIONS; var position = getPositionToCreateEntity(); @@ -410,11 +180,7 @@ var toolBar = (function() { if (position != null) { position = grid.snapToSurface(grid.snapToGrid(position, false, dimensions), dimensions), properties.position = position; - entityID = Entities.addEntity(properties); - if (dragOnCreate) { - placingEntityID = entityID; - } } else { Window.alert("Can't create " + properties.type + ": " + properties.type + " would be out of bounds."); } @@ -426,35 +192,74 @@ var toolBar = (function() { return entityID; } - that.mousePressEvent = function(event) { - var clickedOverlay, - url, - file; - - if (!event.isLeftButton) { - // if another mouse button than left is pressed ignore it - return false; + function cleanup() { + that.setActive(false); + systemToolbar.removeButton(EDIT_TOGGLE_BUTTON); + } + + function addButton(name, image, handler) { + var imageUrl = TOOL_ICON_URL + image; + var button = toolBar.addButton({ + objectName: name, + imageURL: imageUrl, + buttonState: 1, + alpha: 0.9, + visible: true, + }); + if (handler) { + button.clicked.connect(function() { + Script.setTimeout(handler, 100); + }); } + return button; + } - clickedOverlay = Overlays.getOverlayAtPoint({ - x: event.x, - y: event.y + function initialize() { + print("QQQ creating edit toolbar"); + Script.scriptEnding.connect(cleanup); + + Window.domainChanged.connect(function() { + that.setActive(false); + that.clearEntityList(); }); - if (activeButton === toolBar.clicked(clickedOverlay)) { - that.setActive(!isActive); - return true; - } - - if (newModelButton === toolBar.clicked(clickedOverlay)) { - url = Window.prompt("Model URL"); - if (url !== null && url !== "") { - addModel(url); + Entities.canAdjustLocksChanged.connect(function(canAdjustLocks) { + if (isActive && !canAdjustLocks) { + that.setActive(false); } - return true; - } + }); - if (newCubeButton === toolBar.clicked(clickedOverlay)) { + systemToolbar = Toolbars.getToolbar(SYSTEM_TOOLBAR); + activeButton = systemToolbar.addButton({ + objectName: EDIT_TOGGLE_BUTTON, + imageURL: TOOL_ICON_URL + "edit.svg", + visible: true, + alpha: 0.9, + buttonState: 1, + hoverState: 3, + defaultState: 1, + }); + activeButton.clicked.connect(function() { + that.setActive(!isActive); + activeButton.writeProperty("buttonState", isActive ? 0 : 1); + activeButton.writeProperty("defaultState", isActive ? 0 : 1); + activeButton.writeProperty("hoverState", isActive ? 2 : 3); + }); + + toolBar = Toolbars.getToolbar(EDIT_TOOLBAR); + toolBar.writeProperty("shown", false); + + addButton("newModelButton", "model-01.svg", function() { + var url = Window.prompt("Model URL"); + if (url !== null && url !== "") { + createNewEntity({ + type: "Model", + modelURL: url + }); + } + }); + + addButton("newCubeButton", "cube-01.svg", function() { createNewEntity({ type: "Box", dimensions: DEFAULT_DIMENSIONS, @@ -464,11 +269,9 @@ var toolBar = (function() { blue: 0 } }); + }); - return true; - } - - if (newSphereButton === toolBar.clicked(clickedOverlay)) { + addButton("newSphereButton", "sphere-01.svg", function() { createNewEntity({ type: "Sphere", dimensions: DEFAULT_DIMENSIONS, @@ -478,11 +281,9 @@ var toolBar = (function() { blue: 0 } }); + }); - return true; - } - - if (newLightButton === toolBar.clicked(clickedOverlay)) { + addButton("newLightButton", "light-01.svg", function() { createNewEntity({ type: "Light", dimensions: DEFAULT_LIGHT_DIMENSIONS, @@ -499,11 +300,9 @@ var toolBar = (function() { exponent: 0, cutoff: 180, // in degrees }); + }); - return true; - } - - if (newTextButton === toolBar.clicked(clickedOverlay)) { + addButton("newTextButton", "text-01.svg", function() { createNewEntity({ type: "Text", dimensions: { @@ -524,11 +323,9 @@ var toolBar = (function() { text: "some text", lineHeight: 0.06 }); + }); - return true; - } - - if (newWebButton === toolBar.clicked(clickedOverlay)) { + addButton("newWebButton", "web-01.svg", function() { createNewEntity({ type: "Web", dimensions: { @@ -538,11 +335,9 @@ var toolBar = (function() { }, sourceUrl: "https://highfidelity.com/", }); + }); - return true; - } - - if (newZoneButton === toolBar.clicked(clickedOverlay)) { + addButton("newZoneButton", "zone-01.svg", function() { createNewEntity({ type: "Zone", dimensions: { @@ -551,11 +346,9 @@ var toolBar = (function() { z: 10 }, }); + }); - return true; - } - - if (newParticleButton === toolBar.clicked(clickedOverlay)) { + addButton("newParticleButton", "particle-01.svg", function() { createNewEntity({ type: "ParticleEffect", isEmitting: true, @@ -577,33 +370,63 @@ var toolBar = (function() { emitRate: 100, textures: "https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png", }); - } + }); - return false; + that.setActive(false); + } + + that.clearEntityList = function() { + entityListTool.clearEntityList(); }; - that.mouseReleaseEvent = function(event) { - return false; - } - - Window.domainChanged.connect(function() { - that.setActive(false); - that.clearEntityList(); - }); - - Entities.canAdjustLocksChanged.connect(function(canAdjustLocks) { - if (isActive && !canAdjustLocks) { - that.setActive(false); + that.setActive = function(active) { + if (active == isActive) { + return; } - }); - - that.cleanup = function() { - toolBar.cleanup(); + if (active && !Entities.canRez() && !Entities.canRezTmp()) { + Window.alert(INSUFFICIENT_PERMISSIONS_ERROR_MSG); + return; + } + Messages.sendLocalMessage("edit-events", JSON.stringify({ + enabled: active + })); + isActive = active; + Settings.setValue(EDIT_SETTING, active); + if (!isActive) { + entityListTool.setVisible(false); + gridTool.setVisible(false); + grid.setEnabled(false); + propertiesTool.setVisible(false); + selectionManager.clearSelections(); + cameraManager.disable(); + selectionDisplay.triggerMapping.disable(); + } else { + UserActivityLogger.enabledEdit(); + hasShownPropertiesTool = false; + entityListTool.setVisible(true); + gridTool.setVisible(true); + grid.setEnabled(true); + propertiesTool.setVisible(true); + selectionDisplay.triggerMapping.enable(); + // Not sure what the following was meant to accomplish, but it currently causes + // everybody else to think that Interface has lost focus overall. fogbugzid:558 + // Window.setFocus(); + } + // Sets visibility of tool buttons, excluding the power button + toolBar.writeProperty("shown", active); + var visible = toolBar.readProperty("visible"); + if (active && !visible) { + toolBar.writeProperty("shown", false); + toolBar.writeProperty("shown", true); + } + //toolBar.selectTool(activeButton, isActive); + lightOverlayManager.setVisible(isActive && Menu.isOptionChecked(MENU_SHOW_LIGHTS_IN_EDIT_MODE)); + Entities.setDrawZoneBoundaries(isActive && Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); }; initialize(); return that; -}()); +})(); function isLocked(properties) { @@ -712,7 +535,7 @@ function mousePressEvent(event) { mouseHasMovedSincePress = false; mouseCapturedByTool = false; - if (propertyMenu.mousePressEvent(event) || toolBar.mousePressEvent(event) || progressDialog.mousePressEvent(event)) { + if (propertyMenu.mousePressEvent(event) || progressDialog.mousePressEvent(event)) { mouseCapturedByTool = true; return; } @@ -755,16 +578,6 @@ function mouseMove(event) { mouseHasMovedSincePress = true; } - if (placingEntityID) { - var pickRay = Camera.computePickRay(event.x, event.y); - var distance = cameraManager.enabled ? cameraManager.zoomDistance : DEFAULT_ENTITY_DRAG_DROP_DISTANCE; - var offset = Vec3.multiply(distance, pickRay.direction); - var position = Vec3.sum(Camera.position, offset); - Entities.editEntity(placingEntityID, { - position: position, - }); - return; - } if (!isActive) { return; } @@ -796,20 +609,10 @@ function mouseReleaseEvent(event) { mouseMove(lastMouseMoveEvent); lastMouseMoveEvent = null; } - if (propertyMenu.mouseReleaseEvent(event) || toolBar.mouseReleaseEvent(event)) { - + if (propertyMenu.mouseReleaseEvent(event)) { return true; } - if (placingEntityID) { - - if (isActive) { - - selectionManager.setSelections([placingEntityID]); - } - placingEntityID = null; - } if (isActive && selectionManager.hasSelection()) { - tooltip.show(false); } if (mouseCapturedByTool) { @@ -1094,7 +897,6 @@ Script.scriptEnding.connect(function() { Settings.setValue(SETTING_SHOW_ZONES_IN_EDIT_MODE, Menu.isOptionChecked(MENU_SHOW_ZONES_IN_EDIT_MODE)); progressDialog.cleanup(); - toolBar.cleanup(); cleanupModelMenus(); tooltip.cleanup(); selectionDisplay.cleanup(); diff --git a/scripts/system/examples.js b/scripts/system/examples.js index 0ec2644f9c..6fdb2a2874 100644 --- a/scripts/system/examples.js +++ b/scripts/system/examples.js @@ -56,11 +56,15 @@ var browseExamplesButton = toolBar.addButton({ imageURL: toolIconUrl + "market.svg", objectName: "examples", buttonState: 1, + defaultState: 1, + hoverState: 3, alpha: 0.9 }); function onExamplesWindowVisibilityChanged() { browseExamplesButton.writeProperty('buttonState', examplesWindow.visible ? 0 : 1); + browseExamplesButton.writeProperty('defaultState', examplesWindow.visible ? 0 : 1); + browseExamplesButton.writeProperty('hoverState', examplesWindow.visible ? 2 : 3); } function onClick() { toggleExamples(); @@ -69,6 +73,7 @@ browseExamplesButton.clicked.connect(onClick); examplesWindow.visibleChanged.connect(onExamplesWindowVisibilityChanged); Script.scriptEnding.connect(function () { + toolBar.removeButton("examples"); browseExamplesButton.clicked.disconnect(onClick); examplesWindow.visibleChanged.disconnect(onExamplesWindowVisibilityChanged); }); diff --git a/scripts/system/goto.js b/scripts/system/goto.js index 24c402ab85..2ed98c689b 100644 --- a/scripts/system/goto.js +++ b/scripts/system/goto.js @@ -17,11 +17,15 @@ var button = toolBar.addButton({ imageURL: Script.resolvePath("assets/images/tools/directory.svg"), visible: true, buttonState: 1, + defaultState: 1, + hoverState: 3, alpha: 0.9, }); function onAddressBarShown(visible) { button.writeProperty('buttonState', visible ? 0 : 1); + button.writeProperty('defaultState', visible ? 0 : 1); + button.writeProperty('hoverState', visible ? 2 : 3); } function onClicked(){ DialogsManager.toggleAddressBar(); @@ -30,6 +34,7 @@ button.clicked.connect(onClicked); DialogsManager.addressBarShown.connect(onAddressBarShown); Script.scriptEnding.connect(function () { + toolBar.removeButton("goto"); button.clicked.disconnect(onClicked); DialogsManager.addressBarShown.disconnect(onAddressBarShown); }); diff --git a/scripts/system/hmd.js b/scripts/system/hmd.js index 305557b60c..ac1918b001 100644 --- a/scripts/system/hmd.js +++ b/scripts/system/hmd.js @@ -22,6 +22,8 @@ var toolBar = Toolbars.getToolbar("com.highfidelity.interface.toolbar.system"); var button; function onHmdChanged(isHmd) { button.writeProperty('buttonState', isHmd ? 0 : 1); + button.writeProperty('defaultState', isHmd ? 0 : 1); + button.writeProperty('hoverState', isHmd ? 2 : 3); } function onClicked(){ var isDesktop = Menu.isOptionChecked(desktopMenuItemName); @@ -32,6 +34,8 @@ if (headset) { objectName: "hmdToggle", imageURL: Script.resolvePath("assets/images/tools/switch.svg"), visible: true, + hoverState: 2, + defaultState: 0, alpha: 0.9, }); onHmdChanged(HMD.active); @@ -40,6 +44,7 @@ if (headset) { HMD.displayModeChanged.connect(onHmdChanged); Script.scriptEnding.connect(function () { + toolBar.removeButton("hmdToggle"); button.clicked.disconnect(onClicked); HMD.displayModeChanged.disconnect(onHmdChanged); }); diff --git a/scripts/system/ignore.js b/scripts/system/ignore.js index 39405a2e57..1c996a7fcc 100644 --- a/scripts/system/ignore.js +++ b/scripts/system/ignore.js @@ -18,6 +18,8 @@ var button = toolbar.addButton({ imageURL: Script.resolvePath("assets/images/tools/ignore.svg"), visible: true, buttonState: 1, + defaultState: 2, + hoverState: 3, alpha: 0.9 }); @@ -46,6 +48,8 @@ function buttonClicked(){ } button.writeProperty('buttonState', isShowingOverlays ? 0 : 1); + button.writeProperty('defaultState', isShowingOverlays ? 0 : 1); + button.writeProperty('hoverState', isShowingOverlays ? 2 : 3); } button.clicked.connect(buttonClicked); diff --git a/scripts/system/libraries/Trigger.js b/scripts/system/libraries/Trigger.js new file mode 100644 index 0000000000..ffde021f5d --- /dev/null +++ b/scripts/system/libraries/Trigger.js @@ -0,0 +1,87 @@ +"use strict"; + +/*jslint vars: true, plusplus: true*/ +/*globals Script, Overlays, Controller, Reticle, HMD, Camera, Entities, MyAvatar, Settings, Menu, ScriptDiscoveryService, Window, Vec3, Quat, print*/ + +Trigger = function(properties) { + properties = properties || {}; + var that = this; + that.label = properties.label || Math.random(); + that.SMOOTH_RATIO = properties.smooth || 0.1; // Time averaging of trigger - 0.0 disables smoothing + that.DEADZONE = properties.deadzone || 0.10; // Once pressed, a trigger must fall below the deadzone to be considered un-pressed once pressed. + that.HYSTERESIS = properties.hystersis || 0.05; // If not pressed, a trigger must go above DEADZONE + HYSTERSIS to be considered pressed + + that.value = 0; + that.pressed = false; + that.clicked = false; + + // Handlers + that.onPress = properties.onPress || function(){ + print("Pressed trigger " + that.label) + }; + that.onRelease = properties.onRelease || function(){ + print("Released trigger " + that.label) + }; + that.onClick = properties.onClick || function(){ + print("Clicked trigger " + that.label) + }; + that.onUnclick = properties.onUnclick || function(){ + print("Unclicked trigger " + that.label) + }; + + // Getters + that.isPressed = function() { + return that.pressed; + } + + that.isClicked = function() { + return that.clicked; + } + + that.getValue = function() { + return that.value; + } + + + // Private values + var controller = properties.controller || Controller.Standard.LT; + var controllerClick = properties.controllerClick || Controller.Standard.LTClick; + that.mapping = Controller.newMapping('com.highfidelity.controller.trigger.' + controller + '-' + controllerClick + '.' + that.label + Math.random()); + Script.scriptEnding.connect(that.mapping.disable); + + // Setup mapping, + that.mapping.from(controller).peek().to(function(value) { + that.value = (that.value * that.SMOOTH_RATIO) + + (value * (1.0 - that.SMOOTH_RATIO)); + + var oldPressed = that.pressed; + if (!that.pressed && that.value >= (that.DEADZONE + that.HYSTERESIS)) { + that.pressed = true; + that.onPress(); + } + + if (that.pressed && that.value < that.HYSTERESIS) { + that.pressed = false; + that.onRelease(); + } + }); + + that.mapping.from(controllerClick).peek().to(function(value){ + if (!that.clicked && value > 0.0) { + that.clicked = true; + that.onClick(); + } + if (that.clicked && value == 0.0) { + that.clicked = false; + that.onUnclick(); + } + }); + + that.enable = function() { + that.mapping.enable(); + } + + that.disable = function() { + that.mapping.disable(); + } +} diff --git a/scripts/system/mute.js b/scripts/system/mute.js index 1a575efa01..4ea8aee546 100644 --- a/scripts/system/mute.js +++ b/scripts/system/mute.js @@ -17,13 +17,19 @@ var button = toolBar.addButton({ imageURL: Script.resolvePath("assets/images/tools/mic.svg"), visible: true, buttonState: 1, + defaultState: 1, + hoverState: 3, alpha: 0.9 }); function onMuteToggled() { // We could just toggle state, but we're less likely to get out of wack if we read the AudioDevice. // muted => button "on" state => 1. go figure. - button.writeProperty('buttonState', AudioDevice.getMuted() ? 0 : 1); + var state = AudioDevice.getMuted() ? 0 : 1; + var hoverState = AudioDevice.getMuted() ? 2 : 3; + button.writeProperty('buttonState', state); + button.writeProperty('defaultState', state); + button.writeProperty('hoverState', hoverState); } onMuteToggled(); function onClicked(){ @@ -34,6 +40,7 @@ button.clicked.connect(onClicked); AudioDevice.muteToggled.connect(onMuteToggled); Script.scriptEnding.connect(function () { + toolBar.removeButton("mute"); button.clicked.disconnect(onClicked); AudioDevice.muteToggled.disconnect(onMuteToggled); }); diff --git a/scripts/tutorials/createDice.js b/scripts/tutorials/createDice.js index 00ec1184bc..8975578c66 100644 --- a/scripts/tutorials/createDice.js +++ b/scripts/tutorials/createDice.js @@ -46,11 +46,6 @@ var offButton = toolBar.addOverlay("image", { width: BUTTON_SIZE, height: BUTTON_SIZE, imageURL: "http://hifi-production.s3.amazonaws.com/tutorials/dice/close.png", - color: { - red: 255, - green: 255, - blue: 255 - }, alpha: 1 }); @@ -60,11 +55,6 @@ var deleteButton = toolBar.addOverlay("image", { width: BUTTON_SIZE, height: BUTTON_SIZE, imageURL: "http://hifi-production.s3.amazonaws.com/tutorials/dice/delete.png", - color: { - red: 255, - green: 255, - blue: 255 - }, alpha: 1 }); @@ -75,11 +65,6 @@ var diceButton = toolBar.addOverlay("image", { width: BUTTON_SIZE, height: BUTTON_SIZE, imageURL: diceIconURL, - color: { - red: 255, - green: 255, - blue: 255 - }, alpha: 1 }); diff --git a/tests/render-perf/src/Camera.hpp b/tests/render-perf/src/Camera.hpp new file mode 100644 index 0000000000..fbae04540b --- /dev/null +++ b/tests/render-perf/src/Camera.hpp @@ -0,0 +1,138 @@ +#pragma once + +#include + +class Camera { +protected: + float fov { 60.0f }; + float znear { DEFAULT_NEAR_CLIP }, zfar { DEFAULT_FAR_CLIP }; + float aspect { 1.0f }; + + void updateViewMatrix() { + matrices.view = glm::inverse(glm::translate(glm::mat4(), position) * glm::mat4_cast(getOrientation())); + } + +public: + glm::quat getOrientation() const { + return glm::angleAxis(yaw, Vectors::UP); + } + float yaw { 0 }; + glm::vec3 position; + float rotationSpeed { 1.0f }; + float movementSpeed { 1.0f }; + + struct Matrices { + glm::mat4 perspective; + glm::mat4 view; + } matrices; + enum Key { + RIGHT, + LEFT, + UP, + DOWN, + BACK, + FORWARD, + KEYS_SIZE, + INVALID = -1, + }; + + std::bitset keys; + + Camera() { + matrices.perspective = glm::perspective(glm::radians(fov), aspect, znear, zfar); + } + + bool moving() { + return keys.any(); + } + + void setFieldOfView(float fov) { + this->fov = fov; + matrices.perspective = glm::perspective(glm::radians(fov), aspect, znear, zfar); + } + + void setAspectRatio(const glm::vec2& size) { + setAspectRatio(size.x / size.y); + } + + void setAspectRatio(float aspect) { + this->aspect = aspect; + matrices.perspective = glm::perspective(glm::radians(fov), aspect, znear, zfar); + } + + void setPerspective(float fov, const glm::vec2& size, float znear = 0.1f, float zfar = 512.0f) { + setPerspective(fov, size.x / size.y, znear, zfar); + } + + void setPerspective(float fov, float aspect, float znear = 0.1f, float zfar = 512.0f) { + this->aspect = aspect; + this->fov = fov; + this->znear = znear; + this->zfar = zfar; + matrices.perspective = glm::perspective(glm::radians(fov), aspect, znear, zfar); + }; + + void rotate(const float delta) { + yaw += delta; + updateViewMatrix(); + } + + void setRotation(const glm::quat& rotation) { + glm::vec3 f = rotation * Vectors::UNIT_NEG_Z; + f.y = 0; + f = glm::normalize(f); + yaw = angleBetween(Vectors::UNIT_NEG_Z, f); + updateViewMatrix(); + } + + void setPosition(const glm::vec3& position) { + this->position = position; + updateViewMatrix(); + } + + // Translate in the Z axis of the camera + void dolly(float delta) { + auto direction = glm::vec3(0, 0, delta); + translate(direction); + } + + // Translate in the XY plane of the camera + void translate(const glm::vec2& delta) { + auto move = glm::vec3(delta.x, delta.y, 0); + translate(move); + } + + void translate(const glm::vec3& delta) { + position += getOrientation() * delta; + updateViewMatrix(); + } + + void update(float deltaTime) { + if (moving()) { + glm::vec3 camFront = getOrientation() * Vectors::FRONT; + glm::vec3 camRight = getOrientation() * Vectors::RIGHT; + glm::vec3 camUp = getOrientation() * Vectors::UP; + float moveSpeed = deltaTime * movementSpeed; + + if (keys[FORWARD]) { + position += camFront * moveSpeed; + } + if (keys[BACK]) { + position -= camFront * moveSpeed; + } + if (keys[LEFT]) { + position -= camRight * moveSpeed; + } + if (keys[RIGHT]) { + position += camRight * moveSpeed; + } + if (keys[UP]) { + position += camUp * moveSpeed; + } + if (keys[DOWN]) { + position -= camUp * moveSpeed; + } + updateViewMatrix(); + } + } +}; diff --git a/tests/render-perf/src/TextOverlay.hpp b/tests/render-perf/src/TextOverlay.hpp new file mode 100644 index 0000000000..d9a9f0a320 --- /dev/null +++ b/tests/render-perf/src/TextOverlay.hpp @@ -0,0 +1,239 @@ +/* +* Text overlay class for displaying debug information +* +* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de +* +* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +#include "stb_font_consolas_24_latin1.inl" + +// Defines for the STB font used +// STB font files can be found at http://nothings.org/stb/font/ +#define STB_FONT_NAME stb_font_consolas_24_latin1 +#define STB_FONT_WIDTH STB_FONT_consolas_24_latin1_BITMAP_WIDTH +#define STB_FONT_HEIGHT STB_FONT_consolas_24_latin1_BITMAP_HEIGHT +#define STB_FIRST_CHAR STB_FONT_consolas_24_latin1_FIRST_CHAR +#define STB_NUM_CHARS STB_FONT_consolas_24_latin1_NUM_CHARS + +// Max. number of chars the text overlay buffer can hold +#define MAX_CHAR_COUNT 1024 + +// Mostly self-contained text overlay class +// todo : comment +class TextOverlay { +private: + FramebufferPtr _framebuffer; + TexturePtr _texture; + uvec2 _size; + BufferPtr _vertexBuffer; + ProgramPtr _program; + VertexArrayPtr _vertexArray; + + //vk::DescriptorPool descriptorPool; + //vk::DescriptorSetLayout descriptorSetLayout; + //vk::DescriptorSet descriptorSet; + //vk::PipelineLayout pipelineLayout; + //vk::Pipeline pipeline; + + // Pointer to mapped vertex buffer + glm::vec4* _mapped { nullptr }; + stb_fontchar stbFontData[STB_NUM_CHARS]; + uint32_t numLetters; + + const char* const VERTEX_SHADER = R"SHADER( +#version 450 core +layout (location = 0) in vec2 inPos; +layout (location = 1) in vec2 inUV; + +layout (location = 0) out vec2 outUV; + +out gl_PerVertex +{ + vec4 gl_Position; +}; + +void main(void) +{ + vec2 pos = inPos; + pos.y *= -1.0; + gl_Position = vec4(pos, 0.0, 1.0); + outUV = inUV; +} +)SHADER"; + + const char* const FRAGMENT_SHADER = R"SHADER( +#version 450 core + +layout (location = 0) in vec2 inUV; + +layout (binding = 0) uniform sampler2D samplerFont; + +layout (location = 0) out vec4 outFragColor; + +void main(void) +{ + float color = texture(samplerFont, inUV).r; + outFragColor = vec4(vec3(color), 1.0); +} +)SHADER"; + +public: + enum TextAlign { alignLeft, alignCenter, alignRight }; + + bool visible = true; + bool invalidated = false; + + + TextOverlay(const glm::uvec2& size) : _size(size) { + prepare(); + } + + ~TextOverlay() { + // Free up all Vulkan resources requested by the text overlay + _program.reset(); + _texture.reset(); + _vertexBuffer.reset(); + _vertexArray.reset(); + } + + void resize(const uvec2& size) { + _size = size; + } + + // Prepare all vulkan resources required to render the font + // The text overlay uses separate resources for descriptors (pool, sets, layouts), pipelines and command buffers + void prepare() { + static unsigned char font24pixels[STB_FONT_HEIGHT][STB_FONT_WIDTH]; + STB_FONT_NAME(stbFontData, font24pixels, STB_FONT_HEIGHT); + + // Vertex buffer + { + GLuint buffer; + GLuint bufferSize = MAX_CHAR_COUNT * sizeof(glm::vec4); + glCreateBuffers(1, &buffer); + glNamedBufferStorage(buffer, bufferSize, nullptr, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT | GL_MAP_COHERENT_BIT); + using BufferName = oglplus::ObjectName; + BufferName name = BufferName(buffer); + _vertexBuffer = std::make_shared(oglplus::Buffer::FromRawName(name)); + _vertexArray = std::make_shared(); + _vertexArray->Bind(); + glBindBuffer(GL_ARRAY_BUFFER, buffer); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(glm::vec4), 0); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(glm::vec4), (void*)sizeof(glm::vec2)); + glBindVertexArray(0); + } + + // Font texture + { + GLuint texture; + glCreateTextures(GL_TEXTURE_2D, 1, &texture); + glTextureStorage2D(texture, 1, GL_R8, STB_FONT_WIDTH, STB_FONT_HEIGHT); + glTextureSubImage2D(texture, 0, 0, 0, STB_FONT_WIDTH, STB_FONT_HEIGHT, GL_RED, GL_UNSIGNED_BYTE, &font24pixels[0][0]); + glTextureParameteri(texture, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTextureParameteri(texture, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + using TextureName = oglplus::ObjectName; + TextureName name = TextureName(texture); + _texture = std::make_shared(oglplus::Texture::FromRawName(name)); + } + + compileProgram(_program, VERTEX_SHADER, FRAGMENT_SHADER); + } + + + // Map buffer + void beginTextUpdate() { + using namespace oglplus; + _mapped = (glm::vec4*)glMapNamedBuffer(GetName(*_vertexBuffer), GL_WRITE_ONLY); + numLetters = 0; + } + + // Add text to the current buffer + // todo : drop shadow? color attribute? + void addText(std::string text, vec2 pos, TextAlign align) { + assert(_mapped != nullptr); + const vec2 fbSize = _size; + const vec2 charSize = vec2(1.5f) / fbSize; + pos = (pos / fbSize * 2.0f) - 1.0f; + + // Calculate text width + float textWidth = 0; + for (auto letter : text) { + stb_fontchar *charData = &stbFontData[(uint32_t)letter - STB_FIRST_CHAR]; + textWidth += charData->advance * charSize.x; + } + + switch (align) { + case alignRight: + pos.x -= textWidth; + break; + case alignCenter: + pos.x -= textWidth / 2.0f; + break; + case alignLeft: + break; + } + + // Generate a uv mapped quad per char in the new text + for (auto letter : text) { + stb_fontchar *charData = &stbFontData[(uint32_t)letter - STB_FIRST_CHAR]; + _mapped->x = pos.x + (float)charData->x0 * charSize.x; + _mapped->y = pos.y + (float)charData->y0 * charSize.y; + _mapped->z = charData->s0; + _mapped->w = charData->t0; + ++_mapped; + + _mapped->x = pos.x + (float)charData->x1 * charSize.x; + _mapped->y = pos.y + (float)charData->y0 * charSize.y; + _mapped->z = charData->s1; + _mapped->w = charData->t0; + ++_mapped; + + _mapped->x = pos.x + (float)charData->x0 * charSize.x; + _mapped->y = pos.y + (float)charData->y1 * charSize.y; + _mapped->z = charData->s0; + _mapped->w = charData->t1; + _mapped++; + + _mapped->x = pos.x + (float)charData->x1 * charSize.x; + _mapped->y = pos.y + (float)charData->y1 * charSize.y; + _mapped->z = charData->s1; + _mapped->w = charData->t1; + _mapped++; + pos.x += charData->advance * charSize.x; + numLetters++; + } + } + + // Unmap buffer and update command buffers + void endTextUpdate() { + glUnmapNamedBuffer(GetName(*_vertexBuffer)); + _mapped = nullptr; + } + + // Needs to be called by the application + void render() { + _texture->Bind(oglplus::TextureTarget::_2D); + _program->Use(); + _vertexArray->Bind(); + for (uint32_t j = 0; j < numLetters; j++) { + glDrawArrays(GL_TRIANGLE_STRIP, j * 4, 4); + } + } +}; diff --git a/tests/render-perf/src/main.cpp b/tests/render-perf/src/main.cpp index 5367c65d94..f3e3271a5d 100644 --- a/tests/render-perf/src/main.cpp +++ b/tests/render-perf/src/main.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -26,14 +27,19 @@ #include #include +#include +#include +#include #include #include #include #include #include +#include +#include #include #include #include @@ -51,7 +57,11 @@ #include #include +#include "Camera.hpp" +#include "TextOverlay.hpp" + static const QString LAST_SCENE_KEY = "lastSceneFile"; +static const QString LAST_LOCATION_KEY = "lastLocation"; class ParentFinder : public SpatialParentFinder { public: @@ -83,133 +93,6 @@ public: } }; -class Camera { -protected: - float fov { 60.0f }; - float znear { 0.1f }, zfar { 512.0f }; - float aspect { 1.0f }; - - void updateViewMatrix() { - matrices.view = glm::inverse(glm::translate(glm::mat4(), position) * glm::mat4_cast(getOrientation())); - } - - glm::quat getOrientation() const { - return glm::angleAxis(yaw, Vectors::UP); - } -public: - float yaw { 0 }; - glm::vec3 position; - - float rotationSpeed { 1.0f }; - float movementSpeed { 1.0f }; - - struct Matrices { - glm::mat4 perspective; - glm::mat4 view; - } matrices; - enum Key { - RIGHT, - LEFT, - UP, - DOWN, - BACK, - FORWARD, - KEYS_SIZE, - INVALID = -1, - }; - - std::bitset keys; - - Camera() { - matrices.perspective = glm::perspective(glm::radians(fov), aspect, znear, zfar); - } - - bool moving() { - return keys.any(); - } - - void setFieldOfView(float fov) { - this->fov = fov; - matrices.perspective = glm::perspective(glm::radians(fov), aspect, znear, zfar); - } - - void setAspectRatio(const glm::vec2& size) { - setAspectRatio(size.x / size.y); - } - - void setAspectRatio(float aspect) { - this->aspect = aspect; - matrices.perspective = glm::perspective(glm::radians(fov), aspect, znear, zfar); - } - - void setPerspective(float fov, const glm::vec2& size, float znear = 0.1f, float zfar = 512.0f) { - setPerspective(fov, size.x / size.y, znear, zfar); - } - - void setPerspective(float fov, float aspect, float znear = 0.1f, float zfar = 512.0f) { - this->aspect = aspect; - this->fov = fov; - this->znear = znear; - this->zfar = zfar; - matrices.perspective = glm::perspective(glm::radians(fov), aspect, znear, zfar); - }; - - void rotate(const float delta) { - yaw += delta; - updateViewMatrix(); - } - - void setPosition(const glm::vec3& position) { - this->position = position; - updateViewMatrix(); - } - - // Translate in the Z axis of the camera - void dolly(float delta) { - auto direction = glm::vec3(0, 0, delta); - translate(direction); - } - - // Translate in the XY plane of the camera - void translate(const glm::vec2& delta) { - auto move = glm::vec3(delta.x, delta.y, 0); - translate(move); - } - - void translate(const glm::vec3& delta) { - position += getOrientation() * delta; - updateViewMatrix(); - } - - void update(float deltaTime) { - if (moving()) { - glm::vec3 camFront = getOrientation() * Vectors::FRONT; - glm::vec3 camRight = getOrientation() * Vectors::RIGHT; - glm::vec3 camUp = getOrientation() * Vectors::UP; - float moveSpeed = deltaTime * movementSpeed; - - if (keys[FORWARD]) { - position += camFront * moveSpeed; - } - if (keys[BACK]) { - position -= camFront * moveSpeed; - } - if (keys[LEFT]) { - position -= camRight * moveSpeed; - } - if (keys[RIGHT]) { - position += camRight * moveSpeed; - } - if (keys[UP]) { - position += camUp * moveSpeed; - } - if (keys[DOWN]) { - position -= camUp * moveSpeed; - } - updateViewMatrix(); - } - } -}; class QWindowCamera : public Camera { Key forKey(int key) { @@ -260,6 +143,8 @@ public: } }; + + // Create a simple OpenGL window that renders text in various ways class QTestWindow : public QWindow, public AbstractViewStateInterface { Q_OBJECT @@ -297,7 +182,10 @@ protected: return _renderEngine; } - void pushPostUpdateLambda(void* key, std::function func) override {} + std::map> _postUpdateLambdas; + void pushPostUpdateLambda(void* key, std::function func) override { + _postUpdateLambdas[key] = func; + } public: //"/-17.2049,-8.08629,-19.4153/0,0.881994,0,-0.47126" @@ -338,28 +226,39 @@ public: _context.setFormat(format); _context.create(); - + resize(QSize(800, 600)); show(); makeCurrent(); + glewExperimental = true; glewInit(); glGetError(); + setupDebugLogger(this); #ifdef Q_OS_WIN wglSwapIntervalEXT(0); #endif - _camera.movementSpeed = 3.0f; + { + makeCurrent(); + _quadProgram = loadDefaultShader(); + _plane = loadPlane(_quadProgram); + _textOverlay = new TextOverlay(glm::uvec2(800, 600)); + glViewport(0, 0, 800, 600); + } + + _camera.movementSpeed = 50.0f; - setupDebugLogger(this); - qDebug() << (const char*)glGetString(GL_VERSION); // GPU library init { + _offscreenContext = new OffscreenGLCanvas(); + _offscreenContext->create(_context.getContext()); + _offscreenContext->makeCurrent(); gpu::Context::init(); _gpuContext = std::make_shared(); } // Render engine library init { - makeCurrent(); + _offscreenContext->makeCurrent(); DependencyManager::get()->init(); _renderEngine->addJob("RenderShadowTask", _cullFunctor); _renderEngine->addJob("RenderDeferredTask", _cullFunctor); @@ -367,27 +266,23 @@ public: _renderEngine->registerScene(_main3DScene); } - QVariant lastScene = _settings.value(LAST_SCENE_KEY); - if (lastScene.isValid()) { - auto result = QMessageBox::question(nullptr, "Question", "Load last scene " + lastScene.toString()); - if (result != QMessageBox::No) { - importScene(lastScene.toString()); - } - } + reloadScene(); + restorePosition(); - resize(QSize(800, 600)); _elapsed.start(); - QTimer* timer = new QTimer(this); timer->setInterval(0); connect(timer, &QTimer::timeout, this, [this] { draw(); }); timer->start(); + _ready = true; } virtual ~QTestWindow() { ResourceManager::cleanup(); + try { _quadProgram.reset(); } catch (std::runtime_error&) {} + try { _plane.reset(); } catch (std::runtime_error&) {} } protected: @@ -398,9 +293,30 @@ protected: return; case Qt::Key_F2: + reloadScene(); + return; + + case Qt::Key_F5: goTo(); return; + case Qt::Key_F6: + savePosition(); + return; + + case Qt::Key_F7: + restorePosition(); + return; + + case Qt::Key_F8: + resetPosition(); + return; + + case Qt::Key_F9: + toggleCulling(); + return; + + default: break; } @@ -421,39 +337,22 @@ protected: private: - static bool cull(const RenderArgs* renderArgs, const AABox& box) { - return true; - } - - void update() { - auto now = usecTimestampNow(); - static auto last = now; - - float delta = now - last; - // Update the camera - _camera.update(delta / USECS_PER_SECOND); - - - // load the view frustum - { - _viewFrustum.setProjection(_camera.matrices.perspective); - auto view = glm::inverse(_camera.matrices.view); - _viewFrustum.setPosition(glm::vec3(view[3])); - _viewFrustum.setOrientation(glm::quat_cast(view)); - } - last = now; + static bool cull(const RenderArgs* args, const AABox& bounds) { + float renderAccuracy = calculateRenderAccuracy(args->getViewFrustum().getPosition(), bounds, args->_sizeScale, args->_boundaryLevelAdjust); + return (renderAccuracy > 0.0f); } void draw() { + if (!_ready) { + return; + } if (!isVisible()) { return; } update(); - makeCurrent(); -#define RENDER_SCENE 1 + _offscreenContext->makeCurrent(); -#if RENDER_SCENE RenderArgs renderArgs(_gpuContext, _octree.data(), DEFAULT_OCTREE_SIZE_SCALE, 0, RenderArgs::DEFAULT_RENDER_MODE, RenderArgs::MONO, RenderArgs::RENDER_DEBUG_NONE); @@ -473,40 +372,114 @@ private: } render(&renderArgs); + GLuint glTex; + { + auto gpuTex = renderArgs._blitFramebuffer->getRenderBuffer(0); + glTex = gpu::Backend::getGPUObject(*gpuTex)->_id; + } + + makeCurrent(); + { + glBindTexture(GL_TEXTURE_2D, glTex); + _quadProgram->Use(); + _plane->Use(); + _plane->Draw(); + glBindVertexArray(0); + } { - gpu::gl::GLFramebuffer* framebuffer = gpu::Backend::getGPUObject(*renderArgs._blitFramebuffer); - auto fbo = framebuffer->_id; - glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo); - glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); - const auto& vp = renderArgs._viewport; - glBlitFramebuffer(vp.x, vp.y, vp.z, vp.w, vp.x, vp.y, vp.z, vp.w, GL_COLOR_BUFFER_BIT, GL_NEAREST); + _textOverlay->render(); } -#else - glClearColor(0.0f, 0.5f, 0.8f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); -#endif _context.swapBuffers(this); -#if RENDER_SCENE + _offscreenContext->makeCurrent(); framebufferCache->releaseFramebuffer(renderArgs._blitFramebuffer); renderArgs._blitFramebuffer.reset(); gpu::doInBatch(renderArgs._context, [&](gpu::Batch& batch) { batch.resetStages(); }); -#endif - fps.increment(); + _fpsCounter.increment(); static size_t _frameCount { 0 }; ++_frameCount; - if (_elapsed.elapsed() >= 4000) { - qDebug() << "FPS " << fps.rate(); + if (_elapsed.elapsed() >= 500) { + _fps = _fpsCounter.rate(); + updateText(); _frameCount = 0; _elapsed.restart(); } + } - if (0 == ++_frameCount % 100) { +private: + class EntityUpdateOperator : public RecurseOctreeOperator { + public: + EntityUpdateOperator(const qint64& now) : now(now) {} + bool preRecursion(OctreeElementPointer element) override { return true; } + bool postRecursion(OctreeElementPointer element) override { + EntityTreeElementPointer entityTreeElement = std::static_pointer_cast(element); + entityTreeElement->forEachEntity([&](EntityItemPointer entityItem) { + if (!entityItem->isParentIDValid()) { + return; // we weren't able to resolve a parent from _parentID, so don't save this entity. + } + entityItem->update(now); + }); + return true; } + + const qint64& now; + }; + + void updateText() { + //qDebug() << "FPS " << fps.rate(); + { + _textBlocks.erase(TextBlock::Info); + auto& infoTextBlock = _textBlocks[TextBlock::Info]; + infoTextBlock.push_back({ vec2(98, 10), "FPS: ", TextOverlay::alignRight }); + infoTextBlock.push_back({ vec2(100, 10), std::to_string((uint32_t)_fps), TextOverlay::alignLeft }); + infoTextBlock.push_back({ vec2(98, 30), "Culling: ", TextOverlay::alignRight }); + infoTextBlock.push_back({ vec2(100, 30), _cullingEnabled ? "Enabled" : "Disabled", TextOverlay::alignLeft }); + } + + _textOverlay->beginTextUpdate(); + for (const auto& e : _textBlocks) { + for (const auto& b : e.second) { + _textOverlay->addText(b.text, b.position, b.alignment); + } + } + _textOverlay->endTextUpdate(); + } + + void update() { + auto now = usecTimestampNow(); + static auto last = now; + + float delta = now - last; + // Update the camera + _camera.update(delta / USECS_PER_SECOND); + { + _viewFrustum = ViewFrustum(); + _viewFrustum.setProjection(_camera.matrices.perspective); + auto view = glm::inverse(_camera.matrices.view); + _viewFrustum.setPosition(glm::vec3(view[3])); + _viewFrustum.setOrientation(glm::quat_cast(view)); + // Failing to do the calculation of the bound planes causes everything to be considered inside the frustum + if (_cullingEnabled) { + _viewFrustum.calculate(); + } + } + + getEntities()->setViewFrustum(_viewFrustum); + EntityUpdateOperator updateOperator(now); + //getEntities()->getTree()->recurseTreeWithOperator(&updateOperator); + { + PROFILE_RANGE_EX("PreRenderLambdas", 0xffff0000, (uint64_t)0); + for (auto& iter : _postUpdateLambdas) { + iter.second(); + } + _postUpdateLambdas.clear(); + } + + last = now; } void render(RenderArgs* renderArgs) { @@ -531,13 +504,21 @@ private: } } - void makeCurrent() { - _context.makeCurrent(this); + bool makeCurrent() { + bool currentResult = _context.makeCurrent(this); + Q_ASSERT(currentResult); + return currentResult; } void resizeWindow(const QSize& size) { _size = size; _camera.setAspectRatio((float)_size.width() / (float)_size.height()); + if (!_ready) { + return; + } + _textOverlay->resize(toGlm(_size)); + makeCurrent(); + glViewport(0, 0, size.width(), size.height()); } void parsePath(const QString& viewpointString) { @@ -563,20 +544,24 @@ private: // we may also have an orientation if (viewpointString[positionRegex.matchedLength() - 1] == QChar('/') && orientationRegex.indexIn(viewpointString, positionRegex.matchedLength() - 1) != -1) { - //glm::vec4 v = glm::vec4( - // orientationRegex.cap(1).toFloat(), - // orientationRegex.cap(2).toFloat(), - // orientationRegex.cap(3).toFloat(), - // orientationRegex.cap(4).toFloat()); - //if (!glm::any(glm::isnan(v))) { - // _camera.setRotation(glm::normalize(glm::quat(v.w, v.x, v.y, v.z))); - //} + + glm::vec4 v = glm::vec4( + orientationRegex.cap(1).toFloat(), + orientationRegex.cap(2).toFloat(), + orientationRegex.cap(3).toFloat(), + orientationRegex.cap(4).toFloat()); + if (!glm::any(glm::isnan(v))) { + _camera.setRotation(glm::normalize(glm::quat(v.w, v.x, v.y, v.z))); + } } } } } void importScene(const QString& fileName) { + auto assetClient = DependencyManager::get(); + QFileInfo fileInfo(fileName); + //assetClient->loadLocalMappings(fileInfo.absolutePath() + "/" + fileInfo.baseName() + ".atp"); _settings.setValue(LAST_SCENE_KEY, fileName); _octree->clear(); _octree->getTree()->readFromURL(fileName); @@ -598,26 +583,89 @@ private: parsePath(destination); } + void reloadScene() { + QVariant lastScene = _settings.value(LAST_SCENE_KEY); + if (lastScene.isValid()) { + importScene(lastScene.toString()); + } + } + + void savePosition() { + // /-17.2049,-8.08629,-19.4153/0,-0.48551,0,0.874231 + glm::quat q = _camera.getOrientation(); + glm::vec3 v = _camera.position; + QString viewpoint = QString("/%1,%2,%3/%4,%5,%6,%7"). + arg(v.x).arg(v.y).arg(v.z). + arg(q.x).arg(q.y).arg(q.z).arg(q.w); + _settings.setValue(LAST_LOCATION_KEY, viewpoint); + } + + void restorePosition() { + // /-17.2049,-8.08629,-19.4153/0,-0.48551,0,0.874231 + QVariant viewpoint = _settings.value(LAST_LOCATION_KEY); + if (viewpoint.isValid()) { + parsePath(viewpoint.toString()); + } + } + + void resetPosition() { + _camera.yaw = 0; + _camera.setPosition(vec3()); + } + + void toggleCulling() { + _cullingEnabled = !_cullingEnabled; + } + + QSharedPointer getEntities() { + return _octree; + } + private: - render::CullFunctor _cullFunctor { cull }; + render::CullFunctor _cullFunctor { [&](const RenderArgs* args, const AABox& bounds)->bool{ + if (_cullingEnabled) { + return cull(args, bounds); + } else { + return true; + } + } }; + + struct TextElement { + const glm::vec2 position; + const std::string text; + TextOverlay::TextAlign alignment; + }; + + enum TextBlock { + Help, + Info, + }; + + std::map> _textBlocks; + gpu::ContextPointer _gpuContext; // initialized during window creation render::EnginePointer _renderEngine { new render::Engine() }; render::ScenePointer _main3DScene { new render::Scene(glm::vec3(-0.5f * (float)TREE_SCALE), (float)TREE_SCALE) }; + OffscreenGLCanvas* _offscreenContext { nullptr }; QOpenGLContextWrapper _context; QSize _size; - RateCounter<> fps; + RateCounter<200> _fpsCounter; QSettings _settings; + ProgramPtr _quadProgram; + ShapeWrapperPtr _plane; + QWindowCamera _camera; ViewFrustum _viewFrustum; // current state of view frustum, perspective, orientation, etc. ViewFrustum _shadowViewFrustum; // current state of view frustum, perspective, orientation, etc. model::SunSkyStage _sunSkyStage; model::LightPointer _globalLight { std::make_shared() }; QElapsedTimer _elapsed; + bool _ready { false }; + float _fps { 0 }; + TextOverlay* _textOverlay; + bool _cullingEnabled { true }; QSharedPointer _octree; - QSharedPointer getEntities() { - return _octree; - } }; void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { diff --git a/tests/render-perf/src/stb_font_consolas_24_latin1.inl b/tests/render-perf/src/stb_font_consolas_24_latin1.inl new file mode 100644 index 0000000000..12eedd2f43 --- /dev/null +++ b/tests/render-perf/src/stb_font_consolas_24_latin1.inl @@ -0,0 +1,734 @@ +// Font generated by stb_font_inl_generator.c (4/1 bpp) +// +// Following instructions show how to use the only included font, whatever it is, in +// a generic way so you can replace it with any other font by changing the include. +// To use multiple fonts, replace STB_SOMEFONT_* below with STB_FONT_consolas_24_latin1_*, +// and separately install each font. Note that the CREATE function call has a +// totally different name; it's just 'stb_font_consolas_24_latin1'. +// +/* // Example usage: + +static stb_fontchar fontdata[STB_SOMEFONT_NUM_CHARS]; + +static void init(void) +{ + // optionally replace both STB_SOMEFONT_BITMAP_HEIGHT with STB_SOMEFONT_BITMAP_HEIGHT_POW2 + static unsigned char fontpixels[STB_SOMEFONT_BITMAP_HEIGHT][STB_SOMEFONT_BITMAP_WIDTH]; + STB_SOMEFONT_CREATE(fontdata, fontpixels, STB_SOMEFONT_BITMAP_HEIGHT); + ... create texture ... + // for best results rendering 1:1 pixels texels, use nearest-neighbor sampling + // if allowed to scale up, use bilerp +} + +// This function positions characters on integer coordinates, and assumes 1:1 texels to pixels +// Appropriate if nearest-neighbor sampling is used +static void draw_string_integer(int x, int y, char *str) // draw with top-left point x,y +{ + ... use texture ... + ... turn on alpha blending and gamma-correct alpha blending ... + glBegin(GL_QUADS); + while (*str) { + int char_codepoint = *str++; + stb_fontchar *cd = &fontdata[char_codepoint - STB_SOMEFONT_FIRST_CHAR]; + glTexCoord2f(cd->s0, cd->t0); glVertex2i(x + cd->x0, y + cd->y0); + glTexCoord2f(cd->s1, cd->t0); glVertex2i(x + cd->x1, y + cd->y0); + glTexCoord2f(cd->s1, cd->t1); glVertex2i(x + cd->x1, y + cd->y1); + glTexCoord2f(cd->s0, cd->t1); glVertex2i(x + cd->x0, y + cd->y1); + // if bilerping, in D3D9 you'll need a half-pixel offset here for 1:1 to behave correct + x += cd->advance_int; + } + glEnd(); +} + +// This function positions characters on float coordinates, and doesn't require 1:1 texels to pixels +// Appropriate if bilinear filtering is used +static void draw_string_float(float x, float y, char *str) // draw with top-left point x,y +{ + ... use texture ... + ... turn on alpha blending and gamma-correct alpha blending ... + glBegin(GL_QUADS); + while (*str) { + int char_codepoint = *str++; + stb_fontchar *cd = &fontdata[char_codepoint - STB_SOMEFONT_FIRST_CHAR]; + glTexCoord2f(cd->s0f, cd->t0f); glVertex2f(x + cd->x0f, y + cd->y0f); + glTexCoord2f(cd->s1f, cd->t0f); glVertex2f(x + cd->x1f, y + cd->y0f); + glTexCoord2f(cd->s1f, cd->t1f); glVertex2f(x + cd->x1f, y + cd->y1f); + glTexCoord2f(cd->s0f, cd->t1f); glVertex2f(x + cd->x0f, y + cd->y1f); + // if bilerping, in D3D9 you'll need a half-pixel offset here for 1:1 to behave correct + x += cd->advance; + } + glEnd(); +} +*/ + +#pragma once + +#ifndef STB_FONTCHAR__TYPEDEF +#define STB_FONTCHAR__TYPEDEF +typedef struct +{ + // coordinates if using integer positioning + float s0,t0,s1,t1; + signed short x0,y0,x1,y1; + int advance_int; + // coordinates if using floating positioning + float s0f,t0f,s1f,t1f; + float x0f,y0f,x1f,y1f; + float advance; +} stb_fontchar; +#endif + +#define STB_FONT_consolas_24_latin1_BITMAP_WIDTH 256 +#define STB_FONT_consolas_24_latin1_BITMAP_HEIGHT 170 +#define STB_FONT_consolas_24_latin1_BITMAP_HEIGHT_POW2 256 + +#define STB_FONT_consolas_24_latin1_FIRST_CHAR 32 +#define STB_FONT_consolas_24_latin1_NUM_CHARS 224 + +#define STB_FONT_consolas_24_latin1_LINE_SPACING 16 + +static unsigned int stb__consolas_24_latin1_pixels[]={ + 0x08262131,0xff904400,0x3ffe1fff,0x3b2206ff,0x2007913f,0x0000defa, + 0x64c00f32,0x0de5c402,0x00a614c0,0x4002ae62,0x98014c19,0x01aa881c, + 0x00d54400,0xb880154c,0x8330020b,0x1e980029,0xaa7d5dd4,0x2001d94f, + 0x3332a6e8,0x999ff0ff,0x37ffa207,0x2600df12,0x8000fffd,0x3fa005fd, + 0xfdff700f,0x8ffc409f,0x3ea02ff8,0x200dffff,0x0bfe0ff8,0x7d407ee0, + 0xfd10001f,0x9fff5007,0xcffff880,0x1ff104eb,0x320017fc,0x7d77e40f, + 0x17ee9f54,0xfd027ec0,0x7dc01fe1,0x0037c40d,0xb0017f22,0xffe8007f, + 0x7dc3fd80,0x741ff104,0x59ff701f,0x7401dfd7,0x2003f60e,0x3fa200fc, + 0x0ff44001,0x7dc6fdc0,0x32236604,0x3ba00eff,0x31003f60,0xdf90dd57, + 0xd93ea9f5,0x037e403f,0x803fc3fa,0x06f882fd,0x2006f980,0xa8800098, + 0xf903fb81,0x88040401,0x1ff441ff,0x0fb00000,0x00000000,0x00020000, + 0xffffa800,0xfadfb86f,0x7fc49f54,0x803ff301,0x200ff0fe,0x06f880fe, + 0x0000ff00,0x05f88000,0x40000fe6,0x0ff504fc,0x81540153,0x100affb9, + 0x22001573,0x31000ab9,0x26200157,0x731000ab,0xcffb8015,0x7dc4ffda, + 0x89f54fad,0x3fe80ff9,0x0ff0fe80,0x3e203fa0,0x807ddb36,0x01dd107f, + 0xdddb076c,0x1fb8bddd,0x1dd12fc0,0x3ff076c0,0x3f60ffc0,0xe98ff102, + 0xa84fffff,0x00dfffff,0x37ffffea,0x3fffea00,0x3fea00df,0x2a00dfff, + 0x80dfffff,0x3bb61ff8,0x3eb3ea3f,0x7f909f54,0xfd005fa8,0x7f401fe1, + 0xffaef880,0x1fe05ffe,0xdf504fd8,0xfffffff8,0x9db33746,0x09fb1b6b, + 0x80ff1bea,0x817ec2fd,0x33fe67f8,0x7dc3acfa,0x0efebacf,0xebacffb8, + 0xcffb80ef,0xb80efeba,0xefebacff,0xbacffb80,0x27e40efe,0x20df59f1, + 0x509f54fa,0x003fd8bf,0x401fe1fd,0x9fff107e,0x7407fe61,0x20ff500f, + 0x6f9803fd,0x777ccfe2,0x7fa8db6f,0x37cc7fb0,0x5fb0ff60,0x3fe9fe20, + 0x3ff105f3,0x7c43fe88,0x21ff441f,0x7f441ff8,0x220ffc43,0x0ffc43fe, + 0x3ff0ffa2,0x267f97d4,0x9f54fadf,0x1ff8ff10,0x1fe1fd00,0x7c417e20, + 0x704fb84f,0x217f405f,0xf9800ff8,0x47f47ea6,0xfd0f95f8,0x260ff885, + 0x21fe406f,0x2ff102fd,0x207ea6f9,0x8ff504fc,0x8ff504fc,0x8ff504fc, + 0x8ff504fc,0x8ff504fc,0x3fd3e47f,0x553eafea,0x261ff04f,0x1fd000ff, + 0xefcc81fe,0x260ff101,0x9bfb105f,0x7d45fb81,0x64df3005,0x9f34f98f, + 0x517ee1f2,0x407f98bf,0x817ec2fd,0x5cbf57f8,0x201ff80f,0x807fe1ff, + 0x807fe1ff,0x807fe1ff,0x807fe1ff,0xf8df31ff,0x47e4bf65,0x203514fa, + 0x00df52fd,0x107f87f4,0x7c403fff,0x440df306,0x7fc41ffe,0x4c00bf60, + 0x97ddf66f,0xf10db2fa,0x2217ec1f,0x20ff407f,0x2ff102fd,0x7c1f64fb, + 0xff17ec07,0x1fe2fd80,0x03fc5fb0,0x407f8bf6,0x4cdf32fd,0x9d4ff22f, + 0x9f7004fa,0xfe8013ee,0x6cc40ff0,0x20df103f,0x3bf506f9,0x7c4ff601, + 0xd9be6007,0x47f23f96,0x227fb06d,0x203ff07f,0x17ec0ff8,0x4df57f88, + 0x406f986e,0x80df33fd,0x80df33fd,0x80df33fd,0x80df33fd,0x64ff13fd, + 0x2a0bf60f,0x29f9004f,0x3fa005fa,0x1be00ff0,0x5fa837c4,0xfa801f90, + 0x4c009f76,0x87edba6f,0x2a09f0fd,0x3209f76f,0xb0bf704f,0x4dfe205f, + 0xf30bf0ff,0xf33fc80d,0xf33fc80d,0xf33fc80d,0xf33fc80d,0x3e3fc80d, + 0x03fd1ba7,0x3f6009f5,0x7400ff13,0x3a00ff0f,0x906f880f,0x401fa07f, + 0x001fe9ff,0xf96e9be6,0x017cdfe3,0x203fd3ff,0x7fd43ff9,0xf102fd81, + 0x27d7fecf,0xfb01fe63,0xfb01fe65,0xfb01fe65,0xfb01fe65,0xfb01fe65, + 0x1fc4ff45,0x9f501ff1,0xfe8bfe00,0x3e1fd001,0x101fd007,0x40df50df, + 0xfbf9007e,0x26f9800d,0xfdb9f56d,0x7e401fb7,0x7fdc06fd,0xb02ffede, + 0x89fe205f,0x4feefffc,0x1fe80ff1,0x1fe80ff1,0x1fe80ff1,0x1fe80ff1, + 0x1fe80ff1,0xb87ef7f2,0x2a9f505f,0x647f984f,0x21fd002f,0x01fd007f, + 0xfb99dff1,0x803f403f,0x4003fff8,0x6c5f26f9,0x01ffe8ef,0x401fffc4, + 0x01dfffda,0x2fd40ff2,0x0e77ff4c,0x3fe203ff,0x7c407fe0,0x4407fe0f, + 0x407fe0ff,0x07fe0ff8,0xff107fc4,0x807fd4fd,0x509f54fa,0x004fa8bf, + 0x401fe1fd,0xfff880fe,0x7400deff,0x01ff6007,0x0fb9be60,0x7ec00302, + 0x027dc007,0x4fc827dc,0x3f203f70,0xfc8bf704,0xfc8bf704,0xfc8bf704, + 0xfc8bf704,0xf30bf704,0x07ffd9df,0x84faa7d4,0x07fc41fe,0x07f87f40, + 0xdf101fd0,0x07f80022,0x2000ff60,0x00fe65fa,0x001fec00,0x98103fe6, + 0x0ffcc1ff,0xff100fc8,0x440ffa87,0x07fd43ff,0xff50ffe2,0x543ff881, + 0x1ffc40ff,0x7dc03fea,0x5401dfff,0xfc89f54f,0xd00df705,0x6c01fe1f, + 0x006f883f,0xf5006f98,0x9fb0001f,0xa80006e8,0xfd8000ff,0xfc86fcdf, + 0x04ffecdf,0xdff701f6,0x2e07ffd9,0x3ffeceff,0xfd9dff70,0x3bfee07f, + 0xf703ffec,0x07ffd9df,0x5000cfec,0xfa93ea9f,0x013f600f,0x401fe1fd, + 0x37c41efb,0x013f6600,0x33007fea,0x2e07fdc4,0xf500505f,0x7e40003f, + 0xfd703fff,0x6e805dff,0xdfffea80,0x7fff5401,0x7ff5401d,0x7f5401df, + 0x75401dff,0x7c01dfff,0x54fa8005,0x00bfe29f,0x775c3fd1,0xddff0ffe, + 0x7fff409d,0x2600df12,0xf300effe,0xf7007fff,0x207fffdf,0x7fdcdffb, + 0x07ffff30,0x80022000,0x0017e001,0x00060003,0x0018000c,0x07220030, + 0x7d53ea00,0x26007fb4,0x3bbbae6f,0x9ddddb0e,0xd12ecb80,0x0f3a600b, + 0x0019bd30,0x073bbb22,0x17bdb730,0x00337a60,0x00000000,0x00000000, + 0x00000000,0x00000000,0x4d3ea9f5,0x00154004,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000,0x20000000,0x009f54fa, + 0x77777400,0xeeeeeeee,0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x4c000000,0x0007d33e,0x77777740,0x0eeeeeee, + 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x54400000,0x2a20001a,0x0154c01a,0x54400b20, + 0xaaa98000,0x99953100,0x032e0059,0x10053066,0x22004177,0x04c801aa, + 0x01aa8800,0x500d5440,0x51017997,0x51000035,0x0bb88035,0x00d54402, + 0x0007fea0,0xf500ffa2,0x3e6009ff,0x3fef9803,0xfffff700,0xffffb89f, + 0xf8804fff,0x3e0ff886,0x3ffe202f,0x5404ebcf,0x0fec01ff,0x07fd1000, + 0x103fe880,0x05fffffd,0x80007fea,0x7c403fe8,0x04ebcfff,0x88003ff5, + 0x3a2001fe,0x46fdc01f,0x7dc404fb,0x6baec00b,0xabcffd80,0x3fff24ec, + 0x804fb9df,0x41dd03fb,0x236600fd,0x800effc8,0x3e601fe8,0x3fd10005, + 0x01fe8800,0x008833f6,0x20007fa2,0xd9801fe8,0x00effc88,0x00007fa2, + 0x00000000,0x9fffffd5,0x036cf640,0x98407bee,0xf54fffff,0x001fd009, + 0x00020000,0x0007f400,0x44000000,0x0000007f,0x00200000,0x40153000, + 0xa802a62a,0x2a802a62,0x33fb7ff2,0x3bfa204d,0x007fe201,0x53fffff2, + 0x27d404fa,0x40015540,0x553002aa,0x50555555,0x01aa807f,0x554c1544, + 0xf82aaaaa,0x2aa8002f,0x802aa800,0x50aa02a9,0x55555555,0xf102fd81, + 0xf8817ecf,0x7c40bf67,0x1f61ff37,0x5c02aa80,0xfff9005f,0x013ea9ff, + 0xff8803fb,0x3fe2002f,0xfffc802f,0xdf07ffff,0x6406fb80,0xffffc85f, + 0xffb87fff,0x3ffe2003,0x3ffe2002,0x41ffe402,0x7fffc6f8,0x6c0fffff, + 0x6cff102f,0x6cff102f,0x2eff102f,0x4401ba5f,0x3f202fff,0xffff7003, + 0x8813ea9f,0xfffa805f,0x3ffea004,0xaacfc804,0x5f902aaa,0xf103ff80, + 0x559f901f,0xff985555,0xfa802eff,0x3ea004ff,0x7fe404ff,0x5546f886, + 0x0aaadfda,0x7f8817ec,0x3fc40bf6,0x5fe205fb,0x4017e7fa,0x3604fffa, + 0xfff3002f,0x813ea9ff,0xafd802fc,0x2bf6007f,0x03fc807f,0x2a02fc40, + 0x04fd80ff,0x3fa007f9,0x404ffd89,0x2007fafd,0x6407fafd,0x37c42fef, + 0xfd809f70,0x7ecff102,0x7ecff102,0x3e2ff102,0xb004faef,0x7f40ff5f, + 0x3fff2002,0x7c09f54f,0xfd7f8807,0x3aff1003,0x01fe401f,0x3a00fec0, + 0x807fcc4f,0x5f9803fc,0xf8827fd4,0xf1003fd7,0xfc807faf,0x037c46fb, + 0x2fd809f7,0x17ecff10,0x0bf67f88,0xffd33fc4,0x7f88019f,0x0bfa03fd, + 0x29ffd500,0x07f504fa,0x13ee9f50,0x27dd3ea0,0x2000ff20,0x7fcc04fa, + 0xfc80ff60,0x886f9803,0x74fa81ff,0x29f5009f,0x2bf204fb,0x81be22fd, + 0x17ec04fb,0x0bf67f88,0x05fb3fc4,0x3fae1fe2,0x53ea03ff,0x07fb04fb, + 0x4fa84c00,0xfb001fd0,0x3601fe65,0xc80ff32f,0x1fd0003f,0xdf34fd80, + 0xf003fc80,0xb07f905f,0x201fe65f,0x40ff32fd,0x226faafc,0x013ee06f, + 0x9fe205fb,0x4ff102fd,0x0ff102fd,0x417ff7ee,0x20ff32fd,0x500006fb, + 0x00bf309f,0x01fe8ff1,0x03fd1fe2,0x777777e4,0x01fdc04e,0x0bf63fe2, + 0xdddddf90,0x43ffa89d,0x23fc43fb,0x1fe201fe,0x4bf203fd,0x40df11fe, + 0x17ec04fb,0x0bf67f88,0x05fb3fc4,0x9be41fe2,0x23fc43ff,0x2ff881fe, + 0x84fa8000,0x5fa801fc,0xbf5027e4,0x3f204fc8,0x06ffffff,0x7e4037c4, + 0xffc806fe,0xa86fffff,0x0ff89eff,0x13f22fd4,0x27e45fa8,0x2bf52fc8, + 0x13ee06f8,0x3e205fb0,0x7c40bf67,0x7c40bf67,0x2fdcdb07,0x09f917ea, + 0x0620bff2,0x3e227d40,0x985fb006,0x30bf607f,0x01fe40ff,0x8803f900, + 0xfc801fff,0x7fec4003,0x42fd82ff,0x0bf607f9,0x8bf20ff3,0x206f89ff, + 0x17ec04fb,0x0bf67f88,0x05fb3fc4,0xb2f41fe2,0x4c2fd89f,0x3fff607f, + 0x5004fedd,0x802fb89f,0x99999ff8,0x9ff881ff,0x01ff9999,0x4c0007f9, + 0x05fb805f,0x20007f90,0xff886fe9,0x1ff99999,0x9999ff88,0x2fc81ff9, + 0x81be33ee,0x1fe404fb,0x0ff25fa8,0x07f92fd4,0x9f04d7ea,0x7c43ff71, + 0xff99999f,0x3fffaa01,0x7f9002ce,0xff5007e8,0x9fffffff,0xffffff50, + 0x3f209fff,0x07f40003,0x64013ee0,0x3a20003f,0x7fffd42f,0xa84fffff, + 0xffffffff,0x222fc84f,0x2e06f9ff,0x827dc04f,0x413ee4fc,0x413ee4fc, + 0xfdffb4fc,0xfa87ffff,0xffffffff,0x0077c404,0x9f517f40,0x3337f600, + 0xd86fdccc,0xdcccccdf,0x007f906f,0x5c04fa80,0x07f9004f,0x6c2fe800, + 0xdcccccdf,0xccdfd86f,0xc86fdccc,0x37e7e42f,0xf9809f70,0x30ffcc1f, + 0x1ff983ff,0xff307fe6,0x3fffb6a3,0xcdfd80ce,0x06fdcccc,0x37601fd4, + 0x6c6fd989,0x0ff8800f,0xff887fe0,0x3207fe00,0x7f80003f,0xc8027dc0, + 0x4c15003f,0x3fe20ffb,0xf887fe00,0x907fe00f,0x6fff885f,0x32013ee0, + 0x4ffecdff,0xfecdffc8,0xcdffc84f,0x3f704ffe,0xf007fc40,0x00fe403f, + 0xdfffffb1,0xa802fcc3,0x527ec05f,0x84fd80bf,0xeeeeeefc,0x80bee006, + 0xdf9004fb,0xf8dddddd,0x41ffffff,0x27ec05fa,0x4fd80bf5,0x7fe417e4, + 0x7ff776c6,0xfd700eee,0x75c05dff,0x2e02efff,0x202efffe,0x17ea00fc, + 0x14c09fb0,0x82cdba80,0x7fb001ca,0x3f66fa80,0x6437d403,0x7fffffff, + 0xb80f2200,0xfff9004f,0xcb8fffff,0x7fb02cdd,0x3f66fa80,0x3237d403, + 0x46ff882f,0xffffffff,0x8001800f,0x90018001,0x403fd80b,0x000006fa, + 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x0d544000,0x2600aa60,0x54c014c1,0x14c19802,0x400154c0, + 0xcb9802c9,0x0332e02c,0x30a60f22,0x00332a05,0x4000b260,0x0cca83cc, + 0x01e64000,0x2e200b26,0x6644020b,0x00cca801,0xe8803ca8,0x7ffd403f, + 0xf83fe204,0x3ffea02f,0xf83fe204,0x3ffea02f,0x3f7ee004,0x3ffff604, + 0x7f7ec0ef,0x220fe40f,0x05ff11ff,0xa8003bee,0x6c003fff,0x03bee05f, + 0x202fec00,0x2203fffa,0x4eacffff,0x9d95ff70,0x9003bee0,0x1fe880bf, + 0x7dc6fdc0,0xfd83ba04,0xf71bf700,0xfb077409,0x2e37ee01,0x2a7d004f, + 0x261bf907,0x54fea4fd,0x2213ea3f,0x807fa0ff,0xfa800ef9,0xb003fc8d, + 0x1df3007f,0x403fd800,0x03fc8dfa,0xdffb31d3,0xfffdb881,0x3be603ef, + 0x0017f200,0x00000000,0x00000000,0x7b8dd800,0x6f981ff0,0x8a7c47ee, + 0x020200ee,0x22002620,0x4c400cc1,0x00262000,0x18800988,0x011000cc, + 0x0ffb7fe2,0xf9004c40,0x5555550b,0xaa981555,0x982aaaaa,0x2aaaaaaa, + 0xaaaaaaa8,0x555540aa,0x200aaaaa,0x7cc002aa,0x86f882ff,0x44bee5fa, + 0x0005f94f,0x00000000,0x00000000,0x00000000,0x001ff982,0xff0bf900, + 0x1fffffff,0xffffffc8,0xffffc87f,0xfff87fff,0x40ffffff,0xffffffff, + 0x5fff100f,0x26013000,0x30ffd45f,0xf33fb3bf,0x9dfb7009,0xcefdb801, + 0x677edc00,0x677edc00,0xdfdb7100,0x3b6e203b,0xb7101def,0x2203bdfd, + 0x01defedb,0x01bffb2a,0x7039dfb7,0xfb5550bf,0xfc81555b,0x82aaaaac, + 0xaaaaacfc,0xdfdaaa82,0x55540aaa,0x00aaadfd,0x1009fff5,0x83bdfdb7, + 0x07bee5f9,0x7f4fffee,0x76f7ec00,0xbdfb02ff,0x3f605ffd,0xb02ffede, + 0x05ffdbdf,0xfffddff7,0xfddff705,0xdff705ff,0xf705fffd,0x05fffddf, + 0x5ffddffb,0xffdffd30,0x404fb87f,0x1fe404fb,0x0007f900,0xfb8013ee, + 0xff5fb004,0xfddff700,0x8afcc5ff,0x426200ff,0x27e402fc,0x9f903fea, + 0x7e40ffa8,0x3207fd44,0x207fd44f,0x43fdc419,0x43fdc419,0x43fdc419, + 0x23fdc419,0x1bea0dfc,0x3ff513fa,0x3ee027dc,0x001fe404,0x2e0007f9, + 0x13ee004f,0x0ff5fe20,0xff710660,0x07f8afcc,0xf1017e60,0xf88ff20d, + 0x7c47f906,0x7c47f906,0x4007f906,0x3fe000ff,0x003fe000,0x0df30ff8, + 0x05fa87fa,0x027dcbf9,0x7f9013ee,0x001fe400,0x2e004fb8,0x74fa804f, + 0x7fc0009f,0x27fcbf30,0x5400fe80,0x549f504f,0x549f504f,0x549f504f, + 0x009f504f,0xfb000fec,0x00fec003,0x0fee3fb0,0x05f927e4,0x04fb9be2, + 0x3f2027dc,0x00ff2003,0x70027dc0,0x25fb009f,0x360007f9,0x7ccbf31f, + 0x4bee00df,0x77dc1dec,0x5feeeeee,0x3bbbbbee,0x3bee5fee,0x5feeeeee, + 0x3bbbbbee,0xec985fee,0x981ffffe,0x1ffffeec,0xfffeec98,0xfeec981f, + 0x05fb1fff,0x01fe97ea,0x403fa9fe,0x77e404fb,0xc84eeeee,0x4eeeeeef, + 0x70027dc0,0x47f8809f,0x764c01fe,0xf31ffffe,0x80efe98b,0xffbfd5f8, + 0x77777e43,0x3f24eeee,0xeeeeeeee,0x3bbbbf24,0x3f24eeee,0xeeeeeeee, + 0x6677fdc4,0x7fdc1ffc,0x41ffccce,0xfccceffb,0x677fdc1f,0x3fb1ffcc, + 0x1fe97ea0,0x03fa9fe0,0x3f2027dc,0x86ffffff,0xfffffffc,0x0027dc06, + 0x5fa809f7,0xff7027e4,0x23ff999d,0x4fe885f9,0x33f98fe8,0x002fdc9f, + 0x7dc00bf7,0x017ee005,0xfd81ff88,0x3607fe21,0x207fe21f,0x07fe21fd, + 0x817e47f6,0x40bf64fb,0x007266f8,0x3fc809f7,0x000ff200,0xf70027dc, + 0x4c2fd809,0x03ff107f,0x417e63fb,0xb9fdc6f9,0xffa97e1f,0x01ff5000, + 0x4003fea0,0xf5000ffa,0xfa87fa0b,0x7d43fd05,0x7d43fd05,0x3ee3fd05, + 0x7e45fb05,0x000bf704,0x3fc809f7,0x000ff200,0xf70027dc,0x4cffc409, + 0xa81ff999,0x263fd05f,0x44df305f,0x7c4bea6f,0x80067f44,0xfd000cfe, + 0x33fa0019,0x446fa800,0x1bea1ffd,0x7d43ffb1,0x50ffec46,0x1ffd88df, + 0x7fa85ff1,0x3e60ffc4,0x700faa1f,0x03fc809f,0x4000ff20,0x3ee004fb, + 0x3fffea04,0xa84fffff,0x8ffec46f,0xfb9935f9,0xf10fec5f,0xf983fb7d, + 0x0eccbdff,0x65efffcc,0x7ffcc0ec,0x4c0eccbd,0xeccbdfff,0x373bfe20, + 0x3e21feff,0xfeffdcef,0x373bfe21,0x3e21feff,0xfeffdcef,0x3737fee1, + 0xdffb82ff,0x7fc3ffdc,0x2027dc07,0x3f2003fc,0x09f70003,0xfb027dc0, + 0xfb99999b,0xb9dff10d,0x3e63fdff,0x45dfff35,0xfffa83fa,0xffffb102, + 0xffb101df,0xb101dfff,0x01dfffff,0xdfffffb1,0xdfffe981,0x7f4c1fc8, + 0x41fc8dff,0xc8dfffe9,0x7fff4c1f,0x7541fc8d,0x2a01efff,0xc81efffe, + 0x027dc05f,0xfc800ff2,0x09f70003,0xf8827dc0,0x207fe00f,0xc8dfffe9, + 0x0002201f,0x02620008,0x20009880,0x26200098,0x40008800,0x00440008, + 0x06000220,0x40600600,0xeeffeeed,0x7777e40e,0xefc86eee,0xd86eeeee, + 0xeeeffeee,0x7ff776c0,0x0bf50eee,0x02204fd8,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0xffffff80,0x7fe40fff,0xc87fffff, + 0x7fffffff,0xfffffff8,0x7fffc0ff,0xfb0fffff,0x006fa807,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, + 0x40f32000,0xca814c29,0x055cc00c,0x203cc800,0x98014c29,0x0bb8802c, + 0x40044002,0x298003c8,0x0310014c,0x8000b260,0x157300cb,0x76c17a20, + 0x00f32000,0x32a0aa62,0x027d400c,0xf882fec0,0x205ff11f,0x3ee00efb, + 0x36001eff,0x47fe205f,0x7d402ff8,0x3fe203ff,0x204eacff,0x203ffffa, + 0x7c4006f8,0x005ff11f,0x3fea01f6,0x3fb0003f,0x4fffff98,0x1fd06f88, + 0x8817f600,0x706ffffb,0x7ff401df,0x80ff6000,0x07fa0ff8,0x5100ef98, + 0xb005ffd7,0x0ff8807f,0xdfa807fa,0x1d303fc8,0x201dffb3,0x2ffdcffa, + 0x8800df10,0x007fa0ff,0x37ea02fc,0xd8003fc8,0x26ffea1f,0x37c44feb, + 0xfd800fe8,0x37ffe603,0x3be602ac,0x400bf700,0x10100098,0x20013100, + 0x26200ffb,0x00202000,0x20019831,0x717ec008,0x01be20bf,0xb7004040, + 0x18807fff,0x7ec000cc,0xff10bfa1,0xfe837c41,0x1004c400,0x310007ff, + 0x00000001,0x20000000,0x000004fd,0x00000000,0x6f983fe0,0x0000df10, + 0xfeffe980,0x000001ff,0x17ea3fb0,0x0df127dc,0x200003fa,0x000003fc, + 0x05e88026,0x82f443d9,0x417a21ec,0x17ee01ec,0x00e77edc,0x80e77edc, + 0x03d905e8,0x8073bf6e,0x413ee2fe,0x7ddb36f8,0x3bfb6e20,0xf13fe81d, + 0x6dc03ffd,0x65401cef,0xf91ffdee,0x44dfd305,0x701fd06f,0x7c0bdddd, + 0x7775c007,0x407f305e,0x45fb06f8,0x45fb06f8,0x05fb06f8,0x3fa61fdc, + 0x303fffef,0x7fffdffd,0x3f60df10,0x7f7ff4c2,0x2df903ff,0xdf100ffa, + 0x0bffdff5,0xfffddff7,0x5f52fdc5,0xffd309f7,0xb107fffd,0x3fffdfff, + 0xfff707f6,0xfe837c4f,0x7ffffc80,0x2aa2bf30,0xffff9009,0x8813ea0f, + 0x445fb06f,0x445fb06f,0x205fb06f,0x27f40ff9,0x3fa07fea,0x220ffd44, + 0xe85fb06f,0x40ffd44f,0x01efeff8,0x4c33ffe2,0x220cc1ff,0xd8bf27fb, + 0x9fd0bf17,0x7e41ffa8,0xfe8ff42d,0xff3dfb10,0x0fe837c4,0xfa83fc40, + 0x2fffffee,0xfa83fc40,0x6c1be204,0x6c1be22f,0x6c1be22f,0x3fffe62f, + 0xfc82fd44,0xfc82fd45,0xfd837c45,0x7e417ea2,0x00bffb05,0x9f709ff1, + 0xfe87fc00,0x547f93e0,0x44bf905f,0x3e3fb07f,0xf0dff98f,0x303fe21f, + 0x7f8803ff,0x537bff70,0x7c403ff9,0x4409f507,0x445fb06f,0x445fb06f, + 0x4c5fb06f,0x05f902ef,0x05f91be2,0x0df11be2,0x02fc8bf6,0xefe88df1, + 0x88ff11ff,0x00bf307f,0x50fe8fec,0x3f23fc5f,0x7dcdf102,0x3fa3fb04, + 0x13fc3ffc,0x7ff445ff,0xb83fc401,0x40bf904f,0x09f507f8,0x2fd837c4, + 0x17ec1be2,0x8bf60df1,0x07fa04f9,0x00ff47f8,0xb06f88ff,0xf00ff45f, + 0xdfb4fd8f,0x6f88df31,0xd930df30,0x323ffffd,0x37c4fb2f,0x27f807fa, + 0x23fb03fc,0x7c41effd,0x337ffe27,0x202efbef,0x09f707f8,0x7f881be6, + 0x7c40bf50,0x7c45fb06,0x7c45fb06,0x7cc5fb06,0xf807fa04,0xff00ff47, + 0x5fb06f88,0x4ff00ff4,0x56ff47f8,0x9837c45f,0x677fdc6f,0x9f51ffcc, + 0x745fa97e,0xfd9fe01f,0x3f23fb02,0x225fa80d,0xb0dffeef,0x83fc409f, + 0x0ff105fa,0x5fb83fc4,0x7ec1be20,0x7ec1be22,0x7ec1be22,0xfd80b222, + 0xfd8df102,0xf88df102,0x7ec5fb06,0x7ccdf102,0x17fffc46,0x2fd41be2, + 0x3fb03ff1,0x44bf3fe2,0x817ec1fe,0x413f26f8,0x20df31fe,0x45be22fd, + 0x07f88000,0x17ea0ff1,0xbf707f88,0x7c40ff80,0x2207fc2f,0x207fc2ff, + 0x64002ff8,0xc8bf704f,0xf0bf704f,0x22ff881f,0x4bf704fc,0xfffa87f9, + 0xfc837c40,0x7f417ea3,0x373ffea1,0x04fc84ff,0x42fdcbf7,0x0ffa1ffb, + 0x06f88ff5,0xb03fc400,0x02fe889f,0x2fdc1fe2,0xfe88bfe0,0xd117fc2f, + 0x22ff85ff,0x3a62ffe8,0x307fe204,0x1ff883ff,0x7fc0ffcc,0x88bffa22, + 0x0ffcc1ff,0xffd50ffe,0xfa86f883,0x36237d46,0x7ffd41ff,0x3ff102ef, + 0x3e21ff98,0x87ffea1f,0xffdcdff9,0x00037c41,0xff981fe2,0x404fecbd, + 0x0bf707f8,0xefcdffb8,0x6ffdc2fd,0x5c2fdefc,0xfdefcdff,0xb803ff62, + 0x3ffdcdff,0xfb9bff70,0x37fee07f,0x5c2fdefc,0x3ffdcdff,0xffceffa8, + 0x3e20efef,0x1ffdccef,0x7ee77fc4,0x5fd41fef,0x9bff7001,0xffb07ffb, + 0x83f99fdb,0x81dfffd8,0xcc8006f8,0x1cccffcc,0x177ffec4,0xcffcccc8, + 0x00df71cc,0xf71bffd5,0x1bffd505,0xffd505f7,0x7dc5f71b,0xfffea806, + 0x7ff5401e,0x7f5401ef,0xa82fb8df,0x201efffe,0xd1dfffea,0xfffdb8bf, + 0xffd300de,0xd83f91bf,0xffea8007,0x3ff201ef,0x0c03f93e,0x20017a20, + 0xffffffff,0x3e00603f,0xffffffff,0x4400bd73,0x00044000,0x00020022, + 0x000c0006,0x00c00044,0x01000040,0x3c800440,0x2000c000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x55530000,0x53035555,0x54c00557, + 0xba9800aa,0x2aaaa60a,0x2a600aaa,0x0032e009,0x32205950,0x020bb883, + 0x3c881654,0x6440736e,0x09999953,0x80357510,0x209aca98,0x20aa00a9, + 0x2e014c29,0x0164c03f,0x1dd127dc,0x64c076c0,0xfffffc82,0x7ffe44ff, + 0x3ee03fff,0x904fffff,0x2bffffff,0xfffffffc,0xfffffb81,0x000fe80d, + 0x320bffd3,0x7fffc41f,0x3fa64eac,0x6c3f905f,0x1fc80ffd,0x40fffff9, + 0xefffffc8,0xfffffc81,0x440bf65f,0x88ffc47f,0x1ffe02ff,0x203fffa8, + 0x3f62fff8,0x3a0df504,0x567e40ff,0x5dc1aaaa,0x81ffdb9a,0xecabcffd, + 0x5e7ff444,0x55535dca,0xfd83fd55,0x0efc989c,0xea8007f4,0x84fa85fa, + 0xeffd98e9,0x217ebaa0,0x83fb04fa,0x266624fa,0x2fbf607f,0x360ffda9, + 0x3baabcef,0x3fc40bf6,0x1fe83fe2,0x7d413f60,0x3e03fc8d,0x41fea1ff, + 0x1ffd03fd,0x40001fc8,0x0f7dc4fe,0x8077ec08,0xf70ff400,0xfd17ea07, + 0x45f88001,0x11000ee8,0x3a22fc40,0x22ffe40e,0xff100ee8,0x3e03fe20, + 0x003ff32f,0x1fe205fb,0x20000404,0x9300cc18,0xf885fd05,0x9035100f, + 0xfb80003f,0x4007fe25,0x20000ffa,0x4cdf11fe,0x743f91bb,0x2fc4000f, + 0x80000bf2,0x417e45f8,0x3f23fda9,0x441fe202,0x2e5fb06f,0x05fb005f, + 0x80001fe2,0x00000098,0x5fa8bf70,0x003f9000,0x3ee2fc80,0x00ff6005, + 0x3ee3fd00,0x22ffff52,0x3603fa4f,0x265f884e,0x2a9d104f,0xbf101cfd, + 0xc99827cc,0x8809f34f,0x10bfa07f,0x007fd4ff,0x7f8817ec,0x02f7775c, + 0xeeb82fb8,0x440005ee,0x70bf60ff,0xf90bdddd,0xf3000335,0x001fe41d, + 0xe80003fd,0xdf11f91f,0x3fa5f823,0x44077ec0,0x4403fa5f,0xffeefcdf, + 0x3fa5f884,0x21dfff00,0x3fc400fe,0xf519ff50,0x177f447f,0x7c40bf60, + 0x3ffffe47,0xfc82fb80,0x80007fff,0x90ff13fd,0xf90fffff,0x205bffff, + 0xd85feee8,0x83fe002f,0x403cccc9,0x3eafb1fe,0x07f4fb03,0x90980bfb, + 0x7ffc405f,0x2603fea3,0x4cc05f90,0x2200bf20,0x7ffcc07f,0xffe981ff, + 0x02fd80be,0x3fc40ff1,0x2017e440,0xa80007f8,0x8809f76f,0xdccca87f, + 0xff982fff,0x3fa0cfff,0xa8ff1002,0x7406ffff,0x0beadd1f,0x361fd3ec, + 0x17e6005f,0xff07ff10,0x002fcc03,0x7c4017e6,0x7ffec407,0x3ffae03f, + 0x8817ec3f,0x81fe207f,0x403fffd9,0x2da807f8,0x07fa7fe0,0x6400ff10, + 0x6fd9807f,0x7c400bf6,0x37d4cc47,0x8bec7fa0,0x7f4dd04f,0x74005fd8, + 0x83fc400f,0x03fa02fd,0x2001fd00,0x3fa607f8,0x405ffc8c,0x3f65ffda, + 0x440ff102,0x273fa07f,0x03fc4009,0xfc80fffc,0x7f8806fd,0x007fe200, + 0x03fc97f4,0x7cc03fe0,0xfc8ff406,0x7c737fd0,0x01bf7fa5,0x33265f70, + 0xfd837c42,0xd915f702,0x997dc05b,0x0ff102cc,0x3fe617fc,0xb2ffa802, + 0x81fe205f,0x0bf307f8,0xe807f880,0xfff103ff,0x00ff1007,0xf9002fe8, + 0xe801bee7,0x80df303f,0x267f51fe,0x47f37ffd,0x002ffafe,0x27ff4bf1, + 0x17ec1be2,0xecfaafc4,0x3a5f881f,0x0ff104ff,0x2fe417e6,0x7f94fc80, + 0xf8817ea0,0x8009f707,0xff9807f8,0x401ff603,0x3e2007f8,0x23fd000f, + 0xf5002ff8,0x40df301f,0x11fa0ff9,0x1fd07ec1,0xfd005ff3,0x113eff21, + 0x20bf60df,0x17dc20fe,0xfbfc87f4,0x2e0ff104,0x00bf704f,0x827dcff2, + 0x1fe204fc,0x220037dc,0x03ff007f,0xf8801fec,0x09fb1007,0xff91bee0, + 0xefe83105,0x20bb7cc0,0x77d46fc8,0x7f43fc80,0x5c03ff50,0x9f39f33f, + 0x5fb06f88,0xfe883fb8,0xcf99fdc0,0x0ff104f9,0x3fa03fea,0x8ffcc013, + 0x7fcc1ff9,0x441fe201,0x3e2003ff,0x206fb807,0xf1000ffa,0xfb999b0f, + 0xb999b0bf,0xfd881fff,0x44feddff,0xecdeffe8,0xffbdfd6f,0x59df703f, + 0x1fd09fd7,0x7c40ffdc,0x3bfbbf66,0x7ec1be23,0x3e61be22,0xfb37c41e, + 0xf107dfdd,0x667ff40f,0x3bf66ffc,0x44ffedcd,0xffecdffc,0x703fc404, + 0x88013bff,0x3bfb207f,0x003ff500,0x7ff43fc4,0xfff02dff,0xea807dff, + 0x702cefff,0x25bffffd,0x00dfffeb,0x0b7fff66,0x3ff207f4,0xdd90fec0, + 0x37c47dfd,0x07f62fd8,0x6c357ff5,0x3fbbb21f,0xff99993e,0x7dc43999, + 0x2e0bffff,0x2dffffff,0x5dfffd70,0x3ff33320,0x7fd41ccc,0x666644ff, + 0x3e1cccff,0xffff983d,0xfcccc803,0x1331cccf,0x00026600,0x00cc0004, + 0x00100011,0x1dfb01fd,0x4f980fea,0x2fd837c4,0xfff707f5,0x980fea9f, + 0x3ffffe4f,0x3103ffff,0x00133001,0xffff8018,0x503fffff,0xffff87b9, + 0x003fffff,0x20019bd3,0xffffffff,0x0000003f,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x02ae6200, + 0xdddfd910,0xdd9501dd,0x0f223b9b,0x00014400,0x22179995,0x07ddb34e, + 0x23deedb8,0x0d4c02a8,0x55440055,0x4c40009a,0x551001ab,0x2aa35555, + 0xaaaaaaaa,0x5555530a,0x5554c555,0xaaaa8009,0x57510009,0x00aaa001, + 0xff5013ee,0xf101bfff,0xddffd9bf,0xfeeffd81,0x00df11ff,0x22001fe0, + 0x12fffffe,0xffdff5bf,0xddffd30b,0x103fe8ff,0x01fe21ff,0xffffffa8, + 0x7ffd401d,0x7e401eff,0xdf4fffff,0xdddddddd,0x3ffff21f,0x3ff67fff, + 0xf00dffff,0x07ffffff,0x1fffffe4,0x80bffe20,0xf702fff8,0x1dfd759f, + 0x27e43fd8,0x3fa06fe4,0x2000df11,0xdfd8007f,0x3fe21312,0x83ff30cf, + 0x26140dfe,0x23fd80ff,0x3ea007f8,0x3ffecbae,0x9339ff30,0xefef805f, + 0x021f1aaa,0x559f90f8,0x67ec5555,0x82ffecba,0xffecabff,0xa9adfd83, + 0x3fea03ff,0x0fffc04f,0x3a20ffc4,0x7c43fc3f,0x6c07fc47,0x000df11f, + 0x3fe001fe,0x213fe201,0x017f24fb,0x37cc4fd8,0x3bffffe2,0xb85fa81d, + 0x83fd80ff,0x2fdfd400,0x85dfd0f8,0xb007f90f,0x81ff705f,0x547fc87f, + 0x206f986f,0x4c07fafd,0x209f902c,0x21fe27fa,0x837dc7f8,0x00df11fd, + 0x3bffbba6,0xf301eeee,0x307f880d,0x000ff4bf,0x17f43ff1,0x3b733fe2, + 0x82fd43ff,0x00fe85fc,0x05f9fe80,0xf27dc41f,0x3600ff21,0xf8bfb02f, + 0x320ff887,0x105fb03f,0x0007faff,0x3fe01ff8,0xbf70bfa1,0x3fb04fc8, + 0x67ed5be2,0x7ffffcc1,0x302fffff,0x06f880bf,0x007fcdf3,0x2fd57ee0, + 0x7fd43fc4,0x7cc17ea1,0x98007f87,0x1f05f8cf,0x643e1f50,0x02fd803f, + 0x887f8ff3,0xb817ec7f,0xf74fa83f,0x03fc0009,0xcffa8bf6,0x7ec0ffda, + 0x3e23fb02,0x4ffeefce,0xf3001fe0,0x306f880b,0x001fe2df,0x407f57f4, + 0x49f907f8,0x03fe05fa,0x3f9000ff,0x203e0bf1,0x3f21f1f9,0x202fd803, + 0x321fe0ff,0xb81fec5f,0xf32fd84f,0x1be6000f,0x6ff47fb0,0xfc80dfff, + 0x3e23fd03,0x03fea4ff,0x7ffc03fc,0x7fffffff,0x27d41be2,0xf50001fd, + 0x1fe207ff,0xffeeb7d4,0xa8ffc1ee,0x2aaaaffa,0xeff8b7c0,0x4cc1f1ee, + 0x3f21f0fd,0x24eeeeee,0x87fe02fd,0xefecbbff,0x64077d40,0x3a3fc45f, + 0x6f98001f,0x4f99fe40,0x709f7002,0x0ffe23ff,0x03fc07fe,0x67fee664, + 0x1be24ccc,0x07f71fe4,0x881bf600,0x3ebf707f,0x742fffff,0xffffff1f, + 0x3ee01fff,0x3dddff13,0x06f7c43e,0x3ffff21f,0x0bf66fff,0x3ffe1fe8, + 0xfd00bfff,0x9fffb99f,0x27e45fa8,0x0ff30150,0x3df52fd8,0xa83fe200, + 0x0ff11fff,0x03fc0bf6,0xf1017e60,0x4c6fb88d,0x74040bff,0xeeeffeee, + 0x7f41fe21,0xff817ea3,0x333ff331,0x887f4033,0x3e21f05f,0x07f90f80, + 0x7fc05fb0,0x3f267fe1,0x3ffb200e,0x7ec3fccf,0xfe83fcc2,0x203fc40f, + 0xfffd11fe,0xfb05bfff,0x3fb9fd9f,0x17ec1be2,0x7cc007f8,0x677fc405, + 0xfc81fffc,0xe87ecdff,0xeeeffeee,0xf931fe21,0x882fd41f,0x003fc0ff, + 0x82fc4bf3,0x43e03a0f,0x2fd803fc,0x3fc1ff10,0x320013f2,0x267fe22f, + 0x441ff999,0x1ff82fff,0x7e41ff10,0x4fffeeed,0xfb3effc8,0x7ec1be23, + 0x9800ff02,0x7ffc405f,0x2e00dfff,0x405ffffe,0x3fe204fb,0x41efffee, + 0x0df705fa,0x7fe400ff,0x1f05ffff,0x7f90f804,0x2e05fb00,0x3e23fc6f, + 0x07f4000f,0xfffffff5,0x1dfb09ff,0x3ee09f90,0x7dc17ee5,0x23fb0207, + 0x05fb06f8,0xf98007fa,0x08b7c405,0x803be200,0x99dfc999,0x77fffc41, + 0x10bf503d,0x00ff07ff,0x3bbbbfe2,0x3ea1f05f,0x07f90f82,0xff105fb0, + 0x4fc87f85,0xfb0ff200,0xfb99999b,0xff88060d,0xfb07fd43,0x003fe205, + 0x06f88fec,0x13f605fb,0x4405f980,0x7f50006f,0xffffff80,0x1fe21fff, + 0x7545fa80,0x200ff06f,0x82fc43fb,0x1f03d30f,0x3f600ff2,0x7c2ffd42, + 0x400ff987,0x7fc46fd9,0x0007fe00,0x3fb3bfee,0xc837ec3f,0x47f6005f, + 0x05fb06f8,0x3733ffe6,0x880bf301,0x3f90006f,0xdfdaaa80,0x1fe20aaa, + 0xfeeffa80,0x7f6c0dff,0x3eeeeeef,0x9ff103fa,0x2003e599,0xddddf90f, + 0x777ecddd,0xff05fffe,0xddb13f60,0xfa819fff,0x0027ec05,0x1dfffea8, + 0xeeefff98,0x36000eff,0x360df11f,0x3ffaa02f,0x0bf301ff,0x30006f88, + 0x04fb8005,0xfa801fe2,0xf01cefff,0xffffffff,0x2217e69f,0xff5fffff, + 0xffffffff,0x3ffff21f,0x3ff67fff,0x3e01ceef,0x741ff307,0xfb01cdef, + 0x006fa807,0xeb880180,0x8002ceee,0x20df11ec,0x013002fd,0x74405f98, + 0x00000005,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, + 0x00000000,0xaaaa8000,0xaaa98099,0x31aaaaaa,0x55555555,0x260aaa00, + 0x2055100a,0x2aa0d42a,0x0aaaaaaa,0x0aa60551,0x40335555,0x455302a9, + 0xaaaaaaa9,0x803551aa,0x02aa22a8,0x03530aa8,0x01551a88,0x0154c550, + 0x2aaaaa55,0x00aaaaaa,0x751002aa,0x80551015,0xfffffff8,0x3ffff20c, + 0xf95fffff,0x0fffffff,0xfb0fff70,0x7c1be205,0xfff0fe65,0x21ffffff, + 0x2ff886f8,0x3fffffe2,0x07fec0bf,0xfff737fc,0x2bffffff,0x2fe406fb, + 0x7fd413fa,0xf9807f50,0xfa809fb4,0x220fff26,0xffffff6f,0x501fffff, + 0x36203ffd,0x4c3fffff,0x57fc406f,0x2a5ffcba,0xdccccccc,0x555bf95f, + 0xff880555,0x102fd87f,0xfa93e0df,0x6fed5542,0x0df10aaa,0xaff887fd, + 0x6c5ffeba,0x3ffd43ff,0x99999993,0x81ffc9ff,0x7fcc0ff8,0xf517f441, + 0xf53f9809,0x323fc80f,0x56f886ff,0x55bfb555,0x7ff4c155,0x7bfd01ff, + 0x7cc3ff95,0x443fc406,0x3fd000ff,0x6c000ff2,0x2fd87fbf,0x9f10df10, + 0x9f700fdc,0x9fb1be20,0xff10ff10,0x3637f747,0xdf7007ee,0xfd80ffa8, + 0xfc8bf904,0x2a027cc5,0xf807fe3f,0x0bfbf21f,0x13ee0df1,0xff9dff98, + 0xbf905501,0x3e2037cc,0x3003fd07,0x001fe4df,0x43fc67d4,0x4df102fd, + 0xdaadfbaa,0x4fb81abf,0x2fdcdf10,0xbf907f88,0xf887e774,0xff8807dc, + 0x7cc4fe81,0x25ff100f,0x2fcc0ff9,0x4fd8bea0,0xbfc8df30,0xb837c46f, + 0xff0e404f,0x26f98003,0x3fc406f9,0x5fb007f8,0x22001fe4,0xd87f88ff, + 0x74df102f,0xffffffff,0x04fb85ff,0x03be6df1,0x6fa83fc4,0x7dcfe7ba, + 0x7ec00fd9,0x6c1ff304,0x47fd403f,0x45f883fe,0xfa8bee09,0xfc87f906, + 0x1be22fda,0x3e0027dc,0x37cc001f,0x7f880df3,0xf9804fc8,0x2001fe46, + 0xb0ff12fd,0x21be205f,0x701f61fb,0x23be209f,0x1fe201ff,0x3adf2fec, + 0x803f6dd6,0xa7ec06fa,0xdfb006f9,0xa9be20df,0xff07ee3f,0xfc81fd03, + 0x1be26faa,0x3e0027dc,0x2fdc001f,0xff880df3,0x002ffeee,0xcefc85fb, + 0xfa82cccc,0x3f61fe25,0xfeeeeeee,0x1ba0fc86,0x3e209f70,0x7c402fef, + 0x3e2ff887,0x367f5f95,0x03ff100f,0x2fd8ff88,0x03fff100,0x64df937c, + 0x2627ec1f,0xfd2fc86f,0x7dc1be23,0x00ffc004,0x7cc5fd10,0x7fffc406, + 0x4c02efff,0xffffc86f,0x0fe85fff,0x3ff61fe2,0x6fffffff,0x202fc7c8, + 0xfff104fb,0x9ff8805f,0x7c5ffdb9,0x321fff35,0x009fb01f,0x001bfbf2, + 0x37c01ffd,0x03f23fff,0x83fc8df5,0x22bf52fc,0x009f706f,0x36001ff8, + 0x2037cc5f,0x7fe447f8,0x320bf601,0x099999cf,0x1fe217e4,0x37c40bf6, + 0x7013e3ec,0x2bbe209f,0x3fe200ff,0x443fffff,0xfc97fa5f,0x2006fa81, + 0x2001fff8,0x3a05fffb,0x329f9f57,0x3a1ff80f,0x3e2fc80f,0xf706f89f, + 0x01ff8009,0xf981df90,0xc83fc406,0x20df305f,0xef9803fc,0x9ff99999, + 0x2205fb09,0xdffdd56f,0x20ddffdd,0x2df104fb,0xff100efb,0xf1013599, + 0x3f917dcb,0x4001ff88,0x3e6005fb,0xfd02ff9f,0x3edbe3f2,0x0df33fd8, + 0x8cfb8bf2,0x009f706f,0xfc801ff8,0x406f980e,0x0df507f8,0x1fe40ff6, + 0x7ffffdc0,0xb4ffffff,0x4dbe205f,0xfdccefcc,0x09f704cd,0x05fd9be2, + 0x3e600ff1,0x3617e405,0x4fb8004f,0x7dcffa00,0x5f8fd80f,0x2a0fb3f9, + 0x3207f76f,0x3e7fe22f,0x8009f706,0x77e401ff,0x880df300,0x309fb07f, + 0x01fe40ff,0x6666664c,0xfb2ccffc,0xf11be205,0x2e017d49,0x44df104f, + 0x07f883ff,0xfb809f30,0x0003bea2,0x7dc013ee,0x7e427f46,0xdd9f32fb, + 0x07f47fc0,0x67e42fc8,0x009f706f,0x3f201ff8,0x01be600e,0xffa88ff1, + 0x3203fd82,0x7c40003f,0xf102fd87,0x3ee4f88d,0x8827dc01,0x20ffcc6f, + 0x9f3007f8,0xff13fb80,0x13ee0003,0xf30bfe20,0x47efc83f,0x3f606eff, + 0x0bf206fc,0x2e0dfff1,0x0ffc004f,0x4c00efc8,0x77fc406f,0x983fffee, + 0x0ff200ff,0xb0ff1000,0x31be205f,0x6c07e47f,0xeeeffeee,0xff70df10, + 0xa803fc41,0xc9fdc04f,0xffffffff,0x013ee06f,0x37e417f6,0xff917fee, + 0x1fffd40b,0xff905f90,0xb013ee0d,0xdddffddd,0x3ffffe67,0xff36ffff, + 0x15dddddd,0x19dfffff,0xf900ff60,0x7f880007,0xdf102fd8,0x03f22fa8, + 0x3ffffffe,0x20df10ff,0x01fe26fd,0x3ee027d4,0xffffffb3,0x7dc0dfff, + 0x807fdc04,0x3fee4ff8,0x202ffcc2,0x3f200fff,0x706ff882,0xffff809f, + 0xf34fffff,0xffffffff,0x3ffffe6d,0x00003fff,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x80322000, + 0x8026200b,0x6c40001c,0xdb880000,0x2e01defe,0xe881cefd,0x6d43d905, + 0xdfd98cef,0x00054400,0x207bddb7,0x700cefdb,0xc83ddfdb,0x21bd505e, + 0xd52ed8bd,0xeeeed81b,0xaa986eee,0x2017540a,0x2e1d74e9,0x77441dfd, + 0x6c03b600,0x40df504f,0x7ff304fa,0x0ffe6000,0x7dc04fa8,0x42fffeef, + 0xfffeffe9,0xfd837c43,0x3bff7be2,0x7406fdef,0xffe9807f,0xefd87fee, + 0x7f42ffed,0x442feeef,0x17fc43ff,0xf3ffdb9f,0xfffe8bfd,0x7dc7ffff, + 0x3ea1ffff,0xfca7d404,0x1fffefc9,0x6fa81fec,0x32ebfe20,0x2a01ff9b, + 0x13fe604f,0x805ff700,0x20cc04fa,0x27f47fb8,0x6f887fea,0x36145fb0, + 0x405f90ff,0xdfe807fe,0x513f2140,0x417ee1ff,0x361ff980,0x5c77fc4f, + 0x200fd4ef,0xf70cc3fe,0x2e02fccb,0x45fdf93f,0x837d45fc,0x7fd403fd, + 0x403fffff,0x7f4404fa,0x1efc800d,0x0004fa80,0x82fd43fe,0x41be25fc, + 0x97ee02fd,0x072204fa,0xf100bf90,0x7e4ff20d,0xab7e4003,0xf52ff86f, + 0x32007ecf,0x37cc405f,0x9174cdf1,0x307ff23f,0x883ff0df,0x7fc400ff, + 0x402ffc9c,0xdfb004fa,0x03bf6201,0x00027d40,0x40bf23fb,0x41be26f8, + 0x8fea02fd,0xe80004f9,0x04fa801f,0x0bfee9f5,0x1ff9fd00,0xb27d4ff0, + 0x077d401f,0x37fff644,0x365fc9fe,0xd107f90f,0xfb89f90b,0x645fb804, + 0x7777645f,0x205eeeff,0x7f441ffb,0x5ccccc04,0x981999df,0x1ffffeec, + 0x13fc03fd,0x88bf60df,0xeeefeedb,0x266665fe,0x41999999,0xefb800ff, + 0x5feeeeee,0x0077fff6,0x7c0bffe2,0x0fd8fea7,0x3203ff30,0x746f99af, + 0x3f43ffa7,0xf88005f9,0x6c00ff47,0x363fcc2f,0xffffffff,0x8ffdc07f, + 0xff805ff8,0xffffffff,0x33bfee1f,0x3fd1ffcc,0x0df13fc0,0xcffe8bf6, + 0x2ccccefd,0x3ffffffe,0xff11ffff,0x3bbbf200,0x4c4eeeee,0x200efffd, + 0xff00ffe8,0x01fb1fd4,0x37c05fd1,0x98fd8df5,0x64df3fcf,0x2fd8002f, + 0x3f600df3,0x2a03fc42,0x3fe6004f,0x201ffd43,0xcefdcccc,0x3ff10ccc, + 0x0bf63fb0,0x0df137c4,0x52fd4bf6,0xcccc809f,0x0ccccccc,0x3ee003fe, + 0xfd510005,0x77f7ec0d,0x8fea7f80,0x04fd80fd,0x37ff6fec,0x3e3f27ee, + 0x017e4bf6,0x3fcafd40,0xf989fb00,0x8027d406,0xfd302ffb,0x027d4009, + 0x47fa0bf5,0x8bf704fc,0x97fc40ff,0x01bea2fc,0x01ff4000,0x00007fd4, + 0xdf701ff1,0xa9fe17f6,0xf703f63f,0x17b7100d,0x5ebfa877,0x7e49f5f9, + 0xd1ff0002,0x3bea001f,0xf500ff60,0x03df9009,0x800dfe88,0x1bea04fa, + 0x3e23ffb1,0x20ffcc1f,0x3ffa22ff,0x3fa27f72,0x0054001f,0x8102ffea, + 0x30000cfe,0x23ff30ff,0x53fc3ff8,0xf307ec7f,0x4c00001f,0xfbf32fdf, + 0x80017e47,0x2005fdfc,0xfffdfff8,0x1027d401,0x6c001dfb,0x27d400ef, + 0xfb9dff10,0x7fdc3fdf,0xb83ffdcd,0xfdefcdff,0x7dd9df12,0x403b99ff, + 0xffd806fd,0x7cc7ecdf,0x0eccbdff,0xffb999dd,0x4c3ff889,0xfa9fe1ff, + 0xfff83f63,0x32eeeeee,0x7ddddddd,0xff07ffc4,0x0017e45f,0x002fff98, + 0x37ffbbf6,0x8164c06f,0x70005fe8,0x27d403ff,0x37fffa60,0x7f541fc8, + 0x3aa01eff,0x22fb8dff,0xff90efeb,0x3ff403ff,0xffffea80,0xffffd885, + 0xffffb0ef,0x0bfb05df,0x53fc3ff2,0xff07ec7f,0x5fffffff,0x3bbbbba6, + 0x322ffc3e,0x00bf20ff,0x2006fe80,0x09fb06fb,0x001f4400,0x98807ea0, + 0x00011000,0x00088003,0x26002601,0x0088002c,0x4cc01310,0x00000009, + 0x00000000,0x00000000,0x4407ee00,0x3333264f,0x002ccccc,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x40000000, + 0x3fee0400,0x4fffffff,0x74400000,0x4039fb54,0x64400aa9,0xb9800001, + 0x000001bc,0x03372a00,0x4017bb93,0x16dc04c9,0x200e65c4,0x333100a8, + 0x81333333,0x4cc40bb9,0x09999999,0x26057700,0x257714c2,0x00000bb9, + 0x40000000,0xfeefcdf8,0x7fff444f,0x80be202f,0xc81d705c,0x40dfdcdf, + 0x3203645c,0xfd83320d,0x3f21ffff,0x6cc0ffff,0x3fe207ff,0x3fffea0f, + 0x41bf604f,0xfffffffa,0x0efb84ff,0x3ffffff2,0xfd802fff,0x11ff880d, + 0x54ffa3ff,0x000000ff,0x44000000,0x3fea3fff,0x3ee6ff60,0x8fc46a0f, + 0x717fa238,0x557641df,0x7ec5d88a,0x7d40ff23,0x1731be65,0x32049fb1, + 0x3f7fe23f,0x897ffc07,0x05fd10ff,0x5107fbf5,0x55555555,0x543be603, + 0xebbbbbbb,0x01fec02f,0xd1fe83fa,0x001fe67f,0x00000000,0x3e0ffe20, + 0xfc8bf31f,0x5cfd3fa3,0x57fa20ff,0x27e60efb,0x3f8afcfa,0x0fe83fa2, + 0x07fa0fe8,0x7ec0bf70,0x03fc45c2,0x1fd4bfea,0x75ba13ea,0x8800000f, + 0x05f90009,0x808004c4,0x3fccbf60,0x00000000,0x83fc4000,0xa87f52fd, + 0x77f7544f,0xefe880bf,0x6ab5c0ef,0xbf317299,0x8ff22fcc,0x7f4403fc, + 0x027ff542,0xff880ff1,0x4f987f70,0x44f98fdc,0x99999998,0x20000099, + 0x000002fc,0x37c4bf60,0x00000000,0x837c4000,0xb8bf32fd,0x1ffe883f, + 0x407ff440,0x21ddf54d,0x362fc86b,0x7ccdf12f,0x42ff4406,0x103ffdc9, + 0x25fc80ff,0x117e45f9,0x2a1fd8bf,0xffffffff,0x3200004f,0x0000002f, + 0x037c47f2,0x00000000,0xd837c400,0x223ff12f,0x37f220fe,0xf701dfdf, + 0x2ab90bff,0x88b90fae,0xd8df10ff,0x5407f62f,0x7f9804ff,0xd910ff10, + 0xbdfe81df,0x207e46fd,0x2eee66f8,0x02bbbbbb,0x00000000,0x00000000, + 0x00000000,0x17ec1be2,0x87ffdff7,0xfd33f2fe,0xfe8efb81,0xdb547d45, + 0x22fd89d4,0x137c42fd,0xbffb81df,0xff700999,0x3a61fe20,0xfffb102d, + 0xb827cc19,0x6d40003f,0x2ca8103d,0xffb80dcc,0x55534fff,0x00000555, + 0x00000000,0x7ec1be20,0x40e6e4c2,0x20c3f109,0xbfd10efb,0x99339dd8, + 0xf527dc1f,0xfb93ee0b,0x3ffffe25,0xffddb2ff,0xffffe85f,0x010001ff, + 0x00130062,0x2ffffdc0,0x7ffccbee,0x503fff11,0x3a799999,0x007fffff, + 0x00000000,0x0df10000,0x10000bf6,0x2077405f,0x3ba20fe8,0x741fda9b, + 0xfd007f46,0x999076c1,0x3ae39999,0xccb80bde,0x0000cccc,0x80000000, + 0x8dfd89fe,0xfff50fd8,0x00fffe65,0x333332e0,0x00000004,0x10000000, + 0x4cbf60df,0x3eeeeeee,0x04401510,0xdfb70088,0x00202019,0x00000101, + 0x00000000,0x7c400000,0x2ffffec5,0x1ffd1bfa,0x00000000,0x00000000, + 0x20df1000,0xdddd32fd,0x00007ddd,0x00000000,0x00000000,0x00000000, + 0x06a00000,0x80413bae,0x00000009,0x00000000,0x00000000,0x00000000, + 0x00000000,0x00000000,0x00000000,0x00000000,0x00000000,0x00000000, +}; + +static signed short stb__consolas_24_latin1_x[224]={ 0,5,3,0,1,0,0,5,3,3,1,0,2,3, +4,1,1,1,1,1,0,2,1,1,1,1,4,2,1,1,2,3,0,0,1,1,1,2,2,0,1,2,2,1, +2,0,1,0,1,0,1,1,1,1,0,0,0,0,1,4,1,3,1,0,0,1,1,1,1,1,0,1,1,2, +1,2,2,1,1,1,1,1,2,2,0,1,0,0,0,0,1,1,5,2,0,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,5,1,1,1,0, +5,1,0,0,2,1,1,3,1,0,2,1,2,3,0,1,1,4,5,2,2,1,0,0,0,2,0,0,0,0, +0,0,-1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,1,1,0,0, +0,0,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,1,0, + }; +static signed short stb__consolas_24_latin1_y[224]={ 17,0,0,1,-1,0,0,0,-1,-1,0,4,13,9, +13,0,1,1,1,1,1,1,1,1,1,1,5,5,4,7,4,0,0,1,1,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,1,20,0,5,0,5,0,5,0,5,0,0, +0,0,0,5,5,5,5,5,5,5,1,5,5,5,5,5,5,0,-3,0,8,1,1,1,1,1,1,1,1,1, +1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,17,5,-1,1,2,1, +-3,0,0,1,1,5,9,9,0,1,0,2,0,0,0,5,0,8,17,0,1,5,0,0,0,5,-3,-3,-3,-3, +-3,-4,1,1,-3,-3,-3,-3,-3,-3,-3,-3,1,-3,-3,-3,-3,-3,-3,5,-1,-3,-3,-3,-3,-3,1,0,0,0, +0,0,0,-1,5,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,2,0,0,0,0,0,0,0, + }; +static unsigned short stb__consolas_24_latin1_w[224]={ 0,4,8,13,11,13,14,3,8,7,11,13,7,8, +5,11,12,11,11,11,13,10,11,11,11,11,5,7,10,11,10,8,14,14,11,11,12,10,10,12,11,10,9,12, +10,13,11,13,11,14,12,11,12,11,14,13,13,14,11,6,11,7,11,14,8,11,11,11,11,11,13,12,11,10, +10,11,10,12,11,12,11,11,11,10,12,11,13,13,13,13,11,10,3,10,13,12,12,12,12,12,12,12,12,12, +12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,0,4,10,11,12,13, +3,11,11,14,9,11,11,8,11,10,9,11,9,8,12,12,11,5,3,9,9,11,13,13,13,8,14,14,14,14, +14,14,14,11,12,12,12,12,12,12,12,12,13,12,13,13,13,13,13,11,13,12,12,12,12,14,11,11,12,12, +12,12,12,12,13,11,12,12,12,12,12,12,12,12,11,12,13,13,13,13,13,13,12,12,12,12,12,13,11,13, + }; +static unsigned short stb__consolas_24_latin1_h[224]={ 0,18,6,16,21,18,18,6,23,23,11,13,9,3, +5,20,17,16,16,17,16,17,17,16,17,16,13,17,14,7,14,18,22,16,16,17,16,16,16,17,16,16,17,16, +16,16,16,17,16,21,16,17,16,17,16,16,16,16,16,22,20,22,9,2,6,13,18,13,18,13,17,17,17,17, +22,17,17,12,12,13,17,17,12,13,17,13,12,12,12,17,12,22,25,22,5,16,16,16,16,16,16,16,16,16, +16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,0,17,21,16,15,16, +25,20,6,17,12,11,6,3,11,5,9,15,10,10,6,17,20,5,4,10,12,11,17,17,17,17,20,20,20,20, +20,21,16,20,20,20,20,20,20,20,20,20,16,20,21,21,21,21,21,11,21,21,21,21,21,20,16,18,18,18, +18,18,18,19,13,16,18,18,18,18,17,17,17,17,18,17,18,18,18,18,18,13,18,18,18,18,18,22,22,22, + }; +static unsigned short stb__consolas_24_latin1_s[224]={ 252,250,247,62,40,106,104,252,17,9,70, +48,159,238,215,91,183,221,233,12,36,1,222,13,152,220,247,223,37,189,26, +40,100,232,1,24,194,183,25,36,50,76,49,87,245,112,196,1,100,129,207, +164,208,176,181,167,153,138,126,34,146,26,177,26,201,62,119,127,171,139,65, +15,40,245,89,74,141,176,48,74,79,28,225,151,52,87,237,211,162,231,189, +41,1,64,201,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170, +170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,170,252,247,157, +143,1,103,5,186,235,59,201,118,210,238,94,227,167,14,130,140,222,196,79, +221,252,149,60,106,86,113,127,201,198,213,66,118,103,52,155,67,133,173,14, +27,241,1,40,53,129,228,168,182,196,210,224,82,238,1,14,27,144,158,117, +94,172,185,198,211,131,81,99,91,133,159,146,120,234,209,210,188,224,100,236, +49,157,90,63,113,144,27,1,77,14,75,52,115, }; +static unsigned short stb__consolas_24_latin1_t[224]={ 13,49,156,125,27,49,70,1,1,1,156, +142,156,163,163,27,70,125,125,89,125,89,70,125,89,107,107,89,142,156,142, +70,1,107,125,89,107,107,125,89,125,125,89,125,125,125,125,107,125,1,107, +89,125,89,125,125,125,125,125,1,27,1,156,24,156,142,70,142,70,142,107, +107,107,89,1,89,89,142,156,142,107,107,142,142,107,142,142,142,142,89,142, +1,1,1,163,107,107,107,107,107,107,107,107,107,107,107,107,107,107,107,107, +107,107,107,107,107,107,107,107,107,107,107,107,107,107,107,107,107,13,70,1, +107,142,107,1,27,156,89,142,156,156,163,156,163,156,142,156,156,156,70,27, +163,8,156,156,156,89,89,89,89,27,27,49,27,27,27,107,27,27,27,49, +49,27,49,49,49,107,27,1,1,1,1,1,156,1,27,27,27,1,27,107, +49,49,49,49,49,70,49,142,107,49,49,49,49,70,70,89,89,49,89,49, +70,70,70,70,142,70,70,70,70,70,1,1,1, }; +static unsigned short stb__consolas_24_latin1_a[224]={ 211,211,211,211,211,211,211,211, +211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211, +211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211, +211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211, +211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211, +211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211, +211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211, +211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211, +211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211, +211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211, +211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211, +211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211, +211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211, +211,211,211,211,211,211,211,211,211,211,211,211,211,211,211,211, +211,211,211,211,211,211,211,211, }; + +// Call this function with +// font: NULL or array length +// data: NULL or specified size +// height: STB_FONT_consolas_24_latin1_BITMAP_HEIGHT or STB_FONT_consolas_24_latin1_BITMAP_HEIGHT_POW2 +// return value: spacing between lines +static void stb_font_consolas_24_latin1(stb_fontchar font[STB_FONT_consolas_24_latin1_NUM_CHARS], + unsigned char data[STB_FONT_consolas_24_latin1_BITMAP_HEIGHT][STB_FONT_consolas_24_latin1_BITMAP_WIDTH], + int height) +{ + int i,j; + if (data != 0) { + unsigned int *bits = stb__consolas_24_latin1_pixels; + unsigned int bitpack = *bits++, numbits = 32; + for (i=0; i < STB_FONT_consolas_24_latin1_BITMAP_WIDTH*height; ++i) + data[0][i] = 0; // zero entire bitmap + for (j=1; j < STB_FONT_consolas_24_latin1_BITMAP_HEIGHT-1; ++j) { + for (i=1; i < STB_FONT_consolas_24_latin1_BITMAP_WIDTH-1; ++i) { + unsigned int value; + if (numbits==0) bitpack = *bits++, numbits=32; + value = bitpack & 1; + bitpack >>= 1, --numbits; + if (value) { + if (numbits < 3) bitpack = *bits++, numbits = 32; + data[j][i] = (bitpack & 7) * 0x20 + 0x1f; + bitpack >>= 3, numbits -= 3; + } else { + data[j][i] = 0; + } + } + } + } + + // build font description + if (font != 0) { + float recip_width = 1.0f / STB_FONT_consolas_24_latin1_BITMAP_WIDTH; + float recip_height = 1.0f / height; + for (i=0; i < STB_FONT_consolas_24_latin1_NUM_CHARS; ++i) { + // pad characters so they bilerp from empty space around each character + font[i].s0 = (stb__consolas_24_latin1_s[i]) * recip_width; + font[i].t0 = (stb__consolas_24_latin1_t[i]) * recip_height; + font[i].s1 = (stb__consolas_24_latin1_s[i] + stb__consolas_24_latin1_w[i]) * recip_width; + font[i].t1 = (stb__consolas_24_latin1_t[i] + stb__consolas_24_latin1_h[i]) * recip_height; + font[i].x0 = stb__consolas_24_latin1_x[i]; + font[i].y0 = stb__consolas_24_latin1_y[i]; + font[i].x1 = stb__consolas_24_latin1_x[i] + stb__consolas_24_latin1_w[i]; + font[i].y1 = stb__consolas_24_latin1_y[i] + stb__consolas_24_latin1_h[i]; + font[i].advance_int = (stb__consolas_24_latin1_a[i]+8)>>4; + font[i].s0f = (stb__consolas_24_latin1_s[i] - 0.5f) * recip_width; + font[i].t0f = (stb__consolas_24_latin1_t[i] - 0.5f) * recip_height; + font[i].s1f = (stb__consolas_24_latin1_s[i] + stb__consolas_24_latin1_w[i] + 0.5f) * recip_width; + font[i].t1f = (stb__consolas_24_latin1_t[i] + stb__consolas_24_latin1_h[i] + 0.5f) * recip_height; + font[i].x0f = stb__consolas_24_latin1_x[i] - 0.5f; + font[i].y0f = stb__consolas_24_latin1_y[i] - 0.5f; + font[i].x1f = stb__consolas_24_latin1_x[i] + stb__consolas_24_latin1_w[i] + 0.5f; + font[i].y1f = stb__consolas_24_latin1_y[i] + stb__consolas_24_latin1_h[i] + 0.5f; + font[i].advance = stb__consolas_24_latin1_a[i]/16.0f; + } + } +} + +#ifndef STB_SOMEFONT_CREATE +#define STB_SOMEFONT_CREATE stb_font_consolas_24_latin1 +#define STB_SOMEFONT_BITMAP_WIDTH STB_FONT_consolas_24_latin1_BITMAP_WIDTH +#define STB_SOMEFONT_BITMAP_HEIGHT STB_FONT_consolas_24_latin1_BITMAP_HEIGHT +#define STB_SOMEFONT_BITMAP_HEIGHT_POW2 STB_FONT_consolas_24_latin1_BITMAP_HEIGHT_POW2 +#define STB_SOMEFONT_FIRST_CHAR STB_FONT_consolas_24_latin1_FIRST_CHAR +#define STB_SOMEFONT_NUM_CHARS STB_FONT_consolas_24_latin1_NUM_CHARS +#define STB_SOMEFONT_LINE_SPACING STB_FONT_consolas_24_latin1_LINE_SPACING +#endif +