mirror of
https://github.com/AleziaKurdis/overte.git
synced 2025-04-09 15:52:09 +02:00
Merge branch 'master' into reliable-service-min-timeout
This commit is contained in:
commit
c88af5c4be
331 changed files with 14223 additions and 5972 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -98,5 +98,7 @@ tools/jsdoc/package-lock.json
|
|||
|
||||
# ignore unneeded unity project files for avatar exporter
|
||||
tools/unity-avatar-exporter/Library
|
||||
tools/unity-avatar-exporter/Logs
|
||||
tools/unity-avatar-exporter/Packages
|
||||
tools/unity-avatar-exporter/ProjectSettings
|
||||
tools/unity-avatar-exporter/Temp
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env bash
|
||||
set -xeuo pipefail
|
||||
./gradlew -PHIFI_ANDROID_PRECOMPILED=${HIFI_ANDROID_PRECOMPILED} -PRELEASE_NUMBER=${RELEASE_NUMBER} -PRELEASE_TYPE=${RELEASE_TYPE} setupDependencies
|
||||
./gradlew -PHIFI_ANDROID_PRECOMPILED=${HIFI_ANDROID_PRECOMPILED} -PRELEASE_NUMBER=${RELEASE_NUMBER} -PRELEASE_TYPE=${RELEASE_TYPE} app:${ANDROID_BUILD_TARGET}
|
||||
./gradlew -PHIFI_ANDROID_PRECOMPILED=${HIFI_ANDROID_PRECOMPILED} -PVERSION_CODE=${VERSION_CODE} -PRELEASE_NUMBER=${RELEASE_NUMBER} -PRELEASE_TYPE=${RELEASE_TYPE} setupDependencies
|
||||
./gradlew -PHIFI_ANDROID_PRECOMPILED=${HIFI_ANDROID_PRECOMPILED} -PVERSION_CODE=${VERSION_CODE} -PRELEASE_NUMBER=${RELEASE_NUMBER} -PRELEASE_TYPE=${RELEASE_TYPE} app:${ANDROID_BUILD_TARGET}
|
|
@ -9,14 +9,19 @@ docker run \
|
|||
--rm \
|
||||
--security-opt seccomp:unconfined \
|
||||
-v "${WORKSPACE}":/home/jenkins/hifi \
|
||||
-e "RELEASE_NUMBER=${RELEASE_NUMBER}" \
|
||||
-e "RELEASE_TYPE=${RELEASE_TYPE}" \
|
||||
-e "ANDROID_BUILD_TARGET=assembleDebug" \
|
||||
-e "CMAKE_BACKTRACE_URL=${CMAKE_BACKTRACE_URL}" \
|
||||
-e "CMAKE_BACKTRACE_TOKEN=${CMAKE_BACKTRACE_TOKEN}" \
|
||||
-e "CMAKE_BACKTRACE_SYMBOLS_TOKEN=${CMAKE_BACKTRACE_SYMBOLS_TOKEN}" \
|
||||
-e "GA_TRACKING_ID=${GA_TRACKING_ID}" \
|
||||
-e "GIT_PR_COMMIT=${GIT_PR_COMMIT}" \
|
||||
-e "VERSION_CODE=${VERSION_CODE}" \
|
||||
-e RELEASE_NUMBER \
|
||||
-e RELEASE_TYPE \
|
||||
-e ANDROID_BUILD_TARGET \
|
||||
-e ANDROID_BUILD_DIR \
|
||||
-e CMAKE_BACKTRACE_URL \
|
||||
-e CMAKE_BACKTRACE_TOKEN \
|
||||
-e CMAKE_BACKTRACE_SYMBOLS_TOKEN \
|
||||
-e GA_TRACKING_ID \
|
||||
-e OAUTH_CLIENT_SECRET \
|
||||
-e OAUTH_CLIENT_ID \
|
||||
-e OAUTH_REDIRECT_URI \
|
||||
-e VERSION_CODE \
|
||||
"${DOCKER_IMAGE_NAME}" \
|
||||
sh -c "./build_android.sh"
|
||||
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ setup_memory_debugger()
|
|||
|
||||
# link in the shared libraries
|
||||
link_hifi_libraries(
|
||||
audio avatars octree gpu graphics fbx hfm entities
|
||||
audio avatars octree gpu graphics shaders fbx hfm entities
|
||||
networking animation recording shared script-engine embedded-webserver
|
||||
controllers physics plugins midi image
|
||||
)
|
||||
|
|
|
@ -428,6 +428,10 @@ void Agent::executeScript() {
|
|||
using namespace recording;
|
||||
static const FrameType AUDIO_FRAME_TYPE = Frame::registerFrameType(AudioConstants::getAudioFrameName());
|
||||
Frame::registerFrameHandler(AUDIO_FRAME_TYPE, [this, &scriptedAvatar](Frame::ConstPointer frame) {
|
||||
if (_shouldMuteRecordingAudio) {
|
||||
return;
|
||||
}
|
||||
|
||||
static quint16 audioSequenceNumber{ 0 };
|
||||
|
||||
QByteArray audio(frame->data);
|
||||
|
@ -756,6 +760,10 @@ void Agent::processAgentAvatarAudio() {
|
|||
const int16_t* nextSoundOutput = NULL;
|
||||
|
||||
if (_avatarSound && _avatarSound->isReady()) {
|
||||
if (isPlayingRecording && !_shouldMuteRecordingAudio) {
|
||||
_shouldMuteRecordingAudio = true;
|
||||
}
|
||||
|
||||
auto audioData = _avatarSound->getAudioData();
|
||||
nextSoundOutput = reinterpret_cast<const int16_t*>(audioData->rawData()
|
||||
+ _numAvatarSoundSentBytes);
|
||||
|
@ -781,6 +789,10 @@ void Agent::processAgentAvatarAudio() {
|
|||
_avatarSound.clear();
|
||||
_numAvatarSoundSentBytes = 0;
|
||||
_flushEncoder = true;
|
||||
|
||||
if (_shouldMuteRecordingAudio) {
|
||||
_shouldMuteRecordingAudio = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -107,6 +107,7 @@ private:
|
|||
ResourceRequest* _pendingScriptRequest { nullptr };
|
||||
bool _isListeningToAudioStream = false;
|
||||
SharedSoundPointer _avatarSound;
|
||||
bool _shouldMuteRecordingAudio { false };
|
||||
int _numAvatarSoundSentBytes = 0;
|
||||
bool _isAvatar = false;
|
||||
QTimer* _avatarIdentityTimer = nullptr;
|
||||
|
|
|
@ -276,6 +276,7 @@ void AssignmentClientMonitor::checkSpares() {
|
|||
|
||||
// Spawn or kill children, as needed. If --min or --max weren't specified, allow the child count
|
||||
// to drift up or down as far as needed.
|
||||
|
||||
if (spareCount < 1 || totalCount < _minAssignmentClientForks) {
|
||||
if (!_maxAssignmentClientForks || totalCount < _maxAssignmentClientForks) {
|
||||
spawnChildClient();
|
||||
|
@ -307,7 +308,7 @@ void AssignmentClientMonitor::handleChildStatusPacket(QSharedPointer<ReceivedMes
|
|||
AssignmentClientChildData* childData = nullptr;
|
||||
|
||||
if (!matchingNode) {
|
||||
// The parent only expects to be talking with prorams running on this same machine.
|
||||
// The parent only expects to be talking with programs running on this same machine.
|
||||
if (senderSockAddr.getAddress() == QHostAddress::LocalHost ||
|
||||
senderSockAddr.getAddress() == QHostAddress::LocalHostIPv6) {
|
||||
|
||||
|
@ -316,9 +317,9 @@ void AssignmentClientMonitor::handleChildStatusPacket(QSharedPointer<ReceivedMes
|
|||
matchingNode = DependencyManager::get<LimitedNodeList>()->addOrUpdateNode(senderID, NodeType::Unassigned,
|
||||
senderSockAddr, senderSockAddr);
|
||||
|
||||
auto childData = std::unique_ptr<AssignmentClientChildData>
|
||||
auto newChildData = std::unique_ptr<AssignmentClientChildData>
|
||||
{ new AssignmentClientChildData(Assignment::Type::AllTypes) };
|
||||
matchingNode->setLinkedData(std::move(childData));
|
||||
matchingNode->setLinkedData(std::move(newChildData));
|
||||
} else {
|
||||
// tell unknown assignment-client child to exit.
|
||||
qDebug() << "Asking unknown child at" << senderSockAddr << "to exit.";
|
||||
|
@ -329,9 +330,8 @@ void AssignmentClientMonitor::handleChildStatusPacket(QSharedPointer<ReceivedMes
|
|||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
childData = dynamic_cast<AssignmentClientChildData*>(matchingNode->getLinkedData());
|
||||
}
|
||||
childData = dynamic_cast<AssignmentClientChildData*>(matchingNode->getLinkedData());
|
||||
|
||||
if (childData) {
|
||||
// update our records about how to reach this child
|
||||
|
|
|
@ -915,59 +915,52 @@ void AssetServer::handleAssetUpload(QSharedPointer<ReceivedMessage> message, Sha
|
|||
void AssetServer::sendStatsPacket() {
|
||||
QJsonObject serverStats;
|
||||
|
||||
auto stats = DependencyManager::get<NodeList>()->sampleStatsForAllConnections();
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->eachNode([&](auto& node) {
|
||||
auto& stats = node->getConnectionStats();
|
||||
|
||||
for (const auto& stat : stats) {
|
||||
QJsonObject nodeStats;
|
||||
auto endTimeMs = std::chrono::duration_cast<std::chrono::milliseconds>(stat.second.endTime);
|
||||
auto endTimeMs = std::chrono::duration_cast<std::chrono::milliseconds>(stats.endTime);
|
||||
QDateTime date = QDateTime::fromMSecsSinceEpoch(endTimeMs.count());
|
||||
|
||||
static const float USEC_PER_SEC = 1000000.0f;
|
||||
static const float MEGABITS_PER_BYTE = 8.0f / 1000000.0f; // Bytes => Mbits
|
||||
float elapsed = (float)(stat.second.endTime - stat.second.startTime).count() / USEC_PER_SEC; // sec
|
||||
float elapsed = (float)(stats.endTime - stats.startTime).count() / USEC_PER_SEC; // sec
|
||||
float megabitsPerSecPerByte = MEGABITS_PER_BYTE / elapsed; // Bytes => Mb/s
|
||||
|
||||
QJsonObject connectionStats;
|
||||
connectionStats["1. Last Heard"] = date.toString();
|
||||
connectionStats["2. Est. Max (P/s)"] = stat.second.estimatedBandwith;
|
||||
connectionStats["3. RTT (ms)"] = stat.second.rtt;
|
||||
connectionStats["4. CW (P)"] = stat.second.congestionWindowSize;
|
||||
connectionStats["5. Period (us)"] = stat.second.packetSendPeriod;
|
||||
connectionStats["6. Up (Mb/s)"] = stat.second.sentBytes * megabitsPerSecPerByte;
|
||||
connectionStats["7. Down (Mb/s)"] = stat.second.receivedBytes * megabitsPerSecPerByte;
|
||||
connectionStats["2. Est. Max (P/s)"] = stats.estimatedBandwith;
|
||||
connectionStats["3. RTT (ms)"] = stats.rtt;
|
||||
connectionStats["4. CW (P)"] = stats.congestionWindowSize;
|
||||
connectionStats["5. Period (us)"] = stats.packetSendPeriod;
|
||||
connectionStats["6. Up (Mb/s)"] = stats.sentBytes * megabitsPerSecPerByte;
|
||||
connectionStats["7. Down (Mb/s)"] = stats.receivedBytes * megabitsPerSecPerByte;
|
||||
nodeStats["Connection Stats"] = connectionStats;
|
||||
|
||||
using Events = udt::ConnectionStats::Stats::Event;
|
||||
const auto& events = stat.second.events;
|
||||
const auto& events = stats.events;
|
||||
|
||||
QJsonObject upstreamStats;
|
||||
upstreamStats["1. Sent (P/s)"] = stat.second.sendRate;
|
||||
upstreamStats["2. Sent Packets"] = stat.second.sentPackets;
|
||||
upstreamStats["1. Sent (P/s)"] = stats.sendRate;
|
||||
upstreamStats["2. Sent Packets"] = (int)stats.sentPackets;
|
||||
upstreamStats["3. Recvd ACK"] = events[Events::ReceivedACK];
|
||||
upstreamStats["4. Procd ACK"] = events[Events::ProcessedACK];
|
||||
upstreamStats["5. Retransmitted"] = events[Events::Retransmission];
|
||||
upstreamStats["5. Retransmitted"] = (int)stats.retransmittedPackets;
|
||||
nodeStats["Upstream Stats"] = upstreamStats;
|
||||
|
||||
QJsonObject downstreamStats;
|
||||
downstreamStats["1. Recvd (P/s)"] = stat.second.receiveRate;
|
||||
downstreamStats["2. Recvd Packets"] = stat.second.receivedPackets;
|
||||
downstreamStats["1. Recvd (P/s)"] = stats.receiveRate;
|
||||
downstreamStats["2. Recvd Packets"] = (int)stats.receivedPackets;
|
||||
downstreamStats["3. Sent ACK"] = events[Events::SentACK];
|
||||
downstreamStats["4. Duplicates"] = events[Events::Duplicate];
|
||||
downstreamStats["4. Duplicates"] = (int)stats.duplicatePackets;
|
||||
nodeStats["Downstream Stats"] = downstreamStats;
|
||||
|
||||
QString uuid;
|
||||
auto nodelist = DependencyManager::get<NodeList>();
|
||||
if (stat.first == nodelist->getDomainHandler().getSockAddr()) {
|
||||
uuid = uuidStringWithoutCurlyBraces(nodelist->getDomainHandler().getUUID());
|
||||
nodeStats[USERNAME_UUID_REPLACEMENT_STATS_KEY] = "DomainServer";
|
||||
} else {
|
||||
auto node = nodelist->findNodeWithAddr(stat.first);
|
||||
uuid = uuidStringWithoutCurlyBraces(node ? node->getUUID() : QUuid());
|
||||
nodeStats[USERNAME_UUID_REPLACEMENT_STATS_KEY] = uuid;
|
||||
}
|
||||
QString uuid = uuidStringWithoutCurlyBraces(node->getUUID());
|
||||
nodeStats[USERNAME_UUID_REPLACEMENT_STATS_KEY] = uuid;
|
||||
|
||||
serverStats[uuid] = nodeStats;
|
||||
}
|
||||
});
|
||||
|
||||
// send off the stats packets
|
||||
ThreadedAssignment::addPacketStatsAndSendStatsPacket(serverStats);
|
||||
|
|
|
@ -338,7 +338,7 @@ void AudioMixer::sendStatsPacket() {
|
|||
QJsonObject nodeStats;
|
||||
QString uuidString = uuidStringWithoutCurlyBraces(node->getUUID());
|
||||
|
||||
nodeStats["outbound_kbps"] = node->getOutboundBandwidth();
|
||||
nodeStats["outbound_kbps"] = node->getOutboundKbps();
|
||||
nodeStats[USERNAME_UUID_REPLACEMENT_STATS_KEY] = uuidString;
|
||||
|
||||
nodeStats["jitter"] = clientData->getAudioStreamStats();
|
||||
|
|
|
@ -746,65 +746,27 @@ void AvatarMixer::sendStatsPacket() {
|
|||
|
||||
|
||||
AvatarMixerSlaveStats aggregateStats;
|
||||
QJsonObject slavesObject;
|
||||
|
||||
float secondsSinceLastStats = (float)(start - _lastStatsTime) / (float)USECS_PER_SECOND;
|
||||
// gather stats
|
||||
int slaveNumber = 1;
|
||||
_slavePool.each([&](AvatarMixerSlave& slave) {
|
||||
QJsonObject slaveObject;
|
||||
AvatarMixerSlaveStats stats;
|
||||
slave.harvestStats(stats);
|
||||
slaveObject["recevied_1_nodesProcessed"] = TIGHT_LOOP_STAT(stats.nodesProcessed);
|
||||
slaveObject["received_2_numPacketsReceived"] = TIGHT_LOOP_STAT(stats.packetsProcessed);
|
||||
|
||||
slaveObject["sent_1_nodesBroadcastedTo"] = TIGHT_LOOP_STAT(stats.nodesBroadcastedTo);
|
||||
slaveObject["sent_2_numBytesSent"] = TIGHT_LOOP_STAT(stats.numBytesSent);
|
||||
slaveObject["sent_3_numPacketsSent"] = TIGHT_LOOP_STAT(stats.numPacketsSent);
|
||||
slaveObject["sent_4_numIdentityPackets"] = TIGHT_LOOP_STAT(stats.numIdentityPackets);
|
||||
|
||||
float averageNodes = ((float)stats.nodesBroadcastedTo / (float)tightLoopFrames);
|
||||
float averageOutboundAvatarKbps = averageNodes ? ((stats.numBytesSent / secondsSinceLastStats) / BYTES_PER_KILOBIT) / averageNodes : 0.0f;
|
||||
slaveObject["sent_5_averageOutboundAvatarKbps"] = averageOutboundAvatarKbps;
|
||||
|
||||
float averageOthersIncluded = averageNodes ? stats.numOthersIncluded / averageNodes : 0.0f;
|
||||
slaveObject["sent_6_averageOthersIncluded"] = TIGHT_LOOP_STAT(averageOthersIncluded);
|
||||
|
||||
float averageOverBudgetAvatars = averageNodes ? stats.overBudgetAvatars / averageNodes : 0.0f;
|
||||
slaveObject["sent_7_averageOverBudgetAvatars"] = TIGHT_LOOP_STAT(averageOverBudgetAvatars);
|
||||
|
||||
slaveObject["timing_1_processIncomingPackets"] = TIGHT_LOOP_STAT_UINT64(stats.processIncomingPacketsElapsedTime);
|
||||
slaveObject["timing_2_ignoreCalculation"] = TIGHT_LOOP_STAT_UINT64(stats.ignoreCalculationElapsedTime);
|
||||
slaveObject["timing_3_toByteArray"] = TIGHT_LOOP_STAT_UINT64(stats.toByteArrayElapsedTime);
|
||||
slaveObject["timing_4_avatarDataPacking"] = TIGHT_LOOP_STAT_UINT64(stats.avatarDataPackingElapsedTime);
|
||||
slaveObject["timing_5_packetSending"] = TIGHT_LOOP_STAT_UINT64(stats.packetSendingElapsedTime);
|
||||
slaveObject["timing_6_jobElapsedTime"] = TIGHT_LOOP_STAT_UINT64(stats.jobElapsedTime);
|
||||
|
||||
slavesObject[QString::number(slaveNumber)] = slaveObject;
|
||||
slaveNumber++;
|
||||
|
||||
aggregateStats += stats;
|
||||
});
|
||||
|
||||
QJsonObject slavesAggregatObject;
|
||||
|
||||
slavesAggregatObject["recevied_1_nodesProcessed"] = TIGHT_LOOP_STAT(aggregateStats.nodesProcessed);
|
||||
slavesAggregatObject["received_2_numPacketsReceived"] = TIGHT_LOOP_STAT(aggregateStats.packetsProcessed);
|
||||
slavesAggregatObject["received_1_nodesProcessed"] = TIGHT_LOOP_STAT(aggregateStats.nodesProcessed);
|
||||
|
||||
slavesAggregatObject["sent_1_nodesBroadcastedTo"] = TIGHT_LOOP_STAT(aggregateStats.nodesBroadcastedTo);
|
||||
slavesAggregatObject["sent_2_numBytesSent"] = TIGHT_LOOP_STAT(aggregateStats.numBytesSent);
|
||||
slavesAggregatObject["sent_3_numPacketsSent"] = TIGHT_LOOP_STAT(aggregateStats.numPacketsSent);
|
||||
slavesAggregatObject["sent_4_numIdentityPackets"] = TIGHT_LOOP_STAT(aggregateStats.numIdentityPackets);
|
||||
|
||||
float averageNodes = ((float)aggregateStats.nodesBroadcastedTo / (float)tightLoopFrames);
|
||||
float averageOutboundAvatarKbps = averageNodes ? ((aggregateStats.numBytesSent / secondsSinceLastStats) / BYTES_PER_KILOBIT) / averageNodes : 0.0f;
|
||||
slavesAggregatObject["sent_5_averageOutboundAvatarKbps"] = averageOutboundAvatarKbps;
|
||||
|
||||
float averageOthersIncluded = averageNodes ? aggregateStats.numOthersIncluded / averageNodes : 0.0f;
|
||||
slavesAggregatObject["sent_6_averageOthersIncluded"] = TIGHT_LOOP_STAT(averageOthersIncluded);
|
||||
slavesAggregatObject["sent_2_averageOthersIncluded"] = TIGHT_LOOP_STAT(averageOthersIncluded);
|
||||
|
||||
float averageOverBudgetAvatars = averageNodes ? aggregateStats.overBudgetAvatars / averageNodes : 0.0f;
|
||||
slavesAggregatObject["sent_7_averageOverBudgetAvatars"] = TIGHT_LOOP_STAT(averageOverBudgetAvatars);
|
||||
slavesAggregatObject["sent_3_averageOverBudgetAvatars"] = TIGHT_LOOP_STAT(averageOverBudgetAvatars);
|
||||
|
||||
slavesAggregatObject["timing_1_processIncomingPackets"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.processIncomingPacketsElapsedTime);
|
||||
slavesAggregatObject["timing_2_ignoreCalculation"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.ignoreCalculationElapsedTime);
|
||||
|
@ -814,7 +776,6 @@ void AvatarMixer::sendStatsPacket() {
|
|||
slavesAggregatObject["timing_6_jobElapsedTime"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.jobElapsedTime);
|
||||
|
||||
statsObject["slaves_aggregate"] = slavesAggregatObject;
|
||||
statsObject["slaves_individual"] = slavesObject;
|
||||
|
||||
_handleViewFrustumPacketElapsedTime = 0;
|
||||
_handleAvatarIdentityPacketElapsedTime = 0;
|
||||
|
@ -839,8 +800,8 @@ void AvatarMixer::sendStatsPacket() {
|
|||
// add the key to ask the domain-server for a username replacement, if it has it
|
||||
avatarStats[USERNAME_UUID_REPLACEMENT_STATS_KEY] = uuidStringWithoutCurlyBraces(node->getUUID());
|
||||
|
||||
avatarStats[NODE_OUTBOUND_KBPS_STAT_KEY] = node->getOutboundBandwidth();
|
||||
avatarStats[NODE_INBOUND_KBPS_STAT_KEY] = node->getInboundBandwidth();
|
||||
avatarStats[NODE_OUTBOUND_KBPS_STAT_KEY] = node->getOutboundKbps();
|
||||
avatarStats[NODE_INBOUND_KBPS_STAT_KEY] = node->getInboundKbps();
|
||||
|
||||
AvatarMixerClientData* clientData = static_cast<AvatarMixerClientData*>(node->getLinkedData());
|
||||
if (clientData) {
|
||||
|
|
|
@ -146,7 +146,8 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message,
|
|||
break;
|
||||
}
|
||||
|
||||
if (traitType == AvatarTraits::AvatarEntity) {
|
||||
if (traitType == AvatarTraits::AvatarEntity ||
|
||||
traitType == AvatarTraits::Grab) {
|
||||
auto& instanceVersionRef = _lastReceivedTraitVersions.getInstanceValueRef(traitType, instanceID);
|
||||
|
||||
if (packetTraitVersion > instanceVersionRef) {
|
||||
|
|
|
@ -75,8 +75,8 @@ void MessagesMixer::sendStatsPacket() {
|
|||
DependencyManager::get<NodeList>()->eachNode([&](const SharedNodePointer& node) {
|
||||
QJsonObject clientStats;
|
||||
clientStats[USERNAME_UUID_REPLACEMENT_STATS_KEY] = uuidStringWithoutCurlyBraces(node->getUUID());
|
||||
clientStats["outbound_kbps"] = node->getOutboundBandwidth();
|
||||
clientStats["inbound_kbps"] = node->getInboundBandwidth();
|
||||
clientStats["outbound_kbps"] = node->getOutboundKbps();
|
||||
clientStats["inbound_kbps"] = node->getInboundKbps();
|
||||
messagesMixerObject[uuidStringWithoutCurlyBraces(node->getUUID())] = clientStats;
|
||||
});
|
||||
|
||||
|
|
|
@ -127,6 +127,10 @@ int64_t DomainContentBackupManager::getMostRecentBackupTimeInSecs(const QString&
|
|||
}
|
||||
|
||||
void DomainContentBackupManager::setup() {
|
||||
for (auto& rule : _backupRules) {
|
||||
removeOldBackupVersions(rule);
|
||||
}
|
||||
|
||||
auto backups = getAllBackups();
|
||||
for (auto& backup : backups) {
|
||||
QFile backupFile { backup.absolutePath };
|
||||
|
|
|
@ -206,7 +206,7 @@ endif()
|
|||
link_hifi_libraries(
|
||||
shared workload task octree ktx gpu gl procedural graphics graphics-scripting render
|
||||
pointers
|
||||
recording hfm fbx networking model-networking entities avatars trackers
|
||||
recording hfm fbx networking model-networking model-baker entities avatars trackers
|
||||
audio audio-client animation script-engine physics
|
||||
render-utils entities-renderer avatars-renderer ui qml auto-updater midi
|
||||
controllers plugins image trackers
|
||||
|
|
|
@ -1,16 +1,2 @@
|
|||
{
|
||||
"RenderMainView": {
|
||||
"RenderShadowTask": {
|
||||
"Enabled": {
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"RenderDeferredTask": {
|
||||
"AmbientOcclusion": {
|
||||
"Enabled": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
4
interface/resources/icons/checkmark-stroke.svg
Normal file
4
interface/resources/icons/checkmark-stroke.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg width="149" height="150" viewBox="0 0 149 150" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M74.3055 0C115.543 0 149 33.5916 149 74.6047C149 116.008 115.543 149.6 74.3055 149.6C33.4569 149.6 0 116.008 0 74.6047C0 33.5916 33.4569 0 74.3055 0ZM74.3055 139.054C109.708 139.054 138.496 110.149 138.496 74.6047C138.496 39.4507 109.708 10.5462 74.3055 10.5462C39.2924 10.5462 10.5039 39.4507 10.5039 74.6047C10.5039 110.149 39.2924 139.054 74.3055 139.054Z" fill="#1FC6A6"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M65.3575 89.8376L106.595 43.3562C110.874 38.2784 119.044 45.3092 114.376 50.387L70.0259 100.384C68.0807 102.727 64.9684 102.727 63.0233 101.165L35.0128 78.9008C29.9554 74.6042 36.569 66.4016 41.6264 70.6982L65.3575 89.8376Z" fill="#1FC6A6"/>
|
||||
</svg>
|
After Width: | Height: | Size: 824 B |
BIN
interface/resources/icons/loader-snake-256-wf.gif
Normal file
BIN
interface/resources/icons/loader-snake-256-wf.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
BIN
interface/resources/icons/loader-snake-256.gif
Normal file
BIN
interface/resources/icons/loader-snake-256.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
BIN
interface/resources/images/loader-snake-128.png
Normal file
BIN
interface/resources/images/loader-snake-128.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 463 B |
|
@ -192,13 +192,13 @@ Item {
|
|||
}
|
||||
StatText {
|
||||
visible: root.expanded;
|
||||
text: "Audio In Audio: " + root.audioAudioInboundPPS + " pps, " +
|
||||
"Silent: " + root.audioSilentInboundPPS + " pps";
|
||||
text: "Audio Mixer Out: " + root.audioMixerOutKbps + " kbps, " +
|
||||
root.audioMixerOutPps + "pps";
|
||||
}
|
||||
StatText {
|
||||
visible: root.expanded;
|
||||
text: "Audio Mixer Out: " + root.audioMixerOutKbps + " kbps, " +
|
||||
root.audioMixerOutPps + "pps";
|
||||
text: "Audio In Audio: " + root.audioAudioInboundPPS + " pps, " +
|
||||
"Silent: " + root.audioSilentInboundPPS + " pps";
|
||||
}
|
||||
StatText {
|
||||
visible: root.expanded;
|
||||
|
|
|
@ -275,6 +275,9 @@ Item {
|
|||
Settings.setValue("keepMeLoggedIn/savedUsername", "");
|
||||
}
|
||||
}
|
||||
Component.onCompleted: {
|
||||
keepMeLoggedInCheckbox.checked = !Account.loggedIn;
|
||||
}
|
||||
}
|
||||
HifiControlsUit.Button {
|
||||
id: cancelButton
|
||||
|
|
|
@ -320,6 +320,9 @@ Item {
|
|||
Settings.setValue("keepMeLoggedIn/savedUsername", "");
|
||||
}
|
||||
}
|
||||
Component.onCompleted: {
|
||||
keepMeLoggedInCheckbox.checked = !Account.loggedIn;
|
||||
}
|
||||
}
|
||||
|
||||
TextMetrics {
|
||||
|
|
|
@ -210,13 +210,13 @@ Item {
|
|||
}
|
||||
StatText {
|
||||
visible: root.expanded;
|
||||
text: "Audio In Audio: " + root.audioAudioInboundPPS + " pps, " +
|
||||
"Silent: " + root.audioSilentInboundPPS + " pps";
|
||||
text: "Audio Mixer Out: " + root.audioMixerOutKbps + " kbps, " +
|
||||
root.audioMixerOutPps + "pps";
|
||||
}
|
||||
StatText {
|
||||
visible: root.expanded;
|
||||
text: "Audio Mixer Out: " + root.audioMixerOutKbps + " kbps, " +
|
||||
root.audioMixerOutPps + "pps";
|
||||
text: "Audio In Audio: " + root.audioAudioInboundPPS + " pps, " +
|
||||
"Silent: " + root.audioSilentInboundPPS + " pps";
|
||||
}
|
||||
StatText {
|
||||
visible: root.expanded;
|
||||
|
|
|
@ -32,6 +32,10 @@ Original.Button {
|
|||
width: hifi.dimensions.buttonWidth
|
||||
height: hifi.dimensions.controlLineHeight
|
||||
|
||||
property size implicitPadding: Qt.size(20, 16)
|
||||
property int implicitWidth: buttonContentItem.implicitWidth + implicitPadding.width
|
||||
property int implicitHeight: buttonContentItem.implicitHeight + implicitPadding.height
|
||||
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
onHoveredChanged: {
|
||||
|
@ -94,6 +98,8 @@ Original.Button {
|
|||
|
||||
contentItem: Item {
|
||||
id: buttonContentItem
|
||||
implicitWidth: (buttonGlyph.visible ? buttonGlyph.implicitWidth : 0) + buttonText.implicitWidth
|
||||
implicitHeight: buttonText.implicitHeight
|
||||
TextMetrics {
|
||||
id: buttonGlyphTextMetrics;
|
||||
font: buttonGlyph.font;
|
||||
|
|
|
@ -189,15 +189,17 @@ Windows.ScrollingWindow {
|
|||
var grabbable = MenuInterface.isOptionChecked("Create Entities As Grabbable (except Zones, Particles, and Lights)");
|
||||
|
||||
if (defaultURL.endsWith(".jpg") || defaultURL.endsWith(".png")) {
|
||||
var name = assetProxyModel.data(treeView.selection.currentIndex);
|
||||
var modelURL = "https://hifi-content.s3.amazonaws.com/DomainContent/production/default-image-model.fbx";
|
||||
var textures = JSON.stringify({ "tex.picture": defaultURL});
|
||||
var shapeType = "box";
|
||||
var dynamic = false;
|
||||
var collisionless = true;
|
||||
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation)));
|
||||
var gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0);
|
||||
Entities.addModelEntity(name, modelURL, textures, shapeType, dynamic, collisionless, grabbable, position, gravity);
|
||||
Entities.addEntity({
|
||||
type: "Image",
|
||||
name: assetProxyModel.data(treeView.selection.currentIndex),
|
||||
imageURL: defaultURL,
|
||||
keepAspectRatio: false,
|
||||
dynamic: false,
|
||||
collisionless: true,
|
||||
grabbable: grabbable,
|
||||
position: Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation))),
|
||||
gravity: Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0)
|
||||
});
|
||||
} else {
|
||||
var SHAPE_TYPE_NONE = 0;
|
||||
var SHAPE_TYPE_SIMPLE_HULL = 1;
|
||||
|
|
|
@ -254,7 +254,8 @@ Rectangle {
|
|||
onSaveClicked: function() {
|
||||
var avatarSettings = {
|
||||
dominantHand : settings.dominantHandIsLeft ? 'left' : 'right',
|
||||
collisionsEnabled : settings.avatarCollisionsOn,
|
||||
collisionsEnabled : settings.environmentCollisionsOn,
|
||||
otherAvatarsCollisionsEnabled : settings.otherAvatarsCollisionsOn,
|
||||
animGraphOverrideUrl : settings.avatarAnimationOverrideJSON,
|
||||
collisionSoundUrl : settings.avatarCollisionSoundUrl
|
||||
};
|
||||
|
|
24
interface/resources/qml/hifi/AvatarPackagerWindow.qml
Normal file
24
interface/resources/qml/hifi/AvatarPackagerWindow.qml
Normal file
|
@ -0,0 +1,24 @@
|
|||
import QtQuick 2.6
|
||||
import "../stylesUit" 1.0
|
||||
import "../windows" as Windows
|
||||
import "avatarPackager" 1.0
|
||||
|
||||
Windows.ScrollingWindow {
|
||||
id: root
|
||||
objectName: "AvatarPackager"
|
||||
width: 480
|
||||
height: 706
|
||||
title: "Avatar Packager"
|
||||
resizable: false
|
||||
opacity: parent.opacity
|
||||
destroyOnHidden: true
|
||||
implicitWidth: 384; implicitHeight: 640
|
||||
minSize: Qt.vector2d(480, 706)
|
||||
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
AvatarPackagerApp {
|
||||
height: pane.height
|
||||
width: pane.width
|
||||
}
|
||||
}
|
|
@ -0,0 +1,396 @@
|
|||
import QtQuick 2.6
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQml.Models 2.1
|
||||
import QtGraphicalEffects 1.0
|
||||
import Hifi.AvatarPackager.AvatarProjectStatus 1.0
|
||||
import "../../controlsUit" 1.0 as HifiControls
|
||||
import "../../stylesUit" 1.0
|
||||
import "../../controls" 1.0
|
||||
import "../../dialogs"
|
||||
import "../avatarapp" 1.0 as AvatarApp
|
||||
|
||||
Item {
|
||||
id: windowContent
|
||||
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
property alias desktopObject: avatarPackager.desktopObject
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
||||
onClicked: {
|
||||
unfocusser.forceActiveFocus();
|
||||
}
|
||||
Item {
|
||||
id: unfocusser
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
|
||||
InfoBox {
|
||||
id: fileListPopup
|
||||
|
||||
title: "List of Files"
|
||||
|
||||
content: Rectangle {
|
||||
id: fileList
|
||||
|
||||
color: "#404040"
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 10
|
||||
anchors.bottomMargin: 10
|
||||
anchors.leftMargin: 29
|
||||
anchors.rightMargin: 29
|
||||
|
||||
clip: true
|
||||
|
||||
ListView {
|
||||
anchors.fill: parent
|
||||
model: AvatarPackagerCore.currentAvatarProject === null ? [] : AvatarPackagerCore.currentAvatarProject.projectFiles
|
||||
delegate: Rectangle {
|
||||
width: parent.width
|
||||
height: fileText.implicitHeight + 8
|
||||
color: "#404040"
|
||||
RalewaySemiBold {
|
||||
id: fileText
|
||||
size: 16
|
||||
elide: Text.ElideLeft
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
anchors.topMargin: 4
|
||||
width: parent.width - 10
|
||||
color: "white"
|
||||
text: modelData
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InfoBox {
|
||||
id: errorPopup
|
||||
|
||||
property string errorMessage
|
||||
|
||||
boxWidth: 380
|
||||
boxHeight: 293
|
||||
|
||||
content: RalewayRegular {
|
||||
|
||||
id: bodyMessage
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.bottomMargin: 10
|
||||
anchors.leftMargin: 29
|
||||
anchors.rightMargin: 29
|
||||
|
||||
size: 20
|
||||
color: "white"
|
||||
text: errorPopup.errorMessage
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
function show(title, message) {
|
||||
errorPopup.title = title;
|
||||
errorMessage = message;
|
||||
errorPopup.open();
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: modalOverlay
|
||||
anchors.fill: parent
|
||||
z: 20
|
||||
color: "#a15d5d5d"
|
||||
visible: false
|
||||
|
||||
// This mouse area captures the cursor events while the modalOverlay is active
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
propagateComposedEvents: false
|
||||
hoverEnabled: true
|
||||
}
|
||||
}
|
||||
|
||||
AvatarApp.MessageBox {
|
||||
id: popup
|
||||
anchors.fill: parent
|
||||
visible: false
|
||||
closeOnClickOutside: true
|
||||
}
|
||||
|
||||
Column {
|
||||
id: avatarPackager
|
||||
anchors.fill: parent
|
||||
state: "main"
|
||||
states: [
|
||||
State {
|
||||
name: AvatarPackagerState.main
|
||||
PropertyChanges { target: avatarPackagerHeader; title: qsTr("Avatar Packager"); docsEnabled: true; backButtonVisible: false }
|
||||
PropertyChanges { target: avatarPackagerMain; visible: true }
|
||||
PropertyChanges { target: avatarPackagerFooter; content: avatarPackagerMain.footer }
|
||||
},
|
||||
State {
|
||||
name: AvatarPackagerState.createProject
|
||||
PropertyChanges { target: avatarPackagerHeader; title: qsTr("Create Project") }
|
||||
PropertyChanges { target: createAvatarProject; visible: true }
|
||||
PropertyChanges { target: avatarPackagerFooter; content: createAvatarProject.footer }
|
||||
},
|
||||
State {
|
||||
name: AvatarPackagerState.project
|
||||
PropertyChanges { target: avatarPackagerHeader; title: AvatarPackagerCore.currentAvatarProject.name; canRename: true }
|
||||
PropertyChanges { target: avatarProject; visible: true }
|
||||
PropertyChanges { target: avatarPackagerFooter; content: avatarProject.footer }
|
||||
},
|
||||
State {
|
||||
name: AvatarPackagerState.projectUpload
|
||||
PropertyChanges { target: avatarPackagerHeader; title: AvatarPackagerCore.currentAvatarProject.name; backButtonEnabled: false }
|
||||
PropertyChanges { target: avatarUploader; visible: true }
|
||||
PropertyChanges { target: avatarPackagerFooter; visible: false }
|
||||
}
|
||||
]
|
||||
|
||||
property alias showModalOverlay: modalOverlay.visible
|
||||
|
||||
property var desktopObject: desktop
|
||||
|
||||
function openProject(path) {
|
||||
let status = AvatarPackagerCore.openAvatarProject(path);
|
||||
if (status !== AvatarProjectStatus.SUCCESS) {
|
||||
displayErrorMessage(status);
|
||||
return status;
|
||||
}
|
||||
avatarProject.reset();
|
||||
avatarPackager.state = AvatarPackagerState.project;
|
||||
return status;
|
||||
}
|
||||
|
||||
function displayErrorMessage(status) {
|
||||
if (status === AvatarProjectStatus.SUCCESS) {
|
||||
return;
|
||||
}
|
||||
switch (status) {
|
||||
case AvatarProjectStatus.ERROR_CREATE_PROJECT_NAME:
|
||||
errorPopup.show("Project Folder Already Exists", "A folder with that name already exists at that location. Please choose a different project name or location.");
|
||||
break;
|
||||
case AvatarProjectStatus.ERROR_CREATE_CREATING_DIRECTORIES:
|
||||
errorPopup.show("Project Folders Creation Error", "There was a problem creating the Avatar Project directory. Please check the project location and try again.");
|
||||
break;
|
||||
case AvatarProjectStatus.ERROR_CREATE_FIND_MODEL:
|
||||
errorPopup.show("Cannot Find Model File", "There was a problem while trying to find the specified model file. Please verify that it exists at the specified location.");
|
||||
break;
|
||||
case AvatarProjectStatus.ERROR_CREATE_OPEN_MODEL:
|
||||
errorPopup.show("Cannot Open Model File", "There was a problem while trying to open the specified model file.");
|
||||
break;
|
||||
case AvatarProjectStatus.ERROR_CREATE_READ_MODEL:
|
||||
errorPopup.show("Error Read Model File", "There was a problem while trying to read the specified model file. Please check that the file is a valid FBX file and try again.");
|
||||
break;
|
||||
case AvatarProjectStatus.ERROR_CREATE_WRITE_FST:
|
||||
errorPopup.show("Error Writing Project File", "There was a problem while trying to write the FST file.");
|
||||
break;
|
||||
case AvatarProjectStatus.ERROR_OPEN_INVALID_FILE_TYPE:
|
||||
errorPopup.show("Invalid Project Path", "The avatar packager can only open FST files.");
|
||||
break;
|
||||
case AvatarProjectStatus.ERROR_OPEN_PROJECT_FOLDER:
|
||||
errorPopup.show("Project Missing", "Project folder cannot be found. Please locate the folder and copy/move it to its original location.");
|
||||
break;
|
||||
case AvatarProjectStatus.ERROR_OPEN_FIND_FST:
|
||||
errorPopup.show("File Missing", "We cannot find the project file (.fst) in the project folder. Please locate it and move it to the project folder.");
|
||||
break;
|
||||
case AvatarProjectStatus.ERROR_OPEN_OPEN_FST:
|
||||
errorPopup.show("File Read Error", "We cannot read the project file (.fst).");
|
||||
break;
|
||||
case AvatarProjectStatus.ERROR_OPEN_FIND_MODEL:
|
||||
errorPopup.show("File Missing", "We cannot find the avatar model file (.fbx) in the project folder. Please locate it and move it to the project folder.");
|
||||
break;
|
||||
default:
|
||||
errorPopup.show("Error Message Missing", "Error message missing for status " + status);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function openDocs() {
|
||||
Qt.openUrlExternally("https://docs.highfidelity.com/create/avatars/create-avatars#how-to-package-your-avatar");
|
||||
}
|
||||
|
||||
AvatarPackagerHeader {
|
||||
z: 100
|
||||
|
||||
id: avatarPackagerHeader
|
||||
colorScheme: root.colorScheme
|
||||
onBackButtonClicked: {
|
||||
avatarPackager.state = AvatarPackagerState.main;
|
||||
}
|
||||
onDocsButtonClicked: {
|
||||
avatarPackager.openDocs();
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
height: windowContent.height - avatarPackagerHeader.height - avatarPackagerFooter.height
|
||||
width: windowContent.width
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "#404040"
|
||||
}
|
||||
|
||||
AvatarProject {
|
||||
id: avatarProject
|
||||
colorScheme: root.colorScheme
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
AvatarProjectUpload {
|
||||
id: avatarUploader
|
||||
anchors.fill: parent
|
||||
root: avatarProject
|
||||
}
|
||||
|
||||
CreateAvatarProject {
|
||||
id: createAvatarProject
|
||||
colorScheme: root.colorScheme
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
Item {
|
||||
id: avatarPackagerMain
|
||||
visible: false
|
||||
anchors.fill: parent
|
||||
|
||||
property var footer: Item {
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: 17
|
||||
HifiControls.Button {
|
||||
id: createProjectButton
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: openProjectButton.left
|
||||
anchors.rightMargin: 22
|
||||
height: 40
|
||||
width: 134
|
||||
text: qsTr("New Project")
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
createAvatarProject.clearInputs();
|
||||
avatarPackager.state = AvatarPackagerState.createProject;
|
||||
}
|
||||
}
|
||||
|
||||
HifiControls.Button {
|
||||
id: openProjectButton
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
height: 40
|
||||
width: 133
|
||||
text: qsTr("Open Project")
|
||||
color: hifi.buttons.blue
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
avatarPackager.showModalOverlay = true;
|
||||
|
||||
let browser = avatarPackager.desktopObject.fileDialog({
|
||||
selectDirectory: false,
|
||||
dir: fileDialogHelper.pathToUrl(AvatarPackagerCore.AVATAR_PROJECTS_PATH),
|
||||
filter: "Avatar Project FST Files (*.fst)",
|
||||
title: "Open Project (.fst)",
|
||||
});
|
||||
|
||||
browser.canceled.connect(function() {
|
||||
avatarPackager.showModalOverlay = false;
|
||||
});
|
||||
|
||||
browser.selectedFile.connect(function(fileUrl) {
|
||||
let fstFilePath = fileDialogHelper.urlToPath(fileUrl);
|
||||
avatarPackager.showModalOverlay = false;
|
||||
avatarPackager.openProject(fstFilePath);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Flow {
|
||||
visible: AvatarPackagerCore.recentProjects.length === 0
|
||||
anchors {
|
||||
fill: parent
|
||||
topMargin: 18
|
||||
leftMargin: 16
|
||||
rightMargin: 16
|
||||
}
|
||||
RalewayRegular {
|
||||
size: 20
|
||||
color: "white"
|
||||
text: "Use a custom avatar of your choice."
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
RalewayRegular {
|
||||
size: 20
|
||||
color: "white"
|
||||
text: "<a href='javascript:void'>Visit our docs</a> to learn more about using the packager."
|
||||
linkColor: "#00B4EF"
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
onLinkActivated: {
|
||||
avatarPackager.openDocs();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
visible: AvatarPackagerCore.recentProjects.length > 0
|
||||
|
||||
RalewayRegular {
|
||||
id: recentProjectsText
|
||||
|
||||
color: 'white'
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.topMargin: 16
|
||||
anchors.leftMargin: 16
|
||||
|
||||
size: 20
|
||||
|
||||
text: "Recent Projects"
|
||||
|
||||
onLinkActivated: fileListPopup.open()
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
top: recentProjectsText.bottom
|
||||
topMargin: 16
|
||||
leftMargin: 16
|
||||
rightMargin: 16
|
||||
}
|
||||
spacing: 10
|
||||
|
||||
Repeater {
|
||||
model: AvatarPackagerCore.recentProjects
|
||||
AvatarProjectCard {
|
||||
title: modelData.name
|
||||
path: modelData.projectPath
|
||||
onOpen: avatarPackager.openProject(modelData.path)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
AvatarPackagerFooter {
|
||||
id: avatarPackagerFooter
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
import QtQuick 2.6
|
||||
|
||||
import "../../controlsUit" 1.0 as HifiControls
|
||||
import "../../stylesUit" 1.0
|
||||
|
||||
Rectangle {
|
||||
id: avatarPackagerFooter
|
||||
|
||||
color: "#404040"
|
||||
height: content === defaultContent ? 0 : 74
|
||||
visible: content !== defaultContent
|
||||
width: parent.width
|
||||
|
||||
property var content: Item { id: defaultContent }
|
||||
|
||||
children: [background, content]
|
||||
|
||||
property var background: Rectangle {
|
||||
anchors.fill: parent
|
||||
color: "#404040"
|
||||
|
||||
Rectangle {
|
||||
id: topBorder1
|
||||
|
||||
anchors.top: parent.top
|
||||
|
||||
color: "#252525"
|
||||
height: 1
|
||||
width: parent.width
|
||||
}
|
||||
Rectangle {
|
||||
id: topBorder2
|
||||
|
||||
anchors.top: topBorder1.bottom
|
||||
|
||||
color: "#575757"
|
||||
height: 1
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
import QtQuick 2.6
|
||||
|
||||
import "../../controlsUit" 1.0 as HifiControls
|
||||
import "../../stylesUit" 1.0
|
||||
import "../avatarapp" 1.0
|
||||
|
||||
ShadowRectangle {
|
||||
id: root
|
||||
|
||||
width: parent.width
|
||||
height: 74
|
||||
color: "#252525"
|
||||
|
||||
property string title: qsTr("Avatar Packager")
|
||||
property alias docsEnabled: docs.visible
|
||||
property bool backButtonVisible: true // If false, is not visible and does not take up space
|
||||
property bool backButtonEnabled: true // If false, is not visible but does not affect space
|
||||
property bool canRename: false
|
||||
property int colorScheme
|
||||
|
||||
property color textColor: "white"
|
||||
property color hoverTextColor: "gray"
|
||||
property color pressedTextColor: "#6A6A6A"
|
||||
|
||||
signal backButtonClicked
|
||||
signal docsButtonClicked
|
||||
|
||||
RalewayButton {
|
||||
id: back
|
||||
|
||||
visible: backButtonEnabled && backButtonVisible
|
||||
|
||||
size: 28
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 16
|
||||
|
||||
text: "◀"
|
||||
|
||||
onClicked: root.backButtonClicked()
|
||||
}
|
||||
Item {
|
||||
id: titleArea
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: root.backButtonVisible ? back.right : parent.left
|
||||
anchors.leftMargin: root.backButtonVisible ? 11 : 21
|
||||
anchors.right: docs.left
|
||||
states: [
|
||||
State {
|
||||
name: "renaming"
|
||||
PropertyChanges { target: title; visible: false }
|
||||
PropertyChanges { target: titleInputArea; visible: true }
|
||||
}
|
||||
]
|
||||
|
||||
Item {
|
||||
id: title
|
||||
anchors.fill: parent
|
||||
|
||||
RalewaySemiBold {
|
||||
id: titleNotRenameable
|
||||
|
||||
visible: !root.canRename
|
||||
|
||||
size: 28
|
||||
anchors.fill: parent
|
||||
text: root.title
|
||||
color: "white"
|
||||
}
|
||||
|
||||
RalewayButton {
|
||||
id: titleRenameable
|
||||
|
||||
visible: root.canRename
|
||||
enabled: root.canRename
|
||||
|
||||
size: 28
|
||||
anchors.fill: parent
|
||||
text: root.title
|
||||
|
||||
onClicked: {
|
||||
if (!root.canRename || AvatarPackagerCore.currentAvatarProject === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
titleArea.state = "renaming";
|
||||
titleInput.text = AvatarPackagerCore.currentAvatarProject.name;
|
||||
titleInput.selectAll();
|
||||
titleInput.forceActiveFocus(Qt.MouseFocusReason);
|
||||
}
|
||||
}
|
||||
}
|
||||
Item {
|
||||
id: titleInputArea
|
||||
visible: false
|
||||
anchors.fill: parent
|
||||
|
||||
HifiControls.TextField {
|
||||
id: titleInput
|
||||
anchors.fill: parent
|
||||
text: ""
|
||||
colorScheme: root.colorScheme
|
||||
font.family: "Fira Sans"
|
||||
font.pixelSize: 28
|
||||
z: 200
|
||||
onFocusChanged: {
|
||||
if (titleArea.state === "renaming" && !focus) {
|
||||
accepted();
|
||||
}
|
||||
}
|
||||
Keys.onPressed: {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
titleArea.state = "";
|
||||
}
|
||||
}
|
||||
onAccepted: {
|
||||
if (acceptableInput) {
|
||||
AvatarPackagerCore.currentAvatarProject.name = text;
|
||||
}
|
||||
titleArea.state = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RalewayButton {
|
||||
id: docs
|
||||
visible: false
|
||||
size: 28
|
||||
anchors.top: parent.top
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 16
|
||||
|
||||
text: qsTr("Docs")
|
||||
|
||||
onClicked: {
|
||||
docsButtonClicked();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
pragma Singleton
|
||||
import QtQuick 2.6
|
||||
|
||||
Item {
|
||||
id: singleton
|
||||
readonly property string main: "main"
|
||||
readonly property string project: "project"
|
||||
readonly property string createProject: "createProject"
|
||||
readonly property string projectUpload: "projectUpload"
|
||||
}
|
336
interface/resources/qml/hifi/avatarPackager/AvatarProject.qml
Normal file
336
interface/resources/qml/hifi/avatarPackager/AvatarProject.qml
Normal file
|
@ -0,0 +1,336 @@
|
|||
import QtQuick 2.6
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
import QtQuick.Controls 2.2 as Original
|
||||
|
||||
import "../../controlsUit" 1.0 as HifiControls
|
||||
import "../../stylesUit" 1.0
|
||||
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
Style { id: style }
|
||||
|
||||
property int colorScheme
|
||||
property var uploader: null
|
||||
|
||||
property bool hasSuccessfullyUploaded: true
|
||||
|
||||
visible: false
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
|
||||
function reset() {
|
||||
hasSuccessfullyUploaded = false;
|
||||
uploader = null;
|
||||
}
|
||||
|
||||
property var footer: Item {
|
||||
anchors.fill: parent
|
||||
|
||||
Item {
|
||||
id: uploadFooter
|
||||
|
||||
visible: !root.uploader || root.finished || root.uploader.state !== 4
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: 17
|
||||
|
||||
HifiControls.Button {
|
||||
id: uploadButton
|
||||
|
||||
visible: AvatarPackagerCore.currentAvatarProject && !AvatarPackagerCore.currentAvatarProject.fst.hasMarketplaceID && !root.hasSuccessfullyUploaded
|
||||
enabled: Account.loggedIn
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
|
||||
text: qsTr("Upload")
|
||||
color: hifi.buttons.blue
|
||||
colorScheme: root.colorScheme
|
||||
width: 133
|
||||
height: 40
|
||||
onClicked: {
|
||||
uploadNew();
|
||||
}
|
||||
}
|
||||
HifiControls.Button {
|
||||
id: updateButton
|
||||
|
||||
visible: AvatarPackagerCore.currentAvatarProject && AvatarPackagerCore.currentAvatarProject.fst.hasMarketplaceID && !root.hasSuccessfullyUploaded
|
||||
enabled: Account.loggedIn
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
|
||||
text: qsTr("Update")
|
||||
color: hifi.buttons.blue
|
||||
colorScheme: root.colorScheme
|
||||
width: 134
|
||||
height: 40
|
||||
onClicked: {
|
||||
showConfirmUploadPopup(uploadNew, uploadUpdate);
|
||||
}
|
||||
}
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
visible: root.hasSuccessfullyUploaded
|
||||
|
||||
HifiControls.Button {
|
||||
enabled: Account.loggedIn
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: viewInInventoryButton.left
|
||||
anchors.rightMargin: 16
|
||||
|
||||
text: qsTr("Update")
|
||||
color: hifi.buttons.white
|
||||
colorScheme: root.colorScheme
|
||||
width: 134
|
||||
height: 40
|
||||
onClicked: {
|
||||
showConfirmUploadPopup(uploadNew, uploadUpdate);
|
||||
}
|
||||
}
|
||||
HifiControls.Button {
|
||||
id: viewInInventoryButton
|
||||
|
||||
enabled: Account.loggedIn
|
||||
|
||||
width: 168
|
||||
height: 40
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
|
||||
text: qsTr("View in Inventory")
|
||||
color: hifi.buttons.blue
|
||||
colorScheme: root.colorScheme
|
||||
|
||||
onClicked: AvatarPackagerCore.currentAvatarProject.openInInventory()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: uploadingItemFooter
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 1
|
||||
visible: !!root.uploader && !root.finished && root.uploader.state === 4
|
||||
|
||||
color: "#00B4EF"
|
||||
|
||||
LoadingCircle {
|
||||
id: runningImage
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 16
|
||||
|
||||
width: 28
|
||||
height: 28
|
||||
|
||||
white: true
|
||||
}
|
||||
RalewayRegular {
|
||||
id: stepText
|
||||
|
||||
size: 20
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: runningImage.right
|
||||
anchors.leftMargin: 16
|
||||
|
||||
text: "Adding item to Inventory"
|
||||
color: "white"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function uploadNew() {
|
||||
upload(false);
|
||||
}
|
||||
function uploadUpdate() {
|
||||
upload(true);
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.uploader
|
||||
onStateChanged: {
|
||||
root.hasSuccessfullyUploaded = newState >= 4;
|
||||
}
|
||||
}
|
||||
|
||||
function upload(updateExisting) {
|
||||
root.uploader = AvatarPackagerCore.currentAvatarProject.upload(updateExisting);
|
||||
console.log("uploader: "+ root.uploader);
|
||||
root.uploader.send();
|
||||
avatarPackager.state = AvatarPackagerState.projectUpload;
|
||||
}
|
||||
|
||||
function showConfirmUploadPopup() {
|
||||
popup.titleText = 'Overwrite Avatar';
|
||||
popup.bodyText = 'You have previously uploaded the avatar file from this project.' +
|
||||
' This will overwrite that avatar and you won’t be able to access the older version.';
|
||||
|
||||
popup.button1text = 'CREATE NEW';
|
||||
popup.button2text = 'OVERWRITE';
|
||||
|
||||
popup.onButton2Clicked = function() {
|
||||
popup.close();
|
||||
uploadUpdate();
|
||||
};
|
||||
popup.onButton1Clicked = function() {
|
||||
popup.close();
|
||||
showConfirmCreateNewPopup();
|
||||
};
|
||||
|
||||
popup.open();
|
||||
}
|
||||
|
||||
function showConfirmCreateNewPopup(confirmCallback) {
|
||||
popup.titleText = 'Create New';
|
||||
popup.bodyText = 'This will upload your current files with the same avatar name.' +
|
||||
' You will lose the ability to update the previously uploaded avatar. Are you sure you want to continue?';
|
||||
|
||||
popup.button1text = 'CANCEL';
|
||||
popup.button2text = 'CONFIRM';
|
||||
|
||||
popup.onButton1Clicked = function() {
|
||||
popup.close()
|
||||
};
|
||||
popup.onButton2Clicked = function() {
|
||||
popup.close();
|
||||
uploadNew();
|
||||
};
|
||||
|
||||
popup.open();
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
id: infoMessage
|
||||
|
||||
states: [
|
||||
State {
|
||||
when: root.hasSuccessfullyUploaded
|
||||
name: "upload-success"
|
||||
PropertyChanges {
|
||||
target: infoMessage
|
||||
text: "Your avatar has been successfully uploaded to our servers. Make changes to your avatar by editing and uploading the project files."
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "has-previous-success"
|
||||
when: !!AvatarPackagerCore.currentAvatarProject && AvatarPackagerCore.currentAvatarProject.fst.hasMarketplaceID
|
||||
PropertyChanges {
|
||||
target: infoMessage
|
||||
text: "Click \"Update\" to overwrite the hosted files and update the avatar in your inventory. You will have to “Wear” the avatar again to see changes."
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
color: 'white'
|
||||
size: 20
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
|
||||
anchors.bottomMargin: 24
|
||||
|
||||
wrapMode: Text.Wrap
|
||||
|
||||
text: "You can upload your files to our servers to always access them, and to make your avatar visible to other users."
|
||||
}
|
||||
|
||||
HifiControls.Button {
|
||||
id: openFolderButton
|
||||
|
||||
visible: false
|
||||
width: parent.width
|
||||
anchors.top: infoMessage.bottom
|
||||
anchors.topMargin: 10
|
||||
text: qsTr("Open Project Folder")
|
||||
colorScheme: root.colorScheme
|
||||
height: 30
|
||||
onClicked: {
|
||||
fileDialogHelper.openDirectory(fileDialogHelper.pathToUrl(AvatarPackagerCore.currentAvatarProject.projectFolderPath));
|
||||
}
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
id: showFilesText
|
||||
|
||||
color: 'white'
|
||||
linkColor: style.colors.blueHighlight
|
||||
|
||||
visible: AvatarPackagerCore.currentAvatarProject !== null
|
||||
|
||||
anchors.bottom: loginRequiredMessage.top
|
||||
anchors.bottomMargin: 10
|
||||
|
||||
size: 20
|
||||
|
||||
text: AvatarPackagerCore.currentAvatarProject ? AvatarPackagerCore.currentAvatarProject.projectFiles.length + " files in project. <a href='toggle'>See list</a>" : ""
|
||||
|
||||
onLinkActivated: fileListPopup.open()
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: loginRequiredMessage
|
||||
|
||||
visible: !Account.loggedIn
|
||||
height: !Account.loggedIn ? loginRequiredTextRow.height + 20 : 0
|
||||
|
||||
anchors {
|
||||
bottom: parent.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
color: "#FFD6AD"
|
||||
|
||||
border.color: "#F39622"
|
||||
border.width: 2
|
||||
radius: 2
|
||||
|
||||
Item {
|
||||
id: loginRequiredTextRow
|
||||
|
||||
height: Math.max(loginWarningGlyph.implicitHeight, loginWarningText.implicitHeight)
|
||||
anchors.fill: parent
|
||||
anchors.margins: 10
|
||||
|
||||
HiFiGlyphs {
|
||||
id: loginWarningGlyph
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
|
||||
width: implicitWidth
|
||||
|
||||
size: 48
|
||||
text: "+"
|
||||
color: "black"
|
||||
}
|
||||
RalewayRegular {
|
||||
id: loginWarningText
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.leftMargin: 16
|
||||
anchors.left: loginWarningGlyph.right
|
||||
anchors.right: parent.right
|
||||
|
||||
text: "Please login to upload your avatar to High Fidelity hosting."
|
||||
size: 18
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
import QtQuick 2.0
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
import "../../controlsUit" 1.0 as HifiControls
|
||||
import "../../stylesUit" 1.0
|
||||
|
||||
|
||||
Item {
|
||||
id: projectCard
|
||||
height: 80
|
||||
width: parent.width
|
||||
|
||||
property alias title: title.text
|
||||
property alias path: path.text
|
||||
|
||||
property color textColor: "#E3E3E3"
|
||||
property color hoverTextColor: "#121212"
|
||||
property color pressedTextColor: "#121212"
|
||||
|
||||
property color backgroundColor: "#121212"
|
||||
property color hoverBackgroundColor: "#E3E3E3"
|
||||
property color pressedBackgroundColor: "#6A6A6A"
|
||||
|
||||
signal open
|
||||
|
||||
state: mouseArea.pressed ? "pressed" : (mouseArea.containsMouse ? "hover" : "normal")
|
||||
states: [
|
||||
State {
|
||||
name: "normal"
|
||||
PropertyChanges { target: background; color: backgroundColor }
|
||||
PropertyChanges { target: title; color: textColor }
|
||||
PropertyChanges { target: path; color: textColor }
|
||||
},
|
||||
State {
|
||||
name: "hover"
|
||||
PropertyChanges { target: background; color: hoverBackgroundColor }
|
||||
PropertyChanges { target: title; color: hoverTextColor }
|
||||
PropertyChanges { target: path; color: hoverTextColor }
|
||||
},
|
||||
State {
|
||||
name: "pressed"
|
||||
PropertyChanges { target: background; color: pressedBackgroundColor }
|
||||
PropertyChanges { target: title; color: pressedTextColor }
|
||||
PropertyChanges { target: path; color: pressedTextColor }
|
||||
}
|
||||
]
|
||||
|
||||
Rectangle {
|
||||
id: background
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
color: "#121212"
|
||||
radius: 4
|
||||
|
||||
RalewayBold {
|
||||
id: title
|
||||
elide: "ElideRight"
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: 13
|
||||
left: parent.left
|
||||
leftMargin: 16
|
||||
right: parent.right
|
||||
rightMargin: 16
|
||||
}
|
||||
text: "<title missing>"
|
||||
size: 24
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
id: path
|
||||
anchors {
|
||||
top: title.bottom
|
||||
left: parent.left
|
||||
leftMargin: 32
|
||||
right: background.right
|
||||
rightMargin: 16
|
||||
}
|
||||
elide: "ElideLeft"
|
||||
horizontalAlignment: Text.AlignRight
|
||||
text: "<path missing>"
|
||||
size: 20
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: open()
|
||||
}
|
||||
}
|
||||
|
||||
DropShadow {
|
||||
id: shadow
|
||||
anchors.fill: background
|
||||
radius: 4
|
||||
horizontalOffset: 0
|
||||
verticalOffset: 4
|
||||
color: Qt.rgba(0, 0, 0, 0.25)
|
||||
source: background
|
||||
}
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
import QtQuick 2.6
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
import QtQuick.Controls 2.2 as Original
|
||||
|
||||
import "../../controlsUit" 1.0 as HifiControls
|
||||
import "../../stylesUit" 1.0
|
||||
|
||||
Item {
|
||||
id: uploadingScreen
|
||||
|
||||
property var root: undefined
|
||||
visible: false
|
||||
anchors.fill: parent
|
||||
|
||||
Timer {
|
||||
id: backToProjectTimer
|
||||
interval: 2000
|
||||
running: false
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
if (avatarPackager.state === AvatarPackagerState.projectUpload) {
|
||||
avatarPackager.state = AvatarPackagerState.project;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function stateChangedCallback(newState) {
|
||||
if (newState >= 4) {
|
||||
root.uploader.stateChanged.disconnect(stateChangedCallback);
|
||||
backToProjectTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
root.uploader.stateChanged.connect(stateChangedCallback);
|
||||
root.uploader.finishedChanged.connect(function() {
|
||||
if (root.uploader.error === 0) {
|
||||
backToProjectTimer.start();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: uploadStatus
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
Item {
|
||||
id: statusItem
|
||||
|
||||
width: parent.width
|
||||
height: 256
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "success"
|
||||
when: root.uploader.state >= 4 && root.uploader.error === 0
|
||||
PropertyChanges { target: uploadSpinner; visible: false }
|
||||
PropertyChanges { target: errorIcon; visible: false }
|
||||
PropertyChanges { target: successIcon; visible: true }
|
||||
},
|
||||
State {
|
||||
name: "error"
|
||||
when: root.uploader.finished && root.uploader.error !== 0
|
||||
PropertyChanges { target: uploadSpinner; visible: false }
|
||||
PropertyChanges { target: errorIcon; visible: true }
|
||||
PropertyChanges { target: successIcon; visible: false }
|
||||
PropertyChanges { target: errorFooter; visible: true }
|
||||
PropertyChanges { target: errorMessage; visible: true }
|
||||
}
|
||||
]
|
||||
|
||||
AnimatedImage {
|
||||
id: uploadSpinner
|
||||
|
||||
visible: true
|
||||
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
width: 164
|
||||
height: 164
|
||||
|
||||
source: "../../../icons/loader-snake-256.gif"
|
||||
playing: true
|
||||
}
|
||||
|
||||
HiFiGlyphs {
|
||||
id: errorIcon
|
||||
|
||||
visible: false
|
||||
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
size: 315
|
||||
text: "+"
|
||||
color: "#EA4C5F"
|
||||
}
|
||||
|
||||
Image {
|
||||
id: successIcon
|
||||
|
||||
visible: false
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
width: 148
|
||||
height: 148
|
||||
|
||||
source: "../../../icons/checkmark-stroke.svg"
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: statusRows
|
||||
|
||||
anchors.top: statusItem.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 12
|
||||
|
||||
AvatarUploadStatusItem {
|
||||
id: statusCategories
|
||||
uploader: root.uploader
|
||||
text: "Retrieving categories"
|
||||
|
||||
uploaderState: 1
|
||||
}
|
||||
AvatarUploadStatusItem {
|
||||
id: statusUploading
|
||||
uploader: root.uploader
|
||||
anchors.top: statusCategories.bottom
|
||||
text: "Uploading data"
|
||||
|
||||
uploaderState: 2
|
||||
}
|
||||
AvatarUploadStatusItem {
|
||||
id: statusResponse
|
||||
uploader: root.uploader
|
||||
anchors.top: statusUploading.bottom
|
||||
text: "Waiting for response"
|
||||
|
||||
uploaderState: 3
|
||||
}
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
id: errorMessage
|
||||
|
||||
visible: false
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: errorFooter.top
|
||||
anchors.leftMargin: 16
|
||||
anchors.rightMargin: 16
|
||||
anchors.bottomMargin: 32
|
||||
|
||||
size: 28
|
||||
wrapMode: Text.Wrap
|
||||
color: "white"
|
||||
text: "We couldn't upload your avatar at this time. Please try again later."
|
||||
}
|
||||
|
||||
AvatarPackagerFooter {
|
||||
id: errorFooter
|
||||
|
||||
anchors.bottom: parent.bottom
|
||||
visible: false
|
||||
|
||||
content: Item {
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: 17
|
||||
HifiControls.Button {
|
||||
id: backButton
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
|
||||
text: qsTr("Back")
|
||||
color: hifi.buttons.blue
|
||||
colorScheme: root.colorScheme
|
||||
width: 133
|
||||
height: 40
|
||||
onClicked: {
|
||||
avatarPackager.state = AvatarPackagerState.project;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
import QtQuick 2.6
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
import "../../controlsUit" 1.0 as HifiControls
|
||||
import "../../stylesUit" 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
height: 48
|
||||
|
||||
property string text: "NO STEP TEXT"
|
||||
property int uploaderState;
|
||||
property var uploader;
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: ""
|
||||
when: root.uploader === null
|
||||
},
|
||||
State {
|
||||
name: "success"
|
||||
when: root.uploader.state > uploaderState
|
||||
PropertyChanges { target: stepText; color: "white" }
|
||||
PropertyChanges { target: successGlyph; visible: true }
|
||||
},
|
||||
State {
|
||||
name: "fail"
|
||||
when: root.uploader.error !== 0
|
||||
PropertyChanges { target: stepText; color: "#EA4C5F" }
|
||||
PropertyChanges { target: failGlyph; visible: true }
|
||||
},
|
||||
State {
|
||||
name: "running"
|
||||
when: root.uploader.state === uploaderState
|
||||
PropertyChanges { target: stepText; color: "white" }
|
||||
PropertyChanges { target: runningImage; visible: true; playing: true }
|
||||
}
|
||||
]
|
||||
|
||||
Item {
|
||||
id: statusItem
|
||||
|
||||
width: 48
|
||||
height: parent.height
|
||||
|
||||
LoadingCircle {
|
||||
id: runningImage
|
||||
|
||||
visible: false
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
width: 32
|
||||
height: 32
|
||||
}
|
||||
Image {
|
||||
id: successGlyph
|
||||
|
||||
visible: false
|
||||
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
width: 30
|
||||
height: 30
|
||||
|
||||
source: "../../../icons/checkmark-stroke.svg"
|
||||
}
|
||||
HiFiGlyphs {
|
||||
id: failGlyph
|
||||
|
||||
visible: false
|
||||
|
||||
width: implicitWidth
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
size: 48
|
||||
text: "+"
|
||||
color: "#EA4C5F"
|
||||
}
|
||||
}
|
||||
RalewayRegular {
|
||||
id: stepText
|
||||
|
||||
anchors.left: statusItem.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
text: root.text
|
||||
size: 28
|
||||
color: "#777777"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
import QtQuick 2.6
|
||||
|
||||
import "../../controlsUit" 1.0 as HifiControls
|
||||
import "../../stylesUit" 1.0
|
||||
|
||||
import TabletScriptingInterface 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
readonly property bool pressed: mouseArea.state == "pressed"
|
||||
readonly property bool hovered: mouseArea.state == "hovering"
|
||||
|
||||
signal clicked()
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
hoverEnabled: true
|
||||
|
||||
onClicked: {
|
||||
root.focus = true
|
||||
Tablet.playSound(TabletEnums.ButtonClick);
|
||||
root.clicked();
|
||||
}
|
||||
|
||||
property string lastState: ""
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: ""
|
||||
StateChangeScript {
|
||||
script: {
|
||||
mouseArea.lastState = mouseArea.state;
|
||||
}
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "pressed"
|
||||
when: mouseArea.containsMouse && mouseArea.pressed
|
||||
StateChangeScript {
|
||||
script: {
|
||||
mouseArea.lastState = mouseArea.state;
|
||||
}
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "hovering"
|
||||
when: mouseArea.containsMouse
|
||||
StateChangeScript {
|
||||
script: {
|
||||
if (mouseArea.lastState == "") {
|
||||
Tablet.playSound(TabletEnums.ButtonHover);
|
||||
}
|
||||
mouseArea.lastState = mouseArea.state;
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
import QtQuick 2.6
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
import Hifi.AvatarPackager.AvatarProjectStatus 1.0
|
||||
|
||||
import "../../controlsUit" 1.0 as HifiControls
|
||||
import "../../stylesUit" 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
property int colorScheme
|
||||
|
||||
property var footer: Item {
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: 17
|
||||
HifiControls.Button {
|
||||
id: createButton
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
height: 30
|
||||
width: 133
|
||||
text: qsTr("Create")
|
||||
enabled: false
|
||||
onClicked: {
|
||||
let status = AvatarPackagerCore.createAvatarProject(projectLocation.text, name.text, avatarModel.text, textureFolder.text);
|
||||
if (status !== AvatarProjectStatus.SUCCESS) {
|
||||
avatarPackager.displayErrorMessage(status);
|
||||
return;
|
||||
}
|
||||
avatarProject.reset();
|
||||
avatarPackager.state = AvatarPackagerState.project;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visible: false
|
||||
anchors.fill: parent
|
||||
height: parent.height
|
||||
width: parent.width
|
||||
|
||||
function clearInputs() {
|
||||
name.text = projectLocation.text = avatarModel.text = textureFolder.text = "";
|
||||
}
|
||||
|
||||
function checkErrors() {
|
||||
let newErrorMessageText = "";
|
||||
|
||||
let projectName = name.text;
|
||||
let projectFolder = projectLocation.text;
|
||||
|
||||
let hasProjectNameError = projectName !== "" && projectFolder !== "" && !AvatarPackagerCore.isValidNewProjectName(projectFolder, projectName);
|
||||
|
||||
if (hasProjectNameError) {
|
||||
newErrorMessageText = "A folder with that name already exists at that location. Please choose a different project name or location.";
|
||||
}
|
||||
|
||||
name.error = projectLocation.error = hasProjectNameError;
|
||||
errorMessage.text = newErrorMessageText;
|
||||
createButton.enabled = newErrorMessageText === "" && requiredFieldsFilledIn();
|
||||
}
|
||||
|
||||
function requiredFieldsFilledIn() {
|
||||
return name.text !== "" && projectLocation.text !== "" && avatarModel.text !== "";
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
id: errorMessage
|
||||
visible: text !== ""
|
||||
text: ""
|
||||
color: "#EA4C5F"
|
||||
wrapMode: Text.WordWrap
|
||||
size: 20
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: createAvatarColumns
|
||||
anchors.top: errorMessage.visible ? errorMessage.bottom : parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: 10
|
||||
|
||||
spacing: 17
|
||||
|
||||
property string defaultFileBrowserPath: fileDialogHelper.pathToUrl(AvatarPackagerCore.AVATAR_PROJECTS_PATH)
|
||||
|
||||
ProjectInputControl {
|
||||
id: name
|
||||
label: "Name"
|
||||
colorScheme: root.colorScheme
|
||||
onTextChanged: checkErrors()
|
||||
}
|
||||
|
||||
ProjectInputControl {
|
||||
id: projectLocation
|
||||
label: "Specify Project Location"
|
||||
colorScheme: root.colorScheme
|
||||
browseEnabled: true
|
||||
browseFolder: true
|
||||
browseDir: text !== "" ? fileDialogHelper.pathToUrl(text) : createAvatarColumns.defaultFileBrowserPath
|
||||
browseTitle: "Project Location"
|
||||
onTextChanged: checkErrors()
|
||||
}
|
||||
|
||||
ProjectInputControl {
|
||||
id: avatarModel
|
||||
label: "Specify Avatar Model (.fbx)"
|
||||
colorScheme: root.colorScheme
|
||||
browseEnabled: true
|
||||
browseFolder: false
|
||||
browseDir: text !== "" ? fileDialogHelper.pathToUrl(text) : createAvatarColumns.defaultFileBrowserPath
|
||||
browseFilter: "Avatar Model File (*.fbx)"
|
||||
browseTitle: "Open Avatar Model (.fbx)"
|
||||
onTextChanged: checkErrors()
|
||||
}
|
||||
|
||||
ProjectInputControl {
|
||||
id: textureFolder
|
||||
label: "Specify Texture Folder - <i>Optional</i>"
|
||||
colorScheme: root.colorScheme
|
||||
browseEnabled: true
|
||||
browseFolder: true
|
||||
browseDir: text !== "" ? fileDialogHelper.pathToUrl(text) : createAvatarColumns.defaultFileBrowserPath
|
||||
browseTitle: "Texture Folder"
|
||||
onTextChanged: checkErrors()
|
||||
}
|
||||
}
|
||||
}
|
120
interface/resources/qml/hifi/avatarPackager/InfoBox.qml
Normal file
120
interface/resources/qml/hifi/avatarPackager/InfoBox.qml
Normal file
|
@ -0,0 +1,120 @@
|
|||
import Hifi 1.0 as Hifi
|
||||
import QtQuick 2.5
|
||||
import stylesUit 1.0
|
||||
import controlsUit 1.0 as HifiControlsUit
|
||||
import "../../controls" as HifiControls
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
visible: false
|
||||
color: Qt.rgba(.34, .34, .34, 0.6)
|
||||
z: 999;
|
||||
|
||||
anchors.fill: parent
|
||||
|
||||
property alias title: titleText.text
|
||||
property alias content: loader.sourceComponent
|
||||
|
||||
property bool closeOnClickOutside: false;
|
||||
|
||||
property alias boxWidth: mainContainer.width
|
||||
property alias boxHeight: mainContainer.height
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
focus = true;
|
||||
}
|
||||
}
|
||||
|
||||
function open() {
|
||||
visible = true;
|
||||
}
|
||||
|
||||
function close() {
|
||||
visible = false;
|
||||
}
|
||||
|
||||
HifiConstants {
|
||||
id: hifi
|
||||
}
|
||||
|
||||
// This object is always used in a popup.
|
||||
// This MouseArea is used to prevent a user from being
|
||||
// able to click on a button/mouseArea underneath the popup.
|
||||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
propagateComposedEvents: false;
|
||||
hoverEnabled: true;
|
||||
onClicked: {
|
||||
if (closeOnClickOutside) {
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: mainContainer
|
||||
|
||||
width: Math.max(parent.width * 0.8, 400)
|
||||
height: parent.height * 0.6
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
propagateComposedEvents: false
|
||||
hoverEnabled: true
|
||||
onClicked: function(ev) {
|
||||
ev.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
color: "#252525"
|
||||
|
||||
// TextStyle1
|
||||
RalewaySemiBold {
|
||||
id: titleText
|
||||
size: 24
|
||||
color: "white"
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 30
|
||||
|
||||
text: "Title not defined"
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.topMargin: 10
|
||||
anchors.top: titleText.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: button.top
|
||||
|
||||
Loader {
|
||||
id: loader
|
||||
anchors.fill: parent
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: button
|
||||
|
||||
height: 40
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 12
|
||||
|
||||
HifiControlsUit.Button {
|
||||
anchors.centerIn: parent
|
||||
|
||||
text: "CLOSE"
|
||||
onClicked: close()
|
||||
|
||||
color: hifi.buttons.noneBorderlessWhite;
|
||||
colorScheme: hifi.colorSchemes.dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import QtQuick 2.6
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
AnimatedImage {
|
||||
id: root
|
||||
|
||||
width: 128
|
||||
height: 128
|
||||
|
||||
property bool white: false
|
||||
|
||||
source: white ? "../../../icons/loader-snake-256-wf.gif" : "../../../icons/loader-snake-256.gif"
|
||||
playing: true
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
import QtQuick 2.6
|
||||
|
||||
import "../../controlsUit" 1.0 as HifiControls
|
||||
import "../../stylesUit" 1.0
|
||||
|
||||
Column {
|
||||
id: control
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 21
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 16
|
||||
|
||||
height: 75
|
||||
|
||||
spacing: 4
|
||||
|
||||
property alias label: label.text
|
||||
property alias browseEnabled: browseButton.visible
|
||||
property bool browseFolder: false
|
||||
property string browseFilter: "All Files (*.*)"
|
||||
property string browseTitle: "Open file"
|
||||
property string browseDir: ""
|
||||
property alias text: input.text
|
||||
property alias error: input.error
|
||||
|
||||
property int colorScheme
|
||||
|
||||
Row {
|
||||
RalewaySemiBold {
|
||||
id: label
|
||||
size: 20
|
||||
font.weight: Font.Medium
|
||||
text: ""
|
||||
color: "white"
|
||||
}
|
||||
}
|
||||
Row {
|
||||
width: control.width
|
||||
spacing: 16
|
||||
height: 40
|
||||
HifiControls.TextField {
|
||||
id: input
|
||||
colorScheme: control.colorScheme
|
||||
font.family: "Fira Sans"
|
||||
font.pixelSize: 18
|
||||
height: parent.height
|
||||
width: browseButton.visible ? parent.width - browseButton.width - parent.spacing : parent.width
|
||||
}
|
||||
|
||||
HifiControls.Button {
|
||||
id: browseButton
|
||||
visible: false
|
||||
height: parent.height
|
||||
width: 133
|
||||
text: qsTr("Browse")
|
||||
colorScheme: root.colorScheme
|
||||
onClicked: {
|
||||
avatarPackager.showModalOverlay = true;
|
||||
let browser = avatarPackager.desktopObject.fileDialog({
|
||||
selectDirectory: browseFolder,
|
||||
dir: browseDir,
|
||||
filter: browseFilter,
|
||||
title: browseTitle,
|
||||
});
|
||||
|
||||
browser.canceled.connect(function() {
|
||||
avatarPackager.showModalOverlay = false;
|
||||
});
|
||||
|
||||
browser.selectedFile.connect(function(fileUrl) {
|
||||
input.text = fileDialogHelper.urlToPath(fileUrl);
|
||||
avatarPackager.showModalOverlay = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
import QtQuick 2.6
|
||||
|
||||
import "../../controlsUit" 1.0 as HifiControls
|
||||
import "../../stylesUit" 1.0
|
||||
|
||||
import TabletScriptingInterface 1.0
|
||||
|
||||
RalewaySemiBold {
|
||||
id: root
|
||||
|
||||
property color idleColor: "white"
|
||||
property color hoverColor: "#AFAFAF"
|
||||
property color pressedColor: "#575757"
|
||||
|
||||
color: clickable.hovered ? root.hoverColor : (clickable.pressed ? root.pressedColor : root.idleColor)
|
||||
|
||||
signal clicked()
|
||||
|
||||
ClickableArea {
|
||||
id: clickable
|
||||
|
||||
anchors.fill: root
|
||||
|
||||
onClicked: root.clicked()
|
||||
}
|
||||
}
|
20
interface/resources/qml/hifi/avatarPackager/Style.qml
Normal file
20
interface/resources/qml/hifi/avatarPackager/Style.qml
Normal file
|
@ -0,0 +1,20 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Window 2.2
|
||||
|
||||
import "../../stylesUit" 1.0
|
||||
|
||||
QtObject {
|
||||
readonly property QtObject colors: QtObject {
|
||||
readonly property color lightGrayBackground: "#f2f2f2"
|
||||
readonly property color black: "#000000"
|
||||
readonly property color white: "#ffffff"
|
||||
readonly property color blueHighlight: "#00b4ef"
|
||||
readonly property color inputFieldBackground: "#d4d4d4"
|
||||
readonly property color yellowishOrange: "#ffb017"
|
||||
readonly property color blueAccent: "#0093c5"
|
||||
readonly property color greenHighlight: "#1fc6a6"
|
||||
readonly property color lightGray: "#afafaf"
|
||||
readonly property color redHighlight: "#ea4c5f"
|
||||
readonly property color orangeAccent: "#ff6309"
|
||||
}
|
||||
}
|
2
interface/resources/qml/hifi/avatarPackager/qmldir
Normal file
2
interface/resources/qml/hifi/avatarPackager/qmldir
Normal file
|
@ -0,0 +1,2 @@
|
|||
module AvatarPackager
|
||||
singleton AvatarPackagerState 1.0 AvatarPackagerState.qml
|
|
@ -75,6 +75,10 @@ Rectangle {
|
|||
if(materialUrlOrJson) {
|
||||
wearable.text = 'Material: ' + materialUrlOrJson;
|
||||
}
|
||||
} else if (wearable.sourceUrl) {
|
||||
wearable.text = extractTitleFromUrl(wearable.sourceUrl);
|
||||
} else if (wearable.name) {
|
||||
wearable.text = wearable.name;
|
||||
}
|
||||
wearablesCombobox.model.append(wearable);
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ Rectangle {
|
|||
property string button2color: hifi.buttons.blue;
|
||||
property string button2text: ''
|
||||
|
||||
property bool closeOnClickOutside: false;
|
||||
|
||||
property var onButton2Clicked;
|
||||
property var onButton1Clicked;
|
||||
property var onLinkClicked;
|
||||
|
@ -56,6 +58,11 @@ Rectangle {
|
|||
anchors.fill: parent;
|
||||
propagateComposedEvents: false;
|
||||
hoverEnabled: true;
|
||||
onClicked: {
|
||||
if (closeOnClickOutside) {
|
||||
root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
|
@ -68,6 +75,15 @@ Rectangle {
|
|||
console.debug('mainContainer: height = ', height)
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent;
|
||||
propagateComposedEvents: false;
|
||||
hoverEnabled: true;
|
||||
onClicked: function(ev) {
|
||||
ev.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
color: "white"
|
||||
|
|
|
@ -35,7 +35,8 @@ Rectangle {
|
|||
|
||||
property real scaleValue: scaleSlider.value / 10
|
||||
property alias dominantHandIsLeft: leftHandRadioButton.checked
|
||||
property alias avatarCollisionsOn: collisionsEnabledRadiobutton.checked
|
||||
property alias otherAvatarsCollisionsOn: otherAvatarsCollisionsEnabledCheckBox.checked
|
||||
property alias environmentCollisionsOn: environmentCollisionsEnabledCheckBox.checked
|
||||
property alias avatarAnimationOverrideJSON: avatarAnimationUrlInputText.text
|
||||
property alias avatarAnimationJSON: avatarAnimationUrlInputText.placeholderText
|
||||
property alias avatarCollisionSoundUrl: avatarCollisionSoundUrlInputText.text
|
||||
|
@ -54,11 +55,11 @@ Rectangle {
|
|||
} else {
|
||||
rightHandRadioButton.checked = true;
|
||||
}
|
||||
|
||||
if (settings.otherAvatarsCollisionsEnabled) {
|
||||
otherAvatarsCollisionsEnabledCheckBox.checked = true;
|
||||
}
|
||||
if (settings.collisionsEnabled) {
|
||||
collisionsEnabledRadiobutton.checked = true;
|
||||
} else {
|
||||
collisionsDisabledRadioButton.checked = true;
|
||||
environmentCollisionsEnabledCheckBox.checked = true;
|
||||
}
|
||||
|
||||
avatarAnimationJSON = settings.animGraphUrl;
|
||||
|
@ -255,55 +256,43 @@ Rectangle {
|
|||
text: "Right"
|
||||
boxSize: 20
|
||||
}
|
||||
|
||||
HifiConstants {
|
||||
id: hifi
|
||||
}
|
||||
|
||||
// TextStyle9
|
||||
RalewaySemiBold {
|
||||
size: 17;
|
||||
Layout.row: 1
|
||||
Layout.column: 0
|
||||
|
||||
text: "Avatar Collisions"
|
||||
text: "Avatar collides with other avatars"
|
||||
}
|
||||
|
||||
ButtonGroup {
|
||||
id: onOff
|
||||
}
|
||||
|
||||
HifiControlsUit.RadioButton {
|
||||
id: collisionsEnabledRadiobutton
|
||||
|
||||
Layout.row: 1
|
||||
Layout.column: 1
|
||||
Layout.leftMargin: -40
|
||||
ButtonGroup.group: onOff
|
||||
|
||||
colorScheme: hifi.colorSchemes.light
|
||||
fontSize: 17
|
||||
letterSpacing: 1.4
|
||||
checked: true
|
||||
|
||||
text: "ON"
|
||||
boxSize: 20
|
||||
}
|
||||
|
||||
HifiConstants {
|
||||
id: hifi
|
||||
}
|
||||
|
||||
HifiControlsUit.RadioButton {
|
||||
id: collisionsDisabledRadioButton
|
||||
|
||||
HifiControlsUit.CheckBox {
|
||||
id: otherAvatarsCollisionsEnabledCheckBox;
|
||||
boxSize: 20;
|
||||
Layout.row: 1
|
||||
Layout.column: 2
|
||||
Layout.rightMargin: 20
|
||||
|
||||
ButtonGroup.group: onOff
|
||||
Layout.leftMargin: 60
|
||||
colorScheme: hifi.colorSchemes.light
|
||||
fontSize: 17
|
||||
letterSpacing: 1.4
|
||||
}
|
||||
|
||||
text: "OFF"
|
||||
boxSize: 20
|
||||
// TextStyle9
|
||||
RalewaySemiBold {
|
||||
size: 17;
|
||||
Layout.row: 2
|
||||
Layout.column: 0
|
||||
text: "Avatar collides with environment"
|
||||
}
|
||||
|
||||
HifiControlsUit.CheckBox {
|
||||
id: environmentCollisionsEnabledCheckBox;
|
||||
boxSize: 20;
|
||||
Layout.row: 2
|
||||
Layout.column: 2
|
||||
Layout.leftMargin: 60
|
||||
colorScheme: hifi.colorSchemes.light
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -176,6 +176,7 @@ Item {
|
|||
Item {
|
||||
property alias buttonGlyphText: buttonGlyph.text;
|
||||
property alias buttonText: buttonText.text;
|
||||
property alias glyphSize: buttonGlyph.size;
|
||||
property string buttonColor: hifi.colors.black;
|
||||
property string buttonColor_hover: hifi.colors.blueHighlight;
|
||||
property alias enabled: buttonMouseArea.enabled;
|
||||
|
@ -186,7 +187,8 @@ Item {
|
|||
anchors.top: parent.top;
|
||||
anchors.topMargin: 4;
|
||||
anchors.horizontalCenter: parent.horizontalCenter;
|
||||
anchors.bottom: parent.verticalCenter;
|
||||
anchors.bottom: buttonText.visible ? parent.verticalCenter : parent.bottom;
|
||||
anchors.bottomMargin: buttonText.visible ? 0 : 4;
|
||||
width: parent.width;
|
||||
size: 40;
|
||||
horizontalAlignment: Text.AlignHCenter;
|
||||
|
@ -196,6 +198,7 @@ Item {
|
|||
|
||||
RalewayRegular {
|
||||
id: buttonText;
|
||||
visible: text !== "";
|
||||
anchors.top: parent.verticalCenter;
|
||||
anchors.topMargin: 4;
|
||||
anchors.bottom: parent.bottom;
|
||||
|
@ -300,7 +303,7 @@ Item {
|
|||
anchors.right: certificateButton.left;
|
||||
anchors.top: parent.top;
|
||||
anchors.bottom: parent.bottom;
|
||||
width: 78;
|
||||
width: 72;
|
||||
|
||||
onLoaded: {
|
||||
item.buttonGlyphText = hifi.glyphs.uninstall;
|
||||
|
@ -310,6 +313,10 @@ Item {
|
|||
Commerce.uninstallApp(root.itemHref);
|
||||
}
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
trashButton.updateProperties();
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
|
@ -319,7 +326,7 @@ Item {
|
|||
anchors.right: uninstallButton.visible ? uninstallButton.left : certificateButton.left;
|
||||
anchors.top: parent.top;
|
||||
anchors.bottom: parent.bottom;
|
||||
width: 84;
|
||||
width: 78;
|
||||
|
||||
onLoaded: {
|
||||
item.buttonGlyphText = hifi.glyphs.update;
|
||||
|
@ -339,6 +346,45 @@ Item {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
onVisibleChanged: {
|
||||
trashButton.updateProperties();
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: trashButton;
|
||||
visible: root.itemEdition > 0;
|
||||
sourceComponent: contextCardButton;
|
||||
anchors.right: updateButton.visible ? updateButton.left : (uninstallButton.visible ? uninstallButton.left : certificateButton.left);
|
||||
anchors.top: parent.top;
|
||||
anchors.bottom: parent.bottom;
|
||||
width: (updateButton.visible && uninstallButton.visible) ? 15 : 78;
|
||||
|
||||
onLoaded: {
|
||||
item.buttonGlyphText = hifi.glyphs.trash;
|
||||
updateProperties();
|
||||
item.buttonClicked = function() {
|
||||
sendToPurchases({method: 'showTrashLightbox',
|
||||
isInstalled: root.isInstalled,
|
||||
itemHref: root.itemHref,
|
||||
itemName: root.itemName,
|
||||
certID: root.certificateId,
|
||||
itemType: root.itemType,
|
||||
wornEntityID: root.wornEntityID
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updateProperties() {
|
||||
if (updateButton.visible && uninstallButton.visible) {
|
||||
item.buttonText = "";
|
||||
item.glyphSize = 20;
|
||||
} else {
|
||||
item.buttonText = "Send to Trash";
|
||||
item.glyphSize = 30;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -651,6 +651,42 @@ Rectangle {
|
|||
lightboxPopup.visible = false;
|
||||
};
|
||||
lightboxPopup.visible = true;
|
||||
} else if (msg.method === "showTrashLightbox") {
|
||||
lightboxPopup.titleText = "Send \"" + msg.itemName + "\" to Trash";
|
||||
lightboxPopup.bodyText = "Sending this item to the Trash means you will no longer own this item " +
|
||||
"and it will be inaccessible to you from Purchases.\n\nThis action cannot be undone.";
|
||||
lightboxPopup.button1text = "CANCEL";
|
||||
lightboxPopup.button1method = function() {
|
||||
lightboxPopup.visible = false;
|
||||
}
|
||||
lightboxPopup.button2text = "CONFIRM";
|
||||
lightboxPopup.button2method = function() {
|
||||
if (msg.isInstalled) {
|
||||
Commerce.uninstallApp(msg.itemHref);
|
||||
}
|
||||
|
||||
if (MyAvatar.skeletonModelURL === msg.itemHref) {
|
||||
MyAvatar.useFullAvatarURL('');
|
||||
}
|
||||
|
||||
if (msg.itemType === "wearable" && msg.wornEntityID !== '') {
|
||||
Entities.deleteEntity(msg.wornEntityID);
|
||||
purchasesModel.setProperty(index, 'wornEntityID', '');
|
||||
}
|
||||
|
||||
Commerce.transferAssetToUsername("trashbot", msg.certID, 1, "Sent " + msg.itemName + " to trash.");
|
||||
|
||||
lightboxPopup.titleText = '"' + msg.itemName + '" Sent to Trash';
|
||||
lightboxPopup.button1text = "OK";
|
||||
lightboxPopup.button1method = function() {
|
||||
root.purchasesReceived = false;
|
||||
lightboxPopup.visible = false;
|
||||
getPurchases();
|
||||
}
|
||||
lightboxPopup.button2text = "";
|
||||
lightboxPopup.bodyText = "";
|
||||
};
|
||||
lightboxPopup.visible = true;
|
||||
} else if (msg.method === "showChangeAvatarLightbox") {
|
||||
lightboxPopup.titleText = "Change Avatar";
|
||||
lightboxPopup.bodyText = "This will change your current avatar to " + msg.itemName + " while retaining your wearables.";
|
||||
|
|
|
@ -189,15 +189,17 @@ Rectangle {
|
|||
var grabbable = MenuInterface.isOptionChecked("Create Entities As Grabbable (except Zones, Particles, and Lights)");
|
||||
|
||||
if (defaultURL.endsWith(".jpg") || defaultURL.endsWith(".png")) {
|
||||
var name = assetProxyModel.data(treeView.selection.currentIndex);
|
||||
var modelURL = "https://hifi-content.s3.amazonaws.com/DomainContent/production/default-image-model.fbx";
|
||||
var textures = JSON.stringify({ "tex.picture": defaultURL});
|
||||
var shapeType = "box";
|
||||
var dynamic = false;
|
||||
var collisionless = true;
|
||||
var position = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation)));
|
||||
var gravity = Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0);
|
||||
Entities.addModelEntity(name, modelURL, textures, shapeType, dynamic, collisionless, grabbable, position, gravity);
|
||||
Entities.addEntity({
|
||||
type: "Image",
|
||||
name: assetProxyModel.data(treeView.selection.currentIndex),
|
||||
imageURL: defaultURL,
|
||||
keepAspectRatio: false,
|
||||
dynamic: false,
|
||||
collisionless: true,
|
||||
grabbable: grabbable,
|
||||
position: Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getForward(MyAvatar.orientation))),
|
||||
gravity: Vec3.multiply(Vec3.fromPolar(Math.PI / 2, 0), 0)
|
||||
});
|
||||
} else {
|
||||
var SHAPE_TYPE_NONE = 0;
|
||||
var SHAPE_TYPE_SIMPLE_HULL = 1;
|
||||
|
|
15
interface/resources/qml/hifi/tablet/AvatarPackager.qml
Normal file
15
interface/resources/qml/hifi/tablet/AvatarPackager.qml
Normal file
|
@ -0,0 +1,15 @@
|
|||
import QtQuick 2.0
|
||||
import "../avatarPackager" 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
width: 480
|
||||
height: 706
|
||||
|
||||
AvatarPackagerApp {
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
desktopObject: tabletRoot
|
||||
}
|
||||
}
|
|
@ -869,7 +869,7 @@ Flickable {
|
|||
id: outOfRangeDataStrategyComboBox
|
||||
|
||||
height: 25
|
||||
width: 100
|
||||
width: 150
|
||||
|
||||
editable: true
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
|
|
|
@ -15,6 +15,7 @@ Item {
|
|||
property var openBrowser: null;
|
||||
property string subMenu: ""
|
||||
signal showDesktop();
|
||||
signal screenChanged(var type, var url);
|
||||
property bool shown: true
|
||||
property int currentApp: -1;
|
||||
property alias tabletApps: tabletApps
|
||||
|
@ -113,6 +114,8 @@ Item {
|
|||
if (loader.item.hasOwnProperty("gotoPreviousApp")) {
|
||||
loader.item.gotoPreviousApp = true;
|
||||
}
|
||||
|
||||
screenChanged("Web", url)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -266,6 +269,24 @@ Item {
|
|||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
|
||||
var type = "Unknown";
|
||||
if (newSource === "") {
|
||||
type = "Closed";
|
||||
} else if (newSource === "hifi/tablet/TabletMenu.qml") {
|
||||
type = "Menu";
|
||||
} else if (newSource === "hifi/tablet/TabletHome.qml") {
|
||||
type = "Home";
|
||||
} else if (newSource === "hifi/tablet/TabletWebView.qml") {
|
||||
// Handled in `callback()`
|
||||
return;
|
||||
} else if (newSource.toLowerCase().indexOf(".qml") > -1) {
|
||||
type = "QML";
|
||||
} else {
|
||||
console.log("newSource is of unknown type!");
|
||||
}
|
||||
|
||||
screenChanged(type, newSource);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ Windows.ScrollingWindow {
|
|||
id: tabletRoot
|
||||
objectName: "tabletRoot"
|
||||
property string username: "Unknown user"
|
||||
signal screenChanged(var type, var url);
|
||||
|
||||
property var rootMenu;
|
||||
property string subMenu: ""
|
||||
|
@ -69,6 +70,8 @@ Windows.ScrollingWindow {
|
|||
if (loader.item.hasOwnProperty("closeButtonVisible")) {
|
||||
loader.item.closeButtonVisible = false;
|
||||
}
|
||||
|
||||
screenChanged("Web", url);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -179,7 +182,25 @@ Windows.ScrollingWindow {
|
|||
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
|
||||
var type = "Unknown";
|
||||
if (newSource === "") {
|
||||
type = "Closed";
|
||||
} else if (newSource === "hifi/tablet/TabletMenu.qml") {
|
||||
type = "Menu";
|
||||
} else if (newSource === "hifi/tablet/TabletHome.qml") {
|
||||
type = "Home";
|
||||
} else if (newSource === "hifi/tablet/TabletWebView.qml") {
|
||||
// Handled in `callback()`
|
||||
return;
|
||||
} else if (newSource.toLowerCase().indexOf(".qml") > -1) {
|
||||
type = "QML";
|
||||
} else {
|
||||
console.log("newSource is of unknown type!");
|
||||
}
|
||||
|
||||
screenChanged(type, newSource);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -158,6 +158,7 @@
|
|||
#include "audio/AudioScope.h"
|
||||
#include "avatar/AvatarManager.h"
|
||||
#include "avatar/MyHead.h"
|
||||
#include "avatar/AvatarPackager.h"
|
||||
#include "CrashRecoveryHandler.h"
|
||||
#include "CrashHandler.h"
|
||||
#include "devices/DdeFaceTracker.h"
|
||||
|
@ -170,6 +171,7 @@
|
|||
#include "scripting/Audio.h"
|
||||
#include "networking/CloseEventSender.h"
|
||||
#include "scripting/TestScriptingInterface.h"
|
||||
#include "scripting/PlatformInfoScriptingInterface.h"
|
||||
#include "scripting/AssetMappingsScriptingInterface.h"
|
||||
#include "scripting/ClipboardScriptingInterface.h"
|
||||
#include "scripting/DesktopScriptingInterface.h"
|
||||
|
@ -208,6 +210,8 @@
|
|||
#include "InterfaceParentFinder.h"
|
||||
#include "ui/OctreeStatsProvider.h"
|
||||
|
||||
#include "avatar/GrabManager.h"
|
||||
|
||||
#include <GPUIdent.h>
|
||||
#include <gl/GLHelpers.h>
|
||||
#include <src/scripting/GooglePolyScriptingInterface.h>
|
||||
|
@ -859,7 +863,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
|
|||
DependencyManager::set<LODManager>();
|
||||
DependencyManager::set<StandAloneJSConsole>();
|
||||
DependencyManager::set<DialogsManager>();
|
||||
DependencyManager::set<BandwidthRecorder>();
|
||||
DependencyManager::set<ResourceCacheSharedItems>();
|
||||
DependencyManager::set<DesktopScriptingInterface>();
|
||||
DependencyManager::set<EntityScriptingInterface>(true);
|
||||
|
@ -919,6 +922,8 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
|
|||
DependencyManager::set<ResourceRequestObserver>();
|
||||
DependencyManager::set<Keyboard>();
|
||||
DependencyManager::set<KeyboardScriptingInterface>();
|
||||
DependencyManager::set<GrabManager>();
|
||||
DependencyManager::set<AvatarPackager>();
|
||||
|
||||
return previousSessionCrashed;
|
||||
}
|
||||
|
@ -1078,6 +1083,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->startThread();
|
||||
nodeList->setFlagTimeForConnectionStep(true);
|
||||
|
||||
// move the AddressManager to the NodeList thread so that domain resets due to domain changes always occur
|
||||
// before we tell MyAvatar to go to a new location in the new domain
|
||||
|
@ -1573,13 +1579,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
|
||||
connect(this, SIGNAL(aboutToQuit()), this, SLOT(onAboutToQuit()));
|
||||
|
||||
// hook up bandwidth estimator
|
||||
QSharedPointer<BandwidthRecorder> bandwidthRecorder = DependencyManager::get<BandwidthRecorder>();
|
||||
connect(nodeList.data(), &LimitedNodeList::dataSent,
|
||||
bandwidthRecorder.data(), &BandwidthRecorder::updateOutboundData);
|
||||
connect(nodeList.data(), &LimitedNodeList::dataReceived,
|
||||
bandwidthRecorder.data(), &BandwidthRecorder::updateInboundData);
|
||||
|
||||
// FIXME -- I'm a little concerned about this.
|
||||
connect(myAvatar->getSkeletonModel().get(), &SkeletonModel::skeletonLoaded,
|
||||
this, &Application::checkSkeleton, Qt::QueuedConnection);
|
||||
|
@ -2045,15 +2044,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
properties["deadlock_watchdog_maxElapsed"] = (int)DeadlockWatchdogThread::_maxElapsed;
|
||||
properties["deadlock_watchdog_maxElapsedAverage"] = (int)DeadlockWatchdogThread::_maxElapsedAverage;
|
||||
|
||||
auto bandwidthRecorder = DependencyManager::get<BandwidthRecorder>();
|
||||
properties["packet_rate_in"] = bandwidthRecorder->getCachedTotalAverageInputPacketsPerSecond();
|
||||
properties["packet_rate_out"] = bandwidthRecorder->getCachedTotalAverageOutputPacketsPerSecond();
|
||||
properties["kbps_in"] = bandwidthRecorder->getCachedTotalAverageInputKilobitsPerSecond();
|
||||
properties["kbps_out"] = bandwidthRecorder->getCachedTotalAverageOutputKilobitsPerSecond();
|
||||
|
||||
properties["atp_in_kbps"] = bandwidthRecorder->getAverageInputKilobitsPerSecond(NodeType::AssetServer);
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
properties["packet_rate_in"] = nodeList->getInboundPPS();
|
||||
properties["packet_rate_out"] = nodeList->getOutboundPPS();
|
||||
properties["kbps_in"] = nodeList->getInboundKbps();
|
||||
properties["kbps_out"] = nodeList->getOutboundKbps();
|
||||
|
||||
SharedNodePointer entityServerNode = nodeList->soloNodeOfType(NodeType::EntityServer);
|
||||
SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer);
|
||||
SharedNodePointer avatarMixerNode = nodeList->soloNodeOfType(NodeType::AvatarMixer);
|
||||
|
@ -2064,6 +2060,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
properties["avatar_ping"] = avatarMixerNode ? avatarMixerNode->getPingMs() : -1;
|
||||
properties["asset_ping"] = assetServerNode ? assetServerNode->getPingMs() : -1;
|
||||
properties["messages_ping"] = messagesMixerNode ? messagesMixerNode->getPingMs() : -1;
|
||||
properties["atp_in_kbps"] = messagesMixerNode ? assetServerNode->getInboundKbps() : 0.0f;
|
||||
|
||||
auto loadingRequests = ResourceCache::getLoadingRequests();
|
||||
|
||||
|
@ -2289,7 +2286,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
|
||||
// Setup the mouse ray pick and related operators
|
||||
{
|
||||
auto mouseRayPick = std::make_shared<RayPick>(Vectors::ZERO, Vectors::UP, PickFilter(PickScriptingInterface::PICK_ENTITIES() | PickScriptingInterface::PICK_INCLUDE_NONCOLLIDABLE()), 0.0f, true);
|
||||
auto mouseRayPick = std::make_shared<RayPick>(Vectors::ZERO, Vectors::UP, PickFilter(PickScriptingInterface::PICK_ENTITIES()), 0.0f, true);
|
||||
mouseRayPick->parentTransform = std::make_shared<MouseTransformNode>();
|
||||
mouseRayPick->setJointState(PickQuery::JOINT_STATE_MOUSE);
|
||||
auto mouseRayPickID = DependencyManager::get<PickManager>()->addPick(PickQuery::Ray, mouseRayPick);
|
||||
|
@ -2315,6 +2312,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
DependencyManager::get<PickManager>()->setPrecisionPicking(rayPickID, value);
|
||||
});
|
||||
|
||||
EntityTreeRenderer::setGetAvatarUpOperator([] {
|
||||
return DependencyManager::get<AvatarManager>()->getMyAvatar()->getWorldOrientation() * Vectors::UP;
|
||||
});
|
||||
|
||||
// Preload Tablet sounds
|
||||
DependencyManager::get<TabletScriptingInterface>()->preloadSounds();
|
||||
DependencyManager::get<Keyboard>()->createKeyboard();
|
||||
|
@ -2460,6 +2461,10 @@ void Application::updateHeartbeat() const {
|
|||
void Application::onAboutToQuit() {
|
||||
emit beforeAboutToQuit();
|
||||
|
||||
if (getLoginDialogPoppedUp() && _firstRun.get()) {
|
||||
_firstRun.set(false);
|
||||
}
|
||||
|
||||
foreach(auto inputPlugin, PluginManager::getInstance()->getInputPlugins()) {
|
||||
if (inputPlugin->isActive()) {
|
||||
inputPlugin->deactivate();
|
||||
|
@ -2614,6 +2619,7 @@ void Application::cleanupBeforeQuit() {
|
|||
DependencyManager::destroy<PickManager>();
|
||||
DependencyManager::destroy<KeyboardScriptingInterface>();
|
||||
DependencyManager::destroy<Keyboard>();
|
||||
DependencyManager::destroy<AvatarPackager>();
|
||||
|
||||
qCDebug(interfaceapp) << "Application::cleanupBeforeQuit() complete";
|
||||
}
|
||||
|
@ -4903,7 +4909,7 @@ void Application::calibrateEyeTracker5Points() {
|
|||
#endif
|
||||
|
||||
bool Application::exportEntities(const QString& filename,
|
||||
const QVector<EntityItemID>& entityIDs,
|
||||
const QVector<QUuid>& entityIDs,
|
||||
const glm::vec3* givenOffset) {
|
||||
QHash<EntityItemID, EntityItemPointer> entities;
|
||||
|
||||
|
@ -4978,16 +4984,12 @@ bool Application::exportEntities(const QString& filename, float x, float y, floa
|
|||
glm::vec3 minCorner = center - vec3(scale);
|
||||
float cubeSize = scale * 2;
|
||||
AACube boundingCube(minCorner, cubeSize);
|
||||
QVector<EntityItemPointer> entities;
|
||||
QVector<EntityItemID> ids;
|
||||
QVector<QUuid> entities;
|
||||
auto entityTree = getEntities()->getTree();
|
||||
entityTree->withReadLock([&] {
|
||||
entityTree->findEntities(boundingCube, entities);
|
||||
foreach(EntityItemPointer entity, entities) {
|
||||
ids << entity->getEntityItemID();
|
||||
}
|
||||
entityTree->evalEntitiesInCube(boundingCube, PickFilter(), entities);
|
||||
});
|
||||
return exportEntities(filename, ids, ¢er);
|
||||
return exportEntities(filename, entities, ¢er);
|
||||
}
|
||||
|
||||
void Application::loadSettings() {
|
||||
|
@ -5176,7 +5178,13 @@ void Application::pauseUntilLoginDetermined() {
|
|||
return;
|
||||
}
|
||||
|
||||
getMyAvatar()->setEnableMeshVisible(false);
|
||||
auto myAvatar = getMyAvatar();
|
||||
_previousAvatarTargetScale = myAvatar->getTargetScale();
|
||||
_previousAvatarSkeletonModel = myAvatar->getSkeletonModelURL().toString();
|
||||
myAvatar->setTargetScale(1.0f);
|
||||
myAvatar->setSkeletonModelURLFromScript(myAvatar->defaultFullAvatarModelUrl().toString());
|
||||
myAvatar->setEnableMeshVisible(false);
|
||||
|
||||
_controllerScriptingInterface->disableMapping(STANDARD_TO_ACTION_MAPPING_NAME);
|
||||
|
||||
{
|
||||
|
@ -5231,7 +5239,12 @@ void Application::resumeAfterLoginDialogActionTaken() {
|
|||
userInputMapper->unloadMapping(NO_MOVEMENT_MAPPING_JSON);
|
||||
_controllerScriptingInterface->disableMapping(NO_MOVEMENT_MAPPING_NAME);
|
||||
}
|
||||
getMyAvatar()->setEnableMeshVisible(true);
|
||||
|
||||
auto myAvatar = getMyAvatar();
|
||||
myAvatar->setTargetScale(_previousAvatarTargetScale);
|
||||
myAvatar->setSkeletonModelURLFromScript(_previousAvatarSkeletonModel);
|
||||
myAvatar->setEnableMeshVisible(true);
|
||||
|
||||
_controllerScriptingInterface->enableMapping(STANDARD_TO_ACTION_MAPPING_NAME);
|
||||
|
||||
const auto& nodeList = DependencyManager::get<NodeList>();
|
||||
|
@ -5241,7 +5254,8 @@ void Application::resumeAfterLoginDialogActionTaken() {
|
|||
// this will force the model the look at the correct directory (weird order of operations issue)
|
||||
scriptEngines->reloadLocalFiles();
|
||||
|
||||
if (!_defaultScriptsLocation.exists()) {
|
||||
// if the --scripts command-line argument was used.
|
||||
if (!_defaultScriptsLocation.exists() && (arguments().indexOf(QString("--").append(SCRIPTS_SWITCH))) != -1) {
|
||||
scriptEngines->loadDefaultScripts();
|
||||
scriptEngines->defaultScriptsLocationOverridden(true);
|
||||
} else {
|
||||
|
@ -5249,39 +5263,25 @@ void Application::resumeAfterLoginDialogActionTaken() {
|
|||
}
|
||||
}
|
||||
|
||||
if (_firstRun.get()) {
|
||||
// not first run anymore since action was taken.
|
||||
_firstRun.set(false);
|
||||
}
|
||||
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
auto addressManager = DependencyManager::get<AddressManager>();
|
||||
|
||||
// restart domain handler.
|
||||
nodeList->getDomainHandler().resetting();
|
||||
|
||||
if (!accountManager->isLoggedIn()) {
|
||||
QVariant testProperty = property(hifi::properties::TEST);
|
||||
if (testProperty.isValid()) {
|
||||
const auto testScript = property(hifi::properties::TEST).toUrl();
|
||||
// Set last parameter to exit interface when the test script finishes, if so requested
|
||||
DependencyManager::get<ScriptEngines>()->loadScript(testScript, false, false, false, false, quitWhenFinished);
|
||||
// This is done so we don't get a "connection time-out" message when we haven't passed in a URL.
|
||||
if (arguments().contains("--url")) {
|
||||
auto reply = SandboxUtils::getStatus();
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply] { handleSandboxStatus(reply); });
|
||||
} else {
|
||||
addressManager->goToEntry();
|
||||
}
|
||||
} else {
|
||||
QVariant testProperty = property(hifi::properties::TEST);
|
||||
if (testProperty.isValid()) {
|
||||
const auto testScript = property(hifi::properties::TEST).toUrl();
|
||||
// Set last parameter to exit interface when the test script finishes, if so requested
|
||||
DependencyManager::get<ScriptEngines>()->loadScript(testScript, false, false, false, false, quitWhenFinished);
|
||||
// This is done so we don't get a "connection time-out" message when we haven't passed in a URL.
|
||||
if (arguments().contains("--url")) {
|
||||
auto reply = SandboxUtils::getStatus();
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply] { handleSandboxStatus(reply); });
|
||||
}
|
||||
} else {
|
||||
auto reply = SandboxUtils::getStatus();
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply] { handleSandboxStatus(reply); });
|
||||
}
|
||||
auto reply = SandboxUtils::getStatus();
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply] { handleSandboxStatus(reply); });
|
||||
}
|
||||
|
||||
auto menu = Menu::getInstance();
|
||||
|
@ -6082,6 +6082,9 @@ void Application::update(float deltaTime) {
|
|||
updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process...
|
||||
updateDialogs(deltaTime); // update various stats dialogs if present
|
||||
|
||||
auto grabManager = DependencyManager::get<GrabManager>();
|
||||
grabManager->simulateGrabs();
|
||||
|
||||
QSharedPointer<AvatarManager> avatarManager = DependencyManager::get<AvatarManager>();
|
||||
|
||||
{
|
||||
|
@ -6685,6 +6688,7 @@ void Application::resetSensors(bool andReload) {
|
|||
DependencyManager::get<DdeFaceTracker>()->reset();
|
||||
DependencyManager::get<EyeTracker>()->reset();
|
||||
_overlayConductor.centerUI();
|
||||
getActiveDisplayPlugin()->resetSensors();
|
||||
getMyAvatar()->reset(true, andReload);
|
||||
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "reset", Qt::QueuedConnection);
|
||||
}
|
||||
|
@ -6988,6 +6992,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
|
|||
scriptEngine->registerGlobalObject("Test", TestScriptingInterface::getInstance());
|
||||
}
|
||||
|
||||
scriptEngine->registerGlobalObject("PlatformInfo", PlatformInfoScriptingInterface::getInstance());
|
||||
scriptEngine->registerGlobalObject("Rates", new RatesScriptingInterface(this));
|
||||
|
||||
// hook our avatar and avatar hash map object into this script engine
|
||||
|
@ -7687,16 +7692,13 @@ void Application::addAssetToWorldSetMapping(QString filePath, QString mapping, Q
|
|||
|
||||
void Application::addAssetToWorldAddEntity(QString filePath, QString mapping) {
|
||||
EntityItemProperties properties;
|
||||
properties.setType(EntityTypes::Model);
|
||||
properties.setName(mapping.right(mapping.length() - 1));
|
||||
if (filePath.toLower().endsWith(PNG_EXTENSION) || filePath.toLower().endsWith(JPG_EXTENSION)) {
|
||||
QJsonObject textures {
|
||||
{"tex.picture", QString("atp:" + mapping) }
|
||||
};
|
||||
properties.setModelURL("https://hifi-content.s3.amazonaws.com/DomainContent/production/default-image-model.fbx");
|
||||
properties.setTextures(QJsonDocument(textures).toJson(QJsonDocument::Compact));
|
||||
properties.setShapeType(SHAPE_TYPE_BOX);
|
||||
properties.setType(EntityTypes::Image);
|
||||
properties.setImageURL(QString("atp:" + mapping));
|
||||
properties.setKeepAspectRatio(false);
|
||||
} else {
|
||||
properties.setType(EntityTypes::Model);
|
||||
properties.setModelURL("atp:" + mapping);
|
||||
properties.setShapeType(SHAPE_TYPE_SIMPLE_COMPOUND);
|
||||
}
|
||||
|
@ -8708,6 +8710,14 @@ void Application::updateLoginDialogOverlayPosition() {
|
|||
}
|
||||
}
|
||||
|
||||
bool Application::hasRiftControllers() {
|
||||
return PluginUtils::isOculusTouchControllerAvailable();
|
||||
}
|
||||
|
||||
bool Application::hasViveControllers() {
|
||||
return PluginUtils::isViveControllerAvailable();
|
||||
}
|
||||
|
||||
void Application::onDismissedLoginDialog() {
|
||||
_loginDialogPoppedUp = false;
|
||||
loginDialogPoppedUp.set(false);
|
||||
|
@ -8926,6 +8936,10 @@ void Application::copyToClipboard(const QString& text) {
|
|||
QApplication::clipboard()->setText(text);
|
||||
}
|
||||
|
||||
QString Application::getGraphicsCardType() {
|
||||
return GPUIdent::getInstance()->getName();
|
||||
}
|
||||
|
||||
#if defined(Q_OS_ANDROID)
|
||||
void Application::beforeEnterBackground() {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
|
|
@ -120,7 +120,7 @@ class Application : public QApplication,
|
|||
public:
|
||||
// virtual functions required for PluginContainer
|
||||
virtual ui::Menu* getPrimaryMenu() override;
|
||||
virtual void requestReset() override { resetSensors(true); }
|
||||
virtual void requestReset() override { resetSensors(false); }
|
||||
virtual void showDisplayPluginsTools(bool show) override;
|
||||
virtual GLWidget* getPrimaryWidget() override;
|
||||
virtual MainWindow* getPrimaryWindow() override;
|
||||
|
@ -326,6 +326,10 @@ public:
|
|||
void createLoginDialogOverlay();
|
||||
void updateLoginDialogOverlayPosition();
|
||||
|
||||
// Check if a headset is connected
|
||||
bool hasRiftControllers();
|
||||
bool hasViveControllers();
|
||||
|
||||
#if defined(Q_OS_ANDROID)
|
||||
void beforeEnterBackground();
|
||||
void enterBackground();
|
||||
|
@ -351,7 +355,7 @@ signals:
|
|||
|
||||
public slots:
|
||||
QVector<EntityItemID> pasteEntities(float x, float y, float z);
|
||||
bool exportEntities(const QString& filename, const QVector<EntityItemID>& entityIDs, const glm::vec3* givenOffset = nullptr);
|
||||
bool exportEntities(const QString& filename, const QVector<QUuid>& entityIDs, const glm::vec3* givenOffset = nullptr);
|
||||
bool exportEntities(const QString& filename, float x, float y, float z, float scale);
|
||||
bool importEntities(const QString& url, const bool isObservable = true, const qint64 callerId = -1);
|
||||
void updateThreadPoolCount() const;
|
||||
|
@ -459,6 +463,8 @@ public slots:
|
|||
|
||||
void changeViewAsNeeded(float boomLength);
|
||||
|
||||
QString getGraphicsCardType();
|
||||
|
||||
private slots:
|
||||
void onDesktopRootItemCreated(QQuickItem* qmlContext);
|
||||
void onDesktopRootContextCreated(QQmlContext* qmlContext);
|
||||
|
@ -691,6 +697,8 @@ private:
|
|||
|
||||
bool _loginDialogPoppedUp = false;
|
||||
bool _developerMenuVisible{ false };
|
||||
QString _previousAvatarSkeletonModel;
|
||||
float _previousAvatarTargetScale;
|
||||
CameraMode _previousCameraMode;
|
||||
OverlayID _loginDialogOverlayID;
|
||||
LoginStateManager _loginStateManager;
|
||||
|
@ -785,6 +793,5 @@ private:
|
|||
|
||||
bool _showTrackedObjects { false };
|
||||
bool _prevShowTrackedObjects { false };
|
||||
|
||||
};
|
||||
#endif // hifi_Application_h
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include "assets/ATPAssetMigrator.h"
|
||||
#include "audio/AudioScope.h"
|
||||
#include "avatar/AvatarManager.h"
|
||||
#include "avatar/AvatarPackager.h"
|
||||
#include "AvatarBookmarks.h"
|
||||
#include "devices/DdeFaceTracker.h"
|
||||
#include "MainWindow.h"
|
||||
|
@ -48,6 +49,7 @@
|
|||
#include "DeferredLightingEffect.h"
|
||||
#include "PickManager.h"
|
||||
|
||||
#include "LightingModel.h"
|
||||
#include "AmbientOcclusionEffect.h"
|
||||
#include "RenderShadowTask.h"
|
||||
#include "AntialiasingEffect.h"
|
||||
|
@ -143,9 +145,13 @@ Menu::Menu() {
|
|||
assetServerAction->setEnabled(nodeList->getThisNodeCanWriteAssets());
|
||||
}
|
||||
|
||||
// Edit > Package Avatar as .fst...
|
||||
addActionToQMenuAndActionHash(editMenu, MenuOption::PackageModel, 0,
|
||||
qApp, SLOT(packageModel()));
|
||||
// Edit > Avatar Packager
|
||||
#ifndef Q_OS_ANDROID
|
||||
action = addActionToQMenuAndActionHash(editMenu, MenuOption::AvatarPackager);
|
||||
connect(action, &QAction::triggered, [] {
|
||||
DependencyManager::get<AvatarPackager>()->open();
|
||||
});
|
||||
#endif
|
||||
|
||||
// Edit > Reload All Content
|
||||
addActionToQMenuAndActionHash(editMenu, MenuOption::ReloadContent, 0, qApp, SLOT(reloadResourceCaches()));
|
||||
|
@ -393,13 +399,9 @@ Menu::Menu() {
|
|||
connect(action, &QAction::triggered, [action] {
|
||||
auto renderConfig = qApp->getRenderEngine()->getConfiguration();
|
||||
if (renderConfig) {
|
||||
auto mainViewShadowTaskConfig = renderConfig->getConfig<RenderShadowTask>("RenderMainView.RenderShadowTask");
|
||||
if (mainViewShadowTaskConfig) {
|
||||
if (action->isChecked()) {
|
||||
mainViewShadowTaskConfig->setPreset("Enabled");
|
||||
} else {
|
||||
mainViewShadowTaskConfig->setPreset("None");
|
||||
}
|
||||
auto lightingModelConfig = renderConfig->getConfig<MakeLightingModel>("RenderMainView.LightingModel");
|
||||
if (lightingModelConfig) {
|
||||
lightingModelConfig->setShadow(action->isChecked());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -408,15 +410,11 @@ Menu::Menu() {
|
|||
connect(action, &QAction::triggered, [action] {
|
||||
auto renderConfig = qApp->getRenderEngine()->getConfiguration();
|
||||
if (renderConfig) {
|
||||
auto mainViewAmbientOcclusionConfig = renderConfig->getConfig<AmbientOcclusionEffect>("RenderMainView.AmbientOcclusion");
|
||||
if (mainViewAmbientOcclusionConfig) {
|
||||
if (action->isChecked()) {
|
||||
mainViewAmbientOcclusionConfig->setPreset("Enabled");
|
||||
} else {
|
||||
mainViewAmbientOcclusionConfig->setPreset("None");
|
||||
}
|
||||
auto lightingModelConfig = renderConfig->getConfig<MakeLightingModel>("RenderMainView.LightingModel");
|
||||
if (lightingModelConfig) {
|
||||
lightingModelConfig->setAmbientOcclusion(action->isChecked());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::WorldAxes);
|
||||
|
@ -652,6 +650,8 @@ Menu::Menu() {
|
|||
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowTrackedObjects, 0, false, qApp, SLOT(setShowTrackedObjects(bool)));
|
||||
|
||||
addActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::PackageModel, 0, qApp, SLOT(packageModel()));
|
||||
|
||||
// Developer > Hands >>>
|
||||
MenuWrapper* handOptionsMenu = developerMenu->addMenu("Hands");
|
||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false,
|
||||
|
|
|
@ -46,6 +46,7 @@ namespace MenuOption {
|
|||
const QString AutoMuteAudio = "Auto Mute Microphone";
|
||||
const QString AvatarReceiveStats = "Show Receive Stats";
|
||||
const QString AvatarBookmarks = "Avatar Bookmarks";
|
||||
const QString AvatarPackager = "Avatar Packager";
|
||||
const QString Back = "Back";
|
||||
const QString BinaryEyelidControl = "Binary Eyelid Control";
|
||||
const QString BookmarkAvatar = "Bookmark Avatar";
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
#include <RenderDeferredTask.h>
|
||||
#include <RenderForwardTask.h>
|
||||
#include <RenderViewTask.h>
|
||||
|
||||
#include <glm/gtx/transform.hpp>
|
||||
#include <gpu/Context.h>
|
||||
|
@ -270,14 +271,8 @@ public:
|
|||
|
||||
void SecondaryCameraRenderTask::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, bool isDeferred) {
|
||||
const auto cachedArg = task.addJob<SecondaryCameraJob>("SecondaryCamera");
|
||||
const auto items = task.addJob<RenderFetchCullSortTask>("FetchCullSort", cullFunctor, render::ItemKey::TAG_BITS_1, render::ItemKey::TAG_BITS_1);
|
||||
assert(items.canCast<RenderFetchCullSortTask::Output>());
|
||||
if (isDeferred) {
|
||||
const render::Varying cascadeSceneBBoxes;
|
||||
const auto renderInput = RenderDeferredTask::Input(items, cascadeSceneBBoxes).asVarying();
|
||||
task.addJob<RenderDeferredTask>("RenderDeferredTask", renderInput, false);
|
||||
} else {
|
||||
task.addJob<RenderForwardTask>("Forward", items);
|
||||
}
|
||||
|
||||
task.addJob<RenderViewTask>("RenderSecondView", cullFunctor, isDeferred, render::ItemKey::TAG_BITS_1, render::ItemKey::TAG_BITS_1);
|
||||
|
||||
task.addJob<EndSecondaryCameraFrame>("EndSecondaryCamera", cachedArg);
|
||||
}
|
|
@ -58,6 +58,16 @@ AvatarActionHold::~AvatarActionHold() {
|
|||
#endif
|
||||
}
|
||||
|
||||
void AvatarActionHold::removeFromOwner() {
|
||||
auto avatarManager = DependencyManager::get<AvatarManager>();
|
||||
if (avatarManager) {
|
||||
auto myAvatar = avatarManager->getMyAvatar();
|
||||
if (myAvatar) {
|
||||
myAvatar->removeHoldAction(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AvatarActionHold::getAvatarRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation) {
|
||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
MyCharacterController* controller = myAvatar ? myAvatar->getCharacterController() : nullptr;
|
||||
|
@ -143,7 +153,7 @@ bool AvatarActionHold::getTarget(float deltaTimeStep, glm::quat& rotation, glm::
|
|||
ownerEntity->setTransitingWithAvatar(_isTransitingWithAvatar);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (holdingAvatar->isMyAvatar()) {
|
||||
std::shared_ptr<MyAvatar> myAvatar = avatarManager->getMyAvatar();
|
||||
|
||||
|
@ -226,7 +236,7 @@ bool AvatarActionHold::getTarget(float deltaTimeStep, glm::quat& rotation, glm::
|
|||
}
|
||||
|
||||
rotation = palmRotation * _relativeRotation;
|
||||
position = palmPosition + rotation * _relativePosition;
|
||||
position = palmPosition + palmRotation * _relativePosition;
|
||||
|
||||
// update linearVelocity based on offset via _relativePosition;
|
||||
linearVelocity = linearVelocity + glm::cross(angularVelocity, position - palmPosition);
|
||||
|
@ -369,8 +379,12 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
|
|||
hand = _hand;
|
||||
}
|
||||
|
||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
holderID = myAvatar->getSessionUUID();
|
||||
ok = true;
|
||||
holderID = EntityDynamicInterface::extractStringArgument("hold", arguments, "holderID", ok, false);
|
||||
if (!ok) {
|
||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
holderID = myAvatar->getSessionUUID();
|
||||
}
|
||||
|
||||
ok = true;
|
||||
kinematic = EntityDynamicInterface::extractBooleanArgument("hold", arguments, "kinematic", ok, false);
|
||||
|
@ -417,13 +431,13 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
|
|||
_kinematicSetVelocity = kinematicSetVelocity;
|
||||
_ignoreIK = ignoreIK;
|
||||
_active = true;
|
||||
|
||||
|
||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
|
||||
auto ownerEntity = _ownerEntity.lock();
|
||||
if (ownerEntity) {
|
||||
ownerEntity->setDynamicDataDirty(true);
|
||||
ownerEntity->setDynamicDataNeedsTransmit(true);
|
||||
ownerEntity->setDynamicDataNeedsTransmit(true);
|
||||
ownerEntity->setTransitingWithAvatar(myAvatar->getTransit()->isActive());
|
||||
}
|
||||
});
|
||||
|
|
|
@ -26,6 +26,8 @@ public:
|
|||
AvatarActionHold(const QUuid& id, EntityItemPointer ownerEntity);
|
||||
virtual ~AvatarActionHold();
|
||||
|
||||
virtual void removeFromOwner() override;
|
||||
|
||||
virtual bool updateArguments(QVariantMap arguments) override;
|
||||
virtual QVariantMap getArguments() override;
|
||||
|
||||
|
|
|
@ -28,7 +28,6 @@
|
|||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
|
||||
#include <shared/QtHelpers.h>
|
||||
#include <AvatarData.h>
|
||||
#include <PerfStat.h>
|
||||
|
@ -268,6 +267,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
|||
if (avatar->getSkeletonModel()->isLoaded()) {
|
||||
// remove the orb if it is there
|
||||
avatar->removeOrb();
|
||||
avatar->updateCollisionGroup(_myAvatar->getOtherAvatarsCollisionsEnabled());
|
||||
if (avatar->needsPhysicsUpdate()) {
|
||||
_avatarsToChangeInPhysics.insert(avatar);
|
||||
}
|
||||
|
@ -529,6 +529,7 @@ void AvatarManager::handleChangedMotionStates(const VectorOfMotionStates& motion
|
|||
}
|
||||
|
||||
void AvatarManager::handleCollisionEvents(const CollisionEvents& collisionEvents) {
|
||||
bool playedCollisionSound { false };
|
||||
for (Collision collision : collisionEvents) {
|
||||
// TODO: The plan is to handle MOTIONSTATE_TYPE_AVATAR, and then MOTIONSTATE_TYPE_MYAVATAR. As it is, other
|
||||
// people's avatars will have an id that doesn't match any entities, and one's own avatar will have
|
||||
|
@ -536,43 +537,47 @@ void AvatarManager::handleCollisionEvents(const CollisionEvents& collisionEvents
|
|||
// my avatar. (Other user machines will make a similar analysis and inject sound for their collisions.)
|
||||
if (collision.idA.isNull() || collision.idB.isNull()) {
|
||||
auto myAvatar = getMyAvatar();
|
||||
auto collisionSound = myAvatar->getCollisionSound();
|
||||
if (collisionSound) {
|
||||
const auto characterController = myAvatar->getCharacterController();
|
||||
const float avatarVelocityChange = (characterController ? glm::length(characterController->getVelocityChange()) : 0.0f);
|
||||
const float velocityChange = glm::length(collision.velocityChange) + avatarVelocityChange;
|
||||
const float MIN_AVATAR_COLLISION_ACCELERATION = 2.4f; // walking speed
|
||||
const bool isSound = (collision.type == CONTACT_EVENT_TYPE_START) && (velocityChange > MIN_AVATAR_COLLISION_ACCELERATION);
|
||||
myAvatar->collisionWithEntity(collision);
|
||||
|
||||
if (!isSound) {
|
||||
return; // No sense iterating for others. We only have one avatar.
|
||||
if (!playedCollisionSound) {
|
||||
playedCollisionSound = true;
|
||||
auto collisionSound = myAvatar->getCollisionSound();
|
||||
if (collisionSound) {
|
||||
const auto characterController = myAvatar->getCharacterController();
|
||||
const float avatarVelocityChange =
|
||||
(characterController ? glm::length(characterController->getVelocityChange()) : 0.0f);
|
||||
const float velocityChange = glm::length(collision.velocityChange) + avatarVelocityChange;
|
||||
const float MIN_AVATAR_COLLISION_ACCELERATION = 2.4f; // walking speed
|
||||
const bool isSound =
|
||||
(collision.type == CONTACT_EVENT_TYPE_START) && (velocityChange > MIN_AVATAR_COLLISION_ACCELERATION);
|
||||
|
||||
if (!isSound) {
|
||||
return; // No sense iterating for others. We only have one avatar.
|
||||
}
|
||||
// Your avatar sound is personal to you, so let's say the "mass" part of the kinetic energy is already accounted for.
|
||||
const float energy = velocityChange * velocityChange;
|
||||
const float COLLISION_ENERGY_AT_FULL_VOLUME = 10.0f;
|
||||
const float energyFactorOfFull = fmin(1.0f, energy / COLLISION_ENERGY_AT_FULL_VOLUME);
|
||||
|
||||
// For general entity collisionSoundURL, playSound supports changing the pitch for the sound based on the size of the object,
|
||||
// but most avatars are roughly the same size, so let's not be so fancy yet.
|
||||
const float AVATAR_STRETCH_FACTOR = 1.0f;
|
||||
|
||||
_collisionInjectors.remove_if(
|
||||
[](const AudioInjectorPointer& injector) { return !injector || injector->isFinished(); });
|
||||
|
||||
static const int MAX_INJECTOR_COUNT = 3;
|
||||
if (_collisionInjectors.size() < MAX_INJECTOR_COUNT) {
|
||||
AudioInjectorOptions options;
|
||||
options.stereo = collisionSound->isStereo();
|
||||
options.position = myAvatar->getWorldPosition();
|
||||
options.volume = energyFactorOfFull;
|
||||
options.pitch = 1.0f / AVATAR_STRETCH_FACTOR;
|
||||
|
||||
auto injector = AudioInjector::playSoundAndDelete(collisionSound, options);
|
||||
_collisionInjectors.emplace_back(injector);
|
||||
}
|
||||
}
|
||||
// Your avatar sound is personal to you, so let's say the "mass" part of the kinetic energy is already accounted for.
|
||||
const float energy = velocityChange * velocityChange;
|
||||
const float COLLISION_ENERGY_AT_FULL_VOLUME = 10.0f;
|
||||
const float energyFactorOfFull = fmin(1.0f, energy / COLLISION_ENERGY_AT_FULL_VOLUME);
|
||||
|
||||
// For general entity collisionSoundURL, playSound supports changing the pitch for the sound based on the size of the object,
|
||||
// but most avatars are roughly the same size, so let's not be so fancy yet.
|
||||
const float AVATAR_STRETCH_FACTOR = 1.0f;
|
||||
|
||||
_collisionInjectors.remove_if([](const AudioInjectorPointer& injector) {
|
||||
return !injector || injector->isFinished();
|
||||
});
|
||||
|
||||
static const int MAX_INJECTOR_COUNT = 3;
|
||||
if (_collisionInjectors.size() < MAX_INJECTOR_COUNT) {
|
||||
AudioInjectorOptions options;
|
||||
options.stereo = collisionSound->isStereo();
|
||||
options.position = myAvatar->getWorldPosition();
|
||||
options.volume = energyFactorOfFull;
|
||||
options.pitch = 1.0f / AVATAR_STRETCH_FACTOR;
|
||||
|
||||
auto injector = AudioInjector::playSoundAndDelete(collisionSound, options);
|
||||
_collisionInjectors.emplace_back(injector);
|
||||
}
|
||||
myAvatar->collisionWithEntity(collision);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -887,3 +892,13 @@ QVariantMap AvatarManager::getPalData(const QStringList& specificAvatarIdentifie
|
|||
doc.insert("data", palData);
|
||||
return doc.toVariantMap();
|
||||
}
|
||||
|
||||
void AvatarManager::accumulateGrabPositions(std::map<QUuid, GrabLocationAccumulator>& grabAccumulators) {
|
||||
auto avatarMap = getHashCopy();
|
||||
AvatarHash::iterator itr = avatarMap.begin();
|
||||
while (itr != avatarMap.end()) {
|
||||
const auto& avatar = std::static_pointer_cast<Avatar>(*itr);
|
||||
avatar->accumulateGrabPositions(grabAccumulators);
|
||||
itr++;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -198,6 +198,8 @@ public:
|
|||
void handleProcessedPhysicsTransaction(PhysicsEngine::Transaction& transaction);
|
||||
void removeDeadAvatarEntities(const SetOfEntities& deadEntities);
|
||||
|
||||
void accumulateGrabPositions(std::map<QUuid, GrabLocationAccumulator>& grabAccumulators);
|
||||
|
||||
public slots:
|
||||
/**jsdoc
|
||||
* @function AvatarManager.updateAvatarRenderStatus
|
||||
|
@ -215,7 +217,7 @@ private:
|
|||
void simulateAvatarFades(float deltaTime);
|
||||
|
||||
AvatarSharedPointer newSharedAvatar() override;
|
||||
|
||||
|
||||
// called only from the AvatarHashMap thread - cannot be called while this thread holds the
|
||||
// hash lock, since handleRemovedAvatar needs a write lock on the entity tree and the entity tree
|
||||
// frequently grabs a read lock on the hash to get a given avatar by ID
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
AvatarMotionState::AvatarMotionState(OtherAvatarPointer avatar, const btCollisionShape* shape) : ObjectMotionState(shape), _avatar(avatar) {
|
||||
assert(_avatar);
|
||||
_type = MOTIONSTATE_TYPE_AVATAR;
|
||||
_collisionGroup = BULLET_COLLISION_GROUP_OTHER_AVATAR;
|
||||
cacheShapeDiameter();
|
||||
}
|
||||
|
||||
|
@ -170,8 +171,8 @@ QUuid AvatarMotionState::getSimulatorID() const {
|
|||
|
||||
// virtual
|
||||
void AvatarMotionState::computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const {
|
||||
group = BULLET_COLLISION_GROUP_OTHER_AVATAR;
|
||||
mask = Physics::getDefaultCollisionMask(group);
|
||||
group = _collisionGroup;
|
||||
mask = _collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS ? 0 : Physics::getDefaultCollisionMask(group);
|
||||
}
|
||||
|
||||
// virtual
|
||||
|
|
|
@ -66,6 +66,9 @@ public:
|
|||
|
||||
void addDirtyFlags(uint32_t flags) { _dirtyFlags |= flags; }
|
||||
|
||||
void setCollisionGroup(int32_t group) { _collisionGroup = group; }
|
||||
int32_t getCollisionGroup() { return _collisionGroup; }
|
||||
|
||||
virtual void computeCollisionGroupAndMask(int32_t& group, int32_t& mask) const override;
|
||||
|
||||
virtual float getMass() const override;
|
||||
|
@ -87,7 +90,7 @@ protected:
|
|||
|
||||
OtherAvatarPointer _avatar;
|
||||
float _diameter { 0.0f };
|
||||
|
||||
int32_t _collisionGroup;
|
||||
uint32_t _dirtyFlags;
|
||||
};
|
||||
|
||||
|
|
149
interface/src/avatar/AvatarPackager.cpp
Normal file
149
interface/src/avatar/AvatarPackager.cpp
Normal file
|
@ -0,0 +1,149 @@
|
|||
//
|
||||
// AvatarPackager.cpp
|
||||
//
|
||||
//
|
||||
// Created by Thijs Wenker on 12/6/2018
|
||||
// Copyright 2018 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 "AvatarPackager.h"
|
||||
|
||||
#include "Application.h"
|
||||
|
||||
#include <QQmlContext>
|
||||
#include <QQmlEngine>
|
||||
#include <QUrl>
|
||||
|
||||
#include <OffscreenUi.h>
|
||||
#include "ModelSelector.h"
|
||||
#include <avatar/MarketplaceItemUploader.h>
|
||||
|
||||
#include <mutex>
|
||||
#include "ui/TabletScriptingInterface.h"
|
||||
|
||||
std::once_flag setupQMLTypesFlag;
|
||||
AvatarPackager::AvatarPackager() {
|
||||
std::call_once(setupQMLTypesFlag, []() {
|
||||
qmlRegisterType<FST>();
|
||||
qmlRegisterType<MarketplaceItemUploader>();
|
||||
qRegisterMetaType<AvatarPackager*>();
|
||||
qRegisterMetaType<AvatarProject*>();
|
||||
qRegisterMetaType<AvatarProjectStatus::AvatarProjectStatus>();
|
||||
qmlRegisterUncreatableMetaObject(
|
||||
AvatarProjectStatus::staticMetaObject,
|
||||
"Hifi.AvatarPackager.AvatarProjectStatus",
|
||||
1, 0,
|
||||
"AvatarProjectStatus",
|
||||
"Error: only enums"
|
||||
);
|
||||
});
|
||||
|
||||
recentProjectsFromVariantList(_recentProjectsSetting.get());
|
||||
|
||||
QDir defaultProjectsDir(AvatarProject::getDefaultProjectsPath());
|
||||
defaultProjectsDir.mkpath(".");
|
||||
}
|
||||
|
||||
bool AvatarPackager::open() {
|
||||
const auto packageModelDialogCreated = [=](QQmlContext* context, QObject* newObject) {
|
||||
context->setContextProperty("AvatarPackagerCore", this);
|
||||
};
|
||||
|
||||
static const QString SYSTEM_TABLET = "com.highfidelity.interface.tablet.system";
|
||||
auto tablet = dynamic_cast<TabletProxy*>(DependencyManager::get<TabletScriptingInterface>()->getTablet(SYSTEM_TABLET));
|
||||
|
||||
if (tablet->getToolbarMode()) {
|
||||
static const QUrl url{ "hifi/AvatarPackagerWindow.qml" };
|
||||
DependencyManager::get<OffscreenUi>()->show(url, "AvatarPackager", packageModelDialogCreated);
|
||||
return true;
|
||||
}
|
||||
|
||||
static const QUrl url{ "hifi/tablet/AvatarPackager.qml" };
|
||||
if (!tablet->isPathLoaded(url)) {
|
||||
tablet->getTabletSurface()->getSurfaceContext()->setContextProperty("AvatarPackagerCore", this);
|
||||
tablet->pushOntoStack(url);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void AvatarPackager::addCurrentProjectToRecentProjects() {
|
||||
const int MAX_RECENT_PROJECTS = 5;
|
||||
const QString& fstPath = _currentAvatarProject->getFSTPath();
|
||||
auto removeProjects = QVector<RecentAvatarProject>();
|
||||
for (const auto& project : _recentProjects) {
|
||||
if (project.getProjectFSTPath() == fstPath) {
|
||||
removeProjects.append(project);
|
||||
}
|
||||
}
|
||||
for (const auto& removeProject : removeProjects) {
|
||||
_recentProjects.removeOne(removeProject);
|
||||
}
|
||||
|
||||
const auto newRecentProject = RecentAvatarProject(_currentAvatarProject->getProjectName(), fstPath);
|
||||
_recentProjects.prepend(newRecentProject);
|
||||
|
||||
while (_recentProjects.size() > MAX_RECENT_PROJECTS) {
|
||||
_recentProjects.pop_back();
|
||||
}
|
||||
|
||||
_recentProjectsSetting.set(recentProjectsToVariantList(false));
|
||||
emit recentProjectsChanged();
|
||||
}
|
||||
|
||||
QVariantList AvatarPackager::recentProjectsToVariantList(bool includeProjectPaths) const {
|
||||
QVariantList result;
|
||||
for (const auto& project : _recentProjects) {
|
||||
QVariantMap projectVariant;
|
||||
projectVariant.insert("name", project.getProjectName());
|
||||
projectVariant.insert("path", project.getProjectFSTPath());
|
||||
if (includeProjectPaths) {
|
||||
projectVariant.insert("projectPath", project.getProjectPath());
|
||||
}
|
||||
result.append(projectVariant);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
void AvatarPackager::recentProjectsFromVariantList(QVariantList projectsVariant) {
|
||||
_recentProjects.clear();
|
||||
for (const auto& projectVariant : projectsVariant) {
|
||||
auto map = projectVariant.toMap();
|
||||
_recentProjects.append(RecentAvatarProject(map.value("name").toString(), map.value("path").toString()));
|
||||
}
|
||||
}
|
||||
|
||||
AvatarProjectStatus::AvatarProjectStatus AvatarPackager::openAvatarProject(const QString& avatarProjectFSTPath) {
|
||||
AvatarProjectStatus::AvatarProjectStatus status;
|
||||
setAvatarProject(AvatarProject::openAvatarProject(avatarProjectFSTPath, status));
|
||||
return status;
|
||||
}
|
||||
|
||||
AvatarProjectStatus::AvatarProjectStatus AvatarPackager::createAvatarProject(const QString& projectsFolder,
|
||||
const QString& avatarProjectName,
|
||||
const QString& avatarModelPath,
|
||||
const QString& textureFolder) {
|
||||
AvatarProjectStatus::AvatarProjectStatus status;
|
||||
setAvatarProject(AvatarProject::createAvatarProject(projectsFolder, avatarProjectName, avatarModelPath, textureFolder, status));
|
||||
return status;
|
||||
}
|
||||
|
||||
void AvatarPackager::setAvatarProject(AvatarProject* avatarProject) {
|
||||
if (avatarProject == _currentAvatarProject) {
|
||||
return;
|
||||
}
|
||||
if (_currentAvatarProject) {
|
||||
_currentAvatarProject->deleteLater();
|
||||
}
|
||||
_currentAvatarProject = avatarProject;
|
||||
if (_currentAvatarProject) {
|
||||
addCurrentProjectToRecentProjects();
|
||||
connect(_currentAvatarProject, &AvatarProject::nameChanged, this, &AvatarPackager::addCurrentProjectToRecentProjects);
|
||||
QQmlEngine::setObjectOwnership(_currentAvatarProject, QQmlEngine::CppOwnership);
|
||||
}
|
||||
emit avatarProjectChanged();
|
||||
}
|
100
interface/src/avatar/AvatarPackager.h
Normal file
100
interface/src/avatar/AvatarPackager.h
Normal file
|
@ -0,0 +1,100 @@
|
|||
//
|
||||
// AvatarPackager.h
|
||||
//
|
||||
//
|
||||
// Created by Thijs Wenker on 12/6/2018
|
||||
// Copyright 2018 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
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef hifi_AvatarPackager_h
|
||||
#define hifi_AvatarPackager_h
|
||||
|
||||
#include <QObject>
|
||||
#include <DependencyManager.h>
|
||||
|
||||
#include "FileDialogHelper.h"
|
||||
|
||||
#include "avatar/AvatarProject.h"
|
||||
#include "SettingHandle.h"
|
||||
|
||||
class RecentAvatarProject {
|
||||
public:
|
||||
RecentAvatarProject() = default;
|
||||
|
||||
|
||||
RecentAvatarProject(QString projectName, QString projectFSTPath) {
|
||||
_projectName = projectName;
|
||||
_projectFSTPath = projectFSTPath;
|
||||
}
|
||||
RecentAvatarProject(const RecentAvatarProject& other) {
|
||||
_projectName = other._projectName;
|
||||
_projectFSTPath = other._projectFSTPath;
|
||||
}
|
||||
|
||||
QString getProjectName() const { return _projectName; }
|
||||
|
||||
QString getProjectFSTPath() const { return _projectFSTPath; }
|
||||
|
||||
QString getProjectPath() const {
|
||||
return QFileInfo(_projectFSTPath).absoluteDir().absolutePath();
|
||||
}
|
||||
|
||||
bool operator==(const RecentAvatarProject& other) const {
|
||||
return _projectName == other._projectName && _projectFSTPath == other._projectFSTPath;
|
||||
}
|
||||
|
||||
private:
|
||||
QString _projectName;
|
||||
QString _projectFSTPath;
|
||||
|
||||
};
|
||||
|
||||
class AvatarPackager : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
SINGLETON_DEPENDENCY
|
||||
Q_PROPERTY(AvatarProject* currentAvatarProject READ getAvatarProject NOTIFY avatarProjectChanged)
|
||||
Q_PROPERTY(QString AVATAR_PROJECTS_PATH READ getAvatarProjectsPath CONSTANT)
|
||||
Q_PROPERTY(QVariantList recentProjects READ getRecentProjects NOTIFY recentProjectsChanged)
|
||||
public:
|
||||
AvatarPackager();
|
||||
bool open();
|
||||
|
||||
Q_INVOKABLE AvatarProjectStatus::AvatarProjectStatus createAvatarProject(const QString& projectsFolder,
|
||||
const QString& avatarProjectName,
|
||||
const QString& avatarModelPath,
|
||||
const QString& textureFolder);
|
||||
|
||||
Q_INVOKABLE AvatarProjectStatus::AvatarProjectStatus openAvatarProject(const QString& avatarProjectFSTPath);
|
||||
Q_INVOKABLE bool isValidNewProjectName(const QString& projectPath, const QString& projectName) const {
|
||||
return AvatarProject::isValidNewProjectName(projectPath, projectName);
|
||||
}
|
||||
|
||||
signals:
|
||||
void avatarProjectChanged();
|
||||
void recentProjectsChanged();
|
||||
|
||||
private:
|
||||
Q_INVOKABLE AvatarProject* getAvatarProject() const { return _currentAvatarProject; };
|
||||
Q_INVOKABLE QString getAvatarProjectsPath() const { return AvatarProject::getDefaultProjectsPath(); }
|
||||
Q_INVOKABLE QVariantList getRecentProjects() const { return recentProjectsToVariantList(true); }
|
||||
|
||||
void setAvatarProject(AvatarProject* avatarProject);
|
||||
|
||||
void addCurrentProjectToRecentProjects();
|
||||
|
||||
AvatarProject* _currentAvatarProject { nullptr };
|
||||
QVector<RecentAvatarProject> _recentProjects;
|
||||
|
||||
QVariantList recentProjectsToVariantList(bool includeProjectPaths) const;
|
||||
|
||||
void recentProjectsFromVariantList(QVariantList projectsVariant);
|
||||
|
||||
|
||||
Setting::Handle<QVariantList> _recentProjectsSetting { "io.highfidelity.avatarPackager.recentProjects", QVariantList() };
|
||||
};
|
||||
|
||||
#endif // hifi_AvatarPackager_h
|
260
interface/src/avatar/AvatarProject.cpp
Normal file
260
interface/src/avatar/AvatarProject.cpp
Normal file
|
@ -0,0 +1,260 @@
|
|||
//
|
||||
// AvatarProject.cpp
|
||||
//
|
||||
//
|
||||
// Created by Thijs Wenker on 12/7/2018
|
||||
// Copyright 2018 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 "AvatarProject.h"
|
||||
|
||||
#include <FSTReader.h>
|
||||
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QQmlEngine>
|
||||
#include <QTimer>
|
||||
|
||||
#include "FBXSerializer.h"
|
||||
#include <ui/TabletScriptingInterface.h>
|
||||
#include "scripting/HMDScriptingInterface.h"
|
||||
|
||||
AvatarProject* AvatarProject::openAvatarProject(const QString& path, AvatarProjectStatus::AvatarProjectStatus& status) {
|
||||
status = AvatarProjectStatus::NONE;
|
||||
|
||||
if (!path.toLower().endsWith(".fst")) {
|
||||
status = AvatarProjectStatus::ERROR_OPEN_INVALID_FILE_TYPE;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QFileInfo fstFileInfo{ path };
|
||||
if (!fstFileInfo.absoluteDir().exists()) {
|
||||
status = AvatarProjectStatus::ERROR_OPEN_PROJECT_FOLDER;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!fstFileInfo.exists()) {
|
||||
status = AvatarProjectStatus::ERROR_OPEN_FIND_FST;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QFile file{ fstFileInfo.filePath() };
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
status = AvatarProjectStatus::ERROR_OPEN_OPEN_FST;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto project = new AvatarProject(path, file.readAll());
|
||||
|
||||
QFileInfo fbxFileInfo{ project->getFBXPath() };
|
||||
if (!fbxFileInfo.exists()) {
|
||||
project->deleteLater();
|
||||
status = AvatarProjectStatus::ERROR_OPEN_FIND_MODEL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QQmlEngine::setObjectOwnership(project, QQmlEngine::CppOwnership);
|
||||
status = AvatarProjectStatus::SUCCESS;
|
||||
return project;
|
||||
}
|
||||
|
||||
AvatarProject* AvatarProject::createAvatarProject(const QString& projectsFolder, const QString& avatarProjectName,
|
||||
const QString& avatarModelPath, const QString& textureFolder,
|
||||
AvatarProjectStatus::AvatarProjectStatus& status) {
|
||||
status = AvatarProjectStatus::NONE;
|
||||
|
||||
if (!isValidNewProjectName(projectsFolder, avatarProjectName)) {
|
||||
status = AvatarProjectStatus::ERROR_CREATE_PROJECT_NAME;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QDir projectDir(projectsFolder + "/" + avatarProjectName);
|
||||
if (!projectDir.mkpath(".")) {
|
||||
status = AvatarProjectStatus::ERROR_CREATE_CREATING_DIRECTORIES;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QDir projectTexturesDir(projectDir.path() + "/textures");
|
||||
if (!projectTexturesDir.mkpath(".")) {
|
||||
status = AvatarProjectStatus::ERROR_CREATE_CREATING_DIRECTORIES;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QDir projectScriptsDir(projectDir.path() + "/scripts");
|
||||
if (!projectScriptsDir.mkpath(".")) {
|
||||
status = AvatarProjectStatus::ERROR_CREATE_CREATING_DIRECTORIES;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto fileName = QFileInfo(avatarModelPath).fileName();
|
||||
const auto newModelPath = projectDir.absoluteFilePath(fileName);
|
||||
const auto newFSTPath = projectDir.absoluteFilePath("avatar.fst");
|
||||
QFile::copy(avatarModelPath, newModelPath);
|
||||
|
||||
QFileInfo fbxInfo{ newModelPath };
|
||||
if (!fbxInfo.exists() || !fbxInfo.isFile()) {
|
||||
status = AvatarProjectStatus::ERROR_CREATE_FIND_MODEL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QFile fbx{ fbxInfo.filePath() };
|
||||
if (!fbx.open(QIODevice::ReadOnly)) {
|
||||
status = AvatarProjectStatus::ERROR_CREATE_OPEN_MODEL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<hfm::Model> hfmModel;
|
||||
|
||||
try {
|
||||
const QByteArray fbxContents = fbx.readAll();
|
||||
hfmModel = FBXSerializer().read(fbxContents, QVariantHash(), fbxInfo.filePath());
|
||||
} catch (const QString& error) {
|
||||
Q_UNUSED(error)
|
||||
status = AvatarProjectStatus::ERROR_CREATE_READ_MODEL;
|
||||
return nullptr;
|
||||
}
|
||||
QStringList textures{};
|
||||
|
||||
auto addTextureToList = [&textures](hfm::Texture texture) mutable {
|
||||
if (!texture.filename.isEmpty() && texture.content.isEmpty() && !textures.contains(texture.filename)) {
|
||||
textures << texture.filename;
|
||||
}
|
||||
};
|
||||
|
||||
foreach(const HFMMaterial material, hfmModel->materials) {
|
||||
addTextureToList(material.normalTexture);
|
||||
addTextureToList(material.albedoTexture);
|
||||
addTextureToList(material.opacityTexture);
|
||||
addTextureToList(material.glossTexture);
|
||||
addTextureToList(material.roughnessTexture);
|
||||
addTextureToList(material.specularTexture);
|
||||
addTextureToList(material.metallicTexture);
|
||||
addTextureToList(material.emissiveTexture);
|
||||
addTextureToList(material.occlusionTexture);
|
||||
addTextureToList(material.scatteringTexture);
|
||||
addTextureToList(material.lightmapTexture);
|
||||
}
|
||||
|
||||
QDir textureDir(textureFolder.isEmpty() ? fbxInfo.absoluteDir() : textureFolder);
|
||||
|
||||
for (const auto& texture : textures) {
|
||||
QString sourcePath = textureDir.path() + "/" + texture;
|
||||
QString targetPath = projectTexturesDir.path() + "/" + texture;
|
||||
|
||||
QFileInfo sourceTexturePath(sourcePath);
|
||||
if (sourceTexturePath.exists()) {
|
||||
QFile::copy(sourcePath, targetPath);
|
||||
}
|
||||
}
|
||||
|
||||
auto fst = FST::createFSTFromModel(newFSTPath, newModelPath, *hfmModel);
|
||||
fst->setName(avatarProjectName);
|
||||
|
||||
if (!fst->write()) {
|
||||
status = AvatarProjectStatus::ERROR_CREATE_WRITE_FST;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
status = AvatarProjectStatus::SUCCESS;
|
||||
return new AvatarProject(fst);
|
||||
}
|
||||
|
||||
QStringList AvatarProject::getScriptPaths(const QDir& scriptsDir) const {
|
||||
QStringList result{};
|
||||
constexpr auto flags = QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot | QDir::Hidden;
|
||||
if (!scriptsDir.exists()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (const auto& script : scriptsDir.entryInfoList({}, flags)) {
|
||||
if (script.fileName().toLower().endsWith(".js")) {
|
||||
result.push_back("scripts/" + script.fileName());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool AvatarProject::isValidNewProjectName(const QString& projectPath, const QString& projectName) {
|
||||
if (projectPath.trimmed().isEmpty() || projectName.trimmed().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
QDir dir(projectPath + "/" + projectName);
|
||||
return !dir.exists();
|
||||
}
|
||||
|
||||
AvatarProject::AvatarProject(const QString& fstPath, const QByteArray& data) :
|
||||
AvatarProject::AvatarProject(new FST(fstPath, FSTReader::readMapping(data))) {
|
||||
}
|
||||
AvatarProject::AvatarProject(FST* fst) {
|
||||
_fst = fst;
|
||||
auto fileInfo = QFileInfo(getFSTPath());
|
||||
_directory = fileInfo.absoluteDir();
|
||||
|
||||
_fst->setScriptPaths(getScriptPaths(QDir(_directory.path() + "/scripts")));
|
||||
_fst->write();
|
||||
|
||||
refreshProjectFiles();
|
||||
|
||||
_projectPath = fileInfo.absoluteDir().absolutePath();
|
||||
}
|
||||
|
||||
void AvatarProject::appendDirectory(const QString& prefix, const QDir& dir) {
|
||||
constexpr auto flags = QDir::Dirs | QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot | QDir::Hidden;
|
||||
for (auto& entry : dir.entryInfoList({}, flags)) {
|
||||
if (entry.isFile()) {
|
||||
_projectFiles.append({ entry.absoluteFilePath(), prefix + entry.fileName() });
|
||||
} else if (entry.isDir()) {
|
||||
appendDirectory(prefix + entry.fileName() + "/", entry.absoluteFilePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarProject::refreshProjectFiles() {
|
||||
_projectFiles.clear();
|
||||
appendDirectory("", _directory);
|
||||
}
|
||||
|
||||
QStringList AvatarProject::getProjectFiles() const {
|
||||
QStringList paths;
|
||||
for (auto& path : _projectFiles) {
|
||||
paths.append(path.relativePath);
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
MarketplaceItemUploader* AvatarProject::upload(bool updateExisting) {
|
||||
QUuid itemID;
|
||||
if (updateExisting) {
|
||||
itemID = _fst->getMarketplaceID();
|
||||
}
|
||||
auto uploader = new MarketplaceItemUploader(getProjectName(), "", QFileInfo(getFSTPath()).fileName(),
|
||||
itemID, _projectFiles);
|
||||
connect(uploader, &MarketplaceItemUploader::completed, this, [this, uploader]() {
|
||||
if (uploader->getError() == MarketplaceItemUploader::Error::None) {
|
||||
_fst->setMarketplaceID(uploader->getMarketplaceID());
|
||||
_fst->write();
|
||||
}
|
||||
});
|
||||
|
||||
return uploader;
|
||||
}
|
||||
|
||||
void AvatarProject::openInInventory() const {
|
||||
constexpr int TIME_TO_WAIT_FOR_INVENTORY_TO_OPEN_MS { 1000 };
|
||||
|
||||
auto tablet = dynamic_cast<TabletProxy*>(
|
||||
DependencyManager::get<TabletScriptingInterface>()->getTablet("com.highfidelity.interface.tablet.system"));
|
||||
tablet->loadQMLSource("hifi/commerce/wallet/Wallet.qml");
|
||||
DependencyManager::get<HMDScriptingInterface>()->openTablet();
|
||||
tablet->getTabletRoot()->forceActiveFocus();
|
||||
auto name = getProjectName();
|
||||
|
||||
// I'm not a fan of this, but it's the only current option.
|
||||
QTimer::singleShot(TIME_TO_WAIT_FOR_INVENTORY_TO_OPEN_MS, [name, tablet]() {
|
||||
tablet->sendToQml(QVariantMap({ { "method", "updatePurchases" }, { "filterText", name } }));
|
||||
});
|
||||
}
|
115
interface/src/avatar/AvatarProject.h
Normal file
115
interface/src/avatar/AvatarProject.h
Normal file
|
@ -0,0 +1,115 @@
|
|||
//
|
||||
// AvatarProject.h
|
||||
//
|
||||
//
|
||||
// Created by Thijs Wenker on 12/7/2018
|
||||
// Copyright 2018 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
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef hifi_AvatarProject_h
|
||||
#define hifi_AvatarProject_h
|
||||
|
||||
#include "MarketplaceItemUploader.h"
|
||||
#include "ProjectFile.h"
|
||||
#include "FST.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QDir>
|
||||
#include <QVariantHash>
|
||||
#include <QStandardPaths>
|
||||
|
||||
namespace AvatarProjectStatus {
|
||||
Q_NAMESPACE
|
||||
enum AvatarProjectStatus {
|
||||
NONE,
|
||||
SUCCESS,
|
||||
ERROR_CREATE_PROJECT_NAME,
|
||||
ERROR_CREATE_CREATING_DIRECTORIES,
|
||||
ERROR_CREATE_FIND_MODEL,
|
||||
ERROR_CREATE_OPEN_MODEL,
|
||||
ERROR_CREATE_READ_MODEL,
|
||||
ERROR_CREATE_WRITE_FST,
|
||||
ERROR_OPEN_INVALID_FILE_TYPE,
|
||||
ERROR_OPEN_PROJECT_FOLDER,
|
||||
ERROR_OPEN_FIND_FST,
|
||||
ERROR_OPEN_OPEN_FST,
|
||||
ERROR_OPEN_FIND_MODEL
|
||||
};
|
||||
Q_ENUM_NS(AvatarProjectStatus)
|
||||
}
|
||||
|
||||
|
||||
class AvatarProject : public QObject {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(FST* fst READ getFST CONSTANT)
|
||||
|
||||
Q_PROPERTY(QStringList projectFiles READ getProjectFiles NOTIFY projectFilesChanged)
|
||||
|
||||
Q_PROPERTY(QString projectFolderPath READ getProjectPath CONSTANT)
|
||||
Q_PROPERTY(QString projectFSTPath READ getFSTPath CONSTANT)
|
||||
Q_PROPERTY(QString projectFBXPath READ getFBXPath CONSTANT)
|
||||
Q_PROPERTY(QString name READ getProjectName WRITE setProjectName NOTIFY nameChanged)
|
||||
|
||||
public:
|
||||
Q_INVOKABLE MarketplaceItemUploader* upload(bool updateExisting);
|
||||
Q_INVOKABLE void openInInventory() const;
|
||||
Q_INVOKABLE QStringList getProjectFiles() const;
|
||||
|
||||
Q_INVOKABLE QString getProjectName() const { return _fst->getName(); }
|
||||
Q_INVOKABLE void setProjectName(const QString& newProjectName) {
|
||||
if (newProjectName.trimmed().length() > 0) {
|
||||
_fst->setName(newProjectName);
|
||||
_fst->write();
|
||||
emit nameChanged();
|
||||
}
|
||||
}
|
||||
Q_INVOKABLE QString getProjectPath() const { return _projectPath; }
|
||||
Q_INVOKABLE QString getFSTPath() const { return _fst->getPath(); }
|
||||
Q_INVOKABLE QString getFBXPath() const {
|
||||
return QDir::cleanPath(QDir(_projectPath).absoluteFilePath(_fst->getModelPath()));
|
||||
}
|
||||
|
||||
/**
|
||||
* returns the AvatarProject or a nullptr on failure.
|
||||
*/
|
||||
static AvatarProject* openAvatarProject(const QString& path, AvatarProjectStatus::AvatarProjectStatus& status);
|
||||
static AvatarProject* createAvatarProject(const QString& projectsFolder,
|
||||
const QString& avatarProjectName,
|
||||
const QString& avatarModelPath,
|
||||
const QString& textureFolder,
|
||||
AvatarProjectStatus::AvatarProjectStatus& status);
|
||||
|
||||
static bool isValidNewProjectName(const QString& projectPath, const QString& projectName);
|
||||
|
||||
static QString getDefaultProjectsPath() {
|
||||
return QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/High Fidelity Projects";
|
||||
}
|
||||
|
||||
signals:
|
||||
void nameChanged();
|
||||
void projectFilesChanged();
|
||||
|
||||
private:
|
||||
AvatarProject(const QString& fstPath, const QByteArray& data);
|
||||
AvatarProject(FST* fst);
|
||||
|
||||
~AvatarProject() { _fst->deleteLater(); }
|
||||
|
||||
FST* getFST() { return _fst; }
|
||||
|
||||
void refreshProjectFiles();
|
||||
void appendDirectory(const QString& prefix, const QDir& dir);
|
||||
QStringList getScriptPaths(const QDir& scriptsDir) const;
|
||||
|
||||
FST* _fst;
|
||||
|
||||
QDir _directory;
|
||||
QList<ProjectFilePath> _projectFiles{};
|
||||
QString _projectPath;
|
||||
};
|
||||
|
||||
#endif // hifi_AvatarProject_h
|
39
interface/src/avatar/GrabManager.cpp
Normal file
39
interface/src/avatar/GrabManager.cpp
Normal file
|
@ -0,0 +1,39 @@
|
|||
//
|
||||
// GrabManager.cpp
|
||||
// interface/src/avatar/
|
||||
//
|
||||
// Created by Seth Alves on 2018-12-4.
|
||||
// Copyright 2018 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 "GrabManager.h"
|
||||
|
||||
void GrabManager::simulateGrabs() {
|
||||
QSharedPointer<AvatarManager> avatarManager = DependencyManager::get<AvatarManager>();
|
||||
|
||||
// Update grabbed objects
|
||||
auto entityTreeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||
auto entityTree = entityTreeRenderer->getTree();
|
||||
entityTree->withReadLock([&] {
|
||||
PROFILE_RANGE(simulation, "Grabs");
|
||||
|
||||
std::map<QUuid, GrabLocationAccumulator> grabAccumulators;
|
||||
avatarManager->accumulateGrabPositions(grabAccumulators);
|
||||
|
||||
for (auto& accumulatedLocation : grabAccumulators) {
|
||||
QUuid grabbedThingID = accumulatedLocation.first;
|
||||
GrabLocationAccumulator& acc = accumulatedLocation.second;
|
||||
bool success;
|
||||
SpatiallyNestablePointer grabbedThing = SpatiallyNestable::findByID(grabbedThingID, success);
|
||||
if (success && grabbedThing) {
|
||||
glm::vec3 finalPosition = acc.finalizePosition();
|
||||
glm::quat finalOrientation = acc.finalizeOrientation();
|
||||
grabbedThing->setTransform(createMatFromQuatAndPos(finalOrientation, finalPosition));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
23
interface/src/avatar/GrabManager.h
Normal file
23
interface/src/avatar/GrabManager.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// GrabManager.h
|
||||
// interface/src/avatar/
|
||||
//
|
||||
// Created by Seth Alves on 2018-12-4.
|
||||
// Copyright 2018 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 <AvatarData.h>
|
||||
#include <EntityTreeRenderer.h>
|
||||
#include "AvatarManager.h"
|
||||
|
||||
class GrabManager : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
SINGLETON_DEPENDENCY
|
||||
|
||||
public:
|
||||
void simulateGrabs();
|
||||
|
||||
};
|
321
interface/src/avatar/MarketplaceItemUploader.cpp
Normal file
321
interface/src/avatar/MarketplaceItemUploader.cpp
Normal file
|
@ -0,0 +1,321 @@
|
|||
//
|
||||
// MarketplaceItemUploader.cpp
|
||||
//
|
||||
//
|
||||
// Created by Ryan Huffman on 12/10/2018
|
||||
// Copyright 2018 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 "MarketplaceItemUploader.h"
|
||||
|
||||
#include <AccountManager.h>
|
||||
#include <DependencyManager.h>
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include <quazip5/quazip.h>
|
||||
#include <quazip5/quazipfile.h>
|
||||
#endif
|
||||
|
||||
#include <QTimer>
|
||||
#include <QBuffer>
|
||||
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QUuid>
|
||||
|
||||
MarketplaceItemUploader::MarketplaceItemUploader(QString title,
|
||||
QString description,
|
||||
QString rootFilename,
|
||||
QUuid marketplaceID,
|
||||
QList<ProjectFilePath> filePaths) :
|
||||
_title(title),
|
||||
_description(description),
|
||||
_rootFilename(rootFilename),
|
||||
_marketplaceID(marketplaceID),
|
||||
_filePaths(filePaths) {
|
||||
}
|
||||
|
||||
void MarketplaceItemUploader::setState(State newState) {
|
||||
Q_ASSERT(_state != State::Complete);
|
||||
Q_ASSERT(_error == Error::None);
|
||||
Q_ASSERT(newState != _state);
|
||||
|
||||
_state = newState;
|
||||
emit stateChanged(newState);
|
||||
if (newState == State::Complete) {
|
||||
emit completed();
|
||||
emit finishedChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void MarketplaceItemUploader::setError(Error error) {
|
||||
Q_ASSERT(_state != State::Complete);
|
||||
Q_ASSERT(_error == Error::None);
|
||||
|
||||
_error = error;
|
||||
emit errorChanged(error);
|
||||
emit finishedChanged();
|
||||
}
|
||||
|
||||
void MarketplaceItemUploader::send() {
|
||||
doGetCategories();
|
||||
}
|
||||
|
||||
void MarketplaceItemUploader::doGetCategories() {
|
||||
setState(State::GettingCategories);
|
||||
|
||||
static const QString path = "/api/v1/marketplace/categories";
|
||||
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
auto request = accountManager->createRequest(path, AccountManagerAuth::None);
|
||||
|
||||
qWarning() << "Request url is: " << request.url();
|
||||
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
|
||||
QNetworkReply* reply = networkAccessManager.get(request);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
auto error = reply->error();
|
||||
if (error == QNetworkReply::NoError) {
|
||||
auto doc = QJsonDocument::fromJson(reply->readAll());
|
||||
auto extractCategoryID = [&doc]() -> std::pair<bool, int> {
|
||||
auto items = doc.object()["data"].toObject()["items"];
|
||||
if (!items.isArray()) {
|
||||
qWarning() << "Categories parse error: data.items is not an array";
|
||||
return { false, 0 };
|
||||
}
|
||||
|
||||
auto itemsArray = items.toArray();
|
||||
for (const auto item : itemsArray) {
|
||||
if (!item.isObject()) {
|
||||
qWarning() << "Categories parse error: item is not an object";
|
||||
return { false, 0 };
|
||||
}
|
||||
|
||||
auto itemObject = item.toObject();
|
||||
if (itemObject["name"].toString() == "Avatars") {
|
||||
auto idValue = itemObject["id"];
|
||||
if (!idValue.isDouble()) {
|
||||
qWarning() << "Categories parse error: id is not a number";
|
||||
return { false, 0 };
|
||||
}
|
||||
return { true, (int)idValue.toDouble() };
|
||||
}
|
||||
}
|
||||
|
||||
qWarning() << "Categories parse error: could not find a category for 'Avatar'";
|
||||
return { false, 0 };
|
||||
};
|
||||
|
||||
bool success;
|
||||
std::tie(success, _categoryID) = extractCategoryID();
|
||||
if (!success) {
|
||||
qWarning() << "Failed to find marketplace category id";
|
||||
setError(Error::Unknown);
|
||||
} else {
|
||||
qDebug() << "Marketplace Avatar category ID is" << _categoryID;
|
||||
doUploadAvatar();
|
||||
}
|
||||
} else {
|
||||
setError(Error::Unknown);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void MarketplaceItemUploader::doUploadAvatar() {
|
||||
#ifdef Q_OS_ANDROID
|
||||
qWarning() << "Marketplace uploading is not supported on Android";
|
||||
setError(Error::Unknown);
|
||||
return;
|
||||
#else
|
||||
QBuffer buffer{ &_fileData };
|
||||
QuaZip zip{ &buffer };
|
||||
if (!zip.open(QuaZip::Mode::mdAdd)) {
|
||||
qWarning() << "Failed to open zip";
|
||||
setError(Error::Unknown);
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& filePath : _filePaths) {
|
||||
qWarning() << "Zipping: " << filePath.absolutePath << filePath.relativePath;
|
||||
QFileInfo fileInfo{ filePath.absolutePath };
|
||||
|
||||
QuaZipFile zipFile{ &zip };
|
||||
if (!zipFile.open(QIODevice::WriteOnly, QuaZipNewInfo(filePath.relativePath))) {
|
||||
qWarning() << "Could not open zip file:" << zipFile.getZipError();
|
||||
setError(Error::Unknown);
|
||||
return;
|
||||
}
|
||||
QFile file{ filePath.absolutePath };
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
zipFile.write(file.readAll());
|
||||
} else {
|
||||
qWarning() << "Failed to open: " << filePath.absolutePath;
|
||||
}
|
||||
file.close();
|
||||
zipFile.close();
|
||||
if (zipFile.getZipError() != UNZ_OK) {
|
||||
qWarning() << "Could not close zip file: " << zipFile.getZipError();
|
||||
setState(State::Complete);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
zip.close();
|
||||
|
||||
qDebug() << "Finished zipping, size: " << (buffer.size() / (1000.0f)) << "KB";
|
||||
|
||||
QString path = "/api/v1/marketplace/items";
|
||||
bool creating = true;
|
||||
if (!_marketplaceID.isNull()) {
|
||||
creating = false;
|
||||
auto idWithBraces = _marketplaceID.toString();
|
||||
auto idWithoutBraces = idWithBraces.mid(1, idWithBraces.length() - 2);
|
||||
path += "/" + idWithoutBraces;
|
||||
}
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
auto request = accountManager->createRequest(path, AccountManagerAuth::Required);
|
||||
request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json");
|
||||
|
||||
// TODO(huffman) add JSON escaping
|
||||
auto escapeJson = [](QString str) -> QString { return str; };
|
||||
|
||||
QString jsonString = "{\"marketplace_item\":{";
|
||||
jsonString += "\"title\":\"" + escapeJson(_title) + "\"";
|
||||
|
||||
// Items cannot have their description updated after they have been submitted.
|
||||
if (creating) {
|
||||
jsonString += ",\"description\":\"" + escapeJson(_description) + "\"";
|
||||
}
|
||||
|
||||
jsonString += ",\"root_file_key\":\"" + escapeJson(_rootFilename) + "\"";
|
||||
jsonString += ",\"category_ids\":[" + QStringLiteral("%1").arg(_categoryID) + "]";
|
||||
jsonString += ",\"license\":0";
|
||||
jsonString += ",\"files\":\"" + QString::fromLatin1(_fileData.toBase64()) + "\"}}";
|
||||
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
|
||||
QNetworkReply* reply{ nullptr };
|
||||
if (creating) {
|
||||
reply = networkAccessManager.post(request, jsonString.toUtf8());
|
||||
} else {
|
||||
reply = networkAccessManager.put(request, jsonString.toUtf8());
|
||||
}
|
||||
|
||||
connect(reply, &QNetworkReply::uploadProgress, this, [this](float bytesSent, float bytesTotal) {
|
||||
if (_state == State::UploadingAvatar) {
|
||||
emit uploadProgress(bytesSent, bytesTotal);
|
||||
if (bytesSent >= bytesTotal) {
|
||||
setState(State::WaitingForUploadResponse);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
_responseData = reply->readAll();
|
||||
|
||||
auto error = reply->error();
|
||||
if (error == QNetworkReply::NoError) {
|
||||
auto doc = QJsonDocument::fromJson(_responseData.toLatin1());
|
||||
auto status = doc.object()["status"].toString();
|
||||
if (status == "success") {
|
||||
_marketplaceID = QUuid::fromString(doc["data"].toObject()["marketplace_id"].toString());
|
||||
_itemVersion = doc["data"].toObject()["version"].toDouble();
|
||||
setState(State::WaitingForInventory);
|
||||
doWaitForInventory();
|
||||
} else {
|
||||
qWarning() << "Got error response while uploading avatar: " << _responseData;
|
||||
setError(Error::Unknown);
|
||||
}
|
||||
} else {
|
||||
qWarning() << "Got error while uploading avatar: " << reply->error() << reply->errorString() << _responseData;
|
||||
setError(Error::Unknown);
|
||||
}
|
||||
});
|
||||
|
||||
setState(State::UploadingAvatar);
|
||||
#endif
|
||||
}
|
||||
|
||||
void MarketplaceItemUploader::doWaitForInventory() {
|
||||
static const QString path = "/api/v1/commerce/inventory";
|
||||
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
auto request = accountManager->createRequest(path, AccountManagerAuth::Required);
|
||||
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
|
||||
QNetworkReply* reply = networkAccessManager.post(request, "");
|
||||
|
||||
_numRequestsForInventory++;
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
auto data = reply->readAll();
|
||||
|
||||
bool success = false;
|
||||
|
||||
auto error = reply->error();
|
||||
if (error == QNetworkReply::NoError) {
|
||||
// Parse response data
|
||||
auto doc = QJsonDocument::fromJson(data);
|
||||
auto isAssetAvailable = [this, &doc]() -> bool {
|
||||
if (!doc.isObject()) {
|
||||
return false;
|
||||
}
|
||||
auto root = doc.object();
|
||||
auto status = root["status"].toString();
|
||||
if (status != "success") {
|
||||
return false;
|
||||
}
|
||||
auto data = root["data"];
|
||||
if (!data.isObject()) {
|
||||
return false;
|
||||
}
|
||||
auto assets = data.toObject()["assets"];
|
||||
if (!assets.isArray()) {
|
||||
return false;
|
||||
}
|
||||
for (auto asset : assets.toArray()) {
|
||||
auto assetObject = asset.toObject();
|
||||
auto id = QUuid::fromString(assetObject["id"].toString());
|
||||
if (id.isNull()) {
|
||||
continue;
|
||||
}
|
||||
if (id == _marketplaceID) {
|
||||
auto version = assetObject["version"];
|
||||
auto valid = assetObject["valid"];
|
||||
if (version.isDouble() && valid.isBool()) {
|
||||
if ((int)version.toDouble() >= _itemVersion && valid.toBool()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
success = isAssetAvailable();
|
||||
}
|
||||
if (success) {
|
||||
qDebug() << "Found item in inventory";
|
||||
setState(State::Complete);
|
||||
} else {
|
||||
constexpr int MAX_INVENTORY_REQUESTS { 8 };
|
||||
constexpr int TIME_BETWEEN_INVENTORY_REQUESTS_MS { 5000 };
|
||||
if (_numRequestsForInventory > MAX_INVENTORY_REQUESTS) {
|
||||
qDebug() << "Failed to find item in inventory";
|
||||
setError(Error::Unknown);
|
||||
} else {
|
||||
QTimer::singleShot(TIME_BETWEEN_INVENTORY_REQUESTS_MS, [this]() { doWaitForInventory(); });
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
105
interface/src/avatar/MarketplaceItemUploader.h
Normal file
105
interface/src/avatar/MarketplaceItemUploader.h
Normal file
|
@ -0,0 +1,105 @@
|
|||
//
|
||||
// MarketplaceItemUploader.h
|
||||
//
|
||||
//
|
||||
// Created by Ryan Huffman on 12/10/2018
|
||||
// Copyright 2018 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
|
||||
//
|
||||
|
||||
#pragma once
|
||||
#ifndef hifi_MarketplaceItemUploader_h
|
||||
#define hifi_MarketplaceItemUploader_h
|
||||
|
||||
#include "ProjectFile.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QUuid>
|
||||
|
||||
class QNetworkReply;
|
||||
|
||||
class MarketplaceItemUploader : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(bool finished READ getFinished NOTIFY finishedChanged)
|
||||
|
||||
Q_PROPERTY(bool complete READ getComplete NOTIFY stateChanged)
|
||||
Q_PROPERTY(State state READ getState NOTIFY stateChanged)
|
||||
Q_PROPERTY(Error error READ getError NOTIFY errorChanged)
|
||||
Q_PROPERTY(QString responseData READ getResponseData)
|
||||
public:
|
||||
enum class Error {
|
||||
None,
|
||||
Unknown,
|
||||
};
|
||||
Q_ENUM(Error);
|
||||
|
||||
enum class State {
|
||||
Idle,
|
||||
GettingCategories,
|
||||
UploadingAvatar,
|
||||
WaitingForUploadResponse,
|
||||
WaitingForInventory,
|
||||
Complete,
|
||||
};
|
||||
Q_ENUM(State);
|
||||
|
||||
MarketplaceItemUploader(QString title,
|
||||
QString description,
|
||||
QString rootFilename,
|
||||
QUuid marketplaceID,
|
||||
QList<ProjectFilePath> filePaths);
|
||||
|
||||
Q_INVOKABLE void send();
|
||||
|
||||
void setError(Error error);
|
||||
|
||||
QString getResponseData() const { return _responseData; }
|
||||
void setState(State newState);
|
||||
State getState() const { return _state; }
|
||||
bool getComplete() const { return _state == State::Complete; }
|
||||
|
||||
QUuid getMarketplaceID() const { return _marketplaceID; }
|
||||
|
||||
Error getError() const { return _error; }
|
||||
bool getFinished() const { return _state == State::Complete || _error != Error::None; }
|
||||
|
||||
signals:
|
||||
void uploadProgress(qint64 bytesSent, qint64 bytesTotal);
|
||||
void completed();
|
||||
|
||||
void stateChanged(State newState);
|
||||
void errorChanged(Error error);
|
||||
|
||||
// Triggered when the upload has finished, either succesfully completing, or stopping with an error
|
||||
void finishedChanged();
|
||||
|
||||
private:
|
||||
void doGetCategories();
|
||||
void doUploadAvatar();
|
||||
void doWaitForInventory();
|
||||
|
||||
QNetworkReply* _reply;
|
||||
|
||||
State _state { State::Idle };
|
||||
Error _error { Error::None };
|
||||
|
||||
QString _title;
|
||||
QString _description;
|
||||
QString _rootFilename;
|
||||
QUuid _marketplaceID;
|
||||
int _categoryID;
|
||||
int _itemVersion;
|
||||
|
||||
QString _responseData;
|
||||
|
||||
int _numRequestsForInventory { 0 };
|
||||
|
||||
QString _rootFilePath;
|
||||
QList<ProjectFilePath> _filePaths;
|
||||
QByteArray _fileData;
|
||||
};
|
||||
|
||||
#endif // hifi_MarketplaceItemUploader_h
|
|
@ -746,7 +746,7 @@ void MyAvatar::updateChildCauterization(SpatiallyNestablePointer object, bool ca
|
|||
void MyAvatar::simulate(float deltaTime) {
|
||||
PerformanceTimer perfTimer("simulate");
|
||||
animateScaleChanges(deltaTime);
|
||||
|
||||
|
||||
setFlyingEnabled(getFlyingEnabled());
|
||||
|
||||
if (_cauterizationNeedsUpdate) {
|
||||
|
@ -820,6 +820,7 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
// and all of its joints, now update our attachements.
|
||||
Avatar::simulateAttachments(deltaTime);
|
||||
relayJointDataToChildren();
|
||||
updateGrabs();
|
||||
|
||||
if (!_skeletonModel->hasSkeleton()) {
|
||||
// All the simulation that can be done has been done
|
||||
|
@ -874,47 +875,12 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
zoneAllowsFlying = zone->getFlyingAllowed();
|
||||
collisionlessAllowed = zone->getGhostingAllowed();
|
||||
}
|
||||
auto now = usecTimestampNow();
|
||||
EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender();
|
||||
MovingEntitiesOperator moveOperator;
|
||||
bool force = false;
|
||||
bool iShouldTellServer = true;
|
||||
forEachDescendant([&](SpatiallyNestablePointer object) {
|
||||
// if the queryBox has changed, tell the entity-server
|
||||
if (object->getNestableType() == NestableType::Entity && object->updateQueryAACube()) {
|
||||
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(object);
|
||||
bool success;
|
||||
AACube newCube = entity->getQueryAACube(success);
|
||||
if (success) {
|
||||
moveOperator.addEntityToMoveList(entity, newCube);
|
||||
}
|
||||
// send an edit packet to update the entity-server about the queryAABox
|
||||
if (packetSender && entity->isDomainEntity()) {
|
||||
EntityItemProperties properties = entity->getProperties();
|
||||
properties.setQueryAACubeDirty();
|
||||
properties.setLastEdited(now);
|
||||
|
||||
packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree,
|
||||
entity->getID(), properties);
|
||||
entity->setLastBroadcast(usecTimestampNow());
|
||||
|
||||
entity->forEachDescendant([&](SpatiallyNestablePointer descendant) {
|
||||
EntityItemPointer entityDescendant = std::dynamic_pointer_cast<EntityItem>(descendant);
|
||||
if (entityDescendant && entityDescendant->isDomainEntity() && descendant->updateQueryAACube()) {
|
||||
EntityItemProperties descendantProperties;
|
||||
descendantProperties.setQueryAACube(descendant->getQueryAACube());
|
||||
descendantProperties.setLastEdited(now);
|
||||
packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree,
|
||||
entityDescendant->getID(), descendantProperties);
|
||||
entityDescendant->setLastBroadcast(now); // for debug/physics status icons
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
entityTree->updateEntityQueryAACube(object, packetSender, force, iShouldTellServer);
|
||||
});
|
||||
// also update the position of children in our local octree
|
||||
if (moveOperator.hasMovingEntities()) {
|
||||
PerformanceTimer perfTimer("recurseTreeWithOperator");
|
||||
entityTree->recurseTreeWithOperator(&moveOperator);
|
||||
}
|
||||
});
|
||||
bool isPhysicsEnabled = qApp->isPhysicsEnabled();
|
||||
_characterController.setFlyingAllowed((zoneAllowsFlying && _enableFlying) || !isPhysicsEnabled);
|
||||
|
@ -1479,6 +1445,7 @@ void MyAvatar::loadData() {
|
|||
setSnapTurn(_useSnapTurnSetting.get());
|
||||
setDominantHand(_dominantHandSetting.get(DOMINANT_RIGHT_HAND).toLower());
|
||||
setUserHeight(_userHeightSetting.get(DEFAULT_AVATAR_HEIGHT));
|
||||
setTargetScale(_scaleSetting.get());
|
||||
|
||||
setEnableMeshVisible(Menu::getInstance()->isOptionChecked(MenuOption::MeshVisible));
|
||||
_follow.setToggleHipsFollowing (Menu::getInstance()->isOptionChecked(MenuOption::ToggleHipsFollowing));
|
||||
|
@ -1564,35 +1531,74 @@ ScriptAvatarData* MyAvatar::getTargetAvatar() const {
|
|||
}
|
||||
}
|
||||
|
||||
void MyAvatar::updateLookAtTargetAvatar() {
|
||||
//
|
||||
// Look at the avatar whose eyes are closest to the ray in direction of my avatar's head
|
||||
// And set the correctedLookAt for all (nearby) avatars that are looking at me.
|
||||
_lookAtTargetAvatar.reset();
|
||||
_targetAvatarPosition = glm::vec3(0.0f);
|
||||
static float lookAtCostFunction(const glm::vec3& myForward, const glm::vec3& myPosition, const glm::vec3& otherForward, const glm::vec3& otherPosition,
|
||||
bool otherIsTalking, bool lookingAtOtherAlready) {
|
||||
const float DISTANCE_FACTOR = 3.14f;
|
||||
const float MY_ANGLE_FACTOR = 1.0f;
|
||||
const float OTHER_ANGLE_FACTOR = 1.0f;
|
||||
const float OTHER_IS_TALKING_TERM = otherIsTalking ? 1.0f : 0.0f;
|
||||
const float LOOKING_AT_OTHER_ALREADY_TERM = lookingAtOtherAlready ? -0.2f : 0.0f;
|
||||
|
||||
glm::vec3 lookForward = getHead()->getFinalOrientationInWorldFrame() * IDENTITY_FORWARD;
|
||||
glm::vec3 cameraPosition = qApp->getCamera().getPosition();
|
||||
const float GREATEST_LOOKING_AT_DISTANCE = 10.0f; // meters
|
||||
const float MAX_MY_ANGLE = PI / 8.0f; // 22.5 degrees, Don't look too far away from the head facing.
|
||||
const float MAX_OTHER_ANGLE = (3.0f * PI) / 4.0f; // 135 degrees, Don't stare at the back of another avatars head.
|
||||
|
||||
float smallestAngleTo = glm::radians(DEFAULT_FIELD_OF_VIEW_DEGREES) / 2.0f;
|
||||
const float KEEP_LOOKING_AT_CURRENT_ANGLE_FACTOR = 1.3f;
|
||||
const float GREATEST_LOOKING_AT_DISTANCE = 10.0f;
|
||||
glm::vec3 d = otherPosition - myPosition;
|
||||
float distance = glm::length(d);
|
||||
glm::vec3 dUnit = d / distance;
|
||||
float myAngle = acosf(glm::dot(myForward, dUnit));
|
||||
float otherAngle = acosf(glm::dot(otherForward, -dUnit));
|
||||
|
||||
AvatarHash hash = DependencyManager::get<AvatarManager>()->getHashCopy();
|
||||
if (distance > GREATEST_LOOKING_AT_DISTANCE || myAngle > MAX_MY_ANGLE || otherAngle > MAX_OTHER_ANGLE) {
|
||||
return FLT_MAX;
|
||||
} else {
|
||||
return (DISTANCE_FACTOR * distance +
|
||||
MY_ANGLE_FACTOR * myAngle +
|
||||
OTHER_ANGLE_FACTOR * otherAngle +
|
||||
OTHER_IS_TALKING_TERM +
|
||||
LOOKING_AT_OTHER_ALREADY_TERM);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (const AvatarSharedPointer& avatarPointer, hash) {
|
||||
auto avatar = static_pointer_cast<Avatar>(avatarPointer);
|
||||
bool isCurrentTarget = avatar->getIsLookAtTarget();
|
||||
float distanceTo = glm::length(avatar->getHead()->getEyePosition() - cameraPosition);
|
||||
avatar->setIsLookAtTarget(false);
|
||||
if (!avatar->isMyAvatar() && avatar->isInitialized() &&
|
||||
(distanceTo < GREATEST_LOOKING_AT_DISTANCE * getModelScale())) {
|
||||
float radius = glm::length(avatar->getHead()->getEyePosition() - avatar->getHead()->getRightEyePosition());
|
||||
float angleTo = coneSphereAngle(getHead()->getEyePosition(), lookForward, avatar->getHead()->getEyePosition(), radius);
|
||||
if (angleTo < (smallestAngleTo * (isCurrentTarget ? KEEP_LOOKING_AT_CURRENT_ANGLE_FACTOR : 1.0f))) {
|
||||
_lookAtTargetAvatar = avatarPointer;
|
||||
_targetAvatarPosition = avatarPointer->getWorldPosition();
|
||||
void MyAvatar::computeMyLookAtTarget(const AvatarHash& hash) {
|
||||
glm::vec3 myForward = getHead()->getFinalOrientationInWorldFrame() * IDENTITY_FORWARD;
|
||||
glm::vec3 myPosition = getHead()->getEyePosition();
|
||||
CameraMode mode = qApp->getCamera().getMode();
|
||||
if (mode == CAMERA_MODE_FIRST_PERSON) {
|
||||
myPosition = qApp->getCamera().getPosition();
|
||||
}
|
||||
|
||||
float bestCost = FLT_MAX;
|
||||
std::shared_ptr<Avatar> bestAvatar;
|
||||
|
||||
foreach (const AvatarSharedPointer& avatarData, hash) {
|
||||
std::shared_ptr<Avatar> avatar = std::static_pointer_cast<Avatar>(avatarData);
|
||||
if (!avatar->isMyAvatar() && avatar->isInitialized()) {
|
||||
glm::vec3 otherForward = avatar->getHead()->getForwardDirection();
|
||||
glm::vec3 otherPosition = avatar->getHead()->getEyePosition();
|
||||
const float TIME_WITHOUT_TALKING_THRESHOLD = 1.0f;
|
||||
bool otherIsTalking = avatar->getHead()->getTimeWithoutTalking() <= TIME_WITHOUT_TALKING_THRESHOLD;
|
||||
bool lookingAtOtherAlready = _lookAtTargetAvatar.lock().get() == avatar.get();
|
||||
float cost = lookAtCostFunction(myForward, myPosition, otherForward, otherPosition, otherIsTalking, lookingAtOtherAlready);
|
||||
if (cost < bestCost) {
|
||||
bestCost = cost;
|
||||
bestAvatar = avatar;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestAvatar) {
|
||||
_lookAtTargetAvatar = bestAvatar;
|
||||
_targetAvatarPosition = bestAvatar->getWorldPosition();
|
||||
} else {
|
||||
_lookAtTargetAvatar.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::snapOtherAvatarLookAtTargetsToMe(const AvatarHash& hash) {
|
||||
foreach (const AvatarSharedPointer& avatarData, hash) {
|
||||
std::shared_ptr<Avatar> avatar = std::static_pointer_cast<Avatar>(avatarData);
|
||||
if (!avatar->isMyAvatar() && avatar->isInitialized()) {
|
||||
if (_lookAtSnappingEnabled && avatar->getLookAtSnappingEnabled() && isLookingAtMe(avatar)) {
|
||||
|
||||
// Alter their gaze to look directly at my camera; this looks more natural than looking at my avatar's face.
|
||||
|
@ -1647,10 +1653,19 @@ void MyAvatar::updateLookAtTargetAvatar() {
|
|||
avatar->getHead()->clearCorrectedLookAtPosition();
|
||||
}
|
||||
}
|
||||
auto avatarPointer = _lookAtTargetAvatar.lock();
|
||||
if (avatarPointer) {
|
||||
static_pointer_cast<Avatar>(avatarPointer)->setIsLookAtTarget(true);
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::updateLookAtTargetAvatar() {
|
||||
|
||||
// The AvatarManager is a mutable class shared by many threads. We make a thread-safe deep copy of it,
|
||||
// to avoid having to hold a lock while we iterate over all the avatars within.
|
||||
AvatarHash hash = DependencyManager::get<AvatarManager>()->getHashCopy();
|
||||
|
||||
// determine what the best look at target for my avatar should be.
|
||||
computeMyLookAtTarget(hash);
|
||||
|
||||
// snap look at position for avatars that are looking at me.
|
||||
snapOtherAvatarLookAtTargetsToMe(hash);
|
||||
}
|
||||
|
||||
void MyAvatar::clearLookAtTargetAvatar() {
|
||||
|
@ -2417,10 +2432,10 @@ void MyAvatar::attachmentDataToEntityProperties(const AttachmentData& data, Enti
|
|||
void MyAvatar::initHeadBones() {
|
||||
int neckJointIndex = -1;
|
||||
if (_skeletonModel->isLoaded()) {
|
||||
neckJointIndex = _skeletonModel->getHFMModel().neckJointIndex;
|
||||
neckJointIndex = getJointIndex("Neck");
|
||||
}
|
||||
if (neckJointIndex == -1) {
|
||||
neckJointIndex = (_skeletonModel->getHFMModel().headJointIndex - 1);
|
||||
neckJointIndex = (getJointIndex("Head") - 1);
|
||||
if (neckJointIndex < 0) {
|
||||
// return if the head is not even there. can't cauterize!!
|
||||
return;
|
||||
|
@ -3186,17 +3201,15 @@ bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& bette
|
|||
OctreeElementPointer element;
|
||||
float distance;
|
||||
BoxFace face;
|
||||
const bool visibleOnly = false;
|
||||
// This isn't quite what we really want here. findRayIntersection always works on mesh, skipping entirely based on collidable.
|
||||
// What we really want is to use the collision hull!
|
||||
// See https://highfidelity.fogbugz.com/f/cases/5003/findRayIntersection-has-option-to-use-collidableOnly-but-doesn-t-actually-use-colliders
|
||||
const bool collidableOnly = true;
|
||||
const bool precisionPicking = true;
|
||||
const auto lockType = Octree::Lock; // Should we refactor to take a lock just once?
|
||||
bool* accurateResult = NULL;
|
||||
|
||||
// This isn't quite what we really want here. findRayIntersection always works on mesh, skipping entirely based on collidable.
|
||||
// What we really want is to use the collision hull!
|
||||
// See https://highfidelity.fogbugz.com/f/cases/5003/findRayIntersection-has-option-to-use-collidableOnly-but-doesn-t-actually-use-colliders
|
||||
QVariantMap extraInfo;
|
||||
EntityItemID entityID = entityTree->findRayIntersection(startPointIn, directionIn, include, ignore, visibleOnly, collidableOnly, precisionPicking,
|
||||
EntityItemID entityID = entityTree->evalRayIntersection(startPointIn, directionIn, include, ignore,
|
||||
PickFilter(PickFilter::getBitMask(PickFilter::FlagBit::COLLIDABLE) | PickFilter::getBitMask(PickFilter::FlagBit::PRECISE)),
|
||||
element, distance, face, normalOut, extraInfo, lockType, accurateResult);
|
||||
if (entityID.isNull()) {
|
||||
return false;
|
||||
|
@ -3361,7 +3374,6 @@ void MyAvatar::setCollisionsEnabled(bool enabled) {
|
|||
QMetaObject::invokeMethod(this, "setCollisionsEnabled", Q_ARG(bool, enabled));
|
||||
return;
|
||||
}
|
||||
|
||||
_characterController.setCollisionless(!enabled);
|
||||
emit collisionsEnabledChanged(enabled);
|
||||
}
|
||||
|
@ -3372,6 +3384,20 @@ bool MyAvatar::getCollisionsEnabled() {
|
|||
return _characterController.computeCollisionGroup() != BULLET_COLLISION_GROUP_COLLISIONLESS;
|
||||
}
|
||||
|
||||
void MyAvatar::setOtherAvatarsCollisionsEnabled(bool enabled) {
|
||||
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setOtherAvatarsCollisionsEnabled", Q_ARG(bool, enabled));
|
||||
return;
|
||||
}
|
||||
_collideWithOtherAvatars = enabled;
|
||||
emit otherAvatarsCollisionsEnabledChanged(enabled);
|
||||
}
|
||||
|
||||
bool MyAvatar::getOtherAvatarsCollisionsEnabled() {
|
||||
return _collideWithOtherAvatars;
|
||||
}
|
||||
|
||||
void MyAvatar::updateCollisionCapsuleCache() {
|
||||
glm::vec3 start, end;
|
||||
float radius;
|
||||
|
@ -4747,3 +4773,50 @@ SpatialParentTree* MyAvatar::getParentTree() const {
|
|||
EntityTreePointer entityTree = entityTreeRenderer ? entityTreeRenderer->getTree() : nullptr;
|
||||
return entityTree.get();
|
||||
}
|
||||
|
||||
const QUuid MyAvatar::grab(const QUuid& targetID, int parentJointIndex,
|
||||
glm::vec3 positionalOffset, glm::quat rotationalOffset) {
|
||||
auto grabID = QUuid::createUuid();
|
||||
// create a temporary grab object to get grabData
|
||||
|
||||
QString hand = "none";
|
||||
if (parentJointIndex == CONTROLLER_RIGHTHAND_INDEX ||
|
||||
parentJointIndex == CAMERA_RELATIVE_CONTROLLER_RIGHTHAND_INDEX ||
|
||||
parentJointIndex == FARGRAB_RIGHTHAND_INDEX ||
|
||||
parentJointIndex == getJointIndex("RightHand")) {
|
||||
hand = "right";
|
||||
} else if (parentJointIndex == CONTROLLER_LEFTHAND_INDEX ||
|
||||
parentJointIndex == CAMERA_RELATIVE_CONTROLLER_LEFTHAND_INDEX ||
|
||||
parentJointIndex == FARGRAB_LEFTHAND_INDEX ||
|
||||
parentJointIndex == getJointIndex("LeftHand")) {
|
||||
hand = "left";
|
||||
}
|
||||
|
||||
Grab tmpGrab(DependencyManager::get<NodeList>()->getSessionUUID(),
|
||||
targetID, parentJointIndex, hand, positionalOffset, rotationalOffset);
|
||||
QByteArray grabData = tmpGrab.toByteArray();
|
||||
bool dataChanged = updateAvatarGrabData(grabID, grabData);
|
||||
|
||||
if (dataChanged && _clientTraitsHandler) {
|
||||
// indicate that the changed data should be sent to the mixer
|
||||
_clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::Grab, grabID);
|
||||
}
|
||||
|
||||
return grabID;
|
||||
}
|
||||
|
||||
void MyAvatar::releaseGrab(const QUuid& grabID) {
|
||||
bool tellHandler { false };
|
||||
|
||||
_avatarGrabsLock.withWriteLock([&] {
|
||||
if (_avatarGrabData.remove(grabID)) {
|
||||
_deletedAvatarGrabs.insert(grabID);
|
||||
tellHandler = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (tellHandler && _clientTraitsHandler) {
|
||||
// indicate the deletion of the data to the mixer
|
||||
_clientTraitsHandler->markInstancedTraitDeleted(AvatarTraits::Grab, grabID);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -225,6 +225,7 @@ class MyAvatar : public Avatar {
|
|||
Q_PROPERTY(bool centerOfGravityModelEnabled READ getCenterOfGravityModelEnabled WRITE setCenterOfGravityModelEnabled)
|
||||
Q_PROPERTY(bool hmdLeanRecenterEnabled READ getHMDLeanRecenterEnabled WRITE setHMDLeanRecenterEnabled)
|
||||
Q_PROPERTY(bool collisionsEnabled READ getCollisionsEnabled WRITE setCollisionsEnabled)
|
||||
Q_PROPERTY(bool otherAvatarsCollisionsEnabled READ getOtherAvatarsCollisionsEnabled WRITE setOtherAvatarsCollisionsEnabled)
|
||||
Q_PROPERTY(bool characterControllerEnabled READ getCharacterControllerEnabled WRITE setCharacterControllerEnabled)
|
||||
Q_PROPERTY(bool useAdvancedMovementControls READ useAdvancedMovementControls WRITE setUseAdvancedMovementControls)
|
||||
Q_PROPERTY(bool showPlayArea READ getShowPlayArea WRITE setShowPlayArea)
|
||||
|
@ -832,6 +833,8 @@ public:
|
|||
|
||||
AvatarWeakPointer getLookAtTargetAvatar() const { return _lookAtTargetAvatar; }
|
||||
void updateLookAtTargetAvatar();
|
||||
void computeMyLookAtTarget(const AvatarHash& hash);
|
||||
void snapOtherAvatarLookAtTargetsToMe(const AvatarHash& hash);
|
||||
void clearLookAtTargetAvatar();
|
||||
|
||||
virtual void setJointRotations(const QVector<glm::quat>& jointRotations) override;
|
||||
|
@ -1062,6 +1065,18 @@ public:
|
|||
*/
|
||||
Q_INVOKABLE bool getCollisionsEnabled();
|
||||
|
||||
/**jsdoc
|
||||
* @function MyAvatar.setOtherAvatarsCollisionsEnabled
|
||||
* @param {boolean} enabled
|
||||
*/
|
||||
Q_INVOKABLE void setOtherAvatarsCollisionsEnabled(bool enabled);
|
||||
|
||||
/**jsdoc
|
||||
* @function MyAvatar.getOtherAvatarsCollisionsEnabled
|
||||
* @returns {boolean}
|
||||
*/
|
||||
Q_INVOKABLE bool getOtherAvatarsCollisionsEnabled();
|
||||
|
||||
/**jsdoc
|
||||
* @function MyAvatar.getCollisionCapsule
|
||||
* @returns {object}
|
||||
|
@ -1170,6 +1185,25 @@ public:
|
|||
|
||||
glm::vec3 getNextPosition() { return _goToPending ? _goToPosition : getWorldPosition(); }
|
||||
|
||||
/**jsdoc
|
||||
* Create a new grab.
|
||||
* @function MyAvatar.grab
|
||||
* @param {Uuid} targetID - id of grabbed thing
|
||||
* @param {number} parentJointIndex - avatar joint being used to grab
|
||||
* @param {Vec3} offset - target's positional offset from joint
|
||||
* @param {Quat} rotationalOffset - target's rotational offset from joint
|
||||
* @returns {Uuid} id of the new grab
|
||||
*/
|
||||
Q_INVOKABLE const QUuid grab(const QUuid& targetID, int parentJointIndex,
|
||||
glm::vec3 positionalOffset, glm::quat rotationalOffset);
|
||||
|
||||
/**jsdoc
|
||||
* Release (delete) a grab.
|
||||
* @function MyAvatar.releaseGrab
|
||||
* @param {Uuid} grabID - id of grabbed thing
|
||||
*/
|
||||
Q_INVOKABLE void releaseGrab(const QUuid& grabID);
|
||||
|
||||
public slots:
|
||||
|
||||
/**jsdoc
|
||||
|
@ -1470,6 +1504,14 @@ signals:
|
|||
*/
|
||||
void collisionsEnabledChanged(bool enabled);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when collisions with other avatars enabled or disabled
|
||||
* @function MyAvatar.otherAvatarsCollisionsEnabledChanged
|
||||
* @param {boolean} enabled
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void otherAvatarsCollisionsEnabledChanged(bool enabled);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when avatar's animation url changes
|
||||
* @function MyAvatar.animGraphUrlChanged
|
||||
|
|
|
@ -301,8 +301,8 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
eyeParams.eyeSaccade = head->getSaccade();
|
||||
eyeParams.modelRotation = getRotation();
|
||||
eyeParams.modelTranslation = getTranslation();
|
||||
eyeParams.leftEyeJointIndex = hfmModel.leftEyeJointIndex;
|
||||
eyeParams.rightEyeJointIndex = hfmModel.rightEyeJointIndex;
|
||||
eyeParams.leftEyeJointIndex = _rig.indexOfJoint("LeftEye");
|
||||
eyeParams.rightEyeJointIndex = _rig.indexOfJoint("RightEye");
|
||||
|
||||
_rig.updateFromEyeParameters(eyeParams);
|
||||
|
||||
|
|
|
@ -120,7 +120,7 @@ bool OtherAvatar::shouldBeInPhysicsSimulation() const {
|
|||
}
|
||||
|
||||
bool OtherAvatar::needsPhysicsUpdate() const {
|
||||
constexpr uint32_t FLAGS_OF_INTEREST = Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS | Simulation::DIRTY_POSITION;
|
||||
constexpr uint32_t FLAGS_OF_INTEREST = Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS | Simulation::DIRTY_POSITION | Simulation::DIRTY_COLLISION_GROUP;
|
||||
return (_motionState && (bool)(_motionState->getIncomingDirtyFlags() & FLAGS_OF_INTEREST));
|
||||
}
|
||||
|
||||
|
@ -129,3 +129,17 @@ void OtherAvatar::rebuildCollisionShape() {
|
|||
_motionState->addDirtyFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS);
|
||||
}
|
||||
}
|
||||
|
||||
void OtherAvatar::updateCollisionGroup(bool myAvatarCollide) {
|
||||
if (_motionState) {
|
||||
bool collides = _motionState->getCollisionGroup() == BULLET_COLLISION_GROUP_OTHER_AVATAR && myAvatarCollide;
|
||||
if (_collideWithOtherAvatars != collides) {
|
||||
if (!myAvatarCollide) {
|
||||
_collideWithOtherAvatars = false;
|
||||
}
|
||||
auto newCollisionGroup = _collideWithOtherAvatars ? BULLET_COLLISION_GROUP_OTHER_AVATAR : BULLET_COLLISION_GROUP_COLLISIONLESS;
|
||||
_motionState->setCollisionGroup(newCollisionGroup);
|
||||
_motionState->addDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -45,6 +45,8 @@ public:
|
|||
bool shouldBeInPhysicsSimulation() const;
|
||||
bool needsPhysicsUpdate() const;
|
||||
|
||||
void updateCollisionGroup(bool myAvatarCollide);
|
||||
|
||||
friend AvatarManager;
|
||||
|
||||
protected:
|
||||
|
|
|
@ -125,7 +125,7 @@ LaserPointer::RenderState::RenderState(const OverlayID& startID, const OverlayID
|
|||
StartEndRenderState(startID, endID), _pathID(pathID)
|
||||
{
|
||||
if (!_pathID.isNull()) {
|
||||
_pathIgnoreRays = qApp->getOverlays().getProperty(_pathID, "ignoreRayIntersection").value.toBool();
|
||||
_pathIgnoreRays = qApp->getOverlays().getProperty(_pathID, "ignorePickIntersection").value.toBool();
|
||||
_lineWidth = qApp->getOverlays().getProperty(_pathID, "lineWidth").value.toFloat();
|
||||
}
|
||||
}
|
||||
|
@ -142,7 +142,7 @@ void LaserPointer::RenderState::disable() {
|
|||
if (!getPathID().isNull()) {
|
||||
QVariantMap pathProps;
|
||||
pathProps.insert("visible", false);
|
||||
pathProps.insert("ignoreRayIntersection", true);
|
||||
pathProps.insert("ignorePickIntersection", true);
|
||||
qApp->getOverlays().editOverlay(getPathID(), pathProps);
|
||||
}
|
||||
}
|
||||
|
@ -156,7 +156,7 @@ void LaserPointer::RenderState::update(const glm::vec3& origin, const glm::vec3&
|
|||
pathProps.insert("start", vec3toVariant(origin));
|
||||
pathProps.insert("end", endVariant);
|
||||
pathProps.insert("visible", true);
|
||||
pathProps.insert("ignoreRayIntersection", doesPathIgnoreRays());
|
||||
pathProps.insert("ignorePickIntersection", doesPathIgnoreRays());
|
||||
pathProps.insert("lineWidth", getLineWidth() * parentScale);
|
||||
qApp->getOverlays().editOverlay(getPathID(), pathProps);
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ class LaserPointerScriptingInterface : public QObject, public Dependency {
|
|||
SINGLETON_DEPENDENCY
|
||||
|
||||
/**jsdoc
|
||||
* Synonym for {@link Pointers} as used for laser pointers.
|
||||
* Synonym for {@link Pointers} as used for laser pointers. Deprecated.
|
||||
*
|
||||
* @namespace LaserPointers
|
||||
*
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include "Application.h"
|
||||
#include "EntityScriptingInterface.h"
|
||||
#include "PickScriptingInterface.h"
|
||||
#include "ui/overlays/Overlays.h"
|
||||
#include "avatar/AvatarManager.h"
|
||||
#include "scripting/HMDScriptingInterface.h"
|
||||
|
@ -57,10 +58,15 @@ PickParabola ParabolaPick::getMathematicalPick() const {
|
|||
|
||||
PickResultPointer ParabolaPick::getEntityIntersection(const PickParabola& pick) {
|
||||
if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) {
|
||||
bool precisionPicking = !(getFilter().doesPickCoarse() || DependencyManager::get<PickManager>()->getForceCoarsePicking());
|
||||
PickFilter searchFilter = getFilter();
|
||||
if (DependencyManager::get<PickManager>()->getForceCoarsePicking()) {
|
||||
searchFilter.setFlag(PickFilter::COARSE, true);
|
||||
searchFilter.setFlag(PickFilter::PRECISE, false);
|
||||
}
|
||||
|
||||
ParabolaToEntityIntersectionResult entityRes =
|
||||
DependencyManager::get<EntityScriptingInterface>()->findParabolaIntersectionVector(pick, precisionPicking,
|
||||
getIncludeItemsAs<EntityItemID>(), getIgnoreItemsAs<EntityItemID>(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable());
|
||||
DependencyManager::get<EntityScriptingInterface>()->evalParabolaIntersectionVector(pick, searchFilter,
|
||||
getIncludeItemsAs<EntityItemID>(), getIgnoreItemsAs<EntityItemID>());
|
||||
if (entityRes.intersects) {
|
||||
return std::make_shared<ParabolaPickResult>(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.parabolicDistance, entityRes.intersection, pick, entityRes.surfaceNormal, entityRes.extraInfo);
|
||||
}
|
||||
|
@ -70,7 +76,7 @@ PickResultPointer ParabolaPick::getEntityIntersection(const PickParabola& pick)
|
|||
|
||||
PickResultPointer ParabolaPick::getOverlayIntersection(const PickParabola& pick) {
|
||||
if (glm::length2(pick.acceleration) > EPSILON && glm::length2(pick.velocity) > EPSILON) {
|
||||
bool precisionPicking = !(getFilter().doesPickCoarse() || DependencyManager::get<PickManager>()->getForceCoarsePicking());
|
||||
bool precisionPicking = !(getFilter().isCoarse() || DependencyManager::get<PickManager>()->getForceCoarsePicking());
|
||||
ParabolaToOverlayIntersectionResult overlayRes =
|
||||
qApp->getOverlays().findParabolaIntersectionVector(pick, precisionPicking,
|
||||
getIncludeItemsAs<OverlayID>(), getIgnoreItemsAs<OverlayID>(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable());
|
||||
|
|
|
@ -253,12 +253,12 @@ StartEndRenderState::StartEndRenderState(const OverlayID& startID, const Overlay
|
|||
_startID(startID), _endID(endID) {
|
||||
if (!_startID.isNull()) {
|
||||
_startDim = vec3FromVariant(qApp->getOverlays().getProperty(_startID, "dimensions").value);
|
||||
_startIgnoreRays = qApp->getOverlays().getProperty(_startID, "ignoreRayIntersection").value.toBool();
|
||||
_startIgnoreRays = qApp->getOverlays().getProperty(_startID, "ignorePickIntersection").value.toBool();
|
||||
}
|
||||
if (!_endID.isNull()) {
|
||||
_endDim = vec3FromVariant(qApp->getOverlays().getProperty(_endID, "dimensions").value);
|
||||
_endRot = quatFromVariant(qApp->getOverlays().getProperty(_endID, "rotation").value);
|
||||
_endIgnoreRays = qApp->getOverlays().getProperty(_endID, "ignoreRayIntersection").value.toBool();
|
||||
_endIgnoreRays = qApp->getOverlays().getProperty(_endID, "ignorePickIntersection").value.toBool();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -275,13 +275,13 @@ void StartEndRenderState::disable() {
|
|||
if (!getStartID().isNull()) {
|
||||
QVariantMap startProps;
|
||||
startProps.insert("visible", false);
|
||||
startProps.insert("ignoreRayIntersection", true);
|
||||
startProps.insert("ignorePickIntersection", true);
|
||||
qApp->getOverlays().editOverlay(getStartID(), startProps);
|
||||
}
|
||||
if (!getEndID().isNull()) {
|
||||
QVariantMap endProps;
|
||||
endProps.insert("visible", false);
|
||||
endProps.insert("ignoreRayIntersection", true);
|
||||
endProps.insert("ignorePickIntersection", true);
|
||||
qApp->getOverlays().editOverlay(getEndID(), endProps);
|
||||
}
|
||||
_enabled = false;
|
||||
|
@ -294,7 +294,7 @@ void StartEndRenderState::update(const glm::vec3& origin, const glm::vec3& end,
|
|||
startProps.insert("position", vec3toVariant(origin));
|
||||
startProps.insert("visible", true);
|
||||
startProps.insert("dimensions", vec3toVariant(getStartDim() * parentScale));
|
||||
startProps.insert("ignoreRayIntersection", doesStartIgnoreRays());
|
||||
startProps.insert("ignorePickIntersection", doesStartIgnoreRays());
|
||||
qApp->getOverlays().editOverlay(getStartID(), startProps);
|
||||
}
|
||||
|
||||
|
@ -346,7 +346,7 @@ void StartEndRenderState::update(const glm::vec3& origin, const glm::vec3& end,
|
|||
endProps.insert("position", vec3toVariant(position));
|
||||
endProps.insert("rotation", quatToVariant(rotation));
|
||||
endProps.insert("visible", true);
|
||||
endProps.insert("ignoreRayIntersection", doesEndIgnoreRays());
|
||||
endProps.insert("ignorePickIntersection", doesEndIgnoreRays());
|
||||
qApp->getOverlays().editOverlay(getEndID(), endProps);
|
||||
}
|
||||
_enabled = true;
|
||||
|
|
|
@ -49,11 +49,17 @@ unsigned int PickScriptingInterface::createPick(const PickQuery::PickType type,
|
|||
}
|
||||
}
|
||||
|
||||
PickFilter getPickFilter(unsigned int filter) {
|
||||
// FIXME: Picks always intersect visible and collidable things right now
|
||||
filter = filter | (PickScriptingInterface::PICK_INCLUDE_VISIBLE() | PickScriptingInterface::PICK_INCLUDE_COLLIDABLE());
|
||||
return PickFilter(filter);
|
||||
}
|
||||
|
||||
/**jsdoc
|
||||
* A set of properties that can be passed to {@link Picks.createPick} to create a new Ray Pick.
|
||||
* @typedef {object} Picks.RayPickProperties
|
||||
* @property {boolean} [enabled=false] If this Pick should start enabled or not. Disabled Picks do not updated their pick results.
|
||||
* @property {number} [filter=Picks.PICK_NOTHING] The filter for this Pick to use, constructed using filter flags combined using bitwise OR.
|
||||
* @property {number} [filter=0] The filter for this Pick to use, constructed using filter flags combined using bitwise OR.
|
||||
* @property {number} [maxDistance=0.0] The max distance at which this Pick will intersect. 0.0 = no max. < 0.0 is invalid.
|
||||
* @property {Uuid} parentID - The ID of the parent, either an avatar, an entity, an overlay, or a pick.
|
||||
* @property {number} [parentJointIndex=0] - The joint of the parent to parent to, for example, the joints on the model of an avatar. (default = 0, no joint)
|
||||
|
@ -73,7 +79,7 @@ unsigned int PickScriptingInterface::createRayPick(const QVariant& properties) {
|
|||
|
||||
PickFilter filter = PickFilter();
|
||||
if (propMap["filter"].isValid()) {
|
||||
filter = PickFilter(propMap["filter"].toUInt());
|
||||
filter = getPickFilter(propMap["filter"].toUInt());
|
||||
}
|
||||
|
||||
float maxDistance = 0.0f;
|
||||
|
@ -111,7 +117,7 @@ unsigned int PickScriptingInterface::createRayPick(const QVariant& properties) {
|
|||
* @typedef {object} Picks.StylusPickProperties
|
||||
* @property {number} [hand=-1] An integer. 0 == left, 1 == right. Invalid otherwise.
|
||||
* @property {boolean} [enabled=false] If this Pick should start enabled or not. Disabled Picks do not updated their pick results.
|
||||
* @property {number} [filter=Picks.PICK_NOTHING] The filter for this Pick to use, constructed using filter flags combined using bitwise OR.
|
||||
* @property {number} [filter=0] The filter for this Pick to use, constructed using filter flags combined using bitwise OR.
|
||||
* @property {number} [maxDistance=0.0] The max distance at which this Pick will intersect. 0.0 = no max. < 0.0 is invalid.
|
||||
*/
|
||||
unsigned int PickScriptingInterface::createStylusPick(const QVariant& properties) {
|
||||
|
@ -132,7 +138,7 @@ unsigned int PickScriptingInterface::createStylusPick(const QVariant& properties
|
|||
|
||||
PickFilter filter = PickFilter();
|
||||
if (propMap["filter"].isValid()) {
|
||||
filter = PickFilter(propMap["filter"].toUInt());
|
||||
filter = getPickFilter(propMap["filter"].toUInt());
|
||||
}
|
||||
|
||||
float maxDistance = 0.0f;
|
||||
|
@ -153,7 +159,7 @@ unsigned int PickScriptingInterface::createStylusPick(const QVariant& properties
|
|||
* A set of properties that can be passed to {@link Picks.createPick} to create a new Parabola Pick.
|
||||
* @typedef {object} Picks.ParabolaPickProperties
|
||||
* @property {boolean} [enabled=false] If this Pick should start enabled or not. Disabled Picks do not updated their pick results.
|
||||
* @property {number} [filter=Picks.PICK_NOTHING] The filter for this Pick to use, constructed using filter flags combined using bitwise OR.
|
||||
* @property {number} [filter=0] The filter for this Pick to use, constructed using filter flags combined using bitwise OR.
|
||||
* @property {number} [maxDistance=0.0] The max distance at which this Pick will intersect. 0.0 = no max. < 0.0 is invalid.
|
||||
* @property {Uuid} parentID - The ID of the parent, either an avatar, an entity, an overlay, or a pick.
|
||||
* @property {number} [parentJointIndex=0] - The joint of the parent to parent to, for example, the joints on the model of an avatar. (default = 0, no joint)
|
||||
|
@ -178,7 +184,7 @@ unsigned int PickScriptingInterface::createParabolaPick(const QVariant& properti
|
|||
|
||||
PickFilter filter = PickFilter();
|
||||
if (propMap["filter"].isValid()) {
|
||||
filter = PickFilter(propMap["filter"].toUInt());
|
||||
filter = getPickFilter(propMap["filter"].toUInt());
|
||||
}
|
||||
|
||||
float maxDistance = 0.0f;
|
||||
|
@ -250,7 +256,7 @@ unsigned int PickScriptingInterface::createParabolaPick(const QVariant& properti
|
|||
|
||||
* @typedef {object} Picks.CollisionPickProperties
|
||||
* @property {boolean} [enabled=false] If this Pick should start enabled or not. Disabled Picks do not updated their pick results.
|
||||
* @property {number} [filter=Picks.PICK_NOTHING] The filter for this Pick to use, constructed using filter flags combined using bitwise OR.
|
||||
* @property {number} [filter=0] The filter for this Pick to use, constructed using filter flags combined using bitwise OR.
|
||||
* @property {Shape} shape - The information about the collision region's size and shape. Dimensions are in world space, but will scale with the parent if defined.
|
||||
* @property {Vec3} position - The position of the collision region, relative to a parent if defined.
|
||||
* @property {Quat} orientation - The orientation of the collision region, relative to a parent if defined.
|
||||
|
@ -273,7 +279,7 @@ unsigned int PickScriptingInterface::createCollisionPick(const QVariant& propert
|
|||
|
||||
PickFilter filter = PickFilter();
|
||||
if (propMap["filter"].isValid()) {
|
||||
filter = PickFilter(propMap["filter"].toUInt());
|
||||
filter = getPickFilter(propMap["filter"].toUInt());
|
||||
}
|
||||
|
||||
float maxDistance = 0.0f;
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <DependencyManager.h>
|
||||
#include <PhysicsEngine.h>
|
||||
#include <Pick.h>
|
||||
#include <PickFilter.h>
|
||||
|
||||
/**jsdoc
|
||||
* The Picks API lets you create and manage objects for repeatedly calculating intersections in different ways.
|
||||
|
@ -23,41 +24,62 @@
|
|||
* @hifi-interface
|
||||
* @hifi-client-entity
|
||||
*
|
||||
* @property {number} PICK_NOTHING A filter flag. Don't intersect with anything. <em>Read-only.</em>
|
||||
* @property {number} PICK_ENTITIES A filter flag. Include entities when intersecting. <em>Read-only.</em>
|
||||
* @property {number} PICK_OVERLAYS A filter flag. Include overlays when intersecting. <em>Read-only.</em>
|
||||
* @property {number} PICK_AVATARS A filter flag. Include avatars when intersecting. <em>Read-only.</em>
|
||||
* @property {number} PICK_HUD A filter flag. Include the HUD sphere when intersecting in HMD mode. <em>Read-only.</em>
|
||||
* @property {number} PICK_COARSE A filter flag. Pick against coarse meshes, instead of exact meshes. <em>Read-only.</em>
|
||||
* @property {number} PICK_INCLUDE_INVISIBLE A filter flag. Include invisible objects when intersecting. <em>Read-only.</em>
|
||||
* @property {number} PICK_INCLUDE_NONCOLLIDABLE A filter flag. Include non-collidable objects when intersecting.
|
||||
* <em>Read-only.</em>
|
||||
* @property {number} PICK_ALL_INTERSECTIONS <em>Read-only.</em>
|
||||
* @property {number} INTERSECTED_NONE An intersection type. Intersected nothing with the given filter flags.
|
||||
* <em>Read-only.</em>
|
||||
* @property {number} PICK_ENTITIES A filter flag. Include domain and avatar entities when intersecting. <em>Read-only.</em>. Deprecated.
|
||||
* @property {number} PICK_OVERLAYS A filter flag. Include local entities when intersecting. <em>Read-only.</em>. Deprecated.
|
||||
*
|
||||
* @property {number} PICK_DOMAIN_ENTITIES A filter flag. Include domain entities when intersecting. <em>Read-only.</em>.
|
||||
* @property {number} PICK_AVATAR_ENTITIES A filter flag. Include avatar entities when intersecting. <em>Read-only.</em>.
|
||||
* @property {number} PICK_LOCAL_ENTITIES A filter flag. Include local entities when intersecting. <em>Read-only.</em>.
|
||||
* @property {number} PICK_AVATARS A filter flag. Include avatars when intersecting. <em>Read-only.</em>.
|
||||
* @property {number} PICK_HUD A filter flag. Include the HUD sphere when intersecting in HMD mode. <em>Read-only.</em>.
|
||||
*
|
||||
* @property {number} PICK_INCLUDE_VISIBLE A filter flag. Include visible objects when intersecting. <em>Read-only.</em>.
|
||||
* @property {number} PICK_INCLUDE_INVISIBLE A filter flag. Include invisible objects when intersecting. <em>Read-only.</em>.
|
||||
*
|
||||
* @property {number} PICK_INCLUDE_COLLIDABLE A filter flag. Include collidable objects when intersecting. <em>Read-only.</em>.
|
||||
* @property {number} PICK_INCLUDE_NONCOLLIDABLE A filter flag. Include non-collidable objects when intersecting. <em>Read-only.</em>.
|
||||
*
|
||||
* @property {number} PICK_PRECISE A filter flag. Pick against exact meshes. <em>Read-only.</em>.
|
||||
* @property {number} PICK_COARSE A filter flag. Pick against coarse meshes. <em>Read-only.</em>.
|
||||
*
|
||||
* @property {number} PICK_ALL_INTERSECTIONS <em>Read-only.</em>.
|
||||
*
|
||||
* @property {number} INTERSECTED_NONE An intersection type. Intersected nothing with the given filter flags. <em>Read-only.</em>
|
||||
* @property {number} INTERSECTED_ENTITY An intersection type. Intersected an entity. <em>Read-only.</em>
|
||||
* @property {number} INTERSECTED_OVERLAY An intersection type. Intersected an overlay. <em>Read-only.</em>
|
||||
* @property {number} INTERSECTED_AVATAR An intersection type. Intersected an avatar. <em>Read-only.</em>
|
||||
* @property {number} INTERSECTED_HUD An intersection type. Intersected the HUD sphere. <em>Read-only.</em>
|
||||
* @property {number} perFrameTimeBudget - The max number of usec to spend per frame updating Pick results. <em>Read-only.</em>
|
||||
* @property {number} perFrameTimeBudget - The max number of usec to spend per frame updating Pick results.
|
||||
*/
|
||||
|
||||
class PickScriptingInterface : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(unsigned int PICK_NOTHING READ PICK_NOTHING CONSTANT)
|
||||
Q_PROPERTY(unsigned int PICK_ENTITIES READ PICK_ENTITIES CONSTANT)
|
||||
Q_PROPERTY(unsigned int PICK_OVERLAYS READ PICK_OVERLAYS CONSTANT)
|
||||
|
||||
Q_PROPERTY(unsigned int PICK_DOMAIN_ENTITIES READ PICK_DOMAIN_ENTITIES CONSTANT)
|
||||
Q_PROPERTY(unsigned int PICK_AVATAR_ENTITIES READ PICK_AVATAR_ENTITIES CONSTANT)
|
||||
Q_PROPERTY(unsigned int PICK_LOCAL_ENTITIES READ PICK_LOCAL_ENTITIES CONSTANT)
|
||||
Q_PROPERTY(unsigned int PICK_AVATARS READ PICK_AVATARS CONSTANT)
|
||||
Q_PROPERTY(unsigned int PICK_HUD READ PICK_HUD CONSTANT)
|
||||
Q_PROPERTY(unsigned int PICK_COARSE READ PICK_COARSE CONSTANT)
|
||||
|
||||
Q_PROPERTY(unsigned int PICK_INCLUDE_VISIBLE READ PICK_INCLUDE_VISIBLE CONSTANT)
|
||||
Q_PROPERTY(unsigned int PICK_INCLUDE_INVISIBLE READ PICK_INCLUDE_INVISIBLE CONSTANT)
|
||||
|
||||
Q_PROPERTY(unsigned int PICK_INCLUDE_COLLIDABLE READ PICK_INCLUDE_COLLIDABLE CONSTANT)
|
||||
Q_PROPERTY(unsigned int PICK_INCLUDE_NONCOLLIDABLE READ PICK_INCLUDE_NONCOLLIDABLE CONSTANT)
|
||||
|
||||
Q_PROPERTY(unsigned int PICK_PRECISE READ PICK_PRECISE CONSTANT)
|
||||
Q_PROPERTY(unsigned int PICK_COARSE READ PICK_COARSE CONSTANT)
|
||||
|
||||
Q_PROPERTY(unsigned int PICK_ALL_INTERSECTIONS READ PICK_ALL_INTERSECTIONS CONSTANT)
|
||||
|
||||
Q_PROPERTY(unsigned int INTERSECTED_NONE READ INTERSECTED_NONE CONSTANT)
|
||||
Q_PROPERTY(unsigned int INTERSECTED_ENTITY READ INTERSECTED_ENTITY CONSTANT)
|
||||
Q_PROPERTY(unsigned int INTERSECTED_OVERLAY READ INTERSECTED_OVERLAY CONSTANT)
|
||||
Q_PROPERTY(unsigned int INTERSECTED_AVATAR READ INTERSECTED_AVATAR CONSTANT)
|
||||
Q_PROPERTY(unsigned int INTERSECTED_HUD READ INTERSECTED_HUD CONSTANT)
|
||||
Q_PROPERTY(unsigned int perFrameTimeBudget READ getPerFrameTimeBudget WRITE setPerFrameTimeBudget)
|
||||
SINGLETON_DEPENDENCY
|
||||
|
||||
public:
|
||||
|
@ -72,11 +94,13 @@ public:
|
|||
* Adds a new Pick.
|
||||
* Different {@link PickType}s use different properties, and within one PickType, the properties you choose can lead to a wide range of behaviors. For example,
|
||||
* with PickType.Ray, depending on which optional parameters you pass, you could create a Static Ray Pick, a Mouse Ray Pick, or a Joint Ray Pick.
|
||||
* Picks created with this method always intersect at least visible and collidable things
|
||||
* @function Picks.createPick
|
||||
* @param {PickType} type A PickType that specifies the method of picking to use
|
||||
* @param {Picks.RayPickProperties|Picks.StylusPickProperties|Picks.ParabolaPickProperties|Picks.CollisionPickProperties} properties A PickProperties object, containing all the properties for initializing this Pick
|
||||
* @returns {number} The ID of the created Pick. Used for managing the Pick. 0 if invalid.
|
||||
*/
|
||||
// TODO: expand Pointers to be able to be fully configurable with PickFilters
|
||||
Q_INVOKABLE unsigned int createPick(const PickQuery::PickType type, const QVariant& properties);
|
||||
|
||||
/**jsdoc
|
||||
|
@ -227,61 +251,80 @@ public:
|
|||
*/
|
||||
Q_INVOKABLE bool isMouse(unsigned int uid);
|
||||
|
||||
// FIXME: Move to other property definitions.
|
||||
Q_PROPERTY(unsigned int perFrameTimeBudget READ getPerFrameTimeBudget WRITE setPerFrameTimeBudget)
|
||||
|
||||
unsigned int getPerFrameTimeBudget() const;
|
||||
void setPerFrameTimeBudget(unsigned int numUsecs);
|
||||
|
||||
public slots:
|
||||
|
||||
/**jsdoc
|
||||
* @function Picks.PICK_NOTHING
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int PICK_NOTHING() { return 0; }
|
||||
|
||||
/**jsdoc
|
||||
* @function Picks.PICK_ENTITIES
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int PICK_ENTITIES() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_ENTITIES); }
|
||||
|
||||
static constexpr unsigned int PICK_ENTITIES() { return PickFilter::getBitMask(PickFilter::FlagBit::DOMAIN_ENTITIES) | PickFilter::getBitMask(PickFilter::FlagBit::AVATAR_ENTITIES); }
|
||||
/**jsdoc
|
||||
* @function Picks.PICK_OVERLAYS
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int PICK_OVERLAYS() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_OVERLAYS); }
|
||||
static constexpr unsigned int PICK_OVERLAYS() { return PickFilter::getBitMask(PickFilter::FlagBit::LOCAL_ENTITIES); }
|
||||
|
||||
/**jsdoc
|
||||
* @function Picks.PICK_DOMAIN_ENTITIES
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int PICK_DOMAIN_ENTITIES() { return PickFilter::getBitMask(PickFilter::FlagBit::DOMAIN_ENTITIES); }
|
||||
/**jsdoc
|
||||
* @function Picks.PICK_AVATAR_ENTITIES
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int PICK_AVATAR_ENTITIES() { return PickFilter::getBitMask(PickFilter::FlagBit::AVATAR_ENTITIES); }
|
||||
/**jsdoc
|
||||
* @function Picks.PICK_LOCAL_ENTITIES
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int PICK_LOCAL_ENTITIES() { return PickFilter::getBitMask(PickFilter::FlagBit::LOCAL_ENTITIES); }
|
||||
/**jsdoc
|
||||
* @function Picks.PICK_AVATARS
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int PICK_AVATARS() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_AVATARS); }
|
||||
|
||||
static constexpr unsigned int PICK_AVATARS() { return PickFilter::getBitMask(PickFilter::FlagBit::AVATARS); }
|
||||
/**jsdoc
|
||||
* @function Picks.PICK_HUD
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int PICK_HUD() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_HUD); }
|
||||
static constexpr unsigned int PICK_HUD() { return PickFilter::getBitMask(PickFilter::FlagBit::HUD); }
|
||||
|
||||
/**jsdoc
|
||||
* @function Picks.PICK_COARSE
|
||||
* @function Picks.PICK_INCLUDE_VISIBLE
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int PICK_COARSE() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_COARSE); }
|
||||
|
||||
static constexpr unsigned int PICK_INCLUDE_VISIBLE() { return PickFilter::getBitMask(PickFilter::FlagBit::VISIBLE); }
|
||||
/**jsdoc
|
||||
* @function Picks.PICK_INCLUDE_INVISIBLE
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int PICK_INCLUDE_INVISIBLE() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_INCLUDE_INVISIBLE); }
|
||||
static constexpr unsigned int PICK_INCLUDE_INVISIBLE() { return PickFilter::getBitMask(PickFilter::FlagBit::INVISIBLE); }
|
||||
|
||||
/**jsdoc
|
||||
* @function Picks.PICK_INCLUDE_COLLIDABLE
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int PICK_INCLUDE_COLLIDABLE() { return PickFilter::getBitMask(PickFilter::FlagBit::COLLIDABLE); }
|
||||
/**jsdoc
|
||||
* @function Picks.PICK_INCLUDE_NONCOLLIDABLE
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int PICK_INCLUDE_NONCOLLIDABLE() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_INCLUDE_NONCOLLIDABLE); }
|
||||
static constexpr unsigned int PICK_INCLUDE_NONCOLLIDABLE() { return PickFilter::getBitMask(PickFilter::FlagBit::NONCOLLIDABLE); }
|
||||
|
||||
/**jsdoc
|
||||
* @function Picks.PICK_PRECISE
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int PICK_PRECISE() { return PickFilter::getBitMask(PickFilter::FlagBit::PRECISE); }
|
||||
/**jsdoc
|
||||
* @function Picks.PICK_COARSE
|
||||
* @returns {number}
|
||||
*/
|
||||
static constexpr unsigned int PICK_COARSE() { return PickFilter::getBitMask(PickFilter::FlagBit::COARSE); }
|
||||
|
||||
/**jsdoc
|
||||
* @function Picks.PICK_ALL_INTERSECTIONS
|
||||
|
|
|
@ -41,10 +41,12 @@ public:
|
|||
* @property {string} button Which button to trigger. "Primary", "Secondary", "Tertiary", and "Focus" are currently supported. Only "Primary" will trigger clicks on web surfaces. If "Focus" is triggered,
|
||||
* it will try to set the entity or overlay focus to the object at which the Pointer is aimed. Buttons besides the first three will still trigger events, but event.button will be "None".
|
||||
*/
|
||||
|
||||
/**jsdoc
|
||||
* Adds a new Pointer
|
||||
* Different {@link PickType}s use different properties, and within one PickType, the properties you choose can lead to a wide range of behaviors. For example,
|
||||
* with PickType.Ray, depending on which optional parameters you pass, you could create a Static Ray Pointer, a Mouse Ray Pointer, or a Joint Ray Pointer.
|
||||
* Pointers created with this method always intersect at least visible and collidable things
|
||||
* @function Pointers.createPointer
|
||||
* @param {PickType} type A PickType that specifies the method of picking to use
|
||||
* @param {Pointers.LaserPointerProperties|Pointers.StylusPointerProperties|Pointers.ParabolaPointerProperties} properties A PointerProperties object, containing all the properties for initializing this Pointer <b>and</b> the {@link Picks.PickProperties} for the Pick that
|
||||
|
@ -58,21 +60,21 @@ public:
|
|||
* dimensions: {x:0.5, y:0.5, z:0.5},
|
||||
* solid: true,
|
||||
* color: {red:0, green:255, blue:0},
|
||||
* ignoreRayIntersection: true
|
||||
* ignorePickIntersection: true
|
||||
* };
|
||||
* var end2 = {
|
||||
* type: "sphere",
|
||||
* dimensions: {x:0.5, y:0.5, z:0.5},
|
||||
* solid: true,
|
||||
* color: {red:255, green:0, blue:0},
|
||||
* ignoreRayIntersection: true
|
||||
* ignorePickIntersection: true
|
||||
* };
|
||||
*
|
||||
* var renderStates = [ {name: "test", end: end} ];
|
||||
* var defaultRenderStates = [ {name: "test", distance: 10.0, end: end2} ];
|
||||
* var pointer = Pointers.createPointer(PickType.Ray, {
|
||||
* joint: "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND",
|
||||
* filter: Picks.PICK_OVERLAYS | Picks.PICK_ENTITIES | Picks.PICK_INCLUDE_NONCOLLIDABLE,
|
||||
* filter: Picks.PICK_LOCAL_ENTITIES | Picks.PICK_DOMAIN_ENTITIES | Picks.PICK_INCLUDE_NONCOLLIDABLE,
|
||||
* renderStates: renderStates,
|
||||
* defaultRenderStates: defaultRenderStates,
|
||||
* distanceScaleEnd: true,
|
||||
|
@ -82,6 +84,7 @@ public:
|
|||
* });
|
||||
* Pointers.setRenderState(pointer, "test");
|
||||
*/
|
||||
// TODO: expand Pointers to be able to be fully configurable with PickFilters
|
||||
Q_INVOKABLE unsigned int createPointer(const PickQuery::PickType& type, const QVariant& properties);
|
||||
|
||||
/**jsdoc
|
||||
|
|
|
@ -27,10 +27,15 @@ PickRay RayPick::getMathematicalPick() const {
|
|||
}
|
||||
|
||||
PickResultPointer RayPick::getEntityIntersection(const PickRay& pick) {
|
||||
bool precisionPicking = !(getFilter().doesPickCoarse() || DependencyManager::get<PickManager>()->getForceCoarsePicking());
|
||||
PickFilter searchFilter = getFilter();
|
||||
if (DependencyManager::get<PickManager>()->getForceCoarsePicking()) {
|
||||
searchFilter.setFlag(PickFilter::COARSE, true);
|
||||
searchFilter.setFlag(PickFilter::PRECISE, false);
|
||||
}
|
||||
|
||||
RayToEntityIntersectionResult entityRes =
|
||||
DependencyManager::get<EntityScriptingInterface>()->findRayIntersectionVector(pick, precisionPicking,
|
||||
getIncludeItemsAs<EntityItemID>(), getIgnoreItemsAs<EntityItemID>(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable());
|
||||
DependencyManager::get<EntityScriptingInterface>()->evalRayIntersectionVector(pick, searchFilter,
|
||||
getIncludeItemsAs<EntityItemID>(), getIgnoreItemsAs<EntityItemID>());
|
||||
if (entityRes.intersects) {
|
||||
return std::make_shared<RayPickResult>(IntersectionType::ENTITY, entityRes.entityID, entityRes.distance, entityRes.intersection, pick, entityRes.surfaceNormal, entityRes.extraInfo);
|
||||
} else {
|
||||
|
@ -39,7 +44,7 @@ PickResultPointer RayPick::getEntityIntersection(const PickRay& pick) {
|
|||
}
|
||||
|
||||
PickResultPointer RayPick::getOverlayIntersection(const PickRay& pick) {
|
||||
bool precisionPicking = !(getFilter().doesPickCoarse() || DependencyManager::get<PickManager>()->getForceCoarsePicking());
|
||||
bool precisionPicking = !(getFilter().isCoarse() || DependencyManager::get<PickManager>()->getForceCoarsePicking());
|
||||
RayToOverlayIntersectionResult overlayRes =
|
||||
qApp->getOverlays().findRayIntersectionVector(pick, precisionPicking,
|
||||
getIncludeItemsAs<OverlayID>(), getIgnoreItemsAs<OverlayID>(), !getFilter().doesPickInvisible(), !getFilter().doesPickNonCollidable());
|
||||
|
|
|
@ -19,14 +19,13 @@
|
|||
#include "PickScriptingInterface.h"
|
||||
|
||||
/**jsdoc
|
||||
* Synonym for {@link Picks} as used for ray picks.
|
||||
* Synonym for {@link Picks} as used for ray picks. Deprecated.
|
||||
*
|
||||
* @namespace RayPick
|
||||
*
|
||||
* @hifi-interface
|
||||
* @hifi-client-entity
|
||||
*
|
||||
* @property {number} PICK_NOTHING <em>Read-only.</em>
|
||||
* @property {number} PICK_ENTITIES <em>Read-only.</em>
|
||||
* @property {number} PICK_OVERLAYS <em>Read-only.</em>
|
||||
* @property {number} PICK_AVATARS <em>Read-only.</em>
|
||||
|
@ -44,7 +43,6 @@
|
|||
|
||||
class RayPickScriptingInterface : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(unsigned int PICK_NOTHING READ PICK_NOTHING CONSTANT)
|
||||
Q_PROPERTY(unsigned int PICK_ENTITIES READ PICK_ENTITIES CONSTANT)
|
||||
Q_PROPERTY(unsigned int PICK_OVERLAYS READ PICK_OVERLAYS CONSTANT)
|
||||
Q_PROPERTY(unsigned int PICK_AVATARS READ PICK_AVATARS CONSTANT)
|
||||
|
@ -140,12 +138,6 @@ public:
|
|||
|
||||
public slots:
|
||||
|
||||
/**jsdoc
|
||||
* @function RayPick.PICK_NOTHING
|
||||
* @returns {number}
|
||||
*/
|
||||
static unsigned int PICK_NOTHING() { return PickScriptingInterface::PICK_NOTHING(); }
|
||||
|
||||
/**jsdoc
|
||||
* @function RayPick.PICK_ENTITIES
|
||||
* @returns {number}
|
||||
|
|
|
@ -61,7 +61,7 @@ OverlayID StylusPointer::buildStylusOverlay(const QVariantMap& properties) {
|
|||
overlayProperties["loadPriority"] = 10.0f;
|
||||
overlayProperties["solid"] = true;
|
||||
overlayProperties["visible"] = false;
|
||||
overlayProperties["ignoreRayIntersection"] = true;
|
||||
overlayProperties["ignorePickIntersection"] = true;
|
||||
overlayProperties["drawInFront"] = false;
|
||||
|
||||
return qApp->getOverlays().addOverlay("model", overlayProperties);
|
||||
|
|
135
interface/src/scripting/PlatformInfoScriptingInterface.cpp
Normal file
135
interface/src/scripting/PlatformInfoScriptingInterface.cpp
Normal file
|
@ -0,0 +1,135 @@
|
|||
//
|
||||
// Created by Nissim Hadar on 2018/12/28
|
||||
// Copyright 2013-2016 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 "PlatformInfoScriptingInterface.h"
|
||||
#include "Application.h"
|
||||
|
||||
#include <thread>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <Windows.h>
|
||||
#elif defined Q_OS_MAC
|
||||
#include <sstream>
|
||||
#endif
|
||||
|
||||
PlatformInfoScriptingInterface* PlatformInfoScriptingInterface::getInstance() {
|
||||
static PlatformInfoScriptingInterface sharedInstance;
|
||||
return &sharedInstance;
|
||||
}
|
||||
|
||||
QString PlatformInfoScriptingInterface::getOperatingSystemType() {
|
||||
#ifdef Q_OS_WIN
|
||||
return "WINDOWS";
|
||||
#elif defined Q_OS_MAC
|
||||
return "MACOS";
|
||||
#else
|
||||
return "UNKNOWN";
|
||||
#endif
|
||||
}
|
||||
|
||||
QString PlatformInfoScriptingInterface::getCPUBrand() {
|
||||
#ifdef Q_OS_WIN
|
||||
int CPUInfo[4] = { -1 };
|
||||
unsigned nExIds, i = 0;
|
||||
char CPUBrandString[0x40];
|
||||
// Get the information associated with each extended ID.
|
||||
__cpuid(CPUInfo, 0x80000000);
|
||||
nExIds = CPUInfo[0];
|
||||
|
||||
for (i = 0x80000000; i <= nExIds; ++i) {
|
||||
__cpuid(CPUInfo, i);
|
||||
// Interpret CPU brand string
|
||||
if (i == 0x80000002) {
|
||||
memcpy(CPUBrandString, CPUInfo, sizeof(CPUInfo));
|
||||
} else if (i == 0x80000003) {
|
||||
memcpy(CPUBrandString + 16, CPUInfo, sizeof(CPUInfo));
|
||||
} else if (i == 0x80000004) {
|
||||
memcpy(CPUBrandString + 32, CPUInfo, sizeof(CPUInfo));
|
||||
}
|
||||
}
|
||||
|
||||
return CPUBrandString;
|
||||
#elif defined Q_OS_MAC
|
||||
FILE* stream = popen("sysctl -n machdep.cpu.brand_string", "r");
|
||||
|
||||
std::ostringstream hostStream;
|
||||
while (!feof(stream) && !ferror(stream)) {
|
||||
char buf[128];
|
||||
int bytesRead = fread(buf, 1, 128, stream);
|
||||
hostStream.write(buf, bytesRead);
|
||||
}
|
||||
|
||||
return QString::fromStdString(hostStream.str());
|
||||
#else
|
||||
return QString("NO IMPLEMENTED");
|
||||
#endif
|
||||
}
|
||||
|
||||
unsigned int PlatformInfoScriptingInterface::getNumLogicalCores() {
|
||||
|
||||
return std::thread::hardware_concurrency();
|
||||
}
|
||||
|
||||
int PlatformInfoScriptingInterface::getTotalSystemMemoryMB() {
|
||||
#ifdef Q_OS_WIN
|
||||
MEMORYSTATUSEX statex;
|
||||
statex.dwLength = sizeof (statex);
|
||||
GlobalMemoryStatusEx(&statex);
|
||||
return statex.ullTotalPhys / 1024 / 1024;
|
||||
#elif defined Q_OS_MAC
|
||||
FILE* stream = popen("sysctl -a | grep hw.memsize", "r");
|
||||
|
||||
std::ostringstream hostStream;
|
||||
while (!feof(stream) && !ferror(stream)) {
|
||||
char buf[128];
|
||||
int bytesRead = fread(buf, 1, 128, stream);
|
||||
hostStream.write(buf, bytesRead);
|
||||
}
|
||||
|
||||
QString result = QString::fromStdString(hostStream.str());
|
||||
QStringList parts = result.split(' ');
|
||||
return (int)(parts[1].toDouble() / 1024 / 1024);
|
||||
#else
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
|
||||
QString PlatformInfoScriptingInterface::getGraphicsCardType() {
|
||||
#ifdef Q_OS_WIN
|
||||
return qApp->getGraphicsCardType();
|
||||
#elif defined Q_OS_MAC
|
||||
FILE* stream = popen("system_profiler SPDisplaysDataType | grep Chipset", "r");
|
||||
|
||||
std::ostringstream hostStream;
|
||||
while (!feof(stream) && !ferror(stream)) {
|
||||
char buf[128];
|
||||
int bytesRead = fread(buf, 1, 128, stream);
|
||||
hostStream.write(buf, bytesRead);
|
||||
}
|
||||
|
||||
QString result = QString::fromStdString(hostStream.str());
|
||||
QStringList parts = result.split('\n');
|
||||
for (int i = 0; i < parts.size(); ++i) {
|
||||
if (parts[i].toLower().contains("radeon") || parts[i].toLower().contains("nvidia")) {
|
||||
return parts[i];
|
||||
}
|
||||
}
|
||||
|
||||
// unkown graphics card
|
||||
return "UNKNOWN";
|
||||
#else
|
||||
return QString("NO IMPLEMENTED");
|
||||
#endif
|
||||
}
|
||||
|
||||
bool PlatformInfoScriptingInterface::hasRiftControllers() {
|
||||
return qApp->hasRiftControllers();
|
||||
}
|
||||
|
||||
bool PlatformInfoScriptingInterface::hasViveControllers() {
|
||||
return qApp->hasViveControllers();
|
||||
}
|
70
interface/src/scripting/PlatformInfoScriptingInterface.h
Normal file
70
interface/src/scripting/PlatformInfoScriptingInterface.h
Normal file
|
@ -0,0 +1,70 @@
|
|||
//
|
||||
// Created by Nissim Hadar on 2018/12/28
|
||||
// Copyright 2013-2016 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_PlatformInfoScriptingInterface_h
|
||||
#define hifi_PlatformInfoScriptingInterface_h
|
||||
|
||||
#include <QtCore/QObject>
|
||||
|
||||
class QScriptValue;
|
||||
|
||||
class PlatformInfoScriptingInterface : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public slots:
|
||||
static PlatformInfoScriptingInterface* getInstance();
|
||||
|
||||
/**jsdoc
|
||||
* Returns the Operating Sytem type
|
||||
* @function Test.getOperatingSystemType
|
||||
* @returns {string} "WINDOWS", "MACOS" or "UNKNOWN"
|
||||
*/
|
||||
QString getOperatingSystemType();
|
||||
|
||||
/**jsdoc
|
||||
* Returns the CPU brand
|
||||
*function PlatformInfo.getCPUBrand()
|
||||
* @returns {string} brand of CPU
|
||||
*/
|
||||
QString getCPUBrand();
|
||||
|
||||
/**jsdoc
|
||||
* Returns the number of logical CPU cores
|
||||
*function PlatformInfo.getNumLogicalCores()
|
||||
* @returns {int} number of logical CPU cores
|
||||
*/
|
||||
unsigned int getNumLogicalCores();
|
||||
|
||||
/**jsdoc
|
||||
* Returns the total system memory in megabyte
|
||||
*function PlatformInfo.getTotalSystemMemory()
|
||||
* @returns {int} size of memory in megabytes
|
||||
*/
|
||||
int getTotalSystemMemoryMB();
|
||||
|
||||
/**jsdoc
|
||||
* Returns the graphics card type
|
||||
* @function Test.getGraphicsCardType
|
||||
* @returns {string} graphics card type
|
||||
*/
|
||||
QString getGraphicsCardType();
|
||||
|
||||
/**jsdoc
|
||||
* Returns true if Oculus Rift is connected (looks for hand controllers)
|
||||
* @function Window.hasRift
|
||||
* @returns {boolean} <code>true</code> if running on Windows, otherwise <code>false</code>.*/
|
||||
bool hasRiftControllers();
|
||||
|
||||
/**jsdoc
|
||||
* Returns true if HTC Vive is connected (looks for hand controllers)
|
||||
* @function Window.hasRift
|
||||
* @returns {boolean} <code>true</code> if running on Windows, otherwise <code>false</code>.*/
|
||||
bool hasViveControllers();
|
||||
};
|
||||
|
||||
#endif // hifi_PlatformInfoScriptingInterface_h
|
|
@ -781,7 +781,7 @@ void Keyboard::loadKeyboardFile(const QString& keyboardFile) {
|
|||
{ "isSolid", true },
|
||||
{ "visible", false },
|
||||
{ "grabbable", true },
|
||||
{ "ignoreRayIntersection", false },
|
||||
{ "ignorePickIntersection", false },
|
||||
{ "dimensions", anchorObject["dimensions"].toVariant() },
|
||||
{ "position", anchorObject["position"].toVariant() },
|
||||
{ "orientation", anchorObject["rotation"].toVariant() }
|
||||
|
|
|
@ -95,7 +95,12 @@ void LoginDialog::toggleAction() {
|
|||
} else {
|
||||
// change the menu item to login
|
||||
loginAction->setText("Log In / Sign Up");
|
||||
connection = connect(loginAction, &QAction::triggered, [] { LoginDialog::showWithSelection(); });
|
||||
connection = connect(loginAction, &QAction::triggered, [] {
|
||||
// if not in login state, show.
|
||||
if (!qApp->getLoginDialogPoppedUp()) {
|
||||
LoginDialog::showWithSelection();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,6 @@
|
|||
|
||||
#include <gl/Context.h>
|
||||
|
||||
#include "BandwidthRecorder.h"
|
||||
#include "Menu.h"
|
||||
#include "Util.h"
|
||||
#include "SequenceNumberStats.h"
|
||||
|
@ -166,20 +165,25 @@ void Stats::updateStats(bool force) {
|
|||
STAT_UPDATE(collisionPicksUpdated, updatedPicks[PickQuery::Collision]);
|
||||
}
|
||||
|
||||
auto bandwidthRecorder = DependencyManager::get<BandwidthRecorder>();
|
||||
STAT_UPDATE(packetInCount, (int)bandwidthRecorder->getCachedTotalAverageInputPacketsPerSecond());
|
||||
STAT_UPDATE(packetOutCount, (int)bandwidthRecorder->getCachedTotalAverageOutputPacketsPerSecond());
|
||||
STAT_UPDATE_FLOAT(mbpsIn, (float)bandwidthRecorder->getCachedTotalAverageInputKilobitsPerSecond() / 1000.0f, 0.01f);
|
||||
STAT_UPDATE_FLOAT(mbpsOut, (float)bandwidthRecorder->getCachedTotalAverageOutputKilobitsPerSecond() / 1000.0f, 0.01f);
|
||||
STAT_UPDATE(packetInCount, nodeList->getInboundPPS());
|
||||
STAT_UPDATE(packetOutCount, nodeList->getOutboundPPS());
|
||||
STAT_UPDATE_FLOAT(mbpsIn, nodeList->getInboundKbps() / 1000.0f, 0.01f);
|
||||
STAT_UPDATE_FLOAT(mbpsOut, nodeList->getOutboundKbps() / 1000.0f, 0.01f);
|
||||
|
||||
STAT_UPDATE_FLOAT(assetMbpsIn, (float)bandwidthRecorder->getAverageInputKilobitsPerSecond(NodeType::AssetServer) / 1000.0f, 0.01f);
|
||||
STAT_UPDATE_FLOAT(assetMbpsOut, (float)bandwidthRecorder->getAverageOutputKilobitsPerSecond(NodeType::AssetServer) / 1000.0f, 0.01f);
|
||||
|
||||
// Second column: ping
|
||||
SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer);
|
||||
SharedNodePointer avatarMixerNode = nodeList->soloNodeOfType(NodeType::AvatarMixer);
|
||||
SharedNodePointer assetServerNode = nodeList->soloNodeOfType(NodeType::AssetServer);
|
||||
SharedNodePointer messageMixerNode = nodeList->soloNodeOfType(NodeType::MessagesMixer);
|
||||
|
||||
if (assetServerNode) {
|
||||
STAT_UPDATE_FLOAT(assetMbpsIn, assetServerNode->getInboundKbps() / 1000.0f, 0.01f);
|
||||
STAT_UPDATE_FLOAT(assetMbpsOut, assetServerNode->getOutboundKbps() / 1000.0f, 0.01f);
|
||||
} else {
|
||||
STAT_UPDATE_FLOAT(assetMbpsIn, 0.0f, 0.01f);
|
||||
STAT_UPDATE_FLOAT(assetMbpsOut, 0.0f, 0.01f);
|
||||
}
|
||||
|
||||
// Second column: ping
|
||||
STAT_UPDATE(audioPing, audioMixerNode ? audioMixerNode->getPingMs() : -1);
|
||||
const int mixerLossRate = (int)roundf(_audioStats->data()->getMixerStream()->lossRateWindow() * 100.0f);
|
||||
const int clientLossRate = (int)roundf(_audioStats->data()->getClientStream()->lossRateWindow() * 100.0f);
|
||||
|
@ -198,7 +202,7 @@ void Stats::updateStats(bool force) {
|
|||
// TODO: this should also support entities
|
||||
if (node->getType() == NodeType::EntityServer) {
|
||||
totalPingOctree += node->getPingMs();
|
||||
totalEntityKbps += node->getInboundBandwidth();
|
||||
totalEntityKbps += node->getInboundKbps();
|
||||
octreeServerCount++;
|
||||
if (pingOctreeMax < node->getPingMs()) {
|
||||
pingOctreeMax = node->getPingMs();
|
||||
|
@ -218,10 +222,10 @@ void Stats::updateStats(bool force) {
|
|||
if (_expanded || force) {
|
||||
SharedNodePointer avatarMixer = nodeList->soloNodeOfType(NodeType::AvatarMixer);
|
||||
if (avatarMixer) {
|
||||
STAT_UPDATE(avatarMixerInKbps, (int)roundf(bandwidthRecorder->getAverageInputKilobitsPerSecond(NodeType::AvatarMixer)));
|
||||
STAT_UPDATE(avatarMixerInPps, (int)roundf(bandwidthRecorder->getAverageInputPacketsPerSecond(NodeType::AvatarMixer)));
|
||||
STAT_UPDATE(avatarMixerOutKbps, (int)roundf(bandwidthRecorder->getAverageOutputKilobitsPerSecond(NodeType::AvatarMixer)));
|
||||
STAT_UPDATE(avatarMixerOutPps, (int)roundf(bandwidthRecorder->getAverageOutputPacketsPerSecond(NodeType::AvatarMixer)));
|
||||
STAT_UPDATE(avatarMixerInKbps, (int)roundf(avatarMixer->getInboundKbps()));
|
||||
STAT_UPDATE(avatarMixerInPps, avatarMixer->getInboundPPS());
|
||||
STAT_UPDATE(avatarMixerOutKbps, (int)roundf(avatarMixer->getOutboundKbps()));
|
||||
STAT_UPDATE(avatarMixerOutPps, avatarMixer->getOutboundPPS());
|
||||
} else {
|
||||
STAT_UPDATE(avatarMixerInKbps, -1);
|
||||
STAT_UPDATE(avatarMixerInPps, -1);
|
||||
|
@ -233,17 +237,15 @@ void Stats::updateStats(bool force) {
|
|||
SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer);
|
||||
auto audioClient = DependencyManager::get<AudioClient>().data();
|
||||
if (audioMixerNode || force) {
|
||||
STAT_UPDATE(audioMixerKbps, (int)roundf(
|
||||
bandwidthRecorder->getAverageInputKilobitsPerSecond(NodeType::AudioMixer) +
|
||||
bandwidthRecorder->getAverageOutputKilobitsPerSecond(NodeType::AudioMixer)));
|
||||
STAT_UPDATE(audioMixerPps, (int)roundf(
|
||||
bandwidthRecorder->getAverageInputPacketsPerSecond(NodeType::AudioMixer) +
|
||||
bandwidthRecorder->getAverageOutputPacketsPerSecond(NodeType::AudioMixer)));
|
||||
STAT_UPDATE(audioMixerKbps, (int)roundf(audioMixerNode->getInboundKbps() +
|
||||
audioMixerNode->getOutboundKbps()));
|
||||
STAT_UPDATE(audioMixerPps, audioMixerNode->getInboundPPS() +
|
||||
audioMixerNode->getOutboundPPS());
|
||||
|
||||
STAT_UPDATE(audioMixerInKbps, (int)roundf(bandwidthRecorder->getAverageInputKilobitsPerSecond(NodeType::AudioMixer)));
|
||||
STAT_UPDATE(audioMixerInPps, (int)roundf(bandwidthRecorder->getAverageInputPacketsPerSecond(NodeType::AudioMixer)));
|
||||
STAT_UPDATE(audioMixerOutKbps, (int)roundf(bandwidthRecorder->getAverageOutputKilobitsPerSecond(NodeType::AudioMixer)));
|
||||
STAT_UPDATE(audioMixerOutPps, (int)roundf(bandwidthRecorder->getAverageOutputPacketsPerSecond(NodeType::AudioMixer)));
|
||||
STAT_UPDATE(audioMixerInKbps, (int)roundf(audioMixerNode->getInboundKbps()));
|
||||
STAT_UPDATE(audioMixerInPps, audioMixerNode->getInboundPPS());
|
||||
STAT_UPDATE(audioMixerOutKbps, (int)roundf(audioMixerNode->getOutboundKbps()));
|
||||
STAT_UPDATE(audioMixerOutPps, audioMixerNode->getOutboundPPS());
|
||||
STAT_UPDATE(audioAudioInboundPPS, (int)audioClient->getAudioInboundPPS());
|
||||
STAT_UPDATE(audioSilentInboundPPS, (int)audioClient->getSilentInboundPPS());
|
||||
STAT_UPDATE(audioOutboundPPS, (int)audioClient->getAudioOutboundPPS());
|
||||
|
|
|
@ -178,9 +178,11 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) {
|
|||
}
|
||||
|
||||
if (properties["isDashedLine"].isValid()) {
|
||||
qDebug() << "isDashed is deprecated and will be removed in RC79!";
|
||||
setIsDashedLine(properties["isDashedLine"].toBool());
|
||||
}
|
||||
if (properties["dashed"].isValid()) {
|
||||
qDebug() << "dashed is deprecated and will be removed in RC79!";
|
||||
setIsDashedLine(properties["dashed"].toBool());
|
||||
}
|
||||
if (properties["ignorePickIntersection"].isValid()) {
|
||||
|
@ -223,7 +225,7 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) {
|
|||
* @property {boolean} isSolid=false - Synonyms: <ode>solid</code>, <code>isFilled</code>, and <code>filled</code>.
|
||||
* Antonyms: <code>isWire</code> and <code>wire</code>.
|
||||
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
|
||||
* <code>dashed</code>.
|
||||
* <code>dashed</code>. Deprecated.
|
||||
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
|
||||
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
|
||||
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
|
||||
|
@ -259,6 +261,7 @@ QVariant Base3DOverlay::getProperty(const QString& property) {
|
|||
return !_isSolid;
|
||||
}
|
||||
if (property == "isDashedLine" || property == "dashed") {
|
||||
qDebug() << "isDashedLine/dashed are deprecated and will be removed in RC79!";
|
||||
return _isDashedLine;
|
||||
}
|
||||
if (property == "ignorePickIntersection" || property == "ignoreRayIntersection") {
|
||||
|
|
|
@ -399,7 +399,7 @@ void Circle3DOverlay::setProperties(const QVariantMap& properties) {
|
|||
* @property {boolean} isSolid=false - Synonyms: <ode>solid</code>, <code>isFilled</code>, and <code>filled</code>
|
||||
* Antonyms: <code>isWire</code> and <code>wire</code>.
|
||||
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
|
||||
* <code>dashed</code>.
|
||||
* <code>dashed</code>. Deprecated.
|
||||
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
|
||||
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
|
||||
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
|
||||
|
|
|
@ -156,7 +156,7 @@ void Cube3DOverlay::setProperties(const QVariantMap& properties) {
|
|||
* @property {boolean} isSolid=false - Synonyms: <ode>solid</code>, <code>isFilled</code>, and <code>filled</code>.
|
||||
* Antonyms: <code>isWire</code> and <code>wire</code>.
|
||||
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
|
||||
* <code>dashed</code>.
|
||||
* <code>dashed</code>. Deprecated.
|
||||
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
|
||||
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
|
||||
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
|
||||
|
|
|
@ -85,7 +85,7 @@ void Grid3DOverlay::render(RenderArgs* args) {
|
|||
DependencyManager::get<GeometryCache>()->renderGrid(*batch, minCorner, maxCorner,
|
||||
_minorGridRowDivisions, _minorGridColDivisions, MINOR_GRID_EDGE,
|
||||
_majorGridRowDivisions, _majorGridColDivisions, MAJOR_GRID_EDGE,
|
||||
gridColor, _drawInFront, _geometryId);
|
||||
gridColor, _geometryId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -142,7 +142,7 @@ void Grid3DOverlay::setProperties(const QVariantMap& properties) {
|
|||
* @property {boolean} isSolid=false - Synonyms: <ode>solid</code>, <code>isFilled</code>, and <code>filled</code>.
|
||||
* Antonyms: <code>isWire</code> and <code>wire</code>.
|
||||
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
|
||||
* <code>dashed</code>.
|
||||
* <code>dashed</code>. Deprecated.
|
||||
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
|
||||
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
|
||||
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
|
||||
|
|
|
@ -82,18 +82,21 @@ void Image3DOverlay::render(RenderArgs* args) {
|
|||
float imageHeight = _texture->getHeight();
|
||||
|
||||
QRect fromImage;
|
||||
if (_fromImage.isNull()) {
|
||||
if (_fromImage.width() <= 0) {
|
||||
fromImage.setX(0);
|
||||
fromImage.setY(0);
|
||||
fromImage.setWidth(imageWidth);
|
||||
fromImage.setHeight(imageHeight);
|
||||
} else {
|
||||
float scaleX = imageWidth / _texture->getOriginalWidth();
|
||||
float scaleY = imageHeight / _texture->getOriginalHeight();
|
||||
|
||||
fromImage.setX(scaleX * _fromImage.x());
|
||||
fromImage.setY(scaleY * _fromImage.y());
|
||||
fromImage.setWidth(scaleX * _fromImage.width());
|
||||
}
|
||||
|
||||
if (_fromImage.height() <= 0) {
|
||||
fromImage.setY(0);
|
||||
fromImage.setHeight(imageHeight);
|
||||
} else {
|
||||
float scaleY = imageHeight / _texture->getOriginalHeight();
|
||||
fromImage.setY(scaleY * _fromImage.y());
|
||||
fromImage.setHeight(scaleY * _fromImage.height());
|
||||
}
|
||||
|
||||
|
@ -219,7 +222,7 @@ void Image3DOverlay::setProperties(const QVariantMap& properties) {
|
|||
* @property {boolean} isSolid=false - Synonyms: <ode>solid</code>, <code>isFilled</code>, and <code>filled</code>.
|
||||
* Antonyms: <code>isWire</code> and <code>wire</code>.
|
||||
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
|
||||
* <code>dashed</code>.
|
||||
* <code>dashed</code>. Deprecated.
|
||||
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
|
||||
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
|
||||
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
|
||||
|
@ -247,9 +250,6 @@ QVariant Image3DOverlay::getProperty(const QString& property) {
|
|||
if (property == "subImage") {
|
||||
return _fromImage;
|
||||
}
|
||||
if (property == "offsetPosition") {
|
||||
return vec3toVariant(getOffsetPosition());
|
||||
}
|
||||
if (property == "emissive") {
|
||||
return _emissive;
|
||||
}
|
||||
|
|
|
@ -286,7 +286,7 @@ void Line3DOverlay::setProperties(const QVariantMap& originalProperties) {
|
|||
* @property {boolean} isSolid=false - Synonyms: <ode>solid</code>, <code>isFilled</code>, and <code>filled</code>.
|
||||
* Antonyms: <code>isWire</code> and <code>wire</code>.
|
||||
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
|
||||
* <code>dashed</code>.
|
||||
* <code>dashed</code>. Deprecated.
|
||||
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
|
||||
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
|
||||
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
|
||||
|
|
|
@ -382,7 +382,7 @@ vectorType ModelOverlay::mapJoints(mapFunction<itemType> function) const {
|
|||
* @property {boolean} isSolid=false - Synonyms: <ode>solid</code>, <code>isFilled</code>, and <code>filled</code>.
|
||||
* Antonyms: <code>isWire</code> and <code>wire</code>.
|
||||
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
|
||||
* <code>dashed</code>.
|
||||
* <code>dashed</code>. Deprecated.
|
||||
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
|
||||
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
|
||||
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
|
||||
|
|
|
@ -138,7 +138,7 @@ const render::ShapeKey Rectangle3DOverlay::getShapeKey() {
|
|||
* @property {boolean} isSolid=false - Synonyms: <ode>solid</code>, <code>isFilled</code>, and <code>filled</code>.
|
||||
* Antonyms: <code>isWire</code> and <code>wire</code>.
|
||||
* @property {boolean} isDashedLine=false - If <code>true</code>, a dashed line is drawn on the overlay's edges. Synonym:
|
||||
* <code>dashed</code>.
|
||||
* <code>dashed</code>. Deprecated.
|
||||
* @property {boolean} ignorePickIntersection=false - If <code>true</code>, picks ignore the overlay. <code>ignoreRayIntersection</code> is a synonym.
|
||||
* @property {boolean} drawInFront=false - If <code>true</code>, the overlay is rendered in front of other overlays that don't
|
||||
* have <code>drawInFront</code> set to <code>true</code>, and in front of entities.
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue