Merge branch 'master' into M21956
# Conflicts: # libraries/audio/src/Sound.h
|
@ -52,6 +52,9 @@ else()
|
|||
set(MOBILE 0)
|
||||
endif()
|
||||
|
||||
# Use default time server if none defined in environment
|
||||
set_from_env(TIMESERVER_URL TIMESERVER_URL "http://sha256timestamp.ws.symantec.com/sha256/timestamp")
|
||||
|
||||
set(HIFI_USE_OPTIMIZED_IK OFF)
|
||||
set(BUILD_CLIENT_OPTION ON)
|
||||
set(BUILD_SERVER_OPTION ON)
|
||||
|
|
1008
CODING_STANDARD.md
Normal file
|
@ -16,7 +16,7 @@ Contributing
|
|||
git checkout -b new_branch_name
|
||||
```
|
||||
4. Code
|
||||
* Follow the [coding standard](https://docs.highfidelity.com/build-guide/coding-standards)
|
||||
* Follow the [coding standard](CODING_STANDARD.md)
|
||||
5. Commit
|
||||
* Use [well formed commit messages](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
|
||||
6. Update your branch
|
||||
|
|
|
@ -64,6 +64,10 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) :
|
|||
"UDP port for this assignment client (or monitor)", "port");
|
||||
parser.addOption(portOption);
|
||||
|
||||
const QCommandLineOption minChildListenPort(ASSIGNMENT_MONITOR_MIN_CHILDREN_LISTEN_PORT_OPTION,
|
||||
"Minimum UDP listen port", "port");
|
||||
parser.addOption(minChildListenPort);
|
||||
|
||||
const QCommandLineOption walletDestinationOption(ASSIGNMENT_WALLET_DESTINATION_ID_OPTION,
|
||||
"set wallet destination", "wallet-uuid");
|
||||
parser.addOption(walletDestinationOption);
|
||||
|
@ -195,6 +199,11 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) :
|
|||
assignmentServerPort = parser.value(assignmentServerPortOption).toInt();
|
||||
}
|
||||
|
||||
quint16 childMinListenPort = 0;
|
||||
if (argumentVariantMap.contains(ASSIGNMENT_MONITOR_MIN_CHILDREN_LISTEN_PORT_OPTION)) {
|
||||
childMinListenPort = argumentVariantMap.value(ASSIGNMENT_MONITOR_MIN_CHILDREN_LISTEN_PORT_OPTION).toUInt();
|
||||
}
|
||||
|
||||
// check for an overidden listen port
|
||||
quint16 listenPort = 0;
|
||||
if (argumentVariantMap.contains(ASSIGNMENT_CLIENT_LISTEN_PORT_OPTION)) {
|
||||
|
@ -234,8 +243,8 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) :
|
|||
|
||||
if (numForks || minForks || maxForks) {
|
||||
AssignmentClientMonitor* monitor = new AssignmentClientMonitor(numForks, minForks, maxForks,
|
||||
requestAssignmentType, assignmentPool,
|
||||
listenPort, walletUUID, assignmentServerHostname,
|
||||
requestAssignmentType, assignmentPool, listenPort,
|
||||
childMinListenPort, walletUUID, assignmentServerHostname,
|
||||
assignmentServerPort, httpStatusPort, logDirectory);
|
||||
monitor->setParent(this);
|
||||
connect(this, &QCoreApplication::aboutToQuit, monitor, &AssignmentClientMonitor::aboutToQuit);
|
||||
|
|
|
@ -20,6 +20,7 @@ const QString ASSIGNMENT_POOL_OPTION = "pool";
|
|||
const QString ASSIGNMENT_CLIENT_LISTEN_PORT_OPTION = "p";
|
||||
const QString ASSIGNMENT_WALLET_DESTINATION_ID_OPTION = "wallet";
|
||||
const QString CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION = "a";
|
||||
const QString ASSIGNMENT_MONITOR_MIN_CHILDREN_LISTEN_PORT_OPTION = "min-listen-port";
|
||||
const QString CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION = "server-port";
|
||||
const QString ASSIGNMENT_NUM_FORKS_OPTION = "n";
|
||||
const QString ASSIGNMENT_MIN_FORKS_OPTION = "min";
|
||||
|
|
|
@ -40,7 +40,7 @@ AssignmentClientMonitor::AssignmentClientMonitor(const unsigned int numAssignmen
|
|||
const unsigned int minAssignmentClientForks,
|
||||
const unsigned int maxAssignmentClientForks,
|
||||
Assignment::Type requestAssignmentType, QString assignmentPool,
|
||||
quint16 listenPort, QUuid walletUUID, QString assignmentServerHostname,
|
||||
quint16 listenPort, quint16 childMinListenPort, QUuid walletUUID, QString assignmentServerHostname,
|
||||
quint16 assignmentServerPort, quint16 httpStatusServerPort, QString logDirectory) :
|
||||
_httpManager(QHostAddress::LocalHost, httpStatusServerPort, "", this),
|
||||
_numAssignmentClientForks(numAssignmentClientForks),
|
||||
|
@ -50,8 +50,8 @@ AssignmentClientMonitor::AssignmentClientMonitor(const unsigned int numAssignmen
|
|||
_assignmentPool(assignmentPool),
|
||||
_walletUUID(walletUUID),
|
||||
_assignmentServerHostname(assignmentServerHostname),
|
||||
_assignmentServerPort(assignmentServerPort)
|
||||
|
||||
_assignmentServerPort(assignmentServerPort),
|
||||
_childMinListenPort(childMinListenPort)
|
||||
{
|
||||
qDebug() << "_requestAssignmentType =" << _requestAssignmentType;
|
||||
|
||||
|
@ -100,8 +100,13 @@ void AssignmentClientMonitor::simultaneousWaitOnChildren(int waitMsecs) {
|
|||
}
|
||||
}
|
||||
|
||||
void AssignmentClientMonitor::childProcessFinished(qint64 pid, int exitCode, QProcess::ExitStatus exitStatus) {
|
||||
auto message = "Child process " + QString::number(pid) + " has %1 with exit code " + QString::number(exitCode) + ".";
|
||||
void AssignmentClientMonitor::childProcessFinished(qint64 pid, quint16 listenPort, int exitCode, QProcess::ExitStatus exitStatus) {
|
||||
auto message = "Child process " + QString::number(pid) + " on port " + QString::number(listenPort) +
|
||||
"has %1 with exit code " + QString::number(exitCode) + ".";
|
||||
|
||||
if (listenPort) {
|
||||
_childListenPorts.remove(listenPort);
|
||||
}
|
||||
|
||||
if (_childProcesses.remove(pid)) {
|
||||
message.append(" Removed from internal map.");
|
||||
|
@ -153,6 +158,23 @@ void AssignmentClientMonitor::aboutToQuit() {
|
|||
void AssignmentClientMonitor::spawnChildClient() {
|
||||
QProcess* assignmentClient = new QProcess(this);
|
||||
|
||||
quint16 listenPort = 0;
|
||||
// allocate a port
|
||||
|
||||
if (_childMinListenPort) {
|
||||
for (listenPort = _childMinListenPort; _childListenPorts.contains(listenPort); listenPort++) {
|
||||
if (_maxAssignmentClientForks &&
|
||||
(listenPort >= _maxAssignmentClientForks + _childMinListenPort)) {
|
||||
listenPort = 0;
|
||||
qDebug() << "Insufficient listen ports";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (listenPort) {
|
||||
_childListenPorts.insert(listenPort);
|
||||
}
|
||||
|
||||
// unparse the parts of the command-line that the child cares about
|
||||
QStringList _childArguments;
|
||||
if (_assignmentPool != "") {
|
||||
|
@ -176,6 +198,11 @@ void AssignmentClientMonitor::spawnChildClient() {
|
|||
_childArguments.append(QString::number(_requestAssignmentType));
|
||||
}
|
||||
|
||||
if (listenPort) {
|
||||
_childArguments.append("-" + ASSIGNMENT_CLIENT_LISTEN_PORT_OPTION);
|
||||
_childArguments.append(QString::number(listenPort));
|
||||
}
|
||||
|
||||
// tell children which assignment monitor port to use
|
||||
// for now they simply talk to us on localhost
|
||||
_childArguments.append("--" + ASSIGNMENT_CLIENT_MONITOR_PORT_OPTION);
|
||||
|
@ -247,8 +274,8 @@ void AssignmentClientMonitor::spawnChildClient() {
|
|||
auto pid = assignmentClient->processId();
|
||||
// make sure we hear that this process has finished when it does
|
||||
connect(assignmentClient, static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
|
||||
this, [this, pid](int exitCode, QProcess::ExitStatus exitStatus) {
|
||||
childProcessFinished(pid, exitCode, exitStatus);
|
||||
this, [this, listenPort, pid](int exitCode, QProcess::ExitStatus exitStatus) {
|
||||
childProcessFinished(pid, listenPort, exitCode, exitStatus);
|
||||
});
|
||||
|
||||
qDebug() << "Spawned a child client with PID" << assignmentClient->processId();
|
||||
|
|
|
@ -37,14 +37,15 @@ class AssignmentClientMonitor : public QObject, public HTTPRequestHandler {
|
|||
public:
|
||||
AssignmentClientMonitor(const unsigned int numAssignmentClientForks, const unsigned int minAssignmentClientForks,
|
||||
const unsigned int maxAssignmentClientForks, Assignment::Type requestAssignmentType,
|
||||
QString assignmentPool, quint16 listenPort, QUuid walletUUID, QString assignmentServerHostname,
|
||||
quint16 assignmentServerPort, quint16 httpStatusServerPort, QString logDirectory);
|
||||
QString assignmentPool, quint16 listenPort, quint16 childMinListenPort, QUuid walletUUID,
|
||||
QString assignmentServerHostname, quint16 assignmentServerPort, quint16 httpStatusServerPort,
|
||||
QString logDirectory);
|
||||
~AssignmentClientMonitor();
|
||||
|
||||
void stopChildProcesses();
|
||||
private slots:
|
||||
void checkSpares();
|
||||
void childProcessFinished(qint64 pid, int exitCode, QProcess::ExitStatus exitStatus);
|
||||
void childProcessFinished(qint64 pid, quint16 port, int exitCode, QProcess::ExitStatus exitStatus);
|
||||
void handleChildStatusPacket(QSharedPointer<ReceivedMessage> message);
|
||||
|
||||
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false) override;
|
||||
|
@ -75,6 +76,9 @@ private:
|
|||
|
||||
QMap<qint64, ACProcess> _childProcesses;
|
||||
|
||||
quint16 _childMinListenPort;
|
||||
QSet<quint16> _childListenPorts;
|
||||
|
||||
bool _wantsChildFileLogging { false };
|
||||
};
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
#include <NodeType.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <PathUtils.h>
|
||||
#include <image/Image.h>
|
||||
#include <image/TextureProcessing.h>
|
||||
|
||||
#include "AssetServerLogging.h"
|
||||
#include "BakeAssetTask.h"
|
||||
|
|
|
@ -98,7 +98,8 @@ AudioMixer::AudioMixer(ReceivedMessage& message) :
|
|||
PacketType::RequestsDomainListData,
|
||||
PacketType::PerAvatarGainSet,
|
||||
PacketType::InjectorGainSet,
|
||||
PacketType::AudioSoloRequest },
|
||||
PacketType::AudioSoloRequest,
|
||||
PacketType::StopInjector },
|
||||
this, "queueAudioPacket");
|
||||
|
||||
// packets whose consequences are global should be processed on the main thread
|
||||
|
@ -246,7 +247,8 @@ void AudioMixer::removeHRTFsForFinishedInjector(const QUuid& streamID) {
|
|||
|
||||
if (injectorClientData) {
|
||||
// stage the removal of this stream, workers handle when preparing mixes for listeners
|
||||
_workerSharedData.removedStreams.emplace_back(injectorClientData->getNodeID(), injectorClientData->getNodeLocalID(),
|
||||
_workerSharedData.removedStreams.emplace_back(injectorClientData->getNodeID(),
|
||||
injectorClientData->getNodeLocalID(),
|
||||
streamID);
|
||||
}
|
||||
}
|
||||
|
@ -586,8 +588,8 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
|
|||
// check the payload to see if we have asked for dynamicJitterBuffer support
|
||||
const QString DYNAMIC_JITTER_BUFFER_JSON_KEY = "dynamic_jitter_buffer";
|
||||
bool enableDynamicJitterBuffer = audioBufferGroupObject[DYNAMIC_JITTER_BUFFER_JSON_KEY].toBool();
|
||||
if (enableDynamicJitterBuffer) {
|
||||
qCDebug(audio) << "Enabling dynamic jitter buffers.";
|
||||
if (!enableDynamicJitterBuffer) {
|
||||
qCDebug(audio) << "Disabling dynamic jitter buffers.";
|
||||
|
||||
bool ok;
|
||||
const QString DESIRED_JITTER_BUFFER_FRAMES_KEY = "static_desired_jitter_buffer_frames";
|
||||
|
@ -597,7 +599,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject& settingsObject) {
|
|||
}
|
||||
qCDebug(audio) << "Static desired jitter buffer frames:" << _numStaticJitterFrames;
|
||||
} else {
|
||||
qCDebug(audio) << "Disabling dynamic jitter buffers.";
|
||||
qCDebug(audio) << "Enabling dynamic jitter buffers.";
|
||||
_numStaticJitterFrames = DISABLE_STATIC_JITTER_FRAMES;
|
||||
}
|
||||
|
||||
|
|
|
@ -104,6 +104,9 @@ int AudioMixerClientData::processPackets(ConcurrentAddedStreams& addedStreams) {
|
|||
case PacketType::AudioSoloRequest:
|
||||
parseSoloRequest(packet, node);
|
||||
break;
|
||||
case PacketType::StopInjector:
|
||||
parseStopInjectorPacket(packet);
|
||||
break;
|
||||
default:
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
@ -574,6 +577,19 @@ int AudioMixerClientData::checkBuffersBeforeFrameSend() {
|
|||
return (int)_audioStreams.size();
|
||||
}
|
||||
|
||||
void AudioMixerClientData::parseStopInjectorPacket(QSharedPointer<ReceivedMessage> packet) {
|
||||
auto streamID = QUuid::fromRfc4122(packet->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
||||
|
||||
auto it = std::find_if(std::begin(_audioStreams), std::end(_audioStreams), [&](auto stream) {
|
||||
return streamID == stream->getStreamIdentifier();
|
||||
});
|
||||
|
||||
if (it != std::end(_audioStreams)) {
|
||||
_audioStreams.erase(it);
|
||||
emit injectorStreamFinished(streamID);
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioMixerClientData::shouldSendStats(int frameNumber) {
|
||||
return frameNumber == _frameToSendStats;
|
||||
}
|
||||
|
|
|
@ -67,12 +67,11 @@ public:
|
|||
void parseNodeIgnoreRequest(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& node);
|
||||
void parseRadiusIgnoreRequest(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& node);
|
||||
void parseSoloRequest(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& node);
|
||||
void parseStopInjectorPacket(QSharedPointer<ReceivedMessage> packet);
|
||||
|
||||
// attempt to pop a frame from each audio stream, and return the number of streams from this client
|
||||
int checkBuffersBeforeFrameSend();
|
||||
|
||||
void removeDeadInjectedStreams();
|
||||
|
||||
QJsonObject getAudioStreamStats();
|
||||
|
||||
void sendAudioStreamStatsPackets(const SharedNodePointer& destinationNode);
|
||||
|
@ -163,7 +162,7 @@ public:
|
|||
// end of methods called non-concurrently from single AudioMixerSlave
|
||||
|
||||
signals:
|
||||
void injectorStreamFinished(const QUuid& streamIdentifier);
|
||||
void injectorStreamFinished(const QUuid& streamID);
|
||||
|
||||
public slots:
|
||||
void handleMismatchAudioFormat(SharedNodePointer node, const QString& currentCodec, const QString& recievedCodec);
|
||||
|
|
|
@ -549,38 +549,28 @@ void AudioMixerSlave::addStream(AudioMixerClientData::MixableStream& mixableStre
|
|||
// grab the stream from the ring buffer
|
||||
AudioRingBuffer::ConstIterator streamPopOutput = streamToAdd->getLastPopOutput();
|
||||
|
||||
// stereo sources are not passed through HRTF
|
||||
if (streamToAdd->isStereo()) {
|
||||
|
||||
// apply the avatar gain adjustment
|
||||
gain *= mixableStream.hrtf->getGainAdjustment();
|
||||
streamPopOutput.readSamples(_bufferSamples, AudioConstants::NETWORK_FRAME_SAMPLES_STEREO);
|
||||
|
||||
const float scale = 1 / 32768.0f; // int16_t to float
|
||||
|
||||
for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; i++) {
|
||||
_mixSamples[2*i+0] += (float)streamPopOutput[2*i+0] * gain * scale;
|
||||
_mixSamples[2*i+1] += (float)streamPopOutput[2*i+1] * gain * scale;
|
||||
}
|
||||
// stereo sources are not passed through HRTF
|
||||
mixableStream.hrtf->mixStereo(_bufferSamples, _mixSamples, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||
|
||||
++stats.manualStereoMixes;
|
||||
} else if (isEcho) {
|
||||
|
||||
streamPopOutput.readSamples(_bufferSamples, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||
|
||||
// echo sources are not passed through HRTF
|
||||
|
||||
const float scale = 1/32768.0f; // int16_t to float
|
||||
|
||||
for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; i++) {
|
||||
float sample = (float)streamPopOutput[i] * gain * scale;
|
||||
_mixSamples[2*i+0] += sample;
|
||||
_mixSamples[2*i+1] += sample;
|
||||
}
|
||||
mixableStream.hrtf->mixMono(_bufferSamples, _mixSamples, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||
|
||||
++stats.manualEchoMixes;
|
||||
} else {
|
||||
|
||||
streamPopOutput.readSamples(_bufferSamples, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||
|
||||
mixableStream.hrtf->render(_bufferSamples, _mixSamples, HRTF_DATASET_INDEX, azimuth, distance, gain,
|
||||
AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||
|
||||
++stats.hrtfRenders;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,10 +53,5 @@ macro(add_crashpad)
|
|||
POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${CRASHPAD_HANDLER_EXE_PATH} "$<TARGET_FILE_DIR:${TARGET_NAME}>/"
|
||||
)
|
||||
install(
|
||||
PROGRAMS ${CRASHPAD_HANDLER_EXE_PATH}
|
||||
DESTINATION ${INTERFACE_INSTALL_DIR}
|
||||
COMPONENT ${CLIENT_COMPONENT}
|
||||
)
|
||||
endif ()
|
||||
endmacro()
|
||||
|
|
|
@ -22,7 +22,7 @@ macro(optional_win_executable_signing)
|
|||
# setup a post build command to sign the executable
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME} POST_BUILD
|
||||
COMMAND ${SIGNTOOL_EXECUTABLE} sign /fd sha256 /f %HF_PFX_FILE% /p %HF_PFX_PASSPHRASE% /tr http://sha256timestamp.ws.symantec.com/sha256/timestamp /td SHA256 ${EXECUTABLE_PATH}
|
||||
COMMAND ${SIGNTOOL_EXECUTABLE} sign /fd sha256 /f %HF_PFX_FILE% /p %HF_PFX_PASSPHRASE% /tr ${TIMESERVER_URL} /td SHA256 ${EXECUTABLE_PATH}
|
||||
)
|
||||
else ()
|
||||
message(FATAL_ERROR "HF_PFX_PASSPHRASE must be set for executables to be signed.")
|
||||
|
|
74
cmake/macros/TargetOpenEXR.cmake
Normal file
|
@ -0,0 +1,74 @@
|
|||
#
|
||||
# Copyright 2015 High Fidelity, Inc.
|
||||
# Created by Olivier Prat on 2019/03/26
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
macro(TARGET_OPENEXR)
|
||||
if (NOT ANDROID)
|
||||
set(openexr_config_file "${VCPKG_INSTALL_ROOT}/include/OpenEXR/OpenEXRConfig.h")
|
||||
if(EXISTS ${openexr_config_file})
|
||||
file(STRINGS
|
||||
${openexr_config_file}
|
||||
TMP
|
||||
REGEX "#define OPENEXR_VERSION_STRING.*$")
|
||||
string(REGEX MATCHALL "[0-9.]+" OPENEXR_VERSION ${TMP})
|
||||
|
||||
file(STRINGS
|
||||
${openexr_config_file}
|
||||
TMP
|
||||
REGEX "#define OPENEXR_VERSION_MAJOR.*$")
|
||||
string(REGEX MATCHALL "[0-9]" OPENEXR_MAJOR_VERSION ${TMP})
|
||||
|
||||
file(STRINGS
|
||||
${openexr_config_file}
|
||||
TMP
|
||||
REGEX "#define OPENEXR_VERSION_MINOR.*$")
|
||||
string(REGEX MATCHALL "[0-9]" OPENEXR_MINOR_VERSION ${TMP})
|
||||
endif()
|
||||
|
||||
foreach(OPENEXR_LIB
|
||||
IlmImf
|
||||
IlmImfUtil
|
||||
Half
|
||||
Iex
|
||||
IexMath
|
||||
Imath
|
||||
IlmThread)
|
||||
|
||||
# OpenEXR libraries may be suffixed with the version number, so we search
|
||||
# using both versioned and unversioned names.
|
||||
find_library(OPENEXR_${OPENEXR_LIB}_LIBRARY_RELEASE
|
||||
NAMES
|
||||
${OPENEXR_LIB}-${OPENEXR_MAJOR_VERSION}_${OPENEXR_MINOR_VERSION}_s
|
||||
${OPENEXR_LIB}_s
|
||||
|
||||
PATHS ${VCPKG_INSTALL_ROOT}/lib NO_DEFAULT_PATH
|
||||
)
|
||||
#mark_as_advanced(OPENEXR_${OPENEXR_LIB}_LIBRARY)
|
||||
|
||||
if(OPENEXR_${OPENEXR_LIB}_LIBRARY_RELEASE)
|
||||
list(APPEND OPENEXR_LIBRARY_RELEASE ${OPENEXR_${OPENEXR_LIB}_LIBRARY_RELEASE})
|
||||
endif()
|
||||
|
||||
# OpenEXR libraries may be suffixed with the version number, so we search
|
||||
# using both versioned and unversioned names.
|
||||
find_library(OPENEXR_${OPENEXR_LIB}_LIBRARY_DEBUG
|
||||
NAMES
|
||||
${OPENEXR_LIB}-${OPENEXR_MAJOR_VERSION}_${OPENEXR_MINOR_VERSION}_s_d
|
||||
${OPENEXR_LIB}_s_d
|
||||
|
||||
PATHS ${VCPKG_INSTALL_ROOT}/debug/lib NO_DEFAULT_PATH
|
||||
)
|
||||
#mark_as_advanced(OPENEXR_${OPENEXR_LIB}_DEBUG_LIBRARY)
|
||||
|
||||
if(OPENEXR_${OPENEXR_LIB}_LIBRARY_DEBUG)
|
||||
list(APPEND OPENEXR_LIBRARY_DEBUG ${OPENEXR_${OPENEXR_LIB}_LIBRARY_DEBUG})
|
||||
endif()
|
||||
endforeach(OPENEXR_LIB)
|
||||
|
||||
select_library_configurations(OPENEXR)
|
||||
target_link_libraries(${TARGET_NAME} ${OPENEXR_LIBRARY})
|
||||
endif()
|
||||
endmacro()
|
|
@ -1,4 +1,4 @@
|
|||
Source: hifi-deps
|
||||
Version: 0
|
||||
Version: 0.1
|
||||
Description: Collected dependencies for High Fidelity applications
|
||||
Build-Depends: bullet3, draco, etc2comp, glm, nvtt, openssl (windows), tbb (!android&!osx), zlib
|
||||
Build-Depends: bullet3, draco, etc2comp, glm, nvtt, openexr (!android), openssl (windows), tbb (!android&!osx), zlib
|
||||
|
|
4
cmake/ports/openexr/CONTROL
Normal file
|
@ -0,0 +1,4 @@
|
|||
Source: openexr
|
||||
Version: 2.3.0-2
|
||||
Description: OpenEXR is a high dynamic-range (HDR) image file format developed by Industrial Light & Magic for use in computer imaging applications
|
||||
Build-Depends: zlib
|
87
cmake/ports/openexr/FindOpenEXR.cmake
Normal file
|
@ -0,0 +1,87 @@
|
|||
include(FindPackageHandleStandardArgs)
|
||||
|
||||
find_path(OpenEXR_INCLUDE_DIRS OpenEXR/OpenEXRConfig.h)
|
||||
find_path(OPENEXR_INCLUDE_PATHS NAMES ImfRgbaFile.h PATH_SUFFIXES OpenEXR)
|
||||
|
||||
file(STRINGS "${OpenEXR_INCLUDE_DIRS}/OpenEXR/OpenEXRConfig.h" OPENEXR_CONFIG_H)
|
||||
|
||||
string(REGEX REPLACE "^.*define OPENEXR_VERSION_MAJOR ([0-9]+).*$" "\\1" OpenEXR_VERSION_MAJOR "${OPENEXR_CONFIG_H}")
|
||||
string(REGEX REPLACE "^.*define OPENEXR_VERSION_MINOR ([0-9]+).*$" "\\1" OpenEXR_VERSION_MINOR "${OPENEXR_CONFIG_H}")
|
||||
set(OpenEXR_LIB_SUFFIX "${OpenEXR_VERSION_MAJOR}_${OpenEXR_VERSION_MINOR}")
|
||||
|
||||
include(SelectLibraryConfigurations)
|
||||
|
||||
if(NOT OpenEXR_BASE_LIBRARY)
|
||||
find_library(OpenEXR_BASE_LIBRARY_RELEASE NAMES IlmImf-${OpenEXR_LIB_SUFFIX})
|
||||
find_library(OpenEXR_BASE_LIBRARY_DEBUG NAMES IlmImf-${OpenEXR_LIB_SUFFIX}_d)
|
||||
select_library_configurations(OpenEXR_BASE)
|
||||
endif()
|
||||
|
||||
if(NOT OpenEXR_UTIL_LIBRARY)
|
||||
find_library(OpenEXR_UTIL_LIBRARY_RELEASE NAMES IlmImfUtil-${OpenEXR_LIB_SUFFIX})
|
||||
find_library(OpenEXR_UTIL_LIBRARY_DEBUG NAMES IlmImfUtil-${OpenEXR_LIB_SUFFIX}_d)
|
||||
select_library_configurations(OpenEXR_UTIL)
|
||||
endif()
|
||||
|
||||
if(NOT OpenEXR_HALF_LIBRARY)
|
||||
find_library(OpenEXR_HALF_LIBRARY_RELEASE NAMES Half-${OpenEXR_LIB_SUFFIX})
|
||||
find_library(OpenEXR_HALF_LIBRARY_DEBUG NAMES Half-${OpenEXR_LIB_SUFFIX}_d)
|
||||
select_library_configurations(OpenEXR_HALF)
|
||||
endif()
|
||||
|
||||
if(NOT OpenEXR_IEX_LIBRARY)
|
||||
find_library(OpenEXR_IEX_LIBRARY_RELEASE NAMES Iex-${OpenEXR_LIB_SUFFIX})
|
||||
find_library(OpenEXR_IEX_LIBRARY_DEBUG NAMES Iex-${OpenEXR_LIB_SUFFIX}_d)
|
||||
select_library_configurations(OpenEXR_IEX)
|
||||
endif()
|
||||
|
||||
if(NOT OpenEXR_MATH_LIBRARY)
|
||||
find_library(OpenEXR_MATH_LIBRARY_RELEASE NAMES Imath-${OpenEXR_LIB_SUFFIX})
|
||||
find_library(OpenEXR_MATH_LIBRARY_DEBUG NAMES Imath-${OpenEXR_LIB_SUFFIX}_d)
|
||||
select_library_configurations(OpenEXR_MATH)
|
||||
endif()
|
||||
|
||||
if(NOT OpenEXR_THREAD_LIBRARY)
|
||||
find_library(OpenEXR_THREAD_LIBRARY_RELEASE NAMES IlmThread-${OpenEXR_LIB_SUFFIX})
|
||||
find_library(OpenEXR_THREAD_LIBRARY_DEBUG NAMES IlmThread-${OpenEXR_LIB_SUFFIX}_d)
|
||||
select_library_configurations(OpenEXR_THREAD)
|
||||
endif()
|
||||
|
||||
if(NOT OpenEXR_IEXMATH_LIBRARY)
|
||||
find_library(OpenEXR_IEXMATH_LIBRARY_RELEASE NAMES IexMath-${OpenEXR_LIB_SUFFIX})
|
||||
find_library(OpenEXR_IEXMATH_LIBRARY_DEBUG NAMES IexMath-${OpenEXR_LIB_SUFFIX}d)
|
||||
select_library_configurations(OpenEXR_IEXMATH)
|
||||
endif()
|
||||
|
||||
set(OPENEXR_HALF_LIBRARY "${OpenEXR_HALF_LIBRARY}")
|
||||
set(OPENEXR_IEX_LIBRARY "${OpenEXR_IEX_LIBRARY}")
|
||||
set(OPENEXR_IMATH_LIBRARY "${OpenEXR_MATH_LIBRARY}")
|
||||
set(OPENEXR_ILMIMF_LIBRARY "${OpenEXR_BASE_LIBRARY}")
|
||||
set(OPENEXR_ILMIMFUTIL_LIBRARY "${OpenEXR_UTIL_LIBRARY}")
|
||||
set(OPENEXR_ILMTHREAD_LIBRARY "${OpenEXR_THREAD_LIBRARY}")
|
||||
|
||||
set(OpenEXR_LIBRARY "${OpenEXR_BASE_LIBRARY}")
|
||||
|
||||
set(OpenEXR_LIBRARIES
|
||||
${OpenEXR_LIBRARY}
|
||||
${OpenEXR_MATH_LIBRARY}
|
||||
${OpenEXR_IEXMATH_LIBRARY}
|
||||
${OpenEXR_UTIL_LIBRARY}
|
||||
${OpenEXR_HALF_LIBRARY}
|
||||
${OpenEXR_IEX_LIBRARY}
|
||||
${OpenEXR_THREAD_LIBRARY}
|
||||
)
|
||||
|
||||
set(OPENEXR_LIBRARIES
|
||||
${OPENEXR_HALF_LIBRARY}
|
||||
${OPENEXR_IEX_LIBRARY}
|
||||
${OPENEXR_IMATH_LIBRARY}
|
||||
${OPENEXR_ILMIMF_LIBRARY}
|
||||
${OPENEXR_ILMTHREAD_LIBRARY}
|
||||
)
|
||||
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(OpenEXR REQUIRED_VARS OpenEXR_LIBRARIES OpenEXR_INCLUDE_DIRS)
|
||||
|
||||
if(OpenEXR_FOUND)
|
||||
set(OPENEXR_FOUND 1)
|
||||
endif()
|
19
cmake/ports/openexr/fix_install_ilmimf.patch
Normal file
|
@ -0,0 +1,19 @@
|
|||
diff --git a/OpenEXR/IlmImf/CMakeLists.txt b/OpenEXR/IlmImf/CMakeLists.txt
|
||||
index e1a8740..d31cf68 100644
|
||||
--- a/OpenEXR/IlmImf/CMakeLists.txt
|
||||
+++ b/OpenEXR/IlmImf/CMakeLists.txt
|
||||
@@ -2,14 +2,6 @@
|
||||
|
||||
SET(CMAKE_INCLUDE_CURRENT_DIR 1)
|
||||
|
||||
-IF (WIN32)
|
||||
- SET(RUNTIME_DIR ${OPENEXR_PACKAGE_PREFIX}/bin)
|
||||
- SET(WORKING_DIR ${RUNTIME_DIR})
|
||||
-ELSE ()
|
||||
- SET(RUNTIME_DIR ${OPENEXR_PACKAGE_PREFIX}/lib)
|
||||
- SET(WORKING_DIR .)
|
||||
-ENDIF ()
|
||||
-
|
||||
SET(BUILD_B44EXPLOGTABLE OFF)
|
||||
IF (NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/b44ExpLogTable.h")
|
||||
SET(BUILD_B44EXPLOGTABLE ON)
|
74
cmake/ports/openexr/portfile.cmake
Normal file
|
@ -0,0 +1,74 @@
|
|||
include(vcpkg_common_functions)
|
||||
|
||||
set(OPENEXR_VERSION 2.3.0)
|
||||
set(OPENEXR_HASH 268ae64b40d21d662f405fba97c307dad1456b7d996a447aadafd41b640ca736d4851d9544b4741a94e7b7c335fe6e9d3b16180e710671abfc0c8b2740b147b2)
|
||||
|
||||
vcpkg_from_github(
|
||||
OUT_SOURCE_PATH SOURCE_PATH
|
||||
REPO openexr/openexr
|
||||
REF v${OPENEXR_VERSION}
|
||||
SHA512 ${OPENEXR_HASH}
|
||||
HEAD_REF master
|
||||
PATCHES "fix_install_ilmimf.patch"
|
||||
)
|
||||
|
||||
set(OPENEXR_STATIC ON)
|
||||
set(OPENEXR_SHARED OFF)
|
||||
|
||||
vcpkg_configure_cmake(SOURCE_PATH ${SOURCE_PATH}
|
||||
PREFER_NINJA
|
||||
OPTIONS
|
||||
-DOPENEXR_BUILD_PYTHON_LIBS=OFF
|
||||
-DOPENEXR_BUILD_VIEWERS=OFF
|
||||
-DOPENEXR_ENABLE_TESTS=OFF
|
||||
-DOPENEXR_RUN_FUZZ_TESTS=OFF
|
||||
-DOPENEXR_BUILD_SHARED=${OPENEXR_SHARED}
|
||||
-DOPENEXR_BUILD_STATIC=${OPENEXR_STATIC}
|
||||
OPTIONS_DEBUG
|
||||
-DILMBASE_PACKAGE_PREFIX=${CURRENT_INSTALLED_DIR}/debug
|
||||
OPTIONS_RELEASE
|
||||
-DILMBASE_PACKAGE_PREFIX=${CURRENT_INSTALLED_DIR})
|
||||
|
||||
vcpkg_install_cmake()
|
||||
|
||||
file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/include)
|
||||
file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/debug/share)
|
||||
|
||||
# NOTE: Only use ".exe" extension on Windows executables.
|
||||
# Is there a cleaner way to do this?
|
||||
if(WIN32)
|
||||
set(EXECUTABLE_SUFFIX ".exe")
|
||||
else()
|
||||
set(EXECUTABLE_SUFFIX "")
|
||||
endif()
|
||||
file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrenvmap${EXECUTABLE_SUFFIX})
|
||||
file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrheader${EXECUTABLE_SUFFIX})
|
||||
file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrmakepreview${EXECUTABLE_SUFFIX})
|
||||
file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrmaketiled${EXECUTABLE_SUFFIX})
|
||||
file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrmultipart${EXECUTABLE_SUFFIX})
|
||||
file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrmultiview${EXECUTABLE_SUFFIX})
|
||||
file(REMOVE ${CURRENT_PACKAGES_DIR}/debug/bin/exrstdattr${EXECUTABLE_SUFFIX})
|
||||
file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrenvmap${EXECUTABLE_SUFFIX})
|
||||
file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrheader${EXECUTABLE_SUFFIX})
|
||||
file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrmakepreview${EXECUTABLE_SUFFIX})
|
||||
file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrmaketiled${EXECUTABLE_SUFFIX})
|
||||
file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrmultipart${EXECUTABLE_SUFFIX})
|
||||
file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrmultiview${EXECUTABLE_SUFFIX})
|
||||
file(REMOVE ${CURRENT_PACKAGES_DIR}/bin/exrstdattr${EXECUTABLE_SUFFIX})
|
||||
|
||||
vcpkg_copy_pdbs()
|
||||
|
||||
if (OPENEXR_STATIC)
|
||||
file(REMOVE_RECURSE ${CURRENT_PACKAGES_DIR}/bin ${CURRENT_PACKAGES_DIR}/debug/bin)
|
||||
endif()
|
||||
|
||||
if (VCPKG_CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
set(OPENEXR_PORT_DIR "openexr")
|
||||
else()
|
||||
set(OPENEXR_PORT_DIR "OpenEXR")
|
||||
endif()
|
||||
|
||||
file(COPY ${SOURCE_PATH}/LICENSE DESTINATION ${CURRENT_PACKAGES_DIR}/share/${OPENEXR_PORT_DIR})
|
||||
file(RENAME ${CURRENT_PACKAGES_DIR}/share/${OPENEXR_PORT_DIR}/LICENSE ${CURRENT_PACKAGES_DIR}/share/${OPENEXR_PORT_DIR}/copyright)
|
||||
|
||||
file(COPY ${CMAKE_CURRENT_LIST_DIR}/FindOpenEXR.cmake DESTINATION ${CURRENT_PACKAGES_DIR}/share/${OPENEXR_PORT_DIR})
|
|
@ -481,3 +481,15 @@ function prepareAccessTokenPrompt(callback) {
|
|||
swal.close();
|
||||
});
|
||||
}
|
||||
|
||||
function getMetaverseUrl(callback) {
|
||||
$.ajax('/api/metaverse_info', {
|
||||
success: function(data) {
|
||||
callback(data.metaverse_url);
|
||||
},
|
||||
error: function() {
|
||||
callback(URLs.METAVERSE_URL);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -16,47 +16,55 @@ $(document).ready(function(){
|
|||
|
||||
Settings.extraGroupsAtEnd = Settings.extraDomainGroupsAtEnd;
|
||||
Settings.extraGroupsAtIndex = Settings.extraDomainGroupsAtIndex;
|
||||
var METAVERSE_URL = URLs.METAVERSE_URL;
|
||||
|
||||
Settings.afterReloadActions = function() {
|
||||
// call our method to setup the HF account button
|
||||
setupHFAccountButton();
|
||||
|
||||
// call our method to setup the place names table
|
||||
setupPlacesTable();
|
||||
getMetaverseUrl(function(metaverse_url) {
|
||||
METAVERSE_URL = metaverse_url;
|
||||
|
||||
setupDomainNetworkingSettings();
|
||||
// setupDomainLabelSetting();
|
||||
// call our method to setup the HF account button
|
||||
setupHFAccountButton();
|
||||
|
||||
setupSettingsBackup();
|
||||
// call our method to setup the place names table
|
||||
setupPlacesTable();
|
||||
|
||||
if (domainIDIsSet()) {
|
||||
// now, ask the API for what places, if any, point to this domain
|
||||
reloadDomainInfo();
|
||||
setupDomainNetworkingSettings();
|
||||
// setupDomainLabelSetting();
|
||||
|
||||
// we need to ask the API what a shareable name for this domain is
|
||||
getShareName(function(success, shareName) {
|
||||
if (success) {
|
||||
var shareLink = "https://hifi.place/" + shareName;
|
||||
$('#visit-domain-link').attr("href", shareLink).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
setupSettingsBackup();
|
||||
|
||||
if (Settings.data.values.wizard.cloud_domain) {
|
||||
$('#manage-cloud-domains-link').show();
|
||||
if (domainIDIsSet()) {
|
||||
// now, ask the API for what places, if any, point to this domain
|
||||
reloadDomainInfo();
|
||||
|
||||
var cloudWizardExit = qs["cloud-wizard-exit"];
|
||||
if (cloudWizardExit != undefined) {
|
||||
$('#cloud-domains-alert').show();
|
||||
// we need to ask the API what a shareable name for this domain is
|
||||
getShareName(function(success, shareName) {
|
||||
if (success) {
|
||||
var shareLink = "https://hifi.place/" + shareName;
|
||||
$('#visit-domain-link').attr("href", shareLink).show();
|
||||
}
|
||||
});
|
||||
} else if (accessTokenIsSet()) {
|
||||
$('#' + Settings.GET_TEMPORARY_NAME_BTN_ID).show();
|
||||
}
|
||||
|
||||
$(Settings.DOMAIN_ID_SELECTOR).siblings('span').append("</br><strong>Changing the domain ID for a Cloud Domain may result in an incorrect status for the domain on your Cloud Domains page.</strong>");
|
||||
} else {
|
||||
// append the domain selection modal
|
||||
appendDomainIDButtons();
|
||||
}
|
||||
if (Settings.data.values.wizard.cloud_domain) {
|
||||
$('#manage-cloud-domains-link').show();
|
||||
|
||||
handleAction();
|
||||
var cloudWizardExit = qs["cloud-wizard-exit"];
|
||||
if (cloudWizardExit != undefined) {
|
||||
$('#cloud-domains-alert').show();
|
||||
}
|
||||
|
||||
$(Settings.DOMAIN_ID_SELECTOR).siblings('span').append("</br><strong>Changing the domain ID for a Cloud Domain may result in an incorrect status for the domain on your Cloud Domains page.</strong>");
|
||||
} else {
|
||||
// append the domain selection modal
|
||||
appendDomainIDButtons();
|
||||
}
|
||||
|
||||
handleAction();
|
||||
});
|
||||
}
|
||||
|
||||
Settings.handlePostSettings = function(formJSON) {
|
||||
|
@ -258,7 +266,7 @@ $(document).ready(function(){
|
|||
buttonSetting.button_label = "Connect High Fidelity Account";
|
||||
buttonSetting.html_id = Settings.CONNECT_ACCOUNT_BTN_ID;
|
||||
|
||||
buttonSetting.href = URLs.METAVERSE_URL + "/user/tokens/new?for_domain_server=true";
|
||||
buttonSetting.href = METAVERSE_URL + "/user/tokens/new?for_domain_server=true";
|
||||
|
||||
// since we do not have an access token we change hide domain ID and auto networking settings
|
||||
// without an access token niether of them can do anything
|
||||
|
@ -645,7 +653,7 @@ $(document).ready(function(){
|
|||
label: 'Places',
|
||||
html_id: Settings.PLACES_TABLE_ID,
|
||||
help: "The following places currently point to this domain.</br>To point places to this domain, "
|
||||
+ " go to the <a href='" + URLs.METAVERSE_URL + "/user/places'>My Places</a> "
|
||||
+ " go to the <a href='" + METAVERSE_URL + "/user/places'>My Places</a> "
|
||||
+ "page in your High Fidelity Metaverse account.",
|
||||
read_only: true,
|
||||
can_add_new_rows: false,
|
||||
|
@ -678,12 +686,9 @@ $(document).ready(function(){
|
|||
var errorEl = createDomainLoadingError("There was an error retrieving your places.");
|
||||
$("#" + Settings.PLACES_TABLE_ID).after(errorEl);
|
||||
|
||||
// do we have a domain ID?
|
||||
if (!domainIDIsSet()) {
|
||||
// we don't have a domain ID - add a button to offer the user a chance to get a temporary one
|
||||
var temporaryPlaceButton = dynamicButton(Settings.GET_TEMPORARY_NAME_BTN_ID, 'Get a temporary place name');
|
||||
$('#' + Settings.PLACES_TABLE_ID).after(temporaryPlaceButton);
|
||||
}
|
||||
var temporaryPlaceButton = dynamicButton(Settings.GET_TEMPORARY_NAME_BTN_ID, 'Get a temporary place name');
|
||||
temporaryPlaceButton.hide();
|
||||
$('#' + Settings.PLACES_TABLE_ID).after(temporaryPlaceButton);
|
||||
if (accessTokenIsSet()) {
|
||||
appendAddButtonToPlacesTable();
|
||||
}
|
||||
|
@ -774,8 +779,9 @@ $(document).ready(function(){
|
|||
|
||||
// check if we have owner_places (for a real domain) or a name (for a temporary domain)
|
||||
if (data.status == "success") {
|
||||
$('#' + Settings.GET_TEMPORARY_NAME_BTN_ID).hide();
|
||||
$('.domain-loading-hide').show();
|
||||
if (data.domain.owner_places) {
|
||||
if (data.domain.owner_places && data.domain.owner_places.length > 0) {
|
||||
// add a table row for each of these names
|
||||
_.each(data.domain.owner_places, function(place){
|
||||
$('#' + Settings.PLACES_TABLE_ID + " tbody").append(placeTableRowForPlaceObject(place));
|
||||
|
@ -783,8 +789,9 @@ $(document).ready(function(){
|
|||
} else if (data.domain.name) {
|
||||
// add a table row for this temporary domain name
|
||||
$('#' + Settings.PLACES_TABLE_ID + " tbody").append(placeTableRow(data.domain.name, '/', true));
|
||||
} else {
|
||||
$('#' + Settings.GET_TEMPORARY_NAME_BTN_ID).show();
|
||||
}
|
||||
|
||||
// Update label
|
||||
if (showOrHideLabel()) {
|
||||
var label = data.domain.label;
|
||||
|
@ -953,7 +960,7 @@ $(document).ready(function(){
|
|||
modal_buttons["success"] = {
|
||||
label: 'Create new domain',
|
||||
callback: function() {
|
||||
window.open(URLs.METAVERSE_URL + "/user/domains", '_blank');
|
||||
window.open(METAVERSE_URL + "/user/domains", '_blank');
|
||||
}
|
||||
}
|
||||
modal_body = "<p>You do not have any domains in your High Fidelity account." +
|
||||
|
@ -1001,7 +1008,7 @@ $(document).ready(function(){
|
|||
showSpinnerAlert('Creating temporary place name');
|
||||
|
||||
// make a get request to get a temporary domain
|
||||
$.post(URLs.METAVERSE_URL + '/api/v1/domains/temporary', function(data){
|
||||
$.post(METAVERSE_URL + '/api/v1/domains/temporary', function(data){
|
||||
if (data.status == "success") {
|
||||
var domain = data.data.domain;
|
||||
|
||||
|
|
|
@ -1010,7 +1010,7 @@ void DomainGatekeeper::refreshGroupsCache() {
|
|||
nodeList->eachNode([this](const SharedNodePointer& node) {
|
||||
if (!node->getPermissions().isAssignment) {
|
||||
// this node is an agent
|
||||
const QString& verifiedUserName = node->getPermissions().getVerifiedUserName();
|
||||
QString verifiedUserName = node->getPermissions().getVerifiedUserName();
|
||||
if (!verifiedUserName.isEmpty()) {
|
||||
getGroupMemberships(verifiedUserName);
|
||||
}
|
||||
|
|
|
@ -1916,6 +1916,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
const QString URI_SETTINGS = "/settings";
|
||||
const QString URI_CONTENT_UPLOAD = "/content/upload";
|
||||
const QString URI_RESTART = "/restart";
|
||||
const QString URI_API_METAVERSE_INFO = "/api/metaverse_info";
|
||||
const QString URI_API_PLACES = "/api/places";
|
||||
const QString URI_API_DOMAINS = "/api/domains";
|
||||
const QString URI_API_DOMAINS_ID = "/api/domains/";
|
||||
|
@ -2164,6 +2165,15 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
} else if (url.path() == URI_RESTART) {
|
||||
connection->respond(HTTPConnection::StatusCode200);
|
||||
restart();
|
||||
return true;
|
||||
} else if (url.path() == URI_API_METAVERSE_INFO) {
|
||||
QJsonObject rootJSON {
|
||||
{ "metaverse_url", NetworkingConstants::METAVERSE_SERVER_URL().toString() }
|
||||
};
|
||||
|
||||
QJsonDocument docJSON{ rootJSON };
|
||||
connectionPtr->respond(HTTPConnection::StatusCode200, docJSON.toJson(), JSON_MIME_TYPE.toUtf8());
|
||||
|
||||
return true;
|
||||
} else if (url.path() == URI_API_DOMAINS) {
|
||||
return forwardMetaverseAPIRequest(connection, "/api/v1/domains", "");
|
||||
|
|
|
@ -197,260 +197,100 @@
|
|||
"id": "rightHandStateMachine",
|
||||
"type": "stateMachine",
|
||||
"data": {
|
||||
"currentState": "rightHandGrasp",
|
||||
"currentState": "rightHandAnimNone",
|
||||
"states": [
|
||||
{
|
||||
"id": "rightHandGrasp",
|
||||
"interpTarget": 3,
|
||||
"id": "rightHandAnimNone",
|
||||
"interpTarget": 1,
|
||||
"interpDuration": 3,
|
||||
"transitions": [
|
||||
{ "var": "isRightIndexPoint", "state": "rightIndexPoint" },
|
||||
{ "var": "isRightThumbRaise", "state": "rightThumbRaise" },
|
||||
{ "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" }
|
||||
{ "var": "rightHandAnimA", "state": "rightHandAnimA" },
|
||||
{ "var": "rightHandAnimB", "state": "rightHandAnimB" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "rightIndexPoint",
|
||||
"interpTarget": 15,
|
||||
"id": "rightHandAnimA",
|
||||
"interpTarget": 1,
|
||||
"interpDuration": 3,
|
||||
"transitions": [
|
||||
{ "var": "isRightHandGrasp", "state": "rightHandGrasp" },
|
||||
{ "var": "isRightThumbRaise", "state": "rightThumbRaise" },
|
||||
{ "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" }
|
||||
{ "var": "rightHandAnimNone", "state": "rightHandAnimNone" },
|
||||
{ "var": "rightHandAnimB", "state": "rightHandAnimB" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "rightThumbRaise",
|
||||
"interpTarget": 15,
|
||||
"id": "rightHandAnimB",
|
||||
"interpTarget": 1,
|
||||
"interpDuration": 3,
|
||||
"transitions": [
|
||||
{ "var": "isRightHandGrasp", "state": "rightHandGrasp" },
|
||||
{ "var": "isRightIndexPoint", "state": "rightIndexPoint" },
|
||||
{ "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "rightIndexPointAndThumbRaise",
|
||||
"interpTarget": 15,
|
||||
"interpDuration": 3,
|
||||
"transitions": [
|
||||
{ "var": "isRightHandGrasp", "state": "rightHandGrasp" },
|
||||
{ "var": "isRightIndexPoint", "state": "rightIndexPoint" },
|
||||
{ "var": "isRightThumbRaise", "state": "rightThumbRaise" }
|
||||
{ "var": "rightHandAnimNone", "state": "rightHandAnimNone" },
|
||||
{ "var": "rightHandAnimA", "state": "rightHandAnimA" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "rightHandGrasp",
|
||||
"type": "blendLinear",
|
||||
"data": {
|
||||
"alpha": 0.0,
|
||||
"alphaVar": "rightHandGraspAlpha"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "rightHandGraspOpen",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/hydra_pose_open_right.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 0.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "rightHandGraspClosed",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/hydra_pose_closed_right.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 0.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "rightIndexPoint",
|
||||
"type": "blendLinear",
|
||||
"data": {
|
||||
"alpha": 0.0,
|
||||
"alphaVar": "rightHandGraspAlpha"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "rightIndexPointOpen",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/touch_point_open_right.fbx",
|
||||
"startFrame": 15.0,
|
||||
"endFrame": 15.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "rightIndexPointClosed",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/touch_point_closed_right.fbx",
|
||||
"startFrame": 15.0,
|
||||
"endFrame": 15.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "rightThumbRaise",
|
||||
"type": "blendLinear",
|
||||
"data": {
|
||||
"alpha": 0.0,
|
||||
"alphaVar": "rightHandGraspAlpha"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "rightThumbRaiseOpen",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/touch_thumb_open_right.fbx",
|
||||
"startFrame": 15.0,
|
||||
"endFrame": 15.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "rightThumbRaiseClosed",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/touch_thumb_closed_right.fbx",
|
||||
"startFrame": 15.0,
|
||||
"endFrame": 15.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "rightIndexPointAndThumbRaise",
|
||||
"type": "blendLinear",
|
||||
"data": {
|
||||
"alpha": 0.0,
|
||||
"alphaVar": "rightHandGraspAlpha"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "rightIndexPointAndThumbRaiseOpen",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/touch_thumb_point_open_right.fbx",
|
||||
"startFrame": 15.0,
|
||||
"endFrame": 15.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "rightIndexPointAndThumbRaiseClosed",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/touch_thumb_point_closed_right.fbx",
|
||||
"startFrame": 15.0,
|
||||
"endFrame": 15.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "leftHandOverlay",
|
||||
"type": "overlay",
|
||||
"data": {
|
||||
"alpha": 0.0,
|
||||
"boneSet": "leftHand",
|
||||
"alphaVar": "leftHandOverlayAlpha"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "leftHandStateMachine",
|
||||
"id": "rightHandAnimNone",
|
||||
"type": "stateMachine",
|
||||
"data": {
|
||||
"currentState": "leftHandGrasp",
|
||||
"currentState": "rightHandGrasp",
|
||||
"states": [
|
||||
{
|
||||
"id": "leftHandGrasp",
|
||||
"id": "rightHandGrasp",
|
||||
"interpTarget": 3,
|
||||
"interpDuration": 3,
|
||||
"transitions": [
|
||||
{ "var": "isLeftIndexPoint", "state": "leftIndexPoint" },
|
||||
{ "var": "isLeftThumbRaise", "state": "leftThumbRaise" },
|
||||
{ "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" }
|
||||
{ "var": "isRightIndexPoint", "state": "rightIndexPoint" },
|
||||
{ "var": "isRightThumbRaise", "state": "rightThumbRaise" },
|
||||
{ "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "leftIndexPoint",
|
||||
"id": "rightIndexPoint",
|
||||
"interpTarget": 15,
|
||||
"interpDuration": 3,
|
||||
"transitions": [
|
||||
{ "var": "isLeftHandGrasp", "state": "leftHandGrasp" },
|
||||
{ "var": "isLeftThumbRaise", "state": "leftThumbRaise" },
|
||||
{ "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" }
|
||||
{ "var": "isRightHandGrasp", "state": "rightHandGrasp" },
|
||||
{ "var": "isRightThumbRaise", "state": "rightThumbRaise" },
|
||||
{ "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "leftThumbRaise",
|
||||
"id": "rightThumbRaise",
|
||||
"interpTarget": 15,
|
||||
"interpDuration": 3,
|
||||
"transitions": [
|
||||
{ "var": "isLeftHandGrasp", "state": "leftHandGrasp" },
|
||||
{ "var": "isLeftIndexPoint", "state": "leftIndexPoint" },
|
||||
{ "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" }
|
||||
{ "var": "isRightHandGrasp", "state": "rightHandGrasp" },
|
||||
{ "var": "isRightIndexPoint", "state": "rightIndexPoint" },
|
||||
{ "var": "isRightIndexPointAndThumbRaise", "state": "rightIndexPointAndThumbRaise" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "leftIndexPointAndThumbRaise",
|
||||
"id": "rightIndexPointAndThumbRaise",
|
||||
"interpTarget": 15,
|
||||
"interpDuration": 3,
|
||||
"transitions": [
|
||||
{ "var": "isLeftHandGrasp", "state": "leftHandGrasp" },
|
||||
{ "var": "isLeftIndexPoint", "state": "leftIndexPoint" },
|
||||
{ "var": "isLeftThumbRaise", "state": "leftThumbRaise" }
|
||||
{ "var": "isRightHandGrasp", "state": "rightHandGrasp" },
|
||||
{ "var": "isRightIndexPoint", "state": "rightIndexPoint" },
|
||||
{ "var": "isRightThumbRaise", "state": "rightThumbRaise" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "leftHandGrasp",
|
||||
"id": "rightHandGrasp",
|
||||
"type": "blendLinear",
|
||||
"data": {
|
||||
"alpha": 0.0,
|
||||
"alphaVar": "leftHandGraspAlpha"
|
||||
"alphaVar": "rightHandGraspAlpha"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "leftHandGraspOpen",
|
||||
"id": "rightHandGraspOpen",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/hydra_pose_open_left.fbx",
|
||||
"url": "qrc:///avatar/animations/hydra_pose_open_right.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 0.0,
|
||||
"timeScale": 1.0,
|
||||
|
@ -459,12 +299,12 @@
|
|||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "leftHandGraspClosed",
|
||||
"id": "rightHandGraspClosed",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/hydra_pose_closed_left.fbx",
|
||||
"startFrame": 10.0,
|
||||
"endFrame": 10.0,
|
||||
"url": "qrc:///avatar/animations/hydra_pose_closed_right.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 0.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
|
@ -473,18 +313,18 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id": "leftIndexPoint",
|
||||
"id": "rightIndexPoint",
|
||||
"type": "blendLinear",
|
||||
"data": {
|
||||
"data": {
|
||||
"alpha": 0.0,
|
||||
"alphaVar": "leftHandGraspAlpha"
|
||||
"alphaVar": "rightHandGraspAlpha"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "leftIndexPointOpen",
|
||||
"id": "rightIndexPointOpen",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/touch_point_open_left.fbx",
|
||||
"url": "qrc:///avatar/animations/touch_point_open_right.fbx",
|
||||
"startFrame": 15.0,
|
||||
"endFrame": 15.0,
|
||||
"timeScale": 1.0,
|
||||
|
@ -493,10 +333,10 @@
|
|||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "leftIndexPointClosed",
|
||||
"id": "rightIndexPointClosed",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/touch_point_closed_left.fbx",
|
||||
"url": "qrc:///avatar/animations/touch_point_closed_right.fbx",
|
||||
"startFrame": 15.0,
|
||||
"endFrame": 15.0,
|
||||
"timeScale": 1.0,
|
||||
|
@ -507,18 +347,18 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id": "leftThumbRaise",
|
||||
"id": "rightThumbRaise",
|
||||
"type": "blendLinear",
|
||||
"data": {
|
||||
"data": {
|
||||
"alpha": 0.0,
|
||||
"alphaVar": "leftHandGraspAlpha"
|
||||
"alphaVar": "rightHandGraspAlpha"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "leftThumbRaiseOpen",
|
||||
"id": "rightThumbRaiseOpen",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/touch_thumb_open_left.fbx",
|
||||
"url": "qrc:///avatar/animations/touch_thumb_open_right.fbx",
|
||||
"startFrame": 15.0,
|
||||
"endFrame": 15.0,
|
||||
"timeScale": 1.0,
|
||||
|
@ -527,10 +367,10 @@
|
|||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "leftThumbRaiseClosed",
|
||||
"id": "rightThumbRaiseClosed",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/touch_thumb_closed_left.fbx",
|
||||
"url": "qrc:///avatar/animations/touch_thumb_closed_right.fbx",
|
||||
"startFrame": 15.0,
|
||||
"endFrame": 15.0,
|
||||
"timeScale": 1.0,
|
||||
|
@ -541,18 +381,18 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id": "leftIndexPointAndThumbRaise",
|
||||
"id": "rightIndexPointAndThumbRaise",
|
||||
"type": "blendLinear",
|
||||
"data": {
|
||||
"data": {
|
||||
"alpha": 0.0,
|
||||
"alphaVar": "leftHandGraspAlpha"
|
||||
"alphaVar": "rightHandGraspAlpha"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "leftIndexPointAndThumbRaiseOpen",
|
||||
"id": "rightIndexPointAndThumbRaiseOpen",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/touch_thumb_point_open_left.fbx",
|
||||
"url": "qrc:///avatar/animations/touch_thumb_point_open_right.fbx",
|
||||
"startFrame": 15.0,
|
||||
"endFrame": 15.0,
|
||||
"timeScale": 1.0,
|
||||
|
@ -561,10 +401,10 @@
|
|||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "leftIndexPointAndThumbRaiseClosed",
|
||||
"id": "rightIndexPointAndThumbRaiseClosed",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/touch_thumb_point_closed_left.fbx",
|
||||
"url": "qrc:///avatar/animations/touch_thumb_point_closed_right.fbx",
|
||||
"startFrame": 15.0,
|
||||
"endFrame": 15.0,
|
||||
"timeScale": 1.0,
|
||||
|
@ -577,6 +417,290 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"id": "rightHandAnimA",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/touch_thumb_point_open_right.fbx",
|
||||
"startFrame": 15.0,
|
||||
"endFrame": 15.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "rightHandAnimB",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/touch_thumb_point_open_right.fbx",
|
||||
"startFrame": 15.0,
|
||||
"endFrame": 15.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "leftHandOverlay",
|
||||
"type": "overlay",
|
||||
"data": {
|
||||
"alpha": 0.0,
|
||||
"boneSet": "leftHand",
|
||||
"alphaVar": "leftHandOverlayAlpha"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "leftHandStateMachine",
|
||||
"type": "stateMachine",
|
||||
"data": {
|
||||
"currentState": "leftHandAnimNone",
|
||||
"states": [
|
||||
{
|
||||
"id": "leftHandAnimNone",
|
||||
"interpTarget": 1,
|
||||
"interpDuration": 3,
|
||||
"transitions": [
|
||||
{ "var": "leftHandAnimA", "state": "leftHandAnimA" },
|
||||
{ "var": "leftHandAnimB", "state": "leftHandAnimB" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "leftHandAnimA",
|
||||
"interpTarget": 1,
|
||||
"interpDuration": 3,
|
||||
"transitions": [
|
||||
{ "var": "leftHandAnimNone", "state": "leftHandAnimNone" },
|
||||
{ "var": "leftHandAnimB", "state": "leftHandAnimB" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "leftHandAnimB",
|
||||
"interpTarget": 1,
|
||||
"interpDuration": 3,
|
||||
"transitions": [
|
||||
{ "var": "leftHandAnimNone", "state": "leftHandAnimNone" },
|
||||
{ "var": "leftHandAnimA", "state": "leftHandAnimA" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "leftHandAnimNone",
|
||||
"type": "stateMachine",
|
||||
"data": {
|
||||
"currentState": "leftHandGrasp",
|
||||
"states": [
|
||||
{
|
||||
"id": "leftHandGrasp",
|
||||
"interpTarget": 3,
|
||||
"interpDuration": 3,
|
||||
"transitions": [
|
||||
{ "var": "isLeftIndexPoint", "state": "leftIndexPoint" },
|
||||
{ "var": "isLeftThumbRaise", "state": "leftThumbRaise" },
|
||||
{ "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "leftIndexPoint",
|
||||
"interpTarget": 15,
|
||||
"interpDuration": 3,
|
||||
"transitions": [
|
||||
{ "var": "isLeftHandGrasp", "state": "leftHandGrasp" },
|
||||
{ "var": "isLeftThumbRaise", "state": "leftThumbRaise" },
|
||||
{ "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "leftThumbRaise",
|
||||
"interpTarget": 15,
|
||||
"interpDuration": 3,
|
||||
"transitions": [
|
||||
{ "var": "isLeftHandGrasp", "state": "leftHandGrasp" },
|
||||
{ "var": "isLeftIndexPoint", "state": "leftIndexPoint" },
|
||||
{ "var": "isLeftIndexPointAndThumbRaise", "state": "leftIndexPointAndThumbRaise" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "leftIndexPointAndThumbRaise",
|
||||
"interpTarget": 15,
|
||||
"interpDuration": 3,
|
||||
"transitions": [
|
||||
{ "var": "isLeftHandGrasp", "state": "leftHandGrasp" },
|
||||
{ "var": "isLeftIndexPoint", "state": "leftIndexPoint" },
|
||||
{ "var": "isLeftThumbRaise", "state": "leftThumbRaise" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "leftHandGrasp",
|
||||
"type": "blendLinear",
|
||||
"data": {
|
||||
"alpha": 0.0,
|
||||
"alphaVar": "leftHandGraspAlpha"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "leftHandGraspOpen",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/hydra_pose_open_left.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 0.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "leftHandGraspClosed",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/hydra_pose_closed_left.fbx",
|
||||
"startFrame": 10.0,
|
||||
"endFrame": 10.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "leftIndexPoint",
|
||||
"type": "blendLinear",
|
||||
"data": {
|
||||
"alpha": 0.0,
|
||||
"alphaVar": "leftHandGraspAlpha"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "leftIndexPointOpen",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/touch_point_open_left.fbx",
|
||||
"startFrame": 15.0,
|
||||
"endFrame": 15.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "leftIndexPointClosed",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/touch_point_closed_left.fbx",
|
||||
"startFrame": 15.0,
|
||||
"endFrame": 15.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "leftThumbRaise",
|
||||
"type": "blendLinear",
|
||||
"data": {
|
||||
"alpha": 0.0,
|
||||
"alphaVar": "leftHandGraspAlpha"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "leftThumbRaiseOpen",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/touch_thumb_open_left.fbx",
|
||||
"startFrame": 15.0,
|
||||
"endFrame": 15.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "leftThumbRaiseClosed",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/touch_thumb_closed_left.fbx",
|
||||
"startFrame": 15.0,
|
||||
"endFrame": 15.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "leftIndexPointAndThumbRaise",
|
||||
"type": "blendLinear",
|
||||
"data": {
|
||||
"alpha": 0.0,
|
||||
"alphaVar": "leftHandGraspAlpha"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "leftIndexPointAndThumbRaiseOpen",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/touch_thumb_point_open_left.fbx",
|
||||
"startFrame": 15.0,
|
||||
"endFrame": 15.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "leftIndexPointAndThumbRaiseClosed",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/touch_thumb_point_closed_left.fbx",
|
||||
"startFrame": 15.0,
|
||||
"endFrame": 15.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "leftHandAnimA",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/touch_thumb_point_open_left.fbx",
|
||||
"startFrame": 15.0,
|
||||
"endFrame": 15.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "leftHandAnimB",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/touch_thumb_point_open_left.fbx",
|
||||
"startFrame": 15.0,
|
||||
"endFrame": 15.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "mainStateMachine",
|
||||
"type": "stateMachine",
|
||||
"data": {
|
||||
|
@ -1594,4 +1718,4 @@
|
|||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,11 +13,11 @@
|
|||
|
||||
{ "from": "OculusTouch.LY", "to": "Standard.LY",
|
||||
"filters": [
|
||||
{ "type": "deadZone", "min": 0.7 },
|
||||
{ "type": "deadZone", "min": 0.15 },
|
||||
"invert"
|
||||
]
|
||||
},
|
||||
{ "from": "OculusTouch.LX", "filters": { "type": "deadZone", "min": 0.7 }, "to": "Standard.LX" },
|
||||
{ "from": "OculusTouch.LX", "filters": { "type": "deadZone", "min": 0.15 }, "to": "Standard.LX" },
|
||||
{ "from": "OculusTouch.LT", "to": "Standard.LTClick",
|
||||
"peek": true,
|
||||
"filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ]
|
||||
|
@ -29,11 +29,11 @@
|
|||
|
||||
{ "from": "OculusTouch.RY", "to": "Standard.RY",
|
||||
"filters": [
|
||||
{ "type": "deadZone", "min": 0.7 },
|
||||
{ "type": "deadZone", "min": 0.15 },
|
||||
"invert"
|
||||
]
|
||||
},
|
||||
{ "from": "OculusTouch.RX", "filters": { "type": "deadZone", "min": 0.7 }, "to": "Standard.RX" },
|
||||
{ "from": "OculusTouch.RX", "filters": { "type": "deadZone", "min": 0.15 }, "to": "Standard.RX" },
|
||||
{ "from": "OculusTouch.RT", "to": "Standard.RTClick",
|
||||
"peek": true,
|
||||
"filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ]
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
{
|
||||
"name": "Standard to Action",
|
||||
"channels": [
|
||||
{ "from": "Standard.LY", "to": "Actions.TranslateZ" },
|
||||
{ "from": "Standard.LY",
|
||||
"when": ["Application.RightHandDominant", "!Standard.RY"],
|
||||
"to": "Actions.TranslateZ"
|
||||
},
|
||||
|
||||
{ "from": "Standard.LX",
|
||||
"when": [
|
||||
"Application.InHMD", "!Application.AdvancedMovement",
|
||||
"Application.InHMD", "!Application.AdvancedMovement", "Application.RightHandDominant",
|
||||
"Application.SnapTurn", "!Standard.RX"
|
||||
],
|
||||
"to": "Actions.StepYaw",
|
||||
|
@ -18,14 +21,14 @@
|
|||
]
|
||||
},
|
||||
{ "from": "Standard.LX", "to": "Actions.TranslateX",
|
||||
"when": [ "Application.AdvancedMovement" ]
|
||||
"when": [ "Application.AdvancedMovement", "Application.StrafeEnabled", "Application.RightHandDominant" ]
|
||||
},
|
||||
{ "from": "Standard.LX", "to": "Actions.Yaw",
|
||||
"when": [ "!Application.AdvancedMovement", "!Application.SnapTurn" ]
|
||||
"when": [ "!Application.AdvancedMovement", "!Application.SnapTurn", "Application.RightHandDominant" ]
|
||||
},
|
||||
|
||||
{ "from": "Standard.RX",
|
||||
"when": [ "Application.SnapTurn" ],
|
||||
"when": [ "Application.SnapTurn", "Application.RightHandDominant" ],
|
||||
"to": "Actions.StepYaw",
|
||||
"filters":
|
||||
[
|
||||
|
@ -36,20 +39,69 @@
|
|||
]
|
||||
},
|
||||
{ "from": "Standard.RX", "to": "Actions.Yaw",
|
||||
"when": [ "!Application.SnapTurn" ]
|
||||
"when": [ "!Application.SnapTurn", "Application.RightHandDominant" ]
|
||||
},
|
||||
|
||||
{ "from": "Standard.LeftSecondaryThumb",
|
||||
"when": [ "Application.Grounded", "Application.LeftHandDominant" ],
|
||||
"to": "Actions.Up"
|
||||
},
|
||||
|
||||
{ "from": "Standard.LeftSecondaryThumb",
|
||||
"when": "Application.LeftHandDominant",
|
||||
"to": "Actions.Up"
|
||||
},
|
||||
|
||||
{ "from": "Standard.RY",
|
||||
"when": "Application.Grounded",
|
||||
"to": "Actions.Up",
|
||||
"when": ["Application.LeftHandDominant", "!Standard.LY"],
|
||||
"to": "Actions.TranslateZ"
|
||||
},
|
||||
|
||||
{ "from": "Standard.RX",
|
||||
"when": [
|
||||
"Application.InHMD", "!Application.AdvancedMovement", "Application.LeftHandDominant",
|
||||
"Application.SnapTurn", "!Standard.RX"
|
||||
],
|
||||
"to": "Actions.StepYaw",
|
||||
"filters":
|
||||
[
|
||||
{ "type": "deadZone", "min": 0.6 },
|
||||
"invert"
|
||||
{ "type": "deadZone", "min": 0.15 },
|
||||
"constrainToInteger",
|
||||
{ "type": "pulse", "interval": 0.25 },
|
||||
{ "type": "scale", "scale": 22.5 }
|
||||
]
|
||||
},
|
||||
{ "from": "Standard.RX", "to": "Actions.TranslateX",
|
||||
"when": [ "Application.AdvancedMovement", "Application.StrafeEnabled", "Application.LeftHandDominant" ]
|
||||
},
|
||||
{ "from": "Standard.RX", "to": "Actions.Yaw",
|
||||
"when": [ "!Application.AdvancedMovement", "!Application.SnapTurn", "Application.LeftHandDominant" ]
|
||||
},
|
||||
|
||||
{ "from": "Standard.RY", "to": "Actions.Up", "filters": "invert"},
|
||||
{ "from": "Standard.LX",
|
||||
"when": [ "Application.SnapTurn", "Application.LeftHandDominant" ],
|
||||
"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.Yaw",
|
||||
"when": [ "!Application.SnapTurn", "Application.LeftHandDominant" ]
|
||||
},
|
||||
|
||||
{ "from": "Standard.RightSecondaryThumb",
|
||||
"when": [ "Application.Grounded", "Application.RightHandDominant" ],
|
||||
"to": "Actions.Up"
|
||||
},
|
||||
|
||||
{ "from": "Standard.RightSecondaryThumb",
|
||||
"when": "Application.RightHandDominant",
|
||||
"to": "Actions.Up"
|
||||
},
|
||||
|
||||
{ "from": "Standard.Back", "to": "Actions.CycleCamera" },
|
||||
{ "from": "Standard.Start", "to": "Actions.ContextMenu" },
|
||||
|
@ -128,4 +180,4 @@
|
|||
{ "from": "Standard.TrackedObject14", "to" : "Actions.TrackedObject14" },
|
||||
{ "from": "Standard.TrackedObject15", "to" : "Actions.TrackedObject15" }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,8 +10,9 @@
|
|||
"filters": [ { "type": "hysteresis", "min": 0.7, "max": 0.75 } ]
|
||||
},
|
||||
|
||||
{ "from": "Vive.LY", "when": "Vive.LSY", "filters": ["invert"], "to": "Standard.LY" },
|
||||
{ "from": "Vive.LX", "when": "Vive.LSX", "to": "Standard.LX" },
|
||||
{ "from": "Vive.LY", "when": "Vive.LS", "filters": [ { "type": "deadZone", "min": 0.15 }, "invert" ], "to": "Standard.LY" },
|
||||
{ "from": "Vive.LX", "when": ["Vive.LS", "Application.RightHandDominant"], "filters": { "type": "deadZone", "min": 0.15 }, "to": "Standard.LX" },
|
||||
{ "from": "Vive.LX", "when": ["Vive.LS", "Vive.LSX", "!Vive.LSY", "Application.LeftHandDominant"], "filters": { "type": "deadZone", "min": 0.15 }, "to": "Standard.LX" },
|
||||
{
|
||||
"from": "Vive.LT", "to": "Standard.LT",
|
||||
"filters": [
|
||||
|
@ -28,8 +29,9 @@
|
|||
},
|
||||
{ "from": "Vive.LSTouch", "to": "Standard.LSTouch" },
|
||||
|
||||
{ "from": "Vive.RY", "when": "Vive.RSY", "filters": ["invert"], "to": "Standard.RY" },
|
||||
{ "from": "Vive.RX", "when": "Vive.RSX", "to": "Standard.RX" },
|
||||
{ "from": "Vive.RY", "when": "Vive.RS", "filters": [ { "type": "deadZone", "min": 0.15 }, "invert" ], "to": "Standard.RY" },
|
||||
{ "from": "Vive.RX", "when": ["Vive.RS", "Application.LeftHandDominant"], "filters": { "type": "deadZone", "min": 0.15 }, "to": "Standard.RX" },
|
||||
{ "from": "Vive.RX", "when": ["Vive.RS", "Vive.RSX", "!Vive.RSY", "Application.RightHandDominant"], "filters": { "type": "deadZone", "min": 0.15 }, "to": "Standard.RX" },
|
||||
{
|
||||
"from": "Vive.RT", "to": "Standard.RT",
|
||||
"filters": [
|
||||
|
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 246 KiB |
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 331 KiB |
Before Width: | Height: | Size: 107 KiB After Width: | Height: | Size: 308 KiB |
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 229 KiB |
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 267 KiB |
|
@ -53,6 +53,16 @@
|
|||
position: absolute;
|
||||
top: 0; left: 0; bottom: 0; right: 0;
|
||||
}
|
||||
|
||||
#image_button {
|
||||
position: absolute;
|
||||
width: 463;
|
||||
height: 410;
|
||||
top: 155;
|
||||
left: 8;
|
||||
right: 8;
|
||||
bottom: 146;
|
||||
}
|
||||
|
||||
#report_problem {
|
||||
position: fixed;
|
||||
|
@ -67,17 +77,23 @@
|
|||
var handControllerImageURL = null;
|
||||
var index = 0;
|
||||
var count = 3;
|
||||
var handControllerRefURL = "https://docs.highfidelity.com/en/rc81/explore/get-started/vr-controls.html#vr-controls";
|
||||
var keyboardRefURL = "https://docs.highfidelity.com/en/rc81/explore/get-started/desktop.html#movement-controls";
|
||||
var gamepadRefURL = "https://docs.highfidelity.com/en/rc81/explore/get-started/vr-controls.html#gamepad";
|
||||
|
||||
function showKbm() {
|
||||
document.getElementById("main_image").setAttribute("src", "img/tablet-help-keyboard.jpg");
|
||||
document.getElementById("image_button").setAttribute("href", keyboardRefURL);
|
||||
}
|
||||
|
||||
function showHandControllers() {
|
||||
document.getElementById("main_image").setAttribute("src", handControllerImageURL);
|
||||
document.getElementById("image_button").setAttribute("href", handControllerRefURL);
|
||||
}
|
||||
|
||||
function showGamepad() {
|
||||
document.getElementById("main_image").setAttribute("src", "img/tablet-help-gamepad.jpg");
|
||||
document.getElementById("image_button").setAttribute("href", gamepadRefURL);
|
||||
}
|
||||
|
||||
function cycleRight() {
|
||||
|
@ -171,6 +187,7 @@
|
|||
<img id="main_image" src="img/tablet-help-keyboard.jpg" width="480px" height="720px"></img>
|
||||
<a href="#" id="left_button" onmousedown="cycleLeft()"></a>
|
||||
<a href="#" id="right_button" onmousedown="cycleRight()"></a>
|
||||
<a href="#" id="image_button"></a>
|
||||
</div>
|
||||
<a href="mailto:support@highfidelity.com" id="report_problem">Report Problem</a>
|
||||
</body>
|
||||
|
|
|
@ -336,6 +336,8 @@ Rectangle {
|
|||
case Qt.Key_Return:
|
||||
case Qt.Key_Enter:
|
||||
event.accepted = true;
|
||||
keypressTimer.stop();
|
||||
root.searchString = searchField.text;
|
||||
searchField.text = "";
|
||||
|
||||
getMarketplaceItems();
|
||||
|
|
|
@ -680,6 +680,8 @@ private:
|
|||
* <tr><td><code>InHMD</code></td><td>number</td><td>number</td><td>The user is in HMD mode.</td></tr>
|
||||
* <tr><td><code>AdvancedMovement</code></td><td>number</td><td>number</td><td>Advanced movement controls are enabled.
|
||||
* </td></tr>
|
||||
* <tr><td><code>LeftHandDominant</code></td><td>number</td><td>number</td><td>Dominant hand set to left.</td></tr>
|
||||
* <tr><td><code>RightHandDominant</code></td><td>number</td><td>number</td><td>Dominant hand set to right.</td></tr>
|
||||
* <tr><td><code>SnapTurn</code></td><td>number</td><td>number</td><td>Snap turn is enabled.</td></tr>
|
||||
* <tr><td><code>Grounded</code></td><td>number</td><td>number</td><td>The user's avatar is on the ground.</td></tr>
|
||||
* <tr><td><code>NavigationFocused</code></td><td>number</td><td>number</td><td><em>Not used.</em></td></tr>
|
||||
|
@ -701,6 +703,9 @@ static const QString STATE_NAV_FOCUSED = "NavigationFocused";
|
|||
static const QString STATE_PLATFORM_WINDOWS = "PlatformWindows";
|
||||
static const QString STATE_PLATFORM_MAC = "PlatformMac";
|
||||
static const QString STATE_PLATFORM_ANDROID = "PlatformAndroid";
|
||||
static const QString STATE_LEFT_HAND_DOMINANT = "LeftHandDominant";
|
||||
static const QString STATE_RIGHT_HAND_DOMINANT = "RightHandDominant";
|
||||
static const QString STATE_STRAFE_ENABLED = "StrafeEnabled";
|
||||
|
||||
// Statically provided display and input plugins
|
||||
extern DisplayPluginList getDisplayPlugins();
|
||||
|
@ -902,7 +907,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
|
|||
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_ADVANCED_MOVEMENT_CONTROLS, STATE_GROUNDED, STATE_NAV_FOCUSED,
|
||||
STATE_PLATFORM_WINDOWS, STATE_PLATFORM_MAC, STATE_PLATFORM_ANDROID } });
|
||||
STATE_PLATFORM_WINDOWS, STATE_PLATFORM_MAC, STATE_PLATFORM_ANDROID, STATE_LEFT_HAND_DOMINANT, STATE_RIGHT_HAND_DOMINANT, STATE_STRAFE_ENABLED } });
|
||||
DependencyManager::set<UserInputMapper>();
|
||||
DependencyManager::set<controller::ScriptingInterface, ControllerScriptingInterface>();
|
||||
DependencyManager::set<InterfaceParentFinder>();
|
||||
|
@ -1446,6 +1451,34 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
_overlays.init(); // do this before scripts load
|
||||
DependencyManager::set<ContextOverlayInterface>();
|
||||
|
||||
auto offscreenUi = getOffscreenUI();
|
||||
connect(offscreenUi.data(), &OffscreenUi::desktopReady, []() {
|
||||
// Now that we've loaded the menu and thus switched to the previous display plugin
|
||||
// we can unlock the desktop repositioning code, since all the positions will be
|
||||
// relative to the desktop size for this plugin
|
||||
auto offscreenUi = getOffscreenUI();
|
||||
auto desktop = offscreenUi->getDesktop();
|
||||
if (desktop) {
|
||||
desktop->setProperty("repositionLocked", false);
|
||||
}
|
||||
});
|
||||
|
||||
connect(offscreenUi.data(), &OffscreenUi::keyboardFocusActive, [this]() {
|
||||
#if !defined(Q_OS_ANDROID) && !defined(DISABLE_QML)
|
||||
// Do not show login dialog if requested not to on the command line
|
||||
QString hifiNoLoginCommandLineKey = QString("--").append(HIFI_NO_LOGIN_COMMAND_LINE_KEY);
|
||||
int index = arguments().indexOf(hifiNoLoginCommandLineKey);
|
||||
if (index != -1) {
|
||||
resumeAfterLoginDialogActionTaken();
|
||||
return;
|
||||
}
|
||||
|
||||
showLoginScreen();
|
||||
#else
|
||||
resumeAfterLoginDialogActionTaken();
|
||||
#endif
|
||||
});
|
||||
|
||||
// Initialize the user interface and menu system
|
||||
// Needs to happen AFTER the render engine initialization to access its configuration
|
||||
initializeUi();
|
||||
|
@ -1740,6 +1773,15 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
_applicationStateDevice->setInputVariant(STATE_ADVANCED_MOVEMENT_CONTROLS, []() -> float {
|
||||
return qApp->getMyAvatar()->useAdvancedMovementControls() ? 1 : 0;
|
||||
});
|
||||
_applicationStateDevice->setInputVariant(STATE_LEFT_HAND_DOMINANT, []() -> float {
|
||||
return qApp->getMyAvatar()->getDominantHand() == "left" ? 1 : 0;
|
||||
});
|
||||
_applicationStateDevice->setInputVariant(STATE_RIGHT_HAND_DOMINANT, []() -> float {
|
||||
return qApp->getMyAvatar()->getDominantHand() == "right" ? 1 : 0;
|
||||
});
|
||||
_applicationStateDevice->setInputVariant(STATE_STRAFE_ENABLED, []() -> float {
|
||||
return qApp->getMyAvatar()->getStrafeEnabled() ? 1 : 0;
|
||||
});
|
||||
|
||||
_applicationStateDevice->setInputVariant(STATE_GROUNDED, []() -> float {
|
||||
return qApp->getMyAvatar()->getCharacterController()->onGround() ? 1 : 0;
|
||||
|
@ -1791,34 +1833,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
|
||||
updateVerboseLogging();
|
||||
|
||||
// Now that we've loaded the menu and thus switched to the previous display plugin
|
||||
// we can unlock the desktop repositioning code, since all the positions will be
|
||||
// relative to the desktop size for this plugin
|
||||
auto offscreenUi = getOffscreenUI();
|
||||
connect(offscreenUi.data(), &OffscreenUi::desktopReady, []() {
|
||||
auto offscreenUi = getOffscreenUI();
|
||||
auto desktop = offscreenUi->getDesktop();
|
||||
if (desktop) {
|
||||
desktop->setProperty("repositionLocked", false);
|
||||
}
|
||||
});
|
||||
|
||||
connect(offscreenUi.data(), &OffscreenUi::keyboardFocusActive, [this]() {
|
||||
#if !defined(Q_OS_ANDROID) && !defined(DISABLE_QML)
|
||||
// Do not show login dialog if requested not to on the command line
|
||||
QString hifiNoLoginCommandLineKey = QString("--").append(HIFI_NO_LOGIN_COMMAND_LINE_KEY);
|
||||
int index = arguments().indexOf(hifiNoLoginCommandLineKey);
|
||||
if (index != -1) {
|
||||
resumeAfterLoginDialogActionTaken();
|
||||
return;
|
||||
}
|
||||
|
||||
showLoginScreen();
|
||||
#else
|
||||
resumeAfterLoginDialogActionTaken();
|
||||
#endif
|
||||
});
|
||||
|
||||
// Make sure we don't time out during slow operations at startup
|
||||
updateHeartbeat();
|
||||
QTimer* settingsTimer = new QTimer();
|
||||
|
@ -3967,6 +3981,15 @@ static void dumpEventQueue(QThread* thread) {
|
|||
}
|
||||
#endif // DEBUG_EVENT_QUEUE
|
||||
|
||||
bool Application::notify(QObject * object, QEvent * event) {
|
||||
if (thread() == QThread::currentThread()) {
|
||||
PROFILE_RANGE_IF_LONGER(app, "notify", 2)
|
||||
return QApplication::notify(object, event);
|
||||
}
|
||||
|
||||
return QApplication::notify(object, event);
|
||||
}
|
||||
|
||||
bool Application::event(QEvent* event) {
|
||||
|
||||
if (_aboutToQuit) {
|
||||
|
@ -5458,6 +5481,13 @@ void Application::pauseUntilLoginDetermined() {
|
|||
// disconnect domain handler.
|
||||
nodeList->getDomainHandler().disconnect();
|
||||
|
||||
// From now on, it's permissible to call resumeAfterLoginDialogActionTaken()
|
||||
_resumeAfterLoginDialogActionTaken_SafeToRun = true;
|
||||
|
||||
if (_resumeAfterLoginDialogActionTaken_WasPostponed) {
|
||||
// resumeAfterLoginDialogActionTaken() was already called, but it aborted. Now it's safe to call it again.
|
||||
resumeAfterLoginDialogActionTaken();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::resumeAfterLoginDialogActionTaken() {
|
||||
|
@ -5466,6 +5496,11 @@ void Application::resumeAfterLoginDialogActionTaken() {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!_resumeAfterLoginDialogActionTaken_SafeToRun) {
|
||||
_resumeAfterLoginDialogActionTaken_WasPostponed = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isHMDMode() && getDesktopTabletBecomesToolbarSetting()) {
|
||||
auto toolbar = DependencyManager::get<ToolbarScriptingInterface>()->getToolbar("com.highfidelity.interface.toolbar.system");
|
||||
toolbar->writeProperty("visible", true);
|
||||
|
@ -9285,6 +9320,3 @@ void Application::toggleAwayMode(){
|
|||
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
#include "Application.moc"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//
|
||||
//
|
||||
// Application.h
|
||||
// interface/src
|
||||
//
|
||||
|
@ -156,6 +156,7 @@ public:
|
|||
void updateCamera(RenderArgs& renderArgs, float deltaTime);
|
||||
void resizeGL();
|
||||
|
||||
bool notify(QObject *, QEvent *) override;
|
||||
bool event(QEvent* event) override;
|
||||
bool eventFilter(QObject* object, QEvent* event) override;
|
||||
|
||||
|
@ -807,5 +808,8 @@ private:
|
|||
|
||||
bool _showTrackedObjects { false };
|
||||
bool _prevShowTrackedObjects { false };
|
||||
|
||||
bool _resumeAfterLoginDialogActionTaken_WasPostponed { false };
|
||||
bool _resumeAfterLoginDialogActionTaken_SafeToRun { false };
|
||||
};
|
||||
#endif // hifi_Application_h
|
||||
|
|
|
@ -19,14 +19,22 @@ class FancyCamera : public Camera {
|
|||
Q_OBJECT
|
||||
|
||||
/**jsdoc
|
||||
* @namespace
|
||||
* @augments Camera
|
||||
*/
|
||||
|
||||
// FIXME: JSDoc 3.5.5 doesn't augment @property definitions. The following definition is repeated in Camera.h.
|
||||
/**jsdoc
|
||||
* @property {Uuid} cameraEntity The ID of the entity that the camera position and orientation follow when the camera is in
|
||||
* entity mode.
|
||||
* The <code>Camera</code> API provides access to the "camera" that defines your view in desktop and HMD display modes.
|
||||
*
|
||||
* @namespace Camera
|
||||
*
|
||||
* @hifi-interface
|
||||
* @hifi-client-entity
|
||||
* @hifi-avatar
|
||||
*
|
||||
* @property {Vec3} position - The position of the camera. You can set this value only when the camera is in independent
|
||||
* mode.
|
||||
* @property {Quat} orientation - The orientation of the camera. You can set this value only when the camera is in
|
||||
* independent mode.
|
||||
* @property {Camera.Mode} mode - The camera mode.
|
||||
* @property {ViewFrustum} frustum - The camera frustum.
|
||||
* @property {Uuid} cameraEntity - The ID of the entity that is used for the camera position and orientation when the
|
||||
* camera is in entity mode.
|
||||
*/
|
||||
Q_PROPERTY(QUuid cameraEntity READ getCameraEntity WRITE setCameraEntity)
|
||||
|
||||
|
@ -38,25 +46,25 @@ public:
|
|||
|
||||
|
||||
public slots:
|
||||
/**jsdoc
|
||||
* Get the ID of the entity that the camera is set to use the position and orientation from when it's in entity mode. You can
|
||||
* also get the entity ID using the <code>Camera.cameraEntity</code> property.
|
||||
* @function Camera.getCameraEntity
|
||||
* @returns {Uuid} The ID of the entity that the camera is set to follow when in entity mode; <code>null</code> if no camera
|
||||
* entity has been set.
|
||||
*/
|
||||
/**jsdoc
|
||||
* Gets the ID of the entity that the camera is set to follow (i.e., use the position and orientation from) when it's in
|
||||
* entity mode. You can also get the entity ID using the {@link Camera|Camera.cameraEntity} property.
|
||||
* @function Camera.getCameraEntity
|
||||
* @returns {Uuid} The ID of the entity that the camera is set to follow when in entity mode; <code>null</code> if no
|
||||
* camera entity has been set.
|
||||
*/
|
||||
QUuid getCameraEntity() const;
|
||||
|
||||
/**jsdoc
|
||||
* Set the entity that the camera should use the position and orientation from when it's in entity mode. You can also set the
|
||||
* entity using the <code>Camera.cameraEntity</code> property.
|
||||
* @function Camera.setCameraEntity
|
||||
* @param {Uuid} entityID The entity that the camera should follow when it's in entity mode.
|
||||
* @example <caption>Move your camera to the position and orientation of the closest entity.</caption>
|
||||
* Camera.setModeString("entity");
|
||||
* var entity = Entities.findClosestEntity(MyAvatar.position, 100.0);
|
||||
* Camera.setCameraEntity(entity);
|
||||
*/
|
||||
* Sets the entity that the camera should follow (i.e., use the position and orientation from) when it's in entity mode.
|
||||
* You can also set the entity using the {@link Camera|Camera.cameraEntity} property.
|
||||
* @function Camera.setCameraEntity
|
||||
* @param {Uuid} entityID - The entity that the camera should follow when it's in entity mode.
|
||||
* @example <caption>Move your camera to the position and orientation of the closest entity.</caption>
|
||||
* Camera.setModeString("entity");
|
||||
* var entity = Entities.findClosestEntity(MyAvatar.position, 100.0);
|
||||
* Camera.setCameraEntity(entity);
|
||||
*/
|
||||
void setCameraEntity(QUuid entityID);
|
||||
|
||||
private:
|
||||
|
|
|
@ -116,7 +116,7 @@ Menu::Menu() {
|
|||
// Edit > Delete
|
||||
auto deleteAction = addActionToQMenuAndActionHash(editMenu, "Delete", QKeySequence::Delete);
|
||||
connect(deleteAction, &QAction::triggered, [] {
|
||||
QKeyEvent* keyEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Delete, Qt::ControlModifier);
|
||||
QKeyEvent* keyEvent = new QKeyEvent(QEvent::KeyPress, Qt::Key_Delete, Qt::NoModifier);
|
||||
QCoreApplication::postEvent(QCoreApplication::instance(), keyEvent);
|
||||
});
|
||||
|
||||
|
@ -707,8 +707,7 @@ Menu::Menu() {
|
|||
// Developer > Timing >>>
|
||||
MenuWrapper* timingMenu = developerMenu->addMenu("Timing");
|
||||
MenuWrapper* perfTimerMenu = timingMenu->addMenu("Performance Timer");
|
||||
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::DisplayDebugTimingDetails, 0, false,
|
||||
qApp, SLOT(enablePerfStats(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::DisplayDebugTimingDetails);
|
||||
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::OnlyDisplayTopTen, 0, true);
|
||||
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandUpdateTiming, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandSimulationTiming, 0, false);
|
||||
|
|
|
@ -497,9 +497,11 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar
|
|||
// it might not fire until after we create a new instance for the same remote avatar, which creates a race
|
||||
// on the creation of entities for that avatar instance and the deletion of entities for this instance
|
||||
avatar->removeAvatarEntitiesFromTree();
|
||||
if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble) {
|
||||
emit AvatarInputs::getInstance()->avatarEnteredIgnoreRadius(avatar->getSessionUUID());
|
||||
emit DependencyManager::get<UsersScriptingInterface>()->enteredIgnoreRadius();
|
||||
if (removalReason != KillAvatarReason::AvatarDisconnected) {
|
||||
if (removalReason == KillAvatarReason::TheirAvatarEnteredYourBubble) {
|
||||
emit AvatarInputs::getInstance()->avatarEnteredIgnoreRadius(avatar->getSessionUUID());
|
||||
emit DependencyManager::get<UsersScriptingInterface>()->enteredIgnoreRadius();
|
||||
}
|
||||
|
||||
workload::Transaction workloadTransaction;
|
||||
workloadTransaction.remove(avatar->getSpaceIndex());
|
||||
|
@ -509,7 +511,7 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar
|
|||
render::Transaction transaction;
|
||||
avatar->removeFromScene(avatar, scene, transaction);
|
||||
scene->enqueueTransaction(transaction);
|
||||
} else if (removalReason == KillAvatarReason::AvatarDisconnected) {
|
||||
} else {
|
||||
// remove from node sets, if present
|
||||
DependencyManager::get<NodeList>()->removeFromIgnoreMuteSets(avatar->getSessionUUID());
|
||||
DependencyManager::get<UsersScriptingInterface>()->avatarDisconnected(avatar->getSessionUUID());
|
||||
|
@ -932,6 +934,18 @@ void AvatarManager::setAvatarSortCoefficient(const QString& name, const QScriptV
|
|||
}
|
||||
}
|
||||
|
||||
/**jsdoc
|
||||
* PAL (People Access List) data for an avatar.
|
||||
* @typedef {object} AvatarManager.PalData
|
||||
* @property {Uuid} sessionUUID - The avatar's session ID. <code>""</code> if the avatar is your own.
|
||||
* @property {string} sessionDisplayName - The avatar's display name, sanitized and versioned, as defined by the avatar mixer.
|
||||
* It is unique among all avatars present in the domain at the time.
|
||||
* @property {number} audioLoudness - The instantaneous loudness of the audio input that the avatar is injecting into the
|
||||
* domain.
|
||||
* @property {boolean} isReplicated - <strong>Deprecated.</strong>
|
||||
* @property {Vec3} position - The position of the avatar.
|
||||
* @property {number} palOrbOffset - The vertical offset from the avatar's position that an overlay orb should be displayed at.
|
||||
*/
|
||||
QVariantMap AvatarManager::getPalData(const QStringList& specificAvatarIdentifiers) {
|
||||
QJsonArray palData;
|
||||
|
||||
|
|
|
@ -37,10 +37,11 @@
|
|||
using SortedAvatar = std::pair<float, std::shared_ptr<Avatar>>;
|
||||
|
||||
/**jsdoc
|
||||
* The AvatarManager API has properties and methods which manage Avatars within the same domain.
|
||||
* The <code>AvatarManager</code> API provides information about avatars within the current domain. The avatars available are
|
||||
* those that Interface has displayed and therefore knows about.
|
||||
*
|
||||
* <p><strong>Note:</strong> This API is also provided to Interface and client entity scripts as the synonym,
|
||||
* <code>AvatarList</code>. For assignment client scripts, see the separate {@link AvatarList} API.
|
||||
* <p><strong>Warning:</strong> This API is also provided to Interface, client entity, and avatar scripts as the synonym,
|
||||
* "<code>AvatarList</code>". For assignment client scripts, see the separate {@link AvatarList} API.</p>
|
||||
*
|
||||
* @namespace AvatarManager
|
||||
*
|
||||
|
@ -48,8 +49,9 @@ using SortedAvatar = std::pair<float, std::shared_ptr<Avatar>>;
|
|||
* @hifi-client-entity
|
||||
* @hifi-avatar
|
||||
*
|
||||
* @borrows AvatarList.getAvatarIdentifiers as getAvatarIdentifiers
|
||||
* @borrows AvatarList.getAvatarsInRange as getAvatarsInRange
|
||||
* @borrows AvatarList.getAvatar as getAvatar
|
||||
* @comment AvatarList.getAvatarIdentifiers as getAvatarIdentifiers - Don't borrow because behavior is slightly different.
|
||||
* @comment AvatarList.getAvatarsInRange as getAvatarsInRange - Don't borrow because behavior is slightly different.
|
||||
* @borrows AvatarList.avatarAddedEvent as avatarAddedEvent
|
||||
* @borrows AvatarList.avatarRemovedEvent as avatarRemovedEvent
|
||||
* @borrows AvatarList.avatarSessionChangedEvent as avatarSessionChangedEvent
|
||||
|
@ -67,6 +69,31 @@ class AvatarManager : public AvatarHashMap {
|
|||
|
||||
public:
|
||||
|
||||
/**jsdoc
|
||||
* Gets the IDs of all avatars known about in the domain.
|
||||
* Your own avatar is included in the list as a <code>null</code> value.
|
||||
* @function AvatarManager.getAvatarIdentifiers
|
||||
* @returns {Uuid[]} The IDs of all known avatars in the domain.
|
||||
* @example <caption>Report the IDS of all avatars within the domain.</caption>
|
||||
* var avatars = AvatarManager.getAvatarIdentifiers();
|
||||
* print("Avatars in the domain: " + JSON.stringify(avatars));
|
||||
* // A null item is included for your avatar.
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
* Gets the IDs of all avatars known about within a specified distance from a point.
|
||||
* Your own avatar's ID is included in the list if it is in range.
|
||||
* @function AvatarManager.getAvatarsInRange
|
||||
* @param {Vec3} position - The point about which the search is performed.
|
||||
* @param {number} range - The search radius.
|
||||
* @returns {Uuid[]} The IDs of all known avatars within the search distance from the position.
|
||||
* @example <caption>Report the IDs of all avatars within 10m of your avatar.</caption>
|
||||
* var RANGE = 10;
|
||||
* var avatars = AvatarManager.getAvatarsInRange(MyAvatar.position, RANGE);
|
||||
* print("Nearby avatars: " + JSON.stringify(avatars));
|
||||
* print("Own avatar: " + MyAvatar.sessionUUID);
|
||||
*/
|
||||
|
||||
/// Registers the script types associated with the avatar manager.
|
||||
static void registerMetaTypes(QScriptEngine* engine);
|
||||
|
||||
|
@ -79,9 +106,7 @@ public:
|
|||
glm::vec3 getMyAvatarPosition() const { return _myAvatar->getWorldPosition(); }
|
||||
|
||||
/**jsdoc
|
||||
* @function AvatarManager.getAvatar
|
||||
* @param {Uuid} avatarID
|
||||
* @returns {AvatarData}
|
||||
* @comment Uses the base class's JSDoc.
|
||||
*/
|
||||
// Null/Default-constructed QUuids will return MyAvatar
|
||||
Q_INVOKABLE virtual ScriptAvatarData* getAvatar(QUuid avatarID) override { return new ScriptAvatar(getAvatarBySessionID(avatarID)); }
|
||||
|
@ -112,36 +137,53 @@ public:
|
|||
void handleCollisionEvents(const CollisionEvents& collisionEvents);
|
||||
|
||||
/**jsdoc
|
||||
* Gets the amount of avatar mixer data being generated by an avatar other than your own.
|
||||
* @function AvatarManager.getAvatarDataRate
|
||||
* @param {Uuid} sessionID
|
||||
* @param {string} [rateName=""]
|
||||
* @returns {number}
|
||||
* @param {Uuid} sessionID - The ID of the avatar whose data rate you're retrieving.
|
||||
* @param {AvatarDataRate} [rateName=""] - The type of avatar mixer data to get the data rate of.
|
||||
* @returns {number} The data rate in kbps; <code>0</code> if the avatar is your own.
|
||||
*/
|
||||
Q_INVOKABLE float getAvatarDataRate(const QUuid& sessionID, const QString& rateName = QString("")) const;
|
||||
|
||||
/**jsdoc
|
||||
* Gets the update rate of avatar mixer data being generated by an avatar other than your own.
|
||||
* @function AvatarManager.getAvatarUpdateRate
|
||||
* @param {Uuid} sessionID
|
||||
* @param {string} [rateName=""]
|
||||
* @returns {number}
|
||||
* @param {Uuid} sessionID - The ID of the avatar whose update rate you're retrieving.
|
||||
* @param {AvatarUpdateRate} [rateName=""] - The type of avatar mixer data to get the update rate of.
|
||||
* @returns {number} The update rate in Hz; <code>0</code> if the avatar is your own.
|
||||
*/
|
||||
Q_INVOKABLE float getAvatarUpdateRate(const QUuid& sessionID, const QString& rateName = QString("")) const;
|
||||
|
||||
/**jsdoc
|
||||
* Gets the simulation rate of an avatar other than your own.
|
||||
* @function AvatarManager.getAvatarSimulationRate
|
||||
* @param {Uuid} sessionID
|
||||
* @param {string} [rateName=""]
|
||||
* @returns {number}
|
||||
* @param {Uuid} sessionID - The ID of the avatar whose simulation you're retrieving.
|
||||
* @param {AvatarSimulationRate} [rateName=""] - The type of avatar data to get the simulation rate of.
|
||||
* @returns {number} The simulation rate in Hz; <code>0</code> if the avatar is your own.
|
||||
*/
|
||||
Q_INVOKABLE float getAvatarSimulationRate(const QUuid& sessionID, const QString& rateName = QString("")) const;
|
||||
|
||||
/**jsdoc
|
||||
* Find the first avatar intersected by a {@link PickRay}.
|
||||
* @function AvatarManager.findRayIntersection
|
||||
* @param {PickRay} ray
|
||||
* @param {Uuid[]} [avatarsToInclude=[]]
|
||||
* @param {Uuid[]} [avatarsToDiscard=[]]
|
||||
* @param {boolean} pickAgainstMesh
|
||||
* @returns {RayToAvatarIntersectionResult}
|
||||
* @param {PickRay} ray - The ray to use for finding avatars.
|
||||
* @param {Uuid[]} [avatarsToInclude=[]] - If not empty then search is restricted to these avatars.
|
||||
* @param {Uuid[]} [avatarsToDiscard=[]] - Avatars to ignore in the search.
|
||||
* @param {boolean} [pickAgainstMesh=true] - If <code>true</code> then the exact intersection with the avatar mesh is
|
||||
* calculated, if <code>false</code> then the intersection is approximate.
|
||||
* @returns {RayToAvatarIntersectionResult} The result of the search for the first intersected avatar.
|
||||
* @example <caption>Find the first avatar directly in front of you.</caption>
|
||||
* var pickRay = {
|
||||
* origin: MyAvatar.position,
|
||||
* direction: Quat.getFront(MyAvatar.orientation)
|
||||
* };
|
||||
*
|
||||
* var intersection = AvatarManager.findRayIntersection(pickRay);
|
||||
* if (intersection.intersects) {
|
||||
* print("Avatar found: " + JSON.stringify(intersection));
|
||||
* } else {
|
||||
* print("No avatar found.");
|
||||
* }
|
||||
*/
|
||||
Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersection(const PickRay& ray,
|
||||
const QScriptValue& avatarIdsToInclude = QScriptValue(),
|
||||
|
@ -149,11 +191,12 @@ public:
|
|||
bool pickAgainstMesh = true);
|
||||
/**jsdoc
|
||||
* @function AvatarManager.findRayIntersectionVector
|
||||
* @param {PickRay} ray
|
||||
* @param {Uuid[]} avatarsToInclude
|
||||
* @param {Uuid[]} avatarsToDiscard
|
||||
* @param {boolean} pickAgainstMesh
|
||||
* @returns {RayToAvatarIntersectionResult}
|
||||
* @param {PickRay} ray - Ray.
|
||||
* @param {Uuid[]} avatarsToInclude - Avatars to include.
|
||||
* @param {Uuid[]} avatarsToDiscard - Avatars to discard.
|
||||
* @param {boolean} pickAgainstMesh - Pick against mesh.
|
||||
* @returns {RayToAvatarIntersectionResult} Intersection result.
|
||||
* @deprecated This function is deprecated and will be removed.
|
||||
*/
|
||||
Q_INVOKABLE RayToAvatarIntersectionResult findRayIntersectionVector(const PickRay& ray,
|
||||
const QVector<EntityItemID>& avatarsToInclude,
|
||||
|
@ -162,10 +205,11 @@ public:
|
|||
|
||||
/**jsdoc
|
||||
* @function AvatarManager.findParabolaIntersectionVector
|
||||
* @param {PickParabola} pick
|
||||
* @param {Uuid[]} avatarsToInclude
|
||||
* @param {Uuid[]} avatarsToDiscard
|
||||
* @returns {ParabolaToAvatarIntersectionResult}
|
||||
* @param {PickParabola} pick - Pick.
|
||||
* @param {Uuid[]} avatarsToInclude - Avatars to include.
|
||||
* @param {Uuid[]} avatarsToDiscard - Avatars to discard.
|
||||
* @returns {ParabolaToAvatarIntersectionResult} Intersection result.
|
||||
* @deprecated This function is deprecated and will be removed.
|
||||
*/
|
||||
Q_INVOKABLE ParabolaToAvatarIntersectionResult findParabolaIntersectionVector(const PickParabola& pick,
|
||||
const QVector<EntityItemID>& avatarsToInclude,
|
||||
|
@ -173,27 +217,31 @@ public:
|
|||
|
||||
/**jsdoc
|
||||
* @function AvatarManager.getAvatarSortCoefficient
|
||||
* @param {string} name
|
||||
* @returns {number}
|
||||
* @param {string} name - Name.
|
||||
* @returns {number} Value.
|
||||
* @deprecated This function is deprecated and will be removed.
|
||||
*/
|
||||
// TODO: remove this HACK once we settle on optimal default sort coefficients
|
||||
Q_INVOKABLE float getAvatarSortCoefficient(const QString& name);
|
||||
|
||||
/**jsdoc
|
||||
* @function AvatarManager.setAvatarSortCoefficient
|
||||
* @param {string} name
|
||||
* @param {number} value
|
||||
* @param {string} name - Name
|
||||
* @param {number} value - Value.
|
||||
* @deprecated This function is deprecated and will be removed.
|
||||
*/
|
||||
Q_INVOKABLE void setAvatarSortCoefficient(const QString& name, const QScriptValue& value);
|
||||
|
||||
/**jsdoc
|
||||
* Used in the PAL for getting PAL-related data about avatars nearby. Using this method is faster
|
||||
* than iterating over each avatar and obtaining data about them in JavaScript, as that method
|
||||
* locks and unlocks each avatar's data structure potentially hundreds of times per update tick.
|
||||
* Gets PAL (People Access List) data for one or more avatars. Using this method is faster than iterating over each avatar
|
||||
* and obtaining data about each individually.
|
||||
* @function AvatarManager.getPalData
|
||||
* @param {string[]} [specificAvatarIdentifiers=[]] - The list of IDs of the avatars you want the PAL data for.
|
||||
* If an empty list, the PAL data for all nearby avatars is returned.
|
||||
* @returns {object[]} An array of objects, each object being the PAL data for an avatar.
|
||||
* @param {string[]} [avatarIDs=[]] - The IDs of the avatars to get the PAL data for. If empty, then PAL data is obtained
|
||||
* for all avatars.
|
||||
* @returns {object<"data", AvatarManager.PalData[]>} An array of objects, each object being the PAL data for an avatar.
|
||||
* @example <caption>Report the PAL data for an avatar nearby.</caption>
|
||||
* var palData = AvatarManager.getPalData();
|
||||
* print("PAL data for one avatar: " + JSON.stringify(palData.data[0]));
|
||||
*/
|
||||
Q_INVOKABLE QVariantMap getPalData(const QStringList& specificAvatarIdentifiers = QStringList());
|
||||
|
||||
|
@ -209,7 +257,8 @@ public:
|
|||
public slots:
|
||||
/**jsdoc
|
||||
* @function AvatarManager.updateAvatarRenderStatus
|
||||
* @param {boolean} shouldRenderAvatars
|
||||
* @param {boolean} shouldRenderAvatars - Should render avatars.
|
||||
* @deprecated This function is deprecated and will be removed.
|
||||
*/
|
||||
void updateAvatarRenderStatus(bool shouldRenderAvatars);
|
||||
|
||||
|
|
|
@ -155,6 +155,7 @@ MyAvatar::MyAvatar(QThread* thread) :
|
|||
_prevShouldDrawHead(true),
|
||||
_audioListenerMode(FROM_HEAD),
|
||||
_dominantHandSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "dominantHand", DOMINANT_RIGHT_HAND),
|
||||
_strafeEnabledSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "strafeEnabled", DEFAULT_STRAFE_ENABLED),
|
||||
_hmdAvatarAlignmentTypeSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "hmdAvatarAlignmentType", DEFAULT_HMD_AVATAR_ALIGNMENT_TYPE),
|
||||
_headPitchSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "", 0.0f),
|
||||
_scaleSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "scale", _targetScale),
|
||||
|
@ -169,7 +170,16 @@ MyAvatar::MyAvatar(QThread* thread) :
|
|||
_useSnapTurnSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "useSnapTurn", _useSnapTurn),
|
||||
_userHeightSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "userHeight", DEFAULT_AVATAR_HEIGHT),
|
||||
_flyingHMDSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "flyingHMD", _flyingPrefHMD),
|
||||
_movementReferenceSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "movementReference", _movementReference),
|
||||
_avatarEntityCountSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData" << "size", 0),
|
||||
_driveGear1Setting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "driveGear1", _driveGear1),
|
||||
_driveGear2Setting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "driveGear2", _driveGear2),
|
||||
_driveGear3Setting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "driveGear3", _driveGear3),
|
||||
_driveGear4Setting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "driveGear4", _driveGear4),
|
||||
_driveGear5Setting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "driveGear5", _driveGear5),
|
||||
_analogWalkSpeedSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "analogWalkSpeed", _analogWalkSpeed.get()),
|
||||
_analogPlusWalkSpeedSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "analogPlusWalkSpeed", _analogPlusWalkSpeed.get()),
|
||||
_controlSchemeIndexSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "controlSchemeIndex", _controlSchemeIndex),
|
||||
_userRecenterModelSetting(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "userRecenterModel", USER_RECENTER_MODEL_AUTO)
|
||||
{
|
||||
_clientTraitsHandler.reset(new ClientTraitsHandler(this));
|
||||
|
@ -322,6 +332,14 @@ QString MyAvatar::getDominantHand() const {
|
|||
return _dominantHand.get();
|
||||
}
|
||||
|
||||
void MyAvatar::setStrafeEnabled(bool enabled) {
|
||||
_strafeEnabled.set(enabled);
|
||||
}
|
||||
|
||||
bool MyAvatar::getStrafeEnabled() const {
|
||||
return _strafeEnabled.get();
|
||||
}
|
||||
|
||||
void MyAvatar::setDominantHand(const QString& hand) {
|
||||
if (hand == DOMINANT_LEFT_HAND || hand == DOMINANT_RIGHT_HAND) {
|
||||
bool changed = (hand != _dominantHand.get());
|
||||
|
@ -800,20 +818,8 @@ void MyAvatar::simulate(float deltaTime, bool inView) {
|
|||
if (_cauterizationNeedsUpdate) {
|
||||
_cauterizationNeedsUpdate = false;
|
||||
|
||||
// Redisplay cauterized entities that are no longer children of the avatar.
|
||||
auto cauterizedChild = _cauterizedChildrenOfHead.begin();
|
||||
if (cauterizedChild != _cauterizedChildrenOfHead.end()) {
|
||||
auto children = getChildren();
|
||||
while (cauterizedChild != _cauterizedChildrenOfHead.end()) {
|
||||
if (!children.contains(*cauterizedChild)) {
|
||||
updateChildCauterization(*cauterizedChild, false);
|
||||
cauterizedChild = _cauterizedChildrenOfHead.erase(cauterizedChild);
|
||||
} else {
|
||||
++cauterizedChild;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto objectsToUncauterize = _cauterizedChildrenOfHead;
|
||||
_cauterizedChildrenOfHead.clear();
|
||||
// Update cauterization of entities that are children of the avatar.
|
||||
auto headBoneSet = _skeletonModel->getCauterizeBoneSet();
|
||||
forEachChild([&](SpatiallyNestablePointer object) {
|
||||
|
@ -825,15 +831,19 @@ void MyAvatar::simulate(float deltaTime, bool inView) {
|
|||
updateChildCauterization(descendant, !_prevShouldDrawHead);
|
||||
});
|
||||
_cauterizedChildrenOfHead.insert(object);
|
||||
} else if (_cauterizedChildrenOfHead.find(object) != _cauterizedChildrenOfHead.end()) {
|
||||
// Redisplay cauterized children that are not longer children of the head.
|
||||
updateChildCauterization(object, false);
|
||||
objectsToUncauterize.erase(object);
|
||||
} else if (objectsToUncauterize.find(object) == objectsToUncauterize.end()) {
|
||||
objectsToUncauterize.insert(object);
|
||||
object->forEachDescendant([&](SpatiallyNestablePointer descendant) {
|
||||
updateChildCauterization(descendant, false);
|
||||
objectsToUncauterize.insert(descendant);
|
||||
});
|
||||
_cauterizedChildrenOfHead.erase(object);
|
||||
}
|
||||
});
|
||||
|
||||
// Redisplay cauterized entities that are no longer children of the avatar.
|
||||
for (auto cauterizedChild = objectsToUncauterize.begin(); cauterizedChild != objectsToUncauterize.end(); cauterizedChild++) {
|
||||
updateChildCauterization(*cauterizedChild, false);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -1189,6 +1199,15 @@ void MyAvatar::overrideAnimation(const QString& url, float fps, bool loop, float
|
|||
_skeletonModel->getRig().overrideAnimation(url, fps, loop, firstFrame, lastFrame);
|
||||
}
|
||||
|
||||
void MyAvatar::overrideHandAnimation(bool isLeft, const QString& url, float fps, bool loop, float firstFrame, float lastFrame) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "overrideHandAnimation", Q_ARG(bool, isLeft), Q_ARG(const QString&, url), Q_ARG(float, fps),
|
||||
Q_ARG(bool, loop), Q_ARG(float, firstFrame), Q_ARG(float, lastFrame));
|
||||
return;
|
||||
}
|
||||
_skeletonModel->getRig().overrideHandAnimation(isLeft, url, fps, loop, firstFrame, lastFrame);
|
||||
}
|
||||
|
||||
void MyAvatar::restoreAnimation() {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "restoreAnimation");
|
||||
|
@ -1197,6 +1216,14 @@ void MyAvatar::restoreAnimation() {
|
|||
_skeletonModel->getRig().restoreAnimation();
|
||||
}
|
||||
|
||||
void MyAvatar::restoreHandAnimation(bool isLeft) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "restoreHandAnimation", Q_ARG(bool, isLeft));
|
||||
return;
|
||||
}
|
||||
_skeletonModel->getRig().restoreHandAnimation(isLeft);
|
||||
}
|
||||
|
||||
QStringList MyAvatar::getAnimationRoles() {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QStringList result;
|
||||
|
@ -1256,6 +1283,7 @@ void MyAvatar::resizeAvatarEntitySettingHandles(uint32_t maxIndex) {
|
|||
|
||||
void MyAvatar::saveData() {
|
||||
_dominantHandSetting.set(getDominantHand());
|
||||
_strafeEnabledSetting.set(getStrafeEnabled());
|
||||
_hmdAvatarAlignmentTypeSetting.set(getHmdAvatarAlignmentType());
|
||||
_headPitchSetting.set(getHead()->getBasePitch());
|
||||
_scaleSetting.set(_targetScale);
|
||||
|
@ -1279,6 +1307,15 @@ void MyAvatar::saveData() {
|
|||
_useSnapTurnSetting.set(_useSnapTurn);
|
||||
_userHeightSetting.set(getUserHeight());
|
||||
_flyingHMDSetting.set(getFlyingHMDPref());
|
||||
_movementReferenceSetting.set(getMovementReference());
|
||||
_driveGear1Setting.set(getDriveGear1());
|
||||
_driveGear2Setting.set(getDriveGear2());
|
||||
_driveGear3Setting.set(getDriveGear3());
|
||||
_driveGear4Setting.set(getDriveGear4());
|
||||
_driveGear5Setting.set(getDriveGear5());
|
||||
_analogWalkSpeedSetting.set(getAnalogWalkSpeed());
|
||||
_analogPlusWalkSpeedSetting.set(getAnalogPlusWalkSpeed());
|
||||
_controlSchemeIndexSetting.set(getControlSchemeIndex());
|
||||
_userRecenterModelSetting.set(userRecenterModelToString(getUserRecenterModel()));
|
||||
|
||||
auto hmdInterface = DependencyManager::get<HMDScriptingInterface>();
|
||||
|
@ -1856,12 +1893,22 @@ void MyAvatar::loadData() {
|
|||
// Flying preferences must be loaded before calling setFlyingEnabled()
|
||||
Setting::Handle<bool> firstRunVal { Settings::firstRun, true };
|
||||
setFlyingHMDPref(firstRunVal.get() ? false : _flyingHMDSetting.get());
|
||||
setMovementReference(firstRunVal.get() ? false : _movementReferenceSetting.get());
|
||||
setDriveGear1(firstRunVal.get() ? DEFAULT_GEAR_1 : _driveGear1Setting.get());
|
||||
setDriveGear2(firstRunVal.get() ? DEFAULT_GEAR_2 : _driveGear2Setting.get());
|
||||
setDriveGear3(firstRunVal.get() ? DEFAULT_GEAR_3 : _driveGear3Setting.get());
|
||||
setDriveGear4(firstRunVal.get() ? DEFAULT_GEAR_4 : _driveGear4Setting.get());
|
||||
setDriveGear5(firstRunVal.get() ? DEFAULT_GEAR_5 : _driveGear5Setting.get());
|
||||
setControlSchemeIndex(firstRunVal.get() ? LocomotionControlsMode::CONTROLS_DEFAULT : _controlSchemeIndexSetting.get());
|
||||
setAnalogWalkSpeed(firstRunVal.get() ? ANALOG_AVATAR_MAX_WALKING_SPEED : _analogWalkSpeedSetting.get());
|
||||
setAnalogPlusWalkSpeed(firstRunVal.get() ? ANALOG_PLUS_AVATAR_MAX_WALKING_SPEED : _analogPlusWalkSpeedSetting.get());
|
||||
setFlyingEnabled(getFlyingEnabled());
|
||||
|
||||
setDisplayName(_displayNameSetting.get());
|
||||
setCollisionSoundURL(_collisionSoundURLSetting.get(QUrl(DEFAULT_AVATAR_COLLISION_SOUND_URL)).toString());
|
||||
setSnapTurn(_useSnapTurnSetting.get());
|
||||
setDominantHand(_dominantHandSetting.get(DOMINANT_RIGHT_HAND).toLower());
|
||||
setStrafeEnabled(_strafeEnabledSetting.get(DEFAULT_STRAFE_ENABLED));
|
||||
setHmdAvatarAlignmentType(_hmdAvatarAlignmentTypeSetting.get(DEFAULT_HMD_AVATAR_ALIGNMENT_TYPE).toLower());
|
||||
setUserHeight(_userHeightSetting.get(DEFAULT_AVATAR_HEIGHT));
|
||||
setTargetScale(_scaleSetting.get());
|
||||
|
@ -2519,6 +2566,12 @@ controller::Pose MyAvatar::getControllerPoseInAvatarFrame(controller::Action act
|
|||
}
|
||||
}
|
||||
|
||||
glm::quat MyAvatar::getOffHandRotation() const {
|
||||
auto hand = (getDominantHand() == DOMINANT_RIGHT_HAND) ? controller::Action::LEFT_HAND : controller::Action::RIGHT_HAND;
|
||||
auto pose = getControllerPoseInAvatarFrame(hand);
|
||||
return pose.rotation;
|
||||
}
|
||||
|
||||
void MyAvatar::updateMotors() {
|
||||
_characterController.clearMotors();
|
||||
glm::quat motorRotation;
|
||||
|
@ -3136,17 +3189,40 @@ int MyAvatar::sendAvatarDataPacket(bool sendAll) {
|
|||
return bytesSent;
|
||||
}
|
||||
|
||||
const float RENDER_HEAD_CUTOFF_DISTANCE = 0.47f;
|
||||
|
||||
bool MyAvatar::cameraInsideHead(const glm::vec3& cameraPosition) const {
|
||||
if (!_skeletonModel) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// transform cameraPosition into rig coordinates
|
||||
AnimPose rigToWorld = AnimPose(getWorldOrientation() * Quaternions::Y_180, getWorldPosition());
|
||||
AnimPose worldToRig = rigToWorld.inverse();
|
||||
glm::vec3 rigCameraPosition = worldToRig * cameraPosition;
|
||||
|
||||
// use head k-dop shape to determine if camera is inside head.
|
||||
const Rig& rig = _skeletonModel->getRig();
|
||||
int headJointIndex = rig.indexOfJoint("Head");
|
||||
if (headJointIndex >= 0) {
|
||||
const HFMModel& hfmModel = _skeletonModel->getHFMModel();
|
||||
AnimPose headPose;
|
||||
if (rig.getAbsoluteJointPoseInRigFrame(headJointIndex, headPose)) {
|
||||
glm::vec3 displacement;
|
||||
const HFMJointShapeInfo& headShapeInfo = hfmModel.joints[headJointIndex].shapeInfo;
|
||||
return findPointKDopDisplacement(rigCameraPosition, headPose, headShapeInfo, displacement);
|
||||
}
|
||||
}
|
||||
|
||||
// fall back to simple distance check.
|
||||
const float RENDER_HEAD_CUTOFF_DISTANCE = 0.47f;
|
||||
return glm::length(cameraPosition - getHeadPosition()) < (RENDER_HEAD_CUTOFF_DISTANCE * getModelScale());
|
||||
}
|
||||
|
||||
bool MyAvatar::shouldRenderHead(const RenderArgs* renderArgs) const {
|
||||
bool defaultMode = renderArgs->_renderMode == RenderArgs::DEFAULT_RENDER_MODE;
|
||||
bool firstPerson = qApp->getCamera().getMode() == CAMERA_MODE_FIRST_PERSON;
|
||||
bool overrideAnim = _skeletonModel ? _skeletonModel->getRig().isPlayingOverrideAnimation() : false;
|
||||
bool insideHead = cameraInsideHead(renderArgs->getViewFrustum().getPosition());
|
||||
return !defaultMode || !firstPerson || !insideHead;
|
||||
return !defaultMode || (!firstPerson && !insideHead) || (overrideAnim && !insideHead);
|
||||
}
|
||||
|
||||
void MyAvatar::setHasScriptedBlendshapes(bool hasScriptedBlendshapes) {
|
||||
|
@ -3285,21 +3361,131 @@ void MyAvatar::updateOrientation(float deltaTime) {
|
|||
}
|
||||
}
|
||||
|
||||
static float scaleSpeedByDirection(const glm::vec2 velocityDirection, const float forwardSpeed, const float backwardSpeed) {
|
||||
// for the elipse function --> (x^2)/(backwardSpeed*backwardSpeed) + y^2/(forwardSpeed*forwardSpeed) = 1, scale == y^2 when x is 0
|
||||
float fwdScale = forwardSpeed * forwardSpeed;
|
||||
float backScale = backwardSpeed * backwardSpeed;
|
||||
float scaledX = velocityDirection.x * backwardSpeed;
|
||||
float scaledSpeed = forwardSpeed;
|
||||
if (velocityDirection.y < 0.0f) {
|
||||
if (backScale > 0.0f) {
|
||||
float yValue = sqrtf(fwdScale * (1.0f - ((scaledX * scaledX) / backScale)));
|
||||
scaledSpeed = sqrtf((scaledX * scaledX) + (yValue * yValue));
|
||||
float MyAvatar::calculateGearedSpeed(const float driveKey) {
|
||||
float absDriveKey = abs(driveKey);
|
||||
float sign = (driveKey < 0.0f) ? -1.0f : 1.0f;
|
||||
if (absDriveKey > getDriveGear5()) {
|
||||
return sign * 1.0f;
|
||||
}
|
||||
else if (absDriveKey > getDriveGear4()) {
|
||||
return sign * 0.8f;
|
||||
}
|
||||
else if (absDriveKey > getDriveGear3()) {
|
||||
return sign * 0.6f;
|
||||
}
|
||||
else if (absDriveKey > getDriveGear2()) {
|
||||
return sign * 0.4f;
|
||||
}
|
||||
else if (absDriveKey > getDriveGear1()) {
|
||||
return sign * 0.2f;
|
||||
}
|
||||
else {
|
||||
return sign * 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec3 MyAvatar::scaleMotorSpeed(const glm::vec3 forward, const glm::vec3 right) {
|
||||
float stickFullOn = 0.85f;
|
||||
auto zSpeed = getDriveKey(TRANSLATE_Z);
|
||||
auto xSpeed = getDriveKey(TRANSLATE_X);
|
||||
glm::vec3 direction;
|
||||
if (!useAdvancedMovementControls() && qApp->isHMDMode()) {
|
||||
// Walking disabled in settings.
|
||||
return Vectors::ZERO;
|
||||
} else if (qApp->isHMDMode()) {
|
||||
// HMD advanced movement controls.
|
||||
switch (_controlSchemeIndex) {
|
||||
case LocomotionControlsMode::CONTROLS_DEFAULT:
|
||||
// No acceleration curve for this one, constant speed.
|
||||
if (zSpeed || xSpeed) {
|
||||
direction = (zSpeed * forward) + (xSpeed * right);
|
||||
// Normalize direction.
|
||||
auto length = glm::length(direction);
|
||||
if (length > EPSILON) {
|
||||
direction /= length;
|
||||
}
|
||||
return getSensorToWorldScale() * direction * getSprintSpeed() * _walkSpeedScalar;
|
||||
} else {
|
||||
return Vectors::ZERO;
|
||||
}
|
||||
case LocomotionControlsMode::CONTROLS_ANALOG:
|
||||
case LocomotionControlsMode::CONTROLS_ANALOG_PLUS:
|
||||
if (zSpeed || xSpeed) {
|
||||
glm::vec3 scaledForward = getSensorToWorldScale() * calculateGearedSpeed(zSpeed) * _walkSpeedScalar * ((zSpeed >= stickFullOn) ? getSprintSpeed() : getWalkSpeed()) * forward;
|
||||
glm::vec3 scaledRight = getSensorToWorldScale() * calculateGearedSpeed(xSpeed) * _walkSpeedScalar * ((xSpeed > stickFullOn) ? getSprintSpeed() : getWalkSpeed()) * right;
|
||||
direction = scaledForward + scaledRight;
|
||||
return direction;
|
||||
} else {
|
||||
return Vectors::ZERO;
|
||||
}
|
||||
default:
|
||||
qDebug() << "Invalid control scheme index.";
|
||||
return Vectors::ZERO;
|
||||
}
|
||||
} else {
|
||||
scaledSpeed = backwardSpeed;
|
||||
// Desktop mode.
|
||||
direction = (zSpeed * forward) + (xSpeed * right);
|
||||
auto length = glm::length(direction);
|
||||
if (length > EPSILON) {
|
||||
direction /= length;
|
||||
}
|
||||
direction *= getWalkSpeed() * _walkSpeedScalar;
|
||||
return direction;
|
||||
}
|
||||
return scaledSpeed;
|
||||
}
|
||||
|
||||
glm::vec3 MyAvatar::calculateScaledDirection(){
|
||||
CharacterController::State state = _characterController.getState();
|
||||
|
||||
// compute action input
|
||||
// Determine if we're head or controller relative...
|
||||
glm::vec3 forward, right;
|
||||
|
||||
if (qApp->isHMDMode()) {
|
||||
auto handRotation = getOffHandRotation();
|
||||
glm::vec3 controllerForward(0.0f, 1.0f, 0.0f);
|
||||
glm::vec3 controllerRight(0.0f, 0.0f, (getDominantHand() == DOMINANT_RIGHT_HAND ? 1.0f : -1.0f));
|
||||
glm::vec3 transform;
|
||||
switch (getMovementReference()) {
|
||||
case LocomotionRelativeMovementMode::MOVEMENT_HAND_RELATIVE:
|
||||
forward = (handRotation * controllerForward);
|
||||
right = (handRotation * controllerRight);
|
||||
break;
|
||||
case LocomotionRelativeMovementMode::MOVEMENT_HAND_RELATIVE_LEVELED:
|
||||
forward = (handRotation * controllerForward);
|
||||
transform = forward - (glm::dot(forward, Vectors::UNIT_Y) * Vectors::UNIT_Y);
|
||||
if (glm::length(transform) > EPSILON) {
|
||||
forward = glm::normalize(transform);
|
||||
} else {
|
||||
forward = Vectors::ZERO;
|
||||
}
|
||||
right = (handRotation * controllerRight);
|
||||
transform = right - (glm::dot(right, Vectors::UNIT_Y) * Vectors::UNIT_Y);
|
||||
if (glm::length(transform) > EPSILON) {
|
||||
right = glm::normalize(transform);
|
||||
} else {
|
||||
right = Vectors::ZERO;
|
||||
}
|
||||
break;
|
||||
case LocomotionRelativeMovementMode::MOVEMENT_HMD_RELATIVE:
|
||||
default:
|
||||
forward = IDENTITY_FORWARD;
|
||||
right = IDENTITY_RIGHT;
|
||||
}
|
||||
} else {
|
||||
forward = IDENTITY_FORWARD;
|
||||
right = IDENTITY_RIGHT;
|
||||
}
|
||||
|
||||
glm::vec3 direction = scaleMotorSpeed(forward, right);
|
||||
|
||||
if (state == CharacterController::State::Hover ||
|
||||
_characterController.computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS) {
|
||||
glm::vec3 up = (getDriveKey(TRANSLATE_Y)) * IDENTITY_UP;
|
||||
direction += up;
|
||||
}
|
||||
|
||||
return direction;
|
||||
}
|
||||
|
||||
void MyAvatar::updateActionMotor(float deltaTime) {
|
||||
|
@ -3319,25 +3505,13 @@ void MyAvatar::updateActionMotor(float deltaTime) {
|
|||
|
||||
CharacterController::State state = _characterController.getState();
|
||||
|
||||
// compute action input
|
||||
glm::vec3 forward = (getDriveKey(TRANSLATE_Z)) * IDENTITY_FORWARD;
|
||||
glm::vec3 right = (getDriveKey(TRANSLATE_X)) * IDENTITY_RIGHT;
|
||||
|
||||
glm::vec3 direction = forward + right;
|
||||
if (state == CharacterController::State::Hover ||
|
||||
_characterController.computeCollisionMask() == BULLET_COLLISION_MASK_COLLISIONLESS) {
|
||||
glm::vec3 up = (getDriveKey(TRANSLATE_Y)) * IDENTITY_UP;
|
||||
direction += up;
|
||||
}
|
||||
glm::vec3 direction = calculateScaledDirection();
|
||||
|
||||
_wasPushing = _isPushing;
|
||||
float directionLength = glm::length(direction);
|
||||
_isPushing = directionLength > EPSILON;
|
||||
|
||||
// normalize direction
|
||||
if (_isPushing) {
|
||||
direction /= directionLength;
|
||||
} else {
|
||||
if (!_isPushing) {
|
||||
direction = Vectors::ZERO;
|
||||
}
|
||||
|
||||
|
@ -3353,6 +3527,7 @@ void MyAvatar::updateActionMotor(float deltaTime) {
|
|||
const float maxBoostSpeed = sensorToWorldScale * MAX_BOOST_SPEED;
|
||||
|
||||
if (_isPushing) {
|
||||
direction /= directionLength;
|
||||
if (motorSpeed < maxBoostSpeed) {
|
||||
// an active action motor should never be slower than this
|
||||
float boostCoefficient = (maxBoostSpeed - motorSpeed) / maxBoostSpeed;
|
||||
|
@ -3363,11 +3538,17 @@ void MyAvatar::updateActionMotor(float deltaTime) {
|
|||
}
|
||||
_actionMotorVelocity = motorSpeed * direction;
|
||||
} else {
|
||||
// we're interacting with a floor --> simple horizontal speed and exponential decay
|
||||
const glm::vec2 currentVel = { direction.x, direction.z };
|
||||
float scaledSpeed = scaleSpeedByDirection(currentVel, _walkSpeed.get(), _walkBackwardSpeed.get());
|
||||
// _walkSpeedScalar is a multiplier if we are in sprint mode, otherwise 1.0
|
||||
_actionMotorVelocity = sensorToWorldScale * (scaledSpeed * _walkSpeedScalar) * direction;
|
||||
_actionMotorVelocity = direction;
|
||||
}
|
||||
|
||||
float previousBoomLength = _boomLength;
|
||||
float boomChange = getDriveKey(ZOOM);
|
||||
_boomLength += 2.0f * _boomLength * boomChange + boomChange * boomChange;
|
||||
_boomLength = glm::clamp<float>(_boomLength, ZOOM_MIN, ZOOM_MAX);
|
||||
|
||||
// May need to change view if boom length has changed
|
||||
if (previousBoomLength != _boomLength) {
|
||||
qApp->changeViewAsNeeded(_boomLength);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3740,7 +3921,8 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette
|
|||
// See https://highfidelity.fogbugz.com/f/cases/5003/findRayIntersection-has-option-to-use-collidableOnly-but-doesn-t-actually-use-colliders
|
||||
QVariantMap extraInfo;
|
||||
EntityItemID entityID = entityTree->evalRayIntersection(startPointIn, directionIn, include, ignore,
|
||||
PickFilter(PickFilter::getBitMask(PickFilter::FlagBit::COLLIDABLE) | PickFilter::getBitMask(PickFilter::FlagBit::PRECISE)),
|
||||
PickFilter(PickFilter::getBitMask(PickFilter::FlagBit::COLLIDABLE) | PickFilter::getBitMask(PickFilter::FlagBit::PRECISE)
|
||||
| PickFilter::getBitMask(PickFilter::FlagBit::DOMAIN_ENTITIES) | PickFilter::getBitMask(PickFilter::FlagBit::AVATAR_ENTITIES)), // exclude Local entities
|
||||
element, distance, face, normalOut, extraInfo, lockType, accurateResult);
|
||||
if (entityID.isNull()) {
|
||||
return false;
|
||||
|
@ -3880,6 +4062,136 @@ void MyAvatar::setFlyingHMDPref(bool enabled) {
|
|||
_flyingPrefHMD = enabled;
|
||||
}
|
||||
|
||||
void MyAvatar::setMovementReference(int enabled) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setMovementReference", Q_ARG(bool, enabled));
|
||||
return;
|
||||
}
|
||||
_movementReference = enabled;
|
||||
}
|
||||
|
||||
int MyAvatar::getMovementReference() {
|
||||
return _movementReference;
|
||||
}
|
||||
|
||||
void MyAvatar::setControlSchemeIndex(int index){
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setControlSchemeIndex", Q_ARG(int, index));
|
||||
return;
|
||||
}
|
||||
// Need to add checks for valid indices.
|
||||
_controlSchemeIndex = index;
|
||||
}
|
||||
|
||||
int MyAvatar::getControlSchemeIndex() {
|
||||
return _controlSchemeIndex;
|
||||
}
|
||||
|
||||
void MyAvatar::setDriveGear1(float shiftPoint) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setDriveGear1", Q_ARG(float, shiftPoint));
|
||||
return;
|
||||
}
|
||||
if (shiftPoint > 1.0f || shiftPoint < 0.0f) return;
|
||||
_driveGear1 = (shiftPoint < _driveGear2) ? shiftPoint : _driveGear1;
|
||||
}
|
||||
|
||||
float MyAvatar::getDriveGear1() {
|
||||
switch (_controlSchemeIndex) {
|
||||
case LocomotionControlsMode::CONTROLS_ANALOG:
|
||||
return ANALOG_AVATAR_GEAR_1;
|
||||
case LocomotionControlsMode::CONTROLS_ANALOG_PLUS:
|
||||
return _driveGear1;
|
||||
case LocomotionControlsMode::CONTROLS_DEFAULT:
|
||||
default:
|
||||
return 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::setDriveGear2(float shiftPoint) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setDriveGear2", Q_ARG(float, shiftPoint));
|
||||
return;
|
||||
}
|
||||
if (shiftPoint > 1.0f || shiftPoint < 0.0f) return;
|
||||
_driveGear2 = (shiftPoint < _driveGear3 && shiftPoint >= _driveGear1) ? shiftPoint : _driveGear2;
|
||||
}
|
||||
|
||||
float MyAvatar::getDriveGear2() {
|
||||
switch (_controlSchemeIndex) {
|
||||
case LocomotionControlsMode::CONTROLS_ANALOG:
|
||||
return ANALOG_AVATAR_GEAR_2;
|
||||
case LocomotionControlsMode::CONTROLS_ANALOG_PLUS:
|
||||
return _driveGear2;
|
||||
case LocomotionControlsMode::CONTROLS_DEFAULT:
|
||||
default:
|
||||
return 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::setDriveGear3(float shiftPoint) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setDriveGear3", Q_ARG(float, shiftPoint));
|
||||
return;
|
||||
}
|
||||
if (shiftPoint > 1.0f || shiftPoint < 0.0f) return;
|
||||
_driveGear3 = (shiftPoint < _driveGear4 && shiftPoint >= _driveGear2) ? shiftPoint : _driveGear3;
|
||||
}
|
||||
|
||||
float MyAvatar::getDriveGear3() {
|
||||
switch (_controlSchemeIndex) {
|
||||
case LocomotionControlsMode::CONTROLS_ANALOG:
|
||||
return ANALOG_AVATAR_GEAR_3;
|
||||
case LocomotionControlsMode::CONTROLS_ANALOG_PLUS:
|
||||
return _driveGear3;
|
||||
case LocomotionControlsMode::CONTROLS_DEFAULT:
|
||||
default:
|
||||
return 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::setDriveGear4(float shiftPoint) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setDriveGear4", Q_ARG(float, shiftPoint));
|
||||
return;
|
||||
}
|
||||
if (shiftPoint > 1.0f || shiftPoint < 0.0f) return;
|
||||
_driveGear4 = (shiftPoint < _driveGear5 && shiftPoint >= _driveGear3) ? shiftPoint : _driveGear4;
|
||||
}
|
||||
|
||||
float MyAvatar::getDriveGear4() {
|
||||
switch (_controlSchemeIndex) {
|
||||
case LocomotionControlsMode::CONTROLS_ANALOG:
|
||||
return ANALOG_AVATAR_GEAR_4;
|
||||
case LocomotionControlsMode::CONTROLS_ANALOG_PLUS:
|
||||
return _driveGear4;
|
||||
case LocomotionControlsMode::CONTROLS_DEFAULT:
|
||||
default:
|
||||
return 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::setDriveGear5(float shiftPoint) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setDriveGear5", Q_ARG(float, shiftPoint));
|
||||
return;
|
||||
}
|
||||
if (shiftPoint > 1.0f || shiftPoint < 0.0f) return;
|
||||
_driveGear5 = (shiftPoint > _driveGear4) ? shiftPoint : _driveGear5;
|
||||
}
|
||||
|
||||
float MyAvatar::getDriveGear5() {
|
||||
switch (_controlSchemeIndex) {
|
||||
case LocomotionControlsMode::CONTROLS_ANALOG:
|
||||
return ANALOG_AVATAR_GEAR_5;
|
||||
case LocomotionControlsMode::CONTROLS_ANALOG_PLUS:
|
||||
return _driveGear5;
|
||||
case LocomotionControlsMode::CONTROLS_DEFAULT:
|
||||
default:
|
||||
return 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
bool MyAvatar::getFlyingHMDPref() {
|
||||
return _flyingPrefHMD;
|
||||
}
|
||||
|
@ -4488,11 +4800,37 @@ bool MyAvatar::getIsSitStandStateLocked() const {
|
|||
}
|
||||
|
||||
float MyAvatar::getWalkSpeed() const {
|
||||
return _walkSpeed.get() * _walkSpeedScalar;
|
||||
if (qApp->isHMDMode()) {
|
||||
switch (_controlSchemeIndex) {
|
||||
case LocomotionControlsMode::CONTROLS_ANALOG:
|
||||
return _analogWalkSpeed.get();
|
||||
case LocomotionControlsMode::CONTROLS_ANALOG_PLUS:
|
||||
return _analogPlusWalkSpeed.get();
|
||||
case LocomotionControlsMode::CONTROLS_DEFAULT:
|
||||
default:
|
||||
return _defaultWalkSpeed.get();
|
||||
}
|
||||
} else {
|
||||
return _defaultWalkSpeed.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
float MyAvatar::getWalkBackwardSpeed() const {
|
||||
return _walkSpeed.get() * _walkSpeedScalar;
|
||||
if (qApp->isHMDMode()) {
|
||||
switch (_controlSchemeIndex) {
|
||||
case LocomotionControlsMode::CONTROLS_ANALOG:
|
||||
return _analogWalkBackwardSpeed.get();
|
||||
case LocomotionControlsMode::CONTROLS_ANALOG_PLUS:
|
||||
return _analogPlusWalkBackwardSpeed.get();
|
||||
case LocomotionControlsMode::CONTROLS_DEFAULT:
|
||||
default:
|
||||
return _defaultWalkBackwardSpeed.get();
|
||||
}
|
||||
} else {
|
||||
return _defaultWalkBackwardSpeed.get();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool MyAvatar::isReadyForPhysics() const {
|
||||
|
@ -4500,7 +4838,12 @@ bool MyAvatar::isReadyForPhysics() const {
|
|||
}
|
||||
|
||||
void MyAvatar::setSprintMode(bool sprint) {
|
||||
_walkSpeedScalar = sprint ? _sprintSpeed.get() : AVATAR_WALK_SPEED_SCALAR;
|
||||
if (qApp->isHMDMode()) {
|
||||
_walkSpeedScalar = sprint ? AVATAR_DESKTOP_SPRINT_SPEED_SCALAR : AVATAR_WALK_SPEED_SCALAR;
|
||||
}
|
||||
else {
|
||||
_walkSpeedScalar = sprint ? AVATAR_HMD_SPRINT_SPEED_SCALAR : AVATAR_WALK_SPEED_SCALAR;
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::setIsInWalkingState(bool isWalking) {
|
||||
|
@ -4563,19 +4906,103 @@ void MyAvatar::setIsSitStandStateLocked(bool isLocked) {
|
|||
}
|
||||
|
||||
void MyAvatar::setWalkSpeed(float value) {
|
||||
_walkSpeed.set(value);
|
||||
switch (_controlSchemeIndex) {
|
||||
case LocomotionControlsMode::CONTROLS_DEFAULT:
|
||||
_defaultWalkSpeed.set(value);
|
||||
break;
|
||||
case LocomotionControlsMode::CONTROLS_ANALOG:
|
||||
_analogWalkSpeed.set(value);
|
||||
break;
|
||||
case LocomotionControlsMode::CONTROLS_ANALOG_PLUS:
|
||||
_analogPlusWalkSpeed.set(value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::setWalkBackwardSpeed(float value) {
|
||||
_walkBackwardSpeed.set(value);
|
||||
switch (_controlSchemeIndex) {
|
||||
case LocomotionControlsMode::CONTROLS_DEFAULT:
|
||||
_defaultWalkBackwardSpeed.set(value);
|
||||
break;
|
||||
case LocomotionControlsMode::CONTROLS_ANALOG:
|
||||
_analogWalkBackwardSpeed.set(value);
|
||||
break;
|
||||
case LocomotionControlsMode::CONTROLS_ANALOG_PLUS:
|
||||
_analogPlusWalkBackwardSpeed.set(value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::setSprintSpeed(float value) {
|
||||
_sprintSpeed.set(value);
|
||||
switch (_controlSchemeIndex) {
|
||||
case LocomotionControlsMode::CONTROLS_DEFAULT:
|
||||
_defaultSprintSpeed.set(value);
|
||||
break;
|
||||
case LocomotionControlsMode::CONTROLS_ANALOG:
|
||||
_analogSprintSpeed.set(value);
|
||||
break;
|
||||
case LocomotionControlsMode::CONTROLS_ANALOG_PLUS:
|
||||
_analogPlusSprintSpeed.set(value);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
float MyAvatar::getSprintSpeed() const {
|
||||
return _sprintSpeed.get();
|
||||
if (qApp->isHMDMode()) {
|
||||
switch (_controlSchemeIndex) {
|
||||
case LocomotionControlsMode::CONTROLS_ANALOG:
|
||||
return _analogSprintSpeed.get();
|
||||
case LocomotionControlsMode::CONTROLS_ANALOG_PLUS:
|
||||
return _analogPlusSprintSpeed.get();
|
||||
case LocomotionControlsMode::CONTROLS_DEFAULT:
|
||||
default:
|
||||
return _defaultSprintSpeed.get();
|
||||
}
|
||||
} else {
|
||||
return _defaultSprintSpeed.get();
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::setAnalogWalkSpeed(float value) {
|
||||
_analogWalkSpeed.set(value);
|
||||
// Sprint speed for Analog should be double walk speed.
|
||||
_analogSprintSpeed.set(value * 2.0f);
|
||||
}
|
||||
|
||||
float MyAvatar::getAnalogWalkSpeed() const {
|
||||
return _analogWalkSpeed.get();
|
||||
}
|
||||
|
||||
void MyAvatar::setAnalogSprintSpeed(float value) {
|
||||
_analogSprintSpeed.set(value);
|
||||
}
|
||||
|
||||
float MyAvatar::getAnalogSprintSpeed() const {
|
||||
return _analogSprintSpeed.get();
|
||||
}
|
||||
|
||||
void MyAvatar::setAnalogPlusWalkSpeed(float value) {
|
||||
_analogPlusWalkSpeed.set(value);
|
||||
// Sprint speed for Analog Plus should be double walk speed.
|
||||
_analogPlusSprintSpeed.set(value * 2.0f);
|
||||
}
|
||||
|
||||
float MyAvatar::getAnalogPlusWalkSpeed() const {
|
||||
return _analogPlusWalkSpeed.get();
|
||||
}
|
||||
|
||||
void MyAvatar::setAnalogPlusSprintSpeed(float value) {
|
||||
_analogPlusSprintSpeed.set(value);
|
||||
}
|
||||
|
||||
float MyAvatar::getAnalogPlusSprintSpeed() const {
|
||||
return _analogPlusSprintSpeed.get();
|
||||
}
|
||||
|
||||
void MyAvatar::setSitStandStateChange(bool stateChanged) {
|
||||
|
|
|
@ -39,6 +39,18 @@ class ModelItemID;
|
|||
class MyHead;
|
||||
class DetailedMotionState;
|
||||
|
||||
enum LocomotionControlsMode {
|
||||
CONTROLS_DEFAULT = 0,
|
||||
CONTROLS_ANALOG,
|
||||
CONTROLS_ANALOG_PLUS
|
||||
};
|
||||
|
||||
enum LocomotionRelativeMovementMode {
|
||||
MOVEMENT_HMD_RELATIVE = 0,
|
||||
MOVEMENT_HAND_RELATIVE,
|
||||
MOVEMENT_HAND_RELATIVE_LEVELED
|
||||
};
|
||||
|
||||
enum eyeContactTarget {
|
||||
LEFT_EYE,
|
||||
RIGHT_EYE,
|
||||
|
@ -371,6 +383,13 @@ class MyAvatar : public Avatar {
|
|||
using Clock = std::chrono::system_clock;
|
||||
using TimePoint = Clock::time_point;
|
||||
|
||||
const float DEFAULT_GEAR_1 = 0.2f;
|
||||
const float DEFAULT_GEAR_2 = 0.4f;
|
||||
const float DEFAULT_GEAR_3 = 0.8f;
|
||||
const float DEFAULT_GEAR_4 = 0.9f;
|
||||
const float DEFAULT_GEAR_5 = 1.0f;
|
||||
|
||||
const bool DEFAULT_STRAFE_ENABLED = true;
|
||||
public:
|
||||
|
||||
/**jsdoc
|
||||
|
@ -578,6 +597,26 @@ public:
|
|||
*/
|
||||
Q_INVOKABLE void overrideAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame);
|
||||
|
||||
/**jsdoc
|
||||
* <code>overrideHandAnimation()</code> Gets the overrides the default hand poses that are triggered with controller buttons.
|
||||
* use {@link MyAvatar.restoreHandAnimation}.</p> to restore the default poses.
|
||||
* @function MyAvatar.overrideHandAnimation
|
||||
* @param isLeft {boolean} Set true if using the left hand
|
||||
* @param url {string} The URL to the animation file. Animation files need to be FBX format, but only need to contain the
|
||||
* avatar skeleton and animation data.
|
||||
* @param fps {number} The frames per second (FPS) rate for the animation playback. 30 FPS is normal speed.
|
||||
* @param loop {boolean} Set to true if the animation should loop.
|
||||
* @param firstFrame {number} The frame the animation should start at.
|
||||
* @param lastFrame {number} The frame the animation should end at
|
||||
* @example <caption> Override left hand animation for three seconds. </caption>
|
||||
* // Override the left hand pose then restore the default pose.
|
||||
* MyAvatar.overrideHandAnimation(isLeft, ANIM_URL, 30, true, 0, 53);
|
||||
* Script.setTimeout(function () {
|
||||
* MyAvatar.restoreHandAnimation();
|
||||
* }, 3000);
|
||||
*/
|
||||
Q_INVOKABLE void overrideHandAnimation(bool isLeft, const QString& url, float fps, bool loop, float firstFrame, float lastFrame);
|
||||
|
||||
/**jsdoc
|
||||
* Restores the default animations.
|
||||
* <p>The avatar animation system includes a set of default animations along with rules for how those animations are blended
|
||||
|
@ -596,6 +635,24 @@ public:
|
|||
*/
|
||||
Q_INVOKABLE void restoreAnimation();
|
||||
|
||||
/**jsdoc
|
||||
* Restores the default hand animation state machine that is driven by the state machine in the avatar-animation json.
|
||||
* <p>The avatar animation system includes a set of default animations along with rules for how those animations are blended
|
||||
* together with procedural data (such as look at vectors, hand sensors etc.). Playing your own custom animations will
|
||||
* override the default animations. <code>restoreHandAnimation()</code> is used to restore the default hand poses
|
||||
* If you aren't currently playing an override hand
|
||||
* animation, this function has no effect.</p>
|
||||
* @function MyAvatar.restoreHandAnimation
|
||||
* @param isLeft {boolean} Set to true if using the left hand
|
||||
* @example <caption> Override left hand animation for three seconds. </caption>
|
||||
* // Override the left hand pose then restore the default pose.
|
||||
* MyAvatar.overrideHandAnimation(isLeft, ANIM_URL, 30, true, 0, 53);
|
||||
* Script.setTimeout(function () {
|
||||
* MyAvatar.restoreHandAnimation();
|
||||
* }, 3000);
|
||||
*/
|
||||
Q_INVOKABLE void restoreHandAnimation(bool isLeft);
|
||||
|
||||
/**jsdoc
|
||||
* Gets the current animation roles.
|
||||
* <p>Each avatar has an avatar-animation.json file that defines which animations are used and how they are blended together
|
||||
|
@ -729,7 +786,17 @@ public:
|
|||
*/
|
||||
Q_INVOKABLE void setSnapTurn(bool on) { _useSnapTurn = on; }
|
||||
|
||||
/**
|
||||
* @function MyAvatar.getControlScheme
|
||||
* @returns {number}
|
||||
*/
|
||||
Q_INVOKABLE int getControlScheme() const { return _controlSchemeIndex; }
|
||||
|
||||
/**
|
||||
* @function MyAvatar.setControlScheme
|
||||
* @param {number} index
|
||||
*/
|
||||
Q_INVOKABLE void setControlScheme(int index) { _controlSchemeIndex = (index >= 0 && index <= 2) ? index : 0; }
|
||||
/**jsdoc
|
||||
* Sets the avatar's dominant hand.
|
||||
* @function MyAvatar.setDominantHand
|
||||
|
@ -744,7 +811,16 @@ public:
|
|||
* @returns {string} <code>"left"</code> for the left hand, <code>"right"</code> for the right hand.
|
||||
*/
|
||||
Q_INVOKABLE QString getDominantHand() const;
|
||||
|
||||
/**jsdoc
|
||||
* @function MyAVatar.setStrafeEnabled
|
||||
* @param {bool} enabled
|
||||
*/
|
||||
Q_INVOKABLE void setStrafeEnabled(bool enabled);
|
||||
/**jsdoc
|
||||
* @function MyAvatar.getStrafeEnabled
|
||||
* @returns {bool}
|
||||
*/
|
||||
Q_INVOKABLE bool getStrafeEnabled() const;
|
||||
/**jsdoc
|
||||
* @function MyAvatar.setHmdAvatarAlignmentType
|
||||
* @param {string} type - <code>"head"</code> to align your head and your avatar's head, <code>"eyes"</code> to align your
|
||||
|
@ -1235,6 +1311,7 @@ public:
|
|||
controller::Pose getControllerPoseInSensorFrame(controller::Action action) const;
|
||||
controller::Pose getControllerPoseInWorldFrame(controller::Action action) const;
|
||||
controller::Pose getControllerPoseInAvatarFrame(controller::Action action) const;
|
||||
glm::quat getOffHandRotation() const;
|
||||
|
||||
bool hasDriveInput() const;
|
||||
|
||||
|
@ -1317,6 +1394,106 @@ public:
|
|||
*/
|
||||
Q_INVOKABLE bool getFlyingHMDPref();
|
||||
|
||||
/**jsdoc
|
||||
* Set your preference for hand-relative movement.
|
||||
* @function MyAvatar.setHandRelativeMovement
|
||||
* @param {number} enabled - Set <code>true</code> if you want to enable hand-relative movement, otherwise set to <code>false</code>.
|
||||
*
|
||||
*/
|
||||
Q_INVOKABLE void setMovementReference(int enabled);
|
||||
|
||||
/**jsdoc
|
||||
* Get your preference for hand-relative movement.
|
||||
* @function MyAvatar.getHandRelativeMovement
|
||||
* @returns {number} <code>true</code> if your preference is for user locomotion to be relative to the direction your
|
||||
* controller is pointing, otherwise <code>false</code>.
|
||||
*/
|
||||
Q_INVOKABLE int getMovementReference();
|
||||
|
||||
/**jsdoc
|
||||
* Set the first 'shifting point' for acceleration step function.
|
||||
* @function MyAvatar.setDriveGear1
|
||||
* @param {number} shiftPoint - Set the first shift point for analog movement acceleration step function, between [0.0, 1.0]. Must be less than or equal to Gear 2.
|
||||
*/
|
||||
Q_INVOKABLE void setDriveGear1(float shiftPoint);
|
||||
|
||||
/**jsdoc
|
||||
* Get the first 'shifting point' for acceleration step function.
|
||||
* @function MyAvatar.getDriveGear1
|
||||
* @returns {number} Value between [0.0, 1.0].
|
||||
*/
|
||||
Q_INVOKABLE float getDriveGear1();
|
||||
|
||||
/**jsdoc
|
||||
* Set the second 'shifting point' for acceleration step function.
|
||||
* @function MyAvatar.setDriveGear2
|
||||
* @param {number} shiftPoint - Defines the second shift point for analog movement acceleration step function, between [0, 1]. Must be greater than or equal to Gear 1 and less than or equal to Gear 2.
|
||||
*/
|
||||
Q_INVOKABLE void setDriveGear2(float shiftPoint);
|
||||
|
||||
/**jsdoc
|
||||
* Get the second 'shifting point' for acceleration step function.
|
||||
* @function MyAvatar.getDriveGear2
|
||||
* @returns {number} Value between [0.0, 1.0].
|
||||
*/
|
||||
Q_INVOKABLE float getDriveGear2();
|
||||
|
||||
/**jsdoc
|
||||
* Set the third 'shifting point' for acceleration step function.
|
||||
* @function MyAvatar.setDriveGear3
|
||||
* @param {number} shiftPoint - Defines the third shift point for analog movement acceleration step function, between [0, 1]. Must be greater than or equal to Gear 2 and less than or equal to Gear 4.
|
||||
*/
|
||||
Q_INVOKABLE void setDriveGear3(float shiftPoint);
|
||||
|
||||
/**jsdoc
|
||||
* Get the third 'shifting point' for acceleration step function.
|
||||
* @function MyAvatar.getDriveGear3
|
||||
* @returns {number} Value between [0.0, 1.0].
|
||||
*/
|
||||
Q_INVOKABLE float getDriveGear3();
|
||||
|
||||
/**jsdoc
|
||||
* Set the fourth 'shifting point' for acceleration step function.
|
||||
* @function MyAvatar.setDriveGear4
|
||||
* @param {number} shiftPoint - Defines the fourth shift point for analog movement acceleration step function, between [0, 1]. Must be greater than Gear 3 and less than Gear 5.
|
||||
*/
|
||||
Q_INVOKABLE void setDriveGear4(float shiftPoint);
|
||||
|
||||
/**jsdoc
|
||||
* Get the fourth 'shifting point' for acceleration step function.
|
||||
* @function MyAvatar.getDriveGear4
|
||||
* @returns {number} Value between [0.0, 1.0].
|
||||
*/
|
||||
Q_INVOKABLE float getDriveGear4();
|
||||
|
||||
/**jsdoc
|
||||
* Set the fifth 'shifting point' for acceleration step function.
|
||||
* @function MyAvatar.setDriveGear5
|
||||
* @param {number} shiftPoint - Defines the fifth shift point for analog movement acceleration step function, between [0, 1]. Must be greater than or equal to Gear 4.
|
||||
*/
|
||||
Q_INVOKABLE void setDriveGear5(float shiftPoint);
|
||||
|
||||
/**jsdoc
|
||||
* Get the fifth 'shifting point' for acceleration step function.
|
||||
* @function MyAvatar.getDriveGear5
|
||||
* @returns {number} Value between [0.0, 1.0].
|
||||
*/
|
||||
Q_INVOKABLE float getDriveGear5();
|
||||
|
||||
/**jsdoc
|
||||
* Choose the control scheme.
|
||||
* @function MyAvatar.setControlSchemeIndex
|
||||
* @param {number} Choose the control scheme to be used.
|
||||
*/
|
||||
void setControlSchemeIndex(int index);
|
||||
|
||||
/**jsdoc
|
||||
* Check what control scheme is in use.
|
||||
* @function MyAvatar.getControlSchemeIndex
|
||||
* @returns {number} Returns the index associated with a given control scheme.
|
||||
*/
|
||||
int getControlSchemeIndex();
|
||||
|
||||
/**jsdoc
|
||||
* Gets the target scale of the avatar. The target scale is the desired scale of the avatar without any restrictions on
|
||||
* permissible scale values imposed by the domain.
|
||||
|
@ -1490,6 +1667,14 @@ public:
|
|||
float getWalkBackwardSpeed() const;
|
||||
void setSprintSpeed(float value);
|
||||
float getSprintSpeed() const;
|
||||
void setAnalogWalkSpeed(float value);
|
||||
float getAnalogWalkSpeed() const;
|
||||
void setAnalogSprintSpeed(float value);
|
||||
float getAnalogSprintSpeed() const;
|
||||
void setAnalogPlusWalkSpeed(float value);
|
||||
float getAnalogPlusWalkSpeed() const;
|
||||
void setAnalogPlusSprintSpeed(float value);
|
||||
float getAnalogPlusSprintSpeed() const;
|
||||
void setSitStandStateChange(bool stateChanged);
|
||||
float getSitStandStateChange() const;
|
||||
void updateSitStandState(float newHeightReading, float dt);
|
||||
|
@ -2230,6 +2415,13 @@ private:
|
|||
float _boomLength { ZOOM_DEFAULT };
|
||||
float _yawSpeed; // degrees/sec
|
||||
float _pitchSpeed; // degrees/sec
|
||||
float _driveGear1 { DEFAULT_GEAR_1 };
|
||||
float _driveGear2 { DEFAULT_GEAR_2 };
|
||||
float _driveGear3 { DEFAULT_GEAR_3 };
|
||||
float _driveGear4 { DEFAULT_GEAR_4 };
|
||||
float _driveGear5 { DEFAULT_GEAR_5 };
|
||||
int _controlSchemeIndex { CONTROLS_DEFAULT };
|
||||
int _movementReference{ 0 };
|
||||
|
||||
glm::vec3 _thrust { 0.0f }; // impulse accumulator for outside sources
|
||||
|
||||
|
@ -2270,6 +2462,9 @@ private:
|
|||
|
||||
// private methods
|
||||
void updateOrientation(float deltaTime);
|
||||
glm::vec3 calculateScaledDirection();
|
||||
float calculateGearedSpeed(const float driveKey);
|
||||
glm::vec3 scaleMotorSpeed(const glm::vec3 forward, const glm::vec3 right);
|
||||
void updateActionMotor(float deltaTime);
|
||||
void updatePosition(float deltaTime);
|
||||
void updateViewBoom();
|
||||
|
@ -2287,6 +2482,7 @@ private:
|
|||
bool _useSnapTurn { true };
|
||||
ThreadSafeValueCache<QString> _dominantHand { DOMINANT_RIGHT_HAND };
|
||||
ThreadSafeValueCache<QString> _hmdAvatarAlignmentType { DEFAULT_HMD_AVATAR_ALIGNMENT_TYPE };
|
||||
ThreadSafeValueCache<bool> _strafeEnabled{ DEFAULT_STRAFE_ENABLED };
|
||||
|
||||
const float ROLL_CONTROL_DEAD_ZONE_DEFAULT = 8.0f; // degrees
|
||||
const float ROLL_CONTROL_RATE_DEFAULT = 114.0f; // degrees / sec
|
||||
|
@ -2438,9 +2634,16 @@ private:
|
|||
ThreadSafeValueCache<bool> _lockSitStandState { false };
|
||||
|
||||
// max unscaled forward movement speed
|
||||
ThreadSafeValueCache<float> _walkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED };
|
||||
ThreadSafeValueCache<float> _walkBackwardSpeed { DEFAULT_AVATAR_MAX_WALKING_BACKWARD_SPEED };
|
||||
ThreadSafeValueCache<float> _sprintSpeed { AVATAR_SPRINT_SPEED_SCALAR };
|
||||
ThreadSafeValueCache<float> _defaultWalkSpeed { DEFAULT_AVATAR_MAX_WALKING_SPEED };
|
||||
ThreadSafeValueCache<float> _defaultWalkBackwardSpeed { DEFAULT_AVATAR_MAX_WALKING_BACKWARD_SPEED };
|
||||
ThreadSafeValueCache<float> _defaultSprintSpeed { DEFAULT_AVATAR_MAX_SPRINT_SPEED };
|
||||
ThreadSafeValueCache<float> _analogWalkSpeed { ANALOG_AVATAR_MAX_WALKING_SPEED };
|
||||
ThreadSafeValueCache<float> _analogWalkBackwardSpeed { ANALOG_AVATAR_MAX_WALKING_BACKWARD_SPEED };
|
||||
ThreadSafeValueCache<float> _analogSprintSpeed { ANALOG_AVATAR_MAX_SPRINT_SPEED };
|
||||
ThreadSafeValueCache<float> _analogPlusWalkSpeed { ANALOG_PLUS_AVATAR_MAX_WALKING_SPEED };
|
||||
ThreadSafeValueCache<float> _analogPlusWalkBackwardSpeed { ANALOG_PLUS_AVATAR_MAX_WALKING_BACKWARD_SPEED };
|
||||
ThreadSafeValueCache<float> _analogPlusSprintSpeed { ANALOG_PLUS_AVATAR_MAX_SPRINT_SPEED };
|
||||
|
||||
float _walkSpeedScalar { AVATAR_WALK_SPEED_SCALAR };
|
||||
bool _isInWalkingState { false };
|
||||
ThreadSafeValueCache<bool> _isInSittingState { false };
|
||||
|
@ -2460,6 +2663,7 @@ private:
|
|||
TimePoint _nextTraitsSendWindow;
|
||||
|
||||
Setting::Handle<QString> _dominantHandSetting;
|
||||
Setting::Handle<bool> _strafeEnabledSetting;
|
||||
Setting::Handle<QString> _hmdAvatarAlignmentTypeSetting;
|
||||
Setting::Handle<float> _headPitchSetting;
|
||||
Setting::Handle<float> _scaleSetting;
|
||||
|
@ -2473,8 +2677,17 @@ private:
|
|||
Setting::Handle<bool> _useSnapTurnSetting;
|
||||
Setting::Handle<float> _userHeightSetting;
|
||||
Setting::Handle<bool> _flyingHMDSetting;
|
||||
Setting::Handle<int> _movementReferenceSetting;
|
||||
Setting::Handle<int> _avatarEntityCountSetting;
|
||||
Setting::Handle<bool> _allowTeleportingSetting { "allowTeleporting", true };
|
||||
Setting::Handle<float> _driveGear1Setting;
|
||||
Setting::Handle<float> _driveGear2Setting;
|
||||
Setting::Handle<float> _driveGear3Setting;
|
||||
Setting::Handle<float> _driveGear4Setting;
|
||||
Setting::Handle<float> _driveGear5Setting;
|
||||
Setting::Handle<float> _analogWalkSpeedSetting;
|
||||
Setting::Handle<float> _analogPlusWalkSpeedSetting;
|
||||
Setting::Handle<int> _controlSchemeIndexSetting;
|
||||
std::vector<Setting::Handle<QUuid>> _avatarEntityIDSettings;
|
||||
std::vector<Setting::Handle<QByteArray>> _avatarEntityDataSettings;
|
||||
Setting::Handle<QString> _userRecenterModelSetting;
|
||||
|
|
|
@ -334,7 +334,9 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
eyeParams.leftEyeJointIndex = _rig.indexOfJoint("LeftEye");
|
||||
eyeParams.rightEyeJointIndex = _rig.indexOfJoint("RightEye");
|
||||
|
||||
_rig.updateFromEyeParameters(eyeParams);
|
||||
if (_owningAvatar->getHasProceduralEyeFaceMovement()) {
|
||||
_rig.updateFromEyeParameters(eyeParams);
|
||||
}
|
||||
|
||||
updateFingers();
|
||||
}
|
||||
|
|
|
@ -96,28 +96,32 @@ int passwordCallback(char* password, int maxPasswordSize, int rwFlag, void* u) {
|
|||
}
|
||||
}
|
||||
|
||||
EC_KEY* readKeys(const char* filename) {
|
||||
FILE* fp;
|
||||
EC_KEY *key = NULL;
|
||||
if ((fp = fopen(filename, "rt"))) {
|
||||
EC_KEY* readKeys(QString filename) {
|
||||
QFile file(filename);
|
||||
EC_KEY* key = NULL;
|
||||
if (file.open(QFile::ReadOnly)) {
|
||||
// file opened successfully
|
||||
qCDebug(commerce) << "opened key file" << filename;
|
||||
|
||||
if ((key = PEM_read_EC_PUBKEY(fp, NULL, NULL, NULL))) {
|
||||
QByteArray pemKeyBytes = file.readAll();
|
||||
BIO* bufio = BIO_new_mem_buf((void*)pemKeyBytes.constData(), pemKeyBytes.length());
|
||||
if ((key = PEM_read_bio_EC_PUBKEY(bufio, NULL, NULL, NULL))) {
|
||||
// now read private key
|
||||
|
||||
qCDebug(commerce) << "read public key";
|
||||
|
||||
if ((key = PEM_read_ECPrivateKey(fp, &key, passwordCallback, NULL))) {
|
||||
if ((key = PEM_read_bio_ECPrivateKey(bufio, &key, passwordCallback, NULL))) {
|
||||
qCDebug(commerce) << "read private key";
|
||||
fclose(fp);
|
||||
return key;
|
||||
BIO_free(bufio);
|
||||
file.close();
|
||||
} else {
|
||||
qCDebug(commerce) << "failed to read private key";
|
||||
}
|
||||
qCDebug(commerce) << "failed to read private key";
|
||||
} else {
|
||||
qCDebug(commerce) << "failed to read public key";
|
||||
}
|
||||
fclose(fp);
|
||||
BIO_free(bufio);
|
||||
file.close();
|
||||
} else {
|
||||
qCDebug(commerce) << "failed to open key file" << filename;
|
||||
}
|
||||
|
@ -131,8 +135,7 @@ bool Wallet::writeBackupInstructions() {
|
|||
QFile outputFile(outputFilename);
|
||||
bool retval = false;
|
||||
|
||||
if (getKeyFilePath().isEmpty())
|
||||
{
|
||||
if (getKeyFilePath().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -152,7 +155,7 @@ bool Wallet::writeBackupInstructions() {
|
|||
outputFile.write(text.toUtf8());
|
||||
|
||||
// Close the output file
|
||||
outputFile.close();
|
||||
outputFile.close();
|
||||
|
||||
retval = true;
|
||||
qCDebug(commerce) << "wrote html file successfully";
|
||||
|
@ -165,28 +168,35 @@ bool Wallet::writeBackupInstructions() {
|
|||
return retval;
|
||||
}
|
||||
|
||||
bool writeKeys(const char* filename, EC_KEY* keys) {
|
||||
FILE* fp;
|
||||
bool writeKeys(QString filename, EC_KEY* keys) {
|
||||
BIO* bio = BIO_new(BIO_s_mem());
|
||||
bool retval = false;
|
||||
if ((fp = fopen(filename, "wt"))) {
|
||||
if (!PEM_write_EC_PUBKEY(fp, keys)) {
|
||||
fclose(fp);
|
||||
qCCritical(commerce) << "failed to write public key";
|
||||
return retval;
|
||||
}
|
||||
if (!PEM_write_bio_EC_PUBKEY(bio, keys)) {
|
||||
BIO_free(bio);
|
||||
qCCritical(commerce) << "failed to write public key";
|
||||
return retval;
|
||||
}
|
||||
|
||||
if (!PEM_write_ECPrivateKey(fp, keys, EVP_des_ede3_cbc(), NULL, 0, passwordCallback, NULL)) {
|
||||
fclose(fp);
|
||||
qCCritical(commerce) << "failed to write private key";
|
||||
return retval;
|
||||
}
|
||||
if (!PEM_write_bio_ECPrivateKey(bio, keys, EVP_des_ede3_cbc(), NULL, 0, passwordCallback, NULL)) {
|
||||
BIO_free(bio);
|
||||
qCCritical(commerce) << "failed to write private key";
|
||||
return retval;
|
||||
}
|
||||
|
||||
QFile file(filename);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
const char* bio_data;
|
||||
long bio_size = BIO_get_mem_data(bio, &bio_data);
|
||||
|
||||
QByteArray keyBytes(bio_data, bio_size);
|
||||
file.write(keyBytes);
|
||||
retval = true;
|
||||
qCDebug(commerce) << "wrote keys successfully";
|
||||
fclose(fp);
|
||||
file.close();
|
||||
} else {
|
||||
qCDebug(commerce) << "failed to open key file" << filename;
|
||||
}
|
||||
BIO_free(bio);
|
||||
return retval;
|
||||
}
|
||||
|
||||
|
@ -215,7 +225,6 @@ QByteArray Wallet::getWallet() {
|
|||
}
|
||||
|
||||
QPair<QByteArray*, QByteArray*> generateECKeypair() {
|
||||
|
||||
EC_KEY* keyPair = EC_KEY_new_by_curve_name(NID_secp256k1);
|
||||
QPair<QByteArray*, QByteArray*> retval{};
|
||||
|
||||
|
@ -235,7 +244,6 @@ QPair<QByteArray*, QByteArray*> generateECKeypair() {
|
|||
if (publicKeyLength <= 0 || privateKeyLength <= 0) {
|
||||
qCDebug(commerce) << "Error getting DER public or private key from EC struct -" << ERR_get_error();
|
||||
|
||||
|
||||
// cleanup the EC struct
|
||||
EC_KEY_free(keyPair);
|
||||
|
||||
|
@ -251,8 +259,7 @@ QPair<QByteArray*, QByteArray*> generateECKeypair() {
|
|||
return retval;
|
||||
}
|
||||
|
||||
|
||||
if (!writeKeys(keyFilePath().toStdString().c_str(), keyPair)) {
|
||||
if (!writeKeys(keyFilePath(), keyPair)) {
|
||||
qCDebug(commerce) << "couldn't save keys!";
|
||||
return retval;
|
||||
}
|
||||
|
@ -273,13 +280,18 @@ QPair<QByteArray*, QByteArray*> generateECKeypair() {
|
|||
// END copied code (which will soon change)
|
||||
|
||||
// the public key can just go into a byte array
|
||||
QByteArray readPublicKey(const char* filename) {
|
||||
FILE* fp;
|
||||
EC_KEY* key = NULL;
|
||||
if ((fp = fopen(filename, "r"))) {
|
||||
QByteArray readPublicKey(QString filename) {
|
||||
QByteArray retval;
|
||||
QFile file(filename);
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
// file opened successfully
|
||||
qCDebug(commerce) << "opened key file" << filename;
|
||||
if ((key = PEM_read_EC_PUBKEY(fp, NULL, NULL, NULL))) {
|
||||
|
||||
QByteArray pemKeyBytes = file.readAll();
|
||||
BIO* bufio = BIO_new_mem_buf((void*)pemKeyBytes.constData(), pemKeyBytes.length());
|
||||
|
||||
EC_KEY* key = PEM_read_bio_EC_PUBKEY(bufio, NULL, NULL, NULL);
|
||||
if (key) {
|
||||
// file read successfully
|
||||
unsigned char* publicKeyDER = NULL;
|
||||
int publicKeyLength = i2d_EC_PUBKEY(key, &publicKeyDER);
|
||||
|
@ -287,17 +299,19 @@ QByteArray readPublicKey(const char* filename) {
|
|||
|
||||
// cleanup
|
||||
EC_KEY_free(key);
|
||||
fclose(fp);
|
||||
|
||||
qCDebug(commerce) << "parsed public key file successfully";
|
||||
|
||||
QByteArray retval((char*)publicKeyDER, publicKeyLength);
|
||||
OPENSSL_free(publicKeyDER);
|
||||
BIO_free(bufio);
|
||||
file.close();
|
||||
return retval;
|
||||
} else {
|
||||
qCDebug(commerce) << "couldn't parse" << filename;
|
||||
}
|
||||
fclose(fp);
|
||||
BIO_free(bufio);
|
||||
file.close();
|
||||
} else {
|
||||
qCDebug(commerce) << "couldn't open" << filename;
|
||||
}
|
||||
|
@ -306,13 +320,17 @@ QByteArray readPublicKey(const char* filename) {
|
|||
|
||||
// the private key should be read/copied into heap memory. For now, we need the EC_KEY struct
|
||||
// so I'll return that.
|
||||
EC_KEY* readPrivateKey(const char* filename) {
|
||||
FILE* fp;
|
||||
EC_KEY* readPrivateKey(QString filename) {
|
||||
QFile file(filename);
|
||||
EC_KEY* key = NULL;
|
||||
if ((fp = fopen(filename, "r"))) {
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
// file opened successfully
|
||||
qCDebug(commerce) << "opened key file" << filename;
|
||||
if ((key = PEM_read_ECPrivateKey(fp, &key, passwordCallback, NULL))) {
|
||||
|
||||
QByteArray pemKeyBytes = file.readAll();
|
||||
BIO* bufio = BIO_new_mem_buf((void*)pemKeyBytes.constData(), pemKeyBytes.length());
|
||||
|
||||
if ((key = PEM_read_bio_ECPrivateKey(bufio, &key, passwordCallback, NULL))) {
|
||||
qCDebug(commerce) << "parsed private key file successfully";
|
||||
|
||||
} else {
|
||||
|
@ -320,7 +338,8 @@ EC_KEY* readPrivateKey(const char* filename) {
|
|||
// if the passphrase is wrong, then let's not cache it
|
||||
DependencyManager::get<Wallet>()->setPassphrase("");
|
||||
}
|
||||
fclose(fp);
|
||||
BIO_free(bufio);
|
||||
file.close();
|
||||
} else {
|
||||
qCDebug(commerce) << "couldn't open" << filename;
|
||||
}
|
||||
|
@ -361,7 +380,7 @@ Wallet::Wallet() {
|
|||
if (wallet->getKeyFilePath().isEmpty() || !wallet->getSecurityImage()) {
|
||||
if (keyStatus == "preexisting") {
|
||||
status = (uint) WalletStatus::WALLET_STATUS_PREEXISTING;
|
||||
} else{
|
||||
} else {
|
||||
status = (uint) WalletStatus::WALLET_STATUS_NOT_SET_UP;
|
||||
}
|
||||
} else if (!wallet->walletIsAuthenticatedWithPassphrase()) {
|
||||
|
@ -371,7 +390,6 @@ Wallet::Wallet() {
|
|||
} else {
|
||||
status = (uint) WalletStatus::WALLET_STATUS_READY;
|
||||
}
|
||||
|
||||
walletScriptingInterface->setWalletStatus(status);
|
||||
});
|
||||
|
||||
|
@ -569,10 +587,10 @@ bool Wallet::walletIsAuthenticatedWithPassphrase() {
|
|||
}
|
||||
|
||||
// otherwise, we have a passphrase but no keys, so we have to check
|
||||
auto publicKey = readPublicKey(keyFilePath().toStdString().c_str());
|
||||
auto publicKey = readPublicKey(keyFilePath());
|
||||
|
||||
if (publicKey.size() > 0) {
|
||||
if (auto key = readPrivateKey(keyFilePath().toStdString().c_str())) {
|
||||
if (auto key = readPrivateKey(keyFilePath())) {
|
||||
EC_KEY_free(key);
|
||||
|
||||
// be sure to add the public key so we don't do this over and over
|
||||
|
@ -631,8 +649,7 @@ QStringList Wallet::listPublicKeys() {
|
|||
QString Wallet::signWithKey(const QByteArray& text, const QString& key) {
|
||||
EC_KEY* ecPrivateKey = NULL;
|
||||
|
||||
auto keyFilePathString = keyFilePath().toStdString();
|
||||
if ((ecPrivateKey = readPrivateKey(keyFilePath().toStdString().c_str()))) {
|
||||
if ((ecPrivateKey = readPrivateKey(keyFilePath()))) {
|
||||
unsigned char* sig = new unsigned char[ECDSA_size(ecPrivateKey)];
|
||||
|
||||
unsigned int signatureBytes = 0;
|
||||
|
@ -641,12 +658,8 @@ QString Wallet::signWithKey(const QByteArray& text, const QString& key) {
|
|||
|
||||
QByteArray hashedPlaintext = QCryptographicHash::hash(text, QCryptographicHash::Sha256);
|
||||
|
||||
|
||||
int retrn = ECDSA_sign(0,
|
||||
reinterpret_cast<const unsigned char*>(hashedPlaintext.constData()),
|
||||
hashedPlaintext.size(),
|
||||
sig,
|
||||
&signatureBytes, ecPrivateKey);
|
||||
int retrn = ECDSA_sign(0, reinterpret_cast<const unsigned char*>(hashedPlaintext.constData()), hashedPlaintext.size(),
|
||||
sig, &signatureBytes, ecPrivateKey);
|
||||
|
||||
EC_KEY_free(ecPrivateKey);
|
||||
QByteArray signature(reinterpret_cast<const char*>(sig), signatureBytes);
|
||||
|
@ -682,7 +695,6 @@ void Wallet::updateImageProvider() {
|
|||
}
|
||||
|
||||
void Wallet::chooseSecurityImage(const QString& filename) {
|
||||
|
||||
if (_securityImage) {
|
||||
delete _securityImage;
|
||||
}
|
||||
|
@ -754,7 +766,7 @@ QString Wallet::getKeyFilePath() {
|
|||
}
|
||||
|
||||
bool Wallet::writeWallet(const QString& newPassphrase) {
|
||||
EC_KEY* keys = readKeys(keyFilePath().toStdString().c_str());
|
||||
EC_KEY* keys = readKeys(keyFilePath());
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
// Remove any existing locker, because it will be out of date.
|
||||
if (!_publicKeys.isEmpty() && !ledger->receiveAt(_publicKeys.first(), _publicKeys.first(), QByteArray())) {
|
||||
|
@ -768,7 +780,7 @@ bool Wallet::writeWallet(const QString& newPassphrase) {
|
|||
setPassphrase(newPassphrase);
|
||||
}
|
||||
|
||||
if (writeKeys(tempFileName.toStdString().c_str(), keys)) {
|
||||
if (writeKeys(tempFileName, keys)) {
|
||||
if (writeSecurityImage(_securityImage, tempFileName)) {
|
||||
// ok, now move the temp file to the correct spot
|
||||
QFile(QString(keyFilePath())).remove();
|
||||
|
@ -834,10 +846,10 @@ void Wallet::handleChallengeOwnershipPacket(QSharedPointer<ReceivedMessage> pack
|
|||
challengingNodeUUID = packet->read(challengingNodeUUIDByteArraySize);
|
||||
}
|
||||
|
||||
EC_KEY* ec = readKeys(keyFilePath().toStdString().c_str());
|
||||
EC_KEY* ec = readKeys(keyFilePath());
|
||||
QString sig;
|
||||
|
||||
if (ec) {
|
||||
if (ec) {
|
||||
ERR_clear_error();
|
||||
sig = signWithKey(text, ""); // base64 signature, QByteArray cast (on return) to QString FIXME should pass ec as string so we can tell which key to sign with
|
||||
status = 1;
|
||||
|
|
|
@ -302,8 +302,11 @@ int main(int argc, const char* argv[]) {
|
|||
PROFILE_SYNC_BEGIN(startup, "app full ctor", "");
|
||||
Application app(argcExtended, const_cast<char**>(argvExtended.data()), startupTime, runningMarkerExisted);
|
||||
PROFILE_SYNC_END(startup, "app full ctor", "");
|
||||
|
||||
|
||||
|
||||
#if defined(Q_OS_LINUX)
|
||||
app.setWindowIcon(QIcon(PathUtils::resourcesPath() + "images/hifi-logo.svg"));
|
||||
#endif
|
||||
|
||||
QTimer exitTimer;
|
||||
if (traceDuration > 0.0f) {
|
||||
exitTimer.setSingleShot(true);
|
||||
|
|
|
@ -233,16 +233,19 @@ PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const P
|
|||
|
||||
// If we just started triggering and we haven't moved too much, don't update intersection and pos2D
|
||||
TriggerState& state = hover ? _latestState : _states[button];
|
||||
float sensorToWorldScale = DependencyManager::get<AvatarManager>()->getMyAvatar()->getSensorToWorldScale();
|
||||
float deadspotSquared = TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED * sensorToWorldScale * sensorToWorldScale;
|
||||
bool withinDeadspot = usecTimestampNow() - state.triggerStartTime < POINTER_MOVE_DELAY && glm::distance2(pos2D, state.triggerPos2D) < deadspotSquared;
|
||||
if ((state.triggering || state.wasTriggering) && !state.deadspotExpired && withinDeadspot) {
|
||||
pos2D = state.triggerPos2D;
|
||||
intersection = state.intersection;
|
||||
surfaceNormal = state.surfaceNormal;
|
||||
}
|
||||
if (!withinDeadspot) {
|
||||
state.deadspotExpired = true;
|
||||
auto avatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
if (avatar) {
|
||||
float sensorToWorldScale = avatar->getSensorToWorldScale();
|
||||
float deadspotSquared = TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED * sensorToWorldScale * sensorToWorldScale;
|
||||
bool withinDeadspot = usecTimestampNow() - state.triggerStartTime < POINTER_MOVE_DELAY && glm::distance2(pos2D, state.triggerPos2D) < deadspotSquared;
|
||||
if ((state.triggering || state.wasTriggering) && !state.deadspotExpired && withinDeadspot) {
|
||||
pos2D = state.triggerPos2D;
|
||||
intersection = state.intersection;
|
||||
surfaceNormal = state.surfaceNormal;
|
||||
}
|
||||
if (!withinDeadspot) {
|
||||
state.deadspotExpired = true;
|
||||
}
|
||||
}
|
||||
|
||||
return PointerEvent(pos2D, intersection, surfaceNormal, direction);
|
||||
|
|
|
@ -156,10 +156,10 @@ void DialogsManager::hmdTools(bool showTools) {
|
|||
}
|
||||
_hmdToolsDialog->show();
|
||||
_hmdToolsDialog->raise();
|
||||
qApp->getWindow()->activateWindow();
|
||||
} else {
|
||||
hmdToolsClosed();
|
||||
}
|
||||
qApp->getWindow()->activateWindow();
|
||||
}
|
||||
|
||||
void DialogsManager::hmdToolsClosed() {
|
||||
|
@ -207,4 +207,4 @@ void DialogsManager::showDomainConnectionDialog() {
|
|||
|
||||
_domainConnectionDialog->show();
|
||||
_domainConnectionDialog->raise();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -910,6 +910,9 @@ void Keyboard::loadKeyboardFile(const QString& keyboardFile) {
|
|||
});
|
||||
_layerIndex = 0;
|
||||
addIncludeItemsToMallets();
|
||||
|
||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
scaleKeyboard(myAvatar->getSensorToWorldScale());
|
||||
});
|
||||
|
||||
request->send();
|
||||
|
|
|
@ -98,6 +98,7 @@ public:
|
|||
bool isPassword() const;
|
||||
void setPassword(bool password);
|
||||
void enableRightMallet();
|
||||
void scaleKeyboard(float sensorToWorldScale);
|
||||
void enableLeftMallet();
|
||||
void disableRightMallet();
|
||||
void disableLeftMallet();
|
||||
|
@ -122,7 +123,6 @@ public slots:
|
|||
void handleTriggerContinue(const QUuid& id, const PointerEvent& event);
|
||||
void handleHoverBegin(const QUuid& id, const PointerEvent& event);
|
||||
void handleHoverEnd(const QUuid& id, const PointerEvent& event);
|
||||
void scaleKeyboard(float sensorToWorldScale);
|
||||
|
||||
private:
|
||||
struct Anchor {
|
||||
|
|
|
@ -266,6 +266,11 @@ void setupPreferences() {
|
|||
auto preference = new CheckPreference(VR_MOVEMENT, "Walking", getter, setter);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
{
|
||||
auto getter = [myAvatar]()->bool { return myAvatar->getStrafeEnabled(); };
|
||||
auto setter = [myAvatar](bool value) { myAvatar->setStrafeEnabled(value); };
|
||||
preferences->addPreference(new CheckPreference(VR_MOVEMENT, "Strafing", getter, setter));
|
||||
}
|
||||
{
|
||||
auto getter = [myAvatar]()->bool { return myAvatar->getFlyingHMDPref(); };
|
||||
auto setter = [myAvatar](bool value) { myAvatar->setFlyingHMDPref(value); };
|
||||
|
@ -273,6 +278,22 @@ void setupPreferences() {
|
|||
preference->setIndented(true);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
{
|
||||
auto getter = [myAvatar]()->int { return myAvatar->getMovementReference(); };
|
||||
auto setter = [myAvatar](int value) { myAvatar->setMovementReference(value); };
|
||||
//auto preference = new CheckPreference(VR_MOVEMENT, "Hand-Relative Movement", getter, setter);
|
||||
auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Movement Direction", getter, setter);
|
||||
QStringList items;
|
||||
items << "HMD-Relative" << "Hand-Relative" << "Hand-Relative (Leveled)";
|
||||
preference->setHeading("Movement Direction");
|
||||
preference->setItems(items);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
{
|
||||
auto getter = [myAvatar]()->QString { return myAvatar->getDominantHand(); };
|
||||
auto setter = [myAvatar](const QString& value) { myAvatar->setDominantHand(value); };
|
||||
preferences->addPreference(new PrimaryHandPreference(VR_MOVEMENT, "Dominant Hand", getter, setter));
|
||||
}
|
||||
{
|
||||
auto getter = [myAvatar]()->int { return myAvatar->getSnapTurn() ? 0 : 1; };
|
||||
auto setter = [myAvatar](int value) { myAvatar->setSnapTurn(value == 0); };
|
||||
|
@ -283,6 +304,26 @@ void setupPreferences() {
|
|||
preference->setItems(items);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
{
|
||||
auto getter = [myAvatar]()->int { return myAvatar->getControlScheme(); };
|
||||
auto setter = [myAvatar](int index) { myAvatar->setControlScheme(index); };
|
||||
auto preference = new RadioButtonsPreference(VR_MOVEMENT, "Control Scheme", getter, setter);
|
||||
QStringList items;
|
||||
items << "Default" << "Analog" << "Analog++";
|
||||
preference->setHeading("Control Scheme Selection");
|
||||
preference->setItems(items);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
{
|
||||
auto getter = [myAvatar]()->float { return myAvatar->getAnalogPlusWalkSpeed(); };
|
||||
auto setter = [myAvatar](float value) { myAvatar->setAnalogPlusWalkSpeed(value); };
|
||||
auto preference = new SpinnerSliderPreference(VR_MOVEMENT, "Analog++ Walk Speed", getter, setter);
|
||||
preference->setMin(6.0f);
|
||||
preference->setMax(30.0f);
|
||||
preference->setStep(1);
|
||||
preference->setDecimals(2);
|
||||
preferences->addPreference(preference);
|
||||
}
|
||||
{
|
||||
auto getter = [myAvatar]()->bool { return myAvatar->getShowPlayArea(); };
|
||||
auto setter = [myAvatar](bool value) { myAvatar->setShowPlayArea(value); };
|
||||
|
|
|
@ -128,7 +128,7 @@ const AnimPoseVec& AnimTwoBoneIK::evaluate(const AnimVariantMap& animVars, const
|
|||
|
||||
if (triggersOut.hasKey(endEffectorPositionVar)) {
|
||||
targetPose.trans() = triggersOut.lookupRigToGeometry(endEffectorPositionVar, tipPose.trans());
|
||||
} else if (animVars.hasKey(endEffectorRotationVar)) {
|
||||
} else if (animVars.hasKey(endEffectorPositionVar)) {
|
||||
targetPose.trans() = animVars.lookupRigToGeometry(endEffectorPositionVar, tipPose.trans());
|
||||
}
|
||||
|
||||
|
@ -147,9 +147,11 @@ const AnimPoseVec& AnimTwoBoneIK::evaluate(const AnimVariantMap& animVars, const
|
|||
|
||||
// http://mathworld.wolfram.com/Circle-CircleIntersection.html
|
||||
float midAngle = 0.0f;
|
||||
if (d < r0 + r1) {
|
||||
if ((d < r0 + r1) && (d > 0.0f) && (r0 > 0.0f) && (r1 > 0.0f)) {
|
||||
float y = sqrtf((-d + r1 - r0) * (-d - r1 + r0) * (-d + r1 + r0) * (d + r1 + r0)) / (2.0f * d);
|
||||
midAngle = PI - (acosf(y / r0) + acosf(y / r1));
|
||||
float yR0Quotient = glm::clamp(y / r0, -1.0f, 1.0f);
|
||||
float yR1Quotient = glm::clamp(y / r1, -1.0f, 1.0f);
|
||||
midAngle = PI - (acosf(yR0Quotient) + acosf(yR1Quotient));
|
||||
}
|
||||
|
||||
// compute midJoint rotation
|
||||
|
|
|
@ -142,3 +142,72 @@ glm::quat computeBodyFacingFromHead(const glm::quat& headRot, const glm::vec3& u
|
|||
|
||||
return glmExtractRotation(bodyMat);
|
||||
}
|
||||
|
||||
|
||||
const float INV_SQRT_3 = 1.0f / sqrtf(3.0f);
|
||||
const int DOP14_COUNT = 14;
|
||||
const glm::vec3 DOP14_NORMALS[DOP14_COUNT] = {
|
||||
Vectors::UNIT_X,
|
||||
-Vectors::UNIT_X,
|
||||
Vectors::UNIT_Y,
|
||||
-Vectors::UNIT_Y,
|
||||
Vectors::UNIT_Z,
|
||||
-Vectors::UNIT_Z,
|
||||
glm::vec3(INV_SQRT_3, INV_SQRT_3, INV_SQRT_3),
|
||||
-glm::vec3(INV_SQRT_3, INV_SQRT_3, INV_SQRT_3),
|
||||
glm::vec3(INV_SQRT_3, -INV_SQRT_3, INV_SQRT_3),
|
||||
-glm::vec3(INV_SQRT_3, -INV_SQRT_3, INV_SQRT_3),
|
||||
glm::vec3(INV_SQRT_3, INV_SQRT_3, -INV_SQRT_3),
|
||||
-glm::vec3(INV_SQRT_3, INV_SQRT_3, -INV_SQRT_3),
|
||||
glm::vec3(INV_SQRT_3, -INV_SQRT_3, -INV_SQRT_3),
|
||||
-glm::vec3(INV_SQRT_3, -INV_SQRT_3, -INV_SQRT_3)
|
||||
};
|
||||
|
||||
// returns true if the given point lies inside of the k-dop, specified by shapeInfo & shapePose.
|
||||
// if the given point does lie within the k-dop, it also returns the amount of displacement necessary to push that point outward
|
||||
// such that it lies on the surface of the kdop.
|
||||
bool findPointKDopDisplacement(const glm::vec3& point, const AnimPose& shapePose, const HFMJointShapeInfo& shapeInfo, glm::vec3& displacementOut) {
|
||||
|
||||
// transform point into local space of jointShape.
|
||||
glm::vec3 localPoint = shapePose.inverse().xformPoint(point);
|
||||
|
||||
// Only works for 14-dop shape infos.
|
||||
if (shapeInfo.dots.size() != DOP14_COUNT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
glm::vec3 minDisplacement(FLT_MAX);
|
||||
float minDisplacementLen = FLT_MAX;
|
||||
glm::vec3 p = localPoint - shapeInfo.avgPoint;
|
||||
float pLen = glm::length(p);
|
||||
if (pLen > 0.0f) {
|
||||
int slabCount = 0;
|
||||
for (int i = 0; i < DOP14_COUNT; i++) {
|
||||
float dot = glm::dot(p, DOP14_NORMALS[i]);
|
||||
if (dot > 0.0f && dot < shapeInfo.dots[i]) {
|
||||
slabCount++;
|
||||
float distToPlane = pLen * (shapeInfo.dots[i] / dot);
|
||||
float displacementLen = distToPlane - pLen;
|
||||
|
||||
// keep track of the smallest displacement
|
||||
if (displacementLen < minDisplacementLen) {
|
||||
minDisplacementLen = displacementLen;
|
||||
minDisplacement = (p / pLen) * displacementLen;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (slabCount == (DOP14_COUNT / 2) && minDisplacementLen != FLT_MAX) {
|
||||
// we are within the k-dop so push the point along the minimum displacement found
|
||||
displacementOut = shapePose.xformVectorFast(minDisplacement);
|
||||
return true;
|
||||
} else {
|
||||
// point is outside of kdop
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// point is directly on top of shapeInfo.avgPoint.
|
||||
// push the point out along the x axis.
|
||||
displacementOut = shapePose.xformVectorFast(shapeInfo.points[0]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -128,4 +128,10 @@ protected:
|
|||
bool _snapshotValid { false };
|
||||
};
|
||||
|
||||
|
||||
// returns true if the given point lies inside of the k-dop, specified by shapeInfo & shapePose.
|
||||
// if the given point does lie within the k-dop, it also returns the amount of displacement necessary to push that point outward
|
||||
// such that it lies on the surface of the kdop.
|
||||
bool findPointKDopDisplacement(const glm::vec3& point, const AnimPose& shapePose, const HFMJointShapeInfo& shapeInfo, glm::vec3& displacementOut);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -45,18 +45,6 @@ private:
|
|||
|
||||
Q_DECLARE_METATYPE(AnimationPointer)
|
||||
|
||||
/**jsdoc
|
||||
* @class AnimationObject
|
||||
*
|
||||
* @hifi-interface
|
||||
* @hifi-client-entity
|
||||
* @hifi-avatar
|
||||
* @hifi-server-entity
|
||||
* @hifi-assignment-client
|
||||
*
|
||||
* @property {string[]} jointNames
|
||||
* @property {FBXAnimationFrame[]} frames
|
||||
*/
|
||||
/// An animation loaded from the network.
|
||||
class Animation : public Resource {
|
||||
Q_OBJECT
|
||||
|
@ -72,16 +60,8 @@ public:
|
|||
|
||||
virtual bool isLoaded() const override;
|
||||
|
||||
/**jsdoc
|
||||
* @function AnimationObject.getJointNames
|
||||
* @returns {string[]}
|
||||
*/
|
||||
Q_INVOKABLE QStringList getJointNames() const;
|
||||
|
||||
/**jsdoc
|
||||
* @function AnimationObject.getFrames
|
||||
* @returns {FBXAnimationFrame[]}
|
||||
*/
|
||||
Q_INVOKABLE QVector<HFMAnimationFrame> getFrames() const;
|
||||
|
||||
const QVector<HFMAnimationFrame>& getFramesReference() const;
|
||||
|
|
|
@ -25,7 +25,8 @@ class AnimationCacheScriptingInterface : public ScriptableResourceCache, public
|
|||
// Properties are copied over from ResourceCache (see ResourceCache.h for reason).
|
||||
|
||||
/**jsdoc
|
||||
* API to manage animation cache resources.
|
||||
* The <code>AnimationCache</code> API manages animation cache resources.
|
||||
*
|
||||
* @namespace AnimationCache
|
||||
*
|
||||
* @hifi-interface
|
||||
|
@ -48,10 +49,10 @@ public:
|
|||
AnimationCacheScriptingInterface();
|
||||
|
||||
/**jsdoc
|
||||
* Returns animation resource for particular animation.
|
||||
* Gets information about an animation resource.
|
||||
* @function AnimationCache.getAnimation
|
||||
* @param {string} url - URL to load.
|
||||
* @returns {AnimationObject} animation
|
||||
* @param {string} url - The URL of the animation.
|
||||
* @returns {AnimationObject} An animation object.
|
||||
*/
|
||||
Q_INVOKABLE AnimationPointer getAnimation(const QString& url);
|
||||
};
|
||||
|
|
|
@ -19,6 +19,20 @@
|
|||
|
||||
class QScriptEngine;
|
||||
|
||||
/**jsdoc
|
||||
* Information about an animation resource, created by {@link AnimationCache.getAnimation}.
|
||||
*
|
||||
* @class AnimationObject
|
||||
*
|
||||
* @hifi-interface
|
||||
* @hifi-client-entity
|
||||
* @hifi-avatar
|
||||
* @hifi-server-entity
|
||||
* @hifi-assignment-client
|
||||
*
|
||||
* @property {string[]} jointNames - The names of the joints that are animated. <em>Read-only.</em>
|
||||
* @property {AnimationFrameObject[]} frames - The frames in the animation. <em>Read-only.</em>
|
||||
*/
|
||||
/// Scriptable wrapper for animation pointers.
|
||||
class AnimationObject : public QObject, protected QScriptable {
|
||||
Q_OBJECT
|
||||
|
@ -27,11 +41,34 @@ class AnimationObject : public QObject, protected QScriptable {
|
|||
|
||||
public:
|
||||
|
||||
/**jsdoc
|
||||
* Gets the names of the joints that are animated.
|
||||
* @function AnimationObject.getJointNames
|
||||
* @returns {string[]} The names of the joints that are animated.
|
||||
*/
|
||||
Q_INVOKABLE QStringList getJointNames() const;
|
||||
|
||||
/**jsdoc
|
||||
* Gets the frames in the animation.
|
||||
* @function AnimationObject.getFrames
|
||||
* @returns {AnimationFrameObject[]} The frames in the animation.
|
||||
*/
|
||||
Q_INVOKABLE QVector<HFMAnimationFrame> getFrames() const;
|
||||
};
|
||||
|
||||
/**jsdoc
|
||||
* Joint rotations in one frame of an animation.
|
||||
*
|
||||
* @class AnimationFrameObject
|
||||
*
|
||||
* @hifi-interface
|
||||
* @hifi-client-entity
|
||||
* @hifi-avatar
|
||||
* @hifi-server-entity
|
||||
* @hifi-assignment-client
|
||||
*
|
||||
* @property {Quat[]} rotations - Joint rotations. <em>Read-only.</em>
|
||||
*/
|
||||
/// Scriptable wrapper for animation frames.
|
||||
class AnimationFrameObject : public QObject, protected QScriptable {
|
||||
Q_OBJECT
|
||||
|
@ -39,6 +76,11 @@ class AnimationFrameObject : public QObject, protected QScriptable {
|
|||
|
||||
public:
|
||||
|
||||
/**jsdoc
|
||||
* Gets the joint rotations in the animation frame.
|
||||
* @function AnimationFrameObject.getRotations
|
||||
* @returns {Quat[]} The joint rotations in the animation frame.
|
||||
*/
|
||||
Q_INVOKABLE QVector<glm::quat> getRotations() const;
|
||||
};
|
||||
|
||||
|
|
|
@ -499,12 +499,12 @@ void Flow::calculateConstraints(const std::shared_ptr<AnimSkeleton>& skeleton,
|
|||
bool toFloatSuccess;
|
||||
QStringRef(&name, (int)(name.size() - j), 1).toString().toFloat(&toFloatSuccess);
|
||||
if (!toFloatSuccess && (name.size() - j) > (int)simPrefix.size()) {
|
||||
group = QStringRef(&name, (int)simPrefix.size(), (int)(name.size() - j + 1)).toString();
|
||||
group = QStringRef(&name, (int)simPrefix.size(), (int)(name.size() - j + 1) - (int)simPrefix.size()).toString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (group.isEmpty()) {
|
||||
group = QStringRef(&name, (int)simPrefix.size(), name.size() - 1).toString();
|
||||
group = QStringRef(&name, (int)simPrefix.size(), name.size() - (int)simPrefix.size()).toString();
|
||||
}
|
||||
qCDebug(animation) << "Sim joint added to flow: " << name;
|
||||
} else {
|
||||
|
|
|
@ -370,6 +370,88 @@ void Rig::restoreAnimation() {
|
|||
}
|
||||
}
|
||||
|
||||
void Rig::overrideHandAnimation(bool isLeft, const QString& url, float fps, bool loop, float firstFrame, float lastFrame) {
|
||||
HandAnimState::ClipNodeEnum clipNodeEnum;
|
||||
if (isLeft) {
|
||||
if (_leftHandAnimState.clipNodeEnum == HandAnimState::None || _leftHandAnimState.clipNodeEnum == HandAnimState::B) {
|
||||
clipNodeEnum = HandAnimState::A;
|
||||
} else {
|
||||
clipNodeEnum = HandAnimState::B;
|
||||
}
|
||||
} else {
|
||||
if (_rightHandAnimState.clipNodeEnum == HandAnimState::None || _rightHandAnimState.clipNodeEnum == HandAnimState::B) {
|
||||
clipNodeEnum = HandAnimState::A;
|
||||
} else {
|
||||
clipNodeEnum = HandAnimState::B;
|
||||
}
|
||||
}
|
||||
|
||||
if (_animNode) {
|
||||
std::shared_ptr<AnimClip> clip;
|
||||
if (isLeft) {
|
||||
if (clipNodeEnum == HandAnimState::A) {
|
||||
clip = std::dynamic_pointer_cast<AnimClip>(_animNode->findByName("leftHandAnimA"));
|
||||
} else {
|
||||
clip = std::dynamic_pointer_cast<AnimClip>(_animNode->findByName("leftHandAnimB"));
|
||||
}
|
||||
} else {
|
||||
if (clipNodeEnum == HandAnimState::A) {
|
||||
clip = std::dynamic_pointer_cast<AnimClip>(_animNode->findByName("rightHandAnimA"));
|
||||
} else {
|
||||
clip = std::dynamic_pointer_cast<AnimClip>(_animNode->findByName("rightHandAnimB"));
|
||||
}
|
||||
}
|
||||
|
||||
if (clip) {
|
||||
// set parameters
|
||||
clip->setLoopFlag(loop);
|
||||
clip->setStartFrame(firstFrame);
|
||||
clip->setEndFrame(lastFrame);
|
||||
const float REFERENCE_FRAMES_PER_SECOND = 30.0f;
|
||||
float timeScale = fps / REFERENCE_FRAMES_PER_SECOND;
|
||||
clip->setTimeScale(timeScale);
|
||||
clip->loadURL(url);
|
||||
}
|
||||
}
|
||||
|
||||
// notify the handAnimStateMachine the desired state.
|
||||
if (isLeft) {
|
||||
// store current hand anim state.
|
||||
_leftHandAnimState = { clipNodeEnum, url, fps, loop, firstFrame, lastFrame };
|
||||
_animVars.set("leftHandAnimNone", false);
|
||||
_animVars.set("leftHandAnimA", clipNodeEnum == HandAnimState::A);
|
||||
_animVars.set("leftHandAnimB", clipNodeEnum == HandAnimState::B);
|
||||
} else {
|
||||
// store current hand anim state.
|
||||
_rightHandAnimState = { clipNodeEnum, url, fps, loop, firstFrame, lastFrame };
|
||||
_animVars.set("rightHandAnimNone", false);
|
||||
_animVars.set("rightHandAnimA", clipNodeEnum == HandAnimState::A);
|
||||
_animVars.set("rightHandAnimB", clipNodeEnum == HandAnimState::B);
|
||||
}
|
||||
}
|
||||
|
||||
void Rig::restoreHandAnimation(bool isLeft) {
|
||||
if (isLeft) {
|
||||
if (_leftHandAnimState.clipNodeEnum != HandAnimState::None) {
|
||||
_leftHandAnimState.clipNodeEnum = HandAnimState::None;
|
||||
|
||||
// notify the handAnimStateMachine the desired state.
|
||||
_animVars.set("leftHandAnimNone", true);
|
||||
_animVars.set("leftHandAnimA", false);
|
||||
_animVars.set("leftHandAnimB", false);
|
||||
}
|
||||
} else {
|
||||
if (_rightHandAnimState.clipNodeEnum != HandAnimState::None) {
|
||||
_rightHandAnimState.clipNodeEnum = HandAnimState::None;
|
||||
|
||||
// notify the handAnimStateMachine the desired state.
|
||||
_animVars.set("rightHandAnimNone", true);
|
||||
_animVars.set("rightHandAnimA", false);
|
||||
_animVars.set("rightHandAnimB", false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Rig::overrideNetworkAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame) {
|
||||
|
||||
NetworkAnimState::ClipNodeEnum clipNodeEnum = NetworkAnimState::None;
|
||||
|
@ -1521,74 +1603,6 @@ void Rig::updateHead(bool headEnabled, bool hipsEnabled, const AnimPose& headPos
|
|||
}
|
||||
}
|
||||
|
||||
const float INV_SQRT_3 = 1.0f / sqrtf(3.0f);
|
||||
const int DOP14_COUNT = 14;
|
||||
const glm::vec3 DOP14_NORMALS[DOP14_COUNT] = {
|
||||
Vectors::UNIT_X,
|
||||
-Vectors::UNIT_X,
|
||||
Vectors::UNIT_Y,
|
||||
-Vectors::UNIT_Y,
|
||||
Vectors::UNIT_Z,
|
||||
-Vectors::UNIT_Z,
|
||||
glm::vec3(INV_SQRT_3, INV_SQRT_3, INV_SQRT_3),
|
||||
-glm::vec3(INV_SQRT_3, INV_SQRT_3, INV_SQRT_3),
|
||||
glm::vec3(INV_SQRT_3, -INV_SQRT_3, INV_SQRT_3),
|
||||
-glm::vec3(INV_SQRT_3, -INV_SQRT_3, INV_SQRT_3),
|
||||
glm::vec3(INV_SQRT_3, INV_SQRT_3, -INV_SQRT_3),
|
||||
-glm::vec3(INV_SQRT_3, INV_SQRT_3, -INV_SQRT_3),
|
||||
glm::vec3(INV_SQRT_3, -INV_SQRT_3, -INV_SQRT_3),
|
||||
-glm::vec3(INV_SQRT_3, -INV_SQRT_3, -INV_SQRT_3)
|
||||
};
|
||||
|
||||
// returns true if the given point lies inside of the k-dop, specified by shapeInfo & shapePose.
|
||||
// if the given point does lie within the k-dop, it also returns the amount of displacement necessary to push that point outward
|
||||
// such that it lies on the surface of the kdop.
|
||||
static bool findPointKDopDisplacement(const glm::vec3& point, const AnimPose& shapePose, const HFMJointShapeInfo& shapeInfo, glm::vec3& displacementOut) {
|
||||
|
||||
// transform point into local space of jointShape.
|
||||
glm::vec3 localPoint = shapePose.inverse().xformPoint(point);
|
||||
|
||||
// Only works for 14-dop shape infos.
|
||||
if (shapeInfo.dots.size() != DOP14_COUNT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
glm::vec3 minDisplacement(FLT_MAX);
|
||||
float minDisplacementLen = FLT_MAX;
|
||||
glm::vec3 p = localPoint - shapeInfo.avgPoint;
|
||||
float pLen = glm::length(p);
|
||||
if (pLen > 0.0f) {
|
||||
int slabCount = 0;
|
||||
for (int i = 0; i < DOP14_COUNT; i++) {
|
||||
float dot = glm::dot(p, DOP14_NORMALS[i]);
|
||||
if (dot > 0.0f && dot < shapeInfo.dots[i]) {
|
||||
slabCount++;
|
||||
float distToPlane = pLen * (shapeInfo.dots[i] / dot);
|
||||
float displacementLen = distToPlane - pLen;
|
||||
|
||||
// keep track of the smallest displacement
|
||||
if (displacementLen < minDisplacementLen) {
|
||||
minDisplacementLen = displacementLen;
|
||||
minDisplacement = (p / pLen) * displacementLen;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (slabCount == (DOP14_COUNT / 2) && minDisplacementLen != FLT_MAX) {
|
||||
// we are within the k-dop so push the point along the minimum displacement found
|
||||
displacementOut = shapePose.xformVectorFast(minDisplacement);
|
||||
return true;
|
||||
} else {
|
||||
// point is outside of kdop
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
// point is directly on top of shapeInfo.avgPoint.
|
||||
// push the point out along the x axis.
|
||||
displacementOut = shapePose.xformVectorFast(shapeInfo.points[0]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec3 Rig::deflectHandFromTorso(const glm::vec3& handPosition, const HFMJointShapeInfo& hipsShapeInfo, const HFMJointShapeInfo& spineShapeInfo,
|
||||
const HFMJointShapeInfo& spine1ShapeInfo, const HFMJointShapeInfo& spine2ShapeInfo) const {
|
||||
glm::vec3 position = handPosition;
|
||||
|
@ -2136,6 +2150,20 @@ void Rig::initAnimGraph(const QUrl& url) {
|
|||
overrideAnimation(origState.url, origState.fps, origState.loop, origState.firstFrame, origState.lastFrame);
|
||||
}
|
||||
|
||||
if (_rightHandAnimState.clipNodeEnum != HandAnimState::None) {
|
||||
// restore the right hand animation we had before reset.
|
||||
HandAnimState origState = _rightHandAnimState;
|
||||
_rightHandAnimState = { HandAnimState::None, "", 30.0f, false, 0.0f, 0.0f };
|
||||
overrideHandAnimation(false, origState.url, origState.fps, origState.loop, origState.firstFrame, origState.lastFrame);
|
||||
}
|
||||
|
||||
if (_leftHandAnimState.clipNodeEnum != HandAnimState::None) {
|
||||
// restore the left hand animation we had before reset.
|
||||
HandAnimState origState = _leftHandAnimState;
|
||||
_leftHandAnimState = { HandAnimState::None, "", 30.0f, false, 0.0f, 0.0f };
|
||||
overrideHandAnimation(true, origState.url, origState.fps, origState.loop, origState.firstFrame, origState.lastFrame);
|
||||
}
|
||||
|
||||
// restore the role animations we had before reset.
|
||||
for (auto& roleAnimState : _roleAnimStates) {
|
||||
auto roleState = roleAnimState.second;
|
||||
|
|
|
@ -116,8 +116,12 @@ public:
|
|||
void destroyAnimGraph();
|
||||
|
||||
void overrideAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame);
|
||||
bool isPlayingOverrideAnimation() const { return _userAnimState.clipNodeEnum != UserAnimState::None; };
|
||||
void restoreAnimation();
|
||||
|
||||
void overrideHandAnimation(bool isLeft, const QString& url, float fps, bool loop, float firstFrame, float lastFrame);
|
||||
void restoreHandAnimation(bool isLeft);
|
||||
|
||||
void overrideNetworkAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame);
|
||||
void triggerNetworkRole(const QString& role);
|
||||
void restoreNetworkAnimation();
|
||||
|
@ -333,7 +337,7 @@ protected:
|
|||
RigRole _state { RigRole::Idle };
|
||||
RigRole _desiredState { RigRole::Idle };
|
||||
float _desiredStateAge { 0.0f };
|
||||
|
||||
|
||||
struct NetworkAnimState {
|
||||
enum ClipNodeEnum {
|
||||
None = 0,
|
||||
|
@ -356,6 +360,27 @@ protected:
|
|||
float blendTime;
|
||||
};
|
||||
|
||||
struct HandAnimState {
|
||||
enum ClipNodeEnum {
|
||||
None = 0,
|
||||
A,
|
||||
B
|
||||
};
|
||||
|
||||
HandAnimState() : clipNodeEnum(HandAnimState::None) {}
|
||||
HandAnimState(ClipNodeEnum clipNodeEnumIn, const QString& urlIn, float fpsIn, bool loopIn, float firstFrameIn, float lastFrameIn) :
|
||||
clipNodeEnum(clipNodeEnumIn), url(urlIn), fps(fpsIn), loop(loopIn), firstFrame(firstFrameIn), lastFrame(lastFrameIn) {
|
||||
}
|
||||
|
||||
|
||||
ClipNodeEnum clipNodeEnum;
|
||||
QString url;
|
||||
float fps;
|
||||
bool loop;
|
||||
float firstFrame;
|
||||
float lastFrame;
|
||||
};
|
||||
|
||||
struct UserAnimState {
|
||||
enum ClipNodeEnum {
|
||||
None = 0,
|
||||
|
@ -390,6 +415,8 @@ protected:
|
|||
|
||||
UserAnimState _userAnimState;
|
||||
NetworkAnimState _networkAnimState;
|
||||
HandAnimState _rightHandAnimState;
|
||||
HandAnimState _leftHandAnimState;
|
||||
std::map<QString, RoleAnimState> _roleAnimStates;
|
||||
|
||||
float _leftHandOverlayAlpha { 0.0f };
|
||||
|
|
|
@ -1397,7 +1397,6 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
|
|||
// spatialize into mixBuffer
|
||||
injector->getLocalFOA().render(_localScratchBuffer, mixBuffer, HRTF_DATASET_INDEX,
|
||||
qw, qx, qy, qz, gain, AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||
|
||||
} else if (options.stereo) {
|
||||
|
||||
if (options.positionSet) {
|
||||
|
@ -1409,11 +1408,8 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
|
|||
}
|
||||
|
||||
// direct mix into mixBuffer
|
||||
for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; i++) {
|
||||
mixBuffer[2*i+0] += convertToFloat(_localScratchBuffer[2*i+0]) * gain;
|
||||
mixBuffer[2*i+1] += convertToFloat(_localScratchBuffer[2*i+1]) * gain;
|
||||
}
|
||||
|
||||
injector->getLocalHRTF().mixStereo(_localScratchBuffer, mixBuffer, gain,
|
||||
AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||
} else { // injector is mono
|
||||
|
||||
if (options.positionSet) {
|
||||
|
@ -1431,11 +1427,8 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) {
|
|||
} else {
|
||||
|
||||
// direct mix into mixBuffer
|
||||
for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; i++) {
|
||||
float sample = convertToFloat(_localScratchBuffer[i]) * gain;
|
||||
mixBuffer[2*i+0] += sample;
|
||||
mixBuffer[2*i+1] += sample;
|
||||
}
|
||||
injector->getLocalHRTF().mixMono(_localScratchBuffer, mixBuffer, gain,
|
||||
AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -882,14 +882,16 @@ static void convertInput_ref(int16_t* src, float *dst[4], float gain, int numFra
|
|||
|
||||
#endif
|
||||
|
||||
// in-place rotation of the soundfield
|
||||
// crossfade between old and new rotation, to prevent artifacts
|
||||
static void rotate_3x3_ref(float* buf[4], const float m0[3][3], const float m1[3][3], const float* win, int numFrames) {
|
||||
// in-place rotation and scaling of the soundfield
|
||||
// crossfade between old and new matrix, to prevent artifacts
|
||||
static void rotate_4x4_ref(float* buf[4], const float m0[4][4], const float m1[4][4], const float* win, int numFrames) {
|
||||
|
||||
const float md[3][3] = {
|
||||
{ m0[0][0] - m1[0][0], m0[0][1] - m1[0][1], m0[0][2] - m1[0][2] },
|
||||
{ m0[1][0] - m1[1][0], m0[1][1] - m1[1][1], m0[1][2] - m1[1][2] },
|
||||
{ m0[2][0] - m1[2][0], m0[2][1] - m1[2][1], m0[2][2] - m1[2][2] },
|
||||
// matrix difference
|
||||
const float md[4][4] = {
|
||||
{ m0[0][0] - m1[0][0], m0[0][1] - m1[0][1], m0[0][2] - m1[0][2], m0[0][3] - m1[0][3] },
|
||||
{ m0[1][0] - m1[1][0], m0[1][1] - m1[1][1], m0[1][2] - m1[1][2], m0[1][3] - m1[1][3] },
|
||||
{ m0[2][0] - m1[2][0], m0[2][1] - m1[2][1], m0[2][2] - m1[2][2], m0[2][3] - m1[2][3] },
|
||||
{ m0[3][0] - m1[3][0], m0[3][1] - m1[3][1], m0[3][2] - m1[3][2], m0[3][3] - m1[3][3] },
|
||||
};
|
||||
|
||||
for (int i = 0; i < numFrames; i++) {
|
||||
|
@ -898,22 +900,27 @@ static void rotate_3x3_ref(float* buf[4], const float m0[3][3], const float m1[3
|
|||
|
||||
// interpolate the matrix
|
||||
float m00 = m1[0][0] + frac * md[0][0];
|
||||
float m10 = m1[1][0] + frac * md[1][0];
|
||||
float m20 = m1[2][0] + frac * md[2][0];
|
||||
|
||||
float m01 = m1[0][1] + frac * md[0][1];
|
||||
float m11 = m1[1][1] + frac * md[1][1];
|
||||
float m21 = m1[2][1] + frac * md[2][1];
|
||||
float m31 = m1[3][1] + frac * md[3][1];
|
||||
|
||||
float m02 = m1[0][2] + frac * md[0][2];
|
||||
float m12 = m1[1][2] + frac * md[1][2];
|
||||
float m22 = m1[2][2] + frac * md[2][2];
|
||||
float m32 = m1[3][2] + frac * md[3][2];
|
||||
|
||||
float m13 = m1[1][3] + frac * md[1][3];
|
||||
float m23 = m1[2][3] + frac * md[2][3];
|
||||
float m33 = m1[3][3] + frac * md[3][3];
|
||||
|
||||
// matrix multiply
|
||||
float x = m00 * buf[1][i] + m01 * buf[2][i] + m02 * buf[3][i];
|
||||
float y = m10 * buf[1][i] + m11 * buf[2][i] + m12 * buf[3][i];
|
||||
float z = m20 * buf[1][i] + m21 * buf[2][i] + m22 * buf[3][i];
|
||||
float w = m00 * buf[0][i];
|
||||
|
||||
float x = m11 * buf[1][i] + m12 * buf[2][i] + m13 * buf[3][i];
|
||||
float y = m21 * buf[1][i] + m22 * buf[2][i] + m23 * buf[3][i];
|
||||
float z = m31 * buf[1][i] + m32 * buf[2][i] + m33 * buf[3][i];
|
||||
|
||||
buf[0][i] = w;
|
||||
buf[1][i] = x;
|
||||
buf[2][i] = y;
|
||||
buf[3][i] = z;
|
||||
|
@ -932,7 +939,7 @@ void rfft512_AVX2(float buf[512]);
|
|||
void rifft512_AVX2(float buf[512]);
|
||||
void rfft512_cmadd_1X2_AVX2(const float src[512], const float coef0[512], const float coef1[512], float dst0[512], float dst1[512]);
|
||||
void convertInput_AVX2(int16_t* src, float *dst[4], float gain, int numFrames);
|
||||
void rotate_3x3_AVX2(float* buf[4], const float m0[3][3], const float m1[3][3], const float* win, int numFrames);
|
||||
void rotate_4x4_AVX2(float* buf[4], const float m0[4][4], const float m1[4][4], const float* win, int numFrames);
|
||||
|
||||
static void rfft512(float buf[512]) {
|
||||
static auto f = cpuSupportsAVX2() ? rfft512_AVX2 : rfft512_ref;
|
||||
|
@ -954,8 +961,8 @@ static void convertInput(int16_t* src, float *dst[4], float gain, int numFrames)
|
|||
(*f)(src, dst, gain, numFrames); // dispatch
|
||||
}
|
||||
|
||||
static void rotate_3x3(float* buf[4], const float m0[3][3], const float m1[3][3], const float* win, int numFrames) {
|
||||
static auto f = cpuSupportsAVX2() ? rotate_3x3_AVX2 : rotate_3x3_ref;
|
||||
static void rotate_4x4(float* buf[4], const float m0[4][4], const float m1[4][4], const float* win, int numFrames) {
|
||||
static auto f = cpuSupportsAVX2() ? rotate_4x4_AVX2 : rotate_4x4_ref;
|
||||
(*f)(buf, m0, m1, win, numFrames); // dispatch
|
||||
}
|
||||
|
||||
|
@ -965,7 +972,7 @@ static auto& rfft512 = rfft512_ref;
|
|||
static auto& rifft512 = rifft512_ref;
|
||||
static auto& rfft512_cmadd_1X2 = rfft512_cmadd_1X2_ref;
|
||||
static auto& convertInput = convertInput_ref;
|
||||
static auto& rotate_3x3 = rotate_3x3_ref;
|
||||
static auto& rotate_4x4 = rotate_4x4_ref;
|
||||
|
||||
#endif
|
||||
|
||||
|
@ -1007,8 +1014,8 @@ ALIGN32 static const float crossfadeTable[FOA_BLOCK] = {
|
|||
0.0020975362f, 0.0015413331f, 0.0010705384f, 0.0006852326f, 0.0003854819f, 0.0001713375f, 0.0000428362f, 0.0000000000f,
|
||||
};
|
||||
|
||||
// convert quaternion to a column-major 3x3 rotation matrix
|
||||
static void quatToMatrix_3x3(float w, float x, float y, float z, float m[3][3]) {
|
||||
// convert quaternion to a column-major 4x4 rotation matrix
|
||||
static void quatToMatrix_4x4(float w, float x, float y, float z, float m[4][4]) {
|
||||
|
||||
float xx = x * (x + x);
|
||||
float xy = x * (y + y);
|
||||
|
@ -1022,17 +1029,33 @@ static void quatToMatrix_3x3(float w, float x, float y, float z, float m[3][3])
|
|||
float wy = w * (y + y);
|
||||
float wz = w * (z + z);
|
||||
|
||||
m[0][0] = 1.0f - (yy + zz);
|
||||
m[0][1] = xy - wz;
|
||||
m[0][2] = xz + wy;
|
||||
m[0][0] = 1.0f;
|
||||
m[0][1] = 0.0f;
|
||||
m[0][2] = 0.0f;
|
||||
m[0][3] = 0.0f;
|
||||
|
||||
m[1][0] = xy + wz;
|
||||
m[1][1] = 1.0f - (xx + zz);
|
||||
m[1][2] = yz - wx;
|
||||
m[1][0] = 0.0f;
|
||||
m[1][1] = 1.0f - (yy + zz);
|
||||
m[1][2] = xy - wz;
|
||||
m[1][3] = xz + wy;
|
||||
|
||||
m[2][0] = xz - wy;
|
||||
m[2][1] = yz + wx;
|
||||
m[2][2] = 1.0f - (xx + yy);
|
||||
m[2][0] = 0.0f;
|
||||
m[2][1] = xy + wz;
|
||||
m[2][2] = 1.0f - (xx + zz);
|
||||
m[2][3] = yz - wx;
|
||||
|
||||
m[3][0] = 0.0f;
|
||||
m[3][1] = xz - wy;
|
||||
m[3][2] = yz + wx;
|
||||
m[3][3] = 1.0f - (xx + yy);
|
||||
}
|
||||
|
||||
static void scaleMatrix_4x4(float scale, float m[4][4]) {
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
m[i][j] *= scale;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ambisonic to binaural render
|
||||
|
@ -1047,18 +1070,26 @@ void AudioFOA::render(int16_t* input, float* output, int index, float qw, float
|
|||
ALIGN32 float inBuffer[4][FOA_BLOCK]; // deinterleaved input buffers
|
||||
|
||||
float* in[4] = { inBuffer[0], inBuffer[1], inBuffer[2], inBuffer[3] };
|
||||
float rotation[3][3];
|
||||
float rotation[4][4];
|
||||
|
||||
// convert input to deinterleaved float
|
||||
convertInput(input, in, FOA_GAIN * gain, FOA_BLOCK);
|
||||
convertInput(input, in, FOA_GAIN, FOA_BLOCK);
|
||||
|
||||
// convert quaternion to 3x3 rotation
|
||||
quatToMatrix_3x3(qw, qx, qy, qz, rotation);
|
||||
// convert quaternion to 4x4 rotation
|
||||
quatToMatrix_4x4(qw, qx, qy, qz, rotation);
|
||||
|
||||
// rotate the soundfield
|
||||
rotate_3x3(in, _rotationState, rotation, crossfadeTable, FOA_BLOCK);
|
||||
// apply gain as uniform scale
|
||||
scaleMatrix_4x4(gain, rotation);
|
||||
|
||||
// rotation history update
|
||||
// disable interpolation from reset state
|
||||
if (_resetState) {
|
||||
memcpy(_rotationState, rotation, sizeof(_rotationState));
|
||||
}
|
||||
|
||||
// rotate and scale the soundfield
|
||||
rotate_4x4(in, _rotationState, rotation, crossfadeTable, FOA_BLOCK);
|
||||
|
||||
// new parameters become old
|
||||
memcpy(_rotationState, rotation, sizeof(_rotationState));
|
||||
|
||||
//
|
||||
|
@ -1093,4 +1124,6 @@ void AudioFOA::render(int16_t* input, float* output, int index, float qw, float
|
|||
output[2*i+0] += accBuffer[0][i + FOA_OVERLAP];
|
||||
output[2*i+1] += accBuffer[1][i + FOA_OVERLAP];
|
||||
}
|
||||
|
||||
_resetState = false;
|
||||
}
|
||||
|
|
|
@ -28,12 +28,7 @@ static_assert((FOA_BLOCK + FOA_OVERLAP) == FOA_NFFT, "FFT convolution requires L
|
|||
class AudioFOA {
|
||||
|
||||
public:
|
||||
AudioFOA() {
|
||||
// identity matrix
|
||||
_rotationState[0][0] = 1.0f;
|
||||
_rotationState[1][1] = 1.0f;
|
||||
_rotationState[2][2] = 1.0f;
|
||||
};
|
||||
AudioFOA() {};
|
||||
|
||||
//
|
||||
// input: interleaved First-Order Ambisonic source
|
||||
|
@ -55,8 +50,10 @@ private:
|
|||
// input history, for overlap-save
|
||||
float _fftState[4][FOA_OVERLAP] = {};
|
||||
|
||||
// orientation history
|
||||
float _rotationState[3][3] = {};
|
||||
// orientation and gain history
|
||||
float _rotationState[4][4] = {};
|
||||
|
||||
bool _resetState = true;
|
||||
};
|
||||
|
||||
#endif // AudioFOA_h
|
||||
|
|
|
@ -750,6 +750,43 @@ static void interpolate(const float* src0, const float* src1, float* dst, float
|
|||
|
||||
#endif
|
||||
|
||||
// apply gain crossfade with accumulation (interleaved)
|
||||
static void gainfade_1x2(int16_t* src, float* dst, const float* win, float gain0, float gain1, int numFrames) {
|
||||
|
||||
gain0 *= (1/32768.0f); // int16_t to float
|
||||
gain1 *= (1/32768.0f);
|
||||
|
||||
for (int i = 0; i < numFrames; i++) {
|
||||
|
||||
float frac = win[i];
|
||||
float gain = gain1 + frac * (gain0 - gain1);
|
||||
|
||||
float x0 = (float)src[i] * gain;
|
||||
|
||||
dst[2*i+0] += x0;
|
||||
dst[2*i+1] += x0;
|
||||
}
|
||||
}
|
||||
|
||||
// apply gain crossfade with accumulation (interleaved)
|
||||
static void gainfade_2x2(int16_t* src, float* dst, const float* win, float gain0, float gain1, int numFrames) {
|
||||
|
||||
gain0 *= (1/32768.0f); // int16_t to float
|
||||
gain1 *= (1/32768.0f);
|
||||
|
||||
for (int i = 0; i < numFrames; i++) {
|
||||
|
||||
float frac = win[i];
|
||||
float gain = gain1 + frac * (gain0 - gain1);
|
||||
|
||||
float x0 = (float)src[2*i+0] * gain;
|
||||
float x1 = (float)src[2*i+1] * gain;
|
||||
|
||||
dst[2*i+0] += x0;
|
||||
dst[2*i+1] += x1;
|
||||
}
|
||||
}
|
||||
|
||||
// design a 2nd order Thiran allpass
|
||||
static void ThiranBiquad(float f, float& b0, float& b1, float& b2, float& a1, float& a2) {
|
||||
|
||||
|
@ -1104,6 +1141,13 @@ void AudioHRTF::render(int16_t* input, float* output, int index, float azimuth,
|
|||
// apply global and local gain adjustment
|
||||
gain *= _gainAdjust;
|
||||
|
||||
// disable interpolation from reset state
|
||||
if (_resetState) {
|
||||
_azimuthState = azimuth;
|
||||
_distanceState = distance;
|
||||
_gainState = gain;
|
||||
}
|
||||
|
||||
// to avoid polluting the cache, old filters are recomputed instead of stored
|
||||
setFilters(firCoef, bqCoef, delay, index, _azimuthState, _distanceState, _gainState, L0);
|
||||
|
||||
|
@ -1175,3 +1219,45 @@ void AudioHRTF::render(int16_t* input, float* output, int index, float azimuth,
|
|||
|
||||
_resetState = false;
|
||||
}
|
||||
|
||||
void AudioHRTF::mixMono(int16_t* input, float* output, float gain, int numFrames) {
|
||||
|
||||
assert(numFrames == HRTF_BLOCK);
|
||||
|
||||
// apply global and local gain adjustment
|
||||
gain *= _gainAdjust;
|
||||
|
||||
// disable interpolation from reset state
|
||||
if (_resetState) {
|
||||
_gainState = gain;
|
||||
}
|
||||
|
||||
// crossfade gain and accumulate
|
||||
gainfade_1x2(input, output, crossfadeTable, _gainState, gain, HRTF_BLOCK);
|
||||
|
||||
// new parameters become old
|
||||
_gainState = gain;
|
||||
|
||||
_resetState = false;
|
||||
}
|
||||
|
||||
void AudioHRTF::mixStereo(int16_t* input, float* output, float gain, int numFrames) {
|
||||
|
||||
assert(numFrames == HRTF_BLOCK);
|
||||
|
||||
// apply global and local gain adjustment
|
||||
gain *= _gainAdjust;
|
||||
|
||||
// disable interpolation from reset state
|
||||
if (_resetState) {
|
||||
_gainState = gain;
|
||||
}
|
||||
|
||||
// crossfade gain and accumulate
|
||||
gainfade_2x2(input, output, crossfadeTable, _gainState, gain, HRTF_BLOCK);
|
||||
|
||||
// new parameters become old
|
||||
_gainState = gain;
|
||||
|
||||
_resetState = false;
|
||||
}
|
||||
|
|
|
@ -50,6 +50,12 @@ public:
|
|||
//
|
||||
void render(int16_t* input, float* output, int index, float azimuth, float distance, float gain, int numFrames);
|
||||
|
||||
//
|
||||
// Non-spatialized direct mix (accumulates into existing output)
|
||||
//
|
||||
void mixMono(int16_t* input, float* output, float gain, int numFrames);
|
||||
void mixStereo(int16_t* input, float* output, float gain, int numFrames);
|
||||
|
||||
//
|
||||
// Fast path when input is known to be silent and state as been flushed
|
||||
//
|
||||
|
|
|
@ -103,6 +103,8 @@ void AudioInjector::finishLocalInjection() {
|
|||
|
||||
void AudioInjector::finish() {
|
||||
withWriteLock([&] {
|
||||
_state |= AudioInjectorState::LocalInjectionFinished;
|
||||
_state |= AudioInjectorState::NetworkInjectionFinished;
|
||||
_state |= AudioInjectorState::Finished;
|
||||
});
|
||||
emit finished();
|
||||
|
@ -252,7 +254,7 @@ int64_t AudioInjector::injectNextFrame() {
|
|||
writeStringToStream(noCodecForInjectors, audioPacketStream);
|
||||
|
||||
// pack stream identifier (a generated UUID)
|
||||
audioPacketStream << QUuid::createUuid();
|
||||
audioPacketStream << _streamID;
|
||||
|
||||
// pack the stereo/mono type of the stream
|
||||
audioPacketStream << options.stereo;
|
||||
|
@ -402,4 +404,17 @@ int64_t AudioInjector::injectNextFrame() {
|
|||
int64_t playNextFrameAt = ++_nextFrame * AudioConstants::NETWORK_FRAME_USECS;
|
||||
|
||||
return std::max(INT64_C(0), playNextFrameAt - currentTime);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AudioInjector::sendStopInjectorPacket() {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
if (auto audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer)) {
|
||||
// Build packet
|
||||
auto stopInjectorPacket = NLPacket::create(PacketType::StopInjector);
|
||||
stopInjectorPacket->write(_streamID.toRfc4122());
|
||||
|
||||
// Send packet
|
||||
nodeList->sendUnreliablePacket(*stopInjectorPacket, *audioMixer);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -100,6 +100,7 @@ private:
|
|||
int64_t injectNextFrame();
|
||||
bool inject(bool(AudioInjectorManager::*injection)(const AudioInjectorPointer&));
|
||||
bool injectLocally();
|
||||
void sendStopInjectorPacket();
|
||||
|
||||
static AbstractAudioInterface* _localAudioInterface;
|
||||
|
||||
|
@ -120,6 +121,9 @@ private:
|
|||
// when the injector is local, we need this
|
||||
AudioHRTF _localHRTF;
|
||||
AudioFOA _localFOA;
|
||||
|
||||
QUuid _streamID { QUuid::createUuid() };
|
||||
|
||||
friend class AudioInjectorManager;
|
||||
};
|
||||
|
||||
|
|
|
@ -105,6 +105,8 @@ void AudioInjectorManager::run() {
|
|||
if (nextCallDelta >= 0 && !injector->isFinished()) {
|
||||
// enqueue the injector with the correct timing in our holding queue
|
||||
heldInjectors.emplace(heldInjectors.end(), usecTimestampNow() + nextCallDelta, injector);
|
||||
} else {
|
||||
injector->sendStopInjectorPacket();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -354,4 +356,4 @@ void AudioInjectorManager::stop(const AudioInjectorPointer& injector) {
|
|||
size_t AudioInjectorManager::getNumInjectors() {
|
||||
Lock lock(_injectorsMutex);
|
||||
return _injectors.size();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -124,9 +124,9 @@ typedef QSharedPointer<Sound> SharedSoundPointer;
|
|||
* An audio resource, created by {@link SoundCache.getSound}, to be played back using {@link Audio.playSound}.
|
||||
* <p>Supported formats:</p>
|
||||
* <ul>
|
||||
* <li>WAV: 16-bit uncompressed at any sample rate, with 1 (mono), 2(stereo), or 4 (ambisonic) channels.</li>
|
||||
* <li>WAV: 16-bit uncompressed at any sample rate, with 1 (mono), 2 (stereo), or 4 (ambisonic) channels.</li>
|
||||
* <li>MP3: Mono or stereo, at any sample rate.</li>
|
||||
* <li>RAW: 48khz 16-bit mono or stereo. Filename must include <code>".stereo"</code> to be interpreted as stereo.</li>
|
||||
* <li>RAW: 48khz 16-bit mono or stereo. File name must include <code>".stereo"</code> to be interpreted as stereo.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @class SoundObject
|
||||
|
@ -138,8 +138,8 @@ typedef QSharedPointer<Sound> SharedSoundPointer;
|
|||
* @hifi-assignment-client
|
||||
*
|
||||
* @property {boolean} downloaded - <code>true</code> if the sound has been downloaded and is ready to be played, otherwise
|
||||
* <code>false</code>.
|
||||
* @property {number} duration - The duration of the sound, in seconds.
|
||||
* <code>false</code>. <em>Read-only.</em>
|
||||
* @property {number} duration - The duration of the sound, in seconds. <em>Read-only.</em>
|
||||
*/
|
||||
class SoundScriptingInterface : public QObject {
|
||||
Q_OBJECT
|
||||
|
|
|
@ -25,7 +25,8 @@ class SoundCacheScriptingInterface : public ScriptableResourceCache, public Depe
|
|||
// Properties are copied over from ResourceCache (see ResourceCache.h for reason).
|
||||
|
||||
/**jsdoc
|
||||
* API to manage sound cache resources.
|
||||
* The <code>SoundCache</code> API manages sound cache resources.
|
||||
*
|
||||
* @namespace SoundCache
|
||||
*
|
||||
* @hifi-interface
|
||||
|
|
|
@ -1289,14 +1289,16 @@ void convertInput_AVX2(int16_t* src, float *dst[4], float gain, int numFrames) {
|
|||
|
||||
#endif
|
||||
|
||||
// in-place rotation of the soundfield
|
||||
// crossfade between old and new rotation, to prevent artifacts
|
||||
void rotate_3x3_AVX2(float* buf[4], const float m0[3][3], const float m1[3][3], const float* win, int numFrames) {
|
||||
// in-place rotation and scaling of the soundfield
|
||||
// crossfade between old and new matrix, to prevent artifacts
|
||||
void rotate_4x4_AVX2(float* buf[4], const float m0[4][4], const float m1[4][4], const float* win, int numFrames) {
|
||||
|
||||
const float md[3][3] = {
|
||||
{ m0[0][0] - m1[0][0], m0[0][1] - m1[0][1], m0[0][2] - m1[0][2] },
|
||||
{ m0[1][0] - m1[1][0], m0[1][1] - m1[1][1], m0[1][2] - m1[1][2] },
|
||||
{ m0[2][0] - m1[2][0], m0[2][1] - m1[2][1], m0[2][2] - m1[2][2] },
|
||||
// matrix difference
|
||||
const float md[4][4] = {
|
||||
{ m0[0][0] - m1[0][0], m0[0][1] - m1[0][1], m0[0][2] - m1[0][2], m0[0][3] - m1[0][3] },
|
||||
{ m0[1][0] - m1[1][0], m0[1][1] - m1[1][1], m0[1][2] - m1[1][2], m0[1][3] - m1[1][3] },
|
||||
{ m0[2][0] - m1[2][0], m0[2][1] - m1[2][1], m0[2][2] - m1[2][2], m0[2][3] - m1[2][3] },
|
||||
{ m0[3][0] - m1[3][0], m0[3][1] - m1[3][1], m0[3][2] - m1[3][2], m0[3][3] - m1[3][3] },
|
||||
};
|
||||
|
||||
assert(numFrames % 8 == 0);
|
||||
|
@ -1307,30 +1309,35 @@ void rotate_3x3_AVX2(float* buf[4], const float m0[3][3], const float m1[3][3],
|
|||
|
||||
// interpolate the matrix
|
||||
__m256 m00 = _mm256_fmadd_ps(frac, _mm256_broadcast_ss(&md[0][0]), _mm256_broadcast_ss(&m1[0][0]));
|
||||
__m256 m10 = _mm256_fmadd_ps(frac, _mm256_broadcast_ss(&md[1][0]), _mm256_broadcast_ss(&m1[1][0]));
|
||||
__m256 m20 = _mm256_fmadd_ps(frac, _mm256_broadcast_ss(&md[2][0]), _mm256_broadcast_ss(&m1[2][0]));
|
||||
|
||||
__m256 m01 = _mm256_fmadd_ps(frac, _mm256_broadcast_ss(&md[0][1]), _mm256_broadcast_ss(&m1[0][1]));
|
||||
__m256 m11 = _mm256_fmadd_ps(frac, _mm256_broadcast_ss(&md[1][1]), _mm256_broadcast_ss(&m1[1][1]));
|
||||
__m256 m21 = _mm256_fmadd_ps(frac, _mm256_broadcast_ss(&md[2][1]), _mm256_broadcast_ss(&m1[2][1]));
|
||||
__m256 m31 = _mm256_fmadd_ps(frac, _mm256_broadcast_ss(&md[3][1]), _mm256_broadcast_ss(&m1[3][1]));
|
||||
|
||||
__m256 m02 = _mm256_fmadd_ps(frac, _mm256_broadcast_ss(&md[0][2]), _mm256_broadcast_ss(&m1[0][2]));
|
||||
__m256 m12 = _mm256_fmadd_ps(frac, _mm256_broadcast_ss(&md[1][2]), _mm256_broadcast_ss(&m1[1][2]));
|
||||
__m256 m22 = _mm256_fmadd_ps(frac, _mm256_broadcast_ss(&md[2][2]), _mm256_broadcast_ss(&m1[2][2]));
|
||||
__m256 m32 = _mm256_fmadd_ps(frac, _mm256_broadcast_ss(&md[3][2]), _mm256_broadcast_ss(&m1[3][2]));
|
||||
|
||||
__m256 m13 = _mm256_fmadd_ps(frac, _mm256_broadcast_ss(&md[1][3]), _mm256_broadcast_ss(&m1[1][3]));
|
||||
__m256 m23 = _mm256_fmadd_ps(frac, _mm256_broadcast_ss(&md[2][3]), _mm256_broadcast_ss(&m1[2][3]));
|
||||
__m256 m33 = _mm256_fmadd_ps(frac, _mm256_broadcast_ss(&md[3][3]), _mm256_broadcast_ss(&m1[3][3]));
|
||||
|
||||
// matrix multiply
|
||||
__m256 x = _mm256_mul_ps(m00, _mm256_loadu_ps(&buf[1][i]));
|
||||
__m256 y = _mm256_mul_ps(m10, _mm256_loadu_ps(&buf[1][i]));
|
||||
__m256 z = _mm256_mul_ps(m20, _mm256_loadu_ps(&buf[1][i]));
|
||||
__m256 w = _mm256_mul_ps(m00, _mm256_loadu_ps(&buf[0][i]));
|
||||
|
||||
x = _mm256_fmadd_ps(m01, _mm256_loadu_ps(&buf[2][i]), x);
|
||||
y = _mm256_fmadd_ps(m11, _mm256_loadu_ps(&buf[2][i]), y);
|
||||
z = _mm256_fmadd_ps(m21, _mm256_loadu_ps(&buf[2][i]), z);
|
||||
__m256 x = _mm256_mul_ps(m11, _mm256_loadu_ps(&buf[1][i]));
|
||||
__m256 y = _mm256_mul_ps(m21, _mm256_loadu_ps(&buf[1][i]));
|
||||
__m256 z = _mm256_mul_ps(m31, _mm256_loadu_ps(&buf[1][i]));
|
||||
|
||||
x = _mm256_fmadd_ps(m02, _mm256_loadu_ps(&buf[3][i]), x);
|
||||
y = _mm256_fmadd_ps(m12, _mm256_loadu_ps(&buf[3][i]), y);
|
||||
z = _mm256_fmadd_ps(m22, _mm256_loadu_ps(&buf[3][i]), z);
|
||||
x = _mm256_fmadd_ps(m12, _mm256_loadu_ps(&buf[2][i]), x);
|
||||
y = _mm256_fmadd_ps(m22, _mm256_loadu_ps(&buf[2][i]), y);
|
||||
z = _mm256_fmadd_ps(m32, _mm256_loadu_ps(&buf[2][i]), z);
|
||||
|
||||
x = _mm256_fmadd_ps(m13, _mm256_loadu_ps(&buf[3][i]), x);
|
||||
y = _mm256_fmadd_ps(m23, _mm256_loadu_ps(&buf[3][i]), y);
|
||||
z = _mm256_fmadd_ps(m33, _mm256_loadu_ps(&buf[3][i]), z);
|
||||
|
||||
_mm256_storeu_ps(&buf[0][i], w);
|
||||
_mm256_storeu_ps(&buf[1][i], x);
|
||||
_mm256_storeu_ps(&buf[2][i], y);
|
||||
_mm256_storeu_ps(&buf[3][i], z);
|
||||
|
|
|
@ -509,6 +509,26 @@ void Avatar::relayJointDataToChildren() {
|
|||
_reconstructSoftEntitiesJointMap = false;
|
||||
}
|
||||
|
||||
/**jsdoc
|
||||
* An avatar has different types of data simulated at different rates, in Hz.
|
||||
*
|
||||
* <table>
|
||||
* <thead>
|
||||
* <tr><th>Rate Name</th><th>Description</th></tr>
|
||||
* </thead>
|
||||
* <tbody>
|
||||
* <tr><td><code>"avatar" or ""</code></td><td>The rate at which the avatar is updated even if not in view.</td></tr>
|
||||
* <tr><td><code>"avatarInView"</code></td><td>The rate at which the avatar is updated if in view.</td></tr>
|
||||
* <tr><td><code>"skeletonModel"</code></td><td>The rate at which the skeleton model is being updated, even if there are no
|
||||
* joint data available.</td></tr>
|
||||
* <tr><td><code>"jointData"</code></td><td>The rate at which joint data are being updated.</td></tr>
|
||||
* <tr><td><code>""</code></td><td>When no rate name is specified, the <code>"avatar"</code> update rate is
|
||||
* provided.</td></tr>
|
||||
* </tbody>
|
||||
* </table>
|
||||
*
|
||||
* @typedef {string} AvatarSimulationRate
|
||||
*/
|
||||
float Avatar::getSimulationRate(const QString& rateName) const {
|
||||
if (rateName == "") {
|
||||
return _simulationRate.rate();
|
||||
|
@ -1525,8 +1545,8 @@ void Avatar::rigReset() {
|
|||
|
||||
void Avatar::computeMultiSphereShapes() {
|
||||
const Rig& rig = getSkeletonModel()->getRig();
|
||||
glm::vec3 scale = extractScale(rig.getGeometryOffsetPose());
|
||||
const HFMModel& geometry = getSkeletonModel()->getHFMModel();
|
||||
glm::vec3 geometryScale = extractScale(rig.getGeometryOffsetPose());
|
||||
int jointCount = rig.getJointStateCount();
|
||||
_multiSphereShapes.clear();
|
||||
_multiSphereShapes.reserve(jointCount);
|
||||
|
@ -1535,9 +1555,10 @@ void Avatar::computeMultiSphereShapes() {
|
|||
std::vector<btVector3> btPoints;
|
||||
int lineCount = (int)shapeInfo.debugLines.size();
|
||||
btPoints.reserve(lineCount);
|
||||
glm::vec3 jointScale = rig.getJointPose(i).scale() / extractScale(rig.getGeometryToRigTransform());
|
||||
for (int j = 0; j < lineCount; j++) {
|
||||
const glm::vec3 &point = shapeInfo.debugLines[j];
|
||||
auto rigPoint = scale * point;
|
||||
auto rigPoint = jointScale * geometryScale * point;
|
||||
btVector3 btPoint = glmToBullet(rigPoint);
|
||||
btPoints.push_back(btPoint);
|
||||
}
|
||||
|
|
|
@ -501,8 +501,8 @@ public:
|
|||
|
||||
/**jsdoc
|
||||
* @function MyAvatar.getSimulationRate
|
||||
* @param {string} [rateName=""] - Rate name.
|
||||
* @returns {number} Simulation rate.
|
||||
* @param {AvatarSimulationRate} [rateName=""] - Rate name.
|
||||
* @returns {number} Simulation rate in Hz.
|
||||
* @deprecated This function is deprecated and will be removed.
|
||||
*/
|
||||
Q_INVOKABLE float getSimulationRate(const QString& rateName = QString("")) const;
|
||||
|
|
|
@ -270,28 +270,19 @@ bool SkeletonModel::getEyeModelPositions(glm::vec3& firstEyePosition, glm::vec3&
|
|||
getJointPosition(_rig.indexOfJoint("RightEye"), secondEyePosition)) {
|
||||
return true;
|
||||
}
|
||||
// no eye joints; try to estimate based on head/neck joints
|
||||
glm::vec3 neckPosition, headPosition;
|
||||
if (getJointPosition(_rig.indexOfJoint("Neck"), neckPosition) &&
|
||||
getJointPosition(_rig.indexOfJoint("Head"), headPosition)) {
|
||||
const float EYE_PROPORTION = 0.6f;
|
||||
glm::vec3 baseEyePosition = glm::mix(neckPosition, headPosition, EYE_PROPORTION);
|
||||
|
||||
int headJointIndex = _rig.indexOfJoint("Head");
|
||||
glm::vec3 headPosition;
|
||||
if (getJointPosition(headJointIndex, headPosition)) {
|
||||
|
||||
// get head joint rotation.
|
||||
glm::quat headRotation;
|
||||
getJointRotation(_rig.indexOfJoint("Head"), headRotation);
|
||||
const float EYES_FORWARD = 0.25f;
|
||||
const float EYE_SEPARATION = 0.1f;
|
||||
float headHeight = glm::distance(neckPosition, headPosition);
|
||||
firstEyePosition = baseEyePosition + headRotation * glm::vec3(EYE_SEPARATION, 0.0f, EYES_FORWARD) * headHeight;
|
||||
secondEyePosition = baseEyePosition + headRotation * glm::vec3(-EYE_SEPARATION, 0.0f, EYES_FORWARD) * headHeight;
|
||||
return true;
|
||||
} else if (getJointPosition(_rig.indexOfJoint("Head"), headPosition)) {
|
||||
glm::vec3 baseEyePosition = headPosition;
|
||||
glm::quat headRotation;
|
||||
getJointRotation(_rig.indexOfJoint("Head"), headRotation);
|
||||
const float EYES_FORWARD_HEAD_ONLY = 0.30f;
|
||||
const float EYE_SEPARATION = 0.1f;
|
||||
firstEyePosition = baseEyePosition + headRotation * glm::vec3(EYE_SEPARATION, 0.0f, EYES_FORWARD_HEAD_ONLY);
|
||||
secondEyePosition = baseEyePosition + headRotation * glm::vec3(-EYE_SEPARATION, 0.0f, EYES_FORWARD_HEAD_ONLY);
|
||||
getJointRotation(headJointIndex, headRotation);
|
||||
|
||||
float heightRatio = _rig.getUnscaledEyeHeight() / DEFAULT_AVATAR_EYE_HEIGHT;
|
||||
glm::vec3 ipdOffset = glm::vec3(DEFAULT_AVATAR_IPD / 2.0f, 0.0f, 0.0f);
|
||||
firstEyePosition = headPosition + headRotation * heightRatio * (DEFAULT_AVATAR_HEAD_TO_MIDDLE_EYE_OFFSET + ipdOffset);
|
||||
secondEyePosition = headPosition + headRotation * heightRatio * (DEFAULT_AVATAR_HEAD_TO_MIDDLE_EYE_OFFSET - ipdOffset);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -1545,7 +1545,6 @@ float AvatarData::getDataRate(const QString& rateName) const {
|
|||
* <tr><th>Rate Name</th><th>Description</th></tr>
|
||||
* </thead>
|
||||
* <tbody>
|
||||
|
||||
* <tr><td><code>"globalPosition"</code></td><td>Global position.</td></tr>
|
||||
* <tr><td><code>"localPosition"</code></td><td>Local position.</td></tr>
|
||||
* <tr><td><code>"avatarBoundingBox"</code></td><td>Avatar bounding box.</td></tr>
|
||||
|
@ -1559,7 +1558,6 @@ float AvatarData::getDataRate(const QString& rateName) const {
|
|||
* <tr><td><code>"faceTracker"</code></td><td>Face tracker data.</td></tr>
|
||||
* <tr><td><code>"jointData"</code></td><td>Joint data.</td></tr>
|
||||
* <tr><td><code>"farGrabJointData"</code></td><td>Far grab joint data.</td></tr>
|
||||
|
||||
* <tr><td><code>""</code></td><td>When no rate name is specified, the overall update rate is provided.</td></tr>
|
||||
* </tbody>
|
||||
* </table>
|
||||
|
@ -1721,7 +1719,6 @@ glm::vec3 AvatarData::getJointTranslation(const QString& name) const {
|
|||
// on another thread in between the call to getJointIndex and getJointTranslation
|
||||
// return getJointTranslation(getJointIndex(name));
|
||||
return readLockWithNamedJointIndex<glm::vec3>(name, [this](int index) {
|
||||
return _jointData.at(index).translation;
|
||||
return getJointTranslation(index);
|
||||
});
|
||||
}
|
||||
|
@ -1809,8 +1806,8 @@ glm::quat AvatarData::getJointRotation(const QString& name) const {
|
|||
// Can't do this, not thread safe
|
||||
// return getJointRotation(getJointIndex(name));
|
||||
|
||||
return readLockWithNamedJointIndex<glm::quat>(name, [&](int index) {
|
||||
return _jointData.at(index).rotation;
|
||||
return readLockWithNamedJointIndex<glm::quat>(name, [this](int index) {
|
||||
return getJointRotation(index);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -2905,6 +2902,20 @@ glm::mat4 AvatarData::getControllerRightHandMatrix() const {
|
|||
return _controllerRightHandMatrixCache.get();
|
||||
}
|
||||
|
||||
/**jsdoc
|
||||
* Information about a ray-to-avatar intersection.
|
||||
* @typedef {object} RayToAvatarIntersectionResult
|
||||
* @property {boolean} intersects - <code>true</code> if an avatar is intersected, <code>false</code> if it isn't.
|
||||
* @property {string} avatarID - The ID of the avatar that is intersected.
|
||||
* @property {number} distance - The distance from the ray origin to the intersection.
|
||||
* @property {string} face - The name of the box face that is intersected; <code>"UNKNOWN_FACE"</code> if mesh was picked
|
||||
* against.
|
||||
* @property {Vec3} intersection - The ray intersection point in world coordinates.
|
||||
* @property {Vec3} surfaceNormal - The surface normal at the intersection point.
|
||||
* @property {number} jointIndex - The index of the joint intersected.
|
||||
* @property {SubmeshIntersection} extraInfo - Extra information on the mesh intersected if mesh was picked against,
|
||||
* <code>{}</code> if it wasn't.
|
||||
*/
|
||||
QScriptValue RayToAvatarIntersectionResultToScriptValue(QScriptEngine* engine, const RayToAvatarIntersectionResult& value) {
|
||||
QScriptValue obj = engine->newObject();
|
||||
obj.setProperty("intersects", value.intersects);
|
||||
|
|
|
@ -479,7 +479,8 @@ class AvatarData : public QObject, public SpatiallyNestable {
|
|||
* avatar. <em>Read-only.</em>
|
||||
* @property {number} sensorToWorldScale - The scale that transforms dimensions in the user's real world to the avatar's
|
||||
* size in the virtual world. <em>Read-only.</em>
|
||||
* @property {boolean} hasPriority - is the avatar in a Hero zone? <em>Read-only.</em>
|
||||
* @property {boolean} hasPriority - <code>true</code> if the avatar is in a "hero" zone, <code>false</code> if it isn't.
|
||||
* <em>Read-only.</em>
|
||||
*/
|
||||
Q_PROPERTY(glm::vec3 position READ getWorldPosition WRITE setPositionViaScript)
|
||||
Q_PROPERTY(float scale READ getDomainLimitedScale WRITE setTargetScale)
|
||||
|
@ -1751,14 +1752,11 @@ protected:
|
|||
|
||||
template <typename T, typename F>
|
||||
T readLockWithNamedJointIndex(const QString& name, const T& defaultValue, F f) const {
|
||||
int index = getFauxJointIndex(name);
|
||||
QReadLocker readLock(&_jointDataLock);
|
||||
|
||||
// The first conditional is superfluous, but illustrative
|
||||
if (index == -1 || index < _jointData.size()) {
|
||||
int index = getJointIndex(name);
|
||||
if (index == -1) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
return f(index);
|
||||
}
|
||||
|
||||
|
@ -1769,8 +1767,8 @@ protected:
|
|||
|
||||
template <typename F>
|
||||
void writeLockWithNamedJointIndex(const QString& name, F f) {
|
||||
int index = getFauxJointIndex(name);
|
||||
QWriteLocker writeLock(&_jointDataLock);
|
||||
int index = getJointIndex(name);
|
||||
if (index == -1) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -229,8 +229,9 @@ AvatarSharedPointer AvatarHashMap::newOrExistingAvatar(const QUuid& sessionUUID,
|
|||
|
||||
AvatarSharedPointer AvatarHashMap::findAvatar(const QUuid& sessionUUID) const {
|
||||
QReadLocker locker(&_hashLock);
|
||||
if (_avatarHash.contains(sessionUUID)) {
|
||||
return _avatarHash.value(sessionUUID);
|
||||
auto avatarIter = _avatarHash.find(sessionUUID);
|
||||
if (avatarIter != _avatarHash.end()) {
|
||||
return avatarIter.value();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
@ -36,8 +36,10 @@ const int CLIENT_TO_AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 50;
|
|||
const quint64 MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS = USECS_PER_SECOND / CLIENT_TO_AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND;
|
||||
|
||||
/**jsdoc
|
||||
* <strong>Note:</strong> An <code>AvatarList</code> API is also provided for Interface and client entity scripts: it is a
|
||||
* synonym for the {@link AvatarManager} API.
|
||||
* The <code>AvatarList</code> API provides information about avatars within the current domain.
|
||||
*
|
||||
* <p><strong>Warning:</strong> An API named "<code>AvatarList</code>" is also provided for Interface, client entity, and avatar
|
||||
* scripts, however, it is a synonym for the {@link AvatarManager} API.</p>
|
||||
*
|
||||
* @namespace AvatarList
|
||||
*
|
||||
|
@ -78,23 +80,37 @@ public:
|
|||
// Currently, your own avatar will be included as the null avatar id.
|
||||
|
||||
/**jsdoc
|
||||
* Gets the IDs of all avatars in the domain.
|
||||
* <p><strong>Warning:</strong> If the AC script is acting as an avatar (i.e., <code>Agent.isAvatar == true</code>) the
|
||||
* avatar's ID is NOT included in results.</p>
|
||||
* @function AvatarList.getAvatarIdentifiers
|
||||
* @returns {Uuid[]}
|
||||
* @returns {Uuid[]} The IDs of all avatars in the domain (excluding AC script's avatar).
|
||||
* @example <caption>Report the IDS of all avatars within the domain.</caption>
|
||||
* var avatars = AvatarList.getAvatarIdentifiers();
|
||||
* print("Avatars in the domain: " + JSON.stringify(avatars));
|
||||
*/
|
||||
Q_INVOKABLE QVector<QUuid> getAvatarIdentifiers();
|
||||
|
||||
/**jsdoc
|
||||
* Gets the IDs of all avatars within a specified distance from a point.
|
||||
* <p><strong>Warning:</strong> If the AC script is acting as an avatar (i.e., <code>Agent.isAvatar == true</code>) the
|
||||
* avatar's ID is NOT included in results.</p>
|
||||
* @function AvatarList.getAvatarsInRange
|
||||
* @param {Vec3} position
|
||||
* @param {number} range
|
||||
* @returns {Uuid[]}
|
||||
* @param {Vec3} position - The point about which the search is performed.
|
||||
* @param {number} range - The search radius.
|
||||
* @returns {Uuid[]} The IDs of all avatars within the search distance from the position (excluding AC script's avatar).
|
||||
* @example <caption>Report the IDs of all avatars within 10m of the origin.</caption>
|
||||
* var RANGE = 10;
|
||||
* var avatars = AvatarList.getAvatarsInRange(Vec3.ZERO, RANGE);
|
||||
* print("Avatars near the origin: " + JSON.stringify(avatars));
|
||||
*/
|
||||
Q_INVOKABLE QVector<QUuid> getAvatarsInRange(const glm::vec3& position, float rangeMeters) const;
|
||||
|
||||
/**jsdoc
|
||||
* Gets information about an avatar.
|
||||
* @function AvatarList.getAvatar
|
||||
* @param {Uuid} avatarID
|
||||
* @returns {AvatarData}
|
||||
* @param {Uuid} avatarID - The ID of the avatar.
|
||||
* @returns {AvatarData} Information about the avatar.
|
||||
*/
|
||||
// Null/Default-constructed QUuids will return MyAvatar
|
||||
Q_INVOKABLE virtual ScriptAvatarData* getAvatar(QUuid avatarID) { return new ScriptAvatarData(getAvatarBySessionID(avatarID)); }
|
||||
|
@ -110,34 +126,57 @@ public:
|
|||
signals:
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when an avatar arrives in the domain.
|
||||
* @function AvatarList.avatarAddedEvent
|
||||
* @param {Uuid} sessionUUID
|
||||
* @param {Uuid} sessionUUID - The ID of the avatar that arrived in the domain.
|
||||
* @returns {Signal}
|
||||
* @example <caption>Report when an avatar arrives in the domain.</caption>
|
||||
* AvatarManager.avatarAddedEvent.connect(function (sessionID) {
|
||||
* print("Avatar arrived: " + sessionID);
|
||||
* });
|
||||
*
|
||||
* // Note: If using from the AvatarList API, replace "AvatarManager" with "AvatarList".
|
||||
*/
|
||||
void avatarAddedEvent(const QUuid& sessionUUID);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when an avatar leaves the domain.
|
||||
* @function AvatarList.avatarRemovedEvent
|
||||
* @param {Uuid} sessionUUID
|
||||
* @param {Uuid} sessionUUID - The ID of the avatar that left the domain.
|
||||
* @returns {Signal}
|
||||
* @example <caption>Report when an avatar leaves the domain.</caption>
|
||||
* AvatarManager.avatarRemovedEvent.connect(function (sessionID) {
|
||||
* print("Avatar left: " + sessionID);
|
||||
* });
|
||||
*
|
||||
* // Note: If using from the AvatarList API, replace "AvatarManager" with "AvatarList".
|
||||
*/
|
||||
void avatarRemovedEvent(const QUuid& sessionUUID);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when an avatar's session ID changes.
|
||||
* @function AvatarList.avatarSessionChangedEvent
|
||||
* @param {Uuid} sessionUUID
|
||||
* @param {Uuid} oldSessionUUID
|
||||
* @param {Uuid} newSessionUUID - The new session ID.
|
||||
* @param {Uuid} oldSessionUUID - The old session ID.
|
||||
* @returns {Signal}
|
||||
* @example <caption>Report when an avatar's session ID changes.</caption>
|
||||
* AvatarManager.avatarSessionChangedEvent.connect(function (newSessionID, oldSessionID) {
|
||||
* print("Avatar session ID changed from " + oldSessionID + " to " + newSessionID);
|
||||
* });
|
||||
*
|
||||
* // Note: If using from the AvatarList API, replace "AvatarManager" with "AvatarList".
|
||||
*/
|
||||
void avatarSessionChangedEvent(const QUuid& sessionUUID,const QUuid& oldUUID);
|
||||
|
||||
public slots:
|
||||
|
||||
/**jsdoc
|
||||
* Checks whether there is an avatar within a specified distance from a point.
|
||||
* @function AvatarList.isAvatarInRange
|
||||
* @param {string} position
|
||||
* @param {string} range
|
||||
* @returns {boolean}
|
||||
* @param {string} position - The test position.
|
||||
* @param {string} range - The test distance.
|
||||
* @returns {boolean} <code>true</code> if there's an avatar within the specified distance of the point, <code>false</code>
|
||||
* if not.
|
||||
*/
|
||||
bool isAvatarInRange(const glm::vec3 & position, const float range);
|
||||
|
||||
|
@ -145,36 +184,41 @@ protected slots:
|
|||
|
||||
/**jsdoc
|
||||
* @function AvatarList.sessionUUIDChanged
|
||||
* @param {Uuid} sessionUUID
|
||||
* @param {Uuid} oldSessionUUID
|
||||
* @param {Uuid} sessionUUID - New session ID.
|
||||
* @param {Uuid} oldSessionUUID - Old session ID.
|
||||
* @deprecated This function is deprecated and will be removed.
|
||||
*/
|
||||
void sessionUUIDChanged(const QUuid& sessionUUID, const QUuid& oldUUID);
|
||||
|
||||
/**jsdoc
|
||||
* @function AvatarList.processAvatarDataPacket
|
||||
* @param {} message
|
||||
* @param {} sendingNode
|
||||
* @param {object} message - Message.
|
||||
* @param {object} sendingNode - Sending node.
|
||||
* @deprecated This function is deprecated and will be removed.
|
||||
*/
|
||||
void processAvatarDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||
|
||||
/**jsdoc
|
||||
* @function AvatarList.processAvatarIdentityPacket
|
||||
* @param {} message
|
||||
* @param {} sendingNode
|
||||
* @param {object} message - Message.
|
||||
* @param {object} sendingNode - Sending node.
|
||||
* @deprecated This function is deprecated and will be removed.
|
||||
*/
|
||||
void processAvatarIdentityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||
|
||||
/**jsdoc
|
||||
* @function AvatarList.processBulkAvatarTraits
|
||||
* @param {} message
|
||||
* @param {} sendingNode
|
||||
* @param {object} message - Message.
|
||||
* @param {object} sendingNode - Sending node.
|
||||
* @deprecated This function is deprecated and will be removed.
|
||||
*/
|
||||
void processBulkAvatarTraits(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||
|
||||
/**jsdoc
|
||||
* @function AvatarList.processKillAvatar
|
||||
* @param {} message
|
||||
* @param {} sendingNode
|
||||
* @param {object} message - Message.
|
||||
* @param {object} sendingNode - Sending node.
|
||||
* @deprecated This function is deprecated and will be removed.
|
||||
*/
|
||||
void processKillAvatar(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||
|
||||
|
|
|
@ -16,6 +16,52 @@
|
|||
|
||||
#include "AvatarData.h"
|
||||
|
||||
/**jsdoc
|
||||
* Information about an avatar.
|
||||
* @typedef {object} AvatarData
|
||||
* @property {Vec3} position - The avatar's position.
|
||||
* @property {number} scale - The target scale of the avatar without any restrictions on permissible values imposed by the
|
||||
* domain.
|
||||
* @property {Vec3} handPosition - A user-defined hand position, in world coordinates. The position moves with the avatar but
|
||||
* is otherwise not used or changed by Interface.
|
||||
* @property {number} bodyPitch - The pitch of the avatar's body, in degrees.
|
||||
* @property {number} bodyYaw - The yaw of the avatar's body, in degrees.
|
||||
* @property {number} bodyRoll - The roll of the avatar's body, in degrees.
|
||||
* @property {Quat} orientation - The orientation of the avatar's body.
|
||||
* @property {Quat} headOrientation - The orientation of the avatar's head.
|
||||
* @property {number} headPitch - The pitch of the avatar's head relative to the body, in degrees.
|
||||
* @property {number} headYaw - The yaw of the avatar's head relative to the body, in degrees.
|
||||
* @property {number} headRoll - The roll of the avatar's head relative to the body, in degrees.
|
||||
*
|
||||
* @property {Vec3} velocity - The linear velocity of the avatar.
|
||||
* @property {Vec3} angularVelocity - The angular velocity of the avatar.
|
||||
*
|
||||
* @property {Uuid} sessionUUID - The avatar's session ID.
|
||||
* @property {string} displayName - The avatar's display name.
|
||||
* @property {string} sessionDisplayName - The avatar's display name, sanitized and versioned, as defined by the avatar mixer.
|
||||
* It is unique among all avatars present in the domain at the time.
|
||||
* @property {boolean} isReplicated - <strong>Deprecated.</strong>
|
||||
* @property {boolean} lookAtSnappingEnabled - <code>true</code> if the avatar's eyes snap to look at another avatar's eyes
|
||||
* when the other avatar is in the line of sight and also has <code>lookAtSnappingEnabled == true</code>.
|
||||
*
|
||||
* @property {string} skeletonModelURL - The avatar's FST file.
|
||||
* @property {AttachmentData[]} attachmentData - Information on the avatar's attachments.<br />
|
||||
* <strong>Deprecated:</strong> Use avatar entities instead.
|
||||
* @property {string[]} jointNames - The list of joints in the current avatar model.
|
||||
*
|
||||
* @property {number} audioLoudness - The instantaneous loudness of the audio input that the avatar is injecting into the
|
||||
* domain.
|
||||
* @property {number} audioAverageLoudness - The rolling average loudness of the audio input that the avatar is injecting into
|
||||
* the domain.
|
||||
*
|
||||
* @property {Mat4} sensorToWorldMatrix - The scale, rotation, and translation transform from the user's real world to the
|
||||
* avatar's size, orientation, and position in the virtual world.
|
||||
* @property {Mat4} controllerLeftHandMatrix - The rotation and translation of the left hand controller relative to the avatar.
|
||||
* @property {Mat4} controllerRightHandMatrix - The rotation and translation of the right hand controller relative to the
|
||||
* avatar.
|
||||
*
|
||||
* @property {boolean} hasPriority - <code>true</code> if the avatar is in a "hero" zone, <code>false</code> if it isn't.
|
||||
*/
|
||||
class ScriptAvatarData : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
|
|
|
@ -33,9 +33,8 @@
|
|||
#include "ModelBakingLoggingCategory.h"
|
||||
#include "TextureBaker.h"
|
||||
|
||||
FBXBaker::FBXBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputTextureThreadGetter,
|
||||
const QString& bakedOutputDirectory, const QString& originalOutputDirectory, bool hasBeenBaked) :
|
||||
ModelBaker(inputModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory, hasBeenBaked) {
|
||||
FBXBaker::FBXBaker(const QUrl& inputModelURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory, bool hasBeenBaked) :
|
||||
ModelBaker(inputModelURL, bakedOutputDirectory, originalOutputDirectory, hasBeenBaked) {
|
||||
if (hasBeenBaked) {
|
||||
// Look for the original model file one directory higher. Perhaps this is an oven output directory.
|
||||
QUrl originalRelativePath = QUrl("../original/" + inputModelURL.fileName().replace(BAKED_FBX_EXTENSION, FBX_EXTENSION));
|
||||
|
@ -45,15 +44,6 @@ FBXBaker::FBXBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputText
|
|||
}
|
||||
|
||||
void FBXBaker::bakeProcessedSource(const hfm::Model::Pointer& hfmModel, const std::vector<hifi::ByteArray>& dracoMeshes, const std::vector<std::vector<hifi::ByteArray>>& dracoMaterialLists) {
|
||||
_hfmModel = hfmModel;
|
||||
|
||||
if (shouldStop()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// enumerate the models and textures found in the scene and start a bake for them
|
||||
rewriteAndBakeSceneTextures();
|
||||
|
||||
if (shouldStop()) {
|
||||
return;
|
||||
}
|
||||
|
@ -114,15 +104,17 @@ void FBXBaker::rewriteAndBakeSceneModels(const QVector<hfm::Mesh>& meshes, const
|
|||
int meshIndex = 0;
|
||||
for (FBXNode& rootChild : _rootNode.children) {
|
||||
if (rootChild.name == "Objects") {
|
||||
for (FBXNode& object : rootChild.children) {
|
||||
if (object.name == "Geometry") {
|
||||
if (object.properties.at(2) == "Mesh") {
|
||||
auto object = rootChild.children.begin();
|
||||
while (object != rootChild.children.end()) {
|
||||
if (object->name == "Geometry") {
|
||||
if (object->properties.at(2) == "Mesh") {
|
||||
int meshNum = meshIndexToRuntimeOrder[meshIndex];
|
||||
replaceMeshNodeWithDraco(object, dracoMeshes[meshNum], dracoMaterialLists[meshNum]);
|
||||
replaceMeshNodeWithDraco(*object, dracoMeshes[meshNum], dracoMaterialLists[meshNum]);
|
||||
meshIndex++;
|
||||
}
|
||||
} else if (object.name == "Model") {
|
||||
for (FBXNode& modelChild : object.children) {
|
||||
object++;
|
||||
} else if (object->name == "Model") {
|
||||
for (FBXNode& modelChild : object->children) {
|
||||
if (modelChild.name == "Properties60" || modelChild.name == "Properties70") {
|
||||
// This is a properties node
|
||||
// Remove the geometric transform because that has been applied directly to the vertices in FBXSerializer
|
||||
|
@ -142,10 +134,37 @@ void FBXBaker::rewriteAndBakeSceneModels(const QVector<hfm::Mesh>& meshes, const
|
|||
} else if (modelChild.name == "Vertices") {
|
||||
// This model is also a mesh
|
||||
int meshNum = meshIndexToRuntimeOrder[meshIndex];
|
||||
replaceMeshNodeWithDraco(object, dracoMeshes[meshNum], dracoMaterialLists[meshNum]);
|
||||
replaceMeshNodeWithDraco(*object, dracoMeshes[meshNum], dracoMaterialLists[meshNum]);
|
||||
meshIndex++;
|
||||
}
|
||||
}
|
||||
object++;
|
||||
} else if (object->name == "Texture" || object->name == "Video") {
|
||||
// this is an embedded texture, we need to remove it from the FBX
|
||||
object = rootChild.children.erase(object);
|
||||
} else if (object->name == "Material") {
|
||||
for (FBXNode& materialChild : object->children) {
|
||||
if (materialChild.name == "Properties60" || materialChild.name == "Properties70") {
|
||||
// This is a properties node
|
||||
// Remove the material texture scale because that is now included in the material JSON
|
||||
// Texture nodes are removed, so their texture scale is effectively gone already
|
||||
static const QVariant MAYA_UV_SCALE = hifi::ByteArray("Maya|uv_scale");
|
||||
static const QVariant MAYA_UV_OFFSET = hifi::ByteArray("Maya|uv_offset");
|
||||
for (int i = 0; i < materialChild.children.size(); i++) {
|
||||
const auto& prop = materialChild.children[i];
|
||||
const auto& propertyName = prop.properties.at(0);
|
||||
if (propertyName == MAYA_UV_SCALE ||
|
||||
propertyName == MAYA_UV_OFFSET) {
|
||||
materialChild.children.removeAt(i);
|
||||
--i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object++;
|
||||
} else {
|
||||
object++;
|
||||
}
|
||||
|
||||
if (hasErrors()) {
|
||||
|
@ -154,82 +173,4 @@ void FBXBaker::rewriteAndBakeSceneModels(const QVector<hfm::Mesh>& meshes, const
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FBXBaker::rewriteAndBakeSceneTextures() {
|
||||
using namespace image::TextureUsage;
|
||||
QHash<QString, image::TextureUsage::Type> textureTypes;
|
||||
|
||||
// enumerate the materials in the extracted geometry so we can determine the texture type for each texture ID
|
||||
for (const auto& material : _hfmModel->materials) {
|
||||
if (material.normalTexture.isBumpmap) {
|
||||
textureTypes[material.normalTexture.id] = BUMP_TEXTURE;
|
||||
} else {
|
||||
textureTypes[material.normalTexture.id] = NORMAL_TEXTURE;
|
||||
}
|
||||
|
||||
textureTypes[material.albedoTexture.id] = ALBEDO_TEXTURE;
|
||||
textureTypes[material.glossTexture.id] = GLOSS_TEXTURE;
|
||||
textureTypes[material.roughnessTexture.id] = ROUGHNESS_TEXTURE;
|
||||
textureTypes[material.specularTexture.id] = SPECULAR_TEXTURE;
|
||||
textureTypes[material.metallicTexture.id] = METALLIC_TEXTURE;
|
||||
textureTypes[material.emissiveTexture.id] = EMISSIVE_TEXTURE;
|
||||
textureTypes[material.occlusionTexture.id] = OCCLUSION_TEXTURE;
|
||||
textureTypes[material.lightmapTexture.id] = LIGHTMAP_TEXTURE;
|
||||
}
|
||||
|
||||
// enumerate the children of the root node
|
||||
for (FBXNode& rootChild : _rootNode.children) {
|
||||
|
||||
if (rootChild.name == "Objects") {
|
||||
|
||||
// enumerate the objects
|
||||
auto object = rootChild.children.begin();
|
||||
while (object != rootChild.children.end()) {
|
||||
if (object->name == "Texture") {
|
||||
|
||||
// double check that we didn't get an abort while baking the last texture
|
||||
if (shouldStop()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// enumerate the texture children
|
||||
for (FBXNode& textureChild : object->children) {
|
||||
|
||||
if (textureChild.name == "RelativeFilename") {
|
||||
QString hfmTextureFileName { textureChild.properties.at(0).toString() };
|
||||
|
||||
// grab the ID for this texture so we can figure out the
|
||||
// texture type from the loaded materials
|
||||
auto textureID { object->properties[0].toString() };
|
||||
auto textureType = textureTypes[textureID];
|
||||
|
||||
// Compress the texture information and return the new filename to be added into the FBX scene
|
||||
auto bakedTextureFile = compressTexture(hfmTextureFileName, textureType);
|
||||
|
||||
// If no errors or warnings have occurred during texture compression add the filename to the FBX scene
|
||||
if (!bakedTextureFile.isNull()) {
|
||||
textureChild.properties[0] = bakedTextureFile;
|
||||
} else {
|
||||
// if bake fails - return, if there were errors and continue, if there were warnings.
|
||||
if (hasErrors()) {
|
||||
return;
|
||||
} else if (hasWarnings()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
++object;
|
||||
|
||||
} else if (object->name == "Video") {
|
||||
// this is an embedded texture, we need to remove it from the FBX
|
||||
object = rootChild.children.erase(object);
|
||||
} else {
|
||||
++object;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -31,20 +31,14 @@ using TextureBakerThreadGetter = std::function<QThread*()>;
|
|||
class FBXBaker : public ModelBaker {
|
||||
Q_OBJECT
|
||||
public:
|
||||
FBXBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputTextureThreadGetter,
|
||||
const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "", bool hasBeenBaked = false);
|
||||
FBXBaker(const QUrl& inputModelURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "", bool hasBeenBaked = false);
|
||||
|
||||
protected:
|
||||
virtual void bakeProcessedSource(const hfm::Model::Pointer& hfmModel, const std::vector<hifi::ByteArray>& dracoMeshes, const std::vector<std::vector<hifi::ByteArray>>& dracoMaterialLists) override;
|
||||
|
||||
private:
|
||||
void rewriteAndBakeSceneModels(const QVector<hfm::Mesh>& meshes, const std::vector<hifi::ByteArray>& dracoMeshes, const std::vector<std::vector<hifi::ByteArray>>& dracoMaterialLists);
|
||||
void rewriteAndBakeSceneTextures();
|
||||
void replaceMeshNodeWithDraco(FBXNode& meshNode, const QByteArray& dracoMeshBytes, const std::vector<hifi::ByteArray>& dracoMaterialList);
|
||||
|
||||
hfm::Model::Pointer _hfmModel;
|
||||
|
||||
bool _pendingErrorEmission { false };
|
||||
};
|
||||
|
||||
#endif // hifi_FBXBaker_h
|
||||
|
|
|
@ -27,21 +27,12 @@ std::function<QThread*()> MaterialBaker::_getNextOvenWorkerThreadOperator;
|
|||
|
||||
static int materialNum = 0;
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<graphics::Material::MapChannel> {
|
||||
size_t operator()(const graphics::Material::MapChannel& a) const {
|
||||
return std::hash<size_t>()((size_t)a);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
MaterialBaker::MaterialBaker(const QString& materialData, bool isURL, const QString& bakedOutputDir, const QUrl& destinationPath) :
|
||||
MaterialBaker::MaterialBaker(const QString& materialData, bool isURL, const QString& bakedOutputDir, QUrl destinationPath) :
|
||||
_materialData(materialData),
|
||||
_isURL(isURL),
|
||||
_destinationPath(destinationPath),
|
||||
_bakedOutputDir(bakedOutputDir),
|
||||
_textureOutputDir(bakedOutputDir + "/materialTextures/" + QString::number(materialNum++)),
|
||||
_destinationPath(destinationPath)
|
||||
_textureOutputDir(bakedOutputDir + "/materialTextures/" + QString::number(materialNum++))
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -64,6 +55,14 @@ void MaterialBaker::bake() {
|
|||
}
|
||||
}
|
||||
|
||||
void MaterialBaker::abort() {
|
||||
Baker::abort();
|
||||
|
||||
for (auto& textureBaker : _textureBakers) {
|
||||
textureBaker->abort();
|
||||
}
|
||||
}
|
||||
|
||||
void MaterialBaker::loadMaterial() {
|
||||
if (!_isURL) {
|
||||
qCDebug(material_baking) << "Loading local material" << _materialData;
|
||||
|
@ -104,52 +103,54 @@ void MaterialBaker::processMaterial() {
|
|||
|
||||
for (auto networkMaterial : _materialResource->parsedMaterials.networkMaterials) {
|
||||
if (networkMaterial.second) {
|
||||
auto textureMaps = networkMaterial.second->getTextureMaps();
|
||||
for (auto textureMap : textureMaps) {
|
||||
if (textureMap.second && textureMap.second->getTextureSource()) {
|
||||
graphics::Material::MapChannel mapChannel = textureMap.first;
|
||||
auto texture = textureMap.second->getTextureSource();
|
||||
auto textures = networkMaterial.second->getTextures();
|
||||
for (auto texturePair : textures) {
|
||||
auto mapChannel = texturePair.first;
|
||||
auto textureMap = texturePair.second;
|
||||
if (textureMap.texture && textureMap.texture->_textureSource) {
|
||||
auto type = textureMap.texture->getTextureType();
|
||||
|
||||
QUrl url = texture->getUrl();
|
||||
QString cleanURL = url.adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment).toDisplayString();
|
||||
QByteArray content;
|
||||
QUrl textureURL;
|
||||
{
|
||||
bool foundEmbeddedTexture = false;
|
||||
auto textureContentMapIter = _textureContentMap.find(networkMaterial.second->getName());
|
||||
if (textureContentMapIter != _textureContentMap.end()) {
|
||||
auto textureUsageIter = textureContentMapIter->second.find(type);
|
||||
if (textureUsageIter != textureContentMapIter->second.end()) {
|
||||
content = textureUsageIter->second.first;
|
||||
textureURL = textureUsageIter->second.second;
|
||||
foundEmbeddedTexture = true;
|
||||
}
|
||||
}
|
||||
if (!foundEmbeddedTexture && textureMap.texture->_textureSource) {
|
||||
textureURL = textureMap.texture->_textureSource->getUrl().adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment);
|
||||
}
|
||||
}
|
||||
|
||||
QString cleanURL = textureURL.toDisplayString();
|
||||
auto idx = cleanURL.lastIndexOf('.');
|
||||
auto extension = idx >= 0 ? url.toDisplayString().mid(idx + 1).toLower() : "";
|
||||
QString extension = idx >= 0 ? cleanURL.mid(idx + 1).toLower() : "";
|
||||
|
||||
if (QImageReader::supportedImageFormats().contains(extension.toLatin1())) {
|
||||
QUrl textureURL = url.adjusted(QUrl::RemoveQuery | QUrl::RemoveFragment);
|
||||
|
||||
// FIXME: this isn't properly handling bumpMaps or glossMaps
|
||||
static std::unordered_map<graphics::Material::MapChannel, image::TextureUsage::Type> MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP;
|
||||
if (MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP.empty()) {
|
||||
MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::EMISSIVE_MAP] = image::TextureUsage::EMISSIVE_TEXTURE;
|
||||
MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::ALBEDO_MAP] = image::TextureUsage::ALBEDO_TEXTURE;
|
||||
MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::METALLIC_MAP] = image::TextureUsage::METALLIC_TEXTURE;
|
||||
MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::ROUGHNESS_MAP] = image::TextureUsage::ROUGHNESS_TEXTURE;
|
||||
MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::NORMAL_MAP] = image::TextureUsage::NORMAL_TEXTURE;
|
||||
MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::OCCLUSION_MAP] = image::TextureUsage::OCCLUSION_TEXTURE;
|
||||
MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::LIGHTMAP_MAP] = image::TextureUsage::LIGHTMAP_TEXTURE;
|
||||
MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP[graphics::Material::MapChannel::SCATTERING_MAP] = image::TextureUsage::SCATTERING_TEXTURE;
|
||||
}
|
||||
|
||||
auto it = MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP.find(mapChannel);
|
||||
if (it == MAP_CHANNEL_TO_TEXTURE_USAGE_TYPE_MAP.end()) {
|
||||
handleError("Unknown map channel");
|
||||
return;
|
||||
}
|
||||
|
||||
QPair<QUrl, image::TextureUsage::Type> textureKey(textureURL, it->second);
|
||||
QPair<QUrl, image::TextureUsage::Type> textureKey(textureURL, type);
|
||||
if (!_textureBakers.contains(textureKey)) {
|
||||
auto baseTextureFileName = _textureFileNamer.createBaseTextureFileName(textureURL.fileName(), it->second);
|
||||
auto baseTextureFileName = _textureFileNamer.createBaseTextureFileName(textureURL.fileName(), type);
|
||||
|
||||
QSharedPointer<TextureBaker> textureBaker {
|
||||
new TextureBaker(textureURL, it->second, _textureOutputDir, "", baseTextureFileName),
|
||||
new TextureBaker(textureURL, type, _textureOutputDir, "", baseTextureFileName, content),
|
||||
&TextureBaker::deleteLater
|
||||
};
|
||||
textureBaker->setMapChannel(mapChannel);
|
||||
connect(textureBaker.data(), &TextureBaker::finished, this, &MaterialBaker::handleFinishedTextureBaker);
|
||||
_textureBakers.insert(textureKey, textureBaker);
|
||||
textureBaker->moveToThread(_getNextOvenWorkerThreadOperator ? _getNextOvenWorkerThreadOperator() : thread());
|
||||
QMetaObject::invokeMethod(textureBaker.data(), "bake");
|
||||
// By default, Qt will invoke this bake immediately if the TextureBaker is on the same worker thread as this MaterialBaker.
|
||||
// We don't want that, because threads may be waiting for work while this thread is stuck processing a TextureBaker.
|
||||
// On top of that, _textureBakers isn't fully populated.
|
||||
// So, use Qt::QueuedConnection.
|
||||
// TODO: Better thread utilization at the top level, not just the MaterialBaker level
|
||||
QMetaObject::invokeMethod(textureBaker.data(), "bake", Qt::QueuedConnection);
|
||||
}
|
||||
_materialsNeedingRewrite.insert(textureKey, networkMaterial.second);
|
||||
} else {
|
||||
|
@ -177,9 +178,13 @@ void MaterialBaker::handleFinishedTextureBaker() {
|
|||
auto newURL = QUrl(_textureOutputDir).resolved(baker->getMetaTextureFileName());
|
||||
auto relativeURL = QDir(_bakedOutputDir).relativeFilePath(newURL.toString());
|
||||
|
||||
if (!_destinationPath.isEmpty()) {
|
||||
relativeURL = _destinationPath.resolved(relativeURL).toDisplayString();
|
||||
}
|
||||
|
||||
// Replace the old texture URLs
|
||||
for (auto networkMaterial : _materialsNeedingRewrite.values(textureKey)) {
|
||||
networkMaterial->getTextureMap(baker->getMapChannel())->getTextureSource()->setUrl(_destinationPath.resolved(relativeURL));
|
||||
networkMaterial->getTextureMap(baker->getMapChannel())->getTextureSource()->setUrl(relativeURL);
|
||||
}
|
||||
} else {
|
||||
// this texture failed to bake - this doesn't fail the entire bake but we need to add the errors from
|
||||
|
@ -245,3 +250,30 @@ void MaterialBaker::outputMaterial() {
|
|||
// emit signal to indicate the material baking is finished
|
||||
emit finished();
|
||||
}
|
||||
|
||||
void MaterialBaker::addTexture(const QString& materialName, image::TextureUsage::Type textureUsage, const hfm::Texture& texture) {
|
||||
auto& textureUsageMap = _textureContentMap[materialName.toStdString()];
|
||||
if (textureUsageMap.find(textureUsage) == textureUsageMap.end() && !texture.content.isEmpty()) {
|
||||
textureUsageMap[textureUsage] = { texture.content, texture.filename };
|
||||
}
|
||||
};
|
||||
|
||||
void MaterialBaker::setMaterials(const QHash<QString, hfm::Material>& materials, const QString& baseURL) {
|
||||
_materialResource = NetworkMaterialResourcePointer(new NetworkMaterialResource(), [](NetworkMaterialResource* ptr) { ptr->deleteLater(); });
|
||||
for (auto& material : materials) {
|
||||
_materialResource->parsedMaterials.names.push_back(material.name.toStdString());
|
||||
_materialResource->parsedMaterials.networkMaterials[material.name.toStdString()] = std::make_shared<NetworkMaterial>(material, baseURL);
|
||||
|
||||
// Store any embedded texture content
|
||||
addTexture(material.name, image::TextureUsage::NORMAL_TEXTURE, material.normalTexture);
|
||||
addTexture(material.name, image::TextureUsage::ALBEDO_TEXTURE, material.albedoTexture);
|
||||
addTexture(material.name, image::TextureUsage::GLOSS_TEXTURE, material.glossTexture);
|
||||
addTexture(material.name, image::TextureUsage::ROUGHNESS_TEXTURE, material.roughnessTexture);
|
||||
addTexture(material.name, image::TextureUsage::SPECULAR_TEXTURE, material.specularTexture);
|
||||
addTexture(material.name, image::TextureUsage::METALLIC_TEXTURE, material.metallicTexture);
|
||||
addTexture(material.name, image::TextureUsage::EMISSIVE_TEXTURE, material.emissiveTexture);
|
||||
addTexture(material.name, image::TextureUsage::OCCLUSION_TEXTURE, material.occlusionTexture);
|
||||
addTexture(material.name, image::TextureUsage::SCATTERING_TEXTURE, material.scatteringTexture);
|
||||
addTexture(material.name, image::TextureUsage::LIGHTMAP_TEXTURE, material.lightmapTexture);
|
||||
}
|
||||
}
|
|
@ -24,16 +24,19 @@ static const QString BAKED_MATERIAL_EXTENSION = ".baked.json";
|
|||
class MaterialBaker : public Baker {
|
||||
Q_OBJECT
|
||||
public:
|
||||
MaterialBaker(const QString& materialData, bool isURL, const QString& bakedOutputDir, const QUrl& destinationPath);
|
||||
MaterialBaker(const QString& materialData, bool isURL, const QString& bakedOutputDir, QUrl destinationPath = QUrl());
|
||||
|
||||
QString getMaterialData() const { return _materialData; }
|
||||
bool isURL() const { return _isURL; }
|
||||
QString getBakedMaterialData() const { return _bakedMaterialData; }
|
||||
|
||||
void setMaterials(const QHash<QString, hfm::Material>& materials, const QString& baseURL);
|
||||
|
||||
static void setNextOvenWorkerThreadOperator(std::function<QThread*()> getNextOvenWorkerThreadOperator) { _getNextOvenWorkerThreadOperator = getNextOvenWorkerThreadOperator; }
|
||||
|
||||
public slots:
|
||||
virtual void bake() override;
|
||||
virtual void abort() override;
|
||||
|
||||
signals:
|
||||
void originalMaterialLoaded();
|
||||
|
@ -48,6 +51,7 @@ private:
|
|||
|
||||
QString _materialData;
|
||||
bool _isURL;
|
||||
QUrl _destinationPath;
|
||||
|
||||
NetworkMaterialResourcePointer _materialResource;
|
||||
|
||||
|
@ -57,11 +61,18 @@ private:
|
|||
QString _bakedOutputDir;
|
||||
QString _textureOutputDir;
|
||||
QString _bakedMaterialData;
|
||||
QUrl _destinationPath;
|
||||
|
||||
QScriptEngine _scriptEngine;
|
||||
static std::function<QThread*()> _getNextOvenWorkerThreadOperator;
|
||||
TextureFileNamer _textureFileNamer;
|
||||
|
||||
void addTexture(const QString& materialName, image::TextureUsage::Type textureUsage, const hfm::Texture& texture);
|
||||
struct TextureUsageHash {
|
||||
std::size_t operator()(image::TextureUsage::Type textureUsage) const {
|
||||
return static_cast<std::size_t>(textureUsage);
|
||||
}
|
||||
};
|
||||
std::unordered_map<std::string, std::unordered_map<image::TextureUsage::Type, std::pair<QByteArray, QString>, TextureUsageHash>> _textureContentMap;
|
||||
};
|
||||
|
||||
#endif // !hifi_MaterialBaker_h
|
||||
|
|
|
@ -42,12 +42,12 @@
|
|||
|
||||
#include "baking/BakerLibrary.h"
|
||||
|
||||
ModelBaker::ModelBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputTextureThreadGetter,
|
||||
const QString& bakedOutputDirectory, const QString& originalOutputDirectory, bool hasBeenBaked) :
|
||||
#include <QJsonArray>
|
||||
|
||||
ModelBaker::ModelBaker(const QUrl& inputModelURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory, bool hasBeenBaked) :
|
||||
_modelURL(inputModelURL),
|
||||
_bakedOutputDir(bakedOutputDirectory),
|
||||
_originalOutputDir(originalOutputDirectory),
|
||||
_textureThreadGetter(inputTextureThreadGetter),
|
||||
_hasBeenBaked(hasBeenBaked)
|
||||
{
|
||||
auto bakedFilename = _modelURL.fileName();
|
||||
|
@ -167,6 +167,10 @@ void ModelBaker::saveSourceModel() {
|
|||
|
||||
connect(networkReply, &QNetworkReply::finished, this, &ModelBaker::handleModelNetworkReply);
|
||||
}
|
||||
|
||||
if (_mappingURL.isEmpty()) {
|
||||
outputUnbakedFST();
|
||||
}
|
||||
}
|
||||
|
||||
void ModelBaker::handleModelNetworkReply() {
|
||||
|
@ -209,7 +213,6 @@ void ModelBaker::bakeSourceCopy() {
|
|||
}
|
||||
hifi::ByteArray modelData = modelFile.readAll();
|
||||
|
||||
hfm::Model::Pointer bakedModel;
|
||||
std::vector<hifi::ByteArray> dracoMeshes;
|
||||
std::vector<std::vector<hifi::ByteArray>> dracoMaterialLists; // Material order for per-mesh material lookup used by dracoMeshes
|
||||
|
||||
|
@ -245,40 +248,107 @@ void ModelBaker::bakeSourceCopy() {
|
|||
// Begin hfm baking
|
||||
baker.run();
|
||||
|
||||
bakedModel = baker.getHFMModel();
|
||||
_hfmModel = baker.getHFMModel();
|
||||
dracoMeshes = baker.getDracoMeshes();
|
||||
dracoMaterialLists = baker.getDracoMaterialLists();
|
||||
}
|
||||
|
||||
// Populate _textureContentMap with path to content mappings, for quick lookup by URL
|
||||
for (auto materialIt = bakedModel->materials.cbegin(); materialIt != bakedModel->materials.cend(); materialIt++) {
|
||||
static const auto addTexture = [](QHash<hifi::ByteArray, hifi::ByteArray>& textureContentMap, const hfm::Texture& texture) {
|
||||
if (!textureContentMap.contains(texture.filename)) {
|
||||
// Content may be empty, unless the data is inlined
|
||||
textureContentMap[texture.filename] = texture.content;
|
||||
}
|
||||
};
|
||||
const hfm::Material& material = *materialIt;
|
||||
addTexture(_textureContentMap, material.normalTexture);
|
||||
addTexture(_textureContentMap, material.albedoTexture);
|
||||
addTexture(_textureContentMap, material.opacityTexture);
|
||||
addTexture(_textureContentMap, material.glossTexture);
|
||||
addTexture(_textureContentMap, material.roughnessTexture);
|
||||
addTexture(_textureContentMap, material.specularTexture);
|
||||
addTexture(_textureContentMap, material.metallicTexture);
|
||||
addTexture(_textureContentMap, material.emissiveTexture);
|
||||
addTexture(_textureContentMap, material.occlusionTexture);
|
||||
addTexture(_textureContentMap, material.scatteringTexture);
|
||||
addTexture(_textureContentMap, material.lightmapTexture);
|
||||
}
|
||||
|
||||
// Do format-specific baking
|
||||
bakeProcessedSource(bakedModel, dracoMeshes, dracoMaterialLists);
|
||||
bakeProcessedSource(_hfmModel, dracoMeshes, dracoMaterialLists);
|
||||
|
||||
if (shouldStop()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_hfmModel->materials.size() > 0) {
|
||||
_materialBaker = QSharedPointer<MaterialBaker>(
|
||||
new MaterialBaker(_modelURL.fileName(), true, _bakedOutputDir),
|
||||
&MaterialBaker::deleteLater
|
||||
);
|
||||
_materialBaker->setMaterials(_hfmModel->materials, _modelURL.toString());
|
||||
connect(_materialBaker.data(), &MaterialBaker::finished, this, &ModelBaker::handleFinishedMaterialBaker);
|
||||
_materialBaker->bake();
|
||||
} else {
|
||||
outputBakedFST();
|
||||
}
|
||||
}
|
||||
|
||||
void ModelBaker::handleFinishedMaterialBaker() {
|
||||
auto baker = qobject_cast<MaterialBaker*>(sender());
|
||||
|
||||
if (baker) {
|
||||
if (!baker->hasErrors()) {
|
||||
// this MaterialBaker is done and everything went according to plan
|
||||
qCDebug(model_baking) << "Adding baked material to FST mapping " << baker->getBakedMaterialData();
|
||||
|
||||
QString relativeBakedMaterialURL = _modelURL.fileName();
|
||||
auto baseName = relativeBakedMaterialURL.left(relativeBakedMaterialURL.lastIndexOf('.'));
|
||||
relativeBakedMaterialURL = baseName + BAKED_MATERIAL_EXTENSION;
|
||||
|
||||
// First we add the materials in the model
|
||||
QJsonArray materialMapping;
|
||||
for (auto material : _hfmModel->materials) {
|
||||
QJsonObject json;
|
||||
json["mat::" + material.name] = relativeBakedMaterialURL + "#" + material.name;
|
||||
materialMapping.push_back(json);
|
||||
}
|
||||
|
||||
// The we add any existing mappings from the mapping
|
||||
if (_mapping.contains(MATERIAL_MAPPING_FIELD)) {
|
||||
QByteArray materialMapValue = _mapping[MATERIAL_MAPPING_FIELD].toByteArray();
|
||||
QJsonObject oldMaterialMapping = QJsonDocument::fromJson(materialMapValue).object();
|
||||
for (auto key : oldMaterialMapping.keys()) {
|
||||
QJsonObject json;
|
||||
json[key] = oldMaterialMapping[key];
|
||||
materialMapping.push_back(json);
|
||||
}
|
||||
}
|
||||
|
||||
_mapping[MATERIAL_MAPPING_FIELD] = QJsonDocument(materialMapping).toJson(QJsonDocument::Compact);
|
||||
} else {
|
||||
// this material failed to bake - this doesn't fail the entire bake but we need to add the errors from
|
||||
// the material to our warnings
|
||||
_warningList << baker->getWarnings();
|
||||
}
|
||||
} else {
|
||||
handleWarning("Failed to bake the materials for model with URL " + _modelURL.toString());
|
||||
}
|
||||
|
||||
outputBakedFST();
|
||||
}
|
||||
|
||||
void ModelBaker::outputUnbakedFST() {
|
||||
// Output an unbaked FST file in the original output folder to make it easier for FSTBaker to rebake this model
|
||||
// TODO: Consider a more robust method that does not depend on FSTBaker navigating to a hardcoded relative path
|
||||
QString outputFSTFilename = _modelURL.fileName();
|
||||
auto extensionStart = outputFSTFilename.indexOf(".");
|
||||
if (extensionStart != -1) {
|
||||
outputFSTFilename.resize(extensionStart);
|
||||
}
|
||||
outputFSTFilename += FST_EXTENSION;
|
||||
QString outputFSTURL = _originalOutputDir + "/" + outputFSTFilename;
|
||||
|
||||
hifi::VariantHash outputMapping;
|
||||
outputMapping[FST_VERSION_FIELD] = FST_VERSION;
|
||||
outputMapping[FILENAME_FIELD] = _modelURL.fileName();
|
||||
outputMapping[COMMENT_FIELD] = "This FST file was generated by Oven for use during rebaking. It is not part of the original model. This file's existence is subject to change.";
|
||||
hifi::ByteArray fstOut = FSTReader::writeMapping(outputMapping);
|
||||
|
||||
QFile fstOutputFile { outputFSTURL };
|
||||
if (fstOutputFile.exists()) {
|
||||
handleWarning("The file '" + outputFSTURL + "' already exists. Should that be baked instead of '" + _modelURL.toString() + "'?");
|
||||
return;
|
||||
}
|
||||
if (!fstOutputFile.open(QIODevice::WriteOnly)) {
|
||||
handleWarning("Failed to open file '" + outputFSTURL + "' for writing. Rebaking may fail on the associated model.");
|
||||
return;
|
||||
}
|
||||
if (fstOutputFile.write(fstOut) == -1) {
|
||||
handleWarning("Failed to write to file '" + outputFSTURL + "'. Rebaking may fail on the associated model.");
|
||||
}
|
||||
}
|
||||
|
||||
void ModelBaker::outputBakedFST() {
|
||||
// Output FST file, copying over input mappings if available
|
||||
QString outputFSTFilename = !_mappingURL.isEmpty() ? _mappingURL.fileName() : _modelURL.fileName();
|
||||
auto extensionStart = outputFSTFilename.indexOf(".");
|
||||
|
@ -291,8 +361,8 @@ void ModelBaker::bakeSourceCopy() {
|
|||
auto outputMapping = _mapping;
|
||||
outputMapping[FST_VERSION_FIELD] = FST_VERSION;
|
||||
outputMapping[FILENAME_FIELD] = _bakedModelURL.fileName();
|
||||
// All textures will be found in the same directory as the model
|
||||
outputMapping[TEXDIR_FIELD] = ".";
|
||||
outputMapping.remove(TEXDIR_FIELD);
|
||||
outputMapping.remove(COMMENT_FIELD);
|
||||
hifi::ByteArray fstOut = FSTReader::writeMapping(outputMapping);
|
||||
|
||||
QFile fstOutputFile { outputFSTURL };
|
||||
|
@ -307,17 +377,16 @@ void ModelBaker::bakeSourceCopy() {
|
|||
_outputFiles.push_back(outputFSTURL);
|
||||
_outputMappingURL = outputFSTURL;
|
||||
|
||||
// check if we're already done with textures (in case we had none to re-write)
|
||||
checkIfTexturesFinished();
|
||||
exportScene();
|
||||
qCDebug(model_baking) << "Finished baking, emitting finished" << _modelURL;
|
||||
emit finished();
|
||||
}
|
||||
|
||||
void ModelBaker::abort() {
|
||||
Baker::abort();
|
||||
|
||||
// tell our underlying TextureBaker instances to abort
|
||||
// the ModelBaker will wait until all are aborted before emitting its own abort signal
|
||||
for (auto& textureBaker : _bakingTextures) {
|
||||
textureBaker->abort();
|
||||
if (_materialBaker) {
|
||||
_materialBaker->abort();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -354,247 +423,6 @@ bool ModelBaker::buildDracoMeshNode(FBXNode& dracoMeshNode, const QByteArray& dr
|
|||
return true;
|
||||
}
|
||||
|
||||
QString ModelBaker::compressTexture(QString modelTextureFileName, image::TextureUsage::Type textureType) {
|
||||
|
||||
QFileInfo modelTextureFileInfo { modelTextureFileName.replace("\\", "/") };
|
||||
|
||||
if (modelTextureFileInfo.suffix().toLower() == BAKED_TEXTURE_KTX_EXT.mid(1)) {
|
||||
// re-baking a model that already references baked textures
|
||||
// this is an error - return from here
|
||||
handleError("Cannot re-bake a file that already references compressed textures");
|
||||
return QString::null;
|
||||
}
|
||||
|
||||
if (!image::getSupportedFormats().contains(modelTextureFileInfo.suffix())) {
|
||||
// this is a texture format we don't bake, skip it
|
||||
handleWarning(modelTextureFileName + " is not a bakeable texture format");
|
||||
return QString::null;
|
||||
}
|
||||
|
||||
// make sure this texture points to something and isn't one we've already re-mapped
|
||||
QString textureChild { QString::null };
|
||||
if (!modelTextureFileInfo.filePath().isEmpty()) {
|
||||
// check if this was an embedded texture that we already have in-memory content for
|
||||
QByteArray textureContent;
|
||||
|
||||
// figure out the URL to this texture, embedded or external
|
||||
if (!modelTextureFileInfo.filePath().isEmpty()) {
|
||||
textureContent = _textureContentMap.value(modelTextureFileName.toLocal8Bit());
|
||||
}
|
||||
auto urlToTexture = getTextureURL(modelTextureFileInfo, !textureContent.isNull());
|
||||
|
||||
TextureKey textureKey { urlToTexture, textureType };
|
||||
auto bakingTextureIt = _bakingTextures.find(textureKey);
|
||||
if (bakingTextureIt == _bakingTextures.cend()) {
|
||||
// construct the new baked texture file name and file path
|
||||
// ensuring that the baked texture will have a unique name
|
||||
// even if there was another texture with the same name at a different path
|
||||
QString baseTextureFileName = _textureFileNamer.createBaseTextureFileName(modelTextureFileInfo, textureType);
|
||||
|
||||
QString bakedTextureFilePath {
|
||||
_bakedOutputDir + "/" + baseTextureFileName + BAKED_META_TEXTURE_SUFFIX
|
||||
};
|
||||
|
||||
textureChild = baseTextureFileName + BAKED_META_TEXTURE_SUFFIX;
|
||||
|
||||
_outputFiles.push_back(bakedTextureFilePath);
|
||||
|
||||
// bake this texture asynchronously
|
||||
bakeTexture(textureKey, _bakedOutputDir, baseTextureFileName, textureContent);
|
||||
} else {
|
||||
// Fetch existing texture meta name
|
||||
textureChild = (*bakingTextureIt)->getBaseFilename() + BAKED_META_TEXTURE_SUFFIX;
|
||||
}
|
||||
}
|
||||
|
||||
qCDebug(model_baking).noquote() << "Re-mapping" << modelTextureFileName
|
||||
<< "to" << textureChild;
|
||||
|
||||
return textureChild;
|
||||
}
|
||||
|
||||
void ModelBaker::bakeTexture(const TextureKey& textureKey, const QDir& outputDir, const QString& bakedFilename, const QByteArray& textureContent) {
|
||||
// start a bake for this texture and add it to our list to keep track of
|
||||
QSharedPointer<TextureBaker> bakingTexture{
|
||||
new TextureBaker(textureKey.first, textureKey.second, outputDir, "../", bakedFilename, textureContent),
|
||||
&TextureBaker::deleteLater
|
||||
};
|
||||
|
||||
// make sure we hear when the baking texture is done or aborted
|
||||
connect(bakingTexture.data(), &Baker::finished, this, &ModelBaker::handleBakedTexture);
|
||||
connect(bakingTexture.data(), &TextureBaker::aborted, this, &ModelBaker::handleAbortedTexture);
|
||||
|
||||
// keep a shared pointer to the baking texture
|
||||
_bakingTextures.insert(textureKey, bakingTexture);
|
||||
|
||||
// start baking the texture on one of our available worker threads
|
||||
bakingTexture->moveToThread(_textureThreadGetter());
|
||||
QMetaObject::invokeMethod(bakingTexture.data(), "bake");
|
||||
}
|
||||
|
||||
void ModelBaker::handleBakedTexture() {
|
||||
TextureBaker* bakedTexture = qobject_cast<TextureBaker*>(sender());
|
||||
qDebug() << "Handling baked texture" << bakedTexture->getTextureURL();
|
||||
|
||||
// make sure we haven't already run into errors, and that this is a valid texture
|
||||
if (bakedTexture) {
|
||||
if (!shouldStop()) {
|
||||
if (!bakedTexture->hasErrors()) {
|
||||
if (!_originalOutputDir.isEmpty()) {
|
||||
// we've been asked to make copies of the originals, so we need to make copies of this if it is a linked texture
|
||||
|
||||
// use the path to the texture being baked to determine if this was an embedded or a linked texture
|
||||
|
||||
// it is embeddded if the texure being baked was inside a folder with the name of the model
|
||||
// since that is the fake URL we provide when baking external textures
|
||||
|
||||
if (!_modelURL.isParentOf(bakedTexture->getTextureURL())) {
|
||||
// for linked textures we want to save a copy of original texture beside the original model
|
||||
|
||||
qCDebug(model_baking) << "Saving original texture for" << bakedTexture->getTextureURL();
|
||||
|
||||
// check if we have a relative path to use for the texture
|
||||
auto relativeTexturePath = texturePathRelativeToModel(_modelURL, bakedTexture->getTextureURL());
|
||||
|
||||
QFile originalTextureFile{
|
||||
_originalOutputDir + "/" + relativeTexturePath + bakedTexture->getTextureURL().fileName()
|
||||
};
|
||||
|
||||
if (relativeTexturePath.length() > 0) {
|
||||
// make the folders needed by the relative path
|
||||
}
|
||||
|
||||
if (originalTextureFile.open(QIODevice::WriteOnly) && originalTextureFile.write(bakedTexture->getOriginalTexture()) != -1) {
|
||||
qCDebug(model_baking) << "Saved original texture file" << originalTextureFile.fileName()
|
||||
<< "for" << _modelURL;
|
||||
} else {
|
||||
handleError("Could not save original external texture " + originalTextureFile.fileName()
|
||||
+ " for " + _modelURL.toString());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// now that this texture has been baked and handled, we can remove that TextureBaker from our hash
|
||||
_bakingTextures.remove({ bakedTexture->getTextureURL(), bakedTexture->getTextureType() });
|
||||
|
||||
checkIfTexturesFinished();
|
||||
} else {
|
||||
// there was an error baking this texture - add it to our list of errors
|
||||
_errorList.append(bakedTexture->getErrors());
|
||||
|
||||
// we don't emit finished yet so that the other textures can finish baking first
|
||||
_pendingErrorEmission = true;
|
||||
|
||||
// now that this texture has been baked, even though it failed, we can remove that TextureBaker from our list
|
||||
_bakingTextures.remove({ bakedTexture->getTextureURL(), bakedTexture->getTextureType() });
|
||||
|
||||
// abort any other ongoing texture bakes since we know we'll end up failing
|
||||
for (auto& bakingTexture : _bakingTextures) {
|
||||
bakingTexture->abort();
|
||||
}
|
||||
|
||||
checkIfTexturesFinished();
|
||||
}
|
||||
} else {
|
||||
// we have errors to attend to, so we don't do extra processing for this texture
|
||||
// but we do need to remove that TextureBaker from our list
|
||||
// and then check if we're done with all textures
|
||||
_bakingTextures.remove({ bakedTexture->getTextureURL(), bakedTexture->getTextureType() });
|
||||
|
||||
checkIfTexturesFinished();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModelBaker::handleAbortedTexture() {
|
||||
// grab the texture bake that was aborted and remove it from our hash since we don't need to track it anymore
|
||||
TextureBaker* bakedTexture = qobject_cast<TextureBaker*>(sender());
|
||||
|
||||
qDebug() << "Texture aborted: " << bakedTexture->getTextureURL();
|
||||
|
||||
if (bakedTexture) {
|
||||
_bakingTextures.remove({ bakedTexture->getTextureURL(), bakedTexture->getTextureType() });
|
||||
}
|
||||
|
||||
// since a texture we were baking aborted, our status is also aborted
|
||||
_shouldAbort.store(true);
|
||||
|
||||
// abort any other ongoing texture bakes since we know we'll end up failing
|
||||
for (auto& bakingTexture : _bakingTextures) {
|
||||
bakingTexture->abort();
|
||||
}
|
||||
|
||||
checkIfTexturesFinished();
|
||||
}
|
||||
|
||||
QUrl ModelBaker::getTextureURL(const QFileInfo& textureFileInfo, bool isEmbedded) {
|
||||
QUrl urlToTexture;
|
||||
|
||||
if (isEmbedded) {
|
||||
urlToTexture = _modelURL.toString() + "/" + textureFileInfo.filePath();
|
||||
} else {
|
||||
if (textureFileInfo.exists() && textureFileInfo.isFile()) {
|
||||
// set the texture URL to the local texture that we have confirmed exists
|
||||
urlToTexture = QUrl::fromLocalFile(textureFileInfo.absoluteFilePath());
|
||||
} else {
|
||||
// external texture that we'll need to download or find
|
||||
|
||||
// this is a relative file path which will require different handling
|
||||
// depending on the location of the original model
|
||||
if (_modelURL.isLocalFile() && textureFileInfo.exists() && textureFileInfo.isFile()) {
|
||||
// the absolute path we ran into for the texture in the model exists on this machine
|
||||
// so use that file
|
||||
urlToTexture = QUrl::fromLocalFile(textureFileInfo.absoluteFilePath());
|
||||
} else {
|
||||
// we didn't find the texture on this machine at the absolute path
|
||||
// so assume that it is right beside the model to match the behaviour of interface
|
||||
urlToTexture = _modelURL.resolved(textureFileInfo.fileName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return urlToTexture;
|
||||
}
|
||||
|
||||
QString ModelBaker::texturePathRelativeToModel(QUrl modelURL, QUrl textureURL) {
|
||||
auto modelPath = modelURL.toString(QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment);
|
||||
auto texturePath = textureURL.toString(QUrl::RemoveFilename | QUrl::RemoveQuery | QUrl::RemoveFragment);
|
||||
|
||||
if (texturePath.startsWith(modelPath)) {
|
||||
// texture path is a child of the model path, return the texture path without the model path
|
||||
return texturePath.mid(modelPath.length());
|
||||
} else {
|
||||
// the texture path was not a child of the model path, return the empty string
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
void ModelBaker::checkIfTexturesFinished() {
|
||||
// check if we're done everything we need to do for this model
|
||||
// and emit our finished signal if we're done
|
||||
|
||||
if (_bakingTextures.isEmpty()) {
|
||||
if (shouldStop()) {
|
||||
// if we're checking for completion but we have errors
|
||||
// that means one or more of our texture baking operations failed
|
||||
|
||||
if (_pendingErrorEmission) {
|
||||
setIsFinished(true);
|
||||
}
|
||||
|
||||
return;
|
||||
} else {
|
||||
qCDebug(model_baking) << "Finished baking, emitting finished" << _modelURL;
|
||||
|
||||
texturesFinished();
|
||||
|
||||
setIsFinished(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModelBaker::setWasAborted(bool wasAborted) {
|
||||
if (wasAborted != _wasAborted.load()) {
|
||||
Baker::setWasAborted(wasAborted);
|
||||
|
@ -605,70 +433,6 @@ void ModelBaker::setWasAborted(bool wasAborted) {
|
|||
}
|
||||
}
|
||||
|
||||
void ModelBaker::texturesFinished() {
|
||||
embedTextureMetaData();
|
||||
exportScene();
|
||||
}
|
||||
|
||||
void ModelBaker::embedTextureMetaData() {
|
||||
std::vector<FBXNode> embeddedTextureNodes;
|
||||
|
||||
for (FBXNode& rootChild : _rootNode.children) {
|
||||
if (rootChild.name == "Objects") {
|
||||
qlonglong maxId = 0;
|
||||
for (auto &child : rootChild.children) {
|
||||
if (child.properties.length() == 3) {
|
||||
maxId = std::max(maxId, child.properties[0].toLongLong());
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& object : rootChild.children) {
|
||||
if (object.name == "Texture") {
|
||||
QVariant relativeFilename;
|
||||
for (auto& child : object.children) {
|
||||
if (child.name == "RelativeFilename") {
|
||||
relativeFilename = child.properties[0];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (relativeFilename.isNull()
|
||||
|| !relativeFilename.toString().endsWith(BAKED_META_TEXTURE_SUFFIX)) {
|
||||
continue;
|
||||
}
|
||||
if (object.properties.length() < 2) {
|
||||
qWarning() << "Found texture with unexpected number of properties: " << object.name;
|
||||
continue;
|
||||
}
|
||||
|
||||
FBXNode videoNode;
|
||||
videoNode.name = "Video";
|
||||
videoNode.properties.append(++maxId);
|
||||
videoNode.properties.append(object.properties[1]);
|
||||
videoNode.properties.append("Clip");
|
||||
|
||||
QString bakedTextureFilePath {
|
||||
_bakedOutputDir + "/" + relativeFilename.toString()
|
||||
};
|
||||
|
||||
QFile textureFile { bakedTextureFilePath };
|
||||
if (!textureFile.open(QIODevice::ReadOnly)) {
|
||||
qWarning() << "Failed to open: " << bakedTextureFilePath;
|
||||
continue;
|
||||
}
|
||||
|
||||
videoNode.children.append({ "RelativeFilename", { relativeFilename }, { } });
|
||||
videoNode.children.append({ "Content", { textureFile.readAll() }, { } });
|
||||
|
||||
rootChild.children.append(videoNode);
|
||||
|
||||
textureFile.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ModelBaker::exportScene() {
|
||||
auto fbxData = FBXWriter::encodeFBX(_rootNode);
|
||||
|
||||
|
|
|
@ -18,17 +18,13 @@
|
|||
#include <QtNetwork/QNetworkReply>
|
||||
|
||||
#include "Baker.h"
|
||||
#include "TextureBaker.h"
|
||||
#include "baking/TextureFileNamer.h"
|
||||
#include "MaterialBaker.h"
|
||||
|
||||
#include "ModelBakingLoggingCategory.h"
|
||||
|
||||
#include <gpu/Texture.h>
|
||||
|
||||
#include <FBX.h>
|
||||
#include <hfm/HFM.h>
|
||||
|
||||
using TextureBakerThreadGetter = std::function<QThread*()>;
|
||||
using GetMaterialIDCallback = std::function <int(int)>;
|
||||
|
||||
static const QString FST_EXTENSION { ".fst" };
|
||||
|
@ -42,10 +38,7 @@ class ModelBaker : public Baker {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
using TextureKey = QPair<QUrl, image::TextureUsage::Type>;
|
||||
|
||||
ModelBaker(const QUrl& inputModelURL, TextureBakerThreadGetter inputTextureThreadGetter,
|
||||
const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "", bool hasBeenBaked = false);
|
||||
ModelBaker(const QUrl& inputModelURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "", bool hasBeenBaked = false);
|
||||
|
||||
void setOutputURLSuffix(const QUrl& urlSuffix);
|
||||
void setMappingURL(const QUrl& mappingURL);
|
||||
|
@ -54,7 +47,6 @@ public:
|
|||
void initializeOutputDirs();
|
||||
|
||||
bool buildDracoMeshNode(FBXNode& dracoMeshNode, const QByteArray& dracoMeshBytes, const std::vector<hifi::ByteArray>& dracoMaterialList);
|
||||
QString compressTexture(QString textureFileName, image::TextureUsage::Type = image::TextureUsage::Type::DEFAULT_TEXTURE);
|
||||
virtual void setWasAborted(bool wasAborted) override;
|
||||
|
||||
QUrl getModelURL() const { return _modelURL; }
|
||||
|
@ -71,20 +63,15 @@ public slots:
|
|||
protected:
|
||||
void saveSourceModel();
|
||||
virtual void bakeProcessedSource(const hfm::Model::Pointer& hfmModel, const std::vector<hifi::ByteArray>& dracoMeshes, const std::vector<std::vector<hifi::ByteArray>>& dracoMaterialLists) = 0;
|
||||
void checkIfTexturesFinished();
|
||||
void texturesFinished();
|
||||
void embedTextureMetaData();
|
||||
void exportScene();
|
||||
|
||||
FBXNode _rootNode;
|
||||
QHash<QByteArray, QByteArray> _textureContentMap;
|
||||
QUrl _modelURL;
|
||||
QUrl _outputURLSuffix;
|
||||
QUrl _mappingURL;
|
||||
hifi::VariantHash _mapping;
|
||||
QString _bakedOutputDir;
|
||||
QString _originalOutputDir;
|
||||
TextureBakerThreadGetter _textureThreadGetter;
|
||||
QString _originalOutputModelPath;
|
||||
QString _outputMappingURL;
|
||||
QUrl _bakedModelURL;
|
||||
|
@ -92,23 +79,16 @@ protected:
|
|||
protected slots:
|
||||
void handleModelNetworkReply();
|
||||
virtual void bakeSourceCopy();
|
||||
|
||||
private slots:
|
||||
void handleBakedTexture();
|
||||
void handleAbortedTexture();
|
||||
void handleFinishedMaterialBaker();
|
||||
|
||||
private:
|
||||
QUrl getTextureURL(const QFileInfo& textureFileInfo, bool isEmbedded = false);
|
||||
void bakeTexture(const TextureKey& textureKey, const QDir& outputDir, const QString& bakedFilename, const QByteArray& textureContent);
|
||||
QString texturePathRelativeToModel(QUrl modelURL, QUrl textureURL);
|
||||
|
||||
QMultiHash<TextureKey, QSharedPointer<TextureBaker>> _bakingTextures;
|
||||
QHash<QString, int> _textureNameMatchCount;
|
||||
bool _pendingErrorEmission { false };
|
||||
void outputUnbakedFST();
|
||||
void outputBakedFST();
|
||||
|
||||
bool _hasBeenBaked { false };
|
||||
|
||||
TextureFileNamer _textureFileNamer;
|
||||
hfm::Model::Pointer _hfmModel;
|
||||
QSharedPointer<MaterialBaker> _materialBaker;
|
||||
};
|
||||
|
||||
#endif // hifi_ModelBaker_h
|
||||
|
|
|
@ -132,55 +132,6 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, const hfm::Model::Pointer& h
|
|||
handleWarning("Baked mesh for OBJ model '" + _modelURL.toString() + "' is empty");
|
||||
}
|
||||
|
||||
// Generating Texture Node
|
||||
// iterate through mesh parts and process the associated textures
|
||||
auto size = meshParts.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
QString material = meshParts[i].materialID;
|
||||
HFMMaterial currentMaterial = hfmModel->materials[material];
|
||||
if (!currentMaterial.albedoTexture.filename.isEmpty() || !currentMaterial.specularTexture.filename.isEmpty()) {
|
||||
auto textureID = nextNodeID();
|
||||
_mapTextureMaterial.emplace_back(textureID, i);
|
||||
|
||||
FBXNode textureNode;
|
||||
{
|
||||
textureNode.name = TEXTURE_NODE_NAME;
|
||||
textureNode.properties = { textureID, "texture" + QString::number(textureID) };
|
||||
}
|
||||
|
||||
// Texture node child - TextureName node
|
||||
FBXNode textureNameNode;
|
||||
{
|
||||
textureNameNode.name = TEXTURENAME_NODE_NAME;
|
||||
QByteArray propertyString = (!currentMaterial.albedoTexture.filename.isEmpty()) ? "Kd" : "Ka";
|
||||
textureNameNode.properties = { propertyString };
|
||||
}
|
||||
|
||||
// Texture node child - Relative Filename node
|
||||
FBXNode relativeFilenameNode;
|
||||
{
|
||||
relativeFilenameNode.name = RELATIVEFILENAME_NODE_NAME;
|
||||
}
|
||||
|
||||
QByteArray textureFileName = (!currentMaterial.albedoTexture.filename.isEmpty()) ? currentMaterial.albedoTexture.filename : currentMaterial.specularTexture.filename;
|
||||
|
||||
auto textureType = (!currentMaterial.albedoTexture.filename.isEmpty()) ? image::TextureUsage::Type::ALBEDO_TEXTURE : image::TextureUsage::Type::SPECULAR_TEXTURE;
|
||||
|
||||
// Compress the texture using ModelBaker::compressTexture() and store compressed file's name in the node
|
||||
auto textureFile = compressTexture(textureFileName, textureType);
|
||||
if (textureFile.isNull()) {
|
||||
// Baking failed return
|
||||
handleError("Failed to compress texture: " + textureFileName);
|
||||
return;
|
||||
}
|
||||
relativeFilenameNode.properties = { textureFile };
|
||||
|
||||
textureNode.children = { textureNameNode, relativeFilenameNode };
|
||||
|
||||
objectNode.children.append(textureNode);
|
||||
}
|
||||
}
|
||||
|
||||
// Generating Connections node
|
||||
connectionsNode.name = CONNECTIONS_NODE_NAME;
|
||||
|
||||
|
@ -199,29 +150,6 @@ void OBJBaker::createFBXNodeTree(FBXNode& rootNode, const hfm::Model::Pointer& h
|
|||
cNode.properties = { CONNECTIONS_NODE_PROPERTY, materialID, modelID };
|
||||
connectionsNode.children.append(cNode);
|
||||
}
|
||||
|
||||
// Connect textures to materials
|
||||
for (const auto& texMat : _mapTextureMaterial) {
|
||||
FBXNode cAmbientNode;
|
||||
cAmbientNode.name = C_NODE_NAME;
|
||||
cAmbientNode.properties = {
|
||||
CONNECTIONS_NODE_PROPERTY_1,
|
||||
texMat.first,
|
||||
_materialIDs[texMat.second],
|
||||
"AmbientFactor"
|
||||
};
|
||||
connectionsNode.children.append(cAmbientNode);
|
||||
|
||||
FBXNode cDiffuseNode;
|
||||
cDiffuseNode.name = C_NODE_NAME;
|
||||
cDiffuseNode.properties = {
|
||||
CONNECTIONS_NODE_PROPERTY_1,
|
||||
texMat.first,
|
||||
_materialIDs[texMat.second],
|
||||
"DiffuseColor"
|
||||
};
|
||||
connectionsNode.children.append(cDiffuseNode);
|
||||
}
|
||||
}
|
||||
|
||||
// Set properties for material nodes
|
||||
|
|
|
@ -35,9 +35,7 @@ private:
|
|||
void setMaterialNodeProperties(FBXNode& materialNode, QString material, const hfm::Model::Pointer& hfmModel);
|
||||
NodeID nextNodeID() { return _nodeID++; }
|
||||
|
||||
|
||||
NodeID _nodeID { 0 };
|
||||
std::vector<NodeID> _materialIDs;
|
||||
std::vector<std::pair<NodeID, int>> _mapTextureMaterial;
|
||||
};
|
||||
#endif // hifi_OBJBaker_h
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
#include <QtCore/QFile>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
|
||||
#include <image/Image.h>
|
||||
#include <image/TextureProcessing.h>
|
||||
#include <ktx/KTX.h>
|
||||
#include <NetworkAccessManager.h>
|
||||
#include <SharedUtil.h>
|
||||
|
@ -131,7 +131,10 @@ void TextureBaker::handleTextureNetworkReply() {
|
|||
void TextureBaker::processTexture() {
|
||||
// the baked textures need to have the source hash added for cache checks in Interface
|
||||
// so we add that to the processed texture before handling it off to be serialized
|
||||
auto hashData = QCryptographicHash::hash(_originalTexture, QCryptographicHash::Md5);
|
||||
QCryptographicHash hasher(QCryptographicHash::Md5);
|
||||
hasher.addData(_originalTexture);
|
||||
hasher.addData((const char*)&_textureType, sizeof(_textureType));
|
||||
auto hashData = hasher.result();
|
||||
std::string hash = hashData.toHex().toStdString();
|
||||
|
||||
TextureMeta meta;
|
||||
|
@ -206,7 +209,7 @@ void TextureBaker::processTexture() {
|
|||
}
|
||||
|
||||
// Uncompressed KTX
|
||||
if (_textureType == image::TextureUsage::Type::CUBE_TEXTURE) {
|
||||
if (_textureType == image::TextureUsage::Type::SKY_TEXTURE || _textureType == image::TextureUsage::Type::AMBIENT_TEXTURE) {
|
||||
buffer->reset();
|
||||
auto processedTexture = image::processImage(std::move(buffer), _textureURL.toString().toStdString(), image::ColorChannel::NONE,
|
||||
ABSOLUTE_MAX_TEXTURE_NUM_PIXELS, _textureType, false, gpu::BackendTarget::GL45, _abortProcessing);
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
#include <QDir>
|
||||
#include <QImageReader>
|
||||
|
||||
#include <image/Image.h>
|
||||
#include <image/TextureProcessing.h>
|
||||
|
||||
#include "Baker.h"
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ bool isModelBaked(const QUrl& bakeableModelURL) {
|
|||
return beforeModelExtension.endsWith(".baked");
|
||||
}
|
||||
|
||||
std::unique_ptr<ModelBaker> getModelBaker(const QUrl& bakeableModelURL, TextureBakerThreadGetter inputTextureThreadGetter, const QString& contentOutputPath) {
|
||||
std::unique_ptr<ModelBaker> getModelBaker(const QUrl& bakeableModelURL, const QString& contentOutputPath) {
|
||||
auto filename = bakeableModelURL.fileName();
|
||||
|
||||
// Output in a sub-folder with the name of the model, potentially suffixed by a number to make it unique
|
||||
|
@ -58,20 +58,20 @@ std::unique_ptr<ModelBaker> getModelBaker(const QUrl& bakeableModelURL, TextureB
|
|||
QString bakedOutputDirectory = contentOutputPath + subDirName + "/baked";
|
||||
QString originalOutputDirectory = contentOutputPath + subDirName + "/original";
|
||||
|
||||
return getModelBakerWithOutputDirectories(bakeableModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory);
|
||||
return getModelBakerWithOutputDirectories(bakeableModelURL, bakedOutputDirectory, originalOutputDirectory);
|
||||
}
|
||||
|
||||
std::unique_ptr<ModelBaker> getModelBakerWithOutputDirectories(const QUrl& bakeableModelURL, TextureBakerThreadGetter inputTextureThreadGetter, const QString& bakedOutputDirectory, const QString& originalOutputDirectory) {
|
||||
std::unique_ptr<ModelBaker> getModelBakerWithOutputDirectories(const QUrl& bakeableModelURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory) {
|
||||
auto filename = bakeableModelURL.fileName();
|
||||
|
||||
std::unique_ptr<ModelBaker> baker;
|
||||
|
||||
if (filename.endsWith(FST_EXTENSION, Qt::CaseInsensitive)) {
|
||||
baker = std::make_unique<FSTBaker>(bakeableModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory, filename.endsWith(BAKED_FST_EXTENSION, Qt::CaseInsensitive));
|
||||
baker = std::make_unique<FSTBaker>(bakeableModelURL, bakedOutputDirectory, originalOutputDirectory, filename.endsWith(BAKED_FST_EXTENSION, Qt::CaseInsensitive));
|
||||
} else if (filename.endsWith(FBX_EXTENSION, Qt::CaseInsensitive)) {
|
||||
baker = std::make_unique<FBXBaker>(bakeableModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory, filename.endsWith(BAKED_FBX_EXTENSION, Qt::CaseInsensitive));
|
||||
baker = std::make_unique<FBXBaker>(bakeableModelURL, bakedOutputDirectory, originalOutputDirectory, filename.endsWith(BAKED_FBX_EXTENSION, Qt::CaseInsensitive));
|
||||
} else if (filename.endsWith(OBJ_EXTENSION, Qt::CaseInsensitive)) {
|
||||
baker = std::make_unique<OBJBaker>(bakeableModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory);
|
||||
baker = std::make_unique<OBJBaker>(bakeableModelURL, bakedOutputDirectory, originalOutputDirectory);
|
||||
//} else if (filename.endsWith(GLTF_EXTENSION, Qt::CaseInsensitive)) {
|
||||
//baker = std::make_unique<GLTFBaker>(bakeableModelURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory);
|
||||
} else {
|
||||
|
|
|
@ -23,9 +23,9 @@ bool isModelBaked(const QUrl& bakeableModelURL);
|
|||
|
||||
// Assuming the URL is valid, gets the appropriate baker for the given URL, and creates the base directory where the baker's output will later be stored
|
||||
// Returns an empty pointer if a baker could not be created
|
||||
std::unique_ptr<ModelBaker> getModelBaker(const QUrl& bakeableModelURL, TextureBakerThreadGetter inputTextureThreadGetter, const QString& contentOutputPath);
|
||||
std::unique_ptr<ModelBaker> getModelBaker(const QUrl& bakeableModelURL, const QString& contentOutputPath);
|
||||
|
||||
// Similar to getModelBaker, but gives control over where the output folders will be
|
||||
std::unique_ptr<ModelBaker> getModelBakerWithOutputDirectories(const QUrl& bakeableModelURL, TextureBakerThreadGetter inputTextureThreadGetter, const QString& bakedOutputDirectory, const QString& originalOutputDirectory);
|
||||
std::unique_ptr<ModelBaker> getModelBakerWithOutputDirectories(const QUrl& bakeableModelURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory);
|
||||
|
||||
#endif // hifi_BakerLibrary_h
|
||||
|
|
|
@ -18,9 +18,8 @@
|
|||
|
||||
#include <FSTReader.h>
|
||||
|
||||
FSTBaker::FSTBaker(const QUrl& inputMappingURL, TextureBakerThreadGetter inputTextureThreadGetter,
|
||||
const QString& bakedOutputDirectory, const QString& originalOutputDirectory, bool hasBeenBaked) :
|
||||
ModelBaker(inputMappingURL, inputTextureThreadGetter, bakedOutputDirectory, originalOutputDirectory, hasBeenBaked) {
|
||||
FSTBaker::FSTBaker(const QUrl& inputMappingURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory, bool hasBeenBaked) :
|
||||
ModelBaker(inputMappingURL, bakedOutputDirectory, originalOutputDirectory, hasBeenBaked) {
|
||||
if (hasBeenBaked) {
|
||||
// Look for the original model file one directory higher. Perhaps this is an oven output directory.
|
||||
QUrl originalRelativePath = QUrl("../original/" + inputMappingURL.fileName().replace(BAKED_FST_EXTENSION, FST_EXTENSION));
|
||||
|
@ -70,7 +69,7 @@ void FSTBaker::bakeSourceCopy() {
|
|||
return;
|
||||
}
|
||||
|
||||
auto baker = getModelBakerWithOutputDirectories(bakeableModelURL, _textureThreadGetter, _bakedOutputDir, _originalOutputDir);
|
||||
auto baker = getModelBakerWithOutputDirectories(bakeableModelURL, _bakedOutputDir, _originalOutputDir);
|
||||
_modelBaker = std::unique_ptr<ModelBaker>(dynamic_cast<ModelBaker*>(baker.release()));
|
||||
if (!_modelBaker) {
|
||||
handleError("The model url '" + bakeableModelURL.toString() + "' from the FST file '" + _originalOutputModelPath + "' (property: '" + FILENAME_FIELD + "') could not be used to initialize a valid model baker");
|
||||
|
|
|
@ -18,8 +18,7 @@ class FSTBaker : public ModelBaker {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
FSTBaker(const QUrl& inputMappingURL, TextureBakerThreadGetter inputTextureThreadGetter,
|
||||
const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "", bool hasBeenBaked = false);
|
||||
FSTBaker(const QUrl& inputMappingURL, const QString& bakedOutputDirectory, const QString& originalOutputDirectory = "", bool hasBeenBaked = false);
|
||||
|
||||
virtual QUrl getFullOutputMappingURL() const override;
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
#include <QtCore/QFileInfo>
|
||||
#include <QHash>
|
||||
|
||||
#include <image/Image.h>
|
||||
#include <image/TextureProcessing.h>
|
||||
|
||||
class TextureFileNamer {
|
||||
public:
|
||||
|
|
|
@ -109,7 +109,7 @@ bool Basic2DWindowOpenGLDisplayPlugin::internalActivate() {
|
|||
return Parent::internalActivate();
|
||||
}
|
||||
|
||||
void Basic2DWindowOpenGLDisplayPlugin::compositeExtra(const gpu::FramebufferPointer& compositeFramebuffer) {
|
||||
void Basic2DWindowOpenGLDisplayPlugin::compositeExtra() {
|
||||
#if defined(Q_OS_ANDROID)
|
||||
auto& virtualPadManager = VirtualPad::Manager::instance();
|
||||
if(virtualPadManager.getLeftVirtualPad()->isShown()) {
|
||||
|
@ -121,7 +121,7 @@ void Basic2DWindowOpenGLDisplayPlugin::compositeExtra(const gpu::FramebufferPoin
|
|||
|
||||
render([&](gpu::Batch& batch) {
|
||||
batch.enableStereo(false);
|
||||
batch.setFramebuffer(compositeFramebuffer);
|
||||
batch.setFramebuffer(_compositeFramebuffer);
|
||||
batch.resetViewTransform();
|
||||
batch.setProjectionTransform(mat4());
|
||||
batch.setPipeline(_cursorPipeline);
|
||||
|
@ -140,7 +140,7 @@ void Basic2DWindowOpenGLDisplayPlugin::compositeExtra(const gpu::FramebufferPoin
|
|||
});
|
||||
}
|
||||
#endif
|
||||
Parent::compositeExtra(compositeFramebuffer);
|
||||
Parent::compositeExtra();
|
||||
}
|
||||
|
||||
static const uint32_t MIN_THROTTLE_CHECK_FRAMES = 60;
|
||||
|
|
|
@ -33,7 +33,7 @@ public:
|
|||
|
||||
virtual bool isThrottled() const override;
|
||||
|
||||
virtual void compositeExtra(const gpu::FramebufferPointer&) override;
|
||||
virtual void compositeExtra() override;
|
||||
|
||||
virtual void pluginUpdate() override {};
|
||||
|
||||
|
|
|
@ -109,7 +109,6 @@ public:
|
|||
Q_ASSERT(_context);
|
||||
_context->makeCurrent();
|
||||
CHECK_GL_ERROR();
|
||||
_context->doneCurrent();
|
||||
while (!_shutdown) {
|
||||
if (_pendingOtherThreadOperation) {
|
||||
PROFILE_RANGE(render, "MainThreadOp")
|
||||
|
@ -129,6 +128,7 @@ public:
|
|||
Lock lock(_mutex);
|
||||
_condition.wait(lock, [&] { return _finishedOtherThreadOperation; });
|
||||
}
|
||||
_context->makeCurrent();
|
||||
}
|
||||
|
||||
// Check for a new display plugin
|
||||
|
@ -140,18 +140,16 @@ public:
|
|||
if (newPlugin != currentPlugin) {
|
||||
// Deactivate the old plugin
|
||||
if (currentPlugin != nullptr) {
|
||||
_context->makeCurrent();
|
||||
currentPlugin->uncustomizeContext();
|
||||
CHECK_GL_ERROR();
|
||||
_context->doneCurrent();
|
||||
// Force completion of all pending GL commands
|
||||
glFinish();
|
||||
}
|
||||
|
||||
if (newPlugin) {
|
||||
bool hasVsync = true;
|
||||
QThread::setPriority(newPlugin->getPresentPriority());
|
||||
bool wantVsync = newPlugin->wantVsync();
|
||||
_context->makeCurrent();
|
||||
CHECK_GL_ERROR();
|
||||
#if defined(Q_OS_MAC)
|
||||
newPlugin->swapBuffers();
|
||||
#endif
|
||||
|
@ -163,7 +161,8 @@ public:
|
|||
newPlugin->setVsyncEnabled(hasVsync);
|
||||
newPlugin->customizeContext();
|
||||
CHECK_GL_ERROR();
|
||||
_context->doneCurrent();
|
||||
// Force completion of all pending GL commands
|
||||
glFinish();
|
||||
}
|
||||
currentPlugin = newPlugin;
|
||||
_newPluginQueue.pop();
|
||||
|
@ -180,7 +179,6 @@ public:
|
|||
}
|
||||
|
||||
// Execute the frame and present it to the display device.
|
||||
_context->makeCurrent();
|
||||
{
|
||||
PROFILE_RANGE(render, "PluginPresent")
|
||||
gl::globalLock();
|
||||
|
@ -188,9 +186,9 @@ public:
|
|||
gl::globalRelease(false);
|
||||
CHECK_GL_ERROR();
|
||||
}
|
||||
_context->doneCurrent();
|
||||
}
|
||||
|
||||
_context->doneCurrent();
|
||||
Lock lock(_mutex);
|
||||
_context->moveToThread(qApp->thread());
|
||||
_shutdown = false;
|
||||
|
@ -379,6 +377,14 @@ void OpenGLDisplayPlugin::customizeContext() {
|
|||
scissorState->setDepthTest(gpu::State::DepthTest(false));
|
||||
scissorState->setScissorEnable(true);
|
||||
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTextureGammaLinearToSRGB);
|
||||
#else
|
||||
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTexture);
|
||||
#endif
|
||||
_simplePipeline = gpu::Pipeline::create(program, scissorState);
|
||||
}
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTextureGammaLinearToSRGB);
|
||||
|
@ -388,59 +394,29 @@ void OpenGLDisplayPlugin::customizeContext() {
|
|||
_presentPipeline = gpu::Pipeline::create(program, scissorState);
|
||||
}
|
||||
|
||||
|
||||
// HUD operator
|
||||
{
|
||||
gpu::PipelinePointer hudPipeline;
|
||||
{
|
||||
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTexture);
|
||||
hudPipeline = gpu::Pipeline::create(program, blendState);
|
||||
}
|
||||
|
||||
gpu::PipelinePointer hudMirrorPipeline;
|
||||
{
|
||||
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTextureMirroredX);
|
||||
hudMirrorPipeline = gpu::Pipeline::create(program, blendState);
|
||||
}
|
||||
|
||||
|
||||
_hudOperator = [=](gpu::Batch& batch, const gpu::TexturePointer& hudTexture, const gpu::FramebufferPointer& compositeFramebuffer, bool mirror) {
|
||||
auto hudStereo = isStereo();
|
||||
auto hudCompositeFramebufferSize = compositeFramebuffer->getSize();
|
||||
std::array<glm::ivec4, 2> hudEyeViewports;
|
||||
for_each_eye([&](Eye eye) {
|
||||
hudEyeViewports[eye] = eyeViewport(eye);
|
||||
});
|
||||
if (hudPipeline && hudTexture) {
|
||||
batch.enableStereo(false);
|
||||
batch.setPipeline(mirror ? hudMirrorPipeline : hudPipeline);
|
||||
batch.setResourceTexture(0, hudTexture);
|
||||
if (hudStereo) {
|
||||
for_each_eye([&](Eye eye) {
|
||||
batch.setViewportTransform(hudEyeViewports[eye]);
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
});
|
||||
} else {
|
||||
batch.setViewportTransform(ivec4(uvec2(0), hudCompositeFramebufferSize));
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTexture);
|
||||
_hudPipeline = gpu::Pipeline::create(program, blendState);
|
||||
}
|
||||
{
|
||||
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTextureMirroredX);
|
||||
_mirrorHUDPipeline = gpu::Pipeline::create(program, blendState);
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
gpu::ShaderPointer program = gpu::Shader::createProgram(shader::gpu::program::DrawTransformedTexture);
|
||||
_cursorPipeline = gpu::Pipeline::create(program, blendState);
|
||||
}
|
||||
}
|
||||
updateCompositeFramebuffer();
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::uncustomizeContext() {
|
||||
_presentPipeline.reset();
|
||||
_cursorPipeline.reset();
|
||||
_hudOperator = DEFAULT_HUD_OPERATOR;
|
||||
_hudPipeline.reset();
|
||||
_mirrorHUDPipeline.reset();
|
||||
_compositeFramebuffer.reset();
|
||||
withPresentThreadLock([&] {
|
||||
_currentFrame.reset();
|
||||
_lastFrame = nullptr;
|
||||
|
@ -532,16 +508,24 @@ void OpenGLDisplayPlugin::captureFrame(const std::string& filename) const {
|
|||
});
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor) {
|
||||
renderFromTexture(batch, texture, viewport, scissor, nullptr);
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor, const gpu::FramebufferPointer& destFbo, const gpu::FramebufferPointer& copyFbo /*=gpu::FramebufferPointer()*/) {
|
||||
void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor, const gpu::FramebufferPointer& copyFbo /*=gpu::FramebufferPointer()*/) {
|
||||
auto fbo = gpu::FramebufferPointer();
|
||||
batch.enableStereo(false);
|
||||
batch.resetViewTransform();
|
||||
batch.setFramebuffer(destFbo);
|
||||
batch.setFramebuffer(fbo);
|
||||
batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, vec4(0));
|
||||
batch.setStateScissorRect(scissor);
|
||||
batch.setViewportTransform(viewport);
|
||||
batch.setResourceTexture(0, texture);
|
||||
#ifndef USE_GLES
|
||||
batch.setPipeline(_presentPipeline);
|
||||
#else
|
||||
batch.setPipeline(_simplePipeline);
|
||||
#endif
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
if (copyFbo) {
|
||||
gpu::Vec4i copyFboRect(0, 0, copyFbo->getWidth(), copyFbo->getHeight());
|
||||
|
@ -567,7 +551,7 @@ void OpenGLDisplayPlugin::renderFromTexture(gpu::Batch& batch, const gpu::Textur
|
|||
batch.setViewportTransform(copyFboRect);
|
||||
batch.setStateScissorRect(copyFboRect);
|
||||
batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, {0.0f, 0.0f, 0.0f, 1.0f});
|
||||
batch.blit(destFbo, sourceRect, copyFbo, copyRect);
|
||||
batch.blit(fbo, sourceRect, copyFbo, copyRect);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -595,14 +579,41 @@ void OpenGLDisplayPlugin::updateFrameData() {
|
|||
});
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::compositePointer(const gpu::FramebufferPointer& compositeFramebuffer) {
|
||||
std::function<void(gpu::Batch&, const gpu::TexturePointer&, bool mirror)> OpenGLDisplayPlugin::getHUDOperator() {
|
||||
auto hudPipeline = _hudPipeline;
|
||||
auto hudMirrorPipeline = _mirrorHUDPipeline;
|
||||
auto hudStereo = isStereo();
|
||||
auto hudCompositeFramebufferSize = _compositeFramebuffer->getSize();
|
||||
std::array<glm::ivec4, 2> hudEyeViewports;
|
||||
for_each_eye([&](Eye eye) {
|
||||
hudEyeViewports[eye] = eyeViewport(eye);
|
||||
});
|
||||
return [=](gpu::Batch& batch, const gpu::TexturePointer& hudTexture, bool mirror) {
|
||||
if (hudPipeline && hudTexture) {
|
||||
batch.enableStereo(false);
|
||||
batch.setPipeline(mirror ? hudMirrorPipeline : hudPipeline);
|
||||
batch.setResourceTexture(0, hudTexture);
|
||||
if (hudStereo) {
|
||||
for_each_eye([&](Eye eye) {
|
||||
batch.setViewportTransform(hudEyeViewports[eye]);
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
});
|
||||
} else {
|
||||
batch.setViewportTransform(ivec4(uvec2(0), hudCompositeFramebufferSize));
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::compositePointer() {
|
||||
auto& cursorManager = Cursor::Manager::instance();
|
||||
const auto& cursorData = _cursorsData[cursorManager.getCursor()->getIcon()];
|
||||
auto cursorTransform = DependencyManager::get<CompositorHelper>()->getReticleTransform(glm::mat4());
|
||||
render([&](gpu::Batch& batch) {
|
||||
batch.enableStereo(false);
|
||||
batch.setProjectionTransform(mat4());
|
||||
batch.setFramebuffer(compositeFramebuffer);
|
||||
batch.setFramebuffer(_compositeFramebuffer);
|
||||
batch.setPipeline(_cursorPipeline);
|
||||
batch.setResourceTexture(0, cursorData.texture);
|
||||
batch.resetViewTransform();
|
||||
|
@ -613,13 +624,34 @@ void OpenGLDisplayPlugin::compositePointer(const gpu::FramebufferPointer& compos
|
|||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
});
|
||||
} else {
|
||||
batch.setViewportTransform(ivec4(uvec2(0), compositeFramebuffer->getSize()));
|
||||
batch.setViewportTransform(ivec4(uvec2(0), _compositeFramebuffer->getSize()));
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::compositeLayers(const gpu::FramebufferPointer& compositeFramebuffer) {
|
||||
void OpenGLDisplayPlugin::compositeScene() {
|
||||
render([&](gpu::Batch& batch) {
|
||||
batch.enableStereo(false);
|
||||
batch.setFramebuffer(_compositeFramebuffer);
|
||||
batch.setViewportTransform(ivec4(uvec2(), _compositeFramebuffer->getSize()));
|
||||
batch.setStateScissorRect(ivec4(uvec2(), _compositeFramebuffer->getSize()));
|
||||
batch.resetViewTransform();
|
||||
batch.setProjectionTransform(mat4());
|
||||
batch.setPipeline(_simplePipeline);
|
||||
batch.setResourceTexture(0, _currentFrame->framebuffer->getRenderBuffer(0));
|
||||
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
||||
});
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::compositeLayers() {
|
||||
updateCompositeFramebuffer();
|
||||
|
||||
{
|
||||
PROFILE_RANGE_EX(render_detail, "compositeScene", 0xff0077ff, (uint64_t)presentCount())
|
||||
compositeScene();
|
||||
}
|
||||
|
||||
#ifdef HIFI_ENABLE_NSIGHT_DEBUG
|
||||
if (false) // do not draw the HUD if running nsight debug
|
||||
#endif
|
||||
|
@ -633,35 +665,23 @@ void OpenGLDisplayPlugin::compositeLayers(const gpu::FramebufferPointer& composi
|
|||
|
||||
{
|
||||
PROFILE_RANGE_EX(render_detail, "compositeExtra", 0xff0077ff, (uint64_t)presentCount())
|
||||
compositeExtra(compositeFramebuffer);
|
||||
compositeExtra();
|
||||
}
|
||||
|
||||
// Draw the pointer last so it's on top of everything
|
||||
auto compositorHelper = DependencyManager::get<CompositorHelper>();
|
||||
if (compositorHelper->getReticleVisible()) {
|
||||
PROFILE_RANGE_EX(render_detail, "compositePointer", 0xff0077ff, (uint64_t)presentCount())
|
||||
compositePointer(compositeFramebuffer);
|
||||
compositePointer();
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::internalPresent(const gpu::FramebufferPointer& compositeFramebuffer) {
|
||||
void OpenGLDisplayPlugin::internalPresent() {
|
||||
render([&](gpu::Batch& batch) {
|
||||
// Note: _displayTexture must currently be the same size as the display.
|
||||
uvec2 dims = _displayTexture ? uvec2(_displayTexture->getDimensions()) : getSurfacePixels();
|
||||
auto viewport = ivec4(uvec2(0), dims);
|
||||
|
||||
gpu::TexturePointer finalTexture;
|
||||
if (_displayTexture) {
|
||||
finalTexture = _displayTexture;
|
||||
} else if (compositeFramebuffer) {
|
||||
finalTexture = compositeFramebuffer->getRenderBuffer(0);
|
||||
} else {
|
||||
qCWarning(displayPlugins) << "No valid texture for output";
|
||||
}
|
||||
|
||||
if (finalTexture) {
|
||||
renderFromTexture(batch, finalTexture, viewport, viewport);
|
||||
}
|
||||
renderFromTexture(batch, _displayTexture ? _displayTexture : _compositeFramebuffer->getRenderBuffer(0), viewport, viewport);
|
||||
});
|
||||
swapBuffers();
|
||||
_presentRate.increment();
|
||||
|
@ -678,7 +698,7 @@ void OpenGLDisplayPlugin::present() {
|
|||
}
|
||||
incrementPresentCount();
|
||||
|
||||
if (_currentFrame && _currentFrame->framebuffer) {
|
||||
if (_currentFrame) {
|
||||
auto correction = getViewCorrection();
|
||||
getGLBackend()->setCameraCorrection(correction, _prevRenderView);
|
||||
_prevRenderView = correction * _currentFrame->view;
|
||||
|
@ -698,18 +718,18 @@ void OpenGLDisplayPlugin::present() {
|
|||
// Write all layers to a local framebuffer
|
||||
{
|
||||
PROFILE_RANGE_EX(render, "composite", 0xff00ffff, frameId)
|
||||
compositeLayers(_currentFrame->framebuffer);
|
||||
compositeLayers();
|
||||
}
|
||||
|
||||
// Take the composite framebuffer and send it to the output device
|
||||
{
|
||||
PROFILE_RANGE_EX(render, "internalPresent", 0xff00ffff, frameId)
|
||||
internalPresent(_currentFrame->framebuffer);
|
||||
internalPresent();
|
||||
}
|
||||
|
||||
gpu::Backend::freeGPUMemSize.set(gpu::gl::getFreeDedicatedMemory());
|
||||
} else if (alwaysPresent()) {
|
||||
internalPresent(nullptr);
|
||||
internalPresent();
|
||||
}
|
||||
_movingAveragePresent.addSample((float)(usecTimestampNow() - startPresent));
|
||||
}
|
||||
|
@ -766,12 +786,7 @@ bool OpenGLDisplayPlugin::setDisplayTexture(const QString& name) {
|
|||
}
|
||||
|
||||
QImage OpenGLDisplayPlugin::getScreenshot(float aspectRatio) const {
|
||||
if (!_currentFrame || !_currentFrame->framebuffer) {
|
||||
return QImage();
|
||||
}
|
||||
|
||||
auto compositeFramebuffer = _currentFrame->framebuffer;
|
||||
auto size = compositeFramebuffer->getSize();
|
||||
auto size = _compositeFramebuffer->getSize();
|
||||
if (isHmd()) {
|
||||
size.x /= 2;
|
||||
}
|
||||
|
@ -789,7 +804,7 @@ QImage OpenGLDisplayPlugin::getScreenshot(float aspectRatio) const {
|
|||
auto glBackend = const_cast<OpenGLDisplayPlugin&>(*this).getGLBackend();
|
||||
QImage screenshot(bestSize.x, bestSize.y, QImage::Format_ARGB32);
|
||||
withOtherThreadContext([&] {
|
||||
glBackend->downloadFramebuffer(compositeFramebuffer, ivec4(corner, bestSize), screenshot);
|
||||
glBackend->downloadFramebuffer(_compositeFramebuffer, ivec4(corner, bestSize), screenshot);
|
||||
});
|
||||
return screenshot.mirrored(false, true);
|
||||
}
|
||||
|
@ -841,7 +856,7 @@ bool OpenGLDisplayPlugin::beginFrameRender(uint32_t frameIndex) {
|
|||
}
|
||||
|
||||
ivec4 OpenGLDisplayPlugin::eyeViewport(Eye eye) const {
|
||||
auto vpSize = glm::uvec2(getRecommendedRenderSize());
|
||||
uvec2 vpSize = _compositeFramebuffer->getSize();
|
||||
vpSize.x /= 2;
|
||||
uvec2 vpPos;
|
||||
if (eye == Eye::Right) {
|
||||
|
@ -874,6 +889,14 @@ void OpenGLDisplayPlugin::render(std::function<void(gpu::Batch& batch)> f) {
|
|||
OpenGLDisplayPlugin::~OpenGLDisplayPlugin() {
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::updateCompositeFramebuffer() {
|
||||
auto renderSize = glm::uvec2(getRecommendedRenderSize());
|
||||
if (!_compositeFramebuffer || _compositeFramebuffer->getSize() != renderSize) {
|
||||
_compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OpenGLDisplayPlugin::composite", gpu::Element::COLOR_RGBA_32, renderSize.x, renderSize.y));
|
||||
// _compositeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("OpenGLDisplayPlugin::composite", gpu::Element::COLOR_SRGBA_32, renderSize.x, renderSize.y));
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLDisplayPlugin::copyTextureToQuickFramebuffer(NetworkTexturePointer networkTexture, QOpenGLFramebufferObject* target, GLsync* fenceSync) {
|
||||
#if !defined(USE_GLES)
|
||||
auto glBackend = const_cast<OpenGLDisplayPlugin&>(*this).getGLBackend();
|
||||
|
|
|
@ -94,10 +94,14 @@ protected:
|
|||
// is not populated
|
||||
virtual bool alwaysPresent() const { return false; }
|
||||
|
||||
void updateCompositeFramebuffer();
|
||||
|
||||
virtual QThread::Priority getPresentPriority() { return QThread::HighPriority; }
|
||||
virtual void compositeLayers(const gpu::FramebufferPointer&);
|
||||
virtual void compositePointer(const gpu::FramebufferPointer&);
|
||||
virtual void compositeExtra(const gpu::FramebufferPointer&) {};
|
||||
virtual void compositeLayers();
|
||||
virtual void compositeScene();
|
||||
virtual std::function<void(gpu::Batch&, const gpu::TexturePointer&, bool mirror)> getHUDOperator();
|
||||
virtual void compositePointer();
|
||||
virtual void compositeExtra() {};
|
||||
|
||||
// These functions must only be called on the presentation thread
|
||||
virtual void customizeContext();
|
||||
|
@ -112,10 +116,10 @@ protected:
|
|||
virtual void deactivateSession() {}
|
||||
|
||||
// Plugin specific functionality to send the composed scene to the output window or device
|
||||
virtual void internalPresent(const gpu::FramebufferPointer&);
|
||||
virtual void internalPresent();
|
||||
|
||||
|
||||
void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor, const gpu::FramebufferPointer& destFbo = nullptr, const gpu::FramebufferPointer& copyFbo = nullptr);
|
||||
void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor, const gpu::FramebufferPointer& fbo);
|
||||
void renderFromTexture(gpu::Batch& batch, const gpu::TexturePointer& texture, const glm::ivec4& viewport, const glm::ivec4& scissor);
|
||||
virtual void updateFrameData();
|
||||
virtual glm::mat4 getViewCorrection() { return glm::mat4(); }
|
||||
|
||||
|
@ -138,8 +142,14 @@ protected:
|
|||
gpu::FramePointer _currentFrame;
|
||||
gpu::Frame* _lastFrame { nullptr };
|
||||
mat4 _prevRenderView;
|
||||
gpu::FramebufferPointer _compositeFramebuffer;
|
||||
gpu::PipelinePointer _hudPipeline;
|
||||
gpu::PipelinePointer _mirrorHUDPipeline;
|
||||
gpu::ShaderPointer _mirrorHUDPS;
|
||||
gpu::PipelinePointer _simplePipeline;
|
||||
gpu::PipelinePointer _presentPipeline;
|
||||
gpu::PipelinePointer _cursorPipeline;
|
||||
gpu::TexturePointer _displayTexture;
|
||||
gpu::TexturePointer _displayTexture{};
|
||||
float _compositeHUDAlpha { 1.0f };
|
||||
|
||||
struct CursorData {
|
||||
|
@ -175,9 +185,5 @@ protected:
|
|||
// be serialized through this mutex
|
||||
mutable Mutex _presentMutex;
|
||||
float _hudAlpha{ 1.0f };
|
||||
|
||||
private:
|
||||
gpu::PipelinePointer _presentPipeline;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ public:
|
|||
|
||||
protected:
|
||||
void updatePresentPose() override;
|
||||
void hmdPresent(const gpu::FramebufferPointer&) override {}
|
||||
void hmdPresent() override {}
|
||||
bool isHmdMounted() const override { return true; }
|
||||
bool internalActivate() override;
|
||||
private:
|
||||
|
|
|
@ -114,23 +114,20 @@ void HmdDisplayPlugin::internalDeactivate() {
|
|||
|
||||
void HmdDisplayPlugin::customizeContext() {
|
||||
Parent::customizeContext();
|
||||
_hudOperator = _hudRenderer.build();
|
||||
_hudRenderer.build();
|
||||
}
|
||||
|
||||
void HmdDisplayPlugin::uncustomizeContext() {
|
||||
// This stops the weirdness where if the preview was disabled, on switching back to 2D,
|
||||
// the vsync was stuck in the disabled state. No idea why that happens though.
|
||||
_disablePreview = false;
|
||||
if (_currentFrame && _currentFrame->framebuffer) {
|
||||
render([&](gpu::Batch& batch) {
|
||||
batch.enableStereo(false);
|
||||
batch.resetViewTransform();
|
||||
batch.setFramebuffer(_currentFrame->framebuffer);
|
||||
batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, vec4(0));
|
||||
});
|
||||
|
||||
}
|
||||
_hudRenderer = {};
|
||||
render([&](gpu::Batch& batch) {
|
||||
batch.enableStereo(false);
|
||||
batch.resetViewTransform();
|
||||
batch.setFramebuffer(_compositeFramebuffer);
|
||||
batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, vec4(0));
|
||||
});
|
||||
_hudRenderer = HUDRenderer();
|
||||
_previewTexture.reset();
|
||||
Parent::uncustomizeContext();
|
||||
}
|
||||
|
@ -177,11 +174,11 @@ float HmdDisplayPlugin::getLeftCenterPixel() const {
|
|||
return leftCenterPixel;
|
||||
}
|
||||
|
||||
void HmdDisplayPlugin::internalPresent(const gpu::FramebufferPointer& compositeFramebuffer) {
|
||||
void HmdDisplayPlugin::internalPresent() {
|
||||
PROFILE_RANGE_EX(render, __FUNCTION__, 0xff00ff00, (uint64_t)presentCount())
|
||||
|
||||
// Composite together the scene, hud and mouse cursor
|
||||
hmdPresent(compositeFramebuffer);
|
||||
hmdPresent();
|
||||
|
||||
if (_displayTexture) {
|
||||
// Note: _displayTexture must currently be the same size as the display.
|
||||
|
@ -263,7 +260,7 @@ void HmdDisplayPlugin::internalPresent(const gpu::FramebufferPointer& compositeF
|
|||
|
||||
viewport.z *= 2;
|
||||
}
|
||||
renderFromTexture(batch, compositeFramebuffer->getRenderBuffer(0), viewport, scissor, nullptr, fbo);
|
||||
renderFromTexture(batch, _compositeFramebuffer->getRenderBuffer(0), viewport, scissor, fbo);
|
||||
});
|
||||
swapBuffers();
|
||||
|
||||
|
@ -348,7 +345,7 @@ glm::mat4 HmdDisplayPlugin::getViewCorrection() {
|
|||
}
|
||||
}
|
||||
|
||||
DisplayPlugin::HUDOperator HmdDisplayPlugin::HUDRenderer::build() {
|
||||
void HmdDisplayPlugin::HUDRenderer::build() {
|
||||
vertices = std::make_shared<gpu::Buffer>();
|
||||
indices = std::make_shared<gpu::Buffer>();
|
||||
|
||||
|
@ -383,7 +380,7 @@ DisplayPlugin::HUDOperator HmdDisplayPlugin::HUDRenderer::build() {
|
|||
indexCount = numberOfRectangles * TRIANGLE_PER_RECTANGLE * VERTEX_PER_TRANGLE;
|
||||
|
||||
// Compute indices order
|
||||
std::vector<GLushort> indexData;
|
||||
std::vector<GLushort> indices;
|
||||
for (int i = 0; i < stacks - 1; i++) {
|
||||
for (int j = 0; j < slices - 1; j++) {
|
||||
GLushort bottomLeftIndex = i * slices + j;
|
||||
|
@ -391,21 +388,24 @@ DisplayPlugin::HUDOperator HmdDisplayPlugin::HUDRenderer::build() {
|
|||
GLushort topLeftIndex = bottomLeftIndex + slices;
|
||||
GLushort topRightIndex = topLeftIndex + 1;
|
||||
// FIXME make a z-order curve for better vertex cache locality
|
||||
indexData.push_back(topLeftIndex);
|
||||
indexData.push_back(bottomLeftIndex);
|
||||
indexData.push_back(topRightIndex);
|
||||
indices.push_back(topLeftIndex);
|
||||
indices.push_back(bottomLeftIndex);
|
||||
indices.push_back(topRightIndex);
|
||||
|
||||
indexData.push_back(topRightIndex);
|
||||
indexData.push_back(bottomLeftIndex);
|
||||
indexData.push_back(bottomRightIndex);
|
||||
indices.push_back(topRightIndex);
|
||||
indices.push_back(bottomLeftIndex);
|
||||
indices.push_back(bottomRightIndex);
|
||||
}
|
||||
}
|
||||
indices->append(indexData);
|
||||
this->indices->append(indices);
|
||||
format = std::make_shared<gpu::Stream::Format>(); // 1 for everyone
|
||||
format->setAttribute(gpu::Stream::POSITION, gpu::Stream::POSITION, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), 0);
|
||||
format->setAttribute(gpu::Stream::TEXCOORD, gpu::Stream::TEXCOORD, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV));
|
||||
uniformsBuffer = std::make_shared<gpu::Buffer>(sizeof(Uniforms), nullptr);
|
||||
updatePipeline();
|
||||
}
|
||||
|
||||
void HmdDisplayPlugin::HUDRenderer::updatePipeline() {
|
||||
if (!pipeline) {
|
||||
auto program = gpu::Shader::createProgram(shader::render_utils::program::hmd_ui);
|
||||
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
|
||||
|
@ -416,6 +416,10 @@ DisplayPlugin::HUDOperator HmdDisplayPlugin::HUDRenderer::build() {
|
|||
|
||||
pipeline = gpu::Pipeline::create(program, state);
|
||||
}
|
||||
}
|
||||
|
||||
std::function<void(gpu::Batch&, const gpu::TexturePointer&, bool mirror)> HmdDisplayPlugin::HUDRenderer::render(HmdDisplayPlugin& plugin) {
|
||||
updatePipeline();
|
||||
|
||||
auto hudPipeline = pipeline;
|
||||
auto hudFormat = format;
|
||||
|
@ -424,9 +428,9 @@ DisplayPlugin::HUDOperator HmdDisplayPlugin::HUDRenderer::build() {
|
|||
auto hudUniformBuffer = uniformsBuffer;
|
||||
auto hudUniforms = uniforms;
|
||||
auto hudIndexCount = indexCount;
|
||||
return [=](gpu::Batch& batch, const gpu::TexturePointer& hudTexture, const gpu::FramebufferPointer&, const bool mirror) {
|
||||
if (pipeline && hudTexture) {
|
||||
batch.setPipeline(pipeline);
|
||||
return [=](gpu::Batch& batch, const gpu::TexturePointer& hudTexture, bool mirror) {
|
||||
if (hudPipeline && hudTexture) {
|
||||
batch.setPipeline(hudPipeline);
|
||||
|
||||
batch.setInputFormat(hudFormat);
|
||||
gpu::BufferView posView(hudVertices, VERTEX_OFFSET, hudVertices->getSize(), VERTEX_STRIDE, hudFormat->getAttributes().at(gpu::Stream::POSITION)._element);
|
||||
|
@ -450,7 +454,7 @@ DisplayPlugin::HUDOperator HmdDisplayPlugin::HUDRenderer::build() {
|
|||
};
|
||||
}
|
||||
|
||||
void HmdDisplayPlugin::compositePointer(const gpu::FramebufferPointer& compositeFramebuffer) {
|
||||
void HmdDisplayPlugin::compositePointer() {
|
||||
auto& cursorManager = Cursor::Manager::instance();
|
||||
const auto& cursorData = _cursorsData[cursorManager.getCursor()->getIcon()];
|
||||
auto compositorHelper = DependencyManager::get<CompositorHelper>();
|
||||
|
@ -459,7 +463,7 @@ void HmdDisplayPlugin::compositePointer(const gpu::FramebufferPointer& composite
|
|||
render([&](gpu::Batch& batch) {
|
||||
// FIXME use standard gpu stereo rendering for this.
|
||||
batch.enableStereo(false);
|
||||
batch.setFramebuffer(compositeFramebuffer);
|
||||
batch.setFramebuffer(_compositeFramebuffer);
|
||||
batch.setPipeline(_cursorPipeline);
|
||||
batch.setResourceTexture(0, cursorData.texture);
|
||||
batch.resetViewTransform();
|
||||
|
@ -474,6 +478,10 @@ void HmdDisplayPlugin::compositePointer(const gpu::FramebufferPointer& composite
|
|||
});
|
||||
}
|
||||
|
||||
std::function<void(gpu::Batch&, const gpu::TexturePointer&, bool mirror)> HmdDisplayPlugin::getHUDOperator() {
|
||||
return _hudRenderer.render(*this);
|
||||
}
|
||||
|
||||
HmdDisplayPlugin::~HmdDisplayPlugin() {
|
||||
}
|
||||
|
||||
|
|