mirror of
https://thingvellir.net/git/overte
synced 2025-03-27 23:52:03 +01:00
Merge remote-tracking branch 'upstream/master' into smarter_textures
This commit is contained in:
commit
e482eac437
123 changed files with 3204 additions and 1541 deletions
145
BUILD_WIN.md
145
BUILD_WIN.md
|
@ -1,104 +1,81 @@
|
|||
Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only Windows specific instructions are found in this file.
|
||||
This is a stand-alone guide for creating your first High Fidelity build for Windows 64-bit.
|
||||
|
||||
Interface can be built as 32 or 64 bit.
|
||||
###Step 1. Installing Visual Studio 2013
|
||||
|
||||
###Visual Studio 2013
|
||||
If you don't already have the Community or Professional edition of Visual Studio 2013, download and install [Visual Studio Community 2013](https://www.visualstudio.com/en-us/news/releasenotes/vs2013-community-vs). You do not need to install any of the optional components when going through the installer.
|
||||
|
||||
You can use the Community or Professional editions of Visual Studio 2013.
|
||||
Note: Newer versions of Visual Studio are not yet compatible.
|
||||
|
||||
You can start a Visual Studio 2013 command prompt using the shortcut provided in the Visual Studio Tools folder installed as part of Visual Studio 2013.
|
||||
###Step 2. Installing CMake
|
||||
|
||||
Or you can start a regular command prompt and then run:
|
||||
Download and install the CMake 3.8.0-rc2 "win64-x64 Installer" from the [CMake Website](https://cmake.org/download/). Make sure "Add CMake to system PATH for all users" is checked when going through the installer.
|
||||
|
||||
"%VS120COMNTOOLS%\vsvars32.bat"
|
||||
###Step 3. Installing Qt
|
||||
|
||||
####Windows SDK 8.1
|
||||
Download and install the [Qt 5.6.1 Installer](https://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-windows-x86-msvc2013_64-5.6.1-1.exe). Please note that the download file is large (850MB) and may take some time.
|
||||
|
||||
If using Visual Studio 2013 and building as a Visual Studio 2013 project you need the Windows 8 SDK which you should already have as part of installing Visual Studio 2013. You should be able to see it at `C:\Program Files (x86)\Windows Kits\8.1\Lib\winv6.3\um\x86`.
|
||||
Make sure to select all components when going through the installer.
|
||||
|
||||
####nmake
|
||||
###Step 4. Setting Qt Environment Variable
|
||||
|
||||
Some of the external projects may require nmake to compile and install. If it is not installed at the location listed below, please ensure that it is in your PATH so CMake can find it when required.
|
||||
Go to "Control Panel > System > Advanced System Settings > Environment Variables > New..." (or search “Environment Variables” in Start Search).
|
||||
* Set "Variable name": QT_CMAKE_PREFIX_PATH
|
||||
* Set "Variable value": `C:\Qt\Qt5.6.1\5.6\msvc2013_64\lib\cmake`
|
||||
|
||||
We expect nmake.exe to be located at the following path.
|
||||
###Step 5. Installing OpenSSL
|
||||
|
||||
C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin
|
||||
Download and install the "Win64 OpenSSL v1.0.2k" Installer from [this website](https://slproweb.com/products/Win32OpenSSL.html).
|
||||
|
||||
###Qt
|
||||
You can use the online installer or the offline installer. If you use the offline installer, be sure to select the "OpenGL" version.
|
||||
|
||||
* [Download the online installer](http://www.qt.io/download-open-source/#section-2)
|
||||
* When it asks you to select components, ONLY select one of the following, 32- or 64-bit to match your build preference:
|
||||
* Qt > Qt 5.6.1 > **msvc2013 32-bit**
|
||||
* Qt > Qt 5.6.1 > **msvc2013 64-bit**
|
||||
|
||||
* Download the offline installer, 32- or 64-bit to match your build preference:
|
||||
* [32-bit](https://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-windows-x86-msvc2013-5.6.1-1.exe)
|
||||
* [64-bit](https://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-windows-x86-msvc2013_64-5.6.1-1.exe)
|
||||
|
||||
Once Qt is installed, you need to manually configure the following:
|
||||
* Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt\5.6.1\msvc2013\lib\cmake` or `Qt\5.6.1\msvc2013_64\lib\cmake` directory.
|
||||
* You can set an environment variable from Control Panel > System > Advanced System Settings > Environment Variables > New
|
||||
|
||||
###External Libraries
|
||||
|
||||
All libraries should be 32- or 64-bit to match your build preference.
|
||||
|
||||
CMake will need to know where the headers and libraries for required external dependencies are.
|
||||
|
||||
We use CMake's `fixup_bundle` to find the DLLs all of our executable targets require, and then copy them beside the executable in a post-build step. If `fixup_bundle` is having problems finding a DLL, you can fix it manually on your end by adding the folder containing that DLL to your path. Let us know which DLL CMake had trouble finding, as it is possible a tweak to our CMake files is required.
|
||||
|
||||
The recommended route for CMake to find the external dependencies is to place all of the dependencies in one folder and set one ENV variable - HIFI_LIB_DIR. That ENV variable should point to a directory with the following structure:
|
||||
|
||||
root_lib_dir
|
||||
-> openssl
|
||||
-> bin
|
||||
-> include
|
||||
-> lib
|
||||
|
||||
For many of the external libraries where precompiled binaries are readily available you should be able to simply copy the extracted folder that you get from the download links provided at the top of the guide. Otherwise you may need to build from source and install the built product to this directory. The `root_lib_dir` in the above example can be wherever you choose on your system - as long as the environment variable HIFI_LIB_DIR is set to it. From here on, whenever you see %HIFI_LIB_DIR% you should substitute the directory that you chose.
|
||||
|
||||
####OpenSSL
|
||||
|
||||
Qt will use OpenSSL if it's available, but it doesn't install it, so you must install it separately.
|
||||
|
||||
Your system may already have several versions of the OpenSSL DLL's (ssleay32.dll, libeay32.dll) lying around, but they may be the wrong version. If these DLL's are in the PATH then QT will try to use them, and if they're the wrong version then you will see the following errors in the console:
|
||||
|
||||
QSslSocket: cannot resolve TLSv1_1_client_method
|
||||
QSslSocket: cannot resolve TLSv1_2_client_method
|
||||
QSslSocket: cannot resolve TLSv1_1_server_method
|
||||
QSslSocket: cannot resolve TLSv1_2_server_method
|
||||
QSslSocket: cannot resolve SSL_select_next_proto
|
||||
QSslSocket: cannot resolve SSL_CTX_set_next_proto_select_cb
|
||||
QSslSocket: cannot resolve SSL_get0_next_proto_negotiated
|
||||
|
||||
To prevent these problems, install OpenSSL yourself. Download one of the following binary packages [from this website](https://slproweb.com/products/Win32OpenSSL.html):
|
||||
* Win32 OpenSSL v1.0.1q
|
||||
* Win64 OpenSSL v1.0.1q
|
||||
|
||||
Install OpenSSL into the Windows system directory, to make sure that Qt uses the version that you've just installed, and not some other version.
|
||||
|
||||
###Build High Fidelity using Visual Studio
|
||||
Follow the same build steps from the CMake section of [BUILD.md](BUILD.md), but pass a different generator to CMake.
|
||||
|
||||
For 32-bit builds:
|
||||
|
||||
cmake .. -G "Visual Studio 12"
|
||||
|
||||
For 64-bit builds:
|
||||
###Step 6. Running CMake to Generate Build Files
|
||||
|
||||
Run Command Prompt from Start and run the following commands:
|
||||
cd "%HIFI_DIR%"
|
||||
mkdir build
|
||||
cd build
|
||||
cmake .. -G "Visual Studio 12 Win64"
|
||||
|
||||
Where %HIFI_DIR% is the directory for the highfidelity repository.
|
||||
|
||||
Open %HIFI_DIR%\build\hifi.sln and compile.
|
||||
###Step 7. Making a Build
|
||||
|
||||
###Running Interface
|
||||
If you need to debug Interface, you can run interface from within Visual Studio (see the section below). You can also run Interface by launching it from command line or File Explorer from %HIFI_DIR%\build\interface\Debug\interface.exe
|
||||
Open '%HIFI_DIR%\build\hifi.sln' using Visual Studio.
|
||||
|
||||
###Debugging Interface
|
||||
* In the Solution Explorer, right click interface and click Set as StartUp Project
|
||||
* Set the "Working Directory" for the Interface debugging sessions to the Debug output directory so that your application can load resources. Do this: right click interface and click Properties, choose Debugging from Configuration Properties, set Working Directory to .\Debug
|
||||
* Now you can run and debug interface through Visual Studio
|
||||
Change the Solution Configuration (next to the green play button) from "Debug" to "Release" for best performance.
|
||||
|
||||
For better performance when running debug builds, set the environment variable ```_NO_DEBUG_HEAP``` to ```1```
|
||||
Run Build > Build Solution.
|
||||
|
||||
###Step 8. Testing Interface
|
||||
|
||||
Create another environment variable (see Step #4)
|
||||
* Set "Variable name": _NO_DEBUG_HEAP
|
||||
* Set "Variable value": 1
|
||||
|
||||
In Visual Studio, right+click "interface" under the Apps folder in Solution Explorer and select "Set as Startup Project". Run Debug > Start Debugging.
|
||||
|
||||
Now, you should have a full build of High Fidelity and be able to run the Interface using Visual Studio. Please check our [Docs](https://wiki.highfidelity.com/wiki/Main_Page) for more information regarding the programming workflow.
|
||||
|
||||
Note: You can also run Interface by launching it from command line or File Explorer from %HIFI_DIR%\build\interface\Release\interface.exe
|
||||
|
||||
###Troubleshooting
|
||||
|
||||
For any problems after Step #6, first try this:
|
||||
* Delete your locally cloned copy of the highfidelity repository
|
||||
* Restart your computer
|
||||
* Redownload the [repository](https://github.com/highfidelity/hifi)
|
||||
* Restart directions from Step #6
|
||||
|
||||
####CMake gives you the same error message repeatedly after the build fails
|
||||
|
||||
Remove `CMakeCache.txt` found in the '%HIFI_DIR%\build' directory
|
||||
|
||||
####nmake cannot be found
|
||||
|
||||
Make sure nmake.exe is located at the following path:
|
||||
C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin
|
||||
|
||||
If not, add the directory where nmake is located to the PATH environment variable.
|
||||
|
||||
####Qt is throwing an error
|
||||
|
||||
Make sure you have the correct version (5.6.1-1) installed and 'QT_CMAKE_PREFIX_PATH' environment variable is set correctly.
|
||||
|
||||
http://preshing.com/20110717/the-windows-heap-is-slow-when-launched-from-the-debugger/
|
||||
|
|
|
@ -371,25 +371,39 @@ void Agent::executeScript() {
|
|||
using namespace recording;
|
||||
static const FrameType AUDIO_FRAME_TYPE = Frame::registerFrameType(AudioConstants::getAudioFrameName());
|
||||
Frame::registerFrameHandler(AUDIO_FRAME_TYPE, [this, &scriptedAvatar](Frame::ConstPointer frame) {
|
||||
const QByteArray& audio = frame->data;
|
||||
static quint16 audioSequenceNumber{ 0 };
|
||||
Transform audioTransform;
|
||||
|
||||
QByteArray audio(frame->data);
|
||||
|
||||
if (_isNoiseGateEnabled) {
|
||||
static int numSamples = AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL;
|
||||
_noiseGate.gateSamples(reinterpret_cast<int16_t*>(audio.data()), numSamples);
|
||||
}
|
||||
|
||||
computeLoudness(&audio, scriptedAvatar);
|
||||
|
||||
// the codec needs a flush frame before sending silent packets, so
|
||||
// do not send one if the gate closed in this block (eventually this can be crossfaded).
|
||||
auto packetType = PacketType::MicrophoneAudioNoEcho;
|
||||
if (scriptedAvatar->getAudioLoudness() == 0.0f && !_noiseGate.closedInLastBlock()) {
|
||||
packetType = PacketType::SilentAudioFrame;
|
||||
}
|
||||
|
||||
Transform audioTransform;
|
||||
auto headOrientation = scriptedAvatar->getHeadOrientation();
|
||||
audioTransform.setTranslation(scriptedAvatar->getPosition());
|
||||
audioTransform.setRotation(headOrientation);
|
||||
|
||||
computeLoudness(&audio, scriptedAvatar);
|
||||
|
||||
QByteArray encodedBuffer;
|
||||
if (_encoder) {
|
||||
_encoder->encode(audio, encodedBuffer);
|
||||
} else {
|
||||
encodedBuffer = audio;
|
||||
}
|
||||
|
||||
AbstractAudioInterface::emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), audioSequenceNumber,
|
||||
audioTransform, scriptedAvatar->getPosition(), glm::vec3(0),
|
||||
PacketType::MicrophoneAudioNoEcho, _selectedCodecName);
|
||||
packetType, _selectedCodecName);
|
||||
});
|
||||
|
||||
auto avatarHashMap = DependencyManager::set<AvatarHashMap>();
|
||||
|
@ -483,6 +497,14 @@ void Agent::setIsListeningToAudioStream(bool isListeningToAudioStream) {
|
|||
_isListeningToAudioStream = isListeningToAudioStream;
|
||||
}
|
||||
|
||||
void Agent::setIsNoiseGateEnabled(bool isNoiseGateEnabled) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setIsNoiseGateEnabled", Q_ARG(bool, isNoiseGateEnabled));
|
||||
return;
|
||||
}
|
||||
_isNoiseGateEnabled = isNoiseGateEnabled;
|
||||
}
|
||||
|
||||
void Agent::setIsAvatar(bool isAvatar) {
|
||||
// this must happen on Agent's main thread
|
||||
if (QThread::currentThread() != thread()) {
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
|
||||
#include <plugins/CodecPlugin.h>
|
||||
|
||||
#include "AudioNoiseGate.h"
|
||||
#include "MixedAudioStream.h"
|
||||
#include "avatars/ScriptableAvatar.h"
|
||||
|
||||
|
@ -38,6 +39,7 @@ class Agent : public ThreadedAssignment {
|
|||
Q_PROPERTY(bool isAvatar READ isAvatar WRITE setIsAvatar)
|
||||
Q_PROPERTY(bool isPlayingAvatarSound READ isPlayingAvatarSound)
|
||||
Q_PROPERTY(bool isListeningToAudioStream READ isListeningToAudioStream WRITE setIsListeningToAudioStream)
|
||||
Q_PROPERTY(bool isNoiseGateEnabled READ isNoiseGateEnabled WRITE setIsNoiseGateEnabled)
|
||||
Q_PROPERTY(float lastReceivedAudioLoudness READ getLastReceivedAudioLoudness)
|
||||
Q_PROPERTY(QUuid sessionUUID READ getSessionUUID)
|
||||
|
||||
|
@ -52,6 +54,9 @@ public:
|
|||
bool isListeningToAudioStream() const { return _isListeningToAudioStream; }
|
||||
void setIsListeningToAudioStream(bool isListeningToAudioStream);
|
||||
|
||||
bool isNoiseGateEnabled() const { return _isNoiseGateEnabled; }
|
||||
void setIsNoiseGateEnabled(bool isNoiseGateEnabled);
|
||||
|
||||
float getLastReceivedAudioLoudness() const { return _lastReceivedAudioLoudness; }
|
||||
QUuid getSessionUUID() const;
|
||||
|
||||
|
@ -106,6 +111,9 @@ private:
|
|||
QTimer* _avatarIdentityTimer = nullptr;
|
||||
QHash<QUuid, quint16> _outgoingScriptAudioSequenceNumbers;
|
||||
|
||||
AudioNoiseGate _noiseGate;
|
||||
bool _isNoiseGateEnabled { false };
|
||||
|
||||
CodecPluginPointer _codec;
|
||||
QString _selectedCodecName;
|
||||
Encoder* _encoder { nullptr };
|
||||
|
|
|
@ -455,13 +455,13 @@ void EntityScriptServer::addingEntity(const EntityItemID& entityID) {
|
|||
|
||||
void EntityScriptServer::deletingEntity(const EntityItemID& entityID) {
|
||||
if (_entityViewer.getTree() && !_shuttingDown && _entitiesScriptEngine) {
|
||||
_entitiesScriptEngine->unloadEntityScript(entityID);
|
||||
_entitiesScriptEngine->unloadEntityScript(entityID, true);
|
||||
}
|
||||
}
|
||||
|
||||
void EntityScriptServer::entityServerScriptChanging(const EntityItemID& entityID, const bool reload) {
|
||||
if (_entityViewer.getTree() && !_shuttingDown) {
|
||||
_entitiesScriptEngine->unloadEntityScript(entityID);
|
||||
_entitiesScriptEngine->unloadEntityScript(entityID, true);
|
||||
checkAndCallPreload(entityID, reload);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -246,10 +246,13 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
|
|||
_agentPermissions[editorKey]->set(NodePermissions::Permission::canAdjustLocks);
|
||||
}
|
||||
|
||||
QList<QHash<NodePermissionsKey, NodePermissionsPointer>> permissionsSets;
|
||||
permissionsSets << _standardAgentPermissions.get() << _agentPermissions.get();
|
||||
std::list<std::unordered_map<NodePermissionsKey, NodePermissionsPointer>> permissionsSets{
|
||||
_standardAgentPermissions.get(),
|
||||
_agentPermissions.get()
|
||||
};
|
||||
foreach (auto permissionsSet, permissionsSets) {
|
||||
foreach (NodePermissionsKey userKey, permissionsSet.keys()) {
|
||||
for (auto entry : permissionsSet) {
|
||||
const auto& userKey = entry.first;
|
||||
if (onlyEditorsAreRezzers) {
|
||||
if (permissionsSet[userKey]->can(NodePermissions::Permission::canAdjustLocks)) {
|
||||
permissionsSet[userKey]->set(NodePermissions::Permission::canRezPermanentEntities);
|
||||
|
@ -300,7 +303,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
|
|||
}
|
||||
|
||||
QVariantMap& DomainServerSettingsManager::getDescriptorsMap() {
|
||||
|
||||
static const QString DESCRIPTORS{ "descriptors" };
|
||||
|
||||
auto& settingsMap = getSettingsMap();
|
||||
|
@ -1355,18 +1357,12 @@ QStringList DomainServerSettingsManager::getAllKnownGroupNames() {
|
|||
// extract all the group names from the group-permissions and group-forbiddens settings
|
||||
QSet<QString> result;
|
||||
|
||||
QHashIterator<NodePermissionsKey, NodePermissionsPointer> i(_groupPermissions.get());
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
NodePermissionsKey key = i.key();
|
||||
result += key.first;
|
||||
for (const auto& entry : _groupPermissions.get()) {
|
||||
result += entry.first.first;
|
||||
}
|
||||
|
||||
QHashIterator<NodePermissionsKey, NodePermissionsPointer> j(_groupForbiddens.get());
|
||||
while (j.hasNext()) {
|
||||
j.next();
|
||||
NodePermissionsKey key = j.key();
|
||||
result += key.first;
|
||||
for (const auto& entry : _groupForbiddens.get()) {
|
||||
result += entry.first.first;
|
||||
}
|
||||
|
||||
return result.toList();
|
||||
|
@ -1377,20 +1373,17 @@ bool DomainServerSettingsManager::setGroupID(const QString& groupName, const QUu
|
|||
_groupIDs[groupName.toLower()] = groupID;
|
||||
_groupNames[groupID] = groupName;
|
||||
|
||||
QHashIterator<NodePermissionsKey, NodePermissionsPointer> i(_groupPermissions.get());
|
||||
while (i.hasNext()) {
|
||||
i.next();
|
||||
NodePermissionsPointer perms = i.value();
|
||||
|
||||
for (const auto& entry : _groupPermissions.get()) {
|
||||
auto& perms = entry.second;
|
||||
if (perms->getID().toLower() == groupName.toLower() && !perms->isGroup()) {
|
||||
changed = true;
|
||||
perms->setGroupID(groupID);
|
||||
}
|
||||
}
|
||||
|
||||
QHashIterator<NodePermissionsKey, NodePermissionsPointer> j(_groupForbiddens.get());
|
||||
while (j.hasNext()) {
|
||||
j.next();
|
||||
NodePermissionsPointer perms = j.value();
|
||||
for (const auto& entry : _groupForbiddens.get()) {
|
||||
auto& perms = entry.second;
|
||||
if (perms->getID().toLower() == groupName.toLower() && !perms->isGroup()) {
|
||||
changed = true;
|
||||
perms->setGroupID(groupID);
|
||||
|
|
|
@ -2,7 +2,27 @@
|
|||
"name": "Standard to Action",
|
||||
"channels": [
|
||||
{ "from": "Standard.LY", "to": "Actions.TranslateZ" },
|
||||
{ "from": "Standard.LX", "to": "Actions.TranslateX" },
|
||||
|
||||
{ "from": "Standard.LX",
|
||||
"when": [
|
||||
"Application.InHMD", "!Application.AdvancedMovement",
|
||||
"Application.SnapTurn", "!Standard.RX"
|
||||
],
|
||||
"to": "Actions.StepYaw",
|
||||
"filters":
|
||||
[
|
||||
{ "type": "deadZone", "min": 0.15 },
|
||||
"constrainToInteger",
|
||||
{ "type": "pulse", "interval": 0.25 },
|
||||
{ "type": "scale", "scale": 22.5 }
|
||||
]
|
||||
},
|
||||
{ "from": "Standard.LX", "to": "Actions.TranslateX",
|
||||
"when": [ "Application.AdvancedMovement" ]
|
||||
},
|
||||
{ "from": "Standard.LX", "to": "Actions.Yaw",
|
||||
"when": [ "!Application.AdvancedMovement", "!Application.SnapTurn" ]
|
||||
},
|
||||
|
||||
{ "from": "Standard.RX",
|
||||
"when": [ "Application.InHMD", "Application.SnapTurn" ],
|
||||
|
@ -15,29 +35,29 @@
|
|||
{ "type": "scale", "scale": 22.5 }
|
||||
]
|
||||
},
|
||||
{ "from": "Standard.RX", "to": "Actions.Yaw",
|
||||
"when": [ "!Application.SnapTurn" ]
|
||||
},
|
||||
|
||||
{ "from": "Standard.RX", "to": "Actions.Yaw" },
|
||||
{ "from": "Standard.RY",
|
||||
"when": "Application.Grounded",
|
||||
"to": "Actions.Up",
|
||||
"filters":
|
||||
{ "from": "Standard.RY",
|
||||
"when": "Application.Grounded",
|
||||
"to": "Actions.Up",
|
||||
"filters":
|
||||
[
|
||||
{ "type": "deadZone", "min": 0.6 },
|
||||
"invert"
|
||||
]
|
||||
},
|
||||
},
|
||||
|
||||
{ "from": "Standard.RY", "to": "Actions.Up", "filters": "invert"},
|
||||
{ "from": "Standard.RY", "to": "Actions.Up", "filters": "invert"},
|
||||
|
||||
{ "from": "Standard.Back", "to": "Actions.CycleCamera" },
|
||||
{ "from": "Standard.Start", "to": "Actions.ContextMenu" },
|
||||
|
||||
{ "from": "Standard.LT", "to": "Actions.LeftHandClick" },
|
||||
{ "from": "Standard.LT", "to": "Actions.LeftHandClick" },
|
||||
{ "from": "Standard.RT", "to": "Actions.RightHandClick" },
|
||||
|
||||
{ "from": "Standard.LeftHand", "to": "Actions.LeftHand" },
|
||||
{ "from": "Standard.LeftHand", "to": "Actions.LeftHand" },
|
||||
{ "from": "Standard.RightHand", "to": "Actions.RightHand" }
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 7.3 KiB |
Binary file not shown.
Before Width: | Height: | Size: 8.5 KiB |
Binary file not shown.
Before Width: | Height: | Size: 6 KiB |
Binary file not shown.
Before Width: | Height: | Size: 4.8 KiB |
Binary file not shown.
Before Width: | Height: | Size: 2.5 KiB |
Binary file not shown.
Before Width: | Height: | Size: 2 KiB |
File diff suppressed because one or more lines are too long
|
@ -15,12 +15,11 @@ import Qt.labs.settings 1.0
|
|||
Hifi.AvatarInputs {
|
||||
id: root
|
||||
objectName: "AvatarInputs"
|
||||
width: mirrorWidth
|
||||
height: controls.height + mirror.height
|
||||
width: rootWidth
|
||||
height: controls.height
|
||||
x: 10; y: 5
|
||||
|
||||
readonly property int mirrorHeight: 215
|
||||
readonly property int mirrorWidth: 265
|
||||
readonly property int rootWidth: 265
|
||||
readonly property int iconSize: 24
|
||||
readonly property int iconPadding: 5
|
||||
|
||||
|
@ -39,61 +38,15 @@ Hifi.AvatarInputs {
|
|||
anchors.fill: parent
|
||||
}
|
||||
|
||||
Item {
|
||||
id: mirror
|
||||
width: root.mirrorWidth
|
||||
height: root.mirrorVisible ? root.mirrorHeight : 0
|
||||
visible: root.mirrorVisible
|
||||
anchors.left: parent.left
|
||||
clip: true
|
||||
|
||||
Image {
|
||||
id: closeMirror
|
||||
visible: hover.containsMouse
|
||||
width: root.iconSize
|
||||
height: root.iconSize
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: root.iconPadding
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: root.iconPadding
|
||||
source: "../images/close.svg"
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
root.closeMirror();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Image {
|
||||
id: zoomIn
|
||||
visible: hover.containsMouse
|
||||
width: root.iconSize
|
||||
height: root.iconSize
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: root.iconPadding
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: root.iconPadding
|
||||
source: root.mirrorZoomed ? "../images/minus.svg" : "../images/plus.svg"
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
root.toggleZoom();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: controls
|
||||
width: root.mirrorWidth
|
||||
width: root.rootWidth
|
||||
height: 44
|
||||
visible: root.showAudioTools
|
||||
anchors.top: mirror.bottom
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: root.mirrorVisible ? (root.audioClipping ? "red" : "#696969") : "#00000000"
|
||||
color: "#00000000"
|
||||
|
||||
Item {
|
||||
id: audioMeter
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
QPlainTextEdit {
|
||||
font-family: Inconsolata, Lucida Console, Andale Mono, Monaco;
|
||||
font-family: Inconsolata, Consolas, Courier New, monospace;
|
||||
font-size: 16px;
|
||||
padding-left: 28px;
|
||||
padding-top: 7px;
|
||||
|
@ -11,7 +11,7 @@ QPlainTextEdit {
|
|||
}
|
||||
|
||||
QLineEdit {
|
||||
font-family: Inconsolata, Lucida Console, Andale Mono, Monaco;
|
||||
font-family: Inconsolata, Consolas, Courier New, monospace;
|
||||
padding-left: 7px;
|
||||
background-color: #CCCCCC;
|
||||
border-width: 0;
|
||||
|
|
|
@ -177,6 +177,8 @@
|
|||
#include "FrameTimingsScriptingInterface.h"
|
||||
#include <GPUIdent.h>
|
||||
#include <gl/GLHelpers.h>
|
||||
#include <EntityScriptClient.h>
|
||||
#include <ModelScriptingInterface.h>
|
||||
|
||||
// On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU
|
||||
// FIXME seems to be broken.
|
||||
|
@ -213,18 +215,10 @@ static const QString FBX_EXTENSION = ".fbx";
|
|||
static const QString OBJ_EXTENSION = ".obj";
|
||||
static const QString AVA_JSON_EXTENSION = ".ava.json";
|
||||
|
||||
static const int MIRROR_VIEW_TOP_PADDING = 5;
|
||||
static const int MIRROR_VIEW_LEFT_PADDING = 10;
|
||||
static const int MIRROR_VIEW_WIDTH = 265;
|
||||
static const int MIRROR_VIEW_HEIGHT = 215;
|
||||
static const float MIRROR_FULLSCREEN_DISTANCE = 0.389f;
|
||||
static const float MIRROR_REARVIEW_DISTANCE = 0.722f;
|
||||
static const float MIRROR_REARVIEW_BODY_DISTANCE = 2.56f;
|
||||
static const float MIRROR_FIELD_OF_VIEW = 30.0f;
|
||||
|
||||
static const quint64 TOO_LONG_SINCE_LAST_SEND_DOWNSTREAM_AUDIO_STATS = 1 * USECS_PER_SECOND;
|
||||
|
||||
static const QString INFO_WELCOME_PATH = "html/interface-welcome.html";
|
||||
static const QString INFO_EDIT_ENTITIES_PATH = "html/edit-commands.html";
|
||||
static const QString INFO_HELP_PATH = "html/help.html";
|
||||
|
||||
|
@ -423,6 +417,7 @@ static const QString STATE_CAMERA_THIRD_PERSON = "CameraThirdPerson";
|
|||
static const QString STATE_CAMERA_ENTITY = "CameraEntity";
|
||||
static const QString STATE_CAMERA_INDEPENDENT = "CameraIndependent";
|
||||
static const QString STATE_SNAP_TURN = "SnapTurn";
|
||||
static const QString STATE_ADVANCED_MOVEMENT_CONTROLS = "AdvancedMovement";
|
||||
static const QString STATE_GROUNDED = "Grounded";
|
||||
static const QString STATE_NAV_FOCUSED = "NavigationFocused";
|
||||
|
||||
|
@ -513,7 +508,7 @@ bool setupEssentials(int& argc, char** argv) {
|
|||
DependencyManager::set<MessagesClient>();
|
||||
controller::StateController::setStateVariables({ { STATE_IN_HMD, STATE_CAMERA_FULL_SCREEN_MIRROR,
|
||||
STATE_CAMERA_FIRST_PERSON, STATE_CAMERA_THIRD_PERSON, STATE_CAMERA_ENTITY, STATE_CAMERA_INDEPENDENT,
|
||||
STATE_SNAP_TURN, STATE_GROUNDED, STATE_NAV_FOCUSED } });
|
||||
STATE_SNAP_TURN, STATE_ADVANCED_MOVEMENT_CONTROLS, STATE_GROUNDED, STATE_NAV_FOCUSED } });
|
||||
DependencyManager::set<UserInputMapper>();
|
||||
DependencyManager::set<controller::ScriptingInterface, ControllerScriptingInterface>();
|
||||
DependencyManager::set<InterfaceParentFinder>();
|
||||
|
@ -565,7 +560,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
_entityClipboardRenderer(false, this, this),
|
||||
_entityClipboard(new EntityTree()),
|
||||
_lastQueriedTime(usecTimestampNow()),
|
||||
_mirrorViewRect(QRect(MIRROR_VIEW_LEFT_PADDING, MIRROR_VIEW_TOP_PADDING, MIRROR_VIEW_WIDTH, MIRROR_VIEW_HEIGHT)),
|
||||
_previousScriptLocation("LastScriptLocation", DESKTOP_LOCATION),
|
||||
_fieldOfView("fieldOfView", DEFAULT_FIELD_OF_VIEW_DEGREES),
|
||||
_hmdTabletScale("hmdTabletScale", DEFAULT_HMD_TABLET_SCALE_PERCENT),
|
||||
|
@ -1129,6 +1123,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
_applicationStateDevice->setInputVariant(STATE_SNAP_TURN, []() -> float {
|
||||
return qApp->getMyAvatar()->getSnapTurn() ? 1 : 0;
|
||||
});
|
||||
_applicationStateDevice->setInputVariant(STATE_ADVANCED_MOVEMENT_CONTROLS, []() -> float {
|
||||
return qApp->getMyAvatar()->useAdvancedMovementControls() ? 1 : 0;
|
||||
});
|
||||
|
||||
_applicationStateDevice->setInputVariant(STATE_GROUNDED, []() -> float {
|
||||
return qApp->getMyAvatar()->getCharacterController()->onGround() ? 1 : 0;
|
||||
});
|
||||
|
@ -2119,21 +2117,6 @@ void Application::paintGL() {
|
|||
batch.resetStages();
|
||||
});
|
||||
|
||||
auto inputs = AvatarInputs::getInstance();
|
||||
if (inputs->mirrorVisible()) {
|
||||
PerformanceTimer perfTimer("Mirror");
|
||||
|
||||
renderArgs._renderMode = RenderArgs::MIRROR_RENDER_MODE;
|
||||
renderArgs._blitFramebuffer = DependencyManager::get<FramebufferCache>()->getSelfieFramebuffer();
|
||||
|
||||
_mirrorViewRect.moveTo(inputs->x(), inputs->y());
|
||||
|
||||
renderRearViewMirror(&renderArgs, _mirrorViewRect, inputs->mirrorZoomed());
|
||||
|
||||
renderArgs._blitFramebuffer.reset();
|
||||
renderArgs._renderMode = RenderArgs::DEFAULT_RENDER_MODE;
|
||||
}
|
||||
|
||||
{
|
||||
PerformanceTimer perfTimer("renderOverlay");
|
||||
// NOTE: There is no batch associated with this renderArgs
|
||||
|
@ -2381,10 +2364,6 @@ void Application::setSettingConstrainToolbarPosition(bool setting) {
|
|||
DependencyManager::get<OffscreenUi>()->setConstrainToolbarToCenterX(setting);
|
||||
}
|
||||
|
||||
void Application::aboutApp() {
|
||||
InfoView::show(INFO_WELCOME_PATH);
|
||||
}
|
||||
|
||||
void Application::showHelp() {
|
||||
static const QString HAND_CONTROLLER_NAME_VIVE = "vive";
|
||||
static const QString HAND_CONTROLLER_NAME_OCULUS_TOUCH = "oculus";
|
||||
|
@ -2886,51 +2865,49 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
break;
|
||||
#endif
|
||||
|
||||
case Qt::Key_H:
|
||||
if (isShifted) {
|
||||
Menu::getInstance()->triggerOption(MenuOption::MiniMirror);
|
||||
} else {
|
||||
// whenever switching to/from full screen mirror from the keyboard, remember
|
||||
// the state you were in before full screen mirror, and return to that.
|
||||
auto previousMode = _myCamera.getMode();
|
||||
if (previousMode != CAMERA_MODE_MIRROR) {
|
||||
switch (previousMode) {
|
||||
case CAMERA_MODE_FIRST_PERSON:
|
||||
_returnFromFullScreenMirrorTo = MenuOption::FirstPerson;
|
||||
break;
|
||||
case CAMERA_MODE_THIRD_PERSON:
|
||||
_returnFromFullScreenMirrorTo = MenuOption::ThirdPerson;
|
||||
break;
|
||||
case Qt::Key_H: {
|
||||
// whenever switching to/from full screen mirror from the keyboard, remember
|
||||
// the state you were in before full screen mirror, and return to that.
|
||||
auto previousMode = _myCamera.getMode();
|
||||
if (previousMode != CAMERA_MODE_MIRROR) {
|
||||
switch (previousMode) {
|
||||
case CAMERA_MODE_FIRST_PERSON:
|
||||
_returnFromFullScreenMirrorTo = MenuOption::FirstPerson;
|
||||
break;
|
||||
case CAMERA_MODE_THIRD_PERSON:
|
||||
_returnFromFullScreenMirrorTo = MenuOption::ThirdPerson;
|
||||
break;
|
||||
|
||||
// FIXME - it's not clear that these modes make sense to return to...
|
||||
case CAMERA_MODE_INDEPENDENT:
|
||||
_returnFromFullScreenMirrorTo = MenuOption::IndependentMode;
|
||||
break;
|
||||
case CAMERA_MODE_ENTITY:
|
||||
_returnFromFullScreenMirrorTo = MenuOption::CameraEntityMode;
|
||||
break;
|
||||
// FIXME - it's not clear that these modes make sense to return to...
|
||||
case CAMERA_MODE_INDEPENDENT:
|
||||
_returnFromFullScreenMirrorTo = MenuOption::IndependentMode;
|
||||
break;
|
||||
case CAMERA_MODE_ENTITY:
|
||||
_returnFromFullScreenMirrorTo = MenuOption::CameraEntityMode;
|
||||
break;
|
||||
|
||||
default:
|
||||
_returnFromFullScreenMirrorTo = MenuOption::ThirdPerson;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
_returnFromFullScreenMirrorTo = MenuOption::ThirdPerson;
|
||||
break;
|
||||
}
|
||||
|
||||
bool isMirrorChecked = Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror);
|
||||
Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, !isMirrorChecked);
|
||||
if (isMirrorChecked) {
|
||||
|
||||
// if we got here without coming in from a non-Full Screen mirror case, then our
|
||||
// _returnFromFullScreenMirrorTo is unknown. In that case we'll go to the old
|
||||
// behavior of returning to ThirdPerson
|
||||
if (_returnFromFullScreenMirrorTo.isEmpty()) {
|
||||
_returnFromFullScreenMirrorTo = MenuOption::ThirdPerson;
|
||||
}
|
||||
Menu::getInstance()->setIsOptionChecked(_returnFromFullScreenMirrorTo, true);
|
||||
}
|
||||
cameraMenuChanged();
|
||||
}
|
||||
|
||||
bool isMirrorChecked = Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror);
|
||||
Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, !isMirrorChecked);
|
||||
if (isMirrorChecked) {
|
||||
|
||||
// if we got here without coming in from a non-Full Screen mirror case, then our
|
||||
// _returnFromFullScreenMirrorTo is unknown. In that case we'll go to the old
|
||||
// behavior of returning to ThirdPerson
|
||||
if (_returnFromFullScreenMirrorTo.isEmpty()) {
|
||||
_returnFromFullScreenMirrorTo = MenuOption::ThirdPerson;
|
||||
}
|
||||
Menu::getInstance()->setIsOptionChecked(_returnFromFullScreenMirrorTo, true);
|
||||
}
|
||||
cameraMenuChanged();
|
||||
break;
|
||||
}
|
||||
|
||||
case Qt::Key_P: {
|
||||
if (!(isShifted || isMeta || isOption)) {
|
||||
bool isFirstPersonChecked = Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson);
|
||||
|
@ -3845,8 +3822,6 @@ void Application::init() {
|
|||
DependencyManager::get<AvatarManager>()->init();
|
||||
_myCamera.setMode(CAMERA_MODE_FIRST_PERSON);
|
||||
|
||||
_mirrorCamera.setMode(CAMERA_MODE_MIRROR);
|
||||
|
||||
_timerStart.start();
|
||||
_lastTimeUpdated.start();
|
||||
|
||||
|
@ -4383,16 +4358,16 @@ void Application::update(float deltaTime) {
|
|||
myAvatar->clearDriveKeys();
|
||||
if (_myCamera.getMode() != CAMERA_MODE_INDEPENDENT) {
|
||||
if (!_controllerScriptingInterface->areActionsCaptured()) {
|
||||
myAvatar->setDriveKeys(TRANSLATE_Z, -1.0f * userInputMapper->getActionState(controller::Action::TRANSLATE_Z));
|
||||
myAvatar->setDriveKeys(TRANSLATE_Y, userInputMapper->getActionState(controller::Action::TRANSLATE_Y));
|
||||
myAvatar->setDriveKeys(TRANSLATE_X, userInputMapper->getActionState(controller::Action::TRANSLATE_X));
|
||||
myAvatar->setDriveKey(MyAvatar::TRANSLATE_Z, -1.0f * userInputMapper->getActionState(controller::Action::TRANSLATE_Z));
|
||||
myAvatar->setDriveKey(MyAvatar::TRANSLATE_Y, userInputMapper->getActionState(controller::Action::TRANSLATE_Y));
|
||||
myAvatar->setDriveKey(MyAvatar::TRANSLATE_X, userInputMapper->getActionState(controller::Action::TRANSLATE_X));
|
||||
if (deltaTime > FLT_EPSILON) {
|
||||
myAvatar->setDriveKeys(PITCH, -1.0f * userInputMapper->getActionState(controller::Action::PITCH));
|
||||
myAvatar->setDriveKeys(YAW, -1.0f * userInputMapper->getActionState(controller::Action::YAW));
|
||||
myAvatar->setDriveKeys(STEP_YAW, -1.0f * userInputMapper->getActionState(controller::Action::STEP_YAW));
|
||||
myAvatar->setDriveKey(MyAvatar::PITCH, -1.0f * userInputMapper->getActionState(controller::Action::PITCH));
|
||||
myAvatar->setDriveKey(MyAvatar::YAW, -1.0f * userInputMapper->getActionState(controller::Action::YAW));
|
||||
myAvatar->setDriveKey(MyAvatar::STEP_YAW, -1.0f * userInputMapper->getActionState(controller::Action::STEP_YAW));
|
||||
}
|
||||
}
|
||||
myAvatar->setDriveKeys(ZOOM, userInputMapper->getActionState(controller::Action::TRANSLATE_CAMERA_Z));
|
||||
myAvatar->setDriveKey(MyAvatar::ZOOM, userInputMapper->getActionState(controller::Action::TRANSLATE_CAMERA_Z));
|
||||
}
|
||||
|
||||
controller::Pose leftHandPose = userInputMapper->getPoseState(controller::Action::LEFT_HAND);
|
||||
|
@ -4463,9 +4438,12 @@ void Application::update(float deltaTime) {
|
|||
|
||||
getEntities()->getTree()->withWriteLock([&] {
|
||||
PerformanceTimer perfTimer("handleOutgoingChanges");
|
||||
const VectorOfMotionStates& outgoingChanges = _physicsEngine->getOutgoingChanges();
|
||||
_entitySimulation->handleOutgoingChanges(outgoingChanges);
|
||||
avatarManager->handleOutgoingChanges(outgoingChanges);
|
||||
const VectorOfMotionStates& deactivations = _physicsEngine->getDeactivatedMotionStates();
|
||||
_entitySimulation->handleDeactivatedMotionStates(deactivations);
|
||||
|
||||
const VectorOfMotionStates& outgoingChanges = _physicsEngine->getChangedMotionStates();
|
||||
_entitySimulation->handleChangedMotionStates(outgoingChanges);
|
||||
avatarManager->handleChangedMotionStates(outgoingChanges);
|
||||
});
|
||||
|
||||
if (!_aboutToQuit) {
|
||||
|
@ -5122,58 +5100,6 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se
|
|||
activeRenderingThread = nullptr;
|
||||
}
|
||||
|
||||
void Application::renderRearViewMirror(RenderArgs* renderArgs, const QRect& region, bool isZoomed) {
|
||||
auto originalViewport = renderArgs->_viewport;
|
||||
// Grab current viewport to reset it at the end
|
||||
|
||||
float aspect = (float)region.width() / region.height();
|
||||
float fov = MIRROR_FIELD_OF_VIEW;
|
||||
|
||||
auto myAvatar = getMyAvatar();
|
||||
|
||||
// bool eyeRelativeCamera = false;
|
||||
if (!isZoomed) {
|
||||
_mirrorCamera.setPosition(myAvatar->getChestPosition() +
|
||||
myAvatar->getOrientation() * glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_REARVIEW_BODY_DISTANCE * myAvatar->getScale());
|
||||
|
||||
} else { // HEAD zoom level
|
||||
// FIXME note that the positioning of the camera relative to the avatar can suffer limited
|
||||
// precision as the user's position moves further away from the origin. Thus at
|
||||
// /1e7,1e7,1e7 (well outside the buildable volume) the mirror camera veers and sways
|
||||
// wildly as you rotate your avatar because the floating point values are becoming
|
||||
// larger, squeezing out the available digits of precision you have available at the
|
||||
// human scale for camera positioning.
|
||||
|
||||
// Previously there was a hack to correct this using the mechanism of repositioning
|
||||
// the avatar at the origin of the world for the purposes of rendering the mirror,
|
||||
// but it resulted in failing to render the avatar's head model in the mirror view
|
||||
// when in first person mode. Presumably this was because of some missed culling logic
|
||||
// that was not accounted for in the hack.
|
||||
|
||||
// This was removed in commit 71e59cfa88c6563749594e25494102fe01db38e9 but could be further
|
||||
// investigated in order to adapt the technique while fixing the head rendering issue,
|
||||
// but the complexity of the hack suggests that a better approach
|
||||
_mirrorCamera.setPosition(myAvatar->getDefaultEyePosition() +
|
||||
myAvatar->getOrientation() * glm::vec3(0.0f, 0.0f, -1.0f) * MIRROR_REARVIEW_DISTANCE * myAvatar->getScale());
|
||||
}
|
||||
_mirrorCamera.setProjection(glm::perspective(glm::radians(fov), aspect, DEFAULT_NEAR_CLIP, DEFAULT_FAR_CLIP));
|
||||
_mirrorCamera.setOrientation(myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI, 0.0f)));
|
||||
|
||||
|
||||
// set the bounds of rear mirror view
|
||||
// the region is in device independent coordinates; must convert to device
|
||||
float ratio = (float)QApplication::desktop()->windowHandle()->devicePixelRatio() * getRenderResolutionScale();
|
||||
int width = region.width() * ratio;
|
||||
int height = region.height() * ratio;
|
||||
gpu::Vec4i viewport = gpu::Vec4i(0, 0, width, height);
|
||||
renderArgs->_viewport = viewport;
|
||||
|
||||
// render rear mirror view
|
||||
displaySide(renderArgs, _mirrorCamera, true);
|
||||
|
||||
renderArgs->_viewport = originalViewport;
|
||||
}
|
||||
|
||||
void Application::resetSensors(bool andReload) {
|
||||
DependencyManager::get<Faceshift>()->reset();
|
||||
DependencyManager::get<DdeFaceTracker>()->reset();
|
||||
|
@ -5503,8 +5429,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
|
|||
scriptEngine->registerGlobalObject("Rates", new RatesScriptingInterface(this));
|
||||
|
||||
// hook our avatar and avatar hash map object into this script engine
|
||||
scriptEngine->registerGlobalObject("MyAvatar", getMyAvatar().get());
|
||||
qScriptRegisterMetaType(scriptEngine, audioListenModeToScriptValue, audioListenModeFromScriptValue);
|
||||
getMyAvatar()->registerMetaTypes(scriptEngine);
|
||||
|
||||
scriptEngine->registerGlobalObject("AvatarList", DependencyManager::get<AvatarManager>().data());
|
||||
|
||||
|
|
|
@ -72,6 +72,8 @@
|
|||
|
||||
#include <procedural/ProceduralSkybox.h>
|
||||
#include <model/Skybox.h>
|
||||
#include <ModelScriptingInterface.h>
|
||||
|
||||
|
||||
class OffscreenGLCanvas;
|
||||
class GLCanvas;
|
||||
|
@ -276,8 +278,6 @@ public:
|
|||
|
||||
virtual void pushPostUpdateLambda(void* key, std::function<void()> func) override;
|
||||
|
||||
const QRect& getMirrorViewRect() const { return _mirrorViewRect; }
|
||||
|
||||
void updateMyAvatarLookAtPosition();
|
||||
|
||||
float getAvatarSimrate() const { return _avatarSimCounter.rate(); }
|
||||
|
@ -368,7 +368,6 @@ public slots:
|
|||
void calibrateEyeTracker5Points();
|
||||
#endif
|
||||
|
||||
void aboutApp();
|
||||
static void showHelp();
|
||||
|
||||
void cycleCamera();
|
||||
|
@ -557,8 +556,6 @@ private:
|
|||
int _avatarSimsPerSecondReport {0};
|
||||
quint64 _lastAvatarSimsPerSecondUpdate {0};
|
||||
Camera _myCamera; // My view onto the world
|
||||
Camera _mirrorCamera; // Camera for mirror view
|
||||
QRect _mirrorViewRect;
|
||||
|
||||
Setting::Handle<QString> _previousScriptLocation;
|
||||
Setting::Handle<float> _fieldOfView;
|
||||
|
|
|
@ -74,9 +74,6 @@ Menu::Menu() {
|
|||
// File > Help
|
||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::Help, 0, qApp, SLOT(showHelp()));
|
||||
|
||||
// File > About
|
||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::AboutApp, 0, qApp, SLOT(aboutApp()), QAction::AboutRole);
|
||||
|
||||
// File > Quit
|
||||
addActionToQMenuAndActionHash(fileMenu, MenuOption::Quit, Qt::CTRL | Qt::Key_Q, qApp, SLOT(quit()), QAction::QuitRole);
|
||||
|
||||
|
@ -249,9 +246,6 @@ Menu::Menu() {
|
|||
|
||||
viewMenu->addSeparator();
|
||||
|
||||
// View > Mini Mirror
|
||||
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::MiniMirror, 0, false);
|
||||
|
||||
// View > Center Player In View
|
||||
addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::CenterPlayerInView,
|
||||
0, true, qApp, SLOT(rotationModeChanged()),
|
||||
|
@ -417,6 +411,9 @@ Menu::Menu() {
|
|||
}
|
||||
|
||||
// Developer > Assets >>>
|
||||
// Menu item is not currently needed but code should be kept in case it proves useful again at some stage.
|
||||
//#define WANT_ASSET_MIGRATION
|
||||
#ifdef WANT_ASSET_MIGRATION
|
||||
MenuWrapper* assetDeveloperMenu = developerMenu->addMenu("Assets");
|
||||
auto& atpMigrator = ATPAssetMigrator::getInstance();
|
||||
atpMigrator.setDialogParent(this);
|
||||
|
@ -424,6 +421,7 @@ Menu::Menu() {
|
|||
addActionToQMenuAndActionHash(assetDeveloperMenu, MenuOption::AssetMigration,
|
||||
0, &atpMigrator,
|
||||
SLOT(loadEntityServerFile()));
|
||||
#endif
|
||||
|
||||
// Developer > Avatar >>>
|
||||
MenuWrapper* avatarDebugMenu = developerMenu->addMenu("Avatar");
|
||||
|
@ -554,16 +552,14 @@ Menu::Menu() {
|
|||
"NetworkingPreferencesDialog");
|
||||
});
|
||||
addActionToQMenuAndActionHash(networkMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches()));
|
||||
addActionToQMenuAndActionHash(networkMenu, MenuOption::ClearDiskCache, 0,
|
||||
DependencyManager::get<AssetClient>().data(), SLOT(clearCache()));
|
||||
addCheckableActionToQMenuAndActionHash(networkMenu,
|
||||
MenuOption::DisableActivityLogger,
|
||||
0,
|
||||
false,
|
||||
&UserActivityLogger::getInstance(),
|
||||
SLOT(disable(bool)));
|
||||
addActionToQMenuAndActionHash(networkMenu, MenuOption::CachesSize, 0,
|
||||
dialogsManager.data(), SLOT(cachesSizeDialog()));
|
||||
addActionToQMenuAndActionHash(networkMenu, MenuOption::DiskCacheEditor, 0,
|
||||
dialogsManager.data(), SLOT(toggleDiskCacheEditor()));
|
||||
addActionToQMenuAndActionHash(networkMenu, MenuOption::ShowDSConnectTable, 0,
|
||||
dialogsManager.data(), SLOT(showDomainConnectionDialog()));
|
||||
|
||||
|
|
|
@ -26,7 +26,6 @@ public:
|
|||
};
|
||||
|
||||
namespace MenuOption {
|
||||
const QString AboutApp = "About Interface";
|
||||
const QString AddRemoveFriends = "Add/Remove Friends...";
|
||||
const QString AddressBar = "Show Address Bar";
|
||||
const QString Animations = "Animations...";
|
||||
|
@ -52,11 +51,11 @@ namespace MenuOption {
|
|||
const QString BinaryEyelidControl = "Binary Eyelid Control";
|
||||
const QString BookmarkLocation = "Bookmark Location";
|
||||
const QString Bookmarks = "Bookmarks";
|
||||
const QString CachesSize = "RAM Caches Size";
|
||||
const QString CalibrateCamera = "Calibrate Camera";
|
||||
const QString CameraEntityMode = "Entity Mode";
|
||||
const QString CenterPlayerInView = "Center Player In View";
|
||||
const QString Chat = "Chat...";
|
||||
const QString ClearDiskCache = "Clear Disk Cache";
|
||||
const QString Collisions = "Collisions";
|
||||
const QString Connexion = "Activate 3D Connexion Devices";
|
||||
const QString Console = "Console...";
|
||||
|
@ -83,7 +82,6 @@ namespace MenuOption {
|
|||
const QString DisableActivityLogger = "Disable Activity Logger";
|
||||
const QString DisableEyelidAdjustment = "Disable Eyelid Adjustment";
|
||||
const QString DisableLightEntities = "Disable Light Entities";
|
||||
const QString DiskCacheEditor = "Disk Cache Editor";
|
||||
const QString DisplayCrashOptions = "Display Crash Options";
|
||||
const QString DisplayHandTargets = "Show Hand Targets";
|
||||
const QString DisplayModelBounds = "Display Model Bounds";
|
||||
|
@ -124,7 +122,6 @@ namespace MenuOption {
|
|||
const QString LogExtraTimings = "Log Extra Timing Details";
|
||||
const QString LowVelocityFilter = "Low Velocity Filter";
|
||||
const QString MeshVisible = "Draw Mesh";
|
||||
const QString MiniMirror = "Mini Mirror";
|
||||
const QString MuteAudio = "Mute Microphone";
|
||||
const QString MuteEnvironment = "Mute Environment";
|
||||
const QString MuteFaceTracking = "Mute Face Tracking";
|
||||
|
|
|
@ -424,7 +424,7 @@ void AvatarManager::getObjectsToChange(VectorOfMotionStates& result) {
|
|||
}
|
||||
}
|
||||
|
||||
void AvatarManager::handleOutgoingChanges(const VectorOfMotionStates& motionStates) {
|
||||
void AvatarManager::handleChangedMotionStates(const VectorOfMotionStates& motionStates) {
|
||||
// TODO: extract the MyAvatar results once we use a MotionState for it.
|
||||
}
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ public:
|
|||
void getObjectsToRemoveFromPhysics(VectorOfMotionStates& motionStates);
|
||||
void getObjectsToAddToPhysics(VectorOfMotionStates& motionStates);
|
||||
void getObjectsToChange(VectorOfMotionStates& motionStates);
|
||||
void handleOutgoingChanges(const VectorOfMotionStates& motionStates);
|
||||
void handleChangedMotionStates(const VectorOfMotionStates& motionStates);
|
||||
void handleCollisionEvents(const CollisionEvents& collisionEvents);
|
||||
|
||||
Q_INVOKABLE float getAvatarDataRate(const QUuid& sessionID, const QString& rateName = QString("")) const;
|
||||
|
|
|
@ -20,55 +20,28 @@ using namespace render;
|
|||
CauterizedMeshPartPayload::CauterizedMeshPartPayload(Model* model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform)
|
||||
: ModelMeshPartPayload(model, meshIndex, partIndex, shapeIndex, transform, offsetTransform) {}
|
||||
|
||||
void CauterizedMeshPartPayload::updateTransformForSkinnedCauterizedMesh(const Transform& transform,
|
||||
const QVector<glm::mat4>& clusterMatrices,
|
||||
const QVector<glm::mat4>& cauterizedClusterMatrices) {
|
||||
_transform = transform;
|
||||
_cauterizedTransform = transform;
|
||||
|
||||
if (clusterMatrices.size() > 0) {
|
||||
_worldBound = AABox();
|
||||
for (auto& clusterMatrix : clusterMatrices) {
|
||||
AABox clusterBound = _localBound;
|
||||
clusterBound.transform(clusterMatrix);
|
||||
_worldBound += clusterBound;
|
||||
}
|
||||
|
||||
_worldBound.transform(transform);
|
||||
if (clusterMatrices.size() == 1) {
|
||||
_transform = _transform.worldTransform(Transform(clusterMatrices[0]));
|
||||
if (cauterizedClusterMatrices.size() != 0) {
|
||||
_cauterizedTransform = _cauterizedTransform.worldTransform(Transform(cauterizedClusterMatrices[0]));
|
||||
} else {
|
||||
_cauterizedTransform = _transform;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_worldBound = _localBound;
|
||||
_worldBound.transform(_drawTransform);
|
||||
}
|
||||
void CauterizedMeshPartPayload::updateTransformForCauterizedMesh(
|
||||
const Transform& renderTransform,
|
||||
const gpu::BufferPointer& buffer) {
|
||||
_cauterizedTransform = renderTransform;
|
||||
_cauterizedClusterBuffer = buffer;
|
||||
}
|
||||
|
||||
void CauterizedMeshPartPayload::bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const {
|
||||
// Still relying on the raw data from the model
|
||||
const Model::MeshState& state = _model->getMeshState(_meshIndex);
|
||||
SkeletonModel* skeleton = static_cast<SkeletonModel*>(_model);
|
||||
bool useCauterizedMesh = (renderMode != RenderArgs::RenderMode::SHADOW_RENDER_MODE) && skeleton->getEnableCauterization();
|
||||
|
||||
if (state.clusterBuffer) {
|
||||
if (useCauterizedMesh) {
|
||||
const Model::MeshState& cState = skeleton->getCauterizeMeshState(_meshIndex);
|
||||
batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, cState.clusterBuffer);
|
||||
} else {
|
||||
batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, state.clusterBuffer);
|
||||
if (useCauterizedMesh) {
|
||||
if (_cauterizedClusterBuffer) {
|
||||
batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, _cauterizedClusterBuffer);
|
||||
}
|
||||
batch.setModelTransform(_cauterizedTransform);
|
||||
} else {
|
||||
if (_clusterBuffer) {
|
||||
batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, _clusterBuffer);
|
||||
}
|
||||
batch.setModelTransform(_transform);
|
||||
} else {
|
||||
if (useCauterizedMesh) {
|
||||
batch.setModelTransform(_cauterizedTransform);
|
||||
} else {
|
||||
batch.setModelTransform(_transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,12 +17,13 @@
|
|||
class CauterizedMeshPartPayload : public ModelMeshPartPayload {
|
||||
public:
|
||||
CauterizedMeshPartPayload(Model* model, int meshIndex, int partIndex, int shapeIndex, const Transform& transform, const Transform& offsetTransform);
|
||||
void updateTransformForSkinnedCauterizedMesh(const Transform& transform,
|
||||
const QVector<glm::mat4>& clusterMatrices,
|
||||
const QVector<glm::mat4>& cauterizedClusterMatrices);
|
||||
|
||||
void updateTransformForCauterizedMesh(const Transform& renderTransform, const gpu::BufferPointer& buffer);
|
||||
|
||||
void bindTransform(gpu::Batch& batch, const render::ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const override;
|
||||
|
||||
private:
|
||||
gpu::BufferPointer _cauterizedClusterBuffer;
|
||||
Transform _cauterizedTransform;
|
||||
};
|
||||
|
||||
|
|
|
@ -26,8 +26,8 @@ CauterizedModel::~CauterizedModel() {
|
|||
}
|
||||
|
||||
void CauterizedModel::deleteGeometry() {
|
||||
Model::deleteGeometry();
|
||||
_cauterizeMeshStates.clear();
|
||||
Model::deleteGeometry();
|
||||
_cauterizeMeshStates.clear();
|
||||
}
|
||||
|
||||
bool CauterizedModel::updateGeometry() {
|
||||
|
@ -41,7 +41,7 @@ bool CauterizedModel::updateGeometry() {
|
|||
_cauterizeMeshStates.append(state);
|
||||
}
|
||||
}
|
||||
return needsFullUpdate;
|
||||
return needsFullUpdate;
|
||||
}
|
||||
|
||||
void CauterizedModel::createVisibleRenderItemSet() {
|
||||
|
@ -86,13 +86,13 @@ void CauterizedModel::createVisibleRenderItemSet() {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
Model::createVisibleRenderItemSet();
|
||||
Model::createVisibleRenderItemSet();
|
||||
}
|
||||
}
|
||||
|
||||
void CauterizedModel::createCollisionRenderItemSet() {
|
||||
// Temporary HACK: use base class method for now
|
||||
Model::createCollisionRenderItemSet();
|
||||
Model::createCollisionRenderItemSet();
|
||||
}
|
||||
|
||||
void CauterizedModel::updateClusterMatrices() {
|
||||
|
@ -122,8 +122,8 @@ void CauterizedModel::updateClusterMatrices() {
|
|||
state.clusterBuffer->setSubData(0, state.clusterMatrices.size() * sizeof(glm::mat4),
|
||||
(const gpu::Byte*) state.clusterMatrices.constData());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// as an optimization, don't build cautrizedClusterMatrices if the boneSet is empty.
|
||||
if (!_cauterizeBoneSet.empty()) {
|
||||
|
@ -191,6 +191,9 @@ void CauterizedModel::updateRenderItems() {
|
|||
return;
|
||||
}
|
||||
|
||||
// lazy update of cluster matrices used for rendering. We need to update them here, so we can correctly update the bounding box.
|
||||
self->updateClusterMatrices();
|
||||
|
||||
render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
|
||||
|
||||
Transform modelTransform;
|
||||
|
@ -209,15 +212,22 @@ void CauterizedModel::updateRenderItems() {
|
|||
if (data._model && data._model->isLoaded()) {
|
||||
// Ensure the model geometry was not reset between frames
|
||||
if (deleteGeometryCounter == data._model->getGeometryCounter()) {
|
||||
// lazy update of cluster matrices used for rendering. We need to update them here, so we can correctly update the bounding box.
|
||||
data._model->updateClusterMatrices();
|
||||
|
||||
// update the model transform and bounding box for this render item.
|
||||
// this stuff identical to what happens in regular Model
|
||||
const Model::MeshState& state = data._model->getMeshState(data._meshIndex);
|
||||
Transform renderTransform = modelTransform;
|
||||
if (state.clusterMatrices.size() == 1) {
|
||||
renderTransform = modelTransform.worldTransform(Transform(state.clusterMatrices[0]));
|
||||
}
|
||||
data.updateTransformForSkinnedMesh(renderTransform, modelTransform, state.clusterBuffer);
|
||||
|
||||
// this stuff for cauterized mesh
|
||||
CauterizedModel* cModel = static_cast<CauterizedModel*>(data._model);
|
||||
assert(data._meshIndex < cModel->_cauterizeMeshStates.size());
|
||||
const Model::MeshState& cState = cModel->_cauterizeMeshStates.at(data._meshIndex);
|
||||
data.updateTransformForSkinnedCauterizedMesh(modelTransform, state.clusterMatrices, cState.clusterMatrices);
|
||||
const Model::MeshState& cState = cModel->getCauterizeMeshState(data._meshIndex);
|
||||
renderTransform = modelTransform;
|
||||
if (cState.clusterMatrices.size() == 1) {
|
||||
renderTransform = modelTransform.worldTransform(Transform(cState.clusterMatrices[0]));
|
||||
}
|
||||
data.updateTransformForCauterizedMesh(renderTransform, cState.clusterBuffer);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -104,6 +104,7 @@ MyAvatar::MyAvatar(RigPointer rig) :
|
|||
_eyeContactTarget(LEFT_EYE),
|
||||
_realWorldFieldOfView("realWorldFieldOfView",
|
||||
DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES),
|
||||
_useAdvancedMovementControls("advancedMovementForHandControllersIsChecked", false),
|
||||
_hmdSensorMatrix(),
|
||||
_hmdSensorOrientation(),
|
||||
_hmdSensorPosition(),
|
||||
|
@ -119,9 +120,7 @@ MyAvatar::MyAvatar(RigPointer rig) :
|
|||
using namespace recording;
|
||||
_skeletonModel->flagAsCauterized();
|
||||
|
||||
for (int i = 0; i < MAX_DRIVE_KEYS; i++) {
|
||||
_driveKeys[i] = 0.0f;
|
||||
}
|
||||
clearDriveKeys();
|
||||
|
||||
// Necessary to select the correct slot
|
||||
using SlotType = void(MyAvatar::*)(const glm::vec3&, bool, const glm::quat&, bool);
|
||||
|
@ -154,9 +153,12 @@ MyAvatar::MyAvatar(RigPointer rig) :
|
|||
if (recordingInterface->getPlayFromCurrentLocation()) {
|
||||
setRecordingBasis();
|
||||
}
|
||||
_wasCharacterControllerEnabled = _characterController.isEnabled();
|
||||
_characterController.setEnabled(false);
|
||||
} else {
|
||||
clearRecordingBasis();
|
||||
useFullAvatarURL(_fullAvatarURLFromPreferences, _fullAvatarModelName);
|
||||
_characterController.setEnabled(_wasCharacterControllerEnabled);
|
||||
}
|
||||
|
||||
auto audioIO = DependencyManager::get<AudioClient>();
|
||||
|
@ -227,6 +229,21 @@ MyAvatar::~MyAvatar() {
|
|||
_lookAtTargetAvatar.reset();
|
||||
}
|
||||
|
||||
void MyAvatar::registerMetaTypes(QScriptEngine* engine) {
|
||||
QScriptValue value = engine->newQObject(this, QScriptEngine::QtOwnership, QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects);
|
||||
engine->globalObject().setProperty("MyAvatar", value);
|
||||
|
||||
QScriptValue driveKeys = engine->newObject();
|
||||
auto metaEnum = QMetaEnum::fromType<DriveKeys>();
|
||||
for (int i = 0; i < MAX_DRIVE_KEYS; ++i) {
|
||||
driveKeys.setProperty(metaEnum.key(i), metaEnum.value(i));
|
||||
}
|
||||
engine->globalObject().setProperty("DriveKeys", driveKeys);
|
||||
|
||||
qScriptRegisterMetaType(engine, audioListenModeToScriptValue, audioListenModeFromScriptValue);
|
||||
qScriptRegisterMetaType(engine, driveKeysToScriptValue, driveKeysFromScriptValue);
|
||||
}
|
||||
|
||||
void MyAvatar::setOrientationVar(const QVariant& newOrientationVar) {
|
||||
Avatar::setOrientation(quatFromVariant(newOrientationVar));
|
||||
}
|
||||
|
@ -459,7 +476,7 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
// When there are no step values, we zero out the last step pulse.
|
||||
// This allows a user to do faster snapping by tapping a control
|
||||
for (int i = STEP_TRANSLATE_X; !stepAction && i <= STEP_YAW; ++i) {
|
||||
if (_driveKeys[i] != 0.0f) {
|
||||
if (getDriveKey((DriveKeys)i) != 0.0f) {
|
||||
stepAction = true;
|
||||
}
|
||||
}
|
||||
|
@ -1652,7 +1669,7 @@ bool MyAvatar::shouldRenderHead(const RenderArgs* renderArgs) const {
|
|||
void MyAvatar::updateOrientation(float deltaTime) {
|
||||
|
||||
// Smoothly rotate body with arrow keys
|
||||
float targetSpeed = _driveKeys[YAW] * _yawSpeed;
|
||||
float targetSpeed = getDriveKey(YAW) * _yawSpeed;
|
||||
if (targetSpeed != 0.0f) {
|
||||
const float ROTATION_RAMP_TIMESCALE = 0.1f;
|
||||
float blend = deltaTime / ROTATION_RAMP_TIMESCALE;
|
||||
|
@ -1681,8 +1698,8 @@ void MyAvatar::updateOrientation(float deltaTime) {
|
|||
// Comfort Mode: If you press any of the left/right rotation drive keys or input, you'll
|
||||
// get an instantaneous 15 degree turn. If you keep holding the key down you'll get another
|
||||
// snap turn every half second.
|
||||
if (_driveKeys[STEP_YAW] != 0.0f) {
|
||||
totalBodyYaw += _driveKeys[STEP_YAW];
|
||||
if (getDriveKey(STEP_YAW) != 0.0f) {
|
||||
totalBodyYaw += getDriveKey(STEP_YAW);
|
||||
}
|
||||
|
||||
// use head/HMD orientation to turn while flying
|
||||
|
@ -1719,7 +1736,7 @@ void MyAvatar::updateOrientation(float deltaTime) {
|
|||
// update body orientation by movement inputs
|
||||
setOrientation(getOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, totalBodyYaw, 0.0f))));
|
||||
|
||||
getHead()->setBasePitch(getHead()->getBasePitch() + _driveKeys[PITCH] * _pitchSpeed * deltaTime);
|
||||
getHead()->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime);
|
||||
|
||||
if (qApp->isHMDMode()) {
|
||||
glm::quat orientation = glm::quat_cast(getSensorToWorldMatrix()) * getHMDSensorOrientation();
|
||||
|
@ -1753,14 +1770,14 @@ void MyAvatar::updateActionMotor(float deltaTime) {
|
|||
}
|
||||
|
||||
// compute action input
|
||||
glm::vec3 front = (_driveKeys[TRANSLATE_Z]) * IDENTITY_FRONT;
|
||||
glm::vec3 right = (_driveKeys[TRANSLATE_X]) * IDENTITY_RIGHT;
|
||||
glm::vec3 front = (getDriveKey(TRANSLATE_Z)) * IDENTITY_FRONT;
|
||||
glm::vec3 right = (getDriveKey(TRANSLATE_X)) * IDENTITY_RIGHT;
|
||||
|
||||
glm::vec3 direction = front + right;
|
||||
CharacterController::State state = _characterController.getState();
|
||||
if (state == CharacterController::State::Hover) {
|
||||
// we're flying --> support vertical motion
|
||||
glm::vec3 up = (_driveKeys[TRANSLATE_Y]) * IDENTITY_UP;
|
||||
glm::vec3 up = (getDriveKey(TRANSLATE_Y)) * IDENTITY_UP;
|
||||
direction += up;
|
||||
}
|
||||
|
||||
|
@ -1799,7 +1816,7 @@ void MyAvatar::updateActionMotor(float deltaTime) {
|
|||
_actionMotorVelocity = MAX_WALKING_SPEED * direction;
|
||||
}
|
||||
|
||||
float boomChange = _driveKeys[ZOOM];
|
||||
float boomChange = getDriveKey(ZOOM);
|
||||
_boomLength += 2.0f * _boomLength * boomChange + boomChange * boomChange;
|
||||
_boomLength = glm::clamp<float>(_boomLength, ZOOM_MIN, ZOOM_MAX);
|
||||
}
|
||||
|
@ -1830,11 +1847,11 @@ void MyAvatar::updatePosition(float deltaTime) {
|
|||
}
|
||||
|
||||
// capture the head rotation, in sensor space, when the user first indicates they would like to move/fly.
|
||||
if (!_hoverReferenceCameraFacingIsCaptured && (fabs(_driveKeys[TRANSLATE_Z]) > 0.1f || fabs(_driveKeys[TRANSLATE_X]) > 0.1f)) {
|
||||
if (!_hoverReferenceCameraFacingIsCaptured && (fabs(getDriveKey(TRANSLATE_Z)) > 0.1f || fabs(getDriveKey(TRANSLATE_X)) > 0.1f)) {
|
||||
_hoverReferenceCameraFacingIsCaptured = true;
|
||||
// transform the camera facing vector into sensor space.
|
||||
_hoverReferenceCameraFacing = transformVectorFast(glm::inverse(_sensorToWorldMatrix), getHead()->getCameraOrientation() * Vectors::UNIT_Z);
|
||||
} else if (_hoverReferenceCameraFacingIsCaptured && (fabs(_driveKeys[TRANSLATE_Z]) <= 0.1f && fabs(_driveKeys[TRANSLATE_X]) <= 0.1f)) {
|
||||
} else if (_hoverReferenceCameraFacingIsCaptured && (fabs(getDriveKey(TRANSLATE_Z)) <= 0.1f && fabs(getDriveKey(TRANSLATE_X)) <= 0.1f)) {
|
||||
_hoverReferenceCameraFacingIsCaptured = false;
|
||||
}
|
||||
}
|
||||
|
@ -2090,17 +2107,61 @@ bool MyAvatar::getCharacterControllerEnabled() {
|
|||
}
|
||||
|
||||
void MyAvatar::clearDriveKeys() {
|
||||
for (int i = 0; i < MAX_DRIVE_KEYS; ++i) {
|
||||
_driveKeys[i] = 0.0f;
|
||||
_driveKeys.fill(0.0f);
|
||||
}
|
||||
|
||||
void MyAvatar::setDriveKey(DriveKeys key, float val) {
|
||||
try {
|
||||
_driveKeys.at(key) = val;
|
||||
} catch (const std::exception&) {
|
||||
qCCritical(interfaceapp) << Q_FUNC_INFO << ": Index out of bounds";
|
||||
}
|
||||
}
|
||||
|
||||
float MyAvatar::getDriveKey(DriveKeys key) const {
|
||||
return isDriveKeyDisabled(key) ? 0.0f : getRawDriveKey(key);
|
||||
}
|
||||
|
||||
float MyAvatar::getRawDriveKey(DriveKeys key) const {
|
||||
try {
|
||||
return _driveKeys.at(key);
|
||||
} catch (const std::exception&) {
|
||||
qCCritical(interfaceapp) << Q_FUNC_INFO << ": Index out of bounds";
|
||||
return 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::relayDriveKeysToCharacterController() {
|
||||
if (_driveKeys[TRANSLATE_Y] > 0.0f) {
|
||||
if (getDriveKey(TRANSLATE_Y) > 0.0f) {
|
||||
_characterController.jump();
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::disableDriveKey(DriveKeys key) {
|
||||
try {
|
||||
_disabledDriveKeys.set(key);
|
||||
} catch (const std::exception&) {
|
||||
qCCritical(interfaceapp) << Q_FUNC_INFO << ": Index out of bounds";
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::enableDriveKey(DriveKeys key) {
|
||||
try {
|
||||
_disabledDriveKeys.reset(key);
|
||||
} catch (const std::exception&) {
|
||||
qCCritical(interfaceapp) << Q_FUNC_INFO << ": Index out of bounds";
|
||||
}
|
||||
}
|
||||
|
||||
bool MyAvatar::isDriveKeyDisabled(DriveKeys key) const {
|
||||
try {
|
||||
return _disabledDriveKeys.test(key);
|
||||
} catch (const std::exception&) {
|
||||
qCCritical(interfaceapp) << Q_FUNC_INFO << ": Index out of bounds";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec3 MyAvatar::getWorldBodyPosition() const {
|
||||
return transformPoint(_sensorToWorldMatrix, extractTranslation(_bodySensorMatrix));
|
||||
}
|
||||
|
@ -2186,7 +2247,15 @@ QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioList
|
|||
}
|
||||
|
||||
void audioListenModeFromScriptValue(const QScriptValue& object, AudioListenerMode& audioListenerMode) {
|
||||
audioListenerMode = (AudioListenerMode)object.toUInt16();
|
||||
audioListenerMode = static_cast<AudioListenerMode>(object.toUInt16());
|
||||
}
|
||||
|
||||
QScriptValue driveKeysToScriptValue(QScriptEngine* engine, const MyAvatar::DriveKeys& driveKeys) {
|
||||
return driveKeys;
|
||||
}
|
||||
|
||||
void driveKeysFromScriptValue(const QScriptValue& object, MyAvatar::DriveKeys& driveKeys) {
|
||||
driveKeys = static_cast<MyAvatar::DriveKeys>(object.toUInt16());
|
||||
}
|
||||
|
||||
|
||||
|
@ -2379,7 +2448,7 @@ bool MyAvatar::didTeleport() {
|
|||
}
|
||||
|
||||
bool MyAvatar::hasDriveInput() const {
|
||||
return fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Y]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f;
|
||||
return fabsf(getDriveKey(TRANSLATE_X)) > 0.0f || fabsf(getDriveKey(TRANSLATE_Y)) > 0.0f || fabsf(getDriveKey(TRANSLATE_Z)) > 0.0f;
|
||||
}
|
||||
|
||||
void MyAvatar::setAway(bool value) {
|
||||
|
@ -2495,7 +2564,7 @@ bool MyAvatar::pinJoint(int index, const glm::vec3& position, const glm::quat& o
|
|||
return false;
|
||||
}
|
||||
|
||||
setPosition(position);
|
||||
slamPosition(position);
|
||||
setOrientation(orientation);
|
||||
|
||||
_rig->setMaxHipsOffsetLength(0.05f);
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#ifndef hifi_MyAvatar_h
|
||||
#define hifi_MyAvatar_h
|
||||
|
||||
#include <bitset>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <SettingHandle.h>
|
||||
|
@ -29,20 +31,6 @@
|
|||
class AvatarActionHold;
|
||||
class ModelItemID;
|
||||
|
||||
enum DriveKeys {
|
||||
TRANSLATE_X = 0,
|
||||
TRANSLATE_Y,
|
||||
TRANSLATE_Z,
|
||||
YAW,
|
||||
STEP_TRANSLATE_X,
|
||||
STEP_TRANSLATE_Y,
|
||||
STEP_TRANSLATE_Z,
|
||||
STEP_YAW,
|
||||
PITCH,
|
||||
ZOOM,
|
||||
MAX_DRIVE_KEYS
|
||||
};
|
||||
|
||||
enum eyeContactTarget {
|
||||
LEFT_EYE,
|
||||
RIGHT_EYE,
|
||||
|
@ -86,11 +74,29 @@ class MyAvatar : public Avatar {
|
|||
|
||||
Q_PROPERTY(bool hmdLeanRecenterEnabled READ getHMDLeanRecenterEnabled WRITE setHMDLeanRecenterEnabled)
|
||||
Q_PROPERTY(bool characterControllerEnabled READ getCharacterControllerEnabled WRITE setCharacterControllerEnabled)
|
||||
Q_PROPERTY(bool useAdvancedMovementControls READ useAdvancedMovementControls WRITE setUseAdvancedMovementControls)
|
||||
|
||||
public:
|
||||
enum DriveKeys {
|
||||
TRANSLATE_X = 0,
|
||||
TRANSLATE_Y,
|
||||
TRANSLATE_Z,
|
||||
YAW,
|
||||
STEP_TRANSLATE_X,
|
||||
STEP_TRANSLATE_Y,
|
||||
STEP_TRANSLATE_Z,
|
||||
STEP_YAW,
|
||||
PITCH,
|
||||
ZOOM,
|
||||
MAX_DRIVE_KEYS
|
||||
};
|
||||
Q_ENUM(DriveKeys)
|
||||
|
||||
explicit MyAvatar(RigPointer rig);
|
||||
~MyAvatar();
|
||||
|
||||
void registerMetaTypes(QScriptEngine* engine);
|
||||
|
||||
virtual void simulateAttachments(float deltaTime) override;
|
||||
|
||||
AudioListenerMode getAudioListenerModeHead() const { return FROM_HEAD; }
|
||||
|
@ -171,6 +177,10 @@ public:
|
|||
Q_INVOKABLE void setHMDLeanRecenterEnabled(bool value) { _hmdLeanRecenterEnabled = value; }
|
||||
Q_INVOKABLE bool getHMDLeanRecenterEnabled() const { return _hmdLeanRecenterEnabled; }
|
||||
|
||||
bool useAdvancedMovementControls() const { return _useAdvancedMovementControls.get(); }
|
||||
void setUseAdvancedMovementControls(bool useAdvancedMovementControls)
|
||||
{ _useAdvancedMovementControls.set(useAdvancedMovementControls); }
|
||||
|
||||
// get/set avatar data
|
||||
void saveData();
|
||||
void loadData();
|
||||
|
@ -180,9 +190,15 @@ public:
|
|||
|
||||
// Set what driving keys are being pressed to control thrust levels
|
||||
void clearDriveKeys();
|
||||
void setDriveKeys(int key, float val) { _driveKeys[key] = val; };
|
||||
void setDriveKey(DriveKeys key, float val);
|
||||
float getDriveKey(DriveKeys key) const;
|
||||
Q_INVOKABLE float getRawDriveKey(DriveKeys key) const;
|
||||
void relayDriveKeysToCharacterController();
|
||||
|
||||
Q_INVOKABLE void disableDriveKey(DriveKeys key);
|
||||
Q_INVOKABLE void enableDriveKey(DriveKeys key);
|
||||
Q_INVOKABLE bool isDriveKeyDisabled(DriveKeys key) const;
|
||||
|
||||
eyeContactTarget getEyeContactTarget();
|
||||
|
||||
Q_INVOKABLE glm::vec3 getTrackedHeadPosition() const { return _trackedHeadPosition; }
|
||||
|
@ -352,7 +368,6 @@ private:
|
|||
virtual bool shouldRenderHead(const RenderArgs* renderArgs) const override;
|
||||
void setShouldRenderLocally(bool shouldRender) { _shouldRender = shouldRender; setEnableMeshVisible(shouldRender); }
|
||||
bool getShouldRenderLocally() const { return _shouldRender; }
|
||||
bool getDriveKeys(int key) { return _driveKeys[key] != 0.0f; };
|
||||
bool isMyAvatar() const override { return true; }
|
||||
virtual int parseDataFromBuffer(const QByteArray& buffer) override;
|
||||
virtual glm::vec3 getSkeletonPosition() const override;
|
||||
|
@ -388,7 +403,9 @@ private:
|
|||
void clampScaleChangeToDomainLimits(float desiredScale);
|
||||
glm::mat4 computeCameraRelativeHandControllerMatrix(const glm::mat4& controllerSensorMatrix) const;
|
||||
|
||||
float _driveKeys[MAX_DRIVE_KEYS];
|
||||
std::array<float, MAX_DRIVE_KEYS> _driveKeys;
|
||||
std::bitset<MAX_DRIVE_KEYS> _disabledDriveKeys;
|
||||
|
||||
bool _wasPushing;
|
||||
bool _isPushing;
|
||||
bool _isBeingPushed;
|
||||
|
@ -411,6 +428,7 @@ private:
|
|||
SharedSoundPointer _collisionSound;
|
||||
|
||||
MyCharacterController _characterController;
|
||||
bool _wasCharacterControllerEnabled { true };
|
||||
|
||||
AvatarWeakPointer _lookAtTargetAvatar;
|
||||
glm::vec3 _targetAvatarPosition;
|
||||
|
@ -423,6 +441,7 @@ private:
|
|||
glm::vec3 _trackedHeadPosition;
|
||||
|
||||
Setting::Handle<float> _realWorldFieldOfView;
|
||||
Setting::Handle<bool> _useAdvancedMovementControls;
|
||||
|
||||
// private methods
|
||||
void updateOrientation(float deltaTime);
|
||||
|
@ -540,4 +559,7 @@ private:
|
|||
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);
|
||||
void audioListenModeFromScriptValue(const QScriptValue& object, AudioListenerMode& audioListenerMode);
|
||||
|
||||
QScriptValue driveKeysToScriptValue(QScriptEngine* engine, const MyAvatar::DriveKeys& driveKeys);
|
||||
void driveKeysFromScriptValue(const QScriptValue& object, MyAvatar::DriveKeys& driveKeys);
|
||||
|
||||
#endif // hifi_MyAvatar_h
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
|
||||
#include <avatar/AvatarManager.h>
|
||||
#include <GLMHelpers.h>
|
||||
#include <FramebufferCache.h>
|
||||
#include <GLMHelpers.h>
|
||||
#include <OffscreenUi.h>
|
||||
#include <CursorManager.h>
|
||||
|
@ -42,7 +41,6 @@ ApplicationOverlay::ApplicationOverlay()
|
|||
_domainStatusBorder = geometryCache->allocateID();
|
||||
_magnifierBorder = geometryCache->allocateID();
|
||||
_qmlGeometryId = geometryCache->allocateID();
|
||||
_rearViewGeometryId = geometryCache->allocateID();
|
||||
}
|
||||
|
||||
ApplicationOverlay::~ApplicationOverlay() {
|
||||
|
@ -51,7 +49,6 @@ ApplicationOverlay::~ApplicationOverlay() {
|
|||
geometryCache->releaseID(_domainStatusBorder);
|
||||
geometryCache->releaseID(_magnifierBorder);
|
||||
geometryCache->releaseID(_qmlGeometryId);
|
||||
geometryCache->releaseID(_rearViewGeometryId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,7 +83,6 @@ void ApplicationOverlay::renderOverlay(RenderArgs* renderArgs) {
|
|||
// Now render the overlay components together into a single texture
|
||||
renderDomainConnectionStatusBorder(renderArgs); // renders the connected domain line
|
||||
renderAudioScope(renderArgs); // audio scope in the very back - NOTE: this is the debug audio scope, not the VU meter
|
||||
renderRearView(renderArgs); // renders the mirror view selfie
|
||||
renderOverlays(renderArgs); // renders Scripts Overlay and AudioScope
|
||||
renderQmlUi(renderArgs); // renders a unit quad with the QML UI texture, and the text overlays from scripts
|
||||
renderStatsAndLogs(renderArgs); // currently renders nothing
|
||||
|
@ -163,45 +159,6 @@ void ApplicationOverlay::renderOverlays(RenderArgs* renderArgs) {
|
|||
qApp->getOverlays().renderHUD(renderArgs);
|
||||
}
|
||||
|
||||
void ApplicationOverlay::renderRearViewToFbo(RenderArgs* renderArgs) {
|
||||
}
|
||||
|
||||
void ApplicationOverlay::renderRearView(RenderArgs* renderArgs) {
|
||||
if (!qApp->isHMDMode() && Menu::getInstance()->isOptionChecked(MenuOption::MiniMirror) &&
|
||||
!Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) {
|
||||
gpu::Batch& batch = *renderArgs->_batch;
|
||||
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
|
||||
auto framebuffer = DependencyManager::get<FramebufferCache>();
|
||||
auto selfieTexture = framebuffer->getSelfieFramebuffer()->getRenderBuffer(0);
|
||||
|
||||
int width = renderArgs->_viewport.z;
|
||||
int height = renderArgs->_viewport.w;
|
||||
mat4 legacyProjection = glm::ortho<float>(0, width, height, 0, ORTHO_NEAR_CLIP, ORTHO_FAR_CLIP);
|
||||
batch.setProjectionTransform(legacyProjection);
|
||||
batch.setModelTransform(Transform());
|
||||
batch.resetViewTransform();
|
||||
|
||||
float screenRatio = ((float)qApp->getDevicePixelRatio());
|
||||
float renderRatio = ((float)qApp->getRenderResolutionScale());
|
||||
|
||||
auto viewport = qApp->getMirrorViewRect();
|
||||
glm::vec2 bottomLeft(viewport.left(), viewport.top() + viewport.height());
|
||||
glm::vec2 topRight(viewport.left() + viewport.width(), viewport.top());
|
||||
bottomLeft *= screenRatio;
|
||||
topRight *= screenRatio;
|
||||
glm::vec2 texCoordMinCorner(0.0f, 0.0f);
|
||||
glm::vec2 texCoordMaxCorner(viewport.width() * renderRatio / float(selfieTexture->getWidth()), viewport.height() * renderRatio / float(selfieTexture->getHeight()));
|
||||
|
||||
batch.setResourceTexture(0, selfieTexture);
|
||||
float alpha = DependencyManager::get<OffscreenUi>()->getDesktop()->property("unpinnedAlpha").toFloat();
|
||||
geometryCache->renderQuad(batch, bottomLeft, topRight, texCoordMinCorner, texCoordMaxCorner, glm::vec4(1.0f, 1.0f, 1.0f, alpha), _rearViewGeometryId);
|
||||
|
||||
batch.setResourceTexture(0, renderArgs->_whiteTexture);
|
||||
}
|
||||
}
|
||||
|
||||
void ApplicationOverlay::renderStatsAndLogs(RenderArgs* renderArgs) {
|
||||
|
||||
// Display stats and log text onscreen
|
||||
|
|
|
@ -31,8 +31,6 @@ public:
|
|||
private:
|
||||
void renderStatsAndLogs(RenderArgs* renderArgs);
|
||||
void renderDomainConnectionStatusBorder(RenderArgs* renderArgs);
|
||||
void renderRearViewToFbo(RenderArgs* renderArgs);
|
||||
void renderRearView(RenderArgs* renderArgs);
|
||||
void renderQmlUi(RenderArgs* renderArgs);
|
||||
void renderAudioScope(RenderArgs* renderArgs);
|
||||
void renderOverlays(RenderArgs* renderArgs);
|
||||
|
@ -51,7 +49,6 @@ private:
|
|||
gpu::TexturePointer _overlayColorTexture;
|
||||
gpu::FramebufferPointer _overlayFramebuffer;
|
||||
int _qmlGeometryId { 0 };
|
||||
int _rearViewGeometryId { 0 };
|
||||
};
|
||||
|
||||
#endif // hifi_ApplicationOverlay_h
|
||||
|
|
|
@ -20,10 +20,6 @@ HIFI_QML_DEF(AvatarInputs)
|
|||
|
||||
|
||||
static AvatarInputs* INSTANCE{ nullptr };
|
||||
static const char SETTINGS_GROUP_NAME[] = "Rear View Tools";
|
||||
static const char ZOOM_LEVEL_SETTINGS[] = "ZoomLevel";
|
||||
|
||||
static Setting::Handle<int> rearViewZoomLevel(QStringList() << SETTINGS_GROUP_NAME << ZOOM_LEVEL_SETTINGS, 0);
|
||||
|
||||
AvatarInputs* AvatarInputs::getInstance() {
|
||||
if (!INSTANCE) {
|
||||
|
@ -36,8 +32,6 @@ AvatarInputs* AvatarInputs::getInstance() {
|
|||
|
||||
AvatarInputs::AvatarInputs(QQuickItem* parent) : QQuickItem(parent) {
|
||||
INSTANCE = this;
|
||||
int zoomSetting = rearViewZoomLevel.get();
|
||||
_mirrorZoomed = zoomSetting == 0;
|
||||
}
|
||||
|
||||
#define AI_UPDATE(name, src) \
|
||||
|
@ -62,8 +56,6 @@ void AvatarInputs::update() {
|
|||
if (!Menu::getInstance()) {
|
||||
return;
|
||||
}
|
||||
AI_UPDATE(mirrorVisible, Menu::getInstance()->isOptionChecked(MenuOption::MiniMirror) && !qApp->isHMDMode()
|
||||
&& !Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror));
|
||||
AI_UPDATE(cameraEnabled, !Menu::getInstance()->isOptionChecked(MenuOption::NoFaceTracking));
|
||||
AI_UPDATE(cameraMuted, Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking));
|
||||
AI_UPDATE(isHMD, qApp->isHMDMode());
|
||||
|
@ -122,15 +114,3 @@ void AvatarInputs::toggleAudioMute() {
|
|||
void AvatarInputs::resetSensors() {
|
||||
qApp->resetSensors();
|
||||
}
|
||||
|
||||
void AvatarInputs::toggleZoom() {
|
||||
_mirrorZoomed = !_mirrorZoomed;
|
||||
rearViewZoomLevel.set(_mirrorZoomed ? 0 : 1);
|
||||
emit mirrorZoomedChanged();
|
||||
}
|
||||
|
||||
void AvatarInputs::closeMirror() {
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::MiniMirror)) {
|
||||
Menu::getInstance()->triggerOption(MenuOption::MiniMirror);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,8 +28,6 @@ class AvatarInputs : public QQuickItem {
|
|||
AI_PROPERTY(bool, audioMuted, false)
|
||||
AI_PROPERTY(bool, audioClipping, false)
|
||||
AI_PROPERTY(float, audioLevel, 0)
|
||||
AI_PROPERTY(bool, mirrorVisible, false)
|
||||
AI_PROPERTY(bool, mirrorZoomed, true)
|
||||
AI_PROPERTY(bool, isHMD, false)
|
||||
AI_PROPERTY(bool, showAudioTools, true)
|
||||
|
||||
|
@ -44,8 +42,6 @@ signals:
|
|||
void audioMutedChanged();
|
||||
void audioClippingChanged();
|
||||
void audioLevelChanged();
|
||||
void mirrorVisibleChanged();
|
||||
void mirrorZoomedChanged();
|
||||
void isHMDChanged();
|
||||
void showAudioToolsChanged();
|
||||
|
||||
|
@ -53,8 +49,6 @@ protected:
|
|||
Q_INVOKABLE void resetSensors();
|
||||
Q_INVOKABLE void toggleCameraMute();
|
||||
Q_INVOKABLE void toggleAudioMute();
|
||||
Q_INVOKABLE void toggleZoom();
|
||||
Q_INVOKABLE void closeMirror();
|
||||
|
||||
private:
|
||||
float _trailingAudioLoudness{ 0 };
|
||||
|
|
|
@ -28,17 +28,23 @@ const int SEARCH_BUTTON_LEFT = 25;
|
|||
const int SEARCH_BUTTON_WIDTH = 20;
|
||||
const int SEARCH_TOGGLE_BUTTON_WIDTH = 50;
|
||||
const int SEARCH_TEXT_WIDTH = 240;
|
||||
const int TIME_STAMP_LENGTH = 16;
|
||||
const int FONT_WEIGHT = 75;
|
||||
const QColor HIGHLIGHT_COLOR = QColor("#3366CC");
|
||||
const QColor BOLD_COLOR = QColor("#445c8c");
|
||||
const QString BOLD_PATTERN = "\\[\\d*\\/.*:\\d*:\\d*\\]";
|
||||
|
||||
class KeywordHighlighter : public QSyntaxHighlighter {
|
||||
class Highlighter : public QSyntaxHighlighter {
|
||||
public:
|
||||
KeywordHighlighter(QTextDocument* parent = nullptr);
|
||||
Highlighter(QTextDocument* parent = nullptr);
|
||||
void setBold(int indexToBold);
|
||||
QString keyword;
|
||||
|
||||
protected:
|
||||
void highlightBlock(const QString& text) override;
|
||||
|
||||
private:
|
||||
QTextCharFormat boldFormat;
|
||||
QTextCharFormat keywordFormat;
|
||||
|
||||
};
|
||||
|
@ -89,7 +95,7 @@ void BaseLogDialog::initControls() {
|
|||
_leftPad += SEARCH_TOGGLE_BUTTON_WIDTH + BUTTON_MARGIN;
|
||||
_searchPrevButton->show();
|
||||
connect(_searchPrevButton, SIGNAL(clicked()), SLOT(toggleSearchPrev()));
|
||||
|
||||
|
||||
_searchNextButton = new QPushButton(this);
|
||||
_searchNextButton->setObjectName("searchNextButton");
|
||||
_searchNextButton->setGeometry(_leftPad, ELEMENT_MARGIN, SEARCH_TOGGLE_BUTTON_WIDTH, ELEMENT_HEIGHT);
|
||||
|
@ -101,9 +107,8 @@ void BaseLogDialog::initControls() {
|
|||
_logTextBox = new QPlainTextEdit(this);
|
||||
_logTextBox->setReadOnly(true);
|
||||
_logTextBox->show();
|
||||
_highlighter = new KeywordHighlighter(_logTextBox->document());
|
||||
_highlighter = new Highlighter(_logTextBox->document());
|
||||
connect(_logTextBox, SIGNAL(selectionChanged()), SLOT(updateSelection()));
|
||||
|
||||
}
|
||||
|
||||
void BaseLogDialog::showEvent(QShowEvent* event) {
|
||||
|
@ -116,7 +121,9 @@ void BaseLogDialog::resizeEvent(QResizeEvent* event) {
|
|||
|
||||
void BaseLogDialog::appendLogLine(QString logLine) {
|
||||
if (logLine.contains(_searchTerm, Qt::CaseInsensitive)) {
|
||||
int indexToBold = _logTextBox->document()->characterCount();
|
||||
_logTextBox->appendPlainText(logLine.trimmed());
|
||||
_highlighter->setBold(indexToBold);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -128,7 +135,7 @@ void BaseLogDialog::handleSearchTextChanged(QString searchText) {
|
|||
if (searchText.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
QTextCursor cursor = _logTextBox->textCursor();
|
||||
if (cursor.hasSelection()) {
|
||||
QString selectedTerm = cursor.selectedText();
|
||||
|
@ -136,16 +143,16 @@ void BaseLogDialog::handleSearchTextChanged(QString searchText) {
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
cursor.setPosition(0, QTextCursor::MoveAnchor);
|
||||
_logTextBox->setTextCursor(cursor);
|
||||
bool foundTerm = _logTextBox->find(searchText);
|
||||
|
||||
|
||||
if (!foundTerm) {
|
||||
cursor.movePosition(QTextCursor::End, QTextCursor::MoveAnchor);
|
||||
_logTextBox->setTextCursor(cursor);
|
||||
}
|
||||
|
||||
|
||||
_searchTerm = searchText;
|
||||
_highlighter->keyword = searchText;
|
||||
_highlighter->rehighlight();
|
||||
|
@ -175,6 +182,7 @@ void BaseLogDialog::showLogData() {
|
|||
_logTextBox->clear();
|
||||
_logTextBox->appendPlainText(getCurrentLog());
|
||||
_logTextBox->ensureCursorVisible();
|
||||
_highlighter->rehighlight();
|
||||
}
|
||||
|
||||
void BaseLogDialog::updateSelection() {
|
||||
|
@ -187,16 +195,28 @@ void BaseLogDialog::updateSelection() {
|
|||
}
|
||||
}
|
||||
|
||||
KeywordHighlighter::KeywordHighlighter(QTextDocument* parent) : QSyntaxHighlighter(parent) {
|
||||
Highlighter::Highlighter(QTextDocument* parent) : QSyntaxHighlighter(parent) {
|
||||
boldFormat.setFontWeight(FONT_WEIGHT);
|
||||
boldFormat.setForeground(BOLD_COLOR);
|
||||
keywordFormat.setForeground(HIGHLIGHT_COLOR);
|
||||
}
|
||||
|
||||
void KeywordHighlighter::highlightBlock(const QString& text) {
|
||||
void Highlighter::highlightBlock(const QString& text) {
|
||||
QRegExp expression(BOLD_PATTERN);
|
||||
|
||||
int index = text.indexOf(expression, 0);
|
||||
|
||||
while (index >= 0) {
|
||||
int length = expression.matchedLength();
|
||||
setFormat(index, length, boldFormat);
|
||||
index = text.indexOf(expression, index + length);
|
||||
}
|
||||
|
||||
if (keyword.isNull() || keyword.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int index = text.indexOf(keyword, 0, Qt::CaseInsensitive);
|
||||
index = text.indexOf(keyword, 0, Qt::CaseInsensitive);
|
||||
int length = keyword.length();
|
||||
|
||||
while (index >= 0) {
|
||||
|
@ -204,3 +224,7 @@ void KeywordHighlighter::highlightBlock(const QString& text) {
|
|||
index = text.indexOf(keyword, index + length, Qt::CaseInsensitive);
|
||||
}
|
||||
}
|
||||
|
||||
void Highlighter::setBold(int indexToBold) {
|
||||
setFormat(indexToBold, TIME_STAMP_LENGTH, boldFormat);
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ const int BUTTON_MARGIN = 8;
|
|||
class QPushButton;
|
||||
class QLineEdit;
|
||||
class QPlainTextEdit;
|
||||
class KeywordHighlighter;
|
||||
class Highlighter;
|
||||
|
||||
class BaseLogDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
@ -56,7 +56,7 @@ private:
|
|||
QPushButton* _searchPrevButton { nullptr };
|
||||
QPushButton* _searchNextButton { nullptr };
|
||||
QString _searchTerm;
|
||||
KeywordHighlighter* _highlighter { nullptr };
|
||||
Highlighter* _highlighter { nullptr };
|
||||
|
||||
void initControls();
|
||||
void showLogData();
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
//
|
||||
// CachesSizeDialog.cpp
|
||||
//
|
||||
//
|
||||
// Created by Clement on 1/12/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QFormLayout>
|
||||
#include <QPushButton>
|
||||
|
||||
#include <AnimationCache.h>
|
||||
#include <DependencyManager.h>
|
||||
#include <GeometryCache.h>
|
||||
#include <SoundCache.h>
|
||||
#include <TextureCache.h>
|
||||
|
||||
#include "CachesSizeDialog.h"
|
||||
|
||||
|
||||
QDoubleSpinBox* createDoubleSpinBox(QWidget* parent) {
|
||||
QDoubleSpinBox* box = new QDoubleSpinBox(parent);
|
||||
box->setDecimals(0);
|
||||
box->setRange(MIN_UNUSED_MAX_SIZE / BYTES_PER_MEGABYTES, MAX_UNUSED_MAX_SIZE / BYTES_PER_MEGABYTES);
|
||||
|
||||
return box;
|
||||
}
|
||||
|
||||
CachesSizeDialog::CachesSizeDialog(QWidget* parent) :
|
||||
QDialog(parent, Qt::Window | Qt::WindowCloseButtonHint)
|
||||
{
|
||||
setWindowTitle("Caches Size");
|
||||
|
||||
// Create layouter
|
||||
QFormLayout* form = new QFormLayout(this);
|
||||
setLayout(form);
|
||||
|
||||
form->addRow("Animations cache size (MB):", _animations = createDoubleSpinBox(this));
|
||||
form->addRow("Geometries cache size (MB):", _geometries = createDoubleSpinBox(this));
|
||||
form->addRow("Sounds cache size (MB):", _sounds = createDoubleSpinBox(this));
|
||||
form->addRow("Textures cache size (MB):", _textures = createDoubleSpinBox(this));
|
||||
|
||||
resetClicked(true);
|
||||
|
||||
// Add a button to reset
|
||||
QPushButton* confirmButton = new QPushButton("Confirm", this);
|
||||
QPushButton* resetButton = new QPushButton("Reset", this);
|
||||
form->addRow(confirmButton, resetButton);
|
||||
connect(confirmButton, SIGNAL(clicked(bool)), this, SLOT(confirmClicked(bool)));
|
||||
connect(resetButton, SIGNAL(clicked(bool)), this, SLOT(resetClicked(bool)));
|
||||
}
|
||||
|
||||
void CachesSizeDialog::confirmClicked(bool checked) {
|
||||
DependencyManager::get<AnimationCache>()->setUnusedResourceCacheSize(_animations->value() * BYTES_PER_MEGABYTES);
|
||||
DependencyManager::get<ModelCache>()->setUnusedResourceCacheSize(_geometries->value() * BYTES_PER_MEGABYTES);
|
||||
DependencyManager::get<SoundCache>()->setUnusedResourceCacheSize(_sounds->value() * BYTES_PER_MEGABYTES);
|
||||
// Disabling the texture cache because it's a liability in cases where we're overcommiting GPU memory
|
||||
#if 0
|
||||
DependencyManager::get<TextureCache>()->setUnusedResourceCacheSize(_textures->value() * BYTES_PER_MEGABYTES);
|
||||
#endif
|
||||
|
||||
QDialog::close();
|
||||
}
|
||||
|
||||
void CachesSizeDialog::resetClicked(bool checked) {
|
||||
_animations->setValue(DependencyManager::get<AnimationCache>()->getUnusedResourceCacheSize() / BYTES_PER_MEGABYTES);
|
||||
_geometries->setValue(DependencyManager::get<ModelCache>()->getUnusedResourceCacheSize() / BYTES_PER_MEGABYTES);
|
||||
_sounds->setValue(DependencyManager::get<SoundCache>()->getUnusedResourceCacheSize() / BYTES_PER_MEGABYTES);
|
||||
_textures->setValue(DependencyManager::get<TextureCache>()->getUnusedResourceCacheSize() / BYTES_PER_MEGABYTES);
|
||||
}
|
||||
|
||||
void CachesSizeDialog::reject() {
|
||||
// Just regularly close upon ESC
|
||||
QDialog::close();
|
||||
}
|
||||
|
||||
void CachesSizeDialog::closeEvent(QCloseEvent* event) {
|
||||
QDialog::closeEvent(event);
|
||||
emit closed();
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
//
|
||||
// CachesSizeDialog.h
|
||||
//
|
||||
//
|
||||
// Created by Clement on 1/12/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_CachesSizeDialog_h
|
||||
#define hifi_CachesSizeDialog_h
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
class QDoubleSpinBox;
|
||||
|
||||
class CachesSizeDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
// Sets up the UI
|
||||
CachesSizeDialog(QWidget* parent);
|
||||
|
||||
signals:
|
||||
void closed();
|
||||
|
||||
public slots:
|
||||
void reject() override;
|
||||
void confirmClicked(bool checked);
|
||||
void resetClicked(bool checked);
|
||||
|
||||
protected:
|
||||
// Emits a 'closed' signal when this dialog is closed.
|
||||
void closeEvent(QCloseEvent* event) override;
|
||||
|
||||
private:
|
||||
QDoubleSpinBox* _animations = nullptr;
|
||||
QDoubleSpinBox* _geometries = nullptr;
|
||||
QDoubleSpinBox* _scripts = nullptr;
|
||||
QDoubleSpinBox* _sounds = nullptr;
|
||||
QDoubleSpinBox* _textures = nullptr;
|
||||
};
|
||||
|
||||
#endif // hifi_CachesSizeDialog_h
|
|
@ -19,9 +19,7 @@
|
|||
#include <PathUtils.h>
|
||||
|
||||
#include "AddressBarDialog.h"
|
||||
#include "CachesSizeDialog.h"
|
||||
#include "ConnectionFailureDialog.h"
|
||||
#include "DiskCacheEditor.h"
|
||||
#include "DomainConnectionDialog.h"
|
||||
#include "HMDToolsDialog.h"
|
||||
#include "LodToolsDialog.h"
|
||||
|
@ -67,11 +65,6 @@ void DialogsManager::setDomainConnectionFailureVisibility(bool visible) {
|
|||
}
|
||||
}
|
||||
|
||||
void DialogsManager::toggleDiskCacheEditor() {
|
||||
maybeCreateDialog(_diskCacheEditor);
|
||||
_diskCacheEditor->toggle();
|
||||
}
|
||||
|
||||
void DialogsManager::toggleLoginDialog() {
|
||||
LoginDialog::toggleAction();
|
||||
}
|
||||
|
@ -97,16 +90,6 @@ void DialogsManager::octreeStatsDetails() {
|
|||
_octreeStatsDialog->raise();
|
||||
}
|
||||
|
||||
void DialogsManager::cachesSizeDialog() {
|
||||
if (!_cachesSizeDialog) {
|
||||
maybeCreateDialog(_cachesSizeDialog);
|
||||
|
||||
connect(_cachesSizeDialog, SIGNAL(closed()), _cachesSizeDialog, SLOT(deleteLater()));
|
||||
_cachesSizeDialog->show();
|
||||
}
|
||||
_cachesSizeDialog->raise();
|
||||
}
|
||||
|
||||
void DialogsManager::lodTools() {
|
||||
if (!_lodToolsDialog) {
|
||||
maybeCreateDialog(_lodToolsDialog);
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
class AnimationsDialog;
|
||||
class AttachmentsDialog;
|
||||
class CachesSizeDialog;
|
||||
class DiskCacheEditor;
|
||||
class LodToolsDialog;
|
||||
class OctreeStatsDialog;
|
||||
class ScriptEditorWindow;
|
||||
|
@ -46,11 +45,9 @@ public slots:
|
|||
void showAddressBar();
|
||||
void showFeed();
|
||||
void setDomainConnectionFailureVisibility(bool visible);
|
||||
void toggleDiskCacheEditor();
|
||||
void toggleLoginDialog();
|
||||
void showLoginDialog();
|
||||
void octreeStatsDetails();
|
||||
void cachesSizeDialog();
|
||||
void lodTools();
|
||||
void hmdTools(bool showTools);
|
||||
void showScriptEditor();
|
||||
|
@ -77,7 +74,6 @@ private:
|
|||
QPointer<AnimationsDialog> _animationsDialog;
|
||||
QPointer<AttachmentsDialog> _attachmentsDialog;
|
||||
QPointer<CachesSizeDialog> _cachesSizeDialog;
|
||||
QPointer<DiskCacheEditor> _diskCacheEditor;
|
||||
QPointer<QMessageBox> _ircInfoBox;
|
||||
QPointer<HMDToolsDialog> _hmdToolsDialog;
|
||||
QPointer<LodToolsDialog> _lodToolsDialog;
|
||||
|
|
|
@ -1,146 +0,0 @@
|
|||
//
|
||||
// DiskCacheEditor.cpp
|
||||
//
|
||||
//
|
||||
// Created by Clement on 3/4/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "DiskCacheEditor.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDialog>
|
||||
#include <QGridLayout>
|
||||
#include <QPushButton>
|
||||
#include <QLabel>
|
||||
#include <QTimer>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include <AssetClient.h>
|
||||
|
||||
#include "OffscreenUi.h"
|
||||
|
||||
DiskCacheEditor::DiskCacheEditor(QWidget* parent) : QObject(parent) {
|
||||
}
|
||||
|
||||
QWindow* DiskCacheEditor::windowHandle() {
|
||||
return (_dialog) ? _dialog->windowHandle() : nullptr;
|
||||
}
|
||||
|
||||
void DiskCacheEditor::toggle() {
|
||||
if (!_dialog) {
|
||||
makeDialog();
|
||||
}
|
||||
|
||||
if (!_dialog->isActiveWindow()) {
|
||||
_dialog->show();
|
||||
_dialog->raise();
|
||||
_dialog->activateWindow();
|
||||
} else {
|
||||
_dialog->close();
|
||||
}
|
||||
}
|
||||
|
||||
void DiskCacheEditor::makeDialog() {
|
||||
_dialog = new QDialog(static_cast<QWidget*>(parent()));
|
||||
Q_CHECK_PTR(_dialog);
|
||||
_dialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||
_dialog->setWindowTitle("Disk Cache Editor");
|
||||
|
||||
QGridLayout* layout = new QGridLayout(_dialog);
|
||||
Q_CHECK_PTR(layout);
|
||||
_dialog->setLayout(layout);
|
||||
|
||||
|
||||
QLabel* path = new QLabel("Path : ", _dialog);
|
||||
Q_CHECK_PTR(path);
|
||||
path->setAlignment(Qt::AlignRight);
|
||||
layout->addWidget(path, 0, 0);
|
||||
|
||||
QLabel* size = new QLabel("Current Size : ", _dialog);
|
||||
Q_CHECK_PTR(size);
|
||||
size->setAlignment(Qt::AlignRight);
|
||||
layout->addWidget(size, 1, 0);
|
||||
|
||||
QLabel* maxSize = new QLabel("Max Size : ", _dialog);
|
||||
Q_CHECK_PTR(maxSize);
|
||||
maxSize->setAlignment(Qt::AlignRight);
|
||||
layout->addWidget(maxSize, 2, 0);
|
||||
|
||||
|
||||
_path = new QLabel(_dialog);
|
||||
Q_CHECK_PTR(_path);
|
||||
_path->setAlignment(Qt::AlignLeft);
|
||||
layout->addWidget(_path, 0, 1, 1, 3);
|
||||
|
||||
_size = new QLabel(_dialog);
|
||||
Q_CHECK_PTR(_size);
|
||||
_size->setAlignment(Qt::AlignLeft);
|
||||
layout->addWidget(_size, 1, 1, 1, 3);
|
||||
|
||||
_maxSize = new QLabel(_dialog);
|
||||
Q_CHECK_PTR(_maxSize);
|
||||
_maxSize->setAlignment(Qt::AlignLeft);
|
||||
layout->addWidget(_maxSize, 2, 1, 1, 3);
|
||||
|
||||
refresh();
|
||||
|
||||
|
||||
static const int REFRESH_INTERVAL = 100; // msec
|
||||
_refreshTimer = new QTimer(_dialog);
|
||||
_refreshTimer->setInterval(REFRESH_INTERVAL); // Qt::CoarseTimer acceptable, no need for real time accuracy
|
||||
_refreshTimer->setSingleShot(false);
|
||||
QObject::connect(_refreshTimer.data(), &QTimer::timeout, this, &DiskCacheEditor::refresh);
|
||||
_refreshTimer->start();
|
||||
|
||||
QPushButton* clearCacheButton = new QPushButton(_dialog);
|
||||
Q_CHECK_PTR(clearCacheButton);
|
||||
clearCacheButton->setText("Clear");
|
||||
clearCacheButton->setToolTip("Erases the entire content of the disk cache.");
|
||||
connect(clearCacheButton, SIGNAL(clicked()), SLOT(clear()));
|
||||
layout->addWidget(clearCacheButton, 3, 3);
|
||||
}
|
||||
|
||||
void DiskCacheEditor::refresh() {
|
||||
DependencyManager::get<AssetClient>()->cacheInfoRequest(this, "cacheInfoCallback");
|
||||
}
|
||||
|
||||
void DiskCacheEditor::cacheInfoCallback(QString cacheDirectory, qint64 cacheSize, qint64 maximumCacheSize) {
|
||||
static const auto stringify = [](qint64 number) {
|
||||
static const QStringList UNITS = QStringList() << "B" << "KB" << "MB" << "GB";
|
||||
static const qint64 CHUNK = 1024;
|
||||
QString unit;
|
||||
int i = 0;
|
||||
for (i = 0; i < 4; ++i) {
|
||||
if (number / CHUNK > 0) {
|
||||
number /= CHUNK;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return QString("%0 %1").arg(number).arg(UNITS[i]);
|
||||
};
|
||||
|
||||
if (_path) {
|
||||
_path->setText(cacheDirectory);
|
||||
}
|
||||
if (_size) {
|
||||
_size->setText(stringify(cacheSize));
|
||||
}
|
||||
if (_maxSize) {
|
||||
_maxSize->setText(stringify(maximumCacheSize));
|
||||
}
|
||||
}
|
||||
|
||||
void DiskCacheEditor::clear() {
|
||||
auto buttonClicked = OffscreenUi::question(_dialog, "Clearing disk cache",
|
||||
"You are about to erase all the content of the disk cache, "
|
||||
"are you sure you want to do that?",
|
||||
QMessageBox::Ok | QMessageBox::Cancel);
|
||||
if (buttonClicked == QMessageBox::Ok) {
|
||||
DependencyManager::get<AssetClient>()->clearCache();
|
||||
}
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
//
|
||||
// DiskCacheEditor.h
|
||||
//
|
||||
//
|
||||
// Created by Clement on 3/4/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_DiskCacheEditor_h
|
||||
#define hifi_DiskCacheEditor_h
|
||||
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
|
||||
class QDialog;
|
||||
class QLabel;
|
||||
class QWindow;
|
||||
class QTimer;
|
||||
|
||||
class DiskCacheEditor : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
DiskCacheEditor(QWidget* parent = nullptr);
|
||||
|
||||
QWindow* windowHandle();
|
||||
|
||||
public slots:
|
||||
void toggle();
|
||||
|
||||
private slots:
|
||||
void refresh();
|
||||
void cacheInfoCallback(QString cacheDirectory, qint64 cacheSize, qint64 maximumCacheSize);
|
||||
void clear();
|
||||
|
||||
private:
|
||||
void makeDialog();
|
||||
|
||||
QPointer<QDialog> _dialog;
|
||||
QPointer<QLabel> _path;
|
||||
QPointer<QLabel> _size;
|
||||
QPointer<QLabel> _maxSize;
|
||||
QPointer<QTimer> _refreshTimer;
|
||||
};
|
||||
|
||||
#endif // hifi_DiskCacheEditor_h
|
|
@ -160,7 +160,7 @@ AudioClient::AudioClient() :
|
|||
_loopbackAudioOutput(NULL),
|
||||
_loopbackOutputDevice(NULL),
|
||||
_inputRingBuffer(0),
|
||||
_localInjectorsStream(0),
|
||||
_localInjectorsStream(0, 1),
|
||||
_receivedAudioStream(RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES),
|
||||
_isStereoInput(false),
|
||||
_outputStarveDetectionStartTimeMsec(0),
|
||||
|
|
|
@ -45,13 +45,13 @@
|
|||
#include <AudioReverb.h>
|
||||
#include <AudioLimiter.h>
|
||||
#include <AudioConstants.h>
|
||||
#include <AudioNoiseGate.h>
|
||||
|
||||
#include <shared/RateCounter.h>
|
||||
|
||||
#include <plugins/CodecPlugin.h>
|
||||
|
||||
#include "AudioIOStats.h"
|
||||
#include "AudioNoiseGate.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma warning( push )
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// AudioNoiseGate.cpp
|
||||
// interface/src/audio
|
||||
// libraries/audio
|
||||
//
|
||||
// Created by Stephen Birarda on 2014-12-16.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
|
@ -9,29 +9,23 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "AudioNoiseGate.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <string.h>
|
||||
|
||||
#include <AudioConstants.h>
|
||||
|
||||
#include "AudioNoiseGate.h"
|
||||
#include "AudioConstants.h"
|
||||
|
||||
const float AudioNoiseGate::CLIPPING_THRESHOLD = 0.90f;
|
||||
|
||||
AudioNoiseGate::AudioNoiseGate() :
|
||||
_inputBlockCounter(0),
|
||||
_lastLoudness(0.0f),
|
||||
_quietestBlock(std::numeric_limits<float>::max()),
|
||||
_loudestBlock(0.0f),
|
||||
_didClipInLastBlock(false),
|
||||
_dcOffset(0.0f),
|
||||
_measuredFloor(0.0f),
|
||||
_sampleCounter(0),
|
||||
_isOpen(false),
|
||||
_blocksToClose(0)
|
||||
{
|
||||
|
||||
}
|
||||
_blocksToClose(0) {}
|
||||
|
||||
void AudioNoiseGate::removeDCOffset(int16_t* samples, int numSamples) {
|
||||
//
|
||||
|
@ -80,7 +74,7 @@ void AudioNoiseGate::gateSamples(int16_t* samples, int numSamples) {
|
|||
float loudness = 0;
|
||||
int thisSample = 0;
|
||||
int samplesOverNoiseGate = 0;
|
||||
|
||||
|
||||
const float NOISE_GATE_HEIGHT = 7.0f;
|
||||
const int NOISE_GATE_WIDTH = 5;
|
||||
const int NOISE_GATE_CLOSE_BLOCK_DELAY = 5;
|
||||
|
@ -88,36 +82,22 @@ void AudioNoiseGate::gateSamples(int16_t* samples, int numSamples) {
|
|||
|
||||
// Check clipping, and check if should open noise gate
|
||||
_didClipInLastBlock = false;
|
||||
|
||||
|
||||
for (int i = 0; i < numSamples; i++) {
|
||||
thisSample = std::abs(samples[i]);
|
||||
if (thisSample >= ((float) AudioConstants::MAX_SAMPLE_VALUE * CLIPPING_THRESHOLD)) {
|
||||
_didClipInLastBlock = true;
|
||||
}
|
||||
|
||||
|
||||
loudness += thisSample;
|
||||
// Noise Reduction: Count peaks above the average loudness
|
||||
if (thisSample > (_measuredFloor * NOISE_GATE_HEIGHT)) {
|
||||
samplesOverNoiseGate++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_lastLoudness = fabs(loudness / numSamples);
|
||||
|
||||
if (_quietestBlock > _lastLoudness) {
|
||||
_quietestBlock = _lastLoudness;
|
||||
}
|
||||
if (_loudestBlock < _lastLoudness) {
|
||||
_loudestBlock = _lastLoudness;
|
||||
}
|
||||
|
||||
const int FRAMES_FOR_NOISE_DETECTION = 400;
|
||||
if (_inputBlockCounter++ > FRAMES_FOR_NOISE_DETECTION) {
|
||||
_quietestBlock = std::numeric_limits<float>::max();
|
||||
_loudestBlock = 0.0f;
|
||||
_inputBlockCounter = 0;
|
||||
}
|
||||
|
||||
|
||||
// If Noise Gate is enabled, check and turn the gate on and off
|
||||
float averageOfAllSampleBlocks = 0.0f;
|
||||
_sampleBlocks[_sampleCounter++] = _lastLoudness;
|
||||
|
@ -130,7 +110,7 @@ void AudioNoiseGate::gateSamples(int16_t* samples, int numSamples) {
|
|||
averageOfAllSampleBlocks += _sampleBlocks[j];
|
||||
}
|
||||
thisAverage /= NOISE_GATE_BLOCKS_TO_AVERAGE;
|
||||
|
||||
|
||||
if (thisAverage < smallestSample) {
|
||||
smallestSample = thisAverage;
|
||||
}
|
||||
|
@ -138,7 +118,7 @@ void AudioNoiseGate::gateSamples(int16_t* samples, int numSamples) {
|
|||
averageOfAllSampleBlocks /= NUMBER_OF_NOISE_SAMPLE_BLOCKS;
|
||||
_measuredFloor = smallestSample;
|
||||
_sampleCounter = 0;
|
||||
|
||||
|
||||
}
|
||||
|
||||
_closedInLastBlock = false;
|
||||
|
@ -156,7 +136,7 @@ void AudioNoiseGate::gateSamples(int16_t* samples, int numSamples) {
|
|||
}
|
||||
if (!_isOpen) {
|
||||
// First block after being closed gets faded to silence, we fade across
|
||||
// the entire block on fading out. All subsequent blocks are muted by being slammed
|
||||
// the entire block on fading out. All subsequent blocks are muted by being slammed
|
||||
// to zeros
|
||||
if (_closedInLastBlock) {
|
||||
float fadeSlope = (1.0f / numSamples);
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// AudioNoiseGate.h
|
||||
// interface/src/audio
|
||||
// libraries/audio
|
||||
//
|
||||
// Created by Stephen Birarda on 2014-12-16.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
|
@ -19,24 +19,21 @@ const int NUMBER_OF_NOISE_SAMPLE_BLOCKS = 300;
|
|||
class AudioNoiseGate {
|
||||
public:
|
||||
AudioNoiseGate();
|
||||
|
||||
|
||||
void gateSamples(int16_t* samples, int numSamples);
|
||||
void removeDCOffset(int16_t* samples, int numSamples);
|
||||
|
||||
|
||||
bool clippedInLastBlock() const { return _didClipInLastBlock; }
|
||||
bool closedInLastBlock() const { return _closedInLastBlock; }
|
||||
bool openedInLastBlock() const { return _openedInLastBlock; }
|
||||
bool isOpen() const { return _isOpen; }
|
||||
float getMeasuredFloor() const { return _measuredFloor; }
|
||||
float getLastLoudness() const { return _lastLoudness; }
|
||||
|
||||
|
||||
static const float CLIPPING_THRESHOLD;
|
||||
|
||||
|
||||
private:
|
||||
int _inputBlockCounter;
|
||||
float _lastLoudness;
|
||||
float _quietestBlock;
|
||||
float _loudestBlock;
|
||||
bool _didClipInLastBlock;
|
||||
float _dcOffset;
|
||||
float _measuredFloor;
|
||||
|
@ -48,4 +45,4 @@ private:
|
|||
int _blocksToClose;
|
||||
};
|
||||
|
||||
#endif // hifi_AudioNoiseGate_h
|
||||
#endif // hifi_AudioNoiseGate_h
|
|
@ -146,6 +146,7 @@ void EntityTreeRenderer::clear() {
|
|||
|
||||
void EntityTreeRenderer::reloadEntityScripts() {
|
||||
_entitiesScriptEngine->unloadAllEntityScripts();
|
||||
_entitiesScriptEngine->resetModuleCache();
|
||||
foreach(auto entity, _entitiesInScene) {
|
||||
if (!entity->getScript().isEmpty()) {
|
||||
_entitiesScriptEngine->loadEntityScript(entity->getEntityItemID(), entity->getScript(), true);
|
||||
|
@ -940,7 +941,7 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) {
|
|||
|
||||
void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) {
|
||||
if (_tree && !_shuttingDown && _entitiesScriptEngine) {
|
||||
_entitiesScriptEngine->unloadEntityScript(entityID);
|
||||
_entitiesScriptEngine->unloadEntityScript(entityID, true);
|
||||
}
|
||||
|
||||
forceRecheckEntities(); // reset our state to force checking our inside/outsideness of entities
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <QByteArray>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
#include <glm/gtx/transform.hpp>
|
||||
#include "ModelScriptingInterface.h"
|
||||
|
||||
#if defined(__GNUC__) && !defined(__clang__)
|
||||
#pragma GCC diagnostic push
|
||||
|
@ -53,6 +54,8 @@
|
|||
#include "PhysicalEntitySimulation.h"
|
||||
|
||||
gpu::PipelinePointer RenderablePolyVoxEntityItem::_pipeline = nullptr;
|
||||
gpu::PipelinePointer RenderablePolyVoxEntityItem::_wireframePipeline = nullptr;
|
||||
|
||||
const float MARCHING_CUBE_COLLISION_HULL_OFFSET = 0.5;
|
||||
|
||||
|
||||
|
@ -73,7 +76,7 @@ const float MARCHING_CUBE_COLLISION_HULL_OFFSET = 0.5;
|
|||
_meshDirty
|
||||
|
||||
In RenderablePolyVoxEntityItem::render, these flags are checked and changes are propagated along the chain.
|
||||
decompressVolumeData() is called to decompress _voxelData into _volData. getMesh() is called to invoke the
|
||||
decompressVolumeData() is called to decompress _voxelData into _volData. recomputeMesh() is called to invoke the
|
||||
polyVox surface extractor to create _mesh (as well as set Simulation _dirtyFlags). Because Simulation::DIRTY_SHAPE
|
||||
is set, isReadyToComputeShape() gets called and _shape is created either from _volData or _shape, depending on
|
||||
the surface style.
|
||||
|
@ -81,7 +84,7 @@ const float MARCHING_CUBE_COLLISION_HULL_OFFSET = 0.5;
|
|||
When a script changes _volData, compressVolumeDataAndSendEditPacket is called to update _voxelData and to
|
||||
send a packet to the entity-server.
|
||||
|
||||
decompressVolumeData, getMesh, computeShapeInfoWorker, and compressVolumeDataAndSendEditPacket are too expensive
|
||||
decompressVolumeData, recomputeMesh, computeShapeInfoWorker, and compressVolumeDataAndSendEditPacket are too expensive
|
||||
to run on a thread that has other things to do. These use QtConcurrent::run to spawn a thread. As each thread
|
||||
finishes, it adjusts the dirty flags so that the next call to render() will kick off the next step.
|
||||
|
||||
|
@ -682,7 +685,7 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) {
|
|||
if (voxelDataDirty) {
|
||||
decompressVolumeData();
|
||||
} else if (volDataDirty) {
|
||||
getMesh();
|
||||
recomputeMesh();
|
||||
}
|
||||
|
||||
model::MeshPointer mesh;
|
||||
|
@ -696,7 +699,7 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) {
|
|||
!mesh->getIndexBuffer()._buffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!_pipeline) {
|
||||
gpu::ShaderPointer vertexShader = gpu::Shader::createVertex(std::string(polyvox_vert));
|
||||
gpu::ShaderPointer pixelShader = gpu::Shader::createPixel(std::string(polyvox_frag));
|
||||
|
@ -715,6 +718,13 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) {
|
|||
state->setDepthTest(true, true, gpu::LESS_EQUAL);
|
||||
|
||||
_pipeline = gpu::Pipeline::create(program, state);
|
||||
|
||||
auto wireframeState = std::make_shared<gpu::State>();
|
||||
wireframeState->setCullMode(gpu::State::CULL_BACK);
|
||||
wireframeState->setDepthTest(true, true, gpu::LESS_EQUAL);
|
||||
wireframeState->setFillMode(gpu::State::FILL_LINE);
|
||||
|
||||
_wireframePipeline = gpu::Pipeline::create(program, wireframeState);
|
||||
}
|
||||
|
||||
if (!_vertexFormat) {
|
||||
|
@ -725,7 +735,11 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) {
|
|||
}
|
||||
|
||||
gpu::Batch& batch = *args->_batch;
|
||||
batch.setPipeline(_pipeline);
|
||||
|
||||
// Pick correct Pipeline
|
||||
bool wireframe = (render::ShapeKey(args->_globalShapeKey).isWireframe());
|
||||
auto pipeline = (wireframe ? _wireframePipeline : _pipeline);
|
||||
batch.setPipeline(pipeline);
|
||||
|
||||
Transform transform(voxelToWorldMatrix());
|
||||
batch.setModelTransform(transform);
|
||||
|
@ -762,7 +776,7 @@ void RenderablePolyVoxEntityItem::render(RenderArgs* args) {
|
|||
batch.setResourceTexture(2, DependencyManager::get<TextureCache>()->getWhiteTexture());
|
||||
}
|
||||
|
||||
int voxelVolumeSizeLocation = _pipeline->getProgram()->getUniforms().findLocation("voxelVolumeSize");
|
||||
int voxelVolumeSizeLocation = pipeline->getProgram()->getUniforms().findLocation("voxelVolumeSize");
|
||||
batch._glUniform3f(voxelVolumeSizeLocation, voxelVolumeSize.x, voxelVolumeSize.y, voxelVolumeSize.z);
|
||||
|
||||
batch.drawIndexed(gpu::TRIANGLES, (gpu::uint32)mesh->getNumIndices(), 0);
|
||||
|
@ -1199,7 +1213,7 @@ void RenderablePolyVoxEntityItem::copyUpperEdgesFromNeighbors() {
|
|||
}
|
||||
}
|
||||
|
||||
void RenderablePolyVoxEntityItem::getMesh() {
|
||||
void RenderablePolyVoxEntityItem::recomputeMesh() {
|
||||
// use _volData to make a renderable mesh
|
||||
PolyVoxSurfaceStyle voxelSurfaceStyle;
|
||||
withReadLock([&] {
|
||||
|
@ -1269,12 +1283,20 @@ void RenderablePolyVoxEntityItem::getMesh() {
|
|||
vertexBufferPtr->getSize() ,
|
||||
sizeof(PolyVox::PositionMaterialNormal),
|
||||
gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RAW)));
|
||||
|
||||
std::vector<model::Mesh::Part> parts;
|
||||
parts.emplace_back(model::Mesh::Part((model::Index)0, // startIndex
|
||||
(model::Index)vecIndices.size(), // numIndices
|
||||
(model::Index)0, // baseVertex
|
||||
model::Mesh::TRIANGLES)); // topology
|
||||
mesh->setPartBuffer(gpu::BufferView(new gpu::Buffer(parts.size() * sizeof(model::Mesh::Part),
|
||||
(gpu::Byte*) parts.data()), gpu::Element::PART_DRAWCALL));
|
||||
entity->setMesh(mesh);
|
||||
});
|
||||
}
|
||||
|
||||
void RenderablePolyVoxEntityItem::setMesh(model::MeshPointer mesh) {
|
||||
// this catches the payload from getMesh
|
||||
// this catches the payload from recomputeMesh
|
||||
bool neighborsNeedUpdate;
|
||||
withWriteLock([&] {
|
||||
if (!_collisionless) {
|
||||
|
@ -1531,7 +1553,6 @@ std::shared_ptr<RenderablePolyVoxEntityItem> RenderablePolyVoxEntityItem::getZPN
|
|||
return std::dynamic_pointer_cast<RenderablePolyVoxEntityItem>(_zPNeighbor.lock());
|
||||
}
|
||||
|
||||
|
||||
void RenderablePolyVoxEntityItem::bonkNeighbors() {
|
||||
// flag neighbors to the negative of this entity as needing to rebake their meshes.
|
||||
cacheNeighbors();
|
||||
|
@ -1551,7 +1572,6 @@ void RenderablePolyVoxEntityItem::bonkNeighbors() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
void RenderablePolyVoxEntityItem::locationChanged(bool tellPhysics) {
|
||||
EntityItem::locationChanged(tellPhysics);
|
||||
if (!_pipeline || !render::Item::isValidID(_myItem)) {
|
||||
|
@ -1563,3 +1583,17 @@ void RenderablePolyVoxEntityItem::locationChanged(bool tellPhysics) {
|
|||
|
||||
scene->enqueuePendingChanges(pendingChanges);
|
||||
}
|
||||
|
||||
bool RenderablePolyVoxEntityItem::getMeshAsScriptValue(QScriptEngine *engine, QScriptValue& result) const {
|
||||
bool success = false;
|
||||
MeshProxy* meshProxy = nullptr;
|
||||
model::MeshPointer mesh = nullptr;
|
||||
withReadLock([&] {
|
||||
if (_meshInitialized) {
|
||||
success = true;
|
||||
meshProxy = new MeshProxy(_mesh);
|
||||
}
|
||||
});
|
||||
result = meshToScriptValue(engine, meshProxy);
|
||||
return success;
|
||||
}
|
||||
|
|
|
@ -133,6 +133,7 @@ public:
|
|||
QByteArray volDataToArray(quint16 voxelXSize, quint16 voxelYSize, quint16 voxelZSize) const;
|
||||
|
||||
void setMesh(model::MeshPointer mesh);
|
||||
bool getMeshAsScriptValue(QScriptEngine *engine, QScriptValue& result) const override;
|
||||
void setCollisionPoints(ShapeInfo::PointCollection points, AABox box);
|
||||
PolyVox::SimpleVolume<uint8_t>* getVolData() { return _volData; }
|
||||
|
||||
|
@ -163,11 +164,12 @@ private:
|
|||
const int MATERIAL_GPU_SLOT = 3;
|
||||
render::ItemID _myItem{ render::Item::INVALID_ITEM_ID };
|
||||
static gpu::PipelinePointer _pipeline;
|
||||
static gpu::PipelinePointer _wireframePipeline;
|
||||
|
||||
ShapeInfo _shapeInfo;
|
||||
|
||||
PolyVox::SimpleVolume<uint8_t>* _volData = nullptr;
|
||||
bool _volDataDirty = false; // does getMesh need to be called?
|
||||
bool _volDataDirty = false; // does recomputeMesh need to be called?
|
||||
int _onCount; // how many non-zero voxels are in _volData
|
||||
|
||||
bool _neighborsNeedUpdate { false };
|
||||
|
@ -178,7 +180,7 @@ private:
|
|||
// these are run off the main thread
|
||||
void decompressVolumeData();
|
||||
void compressVolumeDataAndSendEditPacket();
|
||||
virtual void getMesh() override; // recompute mesh
|
||||
virtual void recomputeMesh() override; // recompute mesh
|
||||
void computeShapeInfoWorker();
|
||||
|
||||
// these are cached lookups of _xNNeighborID, _yNNeighborID, _zNNeighborID, _xPNeighborID, _yPNeighborID, _zPNeighborID
|
||||
|
|
|
@ -114,13 +114,22 @@ void RenderableShapeEntityItem::render(RenderArgs* args) {
|
|||
auto outColor = _procedural->getColor(color);
|
||||
outColor.a *= _procedural->isFading() ? Interpolate::calculateFadeRatio(_procedural->getFadeStartTime()) : 1.0f;
|
||||
batch._glColor4f(outColor.r, outColor.g, outColor.b, outColor.a);
|
||||
DependencyManager::get<GeometryCache>()->renderShape(batch, MAPPING[_shape]);
|
||||
if (render::ShapeKey(args->_globalShapeKey).isWireframe()) {
|
||||
DependencyManager::get<GeometryCache>()->renderWireShape(batch, MAPPING[_shape]);
|
||||
} else {
|
||||
DependencyManager::get<GeometryCache>()->renderShape(batch, MAPPING[_shape]);
|
||||
}
|
||||
} else {
|
||||
// FIXME, support instanced multi-shape rendering using multidraw indirect
|
||||
color.a *= _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f;
|
||||
auto geometryCache = DependencyManager::get<GeometryCache>();
|
||||
auto pipeline = color.a < 1.0f ? geometryCache->getTransparentShapePipeline() : geometryCache->getOpaqueShapePipeline();
|
||||
geometryCache->renderSolidShapeInstance(batch, MAPPING[_shape], color, pipeline);
|
||||
|
||||
if (render::ShapeKey(args->_globalShapeKey).isWireframe()) {
|
||||
geometryCache->renderWireShapeInstance(batch, MAPPING[_shape], color, pipeline);
|
||||
} else {
|
||||
geometryCache->renderSolidShapeInstance(batch, MAPPING[_shape], color, pipeline);
|
||||
}
|
||||
}
|
||||
|
||||
static const auto triCount = DependencyManager::get<GeometryCache>()->getShapeTriangleCount(MAPPING[_shape]);
|
||||
|
|
|
@ -15,11 +15,13 @@
|
|||
#define hifi_EntitiesScriptEngineProvider_h
|
||||
|
||||
#include <QtCore/QString>
|
||||
#include <QFuture>
|
||||
#include "EntityItemID.h"
|
||||
|
||||
class EntitiesScriptEngineProvider {
|
||||
public:
|
||||
virtual void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const QStringList& params = QStringList()) = 0;
|
||||
virtual QFuture<QVariant> getLocalEntityScriptDetails(const EntityItemID& entityID) = 0;
|
||||
};
|
||||
|
||||
#endif // hifi_EntitiesScriptEngineProvider_h
|
||||
#endif // hifi_EntitiesScriptEngineProvider_h
|
||||
|
|
|
@ -655,13 +655,11 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
|||
|
||||
|
||||
// pack SimulationOwner and terse update properties near each other
|
||||
|
||||
// NOTE: the server is authoritative for changes to simOwnerID so we always unpack ownership data
|
||||
// even when we would otherwise ignore the rest of the packet.
|
||||
|
||||
bool filterRejection = false;
|
||||
if (propertyFlags.getHasProperty(PROP_SIMULATION_OWNER)) {
|
||||
|
||||
QByteArray simOwnerData;
|
||||
int bytes = OctreePacketData::unpackDataFromBytes(dataAt, simOwnerData);
|
||||
SimulationOwner newSimOwner;
|
||||
|
@ -1879,6 +1877,7 @@ void EntityItem::setSimulationOwner(const SimulationOwner& owner) {
|
|||
}
|
||||
|
||||
void EntityItem::updateSimulationOwner(const SimulationOwner& owner) {
|
||||
// NOTE: this method only used by EntityServer. The Interface uses special code in readEntityDataFromBuffer().
|
||||
if (wantTerseEditLogging() && _simulationOwner != owner) {
|
||||
qCDebug(entities) << "sim ownership for" << getDebugName() << "is now" << owner;
|
||||
}
|
||||
|
@ -1894,8 +1893,9 @@ void EntityItem::clearSimulationOwnership() {
|
|||
}
|
||||
|
||||
_simulationOwner.clear();
|
||||
// don't bother setting the DIRTY_SIMULATOR_ID flag because clearSimulationOwnership()
|
||||
// is only ever called on the entity-server and the flags are only used client-side
|
||||
// don't bother setting the DIRTY_SIMULATOR_ID flag because:
|
||||
// (a) when entity-server calls clearSimulationOwnership() the dirty-flags are meaningless (only used by interface)
|
||||
// (b) the interface only calls clearSimulationOwnership() in a context that already knows best about dirty flags
|
||||
//_dirtyFlags |= Simulation::DIRTY_SIMULATOR_ID;
|
||||
|
||||
}
|
||||
|
|
|
@ -10,6 +10,9 @@
|
|||
//
|
||||
#include "EntityScriptingInterface.h"
|
||||
|
||||
#include <QFutureWatcher>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
|
||||
#include "EntityItemID.h"
|
||||
#include <VariantMapToScriptValue.h>
|
||||
#include <SharedUtil.h>
|
||||
|
@ -680,6 +683,118 @@ bool EntityScriptingInterface::reloadServerScripts(QUuid entityID) {
|
|||
return client->reloadServerScript(entityID);
|
||||
}
|
||||
|
||||
bool EntityPropertyMetadataRequest::script(EntityItemID entityID, QScriptValue handler) {
|
||||
using LocalScriptStatusRequest = QFutureWatcher<QVariant>;
|
||||
|
||||
LocalScriptStatusRequest* request = new LocalScriptStatusRequest;
|
||||
QObject::connect(request, &LocalScriptStatusRequest::finished, _engine, [=]() mutable {
|
||||
auto details = request->result().toMap();
|
||||
QScriptValue err, result;
|
||||
if (details.contains("isError")) {
|
||||
if (!details.contains("message")) {
|
||||
details["message"] = details["errorInfo"];
|
||||
}
|
||||
err = _engine->makeError(_engine->toScriptValue(details));
|
||||
} else {
|
||||
details["success"] = true;
|
||||
result = _engine->toScriptValue(details);
|
||||
}
|
||||
callScopedHandlerObject(handler, err, result);
|
||||
request->deleteLater();
|
||||
});
|
||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||
entityScriptingInterface->withEntitiesScriptEngine([&](EntitiesScriptEngineProvider* entitiesScriptEngine) {
|
||||
if (entitiesScriptEngine) {
|
||||
request->setFuture(entitiesScriptEngine->getLocalEntityScriptDetails(entityID));
|
||||
}
|
||||
});
|
||||
if (!request->isStarted()) {
|
||||
request->deleteLater();
|
||||
callScopedHandlerObject(handler, _engine->makeError("Entities Scripting Provider unavailable", "InternalError"), QScriptValue());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EntityPropertyMetadataRequest::serverScripts(EntityItemID entityID, QScriptValue handler) {
|
||||
auto client = DependencyManager::get<EntityScriptClient>();
|
||||
auto request = client->createScriptStatusRequest(entityID);
|
||||
QPointer<BaseScriptEngine> engine = _engine;
|
||||
QObject::connect(request, &GetScriptStatusRequest::finished, _engine, [=](GetScriptStatusRequest* request) mutable {
|
||||
auto engine = _engine;
|
||||
if (!engine) {
|
||||
qCDebug(entities) << __FUNCTION__ << " -- engine destroyed while inflight" << entityID;
|
||||
return;
|
||||
}
|
||||
QVariantMap details;
|
||||
details["success"] = request->getResponseReceived();
|
||||
details["isRunning"] = request->getIsRunning();
|
||||
details["status"] = EntityScriptStatus_::valueToKey(request->getStatus()).toLower();
|
||||
details["errorInfo"] = request->getErrorInfo();
|
||||
|
||||
QScriptValue err, result;
|
||||
if (!details["success"].toBool()) {
|
||||
if (!details.contains("message") && details.contains("errorInfo")) {
|
||||
details["message"] = details["errorInfo"];
|
||||
}
|
||||
if (details["message"].toString().isEmpty()) {
|
||||
details["message"] = "entity server script details not found";
|
||||
}
|
||||
err = engine->makeError(engine->toScriptValue(details));
|
||||
} else {
|
||||
result = engine->toScriptValue(details);
|
||||
}
|
||||
callScopedHandlerObject(handler, err, result);
|
||||
request->deleteLater();
|
||||
});
|
||||
request->start();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool EntityScriptingInterface::queryPropertyMetadata(QUuid entityID, QScriptValue property, QScriptValue scopeOrCallback, QScriptValue methodOrName) {
|
||||
auto name = property.toString();
|
||||
auto handler = makeScopedHandlerObject(scopeOrCallback, methodOrName);
|
||||
QPointer<BaseScriptEngine> engine = dynamic_cast<BaseScriptEngine*>(handler.engine());
|
||||
if (!engine) {
|
||||
qCDebug(entities) << "queryPropertyMetadata without detectable engine" << entityID << name;
|
||||
return false;
|
||||
}
|
||||
#ifdef DEBUG_ENGINE_STATE
|
||||
connect(engine, &QObject::destroyed, this, [=]() {
|
||||
qDebug() << "queryPropertyMetadata -- engine destroyed!" << (!engine ? "nullptr" : "engine");
|
||||
});
|
||||
#endif
|
||||
if (!handler.property("callback").isFunction()) {
|
||||
qDebug() << "!handler.callback.isFunction" << engine;
|
||||
engine->raiseException(engine->makeError("callback is not a function", "TypeError"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// NOTE: this approach is a work-in-progress and for now just meant to work 100% correctly and provide
|
||||
// some initial structure for organizing metadata adapters around.
|
||||
|
||||
// The extra layer of indirection is *essential* because in real world conditions errors are often introduced
|
||||
// by accident and sometimes without exact memory of "what just changed."
|
||||
|
||||
// Here the scripter only needs to know an entityID and a property name -- which means all scripters can
|
||||
// level this method when stuck in dead-end scenarios or to learn more about "magic" Entity properties
|
||||
// like .script that work in terms of side-effects.
|
||||
|
||||
// This is an async callback pattern -- so if needed C++ can easily throttle or restrict queries later.
|
||||
|
||||
EntityPropertyMetadataRequest request(engine);
|
||||
|
||||
if (name == "script") {
|
||||
return request.script(entityID, handler);
|
||||
} else if (name == "serverScripts") {
|
||||
return request.serverScripts(entityID, handler);
|
||||
} else {
|
||||
engine->raiseException(engine->makeError("metadata for property " + name + " is not yet queryable"));
|
||||
engine->maybeEmitUncaughtException(__FUNCTION__);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool EntityScriptingInterface::getServerScriptStatus(QUuid entityID, QScriptValue callback) {
|
||||
auto client = DependencyManager::get<EntityScriptClient>();
|
||||
auto request = client->createScriptStatusRequest(entityID);
|
||||
|
@ -815,8 +930,7 @@ void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, Ra
|
|||
}
|
||||
}
|
||||
|
||||
bool EntityScriptingInterface::setVoxels(QUuid entityID,
|
||||
std::function<bool(PolyVoxEntityItem&)> actor) {
|
||||
bool EntityScriptingInterface::polyVoxWorker(QUuid entityID, std::function<bool(PolyVoxEntityItem&)> actor) {
|
||||
PROFILE_RANGE(script_entities, __FUNCTION__);
|
||||
|
||||
if (!_entityTree) {
|
||||
|
@ -882,11 +996,9 @@ bool EntityScriptingInterface::setPoints(QUuid entityID, std::function<bool(Line
|
|||
return success;
|
||||
}
|
||||
|
||||
|
||||
bool EntityScriptingInterface::setVoxelSphere(QUuid entityID, const glm::vec3& center, float radius, int value) {
|
||||
PROFILE_RANGE(script_entities, __FUNCTION__);
|
||||
|
||||
return setVoxels(entityID, [center, radius, value](PolyVoxEntityItem& polyVoxEntity) {
|
||||
return polyVoxWorker(entityID, [center, radius, value](PolyVoxEntityItem& polyVoxEntity) {
|
||||
return polyVoxEntity.setSphere(center, radius, value);
|
||||
});
|
||||
}
|
||||
|
@ -896,7 +1008,7 @@ bool EntityScriptingInterface::setVoxelCapsule(QUuid entityID,
|
|||
float radius, int value) {
|
||||
PROFILE_RANGE(script_entities, __FUNCTION__);
|
||||
|
||||
return setVoxels(entityID, [start, end, radius, value](PolyVoxEntityItem& polyVoxEntity) {
|
||||
return polyVoxWorker(entityID, [start, end, radius, value](PolyVoxEntityItem& polyVoxEntity) {
|
||||
return polyVoxEntity.setCapsule(start, end, radius, value);
|
||||
});
|
||||
}
|
||||
|
@ -904,7 +1016,7 @@ bool EntityScriptingInterface::setVoxelCapsule(QUuid entityID,
|
|||
bool EntityScriptingInterface::setVoxel(QUuid entityID, const glm::vec3& position, int value) {
|
||||
PROFILE_RANGE(script_entities, __FUNCTION__);
|
||||
|
||||
return setVoxels(entityID, [position, value](PolyVoxEntityItem& polyVoxEntity) {
|
||||
return polyVoxWorker(entityID, [position, value](PolyVoxEntityItem& polyVoxEntity) {
|
||||
return polyVoxEntity.setVoxelInVolume(position, value);
|
||||
});
|
||||
}
|
||||
|
@ -912,7 +1024,7 @@ bool EntityScriptingInterface::setVoxel(QUuid entityID, const glm::vec3& positio
|
|||
bool EntityScriptingInterface::setAllVoxels(QUuid entityID, int value) {
|
||||
PROFILE_RANGE(script_entities, __FUNCTION__);
|
||||
|
||||
return setVoxels(entityID, [value](PolyVoxEntityItem& polyVoxEntity) {
|
||||
return polyVoxWorker(entityID, [value](PolyVoxEntityItem& polyVoxEntity) {
|
||||
return polyVoxEntity.setAll(value);
|
||||
});
|
||||
}
|
||||
|
@ -921,11 +1033,23 @@ bool EntityScriptingInterface::setVoxelsInCuboid(QUuid entityID, const glm::vec3
|
|||
const glm::vec3& cuboidSize, int value) {
|
||||
PROFILE_RANGE(script_entities, __FUNCTION__);
|
||||
|
||||
return setVoxels(entityID, [lowPosition, cuboidSize, value](PolyVoxEntityItem& polyVoxEntity) {
|
||||
return polyVoxWorker(entityID, [lowPosition, cuboidSize, value](PolyVoxEntityItem& polyVoxEntity) {
|
||||
return polyVoxEntity.setCuboid(lowPosition, cuboidSize, value);
|
||||
});
|
||||
}
|
||||
|
||||
void EntityScriptingInterface::voxelsToMesh(QUuid entityID, QScriptValue callback) {
|
||||
PROFILE_RANGE(script_entities, __FUNCTION__);
|
||||
|
||||
polyVoxWorker(entityID, [callback](PolyVoxEntityItem& polyVoxEntity) mutable {
|
||||
QScriptValue mesh;
|
||||
polyVoxEntity.getMeshAsScriptValue(callback.engine(), mesh);
|
||||
QScriptValueList args { mesh };
|
||||
callback.call(QScriptValue(), args);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
bool EntityScriptingInterface::setAllPoints(QUuid entityID, const QVector<glm::vec3>& points) {
|
||||
PROFILE_RANGE(script_entities, __FUNCTION__);
|
||||
|
||||
|
|
|
@ -34,7 +34,23 @@
|
|||
#include "EntitiesScriptEngineProvider.h"
|
||||
#include "EntityItemProperties.h"
|
||||
|
||||
#include "BaseScriptEngine.h"
|
||||
|
||||
class EntityTree;
|
||||
class MeshProxy;
|
||||
|
||||
// helper factory to compose standardized, async metadata queries for "magic" Entity properties
|
||||
// like .script and .serverScripts. This is used for automated testing of core scripting features
|
||||
// as well as to provide early adopters a self-discoverable, consistent way to diagnose common
|
||||
// problems with their own Entity scripts.
|
||||
class EntityPropertyMetadataRequest {
|
||||
public:
|
||||
EntityPropertyMetadataRequest(BaseScriptEngine* engine) : _engine(engine) {};
|
||||
bool script(EntityItemID entityID, QScriptValue handler);
|
||||
bool serverScripts(EntityItemID entityID, QScriptValue handler);
|
||||
private:
|
||||
QPointer<BaseScriptEngine> _engine;
|
||||
};
|
||||
|
||||
class RayToEntityIntersectionResult {
|
||||
public:
|
||||
|
@ -67,6 +83,7 @@ class EntityScriptingInterface : public OctreeScriptingInterface, public Depende
|
|||
Q_PROPERTY(float costMultiplier READ getCostMultiplier WRITE setCostMultiplier)
|
||||
Q_PROPERTY(QUuid keyboardFocusEntity READ getKeyboardFocusEntity WRITE setKeyboardFocusEntity)
|
||||
|
||||
friend EntityPropertyMetadataRequest;
|
||||
public:
|
||||
EntityScriptingInterface(bool bidOnSimulationOwnership);
|
||||
|
||||
|
@ -211,6 +228,26 @@ public slots:
|
|||
Q_INVOKABLE RayToEntityIntersectionResult findRayIntersectionBlocking(const PickRay& ray, bool precisionPicking = false, const QScriptValue& entityIdsToInclude = QScriptValue(), const QScriptValue& entityIdsToDiscard = QScriptValue());
|
||||
|
||||
Q_INVOKABLE bool reloadServerScripts(QUuid entityID);
|
||||
|
||||
/**jsdoc
|
||||
* Query additional metadata for "magic" Entity properties like `script` and `serverScripts`.
|
||||
*
|
||||
* @function Entities.queryPropertyMetadata
|
||||
* @param {EntityID} entityID The ID of the entity.
|
||||
* @param {string} property The name of the property extended metadata is wanted for.
|
||||
* @param {ResultCallback} callback Executes callback(err, result) with the query results.
|
||||
*/
|
||||
/**jsdoc
|
||||
* Query additional metadata for "magic" Entity properties like `script` and `serverScripts`.
|
||||
*
|
||||
* @function Entities.queryPropertyMetadata
|
||||
* @param {EntityID} entityID The ID of the entity.
|
||||
* @param {string} property The name of the property extended metadata is wanted for.
|
||||
* @param {Object} thisObject The scoping "this" context that callback will be executed within.
|
||||
* @param {ResultCallback} callbackOrMethodName Executes thisObject[callbackOrMethodName](err, result) with the query results.
|
||||
*/
|
||||
Q_INVOKABLE bool queryPropertyMetadata(QUuid entityID, QScriptValue property, QScriptValue scopeOrCallback, QScriptValue methodOrName = QScriptValue());
|
||||
|
||||
Q_INVOKABLE bool getServerScriptStatus(QUuid entityID, QScriptValue callback);
|
||||
|
||||
Q_INVOKABLE void setLightsArePickable(bool value);
|
||||
|
@ -229,6 +266,7 @@ public slots:
|
|||
Q_INVOKABLE bool setAllVoxels(QUuid entityID, int value);
|
||||
Q_INVOKABLE bool setVoxelsInCuboid(QUuid entityID, const glm::vec3& lowPosition,
|
||||
const glm::vec3& cuboidSize, int value);
|
||||
Q_INVOKABLE void voxelsToMesh(QUuid entityID, QScriptValue callback);
|
||||
|
||||
Q_INVOKABLE bool setAllPoints(QUuid entityID, const QVector<glm::vec3>& points);
|
||||
Q_INVOKABLE bool appendPoint(QUuid entityID, const glm::vec3& point);
|
||||
|
@ -323,9 +361,14 @@ signals:
|
|||
|
||||
void webEventReceived(const EntityItemID& entityItemID, const QVariant& message);
|
||||
|
||||
protected:
|
||||
void withEntitiesScriptEngine(std::function<void(EntitiesScriptEngineProvider*)> function) {
|
||||
std::lock_guard<std::recursive_mutex> lock(_entitiesScriptEngineLock);
|
||||
function(_entitiesScriptEngine);
|
||||
};
|
||||
private:
|
||||
bool actionWorker(const QUuid& entityID, std::function<bool(EntitySimulationPointer, EntityItemPointer)> actor);
|
||||
bool setVoxels(QUuid entityID, std::function<bool(PolyVoxEntityItem&)> actor);
|
||||
bool polyVoxWorker(QUuid entityID, std::function<bool(PolyVoxEntityItem&)> actor);
|
||||
bool setPoints(QUuid entityID, std::function<bool(LineEntityItem&)> actor);
|
||||
void queueEntityMessage(PacketType packetType, EntityItemID entityID, const EntityItemProperties& properties);
|
||||
|
||||
|
|
|
@ -242,3 +242,7 @@ const QByteArray PolyVoxEntityItem::getVoxelData() const {
|
|||
});
|
||||
return voxelDataCopy;
|
||||
}
|
||||
|
||||
bool PolyVoxEntityItem::getMeshAsScriptValue(QScriptEngine *engine, QScriptValue& result) const {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -131,7 +131,9 @@ class PolyVoxEntityItem : public EntityItem {
|
|||
virtual void rebakeMesh() {};
|
||||
|
||||
void setVoxelDataDirty(bool value) { withWriteLock([&] { _voxelDataDirty = value; }); }
|
||||
virtual void getMesh() {}; // recompute mesh
|
||||
virtual void recomputeMesh() {};
|
||||
|
||||
virtual bool getMeshAsScriptValue(QScriptEngine *engine, QScriptValue& result) const;
|
||||
|
||||
protected:
|
||||
glm::vec3 _voxelVolumeSize; // this is always 3 bytes
|
||||
|
|
|
@ -54,7 +54,8 @@ template<class T> QVariant readBinaryArray(QDataStream& in, int& position) {
|
|||
in.readRawData(compressed.data() + sizeof(quint32), compressedLength);
|
||||
position += compressedLength;
|
||||
arrayData = qUncompress(compressed);
|
||||
if (arrayData.isEmpty() || arrayData.size() != (sizeof(T) * arrayLength)) { // answers empty byte array if corrupt
|
||||
if (arrayData.isEmpty() ||
|
||||
(unsigned int)arrayData.size() != (sizeof(T) * arrayLength)) { // answers empty byte array if corrupt
|
||||
throw QString("corrupt fbx file");
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -546,6 +546,7 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping,
|
|||
QString queryPart = _url.query();
|
||||
bool suppressMaterialsHack = queryPart.contains("hifiusemat"); // If this appears in query string, don't fetch mtl even if used.
|
||||
OBJMaterial& preDefinedMaterial = materials[SMART_DEFAULT_MATERIAL_NAME];
|
||||
preDefinedMaterial.used = true;
|
||||
if (suppressMaterialsHack) {
|
||||
needsMaterialLibrary = preDefinedMaterial.userSpecifiesUV = false; // I said it was a hack...
|
||||
}
|
||||
|
@ -594,8 +595,8 @@ FBXGeometry* OBJReader::readOBJ(QByteArray& model, const QVariantHash& mapping,
|
|||
}
|
||||
|
||||
foreach (QString materialID, materials.keys()) {
|
||||
OBJMaterial& objMaterial = materials[materialID];
|
||||
if (!objMaterial.used) {
|
||||
OBJMaterial& objMaterial = materials[materialID];
|
||||
if (!objMaterial.used) {
|
||||
continue;
|
||||
}
|
||||
geometry.materials[materialID] = FBXMaterial(objMaterial.diffuseColor,
|
||||
|
|
|
@ -58,7 +58,7 @@ public:
|
|||
QByteArray specularTextureFilename;
|
||||
bool used { false };
|
||||
bool userSpecifiesUV { false };
|
||||
OBJMaterial() : shininess(96.0f), opacity(1.0f), diffuseColor(1.0f), specularColor(1.0f) {}
|
||||
OBJMaterial() : shininess(0.0f), opacity(1.0f), diffuseColor(0.9f), specularColor(0.9f) {}
|
||||
};
|
||||
|
||||
class OBJReader: public QObject { // QObject so we can make network requests.
|
||||
|
|
148
libraries/fbx/src/OBJWriter.cpp
Normal file
148
libraries/fbx/src/OBJWriter.cpp
Normal file
|
@ -0,0 +1,148 @@
|
|||
//
|
||||
// OBJWriter.cpp
|
||||
// libraries/fbx/src/
|
||||
//
|
||||
// Created by Seth Alves on 2017-1-27.
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include "model/Geometry.h"
|
||||
#include "OBJWriter.h"
|
||||
#include "ModelFormatLogging.h"
|
||||
|
||||
static QString formatFloat(double n) {
|
||||
// limit precision to 6, but don't output trailing zeros.
|
||||
QString s = QString::number(n, 'f', 6);
|
||||
while (s.endsWith("0")) {
|
||||
s.remove(s.size() - 1, 1);
|
||||
}
|
||||
if (s.endsWith(".")) {
|
||||
s.remove(s.size() - 1, 1);
|
||||
}
|
||||
|
||||
// check for non-numbers. if we get NaN or inf or scientific notation, just return 0
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
auto c = s.at(i).toLatin1();
|
||||
if (c != '-' &&
|
||||
c != '.' &&
|
||||
(c < '0' || c > '9')) {
|
||||
qCDebug(modelformat) << "OBJWriter zeroing bad vertex coordinate:" << s << "because of" << c;
|
||||
return QString("0");
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
bool writeOBJToTextStream(QTextStream& out, QList<MeshPointer> meshes) {
|
||||
// each mesh's vertices are numbered from zero. We're combining all their vertices into one list here,
|
||||
// so keep track of the start index for each mesh.
|
||||
QList<int> meshVertexStartOffset;
|
||||
int currentVertexStartOffset = 0;
|
||||
|
||||
// write out all vertices
|
||||
foreach (const MeshPointer& mesh, meshes) {
|
||||
meshVertexStartOffset.append(currentVertexStartOffset);
|
||||
const gpu::BufferView& vertexBuffer = mesh->getVertexBuffer();
|
||||
int vertexCount = 0;
|
||||
gpu::BufferView::Iterator<const glm::vec3> vertexItr = vertexBuffer.cbegin<const glm::vec3>();
|
||||
while (vertexItr != vertexBuffer.cend<const glm::vec3>()) {
|
||||
glm::vec3 v = *vertexItr;
|
||||
out << "v ";
|
||||
out << formatFloat(v[0]) << " ";
|
||||
out << formatFloat(v[1]) << " ";
|
||||
out << formatFloat(v[2]) << "\n";
|
||||
vertexItr++;
|
||||
vertexCount++;
|
||||
}
|
||||
currentVertexStartOffset += vertexCount;
|
||||
}
|
||||
out << "\n";
|
||||
|
||||
// write out faces
|
||||
int nth = 0;
|
||||
foreach (const MeshPointer& mesh, meshes) {
|
||||
currentVertexStartOffset = meshVertexStartOffset.takeFirst();
|
||||
|
||||
const gpu::BufferView& partBuffer = mesh->getPartBuffer();
|
||||
const gpu::BufferView& indexBuffer = mesh->getIndexBuffer();
|
||||
|
||||
model::Index partCount = (model::Index)mesh->getNumParts();
|
||||
for (int partIndex = 0; partIndex < partCount; partIndex++) {
|
||||
const model::Mesh::Part& part = partBuffer.get<model::Mesh::Part>(partIndex);
|
||||
|
||||
out << "g part-" << nth++ << "\n";
|
||||
|
||||
// model::Mesh::TRIANGLES
|
||||
// TODO -- handle other formats
|
||||
gpu::BufferView::Iterator<const uint32_t> indexItr = indexBuffer.cbegin<uint32_t>();
|
||||
indexItr += part._startIndex;
|
||||
|
||||
int indexCount = 0;
|
||||
while (indexItr != indexBuffer.cend<uint32_t>() && indexCount < part._numIndices) {
|
||||
uint32_t index0 = *indexItr;
|
||||
indexItr++;
|
||||
indexCount++;
|
||||
if (indexItr == indexBuffer.cend<uint32_t>() || indexCount >= part._numIndices) {
|
||||
qCDebug(modelformat) << "OBJWriter -- index buffer length isn't multiple of 3";
|
||||
break;
|
||||
}
|
||||
uint32_t index1 = *indexItr;
|
||||
indexItr++;
|
||||
indexCount++;
|
||||
if (indexItr == indexBuffer.cend<uint32_t>() || indexCount >= part._numIndices) {
|
||||
qCDebug(modelformat) << "OBJWriter -- index buffer length isn't multiple of 3";
|
||||
break;
|
||||
}
|
||||
uint32_t index2 = *indexItr;
|
||||
indexItr++;
|
||||
indexCount++;
|
||||
|
||||
out << "f ";
|
||||
out << currentVertexStartOffset + index0 + 1 << " ";
|
||||
out << currentVertexStartOffset + index1 + 1 << " ";
|
||||
out << currentVertexStartOffset + index2 + 1 << "\n";
|
||||
}
|
||||
out << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool writeOBJToFile(QString path, QList<MeshPointer> meshes) {
|
||||
if (QFileInfo(path).exists() && !QFile::remove(path)) {
|
||||
qCDebug(modelformat) << "OBJ writer failed, file exists:" << path;
|
||||
return false;
|
||||
}
|
||||
|
||||
QFile file(path);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
qCDebug(modelformat) << "OBJ writer failed to open output file:" << path;
|
||||
return false;
|
||||
}
|
||||
|
||||
QTextStream outStream(&file);
|
||||
|
||||
bool success;
|
||||
success = writeOBJToTextStream(outStream, meshes);
|
||||
|
||||
file.close();
|
||||
return success;
|
||||
}
|
||||
|
||||
QString writeOBJToString(QList<MeshPointer> meshes) {
|
||||
QString result;
|
||||
QTextStream outStream(&result, QIODevice::ReadWrite);
|
||||
bool success;
|
||||
success = writeOBJToTextStream(outStream, meshes);
|
||||
if (success) {
|
||||
return result;
|
||||
}
|
||||
return QString("");
|
||||
}
|
26
libraries/fbx/src/OBJWriter.h
Normal file
26
libraries/fbx/src/OBJWriter.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
//
|
||||
// OBJWriter.h
|
||||
// libraries/fbx/src/
|
||||
//
|
||||
// Created by Seth Alves on 2017-1-27.
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_objwriter_h
|
||||
#define hifi_objwriter_h
|
||||
|
||||
|
||||
#include <QString>
|
||||
#include <QList>
|
||||
#include <model/Geometry.h>
|
||||
|
||||
using MeshPointer = std::shared_ptr<model::Mesh>;
|
||||
|
||||
bool writeOBJToTextStream(QTextStream& out, QList<MeshPointer> meshes);
|
||||
bool writeOBJToFile(QString path, QList<MeshPointer> meshes);
|
||||
QString writeOBJToString(QList<MeshPointer> meshes);
|
||||
|
||||
#endif // hifi_objwriter_h
|
|
@ -13,18 +13,31 @@
|
|||
#define hifi_NodePermissions_h
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <QString>
|
||||
#include <QMap>
|
||||
#include <QVariant>
|
||||
#include <QUuid>
|
||||
|
||||
#include <QHash>
|
||||
#include <utility>
|
||||
#include "GroupRank.h"
|
||||
|
||||
class NodePermissions;
|
||||
using NodePermissionsPointer = std::shared_ptr<NodePermissions>;
|
||||
using NodePermissionsKey = QPair<QString, QUuid>; // name, rankID
|
||||
using NodePermissionsKey = std::pair<QString, QUuid>; // name, rankID
|
||||
using NodePermissionsKeyList = QList<QPair<QString, QUuid>>;
|
||||
|
||||
namespace std {
|
||||
template<>
|
||||
struct hash<NodePermissionsKey> {
|
||||
size_t operator()(const NodePermissionsKey& key) const {
|
||||
size_t result = qHash(key.first);
|
||||
result <<= 32;
|
||||
result |= qHash(key.second);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class NodePermissions {
|
||||
public:
|
||||
|
@ -100,27 +113,40 @@ public:
|
|||
NodePermissionsMap() { }
|
||||
NodePermissionsPointer& operator[](const NodePermissionsKey& key) {
|
||||
NodePermissionsKey dataKey(key.first.toLower(), key.second);
|
||||
if (!_data.contains(dataKey)) {
|
||||
if (0 == _data.count(dataKey)) {
|
||||
_data[dataKey] = NodePermissionsPointer(new NodePermissions(key));
|
||||
}
|
||||
return _data[dataKey];
|
||||
}
|
||||
NodePermissionsPointer operator[](const NodePermissionsKey& key) const {
|
||||
return _data.value(NodePermissionsKey(key.first.toLower(), key.second));
|
||||
NodePermissionsPointer result;
|
||||
auto itr = _data.find(NodePermissionsKey(key.first.toLower(), key.second));
|
||||
if (_data.end() != itr) {
|
||||
result = itr->second;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
bool contains(const NodePermissionsKey& key) const {
|
||||
return _data.contains(NodePermissionsKey(key.first.toLower(), key.second));
|
||||
return 0 != _data.count(NodePermissionsKey(key.first.toLower(), key.second));
|
||||
}
|
||||
bool contains(const QString& keyFirst, QUuid keySecond) const {
|
||||
return _data.contains(NodePermissionsKey(keyFirst.toLower(), keySecond));
|
||||
bool contains(const QString& keyFirst, const QUuid& keySecond) const {
|
||||
return 0 != _data.count(NodePermissionsKey(keyFirst.toLower(), keySecond));
|
||||
}
|
||||
QList<NodePermissionsKey> keys() const { return _data.keys(); }
|
||||
QHash<NodePermissionsKey, NodePermissionsPointer> get() { return _data; }
|
||||
|
||||
QList<NodePermissionsKey> keys() const {
|
||||
QList<NodePermissionsKey> result;
|
||||
for (const auto& entry : _data) {
|
||||
result.push_back(entry.first);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const std::unordered_map<NodePermissionsKey, NodePermissionsPointer>& get() { return _data; }
|
||||
void clear() { _data.clear(); }
|
||||
void remove(const NodePermissionsKey& key) { _data.remove(key); }
|
||||
void remove(const NodePermissionsKey& key) { _data.erase(key); }
|
||||
|
||||
private:
|
||||
QHash<NodePermissionsKey, NodePermissionsPointer> _data;
|
||||
std::unordered_map<NodePermissionsKey, NodePermissionsPointer> _data;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -15,6 +15,10 @@
|
|||
|
||||
using namespace udt;
|
||||
|
||||
PacketQueue::PacketQueue() {
|
||||
_channels.emplace_back(new std::list<PacketPointer>());
|
||||
}
|
||||
|
||||
MessageNumber PacketQueue::getNextMessageNumber() {
|
||||
static const MessageNumber MAX_MESSAGE_NUMBER = MessageNumber(1) << MESSAGE_NUMBER_SIZE;
|
||||
_currentMessageNumber = (_currentMessageNumber + 1) % MAX_MESSAGE_NUMBER;
|
||||
|
@ -24,7 +28,7 @@ MessageNumber PacketQueue::getNextMessageNumber() {
|
|||
bool PacketQueue::isEmpty() const {
|
||||
LockGuard locker(_packetsLock);
|
||||
// Only the main channel and it is empty
|
||||
return (_channels.size() == 1) && _channels.front().empty();
|
||||
return (_channels.size() == 1) && _channels.front()->empty();
|
||||
}
|
||||
|
||||
PacketQueue::PacketPointer PacketQueue::takePacket() {
|
||||
|
@ -34,19 +38,19 @@ PacketQueue::PacketPointer PacketQueue::takePacket() {
|
|||
}
|
||||
|
||||
// Find next non empty channel
|
||||
if (_channels[nextIndex()].empty()) {
|
||||
if (_channels[nextIndex()]->empty()) {
|
||||
nextIndex();
|
||||
}
|
||||
auto& channel = _channels[_currentIndex];
|
||||
Q_ASSERT(!channel.empty());
|
||||
Q_ASSERT(!channel->empty());
|
||||
|
||||
// Take front packet
|
||||
auto packet = std::move(channel.front());
|
||||
channel.pop_front();
|
||||
auto packet = std::move(channel->front());
|
||||
channel->pop_front();
|
||||
|
||||
// Remove now empty channel (Don't remove the main channel)
|
||||
if (channel.empty() && _currentIndex != 0) {
|
||||
channel.swap(_channels.back());
|
||||
if (channel->empty() && _currentIndex != 0) {
|
||||
channel->swap(*_channels.back());
|
||||
_channels.pop_back();
|
||||
--_currentIndex;
|
||||
}
|
||||
|
@ -61,7 +65,7 @@ unsigned int PacketQueue::nextIndex() {
|
|||
|
||||
void PacketQueue::queuePacket(PacketPointer packet) {
|
||||
LockGuard locker(_packetsLock);
|
||||
_channels.front().push_back(std::move(packet));
|
||||
_channels.front()->push_back(std::move(packet));
|
||||
}
|
||||
|
||||
void PacketQueue::queuePacketList(PacketListPointer packetList) {
|
||||
|
@ -70,5 +74,6 @@ void PacketQueue::queuePacketList(PacketListPointer packetList) {
|
|||
}
|
||||
|
||||
LockGuard locker(_packetsLock);
|
||||
_channels.push_back(std::move(packetList->_packets));
|
||||
_channels.emplace_back(new std::list<PacketPointer>());
|
||||
_channels.back()->swap(packetList->_packets);
|
||||
}
|
||||
|
|
|
@ -30,10 +30,11 @@ class PacketQueue {
|
|||
using LockGuard = std::lock_guard<Mutex>;
|
||||
using PacketPointer = std::unique_ptr<Packet>;
|
||||
using PacketListPointer = std::unique_ptr<PacketList>;
|
||||
using Channel = std::list<PacketPointer>;
|
||||
using Channel = std::unique_ptr<std::list<PacketPointer>>;
|
||||
using Channels = std::vector<Channel>;
|
||||
|
||||
public:
|
||||
PacketQueue();
|
||||
void queuePacket(PacketPointer packet);
|
||||
void queuePacketList(PacketListPointer packetList);
|
||||
|
||||
|
@ -49,7 +50,7 @@ private:
|
|||
MessageNumber _currentMessageNumber { 0 };
|
||||
|
||||
mutable Mutex _packetsLock; // Protects the packets to be sent.
|
||||
Channels _channels = Channels(1); // One channel per packet list + Main channel
|
||||
Channels _channels; // One channel per packet list + Main channel
|
||||
unsigned int _currentIndex { 0 };
|
||||
};
|
||||
|
||||
|
|
|
@ -97,6 +97,21 @@ void EntityMotionState::updateServerPhysicsVariables() {
|
|||
_serverActionData = _entity->getActionData();
|
||||
}
|
||||
|
||||
void EntityMotionState::handleDeactivation() {
|
||||
// copy _server data to entity
|
||||
bool success;
|
||||
_entity->setPosition(_serverPosition, success, false);
|
||||
_entity->setOrientation(_serverRotation, success, false);
|
||||
_entity->setVelocity(ENTITY_ITEM_ZERO_VEC3);
|
||||
_entity->setAngularVelocity(ENTITY_ITEM_ZERO_VEC3);
|
||||
// and also to RigidBody
|
||||
btTransform worldTrans;
|
||||
worldTrans.setOrigin(glmToBullet(_serverPosition));
|
||||
worldTrans.setRotation(glmToBullet(_serverRotation));
|
||||
_body->setWorldTransform(worldTrans);
|
||||
// no need to update velocities... should already be zero
|
||||
}
|
||||
|
||||
// virtual
|
||||
void EntityMotionState::handleEasyChanges(uint32_t& flags) {
|
||||
assert(entityTreeIsLocked());
|
||||
|
@ -111,6 +126,8 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) {
|
|||
flags &= ~Simulation::DIRTY_PHYSICS_ACTIVATION;
|
||||
_body->setActivationState(WANTS_DEACTIVATION);
|
||||
_outgoingPriority = 0;
|
||||
const float ACTIVATION_EXPIRY = 3.0f; // something larger than the 2.0 hard coded in Bullet
|
||||
_body->setDeactivationTime(ACTIVATION_EXPIRY);
|
||||
} else {
|
||||
// disowned object is still moving --> start timer for ownership bid
|
||||
// TODO? put a delay in here proportional to distance from object?
|
||||
|
@ -221,12 +238,9 @@ void EntityMotionState::getWorldTransform(btTransform& worldTrans) const {
|
|||
}
|
||||
|
||||
// This callback is invoked by the physics simulation at the end of each simulation step...
|
||||
// iff the corresponding RigidBody is DYNAMIC and has moved.
|
||||
// iff the corresponding RigidBody is DYNAMIC and ACTIVE.
|
||||
void EntityMotionState::setWorldTransform(const btTransform& worldTrans) {
|
||||
if (!_entity) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(_entity);
|
||||
assert(entityTreeIsLocked());
|
||||
measureBodyAcceleration();
|
||||
bool positionSuccess;
|
||||
|
|
|
@ -29,6 +29,7 @@ public:
|
|||
virtual ~EntityMotionState();
|
||||
|
||||
void updateServerPhysicsVariables();
|
||||
void handleDeactivation();
|
||||
virtual void handleEasyChanges(uint32_t& flags) override;
|
||||
virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) override;
|
||||
|
||||
|
|
|
@ -259,13 +259,27 @@ void PhysicalEntitySimulation::getObjectsToChange(VectorOfMotionStates& result)
|
|||
_pendingChanges.clear();
|
||||
}
|
||||
|
||||
void PhysicalEntitySimulation::handleOutgoingChanges(const VectorOfMotionStates& motionStates) {
|
||||
void PhysicalEntitySimulation::handleDeactivatedMotionStates(const VectorOfMotionStates& motionStates) {
|
||||
for (auto stateItr : motionStates) {
|
||||
ObjectMotionState* state = &(*stateItr);
|
||||
assert(state);
|
||||
if (state->getType() == MOTIONSTATE_TYPE_ENTITY) {
|
||||
EntityMotionState* entityState = static_cast<EntityMotionState*>(state);
|
||||
entityState->handleDeactivation();
|
||||
EntityItemPointer entity = entityState->getEntity();
|
||||
_entitiesToSort.insert(entity);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PhysicalEntitySimulation::handleChangedMotionStates(const VectorOfMotionStates& motionStates) {
|
||||
QMutexLocker lock(&_mutex);
|
||||
|
||||
// walk the motionStates looking for those that correspond to entities
|
||||
for (auto stateItr : motionStates) {
|
||||
ObjectMotionState* state = &(*stateItr);
|
||||
if (state && state->getType() == MOTIONSTATE_TYPE_ENTITY) {
|
||||
assert(state);
|
||||
if (state->getType() == MOTIONSTATE_TYPE_ENTITY) {
|
||||
EntityMotionState* entityState = static_cast<EntityMotionState*>(state);
|
||||
EntityItemPointer entity = entityState->getEntity();
|
||||
assert(entity.get());
|
||||
|
|
|
@ -56,7 +56,8 @@ public:
|
|||
void setObjectsToChange(const VectorOfMotionStates& objectsToChange);
|
||||
void getObjectsToChange(VectorOfMotionStates& result);
|
||||
|
||||
void handleOutgoingChanges(const VectorOfMotionStates& motionStates);
|
||||
void handleDeactivatedMotionStates(const VectorOfMotionStates& motionStates);
|
||||
void handleChangedMotionStates(const VectorOfMotionStates& motionStates);
|
||||
void handleCollisionEvents(const CollisionEvents& collisionEvents);
|
||||
|
||||
EntityEditPacketSender* getPacketSender() { return _entityPacketSender; }
|
||||
|
@ -67,7 +68,7 @@ private:
|
|||
SetOfEntities _entitiesToAddToPhysics;
|
||||
|
||||
SetOfEntityMotionStates _pendingChanges; // EntityMotionStates already in PhysicsEngine that need their physics changed
|
||||
SetOfEntityMotionStates _outgoingChanges; // EntityMotionStates for which we need to send updates to entity-server
|
||||
SetOfEntityMotionStates _outgoingChanges; // EntityMotionStates for which we may need to send updates to entity-server
|
||||
|
||||
SetOfMotionStates _physicalObjects; // MotionStates of entities in PhysicsEngine
|
||||
|
||||
|
|
|
@ -143,12 +143,35 @@ void PhysicsEngine::addObjectToDynamicsWorld(ObjectMotionState* motionState) {
|
|||
}
|
||||
|
||||
void PhysicsEngine::removeObjects(const VectorOfMotionStates& objects) {
|
||||
// first bump and prune contacts for all objects in the list
|
||||
// bump and prune contacts for all objects in the list
|
||||
for (auto object : objects) {
|
||||
bumpAndPruneContacts(object);
|
||||
}
|
||||
|
||||
// then remove them
|
||||
if (_activeStaticBodies.size() > 0) {
|
||||
// _activeStaticBodies was not cleared last frame.
|
||||
// The only way to get here is if a static object were moved but we did not actually step the simulation last
|
||||
// frame (because the framerate is faster than our physics simulation rate). When this happens we must scan
|
||||
// _activeStaticBodies for objects that were recently deleted so we don't try to access a dangling pointer.
|
||||
for (auto object : objects) {
|
||||
btRigidBody* body = object->getRigidBody();
|
||||
|
||||
std::vector<btRigidBody*>::reverse_iterator itr = _activeStaticBodies.rbegin();
|
||||
while (itr != _activeStaticBodies.rend()) {
|
||||
if (body == *itr) {
|
||||
if (*itr != *(_activeStaticBodies.rbegin())) {
|
||||
// swap with rbegin
|
||||
*itr = *(_activeStaticBodies.rbegin());
|
||||
}
|
||||
_activeStaticBodies.pop_back();
|
||||
break;
|
||||
}
|
||||
++itr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove bodies
|
||||
for (auto object : objects) {
|
||||
btRigidBody* body = object->getRigidBody();
|
||||
if (body) {
|
||||
|
@ -449,7 +472,7 @@ const CollisionEvents& PhysicsEngine::getCollisionEvents() {
|
|||
return _collisionEvents;
|
||||
}
|
||||
|
||||
const VectorOfMotionStates& PhysicsEngine::getOutgoingChanges() {
|
||||
const VectorOfMotionStates& PhysicsEngine::getChangedMotionStates() {
|
||||
BT_PROFILE("copyOutgoingChanges");
|
||||
// Bullet will not deactivate static objects (it doesn't expect them to be active)
|
||||
// so we must deactivate them ourselves
|
||||
|
|
|
@ -65,7 +65,8 @@ public:
|
|||
bool hasOutgoingChanges() const { return _hasOutgoingChanges; }
|
||||
|
||||
/// \return reference to list of changed MotionStates. The list is only valid until beginning of next simulation loop.
|
||||
const VectorOfMotionStates& getOutgoingChanges();
|
||||
const VectorOfMotionStates& getChangedMotionStates();
|
||||
const VectorOfMotionStates& getDeactivatedMotionStates() const { return _dynamicsWorld->getDeactivatedMotionStates(); }
|
||||
|
||||
/// \return reference to list of Collision events. The list is only valid until beginning of next simulation loop.
|
||||
const CollisionEvents& getCollisionEvents();
|
||||
|
|
|
@ -120,30 +120,41 @@ void ThreadSafeDynamicsWorld::synchronizeMotionState(btRigidBody* body) {
|
|||
void ThreadSafeDynamicsWorld::synchronizeMotionStates() {
|
||||
BT_PROFILE("synchronizeMotionStates");
|
||||
_changedMotionStates.clear();
|
||||
|
||||
// NOTE: m_synchronizeAllMotionStates is 'false' by default for optimization.
|
||||
// See PhysicsEngine::init() where we call _dynamicsWorld->setForceUpdateAllAabbs(false)
|
||||
if (m_synchronizeAllMotionStates) {
|
||||
//iterate over all collision objects
|
||||
for (int i=0;i<m_collisionObjects.size();i++) {
|
||||
btCollisionObject* colObj = m_collisionObjects[i];
|
||||
btRigidBody* body = btRigidBody::upcast(colObj);
|
||||
if (body) {
|
||||
if (body->getMotionState()) {
|
||||
synchronizeMotionState(body);
|
||||
_changedMotionStates.push_back(static_cast<ObjectMotionState*>(body->getMotionState()));
|
||||
}
|
||||
if (body && body->getMotionState()) {
|
||||
synchronizeMotionState(body);
|
||||
_changedMotionStates.push_back(static_cast<ObjectMotionState*>(body->getMotionState()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//iterate over all active rigid bodies
|
||||
// TODO? if this becomes a performance bottleneck we could derive our own SimulationIslandManager
|
||||
// that remembers a list of objects deactivated last step
|
||||
_activeStates.clear();
|
||||
_deactivatedStates.clear();
|
||||
for (int i=0;i<m_nonStaticRigidBodies.size();i++) {
|
||||
btRigidBody* body = m_nonStaticRigidBodies[i];
|
||||
if (body->isActive()) {
|
||||
if (body->getMotionState()) {
|
||||
ObjectMotionState* motionState = static_cast<ObjectMotionState*>(body->getMotionState());
|
||||
if (motionState) {
|
||||
if (body->isActive()) {
|
||||
synchronizeMotionState(body);
|
||||
_changedMotionStates.push_back(static_cast<ObjectMotionState*>(body->getMotionState()));
|
||||
_changedMotionStates.push_back(motionState);
|
||||
_activeStates.insert(motionState);
|
||||
} else if (_lastActiveStates.find(motionState) != _lastActiveStates.end()) {
|
||||
// this object was active last frame but is no longer
|
||||
_deactivatedStates.push_back(motionState);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_activeStates.swap(_lastActiveStates);
|
||||
}
|
||||
|
||||
void ThreadSafeDynamicsWorld::saveKinematicState(btScalar timeStep) {
|
||||
|
|
|
@ -49,12 +49,16 @@ public:
|
|||
float getLocalTimeAccumulation() const { return m_localTime; }
|
||||
|
||||
const VectorOfMotionStates& getChangedMotionStates() const { return _changedMotionStates; }
|
||||
const VectorOfMotionStates& getDeactivatedMotionStates() const { return _deactivatedStates; }
|
||||
|
||||
private:
|
||||
// call this instead of non-virtual btDiscreteDynamicsWorld::synchronizeSingleMotionState()
|
||||
void synchronizeMotionState(btRigidBody* body);
|
||||
|
||||
VectorOfMotionStates _changedMotionStates;
|
||||
VectorOfMotionStates _deactivatedStates;
|
||||
SetOfMotionStates _activeStates;
|
||||
SetOfMotionStates _lastActiveStates;
|
||||
};
|
||||
|
||||
#endif // hifi_ThreadSafeDynamicsWorld_h
|
||||
|
|
|
@ -33,6 +33,7 @@ void Deck::queueClip(ClipPointer clip, float timeOffset) {
|
|||
|
||||
// FIXME disabling multiple clips for now
|
||||
_clips.clear();
|
||||
_length = 0.0f;
|
||||
|
||||
// if the time offset is not zero, wrap in an OffsetClip
|
||||
if (timeOffset != 0.0f) {
|
||||
|
@ -153,8 +154,8 @@ void Deck::processFrames() {
|
|||
// if doing relative movement
|
||||
emit looped();
|
||||
} else {
|
||||
// otherwise pause playback
|
||||
pause();
|
||||
// otherwise stop playback
|
||||
stop();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -403,7 +403,6 @@ void AnimDebugDraw::update() {
|
|||
for (auto& iter : rays) {
|
||||
addLine(std::get<0>(iter), std::get<1>(iter), std::get<2>(iter), v);
|
||||
}
|
||||
DebugDraw::getInstance().clearRays();
|
||||
|
||||
data._vertexBuffer->resize(sizeof(AnimDebugDrawData::Vertex) * numVerts);
|
||||
data._vertexBuffer->setSubData<AnimDebugDrawData::Vertex>(0, vertices);
|
||||
|
|
|
@ -21,7 +21,6 @@ void FramebufferCache::setFrameBufferSize(QSize frameBufferSize) {
|
|||
//If the size changed, we need to delete our FBOs
|
||||
if (_frameBufferSize != frameBufferSize) {
|
||||
_frameBufferSize = frameBufferSize;
|
||||
_selfieFramebuffer.reset();
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(_mutex);
|
||||
_cachedFramebuffers.clear();
|
||||
|
@ -30,16 +29,8 @@ void FramebufferCache::setFrameBufferSize(QSize frameBufferSize) {
|
|||
}
|
||||
|
||||
void FramebufferCache::createPrimaryFramebuffer() {
|
||||
auto colorFormat = gpu::Element::COLOR_SRGBA_32;
|
||||
auto width = _frameBufferSize.width();
|
||||
auto height = _frameBufferSize.height();
|
||||
|
||||
auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT);
|
||||
|
||||
_selfieFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("selfie"));
|
||||
auto tex = gpu::TexturePointer(gpu::Texture::create2D(colorFormat, width * 0.5, height * 0.5, defaultSampler));
|
||||
_selfieFramebuffer->setRenderBuffer(0, tex);
|
||||
|
||||
auto smoothSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR);
|
||||
}
|
||||
|
||||
|
@ -60,10 +51,3 @@ void FramebufferCache::releaseFramebuffer(const gpu::FramebufferPointer& framebu
|
|||
_cachedFramebuffers.push_back(framebuffer);
|
||||
}
|
||||
}
|
||||
|
||||
gpu::FramebufferPointer FramebufferCache::getSelfieFramebuffer() {
|
||||
if (!_selfieFramebuffer) {
|
||||
createPrimaryFramebuffer();
|
||||
}
|
||||
return _selfieFramebuffer;
|
||||
}
|
||||
|
|
|
@ -27,9 +27,6 @@ public:
|
|||
void setFrameBufferSize(QSize frameBufferSize);
|
||||
const QSize& getFrameBufferSize() const { return _frameBufferSize; }
|
||||
|
||||
/// Returns the framebuffer object used to render selfie maps;
|
||||
gpu::FramebufferPointer getSelfieFramebuffer();
|
||||
|
||||
/// Returns a free framebuffer with a single color attachment for temp or intra-frame operations
|
||||
gpu::FramebufferPointer getFramebuffer();
|
||||
|
||||
|
@ -42,8 +39,6 @@ private:
|
|||
|
||||
gpu::FramebufferPointer _shadowFramebuffer;
|
||||
|
||||
gpu::FramebufferPointer _selfieFramebuffer;
|
||||
|
||||
QSize _frameBufferSize{ 100, 100 };
|
||||
|
||||
std::mutex _mutex;
|
||||
|
|
|
@ -133,6 +133,7 @@ void LightingModel::setSpotLight(bool enable) {
|
|||
bool LightingModel::isSpotLightEnabled() const {
|
||||
return (bool)_parametersBuffer.get<Parameters>().enableSpotLight;
|
||||
}
|
||||
|
||||
void LightingModel::setShowLightContour(bool enable) {
|
||||
if (enable != isShowLightContourEnabled()) {
|
||||
_parametersBuffer.edit<Parameters>().showLightContour = (float)enable;
|
||||
|
@ -142,6 +143,14 @@ bool LightingModel::isShowLightContourEnabled() const {
|
|||
return (bool)_parametersBuffer.get<Parameters>().showLightContour;
|
||||
}
|
||||
|
||||
void LightingModel::setWireframe(bool enable) {
|
||||
if (enable != isWireframeEnabled()) {
|
||||
_parametersBuffer.edit<Parameters>().enableWireframe = (float)enable;
|
||||
}
|
||||
}
|
||||
bool LightingModel::isWireframeEnabled() const {
|
||||
return (bool)_parametersBuffer.get<Parameters>().enableWireframe;
|
||||
}
|
||||
MakeLightingModel::MakeLightingModel() {
|
||||
_lightingModel = std::make_shared<LightingModel>();
|
||||
}
|
||||
|
@ -167,6 +176,7 @@ void MakeLightingModel::configure(const Config& config) {
|
|||
_lightingModel->setSpotLight(config.enableSpotLight);
|
||||
|
||||
_lightingModel->setShowLightContour(config.showLightContour);
|
||||
_lightingModel->setWireframe(config.enableWireframe);
|
||||
}
|
||||
|
||||
void MakeLightingModel::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, LightingModelPointer& lightingModel) {
|
||||
|
|
|
@ -64,6 +64,9 @@ public:
|
|||
void setShowLightContour(bool enable);
|
||||
bool isShowLightContourEnabled() const;
|
||||
|
||||
void setWireframe(bool enable);
|
||||
bool isWireframeEnabled() const;
|
||||
|
||||
UniformBufferView getParametersBuffer() const { return _parametersBuffer; }
|
||||
|
||||
protected:
|
||||
|
@ -89,13 +92,12 @@ protected:
|
|||
float enablePointLight{ 1.0f };
|
||||
float enableSpotLight{ 1.0f };
|
||||
|
||||
float showLightContour{ 0.0f }; // false by default
|
||||
float showLightContour { 0.0f }; // false by default
|
||||
|
||||
float enableObscurance{ 1.0f };
|
||||
|
||||
float enableMaterialTexturing { 1.0f };
|
||||
|
||||
float spares{ 0.0f };
|
||||
float enableWireframe { 0.0f }; // false by default
|
||||
|
||||
Parameters() {}
|
||||
};
|
||||
|
@ -129,6 +131,7 @@ class MakeLightingModelConfig : public render::Job::Config {
|
|||
Q_PROPERTY(bool enablePointLight MEMBER enablePointLight NOTIFY dirty)
|
||||
Q_PROPERTY(bool enableSpotLight MEMBER enableSpotLight NOTIFY dirty)
|
||||
|
||||
Q_PROPERTY(bool enableWireframe MEMBER enableWireframe NOTIFY dirty)
|
||||
Q_PROPERTY(bool showLightContour MEMBER showLightContour NOTIFY dirty)
|
||||
|
||||
public:
|
||||
|
@ -152,9 +155,10 @@ public:
|
|||
bool enablePointLight{ true };
|
||||
bool enableSpotLight{ true };
|
||||
|
||||
|
||||
bool showLightContour { false }; // false by default
|
||||
|
||||
bool enableWireframe { false }; // false by default
|
||||
|
||||
signals:
|
||||
void dirty();
|
||||
};
|
||||
|
|
|
@ -17,7 +17,7 @@ struct LightingModel {
|
|||
vec4 _UnlitEmissiveLightmapBackground;
|
||||
vec4 _ScatteringDiffuseSpecularAlbedo;
|
||||
vec4 _AmbientDirectionalPointSpot;
|
||||
vec4 _ShowContourObscuranceSpare2;
|
||||
vec4 _ShowContourObscuranceWireframe;
|
||||
};
|
||||
|
||||
uniform lightingModelBuffer{
|
||||
|
@ -37,7 +37,7 @@ float isBackgroundEnabled() {
|
|||
return lightingModel._UnlitEmissiveLightmapBackground.w;
|
||||
}
|
||||
float isObscuranceEnabled() {
|
||||
return lightingModel._ShowContourObscuranceSpare2.y;
|
||||
return lightingModel._ShowContourObscuranceWireframe.y;
|
||||
}
|
||||
|
||||
float isScatteringEnabled() {
|
||||
|
@ -67,9 +67,12 @@ float isSpotEnabled() {
|
|||
}
|
||||
|
||||
float isShowLightContour() {
|
||||
return lightingModel._ShowContourObscuranceSpare2.x;
|
||||
return lightingModel._ShowContourObscuranceWireframe.x;
|
||||
}
|
||||
|
||||
float isWireframeEnabled() {
|
||||
return lightingModel._ShowContourObscuranceWireframe.z;
|
||||
}
|
||||
|
||||
<@endfunc@>
|
||||
<$declareLightingModel()$>
|
||||
|
|
|
@ -372,19 +372,12 @@ void ModelMeshPartPayload::notifyLocationChanged() {
|
|||
|
||||
}
|
||||
|
||||
void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& transform, const QVector<glm::mat4>& clusterMatrices) {
|
||||
_transform = transform;
|
||||
|
||||
if (clusterMatrices.size() > 0) {
|
||||
_worldBound = _adjustedLocalBound;
|
||||
_worldBound.transform(_transform);
|
||||
if (clusterMatrices.size() == 1) {
|
||||
_transform = _transform.worldTransform(Transform(clusterMatrices[0]));
|
||||
}
|
||||
} else {
|
||||
_worldBound = _localBound;
|
||||
_worldBound.transform(_transform);
|
||||
}
|
||||
void ModelMeshPartPayload::updateTransformForSkinnedMesh(const Transform& renderTransform, const Transform& boundTransform,
|
||||
const gpu::BufferPointer& buffer) {
|
||||
_transform = renderTransform;
|
||||
_worldBound = _adjustedLocalBound;
|
||||
_worldBound.transform(boundTransform);
|
||||
_clusterBuffer = buffer;
|
||||
}
|
||||
|
||||
ItemKey ModelMeshPartPayload::getKey() const {
|
||||
|
@ -532,9 +525,8 @@ void ModelMeshPartPayload::bindMesh(gpu::Batch& batch) const {
|
|||
|
||||
void ModelMeshPartPayload::bindTransform(gpu::Batch& batch, const ShapePipeline::LocationsPointer locations, RenderArgs::RenderMode renderMode) const {
|
||||
// Still relying on the raw data from the model
|
||||
const Model::MeshState& state = _model->getMeshState(_meshIndex);
|
||||
if (state.clusterBuffer) {
|
||||
batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, state.clusterBuffer);
|
||||
if (_clusterBuffer) {
|
||||
batch.setUniformBuffer(ShapePipeline::Slot::BUFFER::SKINNING, _clusterBuffer);
|
||||
}
|
||||
batch.setModelTransform(_transform);
|
||||
}
|
||||
|
@ -590,8 +582,6 @@ void ModelMeshPartPayload::render(RenderArgs* args) const {
|
|||
auto locations = args->_pipeline->locations;
|
||||
assert(locations);
|
||||
|
||||
// Bind the model transform and the skinCLusterMatrices if needed
|
||||
_model->updateClusterMatrices();
|
||||
bindTransform(batch, locations, args->_renderMode);
|
||||
|
||||
//Bind the index buffer and vertex buffer and Blend shapes if needed
|
||||
|
|
|
@ -89,8 +89,9 @@ public:
|
|||
typedef Payload::DataPointer Pointer;
|
||||
|
||||
void notifyLocationChanged() override;
|
||||
void updateTransformForSkinnedMesh(const Transform& transform,
|
||||
const QVector<glm::mat4>& clusterMatrices);
|
||||
void updateTransformForSkinnedMesh(const Transform& renderTransform,
|
||||
const Transform& boundTransform,
|
||||
const gpu::BufferPointer& buffer);
|
||||
|
||||
float computeFadeAlpha() const;
|
||||
|
||||
|
@ -108,6 +109,7 @@ public:
|
|||
|
||||
void computeAdjustedLocalBound(const QVector<glm::mat4>& clusterMatrices);
|
||||
|
||||
gpu::BufferPointer _clusterBuffer;
|
||||
Model* _model;
|
||||
|
||||
int _meshIndex;
|
||||
|
|
|
@ -227,6 +227,10 @@ void Model::updateRenderItems() {
|
|||
return;
|
||||
}
|
||||
|
||||
// lazy update of cluster matrices used for rendering.
|
||||
// We need to update them here so we can correctly update the bounding box.
|
||||
self->updateClusterMatrices();
|
||||
|
||||
render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
|
||||
|
||||
uint32_t deleteGeometryCounter = self->_deleteGeometryCounter;
|
||||
|
@ -240,12 +244,12 @@ void Model::updateRenderItems() {
|
|||
Transform modelTransform = data._model->getTransform();
|
||||
modelTransform.setScale(glm::vec3(1.0f));
|
||||
|
||||
// lazy update of cluster matrices used for rendering. We need to update them here, so we can correctly update the bounding box.
|
||||
data._model->updateClusterMatrices();
|
||||
|
||||
// update the model transform and bounding box for this render item.
|
||||
const Model::MeshState& state = data._model->_meshStates.at(data._meshIndex);
|
||||
data.updateTransformForSkinnedMesh(modelTransform, state.clusterMatrices);
|
||||
const Model::MeshState& state = data._model->getMeshState(data._meshIndex);
|
||||
Transform renderTransform = modelTransform;
|
||||
if (state.clusterMatrices.size() == 1) {
|
||||
renderTransform = modelTransform.worldTransform(Transform(state.clusterMatrices[0]));
|
||||
}
|
||||
data.updateTransformForSkinnedMesh(renderTransform, modelTransform, state.clusterBuffer);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -1048,7 +1052,7 @@ void Model::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
}
|
||||
|
||||
void Model::computeMeshPartLocalBounds() {
|
||||
for (auto& part : _modelMeshRenderItemsSet) {
|
||||
for (auto& part : _modelMeshRenderItemsSet) {
|
||||
assert(part->_meshIndex < _modelMeshRenderItemsSet.size());
|
||||
const Model::MeshState& state = _meshStates.at(part->_meshIndex);
|
||||
part->computeAdjustedLocalBound(state.clusterMatrices);
|
||||
|
|
|
@ -259,8 +259,18 @@ void DrawDeferred::run(const SceneContextPointer& sceneContext, const RenderCont
|
|||
// Setup lighting model for all items;
|
||||
batch.setUniformBuffer(render::ShapePipeline::Slot::LIGHTING_MODEL, lightingModel->getParametersBuffer());
|
||||
|
||||
renderShapes(sceneContext, renderContext, _shapePlumber, inItems, _maxDrawn);
|
||||
// From the lighting model define a global shapKey ORED with individiual keys
|
||||
ShapeKey::Builder keyBuilder;
|
||||
if (lightingModel->isWireframeEnabled()) {
|
||||
keyBuilder.withWireframe();
|
||||
}
|
||||
ShapeKey globalKey = keyBuilder.build();
|
||||
args->_globalShapeKey = globalKey._flags.to_ulong();
|
||||
|
||||
renderShapes(sceneContext, renderContext, _shapePlumber, inItems, _maxDrawn, globalKey);
|
||||
|
||||
args->_batch = nullptr;
|
||||
args->_globalShapeKey = 0;
|
||||
});
|
||||
|
||||
config->setNumDrawn((int)inItems.size());
|
||||
|
@ -295,12 +305,21 @@ void DrawStateSortDeferred::run(const SceneContextPointer& sceneContext, const R
|
|||
// Setup lighting model for all items;
|
||||
batch.setUniformBuffer(render::ShapePipeline::Slot::LIGHTING_MODEL, lightingModel->getParametersBuffer());
|
||||
|
||||
// From the lighting model define a global shapKey ORED with individiual keys
|
||||
ShapeKey::Builder keyBuilder;
|
||||
if (lightingModel->isWireframeEnabled()) {
|
||||
keyBuilder.withWireframe();
|
||||
}
|
||||
ShapeKey globalKey = keyBuilder.build();
|
||||
args->_globalShapeKey = globalKey._flags.to_ulong();
|
||||
|
||||
if (_stateSort) {
|
||||
renderStateSortShapes(sceneContext, renderContext, _shapePlumber, inItems, _maxDrawn);
|
||||
renderStateSortShapes(sceneContext, renderContext, _shapePlumber, inItems, _maxDrawn, globalKey);
|
||||
} else {
|
||||
renderShapes(sceneContext, renderContext, _shapePlumber, inItems, _maxDrawn);
|
||||
renderShapes(sceneContext, renderContext, _shapePlumber, inItems, _maxDrawn, globalKey);
|
||||
}
|
||||
args->_batch = nullptr;
|
||||
args->_globalShapeKey = 0;
|
||||
});
|
||||
|
||||
config->setNumDrawn((int)inItems.size());
|
||||
|
|
|
@ -307,7 +307,7 @@ void initForwardPipelines(render::ShapePlumber& plumber) {
|
|||
void addPlumberPipeline(ShapePlumber& plumber,
|
||||
const ShapeKey& key, const gpu::ShaderPointer& vertex, const gpu::ShaderPointer& pixel) {
|
||||
// These key-values' pipelines are added by this functor in addition to the key passed
|
||||
assert(!key.isWireFrame());
|
||||
assert(!key.isWireframe());
|
||||
assert(!key.isDepthBiased());
|
||||
assert(key.isCullFace());
|
||||
|
||||
|
|
|
@ -39,9 +39,9 @@ void render::renderItems(const SceneContextPointer& sceneContext, const RenderCo
|
|||
}
|
||||
}
|
||||
|
||||
void renderShape(RenderArgs* args, const ShapePlumberPointer& shapeContext, const Item& item) {
|
||||
void renderShape(RenderArgs* args, const ShapePlumberPointer& shapeContext, const Item& item, const ShapeKey& globalKey) {
|
||||
assert(item.getKey().isShape());
|
||||
const auto& key = item.getShapeKey();
|
||||
auto key = item.getShapeKey() | globalKey;
|
||||
if (key.isValid() && !key.hasOwnPipeline()) {
|
||||
args->_pipeline = shapeContext->pickPipeline(args, key);
|
||||
if (args->_pipeline) {
|
||||
|
@ -56,7 +56,7 @@ void renderShape(RenderArgs* args, const ShapePlumberPointer& shapeContext, cons
|
|||
}
|
||||
|
||||
void render::renderShapes(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext,
|
||||
const ShapePlumberPointer& shapeContext, const ItemBounds& inItems, int maxDrawnItems) {
|
||||
const ShapePlumberPointer& shapeContext, const ItemBounds& inItems, int maxDrawnItems, const ShapeKey& globalKey) {
|
||||
auto& scene = sceneContext->_scene;
|
||||
RenderArgs* args = renderContext->args;
|
||||
|
||||
|
@ -66,12 +66,12 @@ void render::renderShapes(const SceneContextPointer& sceneContext, const RenderC
|
|||
}
|
||||
for (auto i = 0; i < numItemsToDraw; ++i) {
|
||||
auto& item = scene->getItem(inItems[i].id);
|
||||
renderShape(args, shapeContext, item);
|
||||
renderShape(args, shapeContext, item, globalKey);
|
||||
}
|
||||
}
|
||||
|
||||
void render::renderStateSortShapes(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext,
|
||||
const ShapePlumberPointer& shapeContext, const ItemBounds& inItems, int maxDrawnItems) {
|
||||
const ShapePlumberPointer& shapeContext, const ItemBounds& inItems, int maxDrawnItems, const ShapeKey& globalKey) {
|
||||
auto& scene = sceneContext->_scene;
|
||||
RenderArgs* args = renderContext->args;
|
||||
|
||||
|
@ -91,7 +91,7 @@ void render::renderStateSortShapes(const SceneContextPointer& sceneContext, cons
|
|||
|
||||
{
|
||||
assert(item.getKey().isShape());
|
||||
const auto key = item.getShapeKey();
|
||||
auto key = item.getShapeKey() | globalKey;
|
||||
if (key.isValid() && !key.hasOwnPipeline()) {
|
||||
auto& bucket = sortedShapes[key];
|
||||
if (bucket.empty()) {
|
||||
|
|
|
@ -17,8 +17,8 @@
|
|||
namespace render {
|
||||
|
||||
void renderItems(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ItemBounds& inItems, int maxDrawnItems = -1);
|
||||
void renderShapes(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ShapePlumberPointer& shapeContext, const ItemBounds& inItems, int maxDrawnItems = -1);
|
||||
void renderStateSortShapes(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ShapePlumberPointer& shapeContext, const ItemBounds& inItems, int maxDrawnItems = -1);
|
||||
void renderShapes(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ShapePlumberPointer& shapeContext, const ItemBounds& inItems, int maxDrawnItems = -1, const ShapeKey& globalKey = ShapeKey());
|
||||
void renderStateSortShapes(const SceneContextPointer& sceneContext, const RenderContextPointer& renderContext, const ShapePlumberPointer& shapeContext, const ItemBounds& inItems, int maxDrawnItems = -1, const ShapeKey& globalKey = ShapeKey());
|
||||
|
||||
class DrawLightConfig : public Job::Config {
|
||||
Q_OBJECT
|
||||
|
|
|
@ -46,6 +46,10 @@ public:
|
|||
ShapeKey() : _flags{ 0 } {}
|
||||
ShapeKey(const Flags& flags) : _flags{flags} {}
|
||||
|
||||
friend ShapeKey operator&(const ShapeKey& _Left, const ShapeKey& _Right) { return ShapeKey(_Left._flags & _Right._flags); }
|
||||
friend ShapeKey operator|(const ShapeKey& _Left, const ShapeKey& _Right) { return ShapeKey(_Left._flags | _Right._flags); }
|
||||
friend ShapeKey operator^(const ShapeKey& _Left, const ShapeKey& _Right) { return ShapeKey(_Left._flags ^ _Right._flags); }
|
||||
|
||||
class Builder {
|
||||
public:
|
||||
Builder() {}
|
||||
|
@ -144,7 +148,7 @@ public:
|
|||
bool isSkinned() const { return _flags[SKINNED]; }
|
||||
bool isDepthOnly() const { return _flags[DEPTH_ONLY]; }
|
||||
bool isDepthBiased() const { return _flags[DEPTH_BIAS]; }
|
||||
bool isWireFrame() const { return _flags[WIREFRAME]; }
|
||||
bool isWireframe() const { return _flags[WIREFRAME]; }
|
||||
bool isCullFace() const { return !_flags[NO_CULL_FACE]; }
|
||||
|
||||
bool hasOwnPipeline() const { return _flags[OWN_PIPELINE]; }
|
||||
|
@ -180,7 +184,7 @@ inline QDebug operator<<(QDebug debug, const ShapeKey& key) {
|
|||
<< "isSkinned:" << key.isSkinned()
|
||||
<< "isDepthOnly:" << key.isDepthOnly()
|
||||
<< "isDepthBiased:" << key.isDepthBiased()
|
||||
<< "isWireFrame:" << key.isWireFrame()
|
||||
<< "isWireframe:" << key.isWireframe()
|
||||
<< "isCullFace:" << key.isCullFace()
|
||||
<< "]";
|
||||
}
|
||||
|
|
|
@ -1,67 +0,0 @@
|
|||
//
|
||||
// BaseScriptEngine.h
|
||||
// libraries/script-engine/src
|
||||
//
|
||||
// Created by Timothy Dedischew on 02/01/17.
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_BaseScriptEngine_h
|
||||
#define hifi_BaseScriptEngine_h
|
||||
|
||||
#include <functional>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtScript/QScriptEngine>
|
||||
|
||||
#include "SettingHandle.h"
|
||||
|
||||
// common base class for extending QScriptEngine itself
|
||||
class BaseScriptEngine : public QScriptEngine {
|
||||
Q_OBJECT
|
||||
public:
|
||||
static const QString SCRIPT_EXCEPTION_FORMAT;
|
||||
static const QString SCRIPT_BACKTRACE_SEP;
|
||||
|
||||
BaseScriptEngine() {}
|
||||
|
||||
Q_INVOKABLE QScriptValue evaluateInClosure(const QScriptValue& locals, const QScriptProgram& program);
|
||||
|
||||
Q_INVOKABLE QScriptValue lintScript(const QString& sourceCode, const QString& fileName, const int lineNumber = 1);
|
||||
Q_INVOKABLE QScriptValue makeError(const QScriptValue& other = QScriptValue(), const QString& type = "Error");
|
||||
Q_INVOKABLE QString formatException(const QScriptValue& exception);
|
||||
QScriptValue cloneUncaughtException(const QString& detail = QString());
|
||||
|
||||
signals:
|
||||
void unhandledException(const QScriptValue& exception);
|
||||
|
||||
protected:
|
||||
void _emitUnhandledException(const QScriptValue& exception);
|
||||
QScriptValue newLambdaFunction(std::function<QScriptValue(QScriptContext *context, QScriptEngine* engine)> operation, const QScriptValue& data = QScriptValue(), const QScriptEngine::ValueOwnership& ownership = QScriptEngine::AutoOwnership);
|
||||
|
||||
static const QString _SETTINGS_ENABLE_EXTENDED_EXCEPTIONS;
|
||||
Setting::Handle<bool> _enableExtendedJSExceptions { _SETTINGS_ENABLE_EXTENDED_EXCEPTIONS, true };
|
||||
#ifdef DEBUG_JS
|
||||
static void _debugDump(const QString& header, const QScriptValue& object, const QString& footer = QString());
|
||||
#endif
|
||||
};
|
||||
|
||||
// Lambda helps create callable QScriptValues out of std::functions:
|
||||
// (just meant for use from within the script engine itself)
|
||||
class Lambda : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Lambda(QScriptEngine *engine, std::function<QScriptValue(QScriptContext *context, QScriptEngine* engine)> operation, QScriptValue data);
|
||||
~Lambda();
|
||||
public slots:
|
||||
QScriptValue call();
|
||||
QString toString() const;
|
||||
private:
|
||||
QScriptEngine* engine;
|
||||
std::function<QScriptValue(QScriptContext *context, QScriptEngine* engine)> operation;
|
||||
QScriptValue data;
|
||||
};
|
||||
|
||||
#endif // hifi_BaseScriptEngine_h
|
41
libraries/script-engine/src/MeshProxy.h
Normal file
41
libraries/script-engine/src/MeshProxy.h
Normal file
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// MeshProxy.h
|
||||
// libraries/script-engine/src
|
||||
//
|
||||
// Created by Seth Alves on 2017-1-27.
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_MeshProxy_h
|
||||
#define hifi_MeshProxy_h
|
||||
|
||||
#include <model/Geometry.h>
|
||||
|
||||
using MeshPointer = std::shared_ptr<model::Mesh>;
|
||||
|
||||
class MeshProxy : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MeshProxy(MeshPointer mesh) : _mesh(mesh) {}
|
||||
~MeshProxy() {}
|
||||
|
||||
MeshPointer getMeshPointer() const { return _mesh; }
|
||||
|
||||
Q_INVOKABLE int getNumVertices() const { return (int)_mesh->getNumVertices(); }
|
||||
Q_INVOKABLE glm::vec3 getPos3(int index) const { return _mesh->getPos3(index); }
|
||||
|
||||
|
||||
protected:
|
||||
MeshPointer _mesh;
|
||||
};
|
||||
|
||||
Q_DECLARE_METATYPE(MeshProxy*);
|
||||
|
||||
class MeshProxyList : public QList<MeshProxy*> {}; // typedef and using fight with the Qt macros/templates, do this instead
|
||||
Q_DECLARE_METATYPE(MeshProxyList);
|
||||
|
||||
#endif // hifi_MeshProxy_h
|
53
libraries/script-engine/src/ModelScriptingInterface.cpp
Normal file
53
libraries/script-engine/src/ModelScriptingInterface.cpp
Normal file
|
@ -0,0 +1,53 @@
|
|||
//
|
||||
// ModelScriptingInterface.cpp
|
||||
// libraries/script-engine/src
|
||||
//
|
||||
// Created by Seth Alves on 2017-1-27.
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QScriptEngine>
|
||||
#include <QScriptValueIterator>
|
||||
#include <QtScript/QScriptValue>
|
||||
#include "ModelScriptingInterface.h"
|
||||
#include "OBJWriter.h"
|
||||
|
||||
|
||||
ModelScriptingInterface::ModelScriptingInterface(QObject* parent) : QObject(parent) {
|
||||
}
|
||||
|
||||
QScriptValue meshToScriptValue(QScriptEngine* engine, MeshProxy* const &in) {
|
||||
return engine->newQObject(in, QScriptEngine::QtOwnership,
|
||||
QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects);
|
||||
}
|
||||
|
||||
void meshFromScriptValue(const QScriptValue& value, MeshProxy* &out) {
|
||||
out = qobject_cast<MeshProxy*>(value.toQObject());
|
||||
}
|
||||
|
||||
QScriptValue meshesToScriptValue(QScriptEngine* engine, const MeshProxyList &in) {
|
||||
return engine->toScriptValue(in);
|
||||
}
|
||||
|
||||
void meshesFromScriptValue(const QScriptValue& value, MeshProxyList &out) {
|
||||
QScriptValueIterator itr(value);
|
||||
while(itr.hasNext()) {
|
||||
itr.next();
|
||||
MeshProxy* meshProxy = qscriptvalue_cast<MeshProxyList::value_type>(itr.value());
|
||||
if (meshProxy) {
|
||||
out.append(meshProxy);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString ModelScriptingInterface::meshToOBJ(MeshProxyList in) {
|
||||
QList<MeshPointer> meshes;
|
||||
foreach (const MeshProxy* meshProxy, in) {
|
||||
meshes.append(meshProxy->getMeshPointer());
|
||||
}
|
||||
|
||||
return writeOBJToString(meshes);
|
||||
}
|
39
libraries/script-engine/src/ModelScriptingInterface.h
Normal file
39
libraries/script-engine/src/ModelScriptingInterface.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
//
|
||||
// ModelScriptingInterface.h
|
||||
// libraries/script-engine/src
|
||||
//
|
||||
// Created by Seth Alves on 2017-1-27.
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
#ifndef hifi_ModelScriptingInterface_h
|
||||
#define hifi_ModelScriptingInterface_h
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QScriptValue>
|
||||
#include <OBJWriter.h>
|
||||
#include <model/Geometry.h>
|
||||
#include "MeshProxy.h"
|
||||
|
||||
using MeshPointer = std::shared_ptr<model::Mesh>;
|
||||
|
||||
class ModelScriptingInterface : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ModelScriptingInterface(QObject* parent);
|
||||
|
||||
Q_INVOKABLE QString meshToOBJ(MeshProxyList in);
|
||||
};
|
||||
|
||||
QScriptValue meshToScriptValue(QScriptEngine* engine, MeshProxy* const &in);
|
||||
void meshFromScriptValue(const QScriptValue& value, MeshProxy* &out);
|
||||
|
||||
QScriptValue meshesToScriptValue(QScriptEngine* engine, const MeshProxyList &in);
|
||||
void meshesFromScriptValue(const QScriptValue& value, MeshProxyList &out);
|
||||
|
||||
#endif // hifi_ModelScriptingInterface_h
|
|
@ -19,6 +19,9 @@
|
|||
#include <QtCore/QThread>
|
||||
#include <QtCore/QRegularExpression>
|
||||
|
||||
#include <QtCore/QFuture>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
|
||||
#include <QtWidgets/QMainWindow>
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
|
@ -65,18 +68,25 @@
|
|||
#include "RecordingScriptingInterface.h"
|
||||
#include "ScriptEngines.h"
|
||||
#include "TabletScriptingInterface.h"
|
||||
#include "ModelScriptingInterface.h"
|
||||
|
||||
|
||||
#include <Profile.h>
|
||||
|
||||
#include "MIDIEvent.h"
|
||||
|
||||
const QString ScriptEngine::_SETTINGS_ENABLE_EXTENDED_EXCEPTIONS {
|
||||
"com.highfidelity.experimental.enableExtendedJSExceptions"
|
||||
};
|
||||
|
||||
static const int MAX_MODULE_ID_LENGTH { 4096 };
|
||||
static const int MAX_DEBUG_VALUE_LENGTH { 80 };
|
||||
|
||||
static const QScriptEngine::QObjectWrapOptions DEFAULT_QOBJECT_WRAP_OPTIONS =
|
||||
QScriptEngine::ExcludeDeleteLater | QScriptEngine::ExcludeChildObjects;
|
||||
static const QScriptValue::PropertyFlags READONLY_PROP_FLAGS { QScriptValue::ReadOnly | QScriptValue::Undeletable };
|
||||
static const QScriptValue::PropertyFlags READONLY_HIDDEN_PROP_FLAGS { READONLY_PROP_FLAGS | QScriptValue::SkipInEnumeration };
|
||||
|
||||
|
||||
|
||||
static const bool HIFI_AUTOREFRESH_FILE_SCRIPTS { true };
|
||||
|
||||
Q_DECLARE_METATYPE(QScriptEngine::FunctionSignature)
|
||||
|
@ -84,7 +94,7 @@ int functionSignatureMetaID = qRegisterMetaType<QScriptEngine::FunctionSignature
|
|||
|
||||
Q_LOGGING_CATEGORY(scriptengineScript, "hifi.scriptengine.script")
|
||||
|
||||
static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine){
|
||||
static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine) {
|
||||
QString message = "";
|
||||
for (int i = 0; i < context->argumentCount(); i++) {
|
||||
if (i > 0) {
|
||||
|
@ -141,8 +151,8 @@ QString encodeEntityIdIntoEntityUrl(const QString& url, const QString& entityID)
|
|||
}
|
||||
|
||||
QString ScriptEngine::logException(const QScriptValue& exception) {
|
||||
auto message = formatException(exception);
|
||||
scriptErrorMessage(qPrintable(message));
|
||||
auto message = formatException(exception, _enableExtendedJSExceptions.get());
|
||||
scriptErrorMessage(message);
|
||||
return message;
|
||||
}
|
||||
|
||||
|
@ -333,7 +343,7 @@ void ScriptEngine::runInThread() {
|
|||
// The thread interface cannot live on itself, and we want to move this into the thread, so
|
||||
// the thread cannot have this as a parent.
|
||||
QThread* workerThread = new QThread();
|
||||
workerThread->setObjectName(QString("Script Thread:") + getFilename());
|
||||
workerThread->setObjectName(QString("js:") + getFilename().replace("about:",""));
|
||||
moveToThread(workerThread);
|
||||
|
||||
// NOTE: If you connect any essential signals for proper shutdown or cleanup of
|
||||
|
@ -453,7 +463,7 @@ void ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) {
|
|||
}
|
||||
|
||||
void ScriptEngine::scriptErrorMessage(const QString& message) {
|
||||
qCCritical(scriptengine) << message;
|
||||
qCCritical(scriptengine) << qPrintable(message);
|
||||
emit errorMessage(message);
|
||||
}
|
||||
|
||||
|
@ -532,6 +542,40 @@ static QScriptValue createScriptableResourcePrototype(QScriptEngine* engine) {
|
|||
return prototype;
|
||||
}
|
||||
|
||||
void ScriptEngine::resetModuleCache(bool deleteScriptCache) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
executeOnScriptThread([=]() { resetModuleCache(deleteScriptCache); });
|
||||
return;
|
||||
}
|
||||
auto jsRequire = globalObject().property("Script").property("require");
|
||||
auto cache = jsRequire.property("cache");
|
||||
auto cacheMeta = jsRequire.data();
|
||||
|
||||
if (deleteScriptCache) {
|
||||
QScriptValueIterator it(cache);
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
if (it.flags() & QScriptValue::SkipInEnumeration) {
|
||||
continue;
|
||||
}
|
||||
qCDebug(scriptengine) << "resetModuleCache(true) -- staging " << it.name() << " for cache reset at next require";
|
||||
cacheMeta.setProperty(it.name(), true);
|
||||
}
|
||||
}
|
||||
cache = newObject();
|
||||
if (!cacheMeta.isObject()) {
|
||||
cacheMeta = newObject();
|
||||
cacheMeta.setProperty("id", "Script.require.cacheMeta");
|
||||
cacheMeta.setProperty("type", "cacheMeta");
|
||||
jsRequire.setData(cacheMeta);
|
||||
}
|
||||
cache.setProperty("__created__", (double)QDateTime::currentMSecsSinceEpoch(), QScriptValue::SkipInEnumeration);
|
||||
#if DEBUG_JS_MODULES
|
||||
cache.setProperty("__meta__", cacheMeta, READONLY_HIDDEN_PROP_FLAGS);
|
||||
#endif
|
||||
jsRequire.setProperty("cache", cache, READONLY_PROP_FLAGS);
|
||||
}
|
||||
|
||||
void ScriptEngine::init() {
|
||||
if (_isInitialized) {
|
||||
return; // only initialize once
|
||||
|
@ -541,16 +585,6 @@ void ScriptEngine::init() {
|
|||
|
||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||
entityScriptingInterface->init();
|
||||
connect(entityScriptingInterface.data(), &EntityScriptingInterface::deletingEntity, this, [this](const EntityItemID& entityID) {
|
||||
if (_entityScripts.contains(entityID)) {
|
||||
if (isEntityScriptRunning(entityID)) {
|
||||
qCWarning(scriptengine) << "deletingEntity while entity script is still running!" << entityID;
|
||||
}
|
||||
_entityScripts.remove(entityID);
|
||||
emit entityScriptDetailsUpdated();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// register various meta-types
|
||||
registerMetaTypes(this);
|
||||
|
@ -595,6 +629,15 @@ void ScriptEngine::init() {
|
|||
|
||||
registerGlobalObject("Script", this);
|
||||
|
||||
{
|
||||
// set up Script.require.resolve and Script.require.cache
|
||||
auto Script = globalObject().property("Script");
|
||||
auto require = Script.property("require");
|
||||
auto resolve = Script.property("_requireResolve");
|
||||
require.setProperty("resolve", resolve, READONLY_PROP_FLAGS);
|
||||
resetModuleCache();
|
||||
}
|
||||
|
||||
registerGlobalObject("Audio", &AudioScriptingInterface::getInstance());
|
||||
registerGlobalObject("Entities", entityScriptingInterface.data());
|
||||
registerGlobalObject("Quat", &_quatLibrary);
|
||||
|
@ -604,7 +647,7 @@ void ScriptEngine::init() {
|
|||
registerGlobalObject("Messages", DependencyManager::get<MessagesClient>().data());
|
||||
|
||||
registerGlobalObject("File", new FileScriptingInterface(this));
|
||||
|
||||
|
||||
qScriptRegisterMetaType(this, animVarMapToScriptValue, animVarMapFromScriptValue);
|
||||
qScriptRegisterMetaType(this, resultHandlerToScriptValue, resultHandlerFromScriptValue);
|
||||
|
||||
|
@ -622,6 +665,10 @@ void ScriptEngine::init() {
|
|||
registerGlobalObject("Resources", DependencyManager::get<ResourceScriptingInterface>().data());
|
||||
|
||||
registerGlobalObject("DebugDraw", &DebugDraw::getInstance());
|
||||
|
||||
registerGlobalObject("Model", new ModelScriptingInterface(this));
|
||||
qScriptRegisterMetaType(this, meshToScriptValue, meshFromScriptValue);
|
||||
qScriptRegisterMetaType(this, meshesToScriptValue, meshesFromScriptValue);
|
||||
}
|
||||
|
||||
void ScriptEngine::registerValue(const QString& valueName, QScriptValue value) {
|
||||
|
@ -863,6 +910,11 @@ void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString&
|
|||
handlersForEvent << handlerData; // Note that the same handler can be added many times. See removeEntityEventHandler().
|
||||
}
|
||||
|
||||
// this is not redundant -- the version in BaseScriptEngine is specifically not Q_INVOKABLE
|
||||
QScriptValue ScriptEngine::evaluateInClosure(const QScriptValue& closure, const QScriptProgram& program) {
|
||||
return BaseScriptEngine::evaluateInClosure(closure, program);
|
||||
}
|
||||
|
||||
QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fileName, int lineNumber) {
|
||||
if (DependencyManager::get<ScriptEngines>()->isStopped()) {
|
||||
return QScriptValue(); // bail early
|
||||
|
@ -885,29 +937,26 @@ QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fi
|
|||
// Check syntax
|
||||
auto syntaxError = lintScript(sourceCode, fileName);
|
||||
if (syntaxError.isError()) {
|
||||
if (isEvaluating()) {
|
||||
currentContext()->throwValue(syntaxError);
|
||||
} else {
|
||||
if (!isEvaluating()) {
|
||||
syntaxError.setProperty("detail", "evaluate");
|
||||
emit unhandledException(syntaxError);
|
||||
}
|
||||
raiseException(syntaxError);
|
||||
maybeEmitUncaughtException("lint");
|
||||
return syntaxError;
|
||||
}
|
||||
QScriptProgram program { sourceCode, fileName, lineNumber };
|
||||
if (program.isNull()) {
|
||||
// can this happen?
|
||||
auto err = makeError("could not create QScriptProgram for " + fileName);
|
||||
emit unhandledException(err);
|
||||
raiseException(err);
|
||||
maybeEmitUncaughtException("compile");
|
||||
return err;
|
||||
}
|
||||
|
||||
QScriptValue result;
|
||||
{
|
||||
result = BaseScriptEngine::evaluate(program);
|
||||
if (!isEvaluating() && hasUncaughtException()) {
|
||||
emit unhandledException(cloneUncaughtException(__FUNCTION__));
|
||||
clearExceptions();
|
||||
}
|
||||
maybeEmitUncaughtException("evaluate");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -930,10 +979,7 @@ void ScriptEngine::run() {
|
|||
|
||||
{
|
||||
evaluate(_scriptContents, _fileNameString);
|
||||
if (!isEvaluating() && hasUncaughtException()) {
|
||||
emit unhandledException(cloneUncaughtException(__FUNCTION__));
|
||||
clearExceptions();
|
||||
}
|
||||
maybeEmitUncaughtException(__FUNCTION__);
|
||||
}
|
||||
#ifdef _WIN32
|
||||
// VS13 does not sleep_until unless it uses the system_clock, see:
|
||||
|
@ -1304,11 +1350,361 @@ void ScriptEngine::print(const QString& message) {
|
|||
emit printedMessage(message);
|
||||
}
|
||||
|
||||
// Script.require.resolve -- like resolvePath, but performs more validation and throws exceptions on invalid module identifiers (for consistency with Node.js)
|
||||
QString ScriptEngine::_requireResolve(const QString& moduleId, const QString& relativeTo) {
|
||||
if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
|
||||
return QString();
|
||||
}
|
||||
QUrl defaultScriptsLoc = defaultScriptsLocation();
|
||||
QUrl url(moduleId);
|
||||
|
||||
auto displayId = moduleId;
|
||||
if (displayId.length() > MAX_DEBUG_VALUE_LENGTH) {
|
||||
displayId = displayId.mid(0, MAX_DEBUG_VALUE_LENGTH) + "...";
|
||||
}
|
||||
auto message = QString("Cannot find module '%1' (%2)").arg(displayId);
|
||||
|
||||
auto throwResolveError = [&](const QScriptValue& error) -> QString {
|
||||
raiseException(error);
|
||||
maybeEmitUncaughtException("require.resolve");
|
||||
return QString();
|
||||
};
|
||||
|
||||
// de-fuzz the input a little by restricting to rational sizes
|
||||
auto idLength = url.toString().length();
|
||||
if (idLength < 1 || idLength > MAX_MODULE_ID_LENGTH) {
|
||||
auto details = QString("rejecting invalid module id size (%1 chars [1,%2])")
|
||||
.arg(idLength).arg(MAX_MODULE_ID_LENGTH);
|
||||
return throwResolveError(makeError(message.arg(details), "RangeError"));
|
||||
}
|
||||
|
||||
// this regex matches: absolute, dotted or path-like URLs
|
||||
// (ie: the kind of stuff ScriptEngine::resolvePath already handles)
|
||||
QRegularExpression qualified ("^\\w+:|^/|^[.]{1,2}(/|$)");
|
||||
|
||||
// this is for module.require (which is a bound version of require that's always relative to the module path)
|
||||
if (!relativeTo.isEmpty()) {
|
||||
url = QUrl(relativeTo).resolved(moduleId);
|
||||
url = resolvePath(url.toString());
|
||||
} else if (qualified.match(moduleId).hasMatch()) {
|
||||
url = resolvePath(moduleId);
|
||||
} else {
|
||||
// check if the moduleId refers to a "system" module
|
||||
QString systemPath = defaultScriptsLoc.path();
|
||||
QString systemModulePath = QString("%1/modules/%2.js").arg(systemPath).arg(moduleId);
|
||||
url = defaultScriptsLoc;
|
||||
url.setPath(systemModulePath);
|
||||
if (!QFileInfo(url.toLocalFile()).isFile()) {
|
||||
if (!moduleId.contains("./")) {
|
||||
// the user might be trying to refer to a relative file without anchoring it
|
||||
// let's do them a favor and test for that case -- offering specific advice if detected
|
||||
auto unanchoredUrl = resolvePath("./" + moduleId);
|
||||
if (QFileInfo(unanchoredUrl.toLocalFile()).isFile()) {
|
||||
auto msg = QString("relative module ids must be anchored; use './%1' instead")
|
||||
.arg(moduleId);
|
||||
return throwResolveError(makeError(message.arg(msg)));
|
||||
}
|
||||
}
|
||||
return throwResolveError(makeError(message.arg("system module not found")));
|
||||
}
|
||||
}
|
||||
|
||||
if (url.isRelative()) {
|
||||
return throwResolveError(makeError(message.arg("could not resolve module id")));
|
||||
}
|
||||
|
||||
// if it looks like a local file, verify that it's an allowed path and really a file
|
||||
if (url.isLocalFile()) {
|
||||
QFileInfo file(url.toLocalFile());
|
||||
QUrl canonical = url;
|
||||
if (file.exists()) {
|
||||
canonical.setPath(file.canonicalFilePath());
|
||||
}
|
||||
|
||||
bool disallowOutsideFiles = !defaultScriptsLocation().isParentOf(canonical) && !currentSandboxURL.isLocalFile();
|
||||
if (disallowOutsideFiles && !PathUtils::isDescendantOf(canonical, currentSandboxURL)) {
|
||||
return throwResolveError(makeError(message.arg(
|
||||
QString("path '%1' outside of origin script '%2' '%3'")
|
||||
.arg(PathUtils::stripFilename(url))
|
||||
.arg(PathUtils::stripFilename(currentSandboxURL))
|
||||
.arg(canonical.toString())
|
||||
)));
|
||||
}
|
||||
if (!file.exists()) {
|
||||
return throwResolveError(makeError(message.arg("path does not exist: " + url.toLocalFile())));
|
||||
}
|
||||
if (!file.isFile()) {
|
||||
return throwResolveError(makeError(message.arg("path is not a file: " + url.toLocalFile())));
|
||||
}
|
||||
}
|
||||
|
||||
maybeEmitUncaughtException(__FUNCTION__);
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
// retrieves the current parent module from the JS scope chain
|
||||
QScriptValue ScriptEngine::currentModule() {
|
||||
if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
|
||||
return unboundNullValue();
|
||||
}
|
||||
auto jsRequire = globalObject().property("Script").property("require");
|
||||
auto cache = jsRequire.property("cache");
|
||||
auto candidate = QScriptValue();
|
||||
for (auto c = currentContext(); c && !candidate.isObject(); c = c->parentContext()) {
|
||||
QScriptContextInfo contextInfo { c };
|
||||
candidate = cache.property(contextInfo.fileName());
|
||||
}
|
||||
if (!candidate.isObject()) {
|
||||
return QScriptValue();
|
||||
}
|
||||
return candidate;
|
||||
}
|
||||
|
||||
// replaces or adds "module" to "parent.children[]" array
|
||||
// (for consistency with Node.js and userscript cache invalidation without "cache busters")
|
||||
bool ScriptEngine::registerModuleWithParent(const QScriptValue& module, const QScriptValue& parent) {
|
||||
auto children = parent.property("children");
|
||||
if (children.isArray()) {
|
||||
auto key = module.property("id");
|
||||
auto length = children.property("length").toInt32();
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (children.property(i).property("id").strictlyEquals(key)) {
|
||||
qCDebug(scriptengine_module) << key.toString() << " updating parent.children[" << i << "] = module";
|
||||
children.setProperty(i, module);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
qCDebug(scriptengine_module) << key.toString() << " appending parent.children[" << length << "] = module";
|
||||
children.setProperty(length, module);
|
||||
return true;
|
||||
} else if (parent.isValid()) {
|
||||
qCDebug(scriptengine_module) << "registerModuleWithParent -- unrecognized parent" << parent.toVariant().toString();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// creates a new JS "module" Object with default metadata properties
|
||||
QScriptValue ScriptEngine::newModule(const QString& modulePath, const QScriptValue& parent) {
|
||||
auto closure = newObject();
|
||||
auto exports = newObject();
|
||||
auto module = newObject();
|
||||
qCDebug(scriptengine_module) << "newModule" << modulePath << parent.property("filename").toString();
|
||||
|
||||
closure.setProperty("module", module, READONLY_PROP_FLAGS);
|
||||
|
||||
// note: this becomes the "exports" free variable, so should not be set read only
|
||||
closure.setProperty("exports", exports);
|
||||
|
||||
// make the closure available to module instantiation
|
||||
module.setProperty("__closure__", closure, READONLY_HIDDEN_PROP_FLAGS);
|
||||
|
||||
// for consistency with Node.js Module
|
||||
module.setProperty("id", modulePath, READONLY_PROP_FLAGS);
|
||||
module.setProperty("filename", modulePath, READONLY_PROP_FLAGS);
|
||||
module.setProperty("exports", exports); // not readonly
|
||||
module.setProperty("loaded", false, READONLY_PROP_FLAGS);
|
||||
module.setProperty("parent", parent, READONLY_PROP_FLAGS);
|
||||
module.setProperty("children", newArray(), READONLY_PROP_FLAGS);
|
||||
|
||||
// module.require is a bound version of require that always resolves relative to that module's path
|
||||
auto boundRequire = QScriptEngine::evaluate("(function(id) { return Script.require(Script.require.resolve(id, this.filename)); })", "(boundRequire)");
|
||||
module.setProperty("require", boundRequire, READONLY_PROP_FLAGS);
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
// synchronously fetch a module's source code using BatchLoader
|
||||
QVariantMap ScriptEngine::fetchModuleSource(const QString& modulePath, const bool forceDownload) {
|
||||
using UrlMap = QMap<QUrl, QString>;
|
||||
auto scriptCache = DependencyManager::get<ScriptCache>();
|
||||
QVariantMap req;
|
||||
qCDebug(scriptengine_module) << "require.fetchModuleSource: " << QUrl(modulePath).fileName() << QThread::currentThread();
|
||||
|
||||
auto onload = [=, &req](const UrlMap& data, const UrlMap& _status) {
|
||||
auto url = modulePath;
|
||||
auto status = _status[url];
|
||||
auto contents = data[url];
|
||||
qCDebug(scriptengine_module) << "require.fetchModuleSource.onload: " << QUrl(url).fileName() << status << QThread::currentThread();
|
||||
if (isStopping()) {
|
||||
req["status"] = "Stopped";
|
||||
req["success"] = false;
|
||||
} else {
|
||||
req["url"] = url;
|
||||
req["status"] = status;
|
||||
req["success"] = ScriptCache::isSuccessStatus(status);
|
||||
req["contents"] = contents;
|
||||
}
|
||||
};
|
||||
|
||||
if (forceDownload) {
|
||||
qCDebug(scriptengine_module) << "require.requestScript -- clearing cache for" << modulePath;
|
||||
scriptCache->deleteScript(modulePath);
|
||||
}
|
||||
BatchLoader* loader = new BatchLoader(QList<QUrl>({ modulePath }));
|
||||
connect(loader, &BatchLoader::finished, this, onload);
|
||||
connect(this, &QObject::destroyed, loader, &QObject::deleteLater);
|
||||
// fail faster? (since require() blocks the engine thread while resolving dependencies)
|
||||
const int MAX_RETRIES = 1;
|
||||
|
||||
loader->start(MAX_RETRIES);
|
||||
|
||||
if (!loader->isFinished()) {
|
||||
QTimer monitor;
|
||||
QEventLoop loop;
|
||||
QObject::connect(loader, &BatchLoader::finished, this, [this, &monitor, &loop]{
|
||||
monitor.stop();
|
||||
loop.quit();
|
||||
});
|
||||
|
||||
// this helps detect the case where stop() is invoked during the download
|
||||
// but not seen in time to abort processing in onload()...
|
||||
connect(&monitor, &QTimer::timeout, this, [this, &loop, &loader]{
|
||||
if (isStopping()) {
|
||||
loop.exit(-1);
|
||||
}
|
||||
});
|
||||
monitor.start(500);
|
||||
loop.exec();
|
||||
}
|
||||
loader->deleteLater();
|
||||
return req;
|
||||
}
|
||||
|
||||
// evaluate a pending module object using the fetched source code
|
||||
QScriptValue ScriptEngine::instantiateModule(const QScriptValue& module, const QString& sourceCode) {
|
||||
QScriptValue result;
|
||||
auto modulePath = module.property("filename").toString();
|
||||
auto closure = module.property("__closure__");
|
||||
|
||||
qCDebug(scriptengine_module) << QString("require.instantiateModule: %1 / %2 bytes")
|
||||
.arg(QUrl(modulePath).fileName()).arg(sourceCode.length());
|
||||
|
||||
if (module.property("content-type").toString() == "application/json") {
|
||||
qCDebug(scriptengine_module) << "... parsing as JSON";
|
||||
closure.setProperty("__json", sourceCode);
|
||||
result = evaluateInClosure(closure, { "module.exports = JSON.parse(__json)", modulePath });
|
||||
} else {
|
||||
// scoped vars for consistency with Node.js
|
||||
closure.setProperty("require", module.property("require"));
|
||||
closure.setProperty("__filename", modulePath, READONLY_HIDDEN_PROP_FLAGS);
|
||||
closure.setProperty("__dirname", QString(modulePath).replace(QRegExp("/[^/]*$"), ""), READONLY_HIDDEN_PROP_FLAGS);
|
||||
result = evaluateInClosure(closure, { sourceCode, modulePath });
|
||||
}
|
||||
maybeEmitUncaughtException(__FUNCTION__);
|
||||
return result;
|
||||
}
|
||||
|
||||
// CommonJS/Node.js like require/module support
|
||||
QScriptValue ScriptEngine::require(const QString& moduleId) {
|
||||
qCDebug(scriptengine_module) << "ScriptEngine::require(" << moduleId.left(MAX_DEBUG_VALUE_LENGTH) << ")";
|
||||
if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
|
||||
return unboundNullValue();
|
||||
}
|
||||
|
||||
auto jsRequire = globalObject().property("Script").property("require");
|
||||
auto cacheMeta = jsRequire.data();
|
||||
auto cache = jsRequire.property("cache");
|
||||
auto parent = currentModule();
|
||||
|
||||
auto throwModuleError = [&](const QString& modulePath, const QScriptValue& error) {
|
||||
cache.setProperty(modulePath, nullValue());
|
||||
if (!error.isNull()) {
|
||||
#ifdef DEBUG_JS_MODULES
|
||||
qCWarning(scriptengine_module) << "throwing module error:" << error.toString() << modulePath << error.property("stack").toString();
|
||||
#endif
|
||||
raiseException(error);
|
||||
}
|
||||
maybeEmitUncaughtException("module");
|
||||
return unboundNullValue();
|
||||
};
|
||||
|
||||
// start by resolving the moduleId into a fully-qualified path/URL
|
||||
QString modulePath = _requireResolve(moduleId);
|
||||
if (modulePath.isNull() || hasUncaughtException()) {
|
||||
// the resolver already threw an exception -- bail early
|
||||
maybeEmitUncaughtException(__FUNCTION__);
|
||||
return unboundNullValue();
|
||||
}
|
||||
|
||||
// check the resolved path against the cache
|
||||
auto module = cache.property(modulePath);
|
||||
|
||||
// modules get cached in `Script.require.cache` and (similar to Node.js) users can access it
|
||||
// to inspect particular entries and invalidate them by deleting the key:
|
||||
// `delete Script.require.cache[Script.require.resolve(moduleId)];`
|
||||
|
||||
// cacheMeta is just used right now to tell deleted keys apart from undefined ones
|
||||
bool invalidateCache = module.isUndefined() && cacheMeta.property(moduleId).isValid();
|
||||
|
||||
// reset the cacheMeta record so invalidation won't apply next time, even if the module fails to load
|
||||
cacheMeta.setProperty(modulePath, QScriptValue());
|
||||
|
||||
auto exports = module.property("exports");
|
||||
if (!invalidateCache && exports.isObject()) {
|
||||
// we have found a cached module -- just need to possibly register it with current parent
|
||||
qCDebug(scriptengine_module) << QString("require - using cached module '%1' for '%2' (loaded: %3)")
|
||||
.arg(modulePath).arg(moduleId).arg(module.property("loaded").toString());
|
||||
registerModuleWithParent(module, parent);
|
||||
maybeEmitUncaughtException("cached module");
|
||||
return exports;
|
||||
}
|
||||
|
||||
// bootstrap / register new empty module
|
||||
module = newModule(modulePath, parent);
|
||||
registerModuleWithParent(module, parent);
|
||||
|
||||
// add it to the cache (this is done early so any cyclic dependencies pick up)
|
||||
cache.setProperty(modulePath, module);
|
||||
|
||||
// download the module source
|
||||
auto req = fetchModuleSource(modulePath, invalidateCache);
|
||||
|
||||
if (!req.contains("success") || !req["success"].toBool()) {
|
||||
auto error = QString("error retrieving script (%1)").arg(req["status"].toString());
|
||||
return throwModuleError(modulePath, error);
|
||||
}
|
||||
|
||||
#if DEBUG_JS_MODULES
|
||||
qCDebug(scriptengine_module) << "require.loaded: " <<
|
||||
QUrl(req["url"].toString()).fileName() << req["status"].toString();
|
||||
#endif
|
||||
|
||||
auto sourceCode = req["contents"].toString();
|
||||
|
||||
if (QUrl(modulePath).fileName().endsWith(".json", Qt::CaseInsensitive)) {
|
||||
module.setProperty("content-type", "application/json");
|
||||
} else {
|
||||
module.setProperty("content-type", "application/javascript");
|
||||
}
|
||||
|
||||
// evaluate the module
|
||||
auto result = instantiateModule(module, sourceCode);
|
||||
|
||||
if (result.isError() && !result.strictlyEquals(module.property("exports"))) {
|
||||
qCWarning(scriptengine_module) << "-- result.isError --" << result.toString();
|
||||
return throwModuleError(modulePath, result);
|
||||
}
|
||||
|
||||
// mark as fully-loaded
|
||||
module.setProperty("loaded", true, READONLY_PROP_FLAGS);
|
||||
|
||||
// set up a new reference point for detecting cache key deletion
|
||||
cacheMeta.setProperty(modulePath, module);
|
||||
|
||||
qCDebug(scriptengine_module) << "//ScriptEngine::require(" << moduleId << ")";
|
||||
|
||||
maybeEmitUncaughtException(__FUNCTION__);
|
||||
return module.property("exports");
|
||||
}
|
||||
|
||||
// If a callback is specified, the included files will be loaded asynchronously and the callback will be called
|
||||
// when all of the files have finished loading.
|
||||
// If no callback is specified, the included files will be loaded synchronously and will block execution until
|
||||
// all of the files have finished loading.
|
||||
void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callback) {
|
||||
if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
|
||||
return;
|
||||
}
|
||||
if (DependencyManager::get<ScriptEngines>()->isStopped()) {
|
||||
scriptWarningMessage("Script.include() while shutting down is ignored... includeFiles:"
|
||||
+ includeFiles.join(",") + "parent script:" + getFilename());
|
||||
|
@ -1371,7 +1767,7 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac
|
|||
|
||||
doWithEnvironment(capturedEntityIdentifier, capturedSandboxURL, operation);
|
||||
if (hasUncaughtException()) {
|
||||
emit unhandledException(cloneUncaughtException(__FUNCTION__));
|
||||
emit unhandledException(cloneUncaughtException("evaluateInclude"));
|
||||
clearExceptions();
|
||||
}
|
||||
} else {
|
||||
|
@ -1418,6 +1814,9 @@ void ScriptEngine::include(const QString& includeFile, QScriptValue callback) {
|
|||
// as a stand-alone script. To accomplish this, the ScriptEngine class just emits a signal which
|
||||
// the Application or other context will connect to in order to know to actually load the script
|
||||
void ScriptEngine::load(const QString& loadFile) {
|
||||
if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
|
||||
return;
|
||||
}
|
||||
if (DependencyManager::get<ScriptEngines>()->isStopped()) {
|
||||
scriptWarningMessage("Script.load() while shutting down is ignored... loadFile:"
|
||||
+ loadFile + "parent script:" + getFilename());
|
||||
|
@ -1487,6 +1886,52 @@ void ScriptEngine::updateEntityScriptStatus(const EntityItemID& entityID, const
|
|||
emit entityScriptDetailsUpdated();
|
||||
}
|
||||
|
||||
QVariant ScriptEngine::cloneEntityScriptDetails(const EntityItemID& entityID) {
|
||||
static const QVariant NULL_VARIANT { qVariantFromValue((QObject*)nullptr) };
|
||||
QVariantMap map;
|
||||
if (entityID.isNull()) {
|
||||
// TODO: find better way to report JS Error across thread/process boundaries
|
||||
map["isError"] = true;
|
||||
map["errorInfo"] = "Error: getEntityScriptDetails -- invalid entityID";
|
||||
} else {
|
||||
#ifdef DEBUG_ENTITY_STATES
|
||||
qDebug() << "cloneEntityScriptDetails" << entityID << QThread::currentThread();
|
||||
#endif
|
||||
EntityScriptDetails scriptDetails;
|
||||
if (getEntityScriptDetails(entityID, scriptDetails)) {
|
||||
#ifdef DEBUG_ENTITY_STATES
|
||||
qDebug() << "gotEntityScriptDetails" << scriptDetails.status << QThread::currentThread();
|
||||
#endif
|
||||
map["isRunning"] = isEntityScriptRunning(entityID);
|
||||
map["status"] = EntityScriptStatus_::valueToKey(scriptDetails.status).toLower();
|
||||
map["errorInfo"] = scriptDetails.errorInfo;
|
||||
map["entityID"] = entityID.toString();
|
||||
#ifdef DEBUG_ENTITY_STATES
|
||||
{
|
||||
auto debug = QVariantMap();
|
||||
debug["script"] = scriptDetails.scriptText;
|
||||
debug["scriptObject"] = scriptDetails.scriptObject.toVariant();
|
||||
debug["lastModified"] = (qlonglong)scriptDetails.lastModified;
|
||||
debug["sandboxURL"] = scriptDetails.definingSandboxURL;
|
||||
map["debug"] = debug;
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
#ifdef DEBUG_ENTITY_STATES
|
||||
qDebug() << "!gotEntityScriptDetails" << QThread::currentThread();
|
||||
#endif
|
||||
map["isError"] = true;
|
||||
map["errorInfo"] = "Entity script details unavailable";
|
||||
map["entityID"] = entityID.toString();
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
QFuture<QVariant> ScriptEngine::getLocalEntityScriptDetails(const EntityItemID& entityID) {
|
||||
return QtConcurrent::run(this, &ScriptEngine::cloneEntityScriptDetails, entityID);
|
||||
}
|
||||
|
||||
bool ScriptEngine::getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const {
|
||||
auto it = _entityScripts.constFind(entityID);
|
||||
if (it == _entityScripts.constEnd()) {
|
||||
|
@ -1625,10 +2070,10 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString&
|
|||
|
||||
auto scriptCache = DependencyManager::get<ScriptCache>();
|
||||
// note: see EntityTreeRenderer.cpp for shared pointer lifecycle management
|
||||
QWeakPointer<ScriptEngine> weakRef(sharedFromThis());
|
||||
QWeakPointer<BaseScriptEngine> weakRef(sharedFromThis());
|
||||
scriptCache->getScriptContents(entityScript,
|
||||
[this, weakRef, entityScript, entityID](const QString& url, const QString& contents, bool isURL, bool success, const QString& status) {
|
||||
QSharedPointer<ScriptEngine> strongRef(weakRef);
|
||||
QSharedPointer<BaseScriptEngine> strongRef(weakRef);
|
||||
if (!strongRef) {
|
||||
qCWarning(scriptengine) << "loadEntityScript.contentAvailable -- ScriptEngine was deleted during getScriptContents!!";
|
||||
return;
|
||||
|
@ -1747,13 +2192,12 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
|
|||
timeout.setSingleShot(true);
|
||||
timeout.start(SANDBOX_TIMEOUT);
|
||||
connect(&timeout, &QTimer::timeout, [&sandbox, SANDBOX_TIMEOUT, scriptOrURL]{
|
||||
auto context = sandbox.currentContext();
|
||||
if (context) {
|
||||
qCDebug(scriptengine) << "ScriptEngine::entityScriptContentAvailable timeout(" << scriptOrURL << ")";
|
||||
|
||||
// Guard against infinite loops and non-performant code
|
||||
context->throwError(QString("Timed out (entity constructors are limited to %1ms)").arg(SANDBOX_TIMEOUT));
|
||||
}
|
||||
sandbox.raiseException(
|
||||
sandbox.makeError(QString("Timed out (entity constructors are limited to %1ms)").arg(SANDBOX_TIMEOUT))
|
||||
);
|
||||
});
|
||||
|
||||
testConstructor = sandbox.evaluate(program);
|
||||
|
@ -1769,7 +2213,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
|
|||
if (exception.isError()) {
|
||||
// create a local copy using makeError to decouple from the sandbox engine
|
||||
exception = makeError(exception);
|
||||
setError(formatException(exception), EntityScriptStatus::ERROR_RUNNING_SCRIPT);
|
||||
setError(formatException(exception, _enableExtendedJSExceptions.get()), EntityScriptStatus::ERROR_RUNNING_SCRIPT);
|
||||
emit unhandledException(exception);
|
||||
return;
|
||||
}
|
||||
|
@ -1781,9 +2225,8 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
|
|||
testConstructorType = "empty";
|
||||
}
|
||||
QString testConstructorValue = testConstructor.toString();
|
||||
const int maxTestConstructorValueSize = 80;
|
||||
if (testConstructorValue.size() > maxTestConstructorValueSize) {
|
||||
testConstructorValue = testConstructorValue.mid(0, maxTestConstructorValueSize) + "...";
|
||||
if (testConstructorValue.size() > MAX_DEBUG_VALUE_LENGTH) {
|
||||
testConstructorValue = testConstructorValue.mid(0, MAX_DEBUG_VALUE_LENGTH) + "...";
|
||||
}
|
||||
auto message = QString("failed to load entity script -- expected a function, got %1, %2")
|
||||
.arg(testConstructorType).arg(testConstructorValue);
|
||||
|
@ -1821,7 +2264,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
|
|||
|
||||
if (entityScriptObject.isError()) {
|
||||
auto exception = entityScriptObject;
|
||||
setError(formatException(exception), EntityScriptStatus::ERROR_RUNNING_SCRIPT);
|
||||
setError(formatException(exception, _enableExtendedJSExceptions.get()), EntityScriptStatus::ERROR_RUNNING_SCRIPT);
|
||||
emit unhandledException(exception);
|
||||
return;
|
||||
}
|
||||
|
@ -1844,7 +2287,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co
|
|||
processDeferredEntityLoads(entityScript, entityID);
|
||||
}
|
||||
|
||||
void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) {
|
||||
void ScriptEngine::unloadEntityScript(const EntityItemID& entityID, bool shouldRemoveFromMap) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
#ifdef THREAD_DEBUGGING
|
||||
qCDebug(scriptengine) << "*** WARNING *** ScriptEngine::unloadEntityScript() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "
|
||||
|
@ -1852,7 +2295,8 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) {
|
|||
#endif
|
||||
|
||||
QMetaObject::invokeMethod(this, "unloadEntityScript",
|
||||
Q_ARG(const EntityItemID&, entityID));
|
||||
Q_ARG(const EntityItemID&, entityID),
|
||||
Q_ARG(bool, shouldRemoveFromMap));
|
||||
return;
|
||||
}
|
||||
#ifdef THREAD_DEBUGGING
|
||||
|
@ -1864,10 +2308,17 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) {
|
|||
const EntityScriptDetails &oldDetails = _entityScripts[entityID];
|
||||
if (isEntityScriptRunning(entityID)) {
|
||||
callEntityScriptMethod(entityID, "unload");
|
||||
} else {
|
||||
}
|
||||
#ifdef DEBUG_ENTITY_STATES
|
||||
else {
|
||||
qCDebug(scriptengine) << "unload called while !running" << entityID << oldDetails.status;
|
||||
}
|
||||
if (oldDetails.status != EntityScriptStatus::UNLOADED) {
|
||||
#endif
|
||||
if (shouldRemoveFromMap) {
|
||||
// this was a deleted entity, we've been asked to remove it from the map
|
||||
_entityScripts.remove(entityID);
|
||||
emit entityScriptDetailsUpdated();
|
||||
} else if (oldDetails.status != EntityScriptStatus::UNLOADED) {
|
||||
EntityScriptDetails newDetails;
|
||||
newDetails.status = EntityScriptStatus::UNLOADED;
|
||||
newDetails.lastModified = QDateTime::currentMSecsSinceEpoch();
|
||||
|
@ -1875,6 +2326,7 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) {
|
|||
newDetails.scriptText = oldDetails.scriptText;
|
||||
setEntityScriptDetails(entityID, newDetails);
|
||||
}
|
||||
|
||||
stopAllTimersForEntityScript(entityID);
|
||||
{
|
||||
// FIXME: shouldn't have to do this here, but currently something seems to be firing unloads moments after firing initial load requests
|
||||
|
@ -1953,10 +2405,7 @@ void ScriptEngine::doWithEnvironment(const EntityItemID& entityID, const QUrl& s
|
|||
#else
|
||||
operation();
|
||||
#endif
|
||||
if (!isEvaluating() && hasUncaughtException()) {
|
||||
emit unhandledException(cloneUncaughtException(!entityID.isNull() ? entityID.toString() : __FUNCTION__));
|
||||
clearExceptions();
|
||||
}
|
||||
maybeEmitUncaughtException(!entityID.isNull() ? entityID.toString() : __FUNCTION__);
|
||||
currentEntityIdentifier = oldIdentifier;
|
||||
currentSandboxURL = oldSandboxURL;
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
#include "ScriptCache.h"
|
||||
#include "ScriptUUID.h"
|
||||
#include "Vec3.h"
|
||||
#include "SettingHandle.h"
|
||||
|
||||
class QScriptEngineDebugger;
|
||||
|
||||
|
@ -78,7 +79,7 @@ public:
|
|||
QUrl definingSandboxURL { QUrl("about:EntityScript") };
|
||||
};
|
||||
|
||||
class ScriptEngine : public BaseScriptEngine, public EntitiesScriptEngineProvider, public QEnableSharedFromThis<ScriptEngine> {
|
||||
class ScriptEngine : public BaseScriptEngine, public EntitiesScriptEngineProvider {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(QString context READ getContext)
|
||||
public:
|
||||
|
@ -137,6 +138,8 @@ public:
|
|||
/// evaluate some code in the context of the ScriptEngine and return the result
|
||||
Q_INVOKABLE QScriptValue evaluate(const QString& program, const QString& fileName, int lineNumber = 1); // this is also used by the script tool widget
|
||||
|
||||
Q_INVOKABLE QScriptValue evaluateInClosure(const QScriptValue& locals, const QScriptProgram& program);
|
||||
|
||||
/// if the script engine is not already running, this will download the URL and start the process of seting it up
|
||||
/// to run... NOTE - this is used by Application currently to load the url. We don't really want it to be exposed
|
||||
/// to scripts. we may not need this to be invokable
|
||||
|
@ -157,6 +160,16 @@ public:
|
|||
Q_INVOKABLE void include(const QStringList& includeFiles, QScriptValue callback = QScriptValue());
|
||||
Q_INVOKABLE void include(const QString& includeFile, QScriptValue callback = QScriptValue());
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// MODULE related methods
|
||||
Q_INVOKABLE QScriptValue require(const QString& moduleId);
|
||||
Q_INVOKABLE void resetModuleCache(bool deleteScriptCache = false);
|
||||
QScriptValue currentModule();
|
||||
bool registerModuleWithParent(const QScriptValue& module, const QScriptValue& parent);
|
||||
QScriptValue newModule(const QString& modulePath, const QScriptValue& parent = QScriptValue());
|
||||
QVariantMap fetchModuleSource(const QString& modulePath, const bool forceDownload = false);
|
||||
QScriptValue instantiateModule(const QScriptValue& module, const QString& sourceCode);
|
||||
|
||||
Q_INVOKABLE QObject* setInterval(const QScriptValue& function, int intervalMS);
|
||||
Q_INVOKABLE QObject* setTimeout(const QScriptValue& function, int timeoutMS);
|
||||
Q_INVOKABLE void clearInterval(QObject* timer) { stopTimer(reinterpret_cast<QTimer*>(timer)); }
|
||||
|
@ -170,8 +183,10 @@ public:
|
|||
Q_INVOKABLE bool isEntityScriptRunning(const EntityItemID& entityID) {
|
||||
return _entityScripts.contains(entityID) && _entityScripts[entityID].status == EntityScriptStatus::RUNNING;
|
||||
}
|
||||
QVariant cloneEntityScriptDetails(const EntityItemID& entityID);
|
||||
QFuture<QVariant> getLocalEntityScriptDetails(const EntityItemID& entityID) override;
|
||||
Q_INVOKABLE void loadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload);
|
||||
Q_INVOKABLE void unloadEntityScript(const EntityItemID& entityID); // will call unload method
|
||||
Q_INVOKABLE void unloadEntityScript(const EntityItemID& entityID, bool shouldRemoveFromMap = false); // will call unload method
|
||||
Q_INVOKABLE void unloadAllEntityScripts();
|
||||
Q_INVOKABLE void callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName,
|
||||
const QStringList& params = QStringList()) override;
|
||||
|
@ -237,6 +252,9 @@ signals:
|
|||
protected:
|
||||
void init();
|
||||
Q_INVOKABLE void executeOnScriptThread(std::function<void()> function, const Qt::ConnectionType& type = Qt::QueuedConnection );
|
||||
// note: this is not meant to be called directly, but just to have QMetaObject take care of wiring it up in general;
|
||||
// then inside of init() we just have to do "Script.require.resolve = Script._requireResolve;"
|
||||
Q_INVOKABLE QString _requireResolve(const QString& moduleId, const QString& relativeTo = QString());
|
||||
|
||||
QString logException(const QScriptValue& exception);
|
||||
void timerFired();
|
||||
|
@ -290,11 +308,16 @@ protected:
|
|||
|
||||
AssetScriptingInterface _assetScriptingInterface{ this };
|
||||
|
||||
std::function<bool()> _emitScriptUpdates{ [](){ return true; } };
|
||||
std::function<bool()> _emitScriptUpdates{ []() { return true; } };
|
||||
|
||||
std::recursive_mutex _lock;
|
||||
|
||||
std::chrono::microseconds _totalTimerExecution { 0 };
|
||||
|
||||
static const QString _SETTINGS_ENABLE_EXTENDED_MODULE_COMPAT;
|
||||
static const QString _SETTINGS_ENABLE_EXTENDED_EXCEPTIONS;
|
||||
|
||||
Setting::Handle<bool> _enableExtendedJSExceptions { _SETTINGS_ENABLE_EXTENDED_EXCEPTIONS, true };
|
||||
};
|
||||
|
||||
#endif // hifi_ScriptEngine_h
|
||||
|
|
|
@ -12,3 +12,4 @@
|
|||
#include "ScriptEngineLogging.h"
|
||||
|
||||
Q_LOGGING_CATEGORY(scriptengine, "hifi.scriptengine")
|
||||
Q_LOGGING_CATEGORY(scriptengine_module, "hifi.scriptengine.module")
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <QLoggingCategory>
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(scriptengine)
|
||||
Q_DECLARE_LOGGING_CATEGORY(scriptengine_module)
|
||||
|
||||
#endif // hifi_ScriptEngineLogging_h
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
//
|
||||
|
||||
#include "BaseScriptEngine.h"
|
||||
#include "SharedLogging.h"
|
||||
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QThread>
|
||||
|
@ -18,18 +19,27 @@
|
|||
#include <QtScript/QScriptValueIterator>
|
||||
#include <QtScript/QScriptContextInfo>
|
||||
|
||||
#include "ScriptEngineLogging.h"
|
||||
#include "Profile.h"
|
||||
|
||||
const QString BaseScriptEngine::_SETTINGS_ENABLE_EXTENDED_EXCEPTIONS {
|
||||
"com.highfidelity.experimental.enableExtendedJSExceptions"
|
||||
};
|
||||
|
||||
const QString BaseScriptEngine::SCRIPT_EXCEPTION_FORMAT { "[%0] %1 in %2:%3" };
|
||||
const QString BaseScriptEngine::SCRIPT_BACKTRACE_SEP { "\n " };
|
||||
|
||||
bool BaseScriptEngine::IS_THREADSAFE_INVOCATION(const QThread *thread, const QString& method) {
|
||||
if (QThread::currentThread() == thread) {
|
||||
return true;
|
||||
}
|
||||
qCCritical(shared) << QString("Scripting::%1 @ %2 -- ignoring thread-unsafe call from %3")
|
||||
.arg(method).arg(thread ? thread->objectName() : "(!thread)").arg(QThread::currentThread()->objectName());
|
||||
qCDebug(shared) << "(please resolve on the calling side by using invokeMethod, executeOnScriptThread, etc.)";
|
||||
Q_ASSERT(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
// engine-aware JS Error copier and factory
|
||||
QScriptValue BaseScriptEngine::makeError(const QScriptValue& _other, const QString& type) {
|
||||
if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
|
||||
return unboundNullValue();
|
||||
}
|
||||
auto other = _other;
|
||||
if (other.isString()) {
|
||||
other = newObject();
|
||||
|
@ -41,7 +51,7 @@ QScriptValue BaseScriptEngine::makeError(const QScriptValue& _other, const QStri
|
|||
}
|
||||
if (!proto.isFunction()) {
|
||||
#ifdef DEBUG_JS_EXCEPTIONS
|
||||
qCDebug(scriptengine) << "BaseScriptEngine::makeError -- couldn't find constructor for" << type << " -- using Error instead";
|
||||
qCDebug(shared) << "BaseScriptEngine::makeError -- couldn't find constructor for" << type << " -- using Error instead";
|
||||
#endif
|
||||
proto = globalObject().property("Error");
|
||||
}
|
||||
|
@ -64,6 +74,9 @@ QScriptValue BaseScriptEngine::makeError(const QScriptValue& _other, const QStri
|
|||
|
||||
// check syntax and when there are issues returns an actual "SyntaxError" with the details
|
||||
QScriptValue BaseScriptEngine::lintScript(const QString& sourceCode, const QString& fileName, const int lineNumber) {
|
||||
if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
|
||||
return unboundNullValue();
|
||||
}
|
||||
const auto syntaxCheck = checkSyntax(sourceCode);
|
||||
if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) {
|
||||
auto err = globalObject().property("SyntaxError")
|
||||
|
@ -82,13 +95,16 @@ QScriptValue BaseScriptEngine::lintScript(const QString& sourceCode, const QStri
|
|||
}
|
||||
return err;
|
||||
}
|
||||
return undefinedValue();
|
||||
return QScriptValue();
|
||||
}
|
||||
|
||||
// this pulls from the best available information to create a detailed snapshot of the current exception
|
||||
QScriptValue BaseScriptEngine::cloneUncaughtException(const QString& extraDetail) {
|
||||
if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
|
||||
return unboundNullValue();
|
||||
}
|
||||
if (!hasUncaughtException()) {
|
||||
return QScriptValue();
|
||||
return unboundNullValue();
|
||||
}
|
||||
auto exception = uncaughtException();
|
||||
// ensure the error object is engine-local
|
||||
|
@ -144,7 +160,10 @@ QScriptValue BaseScriptEngine::cloneUncaughtException(const QString& extraDetail
|
|||
return err;
|
||||
}
|
||||
|
||||
QString BaseScriptEngine::formatException(const QScriptValue& exception) {
|
||||
QString BaseScriptEngine::formatException(const QScriptValue& exception, bool includeExtendedDetails) {
|
||||
if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
|
||||
return QString();
|
||||
}
|
||||
QString note { "UncaughtException" };
|
||||
QString result;
|
||||
|
||||
|
@ -156,8 +175,8 @@ QString BaseScriptEngine::formatException(const QScriptValue& exception) {
|
|||
const auto lineNumber = exception.property("lineNumber").toString();
|
||||
const auto stacktrace = exception.property("stack").toString();
|
||||
|
||||
if (_enableExtendedJSExceptions.get()) {
|
||||
// This setting toggles display of the hints now being added during the loading process.
|
||||
if (includeExtendedDetails) {
|
||||
// Display additional exception / troubleshooting hints that can be added via the custom Error .detail property
|
||||
// Example difference:
|
||||
// [UncaughtExceptions] Error: Can't find variable: foobar in atp:/myentity.js\n...
|
||||
// [UncaughtException (construct {1eb5d3fa-23b1-411c-af83-163af7220e3f})] Error: Can't find variable: foobar in atp:/myentity.js\n...
|
||||
|
@ -173,14 +192,39 @@ QString BaseScriptEngine::formatException(const QScriptValue& exception) {
|
|||
return result;
|
||||
}
|
||||
|
||||
bool BaseScriptEngine::raiseException(const QScriptValue& exception) {
|
||||
if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
|
||||
return false;
|
||||
}
|
||||
if (currentContext()) {
|
||||
// we have an active context / JS stack frame so throw the exception per usual
|
||||
currentContext()->throwValue(makeError(exception));
|
||||
return true;
|
||||
} else {
|
||||
// we are within a pure C++ stack frame (ie: being called directly by other C++ code)
|
||||
// in this case no context information is available so just emit the exception for reporting
|
||||
emit unhandledException(makeError(exception));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BaseScriptEngine::maybeEmitUncaughtException(const QString& debugHint) {
|
||||
if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
|
||||
return false;
|
||||
}
|
||||
if (!isEvaluating() && hasUncaughtException()) {
|
||||
emit unhandledException(cloneUncaughtException(debugHint));
|
||||
clearExceptions();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QScriptValue BaseScriptEngine::evaluateInClosure(const QScriptValue& closure, const QScriptProgram& program) {
|
||||
PROFILE_RANGE(script, "evaluateInClosure");
|
||||
if (QThread::currentThread() != thread()) {
|
||||
qCCritical(scriptengine) << "*** CRITICAL *** ScriptEngine::evaluateInClosure() is meant to be called from engine thread only.";
|
||||
// note: a recursive mutex might be needed around below code if this method ever becomes Q_INVOKABLE
|
||||
return QScriptValue();
|
||||
if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
|
||||
return unboundNullValue();
|
||||
}
|
||||
|
||||
const auto fileName = program.fileName();
|
||||
const auto shortName = QUrl(fileName).fileName();
|
||||
|
||||
|
@ -189,7 +233,7 @@ QScriptValue BaseScriptEngine::evaluateInClosure(const QScriptValue& closure, co
|
|||
auto global = closure.property("global");
|
||||
if (global.isObject()) {
|
||||
#ifdef DEBUG_JS
|
||||
qCDebug(scriptengine) << " setting global = closure.global" << shortName;
|
||||
qCDebug(shared) << " setting global = closure.global" << shortName;
|
||||
#endif
|
||||
oldGlobal = globalObject();
|
||||
setGlobalObject(global);
|
||||
|
@ -200,34 +244,34 @@ QScriptValue BaseScriptEngine::evaluateInClosure(const QScriptValue& closure, co
|
|||
auto thiz = closure.property("this");
|
||||
if (thiz.isObject()) {
|
||||
#ifdef DEBUG_JS
|
||||
qCDebug(scriptengine) << " setting this = closure.this" << shortName;
|
||||
qCDebug(shared) << " setting this = closure.this" << shortName;
|
||||
#endif
|
||||
context->setThisObject(thiz);
|
||||
}
|
||||
|
||||
context->pushScope(closure);
|
||||
#ifdef DEBUG_JS
|
||||
qCDebug(scriptengine) << QString("[%1] evaluateInClosure %2").arg(isEvaluating()).arg(shortName);
|
||||
qCDebug(shared) << QString("[%1] evaluateInClosure %2").arg(isEvaluating()).arg(shortName);
|
||||
#endif
|
||||
{
|
||||
result = BaseScriptEngine::evaluate(program);
|
||||
if (hasUncaughtException()) {
|
||||
auto err = cloneUncaughtException(__FUNCTION__);
|
||||
#ifdef DEBUG_JS_EXCEPTIONS
|
||||
qCWarning(scriptengine) << __FUNCTION__ << "---------- hasCaught:" << err.toString() << result.toString();
|
||||
qCWarning(shared) << __FUNCTION__ << "---------- hasCaught:" << err.toString() << result.toString();
|
||||
err.setProperty("_result", result);
|
||||
#endif
|
||||
result = err;
|
||||
}
|
||||
}
|
||||
#ifdef DEBUG_JS
|
||||
qCDebug(scriptengine) << QString("[%1] //evaluateInClosure %2").arg(isEvaluating()).arg(shortName);
|
||||
qCDebug(shared) << QString("[%1] //evaluateInClosure %2").arg(isEvaluating()).arg(shortName);
|
||||
#endif
|
||||
popContext();
|
||||
|
||||
if (oldGlobal.isValid()) {
|
||||
#ifdef DEBUG_JS
|
||||
qCDebug(scriptengine) << " restoring global" << shortName;
|
||||
qCDebug(shared) << " restoring global" << shortName;
|
||||
#endif
|
||||
setGlobalObject(oldGlobal);
|
||||
}
|
||||
|
@ -236,7 +280,6 @@ QScriptValue BaseScriptEngine::evaluateInClosure(const QScriptValue& closure, co
|
|||
}
|
||||
|
||||
// Lambda
|
||||
|
||||
QScriptValue BaseScriptEngine::newLambdaFunction(std::function<QScriptValue(QScriptContext *, QScriptEngine*)> operation, const QScriptValue& data, const QScriptEngine::ValueOwnership& ownership) {
|
||||
auto lambda = new Lambda(this, operation, data);
|
||||
auto object = newQObject(lambda, ownership);
|
||||
|
@ -262,26 +305,57 @@ Lambda::Lambda(QScriptEngine *engine, std::function<QScriptValue(QScriptContext
|
|||
#endif
|
||||
}
|
||||
QScriptValue Lambda::call() {
|
||||
if (!BaseScriptEngine::IS_THREADSAFE_INVOCATION(engine->thread(), __FUNCTION__)) {
|
||||
return BaseScriptEngine::unboundNullValue();
|
||||
}
|
||||
return operation(engine->currentContext(), engine);
|
||||
}
|
||||
|
||||
QScriptValue makeScopedHandlerObject(QScriptValue scopeOrCallback, QScriptValue methodOrName) {
|
||||
auto engine = scopeOrCallback.engine();
|
||||
if (!engine) {
|
||||
return scopeOrCallback;
|
||||
}
|
||||
auto scope = QScriptValue();
|
||||
auto callback = scopeOrCallback;
|
||||
if (scopeOrCallback.isObject()) {
|
||||
if (methodOrName.isString()) {
|
||||
scope = scopeOrCallback;
|
||||
callback = scope.property(methodOrName.toString());
|
||||
} else if (methodOrName.isFunction()) {
|
||||
scope = scopeOrCallback;
|
||||
callback = methodOrName;
|
||||
}
|
||||
}
|
||||
auto handler = engine->newObject();
|
||||
handler.setProperty("scope", scope);
|
||||
handler.setProperty("callback", callback);
|
||||
return handler;
|
||||
}
|
||||
|
||||
QScriptValue callScopedHandlerObject(QScriptValue handler, QScriptValue err, QScriptValue result) {
|
||||
return handler.property("callback").call(handler.property("scope"), QScriptValueList({ err, result }));
|
||||
}
|
||||
|
||||
#ifdef DEBUG_JS
|
||||
void BaseScriptEngine::_debugDump(const QString& header, const QScriptValue& object, const QString& footer) {
|
||||
if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) {
|
||||
return;
|
||||
}
|
||||
if (!header.isEmpty()) {
|
||||
qCDebug(scriptengine) << header;
|
||||
qCDebug(shared) << header;
|
||||
}
|
||||
if (!object.isObject()) {
|
||||
qCDebug(scriptengine) << "(!isObject)" << object.toVariant().toString() << object.toString();
|
||||
qCDebug(shared) << "(!isObject)" << object.toVariant().toString() << object.toString();
|
||||
return;
|
||||
}
|
||||
QScriptValueIterator it(object);
|
||||
while (it.hasNext()) {
|
||||
it.next();
|
||||
qCDebug(scriptengine) << it.name() << ":" << it.value().toString();
|
||||
qCDebug(shared) << it.name() << ":" << it.value().toString();
|
||||
}
|
||||
if (!footer.isEmpty()) {
|
||||
qCDebug(scriptengine) << footer;
|
||||
qCDebug(shared) << footer;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
90
libraries/shared/src/BaseScriptEngine.h
Normal file
90
libraries/shared/src/BaseScriptEngine.h
Normal file
|
@ -0,0 +1,90 @@
|
|||
//
|
||||
// BaseScriptEngine.h
|
||||
// libraries/script-engine/src
|
||||
//
|
||||
// Created by Timothy Dedischew on 02/01/17.
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_BaseScriptEngine_h
|
||||
#define hifi_BaseScriptEngine_h
|
||||
|
||||
#include <functional>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtScript/QScriptEngine>
|
||||
|
||||
// common base class for extending QScriptEngine itself
|
||||
class BaseScriptEngine : public QScriptEngine, public QEnableSharedFromThis<BaseScriptEngine> {
|
||||
Q_OBJECT
|
||||
public:
|
||||
static const QString SCRIPT_EXCEPTION_FORMAT;
|
||||
static const QString SCRIPT_BACKTRACE_SEP;
|
||||
|
||||
// threadsafe "unbound" version of QScriptEngine::nullValue()
|
||||
static const QScriptValue unboundNullValue() { return QScriptValue(0, QScriptValue::NullValue); }
|
||||
|
||||
BaseScriptEngine() {}
|
||||
|
||||
Q_INVOKABLE QScriptValue lintScript(const QString& sourceCode, const QString& fileName, const int lineNumber = 1);
|
||||
Q_INVOKABLE QScriptValue makeError(const QScriptValue& other = QScriptValue(), const QString& type = "Error");
|
||||
Q_INVOKABLE QString formatException(const QScriptValue& exception, bool includeExtendedDetails);
|
||||
|
||||
QScriptValue cloneUncaughtException(const QString& detail = QString());
|
||||
QScriptValue evaluateInClosure(const QScriptValue& locals, const QScriptProgram& program);
|
||||
|
||||
// if there is a pending exception and we are at the top level (non-recursive) stack frame, this emits and resets it
|
||||
bool maybeEmitUncaughtException(const QString& debugHint = QString());
|
||||
|
||||
// if the currentContext() is valid then throw the passed exception; otherwise, immediately emit it.
|
||||
// note: this is used in cases where C++ code might call into JS API methods directly
|
||||
bool raiseException(const QScriptValue& exception);
|
||||
|
||||
// helper to detect and log warnings when other code invokes QScriptEngine/BaseScriptEngine in thread-unsafe ways
|
||||
static bool IS_THREADSAFE_INVOCATION(const QThread *thread, const QString& method);
|
||||
signals:
|
||||
void unhandledException(const QScriptValue& exception);
|
||||
|
||||
protected:
|
||||
// like `newFunction`, but allows mapping inline C++ lambdas with captures as callable QScriptValues
|
||||
// even though the context/engine parameters are redundant in most cases, the function signature matches `newFunction`
|
||||
// anyway so that newLambdaFunction can be used to rapidly prototype / test utility APIs and then if becoming
|
||||
// permanent more easily promoted into regular static newFunction scenarios.
|
||||
QScriptValue newLambdaFunction(std::function<QScriptValue(QScriptContext *context, QScriptEngine* engine)> operation, const QScriptValue& data = QScriptValue(), const QScriptEngine::ValueOwnership& ownership = QScriptEngine::AutoOwnership);
|
||||
|
||||
#ifdef DEBUG_JS
|
||||
static void _debugDump(const QString& header, const QScriptValue& object, const QString& footer = QString());
|
||||
#endif
|
||||
};
|
||||
|
||||
// Standardized CPS callback helpers (see: http://fredkschott.com/post/2014/03/understanding-error-first-callbacks-in-node-js/)
|
||||
// These two helpers allow async JS APIs that use a callback parameter to be more friendly to scripters by accepting thisObject
|
||||
// context and adopting a consistent and intuitable callback signature:
|
||||
// function callback(err, result) { if (err) { ... } else { /* do stuff with result */ } }
|
||||
//
|
||||
// To use, first pass the user-specified callback args in the same order used with optionally-scoped Qt signal connections:
|
||||
// auto handler = makeScopedHandlerObject(scopeOrCallback, optionalMethodOrName);
|
||||
// And then invoke the scoped handler later per CPS conventions:
|
||||
// auto result = callScopedHandlerObject(handler, err, result);
|
||||
QScriptValue makeScopedHandlerObject(QScriptValue scopeOrCallback, QScriptValue methodOrName);
|
||||
QScriptValue callScopedHandlerObject(QScriptValue handler, QScriptValue err, QScriptValue result);
|
||||
|
||||
// Lambda helps create callable QScriptValues out of std::functions:
|
||||
// (just meant for use from within the script engine itself)
|
||||
class Lambda : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Lambda(QScriptEngine *engine, std::function<QScriptValue(QScriptContext *context, QScriptEngine* engine)> operation, QScriptValue data);
|
||||
~Lambda();
|
||||
public slots:
|
||||
QScriptValue call();
|
||||
QString toString() const;
|
||||
private:
|
||||
QScriptEngine* engine;
|
||||
std::function<QScriptValue(QScriptContext *context, QScriptEngine* engine)> operation;
|
||||
QScriptValue data;
|
||||
};
|
||||
|
||||
#endif // hifi_BaseScriptEngine_h
|
|
@ -122,6 +122,7 @@ public:
|
|||
gpu::Batch* _batch = nullptr;
|
||||
|
||||
std::shared_ptr<gpu::Texture> _whiteTexture;
|
||||
uint32_t _globalShapeKey { 0 };
|
||||
bool _enableTexturing { true };
|
||||
|
||||
RenderDetails _details;
|
||||
|
|
|
@ -470,8 +470,8 @@ void Menu::removeSeparator(const QString& menuName, const QString& separatorName
|
|||
if (menu) {
|
||||
int textAt = findPositionOfMenuItem(menu, separatorName);
|
||||
QList<QAction*> menuActions = menu->actions();
|
||||
QAction* separatorText = menuActions[textAt];
|
||||
if (textAt > 0 && textAt < menuActions.size()) {
|
||||
QAction* separatorText = menuActions[textAt];
|
||||
QAction* separatorLine = menuActions[textAt - 1];
|
||||
if (separatorLine) {
|
||||
if (separatorLine->isSeparator()) {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
var lastSpecStartTime;
|
||||
function ConsoleReporter(options) {
|
||||
var startTime = new Date().getTime();
|
||||
var errorCount = 0;
|
||||
var errorCount = 0, pending = [];
|
||||
this.jasmineStarted = function (obj) {
|
||||
print('Jasmine started with ' + obj.totalSpecsDefined + ' tests.');
|
||||
};
|
||||
|
@ -15,11 +15,14 @@
|
|||
var endTime = new Date().getTime();
|
||||
print('<hr />');
|
||||
if (errorCount === 0) {
|
||||
print ('<span style="color:green">All tests passed!</span>');
|
||||
print ('<span style="color:green">All enabled tests passed!</span>');
|
||||
} else {
|
||||
print('<span style="color:red">Tests completed with ' +
|
||||
errorCount + ' ' + ERROR + '.<span>');
|
||||
}
|
||||
if (pending.length)
|
||||
print ('<span style="color:darkorange">disabled: <br /> '+
|
||||
pending.join('<br /> ')+'</span>');
|
||||
print('Tests completed in ' + (endTime - startTime) + 'ms.');
|
||||
};
|
||||
this.suiteStarted = function(obj) {
|
||||
|
@ -32,6 +35,10 @@
|
|||
lastSpecStartTime = new Date().getTime();
|
||||
};
|
||||
this.specDone = function(obj) {
|
||||
if (obj.status === 'pending') {
|
||||
pending.push(obj.fullName);
|
||||
return print('...(pending ' + obj.fullName +')');
|
||||
}
|
||||
var specEndTime = new Date().getTime();
|
||||
var symbol = obj.status === PASSED ?
|
||||
'<span style="color:green">' + CHECKMARK + '</span>' :
|
||||
|
@ -55,7 +62,7 @@
|
|||
clearTimeout = Script.clearTimeout;
|
||||
clearInterval = Script.clearInterval;
|
||||
|
||||
var jasmine = jasmineRequire.core(jasmineRequire);
|
||||
var jasmine = this.jasmine = jasmineRequire.core(jasmineRequire);
|
||||
|
||||
var env = jasmine.getEnv();
|
||||
|
||||
|
|
10
scripts/developer/tests/unit_tests/moduleTests/cycles/a.js
Normal file
10
scripts/developer/tests/unit_tests/moduleTests/cycles/a.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
/* eslint-env node */
|
||||
var a = exports;
|
||||
a.done = false;
|
||||
var b = require('./b.js');
|
||||
a.done = true;
|
||||
a.name = 'a';
|
||||
a['a.done?'] = a.done;
|
||||
a['b.done?'] = b.done;
|
||||
|
||||
print('from a.js a.done =', a.done, '/ b.done =', b.done);
|
10
scripts/developer/tests/unit_tests/moduleTests/cycles/b.js
Normal file
10
scripts/developer/tests/unit_tests/moduleTests/cycles/b.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
/* eslint-env node */
|
||||
var b = exports;
|
||||
b.done = false;
|
||||
var a = require('./a.js');
|
||||
b.done = true;
|
||||
b.name = 'b';
|
||||
b['a.done?'] = a.done;
|
||||
b['b.done?'] = b.done;
|
||||
|
||||
print('from b.js a.done =', a.done, '/ b.done =', b.done);
|
|
@ -0,0 +1,17 @@
|
|||
/* eslint-env node */
|
||||
/* global print */
|
||||
/* eslint-disable comma-dangle */
|
||||
|
||||
print('main.js');
|
||||
var a = require('./a.js'),
|
||||
b = require('./b.js');
|
||||
|
||||
print('from main.js a.done =', a.done, 'and b.done =', b.done);
|
||||
|
||||
module.exports = {
|
||||
name: 'main',
|
||||
a: a,
|
||||
b: b,
|
||||
'a.done?': a.done,
|
||||
'b.done?': b.done,
|
||||
};
|
|
@ -0,0 +1,13 @@
|
|||
/* eslint-disable comma-dangle */
|
||||
// test module method exception being thrown within main constructor
|
||||
(function() {
|
||||
var apiMethod = Script.require('../exceptions/exceptionInFunction.js');
|
||||
print(Script.resolvePath(''), "apiMethod", apiMethod);
|
||||
// this next line throws from within apiMethod
|
||||
print(apiMethod());
|
||||
return {
|
||||
preload: function(uuid) {
|
||||
print("entityConstructorAPIException::preload -- never seen --", uuid, Script.resolvePath(''));
|
||||
},
|
||||
};
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue