mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-09 16:52:28 +02:00
Merge remote-tracking branch 'upstream/master' into ga/master_build
This commit is contained in:
commit
697f445edd
63 changed files with 5136 additions and 576 deletions
|
@ -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()) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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" } };
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
13
launchers/qt/BUILD.md
Normal 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
34
launchers/qt/readme.md
Normal 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.
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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, "");
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -277,6 +277,7 @@ enum class EntityVersion : PacketVersion {
|
|||
ShadowBiasAndDistance,
|
||||
TextEntityFonts,
|
||||
ScriptServerKinematicMotion,
|
||||
ScreenshareZone,
|
||||
|
||||
// Add new versions above here
|
||||
NUM_PACKET_TYPE,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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> –
|
||||
* <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> – <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>
|
||||
* – <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.
|
||||
|
|
|
@ -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){
|
||||
|
|
1344
scripts/developer/automaticLookAt.js
Normal file
1344
scripts/developer/automaticLookAt.js
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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."
|
||||
},
|
||||
|
|
|
@ -393,7 +393,8 @@ const DEFAULT_ENTITY_PROPERTIES = {
|
|||
},
|
||||
shapeType: "box",
|
||||
bloomMode: "inherit",
|
||||
avatarPriority: "inherit"
|
||||
avatarPriority: "inherit",
|
||||
screenshare: "inherit",
|
||||
},
|
||||
Model: {
|
||||
collisionShape: "none",
|
||||
|
|
|
@ -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
1
tools/animedit/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
*.pro.user
|
4
tools/animedit/LICENCE
Normal file
4
tools/animedit/LICENCE
Normal 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
14
tools/animedit/README.md
Normal 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.
|
||||
|
||||
|
||||
|
||||
|
35
tools/animedit/animedit.pro
Normal file
35
tools/animedit/animedit.pro
Normal 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
36
tools/animedit/main.cpp
Normal 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
26
tools/animedit/qml.qrc
Normal 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>
|
10
tools/animedit/qml/TreeDelegate.qml
Normal file
10
tools/animedit/qml/TreeDelegate.qml
Normal file
|
@ -0,0 +1,10 @@
|
|||
import QtQuick 2.0
|
||||
|
||||
Item {
|
||||
Text {
|
||||
anchors.fill: parent
|
||||
color: styleData.textColor
|
||||
elide: styleData.elideMode
|
||||
text: styleData.value
|
||||
}
|
||||
}
|
44
tools/animedit/qml/fields/BooleanField.qml
Normal file
44
tools/animedit/qml/fields/BooleanField.qml
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
41
tools/animedit/qml/fields/IdField.qml
Normal file
41
tools/animedit/qml/fields/IdField.qml
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
52
tools/animedit/qml/fields/JSONField.qml
Normal file
52
tools/animedit/qml/fields/JSONField.qml
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
54
tools/animedit/qml/fields/NumberArrayField.qml
Normal file
54
tools/animedit/qml/fields/NumberArrayField.qml
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
45
tools/animedit/qml/fields/NumberField.qml
Normal file
45
tools/animedit/qml/fields/NumberField.qml
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
44
tools/animedit/qml/fields/StringField.qml
Normal file
44
tools/animedit/qml/fields/StringField.qml
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
67
tools/animedit/qml/fields/TypeField.qml
Normal file
67
tools/animedit/qml/fields/TypeField.qml
Normal 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
262
tools/animedit/qml/main.qml
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
129
tools/animedit/qml/nodes/BlendDirectional.qml
Normal file
129
tools/animedit/qml/nodes/BlendDirectional.qml
Normal 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
|
||||
}
|
||||
}
|
||||
|
73
tools/animedit/qml/nodes/BlendLinear.qml
Normal file
73
tools/animedit/qml/nodes/BlendLinear.qml
Normal 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
|
||||
}
|
||||
}
|
||||
|
85
tools/animedit/qml/nodes/BlendLinearMove.qml
Normal file
85
tools/animedit/qml/nodes/BlendLinearMove.qml
Normal 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
|
||||
}
|
||||
}
|
||||
|
148
tools/animedit/qml/nodes/ClipData.qml
Normal file
148
tools/animedit/qml/nodes/ClipData.qml
Normal 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
|
||||
}
|
||||
}
|
||||
|
53
tools/animedit/qml/nodes/DefaultPose.qml
Normal file
53
tools/animedit/qml/nodes/DefaultPose.qml
Normal 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)
|
||||
}
|
||||
}
|
||||
|
71
tools/animedit/qml/nodes/InverseKinematics.qml
Normal file
71
tools/animedit/qml/nodes/InverseKinematics.qml
Normal 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: {}
|
||||
}
|
||||
}
|
||||
|
72
tools/animedit/qml/nodes/Manipulator.qml
Normal file
72
tools/animedit/qml/nodes/Manipulator.qml
Normal 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: ""
|
||||
}
|
||||
}
|
||||
|
80
tools/animedit/qml/nodes/Overlay.qml
Normal file
80
tools/animedit/qml/nodes/Overlay.qml
Normal 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
|
||||
}
|
||||
}
|
||||
|
96
tools/animedit/qml/nodes/PoleVector.qml
Normal file
96
tools/animedit/qml/nodes/PoleVector.qml
Normal 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: ""
|
||||
}
|
||||
}
|
||||
|
109
tools/animedit/qml/nodes/RandomStateMachine.qml
Normal file
109
tools/animedit/qml/nodes/RandomStateMachine.qml
Normal 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: {}
|
||||
}
|
||||
}
|
||||
|
139
tools/animedit/qml/nodes/SplineIK.qml
Normal file
139
tools/animedit/qml/nodes/SplineIK.qml
Normal 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: ""
|
||||
}
|
||||
}
|
||||
|
65
tools/animedit/qml/nodes/StateMachine.qml
Normal file
65
tools/animedit/qml/nodes/StateMachine.qml
Normal 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: {}
|
||||
}
|
||||
}
|
||||
|
120
tools/animedit/qml/nodes/TwoBoneIK.qml
Normal file
120
tools/animedit/qml/nodes/TwoBoneIK.qml
Normal 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: ""
|
||||
}
|
||||
}
|
||||
|
91
tools/animedit/treeitem.cpp
Normal file
91
tools/animedit/treeitem.cpp
Normal 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
45
tools/animedit/treeitem.h
Normal 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
|
418
tools/animedit/treemodel.cpp
Normal file
418
tools/animedit/treemodel.cpp
Normal 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;
|
||||
}
|
79
tools/animedit/treemodel.h
Normal file
79
tools/animedit/treemodel.h
Normal 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
|
|
@ -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',
|
||||
|
|
Loading…
Reference in a new issue