Merge remote-tracking branch 'upstream/master' into ga/master_build

This commit is contained in:
Brad Davis 2019-12-04 08:44:12 -08:00
commit 697f445edd
63 changed files with 5136 additions and 576 deletions

View file

@ -23,8 +23,7 @@
#include "AvatarMixerSlave.h"
AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID, Node::LocalID nodeLocalID) :
NodeData(nodeID, nodeLocalID) {
AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID, Node::LocalID nodeLocalID) : NodeData(nodeID, nodeLocalID) {
// in case somebody calls getSessionUUID on the AvatarData instance, make sure it has the right ID
_avatar->setID(nodeID);
}
@ -92,41 +91,48 @@ int AvatarMixerClientData::processPackets(const SlaveSharedData& slaveSharedData
}
namespace {
using std::static_pointer_cast;
using std::static_pointer_cast;
// Operator to find if a point is within an avatar-priority (hero) Zone Entity.
struct FindPriorityZone {
glm::vec3 position;
bool isInPriorityZone { false };
float zoneVolume { std::numeric_limits<float>::max() };
EntityItemID id {};
// Operator to find if a point is within an avatar-priority (hero) Zone Entity.
struct FindContainingZone {
glm::vec3 position;
bool isInPriorityZone { false };
bool isInScreenshareZone { false };
float priorityZoneVolume { std::numeric_limits<float>::max() };
float screenshareZoneVolume { priorityZoneVolume };
EntityItemID screenshareZoneid{};
static bool operation(const OctreeElementPointer& element, void* extraData) {
auto findPriorityZone = static_cast<FindPriorityZone*>(extraData);
if (element->getAACube().contains(findPriorityZone->position)) {
const EntityTreeElementPointer entityTreeElement = static_pointer_cast<EntityTreeElement>(element);
entityTreeElement->forEachEntity([&findPriorityZone](EntityItemPointer item) {
if (item->getType() == EntityTypes::Zone
&& item->contains(findPriorityZone->position)) {
auto zoneItem = static_pointer_cast<ZoneEntityItem>(item);
if (zoneItem->getAvatarPriority() != COMPONENT_MODE_INHERIT) {
float volume = zoneItem->getVolumeEstimate();
if (volume < findPriorityZone->zoneVolume) { // Smaller volume wins
findPriorityZone->isInPriorityZone = zoneItem->getAvatarPriority() == COMPONENT_MODE_ENABLED;
findPriorityZone->zoneVolume = volume;
findPriorityZone->id = zoneItem->getEntityItemID();
}
}
static bool operation(const OctreeElementPointer& element, void* extraData) {
auto findContainingZone = static_cast<FindContainingZone*>(extraData);
if (element->getAACube().contains(findContainingZone->position)) {
const EntityTreeElementPointer entityTreeElement = static_pointer_cast<EntityTreeElement>(element);
entityTreeElement->forEachEntity([&findContainingZone](EntityItemPointer item) {
if (item->getType() == EntityTypes::Zone && item->contains(findContainingZone->position)) {
auto zoneItem = static_pointer_cast<ZoneEntityItem>(item);
auto avatarPriorityProperty = zoneItem->getAvatarPriority();
auto screenshareProperty = zoneItem->getScreenshare();
float volume = zoneItem->getVolumeEstimate();
if (avatarPriorityProperty != COMPONENT_MODE_INHERIT
&& volume < findContainingZone->priorityZoneVolume) { // Smaller volume wins
findContainingZone->isInPriorityZone = avatarPriorityProperty == COMPONENT_MODE_ENABLED;
findContainingZone->priorityZoneVolume = volume;
}
});
return true; // Keep recursing
} else { // Position isn't within this subspace, so end recursion.
return false;
}
if (screenshareProperty != COMPONENT_MODE_INHERIT
&& volume < findContainingZone->screenshareZoneVolume) {
findContainingZone->isInScreenshareZone = screenshareProperty == COMPONENT_MODE_ENABLED;
findContainingZone->screenshareZoneVolume = volume;
findContainingZone->screenshareZoneid = zoneItem->getEntityItemID();
}
}
});
return true; // Keep recursing
} else { // Position isn't within this subspace, so end recursion.
return false;
}
};
}
};
} // Close anonymous namespace.
} // namespace
int AvatarMixerClientData::parseData(ReceivedMessage& message, const SlaveSharedData& slaveSharedData) {
// pull the sequence number from the data first
@ -152,15 +158,22 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message, const SlaveShared
auto newPosition = _avatar->getClientGlobalPosition();
if (newPosition != oldPosition || _avatar->getNeedsHeroCheck()) {
EntityTree& entityTree = *slaveSharedData.entityTree;
FindPriorityZone findPriorityZone { newPosition } ;
entityTree.recurseTreeWithOperation(&FindPriorityZone::operation, &findPriorityZone);
bool currentlyHasPriority = findPriorityZone.isInPriorityZone;
FindContainingZone findContainingZone{ newPosition };
entityTree.recurseTreeWithOperation(&FindContainingZone::operation, &findContainingZone);
bool currentlyHasPriority = findContainingZone.isInPriorityZone;
if (currentlyHasPriority != _avatar->getHasPriority()) {
_avatar->setHasPriority(currentlyHasPriority);
}
bool isInScreenshareZone = findContainingZone.isInScreenshareZone;
if (isInScreenshareZone != _avatar->isInScreenshareZone()
|| findContainingZone.screenshareZoneid != _avatar->getScreenshareZone()) {
_avatar->setInScreenshareZone(isInScreenshareZone);
_avatar->setScreenshareZone(findContainingZone.screenshareZoneid);
const QUuid& zoneId = isInScreenshareZone ? findContainingZone.screenshareZoneid : QUuid();
auto nodeList = DependencyManager::get<NodeList>();
auto packet = NLPacket::create(PacketType::AvatarZonePresence, 2 * NUM_BYTES_RFC4122_UUID, true);
packet->write(_avatar->getSessionUUID().toRfc4122());
packet->write(findPriorityZone.id.toRfc4122());
packet->write(zoneId.toRfc4122());
nodeList->sendPacket(std::move(packet), nodeList->getDomainSockAddr());
}
_avatar->setNeedsHeroCheck(false);
@ -227,8 +240,7 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message,
}
} else {
// Trying to read more bytes than available, bail
if (message.getBytesLeftToRead() < qint64(NUM_BYTES_RFC4122_UUID +
sizeof(AvatarTraits::TraitWireSize))) {
if (message.getBytesLeftToRead() < qint64(NUM_BYTES_RFC4122_UUID + sizeof(AvatarTraits::TraitWireSize))) {
qWarning() << "Refusing to process malformed traits packet from" << message.getSenderSockAddr();
return;
}
@ -244,8 +256,7 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message,
break;
}
if (traitType == AvatarTraits::AvatarEntity ||
traitType == AvatarTraits::Grab) {
if (traitType == AvatarTraits::AvatarEntity || traitType == AvatarTraits::Grab) {
auto& instanceVersionRef = _lastReceivedTraitVersions.getInstanceValueRef(traitType, instanceID);
if (packetTraitVersion > instanceVersionRef) {
@ -303,7 +314,8 @@ void AvatarMixerClientData::processBulkAvatarTraitsAckMessage(ReceivedMessage& m
auto simpleReceivedIt = traitVersions.simpleCBegin();
while (simpleReceivedIt != traitVersions.simpleCEnd()) {
if (*simpleReceivedIt != AvatarTraits::DEFAULT_TRAIT_VERSION) {
auto traitType = static_cast<AvatarTraits::TraitType>(std::distance(traitVersions.simpleCBegin(), simpleReceivedIt));
auto traitType =
static_cast<AvatarTraits::TraitType>(std::distance(traitVersions.simpleCBegin(), simpleReceivedIt));
_perNodeAckedTraitVersions[nodeId][traitType] = *simpleReceivedIt;
}
simpleReceivedIt++;
@ -361,8 +373,8 @@ void AvatarMixerClientData::checkSkeletonURLAgainstWhitelist(const SlaveSharedDa
// make sure we're not unecessarily overriding the default avatar with the default avatar
if (_avatar->getWireSafeSkeletonModelURL() != slaveSharedData.skeletonReplacementURL) {
// we need to change this avatar's skeleton URL, and send them a traits packet informing them of the change
qDebug() << "Overwriting avatar URL" << _avatar->getWireSafeSkeletonModelURL()
<< "to replacement" << slaveSharedData.skeletonReplacementURL << "for" << sendingNode.getUUID();
qDebug() << "Overwriting avatar URL" << _avatar->getWireSafeSkeletonModelURL() << "to replacement"
<< slaveSharedData.skeletonReplacementURL << "for" << sendingNode.getUUID();
_avatar->setSkeletonModelURL(slaveSharedData.skeletonReplacementURL);
auto packet = NLPacket::create(PacketType::SetAvatarTraits, -1, true);
@ -463,9 +475,7 @@ void AvatarMixerClientData::readViewFrustumPacket(const QByteArray& message) {
bool AvatarMixerClientData::otherAvatarInView(const AABox& otherAvatarBox) {
return std::any_of(std::begin(_currentViewFrustums), std::end(_currentViewFrustums),
[&](const ConicalViewFrustum& viewFrustum) {
return viewFrustum.intersects(otherAvatarBox);
});
[&](const ConicalViewFrustum& viewFrustum) { return viewFrustum.intersects(otherAvatarBox); });
}
void AvatarMixerClientData::loadJSONStats(QJsonObject& jsonObject) const {
@ -484,7 +494,8 @@ void AvatarMixerClientData::loadJSONStats(QJsonObject& jsonObject) const {
jsonObject["recent_other_av_out_of_view"] = _recentOtherAvatarsOutOfView;
}
AvatarMixerClientData::TraitsCheckTimestamp AvatarMixerClientData::getLastOtherAvatarTraitsSendPoint(Node::LocalID otherAvatar) const {
AvatarMixerClientData::TraitsCheckTimestamp AvatarMixerClientData::getLastOtherAvatarTraitsSendPoint(
Node::LocalID otherAvatar) const {
auto it = _lastSentTraitsTimestamps.find(otherAvatar);
if (it != _lastSentTraitsTimestamps.end()) {

View file

@ -43,6 +43,11 @@ public:
};
Q_ENUM(VerifyState)
bool isInScreenshareZone() const { return _inScreenshareZone; }
void setInScreenshareZone(bool value = true) { _inScreenshareZone = value; }
const QUuid& getScreenshareZone() const { return _screenshareZone; }
void setScreenshareZone(QUuid zone) { _screenshareZone = zone; }
private:
bool _needsHeroCheck { false };
static const char* stateToName(VerifyState state);
@ -65,6 +70,8 @@ private:
int _numberChallenges { 0 };
bool _certifyFailed { false };
bool _needsIdentityUpdate { false };
bool _inScreenshareZone { false };
QUuid _screenshareZone;
bool generateFSTHash();
bool validateFSTHash(const QString& publicKey) const;

View file

@ -3630,6 +3630,9 @@ void DomainServer::processAvatarZonePresencePacket(QSharedPointer<ReceivedMessag
return;
}
QString verifiedUsername = matchingNode->getPermissions().getVerifiedUserName();
if (verifiedUsername.isEmpty()) { // Silently bail for users who are not logged in.
return;
}
static const int SCREENSHARE_EXPIRATION_SECONDS = 24 * 60 * 60;
screensharePresence(zone.isNull() ? "" : zone.toString(), verifiedUsername, SCREENSHARE_EXPIRATION_SECONDS);
}

View file

@ -17,12 +17,64 @@
static const int VR_TARGET_RATE = 90;
/**jsdoc
* <p>Refresh rate profile.</p>
*
* <table>
* <thead>
* <tr><th>Refresh Rate Profile</th><th>Description</th></tr>
* </thead>
* <tbody>
* <tr><td><code>"Eco"</code></td><td>Low refresh rate, which is reduced when Interface doesn't have focus or is
* minimized.</td></tr>
* <tr><td><code>"Interactive"</code></td><td>Medium refresh rate, which is reduced when Interface doesn't have focus or is
* minimized.</td></tr>
* <tr><td><code>"Realtime"</code></td><td>High refresh rate, even when Interface doesn't have focus or is minimized.
* </tbody>
* </table>
*
* @typedef {string} RefreshRateProfile
*/
static const std::array<std::string, RefreshRateManager::RefreshRateProfile::PROFILE_NUM> REFRESH_RATE_PROFILE_TO_STRING =
{ { "Eco", "Interactive", "Realtime" } };
/**jsdoc
* <p>Interface states that affect the refresh rate.</p>
*
* <table>
* <thead>
* <tr><th>Refresh Rate Regime</th><th>Description</th></tr>
* </thead>
* <tbody>
* <tr><td><code>"FocusActive"</code></td><td>Interface has focus and the user is active or is in VR.</td></tr>
* <tr><td><code>"FocusInactive"</code></td><td>Interface has focus and the user is inactive.</td></tr>
* <tr><td><code>"Unfocus"</code></td><td>Interface doesn't have focus.</td></tr>
* <tr><td><code>"Minimized"</code></td><td>Interface is minimized.</td></tr>
* <tr><td><code>"StartUp"</code></td><td>Interface is starting up.</td></tr>
* <tr><td><code>"ShutDown"</code></td><td>Interface is shutting down.</td></tr>
* </tbody>
* </table>
*
* @typedef {string} RefreshRateRegime
*/
static const std::array<std::string, RefreshRateManager::RefreshRateRegime::REGIME_NUM> REFRESH_RATE_REGIME_TO_STRING =
{ { "FocusActive", "FocusInactive", "Unfocus", "Minimized", "StartUp", "ShutDown" } };
/**jsdoc
* <p>Interface operates in different modes to provide different user experiences (UX).</p>
*
* <table>
* <thead>
* <tr><th>UX Mode</th><th>Description</th></tr>
* </thead>
* <tbody>
* <tr><td><code>"Desktop"</code></td><td>Desktop user experience mode.</td></tr>
* <tr><td><code>"VR"</code></td><td>VR user experience mode.</td></tr>
* </tbody>
* </table>
*
* @typedef {string} UXMode
*/
static const std::array<std::string, RefreshRateManager::UXMode::UX_NUM> UX_MODE_TO_STRING =
{ { "Desktop", "VR" } };

View file

@ -90,7 +90,8 @@ static const EntityTypes::EntityType LOCAL_SCREENSHARE_WEB_ENTITY_TYPE = EntityT
static const uint8_t LOCAL_SCREENSHARE_WEB_ENTITY_FPS = 30;
// This is going to be a good amount of work to make this work dynamically for any screensize.
// V1 will have only hardcoded values.
static const glm::vec3 LOCAL_SCREENSHARE_WEB_ENTITY_LOCAL_POSITION(0.0128f, -0.0918f, 0.0771f);
// The `z` value here is dynamic.
static const glm::vec3 LOCAL_SCREENSHARE_WEB_ENTITY_LOCAL_POSITION(0.0128f, -0.0918f, 0.0f);
static const glm::vec3 LOCAL_SCREENSHARE_WEB_ENTITY_DIMENSIONS(3.6790f, 2.0990f, 0.0100f);
static const QString LOCAL_SCREENSHARE_WEB_ENTITY_URL =
"https://content.highfidelity.com/Experiences/Releases/usefulUtilities/smartBoard/screenshareViewer/screenshareClient.html";
@ -124,7 +125,7 @@ void ScreenshareScriptingInterface::startScreenshare(const QUuid& screenshareZon
// Ensure that the screenshare executable exists where we expect it to.
// Error out and reset the screen share state machine if the executable doesn't exist.
QFileInfo screenshareExecutable(SCREENSHARE_EXE_PATH);
if (!screenshareExecutable.exists() || !screenshareExecutable.isFile()) {
if (!screenshareExecutable.exists() || !(screenshareExecutable.isFile() || screenshareExecutable.isBundle())) {
qDebug() << "Screenshare executable doesn't exist at" << SCREENSHARE_EXE_PATH;
stopScreenshare();
emit screenshareError();
@ -241,7 +242,9 @@ void ScreenshareScriptingInterface::handleSuccessfulScreenshareInfoGet(QNetworkR
EntityItemProperties localScreenshareWebEntityProps;
localScreenshareWebEntityProps.setType(LOCAL_SCREENSHARE_WEB_ENTITY_TYPE);
localScreenshareWebEntityProps.setMaxFPS(LOCAL_SCREENSHARE_WEB_ENTITY_FPS);
localScreenshareWebEntityProps.setLocalPosition(LOCAL_SCREENSHARE_WEB_ENTITY_LOCAL_POSITION);
glm::vec3 localPosition(LOCAL_SCREENSHARE_WEB_ENTITY_LOCAL_POSITION);
localPosition.z = _localWebEntityZOffset;
localScreenshareWebEntityProps.setLocalPosition(localPosition);
localScreenshareWebEntityProps.setSourceUrl(LOCAL_SCREENSHARE_WEB_ENTITY_URL);
localScreenshareWebEntityProps.setParentID(_smartboardEntityID);
localScreenshareWebEntityProps.setDimensions(LOCAL_SCREENSHARE_WEB_ENTITY_DIMENSIONS);

View file

@ -21,6 +21,7 @@
class ScreenshareScriptingInterface : public QObject, public Dependency {
Q_OBJECT
Q_PROPERTY(float localWebEntityZOffset MEMBER _localWebEntityZOffset NOTIFY localWebEntityZOffsetChanged)
public:
ScreenshareScriptingInterface();
~ScreenshareScriptingInterface();
@ -32,6 +33,7 @@ signals:
void screenshareError();
void screenshareProcessTerminated();
void startScreenshareViewer();
void localWebEntityZOffsetChanged(const float& newZOffset);
private slots:
void onWebEventReceived(const QUuid& entityID, const QVariant& message);
@ -43,10 +45,10 @@ private:
#ifdef Q_OS_WIN
const QString SCREENSHARE_EXE_PATH{ PathUtils::projectRootPath() + "/screenshare/hifi-screenshare-win32-x64/hifi-screenshare.exe" };
#elif defined(Q_OS_MAC)
const QString SCREENSHARE_EXE_PATH{ PathUtils::projectRootPath() + "/screenshare/screenshare-darwin-x64/hifi-screenshare.app" };
const QString SCREENSHARE_EXE_PATH{ PathUtils::projectRootPath() + "/build/screenshare/hifi-screenshare-darwin-x64/hifi-screenshare.app" };
#else
// This path won't exist on other platforms, so the Screenshare Scripting Interface will exit early when invoked.
const QString SCREENSHARE_EXE_PATH{ PathUtils::projectRootPath() + "/screenshare/screenshare-other-os/hifi-screenshare" };
const QString SCREENSHARE_EXE_PATH{ PathUtils::projectRootPath() + "/screenshare/hifi-screenshare-other-os/hifi-screenshare" };
#endif
#else
#ifdef Q_OS_WIN
@ -63,6 +65,15 @@ private:
int _requestScreenshareInfoRetries{ 0 };
void requestScreenshareInfo();
// Empirically determined. The default value here can be changed in Screenshare scripts, which enables faster iteration when we discover
// positional issues with various Smartboard entities.
// The following four values are closely linked:
// 1. The z-offset of whiteboard polylines (`STROKE_FORWARD_OFFSET_M` in `drawSphereClient.js`).
// 2. The z-offset of the screenshare local web entity (`LOCAL_WEB_ENTITY_Z_OFFSET` in `smartboardZoneClient.js`).
// 3. The z-offset of the screenshare "glass bezel" (`DEFAULT_SMARTBOARD_SCREENSHARE_GLASS_PROPS` in `smartboardZoneClient.js`).
// 4. The z-offset of the screenshare "status icon" (handled in the screenshare JSON file).
float _localWebEntityZOffset{ 0.0375f };
std::unique_ptr<QProcess> _screenshareProcess{ nullptr };
QUuid _screenshareViewerLocalWebEntityUUID;
QString _token{ "" };

File diff suppressed because it is too large Load diff

13
launchers/qt/BUILD.md Normal file
View file

@ -0,0 +1,13 @@
# Dependencies
- [cmake](https://cmake.org/download/): 3.9
# Windows
* Download `Visual Studio 2019`
`cmake -G "Visual Studio 16 2019" ..`
# MacOS
* Install `Xcode`
`cmake -G Xcode ..`
If you wish to not use the compiled qml files, pass the `-DLAUNCHER_SOURCE_TREE_RESOURCES=On` argument to cmake.

34
launchers/qt/readme.md Normal file
View file

@ -0,0 +1,34 @@
# HQ Launcher
Behavior of the HQ Launcher is as follows:
* Update the HQ Launcher to the latest version
* Sign up or sign in if is the user is not already signed in
* Download the latest Interface client
* Launch the user in the current HQ domain
# directory structure
## src/ - contains the c++ and objective-c.
* `BuildsRequest` - getting / parsing the build info from thunder api
* `CommandlineOptions` - parses and stores commandline arguments
* `Helper` - helper functions
* `Helper_darwin` - objective-c implemention of helper funcions
* `Helper_windows` - helper function that depend on windows api
* `Launcher` - initialized the Launcher Application and resources
* `LauncherInstaller_windows` - logic of how to install/uninstall HQ Launcher on windows
* `LauncherState` - hold majority of the logic of the launcher (signin, config file, updating, running launcher)
* config files hold the following saved data
* logged in
* home location
* `LauncherWindows` - wrapper for `QQuickWindow` that implements drag feature
* `LoginRequest` - checks the login credentials the user typed in.
* `NSTask+NSTaskExecveAdditions` - Extension of NSTask for replacing Launcher process with interface client process
* `PathUtils` - Helper class for getting relative paths for HQ Launcher
* `SignupRequest` - Determines if the users request to signup for a new account succeeded based on the entered credentials
* `Unzipper` - helper class for extracting zip files
* `UserSettingsRequest` - getting the users setting (home location) from metaverse
## resources/
* `images/`- Holds the images and icon that are used by the launcher
* `qml/`
* UI elements
* `QML_FILE_FOR_UI_STATE` variable in `LauncherState` defines what QML files are used by the Launcher.

View file

@ -38,6 +38,10 @@ class AnimationCacheScriptingInterface : public ScriptableResourceCache, public
* @property {number} numCached - Total number of cached resource. <em>Read-only.</em>
* @property {number} sizeTotal - Size in bytes of all resources. <em>Read-only.</em>
* @property {number} sizeCached - Size in bytes of all cached resources. <em>Read-only.</em>
* @property {number} numGlobalQueriesPending - Total number of global queries pending (across all resource cache managers).
* <em>Read-only.</em>
* @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource cache managers).
* <em>Read-only.</em>
*
* @borrows ResourceCache.getResourceList as getResourceList
* @borrows ResourceCache.updateTotalSize as updateTotalSize

View file

@ -335,9 +335,9 @@ AudioClient::AudioClient() {
connect(&_receivedAudioStream, &InboundAudioStream::mismatchedAudioCodec, this, &AudioClient::handleMismatchAudioFormat);
// initialize wasapi; if getAvailableDevices is called from the CheckDevicesThread before this, it will crash
getAvailableDevices(QAudio::AudioInput, QString());
getAvailableDevices(QAudio::AudioOutput, QString());
defaultAudioDeviceName(QAudio::AudioInput);
defaultAudioDeviceName(QAudio::AudioOutput);
// start a thread to detect any device changes
_checkDevicesTimer = new QTimer(this);
const unsigned long DEVICE_CHECK_INTERVAL_MSECS = 2 * 1000;
@ -787,8 +787,11 @@ void AudioClient::start() {
inputName = _hmdInputName;
outputName = _hmdOutputName;
}
//initialize input to the dummy device to prevent starves
switchInputToAudioDevice(HifiAudioDeviceInfo());
switchOutputToAudioDevice(defaultAudioDeviceForMode(QAudio::AudioOutput, QString()));
#if defined(Q_OS_ANDROID)
connect(&_checkInputTimer, &QTimer::timeout, this, &AudioClient::checkInputTimeout);
_checkInputTimer.start(CHECK_INPUT_READS_MSECS);
@ -2064,6 +2067,11 @@ bool AudioClient::switchOutputToAudioDevice(const HifiAudioDeviceInfo outputDevi
Lock localAudioLock(_localAudioMutex);
_localSamplesAvailable.exchange(0, std::memory_order_release);
//wait on local injectors prep to finish running
if ( !_localPrepInjectorFuture.isFinished()) {
_localPrepInjectorFuture.waitForFinished();
}
// cleanup any previously initialized device
if (_audioOutput) {
_audioOutputIODevice.close();
@ -2342,9 +2350,9 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) {
qCDebug(audiostream, "Read %d samples from injectors (%d available, %d requested)", injectorSamplesPopped, _localInjectorsStream.samplesAvailable(), samplesRequested);
}
}
// prepare injectors for the next callback
QtConcurrent::run(QThreadPool::globalInstance(), [this] {
_audio->_localPrepInjectorFuture = QtConcurrent::run(QThreadPool::globalInstance(), [this] {
_audio->prepareLocalAudioInjectors();
});

View file

@ -18,6 +18,7 @@
#include <mutex>
#include <queue>
#include <QFuture>
#include <QtCore/QtGlobal>
#include <QtCore/QByteArray>
#include <QtCore/QElapsedTimer>
@ -506,7 +507,8 @@ private:
#endif
AudioSolo _solo;
QFuture<void> _localPrepInjectorFuture;
QReadWriteLock _hmdNameLock;
Mutex _checkDevicesMutex;
QTimer* _checkDevicesTimer { nullptr };

View file

@ -39,6 +39,10 @@ class SoundCacheScriptingInterface : public ScriptableResourceCache, public Depe
* @property {number} numCached - Total number of cached resource. <em>Read-only.</em>
* @property {number} sizeTotal - Size in bytes of all resources. <em>Read-only.</em>
* @property {number} sizeCached - Size in bytes of all cached resources. <em>Read-only.</em>
* @property {number} numGlobalQueriesPending - Total number of global queries pending (across all resource cache managers).
* <em>Read-only.</em>
* @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource cache managers).
* <em>Read-only.</em>
*
* @borrows ResourceCache.getResourceList as getResourceList
* @borrows ResourceCache.updateTotalSize as updateTotalSize

View file

@ -299,6 +299,16 @@ void EntityItemProperties::setAvatarPriorityFromString(const QString& mode) {
}
}
QString EntityItemProperties::getScreenshareAsString() const { return getComponentModeAsString(_screenshare); }
void EntityItemProperties::setScreenshareFromString(const QString& mode) {
auto modeItr = stringToComponentMode.find(mode.toLower());
if (modeItr != stringToComponentMode.end()) {
_screenshare = modeItr.value();
_screenshareChanged = true;
}
}
inline void addTextEffect(QHash<QString, TextEffect>& lookup, TextEffect effect) { lookup[TextEffectHelpers::getNameForTextEffect(effect)] = effect; }
const QHash<QString, TextEffect> stringToTextEffectLookup = [] {
QHash<QString, TextEffect> toReturn;
@ -566,6 +576,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_HAZE_MODE, hazeMode);
CHECK_PROPERTY_CHANGE(PROP_BLOOM_MODE, bloomMode);
CHECK_PROPERTY_CHANGE(PROP_AVATAR_PRIORITY, avatarPriority);
CHECK_PROPERTY_CHANGE(PROP_SCREENSHARE, screenshare);
// Polyvox
CHECK_PROPERTY_CHANGE(PROP_VOXEL_VOLUME_SIZE, voxelVolumeSize);
@ -1429,6 +1440,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* @property {Entities.AvatarPriorityMode} avatarPriority="inherit" - Configures the priority of updates from avatars in the
* zone to other clients.
*
* @property {Entities.ScreenshareMode} screenshare="inherit" - Configures a zone for screen-sharing.
*
* @example <caption>Create a zone that casts a red key light along the x-axis.</caption>
* var zone = Entities.addEntity({
* type: "Zone",
@ -1779,6 +1792,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_HAZE_MODE, hazeMode, getHazeModeAsString());
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BLOOM_MODE, bloomMode, getBloomModeAsString());
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_AVATAR_PRIORITY, avatarPriority, getAvatarPriorityAsString());
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SCREENSHARE, screenshare, getScreenshareAsString());
}
// Web only
@ -2150,6 +2164,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(hazeMode, HazeMode);
COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(bloomMode, BloomMode);
COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(avatarPriority, AvatarPriority);
COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(screenshare, Screenshare);
// Polyvox
COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelVolumeSize, vec3, setVoxelVolumeSize);
@ -2438,6 +2453,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) {
COPY_PROPERTY_IF_CHANGED(hazeMode);
COPY_PROPERTY_IF_CHANGED(bloomMode);
COPY_PROPERTY_IF_CHANGED(avatarPriority);
COPY_PROPERTY_IF_CHANGED(screenshare);
// Polyvox
COPY_PROPERTY_IF_CHANGED(voxelVolumeSize);
@ -2834,6 +2850,7 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr
ADD_PROPERTY_TO_MAP(PROP_HAZE_MODE, HazeMode, hazeMode, uint32_t);
ADD_PROPERTY_TO_MAP(PROP_BLOOM_MODE, BloomMode, bloomMode, uint32_t);
ADD_PROPERTY_TO_MAP(PROP_AVATAR_PRIORITY, AvatarPriority, avatarPriority, uint32_t);
ADD_PROPERTY_TO_MAP(PROP_SCREENSHARE, Screenshare, screenshare, uint32_t);
// Polyvox
ADD_PROPERTY_TO_MAP(PROP_VOXEL_VOLUME_SIZE, VoxelVolumeSize, voxelVolumeSize, vec3);
@ -3252,6 +3269,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
APPEND_ENTITY_PROPERTY(PROP_HAZE_MODE, (uint32_t)properties.getHazeMode());
APPEND_ENTITY_PROPERTY(PROP_BLOOM_MODE, (uint32_t)properties.getBloomMode());
APPEND_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, (uint32_t)properties.getAvatarPriority());
APPEND_ENTITY_PROPERTY(PROP_SCREENSHARE, (uint32_t)properties.getScreenshare());
}
if (properties.getType() == EntityTypes::PolyVox) {
@ -3726,6 +3744,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_HAZE_MODE, uint32_t, setHazeMode);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BLOOM_MODE, uint32_t, setBloomMode);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_AVATAR_PRIORITY, uint32_t, setAvatarPriority);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SCREENSHARE, uint32_t, setScreenshare);
}
if (properties.getType() == EntityTypes::PolyVox) {
@ -4117,6 +4136,7 @@ void EntityItemProperties::markAllChanged() {
_hazeModeChanged = true;
_bloomModeChanged = true;
_avatarPriorityChanged = true;
_screenshareChanged = true;
// Polyvox
_voxelVolumeSizeChanged = true;
@ -4739,6 +4759,9 @@ QList<QString> EntityItemProperties::listChangedProperties() {
if (avatarPriorityChanged()) {
out += "avatarPriority";
}
if (screenshareChanged()) {
out += "screenshare";
}
// Polyvox
if (voxelVolumeSizeChanged()) {

View file

@ -337,6 +337,7 @@ public:
DEFINE_PROPERTY_REF_ENUM(PROP_HAZE_MODE, HazeMode, hazeMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT);
DEFINE_PROPERTY_REF_ENUM(PROP_BLOOM_MODE, BloomMode, bloomMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT);
DEFINE_PROPERTY_REF_ENUM(PROP_AVATAR_PRIORITY, AvatarPriority, avatarPriority, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT);
DEFINE_PROPERTY_REF_ENUM(PROP_SCREENSHARE, Screenshare, screenshare, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT);
// Polyvox
DEFINE_PROPERTY_REF(PROP_VOXEL_VOLUME_SIZE, VoxelVolumeSize, voxelVolumeSize, glm::vec3, PolyVoxEntityItem::DEFAULT_VOXEL_VOLUME_SIZE);
@ -699,6 +700,8 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) {
DEBUG_PROPERTY_IF_CHANGED(debug, properties, AvatarPriority, avatarPriority, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, Screenshare, screenshare, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, EntityHostTypeAsString, entityHostType, "");
DEBUG_PROPERTY_IF_CHANGED(debug, properties, OwningAvatarID, owningAvatarID, "");

View file

@ -161,6 +161,7 @@ enum EntityPropertyList {
PROP_DERIVED_31,
PROP_DERIVED_32,
PROP_DERIVED_33,
PROP_DERIVED_34,
PROP_AFTER_LAST_ITEM,
@ -290,6 +291,8 @@ enum EntityPropertyList {
PROP_BLOOM_MODE = PROP_DERIVED_32,
// Avatar priority
PROP_AVATAR_PRIORITY = PROP_DERIVED_33,
// Screen-sharing
PROP_SCREENSHARE = PROP_DERIVED_34,
// Polyvox
PROP_VOXEL_VOLUME_SIZE = PROP_DERIVED_0,

View file

@ -71,6 +71,7 @@ EntityItemProperties ZoneEntityItem::getProperties(const EntityPropertyFlags& de
COPY_ENTITY_PROPERTY_TO_PROPERTIES(hazeMode, getHazeMode);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(bloomMode, getBloomMode);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(avatarPriority, getAvatarPriority);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(screenshare, getScreenshare);
return properties;
}
@ -118,6 +119,7 @@ bool ZoneEntityItem::setSubClassProperties(const EntityItemProperties& propertie
SET_ENTITY_PROPERTY_FROM_PROPERTIES(hazeMode, setHazeMode);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(bloomMode, setBloomMode);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(avatarPriority, setAvatarPriority);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(screenshare, setScreenshare);
somethingChanged = somethingChanged || _keyLightPropertiesChanged || _ambientLightPropertiesChanged ||
_skyboxPropertiesChanged || _hazePropertiesChanged || _bloomPropertiesChanged;
@ -194,6 +196,7 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
READ_ENTITY_PROPERTY(PROP_HAZE_MODE, uint32_t, setHazeMode);
READ_ENTITY_PROPERTY(PROP_BLOOM_MODE, uint32_t, setBloomMode);
READ_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, uint32_t, setAvatarPriority);
READ_ENTITY_PROPERTY(PROP_SCREENSHARE, uint32_t, setScreenshare);
return bytesRead;
}
@ -214,6 +217,7 @@ EntityPropertyFlags ZoneEntityItem::getEntityProperties(EncodeBitstreamParams& p
requestedProperties += PROP_GHOSTING_ALLOWED;
requestedProperties += PROP_FILTER_URL;
requestedProperties += PROP_AVATAR_PRIORITY;
requestedProperties += PROP_SCREENSHARE;
requestedProperties += PROP_KEY_LIGHT_MODE;
requestedProperties += PROP_AMBIENT_LIGHT_MODE;
@ -260,6 +264,7 @@ void ZoneEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits
APPEND_ENTITY_PROPERTY(PROP_HAZE_MODE, (uint32_t)getHazeMode());
APPEND_ENTITY_PROPERTY(PROP_BLOOM_MODE, (uint32_t)getBloomMode());
APPEND_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, getAvatarPriority());
APPEND_ENTITY_PROPERTY(PROP_SCREENSHARE, getScreenshare());
}
void ZoneEntityItem::debugDump() const {
@ -471,9 +476,9 @@ bool ZoneEntityItem::matchesJSONFilters(const QJsonObject& jsonFilters) const {
static const QString AVATAR_PRIORITY_PROPERTY = "avatarPriority";
// If set ignore only priority-inherit zones:
// If set match zones of interest to avatar mixer:
if (jsonFilters.contains(AVATAR_PRIORITY_PROPERTY) && jsonFilters[AVATAR_PRIORITY_PROPERTY].toBool()
&& _avatarPriority != COMPONENT_MODE_INHERIT) {
&& (_avatarPriority != COMPONENT_MODE_INHERIT || _screenshare != COMPONENT_MODE_INHERIT)) {
return true;
}

View file

@ -102,6 +102,9 @@ public:
uint32_t getAvatarPriority() const { return _avatarPriority; }
void setAvatarPriority(uint32_t value) { _avatarPriority = value; }
uint32_t getScreenshare() const { return _screenshare; }
void setScreenshare(uint32_t value) { _screenshare = value; }
bool keyLightPropertiesChanged() const { return _keyLightPropertiesChanged; }
bool ambientLightPropertiesChanged() const { return _ambientLightPropertiesChanged; }
bool skyboxPropertiesChanged() const { return _skyboxPropertiesChanged; }
@ -156,6 +159,9 @@ protected:
// Avatar-updates priority
uint32_t _avatarPriority { COMPONENT_MODE_INHERIT };
// Screen-sharing zone
uint32_t _screenshare { COMPONENT_MODE_INHERIT };
// Dirty flags turn true when either keylight properties is changing values.
bool _keyLightPropertiesChanged { false };
bool _ambientLightPropertiesChanged { false };

View file

@ -138,23 +138,28 @@ bool KeyboardMouseDevice::isWheelByTouchPad(QWheelEvent* event) {
QPoint delta = event->angleDelta();
int deltaValueX = abs(delta.x());
int deltaValueY = abs(delta.y());
const int MAX_WHEEL_DELTA_REPEAT = 20;
const int COMMON_WHEEL_DELTA_VALUE = 120;
if (deltaValueX != 0) {
if (abs(_lastWheelDelta.x()) == deltaValueX) {
_wheelDeltaRepeatCount.setX(_wheelDeltaRepeatCount.x() + 1);
} else {
_wheelDeltaRepeatCount.setX(0);
// If deltaValueX or deltaValueY are multiple of 120 they are triggered by a mouse wheel
bool isMouseWheel = (deltaValueX + deltaValueY) % COMMON_WHEEL_DELTA_VALUE == 0;
if (!isMouseWheel) {
// We track repetition in wheel values to detect non-standard mouse wheels
const int MAX_WHEEL_DELTA_REPEAT = 10;
if (deltaValueX != 0) {
if (abs(_lastWheelDelta.x()) == deltaValueX) {
_wheelDeltaRepeatCount.setX(_wheelDeltaRepeatCount.x() + 1);
} else {
_wheelDeltaRepeatCount.setX(0);
}
return _wheelDeltaRepeatCount.x() < MAX_WHEEL_DELTA_REPEAT;
}
return deltaValueX != COMMON_WHEEL_DELTA_VALUE && _wheelDeltaRepeatCount.x() < MAX_WHEEL_DELTA_REPEAT;
}
if (deltaValueY != 0) {
if (abs(_lastWheelDelta.y()) == deltaValueY) {
_wheelDeltaRepeatCount.setY(_wheelDeltaRepeatCount.y() + 1);
} else {
_wheelDeltaRepeatCount.setY(0);
if (deltaValueY != 0) {
if (abs(_lastWheelDelta.y()) == deltaValueY) {
_wheelDeltaRepeatCount.setY(_wheelDeltaRepeatCount.y() + 1);
} else {
_wheelDeltaRepeatCount.setY(0);
}
return _wheelDeltaRepeatCount.y() < MAX_WHEEL_DELTA_REPEAT;
}
return deltaValueY != COMMON_WHEEL_DELTA_VALUE && _wheelDeltaRepeatCount.y() < MAX_WHEEL_DELTA_REPEAT;
}
return false;
}
@ -166,8 +171,9 @@ void KeyboardMouseDevice::wheelEvent(QWheelEvent* event) {
QPoint delta = event->angleDelta();
float deltaX = (float)delta.x();
float deltaY = (float)delta.y();
_inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_POS).getChannel()].value = (deltaX > 0 ? deltaX : 0.0f);
_inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_NEG).getChannel()].value = (deltaX < 0 ? -deltaX : 0.0f);
const float WHEEL_X_ATTENUATION = 0.3f;
_inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_POS).getChannel()].value = (deltaX > 0 ? WHEEL_X_ATTENUATION * deltaX : 0.0f);
_inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_NEG).getChannel()].value = (deltaX < 0 ? -WHEEL_X_ATTENUATION * deltaX : 0.0f);
// Y mouse is inverted positive is pointing up the screen
const float WHEEL_Y_ATTENUATION = 0.02f;
_inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_POS).getChannel()].value = (deltaY < 0 ? -WHEEL_Y_ATTENUATION * deltaY : 0.0f);

View file

@ -37,6 +37,10 @@ class TextureCacheScriptingInterface : public ScriptableResourceCache, public De
* @property {number} numCached - Total number of cached resource. <em>Read-only.</em>
* @property {number} sizeTotal - Size in bytes of all resources. <em>Read-only.</em>
* @property {number} sizeCached - Size in bytes of all cached resources. <em>Read-only.</em>
* @property {number} numGlobalQueriesPending - Total number of global queries pending (across all resource cache managers).
* <em>Read-only.</em>
* @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource cache managers).
* <em>Read-only.</em>
*
* @borrows ResourceCache.getResourceList as getResourceList
* @borrows ResourceCache.updateTotalSize as updateTotalSize

View file

@ -37,6 +37,10 @@ class ModelCacheScriptingInterface : public ScriptableResourceCache, public Depe
* @property {number} numCached - Total number of cached resource. <em>Read-only.</em>
* @property {number} sizeTotal - Size in bytes of all resources. <em>Read-only.</em>
* @property {number} sizeCached - Size in bytes of all cached resources. <em>Read-only.</em>
* @property {number} numGlobalQueriesPending - Total number of global queries pending (across all resource cache managers).
* <em>Read-only.</em>
* @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource cache managers).
* <em>Read-only.</em>
*
* @borrows ResourceCache.getResourceList as getResourceList
* @borrows ResourceCache.updateTotalSize as updateTotalSize

View file

@ -91,8 +91,8 @@ private:
class ScriptableResource : public QObject {
/**jsdoc
* Information about a cached resource. Created by {@link AnimationCache.prefetch}, {@link ModelCache.prefetch},
* {@link SoundCache.prefetch}, or {@link TextureCache.prefetch}.
* Information about a cached resource. Created by {@link AnimationCache.prefetch}, {@link MaterialCache.prefetch},
* {@link ModelCache.prefetch}, {@link SoundCache.prefetch}, or {@link TextureCache.prefetch}.
*
* @class ResourceObject
*
@ -318,9 +318,11 @@ class ScriptableResourceCache : public QObject {
Q_PROPERTY(size_t sizeCached READ getSizeCachedResources NOTIFY dirty)
/**jsdoc
* @property {number} numGlobalQueriesPending - Total number of global queries pending (across all resource managers). <em>Read-only.</em>
* @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource managers). <em>Read-only.</em>
*/
* @property {number} numGlobalQueriesPending - Total number of global queries pending (across all resource cache managers).
* <em>Read-only.</em>
* @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource cache managers).
* <em>Read-only.</em>
*/
Q_PROPERTY(size_t numGlobalQueriesPending READ getNumGlobalQueriesPending NOTIFY dirty)
Q_PROPERTY(size_t numGlobalQueriesLoading READ getNumGlobalQueriesLoading NOTIFY dirty)
@ -332,7 +334,7 @@ public:
* @function ResourceCache.getResourceList
* @returns {string[]} The URLs of all resources in the cache.
* @example <caption>Report cached resources.</caption>
* // Replace AnimationCache with ModelCache, SoundCache, or TextureCache as appropriate.
* // Replace AnimationCache with MaterialCache, ModelCache, SoundCache, or TextureCache as appropriate.
*
* var cachedResources = AnimationCache.getResourceList();
* print("Cached resources: " + JSON.stringify(cachedResources));
@ -352,7 +354,7 @@ public:
* @param {string} url - The URL of the resource to prefetch.
* @returns {ResourceObject} A resource object.
* @example <caption>Prefetch a resource and wait until it has loaded.</caption>
* // Replace AnimationCache with ModelCache, SoundCache, or TextureCache as appropriate.
* // Replace AnimationCache with MaterialCache, ModelCache, SoundCache, or TextureCache as appropriate.
* // TextureCache has its own version of this function.
*
* var resourceURL = "https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/animations/sitting_idle.fbx";

View file

@ -277,6 +277,7 @@ enum class EntityVersion : PacketVersion {
ShadowBiasAndDistance,
TextEntityFonts,
ScriptServerKinematicMotion,
ScreenshareZone,
// Add new versions above here
NUM_PACKET_TYPE,

View file

@ -24,7 +24,7 @@ class MaterialCacheScriptingInterface : public ScriptableResourceCache, public D
// Properties are copied over from ResourceCache (see ResourceCache.h for reason).
/**jsdoc
* The <code>TextureCache</code> API manages texture cache resources.
* The <code>MaterialCache</code> API manages material cache resources.
*
* @namespace MaterialCache
*
@ -36,6 +36,10 @@ class MaterialCacheScriptingInterface : public ScriptableResourceCache, public D
* @property {number} numCached - Total number of cached resource. <em>Read-only.</em>
* @property {number} sizeTotal - Size in bytes of all resources. <em>Read-only.</em>
* @property {number} sizeCached - Size in bytes of all cached resources. <em>Read-only.</em>
* @property {number} numGlobalQueriesPending - Total number of global queries pending (across all resource cache managers).
* <em>Read-only.</em>
* @property {number} numGlobalQueriesLoading - Total number of global queries loading (across all resource cache managers).
* <em>Read-only.</em>
*
* @borrows ResourceCache.getResourceList as getResourceList
* @borrows ResourceCache.updateTotalSize as updateTotalSize

View file

@ -113,9 +113,9 @@ NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMater
/**jsdoc
* A material used in a {@link Entities.MaterialResource|MaterialResource}.
* @typedef {object} Entities.Material
* @property {string} name="" - A name for the material. Supported by all material models.
* @property {string} model="hifi_pbr" - Different material models support different properties and rendering modes.
* Supported models are: <code>"hifi_pbr"</code>, <code>"hifi_shader_simple"</code>.
* @property {string} name="" - A name for the material. Supported by all material models.
* @property {ColorFloat|RGBS|string} emissive - The emissive color, i.e., the color that the material emits. A
* {@link ColorFloat} value is treated as sRGB and must have component values in the range <code>0.0</code> &ndash;
* <code>1.0</code>. A {@link RGBS} value can be either RGB or sRGB.
@ -144,12 +144,17 @@ NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMater
* value for transparency.
* <code>"hifi_pbr"</code> model only.
* @property {number|string} opacityMapMode - The mode defining the interpretation of the opacity map. Values can be:
* <code>"OPACITY_MAP_OPAQUE"</code> for ignoring the opacity map information.
* <code>"OPACITY_MAP_MASK"</code> for using the opacity map as a mask, where only the texel greater than opacityCutoff are visible and rendered opaque.
* <code>"OPACITY_MAP_BLEND"</code> for using the opacity map for alpha blending the material surface with the background.
* <ul>
* <li><code>"OPACITY_MAP_OPAQUE"</code> for ignoring the <code>opacityMap</code> information.</li>
* <li><code>"OPACITY_MAP_MASK"</code> for using the <code>opacityMap</code> as a mask, where only the texel greater
* than <code>opacityCutoff</code> are visible and rendered opaque.</li>
* <li><code>"OPACITY_MAP_BLEND"</code> for using the <code>opacityMap</code> for alpha blending the material surface
* with the background.</li>
* </ul>
* Set to <code>"fallthrough"</code> to fall through to the material below. <code>"hifi_pbr"</code> model only.
* @property {number|string} opacityCutoff - The opacity cutoff threshold used to determine the opaque texels of the Opacity map
* when opacityMapMode is "OPACITY_MAP_MASK", range <code>0.0</code> &ndash; <code>1.0</code>.
* @property {number|string} opacityCutoff - The opacity cutoff threshold used to determine the opaque texels of the
* <code>opacityMap</code> when <code>opacityMapMode</code> is <code>"OPACITY_MAP_MASK"</code>, range <code>0.0</code>
* &ndash; <code>1.0</code>.
* Set to <code>"fallthrough"</code> to fall through to the material below. <code>"hifi_pbr"</code> model only.
* @property {string} roughnessMap - The URL of the roughness texture image. You can use this or <code>glossMap</code>, but not
* both.
@ -179,7 +184,7 @@ NetworkMaterialResource::ParsedMaterials NetworkMaterialResource::parseJSONMater
* Set to <code>"fallthrough"</code> to fall through to the material below. <code>"hifi_pbr"</code> model only.
* @property {Mat4|string} texCoordTransform1 - The transform to use for <code>occlusionMap</code> and <code>lightMap</code>.
* Set to <code>"fallthrough"</code> to fall through to the material below. <code>"hifi_pbr"</code> model only.
* @property {string} lightmapParams - Parameters for controlling how lightMap is used.
* @property {string} lightmapParams - Parameters for controlling how <code>lightMap</code> is used.
* Set to <code>"fallthrough"</code> to fall through to the material below. <code>"hifi_pbr"</code> model only.
* <p><em>Currently not used.</em></p>
* @property {string} materialParams - Parameters for controlling the material projection and repetition.

View file

@ -7,6 +7,8 @@
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
const { remote } = require('electron');
// Helpers
function handleError(error) {
if (error) {
@ -188,6 +190,10 @@ function stopSharing() {
// Callback to start publishing after we have setup the chromium stream
function gotStream(stream) {
if (localStream) {
stopSharing();
}
localStream = stream;
startTokboxPublisher(localStream);
@ -206,6 +212,9 @@ function onAccessApproved(desktop_id) {
console.log('Desktop Capture access rejected.');
return;
}
document.getElementById('screenshare').style.visibility = "block";
desktopSharing = true;
navigator.webkitGetUserMedia({
@ -221,6 +230,7 @@ function onAccessApproved(desktop_id) {
}
}
}, gotStream, handleError);
remote.getCurrentWindow().minimize();
}
@ -248,11 +258,15 @@ var publisher;
function startTokboxPublisher(stream) {
publisher = document.createElement("div");
var publisherOptions = {
videoSource: stream.getVideoTracks()[0],
audioFallbackEnabled: false,
audioSource: null,
fitMode: 'contain',
frameRate: 30,
height: 720,
insertMode: 'append',
width: 1280,
height: 720
publishAudio: false,
videoSource: stream.getVideoTracks()[0],
width: 1280
};
publisher = OT.initPublisher(publisher, publisherOptions, function(error){

File diff suppressed because it is too large Load diff

View file

@ -147,6 +147,9 @@
"avatarPriority": {
"tooltip": "Alter Avatars' update priorities."
},
"screenshare": {
"tooltip": "Enable screen-sharing within this zone"
},
"modelURL": {
"tooltip": "A mesh model from an FBX or OBJ file."
},

View file

@ -393,7 +393,8 @@ const DEFAULT_ENTITY_PROPERTIES = {
},
shapeType: "box",
bloomMode: "inherit",
avatarPriority: "inherit"
avatarPriority: "inherit",
screenshare: "inherit",
},
Model: {
collisionShape: "none",

View file

@ -497,6 +497,12 @@ const GROUPS = [
options: { inherit: "Inherit", crowd: "Crowd", hero: "Hero" },
propertyID: "avatarPriority",
},
{
label: "Screen-share",
type: "dropdown",
options: { inherit: "Inherit", disabled: "Off", enabled: "On" },
propertyID: "screenshare",
}
]
},

1
tools/animedit/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
*.pro.user

4
tools/animedit/LICENCE Normal file
View file

@ -0,0 +1,4 @@
Copyright (c) 2019 High Fidelity, Inc. All rights reserved.
Distributed under the Apache License, Version 2.0.
See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html

14
tools/animedit/README.md Normal file
View file

@ -0,0 +1,14 @@
animedit
-------------
avatar-animation.json editor for High Fidelity.
Use QtCreator to build.
Known issues:
* When switching node types, clear old types fields & set new fields to default values.
* Name field does not change when it has focus and leftHandPane selection is changed.

View file

@ -0,0 +1,35 @@
QT += quick
CONFIG += c++11
# The following define makes your compiler emit warnings if you use
# any Qt feature that has been marked deprecated (the exact warnings
# depend on your compiler). Refer to the documentation for the
# deprecated API to know how to port your code away from it.
DEFINES += QT_DEPRECATED_WARNINGS
# You can also make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
SOURCES += \
main.cpp \
treeitem.cpp \
treemodel.cpp
RESOURCES += qml.qrc
# Additional import path used to resolve QML modules in Qt Creator's code model
QML_IMPORT_PATH =
# Additional import path used to resolve QML modules just for Qt Quick Designer
QML_DESIGNER_IMPORT_PATH =
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
HEADERS += \
treeitem.h \
treemodel.h

36
tools/animedit/main.cpp Normal file
View file

@ -0,0 +1,36 @@
//
// Copyright (c) 2019 High Fidelity, Inc. All rights reserved.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "treemodel.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
// expose model
TreeModel model;
engine.rootContext()->setContextProperty("theModel", &model);
const QUrl url(QStringLiteral("qrc:/qml/main.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) {
if (!obj && url == objUrl) {
// failure loading main.qml
QCoreApplication::exit(-1);
}
}, Qt::QueuedConnection);
engine.load(url);
return app.exec();
}

26
tools/animedit/qml.qrc Normal file
View file

@ -0,0 +1,26 @@
<RCC>
<qresource prefix="/">
<file>qml/main.qml</file>
<file>qml/TreeDelegate.qml</file>
<file>qml/fields/BooleanField.qml</file>
<file>qml/fields/IdField.qml</file>
<file>qml/fields/JSONField.qml</file>
<file>qml/fields/NumberArrayField.qml</file>
<file>qml/fields/NumberField.qml</file>
<file>qml/fields/StringField.qml</file>
<file>qml/fields/TypeField.qml</file>
<file>qml/nodes/BlendDirectional.qml</file>
<file>qml/nodes/BlendLinear.qml</file>
<file>qml/nodes/BlendLinearMove.qml</file>
<file>qml/nodes/ClipData.qml</file>
<file>qml/nodes/DefaultPose.qml</file>
<file>qml/nodes/InverseKinematics.qml</file>
<file>qml/nodes/Manipulator.qml</file>
<file>qml/nodes/Overlay.qml</file>
<file>qml/nodes/PoleVector.qml</file>
<file>qml/nodes/RandomStateMachine.qml</file>
<file>qml/nodes/StateMachine.qml</file>
<file>qml/nodes/SplineIK.qml</file>
<file>qml/nodes/TwoBoneIK.qml</file>
</qresource>
</RCC>

View file

@ -0,0 +1,10 @@
import QtQuick 2.0
Item {
Text {
anchors.fill: parent
color: styleData.textColor
elide: styleData.elideMode
text: styleData.value
}
}

View file

@ -0,0 +1,44 @@
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 1.6
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.0
Row {
id: row
height: 20
width: 300
anchors.left: parent.left
anchors.leftMargin: 0
anchors.right: parent.right
anchors.rightMargin: 0
property string key
property bool value
property bool optional
function setValue(newValue) {
parent.fieldChanged(key, newValue);
}
Text {
id: keyText
y: 5
width: 100
text: key + ":"
font.pixelSize: 12
color: optional ? "blue" : "black"
}
CheckBox {
id: valueCheckBox
x: 100
y: 5
width: 200
checked: value ? Qt.Checked : Qt.Unchecked
onCheckedChanged: {
setValue(checked);
}
}
}

View file

@ -0,0 +1,41 @@
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 1.6
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.0
Row {
id: row
x: 0
y: 0
height: 20
anchors.left: parent.left
anchors.leftMargin: 0
anchors.right: parent.right
anchors.rightMargin: 0
property string theValue
function setValue(newValue) {
var ROLE_NAME = 0x0101;
theModel.setData(leftHandPane.currentIndex, newValue, ROLE_NAME);
}
Text {
id: element
y: 5
width: 100
text: qsTr("Id:")
font.pixelSize: 12
}
TextField {
id: textField
x: 100
width: 200
text: theValue
onEditingFinished: {
setValue(text);
}
}
}

View file

@ -0,0 +1,52 @@
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 1.6
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.0
Column {
id: row
width: parent.width
height: parent.height
anchors.left: parent.left
anchors.leftMargin: 0
anchors.right: parent.right
anchors.rightMargin: 0
property string key
property var value
property bool optional
spacing: 5
function setValue(newValue) {
parent.fieldChanged(key, newValue);
}
Text {
id: keyText
y: 5
width: row.width
text: key + ":"
font.pixelSize: 12
color: optional ? "blue" : "black"
}
TextArea {
id: valueTextField
x: 0
width: (keyText.width - 20)
height: (parent.height - 120)
wrapMode: TextEdit.NoWrap
// TODO: validate
text: JSON.stringify(value, null, 4)
onEditingFinished: {
value = JSON.parse(text);
setValue(value);
}
}
}

View file

@ -0,0 +1,54 @@
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 1.6
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.0
Row {
id: row
height: 20
width: 300
anchors.left: parent.left
anchors.leftMargin: 0
anchors.right: parent.right
anchors.rightMargin: 0
property string key
property var value
property bool optional
function setValue(newValue) {
parent.fieldChanged(key, newValue);
}
Text {
id: keyText
y: 5
width: 100
text: key + ":"
font.pixelSize: 12
color: optional ? "blue" : "black"
}
TextField {
id: valueTextField
x: 100
width: 200
// first start with a regex for an array of numbers
// ^\[\s*(\d+)(\s*,\s*(\d+))*\]$|\[\s*\]
// then a regex for a floating point number
// \d+\.?\d*
// then substitue the second into the \d+ of the first, yeilding this monstrocity.
// ^\[\s*(\d+\.?\d*)(\s*,\s*(\d+\.?\d*))*\]$|\[\s*\]
//validator: RegExpValidator { regExp: /^\[\s*(\d+\.?\d*)(\s*,\s*(\d+\.?\d*))*\]$|\[\s*\]/ }
text: JSON.stringify(value)
onEditingFinished: {
value = JSON.parse(text);
setValue(value);
}
}
}

View file

@ -0,0 +1,45 @@
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 1.6
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.0
Row {
id: row
height: 20
width: 300
anchors.left: parent.left
anchors.leftMargin: 0
anchors.right: parent.right
anchors.rightMargin: 0
property string key
property real value
property bool optional
function setValue(newValue) {
parent.fieldChanged(key, newValue);
}
Text {
id: keyText
y: 5
width: 100
text: key + ":"
font.pixelSize: 12
color: optional ? "blue" : "black"
}
TextField {
id: valueTextField
x: 100
width: 200
inputMethodHints: Qt.ImhFormattedNumbersOnly
text: value
onEditingFinished: {
value = text;
setValue(parseInt(text, 10));
}
}
}

View file

@ -0,0 +1,44 @@
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 1.6
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.0
Row {
id: row
height: 20
width: 300
anchors.left: parent.left
anchors.leftMargin: 0
anchors.right: parent.right
anchors.rightMargin: 0
property string key
property string value
property bool optional
function setValue(newValue) {
parent.fieldChanged(key, newValue);
}
Text {
id: keyText
y: 5
width: 100
text: key + ":"
font.pixelSize: 12
color: optional ? "blue" : "black"
}
TextField {
id: valueTextField
x: 100
width: 200
text: value
onEditingFinished: {
value = text;
setValue(text);
}
}
}

View file

@ -0,0 +1,67 @@
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 1.6
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.0
Row {
id: row
function getTypes() {
return [
"clip",
"blendDirectional",
"blendLinear",
"overlay",
"stateMachine",
"randomSwitchStateMachine",
"manipulator",
"inverseKinematics",
"defaultPose",
"twoBoneIK",
"splineIK",
"poleVectorConstraint"
];
}
function indexFromString(str) {
var index = getTypes().indexOf(str);
return (index === -1) ? 0 : index;
}
x: 0
y: 0
height: 20
anchors.left: parent.left
anchors.leftMargin: 0
anchors.right: parent.right
anchors.rightMargin: 0
property string theValue: "clip"
property int theIndex: indexFromString(theValue)
function setValue(newValue) {
var ROLE_TYPE = 0x0102;
theModel.setData(leftHandPane.currentIndex, newValue, ROLE_TYPE);
rightHandPane.reset();
}
Text {
id: element
y: 5
width: 100
text: qsTr("Type:")
font.pixelSize: 12
}
ComboBox {
id: comboBox
x: 100
width: 200
model: getTypes()
currentIndex: theIndex
onActivated: {
setValue(currentText);
}
}
}

262
tools/animedit/qml/main.qml Normal file
View file

@ -0,0 +1,262 @@
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 1.6
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.0
import "fields"
import "nodes"
ApplicationWindow {
id: root
visible: true
width: 1600
height: 1000
color: "#ffffff"
opacity: 1
title: qsTr("AnimEdit")
menuBar: appMenuBar
SplitView {
id: splitView
anchors.fill: parent
TreeView {
id: leftHandPane
width: 1000
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.top: parent.top
model: theModel
itemDelegate: TreeDelegate {}
TableViewColumn {
role: "name"
title: "Name"
width: 500
}
TableViewColumn {
role: "type"
title: "Type"
}
onClicked: {
rightHandPane.setIndex(index);
}
function expandTreeView() {
function expandAll(index) {
leftHandPane.expand(index);
var children = theModel.getChildrenModelIndices(index);
for (var i = 0; i < children.length; i++) {
leftHandPane.expand(children[i]);
expandAll(children[i]);
}
}
var index = theModel.index(0, 0);
expandAll(index);
}
}
Rectangle {
id: rightHandPane
color: "#adadad"
height: parent.height
width: 500
anchors.left: leftHandPane.right
anchors.leftMargin: 0
anchors.right: parent.right
anchors.rightMargin: 0
anchors.top: parent.top
anchors.topMargin: 0
anchors.bottom: parent.bottom
anchors.bottomMargin: 0
function createCustomData(qml, index) {
var component = Qt.createComponent(qml, Component.PreferSynchronous);
if (component.status === Component.Ready) {
var obj = component.createObject(rightHandPaneColumn);
obj.setIndex(index);
} else if (component.status === Component.Error) {
console.log("ERROR: " + component.errorString());
} else if (component.status === Component.Loading) {
console.log("ERROR: NOT READY");
}
}
function reset() {
setIndex(leftHandPane.currentIndex);
}
function setIndex(index) {
var ROLE_NAME = 0x0101;
var ROLE_TYPE = 0x0102;
var ROLE_DATA = 0x0103;
var idValue = theModel.data(index, ROLE_NAME);
var typeValue = theModel.data(index, ROLE_TYPE);
idField.theValue = idValue;
typeField.theValue = typeValue;
// delete previous custom data obj, if present
var orig = rightHandPaneColumn.children[2];
if (orig) {
orig.destroy();
}
if (typeValue === "clip") {
createCustomData("nodes/ClipData.qml", index);
} else if (typeValue === "blendDirectional") {
createCustomData("nodes/BlendDirectional.qml", index);
} else if (typeValue === "blendLinear") {
createCustomData("nodes/BlendLinear.qml", index);
} else if (typeValue === "blendLinearMove") {
createCustomData("nodes/BlendLinearMove.qml", index);
} else if (typeValue === "overlay") {
createCustomData("nodes/Overlay.qml", index);
} else if (typeValue === "stateMachine") {
createCustomData("nodes/StateMachine.qml", index);
} else if (typeValue === "randomSwitchStateMachine") {
createCustomData("nodes/RandomStateMachine.qml", index);
} else if (typeValue === "inverseKinematics") {
createCustomData("nodes/InverseKinematics.qml", index);
} else if (typeValue === "twoBoneIK") {
createCustomData("nodes/TwoBoneIK.qml", index);
} else if (typeValue === "defaultPose") {
createCustomData("nodes/DefaultPose.qml", index);
} else if (typeValue === "manipulator") {
createCustomData("nodes/Manipulator.qml", index);
} else if (typeValue === "splineIK") {
createCustomData("nodes/SplineIK.qml", index);
} else if (typeValue === "poleVectorConstraint") {
createCustomData("nodes/PoleVector.qml", index);
}
}
Column {
id: rightHandPaneColumn
anchors.right: parent.right
anchors.rightMargin: 0
anchors.left: parent.left
anchors.leftMargin: 10
anchors.bottom: parent.bottom
anchors.bottomMargin: 0
anchors.top: parent.top
anchors.topMargin: 0
spacing: 6
IdField {
id: idField
}
TypeField {
id: typeField
}
}
}
}
MenuBar {
id: appMenuBar
Menu {
title: "File"
MenuItem {
text: "Open..."
onTriggered: openFileDialog.open()
}
MenuItem {
text: "Save As..."
onTriggered: saveAsFileDialog.open()
}
}
Menu {
title: "Edit"
MenuItem {
text: "Add New Node as Child"
onTriggered: {
theModel.newNode(leftHandPane.currentIndex);
}
}
MenuItem {
text: "Delete Selected"
onTriggered: {
theModel.deleteNode(leftHandPane.currentIndex);
}
}
MenuItem {
text: "Insert New Node Above"
onTriggered: {
theModel.insertNodeAbove(leftHandPane.currentIndex);
}
}
MenuItem {
text: "Copy Node Only"
onTriggered: {
theModel.copyNode(leftHandPane.currentIndex);
}
}
MenuItem {
text: "Copy Node And Children"
onTriggered: {
theModel.copyNodeAndChildren(leftHandPane.currentIndex);
}
}
MenuItem {
text: "Paste Over"
onTriggered: {
theModel.pasteOver(leftHandPane.currentIndex);
}
}
MenuItem {
text: "Paste As Child"
onTriggered: {
theModel.pasteAsChild(leftHandPane.currentIndex);
}
}
}
}
FileDialog {
id: openFileDialog
title: "Open an animation json file"
folder: shortcuts.home
nameFilters: ["Json files (*.json)"]
onAccepted: {
var path = openFileDialog.fileUrl.toString();
// remove prefixed "file:///"
path = path.replace(/^(file:\/{3})/,"");
// unescape html codes like '%23' for '#'
var cleanPath = decodeURIComponent(path);
console.log("You chose: " + cleanPath);
theModel.loadFromFile(cleanPath);
leftHandPane.expandTreeView();
}
onRejected: {
console.log("Canceled");
}
}
FileDialog {
id: saveAsFileDialog
title: "Save an animation json file"
folder: shortcuts.home
nameFilters: ["Json files (*.json)"]
selectExisting: false
onAccepted: {
var path = saveAsFileDialog.fileUrl.toString();
// remove prefixed "file:///"
path = path.replace(/^(file:\/{3})/,"");
// unescape html codes like '%23' for '#'
var cleanPath = decodeURIComponent(path);
console.log("You chose: " + cleanPath);
theModel.saveToFile(cleanPath);
}
onRejected: {
console.log("Canceled");
}
}
}

View file

@ -0,0 +1,129 @@
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 1.6
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.0
import "../fields"
Column {
id: column
x: 0
y: 0
height: 50
width: 200
spacing: 6
property var modelIndex
signal fieldChanged(string key, var newValue)
// called by each field when its value is changed.
function fieldChangedMethod(key, newValue) {
var ROLE_DATA = 0x0103;
var dataValue = theModel.data(modelIndex, ROLE_DATA);
dataValue[key] = newValue;
// copy the new value into the model.
theModel.setData(modelIndex, dataValue, ROLE_DATA);
}
// called when a new model is loaded
function setIndex(index) {
modelIndex = index;
var ROLE_DATA = 0x0103;
var dataValue = theModel.data(modelIndex, ROLE_DATA);
var fields = ["alpha", "alphaVar", "centerId", "upId", "downId", "leftId", "rightId", "upLeftId", "upRightId", "downLeftId", "downRightId"];
// copy data from theModel into each field.
var l = fields.length;
for (var i = 0; i < l; i++) {
var val = dataValue[fields[i]];
if (val) {
column.children[i].value = val;
}
}
}
Component.onCompleted: {
// this signal is fired from each data field when values are changed.
column.fieldChanged.connect(fieldChangedMethod)
}
NumberArrayField {
id: alphaField
key: "alpha"
value: [0, 0, 0]
}
StringField {
id: alphaVarField
key: "alphaVar"
value: ""
optional: true
}
StringField {
id: centerIdField
key: "centerId"
value: ""
optional: true
}
StringField {
id: upIdField
key: "upId"
value: ""
optional: true
}
StringField {
id: downIdField
key: "downId"
value: ""
optional: true
}
StringField {
id: leftIdField
key: "leftId"
value: ""
optional: true
}
StringField {
id: rightIdField
key: "rightId"
value: ""
optional: true
}
StringField {
id: upLeftIdField
key: "upLeftId"
value: ""
optional: true
}
StringField {
id: upRightIdField
key: "upRightId"
value: ""
optional: true
}
StringField {
id: downLeftIdField
key: "downLeftId"
value: ""
optional: true
}
StringField {
id: downRightIdField
key: "downRightId"
value: ""
optional: true
}
}

View file

@ -0,0 +1,73 @@
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 1.6
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.0
import "../fields"
Column {
id: column
x: 0
y: 0
height: 50
width: 200
spacing: 6
property var modelIndex
signal fieldChanged(string key, var newValue)
// called by each field when its value is changed.
function fieldChangedMethod(key, newValue) {
var ROLE_DATA = 0x0103;
var dataValue = theModel.data(modelIndex, ROLE_DATA);
dataValue[key] = newValue;
// copy the new value into the model.
theModel.setData(modelIndex, dataValue, ROLE_DATA);
}
// called when a new model is loaded
function setIndex(index) {
modelIndex = index;
var ROLE_DATA = 0x0103;
var dataValue = theModel.data(modelIndex, ROLE_DATA);
var fields = ["alpha", "blendType", "alphaVar"];
// copy data from theModel into each field.
var l = fields.length;
for (var i = 0; i < l; i++) {
var val = dataValue[fields[i]];
if (val) {
column.children[i].value = val;
}
}
}
Component.onCompleted: {
// this signal is fired from each data field when values are changed.
column.fieldChanged.connect(fieldChangedMethod)
}
NumberField {
id: alphaField
key: "alpha"
value: 0.0
}
StringField {
id: blendTypeField
key: "blendType"
value: ""
optional: true
}
StringField {
id: alphaVarField
key: "alphaVar"
value: ""
optional: true
}
}

View file

@ -0,0 +1,85 @@
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 1.6
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.0
import "../fields"
Column {
id: column
x: 0
y: 0
height: 50
width: 200
spacing: 6
property var modelIndex
signal fieldChanged(string key, var newValue)
// called by each field when its value is changed.
function fieldChangedMethod(key, newValue) {
var ROLE_DATA = 0x0103;
var dataValue = theModel.data(modelIndex, ROLE_DATA);
dataValue[key] = newValue;
// copy the new value into the model.
theModel.setData(modelIndex, dataValue, ROLE_DATA);
}
// called when a new model is loaded
function setIndex(index) {
modelIndex = index;
var ROLE_DATA = 0x0103;
var dataValue = theModel.data(modelIndex, ROLE_DATA);
var fields = ["alpha", "desiredSpeed", "characteristicSpeeds", "alphaVar", "desiredSpeedVar"];
// copy data from theModel into each field.
var l = fields.length;
for (var i = 0; i < l; i++) {
var val = dataValue[fields[i]];
if (val) {
column.children[i].value = val;
}
}
}
Component.onCompleted: {
// this signal is fired from each data field when values are changed.
column.fieldChanged.connect(fieldChangedMethod)
}
NumberField {
id: alphaField
key: "alpha"
value: 0.0
}
NumberField {
id: desiredSpeedField
key: "desiredSpeed"
value: 0.0
}
NumberArrayField {
id: characteristicSpeedsField
key: "characteristicSpeeds"
value: []
}
StringField {
id: alphaVarField
key: "alphaVar"
value: ""
optional: true
}
StringField {
id: desiredSpeedVarField
key: "desiredSpeedVar"
value: ""
optional: true
}
}

View file

@ -0,0 +1,148 @@
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 1.6
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.0
import "../fields"
Column {
id: column
x: 0
y: 0
height: 50
width: 200
spacing: 6
property var modelIndex
signal fieldChanged(string key, var newValue)
// called by each field when its value is changed.
function fieldChangedMethod(key, newValue) {
var ROLE_DATA = 0x0103;
var dataValue = theModel.data(modelIndex, ROLE_DATA);
dataValue[key] = newValue;
// copy the new value into the model.
theModel.setData(modelIndex, dataValue, ROLE_DATA);
}
// called when a new model is loaded
function setIndex(index) {
modelIndex = index;
var ROLE_DATA = 0x0103;
var dataValue = theModel.data(modelIndex, ROLE_DATA);
var fields = ["url", "startFrame", "endFrame", "timeScale", "loopFlag", "mirrorFlag",
"blendType", "baseURL", "baseFrame", "startFrameVar", "endFrameVar",
"timeScaleVar", "loopFlagVar", "mirrorFlagVar"];
// copy data from theModel into each field.
var l = fields.length;
for (var i = 0; i < l; i++) {
var val = dataValue[fields[i]];
if (val) {
column.children[i].value = val;
}
}
}
Component.onCompleted: {
// this signal is fired from each data field when values are changed.
column.fieldChanged.connect(fieldChangedMethod)
}
StringField {
id: urlField
key: "url"
value: "qrc:///avatar/animations/idle.fbx"
}
NumberField {
id: startFrameField
key: "startFrame"
value: 0.0
}
NumberField {
id: endFrameField
key: "endFrame"
value: 0.0
}
NumberField {
id: timeScaleField
key: "timeScale"
value: 0.0
}
BooleanField {
id: loopFlagField
key: "loopFlag"
value: false
}
BooleanField {
id: mirrorFlagField
key: "mirrorFlag"
value: false
optional: true
}
StringField {
id: blendTypeField
key: "blendType"
value: ""
optional: true
}
StringField {
id: baseURLField
key: "baseURL"
value: ""
optional: true
}
NumberField {
id: baseFrameField
key: "baseFrame"
value: 0.0
optional: true
}
StringField {
id: startFrameVarField
key: "startFrameVar"
value: ""
optional: true
}
StringField {
id: endFrameVarField
key: "endFrameVar"
value: ""
optional: true
}
StringField {
id: timeScaleVarField
key: "timeScaleVar"
value: ""
optional: true
}
StringField {
id: loopFlagVarField
key: "loopFlagVar"
value: ""
optional: true
}
StringField {
id: mirrorFlagVarField
key: "mirrorFlagVar"
value: ""
optional: true
}
}

View file

@ -0,0 +1,53 @@
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 1.6
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.0
import "../fields"
Column {
id: column
x: 0
y: 0
height: 50
width: 200
spacing: 6
property var modelIndex
signal fieldChanged(string key, var newValue)
// called by each field when its value is changed.
function fieldChangedMethod(key, newValue) {
var ROLE_DATA = 0x0103;
var dataValue = theModel.data(modelIndex, ROLE_DATA);
dataValue[key] = newValue;
// copy the new value into the model.
theModel.setData(modelIndex, dataValue, ROLE_DATA);
}
// called when a new model is loaded
function setIndex(index) {
modelIndex = index;
var ROLE_DATA = 0x0103;
var dataValue = theModel.data(modelIndex, ROLE_DATA);
var fields = []; // This node has no data values
// copy data from theModel into each field.
var l = fields.length;
for (var i = 0; i < l; i++) {
var val = dataValue[fields[i]];
if (val) {
column.children[i].value = val;
}
}
}
Component.onCompleted: {
// this signal is fired from each data field when values are changed.
column.fieldChanged.connect(fieldChangedMethod)
}
}

View file

@ -0,0 +1,71 @@
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 1.6
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.0
import "../fields"
Column {
id: column
x: 0
y: 0
width: parent.width
height: (parent.height - 50)
spacing: 6
property var modelIndex
signal fieldChanged(string key, var newValue)
// called by each field when its value is changed.
function fieldChangedMethod(key, newValue) {
var ROLE_DATA = 0x0103;
var dataValue = theModel.data(modelIndex, ROLE_DATA);
dataValue[key] = newValue;
// copy the new value into the model.
theModel.setData(modelIndex, dataValue, ROLE_DATA);
}
// called when a new model is loaded
function setIndex(index) {
modelIndex = index;
var ROLE_DATA = 0x0103;
var dataValue = theModel.data(modelIndex, ROLE_DATA);
var fields = ["solutionSource", "solutionSourceVar", "targets"];
// copy data from theModel into each field.
var l = fields.length;
for (var i = 0; i < l; i++) {
var val = dataValue[fields[i]];
if (val) {
column.children[i].value = val;
}
}
}
Component.onCompleted: {
// this signal is fired from each data field when values are changed.
column.fieldChanged.connect(fieldChangedMethod)
}
StringField {
id: solutionSourceField
key: "solutionSource"
value: "relaxToUnderPoses"
}
StringField {
id: solutionSourceVarField
key: "solutionSourceVar"
value: "solutionSource"
}
JSONField {
id: statesField
key: "targets"
value: {}
}
}

View file

@ -0,0 +1,72 @@
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 1.6
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.0
import "../fields"
Column {
id: column
x: 0
y: 0
width: parent.width
height: parent.height
spacing: 6
property var modelIndex
signal fieldChanged(string key, var newValue)
// called by each field when its value is changed.
function fieldChangedMethod(key, newValue) {
var ROLE_DATA = 0x0103;
var dataValue = theModel.data(modelIndex, ROLE_DATA);
dataValue[key] = newValue;
// copy the new value into the model.
theModel.setData(modelIndex, dataValue, ROLE_DATA);
}
// called when a new model is loaded
function setIndex(index) {
modelIndex = index;
var ROLE_DATA = 0x0103;
var dataValue = theModel.data(modelIndex, ROLE_DATA);
var fields = ["alpha", "joints", "alphaVar"];
// copy data from theModel into each field.
var l = fields.length;
for (var i = 0; i < l; i++) {
var val = dataValue[fields[i]];
if (val) {
column.children[i].value = val;
}
}
}
Component.onCompleted: {
// this signal is fired from each data field when values are changed.
column.fieldChanged.connect(fieldChangedMethod)
}
NumberField {
id: alphaField
key: "alpha"
value: 1.0
}
JSONField {
id: statesField
key: "joints"
value: {}
}
StringField {
optional: true
id: alphaVarField
key: "alphaVar"
value: ""
}
}

View file

@ -0,0 +1,80 @@
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 1.6
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.0
import "../fields"
Column {
id: column
x: 0
y: 0
height: 50
width: 200
spacing: 6
property var modelIndex
signal fieldChanged(string key, var newValue)
// called by each field when its value is changed.
function fieldChangedMethod(key, newValue) {
var ROLE_DATA = 0x0103;
var dataValue = theModel.data(modelIndex, ROLE_DATA);
dataValue[key] = newValue;
// copy the new value into the model.
theModel.setData(modelIndex, dataValue, ROLE_DATA);
}
// called when a new model is loaded
function setIndex(index) {
modelIndex = index;
var ROLE_DATA = 0x0103;
var dataValue = theModel.data(modelIndex, ROLE_DATA);
var fields = ["boneSet", "alpha", "boneSetVar", "alphaVar"];
// copy data from theModel into each field.
var l = fields.length;
for (var i = 0; i < l; i++) {
var val = dataValue[fields[i]];
if (val) {
column.children[i].value = val;
}
}
}
Component.onCompleted: {
// this signal is fired from each data field when values are changed.
column.fieldChanged.connect(fieldChangedMethod)
}
StringField {
id: boneSetField
key: "boneSet"
value: "fullBody"
optional: true
}
NumberField {
id: alphaField
key: "alpha"
value: 0.0
}
StringField {
id: boneSetVarField
key: "boneSetVar"
value: ""
optional: true
}
StringField {
id: alphaVarField
key: "alphaVar"
value: ""
optional: true
}
}

View file

@ -0,0 +1,96 @@
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 1.6
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.0
import "../fields"
Column {
id: column
x: 0
y: 0
height: 50
width: 200
spacing: 6
property var modelIndex
signal fieldChanged(string key, var newValue)
// called by each field when its value is changed.
function fieldChangedMethod(key, newValue) {
var ROLE_DATA = 0x0103;
var dataValue = theModel.data(modelIndex, ROLE_DATA);
dataValue[key] = newValue;
// copy the new value into the model.
theModel.setData(modelIndex, dataValue, ROLE_DATA);
}
// called when a new model is loaded
function setIndex(index) {
modelIndex = index;
var ROLE_DATA = 0x0103;
var dataValue = theModel.data(modelIndex, ROLE_DATA);
var fields = ["referenceVector", "enabled", "baseJointName", "midJointName", "tipJointName",
"enabledVar", "poleVectorVar"];
// copy data from theModel into each field.
var l = fields.length;
for (var i = 0; i < l; i++) {
var val = dataValue[fields[i]];
if (val) {
column.children[i].value = val;
}
}
}
Component.onCompleted: {
// this signal is fired from each data field when values are changed.
column.fieldChanged.connect(fieldChangedMethod)
}
NumberArrayField {
id: referenceVectorField
key: "referenceVector"
value: [1, 0, 0]
}
BooleanField {
id: enabledField
key: "enabled"
value: false
}
StringField {
id: baseJointNameField
key: "baseJointName"
value: ""
}
StringField {
id: midJointNameField
key: "midJointName"
value: ""
}
StringField {
id: tipJointNameField
key: "tipJointName"
value: ""
}
StringField {
id: enabledVarField
key: "enabledVar"
value: ""
}
StringField {
id: poleVectorVarField
key: "poleVectorVar"
value: ""
}
}

View file

@ -0,0 +1,109 @@
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 1.6
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.0
import "../fields"
Column {
id: column
x: 0
y: 0
width: parent.width
height: (parent.height - 150)
spacing: 6
property var modelIndex
signal fieldChanged(string key, var newValue)
// called by each field when its value is changed.
function fieldChangedMethod(key, newValue) {
var ROLE_DATA = 0x0103;
var dataValue = theModel.data(modelIndex, ROLE_DATA);
dataValue[key] = newValue;
// copy the new value into the model.
theModel.setData(modelIndex, dataValue, ROLE_DATA);
}
// called when a new model is loaded
function setIndex(index) {
modelIndex = index;
var ROLE_DATA = 0x0103;
var dataValue = theModel.data(modelIndex, ROLE_DATA);
var fields = ["currentState", "randomSwitchTimeMin", "randomSwitchTimeMax", "triggerRandomSwitch",
"triggerTimeMin", "triggerTimeMax", "transitionVar", "states"];
// copy data from theModel into each field.
var l = fields.length;
for (var i = 0; i < l; i++) {
var val = dataValue[fields[i]];
if (val) {
column.children[i].value = val;
}
}
}
Component.onCompleted: {
// this signal is fired from each data field when values are changed.
column.fieldChanged.connect(fieldChangedMethod)
}
StringField {
id: currentStateField
key: "currentState"
value: ""
}
NumberField {
id: randomSwitchTimeMinField
key: "randomSwitchTimeMin"
value: 1.0
optional: true
}
NumberField {
id: randomSwitchTimeMaxField
key: "randomSwitchTimeMax"
value: 10.0
optional: true
}
StringField {
id: triggerRandomSwitchField
key: "triggerRandomSwitch"
value: ""
optional: true
}
NumberField {
id: triggerTimeMinField
key: "triggerTimeMin"
value: 1.0
optional: true
}
NumberField {
id: triggerTimeMaxField
key: "triggerTimeMax"
value: 10.0
optional: true
}
StringField {
id: transitionVarField
key: "transitionVar"
value: ""
optional: true
}
JSONField {
id: statesField
key: "states"
value: {}
}
}

View file

@ -0,0 +1,139 @@
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 1.6
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.0
import "../fields"
Column {
id: column
x: 0
y: 0
height: 50
width: 200
spacing: 6
property var modelIndex
signal fieldChanged(string key, var newValue)
// called by each field when its value is changed.
function fieldChangedMethod(key, newValue) {
var ROLE_DATA = 0x0103;
var dataValue = theModel.data(modelIndex, ROLE_DATA);
dataValue[key] = newValue;
// copy the new value into the model.
theModel.setData(modelIndex, dataValue, ROLE_DATA);
}
// called when a new model is loaded
function setIndex(index) {
modelIndex = index;
var ROLE_DATA = 0x0103;
var dataValue = theModel.data(modelIndex, ROLE_DATA);
var fields = ["alpha", "enabled", "interpDuration", "baseJointName", "midJointName", "tipJointName",
"basePositionVar", "baseRotationVar", "midPositionVar", "midRotationVar", "tipPositionVar", "tipRotationVar",
"alphaVar", "enabledVar"];
// copy data from theModel into each field.
var l = fields.length;
for (var i = 0; i < l; i++) {
var val = dataValue[fields[i]];
if (val) {
column.children[i].value = val;
}
}
}
Component.onCompleted: {
// this signal is fired from each data field when values are changed.
column.fieldChanged.connect(fieldChangedMethod)
}
NumberField {
id: alphaField
key: "alpha"
value: 1.0
}
BooleanField {
id: enabledField
key: "enabled"
value: false
}
NumberField {
id: interpDurationField
key: "interpDuration"
value: 15.0
}
StringField {
id: baseJointNameField
key: "baseJointName"
value: ""
}
StringField {
id: midJointNameField
key: "midJointName"
value: ""
}
StringField {
id: tipJointNameField
key: "tipJointName"
value: ""
}
StringField {
id: basePositionVarField
key: "basePositionVar"
value: ""
}
StringField {
id: baseRotationVarField
key: "baseRotationVar"
value: ""
}
StringField {
id: midPositionVarField
key: "midPositionVar"
value: ""
}
StringField {
id: midRotationVarField
key: "midRotationVar"
value: ""
}
StringField {
id: tipPositionVarField
key: "tipPositionVar"
value: ""
}
StringField {
id: tipRotationVarField
key: "tipRotationVar"
value: ""
}
StringField {
id: alphaVarField
key: "alphaVar"
value: ""
}
StringField {
id: enabledVarField
key: "enabledVar"
value: ""
}
}

View file

@ -0,0 +1,65 @@
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 1.6
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.0
import "../fields"
Column {
id: column
x: 0
y: 0
height: parent.height
width: parent.width
spacing: 6
property var modelIndex
signal fieldChanged(string key, var newValue)
// called by each field when its value is changed.
function fieldChangedMethod(key, newValue) {
var ROLE_DATA = 0x0103;
var dataValue = theModel.data(modelIndex, ROLE_DATA);
dataValue[key] = newValue;
// copy the new value into the model.
theModel.setData(modelIndex, dataValue, ROLE_DATA);
}
// called when a new model is loaded
function setIndex(index) {
modelIndex = index;
var ROLE_DATA = 0x0103;
var dataValue = theModel.data(modelIndex, ROLE_DATA);
var fields = ["currentState", "states"];
// copy data from theModel into each field.
var l = fields.length;
for (var i = 0; i < l; i++) {
var val = dataValue[fields[i]];
if (val) {
column.children[i].value = val;
}
}
}
Component.onCompleted: {
// this signal is fired from each data field when values are changed.
column.fieldChanged.connect(fieldChangedMethod)
}
StringField {
id: currentStateField
key: "currentState"
value: "default"
}
JSONField {
id: statesField
key: "states"
value: {}
}
}

View file

@ -0,0 +1,120 @@
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 1.6
import QtQuick.Layouts 1.3
import QtQuick.Dialogs 1.0
import "../fields"
Column {
id: column
x: 0
y: 0
height: 50
width: 200
spacing: 6
property var modelIndex
signal fieldChanged(string key, var newValue)
// called by each field when its value is changed.
function fieldChangedMethod(key, newValue) {
var ROLE_DATA = 0x0103;
var dataValue = theModel.data(modelIndex, ROLE_DATA);
dataValue[key] = newValue;
// copy the new value into the model.
theModel.setData(modelIndex, dataValue, ROLE_DATA);
}
// called when a new model is loaded
function setIndex(index) {
modelIndex = index;
var ROLE_DATA = 0x0103;
var dataValue = theModel.data(modelIndex, ROLE_DATA);
var fields = ["alpha", "enabled", "interpDuration", "baseJointName", "midJointName", "tipJointName", "midHingeAxis",
"alphaVar", "enabledVar", "endEffectorRotationVarVar", "endEffectorPositionVarVar"];
// copy data from theModel into each field.
var l = fields.length;
for (var i = 0; i < l; i++) {
var val = dataValue[fields[i]];
if (val) {
column.children[i].value = val;
}
}
}
Component.onCompleted: {
// this signal is fired from each data field when values are changed.
column.fieldChanged.connect(fieldChangedMethod)
}
NumberField {
id: alphaField
key: "alpha"
value: 1.0
}
BooleanField {
id: enabledField
key: "enabled"
value: false
}
NumberField {
id: interpDurationField
key: "interpDuration"
value: 15.0
}
StringField {
id: baseJointNameField
key: "baseJointName"
value: ""
}
StringField {
id: midJointNameField
key: "midJointName"
value: ""
}
StringField {
id: tipJointNameField
key: "tipJointName"
value: ""
}
NumberArrayField {
id: midHingeAxisField
key: "midHingeAxis"
value: [1, 0, 0]
}
StringField {
id: alphaVarField
key: "alphaVar"
value: ""
}
StringField {
id: enabledVarField
key: "enabledVar"
value: ""
}
StringField {
id: endEffectorRotationVarVarField
key: "endEffectorRotationVarVar"
value: ""
}
StringField {
id: endEffectorPositionVarVarField
key: "endEffectorPositionVarVar"
value: ""
}
}

View file

@ -0,0 +1,91 @@
//
// TreeItem
//
// Created by Anthony Thibault on 6/5/2019
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "treeitem.h"
#include <QDebug>
#include <QStringList>
TreeItem::TreeItem(const QList<QVariant>& data) {
m_parentItem = nullptr;
m_itemData = data;
}
TreeItem::~TreeItem() {
qDeleteAll(m_childItems);
}
void TreeItem::appendChild(TreeItem *item) {
item->m_parentItem = this;
m_childItems.append(item);
}
int TreeItem::findChild(TreeItem* child) {
for (int i = 0; i < m_childItems.size(); i++) {
if (m_childItems[i] == child) {
return i;
}
}
return -1;
}
void TreeItem::removeChild(int index) {
// TODO: delete TreeItem
m_childItems.removeAt(index);
}
void TreeItem::insertChild(int index, TreeItem* child) {
child->m_parentItem = this;
m_childItems.insert(index, child);
}
TreeItem* TreeItem::child(int row) {
return m_childItems.value(row);
}
int TreeItem::childCount() const {
return m_childItems.count();
}
int TreeItem::columnCount() const {
return m_itemData.count();
}
QVariant TreeItem::data(int column) const {
return m_itemData.value(column);
}
bool TreeItem::setData(int column, const QVariant& value) {
m_itemData[column] = QVariant(value);
return true;
}
TreeItem* TreeItem::parentItem() {
return m_parentItem;
}
int TreeItem::row() const {
if (m_parentItem) {
return m_parentItem->m_childItems.indexOf(const_cast<TreeItem*>(this));
}
return 0;
}
TreeItem* TreeItem::cloneNode() const {
return new TreeItem(m_itemData);
}
TreeItem* TreeItem::cloneNodeAndChildren() const {
TreeItem* newNode = new TreeItem(m_itemData);
for (int i = 0; i < m_childItems.size(); ++i) {
newNode->appendChild(m_childItems[i]->cloneNodeAndChildren());
}
return newNode;
}

45
tools/animedit/treeitem.h Normal file
View file

@ -0,0 +1,45 @@
//
// TreeItem
//
// Created by Anthony Thibault on 6/5/2019
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_TreeItem_h
#define hifi_TreeItem_h
#include <QList>
#include <QVariant>
class TreeItem
{
public:
explicit TreeItem(const QList<QVariant>& data);
~TreeItem();
void appendChild(TreeItem* child);
int findChild(TreeItem* child);
void removeChild(int index);
void insertChild(int index, TreeItem* child);
TreeItem* child(int row);
int childCount() const;
int columnCount() const;
QVariant data(int column) const;
bool setData(int column, const QVariant& value);
int row() const;
TreeItem* parentItem();
TreeItem* cloneNode() const;
TreeItem* cloneNodeAndChildren() const;
private:
QList<TreeItem*> m_childItems;
QList<QVariant> m_itemData;
TreeItem* m_parentItem;
};
#endif

View file

@ -0,0 +1,418 @@
//
// TreeModel
//
// Created by Anthony Thibault on 6/5/2019
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "treeitem.h"
#include <QDebug>
#include <QFile>
#include <QtGlobal>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QStringList>
#include <QJSValue>
#include "treemodel.h"
static TreeItem* newBlankTreeItem() {
QList<QVariant> columnData;
columnData << "newNode";
columnData << "clip";
columnData << QJsonObject(); // blank
return new TreeItem(columnData);
}
TreeModel::TreeModel(QObject* parent) : QAbstractItemModel(parent) {
_roleNameMapping[TreeModelRoleName] = "name";
_roleNameMapping[TreeModelRoleType] = "type";
_roleNameMapping[TreeModelRoleData] = "data";
QList<QVariant> rootData;
rootData << "Name" << "Type" << "Data";
_rootItem = new TreeItem(rootData);
_clipboard = nullptr;
}
TreeModel::~TreeModel() {
delete _rootItem;
}
QHash<int, QByteArray> TreeModel::roleNames() const {
return _roleNameMapping;
}
Qt::ItemFlags TreeModel::flags(const QModelIndex& index) const {
if (!index.isValid()) {
return Qt::NoItemFlags;
}
return QAbstractItemModel::flags(index);
}
QVariant TreeModel::data(const QModelIndex& index, int role) const {
TreeItem* item = getItem(index);
return item->data(role - Qt::UserRole - 1);
}
QVariant TreeModel::headerData(int section, Qt::Orientation orientation, int role) const {
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
return _rootItem->data(section);
}
return QVariant();
}
QModelIndex TreeModel::index(int row, int column, const QModelIndex& parent) const {
if (!hasIndex(row, column, parent)) {
return QModelIndex();
}
TreeItem *parentItem;
if (!parent.isValid()) {
parentItem = _rootItem;
} else {
parentItem = static_cast<TreeItem*>(parent.internalPointer());
}
TreeItem *childItem = parentItem->child(row);
if (childItem) {
return createIndex(row, column, childItem);
} else {
return QModelIndex();
}
}
QModelIndex TreeModel::parent(const QModelIndex& index) const {
if (!index.isValid()) {
return QModelIndex();
}
TreeItem *childItem = static_cast<TreeItem*>(index.internalPointer());
TreeItem *parentItem = childItem->parentItem();
if (parentItem == _rootItem) {
return QModelIndex();
}
return createIndex(parentItem->row(), 0, parentItem);
}
int TreeModel::rowCount(const QModelIndex& parent) const {
TreeItem* parentItem = getItem(parent);
return parentItem->childCount();
}
int TreeModel::columnCount(const QModelIndex& parent) const {
return _rootItem->columnCount();
}
bool TreeModel::setData(const QModelIndex& index, const QVariant& value, int role) {
TreeItem* item = getItem(index);
bool returnValue = item->setData(role - Qt::UserRole - 1, value);
emit dataChanged(index, index);
return returnValue;
}
bool TreeModel::setHeaderData(int section, Qt::Orientation orientation, const QVariant& value, int role) {
return false;
}
bool TreeModel::insertColumns(int position, int columns, const QModelIndex& parent) {
return false;
}
bool TreeModel::removeColumns(int position, int columns, const QModelIndex& parent) {
return false;
}
bool TreeModel::insertRows(int position, int rows, const QModelIndex& parent) {
return false;
}
bool TreeModel::removeRows(int position, int rows, const QModelIndex& parent) {
return false;
}
void TreeModel::loadFromFile(const QString& filename) {
beginResetModel();
QFile file(filename);
if (!file.exists()) {
qCritical() << "TreeModel::loadFromFile, failed to open file" << filename;
} else if (!file.open(QIODevice::ReadOnly)) {
qCritical() << "TreeModel::loadFromFile, failed to open file" << filename;
} else {
qDebug() << "TreeModel::loadFromFile, success opening file" << filename;
QByteArray contents = file.readAll();
QJsonParseError error;
auto doc = QJsonDocument::fromJson(contents, &error);
if (error.error != QJsonParseError::NoError) {
qCritical() << "TreeModel::loadFromFile, failed to parse json, error" << error.errorString();
} else {
QJsonObject obj = doc.object();
// version
QJsonValue versionVal = obj.value("version");
if (!versionVal.isString()) {
qCritical() << "TreeModel::loadFromFile, bad string \"version\"";
return;
}
QString version = versionVal.toString();
// check version
if (version != "1.0" && version != "1.1") {
qCritical() << "TreeModel::loadFromFile, bad version number" << version << "expected \"1.0\" or \"1.1\"";
return;
}
// root
QJsonValue rootVal = obj.value("root");
if (!rootVal.isObject()) {
qCritical() << "TreeModel::loadFromFile, bad object \"root\"";
return;
}
QList<QVariant> columnData;
columnData << QString("root");
columnData << QString("root");
columnData << QString("root");
// create root item
_rootItem = new TreeItem(columnData);
_rootItem->appendChild(loadNode(rootVal.toObject()));
}
}
endResetModel();
}
void TreeModel::saveToFile(const QString& filename) {
QJsonObject obj;
obj.insert("version", "1.1");
const int FIRST_CHILD = 0;
obj.insert("root", jsonFromItem(_rootItem->child(FIRST_CHILD)));
QJsonDocument doc(obj);
QByteArray byteArray = doc.toJson(QJsonDocument::Indented);
QFile file(filename);
if (!file.open(QIODevice::WriteOnly)) {
qCritical() << "TreeModel::safeToFile, failed to open file" << filename;
} else {
file.write(byteArray);
}
}
void TreeModel::newNode(const QModelIndex& parent) {
TreeItem* parentItem = _rootItem;
if (parent.isValid()) {
parentItem = static_cast<TreeItem*>(parent.internalPointer());
}
beginInsertRows(parent, parentItem->childCount(), parentItem->childCount());
TreeItem* childItem = newBlankTreeItem();
parentItem->appendChild(childItem);
endInsertRows();
}
void TreeModel::deleteNode(const QModelIndex& index) {
TreeItem* item = static_cast<TreeItem*>(index.internalPointer());
TreeItem* parentItem = item->parentItem();
int childNum = parentItem->findChild(item);
if (childNum >= 0) {
beginRemoveRows(createIndex(0, 0, reinterpret_cast<quintptr>(parentItem)), childNum, childNum);
parentItem->removeChild(childNum);
endRemoveRows();
}
}
void TreeModel::insertNodeAbove(const QModelIndex& index) {
TreeItem* item = static_cast<TreeItem*>(index.internalPointer());
TreeItem* parentItem = item->parentItem();
int childNum = parentItem->findChild(item);
if (childNum >= 0) {
TreeItem* newItem = newBlankTreeItem();
QModelIndex parentIndex = createIndex(0, 0, reinterpret_cast<quintptr>(parentItem));
// remove item
beginRemoveRows(parentIndex, childNum, childNum);
parentItem->removeChild(childNum);
endRemoveRows();
// append item to newItem
newItem->appendChild(item);
// then insert newItem
beginInsertRows(parentIndex, childNum, childNum);
parentItem->insertChild(childNum, newItem);
endInsertRows();
}
}
QVariantList TreeModel::getChildrenModelIndices(const QModelIndex& index) {
QVariantList indices;
TreeItem* parent = static_cast<TreeItem*>(index.internalPointer());
for (int i = 0; i < parent->childCount(); ++i) {
TreeItem* child = parent->child(i);
indices.push_back(createIndex(i, 0, reinterpret_cast<quintptr>(child)));
}
return indices;
}
void TreeModel::copyNode(const QModelIndex& index) {
TreeItem* item = static_cast<TreeItem*>(index.internalPointer());
// TODO: delete previous clipboard
_clipboard = item->cloneNode();
}
void TreeModel::copyNodeAndChildren(const QModelIndex& index) {
TreeItem* item = static_cast<TreeItem*>(index.internalPointer());
// TODO: delete previous clipboard
_clipboard = item->cloneNodeAndChildren();
}
void TreeModel::pasteOver(const QModelIndex& index) {
if (_clipboard) {
TreeItem* item = static_cast<TreeItem*>(index.internalPointer());
TreeItem* parentItem = item->parentItem();
int childNum = parentItem->findChild(item);
if (childNum >= 0) {
QModelIndex parentIndex = createIndex(0, 0, reinterpret_cast<quintptr>(parentItem));
// remove item
beginRemoveRows(parentIndex, childNum, childNum);
parentItem->removeChild(childNum);
endRemoveRows();
// then insert clone of _clipboard
beginInsertRows(parentIndex, childNum, childNum);
parentItem->insertChild(childNum, _clipboard->cloneNodeAndChildren());
endInsertRows();
}
}
}
void TreeModel::pasteAsChild(const QModelIndex& index) {
if (_clipboard) {
TreeItem* parentItem = _rootItem;
if (index.isValid()) {
parentItem = static_cast<TreeItem*>(index.internalPointer());
}
beginInsertRows(index, parentItem->childCount(), parentItem->childCount());
parentItem->appendChild(_clipboard->cloneNodeAndChildren());
endInsertRows();
}
}
TreeItem* TreeModel::loadNode(const QJsonObject& jsonObj) {
// id
auto idVal = jsonObj.value("id");
if (!idVal.isString()) {
qCritical() << "loadNode, bad string \"id\"";
return nullptr;
}
QString id = idVal.toString();
// type
auto typeVal = jsonObj.value("type");
if (!typeVal.isString()) {
qCritical() << "loadNode, bad object \"type\", id =" << id;
return nullptr;
}
QString typeStr = typeVal.toString();
// data
auto dataValue = jsonObj.value("data");
if (!dataValue.isObject()) {
qCritical() << "AnimNodeLoader, bad string \"data\", id =" << id;
return nullptr;
}
QList<QVariant> columnData;
columnData << id;
columnData << typeStr;
columnData << dataValue.toVariant();
// create node
TreeItem* node = new TreeItem(columnData);
// children
auto childrenValue = jsonObj.value("children");
if (!childrenValue.isArray()) {
qCritical() << "AnimNodeLoader, bad array \"children\", id =" << id;
return nullptr;
}
auto childrenArray = childrenValue.toArray();
for (const auto& childValue : childrenArray) {
if (!childValue.isObject()) {
qCritical() << "AnimNodeLoader, bad object in \"children\", id =" << id;
return nullptr;
}
TreeItem* child = loadNode(childValue.toObject());
if (child) {
node->appendChild(child);
} else {
return nullptr;
}
}
return node;
}
TreeItem* TreeModel::getItem(const QModelIndex& index) const {
if (index.isValid()) {
TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
if (item) {
return item;
}
}
return _rootItem;
}
QJsonObject TreeModel::jsonFromItem(TreeItem* treeItem) {
QJsonObject obj;
const int ID_COLUMN = 0;
obj.insert("id", treeItem->data(ID_COLUMN).toJsonValue());
const int TYPE_COLUMN = 1;
obj.insert("type", treeItem->data(TYPE_COLUMN).toJsonValue());
const int DATA_COLUMN = 2;
QVariant data = treeItem->data(DATA_COLUMN);
if (data.canConvert<QJSValue>()) {
obj.insert("data", data.value<QJSValue>().toVariant().toJsonValue());
} else {
obj.insert("data", data.toJsonValue());
}
QJsonArray children;
for (int i = 0; i < treeItem->childCount(); i++) {
children.push_back(jsonFromItem(treeItem->child(i)));
}
obj.insert("children", children);
return obj;
}

View file

@ -0,0 +1,79 @@
//
// TreeModel
//
// Created by Anthony Thibault on 6/5/2019
// Copyright 2019 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_TreeModel_h
#define hifi_TreeModel_h
#include <QAbstractItemModel>
#include <QJsonObject>
#include <QModelIndex>
#include <QObject>
#include <QString>
#include <QVariant>
class TreeItem;
class TreeModel : public QAbstractItemModel {
Q_OBJECT
public:
enum TreeModelRoles
{
TreeModelRoleName = Qt::UserRole + 1,
TreeModelRoleType,
TreeModelRoleData
};
explicit TreeModel(QObject* parent = nullptr);
~TreeModel() override;
// QAbstractItemModel interface
QHash<int, QByteArray> roleNames() const override;
Qt::ItemFlags flags(const QModelIndex& index) const override;
// read methods
QVariant data(const QModelIndex& index, int role) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = TreeModelRoleName) const override;
QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex& index) const override;
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
// write methods
bool setData(const QModelIndex& index, const QVariant& value, int role = TreeModelRoleName) override;
bool setHeaderData(int section, Qt::Orientation orientation, const QVariant& value, int role = TreeModelRoleName) override;
bool insertColumns(int position, int columns, const QModelIndex& parent = QModelIndex()) override;
bool removeColumns(int position, int columns, const QModelIndex& parent = QModelIndex()) override;
bool insertRows(int position, int rows, const QModelIndex& parent = QModelIndex()) override;
bool removeRows(int position, int rows, const QModelIndex& parent = QModelIndex()) override;
// invokable from qml
Q_INVOKABLE void loadFromFile(const QString& filename);
Q_INVOKABLE void saveToFile(const QString& filename);
Q_INVOKABLE void newNode(const QModelIndex& parent);
Q_INVOKABLE void deleteNode(const QModelIndex& index);
Q_INVOKABLE void insertNodeAbove(const QModelIndex& index);
Q_INVOKABLE QVariantList getChildrenModelIndices(const QModelIndex& index);
Q_INVOKABLE void copyNode(const QModelIndex& index);
Q_INVOKABLE void copyNodeAndChildren(const QModelIndex& index);
Q_INVOKABLE void pasteOver(const QModelIndex& index);
Q_INVOKABLE void pasteAsChild(const QModelIndex& index);
private:
TreeItem* loadNode(const QJsonObject& jsonObj);
TreeItem* getItem(const QModelIndex& index) const;
QJsonObject jsonFromItem(TreeItem* treeItem);
TreeItem* _rootItem;
QHash<int, QByteArray> _roleNameMapping;
TreeItem* _clipboard;
};
#endif

View file

@ -57,6 +57,7 @@ exports.handlers = {
'../../libraries/physics/src',
'../../libraries/platform/src/platform/backend',
'../../libraries/plugins/src/plugins',
'../../libraries/procedural/src/procedural',
'../../libraries/pointers/src',
'../../libraries/render-utils/src',
'../../libraries/script-engine/src',