diff --git a/.gitignore b/.gitignore index 09b58d71ef..f5605d7090 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/android/build_android.sh b/android/build_android.sh index f98bd1a4b2..189e6099a8 100755 --- a/android/build_android.sh +++ b/android/build_android.sh @@ -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} \ No newline at end of file +./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} \ No newline at end of file diff --git a/android/containerized_build.sh b/android/containerized_build.sh index cd6f15a92e..e5ec895146 100755 --- a/android/containerized_build.sh +++ b/android/containerized_build.sh @@ -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" + + diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index 1500d7b98e..f28dc90b7d 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -11,9 +11,10 @@ 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 + model-networking ktx shaders ) add_dependencies(${TARGET_NAME} oven) diff --git a/assignment-client/src/AssignmentClientMonitor.cpp b/assignment-client/src/AssignmentClientMonitor.cpp index 330023dae0..fefed6e143 100644 --- a/assignment-client/src/AssignmentClientMonitor.cpp +++ b/assignment-client/src/AssignmentClientMonitor.cpp @@ -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()->addOrUpdateNode(senderID, NodeType::Unassigned, senderSockAddr, senderSockAddr); - auto childData = std::unique_ptr + auto newChildData = std::unique_ptr { 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(matchingNode->getLinkedData()); } + childData = dynamic_cast(matchingNode->getLinkedData()); if (childData) { // update our records about how to reach this child diff --git a/assignment-client/src/assets/AssetServer.cpp b/assignment-client/src/assets/AssetServer.cpp index 41aeaba468..cad6a852cb 100644 --- a/assignment-client/src/assets/AssetServer.cpp +++ b/assignment-client/src/assets/AssetServer.cpp @@ -915,59 +915,52 @@ void AssetServer::handleAssetUpload(QSharedPointer message, Sha void AssetServer::sendStatsPacket() { QJsonObject serverStats; - auto stats = DependencyManager::get()->sampleStatsForAllConnections(); + auto nodeList = DependencyManager::get(); + nodeList->eachNode([&](auto& node) { + auto& stats = node->getConnectionStats(); - for (const auto& stat : stats) { QJsonObject nodeStats; - auto endTimeMs = std::chrono::duration_cast(stat.second.endTime); + auto endTimeMs = std::chrono::duration_cast(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(); - 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); diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 77f416f31e..004e4ad2ea 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -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(); diff --git a/assignment-client/src/audio/AudioMixerSlave.cpp b/assignment-client/src/audio/AudioMixerSlave.cpp index 7a6ab9c3e2..681d822e11 100644 --- a/assignment-client/src/audio/AudioMixerSlave.cpp +++ b/assignment-client/src/audio/AudioMixerSlave.cpp @@ -504,7 +504,7 @@ void AudioMixerSlave::addStream(AudioMixerClientData::MixableStream& mixableStre float distance = glm::max(glm::length(relativePosition), EPSILON); float azimuth = isEcho ? 0.0f : computeAzimuth(listeningNodeStream, listeningNodeStream, relativePosition); - float gain = 1.0f; + float gain = masterListenerGain; if (!isSoloing) { gain = computeGain(masterListenerGain, listeningNodeStream, *streamToAdd, relativePosition, distance, isEcho); } diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 53fc13e5cf..6b90a8fbbd 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -56,6 +56,7 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) : packetReceiver.registerListener(PacketType::RequestsDomainListData, this, "handleRequestsDomainListDataPacket"); packetReceiver.registerListener(PacketType::AvatarIdentityRequest, this, "handleAvatarIdentityRequestPacket"); packetReceiver.registerListener(PacketType::SetAvatarTraits, this, "queueIncomingPacket"); + packetReceiver.registerListener(PacketType::BulkAvatarTraitsAck, this, "queueIncomingPacket"); packetReceiver.registerListenerForTypes({ PacketType::ReplicatedAvatarIdentity, @@ -746,65 +747,30 @@ 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["sent_4_averageDataBytes"] = TIGHT_LOOP_STAT(aggregateStats.numDataBytesSent); + slavesAggregatObject["sent_5_averageTraitsBytes"] = TIGHT_LOOP_STAT(aggregateStats.numTraitsBytesSent); + slavesAggregatObject["sent_6_averageIdentityBytes"] = TIGHT_LOOP_STAT(aggregateStats.numIdentityBytesSent); slavesAggregatObject["timing_1_processIncomingPackets"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.processIncomingPacketsElapsedTime); slavesAggregatObject["timing_2_ignoreCalculation"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.ignoreCalculationElapsedTime); @@ -813,8 +779,7 @@ void AvatarMixer::sendStatsPacket() { slavesAggregatObject["timing_5_packetSending"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.packetSendingElapsedTime); slavesAggregatObject["timing_6_jobElapsedTime"] = TIGHT_LOOP_STAT_UINT64(aggregateStats.jobElapsedTime); - statsObject["slaves_aggregate"] = slavesAggregatObject; - statsObject["slaves_individual"] = slavesObject; + statsObject["slaves_aggregate (per frame)"] = slavesAggregatObject; _handleViewFrustumPacketElapsedTime = 0; _handleAvatarIdentityPacketElapsedTime = 0; @@ -839,8 +804,9 @@ 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(); + float outboundAvatarDataKbps = node->getOutboundKbps(); + avatarStats[NODE_OUTBOUND_KBPS_STAT_KEY] = outboundAvatarDataKbps; + avatarStats[NODE_INBOUND_KBPS_STAT_KEY] = node->getInboundKbps(); AvatarMixerClientData* clientData = static_cast(node->getLinkedData()); if (clientData) { @@ -850,7 +816,7 @@ void AvatarMixer::sendStatsPacket() { // add the diff between the full outbound bandwidth and the measured bandwidth for AvatarData send only avatarStats["delta_full_vs_avatar_data_kbps"] = - avatarStats[NODE_OUTBOUND_KBPS_STAT_KEY].toDouble() - avatarStats[OUTBOUND_AVATAR_DATA_STATS_KEY].toDouble(); + (double)outboundAvatarDataKbps - avatarStats[OUTBOUND_AVATAR_DATA_STATS_KEY].toDouble(); } } diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 5e36d8beaf..b7d2f5cdf8 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -19,9 +19,8 @@ #include "AvatarMixerSlave.h" -AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID, Node::LocalID nodeLocalID) : - NodeData(nodeID, nodeLocalID) -{ +AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID, Node::LocalID nodeLocalID) : + NodeData(nodeID, nodeLocalID) { // in case somebody calls getSessionUUID on the AvatarData instance, make sure it has the right ID _avatar->setID(nodeID); } @@ -68,6 +67,9 @@ int AvatarMixerClientData::processPackets(const SlaveSharedData& slaveSharedData case PacketType::SetAvatarTraits: processSetTraitsMessage(*packet, slaveSharedData, *node); break; + case PacketType::BulkAvatarTraitsAck: + processBulkAvatarTraitsAckMessage(*packet); + break; default: Q_UNREACHABLE(); } @@ -79,12 +81,11 @@ int AvatarMixerClientData::processPackets(const SlaveSharedData& slaveSharedData } int AvatarMixerClientData::parseData(ReceivedMessage& message) { - // pull the sequence number from the data first uint16_t sequenceNumber; message.readPrimitive(&sequenceNumber); - + if (sequenceNumber < _lastReceivedSequenceNumber && _lastReceivedSequenceNumber != UINT16_MAX) { incrementNumOutOfOrderSends(); } @@ -95,7 +96,8 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message) { } void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message, - const SlaveSharedData& slaveSharedData, Node& sendingNode) { + const SlaveSharedData& slaveSharedData, + Node& sendingNode) { // pull the trait version from the message AvatarTraits::TraitVersion packetTraitVersion; message.readPrimitive(&packetTraitVersion); @@ -134,7 +136,7 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message, AvatarTraits::TraitInstanceID instanceID = QUuid::fromRfc4122(message.readWithoutCopy(NUM_BYTES_RFC4122_UUID)); if (message.getBytesLeftToRead() == 0) { - qWarning () << "Received an instanced trait with no size from" << message.getSenderSockAddr(); + qWarning() << "Received an instanced trait with no size from" << message.getSenderSockAddr(); break; } @@ -142,18 +144,20 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message, message.readPrimitive(&traitSize); if (traitSize < -1 || traitSize > message.getBytesLeftToRead()) { - qWarning() << "Refusing to process instanced trait of size" << traitSize << "from" << message.getSenderSockAddr(); + qWarning() << "Refusing to process instanced trait of size" << traitSize << "from" + << message.getSenderSockAddr(); break; } - if (traitType == AvatarTraits::AvatarEntity) { + if (traitType == AvatarTraits::AvatarEntity || + traitType == AvatarTraits::Grab) { auto& instanceVersionRef = _lastReceivedTraitVersions.getInstanceValueRef(traitType, instanceID); if (packetTraitVersion > instanceVersionRef) { if (traitSize == AvatarTraits::DELETED_TRAIT_SIZE) { _avatar->processDeletedTraitInstance(traitType, instanceID); // Mixer doesn't need deleted IDs. - _avatar->getAndClearRecentlyDetachedIDs(); + _avatar->getAndClearRecentlyRemovedIDs(); // to track a deleted instance but keep version information // the avatar mixer uses the negative value of the sent version @@ -168,7 +172,8 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message, message.seek(message.getPosition() + traitSize); } } else { - qWarning() << "Refusing to process traits packet with instanced trait of unprocessable type from" << message.getSenderSockAddr(); + qWarning() << "Refusing to process traits packet with instanced trait of unprocessable type from" + << message.getSenderSockAddr(); break; } } @@ -179,7 +184,63 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message, } } -void AvatarMixerClientData::checkSkeletonURLAgainstWhitelist(const SlaveSharedData &slaveSharedData, Node& sendingNode, +void AvatarMixerClientData::processBulkAvatarTraitsAckMessage(ReceivedMessage& message) { + // Avatar Traits flow control marks each outgoing avatar traits packet with a + // sequence number. The mixer caches the traits sent in the traits packet. + // Until an ack with the sequence number comes back, all updates to _traits + // in that packet_ are ignored. Updates to traits not in that packet will + // be sent. + + // Look up the avatar/trait data associated with this ack and update the 'last ack' list + // with it. + AvatarTraits::TraitMessageSequence seq; + message.readPrimitive(&seq); + auto sentAvatarTraitVersions = _perNodePendingTraitVersions.find(seq); + if (sentAvatarTraitVersions != _perNodePendingTraitVersions.end()) { + for (auto& perNodeTraitVersions : sentAvatarTraitVersions->second) { + auto& nodeId = perNodeTraitVersions.first; + auto& traitVersions = perNodeTraitVersions.second; + // For each trait that was sent in the traits packet, + // update the 'acked' trait version. Traits not + // sent in the traits packet keep their version. + + // process simple traits + auto simpleReceivedIt = traitVersions.simpleCBegin(); + while (simpleReceivedIt != traitVersions.simpleCEnd()) { + if (*simpleReceivedIt != AvatarTraits::DEFAULT_TRAIT_VERSION) { + auto traitType = static_cast(std::distance(traitVersions.simpleCBegin(), simpleReceivedIt)); + _perNodeAckedTraitVersions[nodeId][traitType] = *simpleReceivedIt; + } + simpleReceivedIt++; + } + + // process instanced traits + auto instancedSentIt = traitVersions.instancedCBegin(); + while (instancedSentIt != traitVersions.instancedCEnd()) { + auto traitType = instancedSentIt->traitType; + + for (auto& sentInstance : instancedSentIt->instances) { + auto instanceID = sentInstance.id; + const auto sentVersion = sentInstance.value; + _perNodeAckedTraitVersions[nodeId].instanceInsert(traitType, instanceID, sentVersion); + } + instancedSentIt++; + } + } + _perNodePendingTraitVersions.erase(sentAvatarTraitVersions); + } else { + // This can happen either the BulkAvatarTraits was sent with no simple traits, + // or if the avatar mixer restarts while there are pending + // BulkAvatarTraits messages in-flight. + if (seq > getTraitsMessageSequence()) { + qWarning() << "Received BulkAvatarTraitsAck with future seq (potential avatar mixer restart) " << seq << " from " + << message.getSenderSockAddr(); + } + } +} + +void AvatarMixerClientData::checkSkeletonURLAgainstWhitelist(const SlaveSharedData& slaveSharedData, + Node& sendingNode, AvatarTraits::TraitVersion traitVersion) { const auto& whitelist = slaveSharedData.skeletonURLWhitelist; @@ -281,14 +342,18 @@ void AvatarMixerClientData::removeFromRadiusIgnoringSet(const QUuid& other) { void AvatarMixerClientData::resetSentTraitData(Node::LocalID nodeLocalID) { _lastSentTraitsTimestamps[nodeLocalID] = TraitsCheckTimestamp(); - _sentTraitVersions[nodeLocalID].reset(); + _perNodeSentTraitVersions[nodeLocalID].reset(); + _perNodeAckedTraitVersions[nodeLocalID].reset(); + for (auto && pendingTraitVersions : _perNodePendingTraitVersions) { + pendingTraitVersions.second[nodeLocalID].reset(); + } } void AvatarMixerClientData::readViewFrustumPacket(const QByteArray& message) { _currentViewFrustums.clear(); auto sourceBuffer = reinterpret_cast(message.constData()); - + uint8_t numFrustums = 0; memcpy(&numFrustums, sourceBuffer, sizeof(numFrustums)); sourceBuffer += sizeof(numFrustums); @@ -316,7 +381,8 @@ void AvatarMixerClientData::loadJSONStats(QJsonObject& jsonObject) const { jsonObject["total_num_out_of_order_sends"] = _numOutOfOrderSends; jsonObject[OUTBOUND_AVATAR_DATA_STATS_KEY] = getOutboundAvatarDataKbps(); - jsonObject[INBOUND_AVATAR_DATA_STATS_KEY] = _avatar->getAverageBytesReceivedPerSecond() / (float) BYTES_PER_KILOBIT; + jsonObject[OUTBOUND_AVATAR_TRAITS_STATS_KEY] = getOutboundAvatarTraitsKbps(); + jsonObject[INBOUND_AVATAR_DATA_STATS_KEY] = _avatar->getAverageBytesReceivedPerSecond() / (float)BYTES_PER_KILOBIT; jsonObject["av_data_receive_rate"] = _avatar->getReceiveRate(); jsonObject["recent_other_av_in_view"] = _recentOtherAvatarsInView; @@ -337,5 +403,5 @@ void AvatarMixerClientData::cleanupKilledNode(const QUuid&, Node::LocalID nodeLo removeLastBroadcastSequenceNumber(nodeLocalID); removeLastBroadcastTime(nodeLocalID); _lastSentTraitsTimestamps.erase(nodeLocalID); - _sentTraitVersions.erase(nodeLocalID); + _perNodeSentTraitVersions.erase(nodeLocalID); } diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index 8a86af384a..843f19cf22 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -32,6 +32,7 @@ #include const QString OUTBOUND_AVATAR_DATA_STATS_KEY = "outbound_av_data_kbps"; +const QString OUTBOUND_AVATAR_TRAITS_STATS_KEY = "outbound_av_traits_kbps"; const QString INBOUND_AVATAR_DATA_STATS_KEY = "inbound_av_data_kbps"; struct SlaveSharedData; @@ -42,6 +43,7 @@ public: AvatarMixerClientData(const QUuid& nodeID, Node::LocalID nodeLocalID); virtual ~AvatarMixerClientData() {} using HRCTime = p_high_resolution_clock::time_point; + using PerNodeTraitVersions = std::unordered_map; int parseData(ReceivedMessage& message) override; AvatarData& getAvatar() { return *_avatar; } @@ -85,10 +87,15 @@ public: void incrementNumFramesSinceFRDAdjustment() { ++_numFramesSinceAdjustment; } void resetNumFramesSinceFRDAdjustment() { _numFramesSinceAdjustment = 0; } - void recordSentAvatarData(int numBytes) { _avgOtherAvatarDataRate.updateAverage((float) numBytes); } + void recordSentAvatarData(int numDataBytes, int numTraitsBytes = 0) { + _avgOtherAvatarDataRate.updateAverage(numDataBytes); + _avgOtherAvatarTraitsRate.updateAverage(numTraitsBytes); + } float getOutboundAvatarDataKbps() const { return _avgOtherAvatarDataRate.getAverageSampleValuePerSecond() / (float) BYTES_PER_KILOBIT; } + float getOutboundAvatarTraitsKbps() const + { return _avgOtherAvatarTraitsRate.getAverageSampleValuePerSecond() / BYTES_PER_KILOBIT; } void loadJSONStats(QJsonObject& jsonObject) const; @@ -124,6 +131,7 @@ public: int processPackets(const SlaveSharedData& slaveSharedData); // returns number of packets processed void processSetTraitsMessage(ReceivedMessage& message, const SlaveSharedData& slaveSharedData, Node& sendingNode); + void processBulkAvatarTraitsAckMessage(ReceivedMessage& message); void checkSkeletonURLAgainstWhitelist(const SlaveSharedData& slaveSharedData, Node& sendingNode, AvatarTraits::TraitVersion traitVersion); @@ -138,7 +146,14 @@ public: void setLastOtherAvatarTraitsSendPoint(Node::LocalID otherAvatar, TraitsCheckTimestamp sendPoint) { _lastSentTraitsTimestamps[otherAvatar] = sendPoint; } - AvatarTraits::TraitVersions& getLastSentTraitVersions(Node::LocalID otherAvatar) { return _sentTraitVersions[otherAvatar]; } + AvatarTraits::TraitMessageSequence getTraitsMessageSequence() const { return _currentTraitsMessageSequence; } + AvatarTraits::TraitMessageSequence nextTraitsMessageSequence() { return ++_currentTraitsMessageSequence; } + AvatarTraits::TraitVersions& getPendingTraitVersions(AvatarTraits::TraitMessageSequence seq, Node::LocalID otherId) { + return _perNodePendingTraitVersions[seq][otherId]; + } + + AvatarTraits::TraitVersions& getLastSentTraitVersions(Node::LocalID otherAvatar) { return _perNodeSentTraitVersions[otherAvatar]; } + AvatarTraits::TraitVersions& getLastAckedTraitVersions(Node::LocalID otherAvatar) { return _perNodeAckedTraitVersions[otherAvatar]; } void resetSentTraitData(Node::LocalID nodeID); @@ -171,6 +186,7 @@ private: int _numOutOfOrderSends = 0; SimpleMovingAverage _avgOtherAvatarDataRate; + SimpleMovingAverage _avgOtherAvatarTraitsRate; std::vector _radiusIgnoredOthers; ConicalViewFrustums _currentViewFrustums; @@ -183,8 +199,27 @@ private: AvatarTraits::TraitVersions _lastReceivedTraitVersions; TraitsCheckTimestamp _lastReceivedTraitsChange; + AvatarTraits::TraitMessageSequence _currentTraitsMessageSequence{ 0 }; + + // Cache of trait versions sent in a given packet (indexed by sequence number) + // When an ack is received, the sequence number in the ack is used to look up + // the sent trait versions and they are copied to _perNodeAckedTraitVersions. + // We remember the data in _perNodePendingTraitVersions instead of requiring + // the client to return all of the versions for each trait it received in a given packet, + // reducing the size of the ack packet. + std::unordered_map _perNodePendingTraitVersions; + + // Versions of traits that have been acked, which will be compared to incoming + // trait updates. Incoming updates going to a given node will be ignored if + // the ack for the previous packet (containing those versions) has not been + // received. + PerNodeTraitVersions _perNodeAckedTraitVersions; + std::unordered_map _lastSentTraitsTimestamps; - std::unordered_map _sentTraitVersions; + + // cache of traits sent to a node which are compared to incoming traits to + // prevent sending traits that have already been sent. + PerNodeTraitVersions _perNodeSentTraitVersions; std::atomic_bool _isIgnoreRadiusEnabled { false }; }; diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index cd9d164ef7..6b039e2c03 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -73,52 +73,82 @@ int AvatarMixerSlave::sendIdentityPacket(NLPacketList& packetList, const AvatarM QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray(); individualData.replace(0, NUM_BYTES_RFC4122_UUID, nodeData->getNodeID().toRfc4122()); // FIXME, this looks suspicious packetList.write(individualData); - _stats.numIdentityPackets++; + _stats.numIdentityPacketsSent++; + _stats.numIdentityBytesSent += individualData.size(); return individualData.size(); } else { return 0; } } +qint64 AvatarMixerSlave::addTraitsNodeHeader(AvatarMixerClientData* listeningNodeData, + const AvatarMixerClientData* sendingNodeData, + NLPacketList& traitsPacketList, + qint64 bytesWritten) { + if (bytesWritten == 0) { + if (traitsPacketList.getNumPackets() == 0) { + // This is the beginning of the traits packet, write out the sequence number. + bytesWritten += traitsPacketList.writePrimitive(listeningNodeData->nextTraitsMessageSequence()); + } + // This is the beginning of the traits for a node, write out the node id + bytesWritten += traitsPacketList.write(sendingNodeData->getNodeID().toRfc4122()); + } + return bytesWritten; +} + qint64 AvatarMixerSlave::addChangedTraitsToBulkPacket(AvatarMixerClientData* listeningNodeData, const AvatarMixerClientData* sendingNodeData, NLPacketList& traitsPacketList) { - auto otherNodeLocalID = sendingNodeData->getNodeLocalID(); + // Avatar Traits flow control marks each outgoing avatar traits packet with a + // sequence number. The mixer caches the traits sent in the traits packet. + // Until an ack with the sequence number comes back, all updates to _traits + // in that packet_ are ignored. Updates to traits not in that packet will + // be sent. + + auto sendingNodeLocalID = sendingNodeData->getNodeLocalID(); // Perform a simple check with two server clock time points // to see if there is any new traits data for this avatar that we need to send - auto timeOfLastTraitsSent = listeningNodeData->getLastOtherAvatarTraitsSendPoint(otherNodeLocalID); + auto timeOfLastTraitsSent = listeningNodeData->getLastOtherAvatarTraitsSendPoint(sendingNodeLocalID); auto timeOfLastTraitsChange = sendingNodeData->getLastReceivedTraitsChange(); + bool allTraitsUpdated = true; qint64 bytesWritten = 0; if (timeOfLastTraitsChange > timeOfLastTraitsSent) { // there is definitely new traits data to send - // add the avatar ID to mark the beginning of traits for this avatar - bytesWritten += traitsPacketList.write(sendingNodeData->getNodeID().toRfc4122()); - auto sendingAvatar = sendingNodeData->getAvatarSharedPointer(); // compare trait versions so we can see what exactly needs to go out - auto& lastSentVersions = listeningNodeData->getLastSentTraitVersions(otherNodeLocalID); + auto& lastSentVersions = listeningNodeData->getLastSentTraitVersions(sendingNodeLocalID); + auto& lastAckedVersions = listeningNodeData->getLastAckedTraitVersions(sendingNodeLocalID); const auto& lastReceivedVersions = sendingNodeData->getLastReceivedTraitVersions(); auto simpleReceivedIt = lastReceivedVersions.simpleCBegin(); while (simpleReceivedIt != lastReceivedVersions.simpleCEnd()) { auto traitType = static_cast(std::distance(lastReceivedVersions.simpleCBegin(), simpleReceivedIt)); - auto lastReceivedVersion = *simpleReceivedIt; auto& lastSentVersionRef = lastSentVersions[traitType]; + auto& lastAckedVersionRef = lastAckedVersions[traitType]; - if (lastReceivedVersions[traitType] > lastSentVersionRef) { - // there is an update to this trait, add it to the traits packet - bytesWritten += sendingAvatar->packTrait(traitType, traitsPacketList, lastReceivedVersion); - - // update the last sent version - lastSentVersionRef = lastReceivedVersion; + // hold sending more traits until we've been acked that the last one we sent was received + if (lastSentVersionRef == lastAckedVersionRef) { + if (lastReceivedVersion > lastSentVersionRef) { + bytesWritten += addTraitsNodeHeader(listeningNodeData, sendingNodeData, traitsPacketList, bytesWritten); + // there is an update to this trait, add it to the traits packet + bytesWritten += sendingAvatar->packTrait(traitType, traitsPacketList, lastReceivedVersion); + // update the last sent version + lastSentVersionRef = lastReceivedVersion; + // Remember which versions we sent in this particular packet + // so we can verify when it's acked. + auto& pendingTraitVersions = listeningNodeData->getPendingTraitVersions(listeningNodeData->getTraitsMessageSequence(), sendingNodeLocalID); + pendingTraitVersions[traitType] = lastReceivedVersion; + } + } else { + allTraitsUpdated = false; } ++simpleReceivedIt; @@ -131,6 +161,7 @@ qint64 AvatarMixerSlave::addChangedTraitsToBulkPacket(AvatarMixerClientData* lis // get or create the sent trait versions for this trait type auto& sentIDValuePairs = lastSentVersions.getInstanceIDValuePairs(traitType); + auto& ackIDValuePairs = lastAckedVersions.getInstanceIDValuePairs(traitType); // enumerate each received instance for (auto& receivedInstance : instancedReceivedIt->instances) { @@ -148,8 +179,19 @@ qint64 AvatarMixerSlave::addChangedTraitsToBulkPacket(AvatarMixerClientData* lis { return sentInstance.id == instanceID; }); - + // look for existing acked version for this instance + auto ackedInstanceIt = std::find_if(ackIDValuePairs.begin(), ackIDValuePairs.end(), + [instanceID](auto& ackInstance) { return ackInstance.id == instanceID; }); + + // if we have a sent version, then we must have an acked instance of the same trait with the same + // version to go on, otherwise we drop the received trait + if (sentInstanceIt != sentIDValuePairs.end() && + (ackedInstanceIt == ackIDValuePairs.end() || sentInstanceIt->value != ackedInstanceIt->value)) { + allTraitsUpdated = false; + continue; + } if (!isDeleted && (sentInstanceIt == sentIDValuePairs.end() || receivedVersion > sentInstanceIt->value)) { + bytesWritten += addTraitsNodeHeader(listeningNodeData, sendingNodeData, traitsPacketList, bytesWritten); // this instance version exists and has never been sent or is newer so we need to send it bytesWritten += sendingAvatar->packTraitInstance(traitType, instanceID, traitsPacketList, receivedVersion); @@ -159,25 +201,40 @@ qint64 AvatarMixerSlave::addChangedTraitsToBulkPacket(AvatarMixerClientData* lis } else { sentIDValuePairs.emplace_back(instanceID, receivedVersion); } + + auto& pendingTraitVersions = + listeningNodeData->getPendingTraitVersions(listeningNodeData->getTraitsMessageSequence(), + sendingNodeLocalID); + pendingTraitVersions.instanceInsert(traitType, instanceID, receivedVersion); + } else if (isDeleted && sentInstanceIt != sentIDValuePairs.end() && absoluteReceivedVersion > sentInstanceIt->value) { + bytesWritten += addTraitsNodeHeader(listeningNodeData, sendingNodeData, traitsPacketList, bytesWritten); // this instance version was deleted and we haven't sent the delete to this client yet bytesWritten += AvatarTraits::packInstancedTraitDelete(traitType, instanceID, traitsPacketList, absoluteReceivedVersion); // update the last sent version for this trait instance to the absolute value of the deleted version sentInstanceIt->value = absoluteReceivedVersion; + + auto& pendingTraitVersions = + listeningNodeData->getPendingTraitVersions(listeningNodeData->getTraitsMessageSequence(), + sendingNodeLocalID); + pendingTraitVersions.instanceInsert(traitType, instanceID, absoluteReceivedVersion); + } } ++instancedReceivedIt; } - - // write a null trait type to mark the end of trait data for this avatar - bytesWritten += traitsPacketList.writePrimitive(AvatarTraits::NullTrait); - - // since we send all traits for this other avatar, update the time of last traits sent - // to match the time of last traits change - listeningNodeData->setLastOtherAvatarTraitsSendPoint(otherNodeLocalID, timeOfLastTraitsChange); + if (bytesWritten) { + // write a null trait type to mark the end of trait data for this avatar + bytesWritten += traitsPacketList.writePrimitive(AvatarTraits::NullTrait); + // since we send all traits for this other avatar, update the time of last traits sent + // to match the time of last traits change + if (allTraitsUpdated) { + listeningNodeData->setLastOtherAvatarTraitsSendPoint(sendingNodeLocalID, timeOfLastTraitsChange); + } + } } @@ -191,7 +248,8 @@ int AvatarMixerSlave::sendReplicatedIdentityPacket(const Node& agentNode, const auto identityPacket = NLPacketList::create(PacketType::ReplicatedAvatarIdentity, QByteArray(), true, true); identityPacket->write(individualData); DependencyManager::get()->sendPacketList(std::move(identityPacket), destinationNode); - _stats.numIdentityPackets++; + _stats.numIdentityPacketsSent++; + _stats.numIdentityBytesSent += individualData.size(); return individualData.size(); } else { return 0; @@ -419,6 +477,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) int remainingAvatars = (int)sortedAvatars.size(); auto traitsPacketList = NLPacketList::create(PacketType::BulkAvatarTraits, QByteArray(), true, true); + auto avatarPacket = NLPacket::create(PacketType::BulkAvatarData); const int avatarPacketCapacity = avatarPacket->getPayloadCapacity(); int avatarSpaceAvailable = avatarPacketCapacity; @@ -539,17 +598,16 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) ++numPacketsSent; } - _stats.numPacketsSent += numPacketsSent; - _stats.numBytesSent += numAvatarDataBytes; - - // record the bytes sent for other avatar data in the AvatarMixerClientData - nodeData->recordSentAvatarData(numAvatarDataBytes); + _stats.numDataPacketsSent += numPacketsSent; + _stats.numDataBytesSent += numAvatarDataBytes; // close the current traits packet list traitsPacketList->closeCurrentPacket(); if (traitsPacketList->getNumPackets() >= 1) { // send the traits packet list + _stats.numTraitsBytesSent += traitBytesSent; + _stats.numTraitsPacketsSent += (int) traitsPacketList->getNumPackets(); nodeList->sendPacketList(std::move(traitsPacketList), *destinationNode); } @@ -559,6 +617,10 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) nodeList->sendPacketList(std::move(identityPacketList), *destinationNode); } + // record the bytes sent for other avatar data in the AvatarMixerClientData + nodeData->recordSentAvatarData(numAvatarDataBytes, traitBytesSent); + + // record the number of avatars held back this frame nodeData->recordNumOtherAvatarStarves(numAvatarsHeldBack); nodeData->recordNumOtherAvatarSkips(numAvatarsWithSkippedFrames); @@ -685,8 +747,8 @@ void AvatarMixerSlave::broadcastAvatarDataToDownstreamMixer(const SharedNodePoin // close the current packet so that we're always sending something avatarPacketList->closeCurrentPacket(true); - _stats.numPacketsSent += (int)avatarPacketList->getNumPackets(); - _stats.numBytesSent += numAvatarDataBytes; + _stats.numDataPacketsSent += (int)avatarPacketList->getNumPackets(); + _stats.numDataBytesSent += numAvatarDataBytes; // send the replicated bulk avatar data auto nodeList = DependencyManager::get(); diff --git a/assignment-client/src/avatars/AvatarMixerSlave.h b/assignment-client/src/avatars/AvatarMixerSlave.h index 2ef90af38e..91bb02fd55 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.h +++ b/assignment-client/src/avatars/AvatarMixerSlave.h @@ -24,9 +24,12 @@ public: int nodesBroadcastedTo { 0 }; int downstreamMixersBroadcastedTo { 0 }; - int numPacketsSent { 0 }; - int numBytesSent { 0 }; - int numIdentityPackets { 0 }; + int numDataBytesSent { 0 }; + int numTraitsBytesSent { 0 }; + int numIdentityBytesSent { 0 }; + int numDataPacketsSent { 0 }; + int numTraitsPacketsSent { 0 }; + int numIdentityPacketsSent { 0 }; int numOthersIncluded { 0 }; int overBudgetAvatars { 0 }; @@ -45,9 +48,13 @@ public: // sending job stats nodesBroadcastedTo = 0; downstreamMixersBroadcastedTo = 0; - numPacketsSent = 0; - numBytesSent = 0; - numIdentityPackets = 0; + + numDataBytesSent = 0; + numTraitsBytesSent = 0; + numIdentityBytesSent = 0; + numDataPacketsSent = 0; + numTraitsPacketsSent = 0; + numIdentityPacketsSent = 0; numOthersIncluded = 0; overBudgetAvatars = 0; @@ -65,9 +72,12 @@ public: nodesBroadcastedTo += rhs.nodesBroadcastedTo; downstreamMixersBroadcastedTo += rhs.downstreamMixersBroadcastedTo; - numPacketsSent += rhs.numPacketsSent; - numBytesSent += rhs.numBytesSent; - numIdentityPackets += rhs.numIdentityPackets; + numDataBytesSent += rhs.numDataBytesSent; + numTraitsBytesSent += rhs.numTraitsBytesSent; + numIdentityBytesSent += rhs.numIdentityBytesSent; + numDataPacketsSent += rhs.numDataPacketsSent; + numTraitsPacketsSent += rhs.numTraitsPacketsSent; + numIdentityPacketsSent += rhs.numIdentityPacketsSent; numOthersIncluded += rhs.numOthersIncluded; overBudgetAvatars += rhs.overBudgetAvatars; @@ -104,6 +114,11 @@ private: int sendIdentityPacket(NLPacketList& packet, const AvatarMixerClientData* nodeData, const Node& destinationNode); int sendReplicatedIdentityPacket(const Node& agentNode, const AvatarMixerClientData* nodeData, const Node& destinationNode); + qint64 addTraitsNodeHeader(AvatarMixerClientData* listeningNodeData, + const AvatarMixerClientData* sendingNodeData, + NLPacketList& traitsPacketList, + qint64 bytesWritten); + qint64 addChangedTraitsToBulkPacket(AvatarMixerClientData* listeningNodeData, const AvatarMixerClientData* sendingNodeData, NLPacketList& traitsPacketList); diff --git a/assignment-client/src/avatars/ScriptableAvatar.cpp b/assignment-client/src/avatars/ScriptableAvatar.cpp index a2e13a03be..c61e41fbbe 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.cpp +++ b/assignment-client/src/avatars/ScriptableAvatar.cpp @@ -21,6 +21,8 @@ #include #include #include +#include +#include ScriptableAvatar::ScriptableAvatar() { @@ -249,3 +251,157 @@ void ScriptableAvatar::setHasProceduralEyeFaceMovement(bool hasProceduralEyeFace void ScriptableAvatar::setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement) { _headData->setHasAudioEnabledFaceMovement(hasAudioEnabledFaceMovement); } + +AvatarEntityMap ScriptableAvatar::getAvatarEntityData() const { + // DANGER: Now that we store the AvatarEntityData in packed format this call is potentially Very Expensive! + // Avoid calling this method if possible. + AvatarEntityMap data; + QUuid sessionID = getID(); + _avatarEntitiesLock.withReadLock([&] { + for (const auto& itr : _entities) { + QUuid id = itr.first; + EntityItemPointer entity = itr.second; + EntityItemProperties properties = entity->getProperties(); + QByteArray blob; + EntityItemProperties::propertiesToBlob(_scriptEngine, sessionID, properties, blob); + data[id] = blob; + } + }); + return data; +} + +void ScriptableAvatar::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) { + // Note: this is an invokable Script call + // avatarEntityData is expected to be a map of QByteArrays that represent EntityItemProperties objects from JavaScript + // + if (avatarEntityData.size() > MAX_NUM_AVATAR_ENTITIES) { + // the data is suspect + qCDebug(avatars) << "discard suspect avatarEntityData with size =" << avatarEntityData.size(); + return; + } + + // convert binary data to EntityItemProperties + // NOTE: this operation is NOT efficient + std::map newProperties; + AvatarEntityMap::const_iterator dataItr = avatarEntityData.begin(); + while (dataItr != avatarEntityData.end()) { + EntityItemProperties properties; + const QByteArray& blob = dataItr.value(); + if (!blob.isNull() && EntityItemProperties::blobToProperties(_scriptEngine, blob, properties)) { + newProperties[dataItr.key()] = properties; + } + ++dataItr; + } + + // delete existing entities not found in avatarEntityData + std::vector idsToClear; + _avatarEntitiesLock.withWriteLock([&] { + std::map::iterator entityItr = _entities.begin(); + while (entityItr != _entities.end()) { + QUuid id = entityItr->first; + std::map::const_iterator propertiesItr = newProperties.find(id); + if (propertiesItr == newProperties.end()) { + idsToClear.push_back(id); + entityItr = _entities.erase(entityItr); + } else { + ++entityItr; + } + } + }); + + // add or update entities + _avatarEntitiesLock.withWriteLock([&] { + std::map::const_iterator propertiesItr = newProperties.begin(); + while (propertiesItr != newProperties.end()) { + QUuid id = propertiesItr->first; + const EntityItemProperties& properties = propertiesItr->second; + std::map::iterator entityItr = _entities.find(id); + EntityItemPointer entity; + if (entityItr != _entities.end()) { + entity = entityItr->second; + entity->setProperties(properties); + } else { + entity = EntityTypes::constructEntityItem(id, properties); + } + if (entity) { + // build outgoing payload + OctreePacketData packetData(false, AvatarTraits::MAXIMUM_TRAIT_SIZE); + EncodeBitstreamParams params; + EntityTreeElementExtraEncodeDataPointer extra { nullptr }; + OctreeElement::AppendState appendState = entity->appendEntityData(&packetData, params, extra); + + if (appendState == OctreeElement::COMPLETED) { + _entities[id] = entity; + QByteArray tempArray((const char*)packetData.getUncompressedData(), packetData.getUncompressedSize()); + storeAvatarEntityDataPayload(id, tempArray); + } else { + // payload doesn't fit + entityItr = _entities.find(id); + if (entityItr != _entities.end()) { + _entities.erase(entityItr); + idsToClear.push_back(id); + } + + } + } + ++propertiesItr; + } + }); + + // clear deleted traits + for (const auto& id : idsToClear) { + clearAvatarEntity(id); + } +} + +void ScriptableAvatar::updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) { + if (entityData.isNull()) { + // interpret this as a DELETE + std::map::iterator itr = _entities.find(entityID); + if (itr != _entities.end()) { + _entities.erase(itr); + clearAvatarEntity(entityID); + } + return; + } + + EntityItemPointer entity; + EntityItemProperties properties; + if (!EntityItemProperties::blobToProperties(_scriptEngine, entityData, properties)) { + // entityData is corrupt + return; + } + + std::map::iterator itr = _entities.find(entityID); + if (itr == _entities.end()) { + // this is an ADD + entity = EntityTypes::constructEntityItem(entityID, properties); + if (entity) { + OctreePacketData packetData(false, AvatarTraits::MAXIMUM_TRAIT_SIZE); + EncodeBitstreamParams params; + EntityTreeElementExtraEncodeDataPointer extra { nullptr }; + OctreeElement::AppendState appendState = entity->appendEntityData(&packetData, params, extra); + + if (appendState == OctreeElement::COMPLETED) { + _entities[entityID] = entity; + QByteArray tempArray((const char*)packetData.getUncompressedData(), packetData.getUncompressedSize()); + storeAvatarEntityDataPayload(entityID, tempArray); + } + } + } else { + // this is an UPDATE + entity = itr->second; + bool somethingChanged = entity->setProperties(properties); + if (somethingChanged) { + OctreePacketData packetData(false, AvatarTraits::MAXIMUM_TRAIT_SIZE); + EncodeBitstreamParams params; + EntityTreeElementExtraEncodeDataPointer extra { nullptr }; + OctreeElement::AppendState appendState = entity->appendEntityData(&packetData, params, extra); + + if (appendState == OctreeElement::COMPLETED) { + QByteArray tempArray((const char*)packetData.getUncompressedData(), packetData.getUncompressedSize()); + storeAvatarEntityDataPayload(entityID, tempArray); + } + } + } +} diff --git a/assignment-client/src/avatars/ScriptableAvatar.h b/assignment-client/src/avatars/ScriptableAvatar.h index 66b0b5ae3f..df949f8bff 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.h +++ b/assignment-client/src/avatars/ScriptableAvatar.h @@ -16,6 +16,7 @@ #include #include #include +#include /**jsdoc * The Avatar API is used to manipulate scriptable avatars on the domain. This API is a subset of the @@ -185,6 +186,26 @@ public: void setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement); bool getHasAudioEnabledFaceMovement() const override { return _headData->getHasAudioEnabledFaceMovement(); } + /**jsdoc + * Potentially Very Expensive. Do not use. + * @function Avatar.getAvatarEntityData + * @returns {object} + */ + Q_INVOKABLE AvatarEntityMap getAvatarEntityData() const override; + + /**jsdoc + * @function MyAvatar.setAvatarEntityData + * @param {object} avatarEntityData + */ + Q_INVOKABLE void setAvatarEntityData(const AvatarEntityMap& avatarEntityData) override; + + /**jsdoc + * @function MyAvatar.updateAvatarEntity + * @param {Uuid} entityID + * @param {string} entityData + */ + Q_INVOKABLE void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) override; + public slots: void update(float deltatime); @@ -202,6 +223,8 @@ private: QHash _fstJointIndices; ///< 1-based, since zero is returned for missing keys QStringList _fstJointNames; ///< in order of depth-first traversal QUrl _skeletonFBXURL; + mutable QScriptEngine _scriptEngine; + std::map _entities; /// Loads the joint indices, names from the FST file (if any) void updateJointMappings(); diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index 089fb3e52f..581d854909 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include "../AssignmentDynamicFactory.h" #include "AssignmentParentFinder.h" @@ -45,6 +46,8 @@ EntityServer::EntityServer(ReceivedMessage& message) : DependencyManager::registerInheritance(); DependencyManager::set(); + DependencyManager::set(); // ModelFormatRegistry must be defined before ModelCache. See the ModelCache ctor + DependencyManager::set(); auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); packetReceiver.registerListenerForTypes({ PacketType::EntityAdd, diff --git a/assignment-client/src/messages/MessagesMixer.cpp b/assignment-client/src/messages/MessagesMixer.cpp index c11c8f40a0..d2127835f9 100644 --- a/assignment-client/src/messages/MessagesMixer.cpp +++ b/assignment-client/src/messages/MessagesMixer.cpp @@ -75,8 +75,8 @@ void MessagesMixer::sendStatsPacket() { DependencyManager::get()->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; }); diff --git a/interface/icon/interface-beta.icns b/interface/icon/interface-beta.icns index 1e2a4baeaa..5c1eddd8d2 100644 Binary files a/interface/icon/interface-beta.icns and b/interface/icon/interface-beta.icns differ diff --git a/interface/icon/interface-beta.ico b/interface/icon/interface-beta.ico index 1ed1ebddb9..d934d2fe0c 100644 Binary files a/interface/icon/interface-beta.ico and b/interface/icon/interface-beta.ico differ diff --git a/interface/icon/interface.icns b/interface/icon/interface.icns index 332539af2a..1035942e09 100644 Binary files a/interface/icon/interface.icns and b/interface/icon/interface.icns differ diff --git a/interface/icon/interface.ico b/interface/icon/interface.ico index e3d852cb41..077cfdeb63 100644 Binary files a/interface/icon/interface.ico and b/interface/icon/interface.ico differ diff --git a/interface/resources/config/render.json b/interface/resources/config/render.json index b5b72d7d07..2c63c08510 100644 --- a/interface/resources/config/render.json +++ b/interface/resources/config/render.json @@ -1,16 +1,2 @@ { - "RenderMainView": { - "RenderShadowTask": { - "Enabled": { - "enabled": true - } - }, - "RenderDeferredTask": { - "AmbientOcclusion": { - "Enabled": { - "enabled": true - } - } - } - } } diff --git a/interface/resources/controllers/keyboardMouse.json b/interface/resources/controllers/keyboardMouse.json index 2ad07911c6..74c11203ef 100644 --- a/interface/resources/controllers/keyboardMouse.json +++ b/interface/resources/controllers/keyboardMouse.json @@ -123,7 +123,16 @@ { "from": { "makeAxis" : ["Keyboard.MouseMoveLeft", "Keyboard.MouseMoveRight"] }, "when": "Keyboard.RightMouseButton", - "to": "Actions.Yaw", + "to": "Actions.DeltaYaw", + "filters": + [ + { "type": "scale", "scale": 0.6 } + ] + }, + + { "from": { "makeAxis" : ["Keyboard.MouseMoveUp", "Keyboard.MouseMoveDown"] }, + "when": "Keyboard.RightMouseButton", + "to": "Actions.DeltaPitch", "filters": [ { "type": "scale", "scale": 0.6 } @@ -144,20 +153,6 @@ { "from": "Keyboard.PgDown", "to": "Actions.VERTICAL_DOWN" }, { "from": "Keyboard.PgUp", "to": "Actions.VERTICAL_UP" }, - { "from": "Keyboard.MouseMoveUp", "when": "Keyboard.RightMouseButton", "to": "Actions.PITCH_UP", - "filters": - [ - { "type": "scale", "scale": 0.6 } - ] - - }, - { "from": "Keyboard.MouseMoveDown", "when": "Keyboard.RightMouseButton", "to": "Actions.PITCH_DOWN", - "filters": - [ - { "type": "scale", "scale": 0.6 } - ] - }, - { "from": "Keyboard.TouchpadDown", "to": "Actions.PITCH_DOWN" }, { "from": "Keyboard.TouchpadUp", "to": "Actions.PITCH_UP" }, diff --git a/interface/resources/fonts/hifi-glyphs.ttf b/interface/resources/fonts/hifi-glyphs.ttf index c27553333b..aaeb1d2ace 100644 Binary files a/interface/resources/fonts/hifi-glyphs.ttf and b/interface/resources/fonts/hifi-glyphs.ttf differ diff --git a/interface/resources/icons/checkmark-stroke.svg b/interface/resources/icons/checkmark-stroke.svg new file mode 100644 index 0000000000..cc343c421b --- /dev/null +++ b/interface/resources/icons/checkmark-stroke.svg @@ -0,0 +1,4 @@ + + + + diff --git a/interface/resources/icons/loader-snake-256-wf.gif b/interface/resources/icons/loader-snake-256-wf.gif new file mode 100644 index 0000000000..c0d5eec1ef Binary files /dev/null and b/interface/resources/icons/loader-snake-256-wf.gif differ diff --git a/interface/resources/icons/loader-snake-256.gif b/interface/resources/icons/loader-snake-256.gif new file mode 100644 index 0000000000..ebcbf54bd7 Binary files /dev/null and b/interface/resources/icons/loader-snake-256.gif differ diff --git a/interface/resources/images/loader-snake-128.png b/interface/resources/images/loader-snake-128.png new file mode 100644 index 0000000000..b8ee577664 Binary files /dev/null and b/interface/resources/images/loader-snake-128.png differ diff --git a/interface/resources/qml/+android/Stats.qml b/interface/resources/qml/+android/Stats.qml index e9a2aa47eb..fe56f3797b 100644 --- a/interface/resources/qml/+android/Stats.qml +++ b/interface/resources/qml/+android/Stats.qml @@ -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; diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index 1a29ce87df..a65170ee3b 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -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; diff --git a/interface/resources/qml/controlsUit/Button.qml b/interface/resources/qml/controlsUit/Button.qml index c5c879a24c..3c5626e29e 100644 --- a/interface/resources/qml/controlsUit/Button.qml +++ b/interface/resources/qml/controlsUit/Button.qml @@ -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; diff --git a/interface/resources/qml/hifi/AssetServer.qml b/interface/resources/qml/hifi/AssetServer.qml index ad337a6361..247a42428a 100644 --- a/interface/resources/qml/hifi/AssetServer.qml +++ b/interface/resources/qml/hifi/AssetServer.qml @@ -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; diff --git a/interface/resources/qml/hifi/AvatarApp.qml b/interface/resources/qml/hifi/AvatarApp.qml index 57e4db062a..171ea4fd15 100644 --- a/interface/resources/qml/hifi/AvatarApp.qml +++ b/interface/resources/qml/hifi/AvatarApp.qml @@ -10,7 +10,7 @@ import "avatarapp" Rectangle { id: root width: 480 - height: 706 + height: 706 property bool keyboardEnabled: true property bool keyboardRaised: false @@ -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 }; @@ -415,7 +416,7 @@ Rectangle { width: 21.2 height: 19.3 source: isAvatarInFavorites ? '../../images/FavoriteIconActive.svg' : '../../images/FavoriteIconInActive.svg' - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter } // TextStyle5 @@ -424,7 +425,7 @@ Rectangle { Layout.fillWidth: true text: isAvatarInFavorites ? avatarName : "Add to Favorites" elide: Qt.ElideRight - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter } } diff --git a/interface/resources/qml/hifi/AvatarPackagerWindow.qml b/interface/resources/qml/hifi/AvatarPackagerWindow.qml new file mode 100644 index 0000000000..82bcd3fa40 --- /dev/null +++ b/interface/resources/qml/hifi/AvatarPackagerWindow.qml @@ -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 + } +} diff --git a/interface/resources/qml/hifi/avatarPackager/AvatarPackagerApp.qml b/interface/resources/qml/hifi/avatarPackager/AvatarPackagerApp.qml new file mode 100644 index 0000000000..b4293d5eee --- /dev/null +++ b/interface/resources/qml/hifi/avatarPackager/AvatarPackagerApp.qml @@ -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: "Visit our docs 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 + } + } +} diff --git a/interface/resources/qml/hifi/avatarPackager/AvatarPackagerFooter.qml b/interface/resources/qml/hifi/avatarPackager/AvatarPackagerFooter.qml new file mode 100644 index 0000000000..31e05672d2 --- /dev/null +++ b/interface/resources/qml/hifi/avatarPackager/AvatarPackagerFooter.qml @@ -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 + } + } +} diff --git a/interface/resources/qml/hifi/avatarPackager/AvatarPackagerHeader.qml b/interface/resources/qml/hifi/avatarPackager/AvatarPackagerHeader.qml new file mode 100644 index 0000000000..25201bf81e --- /dev/null +++ b/interface/resources/qml/hifi/avatarPackager/AvatarPackagerHeader.qml @@ -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(); + } + } +} diff --git a/interface/resources/qml/hifi/avatarPackager/AvatarPackagerState.qml b/interface/resources/qml/hifi/avatarPackager/AvatarPackagerState.qml new file mode 100644 index 0000000000..c81173a080 --- /dev/null +++ b/interface/resources/qml/hifi/avatarPackager/AvatarPackagerState.qml @@ -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" +} diff --git a/interface/resources/qml/hifi/avatarPackager/AvatarProject.qml b/interface/resources/qml/hifi/avatarPackager/AvatarProject.qml new file mode 100644 index 0000000000..85ef821a4a --- /dev/null +++ b/interface/resources/qml/hifi/avatarPackager/AvatarProject.qml @@ -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. See list" : "" + + 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 + } + } + } +} diff --git a/interface/resources/qml/hifi/avatarPackager/AvatarProjectCard.qml b/interface/resources/qml/hifi/avatarPackager/AvatarProjectCard.qml new file mode 100644 index 0000000000..25222c814c --- /dev/null +++ b/interface/resources/qml/hifi/avatarPackager/AvatarProjectCard.qml @@ -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: "" + 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 + } +} diff --git a/interface/resources/qml/hifi/avatarPackager/AvatarProjectUpload.qml b/interface/resources/qml/hifi/avatarPackager/AvatarProjectUpload.qml new file mode 100644 index 0000000000..68f465f514 --- /dev/null +++ b/interface/resources/qml/hifi/avatarPackager/AvatarProjectUpload.qml @@ -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; + } + } + } + } + } +} diff --git a/interface/resources/qml/hifi/avatarPackager/AvatarUploadStatusItem.qml b/interface/resources/qml/hifi/avatarPackager/AvatarUploadStatusItem.qml new file mode 100644 index 0000000000..70a0ea0672 --- /dev/null +++ b/interface/resources/qml/hifi/avatarPackager/AvatarUploadStatusItem.qml @@ -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" + } +} diff --git a/interface/resources/qml/hifi/avatarPackager/ClickableArea.qml b/interface/resources/qml/hifi/avatarPackager/ClickableArea.qml new file mode 100644 index 0000000000..0f7b201f72 --- /dev/null +++ b/interface/resources/qml/hifi/avatarPackager/ClickableArea.qml @@ -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; + } + } + } + ] + } +} diff --git a/interface/resources/qml/hifi/avatarPackager/CreateAvatarProject.qml b/interface/resources/qml/hifi/avatarPackager/CreateAvatarProject.qml new file mode 100644 index 0000000000..c299417c27 --- /dev/null +++ b/interface/resources/qml/hifi/avatarPackager/CreateAvatarProject.qml @@ -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() + } + } +} diff --git a/interface/resources/qml/hifi/avatarPackager/InfoBox.qml b/interface/resources/qml/hifi/avatarPackager/InfoBox.qml new file mode 100644 index 0000000000..e33e427af0 --- /dev/null +++ b/interface/resources/qml/hifi/avatarPackager/InfoBox.qml @@ -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; + } + } + } +} diff --git a/interface/resources/qml/hifi/avatarPackager/LoadingCircle.qml b/interface/resources/qml/hifi/avatarPackager/LoadingCircle.qml new file mode 100644 index 0000000000..a1fac72ae4 --- /dev/null +++ b/interface/resources/qml/hifi/avatarPackager/LoadingCircle.qml @@ -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 +} diff --git a/interface/resources/qml/hifi/avatarPackager/ProjectInputControl.qml b/interface/resources/qml/hifi/avatarPackager/ProjectInputControl.qml new file mode 100644 index 0000000000..f0a3aac8a7 --- /dev/null +++ b/interface/resources/qml/hifi/avatarPackager/ProjectInputControl.qml @@ -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; + }); + } + } + } +} diff --git a/interface/resources/qml/hifi/avatarPackager/RalewayButton.qml b/interface/resources/qml/hifi/avatarPackager/RalewayButton.qml new file mode 100644 index 0000000000..86742ddccd --- /dev/null +++ b/interface/resources/qml/hifi/avatarPackager/RalewayButton.qml @@ -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() + } +} diff --git a/interface/resources/qml/hifi/avatarPackager/Style.qml b/interface/resources/qml/hifi/avatarPackager/Style.qml new file mode 100644 index 0000000000..a1dcc8f0c1 --- /dev/null +++ b/interface/resources/qml/hifi/avatarPackager/Style.qml @@ -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" + } +} diff --git a/interface/resources/qml/hifi/avatarPackager/qmldir b/interface/resources/qml/hifi/avatarPackager/qmldir new file mode 100644 index 0000000000..4204b6d89f --- /dev/null +++ b/interface/resources/qml/hifi/avatarPackager/qmldir @@ -0,0 +1,2 @@ +module AvatarPackager +singleton AvatarPackagerState 1.0 AvatarPackagerState.qml diff --git a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml b/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml index 256d951a45..a537c65b23 100644 --- a/interface/resources/qml/hifi/avatarapp/AdjustWearables.qml +++ b/interface/resources/qml/hifi/avatarapp/AdjustWearables.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); } @@ -153,7 +157,7 @@ Rectangle { visible = false; adjustWearablesClosed(status, avatarName); } - + HifiConstants { id: hifi } @@ -226,7 +230,7 @@ Rectangle { lineHeightMode: Text.FixedHeight lineHeight: 18; text: "Wearable" - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter } spacing: 10 @@ -237,7 +241,7 @@ Rectangle { lineHeight: 18; text: "<a href='#'>Get more</a>" linkColor: hifi.colors.blueHighlight - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter onLinkActivated: { popup.showGetWearables(function() { emitSendToScript({'method' : 'navigate', 'url' : 'hifi://AvatarIsland/11.5848,-8.10862,-2.80195'}) diff --git a/interface/resources/qml/hifi/avatarapp/MessageBox.qml b/interface/resources/qml/hifi/avatarapp/MessageBox.qml index 1834364fe4..88f7f888cb 100644 --- a/interface/resources/qml/hifi/avatarapp/MessageBox.qml +++ b/interface/resources/qml/hifi/avatarapp/MessageBox.qml @@ -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" diff --git a/interface/resources/qml/hifi/avatarapp/Settings.qml b/interface/resources/qml/hifi/avatarapp/Settings.qml index cd892c17b1..668a950d0d 100644 --- a/interface/resources/qml/hifi/avatarapp/Settings.qml +++ b/interface/resources/qml/hifi/avatarapp/Settings.qml @@ -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; @@ -103,11 +104,11 @@ Rectangle { size: 17; text: "Avatar Scale" verticalAlignment: Text.AlignVCenter - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter } RowLayout { - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter Layout.fillWidth: true spacing: 0 @@ -117,7 +118,7 @@ Rectangle { text: 'T' verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter } HifiControlsUit.Slider { @@ -135,7 +136,7 @@ Rectangle { } } - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter Layout.fillWidth: true // TextStyle9 @@ -164,7 +165,7 @@ Rectangle { text: 'T' verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter - anchors.verticalCenter: parent.verticalCenter + Layout.alignment: Qt.AlignVCenter } } @@ -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 } } @@ -327,8 +316,7 @@ Rectangle { InputTextStyle4 { id: avatarAnimationUrlInputText font.pixelSize: 17 - anchors.left: parent.left - anchors.right: parent.right + Layout.fillWidth: true placeholderText: 'user\\file\\dir' onFocusChanged: { @@ -357,8 +345,7 @@ Rectangle { InputTextStyle4 { id: avatarCollisionSoundUrlInputText font.pixelSize: 17 - anchors.left: parent.left - anchors.right: parent.right + Layout.fillWidth: true placeholderText: 'https://hifi-public.s3.amazonaws.com/sounds/Collisions-' onFocusChanged: { diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index c8ec7238d6..f7dc26df5f 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -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; + } + } } } diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 18d6bc9f78..9433618b6b 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -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."; diff --git a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml index f665032b01..b5374b2fe0 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml @@ -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; diff --git a/interface/resources/qml/hifi/tablet/AvatarPackager.qml b/interface/resources/qml/hifi/tablet/AvatarPackager.qml new file mode 100644 index 0000000000..c1c234dd73 --- /dev/null +++ b/interface/resources/qml/hifi/tablet/AvatarPackager.qml @@ -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 + } +} diff --git a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml index b7d01a62e0..a2104826c3 100644 --- a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml +++ b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml @@ -869,7 +869,7 @@ Flickable { id: outOfRangeDataStrategyComboBox height: 25 - width: 100 + width: 150 editable: true colorScheme: hifi.colorSchemes.dark diff --git a/interface/resources/qml/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml index fa268ad6ee..a01d978b2f 100644 --- a/interface/resources/qml/hifi/tablet/TabletRoot.qml +++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml @@ -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); }); } } diff --git a/interface/resources/qml/hifi/tablet/WindowRoot.qml b/interface/resources/qml/hifi/tablet/WindowRoot.qml index d55ec363f0..ef2df5e218 100644 --- a/interface/resources/qml/hifi/tablet/WindowRoot.qml +++ b/interface/resources/qml/hifi/tablet/WindowRoot.qml @@ -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); }); } } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 167921f7b0..3711482b34 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -159,6 +159,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" @@ -171,6 +172,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" @@ -209,6 +211,8 @@ #include "InterfaceParentFinder.h" #include "ui/OctreeStatsProvider.h" +#include "avatar/GrabManager.h" + #include <GPUIdent.h> #include <gl/GLHelpers.h> #include <src/scripting/GooglePolyScriptingInterface.h> @@ -721,6 +725,8 @@ const QString TEST_RESULTS_LOCATION_COMMAND{ "--testResultsLocation" }; bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { const char** constArgv = const_cast<const char**>(argv); + qInstallMessageHandler(messageHandler); + // HRS: I could not figure out how to move these any earlier in startup, so when using this option, be sure to also supply // --allowMultipleInstances auto reportAndQuit = [&](const char* commandSwitch, std::function<void(FILE* fp)> report) { @@ -859,7 +865,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 +924,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; } @@ -969,6 +976,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo QApplication(argc, argv), _window(new MainWindow(desktop())), _sessionRunTimer(startupTimer), + _logger(new FileLogger(this)), _previousSessionCrashed(setupEssentials(argc, argv, runningMarkerExisted)), _entitySimulation(new PhysicalEntitySimulation()), _physicsEngine(new PhysicsEngine(Vectors::ZERO)), @@ -1058,9 +1066,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo installNativeEventFilter(&MyNativeEventFilter::getInstance()); #endif - _logger = new FileLogger(this); - qInstallMessageHandler(messageHandler); - QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "styles/Inconsolata.otf"); QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/fontawesome-webfont.ttf"); QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/hifi-glyphs.ttf"); @@ -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"] = assetServerNode ? 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(); @@ -2458,8 +2459,15 @@ void Application::updateHeartbeat() const { } void Application::onAboutToQuit() { + // quickly save AvatarEntityData before the EntityTree is dismantled + getMyAvatar()->saveAvatarEntityDataToSettings(); + emit beforeAboutToQuit(); + if (getLoginDialogPoppedUp() && _firstRun.get()) { + _firstRun.set(false); + } + foreach(auto inputPlugin, PluginManager::getInstance()->getInputPlugins()) { if (inputPlugin->isActive()) { inputPlugin->deactivate(); @@ -2614,6 +2622,7 @@ void Application::cleanupBeforeQuit() { DependencyManager::destroy<PickManager>(); DependencyManager::destroy<KeyboardScriptingInterface>(); DependencyManager::destroy<Keyboard>(); + DependencyManager::destroy<AvatarPackager>(); qCDebug(interfaceapp) << "Application::cleanupBeforeQuit() complete"; } @@ -4908,7 +4917,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; @@ -4983,16 +4992,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() { @@ -5257,7 +5262,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 { @@ -5265,39 +5271,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(); @@ -5980,6 +5972,8 @@ void Application::update(float deltaTime) { if (deltaTime > FLT_EPSILON) { myAvatar->setDriveKey(MyAvatar::PITCH, -1.0f * userInputMapper->getActionState(controller::Action::PITCH)); myAvatar->setDriveKey(MyAvatar::YAW, -1.0f * userInputMapper->getActionState(controller::Action::YAW)); + myAvatar->setDriveKey(MyAvatar::DELTA_PITCH, -1.0f * userInputMapper->getActionState(controller::Action::DELTA_PITCH)); + myAvatar->setDriveKey(MyAvatar::DELTA_YAW, -1.0f * userInputMapper->getActionState(controller::Action::DELTA_YAW)); myAvatar->setDriveKey(MyAvatar::STEP_YAW, -1.0f * userInputMapper->getActionState(controller::Action::STEP_YAW)); } } @@ -6098,6 +6092,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>(); { @@ -6701,6 +6698,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); } @@ -6765,8 +6763,10 @@ void Application::updateWindowTitle() const { } void Application::clearDomainOctreeDetails() { + // before we delete all entities get MyAvatar's AvatarEntityData ready + getMyAvatar()->prepareAvatarEntityDataForReload(); - // if we're about to quit, we really don't need to do any of these things... + // if we're about to quit, we really don't need to do the rest of these things... if (_aboutToQuit) { return; } @@ -6794,8 +6794,6 @@ void Application::clearDomainOctreeDetails() { ShaderCache::instance().clearUnusedResources(); DependencyManager::get<TextureCache>()->clearUnusedResources(); DependencyManager::get<recording::ClipCache>()->clearUnusedResources(); - - getMyAvatar()->setAvatarEntityDataChanged(true); } void Application::domainURLChanged(QUrl domainURL) { @@ -7004,6 +7002,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 @@ -7703,16 +7702,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); } @@ -8724,6 +8720,14 @@ void Application::updateLoginDialogOverlayPosition() { } } +bool Application::hasRiftControllers() { + return PluginUtils::isOculusTouchControllerAvailable(); +} + +bool Application::hasViveControllers() { + return PluginUtils::isViveControllerAvailable(); +} + void Application::onDismissedLoginDialog() { _loginDialogPoppedUp = false; loginDialogPoppedUp.set(false); @@ -8942,6 +8946,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>(); diff --git a/interface/src/Application.h b/interface/src/Application.h index fd45a594b5..4c6d45b8c3 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -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); @@ -588,6 +594,8 @@ private: bool _aboutToQuit { false }; + FileLogger* _logger { nullptr }; + bool _previousSessionCrashed; DisplayPluginPointer _displayPlugin; @@ -668,8 +676,6 @@ private: QPointer<EntityScriptServerLogDialog> _entityScriptServerLogDialog; QDir _defaultScriptsLocation; - FileLogger* _logger; - TouchEvent _lastTouchEvent; quint64 _lastNackTime; @@ -787,6 +793,5 @@ private: bool _showTrackedObjects { false }; bool _prevShowTrackedObjects { false }; - }; #endif // hifi_Application_h diff --git a/interface/src/AvatarBookmarks.cpp b/interface/src/AvatarBookmarks.cpp index ee639f602d..1d003f19c1 100644 --- a/interface/src/AvatarBookmarks.cpp +++ b/interface/src/AvatarBookmarks.cpp @@ -60,7 +60,6 @@ void addAvatarEntities(const QVariantList& avatarEntities) { entityProperties.setParentID(myNodeID); entityProperties.setEntityHostType(entity::HostType::AVATAR); entityProperties.setOwningAvatarID(myNodeID); - entityProperties.setSimulationOwner(myNodeID, AVATAR_ENTITY_SIMULATION_PRIORITY); entityProperties.markAllChanged(); EntityItemID id = EntityItemID(QUuid::createUuid()); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index a3c854d883..810e21daf5 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -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, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 7168b7294e..3611faaf8f 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -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"; diff --git a/interface/src/SecondaryCamera.cpp b/interface/src/SecondaryCamera.cpp index 81944ec747..12c9636746 100644 --- a/interface/src/SecondaryCamera.cpp +++ b/interface/src/SecondaryCamera.cpp @@ -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); } \ No newline at end of file diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index 53074ac4ba..dd99907b8e 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -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()); } }); diff --git a/interface/src/avatar/AvatarActionHold.h b/interface/src/avatar/AvatarActionHold.h index ddc5808d67..4583300012 100644 --- a/interface/src/avatar/AvatarActionHold.h +++ b/interface/src/avatar/AvatarActionHold.h @@ -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; diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 7ca18ca258..53c16c8a61 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -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++; + } +} diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 75dbbc7abb..359af8e361 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -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 diff --git a/interface/src/avatar/AvatarMotionState.cpp b/interface/src/avatar/AvatarMotionState.cpp index ca67f634c8..3fa59ea967 100644 --- a/interface/src/avatar/AvatarMotionState.cpp +++ b/interface/src/avatar/AvatarMotionState.cpp @@ -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 diff --git a/interface/src/avatar/AvatarMotionState.h b/interface/src/avatar/AvatarMotionState.h index 2533c11d56..3103341622 100644 --- a/interface/src/avatar/AvatarMotionState.h +++ b/interface/src/avatar/AvatarMotionState.h @@ -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; }; diff --git a/interface/src/avatar/AvatarPackager.cpp b/interface/src/avatar/AvatarPackager.cpp new file mode 100644 index 0000000000..fa70eee374 --- /dev/null +++ b/interface/src/avatar/AvatarPackager.cpp @@ -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(); +} diff --git a/interface/src/avatar/AvatarPackager.h b/interface/src/avatar/AvatarPackager.h new file mode 100644 index 0000000000..ec954a60d7 --- /dev/null +++ b/interface/src/avatar/AvatarPackager.h @@ -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 diff --git a/interface/src/avatar/AvatarProject.cpp b/interface/src/avatar/AvatarProject.cpp new file mode 100644 index 0000000000..728917e673 --- /dev/null +++ b/interface/src/avatar/AvatarProject.cpp @@ -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 } })); + }); +} diff --git a/interface/src/avatar/AvatarProject.h b/interface/src/avatar/AvatarProject.h new file mode 100644 index 0000000000..1710282a3e --- /dev/null +++ b/interface/src/avatar/AvatarProject.h @@ -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 diff --git a/interface/src/avatar/GrabManager.cpp b/interface/src/avatar/GrabManager.cpp new file mode 100644 index 0000000000..c41435d67e --- /dev/null +++ b/interface/src/avatar/GrabManager.cpp @@ -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)); + } + } + }); +} diff --git a/interface/src/avatar/GrabManager.h b/interface/src/avatar/GrabManager.h new file mode 100644 index 0000000000..8834f95aa1 --- /dev/null +++ b/interface/src/avatar/GrabManager.h @@ -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(); + +}; diff --git a/interface/src/avatar/MarketplaceItemUploader.cpp b/interface/src/avatar/MarketplaceItemUploader.cpp new file mode 100644 index 0000000000..53b37eba4f --- /dev/null +++ b/interface/src/avatar/MarketplaceItemUploader.cpp @@ -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(); }); + } + } + }); +} diff --git a/interface/src/avatar/MarketplaceItemUploader.h b/interface/src/avatar/MarketplaceItemUploader.h new file mode 100644 index 0000000000..998413da88 --- /dev/null +++ b/interface/src/avatar/MarketplaceItemUploader.h @@ -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 diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 0224e5687a..4e5c143919 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -50,6 +50,7 @@ #include <RecordingScriptingInterface.h> #include <trackers/FaceTracker.h> #include <RenderableModelEntityItem.h> +#include <VariantMapToScriptValue.h> #include "MyHead.h" #include "MySkeletonModel.h" @@ -281,6 +282,8 @@ MyAvatar::MyAvatar(QThread* thread) : MyAvatar::~MyAvatar() { _lookAtTargetAvatar.reset(); + delete _myScriptEngine; + _myScriptEngine = nullptr; } void MyAvatar::setDominantHand(const QString& hand) { @@ -671,7 +674,7 @@ void MyAvatar::update(float deltaTime) { _clientTraitsHandler->sendChangedTraitsToMixer(); - simulate(deltaTime); + simulate(deltaTime, true); currentEnergy += energyChargeRate; currentEnergy -= getAccelerationEnergy(); @@ -743,10 +746,10 @@ void MyAvatar::updateChildCauterization(SpatiallyNestablePointer object, bool ca } } -void MyAvatar::simulate(float deltaTime) { +void MyAvatar::simulate(float deltaTime, bool inView) { PerformanceTimer perfTimer("simulate"); animateScaleChanges(deltaTime); - + setFlyingEnabled(getFlyingEnabled()); if (_cauterizationNeedsUpdate) { @@ -820,6 +823,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,54 +878,19 @@ 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); _characterController.setCollisionlessAllowed(collisionlessAllowed); } - updateAvatarEntities(); + handleChangedAvatarEntityData(); updateFadingStatus(); } @@ -1285,7 +1254,7 @@ void MyAvatar::saveAvatarUrl() { } } -void MyAvatar::resizeAvatarEntitySettingHandles(unsigned int avatarEntityIndex) { +void MyAvatar::resizeAvatarEntitySettingHandles(uint32_t maxIndex) { // The original Settings interface saved avatar-entity array data like this: // Avatar/avatarEntityData/size: 5 // Avatar/avatarEntityData/1/id: ... @@ -1295,14 +1264,15 @@ void MyAvatar::resizeAvatarEntitySettingHandles(unsigned int avatarEntityIndex) // Avatar/avatarEntityData/5/properties: ... // // Create Setting::Handles to mimic this. - - while (_avatarEntityIDSettings.size() <= avatarEntityIndex) { + uint32_t settingsIndex = (uint32_t)_avatarEntityIDSettings.size() + 1; + while (settingsIndex <= maxIndex) { Setting::Handle<QUuid> idHandle(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData" - << QString::number(avatarEntityIndex + 1) << "id", QUuid()); + << QString::number(settingsIndex) << "id", QUuid()); _avatarEntityIDSettings.push_back(idHandle); Setting::Handle<QByteArray> dataHandle(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData" - << QString::number(avatarEntityIndex + 1) << "properties", QByteArray()); + << QString::number(settingsIndex) << "properties", QByteArray()); _avatarEntityDataSettings.push_back(dataHandle); + settingsIndex++; } } @@ -1332,26 +1302,54 @@ void MyAvatar::saveData() { _flyingHMDSetting.set(getFlyingHMDPref()); auto hmdInterface = DependencyManager::get<HMDScriptingInterface>(); - _avatarEntitiesLock.withReadLock([&] { - QList<QUuid> avatarEntityIDs = _avatarEntityData.keys(); - unsigned int avatarEntityCount = avatarEntityIDs.size(); - unsigned int previousAvatarEntityCount = _avatarEntityCountSetting.get(0); - resizeAvatarEntitySettingHandles(std::max<unsigned int>(avatarEntityCount, previousAvatarEntityCount)); - _avatarEntityCountSetting.set(avatarEntityCount); + saveAvatarEntityDataToSettings(); +} - unsigned int avatarEntityIndex = 0; - for (auto entityID : avatarEntityIDs) { - _avatarEntityIDSettings[avatarEntityIndex].set(entityID); - _avatarEntityDataSettings[avatarEntityIndex].set(_avatarEntityData.value(entityID)); - avatarEntityIndex++; - } +void MyAvatar::saveAvatarEntityDataToSettings() { + if (!_needToSaveAvatarEntitySettings) { + return; + } + bool success = updateStaleAvatarEntityBlobs(); + if (!success) { + return; + } + _needToSaveAvatarEntitySettings = false; - // clean up any left-over (due to the list shrinking) slots - for (; avatarEntityIndex < previousAvatarEntityCount; avatarEntityIndex++) { - _avatarEntityIDSettings[avatarEntityIndex].remove(); - _avatarEntityDataSettings[avatarEntityIndex].remove(); + uint32_t numEntities = (uint32_t)_cachedAvatarEntityBlobs.size(); + uint32_t prevNumEntities = _avatarEntityCountSetting.get(0); + resizeAvatarEntitySettingHandles(std::max<uint32_t>(numEntities, prevNumEntities)); + + // save new Settings + if (numEntities > 0) { + // save all unfortunately-formatted-binary-blobs to Settings + _avatarEntitiesLock.withWriteLock([&] { + uint32_t i = 0; + AvatarEntityMap::const_iterator itr = _cachedAvatarEntityBlobs.begin(); + while (itr != _cachedAvatarEntityBlobs.end()) { + _avatarEntityIDSettings[i].set(itr.key()); + _avatarEntityDataSettings[i].set(itr.value()); + ++itr; + ++i; + } + numEntities = i; + }); + } + _avatarEntityCountSetting.set(numEntities); + + // remove old Settings if any + if (numEntities < prevNumEntities) { + uint32_t numEntitiesToRemove = prevNumEntities - numEntities; + for (uint32_t i = 0; i < numEntitiesToRemove; ++i) { + if (_avatarEntityIDSettings.size() > numEntities) { + _avatarEntityIDSettings.back().remove(); + _avatarEntityIDSettings.pop_back(); + } + if (_avatarEntityDataSettings.size() > numEntities) { + _avatarEntityDataSettings.back().remove(); + _avatarEntityDataSettings.pop_back(); + } } - }); + } } float loadSetting(Settings& settings, const QString& name, float defaultValue) { @@ -1448,7 +1446,410 @@ void MyAvatar::setEnableInverseKinematics(bool isEnabled) { _skeletonModel->getRig().setEnableInverseKinematics(isEnabled); } +void MyAvatar::storeAvatarEntityDataPayload(const QUuid& entityID, const QByteArray& payload) { + AvatarData::storeAvatarEntityDataPayload(entityID, payload); + _avatarEntitiesLock.withWriteLock([&] { + _cachedAvatarEntityBlobsToAddOrUpdate.push_back(entityID); + }); +} + +void MyAvatar::clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFromTree) { + AvatarData::clearAvatarEntity(entityID, requiresRemovalFromTree); + _avatarEntitiesLock.withWriteLock([&] { + _cachedAvatarEntityBlobsToDelete.push_back(entityID); + }); +} + +void MyAvatar::sanitizeAvatarEntityProperties(EntityItemProperties& properties) const { + properties.setEntityHostType(entity::HostType::AVATAR); + properties.setOwningAvatarID(getID()); + + // there's no entity-server to tell us we're the simulation owner, so always set the + // simulationOwner to the owningAvatarID and a high priority. + properties.setSimulationOwner(getID(), AVATAR_ENTITY_SIMULATION_PRIORITY); + + if (properties.getParentID() == AVATAR_SELF_ID) { + properties.setParentID(getID()); + } + + // When grabbing avatar entities, they are parented to the joint moving them, then when un-grabbed + // they go back to the default parent (null uuid). When un-gripped, others saw the entity disappear. + // The thinking here is the local position was noticed as changing, but not the parentID (since it is now + // back to the default), and the entity flew off somewhere. Marking all changed definitely fixes this, + // and seems safe (per Seth). + properties.markAllChanged(); +} + +void MyAvatar::handleChangedAvatarEntityData() { + // NOTE: this is a per-frame update + if (getID().isNull() || + getID() == AVATAR_SELF_ID || + DependencyManager::get<NodeList>()->getSessionUUID() == QUuid()) { + // wait until MyAvatar and this Node gets an ID before doing this. Otherwise, various things go wrong: + // things get their parent fixed up from AVATAR_SELF_ID to a null uuid which means "no parent". + return; + } + if (_reloadAvatarEntityDataFromSettings) { + loadAvatarEntityDataFromSettings(); + } + + auto treeRenderer = DependencyManager::get<EntityTreeRenderer>(); + EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; + if (!entityTree) { + return; + } + + // We collect changes to AvatarEntities and then handle them all in one spot per frame: handleChangedAvatarEntityData(). + // Basically this is a "transaction pattern" with an extra complication: these changes can come from two + // "directions" and the "authoritative source" of each direction is different, so we maintain two distinct sets + // of transaction lists: + // + // The _entitiesToDelete/Add/Update lists are for changes whose "authoritative sources" are already + // correctly stored in _cachedAvatarEntityBlobs. These come from loadAvatarEntityDataFromSettings() and + // setAvatarEntityData(). These changes need to be extracted from _cachedAvatarEntityBlobs and applied to + // real EntityItems. + // + // The _cachedAvatarEntityBlobsToDelete/Add/Update lists are for changes whose "authoritative sources" are + // already reflected in real EntityItems. These changes need to be propagated to _cachedAvatarEntityBlobs + // and eventually to Settings. + // + // The DELETEs also need to be propagated to the traits, which will eventually propagate to + // AvatarData::_packedAvatarEntityData via deeper logic. + + // move the lists to minimize lock time + std::vector<QUuid> cachedBlobsToDelete; + std::vector<QUuid> cachedBlobsToUpdate; + std::vector<QUuid> entitiesToDelete; + std::vector<QUuid> entitiesToAdd; + std::vector<QUuid> entitiesToUpdate; + _avatarEntitiesLock.withWriteLock([&] { + cachedBlobsToDelete = std::move(_cachedAvatarEntityBlobsToDelete); + cachedBlobsToUpdate = std::move(_cachedAvatarEntityBlobsToAddOrUpdate); + entitiesToDelete = std::move(_entitiesToDelete); + entitiesToAdd = std::move(_entitiesToAdd); + entitiesToUpdate = std::move(_entitiesToUpdate); + }); + + auto removeAllInstancesHelper = [] (const QUuid& id, std::vector<QUuid>& v) { + uint32_t i = 0; + while (i < v.size()) { + if (id == v[i]) { + v[i] = v.back(); + v.pop_back(); + } else { + ++i; + } + } + }; + + // remove delete-add and delete-update overlap + for (const auto& id : entitiesToDelete) { + removeAllInstancesHelper(id, cachedBlobsToUpdate); + removeAllInstancesHelper(id, entitiesToAdd); + removeAllInstancesHelper(id, entitiesToUpdate); + } + for (const auto& id : cachedBlobsToDelete) { + removeAllInstancesHelper(id, entitiesToUpdate); + removeAllInstancesHelper(id, cachedBlobsToUpdate); + } + for (const auto& id : entitiesToAdd) { + removeAllInstancesHelper(id, entitiesToUpdate); + } + + // DELETE real entities + for (const auto& id : entitiesToDelete) { + entityTree->withWriteLock([&] { + entityTree->deleteEntity(id); + }); + } + + // ADD real entities + EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender(); + for (const auto& id : entitiesToAdd) { + bool blobFailed = false; + EntityItemProperties properties; + _avatarEntitiesLock.withReadLock([&] { + AvatarEntityMap::iterator itr = _cachedAvatarEntityBlobs.find(id); + if (itr == _cachedAvatarEntityBlobs.end()) { + blobFailed = true; // blob doesn't exist + return; + } + if (!EntityItemProperties::blobToProperties(*_myScriptEngine, itr.value(), properties)) { + blobFailed = true; // blob is corrupt + } + }); + if (blobFailed) { + // remove from _cachedAvatarEntityBlobUpdatesToSkip just in case: + // avoids a resource leak when blob updates to be skipped are never actually skipped + // when the blob fails to result in a real EntityItem + _avatarEntitiesLock.withWriteLock([&] { + removeAllInstancesHelper(id, _cachedAvatarEntityBlobUpdatesToSkip); + }); + continue; + } + sanitizeAvatarEntityProperties(properties); + entityTree->withWriteLock([&] { + EntityItemPointer entity = entityTree->addEntity(id, properties); + if (entity) { + packetSender->queueEditEntityMessage(PacketType::EntityAdd, entityTree, id, properties); + } + }); + } + + // CHANGE real entities + for (const auto& id : entitiesToUpdate) { + EntityItemProperties properties; + bool skip = false; + _avatarEntitiesLock.withReadLock([&] { + AvatarEntityMap::iterator itr = _cachedAvatarEntityBlobs.find(id); + if (itr == _cachedAvatarEntityBlobs.end()) { + skip = true; + return; + } + if (!EntityItemProperties::blobToProperties(*_myScriptEngine, itr.value(), properties)) { + skip = true; + } + }); + sanitizeAvatarEntityProperties(properties); + entityTree->withWriteLock([&] { + entityTree->updateEntity(id, properties); + }); + } + + // DELETE cached blobs + _avatarEntitiesLock.withWriteLock([&] { + for (const auto& id : cachedBlobsToDelete) { + AvatarEntityMap::iterator itr = _cachedAvatarEntityBlobs.find(id); + // remove blob and remember to remove from settings + if (itr != _cachedAvatarEntityBlobs.end()) { + _cachedAvatarEntityBlobs.erase(itr); + _needToSaveAvatarEntitySettings = true; + } + // also remove from list of stale blobs to avoid failed entity lookup later + std::set<QUuid>::iterator blobItr = _staleCachedAvatarEntityBlobs.find(id); + if (blobItr != _staleCachedAvatarEntityBlobs.end()) { + _staleCachedAvatarEntityBlobs.erase(blobItr); + } + // also remove from _cachedAvatarEntityBlobUpdatesToSkip just in case: + // avoids a resource leak when things are deleted before they could be skipped + removeAllInstancesHelper(id, _cachedAvatarEntityBlobUpdatesToSkip); + } + }); + + // ADD/UPDATE cached blobs + for (const auto& id : cachedBlobsToUpdate) { + // computing the blobs is expensive and we want to avoid it when possible + // so we add these ids to _staleCachedAvatarEntityBlobs for later + // and only build the blobs when absolutely necessary + bool skip = false; + uint32_t i = 0; + _avatarEntitiesLock.withWriteLock([&] { + while (i < _cachedAvatarEntityBlobUpdatesToSkip.size()) { + if (id == _cachedAvatarEntityBlobUpdatesToSkip[i]) { + _cachedAvatarEntityBlobUpdatesToSkip[i] = _cachedAvatarEntityBlobUpdatesToSkip.back(); + _cachedAvatarEntityBlobUpdatesToSkip.pop_back(); + skip = true; + break; // assume no duplicates + } else { + ++i; + } + } + }); + if (!skip) { + _staleCachedAvatarEntityBlobs.insert(id); + _needToSaveAvatarEntitySettings = true; + } + } + + // DELETE traits + // (no need to worry about the ADDs and UPDATEs: each will be handled when the interface + // tries to send a real update packet (via AvatarData::storeAvatarEntityDataPayload())) + if (_clientTraitsHandler) { + // we have a client traits handler + // flag removed entities as deleted so that changes are sent next frame + _avatarEntitiesLock.withWriteLock([&] { + for (const auto& id : entitiesToDelete) { + if (_packedAvatarEntityData.find(id) != _packedAvatarEntityData.end()) { + _clientTraitsHandler->markInstancedTraitDeleted(AvatarTraits::AvatarEntity, id); + } + } + for (const auto& id : cachedBlobsToDelete) { + if (_packedAvatarEntityData.find(id) != _packedAvatarEntityData.end()) { + _clientTraitsHandler->markInstancedTraitDeleted(AvatarTraits::AvatarEntity, id); + } + } + }); + } +} + +bool MyAvatar::updateStaleAvatarEntityBlobs() const { + // call this right before you actually need to use the blobs + // + // Note: this method is const (and modifies mutable data members) + // so we can call it at the Last Minute inside other const methods + // + auto treeRenderer = DependencyManager::get<EntityTreeRenderer>(); + EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; + if (!entityTree) { + return false; + } + + std::set<QUuid> staleBlobs = std::move(_staleCachedAvatarEntityBlobs); + int32_t numFound = 0; + for (const auto& id : staleBlobs) { + bool found = false; + EntityItemProperties properties; + entityTree->withReadLock([&] { + EntityItemPointer entity = entityTree->findEntityByID(id); + if (entity) { + properties = entity->getProperties(); + found = true; + } + }); + if (found) { + ++numFound; + QByteArray blob; + EntityItemProperties::propertiesToBlob(*_myScriptEngine, getID(), properties, blob); + _avatarEntitiesLock.withWriteLock([&] { + _cachedAvatarEntityBlobs[id] = blob; + }); + } + } + return true; +} + +void MyAvatar::prepareAvatarEntityDataForReload() { + saveAvatarEntityDataToSettings(); + + _avatarEntitiesLock.withWriteLock([&] { + _packedAvatarEntityData.clear(); + _entitiesToDelete.clear(); + _entitiesToAdd.clear(); + _entitiesToUpdate.clear(); + _cachedAvatarEntityBlobs.clear(); + _cachedAvatarEntityBlobsToDelete.clear(); + _cachedAvatarEntityBlobsToAddOrUpdate.clear(); + _cachedAvatarEntityBlobUpdatesToSkip.clear(); + }); + + _reloadAvatarEntityDataFromSettings = true; +} + +AvatarEntityMap MyAvatar::getAvatarEntityData() const { + // NOTE: the return value is expected to be a map of unfortunately-formatted-binary-blobs + updateStaleAvatarEntityBlobs(); + AvatarEntityMap result; + _avatarEntitiesLock.withReadLock([&] { + result = _cachedAvatarEntityBlobs; + }); + return result; +} + +void MyAvatar::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) { + // Note: this is an invokable Script call + // avatarEntityData is expected to be a map of QByteArrays that represent EntityItemProperties objects from JavaScript, + // aka: unfortunately-formatted-binary-blobs because we store them in non-human-readable format in Settings. + // + if (avatarEntityData.size() > MAX_NUM_AVATAR_ENTITIES) { + // the data is suspect + qCDebug(interfaceapp) << "discard suspect AvatarEntityData with size =" << avatarEntityData.size(); + return; + } + + // this overwrites ALL AvatarEntityData so we clear pending operations + _avatarEntitiesLock.withWriteLock([&] { + _packedAvatarEntityData.clear(); + _entitiesToDelete.clear(); + _entitiesToAdd.clear(); + _entitiesToUpdate.clear(); + }); + _needToSaveAvatarEntitySettings = true; + + _avatarEntitiesLock.withWriteLock([&] { + // find new and updated IDs + AvatarEntityMap::const_iterator constItr = avatarEntityData.begin(); + while (constItr != avatarEntityData.end()) { + QUuid id = constItr.key(); + if (_cachedAvatarEntityBlobs.find(id) == _cachedAvatarEntityBlobs.end()) { + _entitiesToAdd.push_back(id); + } else { + _entitiesToUpdate.push_back(id); + } + ++constItr; + } + // find and erase deleted IDs from _cachedAvatarEntityBlobs + std::vector<QUuid> deletedIDs; + AvatarEntityMap::iterator itr = _cachedAvatarEntityBlobs.begin(); + while (itr != _cachedAvatarEntityBlobs.end()) { + QUuid id = itr.key(); + if (std::find(_entitiesToUpdate.begin(), _entitiesToUpdate.end(), id) == _entitiesToUpdate.end()) { + deletedIDs.push_back(id); + itr = _cachedAvatarEntityBlobs.erase(itr); + } else { + ++itr; + } + } + // copy new data + constItr = avatarEntityData.begin(); + while (constItr != avatarEntityData.end()) { + _cachedAvatarEntityBlobs.insert(constItr.key(), constItr.value()); + ++constItr; + } + // erase deleted IDs from _packedAvatarEntityData + for (const auto& id : deletedIDs) { + itr = _packedAvatarEntityData.find(id); + if (itr != _packedAvatarEntityData.end()) { + _packedAvatarEntityData.erase(itr); + } else { + ++itr; + } + _entitiesToDelete.push_back(id); + } + }); +} + +void MyAvatar::updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) { + // NOTE: this is an invokable Script call + // TODO: we should handle the case where entityData is corrupt or invalid + // BEFORE we store into _cachedAvatarEntityBlobs + _needToSaveAvatarEntitySettings = true; + _avatarEntitiesLock.withWriteLock([&] { + AvatarEntityMap::iterator itr = _cachedAvatarEntityBlobs.find(entityID); + if (itr != _cachedAvatarEntityBlobs.end()) { + _entitiesToUpdate.push_back(entityID); + itr.value() = entityData; + } else { + _entitiesToAdd.push_back(entityID); + _cachedAvatarEntityBlobs.insert(entityID, entityData); + } + }); +} + +void MyAvatar::avatarEntityDataToJson(QJsonObject& root) const { + updateStaleAvatarEntityBlobs(); + _avatarEntitiesLock.withReadLock([&] { + if (!_cachedAvatarEntityBlobs.empty()) { + QJsonArray avatarEntityJson; + int entityCount = 0; + AvatarEntityMap::const_iterator itr = _cachedAvatarEntityBlobs.begin(); + while (itr != _cachedAvatarEntityBlobs.end()) { + QVariantMap entityData; + QUuid id = _avatarEntityForRecording.size() == _cachedAvatarEntityBlobs.size() ? _avatarEntityForRecording.values()[entityCount++] : itr.key(); + entityData.insert("id", id); + entityData.insert("properties", itr.value().toBase64()); + avatarEntityJson.push_back(QVariant(entityData).toJsonObject()); + ++itr; + } + const QString JSON_AVATAR_ENTITIES = QStringLiteral("attachedEntities"); + root[JSON_AVATAR_ENTITIES] = avatarEntityJson; + } + }); +} + void MyAvatar::loadData() { + if (!_myScriptEngine) { + _myScriptEngine = new QScriptEngine(); + } getHead()->setBasePitch(_headPitchSetting.get()); _yawSpeed = _yawSpeedSetting.get(_yawSpeed); @@ -1460,14 +1861,7 @@ void MyAvatar::loadData() { useFullAvatarURL(_fullAvatarURLFromPreferences, _fullAvatarModelName); - int avatarEntityCount = _avatarEntityCountSetting.get(0); - for (int i = 0; i < avatarEntityCount; i++) { - resizeAvatarEntitySettingHandles(i); - // QUuid entityID = QUuid::createUuid(); // generate a new ID - QUuid entityID = _avatarEntityIDSettings[i].get(QUuid()); - QByteArray properties = _avatarEntityDataSettings[i].get(); - updateAvatarEntity(entityID, properties); - } + loadAvatarEntityDataFromSettings(); // Flying preferences must be loaded before calling setFlyingEnabled() Setting::Handle<bool> firstRunVal { Settings::firstRun, true }; @@ -1489,6 +1883,38 @@ void MyAvatar::loadData() { setEnableDebugDrawPosition(Menu::getInstance()->isOptionChecked(MenuOption::AnimDebugDrawPosition)); } +void MyAvatar::loadAvatarEntityDataFromSettings() { + // this overwrites ALL AvatarEntityData so we clear pending operations + _avatarEntitiesLock.withWriteLock([&] { + _packedAvatarEntityData.clear(); + _entitiesToDelete.clear(); + _entitiesToAdd.clear(); + _entitiesToUpdate.clear(); + }); + _reloadAvatarEntityDataFromSettings = false; + _needToSaveAvatarEntitySettings = false; + + int numEntities = _avatarEntityCountSetting.get(0); + if (numEntities == 0) { + return; + } + resizeAvatarEntitySettingHandles(numEntities); + + _avatarEntitiesLock.withWriteLock([&] { + _entitiesToAdd.reserve(numEntities); + // TODO: build map between old and new IDs so we can restitch parent-child relationships + for (int i = 0; i < numEntities; i++) { + QUuid id = QUuid::createUuid(); // generate a new ID + _cachedAvatarEntityBlobs[id] = _avatarEntityDataSettings[i].get(); + _entitiesToAdd.push_back(id); + // this blob is the "authoritative source" for this AvatarEntity and we want to avoid overwriting it + // (the outgoing update packet will flag it for save-back into the blob) + // which is why we remember its id: to skip its save-back later + _cachedAvatarEntityBlobUpdatesToSkip.push_back(id); + } + }); +} + void MyAvatar::saveAttachmentData(const AttachmentData& attachment) const { Settings settings; settings.beginGroup("savedAttachmentData"); @@ -1565,35 +1991,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. @@ -1648,10 +2113,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() { @@ -1892,8 +2366,11 @@ void MyAvatar::clearAvatarEntities() { auto treeRenderer = DependencyManager::get<EntityTreeRenderer>(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; - AvatarEntityMap avatarEntities = getAvatarEntityData(); - for (auto entityID : avatarEntities.keys()) { + QList<QUuid> avatarEntityIDs; + _avatarEntitiesLock.withReadLock([&] { + avatarEntityIDs = _packedAvatarEntityData.keys(); + }); + for (const auto& entityID : avatarEntityIDs) { entityTree->withWriteLock([&entityID, &entityTree] { // remove this entity first from the entity tree entityTree->deleteEntity(entityID, true, true); @@ -1908,10 +2385,12 @@ void MyAvatar::clearAvatarEntities() { void MyAvatar::removeWearableAvatarEntities() { auto treeRenderer = DependencyManager::get<EntityTreeRenderer>(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; - if (entityTree) { - AvatarEntityMap avatarEntities = getAvatarEntityData(); - for (auto entityID : avatarEntities.keys()) { + QList<QUuid> avatarEntityIDs; + _avatarEntitiesLock.withReadLock([&] { + avatarEntityIDs = _packedAvatarEntityData.keys(); + }); + for (const auto& entityID : avatarEntityIDs) { auto entity = entityTree->findEntityByID(entityID); if (entity && isWearableEntity(entity)) { entityTree->withWriteLock([&entityID, &entityTree] { @@ -1928,13 +2407,16 @@ void MyAvatar::removeWearableAvatarEntities() { } QVariantList MyAvatar::getAvatarEntitiesVariant() { + // NOTE: this method is NOT efficient QVariantList avatarEntitiesData; - QScriptEngine scriptEngine; auto treeRenderer = DependencyManager::get<EntityTreeRenderer>(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; if (entityTree) { - AvatarEntityMap avatarEntities = getAvatarEntityData(); - for (auto entityID : avatarEntities.keys()) { + QList<QUuid> avatarEntityIDs; + _avatarEntitiesLock.withReadLock([&] { + avatarEntityIDs = _packedAvatarEntityData.keys(); + }); + for (const auto& entityID : avatarEntityIDs) { auto entity = entityTree->findEntityByID(entityID); if (!entity) { continue; @@ -1945,7 +2427,7 @@ QVariantList MyAvatar::getAvatarEntitiesVariant() { desiredProperties += PROP_LOCAL_POSITION; desiredProperties += PROP_LOCAL_ROTATION; EntityItemProperties entityProperties = entity->getProperties(desiredProperties); - QScriptValue scriptProperties = EntityItemPropertiesToScriptValue(&scriptEngine, entityProperties); + QScriptValue scriptProperties = EntityItemPropertiesToScriptValue(_myScriptEngine, entityProperties); avatarEntityData["properties"] = scriptProperties.toVariant(); avatarEntitiesData.append(QVariant(avatarEntityData)); } @@ -2334,17 +2816,17 @@ void MyAvatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) } QVector<AttachmentData> MyAvatar::getAttachmentData() const { - QVector<AttachmentData> avatarData; - auto avatarEntities = getAvatarEntityData(); - AvatarEntityMap::const_iterator dataItr = avatarEntities.begin(); - while (dataItr != avatarEntities.end()) { - QUuid entityID = dataItr.key(); + QVector<AttachmentData> attachmentData; + QList<QUuid> avatarEntityIDs; + _avatarEntitiesLock.withReadLock([&] { + avatarEntityIDs = _packedAvatarEntityData.keys(); + }); + for (const auto& entityID : avatarEntityIDs) { auto properties = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(entityID); AttachmentData data = entityPropertiesToAttachmentData(properties); - avatarData.append(data); - dataItr++; + attachmentData.append(data); } - return avatarData; + return attachmentData; } QVariantList MyAvatar::getAttachmentsVariant() const { @@ -2373,16 +2855,16 @@ void MyAvatar::setAttachmentsVariant(const QVariantList& variant) { } bool MyAvatar::findAvatarEntity(const QString& modelURL, const QString& jointName, QUuid& entityID) { - auto avatarEntities = getAvatarEntityData(); - AvatarEntityMap::const_iterator dataItr = avatarEntities.begin(); - while (dataItr != avatarEntities.end()) { - entityID = dataItr.key(); + QList<QUuid> avatarEntityIDs; + _avatarEntitiesLock.withReadLock([&] { + avatarEntityIDs = _packedAvatarEntityData.keys(); + }); + for (const auto& entityID : avatarEntityIDs) { auto props = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(entityID); if (props.getModelURL() == modelURL && (jointName.isEmpty() || props.getParentJointIndex() == getJointIndex(jointName))) { return true; } - dataItr++; } return false; } @@ -2418,10 +2900,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; @@ -2696,9 +3178,10 @@ void MyAvatar::updateOrientation(float deltaTime) { _bodyYawDelta = 0.0f; } } - float totalBodyYaw = _bodyYawDelta * deltaTime; + // Rotate directly proportional to delta yaw and delta pitch from right-click mouse movement. + totalBodyYaw += getDriveKey(DELTA_YAW) * _yawSpeed / YAW_SPEED_DEFAULT; // Comfort Mode: If you press any of the left/right rotation drive keys or input, you'll // get an instantaneous 15 degree turn. If you keep holding the key down you'll get another @@ -2766,7 +3249,8 @@ void MyAvatar::updateOrientation(float deltaTime) { head->setBaseRoll(ROLL(euler)); } else { head->setBaseYaw(0.0f); - head->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime); + head->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime + + getDriveKey(DELTA_PITCH) * _pitchSpeed / PITCH_SPEED_DEFAULT); head->setBaseRoll(0.0f); } } @@ -3187,17 +3671,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; @@ -3362,7 +3844,6 @@ void MyAvatar::setCollisionsEnabled(bool enabled) { QMetaObject::invokeMethod(this, "setCollisionsEnabled", Q_ARG(bool, enabled)); return; } - _characterController.setCollisionless(!enabled); emit collisionsEnabledChanged(enabled); } @@ -3373,6 +3854,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; @@ -4748,3 +5243,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); + } +} diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index b2381366bb..da7f7c47f9 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -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) @@ -264,6 +265,8 @@ public: STEP_YAW, PITCH, ZOOM, + DELTA_YAW, + DELTA_PITCH, MAX_DRIVE_KEYS }; Q_ENUM(DriveKeys) @@ -574,9 +577,11 @@ public: float getHMDRollControlRate() const { return _hmdRollControlRate; } // get/set avatar data - void resizeAvatarEntitySettingHandles(unsigned int avatarEntityIndex); + void resizeAvatarEntitySettingHandles(uint32_t maxIndex); void saveData(); + void saveAvatarEntityDataToSettings(); void loadData(); + void loadAvatarEntityDataFromSettings(); void saveAttachmentData(const AttachmentData& attachment) const; AttachmentData loadAttachmentData(const QUrl& modelURL, const QString& jointName = QString()) const; @@ -832,6 +837,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 +1069,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} @@ -1169,6 +1188,31 @@ public: virtual void setAttachmentsVariant(const QVariantList& variant) override; glm::vec3 getNextPosition() { return _goToPending ? _goToPosition : getWorldPosition(); } + void prepareAvatarEntityDataForReload(); + + /**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); + + AvatarEntityMap getAvatarEntityData() const override; + void setAvatarEntityData(const AvatarEntityMap& avatarEntityData) override; + void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) override; + void avatarEntityDataToJson(QJsonObject& root) const override; public slots: @@ -1368,6 +1412,10 @@ public slots: */ bool getEnableMeshVisible() const override; + void storeAvatarEntityDataPayload(const QUuid& entityID, const QByteArray& payload) override; + void clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFromTree = true) override; + void sanitizeAvatarEntityProperties(EntityItemProperties& properties) const; + /**jsdoc * Set whether or not your avatar mesh is visible. * @function MyAvatar.setEnableMeshVisible @@ -1470,6 +1518,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 @@ -1559,23 +1615,24 @@ signals: */ void disableHandTouchForIDChanged(const QUuid& entityID, bool disable); - private slots: void leaveDomain(); void updateCollisionCapsuleCache(); protected: + void handleChangedAvatarEntityData(); virtual void beParentOfChild(SpatiallyNestablePointer newChild) const override; virtual void forgetChild(SpatiallyNestablePointer newChild) const override; virtual void recalculateChildCauterization() const override; private: + bool updateStaleAvatarEntityBlobs() const; bool requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& positionOut); virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) override; - void simulate(float deltaTime); + void simulate(float deltaTime, bool inView) override; void updateFromTrackers(float deltaTime); void saveAvatarUrl(); virtual void render(RenderArgs* renderArgs) override; @@ -1878,6 +1935,7 @@ private: bool _haveReceivedHeightLimitsFromDomain { false }; int _disableHandTouchCount { 0 }; bool _skeletonModelLoaded { false }; + bool _reloadAvatarEntityDataFromSettings { true }; Setting::Handle<QString> _dominantHandSetting; Setting::Handle<float> _headPitchSetting; @@ -1896,6 +1954,38 @@ private: Setting::Handle<bool> _allowTeleportingSetting { "allowTeleporting", true }; std::vector<Setting::Handle<QUuid>> _avatarEntityIDSettings; std::vector<Setting::Handle<QByteArray>> _avatarEntityDataSettings; + + // AvatarEntities stuff: + // We cache the "map of unfortunately-formatted-binary-blobs" because they are expensive to compute + // Do not confuse these with AvatarData::_packedAvatarEntityData which are in wire-format. + mutable AvatarEntityMap _cachedAvatarEntityBlobs; + + // We collect changes to AvatarEntities and then handle them all in one spot per frame: updateAvatarEntities(). + // Basically this is a "transaction pattern" with an extra complication: these changes can come from two + // "directions" and the "authoritative source" of each direction is different, so maintain two distinct sets of + // transaction lists; + // + // The _entitiesToDelete/Add/Update lists are for changes whose "authoritative sources" are already + // correctly stored in _cachedAvatarEntityBlobs. These come from loadAvatarEntityDataFromSettings() and + // setAvatarEntityData(). These changes need to be extracted from _cachedAvatarEntityBlobs and applied to + // real EntityItems. + std::vector<QUuid> _entitiesToDelete; + std::vector<QUuid> _entitiesToAdd; + std::vector<QUuid> _entitiesToUpdate; + // + // The _cachedAvatarEntityBlobsToDelete/Add/Update lists are for changes whose "authoritative sources" are + // already reflected in real EntityItems. These changes need to be propagated to _cachedAvatarEntityBlobs + // and eventually to settings. + std::vector<QUuid> _cachedAvatarEntityBlobsToDelete; + std::vector<QUuid> _cachedAvatarEntityBlobsToAddOrUpdate; + std::vector<QUuid> _cachedAvatarEntityBlobUpdatesToSkip; + // + // Also these lists for tracking delayed changes to blobs and Settings + mutable std::set<QUuid> _staleCachedAvatarEntityBlobs; + // + // keep a ScriptEngine around so we don't have to instantiate on the fly (these are very slow to create/delete) + QScriptEngine* _myScriptEngine { nullptr }; + bool _needToSaveAvatarEntitySettings { false }; }; QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode); diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index a8a7dd16c2..356b365f93 100644 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -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); diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index c2687fd525..0dfc349e18 100644 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -7,10 +7,18 @@ // #include "OtherAvatar.h" -#include "Application.h" +#include <glm/gtx/norm.hpp> +#include <glm/gtx/vector_angle.hpp> + +#include <AvatarLogging.h> + +#include "Application.h" #include "AvatarMotionState.h" +const float DISPLAYNAME_FADE_TIME = 0.5f; +const float DISPLAYNAME_FADE_FACTOR = pow(0.01f, 1.0f / DISPLAYNAME_FADE_TIME); + static glm::u8vec3 getLoadingOrbColor(Avatar::LoadingStatus loadingStatus) { const glm::u8vec3 NO_MODEL_COLOR(0xe3, 0xe3, 0xe3); @@ -120,7 +128,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 +137,306 @@ 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); + } + } +} + +void OtherAvatar::simulate(float deltaTime, bool inView) { + PROFILE_RANGE(simulation, "simulate"); + + _globalPosition = _transit.isActive() ? _transit.getCurrentPosition() : _serverPosition; + if (!hasParent()) { + setLocalPosition(_globalPosition); + } + + _simulationRate.increment(); + if (inView) { + _simulationInViewRate.increment(); + } + + PerformanceTimer perfTimer("simulate"); + { + PROFILE_RANGE(simulation, "updateJoints"); + if (inView) { + Head* head = getHead(); + if (_hasNewJointData || _transit.isActive()) { + _skeletonModel->getRig().copyJointsFromJointData(_jointData); + glm::mat4 rootTransform = glm::scale(_skeletonModel->getScale()) * glm::translate(_skeletonModel->getOffset()); + _skeletonModel->getRig().computeExternalPoses(rootTransform); + _jointDataSimulationRate.increment(); + + _skeletonModel->simulate(deltaTime, true); + + locationChanged(); // joints changed, so if there are any children, update them. + _hasNewJointData = false; + + glm::vec3 headPosition = getWorldPosition(); + if (!_skeletonModel->getHeadPosition(headPosition)) { + headPosition = getWorldPosition(); + } + head->setPosition(headPosition); + } + head->setScale(getModelScale()); + head->simulate(deltaTime); + relayJointDataToChildren(); + } else { + // a non-full update is still required so that the position, rotation, scale and bounds of the skeletonModel are updated. + _skeletonModel->simulate(deltaTime, false); + } + _skeletonModelSimulationRate.increment(); + } + + // update animation for display name fade in/out + if ( _displayNameTargetAlpha != _displayNameAlpha) { + // the alpha function is + // Fade out => alpha(t) = factor ^ t => alpha(t+dt) = alpha(t) * factor^(dt) + // Fade in => alpha(t) = 1 - factor^t => alpha(t+dt) = 1-(1-alpha(t))*coef^(dt) + // factor^(dt) = coef + float coef = pow(DISPLAYNAME_FADE_FACTOR, deltaTime); + if (_displayNameTargetAlpha < _displayNameAlpha) { + // Fading out + _displayNameAlpha *= coef; + } else { + // Fading in + _displayNameAlpha = 1.0f - (1.0f - _displayNameAlpha) * coef; + } + _displayNameAlpha = glm::abs(_displayNameAlpha - _displayNameTargetAlpha) < 0.01f ? _displayNameTargetAlpha : _displayNameAlpha; + } + + { + PROFILE_RANGE(simulation, "misc"); + measureMotionDerivatives(deltaTime); + simulateAttachments(deltaTime); + updatePalms(); + } + { + PROFILE_RANGE(simulation, "entities"); + handleChangedAvatarEntityData(); + updateAttachedAvatarEntities(); + } + + { + PROFILE_RANGE(simulation, "grabs"); + updateGrabs(); + } + + updateFadingStatus(); +} + +void OtherAvatar::handleChangedAvatarEntityData() { + PerformanceTimer perfTimer("attachments"); + + // AVATAR ENTITY UPDATE FLOW + // - if queueEditEntityMessage() sees "AvatarEntity" HostType it calls _myAvatar->storeAvatarEntityDataPayload() + // - storeAvatarEntityDataPayload() saves the payload and flags the trait instance for the entity as updated, + // - ClientTraitsHandler::sendChangedTraitsToMixea() sends the entity bytes to the mixer which relays them to other interfaces + // - AvatarHashMap::processBulkAvatarTraits() on other interfaces calls avatar->processTraitInstance() + // - AvatarData::processTraitInstance() calls storeAvatarEntityDataPayload(), which sets _avatarEntityDataChanged = true + // - (My)Avatar::simulate() calls handleChangedAvatarEntityData() every frame which checks _avatarEntityDataChanged + // and here we are... + + // AVATAR ENTITY DELETE FLOW + // - EntityScriptingInterface::deleteEntity() calls _myAvatar->clearAvatarEntity() for deleted avatar entities + // - clearAvatarEntity() removes the avatar entity and flags the trait instance for the entity as deleted + // - ClientTraitsHandler::sendChangedTraitsToMixer() sends a deletion to the mixer which relays to other interfaces + // - AvatarHashMap::processBulkAvatarTraits() on other interfaces calls avatar->processDeletedTraitInstace() + // - AvatarData::processDeletedTraitInstance() calls clearAvatarEntity() + // - AvatarData::clearAvatarEntity() sets _avatarEntityDataChanged = true and adds the ID to the detached list + // - (My)Avatar::simulate() calls handleChangedAvatarEntityData() every frame which checks _avatarEntityDataChanged + // and here we are... + + if (!_avatarEntityDataChanged) { + return; + } + + auto treeRenderer = DependencyManager::get<EntityTreeRenderer>(); + EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; + if (!entityTree) { + return; + } + + PackedAvatarEntityMap packedAvatarEntityData; + _avatarEntitiesLock.withReadLock([&] { + packedAvatarEntityData = _packedAvatarEntityData; + }); + entityTree->withWriteLock([&] { + AvatarEntityMap::const_iterator dataItr = packedAvatarEntityData.begin(); + while (dataItr != packedAvatarEntityData.end()) { + // compute hash of data. TODO? cache this? + QByteArray data = dataItr.value(); + uint32_t newHash = qHash(data); + + // check to see if we recognize this hash and whether it was already successfully processed + QUuid entityID = dataItr.key(); + MapOfAvatarEntityDataHashes::iterator stateItr = _avatarEntityDataHashes.find(entityID); + if (stateItr != _avatarEntityDataHashes.end()) { + if (stateItr.value().success) { + if (newHash == stateItr.value().hash) { + // data hasn't changed --> nothing to do + ++dataItr; + continue; + } + } else { + // NOTE: if the data was unsuccessful in producing an entity in the past + // we will try again just in case something changed (unlikely). + // Unfortunately constantly trying to build the entity for this data costs + // CPU cycles that we'd rather not spend. + // TODO? put a maximum number of tries on this? + } + } else { + // sanity check data + QUuid id; + EntityTypes::EntityType type; + EntityTypes::extractEntityTypeAndID((unsigned char*)(data.data()), data.size(), type, id); + if (id != entityID || !EntityTypes::typeIsValid(type)) { + // skip for corrupt + ++dataItr; + continue; + } + // remember this hash for the future + stateItr = _avatarEntityDataHashes.insert(entityID, AvatarEntityDataHash(newHash)); + } + ++dataItr; + + EntityItemProperties properties; + int32_t bytesLeftToRead = data.size(); + unsigned char* dataAt = (unsigned char*)(data.data()); + if (!properties.constructFromBuffer(dataAt, bytesLeftToRead)) { + // properties are corrupt + continue; + } + + properties.setEntityHostType(entity::HostType::AVATAR); + properties.setOwningAvatarID(getID()); + + // there's no entity-server to tell us we're the simulation owner, so always set the + // simulationOwner to the owningAvatarID and a high priority. + properties.setSimulationOwner(getID(), AVATAR_ENTITY_SIMULATION_PRIORITY); + + if (properties.getParentID() == AVATAR_SELF_ID) { + properties.setParentID(getID()); + } + + // NOTE: if this avatar entity is not attached to us, strip its entity script completely... + auto attachedScript = properties.getScript(); + if (!isMyAvatar() && !attachedScript.isEmpty()) { + QString noScript; + properties.setScript(noScript); + } + + auto specifiedHref = properties.getHref(); + if (!isMyAvatar() && !specifiedHref.isEmpty()) { + qCDebug(avatars) << "removing entity href from avatar attached entity:" << entityID << "old href:" << specifiedHref; + QString noHref; + properties.setHref(noHref); + } + + // When grabbing avatar entities, they are parented to the joint moving them, then when un-grabbed + // they go back to the default parent (null uuid). When un-gripped, others saw the entity disappear. + // The thinking here is the local position was noticed as changing, but not the parentID (since it is now + // back to the default), and the entity flew off somewhere. Marking all changed definitely fixes this, + // and seems safe (per Seth). + properties.markAllChanged(); + + // try to build the entity + EntityItemPointer entity = entityTree->findEntityByEntityItemID(EntityItemID(entityID)); + bool success = true; + if (entity) { + QUuid oldParentID = entity->getParentID(); + if (entityTree->updateEntity(entityID, properties)) { + entity->updateLastEditedFromRemote(); + } else { + success = false; + } + if (oldParentID != entity->getParentID()) { + if (entity->getParentID() == getID()) { + onAddAttachedAvatarEntity(entityID); + } else if (oldParentID == getID()) { + onRemoveAttachedAvatarEntity(entityID); + } + } + } else { + entity = entityTree->addEntity(entityID, properties); + if (!entity) { + success = false; + } else if (entity->getParentID() == getID()) { + onAddAttachedAvatarEntity(entityID); + } + } + stateItr.value().success = success; + } + + AvatarEntityIDs recentlyRemovedAvatarEntities = getAndClearRecentlyRemovedIDs(); + if (!recentlyRemovedAvatarEntities.empty()) { + // only lock this thread when absolutely necessary + AvatarEntityMap packedAvatarEntityData; + _avatarEntitiesLock.withReadLock([&] { + packedAvatarEntityData = _packedAvatarEntityData; + }); + foreach (auto entityID, recentlyRemovedAvatarEntities) { + if (!packedAvatarEntityData.contains(entityID)) { + entityTree->deleteEntity(entityID, true, true); + } + } + + // TODO: move this outside of tree lock + // remove stale data hashes + foreach (auto entityID, recentlyRemovedAvatarEntities) { + MapOfAvatarEntityDataHashes::iterator stateItr = _avatarEntityDataHashes.find(entityID); + if (stateItr != _avatarEntityDataHashes.end()) { + _avatarEntityDataHashes.erase(stateItr); + } + onRemoveAttachedAvatarEntity(entityID); + } + } + if (packedAvatarEntityData.size() != _avatarEntityForRecording.size()) { + createRecordingIDs(); + } + }); + + setAvatarEntityDataChanged(false); +} + +void OtherAvatar::onAddAttachedAvatarEntity(const QUuid& id) { + for (uint32_t i = 0; i < _attachedAvatarEntities.size(); ++i) { + if (_attachedAvatarEntities[i] == id) { + return; + } + } + _attachedAvatarEntities.push_back(id); +} + +void OtherAvatar::onRemoveAttachedAvatarEntity(const QUuid& id) { + for (uint32_t i = 0; i < _attachedAvatarEntities.size(); ++i) { + if (_attachedAvatarEntities[i] == id) { + if (i != _attachedAvatarEntities.size() - 1) { + _attachedAvatarEntities[i] = _attachedAvatarEntities.back(); + } + _attachedAvatarEntities.pop_back(); + break; + } + } +} + +void OtherAvatar::updateAttachedAvatarEntities() { + if (!_attachedAvatarEntities.empty()) { + auto treeRenderer = DependencyManager::get<EntityTreeRenderer>(); + if (!treeRenderer) { + return; + } + for (const QUuid& id : _attachedAvatarEntities) { + treeRenderer->onEntityChanged(id); + } + } +} diff --git a/interface/src/avatar/OtherAvatar.h b/interface/src/avatar/OtherAvatar.h index 5b72815757..a1dc5724a9 100644 --- a/interface/src/avatar/OtherAvatar.h +++ b/interface/src/avatar/OtherAvatar.h @@ -10,6 +10,7 @@ #define hifi_OtherAvatar_h #include <memory> +#include <vector> #include <avatars-renderer/Avatar.h> #include <workload/Space.h> @@ -45,9 +46,19 @@ public: bool shouldBeInPhysicsSimulation() const; bool needsPhysicsUpdate() const; + void updateCollisionGroup(bool myAvatarCollide); + + void simulate(float deltaTime, bool inView) override; + friend AvatarManager; protected: + void handleChangedAvatarEntityData(); + void updateAttachedAvatarEntities(); + void onAddAttachedAvatarEntity(const QUuid& id); + void onRemoveAttachedAvatarEntity(const QUuid& id); + + std::vector<QUuid> _attachedAvatarEntities; std::shared_ptr<Sphere3DOverlay> _otherAvatarOrbMeshPlaceholder { nullptr }; OverlayID _otherAvatarOrbMeshPlaceholderID { UNKNOWN_OVERLAY_ID }; AvatarMotionState* _motionState { nullptr }; diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index 1b53f9ab30..236512f2fe 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -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); } diff --git a/interface/src/raypick/LaserPointerScriptingInterface.h b/interface/src/raypick/LaserPointerScriptingInterface.h index 5aaacd7960..d85e329e9a 100644 --- a/interface/src/raypick/LaserPointerScriptingInterface.h +++ b/interface/src/raypick/LaserPointerScriptingInterface.h @@ -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 * diff --git a/interface/src/raypick/ParabolaPick.cpp b/interface/src/raypick/ParabolaPick.cpp index 571f4a6ea6..b93ced17c6 100644 --- a/interface/src/raypick/ParabolaPick.cpp +++ b/interface/src/raypick/ParabolaPick.cpp @@ -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()); diff --git a/interface/src/raypick/PathPointer.cpp b/interface/src/raypick/PathPointer.cpp index 00ab32bde4..ae0ce4671b 100644 --- a/interface/src/raypick/PathPointer.cpp +++ b/interface/src/raypick/PathPointer.cpp @@ -223,7 +223,7 @@ Pointer::Buttons PathPointer::getPressedButtons(const PickResultPointer& pickRes std::string button = trigger.getButton(); TriggerState& state = _states[button]; // TODO: right now, LaserPointers don't support axes, only on/off buttons - if (trigger.getEndpoint()->peek() >= 1.0f) { + if (trigger.getEndpoint()->peek().value >= 1.0f) { toReturn.insert(button); if (_previousButtons.find(button) == _previousButtons.end()) { @@ -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; diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 6e979d2d91..e8f84e63fe 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -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; diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index 94112d5fae..e795068cd3 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -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 diff --git a/interface/src/raypick/PointerScriptingInterface.h b/interface/src/raypick/PointerScriptingInterface.h index 2677f37fae..a21c1f2470 100644 --- a/interface/src/raypick/PointerScriptingInterface.h +++ b/interface/src/raypick/PointerScriptingInterface.h @@ -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 diff --git a/interface/src/raypick/RayPick.cpp b/interface/src/raypick/RayPick.cpp index a48d858504..507e45b470 100644 --- a/interface/src/raypick/RayPick.cpp +++ b/interface/src/raypick/RayPick.cpp @@ -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()); diff --git a/interface/src/raypick/RayPickScriptingInterface.h b/interface/src/raypick/RayPickScriptingInterface.h index d5e224018e..3ad0efd439 100644 --- a/interface/src/raypick/RayPickScriptingInterface.h +++ b/interface/src/raypick/RayPickScriptingInterface.h @@ -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} diff --git a/interface/src/raypick/StylusPointer.cpp b/interface/src/raypick/StylusPointer.cpp index 5595c54b71..867f896763 100644 --- a/interface/src/raypick/StylusPointer.cpp +++ b/interface/src/raypick/StylusPointer.cpp @@ -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); diff --git a/interface/src/scripting/ClipboardScriptingInterface.cpp b/interface/src/scripting/ClipboardScriptingInterface.cpp index c14f4ea895..c0a6b64421 100644 --- a/interface/src/scripting/ClipboardScriptingInterface.cpp +++ b/interface/src/scripting/ClipboardScriptingInterface.cpp @@ -25,12 +25,12 @@ float ClipboardScriptingInterface::getClipboardContentsLargestDimension() { return qApp->getEntityClipboard()->getContentsLargestDimension(); } -bool ClipboardScriptingInterface::exportEntities(const QString& filename, const QVector<EntityItemID>& entityIDs) { +bool ClipboardScriptingInterface::exportEntities(const QString& filename, const QVector<QUuid>& entityIDs) { bool retVal; BLOCKING_INVOKE_METHOD(qApp, "exportEntities", Q_RETURN_ARG(bool, retVal), Q_ARG(const QString&, filename), - Q_ARG(const QVector<EntityItemID>&, entityIDs)); + Q_ARG(const QVector<QUuid>&, entityIDs)); return retVal; } diff --git a/interface/src/scripting/ClipboardScriptingInterface.h b/interface/src/scripting/ClipboardScriptingInterface.h index 60b6ca2e03..f6a0b29779 100644 --- a/interface/src/scripting/ClipboardScriptingInterface.h +++ b/interface/src/scripting/ClipboardScriptingInterface.h @@ -63,7 +63,7 @@ public: * @param {Uuid[]} entityIDs Array of IDs of the entities to export. * @returns {boolean} <code>true</code> if the export was successful, otherwise <code>false</code>. */ - Q_INVOKABLE bool exportEntities(const QString& filename, const QVector<EntityItemID>& entityIDs); + Q_INVOKABLE bool exportEntities(const QString& filename, const QVector<QUuid>& entityIDs); /**jsdoc * Export the entities with centers within a cube to a JSON file. diff --git a/interface/src/scripting/ControllerScriptingInterface.h b/interface/src/scripting/ControllerScriptingInterface.h index 051a372aad..b063e98992 100644 --- a/interface/src/scripting/ControllerScriptingInterface.h +++ b/interface/src/scripting/ControllerScriptingInterface.h @@ -104,7 +104,7 @@ class ScriptEngine; * <ul> * <li>{@link Controller.getValue|getValue}</li> * <li>{@link Controller.getAxisValue|getAxisValue}</li> - * <li>{@link Controller.getPoseValue|getgetPoseValue}</li> + * <li>{@link Controller.getPoseValue|getPoseValue}</li> * <li>{@link Controller.getActionValue|getActionValue}</li> * </ul> * diff --git a/interface/src/scripting/MenuScriptingInterface.cpp b/interface/src/scripting/MenuScriptingInterface.cpp index d6dc2fa703..ae6a7c7d67 100644 --- a/interface/src/scripting/MenuScriptingInterface.cpp +++ b/interface/src/scripting/MenuScriptingInterface.cpp @@ -43,7 +43,7 @@ bool MenuScriptingInterface::menuExists(const QString& menu) { if (QThread::currentThread() == qApp->thread()) { return Menu::getInstance()->menuExists(menu); } - bool result; + bool result { false }; BLOCKING_INVOKE_METHOD(Menu::getInstance(), "menuExists", Q_RETURN_ARG(bool, result), Q_ARG(const QString&, menu)); @@ -86,7 +86,7 @@ bool MenuScriptingInterface::menuItemExists(const QString& menu, const QString& if (QThread::currentThread() == qApp->thread()) { return Menu::getInstance()->menuItemExists(menu, menuitem); } - bool result; + bool result { false }; BLOCKING_INVOKE_METHOD(Menu::getInstance(), "menuItemExists", Q_RETURN_ARG(bool, result), Q_ARG(const QString&, menu), @@ -98,7 +98,7 @@ bool MenuScriptingInterface::isOptionChecked(const QString& menuOption) { if (QThread::currentThread() == qApp->thread()) { return Menu::getInstance()->isOptionChecked(menuOption); } - bool result; + bool result { false }; BLOCKING_INVOKE_METHOD(Menu::getInstance(), "isOptionChecked", Q_RETURN_ARG(bool, result), Q_ARG(const QString&, menuOption)); @@ -115,7 +115,7 @@ bool MenuScriptingInterface::isMenuEnabled(const QString& menuOption) { if (QThread::currentThread() == qApp->thread()) { return Menu::getInstance()->isOptionChecked(menuOption); } - bool result; + bool result { false }; BLOCKING_INVOKE_METHOD(Menu::getInstance(), "isMenuEnabled", Q_RETURN_ARG(bool, result), Q_ARG(const QString&, menuOption)); diff --git a/interface/src/scripting/PlatformInfoScriptingInterface.cpp b/interface/src/scripting/PlatformInfoScriptingInterface.cpp new file mode 100644 index 0000000000..b6e4df0d40 --- /dev/null +++ b/interface/src/scripting/PlatformInfoScriptingInterface.cpp @@ -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(); +} diff --git a/interface/src/scripting/PlatformInfoScriptingInterface.h b/interface/src/scripting/PlatformInfoScriptingInterface.h new file mode 100644 index 0000000000..3ed57965c9 --- /dev/null +++ b/interface/src/scripting/PlatformInfoScriptingInterface.h @@ -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 diff --git a/interface/src/ui/Keyboard.cpp b/interface/src/ui/Keyboard.cpp index 9e3a3ce961..9e9a319802 100644 --- a/interface/src/ui/Keyboard.cpp +++ b/interface/src/ui/Keyboard.cpp @@ -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() } diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp index 2d7500b2eb..d49c27655b 100644 --- a/interface/src/ui/LoginDialog.cpp +++ b/interface/src/ui/LoginDialog.cpp @@ -96,7 +96,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(); + } + }); } } diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 495e29f986..cb204c9772 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -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()); diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index 8599e05332..eb43e8cf45 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -27,6 +27,9 @@ Base3DOverlay::Base3DOverlay() : _drawInFront(false), _drawHUDLayer(false) { + // HACK: queryAACube stuff not actually relevant for 3DOverlays, and by setting _queryAACubeSet true here + // we can avoid incorrect evaluation for sending updates for entities with 3DOverlays children. + _queryAACubeSet = true; } Base3DOverlay::Base3DOverlay(const Base3DOverlay* base3DOverlay) : @@ -41,6 +44,9 @@ Base3DOverlay::Base3DOverlay(const Base3DOverlay* base3DOverlay) : _isVisibleInSecondaryCamera(base3DOverlay->_isVisibleInSecondaryCamera) { setTransform(base3DOverlay->getTransform()); + // HACK: queryAACube stuff not actually relevant for 3DOverlays, and by setting _queryAACubeSet true here + // we can avoid incorrect evaluation for sending updates for entities with 3DOverlays children. + _queryAACubeSet = true; } QVariantMap convertOverlayLocationFromScriptSemantics(const QVariantMap& properties, bool scalesWithParent) { @@ -178,9 +184,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()) { @@ -207,6 +215,7 @@ void Base3DOverlay::setProperties(const QVariantMap& originalProperties) { transaction.updateItem(itemID); scene->enqueueTransaction(transaction); } + _queryAACubeSet = true; // HACK: just in case some SpatiallyNestable code accidentally set it false } } @@ -223,7 +232,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 +268,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") { diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index 6cc5182b56..daf15e676f 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -25,6 +25,7 @@ public: Base3DOverlay(const Base3DOverlay* base3DOverlay); void setVisible(bool visible) override; + bool queryAACubeNeedsUpdate() const override { return false; } // HACK: queryAACube not relevant for Overlays virtual OverlayID getOverlayID() const override { return OverlayID(getID().toString()); } void setOverlayID(OverlayID overlayID) override { setID(overlayID); } diff --git a/interface/src/ui/overlays/Circle3DOverlay.cpp b/interface/src/ui/overlays/Circle3DOverlay.cpp index a98ea7070e..1a7d3228c7 100644 --- a/interface/src/ui/overlays/Circle3DOverlay.cpp +++ b/interface/src/ui/overlays/Circle3DOverlay.cpp @@ -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. diff --git a/interface/src/ui/overlays/Cube3DOverlay.cpp b/interface/src/ui/overlays/Cube3DOverlay.cpp index 581db672a3..9888d696cf 100644 --- a/interface/src/ui/overlays/Cube3DOverlay.cpp +++ b/interface/src/ui/overlays/Cube3DOverlay.cpp @@ -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. diff --git a/interface/src/ui/overlays/Grid3DOverlay.cpp b/interface/src/ui/overlays/Grid3DOverlay.cpp index 87ab0fb2e8..92481b8116 100644 --- a/interface/src/ui/overlays/Grid3DOverlay.cpp +++ b/interface/src/ui/overlays/Grid3DOverlay.cpp @@ -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. diff --git a/interface/src/ui/overlays/Image3DOverlay.cpp b/interface/src/ui/overlays/Image3DOverlay.cpp index e24e3b3ed8..dcf3ca2285 100644 --- a/interface/src/ui/overlays/Image3DOverlay.cpp +++ b/interface/src/ui/overlays/Image3DOverlay.cpp @@ -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; } diff --git a/interface/src/ui/overlays/Line3DOverlay.cpp b/interface/src/ui/overlays/Line3DOverlay.cpp index e6546686b0..b1c316c6af 100644 --- a/interface/src/ui/overlays/Line3DOverlay.cpp +++ b/interface/src/ui/overlays/Line3DOverlay.cpp @@ -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. diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index 805832760e..14b8182abf 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -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. diff --git a/interface/src/ui/overlays/Rectangle3DOverlay.cpp b/interface/src/ui/overlays/Rectangle3DOverlay.cpp index 73606c0467..e30171bd50 100644 --- a/interface/src/ui/overlays/Rectangle3DOverlay.cpp +++ b/interface/src/ui/overlays/Rectangle3DOverlay.cpp @@ -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. diff --git a/interface/src/ui/overlays/Shape3DOverlay.cpp b/interface/src/ui/overlays/Shape3DOverlay.cpp index b424424369..4adbbf3792 100644 --- a/interface/src/ui/overlays/Shape3DOverlay.cpp +++ b/interface/src/ui/overlays/Shape3DOverlay.cpp @@ -158,7 +158,7 @@ void Shape3DOverlay::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. diff --git a/interface/src/ui/overlays/Sphere3DOverlay.cpp b/interface/src/ui/overlays/Sphere3DOverlay.cpp index 97294ae871..b1d5c878c4 100644 --- a/interface/src/ui/overlays/Sphere3DOverlay.cpp +++ b/interface/src/ui/overlays/Sphere3DOverlay.cpp @@ -59,7 +59,7 @@ Sphere3DOverlay::Sphere3DOverlay(const Sphere3DOverlay* Sphere3DOverlay) : * @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. diff --git a/interface/src/ui/overlays/Text3DOverlay.cpp b/interface/src/ui/overlays/Text3DOverlay.cpp index c8f8550e8e..58ce16a9fc 100644 --- a/interface/src/ui/overlays/Text3DOverlay.cpp +++ b/interface/src/ui/overlays/Text3DOverlay.cpp @@ -123,7 +123,7 @@ void Text3DOverlay::render(RenderArgs* args) { glm::vec4 textColor = { toGlm(_color), getTextAlpha() }; // FIXME: Factor out textRenderer so that Text3DOverlay overlay parts can be grouped by pipeline for a gpu performance increase. - _textRenderer->draw(batch, 0, 0, getText(), textColor, glm::vec2(-1.0f), true); + _textRenderer->draw(batch, 0, 0, getText(), textColor, glm::vec2(-1.0f)); } const render::ShapeKey Text3DOverlay::getShapeKey() { @@ -224,7 +224,7 @@ void Text3DOverlay::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. diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 18b7150b4a..ec6b62e237 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -555,7 +555,7 @@ void Web3DOverlay::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. diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index 9dcf5822cd..1adc04ee1b 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -109,9 +109,6 @@ void AnimClip::copyFromNetworkAnim() { jointMap.reserve(animJointCount); for (int i = 0; i < animJointCount; i++) { int skeletonJoint = _skeleton->nameToJointIndex(animSkeleton.getJointName(i)); - if (skeletonJoint == -1) { - qCWarning(animation) << "animation contains joint =" << animSkeleton.getJointName(i) << " which is not in the skeleton"; - } jointMap.push_back(skeletonJoint); } diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 128ac05b81..6e27bee06f 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -343,18 +343,18 @@ void Rig::initJointStates(const HFMModel& hfmModel, const glm::mat4& modelOffset buildAbsoluteRigPoses(_animSkeleton->getRelativeDefaultPoses(), _absoluteDefaultPoses); - _rootJointIndex = hfmModel.rootJointIndex; - _leftEyeJointIndex = hfmModel.leftEyeJointIndex; - _rightEyeJointIndex = hfmModel.rightEyeJointIndex; - _leftHandJointIndex = hfmModel.leftHandJointIndex; + _rootJointIndex = indexOfJoint("Hips"); + _leftEyeJointIndex = indexOfJoint("LeftEye"); + _rightEyeJointIndex = indexOfJoint("RightEye"); + _leftHandJointIndex = indexOfJoint("LeftHand"); _leftElbowJointIndex = _leftHandJointIndex >= 0 ? hfmModel.joints.at(_leftHandJointIndex).parentIndex : -1; _leftShoulderJointIndex = _leftElbowJointIndex >= 0 ? hfmModel.joints.at(_leftElbowJointIndex).parentIndex : -1; - _rightHandJointIndex = hfmModel.rightHandJointIndex; + _rightHandJointIndex = indexOfJoint("RightHand"); _rightElbowJointIndex = _rightHandJointIndex >= 0 ? hfmModel.joints.at(_rightHandJointIndex).parentIndex : -1; _rightShoulderJointIndex = _rightElbowJointIndex >= 0 ? hfmModel.joints.at(_rightElbowJointIndex).parentIndex : -1; - _leftEyeJointChildren = _animSkeleton->getChildrenOfJoint(hfmModel.leftEyeJointIndex); - _rightEyeJointChildren = _animSkeleton->getChildrenOfJoint(hfmModel.rightEyeJointIndex); + _leftEyeJointChildren = _animSkeleton->getChildrenOfJoint(indexOfJoint("LeftEye")); + _rightEyeJointChildren = _animSkeleton->getChildrenOfJoint(indexOfJoint("RightEye")); } void Rig::reset(const HFMModel& hfmModel) { @@ -390,18 +390,18 @@ void Rig::reset(const HFMModel& hfmModel) { buildAbsoluteRigPoses(_animSkeleton->getRelativeDefaultPoses(), _absoluteDefaultPoses); - _rootJointIndex = hfmModel.rootJointIndex; - _leftEyeJointIndex = hfmModel.leftEyeJointIndex; - _rightEyeJointIndex = hfmModel.rightEyeJointIndex; - _leftHandJointIndex = hfmModel.leftHandJointIndex; + _rootJointIndex = indexOfJoint("Hips");; + _leftEyeJointIndex = indexOfJoint("LeftEye"); + _rightEyeJointIndex = indexOfJoint("RightEye"); + _leftHandJointIndex = indexOfJoint("LeftHand"); _leftElbowJointIndex = _leftHandJointIndex >= 0 ? hfmModel.joints.at(_leftHandJointIndex).parentIndex : -1; _leftShoulderJointIndex = _leftElbowJointIndex >= 0 ? hfmModel.joints.at(_leftElbowJointIndex).parentIndex : -1; - _rightHandJointIndex = hfmModel.rightHandJointIndex; + _rightHandJointIndex = indexOfJoint("RightHand"); _rightElbowJointIndex = _rightHandJointIndex >= 0 ? hfmModel.joints.at(_rightHandJointIndex).parentIndex : -1; _rightShoulderJointIndex = _rightElbowJointIndex >= 0 ? hfmModel.joints.at(_rightElbowJointIndex).parentIndex : -1; - _leftEyeJointChildren = _animSkeleton->getChildrenOfJoint(hfmModel.leftEyeJointIndex); - _rightEyeJointChildren = _animSkeleton->getChildrenOfJoint(hfmModel.rightEyeJointIndex); + _leftEyeJointChildren = _animSkeleton->getChildrenOfJoint(indexOfJoint("LeftEye")); + _rightEyeJointChildren = _animSkeleton->getChildrenOfJoint(indexOfJoint("RightEye")); if (!_animGraphURL.isEmpty()) { _animNode.reset(); @@ -425,7 +425,6 @@ int Rig::indexOfJoint(const QString& jointName) const { // This is a content error, so we should issue a warning. if (result < 0 && _jointNameWarningCount < MAX_JOINT_NAME_WARNING_COUNT) { - qCWarning(animation) << "Rig: Missing joint" << jointName << "in avatar model"; _jointNameWarningCount++; } return result; diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 9bad7e2f45..36fb701168 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -1333,8 +1333,12 @@ bool AudioClient::mixLocalAudioInjectors(float* mixBuffer) { } else if (injector->isStereo()) { + // calculate distance, gain + glm::vec3 relativePosition = injector->getPosition() - _positionGetter(); + float distance = glm::max(glm::length(relativePosition), EPSILON); + float gain = gainForSource(distance, injector->getVolume()); + // stereo gets directly mixed into mixBuffer - float gain = injector->getVolume(); for (int i = 0; i < AudioConstants::NETWORK_FRAME_SAMPLES_STEREO; i++) { mixBuffer[i] += convertToFloat(_localScratchBuffer[i]) * gain; } diff --git a/libraries/audio/src/AudioFOA.cpp b/libraries/audio/src/AudioFOA.cpp index 16c0721047..30d29b72b7 100644 --- a/libraries/audio/src/AudioFOA.cpp +++ b/libraries/audio/src/AudioFOA.cpp @@ -695,7 +695,7 @@ static void ifft_radix8_first(complex_t* x, complex_t* y, int n, int p) { // n >= 4 static void rfft_post(complex_t* x, const complex_t* w, int n) { - size_t t = n/4; + int t = n/4; assert(t >= 1); // NOTE: x[n/2].re is packed into x[0].im @@ -707,7 +707,7 @@ static void rfft_post(complex_t* x, const complex_t* w, int n) { complex_t* xp0 = &x[1]; complex_t* xp1 = &x[n/2 - 1]; - for (size_t i = 0; i < t; i++) { + for (int i = 0; i < t; i++) { float ar = xp0[i].re; float ai = xp0[i].im; @@ -743,7 +743,7 @@ static void rfft_post(complex_t* x, const complex_t* w, int n) { // n >= 4 static void rifft_pre(complex_t* x, const complex_t* w, int n) { - size_t t = n/4; + int t = n/4; assert(t >= 1); // NOTE: x[n/2].re is packed into x[0].im @@ -755,7 +755,7 @@ static void rifft_pre(complex_t* x, const complex_t* w, int n) { complex_t* xp0 = &x[1]; complex_t* xp1 = &x[n/2 - 1]; - for (size_t i = 0; i < t; i++) { + for (int i = 0; i < t; i++) { float ar = xp0[i].re; float ai = xp0[i].im; diff --git a/libraries/audio/src/avx2/AudioFOA_avx2.cpp b/libraries/audio/src/avx2/AudioFOA_avx2.cpp index 880f40b185..70f9b0e5f6 100644 --- a/libraries/audio/src/avx2/AudioFOA_avx2.cpp +++ b/libraries/audio/src/avx2/AudioFOA_avx2.cpp @@ -973,8 +973,8 @@ FORCEINLINE static void ifft_radix8_first(complex_t* x, complex_t* y, int n, int // n >= 32 static void rfft_post(complex_t* x, const complex_t* w, int n) { - size_t t = n/4; - assert(n/4 >= 8); // SIMD8 + int t = n/4; + assert(t >= 8); // SIMD8 // NOTE: x[n/2].re is packed into x[0].im float tr = x[0].re; @@ -985,7 +985,7 @@ static void rfft_post(complex_t* x, const complex_t* w, int n) { complex_t* xp0 = &x[1]; complex_t* xp1 = &x[n/2 - 8]; - for (size_t i = 0; i < t; i += 8) { + for (int i = 0; i < t; i += 8) { __m256 z0 = _mm256_loadu_ps(&xp0[i+0].re); __m256 z1 = _mm256_loadu_ps(&xp0[i+4].re); @@ -1033,8 +1033,8 @@ static void rfft_post(complex_t* x, const complex_t* w, int n) { // n >= 32 static void rifft_pre(complex_t* x, const complex_t* w, int n) { - size_t t = n/4; - assert(n/4 >= 8); // SIMD8 + int t = n/4; + assert(t >= 8); // SIMD8 // NOTE: x[n/2].re is packed into x[0].im float tr = x[0].re; @@ -1045,7 +1045,7 @@ static void rifft_pre(complex_t* x, const complex_t* w, int n) { complex_t* xp0 = &x[1]; complex_t* xp1 = &x[n/2 - 8]; - for (size_t i = 0; i < t; i += 8) { + for (int i = 0; i < t; i += 8) { __m256 z0 = _mm256_loadu_ps(&xp0[i+0].re); __m256 z1 = _mm256_loadu_ps(&xp0[i+4].re); diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp index b4ea9c20f9..a6185d7e79 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.cpp @@ -32,6 +32,7 @@ #include <shared/Camera.h> #include <SoftAttachmentModel.h> #include <render/TransitionStage.h> +#include <GLMHelpers.h> #include "ModelEntityItem.h" #include "RenderableModelEntityItem.h" @@ -295,6 +296,7 @@ void Avatar::setTargetScale(float targetScale) { if (_targetScale != newValue) { _targetScale = newValue; _scaleChanged = usecTimestampNow(); + _avatarScaleChanged = _scaleChanged; _isAnimatingScale = true; emit targetScaleChanged(targetScale); @@ -306,181 +308,127 @@ void Avatar::setAvatarEntityDataChanged(bool value) { _avatarEntityDataHashes.clear(); } -void Avatar::updateAvatarEntities() { - PerformanceTimer perfTimer("attachments"); - - // AVATAR ENTITY UPDATE FLOW - // - if queueEditEntityMessage sees avatarEntity flag it does _myAvatar->updateAvatarEntity() - // - updateAvatarEntity saves the bytes and flags the trait instance for the entity as updated - // - ClientTraitsHandler::sendChangedTraitsToMixer sends the entity bytes to the mixer which relays them to other interfaces - // - AvatarHashMap::processBulkAvatarTraits on other interfaces calls avatar->processTraitInstace - // - AvatarData::processTraitInstance calls updateAvatarEntity, which sets _avatarEntityDataChanged = true - // - (My)Avatar::simulate notices _avatarEntityDataChanged and here we are... - - // AVATAR ENTITY DELETE FLOW - // - EntityScriptingInterface::deleteEntity calls _myAvatar->clearAvatarEntity() for deleted avatar entities - // - clearAvatarEntity removes the avatar entity and flags the trait instance for the entity as deleted - // - ClientTraitsHandler::sendChangedTraitsToMixer sends a deletion to the mixer which relays to other interfaces - // - AvatarHashMap::processBulkAvatarTraits on other interfaces calls avatar->processDeletedTraitInstace - // - AvatarData::processDeletedTraitInstance calls clearAvatarEntity - // - AvatarData::clearAvatarEntity sets _avatarEntityDataChanged = true and adds the ID to the detached list - // - Avatar::simulate notices _avatarEntityDataChanged and here we are... - - if (!_avatarEntityDataChanged) { - return; - } - - if (getID().isNull() || - getID() == AVATAR_SELF_ID || - DependencyManager::get<NodeList>()->getSessionUUID() == QUuid()) { - // wait until MyAvatar and this Node gets an ID before doing this. Otherwise, various things go wrong -- - // things get their parent fixed up from AVATAR_SELF_ID to a null uuid which means "no parent". - return; - } - - auto treeRenderer = DependencyManager::get<EntityTreeRenderer>(); - EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; - if (!entityTree) { - return; - } - - QScriptEngine scriptEngine; - entityTree->withWriteLock([&] { - AvatarEntityMap avatarEntities = getAvatarEntityData(); - AvatarEntityMap::const_iterator dataItr = avatarEntities.begin(); - while (dataItr != avatarEntities.end()) { - // compute hash of data. TODO? cache this? - QByteArray data = dataItr.value(); - uint32_t newHash = qHash(data); - - // check to see if we recognize this hash and whether it was already successfully processed - QUuid entityID = dataItr.key(); - MapOfAvatarEntityDataHashes::iterator stateItr = _avatarEntityDataHashes.find(entityID); - if (stateItr != _avatarEntityDataHashes.end()) { - if (stateItr.value().success) { - if (newHash == stateItr.value().hash) { - // data hasn't changed --> nothing to do - ++dataItr; - continue; - } - } else { - // NOTE: if the data was unsuccessful in producing an entity in the past - // we will try again just in case something changed (unlikely). - // Unfortunately constantly trying to build the entity for this data costs - // CPU cycles that we'd rather not spend. - // TODO? put a maximum number of tries on this? - } - } else { - // remember this hash for the future - stateItr = _avatarEntityDataHashes.insert(entityID, AvatarEntityDataHash(newHash)); - } - ++dataItr; - - // see EntityEditPacketSender::queueEditEntityMessage for the other end of this. unpack properties - // and either add or update the entity. - QJsonDocument jsonProperties = QJsonDocument::fromBinaryData(data); - if (!jsonProperties.isObject()) { - qCDebug(avatars_renderer) << "got bad avatarEntity json" << QString(data.toHex()); - continue; - } - - QVariant variantProperties = jsonProperties.toVariant(); - QVariantMap asMap = variantProperties.toMap(); - QScriptValue scriptProperties = variantMapToScriptValue(asMap, scriptEngine); - EntityItemProperties properties; - EntityItemPropertiesFromScriptValueHonorReadOnly(scriptProperties, properties); - properties.setEntityHostType(entity::HostType::AVATAR); - properties.setOwningAvatarID(getID()); - - // there's no entity-server to tell us we're the simulation owner, so always set the - // simulationOwner to the owningAvatarID and a high priority. - properties.setSimulationOwner(getID(), AVATAR_ENTITY_SIMULATION_PRIORITY); - - if (properties.getParentID() == AVATAR_SELF_ID) { - properties.setParentID(getID()); - } - - // NOTE: if this avatar entity is not attached to us, strip its entity script completely... - auto attachedScript = properties.getScript(); - if (!isMyAvatar() && !attachedScript.isEmpty()) { - QString noScript; - properties.setScript(noScript); - } - - auto specifiedHref = properties.getHref(); - if (!isMyAvatar() && !specifiedHref.isEmpty()) { - qCDebug(avatars_renderer) << "removing entity href from avatar attached entity:" << entityID << "old href:" << specifiedHref; - QString noHref; - properties.setHref(noHref); - } - - // When grabbing avatar entities, they are parented to the joint moving them, then when un-grabbed - // they go back to the default parent (null uuid). When un-gripped, others saw the entity disappear. - // The thinking here is the local position was noticed as changing, but not the parentID (since it is now - // back to the default), and the entity flew off somewhere. Marking all changed definitely fixes this, - // and seems safe (per Seth). - properties.markAllChanged(); - - // try to build the entity - EntityItemPointer entity = entityTree->findEntityByEntityItemID(EntityItemID(entityID)); - bool success = true; - if (entity) { - if (entityTree->updateEntity(entityID, properties)) { - entity->updateLastEditedFromRemote(); - } else { - success = false; - } - } else { - entity = entityTree->addEntity(entityID, properties); - if (!entity) { - success = false; - } - } - stateItr.value().success = success; - } - - AvatarEntityIDs recentlyDetachedAvatarEntities = getAndClearRecentlyDetachedIDs(); - if (!recentlyDetachedAvatarEntities.empty()) { - // only lock this thread when absolutely necessary - AvatarEntityMap avatarEntityData; - _avatarEntitiesLock.withReadLock([&] { - avatarEntityData = _avatarEntityData; - }); - foreach (auto entityID, recentlyDetachedAvatarEntities) { - if (!avatarEntityData.contains(entityID)) { - entityTree->deleteEntity(entityID, true, true); - } - } - - // remove stale data hashes - foreach (auto entityID, recentlyDetachedAvatarEntities) { - MapOfAvatarEntityDataHashes::iterator stateItr = _avatarEntityDataHashes.find(entityID); - if (stateItr != _avatarEntityDataHashes.end()) { - _avatarEntityDataHashes.erase(stateItr); - } - } - } - if (avatarEntities.size() != _avatarEntityForRecording.size()) { - createRecordingIDs(); - } - }); - - setAvatarEntityDataChanged(false); -} - void Avatar::removeAvatarEntitiesFromTree() { auto treeRenderer = DependencyManager::get<EntityTreeRenderer>(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; if (entityTree) { + QList<QUuid> avatarEntityIDs; + _avatarEntitiesLock.withReadLock([&] { + avatarEntityIDs = _packedAvatarEntityData.keys(); + }); entityTree->withWriteLock([&] { - AvatarEntityMap avatarEntities = getAvatarEntityData(); - for (auto entityID : avatarEntities.keys()) { + for (const auto& entityID : avatarEntityIDs) { entityTree->deleteEntity(entityID, true, true); } }); } } +void Avatar::updateGrabs() { + + // update the Grabs according to any changes in _avatarGrabData + _avatarGrabsLock.withWriteLock([&] { + if (_avatarGrabDataChanged) { + foreach (auto grabID, _avatarGrabData.keys()) { + AvatarGrabMap::iterator grabItr = _avatarGrabs.find(grabID); + if (grabItr == _avatarGrabs.end()) { + GrabPointer grab = std::make_shared<Grab>(); + grab->fromByteArray(_avatarGrabData.value(grabID)); + _avatarGrabs[grabID] = grab; + _changedAvatarGrabs.insert(grabID); + } else { + GrabPointer grab = grabItr.value(); + bool changed = grab->fromByteArray(_avatarGrabData.value(grabID)); + if (changed) { + _changedAvatarGrabs.insert(grabID); + } + } + } + _avatarGrabDataChanged = false; + } + + auto treeRenderer = DependencyManager::get<EntityTreeRenderer>(); + auto entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; + EntityEditPacketSender* packetSender = treeRenderer ? treeRenderer->getPacketSender() : nullptr; + auto sessionID = DependencyManager::get<NodeList>()->getSessionUUID(); + + QMutableSetIterator<QUuid> delItr(_deletedAvatarGrabs); + while (delItr.hasNext()) { + QUuid grabID = delItr.next(); + GrabPointer grab = _avatarGrabs[grabID]; + if (!grab) { + delItr.remove(); + continue; + } + + bool success; + SpatiallyNestablePointer target = SpatiallyNestable::findByID(grab->getTargetID(), success); + + // only clear this entry from the _deletedAvatarGrabs if we found the entity. + if (success && target) { + bool iShouldTellServer = target->getEditSenderID() == sessionID; + + EntityItemPointer entity = std::dynamic_pointer_cast<EntityItem>(target); + if (entity && entity->isAvatarEntity() && (entity->getOwningAvatarID() == sessionID || + entity->getOwningAvatarID() == AVATAR_SELF_ID)) { + // this is our own avatar-entity, so we always tell the server about the release + iShouldTellServer = true; + } + + target->removeGrab(grab); + delItr.remove(); + // in case this is the last grab on an entity, we need to shrink the queryAACube and tell the server + // about the final position. + if (entityTree) { + bool force = true; + entityTree->withWriteLock([&] { + entityTree->updateEntityQueryAACube(target, packetSender, force, iShouldTellServer); + }); + } + } + _avatarGrabs.remove(grabID); + _changedAvatarGrabs.remove(grabID); + } + + QMutableSetIterator<QUuid> changeItr(_changedAvatarGrabs); + while (changeItr.hasNext()) { + QUuid grabID = changeItr.next(); + GrabPointer& grab = _avatarGrabs[grabID]; + + bool success; + SpatiallyNestablePointer target = SpatiallyNestable::findByID(grab->getTargetID(), success); + + if (success && target) { + target->addGrab(grab); + // only clear this entry from the _changedAvatarGrabs if we found the entity. + changeItr.remove(); + } + } + }); +} + +void Avatar::accumulateGrabPositions(std::map<QUuid, GrabLocationAccumulator>& grabAccumulators) { + // relay avatar's joint position to grabbed target in a way that allows for averaging + _avatarGrabsLock.withReadLock([&] { + foreach (auto grabID, _avatarGrabs.keys()) { + const GrabPointer& grab = _avatarGrabs.value(grabID); + + if (!grab || !grab->getActionID().isNull()) { + continue; // the accumulated value isn't used, in this case. + } + + glm::vec3 jointTranslation = getAbsoluteJointTranslationInObjectFrame(grab->getParentJointIndex()); + glm::quat jointRotation = getAbsoluteJointRotationInObjectFrame(grab->getParentJointIndex()); + glm::mat4 jointMat = createMatFromQuatAndPos(jointRotation, jointTranslation); + glm::mat4 offsetMat = createMatFromQuatAndPos(grab->getRotationalOffset(), grab->getPositionalOffset()); + glm::mat4 avatarMat = getTransform().getMatrix(); + glm::mat4 worldTransform = avatarMat * jointMat * offsetMat; + GrabLocationAccumulator& grabLocationAccumulator = grabAccumulators[grab->getTargetID()]; + grabLocationAccumulator.accumulate(extractTranslation(worldTransform), extractRotation(worldTransform)); + } + }); +} + void Avatar::relayJointDataToChildren() { forEachChild([&](SpatiallyNestablePointer child) { if (child->getNestableType() == NestableType::Entity) { @@ -544,82 +492,6 @@ void Avatar::relayJointDataToChildren() { _reconstructSoftEntitiesJointMap = false; } -void Avatar::simulate(float deltaTime, bool inView) { - PROFILE_RANGE(simulation, "simulate"); - - _globalPosition = _transit.isActive() ? _transit.getCurrentPosition() : _serverPosition; - if (!hasParent()) { - setLocalPosition(_globalPosition); - } - - _simulationRate.increment(); - if (inView) { - _simulationInViewRate.increment(); - } - - PerformanceTimer perfTimer("simulate"); - { - PROFILE_RANGE(simulation, "updateJoints"); - if (inView) { - Head* head = getHead(); - if (_hasNewJointData || _transit.isActive()) { - _skeletonModel->getRig().copyJointsFromJointData(_jointData); - glm::mat4 rootTransform = glm::scale(_skeletonModel->getScale()) * glm::translate(_skeletonModel->getOffset()); - _skeletonModel->getRig().computeExternalPoses(rootTransform); - _jointDataSimulationRate.increment(); - - _skeletonModel->simulate(deltaTime, true); - - locationChanged(); // joints changed, so if there are any children, update them. - _hasNewJointData = false; - - glm::vec3 headPosition = getWorldPosition(); - if (!_skeletonModel->getHeadPosition(headPosition)) { - headPosition = getWorldPosition(); - } - head->setPosition(headPosition); - } - head->setScale(getModelScale()); - head->simulate(deltaTime); - relayJointDataToChildren(); - } else { - // a non-full update is still required so that the position, rotation, scale and bounds of the skeletonModel are updated. - _skeletonModel->simulate(deltaTime, false); - } - _skeletonModelSimulationRate.increment(); - } - - // update animation for display name fade in/out - if ( _displayNameTargetAlpha != _displayNameAlpha) { - // the alpha function is - // Fade out => alpha(t) = factor ^ t => alpha(t+dt) = alpha(t) * factor^(dt) - // Fade in => alpha(t) = 1 - factor^t => alpha(t+dt) = 1-(1-alpha(t))*coef^(dt) - // factor^(dt) = coef - float coef = pow(DISPLAYNAME_FADE_FACTOR, deltaTime); - if (_displayNameTargetAlpha < _displayNameAlpha) { - // Fading out - _displayNameAlpha *= coef; - } else { - // Fading in - _displayNameAlpha = 1 - (1 - _displayNameAlpha) * coef; - } - _displayNameAlpha = abs(_displayNameAlpha - _displayNameTargetAlpha) < 0.01f ? _displayNameTargetAlpha : _displayNameAlpha; - } - - { - PROFILE_RANGE(simulation, "misc"); - measureMotionDerivatives(deltaTime); - simulateAttachments(deltaTime); - updatePalms(); - } - { - PROFILE_RANGE(simulation, "entities"); - updateAvatarEntities(); - } - - updateFadingStatus(); -} - float Avatar::getSimulationRate(const QString& rateName) const { if (rateName == "") { return _simulationRate.rate(); @@ -934,7 +806,6 @@ void Avatar::render(RenderArgs* renderArgs) { } } - void Avatar::setEnableMeshVisible(bool isEnabled) { if (_isMeshVisible != isEnabled) { _isMeshVisible = isEnabled; @@ -1295,6 +1166,9 @@ glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const { } switch (index) { + case NO_JOINT_INDEX: { + return glm::quat(); + } case SENSOR_TO_WORLD_MATRIX_INDEX: { glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix(); glm::mat4 avatarMatrix = getLocalTransform().getMatrix(); @@ -1314,7 +1188,7 @@ glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const { case CAMERA_MATRIX_INDEX: { glm::quat rotation; if (_skeletonModel && _skeletonModel->isActive()) { - int headJointIndex = _skeletonModel->getHFMModel().headJointIndex; + int headJointIndex = getJointIndex("Head"); if (headJointIndex >= 0) { _skeletonModel->getAbsoluteJointRotationInRigFrame(headJointIndex, rotation); } @@ -1344,6 +1218,9 @@ glm::vec3 Avatar::getAbsoluteJointTranslationInObjectFrame(int index) const { } switch (index) { + case NO_JOINT_INDEX: { + return glm::vec3(0.0f); + } case SENSOR_TO_WORLD_MATRIX_INDEX: { glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix(); glm::mat4 avatarMatrix = getLocalTransform().getMatrix(); @@ -1363,7 +1240,7 @@ glm::vec3 Avatar::getAbsoluteJointTranslationInObjectFrame(int index) const { case CAMERA_MATRIX_INDEX: { glm::vec3 translation; if (_skeletonModel && _skeletonModel->isActive()) { - int headJointIndex = _skeletonModel->getHFMModel().headJointIndex; + int headJointIndex = getJointIndex("Head"); if (headJointIndex >= 0) { _skeletonModel->getAbsoluteJointTranslationInRigFrame(headJointIndex, translation); } diff --git a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h index 8f70b12122..4ff3e9cc13 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/Avatar.h +++ b/libraries/avatars-renderer/src/avatars-renderer/Avatar.h @@ -139,9 +139,8 @@ public: typedef render::Payload<AvatarData> Payload; void init(); - void updateAvatarEntities(); void removeAvatarEntitiesFromTree(); - void simulate(float deltaTime, bool inView); + virtual void simulate(float deltaTime, bool inView) = 0; virtual void simulateAttachments(float deltaTime); virtual void render(RenderArgs* renderArgs); @@ -156,9 +155,6 @@ public: virtual void postUpdate(float deltaTime, const render::ScenePointer& scene); - //setters - void setIsLookAtTarget(const bool isLookAtTarget) { _isLookAtTarget = isLookAtTarget; } - bool getIsLookAtTarget() const { return _isLookAtTarget; } //getters bool isInitialized() const { return _initialized; } SkeletonModelPointer getSkeletonModel() { return _skeletonModel; } @@ -243,8 +239,6 @@ public: static void renderJointConnectingCone(gpu::Batch& batch, glm::vec3 position1, glm::vec3 position2, float radius1, float radius2, const glm::vec4& color); - virtual void applyCollision(const glm::vec3& contactPoint, const glm::vec3& penetration) { } - /**jsdoc * Set the offset applied to the current avatar. The offset adjusts the position that the avatar is rendered. For example, * with an offset of <code>{ x: 0, y: 0.1, z: 0 }</code>, your avatar will appear to be raised off the ground slightly. @@ -440,6 +434,8 @@ public: std::shared_ptr<AvatarTransit> getTransit() { return std::make_shared<AvatarTransit>(_transit); }; AvatarTransit::Status updateTransit(float deltaTime, const glm::vec3& avatarPosition, float avatarScale, const AvatarTransit::TransitConfig& config); + void accumulateGrabPositions(std::map<QUuid, GrabLocationAccumulator>& grabAccumulators); + signals: void targetScaleChanged(float targetScale); @@ -542,6 +538,7 @@ protected: // protected methods... bool isLookingAtMe(AvatarSharedPointer avatar) const; + void updateGrabs(); void relayJointDataToChildren(); void fade(render::Transaction& transaction, render::Transition::Type type); @@ -549,6 +546,7 @@ protected: glm::vec3 getBodyRightDirection() const { return getWorldOrientation() * IDENTITY_RIGHT; } glm::vec3 getBodyUpDirection() const { return getWorldOrientation() * IDENTITY_UP; } void measureMotionDerivatives(float deltaTime); + bool getCollideWithOtherAvatars() const { return _collideWithOtherAvatars; } float getSkeletonHeight() const; float getHeadHeight() const; @@ -592,7 +590,6 @@ protected: int _rightPointerGeometryID { 0 }; int _nameRectGeometryID { 0 }; bool _initialized { false }; - bool _isLookAtTarget { false }; bool _isAnimatingScale { false }; bool _mustFadeIn { false }; bool _isFading { false }; @@ -628,6 +625,8 @@ protected: static void metaBlendshapeOperator(render::ItemID renderItemID, int blendshapeNumber, const QVector<BlendshapeOffset>& blendshapeOffsets, const QVector<int>& blendedMeshSizes, const render::ItemIDs& subItemIDs); + + AvatarGrabMap _avatarGrabs; }; #endif // hifi_Avatar_h diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp index 36e37dd3d4..7f2dbda3de 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp @@ -66,7 +66,7 @@ void SkeletonModel::initJointStates() { } // Determine the default eye position for avatar scale = 1.0 - int headJointIndex = hfmModel.headJointIndex; + int headJointIndex = _rig.indexOfJoint("Head"); if (0 > headJointIndex || headJointIndex >= _rig.getJointStateCount()) { qCWarning(avatars_renderer) << "Bad head joint! Got:" << headJointIndex << "jointCount:" << _rig.getJointStateCount(); } @@ -74,7 +74,7 @@ void SkeletonModel::initJointStates() { getEyeModelPositions(leftEyePosition, rightEyePosition); glm::vec3 midEyePosition = (leftEyePosition + rightEyePosition) / 2.0f; - int rootJointIndex = hfmModel.rootJointIndex; + int rootJointIndex = _rig.indexOfJoint("Hips"); glm::vec3 rootModelPosition; getJointPosition(rootJointIndex, rootModelPosition); @@ -96,7 +96,6 @@ void SkeletonModel::initJointStates() { // Called within Model::simulate call, below. void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { assert(!_owningAvatar->isMyAvatar()); - const HFMModel& hfmModel = getHFMModel(); Head* head = _owningAvatar->getHead(); @@ -124,7 +123,7 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { // If the head is not positioned, updateEyeJoints won't get the math right glm::quat headOrientation; - _rig.getJointRotation(hfmModel.headJointIndex, headOrientation); + _rig.getJointRotation(_rig.indexOfJoint("Head"), headOrientation); glm::vec3 eulers = safeEulerAngles(headOrientation); head->setBasePitch(glm::degrees(-eulers.x)); head->setBaseYaw(glm::degrees(eulers.y)); @@ -135,8 +134,8 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { eyeParams.eyeSaccade = glm::vec3(0.0f); 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); } @@ -259,45 +258,44 @@ bool SkeletonModel::getRightShoulderPosition(glm::vec3& position) const { } bool SkeletonModel::getHeadPosition(glm::vec3& headPosition) const { - return isActive() && getJointPositionInWorldFrame(getHFMModel().headJointIndex, headPosition); + return isActive() && getJointPositionInWorldFrame(_rig.indexOfJoint("Head"), headPosition); } bool SkeletonModel::getNeckPosition(glm::vec3& neckPosition) const { - return isActive() && getJointPositionInWorldFrame(getHFMModel().neckJointIndex, neckPosition); + return isActive() && getJointPositionInWorldFrame(_rig.indexOfJoint("Neck"), neckPosition); } bool SkeletonModel::getLocalNeckPosition(glm::vec3& neckPosition) const { - return isActive() && getJointPosition(getHFMModel().neckJointIndex, neckPosition); + return isActive() && getJointPosition(_rig.indexOfJoint("Neck"), neckPosition); } bool SkeletonModel::getEyeModelPositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const { if (!isActive()) { return false; } - const HFMModel& hfmModel = getHFMModel(); - if (getJointPosition(hfmModel.leftEyeJointIndex, firstEyePosition) && - getJointPosition(hfmModel.rightEyeJointIndex, secondEyePosition)) { + if (getJointPosition(_rig.indexOfJoint("LeftEye"), firstEyePosition) && + getJointPosition(_rig.indexOfJoint("RightEye"), secondEyePosition)) { return true; } // no eye joints; try to estimate based on head/neck joints glm::vec3 neckPosition, headPosition; - if (getJointPosition(hfmModel.neckJointIndex, neckPosition) && - getJointPosition(hfmModel.headJointIndex, headPosition)) { + if (getJointPosition(_rig.indexOfJoint("Neck"), neckPosition) && + getJointPosition(_rig.indexOfJoint("Head"), headPosition)) { const float EYE_PROPORTION = 0.6f; glm::vec3 baseEyePosition = glm::mix(neckPosition, headPosition, EYE_PROPORTION); glm::quat headRotation; - getJointRotation(hfmModel.headJointIndex, headRotation); + getJointRotation(_rig.indexOfJoint("Head"), headRotation); const float EYES_FORWARD = 0.25f; const float EYE_SEPARATION = 0.1f; float headHeight = glm::distance(neckPosition, headPosition); firstEyePosition = baseEyePosition + headRotation * glm::vec3(EYE_SEPARATION, 0.0f, EYES_FORWARD) * headHeight; secondEyePosition = baseEyePosition + headRotation * glm::vec3(-EYE_SEPARATION, 0.0f, EYES_FORWARD) * headHeight; return true; - } else if (getJointPosition(hfmModel.headJointIndex, headPosition)) { + } else if (getJointPosition(_rig.indexOfJoint("Head"), headPosition)) { glm::vec3 baseEyePosition = headPosition; glm::quat headRotation; - getJointRotation(hfmModel.headJointIndex, headRotation); + getJointRotation(_rig.indexOfJoint("Head"), headRotation); const float EYES_FORWARD_HEAD_ONLY = 0.30f; const float EYE_SEPARATION = 0.1f; firstEyePosition = baseEyePosition + headRotation * glm::vec3(EYE_SEPARATION, 0.0f, EYES_FORWARD_HEAD_ONLY); @@ -331,7 +329,7 @@ void SkeletonModel::computeBoundingShape() { } const HFMModel& hfmModel = getHFMModel(); - if (hfmModel.joints.isEmpty() || hfmModel.rootJointIndex == -1) { + if (hfmModel.joints.isEmpty() || _rig.indexOfJoint("Hips") == -1) { // rootJointIndex == -1 if the avatar model has no skeleton return; } @@ -369,7 +367,7 @@ void SkeletonModel::renderBoundingCollisionShapes(RenderArgs* args, gpu::Batch& } bool SkeletonModel::hasSkeleton() { - return isActive() ? getHFMModel().rootJointIndex != -1 : false; + return isActive() ? _rig.indexOfJoint("Hips") != -1 : false; } void SkeletonModel::onInvalidate() { diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h index c53cf8d333..ef0e1e0fae 100644 --- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h +++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.h @@ -41,10 +41,10 @@ public: void updateAttitude(const glm::quat& orientation); /// Returns the index of the left hand joint, or -1 if not found. - int getLeftHandJointIndex() const { return isActive() ? getHFMModel().leftHandJointIndex : -1; } + int getLeftHandJointIndex() const { return isActive() ? _rig.indexOfJoint("LeftHand") : -1; } /// Returns the index of the right hand joint, or -1 if not found. - int getRightHandJointIndex() const { return isActive() ? getHFMModel().rightHandJointIndex : -1; } + int getRightHandJointIndex() const { return isActive() ? _rig.indexOfJoint("RightHand") : -1; } bool getLeftGrabPosition(glm::vec3& position) const; bool getRightGrabPosition(glm::vec3& position) const; diff --git a/libraries/avatars/src/AssociatedTraitValues.h b/libraries/avatars/src/AssociatedTraitValues.h index cf1edef7f7..e3060a8097 100644 --- a/libraries/avatars/src/AssociatedTraitValues.h +++ b/libraries/avatars/src/AssociatedTraitValues.h @@ -14,16 +14,33 @@ #include "AvatarTraits.h" +// This templated class is admittedly fairly confusing to look at. It is used +// to hold some associated value of type T for both simple (non-instanced) and instanced traits. +// Most of the complexity comes from the fact that simple and instanced trait types are +// handled differently. For each simple trait type there can be a value T, but for +// each instance of each instanced trait +// (keyed by a TraitInstanceID, which at the time of this writing is a UUID) there can be a value T. +// There are separate methods in most cases for simple traits and instanced traits +// because of this different behaviour. This class is not used to hold the values +// of the traits themselves, but instead an associated value like the latest version +// of each trait (see TraitVersions) or a state associated with each trait (like added/changed/deleted). + namespace AvatarTraits { template<typename T, T defaultValue> class AssociatedTraitValues { public: + // constructor that pre-fills _simpleTypes with the default value specified by the template AssociatedTraitValues() : _simpleTypes(FirstInstancedTrait, defaultValue) {} + /// inserts the given value for the given simple trait type void insert(TraitType type, T value) { _simpleTypes[type] = value; } + /// resets the simple trait type value to the default void erase(TraitType type) { _simpleTypes[type] = defaultValue; } + /// returns a reference to the value for a given instance for a given instanced trait type T& getInstanceValueRef(TraitType traitType, TraitInstanceID instanceID); + + /// inserts the passed value for the given instance for the given instanced trait type void instanceInsert(TraitType traitType, TraitInstanceID instanceID, T value); struct InstanceIDValuePair { @@ -34,24 +51,30 @@ namespace AvatarTraits { }; using InstanceIDValuePairs = std::vector<InstanceIDValuePair>; - + + /// returns a vector of InstanceIDValuePair objects for the given instanced trait type InstanceIDValuePairs& getInstanceIDValuePairs(TraitType traitType); + /// erases the a given instance for a given instanced trait type void instanceErase(TraitType traitType, TraitInstanceID instanceID); + /// erases the value for all instances for a given instanced trait type void eraseAllInstances(TraitType traitType); - // will return defaultValue for instanced traits + /// value getters for simple trait types, will be default value if value has been erased or not set T operator[](TraitType traitType) const { return _simpleTypes[traitType]; } T& operator[](TraitType traitType) { return _simpleTypes[traitType]; } + /// resets all simple trait types to the default value and erases all values for instanced trait types void reset() { std::fill(_simpleTypes.begin(), _simpleTypes.end(), defaultValue); _instancedTypes.clear(); } + /// const iterators for the vector of simple type values typename std::vector<T>::const_iterator simpleCBegin() const { return _simpleTypes.cbegin(); } typename std::vector<T>::const_iterator simpleCEnd() const { return _simpleTypes.cend(); } + /// non-const iterators for the vector of simple type values typename std::vector<T>::iterator simpleBegin() { return _simpleTypes.begin(); } typename std::vector<T>::iterator simpleEnd() { return _simpleTypes.end(); } @@ -64,15 +87,18 @@ namespace AvatarTraits { traitType(traitType), instances({{ instanceID, value }}) {}; }; + /// const iterators for the vector of TraitWithInstances objects typename std::vector<TraitWithInstances>::const_iterator instancedCBegin() const { return _instancedTypes.cbegin(); } typename std::vector<TraitWithInstances>::const_iterator instancedCEnd() const { return _instancedTypes.cend(); } + /// non-const iterators for the vector of TraitWithInstances objects typename std::vector<TraitWithInstances>::iterator instancedBegin() { return _instancedTypes.begin(); } typename std::vector<TraitWithInstances>::iterator instancedEnd() { return _instancedTypes.end(); } private: std::vector<T> _simpleTypes; + /// return the iterator to the matching TraitWithInstances object for a given instanced trait type typename std::vector<TraitWithInstances>::iterator instancesForTrait(TraitType traitType) { return std::find_if(_instancedTypes.begin(), _instancedTypes.end(), [traitType](TraitWithInstances& traitWithInstances){ @@ -83,25 +109,34 @@ namespace AvatarTraits { std::vector<TraitWithInstances> _instancedTypes; }; + /// returns a reference to the InstanceIDValuePairs object for a given instanced trait type template <typename T, T defaultValue> inline typename AssociatedTraitValues<T, defaultValue>::InstanceIDValuePairs& AssociatedTraitValues<T, defaultValue>::getInstanceIDValuePairs(TraitType traitType) { + // first check if we already have some values for instances of this trait type auto it = instancesForTrait(traitType); if (it != _instancedTypes.end()) { return it->instances; } else { + // if we didn't have any values for instances of the instanced trait type + // add an empty InstanceIDValuePairs object first and then return the reference to it _instancedTypes.emplace_back(traitType); return _instancedTypes.back().instances; } } + // returns a reference to value for the given instance of the given instanced trait type template <typename T, T defaultValue> inline T& AssociatedTraitValues<T, defaultValue>::getInstanceValueRef(TraitType traitType, TraitInstanceID instanceID) { + // first check if we already have some values for instances of this trait type auto it = instancesForTrait(traitType); if (it != _instancedTypes.end()) { + // grab the matching vector of instances auto& instancesVector = it->instances; + + // check if we have a value for this specific instance ID auto instanceIt = std::find_if(instancesVector.begin(), instancesVector.end(), [instanceID](InstanceIDValuePair& idValuePair){ return idValuePair.id == instanceID; @@ -109,40 +144,53 @@ namespace AvatarTraits { if (instanceIt != instancesVector.end()) { return instanceIt->value; } else { + // no value for this specific instance ID, insert the default value and return it instancesVector.emplace_back(instanceID, defaultValue); return instancesVector.back().value; } } else { + // no values for any instances of this trait type + // insert the default value for the specific instance for the instanced trait type _instancedTypes.emplace_back(traitType, instanceID, defaultValue); return _instancedTypes.back().instances.back().value; } } + /// inserts the passed value for the specific instance of the given instanced trait type template <typename T, T defaultValue> inline void AssociatedTraitValues<T, defaultValue>::instanceInsert(TraitType traitType, TraitInstanceID instanceID, T value) { + // first check if we already have some instances for this trait type auto it = instancesForTrait(traitType); if (it != _instancedTypes.end()) { + // found some instances for the instanced trait type, check if our specific instance is one of them auto& instancesVector = it->instances; auto instanceIt = std::find_if(instancesVector.begin(), instancesVector.end(), [instanceID](InstanceIDValuePair& idValuePair){ return idValuePair.id == instanceID; }); if (instanceIt != instancesVector.end()) { + // the instance already existed, update the value instanceIt->value = value; } else { + // the instance was not present, emplace the new value instancesVector.emplace_back(instanceID, value); } } else { + // there were no existing instances for the given trait type + // setup the container for instances and insert the passed value for this instance ID _instancedTypes.emplace_back(traitType, instanceID, value); } } + /// erases the value for a specific instance of the given instanced trait type template <typename T, T defaultValue> inline void AssociatedTraitValues<T, defaultValue>::instanceErase(TraitType traitType, TraitInstanceID instanceID) { + // check if we have any instances at all for this instanced trait type auto it = instancesForTrait(traitType); if (it != _instancedTypes.end()) { + // we have some instances, erase the value for the passed instance ID if it is present auto& instancesVector = it->instances; instancesVector.erase(std::remove_if(instancesVector.begin(), instancesVector.end(), diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index b6c7954f67..21e0a6aba2 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -540,6 +540,10 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (_headData->getHasProceduralBlinkFaceMovement()) { setAtBit16(flags, PROCEDURAL_BLINK_FACE_MOVEMENT); } + // avatar collisions enabled + if (_collideWithOtherAvatars) { + setAtBit16(flags, COLLIDE_WITH_OTHER_AVATARS); + } data->flags = flags; destinationBuffer += sizeof(AvatarDataPacket::AdditionalFlags); @@ -1116,7 +1120,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { auto newHasAudioEnabledFaceMovement = oneAtBit16(bitItems, AUDIO_ENABLED_FACE_MOVEMENT); auto newHasProceduralEyeFaceMovement = oneAtBit16(bitItems, PROCEDURAL_EYE_FACE_MOVEMENT); auto newHasProceduralBlinkFaceMovement = oneAtBit16(bitItems, PROCEDURAL_BLINK_FACE_MOVEMENT); - + auto newCollideWithOtherAvatars = oneAtBit16(bitItems, COLLIDE_WITH_OTHER_AVATARS); bool keyStateChanged = (_keyState != newKeyState); bool handStateChanged = (_handState != newHandState); @@ -1125,7 +1129,9 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { bool audioEnableFaceMovementChanged = (_headData->getHasAudioEnabledFaceMovement() != newHasAudioEnabledFaceMovement); bool proceduralEyeFaceMovementChanged = (_headData->getHasProceduralEyeFaceMovement() != newHasProceduralEyeFaceMovement); bool proceduralBlinkFaceMovementChanged = (_headData->getHasProceduralBlinkFaceMovement() != newHasProceduralBlinkFaceMovement); - bool somethingChanged = keyStateChanged || handStateChanged || faceStateChanged || eyeStateChanged || audioEnableFaceMovementChanged || proceduralEyeFaceMovementChanged || proceduralBlinkFaceMovementChanged; + bool collideWithOtherAvatarsChanged = (_collideWithOtherAvatars != newCollideWithOtherAvatars); + bool somethingChanged = keyStateChanged || handStateChanged || faceStateChanged || eyeStateChanged || audioEnableFaceMovementChanged || + proceduralEyeFaceMovementChanged || proceduralBlinkFaceMovementChanged || collideWithOtherAvatarsChanged; _keyState = newKeyState; _handState = newHandState; @@ -1134,6 +1140,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { _headData->setHasAudioEnabledFaceMovement(newHasAudioEnabledFaceMovement); _headData->setHasProceduralEyeFaceMovement(newHasProceduralEyeFaceMovement); _headData->setHasProceduralBlinkFaceMovement(newHasProceduralBlinkFaceMovement); + _collideWithOtherAvatars = newCollideWithOtherAvatars; sourceBuffer += sizeof(AvatarDataPacket::AdditionalFlags); @@ -1893,42 +1900,95 @@ qint64 AvatarData::packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice return bytesWritten; } + +qint64 AvatarData::packAvatarEntityTraitInstance(AvatarTraits::TraitType traitType, + AvatarTraits::TraitInstanceID traitInstanceID, + ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion) { + qint64 bytesWritten = 0; + + // grab a read lock on the avatar entities and check for entity data for the given ID + QByteArray entityBinaryData; + _avatarEntitiesLock.withReadLock([this, &entityBinaryData, &traitInstanceID] { + if (_packedAvatarEntityData.contains(traitInstanceID)) { + entityBinaryData = _packedAvatarEntityData[traitInstanceID]; + } + }); + + if (entityBinaryData.size() > AvatarTraits::MAXIMUM_TRAIT_SIZE) { + qWarning() << "Refusing to pack instanced trait" << traitType << "of size" << entityBinaryData.size() + << "bytes since it exceeds the maximum size " << AvatarTraits::MAXIMUM_TRAIT_SIZE << "bytes"; + return 0; + } + + bytesWritten += destination.writePrimitive(traitType); + + if (traitVersion > AvatarTraits::DEFAULT_TRAIT_VERSION) { + bytesWritten += destination.writePrimitive(traitVersion); + } + + bytesWritten += destination.write(traitInstanceID.toRfc4122()); + + if (!entityBinaryData.isNull()) { + AvatarTraits::TraitWireSize entityBinarySize = entityBinaryData.size(); + + bytesWritten += destination.writePrimitive(entityBinarySize); + bytesWritten += destination.write(entityBinaryData); + } else { + bytesWritten += destination.writePrimitive(AvatarTraits::DELETED_TRAIT_SIZE); + } + + return bytesWritten; +} + + +qint64 AvatarData::packGrabTraitInstance(AvatarTraits::TraitType traitType, + AvatarTraits::TraitInstanceID traitInstanceID, + ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion) { + qint64 bytesWritten = 0; + + // grab a read lock on the avatar grabs and check for grab data for the given ID + QByteArray grabBinaryData; + + _avatarGrabsLock.withReadLock([this, &grabBinaryData, &traitInstanceID] { + if (_avatarGrabData.contains(traitInstanceID)) { + grabBinaryData = _avatarGrabData[traitInstanceID]; + } + }); + + if (grabBinaryData.size() > AvatarTraits::MAXIMUM_TRAIT_SIZE) { + qWarning() << "Refusing to pack instanced trait" << traitType << "of size" << grabBinaryData.size() + << "bytes since it exceeds the maximum size " << AvatarTraits::MAXIMUM_TRAIT_SIZE << "bytes"; + return 0; + } + + bytesWritten += destination.writePrimitive(traitType); + + if (traitVersion > AvatarTraits::DEFAULT_TRAIT_VERSION) { + bytesWritten += destination.writePrimitive(traitVersion); + } + + bytesWritten += destination.write(traitInstanceID.toRfc4122()); + + if (!grabBinaryData.isNull()) { + AvatarTraits::TraitWireSize grabBinarySize = grabBinaryData.size(); + + bytesWritten += destination.writePrimitive(grabBinarySize); + bytesWritten += destination.write(grabBinaryData); + } else { + bytesWritten += destination.writePrimitive(AvatarTraits::DELETED_TRAIT_SIZE); + } + + return bytesWritten; +} + qint64 AvatarData::packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID traitInstanceID, ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion) { qint64 bytesWritten = 0; if (traitType == AvatarTraits::AvatarEntity) { - // grab a read lock on the avatar entities and check for entity data for the given ID - QByteArray entityBinaryData; - - _avatarEntitiesLock.withReadLock([this, &entityBinaryData, &traitInstanceID] { - if (_avatarEntityData.contains(traitInstanceID)) { - entityBinaryData = _avatarEntityData[traitInstanceID]; - } - }); - - if (entityBinaryData.size() > AvatarTraits::MAXIMUM_TRAIT_SIZE) { - qWarning() << "Refusing to pack instanced trait" << traitType << "of size" << entityBinaryData.size() - << "bytes since it exceeds the maximum size " << AvatarTraits::MAXIMUM_TRAIT_SIZE << "bytes"; - return 0; - } - - bytesWritten += destination.writePrimitive(traitType); - - if (traitVersion > AvatarTraits::DEFAULT_TRAIT_VERSION) { - bytesWritten += destination.writePrimitive(traitVersion); - } - - bytesWritten += destination.write(traitInstanceID.toRfc4122()); - - if (!entityBinaryData.isNull()) { - AvatarTraits::TraitWireSize entityBinarySize = entityBinaryData.size(); - - bytesWritten += destination.writePrimitive(entityBinarySize); - bytesWritten += destination.write(entityBinaryData); - } else { - bytesWritten += destination.writePrimitive(AvatarTraits::DELETED_TRAIT_SIZE); - } + bytesWritten += packAvatarEntityTraitInstance(traitType, traitInstanceID, destination, traitVersion); + } else if (traitType == AvatarTraits::Grab) { + bytesWritten += packGrabTraitInstance(traitType, traitInstanceID, destination, traitVersion); } return bytesWritten; @@ -1937,9 +1997,12 @@ qint64 AvatarData::packTraitInstance(AvatarTraits::TraitType traitType, AvatarTr void AvatarData::prepareResetTraitInstances() { if (_clientTraitsHandler) { _avatarEntitiesLock.withReadLock([this]{ - foreach (auto entityID, _avatarEntityData.keys()) { + foreach (auto entityID, _packedAvatarEntityData.keys()) { _clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID); } + foreach (auto grabID, _avatarGrabData.keys()) { + _clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::Grab, grabID); + } }); } } @@ -1955,13 +2018,17 @@ void AvatarData::processTrait(AvatarTraits::TraitType traitType, QByteArray trai void AvatarData::processTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID, QByteArray traitBinaryData) { if (traitType == AvatarTraits::AvatarEntity) { - updateAvatarEntity(instanceID, traitBinaryData); + storeAvatarEntityDataPayload(instanceID, traitBinaryData); + } else if (traitType == AvatarTraits::Grab) { + updateAvatarGrabData(instanceID, traitBinaryData); } } void AvatarData::processDeletedTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID) { if (traitType == AvatarTraits::AvatarEntity) { clearAvatarEntity(instanceID); + } else if (traitType == AvatarTraits::Grab) { + clearAvatarGrabData(instanceID); } } @@ -2299,7 +2366,7 @@ void AvatarData::setRecordingBasis(std::shared_ptr<Transform> recordingBasis) { void AvatarData::createRecordingIDs() { _avatarEntitiesLock.withReadLock([&] { _avatarEntityForRecording.clear(); - for (int i = 0; i < _avatarEntityData.size(); i++) { + for (int i = 0; i < _packedAvatarEntityData.size(); i++) { _avatarEntityForRecording.insert(QUuid::createUuid()); } }); @@ -2354,6 +2421,10 @@ JointData jointDataFromJsonValue(int version, const QJsonValue& json) { return result; } +void AvatarData::avatarEntityDataToJson(QJsonObject& root) const { + // overridden where needed +} + QJsonObject AvatarData::toJson() const { QJsonObject root; @@ -2365,20 +2436,8 @@ QJsonObject AvatarData::toJson() const { if (!getDisplayName().isEmpty()) { root[JSON_AVATAR_DISPLAY_NAME] = getDisplayName(); } - _avatarEntitiesLock.withReadLock([&] { - if (!_avatarEntityData.empty()) { - QJsonArray avatarEntityJson; - int entityCount = 0; - for (auto entityID : _avatarEntityData.keys()) { - QVariantMap entityData; - QUuid newId = _avatarEntityForRecording.size() == _avatarEntityData.size() ? _avatarEntityForRecording.values()[entityCount++] : entityID; - entityData.insert("id", newId); - entityData.insert("properties", _avatarEntityData.value(entityID).toBase64()); - avatarEntityJson.push_back(QVariant(entityData).toJsonObject()); - } - root[JSON_AVATAR_ENTITIES] = avatarEntityJson; - } - }); + + avatarEntityDataToJson(root); auto recordingBasis = getRecordingBasis(); bool success; @@ -2500,9 +2559,9 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) { for (auto attachmentJson : attachmentsJson) { if (attachmentJson.isObject()) { QVariantMap entityData = attachmentJson.toObject().toVariantMap(); - QUuid entityID = entityData.value("id").toUuid(); - QByteArray properties = QByteArray::fromBase64(entityData.value("properties").toByteArray()); - updateAvatarEntity(entityID, properties); + QUuid id = entityData.value("id").toUuid(); + QByteArray data = QByteArray::fromBase64(entityData.value("properties").toByteArray()); + updateAvatarEntity(id, data); } } } @@ -2684,17 +2743,15 @@ void AvatarData::setAttachmentsVariant(const QVariantList& variant) { setAttachmentData(newAttachments); } -const int MAX_NUM_AVATAR_ENTITIES = 42; - -void AvatarData::updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) { +void AvatarData::storeAvatarEntityDataPayload(const QUuid& entityID, const QByteArray& data) { _avatarEntitiesLock.withWriteLock([&] { - AvatarEntityMap::iterator itr = _avatarEntityData.find(entityID); - if (itr == _avatarEntityData.end()) { - if (_avatarEntityData.size() < MAX_NUM_AVATAR_ENTITIES) { - _avatarEntityData.insert(entityID, entityData); + PackedAvatarEntityMap::iterator itr = _packedAvatarEntityData.find(entityID); + if (itr == _packedAvatarEntityData.end()) { + if (_packedAvatarEntityData.size() < MAX_NUM_AVATAR_ENTITIES) { + _packedAvatarEntityData.insert(entityID, data); } } else { - itr.value() = entityData; + itr.value() = data; } }); @@ -2707,15 +2764,20 @@ void AvatarData::updateAvatarEntity(const QUuid& entityID, const QByteArray& ent } } +void AvatarData::updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) { + // overridden where needed + // expects 'entityData' to be a JavaScript EntityItemProperties Object in QByteArray form +} + void AvatarData::clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFromTree) { bool removedEntity = false; _avatarEntitiesLock.withWriteLock([this, &removedEntity, &entityID] { - removedEntity = _avatarEntityData.remove(entityID); + removedEntity = _packedAvatarEntityData.remove(entityID); }); - insertDetachedEntityID(entityID); + insertRemovedEntityID(entityID); if (removedEntity && _clientTraitsHandler) { // we have a client traits handler, so we need to mark this removed instance trait as deleted @@ -2725,75 +2787,29 @@ void AvatarData::clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFr } AvatarEntityMap AvatarData::getAvatarEntityData() const { - AvatarEntityMap result; - _avatarEntitiesLock.withReadLock([&] { - result = _avatarEntityData; - }); - return result; -} - -void AvatarData::insertDetachedEntityID(const QUuid entityID) { - _avatarEntitiesLock.withWriteLock([&] { - _avatarEntityDetached.insert(entityID); - }); - - _avatarEntityDataChanged = true; + // overridden where needed + // NOTE: the return value is expected to be a map of unfortunately-formatted-binary-blobs + return AvatarEntityMap(); } void AvatarData::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) { - if (avatarEntityData.size() > MAX_NUM_AVATAR_ENTITIES) { - // the data is suspect - qCDebug(avatars) << "discard suspect AvatarEntityData with size =" << avatarEntityData.size(); - return; - } - - std::vector<QUuid> deletedEntityIDs; - QList<QUuid> updatedEntityIDs; - - _avatarEntitiesLock.withWriteLock([&] { - if (_avatarEntityData != avatarEntityData) { - - // keep track of entities that were attached to this avatar but no longer are - AvatarEntityIDs previousAvatarEntityIDs = QSet<QUuid>::fromList(_avatarEntityData.keys()); - - _avatarEntityData = avatarEntityData; - setAvatarEntityDataChanged(true); - - deletedEntityIDs.reserve(previousAvatarEntityIDs.size()); - - foreach (auto entityID, previousAvatarEntityIDs) { - if (!_avatarEntityData.contains(entityID)) { - _avatarEntityDetached.insert(entityID); - deletedEntityIDs.push_back(entityID); - } - } - - updatedEntityIDs = _avatarEntityData.keys(); - } - }); - - if (_clientTraitsHandler) { - // we have a client traits handler - - // flag removed entities as deleted so that changes are sent next frame - for (auto& deletedEntityID : deletedEntityIDs) { - _clientTraitsHandler->markInstancedTraitDeleted(AvatarTraits::AvatarEntity, deletedEntityID); - } - - // flag any updated or created entities so that we send changes for them next frame - for (auto& entityID : updatedEntityIDs) { - _clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID); - } - } - - + // overridden where needed + // avatarEntityData is expected to be a map of QByteArrays + // each QByteArray represents an EntityItemProperties object from JavaScript } -AvatarEntityIDs AvatarData::getAndClearRecentlyDetachedIDs() { +void AvatarData::insertRemovedEntityID(const QUuid entityID) { + _avatarEntitiesLock.withWriteLock([&] { + _avatarEntityRemoved.insert(entityID); + }); + _avatarEntityDataChanged = true; +} + +AvatarEntityIDs AvatarData::getAndClearRecentlyRemovedIDs() { AvatarEntityIDs result; _avatarEntitiesLock.withWriteLock([&] { - result = _avatarEntityDetached; - _avatarEntityDetached.clear(); + result = _avatarEntityRemoved; + _avatarEntityRemoved.clear(); }); return result; } @@ -2909,3 +2925,38 @@ AABox AvatarData::getDefaultBubbleBox() const { bubbleBox.translate(_globalPosition); return bubbleBox; } + +bool AvatarData::updateAvatarGrabData(const QUuid& grabID, const QByteArray& grabData) { + bool changed { false }; + _avatarGrabsLock.withWriteLock([&] { + AvatarGrabDataMap::iterator itr = _avatarGrabData.find(grabID); + if (itr == _avatarGrabData.end()) { + // create a new one + if (_avatarGrabData.size() < MAX_NUM_AVATAR_GRABS) { + _avatarGrabData.insert(grabID, grabData); + _avatarGrabDataChanged = true; + changed = true; + } else { + qCWarning(avatars) << "Can't create more grabs on avatar, limit reached."; + } + } else { + // update an existing one + if (itr.value() != grabData) { + itr.value() = grabData; + _avatarGrabDataChanged = true; + changed = true; + } + } + }); + + return changed; +} + +void AvatarData::clearAvatarGrabData(const QUuid& grabID) { + _avatarGrabsLock.withWriteLock([&] { + if (_avatarGrabData.remove(grabID)) { + _avatarGrabDataChanged = true; + _deletedAvatarGrabs.insert(grabID); + } + }); +} diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 71e79191bc..ec5ea4a5d0 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -54,17 +54,26 @@ #include "AvatarTraits.h" #include "HeadData.h" #include "PathUtils.h" +#include "Grab.h" #include <graphics/Material.h> using AvatarSharedPointer = std::shared_ptr<AvatarData>; using AvatarWeakPointer = std::weak_ptr<AvatarData>; using AvatarHash = QHash<QUuid, AvatarSharedPointer>; + using AvatarEntityMap = QMap<QUuid, QByteArray>; +using PackedAvatarEntityMap = QMap<QUuid, QByteArray>; // similar to AvatarEntityMap, but different internal format using AvatarEntityIDs = QSet<QUuid>; +using AvatarGrabDataMap = QMap<QUuid, QByteArray>; +using AvatarGrabIDs = QSet<QUuid>; +using AvatarGrabMap = QMap<QUuid, GrabPointer>; + using AvatarDataSequenceNumber = uint16_t; +const int MAX_NUM_AVATAR_ENTITIES = 42; + // avatar motion behaviors const quint32 AVATAR_MOTION_ACTION_MOTOR_ENABLED = 1U << 0; const quint32 AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED = 1U << 1; @@ -104,6 +113,7 @@ const int HAND_STATE_FINGER_POINTING_BIT = 7; // 8th bit const int AUDIO_ENABLED_FACE_MOVEMENT = 8; // 9th bit const int PROCEDURAL_EYE_FACE_MOVEMENT = 9; // 10th bit const int PROCEDURAL_BLINK_FACE_MOVEMENT = 10; // 11th bit +const int COLLIDE_WITH_OTHER_AVATARS = 11; // 12th bit const char HAND_STATE_NULL = 0; @@ -945,19 +955,20 @@ public: // FIXME: Can this name be improved? Can it be deprecated? Q_INVOKABLE virtual void setAttachmentsVariant(const QVariantList& variant); + virtual void storeAvatarEntityDataPayload(const QUuid& entityID, const QByteArray& payload); /**jsdoc * @function MyAvatar.updateAvatarEntity * @param {Uuid} entityID * @param {string} entityData */ - Q_INVOKABLE void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData); + Q_INVOKABLE virtual void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData); /**jsdoc * @function MyAvatar.clearAvatarEntity * @param {Uuid} entityID */ - Q_INVOKABLE void clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFromTree = true); + Q_INVOKABLE virtual void clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFromTree = true); /**jsdoc @@ -1118,6 +1129,7 @@ public: TransformPointer getRecordingBasis() const; void setRecordingBasis(TransformPointer recordingBasis = TransformPointer()); void createRecordingIDs(); + virtual void avatarEntityDataToJson(QJsonObject& root) const; QJsonObject toJson() const; void fromJson(const QJsonObject& json, bool useFrameSkeleton = true); @@ -1129,17 +1141,16 @@ public: * @function MyAvatar.getAvatarEntityData * @returns {object} */ - Q_INVOKABLE AvatarEntityMap getAvatarEntityData() const; + Q_INVOKABLE virtual AvatarEntityMap getAvatarEntityData() const; /**jsdoc * @function MyAvatar.setAvatarEntityData * @param {object} avatarEntityData */ - Q_INVOKABLE void setAvatarEntityData(const AvatarEntityMap& avatarEntityData); + Q_INVOKABLE virtual void setAvatarEntityData(const AvatarEntityMap& avatarEntityData); virtual void setAvatarEntityDataChanged(bool value) { _avatarEntityDataChanged = value; } - void insertDetachedEntityID(const QUuid entityID); - AvatarEntityIDs getAndClearRecentlyDetachedIDs(); + AvatarEntityIDs getAndClearRecentlyRemovedIDs(); /**jsdoc * @function MyAvatar.getSensorToWorldMatrix @@ -1326,6 +1337,7 @@ public slots: void resetLastSent() { _lastToByteArray = 0; } protected: + void insertRemovedEntityID(const QUuid entityID); void lazyInitHeadData() const; float getDistanceBasedMinRotationDOT(glm::vec3 viewerPosition) const; @@ -1342,6 +1354,13 @@ protected: bool hasParent() const { return !getParentID().isNull(); } bool hasFaceTracker() const { return _headData ? _headData->_isFaceTrackerConnected : false; } + qint64 packAvatarEntityTraitInstance(AvatarTraits::TraitType traitType, + AvatarTraits::TraitInstanceID traitInstanceID, + ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion); + qint64 packGrabTraitInstance(AvatarTraits::TraitType traitType, + AvatarTraits::TraitInstanceID traitInstanceID, + ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion); + // isReplicated will be true on downstream Avatar Mixers and their clients, but false on the upstream "master" // Audio Mixer that the replicated avatar is connected to. bool _isReplicated{ false }; @@ -1447,11 +1466,17 @@ protected: AABox _defaultBubbleBox; mutable ReadWriteLockable _avatarEntitiesLock; - AvatarEntityIDs _avatarEntityDetached; // recently detached from this avatar + AvatarEntityIDs _avatarEntityRemoved; // recently removed AvatarEntity ids AvatarEntityIDs _avatarEntityForRecording; // create new entities id for avatar recording - AvatarEntityMap _avatarEntityData; + PackedAvatarEntityMap _packedAvatarEntityData; bool _avatarEntityDataChanged { false }; + mutable ReadWriteLockable _avatarGrabsLock; + AvatarGrabDataMap _avatarGrabData; + bool _avatarGrabDataChanged { false }; // by network + AvatarGrabIDs _changedAvatarGrabs; // updated grab IDs -- changes needed to entities or physics + AvatarGrabIDs _deletedAvatarGrabs; // deleted grab IDs -- changes needed to entities or physics + // used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers. ThreadSafeValueCache<glm::mat4> _sensorToWorldMatrixCache { glm::mat4() }; ThreadSafeValueCache<glm::mat4> _controllerLeftHandMatrixCache { glm::mat4() }; @@ -1476,6 +1501,7 @@ protected: int _replicaIndex { 0 }; bool _isNewAvatar { true }; bool _isClientAvatar { false }; + bool _collideWithOtherAvatars { true }; // null unless MyAvatar or ScriptableAvatar sending traits data to mixer std::unique_ptr<ClientTraitsHandler, LaterDeleter> _clientTraitsHandler; @@ -1511,6 +1537,9 @@ protected: f(index); } + bool updateAvatarGrabData(const QUuid& grabID, const QByteArray& grabData); + void clearAvatarGrabData(const QUuid& grabID); + private: friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar); static QUrl _defaultFullAvatarModelUrl; @@ -1614,6 +1643,7 @@ QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEnt void AvatarEntityMapFromScriptValue(const QScriptValue& object, AvatarEntityMap& value); // faux joint indexes (-1 means invalid) +const int NO_JOINT_INDEX = 65535; // -1 const int SENSOR_TO_WORLD_MATRIX_INDEX = 65534; // -2 const int CONTROLLER_RIGHTHAND_INDEX = 65533; // -3 const int CONTROLLER_LEFTHAND_INDEX = 65532; // -4 @@ -1626,5 +1656,6 @@ const int FARGRAB_MOUSE_INDEX = 65526; // -10 const int LOWEST_PSEUDO_JOINT_INDEX = 65526; +const int MAX_NUM_AVATAR_GRABS = 6; #endif // hifi_AvatarData_h diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 41ca950b3b..6a67ef6638 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -328,6 +328,19 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer<ReceivedMessage> } void AvatarHashMap::processBulkAvatarTraits(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) { + AvatarTraits::TraitMessageSequence seq; + + message->readPrimitive(&seq); + + auto traitsAckPacket = NLPacket::create(PacketType::BulkAvatarTraitsAck, sizeof(AvatarTraits::TraitMessageSequence), true); + traitsAckPacket->writePrimitive(seq); + auto nodeList = DependencyManager::get<LimitedNodeList>(); + SharedNodePointer avatarMixer = nodeList->soloNodeOfType(NodeType::AvatarMixer); + if (!avatarMixer.isNull()) { + // we have a mixer to send to, acknowledge that we received these + // traits. + nodeList->sendPacket(std::move(traitsAckPacket), *avatarMixer); + } while (message->getBytesLeftToRead()) { // read the avatar ID to figure out which avatar this is for diff --git a/libraries/avatars/src/AvatarTraits.h b/libraries/avatars/src/AvatarTraits.h index 5e28515d12..4516572e42 100644 --- a/libraries/avatars/src/AvatarTraits.h +++ b/libraries/avatars/src/AvatarTraits.h @@ -24,6 +24,7 @@ namespace AvatarTraits { SkeletonModelURL, FirstInstancedTrait, AvatarEntity = FirstInstancedTrait, + Grab, TotalTraitTypes }; @@ -41,6 +42,10 @@ namespace AvatarTraits { const TraitWireSize DELETED_TRAIT_SIZE = -1; const TraitWireSize MAXIMUM_TRAIT_SIZE = INT16_MAX; + using TraitMessageSequence = int64_t; + const TraitMessageSequence FIRST_TRAIT_SEQUENCE = 0; + const TraitMessageSequence MAX_TRAIT_SEQUENCE = INT64_MAX; + inline qint64 packInstancedTraitDelete(TraitType traitType, TraitInstanceID instanceID, ExtendedIODevice& destination, TraitVersion traitVersion = NULL_TRAIT_VERSION) { qint64 bytesWritten = 0; diff --git a/libraries/avatars/src/ProjectFile.h b/libraries/avatars/src/ProjectFile.h new file mode 100644 index 0000000000..4040eb1ce5 --- /dev/null +++ b/libraries/avatars/src/ProjectFile.h @@ -0,0 +1,13 @@ +#ifndef hifi_AvatarProjectFile_h +#define hifi_AvatarProjectFile_h + +#include <QObject> + +class ProjectFilePath { + Q_GADGET; +public: + QString absolutePath; + QString relativePath; +}; + +#endif // hifi_AvatarProjectFile_h diff --git a/libraries/controllers/src/controllers/Actions.cpp b/libraries/controllers/src/controllers/Actions.cpp index 6923ef4b98..5a396231b6 100644 --- a/libraries/controllers/src/controllers/Actions.cpp +++ b/libraries/controllers/src/controllers/Actions.cpp @@ -52,11 +52,17 @@ namespace controller { * <tr><td><code>TranslateZ</code></td><td>number</td><td>number</td><td>Move the user's avatar in the direction of its * z-axis, if the camera isn't in independent or mirror modes.</td></tr> * <tr><td><code>Pitch</code></td><td>number</td><td>number</td><td>Rotate the user's avatar head and attached camera - * about its negative x-axis (i.e., positive values pitch down), if the camera isn't in HMD, independent, or mirror - * modes.</td></tr> - * <tr><td><code>Yaw</code></td><td>number</td><td>number</td><td>Rotate the user's avatar about its y-axis, if the - * camera isn't in independent or mirror modes.</td></tr> + * about its negative x-axis (i.e., positive values pitch down) at a rate proportional to the control value, if the + * camera isn't in HMD, independent, or mirror modes.</td></tr> + * <tr><td><code>Yaw</code></td><td>number</td><td>number</td><td>Rotate the user's avatar about its y-axis at a rate + * proportional to the control value, if the camera isn't in independent or mirror modes.</td></tr> * <tr><td><code>Roll</code></td><td>number</td><td>number</td><td>No action.</td></tr> + * <tr><td><code>DeltaPitch</code></td><td>number</td><td>number</td><td>Rotate the user's avatar head and attached + * camera about its negative x-axis (i.e., positive values pitch down) by an amount proportional to the control value, + * if the camera isn't in HMD, independent, or mirror modes.</td></tr> + * <tr><td><code>DeltaYaw</code></td><td>number</td><td>number</td><td>Rotate the user's avatar about its y-axis by an + * amount proportional to the control value, if the camera isn't in independent or mirror modes.</td></tr> + * <tr><td><code>DeltaRoll</code></td><td>number</td><td>number</td><td>No action.</td></tr> * <tr><td><code>StepTranslateX</code></td><td>number</td><td>number</td><td>No action.</td></tr> * <tr><td><code>StepTranslateY</code></td><td>number</td><td>number</td><td>No action.</td></tr> * <tr><td><code>StepTranslateZ</code></td><td>number</td><td>number</td><td>No action.</td></tr> @@ -318,6 +324,9 @@ namespace controller { makeAxisPair(Action::ROLL, "Roll"), makeAxisPair(Action::PITCH, "Pitch"), makeAxisPair(Action::YAW, "Yaw"), + makeAxisPair(Action::DELTA_YAW, "DeltaYaw"), + makeAxisPair(Action::DELTA_PITCH, "DeltaPitch"), + makeAxisPair(Action::DELTA_ROLL, "DeltaRoll"), makeAxisPair(Action::STEP_YAW, "StepYaw"), makeAxisPair(Action::STEP_PITCH, "StepPitch"), makeAxisPair(Action::STEP_ROLL, "StepRoll"), diff --git a/libraries/controllers/src/controllers/Actions.h b/libraries/controllers/src/controllers/Actions.h index 0c77d63863..a12a3d60a9 100644 --- a/libraries/controllers/src/controllers/Actions.h +++ b/libraries/controllers/src/controllers/Actions.h @@ -27,6 +27,10 @@ enum class Action { ROTATE_Y, YAW = ROTATE_Y, ROTATE_Z, ROLL = ROTATE_Z, + DELTA_PITCH, + DELTA_YAW, + DELTA_ROLL, + STEP_YAW, // FIXME does this have a use case? STEP_PITCH, diff --git a/libraries/controllers/src/controllers/AxisValue.cpp b/libraries/controllers/src/controllers/AxisValue.cpp new file mode 100644 index 0000000000..4b7913754c --- /dev/null +++ b/libraries/controllers/src/controllers/AxisValue.cpp @@ -0,0 +1,21 @@ +// +// AxisValue.cpp +// +// Created by David Rowe on 14 Dec 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 "AxisValue.h" + +namespace controller { + + AxisValue::AxisValue(const float value, const quint64 timestamp) : + value(value), timestamp(timestamp) { } + + bool AxisValue::operator==(const AxisValue& right) const { + return value == right.value && timestamp == right.timestamp; + } +} diff --git a/libraries/controllers/src/controllers/AxisValue.h b/libraries/controllers/src/controllers/AxisValue.h new file mode 100644 index 0000000000..e4bc20f7d2 --- /dev/null +++ b/libraries/controllers/src/controllers/AxisValue.h @@ -0,0 +1,34 @@ +// +// AxisValue.h +// +// Created by David Rowe on 13 Dec 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_controllers_AxisValue_h +#define hifi_controllers_AxisValue_h + +#include <QtCore/qglobal.h> + +namespace controller { + + struct AxisValue { + public: + float value { 0.0f }; + // The value can be timestamped to determine if consecutive identical values should be output (e.g., mouse movement). + quint64 timestamp { 0 }; + + AxisValue() {} + AxisValue(const float value, const quint64 timestamp); + + bool operator ==(const AxisValue& right) const; + bool operator !=(const AxisValue& right) const { return !(*this == right); } + }; + +} + +#endif // hifi_controllers_AxisValue_h diff --git a/libraries/controllers/src/controllers/InputDevice.cpp b/libraries/controllers/src/controllers/InputDevice.cpp index a907842a17..dd430263fa 100644 --- a/libraries/controllers/src/controllers/InputDevice.cpp +++ b/libraries/controllers/src/controllers/InputDevice.cpp @@ -26,12 +26,12 @@ namespace controller { return 0.0f; } - float InputDevice::getAxis(int channel) const { + AxisValue InputDevice::getAxis(int channel) const { auto axis = _axisStateMap.find(channel); if (axis != _axisStateMap.end()) { return (*axis).second; } else { - return 0.0f; + return AxisValue(); } } @@ -68,26 +68,25 @@ namespace controller { return Input::NamedPair(makeInput(pose), name); } - float InputDevice::getValue(ChannelType channelType, uint16_t channel) const { + AxisValue InputDevice::getValue(ChannelType channelType, uint16_t channel) const { switch (channelType) { case ChannelType::AXIS: return getAxis(channel); case ChannelType::BUTTON: - return getButton(channel); + return { getButton(channel), 0 }; case ChannelType::POSE: - return getPose(channel).valid ? 1.0f : 0.0f; + return { getPose(channel).valid ? 1.0f : 0.0f, 0 }; default: break; } - return 0.0f; + return { 0.0f, 0 }; } - - float InputDevice::getValue(const Input& input) const { + AxisValue InputDevice::getValue(const Input& input) const { return getValue(input.getType(), input.channel); } diff --git a/libraries/controllers/src/controllers/InputDevice.h b/libraries/controllers/src/controllers/InputDevice.h index 7479ef7b75..7c3c31cb38 100644 --- a/libraries/controllers/src/controllers/InputDevice.h +++ b/libraries/controllers/src/controllers/InputDevice.h @@ -16,6 +16,7 @@ #include <QtCore/QString> +#include "AxisValue.h" #include "Pose.h" #include "Input.h" #include "StandardControls.h" @@ -103,16 +104,16 @@ public: using Pointer = std::shared_ptr<InputDevice>; typedef std::unordered_set<int> ButtonPressedMap; - typedef std::map<int, float> AxisStateMap; + typedef std::map<int, AxisValue> AxisStateMap; typedef std::map<int, Pose> PoseStateMap; // Get current state for each channel float getButton(int channel) const; - float getAxis(int channel) const; + AxisValue getAxis(int channel) const; Pose getPose(int channel) const; - float getValue(const Input& input) const; - float getValue(ChannelType channelType, uint16_t channel) const; + AxisValue getValue(const Input& input) const; + AxisValue getValue(ChannelType channelType, uint16_t channel) const; Pose getPoseValue(uint16_t channel) const; const QString& getName() const { return _name; } diff --git a/libraries/controllers/src/controllers/InputRecorder.cpp b/libraries/controllers/src/controllers/InputRecorder.cpp index 54d1aaf131..928dbf0521 100644 --- a/libraries/controllers/src/controllers/InputRecorder.cpp +++ b/libraries/controllers/src/controllers/InputRecorder.cpp @@ -297,6 +297,13 @@ namespace controller { return 0.0f; } + InputRecorder::ActionStates InputRecorder::getActionstates() { + if (_actionStateList.size() > 0) { + return _actionStateList[_playCount]; + } + return {}; + } + controller::Pose InputRecorder::getPoseState(const QString& action) { if (_poseStateList.size() > 0) { return _poseStateList[_playCount][action]; diff --git a/libraries/controllers/src/controllers/InputRecorder.h b/libraries/controllers/src/controllers/InputRecorder.h index 9adb8e386f..a0fcb2e87e 100644 --- a/libraries/controllers/src/controllers/InputRecorder.h +++ b/libraries/controllers/src/controllers/InputRecorder.h @@ -45,6 +45,7 @@ namespace controller { void setActionState(const QString& action, float value); void setActionState(const QString& action, const controller::Pose& pose); float getActionState(const QString& action); + ActionStates getActionstates(); controller::Pose getPoseState(const QString& action); QString getSaveDirectory(); void frameTick(); diff --git a/libraries/controllers/src/controllers/ScriptingInterface.cpp b/libraries/controllers/src/controllers/ScriptingInterface.cpp index ce730bffa8..07c59e1aaa 100644 --- a/libraries/controllers/src/controllers/ScriptingInterface.cpp +++ b/libraries/controllers/src/controllers/ScriptingInterface.cpp @@ -89,17 +89,17 @@ namespace controller { float ScriptingInterface::getValue(const int& source) const { auto userInputMapper = DependencyManager::get<UserInputMapper>(); - return userInputMapper->getValue(Input((uint32_t)source)); + return userInputMapper->getValue(Input((uint32_t)source)).value; } float ScriptingInterface::getAxisValue(int source) const { auto userInputMapper = DependencyManager::get<UserInputMapper>(); - return userInputMapper->getValue(Input((uint32_t)source)); + return userInputMapper->getValue(Input((uint32_t)source)).value; } Pose ScriptingInterface::getPoseValue(const int& source) const { auto userInputMapper = DependencyManager::get<UserInputMapper>(); - return userInputMapper->getPose(Input((uint32_t)source)); + return userInputMapper->getPose(Input((uint32_t)source)); } QVector<Action> ScriptingInterface::getAllActions() { diff --git a/libraries/controllers/src/controllers/UserInputMapper.cpp b/libraries/controllers/src/controllers/UserInputMapper.cpp index 307064c073..33dc37312e 100755 --- a/libraries/controllers/src/controllers/UserInputMapper.cpp +++ b/libraries/controllers/src/controllers/UserInputMapper.cpp @@ -290,17 +290,17 @@ void UserInputMapper::update(float deltaTime) { if ((int)_lastStandardStates.size() != standardInputs.size()) { _lastStandardStates.resize(standardInputs.size()); for (auto& lastValue : _lastStandardStates) { - lastValue = 0; + lastValue = AxisValue(); } } for (int i = 0; i < standardInputs.size(); ++i) { const auto& input = standardInputs[i].first; - float value = getValue(input); - float& oldValue = _lastStandardStates[i]; + AxisValue value = getValue(input); + AxisValue& oldValue = _lastStandardStates[i]; if (value != oldValue) { oldValue = value; - emit inputEvent(input.id, value); + emit inputEvent(input.id, value.value); } } inputRecorder->frameTick(); @@ -489,6 +489,21 @@ void UserInputMapper::runMappings() { } applyRoutes(_standardRoutes); + InputRecorder* inputRecorder = InputRecorder::getInstance(); + if (inputRecorder->isPlayingback()) { + if (debugRoutes) { + qCDebug(controllers) << "Playing back recording actions"; + } + + // Play back each numeric action even if there is no current route active for the action. + auto actionStates = inputRecorder->getActionstates(); + for (InputRecorder::ActionStates::iterator it = actionStates.begin(); it != actionStates.end(); ++it) { + setActionState((Action)findAction(it->first), it->second); + } + + // Poses are played back in StandardEndpoint. + } + if (debugRoutes) { qCDebug(controllers) << "Done with mappings"; } @@ -604,10 +619,10 @@ bool UserInputMapper::applyRoute(const Route::Pointer& route, bool force) { destination->apply(value, source); } else { // Fetch the value, may have been overriden by previous loopback routes - float value = getValue(source, route->peek); + auto value = getValue(source, route->peek); if (debugRoutes && route->debug) { - qCDebug(controllers) << "Value was " << value; + qCDebug(controllers) << "Value was " << value.value << value.timestamp; } // Apply each of the filters. for (const auto& filter : route->filters) { @@ -615,7 +630,7 @@ bool UserInputMapper::applyRoute(const Route::Pointer& route, bool force) { } if (debugRoutes && route->debug) { - qCDebug(controllers) << "Filtered value was " << value; + qCDebug(controllers) << "Filtered value was " << value.value << value.timestamp; } destination->apply(value, source); @@ -741,15 +756,15 @@ void UserInputMapper::enableMapping(const QString& mappingName, bool enable) { } } -float UserInputMapper::getValue(const Endpoint::Pointer& endpoint, bool peek) { +AxisValue UserInputMapper::getValue(const Endpoint::Pointer& endpoint, bool peek) { return peek ? endpoint->peek() : endpoint->value(); } -float UserInputMapper::getValue(const Input& input) const { +AxisValue UserInputMapper::getValue(const Input& input) const { Locker locker(_lock); auto endpoint = endpointFor(input); if (!endpoint) { - return 0; + return AxisValue(); } return endpoint->value(); } diff --git a/libraries/controllers/src/controllers/UserInputMapper.h b/libraries/controllers/src/controllers/UserInputMapper.h index 5fd21e6299..2b3c947491 100644 --- a/libraries/controllers/src/controllers/UserInputMapper.h +++ b/libraries/controllers/src/controllers/UserInputMapper.h @@ -121,7 +121,7 @@ namespace controller { void unloadMappings(const QStringList& jsonFiles); void unloadMapping(const QString& jsonFile); - float getValue(const Input& input) const; + AxisValue getValue(const Input& input) const; Pose getPose(const Input& input) const; // perform an action when the UserInputMapper mutex is acquired. @@ -147,9 +147,9 @@ namespace controller { std::vector<float> _actionScales = std::vector<float>(toInt(Action::NUM_ACTIONS), 1.0f); std::vector<float> _lastActionStates = std::vector<float>(toInt(Action::NUM_ACTIONS), 0.0f); std::vector<Pose> _poseStates = std::vector<Pose>(toInt(Action::NUM_ACTIONS)); - std::vector<float> _lastStandardStates = std::vector<float>(); + std::vector<AxisValue> _lastStandardStates = std::vector<AxisValue>(); - static float getValue(const EndpointPointer& endpoint, bool peek = false); + static AxisValue getValue(const EndpointPointer& endpoint, bool peek = false); static Pose getPose(const EndpointPointer& endpoint, bool peek = false); friend class RouteBuilderProxy; diff --git a/libraries/controllers/src/controllers/impl/Endpoint.h b/libraries/controllers/src/controllers/impl/Endpoint.h index a938dd30b6..bcf71f3094 100644 --- a/libraries/controllers/src/controllers/impl/Endpoint.h +++ b/libraries/controllers/src/controllers/impl/Endpoint.h @@ -16,6 +16,7 @@ #include <QtCore/QObject> +#include "../AxisValue.h" #include "../Input.h" #include "../Pose.h" @@ -36,9 +37,9 @@ namespace controller { using WriteLambda = std::function<void(float)>; Endpoint(const Input& input) : _input(input) {} - virtual float value() { return peek(); } - virtual float peek() const = 0; - virtual void apply(float value, const Pointer& source) = 0; + virtual AxisValue value() { return peek(); } + virtual AxisValue peek() const = 0; + virtual void apply(AxisValue value, const Pointer& source) = 0; virtual Pose peekPose() const { return Pose(); }; virtual Pose pose() { return peekPose(); } virtual void apply(const Pose& value, const Pointer& source) {} @@ -59,8 +60,8 @@ namespace controller { LambdaEndpoint(ReadLambda readLambda, WriteLambda writeLambda = [](float) {}) : Endpoint(Input::INVALID_INPUT), _readLambda(readLambda), _writeLambda(writeLambda) { } - virtual float peek() const override { return _readLambda(); } - virtual void apply(float value, const Pointer& source) override { _writeLambda(value); } + virtual AxisValue peek() const override { return AxisValue(_readLambda(), 0); } + virtual void apply(AxisValue value, const Pointer& source) override { _writeLambda(value.value); } private: ReadLambda _readLambda; @@ -76,8 +77,8 @@ namespace controller { : Endpoint(Input::INVALID_INPUT), _readLambda(readLambda), _writeLambda(writeLambda) { } - virtual float peek() const override { return _readLambda(); } - virtual void apply(float value, const Pointer& source) override { _writeLambda(value); } + virtual AxisValue peek() const override { return AxisValue(_readLambda(), 0); } + virtual void apply(AxisValue value, const Pointer& source) override { _writeLambda(value.value); } private: const ReadLambda& _readLambda; @@ -91,15 +92,15 @@ namespace controller { : Endpoint(id) { } - virtual float peek() const override { return _currentValue; } - virtual void apply(float value, const Pointer& source) override { _currentValue = value; } + virtual AxisValue peek() const override { return _currentValue; } + virtual void apply(AxisValue value, const Pointer& source) override { _currentValue = value; } virtual Pose peekPose() const override { return _currentPose; } virtual void apply(const Pose& value, const Pointer& source) override { _currentPose = value; } protected: - float _currentValue { 0.0f }; + AxisValue _currentValue { 0.0f, 0 }; Pose _currentPose {}; }; diff --git a/libraries/controllers/src/controllers/impl/Filter.h b/libraries/controllers/src/controllers/impl/Filter.h index 7afabb4bcb..5c88a5be58 100644 --- a/libraries/controllers/src/controllers/impl/Filter.h +++ b/libraries/controllers/src/controllers/impl/Filter.h @@ -21,6 +21,7 @@ #include <QtCore/QEasingCurve> +#include "../AxisValue.h" #include "../Pose.h" class QJsonValue; @@ -37,7 +38,7 @@ namespace controller { virtual ~Filter() = default; - virtual float apply(float value) const = 0; + virtual AxisValue apply(AxisValue value) const = 0; virtual Pose apply(Pose value) const = 0; // Factory features diff --git a/libraries/controllers/src/controllers/impl/conditionals/EndpointConditional.h b/libraries/controllers/src/controllers/impl/conditionals/EndpointConditional.h index 0ba1347087..50a33471e7 100644 --- a/libraries/controllers/src/controllers/impl/conditionals/EndpointConditional.h +++ b/libraries/controllers/src/controllers/impl/conditionals/EndpointConditional.h @@ -18,7 +18,7 @@ namespace controller { class EndpointConditional : public Conditional { public: EndpointConditional(Endpoint::Pointer endpoint) : _endpoint(endpoint) {} - virtual bool satisfied() override { return _endpoint && _endpoint->peek() != 0.0f; } + virtual bool satisfied() override { return _endpoint && _endpoint->peek().value != 0.0f; } private: Endpoint::Pointer _endpoint; }; diff --git a/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.cpp index f73268b41f..58744c468c 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.cpp +++ b/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.cpp @@ -15,22 +15,19 @@ using namespace controller; -void ActionEndpoint::apply(float newValue, const Pointer& source) { +void ActionEndpoint::apply(AxisValue newValue, const Pointer& source) { auto userInputMapper = DependencyManager::get<UserInputMapper>(); InputRecorder* inputRecorder = InputRecorder::getInstance(); - QString actionName; if (inputRecorder->isPlayingback() || inputRecorder->isRecording()) { - actionName = userInputMapper->getActionName(Action(_input.getChannel())); - if (inputRecorder->isPlayingback()) { - newValue = inputRecorder->getActionState(actionName); - } + QString actionName = userInputMapper->getActionName(Action(_input.getChannel())); + inputRecorder->setActionState(actionName, newValue.value); } - _currentValue += newValue; + _currentValue.value += newValue.value; + if (_input != Input::INVALID_INPUT) { - userInputMapper->deltaActionState(Action(_input.getChannel()), newValue); + userInputMapper->deltaActionState(Action(_input.getChannel()), newValue.value); } - inputRecorder->setActionState(actionName, newValue); } void ActionEndpoint::apply(const Pose& value, const Pointer& source) { @@ -51,7 +48,7 @@ void ActionEndpoint::apply(const Pose& value, const Pointer& source) { } void ActionEndpoint::reset() { - _currentValue = 0.0f; + _currentValue = AxisValue(); _currentPose = Pose(); } diff --git a/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.h b/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.h index 1073dc6593..94da4663aa 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.h +++ b/libraries/controllers/src/controllers/impl/endpoints/ActionEndpoint.h @@ -23,8 +23,8 @@ class ActionEndpoint : public Endpoint { public: ActionEndpoint(const Input& id = Input::INVALID_INPUT) : Endpoint(id) { } - virtual float peek() const override { return _currentValue; } - virtual void apply(float newValue, const Pointer& source) override; + virtual AxisValue peek() const override { return _currentValue; } + virtual void apply(AxisValue newValue, const Pointer& source) override; virtual Pose peekPose() const override { return _currentPose; } virtual void apply(const Pose& value, const Pointer& source) override; @@ -32,7 +32,7 @@ public: virtual void reset() override; private: - float _currentValue{ 0.0f }; + AxisValue _currentValue { 0.0f, 0 }; Pose _currentPose{}; }; diff --git a/libraries/controllers/src/controllers/impl/endpoints/AnyEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/AnyEndpoint.cpp index 598dfb6ee8..7f0f0fec48 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/AnyEndpoint.cpp +++ b/libraries/controllers/src/controllers/impl/endpoints/AnyEndpoint.cpp @@ -27,13 +27,13 @@ AnyEndpoint::AnyEndpoint(Endpoint::List children) : Endpoint(Input::INVALID_INPU } } -// The value of an any-point is considered to be the maxiumum absolute value, +// The value of an any-point is considered to be the maximum absolute value, // this handles any's of multiple axis values as well as single values as well -float AnyEndpoint::peek() const { - float result = 0.0f; +AxisValue AnyEndpoint::peek() const { + auto result = AxisValue(); for (auto& child : _children) { auto childValue = child->peek(); - if (std::abs(childValue) > std::abs(result)) { + if (std::abs(childValue.value) > std::abs(result.value)) { result = childValue; } } @@ -41,18 +41,18 @@ float AnyEndpoint::peek() const { } // Fetching the value must trigger any necessary side effects of value() on ALL the children. -float AnyEndpoint::value() { - float result = 0.0f; +AxisValue AnyEndpoint::value() { + auto result = AxisValue(); for (auto& child : _children) { auto childValue = child->value(); - if (std::abs(childValue) > std::abs(result)) { + if (std::abs(childValue.value) > std::abs(result.value)) { result = childValue; } } return result; } -void AnyEndpoint::apply(float newValue, const Endpoint::Pointer& source) { +void AnyEndpoint::apply(AxisValue newValue, const Endpoint::Pointer& source) { qFatal("AnyEndpoint is read only"); } diff --git a/libraries/controllers/src/controllers/impl/endpoints/AnyEndpoint.h b/libraries/controllers/src/controllers/impl/endpoints/AnyEndpoint.h index 8947eb675f..a9634f10d8 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/AnyEndpoint.h +++ b/libraries/controllers/src/controllers/impl/endpoints/AnyEndpoint.h @@ -19,9 +19,9 @@ class AnyEndpoint : public Endpoint { public: using Endpoint::apply; AnyEndpoint(Endpoint::List children); - virtual float peek() const override; - virtual float value() override; - virtual void apply(float newValue, const Endpoint::Pointer& source) override; + virtual AxisValue peek() const override; + virtual AxisValue value() override; + virtual void apply(AxisValue newValue, const Endpoint::Pointer& source) override; virtual bool writeable() const override; virtual bool readable() const override; diff --git a/libraries/controllers/src/controllers/impl/endpoints/ArrayEndpoint.h b/libraries/controllers/src/controllers/impl/endpoints/ArrayEndpoint.h index 34d30a2e97..f928a4f568 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/ArrayEndpoint.h +++ b/libraries/controllers/src/controllers/impl/endpoints/ArrayEndpoint.h @@ -21,9 +21,9 @@ public: using Pointer = std::shared_ptr<ArrayEndpoint>; ArrayEndpoint() : Endpoint(Input::INVALID_INPUT) { } - virtual float peek() const override { return 0.0f; } + virtual AxisValue peek() const override { return AxisValue(); } - virtual void apply(float value, const Endpoint::Pointer& source) override { + virtual void apply(AxisValue value, const Endpoint::Pointer& source) override { for (auto& child : _children) { if (child->writeable()) { child->apply(value, source); diff --git a/libraries/controllers/src/controllers/impl/endpoints/CompositeEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/CompositeEndpoint.cpp index 1bd27489f8..f54c786a33 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/CompositeEndpoint.cpp +++ b/libraries/controllers/src/controllers/impl/endpoints/CompositeEndpoint.cpp @@ -24,18 +24,22 @@ bool CompositeEndpoint::readable() const { return first->readable() && second->readable(); } -float CompositeEndpoint::peek() const { - float result = first->peek() * -1.0f + second->peek(); +AxisValue CompositeEndpoint::peek() const { + auto negative = first->peek(); + auto positive = second->peek(); + auto result = AxisValue(positive.value - negative.value, std::max(positive.timestamp, negative.timestamp)); return result; } // Fetching via value() must trigger any side effects of value() on the children -float CompositeEndpoint::value() { - float result = first->value() * -1.0f + second->value(); +AxisValue CompositeEndpoint::value() { + auto negative = first->value(); + auto positive = second->value(); + auto result = AxisValue(positive.value - negative.value, std::max(positive.timestamp, negative.timestamp)); return result; } -void CompositeEndpoint::apply(float newValue, const Pointer& source) { +void CompositeEndpoint::apply(AxisValue newValue, const Pointer& source) { // Composites are read only } diff --git a/libraries/controllers/src/controllers/impl/endpoints/CompositeEndpoint.h b/libraries/controllers/src/controllers/impl/endpoints/CompositeEndpoint.h index 3249aa1d37..004df36273 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/CompositeEndpoint.h +++ b/libraries/controllers/src/controllers/impl/endpoints/CompositeEndpoint.h @@ -18,9 +18,9 @@ namespace controller { using Endpoint::apply; CompositeEndpoint(Endpoint::Pointer first, Endpoint::Pointer second); - virtual float peek() const override; - virtual float value() override; - virtual void apply(float newValue, const Pointer& source) override; + virtual AxisValue peek() const override; + virtual AxisValue value() override; + virtual void apply(AxisValue newValue, const Pointer& source) override; virtual bool readable() const override; }; diff --git a/libraries/controllers/src/controllers/impl/endpoints/InputEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/InputEndpoint.cpp index ce58c948d1..3755d860b6 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/InputEndpoint.cpp +++ b/libraries/controllers/src/controllers/impl/endpoints/InputEndpoint.cpp @@ -14,19 +14,19 @@ using namespace controller; -float InputEndpoint::peek() const { +AxisValue InputEndpoint::peek() const { if (isPose()) { - return peekPose().valid ? 1.0f : 0.0f; + return peekPose().valid ? AxisValue(1.0f, 0) : AxisValue(0.0f, 0); } auto userInputMapper = DependencyManager::get<UserInputMapper>(); auto deviceProxy = userInputMapper->getDevice(_input); if (!deviceProxy) { - return 0.0f; + return AxisValue(); } return deviceProxy->getValue(_input); } -float InputEndpoint::value(){ +AxisValue InputEndpoint::value() { _read = true; return peek(); } diff --git a/libraries/controllers/src/controllers/impl/endpoints/InputEndpoint.h b/libraries/controllers/src/controllers/impl/endpoints/InputEndpoint.h index 7e4560dcf9..9581228cef 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/InputEndpoint.h +++ b/libraries/controllers/src/controllers/impl/endpoints/InputEndpoint.h @@ -20,10 +20,11 @@ public: : Endpoint(id) { } - virtual float peek() const override; - virtual float value() override; + virtual AxisValue peek() const override; + virtual AxisValue value() override; // FIXME need support for writing back to vibration / force feedback effects - virtual void apply(float newValue, const Pointer& source) override {} + virtual void apply(AxisValue newValue, const Pointer& source) override {} + virtual Pose peekPose() const override; virtual Pose pose() override; virtual void apply(const Pose& value, const Pointer& source) override { } diff --git a/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.cpp index 2f20cd82c6..c01b67f1bc 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.cpp +++ b/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.cpp @@ -30,18 +30,18 @@ QString formatException(const QJSValue& exception) { return result; } -float JSEndpoint::peek() const { +AxisValue JSEndpoint::peek() const { QJSValue result = _callable.call(); if (result.isError()) { qCDebug(controllers).noquote() << formatException(result); - return 0.0f; + return AxisValue(); } else { - return (float)result.toNumber(); + return AxisValue((float)result.toNumber(), 0); } } -void JSEndpoint::apply(float newValue, const Pointer& source) { - QJSValue result = _callable.call(QJSValueList({ QJSValue(newValue) })); +void JSEndpoint::apply(AxisValue newValue, const Pointer& source) { + QJSValue result = _callable.call(QJSValueList({ QJSValue(newValue.value) })); if (result.isError()) { qCDebug(controllers).noquote() << formatException(result); } diff --git a/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.h b/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.h index 4d179da8e6..6c953399dd 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.h +++ b/libraries/controllers/src/controllers/impl/endpoints/JSEndpoint.h @@ -24,8 +24,8 @@ public: : Endpoint(Input::INVALID_INPUT), _callable(callable) { } - virtual float peek() const override; - virtual void apply(float newValue, const Pointer& source) override; + virtual AxisValue peek() const override; + virtual void apply(AxisValue newValue, const Pointer& source) override; private: mutable QJSValue _callable; diff --git a/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.cpp b/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.cpp index e2c48d776f..9f971d2f04 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.cpp +++ b/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.cpp @@ -34,9 +34,9 @@ QString formatException(const QScriptValue& exception) { return result; } -float ScriptEndpoint::peek() const { +AxisValue ScriptEndpoint::peek() const { const_cast<ScriptEndpoint*>(this)->updateValue(); - return _lastValueRead; + return AxisValue(_lastValueRead, 0); } void ScriptEndpoint::updateValue() { @@ -58,15 +58,15 @@ void ScriptEndpoint::updateValue() { } } -void ScriptEndpoint::apply(float value, const Pointer& source) { +void ScriptEndpoint::apply(AxisValue value, const Pointer& source) { if (value == _lastValueWritten) { return; } - internalApply(value, source->getInput().getID()); + _lastValueWritten = value; + internalApply(value.value, source->getInput().getID()); } void ScriptEndpoint::internalApply(float value, int sourceID) { - _lastValueWritten = value; if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "internalApply", Qt::QueuedConnection, Q_ARG(float, value), diff --git a/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.h b/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.h index 00439f5560..e739ab0b01 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.h +++ b/libraries/controllers/src/controllers/impl/endpoints/ScriptEndpoint.h @@ -24,9 +24,8 @@ public: : Endpoint(Input::INVALID_INPUT), _callable(callable) { } - virtual float peek() const override; - virtual void apply(float newValue, const Pointer& source) override; - + virtual AxisValue peek() const override; + virtual void apply(AxisValue newValue, const Pointer& source) override; virtual Pose peekPose() const override; virtual void apply(const Pose& newValue, const Pointer& source) override; @@ -42,7 +41,7 @@ protected: private: QScriptValue _callable; float _lastValueRead { 0.0f }; - float _lastValueWritten { 0.0f }; + AxisValue _lastValueWritten { 0.0f, 0 }; bool _returnPose { false }; Pose _lastPoseRead; diff --git a/libraries/controllers/src/controllers/impl/endpoints/StandardEndpoint.h b/libraries/controllers/src/controllers/impl/endpoints/StandardEndpoint.h index 2006809fed..26a8063cdc 100644 --- a/libraries/controllers/src/controllers/impl/endpoints/StandardEndpoint.h +++ b/libraries/controllers/src/controllers/impl/endpoints/StandardEndpoint.h @@ -25,19 +25,19 @@ public: virtual bool writeable() const override { return !_written; } virtual bool readable() const override { return !_read; } virtual void reset() override { - apply(0.0f, Endpoint::Pointer()); + apply(AxisValue(), Endpoint::Pointer()); apply(Pose(), Endpoint::Pointer()); _written = _read = false; } - virtual float value() override { + virtual AxisValue value() override { _read = true; return VirtualEndpoint::value(); } - virtual void apply(float value, const Pointer& source) override { + virtual void apply(AxisValue value, const Pointer& source) override { // For standard endpoints, the first NON-ZERO write counts. - if (value != 0.0f) { + if (value != AxisValue()) { _written = true; } VirtualEndpoint::apply(value, source); diff --git a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h index 269fd54102..15653a7df6 100644 --- a/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/AccelerationLimiterFilter.h @@ -19,7 +19,7 @@ namespace controller { public: AccelerationLimiterFilter() {} - float apply(float value) const override { return value; } + AxisValue apply(AxisValue value) const override { return value; } Pose apply(Pose value) const override; bool parseParameters(const QJsonValue& parameters) override; diff --git a/libraries/controllers/src/controllers/impl/filters/ClampFilter.h b/libraries/controllers/src/controllers/impl/filters/ClampFilter.h index b06a43515f..04684655c9 100644 --- a/libraries/controllers/src/controllers/impl/filters/ClampFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/ClampFilter.h @@ -18,8 +18,8 @@ class ClampFilter : public Filter { REGISTER_FILTER_CLASS(ClampFilter); public: ClampFilter(float min = 0.0, float max = 1.0) : _min(min), _max(max) {}; - virtual float apply(float value) const override { - return glm::clamp(value, _min, _max); + virtual AxisValue apply(AxisValue value) const override { + return { glm::clamp(value.value, _min, _max), value.timestamp }; } virtual Pose apply(Pose value) const override { return value; } diff --git a/libraries/controllers/src/controllers/impl/filters/ConstrainToIntegerFilter.h b/libraries/controllers/src/controllers/impl/filters/ConstrainToIntegerFilter.h index 129e08741d..2cce5f828d 100644 --- a/libraries/controllers/src/controllers/impl/filters/ConstrainToIntegerFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/ConstrainToIntegerFilter.h @@ -19,8 +19,8 @@ class ConstrainToIntegerFilter : public Filter { public: ConstrainToIntegerFilter() = default; - virtual float apply(float value) const override { - return glm::sign(value); + virtual AxisValue apply(AxisValue value) const override { + return { glm::sign(value.value), value.timestamp }; } virtual Pose apply(Pose value) const override { return value; } diff --git a/libraries/controllers/src/controllers/impl/filters/ConstrainToPositiveIntegerFilter.h b/libraries/controllers/src/controllers/impl/filters/ConstrainToPositiveIntegerFilter.h index 8f2140721a..07dd6654f1 100644 --- a/libraries/controllers/src/controllers/impl/filters/ConstrainToPositiveIntegerFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/ConstrainToPositiveIntegerFilter.h @@ -19,8 +19,8 @@ class ConstrainToPositiveIntegerFilter : public Filter { public: ConstrainToPositiveIntegerFilter() = default; - virtual float apply(float value) const override { - return (value <= 0.0f) ? 0.0f : 1.0f; + virtual AxisValue apply(AxisValue value) const override { + return { (value.value <= 0.0f) ? 0.0f : 1.0f, value.timestamp }; } virtual Pose apply(Pose value) const override { return value; } diff --git a/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.cpp b/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.cpp index f07ef25976..84d3b9de60 100644 --- a/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.cpp +++ b/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.cpp @@ -12,13 +12,13 @@ #include <QtCore/QJsonArray> using namespace controller; -float DeadZoneFilter::apply(float value) const { - float scale = ((value < 0.0f) ? -1.0f : 1.0f) / (1.0f - _min); - float magnitude = std::abs(value); +AxisValue DeadZoneFilter::apply(AxisValue value) const { + float scale = ((value.value < 0.0f) ? -1.0f : 1.0f) / (1.0f - _min); + float magnitude = std::abs(value.value); if (magnitude < _min) { - return 0.0f; + return { 0.0f, value.timestamp }; } - return (magnitude - _min) * scale; + return { (magnitude - _min) * scale, value.timestamp }; } bool DeadZoneFilter::parseParameters(const QJsonValue& parameters) { diff --git a/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.h b/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.h index d898647126..7ac1e8a51d 100644 --- a/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/DeadZoneFilter.h @@ -19,7 +19,7 @@ class DeadZoneFilter : public Filter { public: DeadZoneFilter(float min = 0.0) : _min(min) {}; - virtual float apply(float value) const override; + virtual AxisValue apply(AxisValue value) const override; virtual Pose apply(Pose value) const override { return value; } diff --git a/libraries/controllers/src/controllers/impl/filters/ExponentialSmoothingFilter.h b/libraries/controllers/src/controllers/impl/filters/ExponentialSmoothingFilter.h index 134f57243e..e0135a8f68 100644 --- a/libraries/controllers/src/controllers/impl/filters/ExponentialSmoothingFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/ExponentialSmoothingFilter.h @@ -21,7 +21,7 @@ namespace controller { ExponentialSmoothingFilter(float rotationConstant, float translationConstant) : _translationConstant(translationConstant), _rotationConstant(rotationConstant) {} - float apply(float value) const override { return value; } + AxisValue apply(AxisValue value) const override { return value; } Pose apply(Pose value) const override; bool parseParameters(const QJsonValue& parameters) override; diff --git a/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.cpp b/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.cpp index a7f22e1de4..91e59a39b9 100644 --- a/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.cpp +++ b/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.cpp @@ -20,17 +20,17 @@ HysteresisFilter::HysteresisFilter(float min, float max) : _min(min), _max(max) }; -float HysteresisFilter::apply(float value) const { +AxisValue HysteresisFilter::apply(AxisValue value) const { if (_signaled) { - if (value <= _min) { + if (value.value <= _min) { _signaled = false; } } else { - if (value >= _max) { + if (value.value >= _max) { _signaled = true; } } - return _signaled ? 1.0f : 0.0f; + return { _signaled ? 1.0f : 0.0f, value.timestamp }; } bool HysteresisFilter::parseParameters(const QJsonValue& parameters) { diff --git a/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.h b/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.h index 4eb563754f..c7c817ecfa 100644 --- a/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/HysteresisFilter.h @@ -18,7 +18,7 @@ class HysteresisFilter : public Filter { REGISTER_FILTER_CLASS(HysteresisFilter); public: HysteresisFilter(float min = 0.25, float max = 0.75); - virtual float apply(float value) const override; + virtual AxisValue apply(AxisValue value) const override; virtual Pose apply(Pose value) const override { return value; } diff --git a/libraries/controllers/src/controllers/impl/filters/LowVelocityFilter.h b/libraries/controllers/src/controllers/impl/filters/LowVelocityFilter.h index ac5299dc6f..eb4f53797f 100644 --- a/libraries/controllers/src/controllers/impl/filters/LowVelocityFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/LowVelocityFilter.h @@ -21,7 +21,7 @@ namespace controller { LowVelocityFilter(float rotationConstant, float translationConstant) : _translationConstant(translationConstant), _rotationConstant(rotationConstant) {} - float apply(float value) const override { return value; } + AxisValue apply(AxisValue value) const override { return value; } Pose apply(Pose newPose) const override; bool parseParameters(const QJsonValue& parameters) override; diff --git a/libraries/controllers/src/controllers/impl/filters/NotFilter.cpp b/libraries/controllers/src/controllers/impl/filters/NotFilter.cpp index 73460aed91..c0396857e5 100644 --- a/libraries/controllers/src/controllers/impl/filters/NotFilter.cpp +++ b/libraries/controllers/src/controllers/impl/filters/NotFilter.cpp @@ -5,6 +5,6 @@ using namespace controller; NotFilter::NotFilter() { } -float NotFilter::apply(float value) const { - return (value == 0.0f) ? 1.0f : 0.0f; +AxisValue NotFilter::apply(AxisValue value) const { + return { (value.value == 0.0f) ? 1.0f : 0.0f, value.timestamp }; } diff --git a/libraries/controllers/src/controllers/impl/filters/NotFilter.h b/libraries/controllers/src/controllers/impl/filters/NotFilter.h index ceb7d29de3..d68a9feff7 100644 --- a/libraries/controllers/src/controllers/impl/filters/NotFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/NotFilter.h @@ -11,7 +11,7 @@ class NotFilter : public Filter { public: NotFilter(); - virtual float apply(float value) const override; + virtual AxisValue apply(AxisValue value) const override; virtual Pose apply(Pose value) const override { return value; } }; diff --git a/libraries/controllers/src/controllers/impl/filters/PostTransformFilter.h b/libraries/controllers/src/controllers/impl/filters/PostTransformFilter.h index 3c1cb4f80c..aeb24cc3f8 100644 --- a/libraries/controllers/src/controllers/impl/filters/PostTransformFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/PostTransformFilter.h @@ -21,7 +21,7 @@ class PostTransformFilter : public Filter { public: PostTransformFilter() = default; PostTransformFilter(glm::mat4 transform) : _transform(transform) {} - virtual float apply(float value) const override { return value; } + virtual AxisValue apply(AxisValue value) const override { return value; } virtual Pose apply(Pose value) const override { return value.postTransform(_transform); } virtual bool parseParameters(const QJsonValue& parameters) override { return parseMat4Parameter(parameters, _transform); } private: diff --git a/libraries/controllers/src/controllers/impl/filters/PulseFilter.cpp b/libraries/controllers/src/controllers/impl/filters/PulseFilter.cpp index 6ef9d40802..d37eb99ca9 100644 --- a/libraries/controllers/src/controllers/impl/filters/PulseFilter.cpp +++ b/libraries/controllers/src/controllers/impl/filters/PulseFilter.cpp @@ -15,21 +15,21 @@ using namespace controller; const float PulseFilter::DEFAULT_LAST_EMIT_TIME = -::std::numeric_limits<float>::max(); -float PulseFilter::apply(float value) const { +AxisValue PulseFilter::apply(AxisValue value) const { float result = 0.0f; - if (0.0f != value) { + if (0.0f != value.value) { float now = secTimestampNow(); float delta = now - _lastEmitTime; if (delta >= _interval) { _lastEmitTime = now; - result = value; + result = value.value; } } else if (_resetOnZero) { _lastEmitTime = DEFAULT_LAST_EMIT_TIME; } - return result; + return { result, value.timestamp }; } bool PulseFilter::parseParameters(const QJsonValue& parameters) { diff --git a/libraries/controllers/src/controllers/impl/filters/PulseFilter.h b/libraries/controllers/src/controllers/impl/filters/PulseFilter.h index 2e0da0efa9..dc56b3c6c9 100644 --- a/libraries/controllers/src/controllers/impl/filters/PulseFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/PulseFilter.h @@ -21,7 +21,7 @@ public: PulseFilter() = default; PulseFilter(float interval) : _interval(interval) {} - virtual float apply(float value) const override; + virtual AxisValue apply(AxisValue value) const override; virtual Pose apply(Pose value) const override { return value; } diff --git a/libraries/controllers/src/controllers/impl/filters/RotateFilter.h b/libraries/controllers/src/controllers/impl/filters/RotateFilter.h index c6735f6aa9..d1f1f2d269 100644 --- a/libraries/controllers/src/controllers/impl/filters/RotateFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/RotateFilter.h @@ -22,7 +22,7 @@ public: RotateFilter() = default; RotateFilter(glm::quat rotation) : _rotation(rotation) {} - virtual float apply(float value) const override { return value; } + virtual AxisValue apply(AxisValue value) const override { return value; } virtual Pose apply(Pose value) const override { return value.transform(glm::mat4(glm::quat(_rotation))); diff --git a/libraries/controllers/src/controllers/impl/filters/ScaleFilter.h b/libraries/controllers/src/controllers/impl/filters/ScaleFilter.h index 936498a7a1..3eb58e7f47 100644 --- a/libraries/controllers/src/controllers/impl/filters/ScaleFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/ScaleFilter.h @@ -22,8 +22,8 @@ public: ScaleFilter() = default; ScaleFilter(float scale) : _scale(scale) {} - virtual float apply(float value) const override { - return value * _scale; + virtual AxisValue apply(AxisValue value) const override { + return { value.value * _scale, value.timestamp }; } virtual Pose apply(Pose value) const override { diff --git a/libraries/controllers/src/controllers/impl/filters/TransformFilter.h b/libraries/controllers/src/controllers/impl/filters/TransformFilter.h index a34edaa337..c81af2ef17 100644 --- a/libraries/controllers/src/controllers/impl/filters/TransformFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/TransformFilter.h @@ -22,7 +22,7 @@ public: TransformFilter() = default; TransformFilter(glm::mat4 transform) : _transform(transform) {} - virtual float apply(float value) const override { return value; } + virtual AxisValue apply(AxisValue value) const override { return value; } virtual Pose apply(Pose value) const override { return value.transform(_transform); } virtual bool parseParameters(const QJsonValue& parameters) override { return parseMat4Parameter(parameters, _transform); } diff --git a/libraries/controllers/src/controllers/impl/filters/TranslateFilter.h b/libraries/controllers/src/controllers/impl/filters/TranslateFilter.h index ced9cbc689..476481807d 100644 --- a/libraries/controllers/src/controllers/impl/filters/TranslateFilter.h +++ b/libraries/controllers/src/controllers/impl/filters/TranslateFilter.h @@ -22,7 +22,7 @@ public: TranslateFilter() = default; TranslateFilter(glm::vec3 translate) : _translate(translate) {} - virtual float apply(float value) const override { return value; } + virtual AxisValue apply(AxisValue value) const override { return value; } virtual Pose apply(Pose value) const override { return value.transform(glm::translate(_translate)); } virtual bool parseParameters(const QJsonValue& parameters) override { return parseVec3Parameter(parameters, _translate); } diff --git a/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h b/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h index 4234a8731b..c1253f825f 100644 --- a/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h +++ b/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h @@ -15,7 +15,7 @@ // These properties have JSDoc documentation in HMDScriptingInterface.h. class AbstractHMDScriptingInterface : public QObject { Q_OBJECT - Q_PROPERTY(bool active READ isHMDMode) + Q_PROPERTY(bool active READ isHMDMode NOTIFY mountedChanged) Q_PROPERTY(float ipd READ getIPD) Q_PROPERTY(float eyeHeight READ getEyeHeight) Q_PROPERTY(float playerHeight READ getPlayerHeight) @@ -43,7 +43,7 @@ signals: /**jsdoc * Triggered when Interface's display mode changes and when the user puts on or takes off their HMD. * @function HMD.displayModeChanged - * @param {boolean} isHMDMode - <code>true</code> if the display mode is HMD, otherwise <code>false</code>. This is the + * @param {boolean} isHMDMode - <code>true</code> if the display mode is HMD, otherwise <code>false</code>. This is the * same value as provided by <code>HMD.active</code>. * @returns {Signal} * @example <caption>Report when the display mode changes.</caption> diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 980ff8834c..380998321f 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -32,6 +32,7 @@ #include <ScriptEngine.h> #include <EntitySimulation.h> #include <ZoneRenderer.h> +#include <PhysicalEntitySimulation.h> #include "EntitiesRendererLogging.h" #include "RenderableEntityItem.h" @@ -41,6 +42,7 @@ #include <PointerManager.h> std::function<bool()> EntityTreeRenderer::_entitiesShouldFadeFunction = []() { return true; }; +std::function<glm::vec3()> EntityTreeRenderer::_getAvatarUpOperator = []() { return Vectors::UP; }; QString resolveScriptURL(const QString& scriptUrl) { auto normalizedScriptUrl = DependencyManager::get<ResourceManager>()->normalizeURL(scriptUrl); @@ -61,8 +63,7 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf _lastPointerEventValid(false), _viewState(viewState), _scriptingServices(scriptingServices), - _displayModelBounds(false), - _layeredZones(this) + _displayModelBounds(false) { setMouseRayPickResultOperator([](unsigned int rayPickID) { RayToEntityIntersectionResult entityResult; @@ -498,59 +499,61 @@ void EntityTreeRenderer::handleSpaceUpdate(std::pair<int32_t, glm::vec4> proxyUp bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(QVector<EntityItemID>* entitiesContainingAvatar) { bool didUpdate = false; float radius = 0.01f; // for now, assume 0.01 meter radius, because we actually check the point inside later - QVector<EntityItemPointer> foundEntities; + QVector<QUuid> entityIDs; // find the entities near us // don't let someone else change our tree while we search _tree->withReadLock([&] { + auto entityTree = std::static_pointer_cast<EntityTree>(_tree); // FIXME - if EntityTree had a findEntitiesContainingPoint() this could theoretically be a little faster - std::static_pointer_cast<EntityTree>(_tree)->findEntities(_avatarPosition, radius, foundEntities); + entityTree->evalEntitiesInSphere(_avatarPosition, radius, + PickFilter(PickFilter::getBitMask(PickFilter::FlagBit::DOMAIN_ENTITIES) | PickFilter::getBitMask(PickFilter::FlagBit::AVATAR_ENTITIES)), entityIDs); LayeredZones oldLayeredZones(std::move(_layeredZones)); _layeredZones.clear(); // create a list of entities that actually contain the avatar's position - for (auto& entity : foundEntities) { + for (auto& entityID : entityIDs) { + auto entity = entityTree->findEntityByID(entityID); + if (!entity) { + continue; + } + auto isZone = entity->getType() == EntityTypes::Zone; auto hasScript = !entity->getScript().isEmpty(); // only consider entities that are zones or have scripts, all other entities can - // be ignored because they can have events fired on them. + // be ignored because they can't have events fired on them. // FIXME - this could be optimized further by determining if the script is loaded // and if it has either an enterEntity or leaveEntity method // // also, don't flag a scripted entity as containing the avatar until the script is loaded, // so that the script is awake in time to receive the "entityEntity" call (even if the entity is a zone). - if ((!hasScript && isZone) || - (hasScript && entity->isScriptPreloadFinished())) { - // now check to see if the point contains our entity, this can be expensive if - // the entity has a collision hull - if (entity->contains(_avatarPosition)) { + bool contains = false; + bool scriptHasLoaded = hasScript && entity->isScriptPreloadFinished(); + if (isZone || scriptHasLoaded) { + contains = entity->contains(_avatarPosition); + } + + if (contains) { + // if this entity is a zone and visible, add it to our layered zones + if (isZone && entity->getVisible() && renderableForEntity(entity)) { + _layeredZones.insert(std::dynamic_pointer_cast<ZoneEntityItem>(entity)); + } + + if ((!hasScript && isZone) || scriptHasLoaded) { if (entitiesContainingAvatar) { *entitiesContainingAvatar << entity->getEntityItemID(); } - - // if this entity is a zone and visible, determine if it is the bestZone - if (isZone && entity->getVisible() && renderableForEntity(entity)) { - auto zone = std::dynamic_pointer_cast<ZoneEntityItem>(entity); - _layeredZones.insert(zone); - } - } } } + } // check if our layered zones have changed - if (_layeredZones.empty()) { - if (oldLayeredZones.empty()) { - return; - } - } else if (!oldLayeredZones.empty()) { - if (_layeredZones.contains(oldLayeredZones)) { - return; - } + if ((_layeredZones.empty() && oldLayeredZones.empty()) || (!oldLayeredZones.empty() && _layeredZones.contains(oldLayeredZones))) { + return; } - _layeredZones.apply(); applyLayeredZones(); @@ -653,8 +656,8 @@ bool EntityTreeRenderer::applyLayeredZones() { } else { qCWarning(entitiesrenderer) << "EntityTreeRenderer::applyLayeredZones(), Unexpected null scene, possibly during application shutdown"; } - - return true; + + return true; } void EntityTreeRenderer::processEraseMessage(ReceivedMessage& message, const SharedNodePointer& sourceNode) { @@ -1151,18 +1154,12 @@ std::pair<EntityTreeRenderer::LayeredZones::iterator, bool> EntityTreeRenderer:: return { it, success }; } -void EntityTreeRenderer::LayeredZones::apply() { - assert(_entityTreeRenderer); -} - void EntityTreeRenderer::LayeredZones::update(std::shared_ptr<ZoneEntityItem> zone) { - assert(_entityTreeRenderer); bool isVisible = zone->isVisible(); if (empty() && isVisible) { // there are no zones: set this one insert(zone); - apply(); return; } else { LayeredZone zoneLayer(zone); @@ -1254,3 +1251,11 @@ void EntityTreeRenderer::onEntityChanged(const EntityItemID& id) { _changedEntities.insert(id); }); } + +EntityEditPacketSender* EntityTreeRenderer::getPacketSender() { + EntityTreePointer tree = getTree(); + EntitySimulationPointer simulation = tree ? tree->getSimulation() : nullptr; + PhysicalEntitySimulationPointer peSimulation = std::static_pointer_cast<PhysicalEntitySimulation>(simulation); + EntityEditPacketSender* packetSender = peSimulation ? peSimulation->getPacketSender() : nullptr; + return packetSender; +} diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index 4ba1a0060b..b4f0bda703 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -117,6 +117,11 @@ public: // Access the workload Space workload::SpacePointer getWorkloadSpace() const { return _space; } + static void setGetAvatarUpOperator(std::function<glm::vec3()> getAvatarUpOperator) { _getAvatarUpOperator = getAvatarUpOperator; } + static glm::vec3 getAvatarUp() { return _getAvatarUpOperator(); } + + EntityEditPacketSender* getPacketSender(); + signals: void enterEntity(const EntityItemID& entityItemID); void leaveEntity(const EntityItemID& entityItemID); @@ -205,42 +210,28 @@ private: class LayeredZones : public std::set<LayeredZone> { public: - LayeredZones(EntityTreeRenderer* parent) : _entityTreeRenderer(parent) {} + LayeredZones() {}; LayeredZones(LayeredZones&& other); // avoid accidental misconstruction - LayeredZones() = delete; LayeredZones(const LayeredZones&) = delete; LayeredZones& operator=(const LayeredZones&) = delete; LayeredZones& operator=(LayeredZones&&) = delete; void clear(); std::pair<iterator, bool> insert(const LayeredZone& layer); - - void apply(); void update(std::shared_ptr<ZoneEntityItem> zone); - bool contains(const LayeredZones& other); std::shared_ptr<ZoneEntityItem> getZone() { return empty() ? nullptr : begin()->zone; } private: - void applyPartial(iterator layer); - std::map<QUuid, iterator> _map; - iterator _skyboxLayer{ end() }; - EntityTreeRenderer* _entityTreeRenderer; + iterator _skyboxLayer { end() }; }; LayeredZones _layeredZones; - QString _zoneUserData; - NetworkTexturePointer _ambientTexture; - NetworkTexturePointer _skyboxTexture; - QString _ambientTextureURL; - QString _skyboxTextureURL; float _avgRenderableUpdateCost { 0.0f }; - bool _pendingAmbientTexture { false }; - bool _pendingSkyboxTexture { false }; uint64_t _lastZoneCheck { 0 }; const uint64_t ZONE_CHECK_INTERVAL = USECS_PER_MSEC * 100; // ~10hz @@ -262,6 +253,8 @@ private: mutable std::mutex _spaceLock; workload::SpacePointer _space{ new workload::Space() }; workload::Transaction::Updates _spaceUpdates; + + static std::function<glm::vec3()> _getAvatarUpOperator; }; diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index 75d06191ea..5fb5a15d2c 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -25,6 +25,8 @@ #include "RenderableWebEntityItem.h" #include "RenderableZoneEntityItem.h" #include "RenderableMaterialEntityItem.h" +#include "RenderableImageEntityItem.h" +#include "RenderableGridEntityItem.h" using namespace render; @@ -217,22 +219,39 @@ EntityRenderer::Pointer EntityRenderer::addToScene(EntityTreeRenderer& renderer, using Type = EntityTypes::EntityType_t; auto type = entity->getType(); switch (type) { - case Type::Light: - result = make_renderer<LightEntityRenderer>(entity); - break; - case Type::Line: - result = make_renderer<LineEntityRenderer>(entity); + case Type::Shape: + case Type::Box: + case Type::Sphere: + result = make_renderer<ShapeEntityRenderer>(entity); break; case Type::Model: result = make_renderer<ModelEntityRenderer>(entity); break; + case Type::Text: + result = make_renderer<TextEntityRenderer>(entity); + break; + + case Type::Image: + result = make_renderer<ImageEntityRenderer>(entity); + break; + + case Type::Web: + if (!nsightActive()) { + result = make_renderer<WebEntityRenderer>(entity); + } + break; + case Type::ParticleEffect: result = make_renderer<ParticleEffectEntityRenderer>(entity); break; + case Type::Line: + result = make_renderer<LineEntityRenderer>(entity); + break; + case Type::PolyLine: result = make_renderer<PolyLineEntityRenderer>(entity); break; @@ -241,20 +260,12 @@ EntityRenderer::Pointer EntityRenderer::addToScene(EntityTreeRenderer& renderer, result = make_renderer<PolyVoxEntityRenderer>(entity); break; - case Type::Shape: - case Type::Box: - case Type::Sphere: - result = make_renderer<ShapeEntityRenderer>(entity); + case Type::Grid: + result = make_renderer<GridEntityRenderer>(entity); break; - case Type::Text: - result = make_renderer<TextEntityRenderer>(entity); - break; - - case Type::Web: - if (!nsightActive()) { - result = make_renderer<WebEntityRenderer>(entity); - } + case Type::Light: + result = make_renderer<LightEntityRenderer>(entity); break; case Type::Zone: diff --git a/libraries/entities-renderer/src/RenderableGridEntityItem.cpp b/libraries/entities-renderer/src/RenderableGridEntityItem.cpp new file mode 100644 index 0000000000..22cf72cec6 --- /dev/null +++ b/libraries/entities-renderer/src/RenderableGridEntityItem.cpp @@ -0,0 +1,148 @@ +// +// Created by Sam Gondelman on 11/29/18 +// 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 "RenderableGridEntityItem.h" + +#include <DependencyManager.h> +#include <GeometryCache.h> + +using namespace render; +using namespace render::entities; + +GridEntityRenderer::GridEntityRenderer(const EntityItemPointer& entity) : Parent(entity) { + _geometryId = DependencyManager::get<GeometryCache>()->allocateID(); +} + +GridEntityRenderer::~GridEntityRenderer() { + auto geometryCache = DependencyManager::get<GeometryCache>(); + if (geometryCache) { + geometryCache->releaseID(_geometryId); + } +} + +bool GridEntityRenderer::isTransparent() const { + return Parent::isTransparent() || _alpha < 1.0f; +} + +bool GridEntityRenderer::needsRenderUpdate() const { + return Parent::needsRenderUpdate(); +} + +bool GridEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const { + bool needsUpdate = resultWithReadLock<bool>([&] { + if (_color != entity->getColor()) { + return true; + } + + if (_alpha != entity->getAlpha()) { + return true; + } + + if (_followCamera != entity->getFollowCamera()) { + return true; + } + + if (_majorGridEvery != entity->getMajorGridEvery()) { + return true; + } + + if (_minorGridEvery != entity->getMinorGridEvery()) { + return true; + } + + return false; + }); + + return needsUpdate; +} + +void GridEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { + withWriteLock([&] { + _color = entity->getColor(); + _alpha = entity->getAlpha(); + + _followCamera = entity->getFollowCamera(); + _majorGridEvery = entity->getMajorGridEvery(); + _minorGridEvery = entity->getMinorGridEvery(); + }); + + void* key = (void*)this; + AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity]() { + withWriteLock([&] { + _dimensions = entity->getScaledDimensions(); + updateModelTransformAndBound(); + _renderTransform = getModelTransform(); + }); + }); +} + +Item::Bound GridEntityRenderer::getBound() { + if (_followCamera) { + // This is a UI element that should always be in view, lie to the octree to avoid culling + const AABox DOMAIN_BOX = AABox(glm::vec3(-TREE_SCALE / 2), TREE_SCALE); + return DOMAIN_BOX; + } + return Parent::getBound(); +} + +ShapeKey GridEntityRenderer::getShapeKey() { + auto builder = render::ShapeKey::Builder().withOwnPipeline().withUnlit().withDepthBias(); + + if (isTransparent()) { + builder.withTranslucent(); + } + + return builder.build(); +} + +void GridEntityRenderer::doRender(RenderArgs* args) { + glm::u8vec3 color; + glm::vec3 dimensions; + Transform renderTransform; + withReadLock([&] { + color = _color; + dimensions = _dimensions; + renderTransform = _renderTransform; + }); + + if (!_visible) { + return; + } + + auto batch = args->_batch; + + Transform transform; + transform.setScale(dimensions); + transform.setRotation(renderTransform.getRotation()); + if (_followCamera) { + // Get the camera position rounded to the nearest major grid line + // This grid is for UI and should lie on worldlines + glm::vec3 localCameraPosition = glm::inverse(transform.getRotation()) * (args->getViewFrustum().getPosition() - renderTransform.getTranslation()); + localCameraPosition.z = 0; + localCameraPosition = (float)_majorGridEvery * glm::round(localCameraPosition / (float)_majorGridEvery); + transform.setTranslation(renderTransform.getTranslation() + transform.getRotation() * localCameraPosition); + } else { + transform.setTranslation(renderTransform.getTranslation()); + } + batch->setModelTransform(transform); + + auto minCorner = glm::vec2(-0.5f, -0.5f); + auto maxCorner = glm::vec2(0.5f, 0.5f); + float majorGridRowDivisions = dimensions.x / _majorGridEvery; + float majorGridColDivisions = dimensions.y / _majorGridEvery; + float minorGridRowDivisions = dimensions.x / _minorGridEvery; + float minorGridColDivisions = dimensions.y / _minorGridEvery; + glm::vec4 gridColor(toGlm(color), _alpha); + + const float MINOR_GRID_EDGE = 0.0025f; + const float MAJOR_GRID_EDGE = 0.005f; + DependencyManager::get<GeometryCache>()->renderGrid(*batch, minCorner, maxCorner, + minorGridRowDivisions, minorGridColDivisions, MINOR_GRID_EDGE, + majorGridRowDivisions, majorGridColDivisions, MAJOR_GRID_EDGE, + gridColor, _geometryId); +} \ No newline at end of file diff --git a/libraries/entities-renderer/src/RenderableGridEntityItem.h b/libraries/entities-renderer/src/RenderableGridEntityItem.h new file mode 100644 index 0000000000..23f1f03d0d --- /dev/null +++ b/libraries/entities-renderer/src/RenderableGridEntityItem.h @@ -0,0 +1,51 @@ +// +// Created by Sam Gondelman on 11/29/18 +// 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 +// + +#ifndef hifi_RenderableGridEntityItem_h +#define hifi_RenderableGridEntityItem_h + +#include "RenderableEntityItem.h" + +#include <GridEntityItem.h> + +namespace render { namespace entities { + +class GridEntityRenderer : public TypedEntityRenderer<GridEntityItem> { + using Parent = TypedEntityRenderer<GridEntityItem>; + using Pointer = std::shared_ptr<GridEntityRenderer>; +public: + GridEntityRenderer(const EntityItemPointer& entity); + ~GridEntityRenderer(); + +protected: + Item::Bound getBound() override; + ShapeKey getShapeKey() override; + + bool isTransparent() const override; + +private: + virtual bool needsRenderUpdate() const override; + virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override; + virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override; + virtual void doRender(RenderArgs* args) override; + + glm::u8vec3 _color; + float _alpha; + + bool _followCamera; + uint32_t _majorGridEvery; + float _minorGridEvery; + + glm::vec3 _dimensions; + + int _geometryId { 0 }; + +}; + +} } +#endif // hifi_RenderableGridEntityItem_h diff --git a/libraries/entities-renderer/src/RenderableImageEntityItem.cpp b/libraries/entities-renderer/src/RenderableImageEntityItem.cpp new file mode 100644 index 0000000000..7c5b7fc0da --- /dev/null +++ b/libraries/entities-renderer/src/RenderableImageEntityItem.cpp @@ -0,0 +1,218 @@ +// +// Created by Sam Gondelman on 11/29/18 +// 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 "RenderableImageEntityItem.h" + +#include <DependencyManager.h> +#include <GeometryCache.h> + +using namespace render; +using namespace render::entities; + +ImageEntityRenderer::ImageEntityRenderer(const EntityItemPointer& entity) : Parent(entity) { + _geometryId = DependencyManager::get<GeometryCache>()->allocateID(); +} + +ImageEntityRenderer::~ImageEntityRenderer() { + auto geometryCache = DependencyManager::get<GeometryCache>(); + if (geometryCache) { + geometryCache->releaseID(_geometryId); + } +} + +bool ImageEntityRenderer::isTransparent() const { + return Parent::isTransparent() || (_textureIsLoaded && _texture->getGPUTexture() && _texture->getGPUTexture()->getUsage().isAlpha()) || _alpha < 1.0f; +} + +bool ImageEntityRenderer::needsRenderUpdate() const { + bool textureLoadedChanged = resultWithReadLock<bool>([&] { + return (!_textureIsLoaded && _texture && _texture->isLoaded()); + }); + + if (textureLoadedChanged) { + return true; + } + + return Parent::needsRenderUpdate(); +} + +bool ImageEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const { + bool needsUpdate = resultWithReadLock<bool>([&] { + if (_imageURL != entity->getImageURL()) { + return true; + } + + if (_emissive != entity->getEmissive()) { + return true; + } + + if (_keepAspectRatio != entity->getKeepAspectRatio()) { + return true; + } + + if (_billboardMode != entity->getBillboardMode()) { + return true; + } + + if (_subImage != entity->getSubImage()) { + return true; + } + + if (_color != entity->getColor()) { + return true; + } + + if (_alpha != entity->getAlpha()) { + return true; + } + + return false; + }); + + return needsUpdate; +} + +void ImageEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { + withWriteLock([&] { + auto imageURL = entity->getImageURL(); + if (_imageURL != imageURL) { + _imageURL = imageURL; + if (imageURL.isEmpty()) { + _texture.reset(); + } else { + _texture = DependencyManager::get<TextureCache>()->getTexture(_imageURL); + } + _textureIsLoaded = false; + } + + _emissive = entity->getEmissive(); + _keepAspectRatio = entity->getKeepAspectRatio(); + _billboardMode = entity->getBillboardMode(); + _subImage = entity->getSubImage(); + + _color = entity->getColor(); + _alpha = entity->getAlpha(); + + if (!_textureIsLoaded && _texture && _texture->isLoaded()) { + _textureIsLoaded = true; + } + }); + + void* key = (void*)this; + AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity]() { + withWriteLock([&] { + _dimensions = entity->getScaledDimensions(); + updateModelTransformAndBound(); + _renderTransform = getModelTransform(); + }); + }); +} + +ShapeKey ImageEntityRenderer::getShapeKey() { + auto builder = render::ShapeKey::Builder().withoutCullFace().withDepthBias(); + if (isTransparent()) { + builder.withTranslucent(); + } + + withReadLock([&] { + if (_emissive) { + builder.withUnlit(); + } + }); + + return builder.build(); +} + +void ImageEntityRenderer::doRender(RenderArgs* args) { + NetworkTexturePointer texture; + QRect subImage; + glm::u8vec3 color; + glm::vec3 dimensions; + Transform transform; + withReadLock([&] { + texture = _texture; + subImage = _subImage; + color = _color; + dimensions = _dimensions; + transform = _renderTransform; + }); + + if (!_visible || !texture || !texture->isLoaded()) { + return; + } + + Q_ASSERT(args->_batch); + gpu::Batch* batch = args->_batch; + + if (_billboardMode == BillboardMode::YAW) { + //rotate about vertical to face the camera + glm::vec3 dPosition = args->getViewFrustum().getPosition() - transform.getTranslation(); + // If x and z are 0, atan(x, z) is undefined, so default to 0 degrees + float yawRotation = dPosition.x == 0.0f && dPosition.z == 0.0f ? 0.0f : glm::atan(dPosition.x, dPosition.z); + glm::quat orientation = glm::quat(glm::vec3(0.0f, yawRotation, 0.0f)); + transform.setRotation(orientation); + } else if (_billboardMode == BillboardMode::FULL) { + glm::vec3 billboardPos = transform.getTranslation(); + glm::vec3 cameraPos = args->getViewFrustum().getPosition(); + // use the referencial from the avatar, y isn't always up + glm::vec3 avatarUP = EntityTreeRenderer::getAvatarUp(); + // check to see if glm::lookAt will work / using glm::lookAt variable name + glm::highp_vec3 s(glm::cross(billboardPos - cameraPos, avatarUP)); + + // make sure s is not NaN for any component + if (glm::length2(s) > 0.0f) { + glm::quat rotation(conjugate(toQuat(glm::lookAt(cameraPos, billboardPos, avatarUP)))); + transform.setRotation(rotation); + } + } + transform.postScale(dimensions); + + batch->setModelTransform(transform); + batch->setResourceTexture(0, texture->getGPUTexture()); + + float imageWidth = texture->getWidth(); + float imageHeight = texture->getHeight(); + + QRect fromImage; + if (subImage.width() <= 0) { + fromImage.setX(0); + fromImage.setWidth(imageWidth); + } else { + float scaleX = imageWidth / texture->getOriginalWidth(); + fromImage.setX(scaleX * subImage.x()); + fromImage.setWidth(scaleX * subImage.width()); + } + + if (subImage.height() <= 0) { + fromImage.setY(0); + fromImage.setHeight(imageHeight); + } else { + float scaleY = imageHeight / texture->getOriginalHeight(); + fromImage.setY(scaleY * subImage.y()); + fromImage.setHeight(scaleY * subImage.height()); + } + + float maxSize = glm::max(fromImage.width(), fromImage.height()); + float x = _keepAspectRatio ? fromImage.width() / (2.0f * maxSize) : 0.5f; + float y = _keepAspectRatio ? -fromImage.height() / (2.0f * maxSize) : -0.5f; + + glm::vec2 topLeft(-x, -y); + glm::vec2 bottomRight(x, y); + glm::vec2 texCoordTopLeft((fromImage.x() + 0.5f) / imageWidth, (fromImage.y() + 0.5f) / imageHeight); + glm::vec2 texCoordBottomRight((fromImage.x() + fromImage.width() - 0.5f) / imageWidth, + (fromImage.y() + fromImage.height() - 0.5f) / imageHeight); + + glm::vec4 imageColor(toGlm(color), _alpha); + + DependencyManager::get<GeometryCache>()->renderQuad( + *batch, topLeft, bottomRight, texCoordTopLeft, texCoordBottomRight, + imageColor, _geometryId + ); + + batch->setResourceTexture(0, nullptr); +} \ No newline at end of file diff --git a/libraries/entities-renderer/src/RenderableImageEntityItem.h b/libraries/entities-renderer/src/RenderableImageEntityItem.h new file mode 100644 index 0000000000..9bc990f6fa --- /dev/null +++ b/libraries/entities-renderer/src/RenderableImageEntityItem.h @@ -0,0 +1,54 @@ +// +// Created by Sam Gondelman on 11/29/18 +// 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 +// + +#ifndef hifi_RenderableImageEntityItem_h +#define hifi_RenderableImageEntityItem_h + +#include "RenderableEntityItem.h" + +#include <ImageEntityItem.h> + +namespace render { namespace entities { + +class ImageEntityRenderer : public TypedEntityRenderer<ImageEntityItem> { + using Parent = TypedEntityRenderer<ImageEntityItem>; + using Pointer = std::shared_ptr<ImageEntityRenderer>; +public: + ImageEntityRenderer(const EntityItemPointer& entity); + ~ImageEntityRenderer(); + +protected: + ShapeKey getShapeKey() override; + + bool isTransparent() const override; + +private: + virtual bool needsRenderUpdate() const override; + virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override; + virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override; + virtual void doRender(RenderArgs* args) override; + + QString _imageURL; + NetworkTexturePointer _texture; + bool _textureIsLoaded { false }; + + bool _emissive; + bool _keepAspectRatio; + BillboardMode _billboardMode; + QRect _subImage; + + glm::u8vec3 _color; + float _alpha; + + glm::vec3 _dimensions; + + int _geometryId { 0 }; +}; + +} } +#endif // hifi_RenderableImageEntityItem_h diff --git a/libraries/entities-renderer/src/RenderableLineEntityItem.cpp b/libraries/entities-renderer/src/RenderableLineEntityItem.cpp index 6a472cab1e..9c5424950a 100644 --- a/libraries/entities-renderer/src/RenderableLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableLineEntityItem.cpp @@ -37,7 +37,7 @@ void LineEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointe if (_lineVerticesID == GeometryCache::UNKNOWN_ID) { _lineVerticesID = geometryCache->allocateID(); } - glm::vec4 lineColor(toGlm(entity->getColor()), entity->getLocalRenderAlpha()); + glm::vec4 lineColor(toGlm(entity->getColor()), 1.0f); geometryCache->updateVertices(_lineVerticesID, _linePoints, lineColor); } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 0497bc5a2b..aa449b8919 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -83,34 +83,6 @@ void RenderableModelEntityItem::setUnscaledDimensions(const glm::vec3& value) { } } -QVariantMap parseTexturesToMap(QString textures, const QVariantMap& defaultTextures) { - // If textures are unset, revert to original textures - if (textures.isEmpty()) { - return defaultTextures; - } - - // Legacy: a ,\n-delimited list of filename:"texturepath" - if (*textures.cbegin() != '{') { - textures = "{\"" + textures.replace(":\"", "\":\"").replace(",\n", ",\"") + "}"; - } - - QJsonParseError error; - QJsonDocument texturesJson = QJsonDocument::fromJson(textures.toUtf8(), &error); - // If textures are invalid, revert to original textures - if (error.error != QJsonParseError::NoError) { - qCWarning(entitiesrenderer) << "Could not evaluate textures property value:" << textures; - return defaultTextures; - } - - QVariantMap texturesMap = texturesJson.toVariant().toMap(); - // If textures are unset, revert to original textures - if (texturesMap.isEmpty()) { - return defaultTextures; - } - - return texturesJson.toVariant().toMap(); -} - void RenderableModelEntityItem::doInitialModelSimulation() { DETAILED_PROFILE_RANGE(simulation_physics, __FUNCTION__); ModelPointer model = getModel(); @@ -307,7 +279,7 @@ bool RenderableModelEntityItem::findDetailedParabolaIntersection(const glm::vec3 face, surfaceNormal, extraInfo, precisionPicking, false); } -void RenderableModelEntityItem::getCollisionGeometryResource() { +void RenderableModelEntityItem::fetchCollisionGeometryResource() { QUrl hullURL(getCollisionShapeURL()); QUrlQuery queryArgs(hullURL); queryArgs.addQueryItem("collision-hull", ""); @@ -317,7 +289,7 @@ void RenderableModelEntityItem::getCollisionGeometryResource() { bool RenderableModelEntityItem::computeShapeFailedToLoad() { if (!_compoundShapeResource) { - getCollisionGeometryResource(); + fetchCollisionGeometryResource(); } return (_compoundShapeResource && _compoundShapeResource->isFailed()); @@ -328,7 +300,7 @@ void RenderableModelEntityItem::setShapeType(ShapeType type) { auto shapeType = getShapeType(); if (shapeType == SHAPE_TYPE_COMPOUND || shapeType == SHAPE_TYPE_SIMPLE_COMPOUND) { if (!_compoundShapeResource && !getCollisionShapeURL().isEmpty()) { - getCollisionGeometryResource(); + fetchCollisionGeometryResource(); } } else if (_compoundShapeResource && !getCompoundShapeURL().isEmpty()) { // the compoundURL has been set but the shapeType does not agree @@ -345,7 +317,7 @@ void RenderableModelEntityItem::setCompoundShapeURL(const QString& url) { ModelEntityItem::setCompoundShapeURL(url); if (getCompoundShapeURL() != currentCompoundShapeURL || !getModel()) { if (getShapeType() == SHAPE_TYPE_COMPOUND) { - getCollisionGeometryResource(); + fetchCollisionGeometryResource(); } } } @@ -368,7 +340,7 @@ bool RenderableModelEntityItem::isReadyToComputeShape() const { if (model->isLoaded()) { if (!shapeURL.isEmpty() && !_compoundShapeResource) { - const_cast<RenderableModelEntityItem*>(this)->getCollisionGeometryResource(); + const_cast<RenderableModelEntityItem*>(this)->fetchCollisionGeometryResource(); } if (_compoundShapeResource && _compoundShapeResource->isLoaded()) { @@ -395,8 +367,6 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { const uint32_t QUAD_STRIDE = 4; ShapeType type = getShapeType(); - glm::vec3 dimensions = getScaledDimensions(); - auto model = getModel(); if (type == SHAPE_TYPE_COMPOUND) { updateModelBounds(); @@ -478,6 +448,11 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { // to the visual model and apply them to the collision model (without regard for the // collision model's extents). + auto model = getModel(); + // assert we never fall in here when model not fully loaded + assert(model && model->isLoaded()); + + glm::vec3 dimensions = getScaledDimensions(); glm::vec3 scaleToFit = dimensions / model->getHFMModel().getUnscaledMeshExtents().size(); // multiply each point by scale before handing the point-set off to the physics engine. // also determine the extents of the collision model. @@ -489,11 +464,12 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { } } shapeInfo.setParams(type, dimensions, getCompoundShapeURL()); + adjustShapeInfoByRegistration(shapeInfo); } else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) { - // TODO: assert we never fall in here when model not fully loaded - // assert(_model && _model->isLoaded()); - updateModelBounds(); + auto model = getModel(); + // assert we never fall in here when model not fully loaded + assert(model && model->isLoaded()); model->updateGeometry(); // compute meshPart local transforms @@ -501,6 +477,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { const HFMModel& hfmModel = model->getHFMModel(); int numHFMMeshes = hfmModel.meshes.size(); int totalNumVertices = 0; + glm::vec3 dimensions = getScaledDimensions(); glm::mat4 invRegistraionOffset = glm::translate(dimensions * (getRegistrationPoint() - ENTITY_ITEM_DEFAULT_REGISTRATION_POINT)); for (int i = 0; i < numHFMMeshes; i++) { const HFMMesh& mesh = hfmModel.meshes.at(i); @@ -723,12 +700,10 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) { } shapeInfo.setParams(type, 0.5f * dimensions, getModelURL()); + adjustShapeInfoByRegistration(shapeInfo); } else { - ModelEntityItem::computeShapeInfo(shapeInfo); - shapeInfo.setParams(type, 0.5f * dimensions); + EntityItem::computeShapeInfo(shapeInfo); } - // finally apply the registration offset to the shapeInfo - adjustShapeInfoByRegistration(shapeInfo); } void RenderableModelEntityItem::setJointMap(std::vector<int> jointMap) { @@ -754,7 +729,9 @@ int RenderableModelEntityItem::avatarJointIndex(int modelJointIndex) { bool RenderableModelEntityItem::contains(const glm::vec3& point) const { auto model = getModel(); if (EntityItem::contains(point) && model && _compoundShapeResource && _compoundShapeResource->isLoaded()) { - return _compoundShapeResource->getHFMModel().convexHullContains(worldToEntity(point)); + glm::mat4 worldToHFMMatrix = model->getWorldToHFMMatrix(); + glm::vec3 hfmPoint = worldToHFMMatrix * glm::vec4(point, 1.0f); + return _compoundShapeResource->getHFMModel().convexHullContains(hfmPoint); } return false; diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index ba185dee96..725c1d96c3 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -122,7 +122,7 @@ private: void autoResizeJointArrays(); void copyAnimationJointDataToModel(); bool readyToAnimate() const; - void getCollisionGeometryResource(); + void fetchCollisionGeometryResource(); GeometryResource::Pointer _compoundShapeResource; std::vector<int> _jointMap; @@ -179,7 +179,6 @@ private: bool _hasModel { false }; ModelPointer _model; - GeometryResource::Pointer _compoundShapeResource; QString _lastTextures; bool _texturesLoaded { false }; int _lastKnownCurrentFrame { -1 }; diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index 0d9e948db8..d4a10e551d 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -19,66 +19,39 @@ #include <PerfStat.h> #include <shaders/Shaders.h> -//#define POLYLINE_ENTITY_USE_FADE_EFFECT -#ifdef POLYLINE_ENTITY_USE_FADE_EFFECT -# include <FadeEffect.h> -#endif +#include "paintStroke_Shared.slh" using namespace render; using namespace render::entities; -static uint8_t CUSTOM_PIPELINE_NUMBER { 0 }; -static const int32_t PAINTSTROKE_TEXTURE_SLOT { 0 }; -static gpu::Stream::FormatPointer polylineFormat; -static gpu::PipelinePointer polylinePipeline; -#ifdef POLYLINE_ENTITY_USE_FADE_EFFECT -static gpu::PipelinePointer polylineFadePipeline; -#endif +gpu::PipelinePointer PolyLineEntityRenderer::_pipeline = nullptr; -static render::ShapePipelinePointer shapePipelineFactory(const render::ShapePlumber& plumber, const render::ShapeKey& key, gpu::Batch& batch) { - if (!polylinePipeline) { - gpu::ShaderPointer program = gpu::Shader::createProgram(shader::entities_renderer::program::paintStroke); -#ifdef POLYLINE_ENTITY_USE_FADE_EFFECT - auto fadeVS = gpu::Shader::createVertex(std::string(paintStroke_fade_vert)); - auto fadePS = gpu::Shader::createPixel(std::string(paintStroke_fade_frag)); - gpu::ShaderPointer fadeProgram = gpu::Shader::createProgram(fadeVS, fadePS); -#endif - gpu::StatePointer state = gpu::StatePointer(new gpu::State()); - state->setDepthTest(true, true, gpu::LESS_EQUAL); - PrepareStencil::testMask(*state); - state->setBlendFunction(true, - gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, - gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - polylinePipeline = gpu::Pipeline::create(program, state); -#ifdef POLYLINE_ENTITY_USE_FADE_EFFECT - _fadePipeline = gpu::Pipeline::create(fadeProgram, state); -#endif - } - -#ifdef POLYLINE_ENTITY_USE_FADE_EFFECT - if (key.isFaded()) { - auto fadeEffect = DependencyManager::get<FadeEffect>(); - return std::make_shared<render::ShapePipeline>(_fadePipeline, nullptr, fadeEffect->getBatchSetter(), fadeEffect->getItemUniformSetter()); - } else { -#endif - return std::make_shared<render::ShapePipeline>(polylinePipeline, nullptr, nullptr, nullptr); -#ifdef POLYLINE_ENTITY_USE_FADE_EFFECT - } -#endif -} +static const QUrl DEFAULT_POLYLINE_TEXTURE = PathUtils::resourcesUrl("images/paintStroke.png"); PolyLineEntityRenderer::PolyLineEntityRenderer(const EntityItemPointer& entity) : Parent(entity) { - static std::once_flag once; - std::call_once(once, [&] { - CUSTOM_PIPELINE_NUMBER = render::ShapePipeline::registerCustomShapePipelineFactory(shapePipelineFactory); - polylineFormat.reset(new gpu::Stream::Format()); - polylineFormat->setAttribute(gpu::Stream::POSITION, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), offsetof(Vertex, position)); - polylineFormat->setAttribute(gpu::Stream::NORMAL, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ), offsetof(Vertex, normal)); - polylineFormat->setAttribute(gpu::Stream::TEXCOORD, 0, gpu::Element(gpu::VEC2, gpu::FLOAT, gpu::UV), offsetof(Vertex, uv)); - polylineFormat->setAttribute(gpu::Stream::COLOR, 0, gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RGB), offsetof(Vertex, color)); - }); + _texture = DependencyManager::get<TextureCache>()->getTexture(DEFAULT_POLYLINE_TEXTURE); - _verticesBuffer = std::make_shared<gpu::Buffer>(); + { // Initialize our buffers + _polylineDataBuffer = std::make_shared<gpu::Buffer>(); + _polylineDataBuffer->resize(sizeof(PolylineData)); + PolylineData data { glm::vec2(_faceCamera, _glow), glm::vec2(0.0f) }; + _polylineDataBuffer->setSubData(0, data); + + _polylineGeometryBuffer = std::make_shared<gpu::Buffer>(); + } +} + +void PolyLineEntityRenderer::buildPipeline() { + // FIXME: opaque pipeline + gpu::ShaderPointer program = gpu::Shader::createProgram(shader::entities_renderer::program::paintStroke); + gpu::StatePointer state = gpu::StatePointer(new gpu::State()); + state->setCullMode(gpu::State::CullMode::CULL_NONE); + state->setDepthTest(true, true, gpu::LESS_EQUAL); + PrepareStencil::testMask(*state); + state->setBlendFunction(true, + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + _pipeline = gpu::Pipeline::create(program, state); } ItemKey PolyLineEntityRenderer::getKey() { @@ -86,152 +59,164 @@ ItemKey PolyLineEntityRenderer::getKey() { } ShapeKey PolyLineEntityRenderer::getShapeKey() { - return ShapeKey::Builder().withCustom(CUSTOM_PIPELINE_NUMBER).build(); + return ShapeKey::Builder().withOwnPipeline().withTranslucent().withoutCullFace(); +} + +bool PolyLineEntityRenderer::needsRenderUpdate() const { + bool textureLoadedChanged = resultWithReadLock<bool>([&] { + return (!_textureLoaded && _texture && _texture->isLoaded()); + }); + + if (textureLoadedChanged) { + return true; + } + + return Parent::needsRenderUpdate(); } bool PolyLineEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const { return ( entity->pointsChanged() || - entity->strokeWidthsChanged() || + entity->widthsChanged() || entity->normalsChanged() || entity->texturesChanged() || - entity->strokeColorsChanged() + entity->colorsChanged() || + _isUVModeStretch != entity->getIsUVModeStretch() || + _glow != entity->getGlow() || + _faceCamera != entity->getFaceCamera() ); } -void PolyLineEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { - static const QUrl DEFAULT_POLYLINE_TEXTURE = QUrl(PathUtils::resourcesPath() + "images/paintStroke.png"); - QUrl entityTextures = DEFAULT_POLYLINE_TEXTURE; +void PolyLineEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { + auto pointsChanged = entity->pointsChanged(); + auto widthsChanged = entity->widthsChanged(); + auto normalsChanged = entity->normalsChanged(); + auto colorsChanged = entity->colorsChanged(); + + bool isUVModeStretch = entity->getIsUVModeStretch(); + bool glow = entity->getGlow(); + bool faceCamera = entity->getFaceCamera(); + + entity->resetPolyLineChanged(); + + // Transform + updateModelTransformAndBound(); + _renderTransform = getModelTransform(); + + // Textures if (entity->texturesChanged()) { entity->resetTexturesChanged(); + QUrl entityTextures = DEFAULT_POLYLINE_TEXTURE; auto textures = entity->getTextures(); if (!textures.isEmpty()) { entityTextures = QUrl(textures); } _texture = DependencyManager::get<TextureCache>()->getTexture(entityTextures); + _textureAspectRatio = 1.0f; + _textureLoaded = false; } - - - if (!_texture) { - _texture = DependencyManager::get<TextureCache>()->getTexture(entityTextures); + + bool textureChanged = false; + if (!_textureLoaded && _texture && _texture->isLoaded()) { + textureChanged = true; + _textureAspectRatio = (float)_texture->getOriginalHeight() / (float)_texture->getOriginalWidth(); + _textureLoaded = true; } -} -void PolyLineEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { - auto pointsChanged = entity->pointsChanged(); - auto strokeWidthsChanged = entity->strokeWidthsChanged(); - auto normalsChanged = entity->normalsChanged(); - auto strokeColorsChanged = entity->strokeColorsChanged(); - - - bool isUVModeStretch = entity->getIsUVModeStretch(); - entity->resetPolyLineChanged(); - - _polylineTransform = Transform(); - _polylineTransform.setTranslation(entity->getWorldPosition()); - _polylineTransform.setRotation(entity->getWorldOrientation()); + // Data + if (faceCamera != _faceCamera || glow != _glow) { + _faceCamera = faceCamera; + _glow = glow; + updateData(); + } + // Geometry if (pointsChanged) { - _lastPoints = entity->getLinePoints(); + _points = entity->getLinePoints(); } - if (strokeWidthsChanged) { - _lastStrokeWidths = entity->getStrokeWidths(); + if (widthsChanged) { + _widths = entity->getStrokeWidths(); } if (normalsChanged) { - _lastNormals = entity->getNormals(); + _normals = entity->getNormals(); } - if (strokeColorsChanged) { - _lastStrokeColors = entity->getStrokeColors(); - _lastStrokeColors = _lastNormals.size() == _lastStrokeColors.size() ? _lastStrokeColors : QVector<glm::vec3>({ toGlm(entity->getColor()) }); + if (colorsChanged) { + _colors = entity->getStrokeColors(); + _color = toGlm(entity->getColor()); } - if (pointsChanged || strokeWidthsChanged || normalsChanged || strokeColorsChanged) { - _empty = std::min(_lastPoints.size(), std::min(_lastNormals.size(), _lastStrokeWidths.size())) < 2; - if (!_empty) { - updateGeometry(updateVertices(_lastPoints, _lastNormals, _lastStrokeWidths, _lastStrokeColors, isUVModeStretch, _textureAspectRatio)); - } + if (_isUVModeStretch != isUVModeStretch || pointsChanged || widthsChanged || normalsChanged || colorsChanged || textureChanged) { + _isUVModeStretch = isUVModeStretch; + updateGeometry(); } } -void PolyLineEntityRenderer::updateGeometry(const std::vector<Vertex>& vertices) { - _numVertices = (uint32_t)vertices.size(); - auto bufferSize = _numVertices * sizeof(Vertex); - if (bufferSize > _verticesBuffer->getSize()) { - _verticesBuffer->resize(bufferSize); - } - _verticesBuffer->setSubData(0, vertices); -} +void PolyLineEntityRenderer::updateGeometry() { + int maxNumVertices = std::min(_points.length(), _normals.length()); -std::vector<PolyLineEntityRenderer::Vertex> PolyLineEntityRenderer::updateVertices(const QVector<glm::vec3>& points, - const QVector<glm::vec3>& normals, - const QVector<float>& strokeWidths, - const QVector<glm::vec3>& strokeColors, - const bool isUVModeStretch, - const float textureAspectRatio) { - // Calculate the minimum vector size out of normals, points, and stroke widths - int size = std::min(points.size(), std::min(normals.size(), strokeWidths.size())); - - std::vector<Vertex> vertices; - - // Guard against an empty polyline - if (size <= 0) { - return vertices; - } - - float uCoordInc = 1.0f / size; - float uCoord = 0.0f; - int finalIndex = size - 1; - glm::vec3 binormal; - float accumulatedDistance = 0.0f; - float distanceToLastPoint = 0.0f; - float accumulatedStrokeWidth = 0.0f; - float strokeWidth = 0.0f; bool doesStrokeWidthVary = false; - - - for (int i = 1; i < strokeWidths.size(); i++) { - if (strokeWidths[i] != strokeWidths[i - 1]) { - doesStrokeWidthVary = true; - break; + if (_widths.size() >= 0) { + for (int i = 1; i < maxNumVertices; i++) { + float width = PolyLineEntityItem::DEFAULT_LINE_WIDTH; + if (i < _widths.length()) { + width = _widths[i]; + } + if (width != _widths[i - 1]) { + doesStrokeWidthVary = true; + break; + } } } - for (int i = 0; i <= finalIndex; i++) { - const float& width = strokeWidths.at(i); - const auto& point = points.at(i); - const auto& normal = normals.at(i); - const auto& color = strokeColors.size() == normals.size() ? strokeColors.at(i) : strokeColors.at(0); - int vertexIndex = i * 2; - + float uCoordInc = 1.0f / maxNumVertices; + float uCoord = 0.0f; + float accumulatedDistance = 0.0f; + float accumulatedStrokeWidth = 0.0f; + glm::vec3 binormal; - if (!isUVModeStretch && i >= 1) { - distanceToLastPoint = glm::distance(points.at(i), points.at(i - 1)); - accumulatedDistance += distanceToLastPoint; - strokeWidth = 2 * strokeWidths[i]; + std::vector<PolylineVertex> vertices; + vertices.reserve(maxNumVertices); + for (int i = 0; i < maxNumVertices; i++) { + // Position + glm::vec3 point = _points[i]; - if (doesStrokeWidthVary) { - //If the stroke varies along the line the texture will stretch more or less depending on the speed - //because it looks better than using the same method as below - accumulatedStrokeWidth += strokeWidth; - float increaseValue = 1; - if (accumulatedStrokeWidth != 0) { - float newUcoord = glm::ceil(((1.0f / textureAspectRatio) * accumulatedDistance) / (accumulatedStrokeWidth / i)); - increaseValue = newUcoord - uCoord; + // uCoord + float width = i < _widths.size() ? _widths[i] : PolyLineEntityItem::DEFAULT_LINE_WIDTH; + if (i > 0) { // First uCoord is 0.0f + if (!_isUVModeStretch) { + accumulatedDistance += glm::distance(point, _points[i - 1]); + + if (doesStrokeWidthVary) { + //If the stroke varies along the line the texture will stretch more or less depending on the speed + //because it looks better than using the same method as below + accumulatedStrokeWidth += width; + float increaseValue = 1; + if (accumulatedStrokeWidth != 0) { + float newUcoord = glm::ceil((_textureAspectRatio * accumulatedDistance) / (accumulatedStrokeWidth / i)); + increaseValue = newUcoord - uCoord; + } + + increaseValue = increaseValue > 0 ? increaseValue : 1; + uCoord += increaseValue; + } else { + // If the stroke width is constant then the textures should keep the aspect ratio along the line + uCoord = (_textureAspectRatio * accumulatedDistance) / width; } - - increaseValue = increaseValue > 0 ? increaseValue : 1; - uCoord += increaseValue; } else { - //If the stroke width is constant then the textures should keep the aspect ratio along the line - uCoord = ((1.0f / textureAspectRatio) * accumulatedDistance) / strokeWidth; + uCoord += uCoordInc; } - } else if (vertexIndex >= 2) { - uCoord += uCoordInc; } + // Color + glm::vec3 color = i < _colors.length() ? _colors[i] : _color; + + // Normal + glm::vec3 normal = _normals[i]; + + // Binormal // For last point we can assume binormals are the same since it represents the last two vertices of quad - if (i < finalIndex) { - const auto tangent = points.at(i + 1) - point; - binormal = glm::normalize(glm::cross(tangent, normal)) * width; + if (i < maxNumVertices - 1) { + glm::vec3 tangent = _points[i + 1] - point; + binormal = glm::normalize(glm::cross(tangent, normal)); // Check to make sure binormal is not a NAN. If it is, don't add to vertices vector if (binormal.x != binormal.x) { @@ -239,54 +224,36 @@ std::vector<PolyLineEntityRenderer::Vertex> PolyLineEntityRenderer::updateVertic } } - const auto v1 = points.at(i) + binormal; - const auto v2 = points.at(i) - binormal; - vertices.emplace_back(v1, normal, vec2(uCoord, 0.0f), color); - vertices.emplace_back(v2, normal, vec2(uCoord, 1.0f), color); + PolylineVertex vertex = { glm::vec4(point, uCoord), glm::vec4(color, 1.0f), glm::vec4(normal, 0.0f), glm::vec4(binormal, 0.5f * width) }; + vertices.push_back(vertex); } - return vertices; + _numVertices = vertices.size(); + _polylineGeometryBuffer->setData(vertices.size() * sizeof(PolylineVertex), (const gpu::Byte*) vertices.data()); } -scriptable::ScriptableModelBase PolyLineEntityRenderer::getScriptableModel() { - // TODO: adapt polyline into a triangles mesh... - return EntityRenderer::getScriptableModel(); +void PolyLineEntityRenderer::updateData() { + PolylineData data { glm::vec2(_faceCamera, _glow), glm::vec2(0.0f) }; + _polylineDataBuffer->setSubData(0, data); } void PolyLineEntityRenderer::doRender(RenderArgs* args) { - if (_empty) { + if (_numVertices < 2) { return; } PerformanceTimer perfTimer("RenderablePolyLineEntityItem::render"); Q_ASSERT(args->_batch); - gpu::Batch& batch = *args->_batch; - batch.setModelTransform(_polylineTransform); - if (_texture && _texture->isLoaded()) { - batch.setResourceTexture(PAINTSTROKE_TEXTURE_SLOT, _texture->getGPUTexture()); - } else { - batch.setResourceTexture(PAINTSTROKE_TEXTURE_SLOT, DependencyManager::get<TextureCache>()->getWhiteTexture()); + if (!_pipeline) { + buildPipeline(); } - float textureWidth = (float)_texture->getOriginalWidth(); - float textureHeight = (float)_texture->getOriginalHeight(); - if (textureWidth != 0 && textureHeight != 0) { - _textureAspectRatio = textureWidth / textureHeight; - } - - batch.setInputFormat(polylineFormat); - batch.setInputBuffer(0, _verticesBuffer, 0, sizeof(Vertex)); - -#ifndef POLYLINE_ENTITY_USE_FADE_EFFECT - // glColor4f must be called after setInputFormat if it must be taken into account - if (_isFading) { - batch._glColor4f(1.0f, 1.0f, 1.0f, Interpolate::calculateFadeRatio(_fadeStartTime)); - } else { - batch._glColor4f(1.0f, 1.0f, 1.0f, 1.0f); - } -#endif - - batch.draw(gpu::TRIANGLE_STRIP, _numVertices, 0); + batch.setPipeline(_pipeline); + batch.setModelTransform(_renderTransform); + batch.setResourceTexture(0, _textureLoaded ? _texture->getGPUTexture() : DependencyManager::get<TextureCache>()->getWhiteTexture()); + batch.setResourceBuffer(0, _polylineGeometryBuffer); + batch.setUniformBuffer(0, _polylineDataBuffer); + batch.draw(gpu::TRIANGLE_STRIP, (gpu::uint32)(2 * _numVertices), 0); } diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h index 8130171da8..fd37a49598 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h @@ -25,52 +25,40 @@ class PolyLineEntityRenderer : public TypedEntityRenderer<PolyLineEntityItem> { public: PolyLineEntityRenderer(const EntityItemPointer& entity); - virtual scriptable::ScriptableModelBase getScriptableModel() override; + // FIXME: shouldn't always be transparent: take into account texture and glow + virtual bool isTransparent() const override { return true; } + protected: + virtual bool needsRenderUpdate() const override; virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override; - virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, - Transaction& transaction, - const TypedEntityPointer& entity) override; virtual void doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) override; virtual ItemKey getKey() override; virtual ShapeKey getShapeKey() override; virtual void doRender(RenderArgs* args) override; - virtual bool isTransparent() const override { return true; } + void buildPipeline(); + void updateGeometry(); + void updateData(); - struct Vertex { - Vertex() {} - Vertex(const vec3& position, const vec3& normal, const vec2& uv, const vec3& color) : position(position), - normal(normal), - uv(uv), - color(color) {} - vec3 position; - vec3 normal; - vec2 uv; - vec3 color; - }; + QVector<glm::vec3> _points; + QVector<glm::vec3> _normals; + QVector<glm::vec3> _colors; + glm::vec3 _color; + QVector<float> _widths; - void updateGeometry(const std::vector<Vertex>& vertices); - static std::vector<Vertex> updateVertices(const QVector<glm::vec3>& points, - const QVector<glm::vec3>& normals, - const QVector<float>& strokeWidths, - const QVector<glm::vec3>& strokeColors, - const bool isUVModeStretch, - const float textureAspectRatio); - - Transform _polylineTransform; - QVector<glm::vec3> _lastPoints; - QVector<glm::vec3> _lastNormals; - QVector<glm::vec3> _lastStrokeColors; - QVector<float> _lastStrokeWidths; - gpu::BufferPointer _verticesBuffer; - - uint32_t _numVertices { 0 }; - bool _empty{ true }; NetworkTexturePointer _texture; float _textureAspectRatio { 1.0f }; + bool _textureLoaded { false }; + bool _isUVModeStretch; + bool _faceCamera; + bool _glow; + + size_t _numVertices; + gpu::BufferPointer _polylineDataBuffer; + gpu::BufferPointer _polylineGeometryBuffer; + static gpu::PipelinePointer _pipeline; }; } } // namespace diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index a705b61cd3..1569c75eec 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -38,18 +38,10 @@ ShapeEntityRenderer::ShapeEntityRenderer(const EntityItemPointer& entity) : Pare // FIXME: Setup proper uniform slots and use correct pipelines for forward rendering _procedural._opaqueFragmentSource = gpu::Shader::Source::get(shader::render_utils::fragment::simple); _procedural._transparentFragmentSource = gpu::Shader::Source::get(shader::render_utils::fragment::simple_transparent); - _procedural._opaqueState->setCullMode(gpu::State::CULL_NONE); - _procedural._opaqueState->setDepthTest(true, true, gpu::LESS_EQUAL); + + // TODO: move into Procedural.cpp PrepareStencil::testMaskDrawShape(*_procedural._opaqueState); - _procedural._opaqueState->setBlendFunction(false, - gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, - gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - _procedural._transparentState->setCullMode(gpu::State::CULL_BACK); - _procedural._transparentState->setDepthTest(true, true, gpu::LESS_EQUAL); PrepareStencil::testMask(*_procedural._transparentState); - _procedural._transparentState->setBlendFunction(true, - gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, - gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); } bool ShapeEntityRenderer::needsRenderUpdate() const { @@ -139,21 +131,6 @@ bool ShapeEntityRenderer::isTransparent() const { return Parent::isTransparent(); } -ItemKey ShapeEntityRenderer::getKey() { - ItemKey::Builder builder; - builder.withTypeShape().withTypeMeta().withTagBits(getTagMask()); - - withReadLock([&] { - if (isTransparent()) { - builder.withTransparent(); - } else if (_canCastShadow) { - builder.withShadowCaster(); - } - }); - - return builder.build(); -} - bool ShapeEntityRenderer::useMaterialPipeline() const { bool proceduralReady = resultWithReadLock<bool>([&] { return _procedural.isReady(); @@ -212,7 +189,10 @@ ShapeKey ShapeEntityRenderer::getShapeKey() { return builder.build(); } else { ShapeKey::Builder builder; - if (_procedural.isReady()) { + bool proceduralReady = resultWithReadLock<bool>([&] { + return _procedural.isReady(); + }); + if (proceduralReady) { builder.withOwnPipeline(); } if (isTransparent()) { @@ -242,7 +222,7 @@ void ShapeEntityRenderer::doRender(RenderArgs* args) { if (_procedural.isReady()) { outColor = _procedural.getColor(outColor); outColor.a *= _procedural.isFading() ? Interpolate::calculateFadeRatio(_procedural.getFadeStartTime()) : 1.0f; - _procedural.prepare(batch, _position, _dimensions, _orientation, outColor); + _procedural.prepare(batch, _position, _dimensions, _orientation, ProceduralProgramKey(outColor.a < 1.0f)); proceduralRender = true; } } diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.h b/libraries/entities-renderer/src/RenderableShapeEntityItem.h index 7700aa6ef0..a33f023213 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.h +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.h @@ -25,7 +25,6 @@ public: virtual scriptable::ScriptableModelBase getScriptableModel() override; protected: - ItemKey getKey() override; ShapeKey getShapeKey() override; private: diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index ce9e7ab764..4ddb398fbf 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -15,7 +15,6 @@ #include <GeometryCache.h> #include <PerfStat.h> #include <Transform.h> -#include <TextEntityItem.h> #include <TextRenderer3D.h> #include "GLMHelpers.h" @@ -28,7 +27,10 @@ static const int FIXED_FONT_POINT_SIZE = 40; TextEntityRenderer::TextEntityRenderer(const EntityItemPointer& entity) : Parent(entity), _textRenderer(TextRenderer3D::getInstance(SANS_FONT_FAMILY, FIXED_FONT_POINT_SIZE / 2.0f)) { - + auto geometryCache = DependencyManager::get<GeometryCache>(); + if (geometryCache) { + _geometryID = geometryCache->allocateID(); + } } TextEntityRenderer::~TextEntityRenderer() { @@ -38,6 +40,18 @@ TextEntityRenderer::~TextEntityRenderer() { } } +bool TextEntityRenderer::isTransparent() const { + return Parent::isTransparent() || _textAlpha < 1.0f || _backgroundAlpha < 1.0f; +} + +ShapeKey TextEntityRenderer::getShapeKey() { + auto builder = render::ShapeKey::Builder().withOwnPipeline(); + if (isTransparent()) { + builder.withTranslucent(); + } + return builder.build(); +} + bool TextEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const { if (_text != entity->getText()) { return true; @@ -51,17 +65,42 @@ bool TextEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoint return true; } + if (_textAlpha != entity->getTextAlpha()) { + return true; + } + if (_backgroundColor != toGlm(entity->getBackgroundColor())) { return true; } + if (_backgroundAlpha != entity->getBackgroundAlpha()) { + return true; + } + if (_dimensions != entity->getScaledDimensions()) { return true; } - if (_faceCamera != entity->getFaceCamera()) { + if (_billboardMode != entity->getBillboardMode()) { return true; } + + if (_leftMargin != entity->getLeftMargin()) { + return true; + } + + if (_rightMargin != entity->getRightMargin()) { + return true; + } + + if (_topMargin != entity->getTopMargin()) { + return true; + } + + if (_bottomMargin != entity->getBottomMargin()) { + return true; + } + return false; } @@ -77,14 +116,19 @@ void TextEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen } void TextEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { - _textColor = toGlm(entity->getTextColor()); - _backgroundColor = toGlm(entity->getBackgroundColor()); - _faceCamera = entity->getFaceCamera(); - _lineHeight = entity->getLineHeight(); _text = entity->getText(); + _lineHeight = entity->getLineHeight(); + _textColor = toGlm(entity->getTextColor()); + _textAlpha = entity->getTextAlpha(); + _backgroundColor = toGlm(entity->getBackgroundColor()); + _backgroundAlpha = entity->getBackgroundAlpha(); + _billboardMode = entity->getBillboardMode(); + _leftMargin = entity->getLeftMargin(); + _rightMargin = entity->getRightMargin(); + _topMargin = entity->getTopMargin(); + _bottomMargin = entity->getBottomMargin(); } - void TextEntityRenderer::doRender(RenderArgs* args) { PerformanceTimer perfTimer("RenderableTextEntityItem::render"); @@ -94,47 +138,56 @@ void TextEntityRenderer::doRender(RenderArgs* args) { modelTransform = _renderTransform; dimensions = _dimensions; }); - static const float SLIGHTLY_BEHIND = -0.005f; + float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; - bool transparent = fadeRatio < 1.0f; - glm::vec4 textColor = glm::vec4(_textColor, fadeRatio); - glm::vec4 backgroundColor = glm::vec4(_backgroundColor, fadeRatio); + glm::vec4 textColor = glm::vec4(_textColor, fadeRatio * _textAlpha); + glm::vec4 backgroundColor = glm::vec4(_backgroundColor, fadeRatio * _backgroundAlpha); // Render background + static const float SLIGHTLY_BEHIND = -0.005f; glm::vec3 minCorner = glm::vec3(0.0f, -dimensions.y, SLIGHTLY_BEHIND); glm::vec3 maxCorner = glm::vec3(dimensions.x, 0.0f, SLIGHTLY_BEHIND); - // Batch render calls Q_ASSERT(args->_batch); gpu::Batch& batch = *args->_batch; auto transformToTopLeft = modelTransform; - if (_faceCamera) { + if (_billboardMode == BillboardMode::YAW) { //rotate about vertical to face the camera glm::vec3 dPosition = args->getViewFrustum().getPosition() - modelTransform.getTranslation(); // If x and z are 0, atan(x, z) is undefined, so default to 0 degrees float yawRotation = dPosition.x == 0.0f && dPosition.z == 0.0f ? 0.0f : glm::atan(dPosition.x, dPosition.z); glm::quat orientation = glm::quat(glm::vec3(0.0f, yawRotation, 0.0f)); transformToTopLeft.setRotation(orientation); + } else if (_billboardMode == BillboardMode::FULL) { + glm::vec3 billboardPos = transformToTopLeft.getTranslation(); + glm::vec3 cameraPos = args->getViewFrustum().getPosition(); + // use the referencial from the avatar, y isn't always up + glm::vec3 avatarUP = EntityTreeRenderer::getAvatarUp(); + // check to see if glm::lookAt will work / using glm::lookAt variable name + glm::highp_vec3 s(glm::cross(billboardPos - cameraPos, avatarUP)); + + // make sure s is not NaN for any component + if (glm::length2(s) > 0.0f) { + glm::quat rotation(conjugate(toQuat(glm::lookAt(cameraPos, billboardPos, avatarUP)))); + transformToTopLeft.setRotation(rotation); + } } transformToTopLeft.postTranslate(dimensions * glm::vec3(-0.5f, 0.5f, 0.0f)); // Go to the top left transformToTopLeft.setScale(1.0f); // Use a scale of one so that the text is not deformed batch.setModelTransform(transformToTopLeft); auto geometryCache = DependencyManager::get<GeometryCache>(); - if (!_geometryID) { - _geometryID = geometryCache->allocateID(); - } - geometryCache->bindSimpleProgram(batch, false, transparent, false, false, false); + geometryCache->bindSimpleProgram(batch, false, backgroundColor.a < 1.0f, false, false, false); geometryCache->renderQuad(batch, minCorner, maxCorner, backgroundColor, _geometryID); + // FIXME: Factor out textRenderer so that Text3DOverlay overlay parts can be grouped by pipeline for a gpu performance increase. float scale = _lineHeight / _textRenderer->getFontSize(); transformToTopLeft.setScale(scale); // Scale to have the correct line height batch.setModelTransform(transformToTopLeft); - float leftMargin = 0.1f * _lineHeight, topMargin = 0.1f * _lineHeight; - glm::vec2 bounds = glm::vec2(dimensions.x - 2.0f * leftMargin, - dimensions.y - 2.0f * topMargin); - _textRenderer->draw(batch, leftMargin / scale, -topMargin / scale, _text, textColor, bounds / scale); + glm::vec2 bounds = glm::vec2(dimensions.x - (_leftMargin + _rightMargin), + dimensions.y - (_topMargin + _bottomMargin)); + _textRenderer->draw(batch, _leftMargin / scale, -_topMargin / scale, _text, textColor, bounds / scale); } diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.h b/libraries/entities-renderer/src/RenderableTextEntityItem.h index ac7f2b620f..6b20705209 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.h +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.h @@ -25,19 +25,33 @@ class TextEntityRenderer : public TypedEntityRenderer<TextEntityItem> { public: TextEntityRenderer(const EntityItemPointer& entity); ~TextEntityRenderer(); + + bool isTransparent() const override; + ShapeKey getShapeKey() override; + private: virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override; virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override; virtual void doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) override; virtual void doRender(RenderArgs* args) override; + int _geometryID{ 0 }; std::shared_ptr<TextRenderer3D> _textRenderer; - bool _faceCamera; - glm::vec3 _dimensions; - glm::vec3 _textColor; - glm::vec3 _backgroundColor; + QString _text; float _lineHeight; + glm::vec3 _textColor; + float _textAlpha; + glm::vec3 _backgroundColor; + float _backgroundAlpha; + + float _leftMargin; + float _rightMargin; + float _topMargin; + float _bottomMargin; + + BillboardMode _billboardMode; + glm::vec3 _dimensions; }; } } diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 476372160e..2942de0ba4 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -55,6 +55,7 @@ WebEntityRenderer::ContentType WebEntityRenderer::getContentType(const QString& const QUrl url(urlString); auto scheme = url.scheme(); if (scheme == HIFI_URL_SCHEME_ABOUT || scheme == HIFI_URL_SCHEME_HTTP || scheme == HIFI_URL_SCHEME_HTTPS || + scheme == URL_SCHEME_DATA || urlString.toLower().endsWith(".htm") || urlString.toLower().endsWith(".html")) { return ContentType::HtmlContent; } diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index 2c017da71d..57ff8ed8c2 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -27,8 +27,6 @@ // Sphere entities should fit inside a cube entity of the same size, so a sphere that has dimensions 1x1x1 // is a half unit sphere. However, the geometry cache renders a UNIT sphere, so we need to scale down. static const float SPHERE_ENTITY_SCALE = 0.5f; -static const unsigned int SUN_SHADOW_CASCADE_COUNT{ 4 }; -static const float SUN_SHADOW_MAX_DISTANCE{ 40.0f }; using namespace render; using namespace render::entities; @@ -43,7 +41,6 @@ void ZoneEntityRenderer::onRemoveFromSceneTyped(const TypedEntityPointer& entity if (!LightStage::isIndexInvalid(_sunIndex)) { _stage->removeLight(_sunIndex); _sunIndex = INVALID_INDEX; - _shadowIndex = INVALID_INDEX; } if (!LightStage::isIndexInvalid(_ambientIndex)) { _stage->removeLight(_ambientIndex); @@ -74,36 +71,6 @@ void ZoneEntityRenderer::onRemoveFromSceneTyped(const TypedEntityPointer& entity } void ZoneEntityRenderer::doRender(RenderArgs* args) { -#if 0 - if (ZoneEntityItem::getDrawZoneBoundaries()) { - switch (_entity->getShapeType()) { - case SHAPE_TYPE_BOX: - case SHAPE_TYPE_SPHERE: - { - PerformanceTimer perfTimer("zone->renderPrimitive"); - static const glm::vec4 DEFAULT_COLOR(1.0f, 1.0f, 1.0f, 1.0f); - if (!updateModelTransform()) { - break; - } - auto geometryCache = DependencyManager::get<GeometryCache>(); - gpu::Batch& batch = *args->_batch; - batch.setModelTransform(_modelTransform); - if (_entity->getShapeType() == SHAPE_TYPE_SPHERE) { - geometryCache->renderWireSphereInstance(args, batch, DEFAULT_COLOR); - } else { - geometryCache->renderWireCubeInstance(args, batch, DEFAULT_COLOR); - } - } - break; - - // Compund shapes are handled by the _model member - case SHAPE_TYPE_COMPOUND: - default: - // Not handled - break; - } - } -#endif if (!_stage) { _stage = args->_scene->getStage<LightStage>(); assert(_stage); @@ -130,7 +97,6 @@ void ZoneEntityRenderer::doRender(RenderArgs* args) { // Do we need to allocate the light in the stage ? if (LightStage::isIndexInvalid(_sunIndex)) { _sunIndex = _stage->addLight(_sunLight); - _shadowIndex = _stage->addShadow(_sunIndex, SUN_SHADOW_MAX_DISTANCE, SUN_SHADOW_CASCADE_COUNT); } else { _stage->updateLightArrayBuffer(_sunIndex); } @@ -580,22 +546,3 @@ void ZoneEntityRenderer::setProceduralUserData(const QString& userData) { } } -#if 0 -bool RenderableZoneEntityItem::contains(const glm::vec3& point) const { - if (getShapeType() != SHAPE_TYPE_COMPOUND) { - return EntityItem::contains(point); - } - const_cast<RenderableZoneEntityItem*>(this)->updateGeometry(); - - if (_model && _model->isActive() && EntityItem::contains(point)) { - return _model->convexHullContains(point); - } - - return false; -} - -void RenderableZoneEntityItem::notifyBoundChanged() { - notifyChangedRenderItem(); -} - -#endif diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.h b/libraries/entities-renderer/src/RenderableZoneEntityItem.h index 3e2690e1bd..32b5cf94a0 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.h +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.h @@ -99,7 +99,6 @@ private: ComponentMode _bloomMode { COMPONENT_MODE_INHERIT }; indexed_container::Index _sunIndex { LightStage::INVALID_INDEX }; - indexed_container::Index _shadowIndex { LightStage::INVALID_INDEX }; indexed_container::Index _ambientIndex { LightStage::INVALID_INDEX }; BackgroundStagePointer _backgroundStage; diff --git a/libraries/entities-renderer/src/paintStroke.slf b/libraries/entities-renderer/src/paintStroke.slf index f2c0d5572d..6ea088751f 100644 --- a/libraries/entities-renderer/src/paintStroke.slf +++ b/libraries/entities-renderer/src/paintStroke.slf @@ -14,21 +14,27 @@ <@include DeferredBufferWrite.slh@> -// the albedo texture -LAYOUT(binding=0) uniform sampler2D originalTexture; +<@include paintStroke.slh@> +<$declarePolyLineBuffers()$> -// the interpolated normal -layout(location=0) in vec3 interpolatedNormal; -layout(location=1) in vec2 varTexcoord; -layout(location=2) in vec4 varColor; +LAYOUT(binding=0) uniform sampler2D _texture; + +layout(location=0) in vec3 _normalWS; +layout(location=1) in vec2 _texCoord; +layout(location=2) in vec4 _color; +layout(location=3) in float _distanceFromCenter; void main(void) { - vec4 texel = texture(originalTexture, varTexcoord); - int frontCondition = 1 -int(gl_FrontFacing) * 2; - vec3 color = varColor.rgb; + vec4 texel = texture(_texture, _texCoord); + int frontCondition = 1 - 2 * int(gl_FrontFacing); + vec3 color = _color.rgb * texel.rgb; + float alpha = texel.a * _color.a; + + alpha *= mix(1.0, pow(1.0 - abs(_distanceFromCenter), 10.0), _polylineData.faceCameraGlow.y); + packDeferredFragmentTranslucent( - float(frontCondition) * interpolatedNormal, - texel.a * varColor.a, - color * texel.rgb, - 10.0); + float(frontCondition) * _normalWS, + alpha, + color, + DEFAULT_ROUGHNESS); } diff --git a/libraries/entities-renderer/src/paintStroke.slh b/libraries/entities-renderer/src/paintStroke.slh new file mode 100644 index 0000000000..6189ac461b --- /dev/null +++ b/libraries/entities-renderer/src/paintStroke.slh @@ -0,0 +1,48 @@ +<! +// paintStroke.slh +// +// Created by Sam Gondelman on 12/13/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 +!> + +<@if not PAINTSTROKE_SLH@> +<@def PAINTSTROKE_SLH@> + +<@include paintStroke_Shared.slh@> +<@include gpu/ShaderConstants.h@> + +<@func declarePolyLineBuffers() @> + +// Hack comment to absorb the extra '//' scribe prepends + +#if !defined(GPU_SSBO_TRANSFORM_OBJECT) +LAYOUT(binding=GPU_RESOURCE_BUFFER_SLOT0_TEXTURE) uniform samplerBuffer polylineVerticesBuffer; +PolylineVertex getPolylineVertex(int i) { + int offset = 4 * i; + PolylineVertex vertex; + vertex.positionAndUCoord = texelFetch(polylineVerticesBuffer, offset); + vertex.color = texelFetch(polylineVerticesBuffer, offset + 1); + vertex.normal = texelFetch(polylineVerticesBuffer, offset + 2); + vertex.binormalAndHalfWidth = texelFetch(polylineVerticesBuffer, offset + 3); + return vertex; +} +#else +LAYOUT_STD140(binding=GPU_RESOURCE_BUFFER_SLOT0_STORAGE) buffer polylineVerticesBuffer { + PolylineVertex _vertices[]; +}; +PolylineVertex getPolylineVertex(int i) { + PolylineVertex vertex = _vertices[i]; + return vertex; +} +#endif + +LAYOUT_STD140(binding=0) uniform polylineDataBuffer { + PolylineData _polylineData; +}; + +<@endfunc@> + +<@endif@> diff --git a/libraries/entities-renderer/src/paintStroke.slv b/libraries/entities-renderer/src/paintStroke.slv index ecf52d61cf..c033d2c247 100644 --- a/libraries/entities-renderer/src/paintStroke.slv +++ b/libraries/entities-renderer/src/paintStroke.slv @@ -17,23 +17,45 @@ <@include gpu/Transform.slh@> <$declareStandardTransform()$> -// the interpolated normal -layout(location=0) out vec3 interpolatedNormal; +<@include paintStroke.slh@> +<$declarePolyLineBuffers()$> -//the diffuse texture -layout(location=1) out vec2 varTexcoord; - -layout(location=2) out vec4 varColor; +layout(location=0) out vec3 _normalWS; +layout(location=1) out vec2 _texCoord; +layout(location=2) out vec4 _color; +layout(location=3) out float _distanceFromCenter; void main(void) { - varTexcoord = inTexCoord0.st; + PolylineVertex vertex = getPolylineVertex(gl_VertexID / 2); + float evenVertex = float(gl_VertexID % 2 == 0); - // pass along the diffuse color - varColor = color_sRGBAToLinear(inColor); + _texCoord = vec2(vertex.positionAndUCoord.w, mix(1.0, 0.0, evenVertex)); + _color = color_sRGBAToLinear(vertex.color); - // standard transform TransformCamera cam = getTransformCamera(); TransformObject obj = getTransformObject(); - <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$> - <$transformModelToEyeDir(cam, obj, inNormal.xyz, interpolatedNormal)$> + _distanceFromCenter = -1.0 + 2.0 * evenVertex; + vec4 position = vec4(vertex.positionAndUCoord.xyz, 1.0); + vec3 normal = vertex.normal.xyz; + vec3 binormal = vertex.binormalAndHalfWidth.xyz; + if (_polylineData.faceCameraGlow.x != 0.0) { + vec4 posEye; + vec3 normalEye; + vec3 binormalEye; + <$transformModelToEyePos(cam, obj, position, posEye)$> + <$transformModelToEyeDir(cam, obj, normal, normalEye)$> + <$transformModelToEyeDir(cam, obj, binormal, binormalEye)$> + + vec3 tangentEye = cross(binormalEye, normalEye); + // new normal faces the camera + normalEye = normalize(posEye.xyz); + binormalEye = normalize(cross(normalEye, tangentEye)); + posEye.xyz += _distanceFromCenter * vertex.binormalAndHalfWidth.w * binormalEye; + <$transformEyeToClipPos(cam, posEye, gl_Position)$> + <$transformEyeToWorldDir(cam, normalEye, _normalWS)$> + } else { + position.xyz += _distanceFromCenter * vertex.binormalAndHalfWidth.w * binormal; + <$transformModelToClipPos(cam, obj, position, gl_Position)$> + <$transformModelToWorldDir(cam, obj, normal, _normalWS)$> + } } \ No newline at end of file diff --git a/libraries/entities-renderer/src/paintStroke_Shared.slh b/libraries/entities-renderer/src/paintStroke_Shared.slh new file mode 100644 index 0000000000..52c10df99b --- /dev/null +++ b/libraries/entities-renderer/src/paintStroke_Shared.slh @@ -0,0 +1,25 @@ +// glsl / C++ compatible source as interface for FadeEffect +#ifdef __cplusplus +# define _PL_VEC4 glm::vec4 +# define _PL_VEC2 glm::vec2 +#else +# define _PL_VEC4 vec4 +# define _PL_VEC2 vec2 +#endif + +struct PolylineVertex { + _PL_VEC4 positionAndUCoord; + _PL_VEC4 color; + _PL_VEC4 normal; + _PL_VEC4 binormalAndHalfWidth; +}; + +struct PolylineData { + _PL_VEC2 faceCameraGlow; + _PL_VEC2 spare; +}; + +// <@if 1@> +// Trigger Scribe include +// <@endif@> <!def that !> +// \ No newline at end of file diff --git a/libraries/entities-renderer/src/paintStroke_fade.slf b/libraries/entities-renderer/src/paintStroke_fade.slf deleted file mode 100644 index fa6d0aab75..0000000000 --- a/libraries/entities-renderer/src/paintStroke_fade.slf +++ /dev/null @@ -1,52 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// -// paintStroke_fade.frag -// fragment shader -// -// Created by Olivier Prat on 19/07/17. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -<@include DeferredBufferWrite.slh@> - -<@include Fade.slh@> -<$declareFadeFragment()$> - -// the albedo texture -LAYOUT(binding=0) uniform sampler2D originalTexture; - -// the interpolated normal -layout(location=0) in vec3 interpolatedNormal; -layout(location=1) in vec2 varTexcoord; -layout(location=2) in vec4 varColor; -layout(location=3) in vec4 _worldPosition; - -struct PolyLineUniforms { - vec3 color; -}; - -LAYOUT(binding=0) uniform polyLineBuffer { - PolyLineUniforms polyline; -}; - -void main(void) { - vec3 fadeEmissive; - FadeObjectParams fadeParams; - - <$fetchFadeObjectParams(fadeParams)$> - applyFade(fadeParams, _worldPosition.xyz, fadeEmissive); - - vec4 texel = texture(originalTexture, varTexcoord); - int frontCondition = 1 -int(gl_FrontFacing) * 2; - vec3 color = varColor.rgb; - packDeferredFragmentTranslucent( - interpolatedNormal * float(frontCondition), - texel.a * varColor.a, - polyline.color * texel.rgb + fadeEmissive, - 10.0); -} diff --git a/libraries/entities-renderer/src/paintStroke_fade.slv b/libraries/entities-renderer/src/paintStroke_fade.slv deleted file mode 100644 index f6fcb18c98..0000000000 --- a/libraries/entities-renderer/src/paintStroke_fade.slv +++ /dev/null @@ -1,43 +0,0 @@ -<@include gpu/Config.slh@> -<$VERSION_HEADER$> -// Generated on <$_SCRIBE_DATE$> -// -// paintStroke_fade.vert -// vertex shader -// -// Created by Olivier Prat on 19/07/17. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -<@include gpu/Inputs.slh@> -<@include gpu/Color.slh@> -<@include gpu/Transform.slh@> -<$declareStandardTransform()$> - -// the interpolated normal -layout(location=0) out vec3 interpolatedNormal; - -//the diffuse texture -layout(location=1) out vec2 varTexcoord; - -layout(location=2) out vec4 varColor; -layout(location=3) out vec4 _worldPosition; - -void main(void) { - - varTexcoord = inTexCoord0.st; - - // pass along the diffuse color - varColor = color_sRGBAToLinear(inColor); - - - // standard transform - TransformCamera cam = getTransformCamera(); - TransformObject obj = getTransformObject(); - <$transformModelToClipPos(cam, obj, inPosition, gl_Position)$> - <$transformModelToEyeDir(cam, obj, inNormal.xyz, interpolatedNormal)$> - <$transformModelToWorldPos(obj, inPosition, _worldPosition)$> -} \ No newline at end of file diff --git a/libraries/entities/CMakeLists.txt b/libraries/entities/CMakeLists.txt index c51ae28d5f..fcbe563f88 100644 --- a/libraries/entities/CMakeLists.txt +++ b/libraries/entities/CMakeLists.txt @@ -6,4 +6,4 @@ include_hifi_library_headers(fbx) include_hifi_library_headers(gpu) include_hifi_library_headers(image) include_hifi_library_headers(ktx) -link_hifi_libraries(shared shaders networking octree avatars graphics model-networking) \ No newline at end of file +link_hifi_libraries(shared shaders networking octree avatars graphics model-networking) diff --git a/libraries/entities/src/AnimationPropertyGroup.cpp b/libraries/entities/src/AnimationPropertyGroup.cpp index cf031f2d0f..7e97787ff2 100644 --- a/libraries/entities/src/AnimationPropertyGroup.cpp +++ b/libraries/entities/src/AnimationPropertyGroup.cpp @@ -182,14 +182,29 @@ void AnimationPropertyGroup::listChangedProperties(QList<QString>& out) { if (urlChanged()) { out << "animation-url"; } + if (allowTranslationChanged()) { + out << "animation-allowTranslation"; + } if (fpsChanged()) { out << "animation-fps"; } if (currentFrameChanged()) { out << "animation-currentFrame"; } - if (allowTranslationChanged()) { - out << "animation-allowTranslation"; + if (runningChanged()) { + out << "animation-running"; + } + if (loopChanged()) { + out << "animation-loop"; + } + if (firstFrameChanged()) { + out << "animation-firstFrame"; + } + if (lastFrameChanged()) { + out << "animation-lastFrame"; + } + if (holdChanged()) { + out << "animation-hold"; } } @@ -224,7 +239,6 @@ bool AnimationPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyF READ_ENTITY_PROPERTY(PROP_ANIMATION_URL, QString, setURL); READ_ENTITY_PROPERTY(PROP_ANIMATION_ALLOW_TRANSLATION, bool, setAllowTranslation); - READ_ENTITY_PROPERTY(PROP_ANIMATION_FPS, float, setFPS); READ_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, float, setCurrentFrame); READ_ENTITY_PROPERTY(PROP_ANIMATION_PLAYING, bool, setRunning); @@ -234,6 +248,7 @@ bool AnimationPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyF READ_ENTITY_PROPERTY(PROP_ANIMATION_HOLD, bool, setHold); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ANIMATION_URL, URL); + DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ANIMATION_ALLOW_TRANSLATION, AllowTranslation); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ANIMATION_FPS, FPS); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ANIMATION_FRAME_INDEX, CurrentFrame); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ANIMATION_PLAYING, Running); @@ -241,7 +256,6 @@ bool AnimationPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyF DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ANIMATION_FIRST_FRAME, FirstFrame); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ANIMATION_LAST_FRAME, LastFrame); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ANIMATION_HOLD, Hold); - DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_ANIMATION_ALLOW_TRANSLATION, AllowTranslation); processedBytes += bytesRead; @@ -252,6 +266,7 @@ bool AnimationPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyF void AnimationPropertyGroup::markAllChanged() { _urlChanged = true; + _allowTranslationChanged = true; _fpsChanged = true; _currentFrameChanged = true; _runningChanged = true; @@ -259,13 +274,13 @@ void AnimationPropertyGroup::markAllChanged() { _firstFrameChanged = true; _lastFrameChanged = true; _holdChanged = true; - _allowTranslationChanged = true; } EntityPropertyFlags AnimationPropertyGroup::getChangedProperties() const { EntityPropertyFlags changedProperties; CHECK_PROPERTY_CHANGE(PROP_ANIMATION_URL, url); + CHECK_PROPERTY_CHANGE(PROP_ANIMATION_ALLOW_TRANSLATION, allowTranslation); CHECK_PROPERTY_CHANGE(PROP_ANIMATION_FPS, fps); CHECK_PROPERTY_CHANGE(PROP_ANIMATION_FRAME_INDEX, currentFrame); CHECK_PROPERTY_CHANGE(PROP_ANIMATION_PLAYING, running); @@ -273,7 +288,6 @@ EntityPropertyFlags AnimationPropertyGroup::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_ANIMATION_FIRST_FRAME, firstFrame); CHECK_PROPERTY_CHANGE(PROP_ANIMATION_LAST_FRAME, lastFrame); CHECK_PROPERTY_CHANGE(PROP_ANIMATION_HOLD, hold); - CHECK_PROPERTY_CHANGE(PROP_ANIMATION_ALLOW_TRANSLATION, allowTranslation); return changedProperties; } @@ -309,6 +323,7 @@ EntityPropertyFlags AnimationPropertyGroup::getEntityProperties(EncodeBitstreamP EntityPropertyFlags requestedProperties; requestedProperties += PROP_ANIMATION_URL; + requestedProperties += PROP_ANIMATION_ALLOW_TRANSLATION; requestedProperties += PROP_ANIMATION_FPS; requestedProperties += PROP_ANIMATION_FRAME_INDEX; requestedProperties += PROP_ANIMATION_PLAYING; @@ -316,7 +331,6 @@ EntityPropertyFlags AnimationPropertyGroup::getEntityProperties(EncodeBitstreamP requestedProperties += PROP_ANIMATION_FIRST_FRAME; requestedProperties += PROP_ANIMATION_LAST_FRAME; requestedProperties += PROP_ANIMATION_HOLD; - requestedProperties += PROP_ANIMATION_ALLOW_TRANSLATION; return requestedProperties; } diff --git a/libraries/entities/src/EntityDynamicInterface.h b/libraries/entities/src/EntityDynamicInterface.h index 6b82e7df73..836dae2057 100644 --- a/libraries/entities/src/EntityDynamicInterface.h +++ b/libraries/entities/src/EntityDynamicInterface.h @@ -50,6 +50,8 @@ public: const QUuid& getID() const { return _id; } EntityDynamicType getType() const { return _type; } + virtual void removeFromOwner() { } + virtual void remapIDs(QHash<EntityItemID, EntityItemID>& map) = 0; virtual bool isAction() const { return false; } diff --git a/libraries/entities/src/EntityEditPacketSender.cpp b/libraries/entities/src/EntityEditPacketSender.cpp index c414a7a4ac..af0e34303b 100644 --- a/libraries/entities/src/EntityEditPacketSender.cpp +++ b/libraries/entities/src/EntityEditPacketSender.cpp @@ -39,13 +39,12 @@ void EntityEditPacketSender::adjustEditPacketForClockSkew(PacketType type, QByte } } -void EntityEditPacketSender::queueEditAvatarEntityMessage(PacketType type, - EntityTreePointer entityTree, +void EntityEditPacketSender::queueEditAvatarEntityMessage(EntityTreePointer entityTree, EntityItemID entityItemID, const EntityItemProperties& properties) { assert(_myAvatar); if (!entityTree) { - qCDebug(entities) << "EntityEditPacketSender::queueEditEntityMessage null entityTree."; + qCDebug(entities) << "EntityEditPacketSender::queueEditAvatarEntityMessage null entityTree."; return; } EntityItemPointer entity = entityTree->findEntityByEntityItemID(entityItemID); @@ -53,33 +52,27 @@ void EntityEditPacketSender::queueEditAvatarEntityMessage(PacketType type, qCDebug(entities) << "EntityEditPacketSender::queueEditAvatarEntityMessage can't find entity: " << entityItemID; return; } + entity->setLastBroadcast(usecTimestampNow()); - // the properties that get serialized into the avatar identity packet should be the entire set + // serialize ALL properties in an "AvatarEntity" packet // rather than just the ones being edited. EntityItemProperties entityProperties = entity->getProperties(); entityProperties.merge(properties); - std::lock_guard<std::mutex> lock(_mutex); - QScriptValue scriptProperties = EntityItemNonDefaultPropertiesToScriptValue(&_scriptEngine, entityProperties); - QVariant variantProperties = scriptProperties.toVariant(); - QJsonDocument jsonProperties = QJsonDocument::fromVariant(variantProperties); + OctreePacketData packetData(false, AvatarTraits::MAXIMUM_TRAIT_SIZE); + EncodeBitstreamParams params; + EntityTreeElementExtraEncodeDataPointer extra { nullptr }; + OctreeElement::AppendState appendState = entity->appendEntityData(&packetData, params, extra); - // the ID of the parent/avatar changes from session to session. use a special UUID to indicate the avatar - QJsonObject jsonObject = jsonProperties.object(); - if (jsonObject.contains("parentID")) { - if (QUuid(jsonObject["parentID"].toString()) == _myAvatar->getID()) { - jsonObject["parentID"] = AVATAR_SELF_ID.toString(); - } + if (appendState != OctreeElement::COMPLETED) { + // this entity's payload is too big + return; } - jsonProperties = QJsonDocument(jsonObject); - QByteArray binaryProperties = jsonProperties.toBinaryData(); - _myAvatar->updateAvatarEntity(entityItemID, binaryProperties); - - entity->setLastBroadcast(usecTimestampNow()); + QByteArray tempArray((const char*)packetData.getUncompressedData(), packetData.getUncompressedSize()); + _myAvatar->storeAvatarEntityDataPayload(entityItemID, tempArray); } - void EntityEditPacketSender::queueEditEntityMessage(PacketType type, EntityTreePointer entityTree, EntityItemID entityItemID, @@ -89,7 +82,7 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type, qCWarning(entities) << "Suppressing entity edit message: cannot send avatar entity edit with no myAvatar"; } else if (properties.getOwningAvatarID() == _myAvatar->getID()) { // this is an avatar-based entity --> update our avatar-data rather than sending to the entity-server - queueEditAvatarEntityMessage(type, entityTree, entityItemID, properties); + queueEditAvatarEntityMessage(entityTree, entityItemID, properties); } else { qCWarning(entities) << "Suppressing entity edit message: cannot send avatar entity edit for another avatar"; } @@ -127,7 +120,13 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type, while (encodeResult == OctreeElement::PARTIAL) { encodeResult = EntityItemProperties::encodeEntityEditPacket(type, entityItemID, propertiesCopy, bufferOut, requestedProperties, didntFitProperties); - if (encodeResult != OctreeElement::NONE) { + if (encodeResult == OctreeElement::NONE) { + // This can happen for two reasons: + // 1. One of the properties is too large to fit in a single packet. + // 2. The requested properties don't exist in this entity type (e.g., 'modelUrl' in a Zone Entity). + // Since case #1 is more likely (and more critical), that's the one we warn about. + qCWarning(entities).nospace() << "queueEditEntityMessage: some of the properties don't fit and can't be sent. entityID=" << uuidStringWithoutCurlyBraces(entityItemID); + } else { #ifdef WANT_DEBUG qCDebug(entities) << "calling queueOctreeEditMessage()..."; qCDebug(entities) << " id:" << entityItemID; diff --git a/libraries/entities/src/EntityEditPacketSender.h b/libraries/entities/src/EntityEditPacketSender.h index 9bf9095f7f..a4ec2c45f9 100644 --- a/libraries/entities/src/EntityEditPacketSender.h +++ b/libraries/entities/src/EntityEditPacketSender.h @@ -50,7 +50,7 @@ public slots: void processEntityEditNackPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode); private: - void queueEditAvatarEntityMessage(PacketType type, EntityTreePointer entityTree, + void queueEditAvatarEntityMessage(EntityTreePointer entityTree, EntityItemID entityItemID, const EntityItemProperties& properties); private: diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 94d0024fd5..498f0ff066 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -20,6 +20,7 @@ #include <QtNetwork/QNetworkRequest> #include <glm/gtx/transform.hpp> +#include <glm/gtx/component_wise.hpp> #include <BufferParser.h> #include <ByteCountCoding.h> @@ -31,6 +32,8 @@ #include <SharedUtil.h> // usecTimestampNow() #include <LogHandler.h> #include <Extents.h> +#include <QVariantGLM.h> +#include <Grab.h> #include "EntityScriptingInterface.h" #include "EntitiesLogging.h" @@ -38,6 +41,7 @@ #include "EntitySimulation.h" #include "EntityDynamicFactoryInterface.h" + Q_DECLARE_METATYPE(EntityItemPointer); int entityItemPointernMetaTypeId = qRegisterMetaType<EntityItemPointer>(); @@ -70,33 +74,60 @@ EntityItem::~EntityItem() { EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties; + // Core requestedProperties += PROP_SIMULATION_OWNER; + requestedProperties += PROP_VISIBLE; + requestedProperties += PROP_NAME; + requestedProperties += PROP_LOCKED; + requestedProperties += PROP_USER_DATA; + requestedProperties += PROP_HREF; + requestedProperties += PROP_DESCRIPTION; requestedProperties += PROP_POSITION; + requestedProperties += PROP_DIMENSIONS; requestedProperties += PROP_ROTATION; + requestedProperties += PROP_REGISTRATION_POINT; + requestedProperties += PROP_CREATED; + requestedProperties += PROP_LAST_EDITED_BY; + //requestedProperties += PROP_ENTITY_HOST_TYPE; // not sent over the wire + //requestedProperties += PROP_OWNING_AVATAR_ID; // not sent over the wire + requestedProperties += PROP_PARENT_ID; + requestedProperties += PROP_PARENT_JOINT_INDEX; + requestedProperties += PROP_QUERY_AA_CUBE; + requestedProperties += PROP_CAN_CAST_SHADOW; + // requestedProperties += PROP_VISIBLE_IN_SECONDARY_CAMERA; // not sent over the wire + withReadLock([&] { + requestedProperties += _grabProperties.getEntityProperties(params); + }); + + // Physics + requestedProperties += PROP_DENSITY; requestedProperties += PROP_VELOCITY; requestedProperties += PROP_ANGULAR_VELOCITY; - requestedProperties += PROP_ACCELERATION; - - requestedProperties += PROP_DIMENSIONS; - requestedProperties += PROP_DENSITY; requestedProperties += PROP_GRAVITY; + requestedProperties += PROP_ACCELERATION; requestedProperties += PROP_DAMPING; + requestedProperties += PROP_ANGULAR_DAMPING; requestedProperties += PROP_RESTITUTION; requestedProperties += PROP_FRICTION; requestedProperties += PROP_LIFETIME; - requestedProperties += PROP_SCRIPT; - requestedProperties += PROP_SCRIPT_TIMESTAMP; - requestedProperties += PROP_SERVER_SCRIPTS; - requestedProperties += PROP_COLLISION_SOUND_URL; - requestedProperties += PROP_REGISTRATION_POINT; - requestedProperties += PROP_ANGULAR_DAMPING; - requestedProperties += PROP_VISIBLE; - requestedProperties += PROP_CAN_CAST_SHADOW; requestedProperties += PROP_COLLISIONLESS; requestedProperties += PROP_COLLISION_MASK; requestedProperties += PROP_DYNAMIC; - requestedProperties += PROP_LOCKED; - requestedProperties += PROP_USER_DATA; + requestedProperties += PROP_COLLISION_SOUND_URL; + requestedProperties += PROP_ACTION_DATA; + + // Cloning + requestedProperties += PROP_CLONEABLE; + requestedProperties += PROP_CLONE_LIFETIME; + requestedProperties += PROP_CLONE_LIMIT; + requestedProperties += PROP_CLONE_DYNAMIC; + requestedProperties += PROP_CLONE_AVATAR_ENTITY; + requestedProperties += PROP_CLONE_ORIGIN_ID; + + // Scripts + requestedProperties += PROP_SCRIPT; + requestedProperties += PROP_SCRIPT_TIMESTAMP; + requestedProperties += PROP_SERVER_SCRIPTS; // Certifiable properties requestedProperties += PROP_ITEM_NAME; @@ -111,30 +142,6 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_CERTIFICATE_ID; requestedProperties += PROP_STATIC_CERTIFICATE_VERSION; - requestedProperties += PROP_NAME; - requestedProperties += PROP_HREF; - requestedProperties += PROP_DESCRIPTION; - requestedProperties += PROP_ACTION_DATA; - requestedProperties += PROP_PARENT_ID; - requestedProperties += PROP_PARENT_JOINT_INDEX; - requestedProperties += PROP_QUERY_AA_CUBE; - - requestedProperties += PROP_ENTITY_HOST_TYPE; - requestedProperties += PROP_OWNING_AVATAR_ID; - - requestedProperties += PROP_LAST_EDITED_BY; - - requestedProperties += PROP_CLONEABLE; - requestedProperties += PROP_CLONE_LIFETIME; - requestedProperties += PROP_CLONE_LIMIT; - requestedProperties += PROP_CLONE_DYNAMIC; - requestedProperties += PROP_CLONE_AVATAR_ENTITY; - requestedProperties += PROP_CLONE_ORIGIN_ID; - - withReadLock([&] { - requestedProperties += _grabProperties.getEntityProperties(params); - }); - return requestedProperties; } @@ -172,9 +179,6 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet EntityPropertyFlags propertyFlags(PROP_LAST_ITEM); EntityPropertyFlags requestedProperties = getEntityProperties(params); - requestedProperties -= PROP_ENTITY_HOST_TYPE; - requestedProperties -= PROP_OWNING_AVATAR_ID; - // If we are being called for a subsequent pass at appendEntityData() that failed to completely encode this item, // then our entityTreeElementExtraEncodeData should include data about which properties we need to append. if (entityTreeElementExtraEncodeData && entityTreeElementExtraEncodeData->entities.contains(getEntityItemID())) { @@ -240,56 +244,27 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet propertyFlags -= PROP_LAST_ITEM; // clear the last item for now, we may or may not set it as the actual item + // NOTE: When we enable partial packing of entity properties, we'll want to pack simulationOwner, transform, and velocity properties near each other + // since they will commonly be transmitted together. simulationOwner must always go first, to avoid race conditions of simulation ownership bids // These items would go here once supported.... // PROP_PAGED_PROPERTY, // PROP_CUSTOM_PROPERTIES_INCLUDED, APPEND_ENTITY_PROPERTY(PROP_SIMULATION_OWNER, _simulationOwner.toByteArray()); - APPEND_ENTITY_PROPERTY(PROP_POSITION, getLocalPosition()); - APPEND_ENTITY_PROPERTY(PROP_ROTATION, getLocalOrientation()); - APPEND_ENTITY_PROPERTY(PROP_VELOCITY, getLocalVelocity()); - APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, getLocalAngularVelocity()); - APPEND_ENTITY_PROPERTY(PROP_ACCELERATION, getAcceleration()); - - APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, getUnscaledDimensions()); - APPEND_ENTITY_PROPERTY(PROP_DENSITY, getDensity()); - APPEND_ENTITY_PROPERTY(PROP_GRAVITY, getGravity()); - APPEND_ENTITY_PROPERTY(PROP_DAMPING, getDamping()); - APPEND_ENTITY_PROPERTY(PROP_RESTITUTION, getRestitution()); - APPEND_ENTITY_PROPERTY(PROP_FRICTION, getFriction()); - APPEND_ENTITY_PROPERTY(PROP_LIFETIME, getLifetime()); - APPEND_ENTITY_PROPERTY(PROP_SCRIPT, getScript()); - APPEND_ENTITY_PROPERTY(PROP_SCRIPT_TIMESTAMP, getScriptTimestamp()); - APPEND_ENTITY_PROPERTY(PROP_SERVER_SCRIPTS, getServerScripts()); - APPEND_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, getRegistrationPoint()); - APPEND_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, getAngularDamping()); APPEND_ENTITY_PROPERTY(PROP_VISIBLE, getVisible()); - APPEND_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, getCanCastShadow()); - APPEND_ENTITY_PROPERTY(PROP_COLLISIONLESS, getCollisionless()); - APPEND_ENTITY_PROPERTY(PROP_COLLISION_MASK, getCollisionMask()); - APPEND_ENTITY_PROPERTY(PROP_DYNAMIC, getDynamic()); + APPEND_ENTITY_PROPERTY(PROP_NAME, getName()); APPEND_ENTITY_PROPERTY(PROP_LOCKED, getLocked()); APPEND_ENTITY_PROPERTY(PROP_USER_DATA, getUserData()); - - // Certifiable Properties - APPEND_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, getMarketplaceID()); - APPEND_ENTITY_PROPERTY(PROP_ITEM_NAME, getItemName()); - APPEND_ENTITY_PROPERTY(PROP_ITEM_DESCRIPTION, getItemDescription()); - APPEND_ENTITY_PROPERTY(PROP_ITEM_CATEGORIES, getItemCategories()); - APPEND_ENTITY_PROPERTY(PROP_ITEM_ARTIST, getItemArtist()); - APPEND_ENTITY_PROPERTY(PROP_ITEM_LICENSE, getItemLicense()); - APPEND_ENTITY_PROPERTY(PROP_LIMITED_RUN, getLimitedRun()); - APPEND_ENTITY_PROPERTY(PROP_EDITION_NUMBER, getEditionNumber()); - APPEND_ENTITY_PROPERTY(PROP_ENTITY_INSTANCE_NUMBER, getEntityInstanceNumber()); - APPEND_ENTITY_PROPERTY(PROP_CERTIFICATE_ID, getCertificateID()); - APPEND_ENTITY_PROPERTY(PROP_STATIC_CERTIFICATE_VERSION, getStaticCertificateVersion()); - - APPEND_ENTITY_PROPERTY(PROP_NAME, getName()); - APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, getCollisionSoundURL()); APPEND_ENTITY_PROPERTY(PROP_HREF, getHref()); APPEND_ENTITY_PROPERTY(PROP_DESCRIPTION, getDescription()); - APPEND_ENTITY_PROPERTY(PROP_ACTION_DATA, getDynamicData()); - + APPEND_ENTITY_PROPERTY(PROP_POSITION, getLocalPosition()); + APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, getUnscaledDimensions()); + APPEND_ENTITY_PROPERTY(PROP_ROTATION, getLocalOrientation()); + APPEND_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, getRegistrationPoint()); + APPEND_ENTITY_PROPERTY(PROP_CREATED, getCreated()); + APPEND_ENTITY_PROPERTY(PROP_LAST_EDITED_BY, getLastEditedBy()); + // APPEND_ENTITY_PROPERTY(PROP_ENTITY_HOST_TYPE, getEntityHostType()); // not sent over the wire + // APPEND_ENTITY_PROPERTY(PROP_OWNING_AVATAR_ID, getOwningAvatarID()); // not sent over the wire // convert AVATAR_SELF_ID to actual sessionUUID. QUuid actualParentID = getParentID(); if (actualParentID == AVATAR_SELF_ID) { @@ -297,11 +272,33 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet actualParentID = nodeList->getSessionUUID(); } APPEND_ENTITY_PROPERTY(PROP_PARENT_ID, actualParentID); - APPEND_ENTITY_PROPERTY(PROP_PARENT_JOINT_INDEX, getParentJointIndex()); APPEND_ENTITY_PROPERTY(PROP_QUERY_AA_CUBE, getQueryAACube()); - APPEND_ENTITY_PROPERTY(PROP_LAST_EDITED_BY, getLastEditedBy()); + APPEND_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, getCanCastShadow()); + // APPEND_ENTITY_PROPERTY(PROP_VISIBLE_IN_SECONDARY_CAMERA, getIsVisibleInSecondaryCamera()); // not sent over the wire + withReadLock([&] { + _grabProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, + propertyFlags, propertiesDidntFit, propertyCount, appendState); + }); + // Physics + APPEND_ENTITY_PROPERTY(PROP_DENSITY, getDensity()); + APPEND_ENTITY_PROPERTY(PROP_VELOCITY, getLocalVelocity()); + APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, getLocalAngularVelocity()); + APPEND_ENTITY_PROPERTY(PROP_GRAVITY, getGravity()); + APPEND_ENTITY_PROPERTY(PROP_ACCELERATION, getAcceleration()); + APPEND_ENTITY_PROPERTY(PROP_DAMPING, getDamping()); + APPEND_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, getAngularDamping()); + APPEND_ENTITY_PROPERTY(PROP_RESTITUTION, getRestitution()); + APPEND_ENTITY_PROPERTY(PROP_FRICTION, getFriction()); + APPEND_ENTITY_PROPERTY(PROP_LIFETIME, getLifetime()); + APPEND_ENTITY_PROPERTY(PROP_COLLISIONLESS, getCollisionless()); + APPEND_ENTITY_PROPERTY(PROP_COLLISION_MASK, getCollisionMask()); + APPEND_ENTITY_PROPERTY(PROP_DYNAMIC, getDynamic()); + APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, getCollisionSoundURL()); + APPEND_ENTITY_PROPERTY(PROP_ACTION_DATA, getDynamicData()); + + // Cloning APPEND_ENTITY_PROPERTY(PROP_CLONEABLE, getCloneable()); APPEND_ENTITY_PROPERTY(PROP_CLONE_LIFETIME, getCloneLifetime()); APPEND_ENTITY_PROPERTY(PROP_CLONE_LIMIT, getCloneLimit()); @@ -309,10 +306,23 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet APPEND_ENTITY_PROPERTY(PROP_CLONE_AVATAR_ENTITY, getCloneAvatarEntity()); APPEND_ENTITY_PROPERTY(PROP_CLONE_ORIGIN_ID, getCloneOriginID()); - withReadLock([&] { - _grabProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, - propertyFlags, propertiesDidntFit, propertyCount, appendState); - }); + // Scripts + APPEND_ENTITY_PROPERTY(PROP_SCRIPT, getScript()); + APPEND_ENTITY_PROPERTY(PROP_SCRIPT_TIMESTAMP, getScriptTimestamp()); + APPEND_ENTITY_PROPERTY(PROP_SERVER_SCRIPTS, getServerScripts()); + + // Certifiable Properties + APPEND_ENTITY_PROPERTY(PROP_ITEM_NAME, getItemName()); + APPEND_ENTITY_PROPERTY(PROP_ITEM_DESCRIPTION, getItemDescription()); + APPEND_ENTITY_PROPERTY(PROP_ITEM_CATEGORIES, getItemCategories()); + APPEND_ENTITY_PROPERTY(PROP_ITEM_ARTIST, getItemArtist()); + APPEND_ENTITY_PROPERTY(PROP_ITEM_LICENSE, getItemLicense()); + APPEND_ENTITY_PROPERTY(PROP_LIMITED_RUN, getLimitedRun()); + APPEND_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, getMarketplaceID()); + APPEND_ENTITY_PROPERTY(PROP_EDITION_NUMBER, getEditionNumber()); + APPEND_ENTITY_PROPERTY(PROP_ENTITY_INSTANCE_NUMBER, getEntityInstanceNumber()); + APPEND_ENTITY_PROPERTY(PROP_CERTIFICATE_ID, getCertificateID()); + APPEND_ENTITY_PROPERTY(PROP_STATIC_CERTIFICATE_VERSION, getStaticCertificateVersion()); appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, @@ -673,7 +683,6 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef const QUuid& myNodeID = nodeList->getSessionUUID(); bool weOwnSimulation = _simulationOwner.matchesValidID(myNodeID); - // pack SimulationOwner, transform, and velocity properties near each other // NOTE: the server is authoritative for changes to simOwnerID so we always unpack ownership data // even when we would otherwise ignore the rest of the packet. @@ -766,6 +775,14 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef return otherOverwrites && simulationChanged && (valueChanged || filterRejection); }; + // Core + // PROP_SIMULATION_OWNER handled above + READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, setVisible); + READ_ENTITY_PROPERTY(PROP_NAME, QString, setName); + READ_ENTITY_PROPERTY(PROP_LOCKED, bool, setLocked); + READ_ENTITY_PROPERTY(PROP_USER_DATA, QString, setUserData); + READ_ENTITY_PROPERTY(PROP_HREF, QString, setHref); + READ_ENTITY_PROPERTY(PROP_DESCRIPTION, QString, setDescription); { // When we own the simulation we don't accept updates to the entity's transform/velocities // we also want to ignore any duplicate packets that have the same "recently updated" values // as a packet we've already recieved. This is because we want multiple edits of the same @@ -778,116 +795,40 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef // Note: duplicate packets are expected and not wrong. They may be sent for any number of // reasons and the contract is that the client handles them in an idempotent manner. - auto customUpdatePositionFromNetwork = [this, shouldUpdate, lastEdited](glm::vec3 value){ + auto customUpdatePositionFromNetwork = [this, shouldUpdate, lastEdited](glm::vec3 value) { if (shouldUpdate(_lastUpdatedPositionTimestamp, value != _lastUpdatedPositionValue)) { setPosition(value); _lastUpdatedPositionTimestamp = lastEdited; _lastUpdatedPositionValue = value; } }; - - auto customUpdateRotationFromNetwork = [this, shouldUpdate, lastEdited](glm::quat value){ + READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, customUpdatePositionFromNetwork); + } + READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, setUnscaledDimensions); + { // See comment above + auto customUpdateRotationFromNetwork = [this, shouldUpdate, lastEdited](glm::quat value) { if (shouldUpdate(_lastUpdatedRotationTimestamp, value != _lastUpdatedRotationValue)) { setRotation(value); _lastUpdatedRotationTimestamp = lastEdited; _lastUpdatedRotationValue = value; } }; - - auto customUpdateVelocityFromNetwork = [this, shouldUpdate, lastEdited](glm::vec3 value){ - if (shouldUpdate(_lastUpdatedVelocityTimestamp, value != _lastUpdatedVelocityValue)) { - setVelocity(value); - _lastUpdatedVelocityTimestamp = lastEdited; - _lastUpdatedVelocityValue = value; - } - }; - - auto customUpdateAngularVelocityFromNetwork = [this, shouldUpdate, lastEdited](glm::vec3 value){ - if (shouldUpdate(_lastUpdatedAngularVelocityTimestamp, value != _lastUpdatedAngularVelocityValue)) { - setAngularVelocity(value); - _lastUpdatedAngularVelocityTimestamp = lastEdited; - _lastUpdatedAngularVelocityValue = value; - } - }; - - auto customSetAcceleration = [this, shouldUpdate, lastEdited](glm::vec3 value){ - if (shouldUpdate(_lastUpdatedAccelerationTimestamp, value != _lastUpdatedAccelerationValue)) { - setAcceleration(value); - _lastUpdatedAccelerationTimestamp = lastEdited; - _lastUpdatedAccelerationValue = value; - } - }; - - READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, customUpdatePositionFromNetwork); READ_ENTITY_PROPERTY(PROP_ROTATION, glm::quat, customUpdateRotationFromNetwork); - READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, customUpdateVelocityFromNetwork); - READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, customUpdateAngularVelocityFromNetwork); - READ_ENTITY_PROPERTY(PROP_ACCELERATION, glm::vec3, customSetAcceleration); } - - READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, setUnscaledDimensions); - READ_ENTITY_PROPERTY(PROP_DENSITY, float, setDensity); - READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, setGravity); - - READ_ENTITY_PROPERTY(PROP_DAMPING, float, setDamping); - READ_ENTITY_PROPERTY(PROP_RESTITUTION, float, setRestitution); - READ_ENTITY_PROPERTY(PROP_FRICTION, float, setFriction); - READ_ENTITY_PROPERTY(PROP_LIFETIME, float, setLifetime); - READ_ENTITY_PROPERTY(PROP_SCRIPT, QString, setScript); - READ_ENTITY_PROPERTY(PROP_SCRIPT_TIMESTAMP, quint64, setScriptTimestamp); - - { - // We use this scope to work around an issue stopping server script changes - // from being received by an entity script server running a script that continously updates an entity. - - // Basically, we'll allow recent changes to the server scripts even if there are local changes to other properties - // that have been made more recently. - - bool overwriteLocalData = !ignoreServerPacket || (lastEditedFromBufferAdjusted > _serverScriptsChangedTimestamp); - - READ_ENTITY_PROPERTY(PROP_SERVER_SCRIPTS, QString, setServerScripts); - } - READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, setRegistrationPoint); - - READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, setAngularDamping); - READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, setVisible); - READ_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, bool, setCanCastShadow); - READ_ENTITY_PROPERTY(PROP_COLLISIONLESS, bool, setCollisionless); - READ_ENTITY_PROPERTY(PROP_COLLISION_MASK, uint16_t, setCollisionMask); - READ_ENTITY_PROPERTY(PROP_DYNAMIC, bool, setDynamic); - READ_ENTITY_PROPERTY(PROP_LOCKED, bool, setLocked); - READ_ENTITY_PROPERTY(PROP_USER_DATA, QString, setUserData); - - READ_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, QString, setMarketplaceID); - READ_ENTITY_PROPERTY(PROP_ITEM_NAME, QString, setItemName); - READ_ENTITY_PROPERTY(PROP_ITEM_DESCRIPTION, QString, setItemDescription); - READ_ENTITY_PROPERTY(PROP_ITEM_CATEGORIES, QString, setItemCategories); - READ_ENTITY_PROPERTY(PROP_ITEM_ARTIST, QString, setItemArtist); - READ_ENTITY_PROPERTY(PROP_ITEM_LICENSE, QString, setItemLicense); - READ_ENTITY_PROPERTY(PROP_LIMITED_RUN, quint32, setLimitedRun); - READ_ENTITY_PROPERTY(PROP_EDITION_NUMBER, quint32, setEditionNumber); - READ_ENTITY_PROPERTY(PROP_ENTITY_INSTANCE_NUMBER, quint32, setEntityInstanceNumber); - READ_ENTITY_PROPERTY(PROP_CERTIFICATE_ID, QString, setCertificateID); - READ_ENTITY_PROPERTY(PROP_STATIC_CERTIFICATE_VERSION, quint32, setStaticCertificateVersion); - - READ_ENTITY_PROPERTY(PROP_NAME, QString, setName); - READ_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, QString, setCollisionSoundURL); - READ_ENTITY_PROPERTY(PROP_HREF, QString, setHref); - READ_ENTITY_PROPERTY(PROP_DESCRIPTION, QString, setDescription); - READ_ENTITY_PROPERTY(PROP_ACTION_DATA, QByteArray, setDynamicData); - - { // parentID and parentJointIndex are also protected by simulation ownership + READ_ENTITY_PROPERTY(PROP_CREATED, quint64, setCreated); + READ_ENTITY_PROPERTY(PROP_LAST_EDITED_BY, QUuid, setLastEditedBy); + // READ_ENTITY_PROPERTY(PROP_ENTITY_HOST_TYPE, entity::HostType, setEntityHostType); // not sent over the wire + // READ_ENTITY_PROPERTY(PROP_OWNING_AVATAR_ID, QUuuid, setOwningAvatarID); // not sent over the wire + { // parentID and parentJointIndex are protected by simulation ownership bool oldOverwrite = overwriteLocalData; overwriteLocalData = overwriteLocalData && !weOwnSimulation; READ_ENTITY_PROPERTY(PROP_PARENT_ID, QUuid, setParentID); READ_ENTITY_PROPERTY(PROP_PARENT_JOINT_INDEX, quint16, setParentJointIndex); overwriteLocalData = oldOverwrite; } - - - { - auto customUpdateQueryAACubeFromNetwork = [this, shouldUpdate, lastEdited](AACube value){ + { // See comment above + auto customUpdateQueryAACubeFromNetwork = [this, shouldUpdate, lastEdited](AACube value) { if (shouldUpdate(_lastUpdatedQueryAACubeTimestamp, value != _lastUpdatedQueryAACubeValue)) { setQueryAACube(value); _lastUpdatedQueryAACubeTimestamp = lastEdited; @@ -896,9 +837,56 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef }; READ_ENTITY_PROPERTY(PROP_QUERY_AA_CUBE, AACube, customUpdateQueryAACubeFromNetwork); } + READ_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, bool, setCanCastShadow); + // READ_ENTITY_PROPERTY(PROP_VISIBLE_IN_SECONDARY_CAMERA, bool, setIsVisibleInSecondaryCamera); // not sent over the wire + withWriteLock([&] { + int bytesFromGrab = _grabProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, + propertyFlags, overwriteLocalData, + somethingChanged); + bytesRead += bytesFromGrab; + dataAt += bytesFromGrab; + }); - READ_ENTITY_PROPERTY(PROP_LAST_EDITED_BY, QUuid, setLastEditedBy); + READ_ENTITY_PROPERTY(PROP_DENSITY, float, setDensity); + { + auto customUpdateVelocityFromNetwork = [this, shouldUpdate, lastEdited](glm::vec3 value) { + if (shouldUpdate(_lastUpdatedVelocityTimestamp, value != _lastUpdatedVelocityValue)) { + setVelocity(value); + _lastUpdatedVelocityTimestamp = lastEdited; + _lastUpdatedVelocityValue = value; + } + }; + READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, customUpdateVelocityFromNetwork); + auto customUpdateAngularVelocityFromNetwork = [this, shouldUpdate, lastEdited](glm::vec3 value){ + if (shouldUpdate(_lastUpdatedAngularVelocityTimestamp, value != _lastUpdatedAngularVelocityValue)) { + setAngularVelocity(value); + _lastUpdatedAngularVelocityTimestamp = lastEdited; + _lastUpdatedAngularVelocityValue = value; + } + }; + READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, customUpdateAngularVelocityFromNetwork); + READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, setGravity); + auto customSetAcceleration = [this, shouldUpdate, lastEdited](glm::vec3 value){ + if (shouldUpdate(_lastUpdatedAccelerationTimestamp, value != _lastUpdatedAccelerationValue)) { + setAcceleration(value); + _lastUpdatedAccelerationTimestamp = lastEdited; + _lastUpdatedAccelerationValue = value; + } + }; + READ_ENTITY_PROPERTY(PROP_ACCELERATION, glm::vec3, customSetAcceleration); + } + READ_ENTITY_PROPERTY(PROP_DAMPING, float, setDamping); + READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, setAngularDamping); + READ_ENTITY_PROPERTY(PROP_RESTITUTION, float, setRestitution); + READ_ENTITY_PROPERTY(PROP_FRICTION, float, setFriction); + READ_ENTITY_PROPERTY(PROP_LIFETIME, float, setLifetime); + READ_ENTITY_PROPERTY(PROP_COLLISIONLESS, bool, setCollisionless); + READ_ENTITY_PROPERTY(PROP_COLLISION_MASK, uint16_t, setCollisionMask); + READ_ENTITY_PROPERTY(PROP_DYNAMIC, bool, setDynamic); + READ_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, QString, setCollisionSoundURL); + READ_ENTITY_PROPERTY(PROP_ACTION_DATA, QByteArray, setDynamicData); + // Cloning READ_ENTITY_PROPERTY(PROP_CLONEABLE, bool, setCloneable); READ_ENTITY_PROPERTY(PROP_CLONE_LIFETIME, float, setCloneLifetime); READ_ENTITY_PROPERTY(PROP_CLONE_LIMIT, float, setCloneLimit); @@ -906,13 +894,32 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY(PROP_CLONE_AVATAR_ENTITY, bool, setCloneAvatarEntity); READ_ENTITY_PROPERTY(PROP_CLONE_ORIGIN_ID, QUuid, setCloneOriginID); - withWriteLock([&] { - int bytesFromGrab = _grabProperties.readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, - propertyFlags, overwriteLocalData, - somethingChanged); - bytesRead += bytesFromGrab; - dataAt += bytesFromGrab; - }); + // Scripts + READ_ENTITY_PROPERTY(PROP_SCRIPT, QString, setScript); + READ_ENTITY_PROPERTY(PROP_SCRIPT_TIMESTAMP, quint64, setScriptTimestamp); + { + // We use this scope to work around an issue stopping server script changes + // from being received by an entity script server running a script that continously updates an entity. + // Basically, we'll allow recent changes to the server scripts even if there are local changes to other properties + // that have been made more recently. + bool oldOverwrite = overwriteLocalData; + overwriteLocalData = !ignoreServerPacket || (lastEditedFromBufferAdjusted > _serverScriptsChangedTimestamp); + READ_ENTITY_PROPERTY(PROP_SERVER_SCRIPTS, QString, setServerScripts); + overwriteLocalData = oldOverwrite; + } + + // Certifiable props + READ_ENTITY_PROPERTY(PROP_ITEM_NAME, QString, setItemName); + READ_ENTITY_PROPERTY(PROP_ITEM_DESCRIPTION, QString, setItemDescription); + READ_ENTITY_PROPERTY(PROP_ITEM_CATEGORIES, QString, setItemCategories); + READ_ENTITY_PROPERTY(PROP_ITEM_ARTIST, QString, setItemArtist); + READ_ENTITY_PROPERTY(PROP_ITEM_LICENSE, QString, setItemLicense); + READ_ENTITY_PROPERTY(PROP_LIMITED_RUN, quint32, setLimitedRun); + READ_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, QString, setMarketplaceID); + READ_ENTITY_PROPERTY(PROP_EDITION_NUMBER, quint32, setEditionNumber); + READ_ENTITY_PROPERTY(PROP_ENTITY_INSTANCE_NUMBER, quint32, setEntityInstanceNumber); + READ_ENTITY_PROPERTY(PROP_CERTIFICATE_ID, QString, setCertificateID); + READ_ENTITY_PROPERTY(PROP_STATIC_CERTIFICATE_VERSION, quint32, setStaticCertificateVersion); bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData, somethingChanged); @@ -1282,34 +1289,60 @@ EntityItemProperties EntityItem::getProperties(const EntityPropertyFlags& desire properties._type = getType(); + // Core COPY_ENTITY_PROPERTY_TO_PROPERTIES(simulationOwner, getSimulationOwner); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(visible, getVisible); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(name, getName); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(locked, getLocked); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(userData, getUserData); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(href, getHref); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(description, getDescription); COPY_ENTITY_PROPERTY_TO_PROPERTIES(position, getLocalPosition); COPY_ENTITY_PROPERTY_TO_PROPERTIES(dimensions, getUnscaledDimensions); COPY_ENTITY_PROPERTY_TO_PROPERTIES(rotation, getLocalOrientation); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(registrationPoint, getRegistrationPoint); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(created, getCreated); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(lastEditedBy, getLastEditedBy); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(entityHostType, getEntityHostType); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(owningAvatarID, getOwningAvatarID); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(parentID, getParentID); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(parentJointIndex, getParentJointIndex); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(queryAACube, getQueryAACube); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(canCastShadow, getCanCastShadow); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(isVisibleInSecondaryCamera, isVisibleInSecondaryCamera); + withReadLock([&] { + _grabProperties.getProperties(properties); + }); + + // Physics COPY_ENTITY_PROPERTY_TO_PROPERTIES(density, getDensity); COPY_ENTITY_PROPERTY_TO_PROPERTIES(velocity, getLocalVelocity); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(angularVelocity, getLocalAngularVelocity); COPY_ENTITY_PROPERTY_TO_PROPERTIES(gravity, getGravity); COPY_ENTITY_PROPERTY_TO_PROPERTIES(acceleration, getAcceleration); COPY_ENTITY_PROPERTY_TO_PROPERTIES(damping, getDamping); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(angularDamping, getAngularDamping); COPY_ENTITY_PROPERTY_TO_PROPERTIES(restitution, getRestitution); COPY_ENTITY_PROPERTY_TO_PROPERTIES(friction, getFriction); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(created, getCreated); COPY_ENTITY_PROPERTY_TO_PROPERTIES(lifetime, getLifetime); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(script, getScript); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(scriptTimestamp, getScriptTimestamp); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(serverScripts, getServerScripts); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(collisionSoundURL, getCollisionSoundURL); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(registrationPoint, getRegistrationPoint); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(angularVelocity, getLocalAngularVelocity); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(angularDamping, getAngularDamping); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(localRenderAlpha, getLocalRenderAlpha); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(visible, getVisible); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(canCastShadow, getCanCastShadow); COPY_ENTITY_PROPERTY_TO_PROPERTIES(collisionless, getCollisionless); COPY_ENTITY_PROPERTY_TO_PROPERTIES(collisionMask, getCollisionMask); COPY_ENTITY_PROPERTY_TO_PROPERTIES(dynamic, getDynamic); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(locked, getLocked); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(userData, getUserData); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(collisionSoundURL, getCollisionSoundURL); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(actionData, getDynamicData); + + // Cloning + COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneable, getCloneable); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneLifetime, getCloneLifetime); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneLimit, getCloneLimit); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneDynamic, getCloneDynamic); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneAvatarEntity, getCloneAvatarEntity); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneOriginID, getCloneOriginID); + + // Scripts + COPY_ENTITY_PROPERTY_TO_PROPERTIES(script, getScript); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(scriptTimestamp, getScriptTimestamp); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(serverScripts, getServerScripts); // Certifiable Properties COPY_ENTITY_PROPERTY_TO_PROPERTIES(itemName, getItemName); @@ -1324,31 +1357,13 @@ EntityItemProperties EntityItem::getProperties(const EntityPropertyFlags& desire COPY_ENTITY_PROPERTY_TO_PROPERTIES(certificateID, getCertificateID); COPY_ENTITY_PROPERTY_TO_PROPERTIES(staticCertificateVersion, getStaticCertificateVersion); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(name, getName); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(href, getHref); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(description, getDescription); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(actionData, getDynamicData); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(parentID, getParentID); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(parentJointIndex, getParentJointIndex); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(queryAACube, getQueryAACube); + // Script local data COPY_ENTITY_PROPERTY_TO_PROPERTIES(localPosition, getLocalPosition); COPY_ENTITY_PROPERTY_TO_PROPERTIES(localRotation, getLocalOrientation); - - COPY_ENTITY_PROPERTY_TO_PROPERTIES(entityHostType, getEntityHostType); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(owningAvatarID, getOwningAvatarID); - - COPY_ENTITY_PROPERTY_TO_PROPERTIES(lastEditedBy, getLastEditedBy); - - COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneable, getCloneable); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneLifetime, getCloneLifetime); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneLimit, getCloneLimit); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneDynamic, getCloneDynamic); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneAvatarEntity, getCloneAvatarEntity); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(cloneOriginID, getCloneOriginID); - - withReadLock([&] { - _grabProperties.getProperties(properties); - }); + // FIXME: are these needed? + //COPY_ENTITY_PROPERTY_TO_PROPERTIES(localVelocity, getLocalVelocity); + //COPY_ENTITY_PROPERTY_TO_PROPERTIES(localAngularVelocity, getLocalAngularVelocity); + //COPY_ENTITY_PROPERTY_TO_PROPERTIES(localDimensions, getLocalDimensions); properties._defaultSettings = false; @@ -1418,42 +1433,61 @@ bool EntityItem::stillWaitingToTakeOwnership(uint64_t timestamp) const { bool EntityItem::setProperties(const EntityItemProperties& properties) { bool somethingChanged = false; - // these affect transform and velocity properties + // Core SET_ENTITY_PROPERTY_FROM_PROPERTIES(simulationOwner, setSimulationOwner); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(visible, setVisible); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(name, setName); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(locked, setLocked); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(userData, setUserData); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(href, setHref); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(description, setDescription); SET_ENTITY_PROPERTY_FROM_PROPERTIES(position, setPosition); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(dimensions, setUnscaledDimensions); SET_ENTITY_PROPERTY_FROM_PROPERTIES(rotation, setRotation); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(registrationPoint, setRegistrationPoint); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(created, setCreated); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(lastEditedBy, setLastEditedBy); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(entityHostType, setEntityHostType); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(owningAvatarID, setOwningAvatarID); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(parentID, setParentID); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(parentJointIndex, setParentJointIndex); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(queryAACube, setQueryAACube); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(canCastShadow, setCanCastShadow); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(isVisibleInSecondaryCamera, setIsVisibleInSecondaryCamera); + withWriteLock([&] { + bool grabPropertiesChanged = _grabProperties.setProperties(properties); + somethingChanged |= grabPropertiesChanged; + }); + + // Physics + SET_ENTITY_PROPERTY_FROM_PROPERTIES(density, setDensity); SET_ENTITY_PROPERTY_FROM_PROPERTIES(velocity, setVelocity); SET_ENTITY_PROPERTY_FROM_PROPERTIES(angularVelocity, setAngularVelocity); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(acceleration, setAcceleration); - - // these (along with "position" above) affect tree structure - SET_ENTITY_PROPERTY_FROM_PROPERTIES(dimensions, setUnscaledDimensions); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(registrationPoint, setRegistrationPoint); - - // these (along with all properties above) affect the simulation - SET_ENTITY_PROPERTY_FROM_PROPERTIES(density, setDensity); SET_ENTITY_PROPERTY_FROM_PROPERTIES(gravity, setGravity); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(acceleration, setAcceleration); SET_ENTITY_PROPERTY_FROM_PROPERTIES(damping, setDamping); SET_ENTITY_PROPERTY_FROM_PROPERTIES(angularDamping, setAngularDamping); SET_ENTITY_PROPERTY_FROM_PROPERTIES(restitution, setRestitution); SET_ENTITY_PROPERTY_FROM_PROPERTIES(friction, setFriction); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifetime, setLifetime); SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionless, setCollisionless); SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionMask, setCollisionMask); SET_ENTITY_PROPERTY_FROM_PROPERTIES(dynamic, setDynamic); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(created, setCreated); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifetime, setLifetime); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(locked, setLocked); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionSoundURL, setCollisionSoundURL); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(actionData, setDynamicData); - // non-simulation properties below + // Cloning + SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneable, setCloneable); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneLifetime, setCloneLifetime); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneLimit, setCloneLimit); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneDynamic, setCloneDynamic); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneAvatarEntity, setCloneAvatarEntity); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneOriginID, setCloneOriginID); + + // Scripts SET_ENTITY_PROPERTY_FROM_PROPERTIES(script, setScript); SET_ENTITY_PROPERTY_FROM_PROPERTIES(scriptTimestamp, setScriptTimestamp); SET_ENTITY_PROPERTY_FROM_PROPERTIES(serverScripts, setServerScripts); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionSoundURL, setCollisionSoundURL); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(localRenderAlpha, setLocalRenderAlpha); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(visible, setVisible); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(canCastShadow, setCanCastShadow); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(userData, setUserData); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(isVisibleInSecondaryCamera, setIsVisibleInSecondaryCamera); // Certifiable Properties SET_ENTITY_PROPERTY_FROM_PROPERTIES(itemName, setItemName); @@ -1468,31 +1502,6 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(certificateID, setCertificateID); SET_ENTITY_PROPERTY_FROM_PROPERTIES(staticCertificateVersion, setStaticCertificateVersion); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(name, setName); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(href, setHref); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(description, setDescription); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(actionData, setDynamicData); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(parentID, setParentID); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(parentJointIndex, setParentJointIndex); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(queryAACube, setQueryAACube); - - SET_ENTITY_PROPERTY_FROM_PROPERTIES(entityHostType, setEntityHostType); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(owningAvatarID, setOwningAvatarID); - - SET_ENTITY_PROPERTY_FROM_PROPERTIES(lastEditedBy, setLastEditedBy); - - SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneable, setCloneable); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneLifetime, setCloneLifetime); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneLimit, setCloneLimit); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneDynamic, setCloneDynamic); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneAvatarEntity, setCloneAvatarEntity); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(cloneOriginID, setCloneOriginID); - - withWriteLock([&] { - bool grabPropertiesChanged = _grabProperties.setProperties(properties); - somethingChanged |= grabPropertiesChanged; - }); - if (updateQueryAACube()) { somethingChanged = true; } @@ -1647,7 +1656,10 @@ AACube EntityItem::getQueryAACube(bool& success) const { } bool EntityItem::shouldPuffQueryAACube() const { - return hasActions() || isChildOfMyAvatar() || isMovingRelativeToParent(); + bool hasGrabs = _grabsLock.resultWithReadLock<bool>([&] { + return _grabs.count() > 0; + }); + return hasActions() || isChildOfMyAvatar() || isMovingRelativeToParent() || hasGrabs; } // TODO: get rid of all users of this function... @@ -1668,15 +1680,57 @@ void EntityItem::adjustShapeInfoByRegistration(ShapeInfo& info) const { } bool EntityItem::contains(const glm::vec3& point) const { - if (getShapeType() == SHAPE_TYPE_COMPOUND) { - bool success; - bool result = getAABox(success).contains(point); - return result && success; - } else { - ShapeInfo info; - info.setParams(getShapeType(), glm::vec3(0.5f)); - adjustShapeInfoByRegistration(info); - return info.contains(worldToEntity(point)); + ShapeType shapeType = getShapeType(); + + if (shapeType == SHAPE_TYPE_SPHERE) { + // SPHERE case is special: + // anything with shapeType == SPHERE must collide as a bounding sphere in the world-frame regardless of dimensions + // therefore we must do math using an unscaled localPoint relative to sphere center + glm::vec3 dimensions = getScaledDimensions(); + glm::vec3 localPoint = point - (getWorldPosition() + getWorldOrientation() * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint()))); + const float HALF_SQUARED = 0.25f; + return glm::length2(localPoint) < HALF_SQUARED * glm::length2(dimensions); + } + + // we transform into the "normalized entity-frame" where the bounding box is centered on the origin + // and has dimensions <1,1,1> + glm::vec3 localPoint = glm::vec3(glm::inverse(getEntityToWorldMatrix()) * glm::vec4(point, 1.0f)); + + const float NORMALIZED_HALF_SIDE = 0.5f; + const float NORMALIZED_RADIUS_SQUARED = NORMALIZED_HALF_SIDE * NORMALIZED_HALF_SIDE; + switch(shapeType) { + case SHAPE_TYPE_NONE: + return false; + case SHAPE_TYPE_CAPSULE_X: + case SHAPE_TYPE_CAPSULE_Y: + case SHAPE_TYPE_CAPSULE_Z: + case SHAPE_TYPE_HULL: + case SHAPE_TYPE_PLANE: + case SHAPE_TYPE_COMPOUND: + case SHAPE_TYPE_SIMPLE_HULL: + case SHAPE_TYPE_SIMPLE_COMPOUND: + case SHAPE_TYPE_STATIC_MESH: + case SHAPE_TYPE_CIRCLE: + // the above cases not yet supported --> fall through to BOX case + case SHAPE_TYPE_BOX: { + localPoint = glm::abs(localPoint); + return glm::any(glm::lessThanEqual(localPoint, glm::vec3(NORMALIZED_HALF_SIDE))); + } + case SHAPE_TYPE_ELLIPSOID: { + // since we've transformed into the normalized space this is just a sphere-point intersection test + return glm::length2(localPoint) <= NORMALIZED_RADIUS_SQUARED; + } + case SHAPE_TYPE_CYLINDER_X: + return fabsf(localPoint.x) <= NORMALIZED_HALF_SIDE && + localPoint.y * localPoint.y + localPoint.z * localPoint.z <= NORMALIZED_RADIUS_SQUARED; + case SHAPE_TYPE_CYLINDER_Y: + return fabsf(localPoint.y) <= NORMALIZED_HALF_SIDE && + localPoint.z * localPoint.z + localPoint.x * localPoint.x <= NORMALIZED_RADIUS_SQUARED; + case SHAPE_TYPE_CYLINDER_Z: + return fabsf(localPoint.z) <= NORMALIZED_HALF_SIDE && + localPoint.x * localPoint.x + localPoint.y * localPoint.y <= NORMALIZED_RADIUS_SQUARED; + default: + return false; } } @@ -1693,7 +1747,8 @@ float EntityItem::getVolumeEstimate() const { void EntityItem::setRegistrationPoint(const glm::vec3& value) { if (value != _registrationPoint) { withWriteLock([&] { - _registrationPoint = glm::clamp(value, 0.0f, 1.0f); + _registrationPoint = glm::clamp(value, glm::vec3(ENTITY_ITEM_MIN_REGISTRATION_POINT), + glm::vec3(ENTITY_ITEM_MAX_REGISTRATION_POINT)); }); dimensionsChanged(); // Registration Point affects the bounding box markDirtyFlags(Simulation::DIRTY_SHAPE); @@ -1858,7 +1913,7 @@ void EntityItem::setVelocity(const glm::vec3& value) { } void EntityItem::setDamping(float value) { - auto clampedDamping = glm::clamp(value, 0.0f, 1.0f); + auto clampedDamping = glm::clamp(value, ENTITY_ITEM_MIN_DAMPING, ENTITY_ITEM_MAX_DAMPING); withWriteLock([&] { if (_damping != clampedDamping) { _damping = clampedDamping; @@ -1913,7 +1968,7 @@ void EntityItem::setAngularVelocity(const glm::vec3& value) { } void EntityItem::setAngularDamping(float value) { - auto clampedDamping = glm::clamp(value, 0.0f, 1.0f); + auto clampedDamping = glm::clamp(value, ENTITY_ITEM_MIN_DAMPING, ENTITY_ITEM_MAX_DAMPING); withWriteLock([&] { if (_angularDamping != clampedDamping) { _angularDamping = clampedDamping; @@ -2090,6 +2145,35 @@ bool EntityItem::addAction(EntitySimulationPointer simulation, EntityDynamicPoin return result; } +void EntityItem::enableNoBootstrap() { + if (!(bool)(_flags & Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING)) { + _flags |= Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING; + _flags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar + forEachDescendant([&](SpatiallyNestablePointer child) { + if (child->getNestableType() == NestableType::Entity) { + EntityItemPointer entity = std::static_pointer_cast<EntityItem>(child); + entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP); + entity->markSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING); + } + }); + } +} + +void EntityItem::disableNoBootstrap() { + if (!stillHasGrabActions()) { + _flags &= ~Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING; + _flags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar + forEachDescendant([&](SpatiallyNestablePointer child) { + if (child->getNestableType() == NestableType::Entity) { + EntityItemPointer entity = std::static_pointer_cast<EntityItem>(child); + entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP); + entity->clearSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING); + } + }); + } +} + + bool EntityItem::addActionInternal(EntitySimulationPointer simulation, EntityDynamicPointer action) { assert(action); assert(simulation); @@ -2111,17 +2195,7 @@ bool EntityItem::addActionInternal(EntitySimulationPointer simulation, EntityDyn auto actionType = action->getType(); if (actionType == DYNAMIC_TYPE_HOLD || actionType == DYNAMIC_TYPE_FAR_GRAB) { - if (!(bool)(_flags & Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING)) { - _flags |= Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING; - _flags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar - forEachDescendant([&](SpatiallyNestablePointer child) { - if (child->getNestableType() == NestableType::Entity) { - EntityItemPointer entity = std::static_pointer_cast<EntityItem>(child); - entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP); - entity->markSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING); - } - }); - } + enableNoBootstrap(); } } else { qCDebug(entities) << "EntityItem::addActionInternal -- serializeActions failed"; @@ -2202,16 +2276,8 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi action->setIsMine(false); _objectActions.remove(actionID); - if ((removedActionType == DYNAMIC_TYPE_HOLD || removedActionType == DYNAMIC_TYPE_FAR_GRAB) && !stillHasGrabActions()) { - _flags &= ~Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING; - _flags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar - forEachDescendant([&](SpatiallyNestablePointer child) { - if (child->getNestableType() == NestableType::Entity) { - EntityItemPointer entity = std::static_pointer_cast<EntityItem>(child); - entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP); - entity->clearSpecialFlags(Simulation::SPECIAL_FLAGS_NO_BOOTSTRAPPING); - } - }); + if (removedActionType == DYNAMIC_TYPE_HOLD || removedActionType == DYNAMIC_TYPE_FAR_GRAB) { + disableNoBootstrap(); } else { // NO-OP: we assume SPECIAL_FLAGS_NO_BOOTSTRAPPING bits and collision group are correct // because they should have been set correctly when the action was added @@ -2448,6 +2514,14 @@ bool EntityItem::shouldSuppressLocationEdits() const { i++; } + i = _grabActions.begin(); + while (i != _grabActions.end()) { + if (i.value()->shouldSuppressLocationEdits()) { + return true; + } + i++; + } + // if any of the ancestors are MyAvatar, suppress return isChildOfMyAvatar(); } @@ -2455,13 +2529,22 @@ bool EntityItem::shouldSuppressLocationEdits() const { QList<EntityDynamicPointer> EntityItem::getActionsOfType(EntityDynamicType typeToGet) const { QList<EntityDynamicPointer> result; - QHash<QUuid, EntityDynamicPointer>::const_iterator i = _objectActions.begin(); - while (i != _objectActions.end()) { + for (QHash<QUuid, EntityDynamicPointer>::const_iterator i = _objectActions.begin(); + i != _objectActions.end(); + i++) { + EntityDynamicPointer action = i.value(); + if (action->getType() == typeToGet && action->isActive()) { + result += action; + } + } + + for (QHash<QUuid, EntityDynamicPointer>::const_iterator i = _grabActions.begin(); + i != _grabActions.end(); + i++) { EntityDynamicPointer action = i.value(); if (action->getType() == typeToGet && action->isActive()) { result += action; } - i++; } return result; @@ -2652,20 +2735,6 @@ void EntityItem::setDescription(const QString& value) { }); } -float EntityItem::getLocalRenderAlpha() const { - float result; - withReadLock([&] { - result = _localRenderAlpha; - }); - return result; -} - -void EntityItem::setLocalRenderAlpha(float localRenderAlpha) { - withWriteLock([&] { - _localRenderAlpha = localRenderAlpha; - }); -} - glm::vec3 EntityItem::getGravity() const { glm::vec3 result; withReadLock([&] { @@ -3281,4 +3350,67 @@ void EntityItem::prepareForSimulationOwnershipBid(EntityItemProperties& properti bool EntityItem::isWearable() const { return isVisible() && (getParentID() == DependencyManager::get<NodeList>()->getSessionUUID() || getParentID() == AVATAR_SELF_ID); -} \ No newline at end of file +} + +void EntityItem::addGrab(GrabPointer grab) { + enableNoBootstrap(); + SpatiallyNestable::addGrab(grab); + + if (getDynamic() && getParentID().isNull()) { + EntityTreePointer entityTree = getTree(); + assert(entityTree); + EntitySimulationPointer simulation = entityTree ? entityTree->getSimulation() : nullptr; + assert(simulation); + + auto actionFactory = DependencyManager::get<EntityDynamicFactoryInterface>(); + QUuid actionID = QUuid::createUuid(); + + EntityDynamicType dynamicType; + QVariantMap arguments; + int grabParentJointIndex =grab->getParentJointIndex(); + if (grabParentJointIndex == FARGRAB_RIGHTHAND_INDEX || grabParentJointIndex == FARGRAB_LEFTHAND_INDEX) { + // add a far-grab action + dynamicType = DYNAMIC_TYPE_FAR_GRAB; + arguments["otherID"] = grab->getOwnerID(); + arguments["otherJointIndex"] = grabParentJointIndex; + arguments["targetPosition"] = vec3ToQMap(grab->getPositionalOffset()); + arguments["targetRotation"] = quatToQMap(grab->getRotationalOffset()); + arguments["linearTimeScale"] = 0.05; + arguments["angularTimeScale"] = 0.05; + } else { + // add a near-grab action + dynamicType = DYNAMIC_TYPE_HOLD; + arguments["holderID"] = grab->getOwnerID(); + arguments["hand"] = grab->getHand(); + arguments["timeScale"] = 0.05; + arguments["relativePosition"] = vec3ToQMap(grab->getPositionalOffset()); + arguments["relativeRotation"] = quatToQMap(grab->getRotationalOffset()); + arguments["kinematic"] = _grabProperties.getGrabKinematic(); + arguments["kinematicSetVelocity"] = true; + arguments["ignoreIK"] = _grabProperties.getGrabFollowsController(); + } + EntityDynamicPointer action = actionFactory->factory(dynamicType, actionID, getThisPointer(), arguments); + grab->setActionID(actionID); + _grabActions[actionID] = action; + simulation->addDynamic(action); + } +} + +void EntityItem::removeGrab(GrabPointer grab) { + SpatiallyNestable::removeGrab(grab); + + QUuid actionID = grab->getActionID(); + if (!actionID.isNull()) { + EntityDynamicPointer action = _grabActions.value(actionID); + if (action) { + _grabActions.remove(actionID); + EntityTreePointer entityTree = getTree(); + EntitySimulationPointer simulation = entityTree ? entityTree->getSimulation() : nullptr; + if (simulation) { + action->removeFromSimulation(simulation); + action->removeFromOwner(); + } + } + } + disableNoBootstrap(); +} diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 5c45c96702..826a9c34a0 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -51,7 +51,6 @@ typedef std::shared_ptr<EntityDynamicInterface> EntityDynamicPointer; typedef std::shared_ptr<EntityTreeElement> EntityTreeElementPointer; using EntityTreeElementExtraEncodeDataPointer = std::shared_ptr<EntityTreeElementExtraEncodeData>; - #define DONT_ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() = 0; #define ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() override { }; @@ -207,9 +206,6 @@ public: glm::vec3 getUnscaledDimensions() const; virtual void setUnscaledDimensions(const glm::vec3& value); - float getLocalRenderAlpha() const; - void setLocalRenderAlpha(float localRenderAlpha); - void setDensity(float density); float computeMass() const; void setMass(float mass); @@ -554,6 +550,9 @@ public: void prepareForSimulationOwnershipBid(EntityItemProperties& properties, uint64_t now, uint8_t priority); + virtual void addGrab(GrabPointer grab) override; + virtual void removeGrab(GrabPointer grab) override; + signals: void requestRenderUpdate(); void spaceUpdate(std::pair<int32_t, glm::vec4> data); @@ -592,7 +591,6 @@ protected: mutable bool _recalcMinAACube { true }; mutable bool _recalcMaxAACube { true }; - float _localRenderAlpha { ENTITY_ITEM_DEFAULT_LOCAL_RENDER_ALPHA }; float _density { ENTITY_ITEM_DEFAULT_DENSITY }; // kg/m^3 // NOTE: _volumeMultiplier is used to allow some mass properties code exist in the EntityItem base class // rather than in all of the derived classes. If we ever collapse these classes to one we could do it a @@ -668,6 +666,9 @@ protected: bool _simulated { false }; // set by EntitySimulation bool _visuallyReady { true }; + void enableNoBootstrap(); + void disableNoBootstrap(); + bool addActionInternal(EntitySimulationPointer simulation, EntityDynamicPointer action); bool removeActionInternal(const QUuid& actionID, EntitySimulationPointer simulation = nullptr); void deserializeActionsInternal(); @@ -734,6 +735,8 @@ protected: GrabPropertyGroup _grabProperties; + QHash<QUuid, EntityDynamicPointer> _grabActions; + private: std::unordered_map<std::string, graphics::MultiMaterial> _materials; std::mutex _materialsLock; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index b6307a83da..3b1e9a3e2b 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -28,6 +28,7 @@ #include <GLMHelpers.h> #include <RegisteredMetaTypes.h> #include <Extents.h> +#include <VariantMapToScriptValue.h> #include "EntitiesLogging.h" #include "EntityItem.h" @@ -50,10 +51,6 @@ EntityItemProperties::EntityItemProperties(EntityPropertyFlags desiredProperties _lastEdited(0), _type(EntityTypes::Unknown), - _localRenderAlpha(1.0f), - - _localRenderAlphaChanged(false), - _defaultSettings(true), _naturalDimensions(1.0f, 1.0f, 1.0f), _naturalPosition(0.0f, 0.0f, 0.0f), @@ -67,10 +64,6 @@ void EntityItemProperties::calculateNaturalPosition(const vec3& min, const vec3& _naturalPosition = max - halfDimension; } -void EntityItemProperties::setCreated(QDateTime &v) { - _created = v.toMSecsSinceEpoch() * 1000; // usec per msec -} - void EntityItemProperties::debugDump() const { qCDebug(entities) << "EntityItemProperties..."; qCDebug(entities) << " _type=" << EntityTypes::getEntityTypeName(_type); @@ -98,6 +91,16 @@ void EntityItemProperties::setLastEdited(quint64 usecTime) { _lastEdited = usecTime > _created ? usecTime : _created; } +bool EntityItemProperties::constructFromBuffer(const unsigned char* data, int dataLength) { + ReadBitstreamToTreeParams args; + EntityItemPointer tempEntity = EntityTypes::constructEntityItem(data, dataLength); + if (!tempEntity) { + return false; + } + tempEntity->readEntityDataFromBuffer(data, dataLength, args); + (*this) = tempEntity->getProperties(); + return true; +} QHash<QString, ShapeType> stringToShapeTypeLookup; @@ -121,6 +124,7 @@ void buildStringToShapeTypeLookup() { addShapeType(SHAPE_TYPE_SIMPLE_HULL); addShapeType(SHAPE_TYPE_SIMPLE_COMPOUND); addShapeType(SHAPE_TYPE_STATIC_MESH); + addShapeType(SHAPE_TYPE_ELLIPSOID); } QHash<QString, MaterialMappingMode> stringToMaterialMappingModeLookup; @@ -219,15 +223,6 @@ QString EntityItemProperties::getBloomModeAsString() const { return getComponentModeAsString(_bloomMode); } -QString EntityItemProperties::getComponentModeString(uint32_t mode) { - // return "inherit" if mode is not valid - if (mode < COMPONENT_MODE_ITEM_COUNT) { - return COMPONENT_MODES[mode].second; - } else { - return COMPONENT_MODES[COMPONENT_MODE_INHERIT].second; - } -} - std::array<ComponentPair, COMPONENT_MODE_ITEM_COUNT>::const_iterator EntityItemProperties::findComponent(const QString& mode) { return std::find_if(COMPONENT_MODES.begin(), COMPONENT_MODES.end(), [&](const ComponentPair& pair) { return (pair.second == mode); @@ -329,61 +324,120 @@ void EntityItemProperties::setEntityHostTypeFromString(const QString& entityHost } } +QHash<QString, BillboardMode> stringToBillboardModeLookup; + +void addBillboardMode(BillboardMode mode) { + stringToBillboardModeLookup[BillboardModeHelpers::getNameForBillboardMode(mode)] = mode; +} + +void buildStringToBillboardModeLookup() { + addBillboardMode(BillboardMode::NONE); + addBillboardMode(BillboardMode::YAW); + addBillboardMode(BillboardMode::FULL); +} + +QString EntityItemProperties::getBillboardModeAsString() const { + return BillboardModeHelpers::getNameForBillboardMode(_billboardMode); +} + +void EntityItemProperties::setBillboardModeFromString(const QString& materialMappingMode) { + if (stringToBillboardModeLookup.empty()) { + buildStringToBillboardModeLookup(); + } + auto billboardModeItr = stringToBillboardModeLookup.find(materialMappingMode.toLower()); + if (billboardModeItr != stringToBillboardModeLookup.end()) { + _billboardMode = billboardModeItr.value(); + _billboardModeChanged = true; + } +} + EntityPropertyFlags EntityItemProperties::getChangedProperties() const { EntityPropertyFlags changedProperties; - CHECK_PROPERTY_CHANGE(PROP_LAST_EDITED_BY, lastEditedBy); + // Core + CHECK_PROPERTY_CHANGE(PROP_SIMULATION_OWNER, simulationOwner); + CHECK_PROPERTY_CHANGE(PROP_VISIBLE, visible); + CHECK_PROPERTY_CHANGE(PROP_NAME, name); + CHECK_PROPERTY_CHANGE(PROP_LOCKED, locked); + CHECK_PROPERTY_CHANGE(PROP_USER_DATA, userData); + CHECK_PROPERTY_CHANGE(PROP_HREF, href); + CHECK_PROPERTY_CHANGE(PROP_DESCRIPTION, description); CHECK_PROPERTY_CHANGE(PROP_POSITION, position); CHECK_PROPERTY_CHANGE(PROP_DIMENSIONS, dimensions); CHECK_PROPERTY_CHANGE(PROP_ROTATION, rotation); + CHECK_PROPERTY_CHANGE(PROP_REGISTRATION_POINT, registrationPoint); + CHECK_PROPERTY_CHANGE(PROP_CREATED, created); + CHECK_PROPERTY_CHANGE(PROP_LAST_EDITED_BY, lastEditedBy); + CHECK_PROPERTY_CHANGE(PROP_ENTITY_HOST_TYPE, entityHostType); + CHECK_PROPERTY_CHANGE(PROP_OWNING_AVATAR_ID, owningAvatarID); + CHECK_PROPERTY_CHANGE(PROP_PARENT_ID, parentID); + CHECK_PROPERTY_CHANGE(PROP_PARENT_JOINT_INDEX, parentJointIndex); + CHECK_PROPERTY_CHANGE(PROP_QUERY_AA_CUBE, queryAACube); + CHECK_PROPERTY_CHANGE(PROP_CAN_CAST_SHADOW, canCastShadow); + CHECK_PROPERTY_CHANGE(PROP_VISIBLE_IN_SECONDARY_CAMERA, isVisibleInSecondaryCamera); + changedProperties += _grab.getChangedProperties(); + + // Physics CHECK_PROPERTY_CHANGE(PROP_DENSITY, density); CHECK_PROPERTY_CHANGE(PROP_VELOCITY, velocity); + CHECK_PROPERTY_CHANGE(PROP_ANGULAR_VELOCITY, angularVelocity); CHECK_PROPERTY_CHANGE(PROP_GRAVITY, gravity); CHECK_PROPERTY_CHANGE(PROP_ACCELERATION, acceleration); CHECK_PROPERTY_CHANGE(PROP_DAMPING, damping); + CHECK_PROPERTY_CHANGE(PROP_ANGULAR_DAMPING, angularDamping); CHECK_PROPERTY_CHANGE(PROP_RESTITUTION, restitution); CHECK_PROPERTY_CHANGE(PROP_FRICTION, friction); CHECK_PROPERTY_CHANGE(PROP_LIFETIME, lifetime); - CHECK_PROPERTY_CHANGE(PROP_SCRIPT, script); - CHECK_PROPERTY_CHANGE(PROP_SCRIPT_TIMESTAMP, scriptTimestamp); - CHECK_PROPERTY_CHANGE(PROP_SERVER_SCRIPTS, serverScripts); - CHECK_PROPERTY_CHANGE(PROP_COLLISION_SOUND_URL, collisionSoundURL); - CHECK_PROPERTY_CHANGE(PROP_COLOR, color); - CHECK_PROPERTY_CHANGE(PROP_COLOR_SPREAD, colorSpread); - CHECK_PROPERTY_CHANGE(PROP_COLOR_START, colorStart); - CHECK_PROPERTY_CHANGE(PROP_COLOR_FINISH, colorFinish); - CHECK_PROPERTY_CHANGE(PROP_ALPHA, alpha); - CHECK_PROPERTY_CHANGE(PROP_ALPHA_SPREAD, alphaSpread); - CHECK_PROPERTY_CHANGE(PROP_ALPHA_START, alphaStart); - CHECK_PROPERTY_CHANGE(PROP_ALPHA_FINISH, alphaFinish); - CHECK_PROPERTY_CHANGE(PROP_EMITTER_SHOULD_TRAIL, emitterShouldTrail); - CHECK_PROPERTY_CHANGE(PROP_MODEL_URL, modelURL); - CHECK_PROPERTY_CHANGE(PROP_COMPOUND_SHAPE_URL, compoundShapeURL); - CHECK_PROPERTY_CHANGE(PROP_VISIBLE, visible); - CHECK_PROPERTY_CHANGE(PROP_CAN_CAST_SHADOW, canCastShadow); - CHECK_PROPERTY_CHANGE(PROP_REGISTRATION_POINT, registrationPoint); - CHECK_PROPERTY_CHANGE(PROP_ANGULAR_VELOCITY, angularVelocity); - CHECK_PROPERTY_CHANGE(PROP_ANGULAR_DAMPING, angularDamping); CHECK_PROPERTY_CHANGE(PROP_COLLISIONLESS, collisionless); CHECK_PROPERTY_CHANGE(PROP_COLLISION_MASK, collisionMask); CHECK_PROPERTY_CHANGE(PROP_DYNAMIC, dynamic); - CHECK_PROPERTY_CHANGE(PROP_IS_SPOTLIGHT, isSpotlight); - CHECK_PROPERTY_CHANGE(PROP_INTENSITY, intensity); - CHECK_PROPERTY_CHANGE(PROP_FALLOFF_RADIUS, falloffRadius); - CHECK_PROPERTY_CHANGE(PROP_EXPONENT, exponent); - CHECK_PROPERTY_CHANGE(PROP_CUTOFF, cutoff); - CHECK_PROPERTY_CHANGE(PROP_LOCKED, locked); - CHECK_PROPERTY_CHANGE(PROP_TEXTURES, textures); - CHECK_PROPERTY_CHANGE(PROP_USER_DATA, userData); - CHECK_PROPERTY_CHANGE(PROP_SIMULATION_OWNER, simulationOwner); - CHECK_PROPERTY_CHANGE(PROP_TEXT, text); - CHECK_PROPERTY_CHANGE(PROP_LINE_HEIGHT, lineHeight); - CHECK_PROPERTY_CHANGE(PROP_TEXT_COLOR, textColor); - CHECK_PROPERTY_CHANGE(PROP_BACKGROUND_COLOR, backgroundColor); + CHECK_PROPERTY_CHANGE(PROP_COLLISION_SOUND_URL, collisionSoundURL); + CHECK_PROPERTY_CHANGE(PROP_ACTION_DATA, actionData); + + // Cloning + CHECK_PROPERTY_CHANGE(PROP_CLONEABLE, cloneable); + CHECK_PROPERTY_CHANGE(PROP_CLONE_LIFETIME, cloneLifetime); + CHECK_PROPERTY_CHANGE(PROP_CLONE_LIMIT, cloneLimit); + CHECK_PROPERTY_CHANGE(PROP_CLONE_DYNAMIC, cloneDynamic); + CHECK_PROPERTY_CHANGE(PROP_CLONE_AVATAR_ENTITY, cloneAvatarEntity); + CHECK_PROPERTY_CHANGE(PROP_CLONE_ORIGIN_ID, cloneOriginID); + + // Scripts + CHECK_PROPERTY_CHANGE(PROP_SCRIPT, script); + CHECK_PROPERTY_CHANGE(PROP_SCRIPT_TIMESTAMP, scriptTimestamp); + CHECK_PROPERTY_CHANGE(PROP_SERVER_SCRIPTS, serverScripts); + + // Certifiable Properties + CHECK_PROPERTY_CHANGE(PROP_ITEM_NAME, itemName); + CHECK_PROPERTY_CHANGE(PROP_ITEM_DESCRIPTION, itemDescription); + CHECK_PROPERTY_CHANGE(PROP_ITEM_CATEGORIES, itemCategories); + CHECK_PROPERTY_CHANGE(PROP_ITEM_ARTIST, itemArtist); + CHECK_PROPERTY_CHANGE(PROP_ITEM_LICENSE, itemLicense); + CHECK_PROPERTY_CHANGE(PROP_LIMITED_RUN, limitedRun); + CHECK_PROPERTY_CHANGE(PROP_MARKETPLACE_ID, marketplaceID); + CHECK_PROPERTY_CHANGE(PROP_EDITION_NUMBER, editionNumber); + CHECK_PROPERTY_CHANGE(PROP_ENTITY_INSTANCE_NUMBER, entityInstanceNumber); + CHECK_PROPERTY_CHANGE(PROP_CERTIFICATE_ID, certificateID); + CHECK_PROPERTY_CHANGE(PROP_STATIC_CERTIFICATE_VERSION, staticCertificateVersion); + + // Location data for scripts + CHECK_PROPERTY_CHANGE(PROP_LOCAL_POSITION, localPosition); + CHECK_PROPERTY_CHANGE(PROP_LOCAL_ROTATION, localRotation); + CHECK_PROPERTY_CHANGE(PROP_LOCAL_VELOCITY, localVelocity); + CHECK_PROPERTY_CHANGE(PROP_LOCAL_ANGULAR_VELOCITY, localAngularVelocity); + CHECK_PROPERTY_CHANGE(PROP_LOCAL_DIMENSIONS, localDimensions); + + // Common CHECK_PROPERTY_CHANGE(PROP_SHAPE_TYPE, shapeType); - CHECK_PROPERTY_CHANGE(PROP_EMITTING_PARTICLES, isEmitting); + CHECK_PROPERTY_CHANGE(PROP_COMPOUND_SHAPE_URL, compoundShapeURL); + CHECK_PROPERTY_CHANGE(PROP_COLOR, color); + CHECK_PROPERTY_CHANGE(PROP_ALPHA, alpha); + CHECK_PROPERTY_CHANGE(PROP_TEXTURES, textures); + + // Particles CHECK_PROPERTY_CHANGE(PROP_MAX_PARTICLES, maxParticles); CHECK_PROPERTY_CHANGE(PROP_LIFESPAN, lifespan); + CHECK_PROPERTY_CHANGE(PROP_EMITTING_PARTICLES, isEmitting); CHECK_PROPERTY_CHANGE(PROP_EMIT_RATE, emitRate); CHECK_PROPERTY_CHANGE(PROP_EMIT_SPEED, emitSpeed); CHECK_PROPERTY_CHANGE(PROP_SPEED_SPREAD, speedSpread); @@ -400,57 +454,67 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_RADIUS_SPREAD, radiusSpread); CHECK_PROPERTY_CHANGE(PROP_RADIUS_START, radiusStart); CHECK_PROPERTY_CHANGE(PROP_RADIUS_FINISH, radiusFinish); - CHECK_PROPERTY_CHANGE(PROP_MATERIAL_URL, materialURL); - CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_MODE, materialMappingMode); - CHECK_PROPERTY_CHANGE(PROP_MATERIAL_PRIORITY, priority); - CHECK_PROPERTY_CHANGE(PROP_PARENT_MATERIAL_NAME, parentMaterialName); - CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_POS, materialMappingPos); - CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_SCALE, materialMappingScale); - CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_ROT, materialMappingRot); - CHECK_PROPERTY_CHANGE(PROP_MATERIAL_DATA, materialData); - CHECK_PROPERTY_CHANGE(PROP_MATERIAL_REPEAT, materialRepeat); - CHECK_PROPERTY_CHANGE(PROP_VISIBLE_IN_SECONDARY_CAMERA, isVisibleInSecondaryCamera); + CHECK_PROPERTY_CHANGE(PROP_COLOR_SPREAD, colorSpread); + CHECK_PROPERTY_CHANGE(PROP_COLOR_START, colorStart); + CHECK_PROPERTY_CHANGE(PROP_COLOR_FINISH, colorFinish); + CHECK_PROPERTY_CHANGE(PROP_ALPHA_SPREAD, alphaSpread); + CHECK_PROPERTY_CHANGE(PROP_ALPHA_START, alphaStart); + CHECK_PROPERTY_CHANGE(PROP_ALPHA_FINISH, alphaFinish); + CHECK_PROPERTY_CHANGE(PROP_EMITTER_SHOULD_TRAIL, emitterShouldTrail); CHECK_PROPERTY_CHANGE(PROP_PARTICLE_SPIN, particleSpin); CHECK_PROPERTY_CHANGE(PROP_SPIN_SPREAD, spinSpread); CHECK_PROPERTY_CHANGE(PROP_SPIN_START, spinStart); CHECK_PROPERTY_CHANGE(PROP_SPIN_FINISH, spinFinish); CHECK_PROPERTY_CHANGE(PROP_PARTICLE_ROTATE_WITH_ENTITY, rotateWithEntity); - // Certifiable Properties - CHECK_PROPERTY_CHANGE(PROP_ITEM_NAME, itemName); - CHECK_PROPERTY_CHANGE(PROP_ITEM_DESCRIPTION, itemDescription); - CHECK_PROPERTY_CHANGE(PROP_ITEM_CATEGORIES, itemCategories); - CHECK_PROPERTY_CHANGE(PROP_ITEM_ARTIST, itemArtist); - CHECK_PROPERTY_CHANGE(PROP_ITEM_LICENSE, itemLicense); - CHECK_PROPERTY_CHANGE(PROP_LIMITED_RUN, limitedRun); - CHECK_PROPERTY_CHANGE(PROP_MARKETPLACE_ID, marketplaceID); - CHECK_PROPERTY_CHANGE(PROP_EDITION_NUMBER, editionNumber); - CHECK_PROPERTY_CHANGE(PROP_ENTITY_INSTANCE_NUMBER, entityInstanceNumber); - CHECK_PROPERTY_CHANGE(PROP_CERTIFICATE_ID, certificateID); - CHECK_PROPERTY_CHANGE(PROP_STATIC_CERTIFICATE_VERSION, staticCertificateVersion); + // Model + CHECK_PROPERTY_CHANGE(PROP_MODEL_URL, modelURL); + CHECK_PROPERTY_CHANGE(PROP_JOINT_ROTATIONS_SET, jointRotationsSet); + CHECK_PROPERTY_CHANGE(PROP_JOINT_ROTATIONS, jointRotations); + CHECK_PROPERTY_CHANGE(PROP_JOINT_TRANSLATIONS_SET, jointTranslationsSet); + CHECK_PROPERTY_CHANGE(PROP_JOINT_TRANSLATIONS, jointTranslations); + CHECK_PROPERTY_CHANGE(PROP_RELAY_PARENT_JOINTS, relayParentJoints); + changedProperties += _animation.getChangedProperties(); - CHECK_PROPERTY_CHANGE(PROP_NAME, name); + // Light + CHECK_PROPERTY_CHANGE(PROP_IS_SPOTLIGHT, isSpotlight); + CHECK_PROPERTY_CHANGE(PROP_INTENSITY, intensity); + CHECK_PROPERTY_CHANGE(PROP_EXPONENT, exponent); + CHECK_PROPERTY_CHANGE(PROP_CUTOFF, cutoff); + CHECK_PROPERTY_CHANGE(PROP_FALLOFF_RADIUS, falloffRadius); - CHECK_PROPERTY_CHANGE(PROP_HAZE_MODE, hazeMode); + // Text + CHECK_PROPERTY_CHANGE(PROP_TEXT, text); + CHECK_PROPERTY_CHANGE(PROP_LINE_HEIGHT, lineHeight); + CHECK_PROPERTY_CHANGE(PROP_TEXT_COLOR, textColor); + CHECK_PROPERTY_CHANGE(PROP_TEXT_ALPHA, textAlpha); + CHECK_PROPERTY_CHANGE(PROP_BACKGROUND_COLOR, backgroundColor); + CHECK_PROPERTY_CHANGE(PROP_BACKGROUND_ALPHA, backgroundAlpha); + CHECK_PROPERTY_CHANGE(PROP_BILLBOARD_MODE, billboardMode); + CHECK_PROPERTY_CHANGE(PROP_LEFT_MARGIN, leftMargin); + CHECK_PROPERTY_CHANGE(PROP_RIGHT_MARGIN, rightMargin); + CHECK_PROPERTY_CHANGE(PROP_TOP_MARGIN, topMargin); + CHECK_PROPERTY_CHANGE(PROP_BOTTOM_MARGIN, bottomMargin); + + // Zone + changedProperties += _keyLight.getChangedProperties(); + changedProperties += _ambientLight.getChangedProperties(); + changedProperties += _skybox.getChangedProperties(); + changedProperties += _haze.getChangedProperties(); + changedProperties += _bloom.getChangedProperties(); + CHECK_PROPERTY_CHANGE(PROP_FLYING_ALLOWED, flyingAllowed); + CHECK_PROPERTY_CHANGE(PROP_GHOSTING_ALLOWED, ghostingAllowed); + CHECK_PROPERTY_CHANGE(PROP_FILTER_URL, filterURL); CHECK_PROPERTY_CHANGE(PROP_KEY_LIGHT_MODE, keyLightMode); CHECK_PROPERTY_CHANGE(PROP_AMBIENT_LIGHT_MODE, ambientLightMode); CHECK_PROPERTY_CHANGE(PROP_SKYBOX_MODE, skyboxMode); + CHECK_PROPERTY_CHANGE(PROP_HAZE_MODE, hazeMode); CHECK_PROPERTY_CHANGE(PROP_BLOOM_MODE, bloomMode); - CHECK_PROPERTY_CHANGE(PROP_SOURCE_URL, sourceUrl); + // Polyvox CHECK_PROPERTY_CHANGE(PROP_VOXEL_VOLUME_SIZE, voxelVolumeSize); CHECK_PROPERTY_CHANGE(PROP_VOXEL_DATA, voxelData); CHECK_PROPERTY_CHANGE(PROP_VOXEL_SURFACE_STYLE, voxelSurfaceStyle); - CHECK_PROPERTY_CHANGE(PROP_LINE_WIDTH, lineWidth); - CHECK_PROPERTY_CHANGE(PROP_LINE_POINTS, linePoints); - CHECK_PROPERTY_CHANGE(PROP_HREF, href); - CHECK_PROPERTY_CHANGE(PROP_DESCRIPTION, description); - CHECK_PROPERTY_CHANGE(PROP_FACE_CAMERA, faceCamera); - CHECK_PROPERTY_CHANGE(PROP_ACTION_DATA, actionData); - CHECK_PROPERTY_CHANGE(PROP_NORMALS, normals); - CHECK_PROPERTY_CHANGE(PROP_STROKE_COLORS, strokeColors); - CHECK_PROPERTY_CHANGE(PROP_STROKE_WIDTHS, strokeWidths); - CHECK_PROPERTY_CHANGE(PROP_IS_UV_MODE_STRETCH, isUVModeStretch); CHECK_PROPERTY_CHANGE(PROP_X_TEXTURE_URL, xTextureURL); CHECK_PROPERTY_CHANGE(PROP_Y_TEXTURE_URL, yTextureURL); CHECK_PROPERTY_CHANGE(PROP_Z_TEXTURE_URL, zTextureURL); @@ -460,44 +524,44 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_X_P_NEIGHBOR_ID, xPNeighborID); CHECK_PROPERTY_CHANGE(PROP_Y_P_NEIGHBOR_ID, yPNeighborID); CHECK_PROPERTY_CHANGE(PROP_Z_P_NEIGHBOR_ID, zPNeighborID); - CHECK_PROPERTY_CHANGE(PROP_PARENT_ID, parentID); - CHECK_PROPERTY_CHANGE(PROP_PARENT_JOINT_INDEX, parentJointIndex); - CHECK_PROPERTY_CHANGE(PROP_JOINT_ROTATIONS_SET, jointRotationsSet); - CHECK_PROPERTY_CHANGE(PROP_JOINT_ROTATIONS, jointRotations); - CHECK_PROPERTY_CHANGE(PROP_JOINT_TRANSLATIONS_SET, jointTranslationsSet); - CHECK_PROPERTY_CHANGE(PROP_JOINT_TRANSLATIONS, jointTranslations); - CHECK_PROPERTY_CHANGE(PROP_QUERY_AA_CUBE, queryAACube); - CHECK_PROPERTY_CHANGE(PROP_LOCAL_POSITION, localPosition); - CHECK_PROPERTY_CHANGE(PROP_LOCAL_ROTATION, localRotation); - CHECK_PROPERTY_CHANGE(PROP_LOCAL_VELOCITY, localVelocity); - CHECK_PROPERTY_CHANGE(PROP_LOCAL_ANGULAR_VELOCITY, localAngularVelocity); - CHECK_PROPERTY_CHANGE(PROP_LOCAL_DIMENSIONS, localDimensions); - CHECK_PROPERTY_CHANGE(PROP_FLYING_ALLOWED, flyingAllowed); - CHECK_PROPERTY_CHANGE(PROP_GHOSTING_ALLOWED, ghostingAllowed); - CHECK_PROPERTY_CHANGE(PROP_FILTER_URL, filterURL); - - CHECK_PROPERTY_CHANGE(PROP_ENTITY_HOST_TYPE, entityHostType); - CHECK_PROPERTY_CHANGE(PROP_OWNING_AVATAR_ID, owningAvatarID); - - CHECK_PROPERTY_CHANGE(PROP_SHAPE, shape); + // Web + CHECK_PROPERTY_CHANGE(PROP_SOURCE_URL, sourceUrl); CHECK_PROPERTY_CHANGE(PROP_DPI, dpi); - CHECK_PROPERTY_CHANGE(PROP_RELAY_PARENT_JOINTS, relayParentJoints); - CHECK_PROPERTY_CHANGE(PROP_CLONEABLE, cloneable); - CHECK_PROPERTY_CHANGE(PROP_CLONE_LIFETIME, cloneLifetime); - CHECK_PROPERTY_CHANGE(PROP_CLONE_LIMIT, cloneLimit); - CHECK_PROPERTY_CHANGE(PROP_CLONE_DYNAMIC, cloneDynamic); - CHECK_PROPERTY_CHANGE(PROP_CLONE_AVATAR_ENTITY, cloneAvatarEntity); - CHECK_PROPERTY_CHANGE(PROP_CLONE_ORIGIN_ID, cloneOriginID); + // Polyline + CHECK_PROPERTY_CHANGE(PROP_LINE_POINTS, linePoints); + CHECK_PROPERTY_CHANGE(PROP_STROKE_WIDTHS, strokeWidths); + CHECK_PROPERTY_CHANGE(PROP_STROKE_NORMALS, normals); + CHECK_PROPERTY_CHANGE(PROP_STROKE_COLORS, strokeColors); + CHECK_PROPERTY_CHANGE(PROP_IS_UV_MODE_STRETCH, isUVModeStretch); + CHECK_PROPERTY_CHANGE(PROP_LINE_GLOW, glow); + CHECK_PROPERTY_CHANGE(PROP_LINE_FACE_CAMERA, faceCamera); - changedProperties += _animation.getChangedProperties(); - changedProperties += _keyLight.getChangedProperties(); - changedProperties += _ambientLight.getChangedProperties(); - changedProperties += _skybox.getChangedProperties(); - changedProperties += _haze.getChangedProperties(); - changedProperties += _bloom.getChangedProperties(); - changedProperties += _grab.getChangedProperties(); + // Shape + CHECK_PROPERTY_CHANGE(PROP_SHAPE, shape); + + // Material + CHECK_PROPERTY_CHANGE(PROP_MATERIAL_URL, materialURL); + CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_MODE, materialMappingMode); + CHECK_PROPERTY_CHANGE(PROP_MATERIAL_PRIORITY, priority); + CHECK_PROPERTY_CHANGE(PROP_PARENT_MATERIAL_NAME, parentMaterialName); + CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_POS, materialMappingPos); + CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_SCALE, materialMappingScale); + CHECK_PROPERTY_CHANGE(PROP_MATERIAL_MAPPING_ROT, materialMappingRot); + CHECK_PROPERTY_CHANGE(PROP_MATERIAL_DATA, materialData); + CHECK_PROPERTY_CHANGE(PROP_MATERIAL_REPEAT, materialRepeat); + + // Image + CHECK_PROPERTY_CHANGE(PROP_IMAGE_URL, imageURL); + CHECK_PROPERTY_CHANGE(PROP_EMISSIVE, emissive); + CHECK_PROPERTY_CHANGE(PROP_KEEP_ASPECT_RATIO, keepAspectRatio); + CHECK_PROPERTY_CHANGE(PROP_SUB_IMAGE, subImage); + + // Grid + CHECK_PROPERTY_CHANGE(PROP_GRID_FOLLOW_CAMERA, followCamera); + CHECK_PROPERTY_CHANGE(PROP_MAJOR_GRID_EVERY, majorGridEvery); + CHECK_PROPERTY_CHANGE(PROP_MINOR_GRID_EVERY, minorGridEvery); return changedProperties; } @@ -592,16 +656,14 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * mass in the application of physics. * * @property {boolean} collisionless=false - Whether or not the entity should collide with items per its - * <code>collisionMask<code> property. If <code>true</code> then the entity does not collide. - * @property {boolean} ignoreForCollisions=false - Synonym for <code>collisionless</code>. + * <code>collisionMask<code> property. If <code>true</code> then the entity does not collide. A synonym is <code>ignoreForCollisions</code>. * @property {Entities.CollisionMask} collisionMask=31 - What types of items the entity should collide with. * @property {string} collidesWith="static,dynamic,kinematic,myAvatar,otherAvatar," - Synonym for <code>collisionMask</code>, * in text format. * @property {string} collisionSoundURL="" - The sound to play when the entity experiences a collision. Valid file formats are * as per the {@link SoundCache} object. * @property {boolean} dynamic=false - Whether or not the entity should be affected by collisions. If <code>true</code> then - * the entity's movement is affected by collisions. - * @property {boolean} collisionsWillMove=false - Synonym for <code>dynamic</code>. + * the entity's movement is affected by collisions. A synonym is <code>collisionsWillMove</code>. * * @property {string} href="" - A "hifi://" metaverse address that a user is taken to when they click on the entity. * @property {string} description="" - A description of the <code>href</code> property value. @@ -647,8 +709,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * is typically not used in scripts directly; rather, functions that manipulate an entity's actions update it. * The size of this property increases with the number of actions. Because this property value has to fit within a High * Fidelity datagram packet there is a limit to the number of actions that an entity can have, and edits which would result - * in overflow are rejected. - * <em>Read-only.</em> + * in overflow are rejected. <em>Read-only.</em> * @property {Entities.RenderInfo} renderInfo - Information on the cost of rendering the entity. Currently information is only * provided for <code>Model</code> entities. <em>Read-only.</em> * @@ -681,18 +742,20 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * * @see The different entity types have additional properties as follows: * @see {@link Entities.EntityProperties-Box|EntityProperties-Box} - * @see {@link Entities.EntityProperties-Light|EntityProperties-Light} - * @see {@link Entities.EntityProperties-Line|EntityProperties-Line} - * @see {@link Entities.EntityProperties-Material|EntityProperties-Material} + * @see {@link Entities.EntityProperties-Sphere|EntityProperties-Sphere} + * @see {@link Entities.EntityProperties-Shape|EntityProperties-Shape} * @see {@link Entities.EntityProperties-Model|EntityProperties-Model} + * @see {@link Entities.EntityProperties-Text|EntityProperties-Text} + * @see {@link Entities.EntityProperties-Image|EntityProperties-Image} + * @see {@link Entities.EntityProperties-Web|EntityProperties-Web} * @see {@link Entities.EntityProperties-ParticleEffect|EntityProperties-ParticleEffect} + * @see {@link Entities.EntityProperties-Line|EntityProperties-Line} * @see {@link Entities.EntityProperties-PolyLine|EntityProperties-PolyLine} * @see {@link Entities.EntityProperties-PolyVox|EntityProperties-PolyVox} - * @see {@link Entities.EntityProperties-Shape|EntityProperties-Shape} - * @see {@link Entities.EntityProperties-Sphere|EntityProperties-Sphere} - * @see {@link Entities.EntityProperties-Text|EntityProperties-Text} - * @see {@link Entities.EntityProperties-Web|EntityProperties-Web} + * @see {@link Entities.EntityProperties-Grid|EntityProperties-Grid} + * @see {@link Entities.EntityProperties-Light|EntityProperties-Light} * @see {@link Entities.EntityProperties-Zone|EntityProperties-Zone} + * @see {@link Entities.EntityProperties-Material|EntityProperties-Material} */ /**jsdoc @@ -733,16 +796,15 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { */ /**jsdoc - * The <code>"Line"</code> {@link Entities.EntityType|EntityType} draws thin, straight lines between a sequence of two or more - * points. + * The <code>"Line"</code> {@link Entities.EntityType|EntityType} draws thin, straight lines between a sequence of two or more + * points. Deprecated: Use PolyLines instead. * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. * @typedef {object} Entities.EntityProperties-Line - * @property {Vec3} dimensions=0.1,0.1,0.1 - The dimensions of the entity. Must be sufficient to contain all the + * @property {Vec3} dimensions=0.1,0.1,0.1 - The dimensions of the entity. Must be sufficient to contain all the * <code>linePoints</code>. * @property {Vec3[]} linePoints=[]] - The sequence of points to draw lines between. The values are relative to the entity's - * position. A maximum of 70 points can be specified. The property's value is set only if all the <code>linePoints</code> + * position. A maximum of 70 points can be specified. The property's value is set only if all the <code>linePoints</code> * lie within the entity's <code>dimensions</code>. - * @property {number} lineWidth=2 - <em>Currently not used.</em> * @property {Color} color=255,255,255 - The color of the line. * @example <caption>Draw lines in a "V".</caption> * var entity = Entities.addEntity({ @@ -812,7 +874,6 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * materialData: JSON.stringify({ * materialVersion: 1, * materials: { - * // Can only set albedo on a Shape entity. * // Value overrides entity's "color" property. * albedo: [1.0, 1.0, 0] // Yellow * } @@ -829,8 +890,6 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * <code>{@link Entities.EntityProperties|naturalDimensions}</code>. * @property {Color} color=255,255,255 - <em>Currently not used.</em> * @property {string} modelURL="" - The URL of the FBX of OBJ model. Baked FBX models' URLs end in ".baked.fbx".<br /> - * Note: If the name ends with <code>"default-image-model.fbx"</code> then the entity is considered to be an "Image" - * entity, in which case the <code>textures</code> property should be set per the example. * @property {string} textures="" - A JSON string of texture name, URL pairs used when rendering the model in place of the * model's original textures. Use a texture name from the <code>originalTextures</code> property to override that texture. * Only the texture names and URLs to be overridden need be specified; original textures are used where there are no @@ -846,22 +905,22 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * * @property {Entities.AnimationProperties} animation - An animation to play on the model. * - * @property {Quat[]} jointRotations=[]] - Joint rotations applied to the model; <code>[]</code> if none are applied or the + * @property {Quat[]} jointRotations=[] - Joint rotations applied to the model; <code>[]</code> if none are applied or the * model hasn't loaded. The array indexes are per {@link Entities.getJointIndex|getJointIndex}. Rotations are relative to * each joint's parent.<br /> * Joint rotations can be set by {@link Entities.setLocalJointRotation|setLocalJointRotation} and similar functions, or by * setting the value of this property. If you set a joint rotation using this property you also need to set the * corresponding <code>jointRotationsSet</code> value to <code>true</code>. - * @property {boolean[]} jointRotationsSet=[]] - <code>true</code> values for joints that have had rotations applied, + * @property {boolean[]} jointRotationsSet=[] - <code>true</code> values for joints that have had rotations applied, * <code>false</code> otherwise; <code>[]</code> if none are applied or the model hasn't loaded. The array indexes are per * {@link Entities.getJointIndex|getJointIndex}. - * @property {Vec3[]} jointTranslations=[]] - Joint translations applied to the model; <code>[]</code> if none are applied or + * @property {Vec3[]} jointTranslations=[] - Joint translations applied to the model; <code>[]</code> if none are applied or * the model hasn't loaded. The array indexes are per {@link Entities.getJointIndex|getJointIndex}. Rotations are relative * to each joint's parent.<br /> * Joint translations can be set by {@link Entities.setLocalJointTranslation|setLocalJointTranslation} and similar * functions, or by setting the value of this property. If you set a joint translation using this property you also need to * set the corresponding <code>jointTranslationsSet</code> value to <code>true</code>. - * @property {boolean[]} jointTranslationsSet=[]] - <code>true</code> values for joints that have had translations applied, + * @property {boolean[]} jointTranslationsSet=[] - <code>true</code> values for joints that have had translations applied, * <code>false</code> otherwise; <code>[]</code> if none are applied or the model hasn't loaded. The array indexes are per * {@link Entities.getJointIndex|getJointIndex}. * @property {boolean} relayParentJoints=false - If <code>true</code> and the entity is parented to an avatar, then the @@ -876,24 +935,6 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * dimensions: { x: 0.0945, y: 0.0921, z: 0.0423 }, * lifetime: 300 // Delete after 5 minutes. * }); - * @example <caption>Create an "Image" entity like you can in the Create app.</caption> - * var IMAGE_MODEL = "https://hifi-content.s3.amazonaws.com/DomainContent/production/default-image-model.fbx"; - * var DEFAULT_IMAGE = "https://hifi-content.s3.amazonaws.com/DomainContent/production/no-image.jpg"; - * var entity = Entities.addEntity({ - * type: "Model", - * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0.5, z: -3 })), - * rotation: MyAvatar.orientation, - * dimensions: { - * x: 0.5385, - * y: 0.2819, - * z: 0.0092 - * }, - * shapeType: "box", - * collisionless: true, - * modelURL: IMAGE_MODEL, - * textures: JSON.stringify({ "tex.picture": DEFAULT_IMAGE }), - * lifetime: 300 // Delete after 5 minutes - * }); */ /**jsdoc @@ -1007,20 +1048,21 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @typedef {object} Entities.EntityProperties-PolyLine * @property {Vec3} dimensions=1,1,1 - The dimensions of the entity, i.e., the size of the bounding box that contains the * lines drawn. - * @property {Vec3[]} linePoints=[]] - The sequence of points to draw lines between. The values are relative to the entity's + * @property {Vec3[]} linePoints=[] - The sequence of points to draw lines between. The values are relative to the entity's * position. A maximum of 70 points can be specified. Must be specified in order for the entity to render. - * @property {Vec3[]} normals=[]] - The normal vectors for the line's surface at the <code>linePoints</code>. The values are + * @property {Vec3[]} normals=[] - The normal vectors for the line's surface at the <code>linePoints</code>. The values are * relative to the entity's orientation. Must be specified in order for the entity to render. - * @property {number[]} strokeWidths=[]] - The widths, in m, of the line at the <code>linePoints</code>. Must be specified in + * @property {number[]} strokeWidths=[] - The widths, in m, of the line at the <code>linePoints</code>. Must be specified in * order for the entity to render. - * @property {number} lineWidth=2 - <em>Currently not used.</code> - * @property {Vec3[]} strokeColors=[]] - <em>Currently not used.</em> - * @property {Color} color=255,255,255 - The base color of the line, which is multiplied with the color of the texture for - * rendering. + * @property {Vec3[]} strokeColors=[] - The base colors of each point, which are multiplied with the color of the texture, going from 0-1. + * If strokeColors.length < the number of points, <code>color</code> is used for the remaining points. + * @property {Color} color=255,255,255 - Used as the color for each point if <code>strokeColors</code> is too short. * @property {string} textures="" - The URL of a JPG or PNG texture to use for the lines. If you want transparency, use PNG * format. * @property {boolean} isUVModeStretch=true - If <code>true</code>, the texture is stretched to fill the whole line, otherwise * the texture repeats along the line. + * @property {bool} glow=false - If <code>true</code>, the alpha of the strokes will drop off farther from the center. + * @property {bool} faceCamera=false - If <code>true</code>, each line segment will rotate to face the camera. * @example <caption>Draw a textured "V".</caption> * var entity = Entities.addEntity({ * type: "PolyLine", @@ -1133,9 +1175,17 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * created using <code>\n</code>. Overflowing lines are not displayed. * @property {number} lineHeight=0.1 - The height of each line of text (thus determining the font size). * @property {Color} textColor=255,255,255 - The color of the text. + * @property {number} textAlpha=1.0 - The text alpha. * @property {Color} backgroundColor=0,0,0 - The color of the background rectangle. - * @property {boolean} faceCamera=false - If <code>true</code>, the entity is oriented to face each user's camera (i.e., it - * differs for each user present). + * @property {number} backgroundAlpha=1.0 - The background alpha. + * @property {BillboardMode} billboardMode="none" - If <code>"none"</code>, the entity is not billboarded. If <code>"yaw"</code>, the entity will be + * oriented to follow your camera around the y-axis. If <code>"full"</code> the entity will be oriented to face your camera. The following deprecated + * behavior is also supported: you can also set <code>"faceCamera"</code> to <code>true</code> to set <code>billboardMode</code> to "yaw", and you can set + * <code>"isFacingAvatar"</code> to <code>true</code> to set <code>billboardMode</code> to "full". Setting either to <code>false</code> sets the mode to "none" + * @property {number} leftMargin=0.0 - The left margin, in meters. + * @property {number} rightMargin=0.0 - The right margin, in meters. + * @property {number} topMargin=0.0 - The top margin, in meters. + * @property {number} bottomMargin=0.0 - The bottom margin, in meters. * @example <caption>Create a text entity.</caption> * var text = Entities.addEntity({ * type: "Text", @@ -1143,7 +1193,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * dimensions: { x: 0.6, y: 0.3, z: 0.01 }, * lineHeight: 0.12, * text: "Hello\nthere!", - * faceCamera: true, + * billboardMode: "yaw", * lifetime: 300 // Delete after 5 minutes. * }); */ @@ -1254,6 +1304,56 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * }); */ +/**jsdoc + * The <code>"Image"</code> {@link Entities.EntityType|EntityType} displays an image on a 2D rectangle in the domain. + * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * @typedef {object} Entities.EntityProperties-Image + * @property {string} imageURL="" - The URL of the image to use. + * @property {boolean} emissive=false - Whether or not the image should be emissive (unlit). + * @property {boolean} keepAspectRatio=true - Whether or not the image should maintain its aspect ratio. + * @property {BillboardMode} billboardMode="none" - If <code>"none"</code>, the entity is not billboarded. If <code>"yaw"</code>, the entity will be + * oriented to follow your camera around the y-axis. If <code>"full"</code> the entity will be oriented to face your camera. The following deprecated + * behavior is also supported: you can also set <code>"faceCamera"</code> to <code>true</code> to set <code>billboardMode</code> to "yaw", and you can set + * <code>"isFacingAvatar"</code> to <code>true</code> to set <code>billboardMode</code> to "full". Setting either to <code>false</code> sets the mode to "none" + * @property {Rect} subImage={ x: 0, y: 0, width: -1, height: -1 } - The portion of the image to display. If width or height are -1, defaults to + * the full image in that dimension. + * @property {Color} color=255,255,255 - The color of the image. + * @property {number} alpha=1 - The alpha of the image. + * @example <caption>Create a image entity.</caption> + * var image = Entities.addEntity({ + * type: "Image", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * dimensions: { x: 0.6, y: 0.3, z: 0.01 }, + * imageURL: "https://images.pexels.com/photos/1020315/pexels-photo-1020315.jpeg", + * billboardMode: "yaw", + * lifetime: 300 // Delete after 5 minutes. + * }); + */ + +/**jsdoc + * The <code>"Grid"</code> {@link Entities.EntityType|EntityType} displays a grid on a 2D plane. + * It has properties in addition to the common {@link Entities.EntityProperties|EntityProperties}. + * @typedef {object} Entities.EntityProperties-Grid + * @property {Color} color=255,255,255 - The color of the grid. + * @property {number} alpha=1 - The alpha of the grid. + * @property {boolean} followCamera=true - If <code>true</code>, the grid is always visible even as the camera moves to another + * position. + * @property {number} majorGridEvery=5 - Integer number of <code>minorGridEvery</code> intervals at which to draw a thick grid + * line. Minimum value = <code>1</code>. + * @property {number} minorGridEvery=1 - Real number of meters at which to draw thin grid lines. Minimum value = + * <code>0.001</code>. + * @example <caption>Create a grid entity.</caption> + * var grid = Entities.addEntity({ + * type: "Grid", + * position: Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, { x: 0, y: 0, z: -5 })), + * dimensions: { x: 100.0, y: 100.0, z: 0.01 }, + * followCamera: false, + * majorGridEvery: 4, + * minorGridEvery: 0.5, + * lifetime: 300 // Delete after 5 minutes. + * }); + */ + QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool skipDefaults, bool allowUnknownCreateTime, bool strictSemantics, EntityPsuedoPropertyFlags psueudoPropertyFlags) const { @@ -1279,12 +1379,6 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool if (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::Type)) { COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_ALWAYS(type, EntityTypes::getEntityTypeName(_type)); } - if (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::Created)) { - auto created = QDateTime::fromMSecsSinceEpoch(getCreated() / 1000.0f, Qt::UTC); // usec per msec - created.setTimeSpec(Qt::OffsetFromUTC); - COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_ALWAYS(created, created.toString(Qt::ISODate)); - } - if ((!skipDefaults || _lifetime != defaultEntityProperties._lifetime) && !strictSemantics) { if (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::Age)) { COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(age, getAge()); // gettable, but not settable @@ -1293,48 +1387,69 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(ageAsText, formatSecondsElapsed(getAge())); // gettable, but not settable } } - if (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::LastEdited)) { properties.setProperty("lastEdited", convertScriptValue(engine, _lastEdited)); } - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LAST_EDITED_BY, lastEditedBy); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_POSITION, position); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DIMENSIONS, dimensions); if (!skipDefaults) { COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DIMENSIONS, naturalDimensions); // gettable, but not settable COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_POSITION, naturalPosition); } + + // Core properties + //COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SIMULATION_OWNER, simulationOwner); // not exposed yet + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_VISIBLE, visible); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_NAME, name); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCKED, locked); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_USER_DATA, userData); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_HREF, href); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DESCRIPTION, description); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_POSITION, position); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DIMENSIONS, dimensions); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ROTATION, rotation); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_REGISTRATION_POINT, registrationPoint); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CREATED, created); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LAST_EDITED_BY, lastEditedBy); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_ENTITY_HOST_TYPE, entityHostType, getEntityHostTypeAsString()); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_OWNING_AVATAR_ID, owningAvatarID); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARENT_ID, parentID); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARENT_JOINT_INDEX, parentJointIndex); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_QUERY_AA_CUBE, queryAACube); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CAN_CAST_SHADOW, canCastShadow); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_VISIBLE_IN_SECONDARY_CAMERA, isVisibleInSecondaryCamera); + _grab.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); + + // Physics + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DENSITY, density); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_VELOCITY, velocity); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ANGULAR_VELOCITY, angularVelocity); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_GRAVITY, gravity); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ACCELERATION, acceleration); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DAMPING, damping); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ANGULAR_DAMPING, angularDamping); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_RESTITUTION, restitution); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FRICTION, friction); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DENSITY, density); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LIFETIME, lifetime); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SCRIPT, script); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SCRIPT_TIMESTAMP, scriptTimestamp); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SERVER_SCRIPTS, serverScripts); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_REGISTRATION_POINT, registrationPoint); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ANGULAR_VELOCITY, angularVelocity); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ANGULAR_DAMPING, angularDamping); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_VISIBLE, visible); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CAN_CAST_SHADOW, canCastShadow); // Relevant to Shape and Model entities only. COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLLISIONLESS, collisionless); COPY_PROXY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_COLLISIONLESS, collisionless, ignoreForCollisions, getCollisionless()); // legacy support COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLLISION_MASK, collisionMask); COPY_PROXY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_COLLISION_MASK, collisionMask, collidesWith, getCollisionMaskAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DYNAMIC, dynamic); COPY_PROXY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_DYNAMIC, dynamic, collisionsWillMove, getDynamic()); // legacy support - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_HREF, href); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_DESCRIPTION, description); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FACE_CAMERA, faceCamera); // Text only. + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLLISION_SOUND_URL, collisionSoundURL); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ACTION_DATA, actionData); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCKED, locked); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_USER_DATA, userData); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_VISIBLE_IN_SECONDARY_CAMERA, isVisibleInSecondaryCamera); + + // Cloning + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONEABLE, cloneable); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONE_LIFETIME, cloneLifetime); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONE_LIMIT, cloneLimit); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONE_DYNAMIC, cloneDynamic); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONE_AVATAR_ENTITY, cloneAvatarEntity); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONE_ORIGIN_ID, cloneOriginID); + + // Scripts + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SCRIPT, script); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SCRIPT_TIMESTAMP, scriptTimestamp); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SERVER_SCRIPTS, serverScripts); // Certifiable Properties COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ITEM_NAME, itemName); @@ -1349,38 +1464,54 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CERTIFICATE_ID, certificateID); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_STATIC_CERTIFICATE_VERSION, staticCertificateVersion); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_NAME, name); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLLISION_SOUND_URL, collisionSoundURL); + // Local props for scripts + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_POSITION, localPosition); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_ROTATION, localRotation); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_VELOCITY, localVelocity); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_ANGULAR_VELOCITY, localAngularVelocity); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_DIMENSIONS, localDimensions); // Particles only if (_type == EntityTypes::ParticleEffect) { - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EMITTING_PARTICLES, isEmitting); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SHAPE_TYPE, shapeType, getShapeTypeAsString()); + COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_TEXTURES, textures); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MAX_PARTICLES, maxParticles); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LIFESPAN, lifespan); + + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EMITTING_PARTICLES, isEmitting); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EMIT_RATE, emitRate); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EMIT_SPEED, emitSpeed); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SPEED_SPREAD, speedSpread); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EMIT_ORIENTATION, emitOrientation); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EMIT_DIMENSIONS, emitDimensions); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EMIT_RADIUS_START, emitRadiusStart); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_POLAR_START, polarStart); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_POLAR_FINISH, polarFinish); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_AZIMUTH_START, azimuthStart); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_AZIMUTH_FINISH, azimuthFinish); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EMIT_ACCELERATION, emitAcceleration); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ACCELERATION_SPREAD, accelerationSpread); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARTICLE_RADIUS, particleRadius); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_RADIUS_SPREAD, radiusSpread); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_RADIUS_START, radiusStart); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_RADIUS_FINISH, radiusFinish); - COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color); + COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR_SPREAD, colorSpread, u8vec3Color); COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR_START, colorStart, vec3Color); COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR_FINISH, colorFinish, vec3Color); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA_SPREAD, alphaSpread); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA_START, alphaStart); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA_FINISH, alphaFinish); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EMITTER_SHOULD_TRAIL, emitterShouldTrail); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARTICLE_SPIN, particleSpin); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SPIN_SPREAD, spinSpread); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SPIN_START, spinStart); @@ -1390,22 +1521,22 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool // Models only if (_type == EntityTypes::Model) { + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SHAPE_TYPE, shapeType, getShapeTypeAsString()); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COMPOUND_SHAPE_URL, compoundShapeURL); + COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_TEXTURES, textures); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MODEL_URL, modelURL); - if (!psuedoPropertyFlagsButDesiredEmpty) { - _animation.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); - } COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_JOINT_ROTATIONS_SET, jointRotationsSet); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_JOINT_ROTATIONS, jointRotations); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_JOINT_TRANSLATIONS_SET, jointTranslationsSet); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_JOINT_TRANSLATIONS, jointTranslations); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_RELAY_PARENT_JOINTS, relayParentJoints); - COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color); + if (!psuedoPropertyFlagsButDesiredEmpty) { + _animation.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); + } } - if (_type == EntityTypes::Model || _type == EntityTypes::Zone || _type == EntityTypes::ParticleEffect) { - COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SHAPE_TYPE, shapeType, getShapeTypeAsString()); - } - // FIXME: Shouldn't provide a shapeType property for Box and Sphere entities. if (_type == EntityTypes::Box) { COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SHAPE_TYPE, shapeType, QString("Box")); @@ -1415,18 +1546,9 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool } if (_type == EntityTypes::Box || _type == EntityTypes::Sphere || _type == EntityTypes::Shape) { - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SHAPE, shape); COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color); - } - - // FIXME - it seems like ParticleEffect should also support this - if (_type == EntityTypes::Model || _type == EntityTypes::Zone) { - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COMPOUND_SHAPE_URL, compoundShapeURL); - } - - // Models & Particles - if (_type == EntityTypes::Model || _type == EntityTypes::ParticleEffect) { - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_TEXTURES, textures); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SHAPE, shape); } // Lights only @@ -1434,9 +1556,9 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IS_SPOTLIGHT, isSpotlight); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_INTENSITY, intensity); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FALLOFF_RADIUS, falloffRadius); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EXPONENT, exponent); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CUTOFF, cutoff); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FALLOFF_RADIUS, falloffRadius); } // Text only @@ -1444,32 +1566,38 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_TEXT, text); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LINE_HEIGHT, lineHeight); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_TYPED(PROP_TEXT_COLOR, textColor, getTextColor(), u8vec3Color); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_TEXT_ALPHA, textAlpha); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_TYPED(PROP_BACKGROUND_COLOR, backgroundColor, getBackgroundColor(), u8vec3Color); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_BACKGROUND_ALPHA, backgroundAlpha); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BILLBOARD_MODE, billboardMode, getBillboardModeAsString()); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LEFT_MARGIN, leftMargin); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_RIGHT_MARGIN, rightMargin); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_TOP_MARGIN, topMargin); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_BOTTOM_MARGIN, bottomMargin); } // Zones only if (_type == EntityTypes::Zone) { + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SHAPE_TYPE, shapeType, getShapeTypeAsString()); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COMPOUND_SHAPE_URL, compoundShapeURL); + if (!psuedoPropertyFlagsButDesiredEmpty) { _keyLight.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); _ambientLight.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); - _skybox.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); + _haze.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); + _bloom.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); } + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FLYING_ALLOWED, flyingAllowed); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_GHOSTING_ALLOWED, ghostingAllowed); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FILTER_URL, filterURL); - COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_HAZE_MODE, hazeMode, getHazeModeAsString()); - if (!psuedoPropertyFlagsButDesiredEmpty) { - _haze.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); - } - COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BLOOM_MODE, bloomMode, getBloomModeAsString()); - if (!psuedoPropertyFlagsButDesiredEmpty) { - _bloom.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); - } COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_KEY_LIGHT_MODE, keyLightMode, getKeyLightModeAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_AMBIENT_LIGHT_MODE, ambientLightMode, getAmbientLightModeAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_SKYBOX_MODE, skyboxMode, getSkyboxModeAsString()); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_HAZE_MODE, hazeMode, getHazeModeAsString()); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BLOOM_MODE, bloomMode, getBloomModeAsString()); } // Web only @@ -1496,16 +1624,25 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_Z_P_NEIGHBOR_ID, zPNeighborID); } - // Lines & PolyLines - if (_type == EntityTypes::Line || _type == EntityTypes::PolyLine) { + // Lines + if (_type == EntityTypes::Line) { COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LINE_WIDTH, lineWidth); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LINE_POINTS, linePoints); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_NORMALS, normals); // Polyline only. - COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_STROKE_COLORS, strokeColors, qVectorVec3Color); // Polyline only. - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_STROKE_WIDTHS, strokeWidths); // Polyline only. - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_TEXTURES, textures); // Polyline only. - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IS_UV_MODE_STRETCH, isUVModeStretch); // Polyline only. + } + + // Polylines + if (_type == EntityTypes::PolyLine) { + COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_TEXTURES, textures); + + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LINE_POINTS, linePoints); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_STROKE_WIDTHS, strokeWidths); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_STROKE_NORMALS, normals); + COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_STROKE_COLORS, strokeColors, qVectorVec3Color); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IS_UV_MODE_STRETCH, isUVModeStretch); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LINE_GLOW, glow); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LINE_FACE_CAMERA, faceCamera); } // Materials @@ -1521,6 +1658,36 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MATERIAL_REPEAT, materialRepeat); } + // Image only + if (_type == EntityTypes::Image) { + COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha); + + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_IMAGE_URL, imageURL); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_EMISSIVE, emissive); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_KEEP_ASPECT_RATIO, keepAspectRatio); + COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_BILLBOARD_MODE, billboardMode, getBillboardModeAsString()); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SUB_IMAGE, subImage); + + // Handle conversions to old 'textures' property from "imageURL" + if (((!psuedoPropertyFlagsButDesiredEmpty && _desiredProperties.isEmpty()) || _desiredProperties.getHasProperty(PROP_IMAGE_URL)) && + (!skipDefaults || defaultEntityProperties._imageURL != _imageURL)) { + QScriptValue textures = engine->newObject(); + textures.setProperty("tex.picture", _imageURL); + properties.setProperty("textures", textures); + } + } + + // Grid only + if (_type == EntityTypes::Grid) { + COPY_PROPERTY_TO_QSCRIPTVALUE_TYPED(PROP_COLOR, color, u8vec3Color); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_ALPHA, alpha); + + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_GRID_FOLLOW_CAMERA, followCamera); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MAJOR_GRID_EVERY, majorGridEvery); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MINOR_GRID_EVERY, minorGridEvery); + } + /**jsdoc * The axis-aligned bounding box of an entity. * @typedef {object} Entities.BoundingBox @@ -1550,29 +1717,6 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER_NO_SKIP(originalTextures, textureNamesStr); // gettable, but not settable } - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARENT_ID, parentID); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_PARENT_JOINT_INDEX, parentJointIndex); - - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_QUERY_AA_CUBE, queryAACube); - - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_POSITION, localPosition); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_ROTATION, localRotation); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_VELOCITY, localVelocity); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_ANGULAR_VELOCITY, localAngularVelocity); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCAL_DIMENSIONS, localDimensions); - - COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_ENTITY_HOST_TYPE, entityHostType, getEntityHostTypeAsString()); // Gettable but not settable except at entity creation - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_OWNING_AVATAR_ID, owningAvatarID); // Gettable but not settable - - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONEABLE, cloneable); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONE_LIFETIME, cloneLifetime); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONE_LIMIT, cloneLimit); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONE_DYNAMIC, cloneDynamic); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONE_AVATAR_ENTITY, cloneAvatarEntity); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_CLONE_ORIGIN_ID, cloneOriginID); - - _grab.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties); - // Rendering info if (!skipDefaults && !strictSemantics && (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::RenderInfo))) { @@ -1611,8 +1755,12 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool properties.setProperty("localEntity", convertScriptValue(engine, getEntityHostType() == entity::HostType::LOCAL)); } - // FIXME - I don't think these properties are supported any more - //COPY_PROPERTY_TO_QSCRIPTVALUE(localRenderAlpha); + if (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::FaceCamera)) { + properties.setProperty("faceCamera", convertScriptValue(engine, getBillboardMode() == BillboardMode::YAW)); + } + if (!psuedoPropertyFlagsActive || psueudoPropertyFlags.test(EntityPsuedoPropertyFlag::IsFacingAvatar)) { + properties.setProperty("isFacingAvatar", convertScriptValue(engine, getBillboardMode() == BillboardMode::FULL)); + } return properties; } @@ -1623,57 +1771,97 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool setType(typeScriptValue.toVariant().toString()); } - COPY_PROPERTY_FROM_QSCRIPTVALUE(lastEditedBy, QUuid, setLastEditedBy); + // Core + if (!honorReadOnly) { + // not handled yet + // COPY_PROPERTY_FROM_QSCRIPTVALUE(simulationOwner, SimulationOwner, setSimulationOwner); + } + COPY_PROPERTY_FROM_QSCRIPTVALUE(visible, bool, setVisible); + COPY_PROPERTY_FROM_QSCRIPTVALUE(name, QString, setName); + COPY_PROPERTY_FROM_QSCRIPTVALUE(locked, bool, setLocked); + COPY_PROPERTY_FROM_QSCRIPTVALUE(userData, QString, setUserData); + COPY_PROPERTY_FROM_QSCRIPTVALUE(href, QString, setHref); + COPY_PROPERTY_FROM_QSCRIPTVALUE(description, QString, setDescription); COPY_PROPERTY_FROM_QSCRIPTVALUE(position, vec3, setPosition); COPY_PROPERTY_FROM_QSCRIPTVALUE(dimensions, vec3, setDimensions); COPY_PROPERTY_FROM_QSCRIPTVALUE(rotation, quat, setRotation); + COPY_PROPERTY_FROM_QSCRIPTVALUE(registrationPoint, vec3, setRegistrationPoint); + if (!honorReadOnly) { + COPY_PROPERTY_FROM_QSCRIPTVALUE(created, quint64, setCreated); + COPY_PROPERTY_FROM_QSCRIPTVALUE(lastEditedBy, QUuid, setLastEditedBy); + COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(entityHostType, EntityHostType); + COPY_PROPERTY_FROM_QSCRIPTVALUE(owningAvatarID, QUuid, setOwningAvatarID); + } + COPY_PROPERTY_FROM_QSCRIPTVALUE(parentID, QUuid, setParentID); + COPY_PROPERTY_FROM_QSCRIPTVALUE(parentJointIndex, quint16, setParentJointIndex); + COPY_PROPERTY_FROM_QSCRIPTVALUE(queryAACube, AACube, setQueryAACube); // TODO: should scripts be able to set this? + COPY_PROPERTY_FROM_QSCRIPTVALUE(canCastShadow, bool, setCanCastShadow); + COPY_PROPERTY_FROM_QSCRIPTVALUE(isVisibleInSecondaryCamera, bool, setIsVisibleInSecondaryCamera); + _grab.copyFromScriptValue(object, _defaultSettings); + + // Physics COPY_PROPERTY_FROM_QSCRIPTVALUE(density, float, setDensity); COPY_PROPERTY_FROM_QSCRIPTVALUE(velocity, vec3, setVelocity); + COPY_PROPERTY_FROM_QSCRIPTVALUE(angularVelocity, vec3, setAngularVelocity); COPY_PROPERTY_FROM_QSCRIPTVALUE(gravity, vec3, setGravity); COPY_PROPERTY_FROM_QSCRIPTVALUE(acceleration, vec3, setAcceleration); COPY_PROPERTY_FROM_QSCRIPTVALUE(damping, float, setDamping); + COPY_PROPERTY_FROM_QSCRIPTVALUE(angularDamping, float, setAngularDamping); COPY_PROPERTY_FROM_QSCRIPTVALUE(restitution, float, setRestitution); COPY_PROPERTY_FROM_QSCRIPTVALUE(friction, float, setFriction); COPY_PROPERTY_FROM_QSCRIPTVALUE(lifetime, float, setLifetime); - COPY_PROPERTY_FROM_QSCRIPTVALUE(script, QString, setScript); - COPY_PROPERTY_FROM_QSCRIPTVALUE(scriptTimestamp, quint64, setScriptTimestamp); - COPY_PROPERTY_FROM_QSCRIPTVALUE(serverScripts, QString, setServerScripts); - COPY_PROPERTY_FROM_QSCRIPTVALUE(registrationPoint, vec3, setRegistrationPoint); - COPY_PROPERTY_FROM_QSCRIPTVALUE(angularVelocity, vec3, setAngularVelocity); - COPY_PROPERTY_FROM_QSCRIPTVALUE(angularDamping, float, setAngularDamping); - COPY_PROPERTY_FROM_QSCRIPTVALUE(visible, bool, setVisible); - COPY_PROPERTY_FROM_QSCRIPTVALUE(canCastShadow, bool, setCanCastShadow); - COPY_PROPERTY_FROM_QSCRIPTVALUE(color, u8vec3Color, setColor); - COPY_PROPERTY_FROM_QSCRIPTVALUE(colorSpread, u8vec3Color, setColorSpread); - COPY_PROPERTY_FROM_QSCRIPTVALUE(colorStart, vec3Color, setColorStart); - COPY_PROPERTY_FROM_QSCRIPTVALUE(colorFinish, vec3Color, setColorFinish); - COPY_PROPERTY_FROM_QSCRIPTVALUE(alpha, float, setAlpha); - COPY_PROPERTY_FROM_QSCRIPTVALUE(alphaSpread, float, setAlphaSpread); - COPY_PROPERTY_FROM_QSCRIPTVALUE(alphaStart, float, setAlphaStart); - COPY_PROPERTY_FROM_QSCRIPTVALUE(alphaFinish, float, setAlphaFinish); - COPY_PROPERTY_FROM_QSCRIPTVALUE(emitterShouldTrail , bool, setEmitterShouldTrail); - COPY_PROPERTY_FROM_QSCRIPTVALUE(modelURL, QString, setModelURL); - COPY_PROPERTY_FROM_QSCRIPTVALUE(compoundShapeURL, QString, setCompoundShapeURL); - COPY_PROPERTY_FROM_QSCRIPTVALUE(localRenderAlpha, float, setLocalRenderAlpha); COPY_PROPERTY_FROM_QSCRIPTVALUE(collisionless, bool, setCollisionless); COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(ignoreForCollisions, bool, setCollisionless, getCollisionless); // legacy support COPY_PROPERTY_FROM_QSCRIPTVALUE(collisionMask, uint16_t, setCollisionMask); COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(collidesWith, CollisionMask); COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(collisionsWillMove, bool, setDynamic, getDynamic); // legacy support COPY_PROPERTY_FROM_QSCRIPTVALUE(dynamic, bool, setDynamic); - COPY_PROPERTY_FROM_QSCRIPTVALUE(isSpotlight, bool, setIsSpotlight); - COPY_PROPERTY_FROM_QSCRIPTVALUE(intensity, float, setIntensity); - COPY_PROPERTY_FROM_QSCRIPTVALUE(falloffRadius, float, setFalloffRadius); - COPY_PROPERTY_FROM_QSCRIPTVALUE(exponent, float, setExponent); - COPY_PROPERTY_FROM_QSCRIPTVALUE(cutoff, float, setCutoff); - COPY_PROPERTY_FROM_QSCRIPTVALUE(locked, bool, setLocked); - COPY_PROPERTY_FROM_QSCRIPTVALUE(textures, QString, setTextures); - COPY_PROPERTY_FROM_QSCRIPTVALUE(userData, QString, setUserData); - COPY_PROPERTY_FROM_QSCRIPTVALUE(text, QString, setText); - COPY_PROPERTY_FROM_QSCRIPTVALUE(lineHeight, float, setLineHeight); - COPY_PROPERTY_FROM_QSCRIPTVALUE(textColor, u8vec3Color, setTextColor); - COPY_PROPERTY_FROM_QSCRIPTVALUE(backgroundColor, u8vec3Color, setBackgroundColor); + COPY_PROPERTY_FROM_QSCRIPTVALUE(collisionSoundURL, QString, setCollisionSoundURL); + if (!honorReadOnly) { + COPY_PROPERTY_FROM_QSCRIPTVALUE(actionData, QByteArray, setActionData); + } + + // Cloning + COPY_PROPERTY_FROM_QSCRIPTVALUE(cloneable, bool, setCloneable); + COPY_PROPERTY_FROM_QSCRIPTVALUE(cloneLifetime, float, setCloneLifetime); + COPY_PROPERTY_FROM_QSCRIPTVALUE(cloneLimit, float, setCloneLimit); + COPY_PROPERTY_FROM_QSCRIPTVALUE(cloneDynamic, bool, setCloneDynamic); + COPY_PROPERTY_FROM_QSCRIPTVALUE(cloneAvatarEntity, bool, setCloneAvatarEntity); + COPY_PROPERTY_FROM_QSCRIPTVALUE(cloneOriginID, QUuid, setCloneOriginID); + + // Scripts + COPY_PROPERTY_FROM_QSCRIPTVALUE(script, QString, setScript); + COPY_PROPERTY_FROM_QSCRIPTVALUE(scriptTimestamp, quint64, setScriptTimestamp); + COPY_PROPERTY_FROM_QSCRIPTVALUE(serverScripts, QString, setServerScripts); + + // Certifiable Properties + COPY_PROPERTY_FROM_QSCRIPTVALUE(itemName, QString, setItemName); + COPY_PROPERTY_FROM_QSCRIPTVALUE(itemDescription, QString, setItemDescription); + COPY_PROPERTY_FROM_QSCRIPTVALUE(itemCategories, QString, setItemCategories); + COPY_PROPERTY_FROM_QSCRIPTVALUE(itemArtist, QString, setItemArtist); + COPY_PROPERTY_FROM_QSCRIPTVALUE(itemLicense, QString, setItemLicense); + COPY_PROPERTY_FROM_QSCRIPTVALUE(limitedRun, quint32, setLimitedRun); + COPY_PROPERTY_FROM_QSCRIPTVALUE(marketplaceID, QString, setMarketplaceID); + COPY_PROPERTY_FROM_QSCRIPTVALUE(editionNumber, quint32, setEditionNumber); + COPY_PROPERTY_FROM_QSCRIPTVALUE(entityInstanceNumber, quint32, setEntityInstanceNumber); + COPY_PROPERTY_FROM_QSCRIPTVALUE(certificateID, QString, setCertificateID); + COPY_PROPERTY_FROM_QSCRIPTVALUE(staticCertificateVersion, quint32, setStaticCertificateVersion); + + // Script location data + COPY_PROPERTY_FROM_QSCRIPTVALUE(localPosition, vec3, setLocalPosition); + COPY_PROPERTY_FROM_QSCRIPTVALUE(localRotation, quat, setLocalRotation); + COPY_PROPERTY_FROM_QSCRIPTVALUE(localVelocity, vec3, setLocalVelocity); + COPY_PROPERTY_FROM_QSCRIPTVALUE(localAngularVelocity, vec3, setLocalAngularVelocity); + COPY_PROPERTY_FROM_QSCRIPTVALUE(localDimensions, vec3, setLocalDimensions); + + // Common COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(shapeType, ShapeType); + COPY_PROPERTY_FROM_QSCRIPTVALUE(compoundShapeURL, QString, setCompoundShapeURL); + COPY_PROPERTY_FROM_QSCRIPTVALUE(color, u8vec3Color, setColor); + COPY_PROPERTY_FROM_QSCRIPTVALUE(alpha, float, setAlpha); + COPY_PROPERTY_FROM_QSCRIPTVALUE(textures, QString, setTextures); + + // Particles COPY_PROPERTY_FROM_QSCRIPTVALUE(maxParticles, quint32, setMaxParticles); COPY_PROPERTY_FROM_QSCRIPTVALUE(lifespan, float, setLifespan); COPY_PROPERTY_FROM_QSCRIPTVALUE(isEmitting, bool, setIsEmitting); @@ -1693,7 +1881,94 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(radiusSpread, float, setRadiusSpread); COPY_PROPERTY_FROM_QSCRIPTVALUE(radiusStart, float, setRadiusStart); COPY_PROPERTY_FROM_QSCRIPTVALUE(radiusFinish, float, setRadiusFinish); + COPY_PROPERTY_FROM_QSCRIPTVALUE(colorSpread, u8vec3Color, setColorSpread); + COPY_PROPERTY_FROM_QSCRIPTVALUE(colorStart, vec3Color, setColorStart); + COPY_PROPERTY_FROM_QSCRIPTVALUE(colorFinish, vec3Color, setColorFinish); + COPY_PROPERTY_FROM_QSCRIPTVALUE(alphaSpread, float, setAlphaSpread); + COPY_PROPERTY_FROM_QSCRIPTVALUE(alphaStart, float, setAlphaStart); + COPY_PROPERTY_FROM_QSCRIPTVALUE(alphaFinish, float, setAlphaFinish); + COPY_PROPERTY_FROM_QSCRIPTVALUE(emitterShouldTrail, bool, setEmitterShouldTrail); + COPY_PROPERTY_FROM_QSCRIPTVALUE(particleSpin, float, setParticleSpin); + COPY_PROPERTY_FROM_QSCRIPTVALUE(spinSpread, float, setSpinSpread); + COPY_PROPERTY_FROM_QSCRIPTVALUE(spinStart, float, setSpinStart); + COPY_PROPERTY_FROM_QSCRIPTVALUE(spinFinish, float, setSpinFinish); + COPY_PROPERTY_FROM_QSCRIPTVALUE(rotateWithEntity, bool, setRotateWithEntity); + + // Model + COPY_PROPERTY_FROM_QSCRIPTVALUE(modelURL, QString, setModelURL); + COPY_PROPERTY_FROM_QSCRIPTVALUE(jointRotationsSet, qVectorBool, setJointRotationsSet); + COPY_PROPERTY_FROM_QSCRIPTVALUE(jointRotations, qVectorQuat, setJointRotations); + COPY_PROPERTY_FROM_QSCRIPTVALUE(jointTranslationsSet, qVectorBool, setJointTranslationsSet); + COPY_PROPERTY_FROM_QSCRIPTVALUE(jointTranslations, qVectorVec3, setJointTranslations); COPY_PROPERTY_FROM_QSCRIPTVALUE(relayParentJoints, bool, setRelayParentJoints); + _animation.copyFromScriptValue(object, _defaultSettings); + + // Light + COPY_PROPERTY_FROM_QSCRIPTVALUE(isSpotlight, bool, setIsSpotlight); + COPY_PROPERTY_FROM_QSCRIPTVALUE(intensity, float, setIntensity); + COPY_PROPERTY_FROM_QSCRIPTVALUE(exponent, float, setExponent); + COPY_PROPERTY_FROM_QSCRIPTVALUE(cutoff, float, setCutoff); + COPY_PROPERTY_FROM_QSCRIPTVALUE(falloffRadius, float, setFalloffRadius); + + // Text + COPY_PROPERTY_FROM_QSCRIPTVALUE(text, QString, setText); + COPY_PROPERTY_FROM_QSCRIPTVALUE(lineHeight, float, setLineHeight); + COPY_PROPERTY_FROM_QSCRIPTVALUE(textColor, u8vec3Color, setTextColor); + COPY_PROPERTY_FROM_QSCRIPTVALUE(textAlpha, float, setTextAlpha); + COPY_PROPERTY_FROM_QSCRIPTVALUE(backgroundColor, u8vec3Color, setBackgroundColor); + COPY_PROPERTY_FROM_QSCRIPTVALUE(backgroundAlpha, float, setBackgroundAlpha); + COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(billboardMode, BillboardMode); + COPY_PROPERTY_FROM_QSCRIPTVALUE(leftMargin, float, setLeftMargin); + COPY_PROPERTY_FROM_QSCRIPTVALUE(rightMargin, float, setRightMargin); + COPY_PROPERTY_FROM_QSCRIPTVALUE(topMargin, float, setTopMargin); + COPY_PROPERTY_FROM_QSCRIPTVALUE(bottomMargin, float, setBottomMargin); + + // Zone + _keyLight.copyFromScriptValue(object, _defaultSettings); + _ambientLight.copyFromScriptValue(object, _defaultSettings); + _skybox.copyFromScriptValue(object, _defaultSettings); + _haze.copyFromScriptValue(object, _defaultSettings); + _bloom.copyFromScriptValue(object, _defaultSettings); + COPY_PROPERTY_FROM_QSCRIPTVALUE(flyingAllowed, bool, setFlyingAllowed); + COPY_PROPERTY_FROM_QSCRIPTVALUE(ghostingAllowed, bool, setGhostingAllowed); + COPY_PROPERTY_FROM_QSCRIPTVALUE(filterURL, QString, setFilterURL); + COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(keyLightMode, KeyLightMode); + COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(ambientLightMode, AmbientLightMode); + COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(skyboxMode, SkyboxMode); + COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(hazeMode, HazeMode); + COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(bloomMode, BloomMode); + + // Polyvox + COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelVolumeSize, vec3, setVoxelVolumeSize); + COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelData, QByteArray, setVoxelData); + COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelSurfaceStyle, uint16_t, setVoxelSurfaceStyle); + COPY_PROPERTY_FROM_QSCRIPTVALUE(xTextureURL, QString, setXTextureURL); + COPY_PROPERTY_FROM_QSCRIPTVALUE(yTextureURL, QString, setYTextureURL); + COPY_PROPERTY_FROM_QSCRIPTVALUE(zTextureURL, QString, setZTextureURL); + COPY_PROPERTY_FROM_QSCRIPTVALUE(xNNeighborID, EntityItemID, setXNNeighborID); + COPY_PROPERTY_FROM_QSCRIPTVALUE(yNNeighborID, EntityItemID, setYNNeighborID); + COPY_PROPERTY_FROM_QSCRIPTVALUE(zNNeighborID, EntityItemID, setZNNeighborID); + COPY_PROPERTY_FROM_QSCRIPTVALUE(xPNeighborID, EntityItemID, setXPNeighborID); + COPY_PROPERTY_FROM_QSCRIPTVALUE(yPNeighborID, EntityItemID, setYPNeighborID); + COPY_PROPERTY_FROM_QSCRIPTVALUE(zPNeighborID, EntityItemID, setZPNeighborID); + + // Web + COPY_PROPERTY_FROM_QSCRIPTVALUE(sourceUrl, QString, setSourceUrl); + COPY_PROPERTY_FROM_QSCRIPTVALUE(dpi, uint16_t, setDPI); + + // Polyline + COPY_PROPERTY_FROM_QSCRIPTVALUE(linePoints, qVectorVec3, setLinePoints); + COPY_PROPERTY_FROM_QSCRIPTVALUE(strokeWidths, qVectorFloat, setStrokeWidths); + COPY_PROPERTY_FROM_QSCRIPTVALUE(normals, qVectorVec3, setNormals); + COPY_PROPERTY_FROM_QSCRIPTVALUE(strokeColors, qVectorVec3, setStrokeColors); + COPY_PROPERTY_FROM_QSCRIPTVALUE(isUVModeStretch, bool, setIsUVModeStretch); + COPY_PROPERTY_FROM_QSCRIPTVALUE(glow, bool, setGlow); + COPY_PROPERTY_FROM_QSCRIPTVALUE(faceCamera, bool, setFaceCamera); + + // Shape + COPY_PROPERTY_FROM_QSCRIPTVALUE(shape, QString, setShape); + + // Material COPY_PROPERTY_FROM_QSCRIPTVALUE(materialURL, QString, setMaterialURL); COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(materialMappingMode, MaterialMappingMode); COPY_PROPERTY_FROM_QSCRIPTVALUE(priority, quint16, setPriority); @@ -1703,164 +1978,156 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(materialMappingRot, float, setMaterialMappingRot); COPY_PROPERTY_FROM_QSCRIPTVALUE(materialData, QString, setMaterialData); COPY_PROPERTY_FROM_QSCRIPTVALUE(materialRepeat, bool, setMaterialRepeat); - COPY_PROPERTY_FROM_QSCRIPTVALUE(isVisibleInSecondaryCamera, bool, setIsVisibleInSecondaryCamera); - COPY_PROPERTY_FROM_QSCRIPTVALUE(particleSpin, float, setParticleSpin); - COPY_PROPERTY_FROM_QSCRIPTVALUE(spinSpread, float, setSpinSpread); - COPY_PROPERTY_FROM_QSCRIPTVALUE(spinStart, float, setSpinStart); - COPY_PROPERTY_FROM_QSCRIPTVALUE(spinFinish, float, setSpinFinish); - COPY_PROPERTY_FROM_QSCRIPTVALUE(rotateWithEntity, bool, setRotateWithEntity); - // Certifiable Properties - COPY_PROPERTY_FROM_QSCRIPTVALUE(itemName, QString, setItemName); - COPY_PROPERTY_FROM_QSCRIPTVALUE(itemDescription, QString, setItemDescription); - COPY_PROPERTY_FROM_QSCRIPTVALUE(itemCategories, QString, setItemCategories); - COPY_PROPERTY_FROM_QSCRIPTVALUE(itemArtist, QString, setItemArtist); - COPY_PROPERTY_FROM_QSCRIPTVALUE(itemLicense, QString, setItemLicense); - COPY_PROPERTY_FROM_QSCRIPTVALUE(limitedRun, quint32, setLimitedRun); - COPY_PROPERTY_FROM_QSCRIPTVALUE(marketplaceID, QString, setMarketplaceID); - COPY_PROPERTY_FROM_QSCRIPTVALUE(editionNumber, quint32, setEditionNumber); - COPY_PROPERTY_FROM_QSCRIPTVALUE(entityInstanceNumber, quint32, setEntityInstanceNumber); - COPY_PROPERTY_FROM_QSCRIPTVALUE(certificateID, QString, setCertificateID); - COPY_PROPERTY_FROM_QSCRIPTVALUE(staticCertificateVersion, quint32, setStaticCertificateVersion); + // Image + COPY_PROPERTY_FROM_QSCRIPTVALUE(imageURL, QString, setImageURL); + COPY_PROPERTY_FROM_QSCRIPTVALUE(emissive, bool, setEmissive); + COPY_PROPERTY_FROM_QSCRIPTVALUE(keepAspectRatio, bool, setKeepAspectRatio); + COPY_PROPERTY_FROM_QSCRIPTVALUE(subImage, QRect, setSubImage); - COPY_PROPERTY_FROM_QSCRIPTVALUE(name, QString, setName); - COPY_PROPERTY_FROM_QSCRIPTVALUE(collisionSoundURL, QString, setCollisionSoundURL); + // Grid + COPY_PROPERTY_FROM_QSCRIPTVALUE(followCamera, bool, setFollowCamera); + COPY_PROPERTY_FROM_QSCRIPTVALUE(majorGridEvery, uint32_t, setMajorGridEvery); + COPY_PROPERTY_FROM_QSCRIPTVALUE(minorGridEvery, float, setMinorGridEvery); - COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(hazeMode, HazeMode); - COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(keyLightMode, KeyLightMode); - COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(ambientLightMode, AmbientLightMode); - COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(skyboxMode, SkyboxMode); - COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(bloomMode, BloomMode); - - COPY_PROPERTY_FROM_QSCRIPTVALUE(sourceUrl, QString, setSourceUrl); - COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelVolumeSize, vec3, setVoxelVolumeSize); - COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelData, QByteArray, setVoxelData); - COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelSurfaceStyle, uint16_t, setVoxelSurfaceStyle); - COPY_PROPERTY_FROM_QSCRIPTVALUE(lineWidth, float, setLineWidth); - COPY_PROPERTY_FROM_QSCRIPTVALUE(linePoints, qVectorVec3, setLinePoints); - COPY_PROPERTY_FROM_QSCRIPTVALUE(href, QString, setHref); - COPY_PROPERTY_FROM_QSCRIPTVALUE(description, QString, setDescription); - COPY_PROPERTY_FROM_QSCRIPTVALUE(faceCamera, bool, setFaceCamera); - COPY_PROPERTY_FROM_QSCRIPTVALUE(actionData, QByteArray, setActionData); - COPY_PROPERTY_FROM_QSCRIPTVALUE(normals, qVectorVec3, setNormals); - COPY_PROPERTY_FROM_QSCRIPTVALUE(strokeColors, qVectorVec3, setStrokeColors); - COPY_PROPERTY_FROM_QSCRIPTVALUE(strokeWidths,qVectorFloat, setStrokeWidths); - COPY_PROPERTY_FROM_QSCRIPTVALUE(isUVModeStretch, bool, setIsUVModeStretch); - - - if (!honorReadOnly) { - // this is used by the json reader to set things that we don't want javascript to able to affect. - COPY_PROPERTY_FROM_QSCRIPTVALUE_GETTER(created, QDateTime, setCreated, [this]() { - auto result = QDateTime::fromMSecsSinceEpoch(_created / 1000, Qt::UTC); // usec per msec - return result; - }); - // TODO: expose this to QScriptValue for JSON saves? - //COPY_PROPERTY_FROM_QSCRIPTVALUE(simulationOwner, ???, setSimulatorPriority); + // Handle conversions from old 'textures' property to "imageURL" + { + QScriptValue V = object.property("textures"); + if (_type == EntityTypes::Image && V.isValid() && !object.property("imageURL").isValid()) { + bool isValid = false; + QString textures = QString_convertFromScriptValue(V, isValid); + if (isValid) { + QVariantMap texturesMap = parseTexturesToMap(textures, QVariantMap()); + auto texPicture = texturesMap.find("tex.picture"); + if (texPicture != texturesMap.end()) { + auto imageURL = texPicture.value().toString(); + if (_defaultSettings || imageURL != _imageURL) { + setImageURL(imageURL); + } + } + } + } } - _animation.copyFromScriptValue(object, _defaultSettings); - _keyLight.copyFromScriptValue(object, _defaultSettings); - _ambientLight.copyFromScriptValue(object, _defaultSettings); - _skybox.copyFromScriptValue(object, _defaultSettings); - _haze.copyFromScriptValue(object, _defaultSettings); - _bloom.copyFromScriptValue(object, _defaultSettings); - _grab.copyFromScriptValue(object, _defaultSettings); - - COPY_PROPERTY_FROM_QSCRIPTVALUE(xTextureURL, QString, setXTextureURL); - COPY_PROPERTY_FROM_QSCRIPTVALUE(yTextureURL, QString, setYTextureURL); - COPY_PROPERTY_FROM_QSCRIPTVALUE(zTextureURL, QString, setZTextureURL); - - COPY_PROPERTY_FROM_QSCRIPTVALUE(xNNeighborID, EntityItemID, setXNNeighborID); - COPY_PROPERTY_FROM_QSCRIPTVALUE(yNNeighborID, EntityItemID, setYNNeighborID); - COPY_PROPERTY_FROM_QSCRIPTVALUE(zNNeighborID, EntityItemID, setZNNeighborID); - - COPY_PROPERTY_FROM_QSCRIPTVALUE(xPNeighborID, EntityItemID, setXPNeighborID); - COPY_PROPERTY_FROM_QSCRIPTVALUE(yPNeighborID, EntityItemID, setYPNeighborID); - COPY_PROPERTY_FROM_QSCRIPTVALUE(zPNeighborID, EntityItemID, setZPNeighborID); - - COPY_PROPERTY_FROM_QSCRIPTVALUE(parentID, QUuid, setParentID); - COPY_PROPERTY_FROM_QSCRIPTVALUE(parentJointIndex, quint16, setParentJointIndex); - COPY_PROPERTY_FROM_QSCRIPTVALUE(queryAACube, AACube, setQueryAACube); - - COPY_PROPERTY_FROM_QSCRIPTVALUE(localPosition, vec3, setLocalPosition); - COPY_PROPERTY_FROM_QSCRIPTVALUE(localRotation, quat, setLocalRotation); - COPY_PROPERTY_FROM_QSCRIPTVALUE(localVelocity, vec3, setLocalVelocity); - COPY_PROPERTY_FROM_QSCRIPTVALUE(localAngularVelocity, vec3, setLocalAngularVelocity); - COPY_PROPERTY_FROM_QSCRIPTVALUE(localDimensions, vec3, setLocalDimensions); - - COPY_PROPERTY_FROM_QSCRIPTVALUE(jointRotationsSet, qVectorBool, setJointRotationsSet); - COPY_PROPERTY_FROM_QSCRIPTVALUE(jointRotations, qVectorQuat, setJointRotations); - COPY_PROPERTY_FROM_QSCRIPTVALUE(jointTranslationsSet, qVectorBool, setJointTranslationsSet); - COPY_PROPERTY_FROM_QSCRIPTVALUE(jointTranslations, qVectorVec3, setJointTranslations); - COPY_PROPERTY_FROM_QSCRIPTVALUE(shape, QString, setShape); - - COPY_PROPERTY_FROM_QSCRIPTVALUE(flyingAllowed, bool, setFlyingAllowed); - COPY_PROPERTY_FROM_QSCRIPTVALUE(ghostingAllowed, bool, setGhostingAllowed); - COPY_PROPERTY_FROM_QSCRIPTVALUE(filterURL, QString, setFilterURL); - - COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(entityHostType, EntityHostType); - COPY_PROPERTY_FROM_QSCRIPTVALUE(owningAvatarID, QUuid, setOwningAvatarID); - - COPY_PROPERTY_FROM_QSCRIPTVALUE(dpi, uint16_t, setDPI); - - COPY_PROPERTY_FROM_QSCRIPTVALUE(cloneable, bool, setCloneable); - COPY_PROPERTY_FROM_QSCRIPTVALUE(cloneLifetime, float, setCloneLifetime); - COPY_PROPERTY_FROM_QSCRIPTVALUE(cloneLimit, float, setCloneLimit); - COPY_PROPERTY_FROM_QSCRIPTVALUE(cloneDynamic, bool, setCloneDynamic); - COPY_PROPERTY_FROM_QSCRIPTVALUE(cloneAvatarEntity, bool, setCloneAvatarEntity); - COPY_PROPERTY_FROM_QSCRIPTVALUE(cloneOriginID, QUuid, setCloneOriginID); + // Handle old "faceCamera" and "isFacingAvatar" props + { + QScriptValue P = object.property("faceCamera"); + if (P.isValid() && !object.property("billboardMode").isValid()) { + bool newValue = P.toVariant().toBool(); + bool oldValue = getBillboardMode() == BillboardMode::YAW; + if (_defaultSettings || newValue != oldValue) { + setBillboardMode(newValue ? BillboardMode::YAW : BillboardMode::NONE); + } + } + } + { + QScriptValue P = object.property("isFacingAvatar"); + if (P.isValid() && !object.property("billboardMode").isValid() && !object.property("faceCamera").isValid()) { + bool newValue = P.toVariant().toBool(); + bool oldValue = getBillboardMode() == BillboardMode::FULL; + if (_defaultSettings || newValue != oldValue) { + setBillboardMode(newValue ? BillboardMode::FULL : BillboardMode::NONE); + } + } + } _lastEdited = usecTimestampNow(); } +void EntityItemProperties::copyFromJSONString(QScriptEngine& scriptEngine, const QString& jsonString) { + // DANGER: this method is expensive + QJsonDocument propertiesDoc = QJsonDocument::fromJson(jsonString.toUtf8()); + QJsonObject propertiesObj = propertiesDoc.object(); + QVariant propertiesVariant(propertiesObj); + QVariantMap propertiesMap = propertiesVariant.toMap(); + QScriptValue propertiesScriptValue = variantMapToScriptValue(propertiesMap, scriptEngine); + bool honorReadOnly = true; + copyFromScriptValue(propertiesScriptValue, honorReadOnly); +} + + void EntityItemProperties::merge(const EntityItemProperties& other) { - COPY_PROPERTY_IF_CHANGED(lastEditedBy); + // Core + COPY_PROPERTY_IF_CHANGED(simulationOwner); + COPY_PROPERTY_IF_CHANGED(visible); + COPY_PROPERTY_IF_CHANGED(name); + COPY_PROPERTY_IF_CHANGED(locked); + COPY_PROPERTY_IF_CHANGED(userData); + COPY_PROPERTY_IF_CHANGED(href); + COPY_PROPERTY_IF_CHANGED(description); COPY_PROPERTY_IF_CHANGED(position); COPY_PROPERTY_IF_CHANGED(dimensions); COPY_PROPERTY_IF_CHANGED(rotation); + COPY_PROPERTY_IF_CHANGED(registrationPoint); + COPY_PROPERTY_IF_CHANGED(created); + COPY_PROPERTY_IF_CHANGED(lastEditedBy); + COPY_PROPERTY_IF_CHANGED(entityHostType); + COPY_PROPERTY_IF_CHANGED(owningAvatarID); + COPY_PROPERTY_IF_CHANGED(parentID); + COPY_PROPERTY_IF_CHANGED(parentJointIndex); + COPY_PROPERTY_IF_CHANGED(queryAACube); + COPY_PROPERTY_IF_CHANGED(canCastShadow); + COPY_PROPERTY_IF_CHANGED(isVisibleInSecondaryCamera); + _grab.merge(other._grab); + + // Physics COPY_PROPERTY_IF_CHANGED(density); COPY_PROPERTY_IF_CHANGED(velocity); + COPY_PROPERTY_IF_CHANGED(angularVelocity); COPY_PROPERTY_IF_CHANGED(gravity); COPY_PROPERTY_IF_CHANGED(acceleration); COPY_PROPERTY_IF_CHANGED(damping); + COPY_PROPERTY_IF_CHANGED(angularDamping); COPY_PROPERTY_IF_CHANGED(restitution); COPY_PROPERTY_IF_CHANGED(friction); COPY_PROPERTY_IF_CHANGED(lifetime); - COPY_PROPERTY_IF_CHANGED(script); - COPY_PROPERTY_IF_CHANGED(scriptTimestamp); - COPY_PROPERTY_IF_CHANGED(registrationPoint); - COPY_PROPERTY_IF_CHANGED(angularVelocity); - COPY_PROPERTY_IF_CHANGED(angularDamping); - COPY_PROPERTY_IF_CHANGED(visible); - COPY_PROPERTY_IF_CHANGED(canCastShadow); - COPY_PROPERTY_IF_CHANGED(color); - COPY_PROPERTY_IF_CHANGED(colorSpread); - COPY_PROPERTY_IF_CHANGED(colorStart); - COPY_PROPERTY_IF_CHANGED(colorFinish); - COPY_PROPERTY_IF_CHANGED(alpha); - COPY_PROPERTY_IF_CHANGED(alphaSpread); - COPY_PROPERTY_IF_CHANGED(alphaStart); - COPY_PROPERTY_IF_CHANGED(alphaFinish); - COPY_PROPERTY_IF_CHANGED(emitterShouldTrail); - COPY_PROPERTY_IF_CHANGED(modelURL); - COPY_PROPERTY_IF_CHANGED(compoundShapeURL); - COPY_PROPERTY_IF_CHANGED(localRenderAlpha); COPY_PROPERTY_IF_CHANGED(collisionless); COPY_PROPERTY_IF_CHANGED(collisionMask); COPY_PROPERTY_IF_CHANGED(dynamic); - COPY_PROPERTY_IF_CHANGED(isSpotlight); - COPY_PROPERTY_IF_CHANGED(intensity); - COPY_PROPERTY_IF_CHANGED(falloffRadius); - COPY_PROPERTY_IF_CHANGED(exponent); - COPY_PROPERTY_IF_CHANGED(cutoff); - COPY_PROPERTY_IF_CHANGED(locked); - COPY_PROPERTY_IF_CHANGED(textures); - COPY_PROPERTY_IF_CHANGED(userData); - COPY_PROPERTY_IF_CHANGED(text); - COPY_PROPERTY_IF_CHANGED(lineHeight); - COPY_PROPERTY_IF_CHANGED(textColor); - COPY_PROPERTY_IF_CHANGED(backgroundColor); + COPY_PROPERTY_IF_CHANGED(collisionSoundURL); + COPY_PROPERTY_IF_CHANGED(actionData); + + // Cloning + COPY_PROPERTY_IF_CHANGED(cloneable); + COPY_PROPERTY_IF_CHANGED(cloneLifetime); + COPY_PROPERTY_IF_CHANGED(cloneLimit); + COPY_PROPERTY_IF_CHANGED(cloneDynamic); + COPY_PROPERTY_IF_CHANGED(cloneAvatarEntity); + COPY_PROPERTY_IF_CHANGED(cloneOriginID); + + // Scripts + COPY_PROPERTY_IF_CHANGED(script); + COPY_PROPERTY_IF_CHANGED(scriptTimestamp); + COPY_PROPERTY_IF_CHANGED(serverScripts); + + // Certifiable Properties + COPY_PROPERTY_IF_CHANGED(itemName); + COPY_PROPERTY_IF_CHANGED(itemDescription); + COPY_PROPERTY_IF_CHANGED(itemCategories); + COPY_PROPERTY_IF_CHANGED(itemArtist); + COPY_PROPERTY_IF_CHANGED(itemLicense); + COPY_PROPERTY_IF_CHANGED(limitedRun); + COPY_PROPERTY_IF_CHANGED(marketplaceID); + COPY_PROPERTY_IF_CHANGED(editionNumber); + COPY_PROPERTY_IF_CHANGED(entityInstanceNumber); + COPY_PROPERTY_IF_CHANGED(certificateID); + COPY_PROPERTY_IF_CHANGED(staticCertificateVersion); + + // Local props for scripts + COPY_PROPERTY_IF_CHANGED(localPosition); + COPY_PROPERTY_IF_CHANGED(localRotation); + COPY_PROPERTY_IF_CHANGED(localVelocity); + COPY_PROPERTY_IF_CHANGED(localAngularVelocity); + COPY_PROPERTY_IF_CHANGED(localDimensions); + + // Common COPY_PROPERTY_IF_CHANGED(shapeType); + COPY_PROPERTY_IF_CHANGED(compoundShapeURL); + COPY_PROPERTY_IF_CHANGED(color); + COPY_PROPERTY_IF_CHANGED(alpha); + COPY_PROPERTY_IF_CHANGED(textures); + + // Particles COPY_PROPERTY_IF_CHANGED(maxParticles); COPY_PROPERTY_IF_CHANGED(lifespan); COPY_PROPERTY_IF_CHANGED(isEmitting); @@ -1880,101 +2147,114 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(radiusSpread); COPY_PROPERTY_IF_CHANGED(radiusStart); COPY_PROPERTY_IF_CHANGED(radiusFinish); + COPY_PROPERTY_IF_CHANGED(colorSpread); + COPY_PROPERTY_IF_CHANGED(colorStart); + COPY_PROPERTY_IF_CHANGED(colorFinish); + COPY_PROPERTY_IF_CHANGED(alphaSpread); + COPY_PROPERTY_IF_CHANGED(alphaStart); + COPY_PROPERTY_IF_CHANGED(alphaFinish); + COPY_PROPERTY_IF_CHANGED(emitterShouldTrail); COPY_PROPERTY_IF_CHANGED(particleSpin); COPY_PROPERTY_IF_CHANGED(spinSpread); COPY_PROPERTY_IF_CHANGED(spinStart); COPY_PROPERTY_IF_CHANGED(spinFinish); COPY_PROPERTY_IF_CHANGED(rotateWithEntity); - // Certifiable Properties - COPY_PROPERTY_IF_CHANGED(itemName); - COPY_PROPERTY_IF_CHANGED(itemDescription); - COPY_PROPERTY_IF_CHANGED(itemCategories); - COPY_PROPERTY_IF_CHANGED(itemArtist); - COPY_PROPERTY_IF_CHANGED(itemLicense); - COPY_PROPERTY_IF_CHANGED(limitedRun); - COPY_PROPERTY_IF_CHANGED(marketplaceID); - COPY_PROPERTY_IF_CHANGED(editionNumber); - COPY_PROPERTY_IF_CHANGED(entityInstanceNumber); - COPY_PROPERTY_IF_CHANGED(certificateID); - COPY_PROPERTY_IF_CHANGED(staticCertificateVersion); - - COPY_PROPERTY_IF_CHANGED(name); - COPY_PROPERTY_IF_CHANGED(collisionSoundURL); - - COPY_PROPERTY_IF_CHANGED(hazeMode); - COPY_PROPERTY_IF_CHANGED(keyLightMode); - COPY_PROPERTY_IF_CHANGED(ambientLightMode); - COPY_PROPERTY_IF_CHANGED(skyboxMode); - COPY_PROPERTY_IF_CHANGED(bloomMode); - - COPY_PROPERTY_IF_CHANGED(sourceUrl); - COPY_PROPERTY_IF_CHANGED(voxelVolumeSize); - COPY_PROPERTY_IF_CHANGED(voxelData); - COPY_PROPERTY_IF_CHANGED(voxelSurfaceStyle); - COPY_PROPERTY_IF_CHANGED(lineWidth); - COPY_PROPERTY_IF_CHANGED(linePoints); - COPY_PROPERTY_IF_CHANGED(href); - COPY_PROPERTY_IF_CHANGED(description); - COPY_PROPERTY_IF_CHANGED(faceCamera); - COPY_PROPERTY_IF_CHANGED(actionData); - COPY_PROPERTY_IF_CHANGED(normals); - COPY_PROPERTY_IF_CHANGED(strokeColors); - COPY_PROPERTY_IF_CHANGED(strokeWidths); - COPY_PROPERTY_IF_CHANGED(isUVModeStretch); - COPY_PROPERTY_IF_CHANGED(created); - + // Model + COPY_PROPERTY_IF_CHANGED(modelURL); + COPY_PROPERTY_IF_CHANGED(jointRotationsSet); + COPY_PROPERTY_IF_CHANGED(jointRotations); + COPY_PROPERTY_IF_CHANGED(jointTranslationsSet); + COPY_PROPERTY_IF_CHANGED(jointTranslations); + COPY_PROPERTY_IF_CHANGED(relayParentJoints); _animation.merge(other._animation); + + // Light + COPY_PROPERTY_IF_CHANGED(isSpotlight); + COPY_PROPERTY_IF_CHANGED(intensity); + COPY_PROPERTY_IF_CHANGED(exponent); + COPY_PROPERTY_IF_CHANGED(cutoff); + COPY_PROPERTY_IF_CHANGED(falloffRadius); + + // Text + COPY_PROPERTY_IF_CHANGED(text); + COPY_PROPERTY_IF_CHANGED(lineHeight); + COPY_PROPERTY_IF_CHANGED(textColor); + COPY_PROPERTY_IF_CHANGED(textAlpha); + COPY_PROPERTY_IF_CHANGED(backgroundColor); + COPY_PROPERTY_IF_CHANGED(backgroundAlpha); + COPY_PROPERTY_IF_CHANGED(billboardMode); + COPY_PROPERTY_IF_CHANGED(leftMargin); + COPY_PROPERTY_IF_CHANGED(rightMargin); + COPY_PROPERTY_IF_CHANGED(topMargin); + COPY_PROPERTY_IF_CHANGED(bottomMargin); + + // Zone _keyLight.merge(other._keyLight); _ambientLight.merge(other._ambientLight); _skybox.merge(other._skybox); _haze.merge(other._haze); _bloom.merge(other._bloom); - _grab.merge(other._grab); + COPY_PROPERTY_IF_CHANGED(flyingAllowed); + COPY_PROPERTY_IF_CHANGED(ghostingAllowed); + COPY_PROPERTY_IF_CHANGED(filterURL); + COPY_PROPERTY_IF_CHANGED(keyLightMode); + COPY_PROPERTY_IF_CHANGED(ambientLightMode); + COPY_PROPERTY_IF_CHANGED(skyboxMode); + COPY_PROPERTY_IF_CHANGED(hazeMode); + COPY_PROPERTY_IF_CHANGED(bloomMode); + // Polyvox + COPY_PROPERTY_IF_CHANGED(voxelVolumeSize); + COPY_PROPERTY_IF_CHANGED(voxelData); + COPY_PROPERTY_IF_CHANGED(voxelSurfaceStyle); COPY_PROPERTY_IF_CHANGED(xTextureURL); COPY_PROPERTY_IF_CHANGED(yTextureURL); COPY_PROPERTY_IF_CHANGED(zTextureURL); - COPY_PROPERTY_IF_CHANGED(xNNeighborID); COPY_PROPERTY_IF_CHANGED(yNNeighborID); COPY_PROPERTY_IF_CHANGED(zNNeighborID); - COPY_PROPERTY_IF_CHANGED(xPNeighborID); COPY_PROPERTY_IF_CHANGED(yPNeighborID); COPY_PROPERTY_IF_CHANGED(zPNeighborID); - COPY_PROPERTY_IF_CHANGED(parentID); - COPY_PROPERTY_IF_CHANGED(parentJointIndex); - COPY_PROPERTY_IF_CHANGED(queryAACube); - - COPY_PROPERTY_IF_CHANGED(localPosition); - COPY_PROPERTY_IF_CHANGED(localRotation); - COPY_PROPERTY_IF_CHANGED(localVelocity); - COPY_PROPERTY_IF_CHANGED(localAngularVelocity); - COPY_PROPERTY_IF_CHANGED(localDimensions); - - COPY_PROPERTY_IF_CHANGED(jointRotationsSet); - COPY_PROPERTY_IF_CHANGED(jointRotations); - COPY_PROPERTY_IF_CHANGED(jointTranslationsSet); - COPY_PROPERTY_IF_CHANGED(jointTranslations); - COPY_PROPERTY_IF_CHANGED(shape); - - COPY_PROPERTY_IF_CHANGED(flyingAllowed); - COPY_PROPERTY_IF_CHANGED(ghostingAllowed); - COPY_PROPERTY_IF_CHANGED(filterURL); - - COPY_PROPERTY_IF_CHANGED(entityHostType); - COPY_PROPERTY_IF_CHANGED(owningAvatarID); - + // Web + COPY_PROPERTY_IF_CHANGED(sourceUrl); COPY_PROPERTY_IF_CHANGED(dpi); - COPY_PROPERTY_IF_CHANGED(cloneable); - COPY_PROPERTY_IF_CHANGED(cloneLifetime); - COPY_PROPERTY_IF_CHANGED(cloneLimit); - COPY_PROPERTY_IF_CHANGED(cloneDynamic); - COPY_PROPERTY_IF_CHANGED(cloneAvatarEntity); - COPY_PROPERTY_IF_CHANGED(cloneOriginID); + // Polyline + COPY_PROPERTY_IF_CHANGED(linePoints); + COPY_PROPERTY_IF_CHANGED(strokeWidths); + COPY_PROPERTY_IF_CHANGED(normals); + COPY_PROPERTY_IF_CHANGED(strokeColors); + COPY_PROPERTY_IF_CHANGED(isUVModeStretch); + COPY_PROPERTY_IF_CHANGED(glow); + COPY_PROPERTY_IF_CHANGED(faceCamera); + + // Shape + COPY_PROPERTY_IF_CHANGED(shape); + + // Material + COPY_PROPERTY_IF_CHANGED(materialURL); + COPY_PROPERTY_IF_CHANGED(materialMappingMode); + COPY_PROPERTY_IF_CHANGED(priority); + COPY_PROPERTY_IF_CHANGED(parentMaterialName); + COPY_PROPERTY_IF_CHANGED(materialMappingPos); + COPY_PROPERTY_IF_CHANGED(materialMappingScale); + COPY_PROPERTY_IF_CHANGED(materialMappingRot); + COPY_PROPERTY_IF_CHANGED(materialData); + COPY_PROPERTY_IF_CHANGED(materialRepeat); + + // Image + COPY_PROPERTY_IF_CHANGED(imageURL); + COPY_PROPERTY_IF_CHANGED(emissive); + COPY_PROPERTY_IF_CHANGED(keepAspectRatio); + COPY_PROPERTY_IF_CHANGED(subImage); + + // Grid + COPY_PROPERTY_IF_CHANGED(followCamera); + COPY_PROPERTY_IF_CHANGED(majorGridEvery); + COPY_PROPERTY_IF_CHANGED(minorGridEvery); _lastEdited = usecTimestampNow(); } @@ -1995,11 +2275,8 @@ void EntityItemPropertiesFromScriptValueHonorReadOnly(const QScriptValue &object properties.copyFromScriptValue(object, true); } - QScriptValue EntityPropertyFlagsToScriptValue(QScriptEngine* engine, const EntityPropertyFlags& flags) { return EntityItemProperties::entityPropertyFlagsToScriptValue(engine, flags); - QScriptValue result = engine->newObject(); - return result; } void EntityPropertyFlagsFromScriptValue(const QScriptValue& object, EntityPropertyFlags& flags) { @@ -2012,101 +2289,115 @@ QScriptValue EntityItemProperties::entityPropertyFlagsToScriptValue(QScriptEngin return result; } -static QHash<QString, EntityPropertyList> _propertyStringsToEnums; - void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue& object, EntityPropertyFlags& flags) { + if (object.isString()) { + EntityPropertyInfo propertyInfo; + if (getPropertyInfo(object.toString(), propertyInfo)) { + flags << propertyInfo.propertyEnum; + } + } + else if (object.isArray()) { + quint32 length = object.property("length").toInt32(); + for (quint32 i = 0; i < length; i++) { + QString propertyName = object.property(i).toString(); + EntityPropertyInfo propertyInfo; + if (getPropertyInfo(propertyName, propertyInfo)) { + flags << propertyInfo.propertyEnum; + } + } + } +} + +static QHash<QString, EntityPropertyInfo> _propertyInfos; +static QHash<EntityPropertyList, QString> _enumsToPropertyStrings; + +bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPropertyInfo& propertyInfo) { static std::once_flag initMap; - std::call_once(initMap, [](){ + std::call_once(initMap, []() { + // Core + ADD_PROPERTY_TO_MAP(PROP_SIMULATION_OWNER, SimulationOwner, simulationOwner, SimulationOwner); ADD_PROPERTY_TO_MAP(PROP_VISIBLE, Visible, visible, bool); - ADD_PROPERTY_TO_MAP(PROP_CAN_CAST_SHADOW, CanCastShadow, canCastShadow, bool); + ADD_PROPERTY_TO_MAP(PROP_NAME, Name, name, QString); + ADD_PROPERTY_TO_MAP(PROP_LOCKED, Locked, locked, bool); + ADD_PROPERTY_TO_MAP(PROP_USER_DATA, UserData, userData, QString); + ADD_PROPERTY_TO_MAP(PROP_HREF, Href, href, QString); + ADD_PROPERTY_TO_MAP(PROP_DESCRIPTION, Description, description, QString); ADD_PROPERTY_TO_MAP(PROP_POSITION, Position, position, vec3); - ADD_PROPERTY_TO_MAP(PROP_DIMENSIONS, Dimensions, dimensions, vec3); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_DIMENSIONS, Dimensions, dimensions, vec3, ENTITY_ITEM_MIN_DIMENSION, FLT_MAX); ADD_PROPERTY_TO_MAP(PROP_ROTATION, Rotation, rotation, quat); - ADD_PROPERTY_TO_MAP(PROP_DENSITY, Density, density, float); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_REGISTRATION_POINT, RegistrationPoint, registrationPoint, vec3, + ENTITY_ITEM_MIN_REGISTRATION_POINT, ENTITY_ITEM_MAX_REGISTRATION_POINT); + ADD_PROPERTY_TO_MAP(PROP_CREATED, Created, created, quint64); + ADD_PROPERTY_TO_MAP(PROP_LAST_EDITED_BY, LastEditedBy, lastEditedBy, QUuid); + ADD_PROPERTY_TO_MAP(PROP_ENTITY_HOST_TYPE, EntityHostType, entityHostType, entity::HostType); + ADD_PROPERTY_TO_MAP(PROP_OWNING_AVATAR_ID, OwningAvatarID, owningAvatarID, QUuid); + ADD_PROPERTY_TO_MAP(PROP_PARENT_ID, ParentID, parentID, QUuid); + ADD_PROPERTY_TO_MAP(PROP_PARENT_JOINT_INDEX, ParentJointIndex, parentJointIndex, uint16_t); + ADD_PROPERTY_TO_MAP(PROP_QUERY_AA_CUBE, QueryAACube, queryAACube, AACube); + ADD_PROPERTY_TO_MAP(PROP_CAN_CAST_SHADOW, CanCastShadow, canCastShadow, bool); + ADD_PROPERTY_TO_MAP(PROP_VISIBLE_IN_SECONDARY_CAMERA, IsVisibleInSecondaryCamera, isVisibleInSecondaryCamera, bool); + { // Grab + ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_GRABBABLE, Grab, grab, Grabbable, grabbable); + ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_KINEMATIC, Grab, grab, GrabKinematic, grabKinematic); + ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_FOLLOWS_CONTROLLER, Grab, grab, GrabFollowsController, grabFollowsController); + ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_TRIGGERABLE, Grab, grab, Triggerable, triggerable); + ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_EQUIPPABLE, Grab, grab, Equippable, equippable); + ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_DELEGATE_TO_PARENT, Grab, grab, GrabDelegateToParent, grabDelegateToParent); + ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, Grab, grab, + EquippableLeftPosition, equippableLeftPosition); + ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, Grab, grab, + EquippableLeftRotation, equippableLeftRotation); + ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET, Grab, grab, + EquippableRightPosition, equippableRightPosition); + ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_RIGHT_EQUIPPABLE_ROTATION_OFFSET, Grab, grab, + EquippableRightRotation, equippableRightRotation); + ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_EQUIPPABLE_INDICATOR_URL, Grab, grab, + EquippableIndicatorURL, equippableIndicatorURL); + ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_EQUIPPABLE_INDICATOR_SCALE, Grab, grab, + EquippableIndicatorScale, equippableIndicatorScale); + ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_EQUIPPABLE_INDICATOR_OFFSET, Grab, grab, + EquippableIndicatorOffset, equippableIndicatorOffset); + } + + // Physics + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_DENSITY, Density, density, float, + ENTITY_ITEM_MIN_DENSITY, ENTITY_ITEM_MAX_DENSITY); ADD_PROPERTY_TO_MAP(PROP_VELOCITY, Velocity, velocity, vec3); + ADD_PROPERTY_TO_MAP(PROP_ANGULAR_VELOCITY, AngularVelocity, angularVelocity, vec3); ADD_PROPERTY_TO_MAP(PROP_GRAVITY, Gravity, gravity, vec3); ADD_PROPERTY_TO_MAP(PROP_ACCELERATION, Acceleration, acceleration, vec3); - ADD_PROPERTY_TO_MAP(PROP_DAMPING, Damping, damping, float); - ADD_PROPERTY_TO_MAP(PROP_RESTITUTION, Restitution, restitution, float); - ADD_PROPERTY_TO_MAP(PROP_FRICTION, Friction, friction, float); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_DAMPING, Damping, damping, float, + ENTITY_ITEM_MIN_DAMPING, ENTITY_ITEM_MAX_DAMPING); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_ANGULAR_DAMPING, AngularDamping, angularDamping, float, + ENTITY_ITEM_MIN_DAMPING, ENTITY_ITEM_MAX_DAMPING); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_RESTITUTION, Restitution, restitution, float, + ENTITY_ITEM_MIN_RESTITUTION, ENTITY_ITEM_MAX_RESTITUTION); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_FRICTION, Friction, friction, float, + ENTITY_ITEM_MIN_FRICTION, ENTITY_ITEM_MAX_FRICTION); ADD_PROPERTY_TO_MAP(PROP_LIFETIME, Lifetime, lifetime, float); - ADD_PROPERTY_TO_MAP(PROP_SCRIPT, Script, script, QString); - ADD_PROPERTY_TO_MAP(PROP_SCRIPT_TIMESTAMP, ScriptTimestamp, scriptTimestamp, quint64); - ADD_PROPERTY_TO_MAP(PROP_SERVER_SCRIPTS, ServerScripts, serverScripts, QString); - ADD_PROPERTY_TO_MAP(PROP_COLLISION_SOUND_URL, CollisionSoundURL, collisionSoundURL, QString); - ADD_PROPERTY_TO_MAP(PROP_COLOR, Color, color, u8vec3Color); - ADD_PROPERTY_TO_MAP(PROP_COLOR_SPREAD, ColorSpread, colorSpread, u8vec3Color); - ADD_PROPERTY_TO_MAP(PROP_COLOR_START, ColorStart, colorStart, vec3Color); - ADD_PROPERTY_TO_MAP(PROP_COLOR_FINISH, ColorFinish, colorFinish, vec3Color); - ADD_PROPERTY_TO_MAP(PROP_ALPHA, Alpha, alpha, float); - ADD_PROPERTY_TO_MAP(PROP_ALPHA_SPREAD, AlphaSpread, alphaSpread, float); - ADD_PROPERTY_TO_MAP(PROP_ALPHA_START, AlphaStart, alphaStart, float); - ADD_PROPERTY_TO_MAP(PROP_ALPHA_FINISH, AlphaFinish, alphaFinish, float); - ADD_PROPERTY_TO_MAP(PROP_EMITTER_SHOULD_TRAIL, EmitterShouldTrail, emitterShouldTrail, bool); - ADD_PROPERTY_TO_MAP(PROP_MODEL_URL, ModelURL, modelURL, QString); - ADD_PROPERTY_TO_MAP(PROP_COMPOUND_SHAPE_URL, CompoundShapeURL, compoundShapeURL, QString); - ADD_PROPERTY_TO_MAP(PROP_REGISTRATION_POINT, RegistrationPoint, registrationPoint, vec3); - ADD_PROPERTY_TO_MAP(PROP_ANGULAR_VELOCITY, AngularVelocity, angularVelocity, vec3); - ADD_PROPERTY_TO_MAP(PROP_ANGULAR_DAMPING, AngularDamping, angularDamping, float); ADD_PROPERTY_TO_MAP(PROP_COLLISIONLESS, Collisionless, collisionless, bool); ADD_PROPERTY_TO_MAP(PROP_COLLISIONLESS, unused, ignoreForCollisions, unused); // legacy support ADD_PROPERTY_TO_MAP(PROP_COLLISION_MASK, unused, collisionMask, unused); ADD_PROPERTY_TO_MAP(PROP_COLLISION_MASK, unused, collidesWith, unused); ADD_PROPERTY_TO_MAP(PROP_DYNAMIC, unused, collisionsWillMove, unused); // legacy support ADD_PROPERTY_TO_MAP(PROP_DYNAMIC, unused, dynamic, unused); - ADD_PROPERTY_TO_MAP(PROP_IS_SPOTLIGHT, IsSpotlight, isSpotlight, bool); - ADD_PROPERTY_TO_MAP(PROP_INTENSITY, Intensity, intensity, float); - ADD_PROPERTY_TO_MAP(PROP_FALLOFF_RADIUS, FalloffRadius, falloffRadius, float); - ADD_PROPERTY_TO_MAP(PROP_EXPONENT, Exponent, exponent, float); - ADD_PROPERTY_TO_MAP(PROP_CUTOFF, Cutoff, cutoff, float); - ADD_PROPERTY_TO_MAP(PROP_LOCKED, Locked, locked, bool); - ADD_PROPERTY_TO_MAP(PROP_TEXTURES, Textures, textures, QString); - ADD_PROPERTY_TO_MAP(PROP_USER_DATA, UserData, userData, QString); - ADD_PROPERTY_TO_MAP(PROP_SIMULATION_OWNER, SimulationOwner, simulationOwner, SimulationOwner); - ADD_PROPERTY_TO_MAP(PROP_TEXT, Text, text, QString); - ADD_PROPERTY_TO_MAP(PROP_LINE_HEIGHT, LineHeight, lineHeight, float); - ADD_PROPERTY_TO_MAP(PROP_TEXT_COLOR, TextColor, textColor, u8vec3Color); - ADD_PROPERTY_TO_MAP(PROP_BACKGROUND_COLOR, BackgroundColor, backgroundColor, u8vec3Color); - ADD_PROPERTY_TO_MAP(PROP_SHAPE_TYPE, ShapeType, shapeType, ShapeType); - ADD_PROPERTY_TO_MAP(PROP_MAX_PARTICLES, MaxParticles, maxParticles, quint32); - ADD_PROPERTY_TO_MAP(PROP_LIFESPAN, Lifespan, lifespan, float); - ADD_PROPERTY_TO_MAP(PROP_EMITTING_PARTICLES, IsEmitting, isEmitting, bool); - ADD_PROPERTY_TO_MAP(PROP_EMIT_RATE, EmitRate, emitRate, float); - ADD_PROPERTY_TO_MAP(PROP_EMIT_SPEED, EmitSpeed, emitSpeed, vec3); - ADD_PROPERTY_TO_MAP(PROP_SPEED_SPREAD, SpeedSpread, speedSpread, vec3); - ADD_PROPERTY_TO_MAP(PROP_EMIT_ORIENTATION, EmitOrientation, emitOrientation, quat); - ADD_PROPERTY_TO_MAP(PROP_EMIT_DIMENSIONS, EmitDimensions, emitDimensions, vec3); - ADD_PROPERTY_TO_MAP(PROP_EMIT_RADIUS_START, EmitRadiusStart, emitRadiusStart, float); - ADD_PROPERTY_TO_MAP(PROP_POLAR_START, EmitPolarStart, polarStart, float); - ADD_PROPERTY_TO_MAP(PROP_POLAR_FINISH, EmitPolarFinish, polarFinish, float); - ADD_PROPERTY_TO_MAP(PROP_AZIMUTH_START, EmitAzimuthStart, azimuthStart, float); - ADD_PROPERTY_TO_MAP(PROP_AZIMUTH_FINISH, EmitAzimuthFinish, azimuthFinish, float); - ADD_PROPERTY_TO_MAP(PROP_EMIT_ACCELERATION, EmitAcceleration, emitAcceleration, vec3); - ADD_PROPERTY_TO_MAP(PROP_ACCELERATION_SPREAD, AccelerationSpread, accelerationSpread, vec3); - ADD_PROPERTY_TO_MAP(PROP_PARTICLE_RADIUS, ParticleRadius, particleRadius, float); - ADD_PROPERTY_TO_MAP(PROP_RADIUS_SPREAD, RadiusSpread, radiusSpread, float); - ADD_PROPERTY_TO_MAP(PROP_RADIUS_START, RadiusStart, radiusStart, float); - ADD_PROPERTY_TO_MAP(PROP_RADIUS_FINISH, RadiusFinish, radiusFinish, float); + ADD_PROPERTY_TO_MAP(PROP_COLLISION_SOUND_URL, CollisionSoundURL, collisionSoundURL, QString); + ADD_PROPERTY_TO_MAP(PROP_ACTION_DATA, ActionData, actionData, QByteArray); - ADD_PROPERTY_TO_MAP(PROP_MATERIAL_URL, MaterialURL, materialURL, QString); - ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_MODE, MaterialMappingMode, materialMappingMode, MaterialMappingMode); - ADD_PROPERTY_TO_MAP(PROP_MATERIAL_PRIORITY, Priority, priority, quint16); - ADD_PROPERTY_TO_MAP(PROP_PARENT_MATERIAL_NAME, ParentMaterialName, parentMaterialName, QString); - ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_POS, MaterialMappingPos, materialMappingPos, vec2); - ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_SCALE, MaterialMappingScale, materialMappingScale, vec2); - ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_ROT, MaterialMappingRot, materialMappingRot, float); - ADD_PROPERTY_TO_MAP(PROP_MATERIAL_DATA, MaterialData, materialData, QString); - ADD_PROPERTY_TO_MAP(PROP_MATERIAL_REPEAT, MaterialRepeat, materialRepeat, bool); + // Cloning + ADD_PROPERTY_TO_MAP(PROP_CLONEABLE, Cloneable, cloneable, bool); + ADD_PROPERTY_TO_MAP(PROP_CLONE_LIFETIME, CloneLifetime, cloneLifetime, float); + ADD_PROPERTY_TO_MAP(PROP_CLONE_LIMIT, CloneLimit, cloneLimit, float); + ADD_PROPERTY_TO_MAP(PROP_CLONE_DYNAMIC, CloneDynamic, cloneDynamic, bool); + ADD_PROPERTY_TO_MAP(PROP_CLONE_AVATAR_ENTITY, CloneAvatarEntity, cloneAvatarEntity, bool); + ADD_PROPERTY_TO_MAP(PROP_CLONE_ORIGIN_ID, CloneOriginID, cloneOriginID, QUuid); - ADD_PROPERTY_TO_MAP(PROP_VISIBLE_IN_SECONDARY_CAMERA, IsVisibleInSecondaryCamera, isVisibleInSecondaryCamera, bool); - - ADD_PROPERTY_TO_MAP(PROP_PARTICLE_SPIN, ParticleSpin, particleSpin, float); - ADD_PROPERTY_TO_MAP(PROP_SPIN_SPREAD, SpinSpread, spinSpread, float); - ADD_PROPERTY_TO_MAP(PROP_SPIN_START, SpinStart, spinStart, float); - ADD_PROPERTY_TO_MAP(PROP_SPIN_FINISH, SpinFinish, spinFinish, float); - ADD_PROPERTY_TO_MAP(PROP_PARTICLE_ROTATE_WITH_ENTITY, RotateWithEntity, rotateWithEntity, float); + // Scripts + ADD_PROPERTY_TO_MAP(PROP_SCRIPT, Script, script, QString); + ADD_PROPERTY_TO_MAP(PROP_SCRIPT_TIMESTAMP, ScriptTimestamp, scriptTimestamp, quint64); + ADD_PROPERTY_TO_MAP(PROP_SERVER_SCRIPTS, ServerScripts, serverScripts, QString); // Certifiable Properties ADD_PROPERTY_TO_MAP(PROP_ITEM_NAME, ItemName, itemName, QString); @@ -2121,26 +2412,168 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_CERTIFICATE_ID, CertificateID, certificateID, QString); ADD_PROPERTY_TO_MAP(PROP_STATIC_CERTIFICATE_VERSION, StaticCertificateVersion, staticCertificateVersion, quint32); - ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_COLOR, KeyLightColor, keyLightColor, u8vec3Color); - ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_INTENSITY, KeyLightIntensity, keyLightIntensity, float); - ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_DIRECTION, KeyLightDirection, keyLightDirection, vec3); - ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_CAST_SHADOW, KeyLightCastShadows, keyLightCastShadows, bool); + // Local script props + ADD_PROPERTY_TO_MAP(PROP_LOCAL_POSITION, LocalPosition, localPosition, vec3); + ADD_PROPERTY_TO_MAP(PROP_LOCAL_ROTATION, LocalRotation, localRotation, quat); + ADD_PROPERTY_TO_MAP(PROP_LOCAL_VELOCITY, LocalVelocity, localVelocity, vec3); + ADD_PROPERTY_TO_MAP(PROP_LOCAL_ANGULAR_VELOCITY, LocalAngularVelocity, localAngularVelocity, vec3); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_LOCAL_DIMENSIONS, LocalDimensions, localDimensions, vec3, + ENTITY_ITEM_MIN_DIMENSION, FLT_MAX); + // Common + ADD_PROPERTY_TO_MAP(PROP_SHAPE_TYPE, ShapeType, shapeType, ShapeType); + ADD_PROPERTY_TO_MAP(PROP_COMPOUND_SHAPE_URL, CompoundShapeURL, compoundShapeURL, QString); + ADD_PROPERTY_TO_MAP(PROP_COLOR, Color, color, u8vec3Color); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_ALPHA, Alpha, alpha, float, particle::MINIMUM_ALPHA, particle::MAXIMUM_ALPHA); + ADD_PROPERTY_TO_MAP(PROP_TEXTURES, Textures, textures, QString); + + // Particles + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_MAX_PARTICLES, MaxParticles, maxParticles, quint32, + particle::MINIMUM_MAX_PARTICLES, particle::MAXIMUM_MAX_PARTICLES); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_LIFESPAN, Lifespan, lifespan, float, + particle::MINIMUM_LIFESPAN, particle::MAXIMUM_LIFESPAN); + ADD_PROPERTY_TO_MAP(PROP_EMITTING_PARTICLES, IsEmitting, isEmitting, bool); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_EMIT_RATE, EmitRate, emitRate, float, + particle::MINIMUM_EMIT_RATE, particle::MAXIMUM_EMIT_RATE); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_EMIT_SPEED, EmitSpeed, emitSpeed, vec3, + particle::MINIMUM_EMIT_SPEED, particle::MAXIMUM_EMIT_SPEED); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_SPEED_SPREAD, SpeedSpread, speedSpread, vec3, + particle::MINIMUM_EMIT_SPEED, particle::MAXIMUM_EMIT_SPEED); + ADD_PROPERTY_TO_MAP(PROP_EMIT_ORIENTATION, EmitOrientation, emitOrientation, quat); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_EMIT_DIMENSIONS, EmitDimensions, emitDimensions, vec3, + particle::MINIMUM_EMIT_DIMENSION, particle::MAXIMUM_EMIT_DIMENSION); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_EMIT_RADIUS_START, EmitRadiusStart, emitRadiusStart, float, + particle::MINIMUM_EMIT_RADIUS_START, particle::MAXIMUM_EMIT_RADIUS_START); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_POLAR_START, EmitPolarStart, polarStart, float, + particle::MINIMUM_POLAR, particle::MAXIMUM_POLAR); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_POLAR_FINISH, EmitPolarFinish, polarFinish, float, + particle::MINIMUM_POLAR, particle::MAXIMUM_POLAR); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_AZIMUTH_START, EmitAzimuthStart, azimuthStart, float, + particle::MINIMUM_AZIMUTH, particle::MAXIMUM_AZIMUTH); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_AZIMUTH_FINISH, EmitAzimuthFinish, azimuthFinish, float, + particle::MINIMUM_AZIMUTH, particle::MAXIMUM_AZIMUTH); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_EMIT_ACCELERATION, EmitAcceleration, emitAcceleration, vec3, + particle::MINIMUM_EMIT_ACCELERATION, particle::MAXIMUM_EMIT_ACCELERATION); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_ACCELERATION_SPREAD, AccelerationSpread, accelerationSpread, vec3, + particle::MINIMUM_ACCELERATION_SPREAD, particle::MAXIMUM_ACCELERATION_SPREAD); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_PARTICLE_RADIUS, ParticleRadius, particleRadius, float, + particle::MINIMUM_PARTICLE_RADIUS, particle::MAXIMUM_PARTICLE_RADIUS); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_RADIUS_SPREAD, RadiusSpread, radiusSpread, float, + particle::MINIMUM_PARTICLE_RADIUS, particle::MAXIMUM_PARTICLE_RADIUS); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_RADIUS_START, RadiusStart, radiusStart, float, + particle::MINIMUM_PARTICLE_RADIUS, particle::MAXIMUM_PARTICLE_RADIUS); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_RADIUS_FINISH, RadiusFinish, radiusFinish, float, + particle::MINIMUM_PARTICLE_RADIUS, particle::MAXIMUM_PARTICLE_RADIUS); + ADD_PROPERTY_TO_MAP(PROP_COLOR_SPREAD, ColorSpread, colorSpread, u8vec3Color); + ADD_PROPERTY_TO_MAP(PROP_COLOR_START, ColorStart, colorStart, vec3Color); + ADD_PROPERTY_TO_MAP(PROP_COLOR_FINISH, ColorFinish, colorFinish, vec3Color); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_ALPHA_SPREAD, AlphaSpread, alphaSpread, float, + particle::MINIMUM_ALPHA, particle::MAXIMUM_ALPHA); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_ALPHA_START, AlphaStart, alphaStart, float, + particle::MINIMUM_ALPHA, particle::MAXIMUM_ALPHA); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_ALPHA_FINISH, AlphaFinish, alphaFinish, float, + particle::MINIMUM_ALPHA, particle::MAXIMUM_ALPHA); + ADD_PROPERTY_TO_MAP(PROP_EMITTER_SHOULD_TRAIL, EmitterShouldTrail, emitterShouldTrail, bool); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_PARTICLE_SPIN, ParticleSpin, particleSpin, float, + particle::MINIMUM_PARTICLE_SPIN, particle::MAXIMUM_PARTICLE_SPIN); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_SPIN_SPREAD, SpinSpread, spinSpread, float, + particle::MINIMUM_PARTICLE_SPIN, particle::MAXIMUM_PARTICLE_SPIN); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_SPIN_START, SpinStart, spinStart, float, + particle::MINIMUM_PARTICLE_SPIN, particle::MAXIMUM_PARTICLE_SPIN); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_SPIN_FINISH, SpinFinish, spinFinish, float, + particle::MINIMUM_PARTICLE_SPIN, particle::MAXIMUM_PARTICLE_SPIN); + ADD_PROPERTY_TO_MAP(PROP_PARTICLE_ROTATE_WITH_ENTITY, RotateWithEntity, rotateWithEntity, float); + + // Model + ADD_PROPERTY_TO_MAP(PROP_MODEL_URL, ModelURL, modelURL, QString); + ADD_PROPERTY_TO_MAP(PROP_JOINT_ROTATIONS_SET, JointRotationsSet, jointRotationsSet, QVector<bool>); + ADD_PROPERTY_TO_MAP(PROP_JOINT_ROTATIONS, JointRotations, jointRotations, QVector<quat>); + ADD_PROPERTY_TO_MAP(PROP_JOINT_TRANSLATIONS_SET, JointTranslationsSet, jointTranslationsSet, QVector<bool>); + ADD_PROPERTY_TO_MAP(PROP_JOINT_TRANSLATIONS, JointTranslations, jointTranslations, QVector<vec3>); + ADD_PROPERTY_TO_MAP(PROP_RELAY_PARENT_JOINTS, RelayParentJoints, relayParentJoints, bool); + { // Animation + ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_URL, Animation, animation, URL, url); + ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_ALLOW_TRANSLATION, Animation, animation, AllowTranslation, allowTranslation); + ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_FPS, Animation, animation, FPS, fps); + ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_FRAME_INDEX, Animation, animation, CurrentFrame, currentFrame); + ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_PLAYING, Animation, animation, Running, running); + ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_LOOP, Animation, animation, Loop, loop); + ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_FIRST_FRAME, Animation, animation, FirstFrame, firstFrame); + ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_LAST_FRAME, Animation, animation, LastFrame, lastFrame); + ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_HOLD, Animation, animation, Hold, hold); + } + + // Light + ADD_PROPERTY_TO_MAP(PROP_IS_SPOTLIGHT, IsSpotlight, isSpotlight, bool); + ADD_PROPERTY_TO_MAP(PROP_INTENSITY, Intensity, intensity, float); + ADD_PROPERTY_TO_MAP(PROP_EXPONENT, Exponent, exponent, float); + ADD_PROPERTY_TO_MAP_WITH_RANGE(PROP_CUTOFF, Cutoff, cutoff, float, + LightEntityItem::MIN_CUTOFF, LightEntityItem::MAX_CUTOFF); + ADD_PROPERTY_TO_MAP(PROP_FALLOFF_RADIUS, FalloffRadius, falloffRadius, float); + + // Text + ADD_PROPERTY_TO_MAP(PROP_TEXT, Text, text, QString); + ADD_PROPERTY_TO_MAP(PROP_LINE_HEIGHT, LineHeight, lineHeight, float); + ADD_PROPERTY_TO_MAP(PROP_TEXT_COLOR, TextColor, textColor, u8vec3Color); + ADD_PROPERTY_TO_MAP(PROP_TEXT_ALPHA, TextAlpha, textAlpha, float); + ADD_PROPERTY_TO_MAP(PROP_BACKGROUND_COLOR, BackgroundColor, backgroundColor, u8vec3Color); + ADD_PROPERTY_TO_MAP(PROP_BACKGROUND_ALPHA, BackgroundAlpha, backgroundAlpha, float); + ADD_PROPERTY_TO_MAP(PROP_BILLBOARD_MODE, BillboardMode, billboardMode, BillboardMode); + ADD_PROPERTY_TO_MAP(PROP_LEFT_MARGIN, LeftMargin, leftMargin, float); + ADD_PROPERTY_TO_MAP(PROP_RIGHT_MARGIN, RightMargin, rightMargin, float); + ADD_PROPERTY_TO_MAP(PROP_TOP_MARGIN, TopMargin, topMargin, float); + ADD_PROPERTY_TO_MAP(PROP_BOTTOM_MARGIN, BottomMargin, bottomMargin, float); + + // Zone + { // Keylight + ADD_GROUP_PROPERTY_TO_MAP(PROP_KEYLIGHT_COLOR, KeyLight, keyLight, Color, color); + ADD_GROUP_PROPERTY_TO_MAP(PROP_KEYLIGHT_INTENSITY, KeyLight, keyLight, Intensity, intensity); + ADD_GROUP_PROPERTY_TO_MAP(PROP_KEYLIGHT_DIRECTION, KeyLight, keylight, Direction, direction); + ADD_GROUP_PROPERTY_TO_MAP(PROP_KEYLIGHT_CAST_SHADOW, KeyLight, keyLight, CastShadows, castShadows); + } + { // Ambient light + ADD_GROUP_PROPERTY_TO_MAP(PROP_AMBIENT_LIGHT_INTENSITY, AmbientLight, ambientLight, Intensity, intensity); + ADD_GROUP_PROPERTY_TO_MAP(PROP_AMBIENT_LIGHT_URL, AmbientLight, ambientLight, URL, url); + } + { // Skybox + ADD_GROUP_PROPERTY_TO_MAP(PROP_SKYBOX_COLOR, Skybox, skybox, Color, color); + ADD_GROUP_PROPERTY_TO_MAP(PROP_SKYBOX_URL, Skybox, skybox, URL, url); + } + { // Haze + ADD_GROUP_PROPERTY_TO_MAP(PROP_HAZE_RANGE, Haze, haze, HazeRange, hazeRange); + ADD_GROUP_PROPERTY_TO_MAP(PROP_HAZE_COLOR, Haze, haze, HazeColor, hazeColor); + ADD_GROUP_PROPERTY_TO_MAP(PROP_HAZE_GLARE_COLOR, Haze, haze, HazeGlareColor, hazeGlareColor); + ADD_GROUP_PROPERTY_TO_MAP(PROP_HAZE_ENABLE_GLARE, Haze, haze, HazeEnableGlare, hazeEnableGlare); + ADD_GROUP_PROPERTY_TO_MAP(PROP_HAZE_GLARE_ANGLE, Haze, haze, HazeGlareAngle, hazeGlareAngle); + + ADD_GROUP_PROPERTY_TO_MAP(PROP_HAZE_ALTITUDE_EFFECT, Haze, haze, HazeAltitudeEffect, hazeAltitudeEfect); + ADD_GROUP_PROPERTY_TO_MAP(PROP_HAZE_CEILING, Haze, haze, HazeCeiling, hazeCeiling); + ADD_GROUP_PROPERTY_TO_MAP(PROP_HAZE_BASE_REF, Haze, haze, HazeBaseRef, hazeBaseRef); + + ADD_GROUP_PROPERTY_TO_MAP(PROP_HAZE_BACKGROUND_BLEND, Haze, haze, HazeBackgroundBlend, hazeBackgroundBlend); + + ADD_GROUP_PROPERTY_TO_MAP(PROP_HAZE_ATTENUATE_KEYLIGHT, Haze, haze, HazeAttenuateKeyLight, hazeAttenuateKeyLight); + ADD_GROUP_PROPERTY_TO_MAP(PROP_HAZE_KEYLIGHT_RANGE, Haze, haze, HazeKeyLightRange, hazeKeyLightRange); + ADD_GROUP_PROPERTY_TO_MAP(PROP_HAZE_KEYLIGHT_ALTITUDE, Haze, haze, HazeKeyLightAltitude, hazeKeyLightAltitude); + } + { // Bloom + ADD_GROUP_PROPERTY_TO_MAP(PROP_BLOOM_INTENSITY, Bloom, bloom, BloomIntensity, bloomIntensity); + ADD_GROUP_PROPERTY_TO_MAP(PROP_BLOOM_THRESHOLD, Bloom, bloom, BloomThreshold, bloomThreshold); + ADD_GROUP_PROPERTY_TO_MAP(PROP_BLOOM_SIZE, Bloom, bloom, BloomSize, bloomSize); + } + ADD_PROPERTY_TO_MAP(PROP_FLYING_ALLOWED, FlyingAllowed, flyingAllowed, bool); + ADD_PROPERTY_TO_MAP(PROP_GHOSTING_ALLOWED, GhostingAllowed, ghostingAllowed, bool); + ADD_PROPERTY_TO_MAP(PROP_FILTER_URL, FilterURL, filterURL, QString); + ADD_PROPERTY_TO_MAP(PROP_KEY_LIGHT_MODE, KeyLightMode, keyLightMode, uint32_t); + ADD_PROPERTY_TO_MAP(PROP_AMBIENT_LIGHT_MODE, AmbientLightMode, ambientLightMode, uint32_t); + ADD_PROPERTY_TO_MAP(PROP_SKYBOX_MODE, SkyboxMode, skyboxMode, uint32_t); + ADD_PROPERTY_TO_MAP(PROP_HAZE_MODE, HazeMode, hazeMode, uint32_t); + ADD_PROPERTY_TO_MAP(PROP_BLOOM_MODE, BloomMode, bloomMode, uint32_t); + + // Polyvox ADD_PROPERTY_TO_MAP(PROP_VOXEL_VOLUME_SIZE, VoxelVolumeSize, voxelVolumeSize, vec3); ADD_PROPERTY_TO_MAP(PROP_VOXEL_DATA, VoxelData, voxelData, QByteArray); ADD_PROPERTY_TO_MAP(PROP_VOXEL_SURFACE_STYLE, VoxelSurfaceStyle, voxelSurfaceStyle, uint16_t); - ADD_PROPERTY_TO_MAP(PROP_NAME, Name, name, QString); - ADD_PROPERTY_TO_MAP(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString); - ADD_PROPERTY_TO_MAP(PROP_LINE_WIDTH, LineWidth, lineWidth, float); - ADD_PROPERTY_TO_MAP(PROP_LINE_POINTS, LinePoints, linePoints, QVector<vec3>); - ADD_PROPERTY_TO_MAP(PROP_HREF, Href, href, QString); - ADD_PROPERTY_TO_MAP(PROP_DESCRIPTION, Description, description, QString); - ADD_PROPERTY_TO_MAP(PROP_FACE_CAMERA, FaceCamera, faceCamera, bool); - ADD_PROPERTY_TO_MAP(PROP_ACTION_DATA, ActionData, actionData, QByteArray); - ADD_PROPERTY_TO_MAP(PROP_NORMALS, Normals, normals, QVector<vec3>); - ADD_PROPERTY_TO_MAP(PROP_STROKE_COLORS, StrokeColors, strokeColors, QVector<vec3>); - ADD_PROPERTY_TO_MAP(PROP_STROKE_WIDTHS, StrokeWidths, strokeWidths, QVector<float>); - ADD_PROPERTY_TO_MAP(PROP_IS_UV_MODE_STRETCH, IsUVModeStretch, isUVModeStretch, QVector<float>); ADD_PROPERTY_TO_MAP(PROP_X_TEXTURE_URL, XTextureURL, xTextureURL, QString); ADD_PROPERTY_TO_MAP(PROP_Y_TEXTURE_URL, YTextureURL, yTextureURL, QString); ADD_PROPERTY_TO_MAP(PROP_Z_TEXTURE_URL, ZTextureURL, zTextureURL, QString); @@ -2151,116 +2584,66 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_Y_P_NEIGHBOR_ID, YPNeighborID, yPNeighborID, EntityItemID); ADD_PROPERTY_TO_MAP(PROP_Z_P_NEIGHBOR_ID, ZPNeighborID, zPNeighborID, EntityItemID); - ADD_PROPERTY_TO_MAP(PROP_PARENT_ID, ParentID, parentID, QUuid); - ADD_PROPERTY_TO_MAP(PROP_PARENT_JOINT_INDEX, ParentJointIndex, parentJointIndex, uint16_t); - - ADD_PROPERTY_TO_MAP(PROP_LOCAL_POSITION, LocalPosition, localPosition, vec3); - ADD_PROPERTY_TO_MAP(PROP_LOCAL_ROTATION, LocalRotation, localRotation, quat); - ADD_PROPERTY_TO_MAP(PROP_LOCAL_VELOCITY, LocalVelocity, localVelocity, vec3); - ADD_PROPERTY_TO_MAP(PROP_LOCAL_ANGULAR_VELOCITY, LocalAngularVelocity, localAngularVelocity, vec3); - ADD_PROPERTY_TO_MAP(PROP_LOCAL_DIMENSIONS, LocalDimensions, localDimensions, vec3); - - ADD_PROPERTY_TO_MAP(PROP_JOINT_ROTATIONS_SET, JointRotationsSet, jointRotationsSet, QVector<bool>); - ADD_PROPERTY_TO_MAP(PROP_JOINT_ROTATIONS, JointRotations, jointRotations, QVector<quat>); - ADD_PROPERTY_TO_MAP(PROP_JOINT_TRANSLATIONS_SET, JointTranslationsSet, jointTranslationsSet, QVector<bool>); - ADD_PROPERTY_TO_MAP(PROP_JOINT_TRANSLATIONS, JointTranslations, jointTranslations, QVector<vec3>); - ADD_PROPERTY_TO_MAP(PROP_RELAY_PARENT_JOINTS, RelayParentJoints, relayParentJoints, bool); - - ADD_PROPERTY_TO_MAP(PROP_SHAPE, Shape, shape, QString); - - ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_URL, Animation, animation, URL, url); - ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_FPS, Animation, animation, FPS, fps); - ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_FRAME_INDEX, Animation, animation, CurrentFrame, currentFrame); - ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_PLAYING, Animation, animation, Running, running); - ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_LOOP, Animation, animation, Loop, loop); - ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_FIRST_FRAME, Animation, animation, FirstFrame, firstFrame); - ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_LAST_FRAME, Animation, animation, LastFrame, lastFrame); - ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_HOLD, Animation, animation, Hold, hold); - ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_ALLOW_TRANSLATION, Animation, animation, AllowTranslation, allowTranslation); - - ADD_GROUP_PROPERTY_TO_MAP(PROP_SKYBOX_COLOR, Skybox, skybox, Color, color); - ADD_GROUP_PROPERTY_TO_MAP(PROP_SKYBOX_URL, Skybox, skybox, URL, url); - - ADD_PROPERTY_TO_MAP(PROP_FLYING_ALLOWED, FlyingAllowed, flyingAllowed, bool); - ADD_PROPERTY_TO_MAP(PROP_GHOSTING_ALLOWED, GhostingAllowed, ghostingAllowed, bool); - ADD_PROPERTY_TO_MAP(PROP_FILTER_URL, FilterURL, filterURL, QString); - - ADD_PROPERTY_TO_MAP(PROP_HAZE_MODE, HazeMode, hazeMode, uint32_t); - - ADD_GROUP_PROPERTY_TO_MAP(PROP_HAZE_RANGE, Haze, haze, HazeRange, hazeRange); - ADD_GROUP_PROPERTY_TO_MAP(PROP_HAZE_COLOR, Haze, haze, HazeColor, hazeColor); - ADD_GROUP_PROPERTY_TO_MAP(PROP_HAZE_GLARE_COLOR, Haze, haze, HazeGlareColor, hazeGlareColor); - ADD_GROUP_PROPERTY_TO_MAP(PROP_HAZE_ENABLE_GLARE, Haze, haze, HazeEnableGlare, hazeEnableGlare); - ADD_GROUP_PROPERTY_TO_MAP(PROP_HAZE_GLARE_ANGLE, Haze, haze, HazeGlareAngle, hazeGlareAngle); - - ADD_GROUP_PROPERTY_TO_MAP(PROP_HAZE_ALTITUDE_EFFECT, Haze, haze, HazeAltitudeEffect, hazeAltitudeEfect); - ADD_GROUP_PROPERTY_TO_MAP(PROP_HAZE_CEILING, Haze, haze, HazeCeiling, hazeCeiling); - ADD_GROUP_PROPERTY_TO_MAP(PROP_HAZE_BASE_REF, Haze, haze, HazeBaseRef, hazeBaseRef); - - ADD_GROUP_PROPERTY_TO_MAP(PROP_HAZE_BACKGROUND_BLEND, Haze, haze, HazeBackgroundBlend, hazeBackgroundBlend); - - ADD_GROUP_PROPERTY_TO_MAP(PROP_HAZE_ATTENUATE_KEYLIGHT, Haze, haze, HazeAttenuateKeyLight, hazeAttenuateKeyLight); - ADD_GROUP_PROPERTY_TO_MAP(PROP_HAZE_KEYLIGHT_RANGE, Haze, haze, HazeKeyLightRange, hazeKeyLightRange); - ADD_GROUP_PROPERTY_TO_MAP(PROP_HAZE_KEYLIGHT_ALTITUDE, Haze, haze, HazeKeyLightAltitude, hazeKeyLightAltitude); - - ADD_PROPERTY_TO_MAP(PROP_BLOOM_MODE, BloomMode, bloomMode, uint32_t); - ADD_GROUP_PROPERTY_TO_MAP(PROP_BLOOM_INTENSITY, Bloom, bloom, BloomIntensity, bloomIntensity); - ADD_GROUP_PROPERTY_TO_MAP(PROP_BLOOM_THRESHOLD, Bloom, bloom, BloomThreshold, bloomThreshold); - ADD_GROUP_PROPERTY_TO_MAP(PROP_BLOOM_SIZE, Bloom, bloom, BloomSize, bloomSize); - - ADD_PROPERTY_TO_MAP(PROP_KEY_LIGHT_MODE, KeyLightMode, keyLightMode, uint32_t); - ADD_PROPERTY_TO_MAP(PROP_AMBIENT_LIGHT_MODE, AmbientLightMode, ambientLightMode, uint32_t); - ADD_PROPERTY_TO_MAP(PROP_SKYBOX_MODE, SkyboxMode, skyboxMode, uint32_t); - + // Web + ADD_PROPERTY_TO_MAP(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString); ADD_PROPERTY_TO_MAP(PROP_DPI, DPI, dpi, uint16_t); - ADD_PROPERTY_TO_MAP(PROP_CLONEABLE, Cloneable, cloneable, bool); - ADD_PROPERTY_TO_MAP(PROP_CLONE_LIFETIME, CloneLifetime, cloneLifetime, float); - ADD_PROPERTY_TO_MAP(PROP_CLONE_LIMIT, CloneLimit, cloneLimit, float); - ADD_PROPERTY_TO_MAP(PROP_CLONE_DYNAMIC, CloneDynamic, cloneDynamic, bool); - ADD_PROPERTY_TO_MAP(PROP_CLONE_AVATAR_ENTITY, CloneAvatarEntity, cloneAvatarEntity, bool); - ADD_PROPERTY_TO_MAP(PROP_CLONE_ORIGIN_ID, CloneOriginID, cloneOriginID, QUuid); + // Polyline + ADD_PROPERTY_TO_MAP(PROP_LINE_POINTS, LinePoints, linePoints, QVector<vec3>); + ADD_PROPERTY_TO_MAP(PROP_STROKE_WIDTHS, StrokeWidths, strokeWidths, QVector<float>); + ADD_PROPERTY_TO_MAP(PROP_STROKE_NORMALS, Normals, normals, QVector<vec3>); + ADD_PROPERTY_TO_MAP(PROP_STROKE_COLORS, StrokeColors, strokeColors, QVector<vec3>); + ADD_PROPERTY_TO_MAP(PROP_IS_UV_MODE_STRETCH, IsUVModeStretch, isUVModeStretch, QVector<float>); + ADD_PROPERTY_TO_MAP(PROP_LINE_GLOW, Glow, glow, bool); + ADD_PROPERTY_TO_MAP(PROP_LINE_FACE_CAMERA, FaceCamera, faceCamera, bool); - ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_GRABBABLE, Grab, grab, Grabbable, grabbable); - ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_KINEMATIC, Grab, grab, GrabKinematic, grabKinematic); - ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_FOLLOWS_CONTROLLER, Grab, grab, GrabFollowsController, grabFollowsController); - ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_TRIGGERABLE, Grab, grab, Triggerable, triggerable); - ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_EQUIPPABLE, Grab, grab, Equippable, equippable); - ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, Grab, grab, - EquippableLeftPosition, equippableLeftPosition); - ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, Grab, grab, - EquippableLeftRotation, equippableLeftRotation); - ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET, Grab, grab, - EquippableRightPosition, equippableRightPosition); - ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_RIGHT_EQUIPPABLE_ROTATION_OFFSET, Grab, grab, - EquippableRightRotation, equippableRightRotation); - ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_EQUIPPABLE_INDICATOR_URL, Grab, grab, - EquippableIndicatorURL, equippableIndicatorURL); - ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_EQUIPPABLE_INDICATOR_SCALE, Grab, grab, - EquippableIndicatorScale, equippableIndicatorScale); - ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_EQUIPPABLE_INDICATOR_OFFSET, Grab, grab, - EquippableIndicatorOffset, equippableIndicatorOffset); + // Shape + ADD_PROPERTY_TO_MAP(PROP_SHAPE, Shape, shape, QString); - // FIXME - these are not yet handled - //ADD_PROPERTY_TO_MAP(PROP_CREATED, Created, created, quint64); + // Material + ADD_PROPERTY_TO_MAP(PROP_MATERIAL_URL, MaterialURL, materialURL, QString); + ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_MODE, MaterialMappingMode, materialMappingMode, MaterialMappingMode); + ADD_PROPERTY_TO_MAP(PROP_MATERIAL_PRIORITY, Priority, priority, quint16); + ADD_PROPERTY_TO_MAP(PROP_PARENT_MATERIAL_NAME, ParentMaterialName, parentMaterialName, QString); + ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_POS, MaterialMappingPos, materialMappingPos, vec2); + ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_SCALE, MaterialMappingScale, materialMappingScale, vec2); + ADD_PROPERTY_TO_MAP(PROP_MATERIAL_MAPPING_ROT, MaterialMappingRot, materialMappingRot, float); + ADD_PROPERTY_TO_MAP(PROP_MATERIAL_DATA, MaterialData, materialData, QString); + ADD_PROPERTY_TO_MAP(PROP_MATERIAL_REPEAT, MaterialRepeat, materialRepeat, bool); + // Image + ADD_PROPERTY_TO_MAP(PROP_IMAGE_URL, ImageURL, imageURL, QString); + ADD_PROPERTY_TO_MAP(PROP_EMISSIVE, Emissive, emissive, bool); + ADD_PROPERTY_TO_MAP(PROP_KEEP_ASPECT_RATIO, KeepAspectRatio, keepAspectRatio, bool); + ADD_PROPERTY_TO_MAP(PROP_SUB_IMAGE, SubImage, subImage, QRect); + + // Grid + ADD_PROPERTY_TO_MAP(PROP_GRID_FOLLOW_CAMERA, FollowCamera, followCamera, bool); + ADD_PROPERTY_TO_MAP(PROP_MAJOR_GRID_EVERY, MajorGridEvery, majorGridEvery, uint32_t); + ADD_PROPERTY_TO_MAP(PROP_MINOR_GRID_EVERY, MinorGridEvery, minorGridEvery, float); }); - if (object.isString()) { - // TODO: figure out how to do this without a double lookup in the map - if (_propertyStringsToEnums.contains(object.toString())) { - flags << _propertyStringsToEnums[object.toString()]; - } - } else if (object.isArray()) { - quint32 length = object.property("length").toInt32(); - for (quint32 i = 0; i < length; i++) { - QString propertyName = object.property(i).toString(); - // TODO: figure out how to do this without a double lookup in the map - if (_propertyStringsToEnums.contains(propertyName)) { - flags << _propertyStringsToEnums[propertyName]; - } - } + auto iter = _propertyInfos.find(propertyName); + if (iter != _propertyInfos.end()) { + propertyInfo = *iter; + return true; } + + return false; +} + +QScriptValue EntityPropertyInfoToScriptValue(QScriptEngine* engine, const EntityPropertyInfo& propertyInfo) { + QScriptValue obj = engine->newObject(); + obj.setProperty("propertyEnum", propertyInfo.propertyEnum); + obj.setProperty("minimum", propertyInfo.minimum.toString()); + obj.setProperty("maximum", propertyInfo.maximum.toString()); + return obj; +} + +void EntityPropertyInfoFromScriptValue(const QScriptValue& object, EntityPropertyInfo& propertyInfo) { + propertyInfo.propertyEnum = (EntityPropertyList)object.property("propertyEnum").toVariant().toUInt(); + propertyInfo.minimum = object.property("minimum").toVariant(); + propertyInfo.maximum = object.property("maximum").toVariant(); } // TODO: Implement support for edit packets that can span an MTU sized buffer. We need to implement a mechanism for the @@ -2350,8 +2733,8 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy bool successPropertyFlagsFits = packetData->appendRawData(encodedPropertyFlags); int propertyCount = 0; - bool headerFits = successIDFits && successTypeFits && successLastEditedFits - && successLastUpdatedFits && successPropertyFlagsFits; + bool headerFits = successIDFits && successTypeFits && successLastEditedFits && + successLastUpdatedFits && successPropertyFlagsFits; int startOfEntityItemData = packetData->getUncompressedByteOffset(); @@ -2363,80 +2746,83 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy // PROP_PAGED_PROPERTY, // PROP_CUSTOM_PROPERTIES_INCLUDED, + APPEND_ENTITY_PROPERTY(PROP_SIMULATION_OWNER, properties._simulationOwner.toByteArray()); - APPEND_ENTITY_PROPERTY(PROP_POSITION, properties.getPosition()); - APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, properties.getDimensions()); - APPEND_ENTITY_PROPERTY(PROP_ROTATION, properties.getRotation()); - APPEND_ENTITY_PROPERTY(PROP_DENSITY, properties.getDensity()); - APPEND_ENTITY_PROPERTY(PROP_VELOCITY, properties.getVelocity()); - APPEND_ENTITY_PROPERTY(PROP_GRAVITY, properties.getGravity()); - APPEND_ENTITY_PROPERTY(PROP_ACCELERATION, properties.getAcceleration()); - APPEND_ENTITY_PROPERTY(PROP_DAMPING, properties.getDamping()); - APPEND_ENTITY_PROPERTY(PROP_RESTITUTION, properties.getRestitution()); - APPEND_ENTITY_PROPERTY(PROP_FRICTION, properties.getFriction()); - APPEND_ENTITY_PROPERTY(PROP_LIFETIME, properties.getLifetime()); - APPEND_ENTITY_PROPERTY(PROP_SCRIPT, properties.getScript()); - APPEND_ENTITY_PROPERTY(PROP_SCRIPT_TIMESTAMP, properties.getScriptTimestamp()); - APPEND_ENTITY_PROPERTY(PROP_SERVER_SCRIPTS, properties.getServerScripts()); - APPEND_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, properties.getRegistrationPoint()); - APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, properties.getAngularVelocity()); - APPEND_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, properties.getAngularDamping()); APPEND_ENTITY_PROPERTY(PROP_VISIBLE, properties.getVisible()); - APPEND_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, properties.getCanCastShadow()); - APPEND_ENTITY_PROPERTY(PROP_COLLISIONLESS, properties.getCollisionless()); - APPEND_ENTITY_PROPERTY(PROP_COLLISION_MASK, properties.getCollisionMask()); - APPEND_ENTITY_PROPERTY(PROP_DYNAMIC, properties.getDynamic()); + APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName()); APPEND_ENTITY_PROPERTY(PROP_LOCKED, properties.getLocked()); APPEND_ENTITY_PROPERTY(PROP_USER_DATA, properties.getUserData()); APPEND_ENTITY_PROPERTY(PROP_HREF, properties.getHref()); APPEND_ENTITY_PROPERTY(PROP_DESCRIPTION, properties.getDescription()); + APPEND_ENTITY_PROPERTY(PROP_POSITION, properties.getPosition()); + APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, properties.getDimensions()); + APPEND_ENTITY_PROPERTY(PROP_ROTATION, properties.getRotation()); + APPEND_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, properties.getRegistrationPoint()); + APPEND_ENTITY_PROPERTY(PROP_CREATED, properties.getCreated()); + APPEND_ENTITY_PROPERTY(PROP_LAST_EDITED_BY, properties.getLastEditedBy()); + // APPEND_ENTITY_PROPERTY(PROP_ENTITY_HOST_TYPE, (uint32_t)properties.getEntityHostType()); // not sent over the wire + // APPEND_ENTITY_PROPERTY(PROP_OWNING_AVATAR_ID, properties.getOwningAvatarID()); // not sent over the wire APPEND_ENTITY_PROPERTY(PROP_PARENT_ID, properties.getParentID()); APPEND_ENTITY_PROPERTY(PROP_PARENT_JOINT_INDEX, properties.getParentJointIndex()); APPEND_ENTITY_PROPERTY(PROP_QUERY_AA_CUBE, properties.getQueryAACube()); + APPEND_ENTITY_PROPERTY(PROP_CAN_CAST_SHADOW, properties.getCanCastShadow()); + // APPEND_ENTITY_PROPERTY(PROP_VISIBLE_IN_SECONDARY_CAMERA, properties.getIsVisibleInSecondaryCamera()); // not sent over the wire + _staticGrab.setProperties(properties); + _staticGrab.appendToEditPacket(packetData, requestedProperties, propertyFlags, + propertiesDidntFit, propertyCount, appendState); - if (properties.getType() == EntityTypes::Web) { - APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, properties.getSourceUrl()); - APPEND_ENTITY_PROPERTY(PROP_DPI, properties.getDPI()); - } + // Physics + APPEND_ENTITY_PROPERTY(PROP_DENSITY, properties.getDensity()); + APPEND_ENTITY_PROPERTY(PROP_VELOCITY, properties.getVelocity()); + APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, properties.getAngularVelocity()); + APPEND_ENTITY_PROPERTY(PROP_GRAVITY, properties.getGravity()); + APPEND_ENTITY_PROPERTY(PROP_ACCELERATION, properties.getAcceleration()); + APPEND_ENTITY_PROPERTY(PROP_DAMPING, properties.getDamping()); + APPEND_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, properties.getAngularDamping()); + APPEND_ENTITY_PROPERTY(PROP_RESTITUTION, properties.getRestitution()); + APPEND_ENTITY_PROPERTY(PROP_FRICTION, properties.getFriction()); + APPEND_ENTITY_PROPERTY(PROP_LIFETIME, properties.getLifetime()); + APPEND_ENTITY_PROPERTY(PROP_COLLISIONLESS, properties.getCollisionless()); + APPEND_ENTITY_PROPERTY(PROP_COLLISION_MASK, properties.getCollisionMask()); + APPEND_ENTITY_PROPERTY(PROP_DYNAMIC, properties.getDynamic()); + APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, properties.getCollisionSoundURL()); + APPEND_ENTITY_PROPERTY(PROP_ACTION_DATA, properties.getActionData()); - if (properties.getType() == EntityTypes::Text) { - APPEND_ENTITY_PROPERTY(PROP_TEXT, properties.getText()); - APPEND_ENTITY_PROPERTY(PROP_LINE_HEIGHT, properties.getLineHeight()); - APPEND_ENTITY_PROPERTY(PROP_TEXT_COLOR, properties.getTextColor()); - APPEND_ENTITY_PROPERTY(PROP_BACKGROUND_COLOR, properties.getBackgroundColor()); - APPEND_ENTITY_PROPERTY(PROP_FACE_CAMERA, properties.getFaceCamera()); - } + // Cloning + APPEND_ENTITY_PROPERTY(PROP_CLONEABLE, properties.getCloneable()); + APPEND_ENTITY_PROPERTY(PROP_CLONE_LIFETIME, properties.getCloneLifetime()); + APPEND_ENTITY_PROPERTY(PROP_CLONE_LIMIT, properties.getCloneLimit()); + APPEND_ENTITY_PROPERTY(PROP_CLONE_DYNAMIC, properties.getCloneDynamic()); + APPEND_ENTITY_PROPERTY(PROP_CLONE_AVATAR_ENTITY, properties.getCloneAvatarEntity()); + APPEND_ENTITY_PROPERTY(PROP_CLONE_ORIGIN_ID, properties.getCloneOriginID()); - if (properties.getType() == EntityTypes::Model) { - APPEND_ENTITY_PROPERTY(PROP_MODEL_URL, properties.getModelURL()); - APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, properties.getCompoundShapeURL()); - APPEND_ENTITY_PROPERTY(PROP_TEXTURES, properties.getTextures()); - APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)(properties.getShapeType())); - APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor()); + // Scripts + APPEND_ENTITY_PROPERTY(PROP_SCRIPT, properties.getScript()); + APPEND_ENTITY_PROPERTY(PROP_SCRIPT_TIMESTAMP, properties.getScriptTimestamp()); + APPEND_ENTITY_PROPERTY(PROP_SERVER_SCRIPTS, properties.getServerScripts()); - _staticAnimation.setProperties(properties); - _staticAnimation.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); - - APPEND_ENTITY_PROPERTY(PROP_JOINT_ROTATIONS_SET, properties.getJointRotationsSet()); - APPEND_ENTITY_PROPERTY(PROP_JOINT_ROTATIONS, properties.getJointRotations()); - APPEND_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS_SET, properties.getJointTranslationsSet()); - APPEND_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS, properties.getJointTranslations()); - APPEND_ENTITY_PROPERTY(PROP_RELAY_PARENT_JOINTS, properties.getRelayParentJoints()); - } - - if (properties.getType() == EntityTypes::Light) { - APPEND_ENTITY_PROPERTY(PROP_IS_SPOTLIGHT, properties.getIsSpotlight()); - APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor()); - APPEND_ENTITY_PROPERTY(PROP_INTENSITY, properties.getIntensity()); - APPEND_ENTITY_PROPERTY(PROP_FALLOFF_RADIUS, properties.getFalloffRadius()); - APPEND_ENTITY_PROPERTY(PROP_EXPONENT, properties.getExponent()); - APPEND_ENTITY_PROPERTY(PROP_CUTOFF, properties.getCutoff()); - } + // Certifiable Properties + APPEND_ENTITY_PROPERTY(PROP_ITEM_NAME, properties.getItemName()); + APPEND_ENTITY_PROPERTY(PROP_ITEM_DESCRIPTION, properties.getItemDescription()); + APPEND_ENTITY_PROPERTY(PROP_ITEM_CATEGORIES, properties.getItemCategories()); + APPEND_ENTITY_PROPERTY(PROP_ITEM_ARTIST, properties.getItemArtist()); + APPEND_ENTITY_PROPERTY(PROP_ITEM_LICENSE, properties.getItemLicense()); + APPEND_ENTITY_PROPERTY(PROP_LIMITED_RUN, properties.getLimitedRun()); + APPEND_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, properties.getMarketplaceID()); + APPEND_ENTITY_PROPERTY(PROP_EDITION_NUMBER, properties.getEditionNumber()); + APPEND_ENTITY_PROPERTY(PROP_ENTITY_INSTANCE_NUMBER, properties.getEntityInstanceNumber()); + APPEND_ENTITY_PROPERTY(PROP_CERTIFICATE_ID, properties.getCertificateID()); + APPEND_ENTITY_PROPERTY(PROP_STATIC_CERTIFICATE_VERSION, properties.getStaticCertificateVersion()); if (properties.getType() == EntityTypes::ParticleEffect) { + APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)(properties.getShapeType())); + APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor()); + APPEND_ENTITY_PROPERTY(PROP_ALPHA, properties.getAlpha()); APPEND_ENTITY_PROPERTY(PROP_TEXTURES, properties.getTextures()); + APPEND_ENTITY_PROPERTY(PROP_MAX_PARTICLES, properties.getMaxParticles()); APPEND_ENTITY_PROPERTY(PROP_LIFESPAN, properties.getLifespan()); + APPEND_ENTITY_PROPERTY(PROP_EMITTING_PARTICLES, properties.getIsEmitting()); APPEND_ENTITY_PROPERTY(PROP_EMIT_RATE, properties.getEmitRate()); APPEND_ENTITY_PROPERTY(PROP_EMIT_SPEED, properties.getEmitSpeed()); @@ -2444,24 +2830,30 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_EMIT_ORIENTATION, properties.getEmitOrientation()); APPEND_ENTITY_PROPERTY(PROP_EMIT_DIMENSIONS, properties.getEmitDimensions()); APPEND_ENTITY_PROPERTY(PROP_EMIT_RADIUS_START, properties.getEmitRadiusStart()); + APPEND_ENTITY_PROPERTY(PROP_POLAR_START, properties.getPolarStart()); APPEND_ENTITY_PROPERTY(PROP_POLAR_FINISH, properties.getPolarFinish()); APPEND_ENTITY_PROPERTY(PROP_AZIMUTH_START, properties.getAzimuthStart()); APPEND_ENTITY_PROPERTY(PROP_AZIMUTH_FINISH, properties.getAzimuthFinish()); + APPEND_ENTITY_PROPERTY(PROP_EMIT_ACCELERATION, properties.getEmitAcceleration()); APPEND_ENTITY_PROPERTY(PROP_ACCELERATION_SPREAD, properties.getAccelerationSpread()); + APPEND_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, properties.getParticleRadius()); APPEND_ENTITY_PROPERTY(PROP_RADIUS_SPREAD, properties.getRadiusSpread()); APPEND_ENTITY_PROPERTY(PROP_RADIUS_START, properties.getRadiusStart()); APPEND_ENTITY_PROPERTY(PROP_RADIUS_FINISH, properties.getRadiusFinish()); - APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor()); + APPEND_ENTITY_PROPERTY(PROP_COLOR_SPREAD, properties.getColorSpread()); APPEND_ENTITY_PROPERTY(PROP_COLOR_START, properties.getColorStart()); APPEND_ENTITY_PROPERTY(PROP_COLOR_FINISH, properties.getColorFinish()); + APPEND_ENTITY_PROPERTY(PROP_ALPHA_SPREAD, properties.getAlphaSpread()); APPEND_ENTITY_PROPERTY(PROP_ALPHA_START, properties.getAlphaStart()); APPEND_ENTITY_PROPERTY(PROP_ALPHA_FINISH, properties.getAlphaFinish()); + APPEND_ENTITY_PROPERTY(PROP_EMITTER_SHOULD_TRAIL, properties.getEmitterShouldTrail()); + APPEND_ENTITY_PROPERTY(PROP_PARTICLE_SPIN, properties.getParticleSpin()); APPEND_ENTITY_PROPERTY(PROP_SPIN_SPREAD, properties.getSpinSpread()); APPEND_ENTITY_PROPERTY(PROP_SPIN_START, properties.getSpinStart()); @@ -2469,34 +2861,74 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_PARTICLE_ROTATE_WITH_ENTITY, properties.getRotateWithEntity()) } + if (properties.getType() == EntityTypes::Model) { + APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)(properties.getShapeType())); + APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, properties.getCompoundShapeURL()); + APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor()); + APPEND_ENTITY_PROPERTY(PROP_TEXTURES, properties.getTextures()); + + APPEND_ENTITY_PROPERTY(PROP_MODEL_URL, properties.getModelURL()); + APPEND_ENTITY_PROPERTY(PROP_JOINT_ROTATIONS_SET, properties.getJointRotationsSet()); + APPEND_ENTITY_PROPERTY(PROP_JOINT_ROTATIONS, properties.getJointRotations()); + APPEND_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS_SET, properties.getJointTranslationsSet()); + APPEND_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS, properties.getJointTranslations()); + APPEND_ENTITY_PROPERTY(PROP_RELAY_PARENT_JOINTS, properties.getRelayParentJoints()); + + _staticAnimation.setProperties(properties); + _staticAnimation.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); + } + + if (properties.getType() == EntityTypes::Light) { + APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor()); + APPEND_ENTITY_PROPERTY(PROP_IS_SPOTLIGHT, properties.getIsSpotlight()); + APPEND_ENTITY_PROPERTY(PROP_INTENSITY, properties.getIntensity()); + APPEND_ENTITY_PROPERTY(PROP_EXPONENT, properties.getExponent()); + APPEND_ENTITY_PROPERTY(PROP_CUTOFF, properties.getCutoff()); + APPEND_ENTITY_PROPERTY(PROP_FALLOFF_RADIUS, properties.getFalloffRadius()); + } + + if (properties.getType() == EntityTypes::Text) { + APPEND_ENTITY_PROPERTY(PROP_TEXT, properties.getText()); + APPEND_ENTITY_PROPERTY(PROP_LINE_HEIGHT, properties.getLineHeight()); + APPEND_ENTITY_PROPERTY(PROP_TEXT_COLOR, properties.getTextColor()); + APPEND_ENTITY_PROPERTY(PROP_TEXT_ALPHA, properties.getTextAlpha()); + APPEND_ENTITY_PROPERTY(PROP_BACKGROUND_COLOR, properties.getBackgroundColor()); + APPEND_ENTITY_PROPERTY(PROP_BACKGROUND_ALPHA, properties.getBackgroundAlpha()); + APPEND_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, (uint32_t)properties.getBillboardMode()); + APPEND_ENTITY_PROPERTY(PROP_LEFT_MARGIN, properties.getLeftMargin()); + APPEND_ENTITY_PROPERTY(PROP_RIGHT_MARGIN, properties.getRightMargin()); + APPEND_ENTITY_PROPERTY(PROP_TOP_MARGIN, properties.getTopMargin()); + APPEND_ENTITY_PROPERTY(PROP_BOTTOM_MARGIN, properties.getBottomMargin()); + } + if (properties.getType() == EntityTypes::Zone) { + APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)properties.getShapeType()); + APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, properties.getCompoundShapeURL()); + _staticKeyLight.setProperties(properties); _staticKeyLight.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); _staticAmbientLight.setProperties(properties); _staticAmbientLight.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); - APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)properties.getShapeType()); - APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, properties.getCompoundShapeURL()); - _staticSkybox.setProperties(properties); _staticSkybox.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); + _staticHaze.setProperties(properties); + _staticHaze.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); + + _staticBloom.setProperties(properties); + _staticBloom.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); + APPEND_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, properties.getFlyingAllowed()); APPEND_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, properties.getGhostingAllowed()); APPEND_ENTITY_PROPERTY(PROP_FILTER_URL, properties.getFilterURL()); - APPEND_ENTITY_PROPERTY(PROP_HAZE_MODE, (uint32_t)properties.getHazeMode()); - _staticHaze.setProperties(properties); - _staticHaze.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); - - APPEND_ENTITY_PROPERTY(PROP_BLOOM_MODE, (uint32_t)properties.getBloomMode()); - _staticBloom.setProperties(properties); - _staticBloom.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); - APPEND_ENTITY_PROPERTY(PROP_KEY_LIGHT_MODE, (uint32_t)properties.getKeyLightMode()); APPEND_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_MODE, (uint32_t)properties.getAmbientLightMode()); APPEND_ENTITY_PROPERTY(PROP_SKYBOX_MODE, (uint32_t)properties.getSkyboxMode()); + APPEND_ENTITY_PROPERTY(PROP_HAZE_MODE, (uint32_t)properties.getHazeMode()); + APPEND_ENTITY_PROPERTY(PROP_BLOOM_MODE, (uint32_t)properties.getBloomMode()); } if (properties.getType() == EntityTypes::PolyVox) { @@ -2514,29 +2946,38 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_Z_P_NEIGHBOR_ID, properties.getZPNeighborID()); } + if (properties.getType() == EntityTypes::Web) { + APPEND_ENTITY_PROPERTY(PROP_SOURCE_URL, properties.getSourceUrl()); + APPEND_ENTITY_PROPERTY(PROP_DPI, properties.getDPI()); + } + if (properties.getType() == EntityTypes::Line) { APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor()); - APPEND_ENTITY_PROPERTY(PROP_LINE_WIDTH, properties.getLineWidth()); + APPEND_ENTITY_PROPERTY(PROP_LINE_POINTS, properties.getLinePoints()); } if (properties.getType() == EntityTypes::PolyLine) { APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor()); - APPEND_ENTITY_PROPERTY(PROP_LINE_WIDTH, properties.getLineWidth()); - APPEND_ENTITY_PROPERTY(PROP_LINE_POINTS, properties.getLinePoints()); - APPEND_ENTITY_PROPERTY(PROP_NORMALS, properties.getPackedNormals()); - APPEND_ENTITY_PROPERTY(PROP_STROKE_COLORS, properties.getPackedStrokeColors()); - APPEND_ENTITY_PROPERTY(PROP_STROKE_WIDTHS, properties.getStrokeWidths()); APPEND_ENTITY_PROPERTY(PROP_TEXTURES, properties.getTextures()); + + APPEND_ENTITY_PROPERTY(PROP_LINE_POINTS, properties.getLinePoints()); + APPEND_ENTITY_PROPERTY(PROP_STROKE_WIDTHS, properties.getStrokeWidths()); + APPEND_ENTITY_PROPERTY(PROP_STROKE_NORMALS, properties.getPackedNormals()); + APPEND_ENTITY_PROPERTY(PROP_STROKE_COLORS, properties.getPackedStrokeColors()); APPEND_ENTITY_PROPERTY(PROP_IS_UV_MODE_STRETCH, properties.getIsUVModeStretch()); + APPEND_ENTITY_PROPERTY(PROP_LINE_GLOW, properties.getGlow()); + APPEND_ENTITY_PROPERTY(PROP_LINE_FACE_CAMERA, properties.getFaceCamera()); } + // NOTE: Spheres and Boxes are just special cases of Shape, and they need to include their PROP_SHAPE // when encoding/decoding edits because otherwise they can't polymorph to other shape types if (properties.getType() == EntityTypes::Shape || properties.getType() == EntityTypes::Box || properties.getType() == EntityTypes::Sphere) { - APPEND_ENTITY_PROPERTY(PROP_SHAPE, properties.getShape()); APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor()); + APPEND_ENTITY_PROPERTY(PROP_ALPHA, properties.getAlpha()); + APPEND_ENTITY_PROPERTY(PROP_SHAPE, properties.getShape()); } // Materials @@ -2552,33 +2993,27 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_MATERIAL_REPEAT, properties.getMaterialRepeat()); } - APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName()); - APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, properties.getCollisionSoundURL()); - APPEND_ENTITY_PROPERTY(PROP_ACTION_DATA, properties.getActionData()); - APPEND_ENTITY_PROPERTY(PROP_ALPHA, properties.getAlpha()); + // Image + if (properties.getType() == EntityTypes::Image) { + APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor()); + APPEND_ENTITY_PROPERTY(PROP_ALPHA, properties.getAlpha()); - // Certifiable Properties - APPEND_ENTITY_PROPERTY(PROP_ITEM_NAME, properties.getItemName()); - APPEND_ENTITY_PROPERTY(PROP_ITEM_DESCRIPTION, properties.getItemDescription()); - APPEND_ENTITY_PROPERTY(PROP_ITEM_CATEGORIES, properties.getItemCategories()); - APPEND_ENTITY_PROPERTY(PROP_ITEM_ARTIST, properties.getItemArtist()); - APPEND_ENTITY_PROPERTY(PROP_ITEM_LICENSE, properties.getItemLicense()); - APPEND_ENTITY_PROPERTY(PROP_LIMITED_RUN, properties.getLimitedRun()); - APPEND_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, properties.getMarketplaceID()); - APPEND_ENTITY_PROPERTY(PROP_EDITION_NUMBER, properties.getEditionNumber()); - APPEND_ENTITY_PROPERTY(PROP_ENTITY_INSTANCE_NUMBER, properties.getEntityInstanceNumber()); - APPEND_ENTITY_PROPERTY(PROP_CERTIFICATE_ID, properties.getCertificateID()); - APPEND_ENTITY_PROPERTY(PROP_STATIC_CERTIFICATE_VERSION, properties.getStaticCertificateVersion()); + APPEND_ENTITY_PROPERTY(PROP_IMAGE_URL, properties.getImageURL()); + APPEND_ENTITY_PROPERTY(PROP_EMISSIVE, properties.getEmissive()); + APPEND_ENTITY_PROPERTY(PROP_KEEP_ASPECT_RATIO, properties.getKeepAspectRatio()); + APPEND_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, (uint32_t)properties.getBillboardMode()); + APPEND_ENTITY_PROPERTY(PROP_SUB_IMAGE, properties.getSubImage()); + } - APPEND_ENTITY_PROPERTY(PROP_CLONEABLE, properties.getCloneable()); - APPEND_ENTITY_PROPERTY(PROP_CLONE_LIFETIME, properties.getCloneLifetime()); - APPEND_ENTITY_PROPERTY(PROP_CLONE_LIMIT, properties.getCloneLimit()); - APPEND_ENTITY_PROPERTY(PROP_CLONE_DYNAMIC, properties.getCloneDynamic()); - APPEND_ENTITY_PROPERTY(PROP_CLONE_AVATAR_ENTITY, properties.getCloneAvatarEntity()); + // Grid + if (properties.getType() == EntityTypes::Grid) { + APPEND_ENTITY_PROPERTY(PROP_COLOR, properties.getColor()); + APPEND_ENTITY_PROPERTY(PROP_ALPHA, properties.getAlpha()); - _staticGrab.setProperties(properties); - _staticGrab.appendToEditPacket(packetData, requestedProperties, propertyFlags, - propertiesDidntFit, propertyCount, appendState); + APPEND_ENTITY_PROPERTY(PROP_GRID_FOLLOW_CAMERA, properties.getFollowCamera()); + APPEND_ENTITY_PROPERTY(PROP_MAJOR_GRID_EVERY, properties.getMajorGridEvery()); + APPEND_ENTITY_PROPERTY(PROP_MINOR_GRID_EVERY, properties.getMinorGridEvery()); + } } if (propertyCount > 0) { @@ -2586,8 +3021,8 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy encodedPropertyFlags = propertyFlags; int newPropertyFlagsLength = encodedPropertyFlags.length(); - packetData->updatePriorBytes(propertyFlagsOffset, - (const unsigned char*)encodedPropertyFlags.constData(), encodedPropertyFlags.length()); + packetData->updatePriorBytes(propertyFlagsOffset, (const unsigned char*)encodedPropertyFlags.constData(), + encodedPropertyFlags.length()); // if the size of the PropertyFlags shrunk, we need to shift everything down to front of packet. if (newPropertyFlagsLength < oldPropertyFlagsLength) { @@ -2715,10 +3150,6 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int processedBytes += sizeof(lastEdited); properties.setLastEdited(lastEdited); - // NOTE: We intentionally do not send "created" times in edit messages. This is because: - // 1) if the edit is to an existing entity, the created time can not be changed - // 2) if the edit is to a new entity, the created time is the last edited time - // encoded id QUuid editID = QUuid::fromRfc4122(QByteArray::fromRawData(reinterpret_cast<const char*>(dataAt), NUM_BYTES_RFC4122_UUID)); dataAt += NUM_BYTES_RFC4122_UUID; @@ -2758,78 +3189,79 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int processedBytes += propertyFlags.getEncodedLength(); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SIMULATION_OWNER, QByteArray, setSimulationOwner); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_POSITION, vec3, setPosition); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DIMENSIONS, vec3, setDimensions); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ROTATION, quat, setRotation); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DENSITY, float, setDensity); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VELOCITY, vec3, setVelocity); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_GRAVITY, vec3, setGravity); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ACCELERATION, vec3, setAcceleration); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DAMPING, float, setDamping); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_RESTITUTION, float, setRestitution); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FRICTION, float, setFriction); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LIFETIME, float, setLifetime); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SCRIPT, QString, setScript); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SCRIPT_TIMESTAMP, quint64, setScriptTimestamp); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SERVER_SCRIPTS, QString, setServerScripts); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_REGISTRATION_POINT, vec3, setRegistrationPoint); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANGULAR_VELOCITY, vec3, setAngularVelocity); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANGULAR_DAMPING, float, setAngularDamping); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VISIBLE, bool, setVisible); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CAN_CAST_SHADOW, bool, setCanCastShadow); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLLISIONLESS, bool, setCollisionless); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLLISION_MASK, uint16_t, setCollisionMask); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DYNAMIC, bool, setDynamic); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_NAME, QString, setName); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LOCKED, bool, setLocked); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_USER_DATA, QString, setUserData); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_HREF, QString, setHref); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DESCRIPTION, QString, setDescription); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_POSITION, vec3, setPosition); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DIMENSIONS, vec3, setDimensions); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ROTATION, quat, setRotation); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_REGISTRATION_POINT, vec3, setRegistrationPoint); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CREATED, quint64, setCreated); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LAST_EDITED_BY, QUuid, setLastEditedBy); + // READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ENTITY_HOST_TYPE, entity::HostType, setEntityHostType); // not sent over the wire + // READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_OWNING_AVATAR_ID, QUuid, setOwningAvatarID); // not sent over the wire READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARENT_ID, QUuid, setParentID); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARENT_JOINT_INDEX, quint16, setParentJointIndex); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_QUERY_AA_CUBE, AACube, setQueryAACube); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CAN_CAST_SHADOW, bool, setCanCastShadow); + // READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VISIBLE_IN_SECONDARY_CAMERA, bool, setIsVisibleInSecondaryCamera); // not sent over the wire + properties.getGrab().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); - if (properties.getType() == EntityTypes::Web) { - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SOURCE_URL, QString, setSourceUrl); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DPI, uint16_t, setDPI); - } + // Physics + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DENSITY, float, setDensity); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VELOCITY, vec3, setVelocity); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANGULAR_VELOCITY, vec3, setAngularVelocity); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_GRAVITY, vec3, setGravity); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ACCELERATION, vec3, setAcceleration); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DAMPING, float, setDamping); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANGULAR_DAMPING, float, setAngularDamping); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_RESTITUTION, float, setRestitution); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FRICTION, float, setFriction); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LIFETIME, float, setLifetime); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLLISIONLESS, bool, setCollisionless); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLLISION_MASK, uint16_t, setCollisionMask); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DYNAMIC, bool, setDynamic); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLLISION_SOUND_URL, QString, setCollisionSoundURL); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ACTION_DATA, QByteArray, setActionData); - if (properties.getType() == EntityTypes::Text) { - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXT, QString, setText); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LINE_HEIGHT, float, setLineHeight); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXT_COLOR, u8vec3Color, setTextColor); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BACKGROUND_COLOR, u8vec3Color, setBackgroundColor); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FACE_CAMERA, bool, setFaceCamera); - } + // Cloning + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CLONEABLE, bool, setCloneable); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CLONE_LIFETIME, float, setCloneLifetime); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CLONE_LIMIT, float, setCloneLimit); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CLONE_DYNAMIC, bool, setCloneDynamic); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CLONE_AVATAR_ENTITY, bool, setCloneAvatarEntity); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CLONE_ORIGIN_ID, QUuid, setCloneOriginID); - if (properties.getType() == EntityTypes::Model) { - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MODEL_URL, QString, setModelURL); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXTURES, QString, setTextures); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE_TYPE, ShapeType, setShapeType); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor); + // Scripts + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SCRIPT, QString, setScript); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SCRIPT_TIMESTAMP, quint64, setScriptTimestamp); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SERVER_SCRIPTS, QString, setServerScripts); - properties.getAnimation().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); - - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_JOINT_ROTATIONS_SET, QVector<bool>, setJointRotationsSet); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_JOINT_ROTATIONS, QVector<quat>, setJointRotations); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_JOINT_TRANSLATIONS_SET, QVector<bool>, setJointTranslationsSet); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_JOINT_TRANSLATIONS, QVector<vec3>, setJointTranslations); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_RELAY_PARENT_JOINTS, bool, setRelayParentJoints); - } - - if (properties.getType() == EntityTypes::Light) { - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_IS_SPOTLIGHT, bool, setIsSpotlight); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_INTENSITY, float, setIntensity); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FALLOFF_RADIUS, float, setFalloffRadius); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EXPONENT, float, setExponent); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CUTOFF, float, setCutoff); - } + // Certifiable Properties + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ITEM_NAME, QString, setItemName); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ITEM_DESCRIPTION, QString, setItemDescription); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ITEM_CATEGORIES, QString, setItemCategories); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ITEM_ARTIST, QString, setItemArtist); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ITEM_LICENSE, QString, setItemLicense); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LIMITED_RUN, quint32, setLimitedRun); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MARKETPLACE_ID, QString, setMarketplaceID); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EDITION_NUMBER, quint32, setEditionNumber); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ENTITY_INSTANCE_NUMBER, quint32, setEntityInstanceNumber); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CERTIFICATE_ID, QString, setCertificateID); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_STATIC_CERTIFICATE_VERSION, quint32, setStaticCertificateVersion); if (properties.getType() == EntityTypes::ParticleEffect) { + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE_TYPE, ShapeType, setShapeType); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA, float, setAlpha); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXTURES, QString, setTextures); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MAX_PARTICLES, quint32, setMaxParticles); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LIFESPAN, float, setLifespan); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMITTING_PARTICLES, bool, setIsEmitting); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMIT_RATE, float, setEmitRate); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMIT_SPEED, float, setEmitSpeed); @@ -2837,24 +3269,30 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMIT_ORIENTATION, quat, setEmitOrientation); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMIT_DIMENSIONS, vec3, setEmitDimensions); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMIT_RADIUS_START, float, setEmitRadiusStart); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_POLAR_START, float, setPolarStart); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_POLAR_FINISH, float, setPolarFinish); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_AZIMUTH_START, float, setAzimuthStart); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_AZIMUTH_FINISH, float, setAzimuthFinish); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMIT_ACCELERATION, vec3, setEmitAcceleration); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ACCELERATION_SPREAD, vec3, setAccelerationSpread); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARTICLE_RADIUS, float, setParticleRadius); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_RADIUS_SPREAD, float, setRadiusSpread); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_RADIUS_START, float, setRadiusStart); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_RADIUS_FINISH, float, setRadiusFinish); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR_SPREAD, u8vec3Color, setColorSpread); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR_START, vec3Color, setColorStart); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR_FINISH, vec3Color, setColorFinish); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA_SPREAD, float, setAlphaSpread); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA_START, float, setAlphaStart); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA_FINISH, float, setAlphaFinish); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMITTER_SHOULD_TRAIL, bool, setEmitterShouldTrail); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARTICLE_SPIN, float, setParticleSpin); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SPIN_SPREAD, float, setSpinSpread); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SPIN_START, float, setSpinStart); @@ -2862,27 +3300,65 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_PARTICLE_ROTATE_WITH_ENTITY, bool, setRotateWithEntity); } - if (properties.getType() == EntityTypes::Zone) { - properties.getKeyLight().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); - properties.getAmbientLight().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); - + if (properties.getType() == EntityTypes::Model) { READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE_TYPE, ShapeType, setShapeType); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL); - properties.getSkybox().decodeFromEditPacket(propertyFlags, dataAt , processedBytes); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXTURES, QString, setTextures); + + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MODEL_URL, QString, setModelURL); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_JOINT_ROTATIONS_SET, QVector<bool>, setJointRotationsSet); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_JOINT_ROTATIONS, QVector<quat>, setJointRotations); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_JOINT_TRANSLATIONS_SET, QVector<bool>, setJointTranslationsSet); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_JOINT_TRANSLATIONS, QVector<vec3>, setJointTranslations); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_RELAY_PARENT_JOINTS, bool, setRelayParentJoints); + + properties.getAnimation().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); + } + + if (properties.getType() == EntityTypes::Light) { + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor); + + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_IS_SPOTLIGHT, bool, setIsSpotlight); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_INTENSITY, float, setIntensity); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EXPONENT, float, setExponent); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CUTOFF, float, setCutoff); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FALLOFF_RADIUS, float, setFalloffRadius); + } + + if (properties.getType() == EntityTypes::Text) { + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXT, QString, setText); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LINE_HEIGHT, float, setLineHeight); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXT_COLOR, u8vec3Color, setTextColor); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXT_ALPHA, float, setTextAlpha); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BACKGROUND_COLOR, u8vec3Color, setBackgroundColor); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BACKGROUND_ALPHA, float, setBackgroundAlpha); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BILLBOARD_MODE, BillboardMode, setBillboardMode); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LEFT_MARGIN, float, setLeftMargin); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_RIGHT_MARGIN, float, setRightMargin); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TOP_MARGIN, float, setTopMargin); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BOTTOM_MARGIN, float, setBottomMargin); + } + + if (properties.getType() == EntityTypes::Zone) { + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE_TYPE, ShapeType, setShapeType); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL); + + properties.getKeyLight().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); + properties.getAmbientLight().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); + properties.getSkybox().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); + properties.getHaze().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); + properties.getBloom().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FLYING_ALLOWED, bool, setFlyingAllowed); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_GHOSTING_ALLOWED, bool, setGhostingAllowed); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FILTER_URL, QString, setFilterURL); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_HAZE_MODE, uint32_t, setHazeMode); - properties.getHaze().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); - - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BLOOM_MODE, uint32_t, setBloomMode); - properties.getBloom().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_KEY_LIGHT_MODE, uint32_t, setKeyLightMode); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_AMBIENT_LIGHT_MODE, uint32_t, setAmbientLightMode); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SKYBOX_MODE, uint32_t, setSkyboxMode); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_HAZE_MODE, uint32_t, setHazeMode); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BLOOM_MODE, uint32_t, setBloomMode); } if (properties.getType() == EntityTypes::PolyVox) { @@ -2900,22 +3376,28 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_Z_P_NEIGHBOR_ID, EntityItemID, setZPNeighborID); } + if (properties.getType() == EntityTypes::Web) { + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SOURCE_URL, QString, setSourceUrl); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DPI, uint16_t, setDPI); + } + if (properties.getType() == EntityTypes::Line) { READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LINE_WIDTH, float, setLineWidth); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LINE_POINTS, QVector<vec3>, setLinePoints); } - if (properties.getType() == EntityTypes::PolyLine) { READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LINE_WIDTH, float, setLineWidth); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LINE_POINTS, QVector<vec3>, setLinePoints); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_NORMALS, QByteArray, setPackedNormals); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_STROKE_COLORS, QByteArray, setPackedStrokeColors); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_STROKE_WIDTHS, QVector<float>, setStrokeWidths); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_TEXTURES, QString, setTextures); + + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LINE_POINTS, QVector<vec3>, setLinePoints); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_STROKE_WIDTHS, QVector<float>, setStrokeWidths); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_STROKE_NORMALS, QByteArray, setPackedNormals); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_STROKE_COLORS, QByteArray, setPackedStrokeColors); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_IS_UV_MODE_STRETCH, bool, setIsUVModeStretch); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LINE_GLOW, bool, setGlow); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LINE_FACE_CAMERA, bool, setFaceCamera); } // NOTE: Spheres and Boxes are just special cases of Shape, and they need to include their PROP_SHAPE @@ -2923,8 +3405,9 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int if (properties.getType() == EntityTypes::Shape || properties.getType() == EntityTypes::Box || properties.getType() == EntityTypes::Sphere) { - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE, QString, setShape); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA, float, setAlpha); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHAPE, QString, setShape); } // Materials @@ -2940,31 +3423,27 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MATERIAL_REPEAT, bool, setMaterialRepeat); } - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_NAME, QString, setName); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLLISION_SOUND_URL, QString, setCollisionSoundURL); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ACTION_DATA, QByteArray, setActionData); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA, float, setAlpha); + // Image + if (properties.getType() == EntityTypes::Image) { + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA, float, setAlpha); - // Certifiable Properties - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ITEM_NAME, QString, setItemName); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ITEM_DESCRIPTION, QString, setItemDescription); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ITEM_CATEGORIES, QString, setItemCategories); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ITEM_ARTIST, QString, setItemArtist); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ITEM_LICENSE, QString, setItemLicense); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LIMITED_RUN, quint32, setLimitedRun); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MARKETPLACE_ID, QString, setMarketplaceID); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EDITION_NUMBER, quint32, setEditionNumber); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ENTITY_INSTANCE_NUMBER, quint32, setEntityInstanceNumber); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CERTIFICATE_ID, QString, setCertificateID); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_STATIC_CERTIFICATE_VERSION, quint32, setStaticCertificateVersion); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_IMAGE_URL, QString, setImageURL); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_EMISSIVE, bool, setEmissive); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_KEEP_ASPECT_RATIO, bool, setKeepAspectRatio); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BILLBOARD_MODE, BillboardMode, setBillboardMode); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SUB_IMAGE, QRect, setSubImage); + } - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CLONEABLE, bool, setCloneable); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CLONE_LIFETIME, float, setCloneLifetime); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CLONE_LIMIT, float, setCloneLimit); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CLONE_DYNAMIC, bool, setCloneDynamic); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_CLONE_AVATAR_ENTITY, bool, setCloneAvatarEntity); + // Grid + if (properties.getType() == EntityTypes::Grid) { + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLOR, u8vec3Color, setColor); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ALPHA, float, setAlpha); - properties.getGrab().decodeFromEditPacket(propertyFlags, dataAt, processedBytes); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_GRID_FOLLOW_CAMERA, bool, setFollowCamera); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MAJOR_GRID_EVERY, uint32_t, setMajorGridEvery); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MINOR_GRID_EVERY, float, setMinorGridEvery); + } return valid; } @@ -2986,7 +3465,7 @@ QVector<vec3> EntityItemProperties::unpackNormals(const QByteArray& normals) { j++; } } else { - qCDebug(entities) << "WARNING - Expected received size for normals does not match. Expected: " << (int)normals[0] + qCDebug(entities) << "WARNING - Expected received size for normals does not match. Expected: " << (int)normals[0] << " Received: " << (normals.size() / 6); } return unpackedNormals; @@ -2999,7 +3478,7 @@ void EntityItemProperties::setPackedStrokeColors(const QByteArray& value) { QVector<vec3> EntityItemProperties::unpackStrokeColors(const QByteArray& strokeColors) { // the size of the vector is packed first QVector<vec3> unpackedStrokeColors = QVector<vec3>((int)strokeColors[0]); - + if ((int)strokeColors[0] == strokeColors.size() / 3) { int j = 0; for (int i = 1; i < strokeColors.size();) { @@ -3010,7 +3489,7 @@ QVector<vec3> EntityItemProperties::unpackStrokeColors(const QByteArray& strokeC unpackedStrokeColors[j++] = vec3(r, g, b); } } else { - qCDebug(entities) << "WARNING - Expected received size for stroke colors does not match. Expected: " + qCDebug(entities) << "WARNING - Expected received size for stroke colors does not match. Expected: " << (int)strokeColors[0] << " Received: " << (strokeColors.size() / 3); } @@ -3027,7 +3506,7 @@ bool EntityItemProperties::encodeEraseEntityMessage(const EntityItemID& entityIt int outputLength = 0; - if (buffer.size() < (int) (sizeof(numberOfIds) + NUM_BYTES_RFC4122_UUID)) { + if (buffer.size() < (int)(sizeof(numberOfIds) + NUM_BYTES_RFC4122_UUID)) { qCDebug(entities) << "ERROR - encodeEraseEntityMessage() called with buffer that is too small!"; return false; } @@ -3090,94 +3569,58 @@ bool EntityItemProperties::decodeCloneEntityMessage(const QByteArray& buffer, in } void EntityItemProperties::markAllChanged() { - _lastEditedByChanged = true; + // Core _simulationOwnerChanged = true; + _visibleChanged = true; + _nameChanged = true; + _lockedChanged = true; + _userDataChanged = true; + _hrefChanged = true; + _descriptionChanged = true; _positionChanged = true; _dimensionsChanged = true; _rotationChanged = true; + _registrationPointChanged = true; + _createdChanged = true; + _lastEditedByChanged = true; + _entityHostTypeChanged = true; + _owningAvatarIDChanged = true; + _parentIDChanged = true; + _parentJointIndexChanged = true; + _queryAACubeChanged = true; + _canCastShadowChanged = true; + _isVisibleInSecondaryCameraChanged = true; + _grab.markAllChanged(); + + // Physics _densityChanged = true; _velocityChanged = true; + _angularVelocityChanged = true; _gravityChanged = true; _accelerationChanged = true; _dampingChanged = true; + _angularDampingChanged = true; _restitutionChanged = true; _frictionChanged = true; _lifetimeChanged = true; - _userDataChanged = true; - _scriptChanged = true; - _scriptTimestampChanged = true; - _serverScriptsChanged = true; - _collisionSoundURLChanged = true; - _registrationPointChanged = true; - _angularVelocityChanged = true; - _angularDampingChanged = true; - _nameChanged = true; - _visibleChanged = true; - _canCastShadowChanged = true; - _colorChanged = true; - _alphaChanged = true; - _modelURLChanged = true; - _compoundShapeURLChanged = true; - _localRenderAlphaChanged = true; - _isSpotlightChanged = true; _collisionlessChanged = true; _collisionMaskChanged = true; _dynamicChanged = true; + _collisionSoundURLChanged = true; + _actionDataChanged = true; - _intensityChanged = true; - _falloffRadiusChanged = true; - _exponentChanged = true; - _cutoffChanged = true; - _lockedChanged = true; - _texturesChanged = true; + // Cloning + _cloneableChanged = true; + _cloneLifetimeChanged = true; + _cloneLimitChanged = true; + _cloneDynamicChanged = true; + _cloneAvatarEntityChanged = true; + _cloneOriginIDChanged = true; - _textChanged = true; - _lineHeightChanged = true; - _textColorChanged = true; - _backgroundColorChanged = true; - _shapeTypeChanged = true; - - _isEmittingChanged = true; - _emitterShouldTrailChanged = true; - _maxParticlesChanged = true; - _lifespanChanged = true; - _emitRateChanged = true; - _emitSpeedChanged = true; - _speedSpreadChanged = true; - _emitOrientationChanged = true; - _emitDimensionsChanged = true; - _emitRadiusStartChanged = true; - _polarStartChanged = true; - _polarFinishChanged = true; - _azimuthStartChanged = true; - _azimuthFinishChanged = true; - _emitAccelerationChanged = true; - _accelerationSpreadChanged = true; - _particleRadiusChanged = true; - _radiusSpreadChanged = true; - _colorSpreadChanged = true; - _alphaSpreadChanged = true; - _radiusStartChanged = true; - _radiusFinishChanged = true; - _colorStartChanged = true; - _colorFinishChanged = true; - _alphaStartChanged = true; - _alphaFinishChanged = true; - _particleSpinChanged = true; - _spinStartChanged = true; - _spinFinishChanged = true; - _spinSpreadChanged = true; - _rotateWithEntityChanged = true; - - _materialURLChanged = true; - _materialMappingModeChanged = true; - _priorityChanged = true; - _parentMaterialNameChanged = true; - _materialMappingPosChanged = true; - _materialMappingScaleChanged = true; - _materialMappingRotChanged = true; - _materialDataChanged = true; - _materialRepeatChanged = true; + // Scripts + _scriptChanged = true; + _scriptTimestampChanged = true; + _serverScriptsChanged = true; // Certifiable Properties _itemNameChanged = true; @@ -3192,82 +3635,141 @@ void EntityItemProperties::markAllChanged() { _certificateIDChanged = true; _staticCertificateVersionChanged = true; - _keyLight.markAllChanged(); - _ambientLight.markAllChanged(); - _skybox.markAllChanged(); + // Common + _shapeTypeChanged = true; + _colorChanged = true; + _alphaChanged = true; + _texturesChanged = true; + _compoundShapeURLChanged = true; - _keyLightModeChanged = true; - _skyboxModeChanged = true; - _ambientLightModeChanged = true; - _hazeModeChanged = true; - _bloomModeChanged = true; - - _animation.markAllChanged(); - _skybox.markAllChanged(); - _haze.markAllChanged(); - _bloom.markAllChanged(); - _grab.markAllChanged(); - - _sourceUrlChanged = true; - _voxelVolumeSizeChanged = true; - _voxelDataChanged = true; - _voxelSurfaceStyleChanged = true; - - _lineWidthChanged = true; - _linePointsChanged = true; - - _hrefChanged = true; - _descriptionChanged = true; - _faceCameraChanged = true; - _actionDataChanged = true; - - _normalsChanged = true; - _strokeColorsChanged = true; - _strokeWidthsChanged = true; - _isUVModeStretchChanged = true; - - _xTextureURLChanged = true; - _yTextureURLChanged = true; - _zTextureURLChanged = true; - - _xNNeighborIDChanged = true; - _yNNeighborIDChanged = true; - _zNNeighborIDChanged = true; - - _xPNeighborIDChanged = true; - _yPNeighborIDChanged = true; - _zPNeighborIDChanged = true; - - _parentIDChanged = true; - _parentJointIndexChanged = true; + // Particles + _maxParticlesChanged = true; + _lifespanChanged = true; + _isEmittingChanged = true; + _emitRateChanged = true; + _emitSpeedChanged = true; + _speedSpreadChanged = true; + _emitOrientationChanged = true; + _emitDimensionsChanged = true; + _emitRadiusStartChanged = true; + _polarStartChanged = true; + _polarFinishChanged = true; + _azimuthStartChanged = true; + _azimuthFinishChanged = true; + _emitAccelerationChanged = true; + _accelerationSpreadChanged = true; + _particleRadiusChanged = true; + _radiusSpreadChanged = true; + _radiusStartChanged = true; + _radiusFinishChanged = true; + _colorSpreadChanged = true; + _colorStartChanged = true; + _colorFinishChanged = true; + _alphaSpreadChanged = true; + _alphaStartChanged = true; + _alphaFinishChanged = true; + _emitterShouldTrailChanged = true; + _particleSpinChanged = true; + _spinStartChanged = true; + _spinFinishChanged = true; + _spinSpreadChanged = true; + _rotateWithEntityChanged = true; + // Model + _modelURLChanged = true; _jointRotationsSetChanged = true; _jointRotationsChanged = true; _jointTranslationsSetChanged = true; _jointTranslationsChanged = true; + _relayParentJointsChanged = true; + _animation.markAllChanged(); - _queryAACubeChanged = true; + // Light + _isSpotlightChanged = true; + _intensityChanged = true; + _exponentChanged = true; + _cutoffChanged = true; + _falloffRadiusChanged = true; - _shapeChanged = true; + // Text + _textChanged = true; + _lineHeightChanged = true; + _textColorChanged = true; + _textAlphaChanged = true; + _backgroundColorChanged = true; + _backgroundAlphaChanged = true; + _billboardModeChanged = true; + _leftMarginChanged = true; + _rightMarginChanged = true; + _topMarginChanged = true; + _bottomMarginChanged = true; + // Zone + _keyLight.markAllChanged(); + _ambientLight.markAllChanged(); + _skybox.markAllChanged(); + _haze.markAllChanged(); + _bloom.markAllChanged(); _flyingAllowedChanged = true; _ghostingAllowedChanged = true; _filterURLChanged = true; + _keyLightModeChanged = true; + _ambientLightModeChanged = true; + _skyboxModeChanged = true; + _hazeModeChanged = true; + _bloomModeChanged = true; - _entityHostTypeChanged = true; - _owningAvatarIDChanged = true; + // Polyvox + _voxelVolumeSizeChanged = true; + _voxelDataChanged = true; + _voxelSurfaceStyleChanged = true; + _xTextureURLChanged = true; + _yTextureURLChanged = true; + _zTextureURLChanged = true; + _xNNeighborIDChanged = true; + _yNNeighborIDChanged = true; + _zNNeighborIDChanged = true; + _xPNeighborIDChanged = true; + _yPNeighborIDChanged = true; + _zPNeighborIDChanged = true; + // Web + _sourceUrlChanged = true; _dpiChanged = true; - _relayParentJointsChanged = true; - _cloneableChanged = true; - _cloneLifetimeChanged = true; - _cloneLimitChanged = true; - _cloneDynamicChanged = true; - _cloneAvatarEntityChanged = true; - _cloneOriginIDChanged = true; + // Polyline + _linePointsChanged = true; + _strokeWidthsChanged = true; + _normalsChanged = true; + _strokeColorsChanged = true; + _isUVModeStretchChanged = true; + _glowChanged = true; + _faceCameraChanged = true; - _isVisibleInSecondaryCameraChanged = true; + // Shape + _shapeChanged = true; + + // Material + _materialURLChanged = true; + _materialMappingModeChanged = true; + _priorityChanged = true; + _parentMaterialNameChanged = true; + _materialMappingPosChanged = true; + _materialMappingScaleChanged = true; + _materialMappingRotChanged = true; + _materialDataChanged = true; + _materialRepeatChanged = true; + + // Image + _imageURLChanged = true; + _emissiveChanged = true; + _keepAspectRatioChanged = true; + _subImageChanged = true; + + // Grid + _followCameraChanged = true; + _majorGridEveryChanged = true; + _minorGridEveryChanged = true; } // The minimum bounding box for the entity. @@ -3276,7 +3778,7 @@ AABox EntityItemProperties::getAABox() const { // _position represents the position of the registration point. vec3 registrationRemainder = vec3(1.0f) - _registrationPoint; - vec3 unrotatedMinRelativeToEntity = - (_dimensions * _registrationPoint); + vec3 unrotatedMinRelativeToEntity = -(_dimensions * _registrationPoint); vec3 unrotatedMaxRelativeToEntity = _dimensions * registrationRemainder; Extents unrotatedExtentsRelativeToRegistrationPoint = { unrotatedMinRelativeToEntity, unrotatedMaxRelativeToEntity }; Extents rotatedExtentsRelativeToRegistrationPoint = unrotatedExtentsRelativeToRegistrationPoint.getRotated(_rotation); @@ -3288,13 +3790,25 @@ AABox EntityItemProperties::getAABox() const { } bool EntityItemProperties::hasTransformOrVelocityChanges() const { - return _positionChanged ||_localPositionChanged + return _positionChanged || _localPositionChanged || _rotationChanged || _localRotationChanged || _velocityChanged || _localVelocityChanged || _angularVelocityChanged || _localAngularVelocityChanged || _accelerationChanged; } +void EntityItemProperties::clearTransformOrVelocityChanges() { + _positionChanged = false; + _localPositionChanged = false; + _rotationChanged = false; + _localRotationChanged = false; + _velocityChanged = false; + _localVelocityChanged = false; + _angularVelocityChanged = false; + _localAngularVelocityChanged = false; + _accelerationChanged = false; +} + bool EntityItemProperties::hasMiscPhysicsChanges() const { return _gravityChanged || _dimensionsChanged || _densityChanged || _frictionChanged || _restitutionChanged || _dampingChanged || _angularDampingChanged || _registrationPointChanged || @@ -3305,6 +3819,7 @@ bool EntityItemProperties::hasSimulationRestrictedChanges() const { return _positionChanged || _localPositionChanged || _rotationChanged || _localRotationChanged || _velocityChanged || _localVelocityChanged + || _localDimensionsChanged || _dimensionsChanged || _angularVelocityChanged || _localAngularVelocityChanged || _accelerationChanged || _parentIDChanged || _parentJointIndexChanged; @@ -3374,7 +3889,7 @@ uint8_t EntityItemProperties::computeSimulationBidPriority() const { if (_parentIDChanged || _parentJointIndexChanged) { // we need higher simulation ownership priority to chang parenting info priority = SCRIPT_GRAB_SIMULATION_PRIORITY; - } else if ( _positionChanged || _localPositionChanged + } else if (_positionChanged || _localPositionChanged || _rotationChanged || _localRotationChanged || _velocityChanged || _localVelocityChanged || _angularVelocityChanged || _localAngularVelocityChanged) { @@ -3385,30 +3900,80 @@ uint8_t EntityItemProperties::computeSimulationBidPriority() const { QList<QString> EntityItemProperties::listChangedProperties() { QList<QString> out; + + // Core + if (simulationOwnerChanged()) { + out += "simulationOwner"; + } + if (visibleChanged()) { + out += "visible"; + } + if (nameChanged()) { + out += "name"; + } + if (lockedChanged()) { + out += "locked"; + } + if (userDataChanged()) { + out += "userData"; + } + if (hrefChanged()) { + out += "href"; + } + if (descriptionChanged()) { + out += "description"; + } if (containsPositionChange()) { out += "position"; } if (dimensionsChanged()) { out += "dimensions"; } - if (velocityChanged()) { - out += "velocity"; + if (rotationChanged()) { + out += "rotation"; } - if (nameChanged()) { - out += "name"; + if (registrationPointChanged()) { + out += "registrationPoint"; } - if (visibleChanged()) { - out += "visible"; + if (createdChanged()) { + out += "created"; + } + if (lastEditedByChanged()) { + out += "lastEditedBy"; + } + if (entityHostTypeChanged()) { + out += "entityHostType"; + } + if (owningAvatarIDChanged()) { + out += "owningAvatarID"; + } + if (parentIDChanged()) { + out += "parentID"; + } + if (parentJointIndexChanged()) { + out += "parentJointIndex"; + } + if (queryAACubeChanged()) { + out += "queryAACube"; } if (canCastShadowChanged()) { out += "canCastShadow"; } - if (rotationChanged()) { - out += "rotation"; + if (isVisibleInSecondaryCameraChanged()) { + out += "isVisibleInSecondaryCamera"; } + getGrab().listChangedProperties(out); + + // Physics if (densityChanged()) { out += "density"; } + if (velocityChanged()) { + out += "velocity"; + } + if (angularVelocityChanged()) { + out += "angularVelocity"; + } if (gravityChanged()) { out += "gravity"; } @@ -3418,6 +3983,9 @@ QList<QString> EntityItemProperties::listChangedProperties() { if (dampingChanged()) { out += "damping"; } + if (angularDampingChanged()) { + out += "angularDamping"; + } if (restitutionChanged()) { out += "restitution"; } @@ -3427,60 +3995,6 @@ QList<QString> EntityItemProperties::listChangedProperties() { if (lifetimeChanged()) { out += "lifetime"; } - if (scriptChanged()) { - out += "script"; - } - if (scriptTimestampChanged()) { - out += "scriptTimestamp"; - } - if (serverScriptsChanged()) { - out += "serverScripts"; - } - if (collisionSoundURLChanged()) { - out += "collisionSoundURL"; - } - if (colorChanged()) { - out += "color"; - } - if (colorSpreadChanged()) { - out += "colorSpread"; - } - if (colorStartChanged()) { - out += "colorStart"; - } - if (colorFinishChanged()) { - out += "colorFinish"; - } - if (alphaChanged()) { - out += "alpha"; - } - if (alphaSpreadChanged()) { - out += "alphaSpread"; - } - if (alphaStartChanged()) { - out += "alphaStart"; - } - if (alphaFinishChanged()) { - out += "alphaFinish"; - } - if (emitterShouldTrailChanged()) { - out += "emitterShouldTrail"; - } - if (modelURLChanged()) { - out += "modelURL"; - } - if (compoundShapeURLChanged()) { - out += "compoundShapeURL"; - } - if (registrationPointChanged()) { - out += "registrationPoint"; - } - if (angularVelocityChanged()) { - out += "angularVelocity"; - } - if (angularDampingChanged()) { - out += "angularDamping"; - } if (collisionlessChanged()) { out += "collisionless"; } @@ -3490,48 +4004,97 @@ QList<QString> EntityItemProperties::listChangedProperties() { if (dynamicChanged()) { out += "dynamic"; } - if (isSpotlightChanged()) { - out += "isSpotlight"; + if (collisionSoundURLChanged()) { + out += "collisionSoundURL"; } - if (intensityChanged()) { - out += "intensity"; + if (actionDataChanged()) { + out += "actionData"; } - if (falloffRadiusChanged()) { - out += "falloffRadius"; + + // Cloning + if (cloneableChanged()) { + out += "cloneable"; } - if (exponentChanged()) { - out += "exponent"; + if (cloneLifetimeChanged()) { + out += "cloneLifetime"; } - if (cutoffChanged()) { - out += "cutoff"; + if (cloneLimitChanged()) { + out += "cloneLimit"; } - if (lockedChanged()) { - out += "locked"; + if (cloneDynamicChanged()) { + out += "cloneDynamic"; + } + if (cloneAvatarEntityChanged()) { + out += "cloneAvatarEntity"; + } + if (cloneOriginIDChanged()) { + out += "cloneOriginID"; + } + + // Scripts + if (scriptChanged()) { + out += "script"; + } + if (scriptTimestampChanged()) { + out += "scriptTimestamp"; + } + if (serverScriptsChanged()) { + out += "serverScripts"; + } + + // Certifiable Properties + if (itemNameChanged()) { + out += "itemName"; + } + if (itemDescriptionChanged()) { + out += "itemDescription"; + } + if (itemCategoriesChanged()) { + out += "itemCategories"; + } + if (itemArtistChanged()) { + out += "itemArtist"; + } + if (itemLicenseChanged()) { + out += "itemLicense"; + } + if (limitedRunChanged()) { + out += "limitedRun"; + } + if (marketplaceIDChanged()) { + out += "marketplaceID"; + } + if (editionNumberChanged()) { + out += "editionNumber"; + } + if (entityInstanceNumberChanged()) { + out += "entityInstanceNumber"; + } + if (certificateIDChanged()) { + out += "certificateID"; + } + if (staticCertificateVersionChanged()) { + out += "staticCertificateVersion"; + } + + // Common + if (shapeTypeChanged()) { + out += "shapeType"; + } + if (compoundShapeURLChanged()) { + out += "compoundShapeURL"; + } + if (colorChanged()) { + out += "color"; + } + if (alphaChanged()) { + out += "alpha"; } if (texturesChanged()) { out += "textures"; } - if (userDataChanged()) { - out += "userData"; - } - if (simulationOwnerChanged()) { - out += "simulationOwner"; - } - if (textChanged()) { - out += "text"; - } - if (lineHeightChanged()) { - out += "lineHeight"; - } - if (textColorChanged()) { - out += "textColor"; - } - if (backgroundColorChanged()) { - out += "backgroundColor"; - } - if (shapeTypeChanged()) { - out += "shapeType"; - } + + // Particles if (maxParticlesChanged()) { out += "maxParticles"; } @@ -3589,6 +4152,27 @@ QList<QString> EntityItemProperties::listChangedProperties() { if (radiusFinishChanged()) { out += "radiusFinish"; } + if (colorSpreadChanged()) { + out += "colorSpread"; + } + if (colorStartChanged()) { + out += "colorStart"; + } + if (colorFinishChanged()) { + out += "colorFinish"; + } + if (alphaSpreadChanged()) { + out += "alphaSpread"; + } + if (alphaStartChanged()) { + out += "alphaStart"; + } + if (alphaFinishChanged()) { + out += "alphaFinish"; + } + if (emitterShouldTrailChanged()) { + out += "emitterShouldTrail"; + } if (particleSpinChanged()) { out += "particleSpin"; } @@ -3604,77 +4188,94 @@ QList<QString> EntityItemProperties::listChangedProperties() { if (rotateWithEntityChanged()) { out += "rotateWithEntity"; } - if (materialURLChanged()) { - out += "materialURL"; + + // Model + if (modelURLChanged()) { + out += "modelURL"; } - if (materialMappingModeChanged()) { - out += "materialMappingMode"; + if (jointRotationsSetChanged()) { + out += "jointRotationsSet"; } - if (priorityChanged()) { - out += "priority"; + if (jointRotationsChanged()) { + out += "jointRotations"; } - if (parentMaterialNameChanged()) { - out += "parentMaterialName"; + if (jointTranslationsSetChanged()) { + out += "jointTranslationsSet"; } - if (materialMappingPosChanged()) { - out += "materialMappingPos"; + if (jointTranslationsChanged()) { + out += "jointTranslations"; } - if (materialMappingScaleChanged()) { - out += "materialMappingScale"; + if (relayParentJointsChanged()) { + out += "relayParentJoints"; } - if (materialMappingRotChanged()) { - out += "materialMappingRot"; + getAnimation().listChangedProperties(out); + + // Light + if (isSpotlightChanged()) { + out += "isSpotlight"; } - if (materialDataChanged()) { - out += "materialData"; + if (intensityChanged()) { + out += "intensity"; } - if (materialRepeatChanged()) { - out += "materialRepeat"; + if (exponentChanged()) { + out += "exponent"; } - if (isVisibleInSecondaryCameraChanged()) { - out += "isVisibleInSecondaryCamera"; + if (cutoffChanged()) { + out += "cutoff"; + } + if (falloffRadiusChanged()) { + out += "falloffRadius"; } - // Certifiable Properties - if (itemNameChanged()) { - out += "itemName"; + // Text + if (textChanged()) { + out += "text"; } - if (itemDescriptionChanged()) { - out += "itemDescription"; + if (lineHeightChanged()) { + out += "lineHeight"; } - if (itemCategoriesChanged()) { - out += "itemCategories"; + if (textColorChanged()) { + out += "textColor"; } - if (itemArtistChanged()) { - out += "itemArtist"; + if (textAlphaChanged()) { + out += "textAlpha"; } - if (itemLicenseChanged()) { - out += "itemLicense"; + if (backgroundColorChanged()) { + out += "backgroundColor"; } - if (limitedRunChanged()) { - out += "limitedRun"; + if (backgroundAlphaChanged()) { + out += "backgroundAlpha"; } - if (marketplaceIDChanged()) { - out += "marketplaceID"; + if (billboardModeChanged()) { + out += "billboardMode"; } - if (editionNumberChanged()) { - out += "editionNumber"; + if (leftMarginChanged()) { + out += "leftMargin"; } - if (entityInstanceNumberChanged()) { - out += "entityInstanceNumber"; + if (rightMarginChanged()) { + out += "rightMargin"; } - if (certificateIDChanged()) { - out += "certificateID"; + if (topMarginChanged()) { + out += "topMargin"; } - if (staticCertificateVersionChanged()) { - out += "staticCertificateVersion"; + if (bottomMarginChanged()) { + out += "bottomMargin"; } - if (hazeModeChanged()) { - out += "hazeMode"; + // Zone + getKeyLight().listChangedProperties(out); + getAmbientLight().listChangedProperties(out); + getSkybox().listChangedProperties(out); + getHaze().listChangedProperties(out); + getBloom().listChangedProperties(out); + if (flyingAllowedChanged()) { + out += "flyingAllowed"; } - if (bloomModeChanged()) { - out += "bloomMode"; + if (ghostingAllowedChanged()) { + out += "ghostingAllowed"; + } + if (filterURLChanged()) { + out += "filterURL"; } if (keyLightModeChanged()) { out += "keyLightMode"; @@ -3685,7 +4286,14 @@ QList<QString> EntityItemProperties::listChangedProperties() { if (skyboxModeChanged()) { out += "skyboxMode"; } + if (hazeModeChanged()) { + out += "hazeMode"; + } + if (bloomModeChanged()) { + out += "bloomMode"; + } + // Polyvox if (voxelVolumeSizeChanged()) { out += "voxelVolumeSize"; } @@ -3695,15 +4303,6 @@ QList<QString> EntityItemProperties::listChangedProperties() { if (voxelSurfaceStyleChanged()) { out += "voxelSurfaceStyle"; } - if (hrefChanged()) { - out += "href"; - } - if (descriptionChanged()) { - out += "description"; - } - if (actionDataChanged()) { - out += "actionData"; - } if (xTextureURLChanged()) { out += "xTextureURL"; } @@ -3731,89 +4330,96 @@ QList<QString> EntityItemProperties::listChangedProperties() { if (zPNeighborIDChanged()) { out += "zPNeighborID"; } - if (parentIDChanged()) { - out += "parentID"; - } - if (parentJointIndexChanged()) { - out += "parentJointIndex"; - } - if (jointRotationsSetChanged()) { - out += "jointRotationsSet"; - } - if (jointRotationsChanged()) { - out += "jointRotations"; - } - if (jointTranslationsSetChanged()) { - out += "jointTranslationsSet"; - } - if (jointTranslationsChanged()) { - out += "jointTranslations"; - } - if (relayParentJointsChanged()) { - out += "relayParentJoints"; - } - if (queryAACubeChanged()) { - out += "queryAACube"; - } - if (entityHostTypeChanged()) { - out += "entityHostType"; - } - if (owningAvatarIDChanged()) { - out += "owningAvatarID"; - } - - if (flyingAllowedChanged()) { - out += "flyingAllowed"; - } - if (ghostingAllowedChanged()) { - out += "ghostingAllowed"; - } - if (filterURLChanged()) { - out += "filterURL"; + // Web + if (sourceUrlChanged()) { + out += "sourceUrl"; } if (dpiChanged()) { out += "dpi"; } + // Polyline + if (linePointsChanged()) { + out += "linePoints"; + } + if (strokeWidthsChanged()) { + out += "strokeWidths"; + } + if (normalsChanged()) { + out += "normals"; + } + if (strokeColorsChanged()) { + out += "strokeColors"; + } + if (isUVModeStretchChanged()) { + out += "isUVModeStretch"; + } + if (glowChanged()) { + out += "glow"; + } + if (faceCameraChanged()) { + out += "faceCamera"; + } + + // Shape if (shapeChanged()) { out += "shape"; } - if (strokeColorsChanged()) { - out += "strokeColors"; + // Material + if (materialURLChanged()) { + out += "materialURL"; + } + if (materialMappingModeChanged()) { + out += "materialMappingMode"; + } + if (priorityChanged()) { + out += "priority"; + } + if (parentMaterialNameChanged()) { + out += "parentMaterialName"; + } + if (materialMappingPosChanged()) { + out += "materialMappingPos"; + } + if (materialMappingScaleChanged()) { + out += "materialMappingScale"; + } + if (materialMappingRotChanged()) { + out += "materialMappingRot"; + } + if (materialDataChanged()) { + out += "materialData"; + } + if (materialRepeatChanged()) { + out += "materialRepeat"; } - if (isUVModeStretchChanged()) { - out += "isUVModeStretch"; + // Image + if (imageURLChanged()) { + out += "imageURL"; + } + if (emissiveChanged()) { + out += "emissive"; + } + if (keepAspectRatioChanged()) { + out += "keepAspectRatio"; + } + if (subImageChanged()) { + out += "subImage"; } - if (cloneableChanged()) { - out += "cloneable"; + // Grid + if (followCameraChanged()) { + out += "followCamera"; } - if (cloneLifetimeChanged()) { - out += "cloneLifetime"; + if (majorGridEveryChanged()) { + out += "majorGridEvery"; } - if (cloneLimitChanged()) { - out += "cloneLimit"; + if (minorGridEveryChanged()) { + out += "minorGridEvery"; } - if (cloneDynamicChanged()) { - out += "cloneDynamic"; - } - if (cloneAvatarEntityChanged()) { - out += "cloneAvatarEntity"; - } - if (cloneOriginIDChanged()) { - out += "cloneOriginID"; - } - - getAnimation().listChangedProperties(out); - getKeyLight().listChangedProperties(out); - getAmbientLight().listChangedProperties(out); - getSkybox().listChangedProperties(out); - getHaze().listChangedProperties(out); - getBloom().listChangedProperties(out); - getGrab().listChangedProperties(out); return out; } @@ -4005,3 +4611,52 @@ void EntityItemProperties::convertToCloneProperties(const EntityItemID& entityID setCloneDynamic(ENTITY_ITEM_DEFAULT_CLONE_DYNAMIC); setCloneAvatarEntity(ENTITY_ITEM_DEFAULT_CLONE_AVATAR_ENTITY); } + +bool EntityItemProperties::blobToProperties(QScriptEngine& scriptEngine, const QByteArray& blob, EntityItemProperties& properties) { + // DANGER: this method is NOT efficient. + // begin recipe for converting unfortunately-formatted-binary-blob to EntityItemProperties + QJsonDocument jsonProperties = QJsonDocument::fromBinaryData(blob); + if (!jsonProperties.isObject()) { + qCDebug(entities) << "bad avatarEntityData json" << QString(blob.toHex()); + return false; + } + QVariant variant = jsonProperties.toVariant(); + QVariantMap variantMap = variant.toMap(); + QScriptValue scriptValue = variantMapToScriptValue(variantMap, scriptEngine); + EntityItemPropertiesFromScriptValueHonorReadOnly(scriptValue, properties); + // end recipe + return true; +} + +void EntityItemProperties::propertiesToBlob(QScriptEngine& scriptEngine, const QUuid& myAvatarID, const EntityItemProperties& properties, QByteArray& blob) { + // DANGER: this method is NOT efficient. + // begin recipe for extracting unfortunately-formatted-binary-blob from EntityItem + QScriptValue scriptValue = EntityItemNonDefaultPropertiesToScriptValue(&scriptEngine, properties); + QVariant variantProperties = scriptValue.toVariant(); + QJsonDocument jsonProperties = QJsonDocument::fromVariant(variantProperties); + // the ID of the parent/avatar changes from session to session. use a special UUID to indicate the avatar + QJsonObject jsonObject = jsonProperties.object(); + if (jsonObject.contains("parentID")) { + if (QUuid(jsonObject["parentID"].toString()) == myAvatarID) { + jsonObject["parentID"] = AVATAR_SELF_ID.toString(); + } + } + jsonProperties = QJsonDocument(jsonObject); + blob = jsonProperties.toBinaryData(); + // end recipe +} + +QDebug& operator<<(QDebug& dbg, const EntityPropertyFlags& f) { + QString result = "[ "; + + for (int i = 0; i < PROP_AFTER_LAST_ITEM; i++) { + auto prop = EntityPropertyList(i); + if (f.getHasProperty(prop)) { + result = result + _enumsToPropertyStrings[prop] + " "; + } + } + + result += "]"; + dbg.nospace() << result; + return dbg; +} diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 71dc1606f4..bb4d8c5878 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -21,6 +21,7 @@ #include <QtCore/QObject> #include <QVector> #include <QString> +#include <QDateTime> #include <AACube.h> #include <NumericalConstants.h> @@ -46,8 +47,10 @@ #include "BloomPropertyGroup.h" #include "TextEntityItem.h" #include "ZoneEntityItem.h" +#include "GridEntityItem.h" #include "MaterialMappingMode.h" +#include "BillboardMode.h" const quint64 UNKNOWN_CREATED_TIME = 0; @@ -61,6 +64,17 @@ const std::array<ComponentPair, COMPONENT_MODE_ITEM_COUNT> COMPONENT_MODES = { { using vec3Color = glm::vec3; using u8vec3Color = glm::u8vec3; +struct EntityPropertyInfo { + EntityPropertyInfo(EntityPropertyList propEnum) : + propertyEnum(propEnum) {} + EntityPropertyInfo(EntityPropertyList propEnum, QVariant min, QVariant max) : + propertyEnum(propEnum), minimum(min), maximum(max) {} + EntityPropertyInfo() = default; + EntityPropertyList propertyEnum; + QVariant minimum; + QVariant maximum; +}; + /// A collection of properties of an entity item used in the scripting API. Translates between the actual properties of an /// entity and a JavaScript style hash/QScriptValue storing a set of properties. Used in scripting to set/get the complete /// set of entity item properties via JavaScript hashes/QScriptValues @@ -68,20 +82,25 @@ using u8vec3Color = glm::u8vec3; class EntityItemProperties { // TODO: consider removing these friend relationship and use public methods friend class EntityItem; - friend class ModelEntityItem; friend class BoxEntityItem; friend class SphereEntityItem; - friend class LightEntityItem; - friend class TextEntityItem; - friend class ParticleEffectEntityItem; - friend class ZoneEntityItem; - friend class WebEntityItem; - friend class LineEntityItem; - friend class PolyVoxEntityItem; - friend class PolyLineEntityItem; friend class ShapeEntityItem; + friend class ModelEntityItem; + friend class TextEntityItem; + friend class ImageEntityItem; + friend class WebEntityItem; + friend class ParticleEffectEntityItem; + friend class LineEntityItem; + friend class PolyLineEntityItem; + friend class PolyVoxEntityItem; + friend class GridEntityItem; + friend class LightEntityItem; + friend class ZoneEntityItem; friend class MaterialEntityItem; public: + static bool blobToProperties(QScriptEngine& scriptEngine, const QByteArray& blob, EntityItemProperties& properties); + static void propertiesToBlob(QScriptEngine& scriptEngine, const QUuid& myAvatarID, const EntityItemProperties& properties, QByteArray& blob); + EntityItemProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()); virtual ~EntityItemProperties() = default; @@ -93,10 +112,13 @@ public: virtual QScriptValue copyToScriptValue(QScriptEngine* engine, bool skipDefaults, bool allowUnknownCreateTime = false, bool strictSemantics = false, EntityPsuedoPropertyFlags psueudoPropertyFlags = EntityPsuedoPropertyFlags()) const; virtual void copyFromScriptValue(const QScriptValue& object, bool honorReadOnly); + void copyFromJSONString(QScriptEngine& scriptEngine, const QString& jsonString); static QScriptValue entityPropertyFlagsToScriptValue(QScriptEngine* engine, const EntityPropertyFlags& flags); static void entityPropertyFlagsFromScriptValue(const QScriptValue& object, EntityPropertyFlags& flags); + static bool getPropertyInfo(const QString& propertyName, EntityPropertyInfo& propertyInfo); + // editing related features supported by all entities quint64 getLastEdited() const { return _lastEdited; } float getEditedAgo() const /// Elapsed seconds since this entity was last edited @@ -117,6 +139,8 @@ public: EntityPropertyFlags getDesiredProperties() { return _desiredProperties; } void setDesiredProperties(EntityPropertyFlags properties) { _desiredProperties = properties; } + bool constructFromBuffer(const unsigned char* data, int dataLength); + // Note: DEFINE_PROPERTY(PROP_FOO, Foo, foo, type, value) creates the following methods and variables: // type getFoo() const; // void setFoo(type); @@ -124,132 +148,58 @@ public: // type _foo { value }; // bool _fooChanged { false }; + // Core Properties + DEFINE_PROPERTY_REF(PROP_SIMULATION_OWNER, SimulationOwner, simulationOwner, SimulationOwner, SimulationOwner()); DEFINE_PROPERTY(PROP_VISIBLE, Visible, visible, bool, ENTITY_ITEM_DEFAULT_VISIBLE); - DEFINE_PROPERTY(PROP_CAN_CAST_SHADOW, CanCastShadow, canCastShadow, bool, ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW); + DEFINE_PROPERTY_REF(PROP_NAME, Name, name, QString, ENTITY_ITEM_DEFAULT_NAME); + DEFINE_PROPERTY(PROP_LOCKED, Locked, locked, bool, ENTITY_ITEM_DEFAULT_LOCKED); + DEFINE_PROPERTY_REF(PROP_USER_DATA, UserData, userData, QString, ENTITY_ITEM_DEFAULT_USER_DATA); + DEFINE_PROPERTY_REF(PROP_HREF, Href, href, QString, ""); + DEFINE_PROPERTY_REF(PROP_DESCRIPTION, Description, description, QString, ""); DEFINE_PROPERTY_REF_WITH_SETTER(PROP_POSITION, Position, position, glm::vec3, ENTITY_ITEM_ZERO_VEC3); DEFINE_PROPERTY_REF(PROP_DIMENSIONS, Dimensions, dimensions, glm::vec3, ENTITY_ITEM_DEFAULT_DIMENSIONS); DEFINE_PROPERTY_REF(PROP_ROTATION, Rotation, rotation, glm::quat, ENTITY_ITEM_DEFAULT_ROTATION); - DEFINE_PROPERTY(PROP_DENSITY, Density, density, float, ENTITY_ITEM_DEFAULT_DENSITY); - DEFINE_PROPERTY_REF(PROP_VELOCITY, Velocity, velocity, glm::vec3, ENTITY_ITEM_DEFAULT_VELOCITY); - DEFINE_PROPERTY_REF(PROP_GRAVITY, Gravity, gravity, glm::vec3, ENTITY_ITEM_DEFAULT_GRAVITY); - DEFINE_PROPERTY_REF(PROP_ACCELERATION, Acceleration, acceleration, glm::vec3, ENTITY_ITEM_DEFAULT_ACCELERATION); - DEFINE_PROPERTY(PROP_DAMPING, Damping, damping, float, ENTITY_ITEM_DEFAULT_DAMPING); - DEFINE_PROPERTY(PROP_RESTITUTION, Restitution, restitution, float, ENTITY_ITEM_DEFAULT_RESTITUTION); - DEFINE_PROPERTY(PROP_FRICTION, Friction, friction, float, ENTITY_ITEM_DEFAULT_FRICTION); - DEFINE_PROPERTY(PROP_LIFETIME, Lifetime, lifetime, float, ENTITY_ITEM_DEFAULT_LIFETIME); - DEFINE_PROPERTY(PROP_CREATED, Created, created, quint64, UNKNOWN_CREATED_TIME); - DEFINE_PROPERTY_REF(PROP_SCRIPT, Script, script, QString, ENTITY_ITEM_DEFAULT_SCRIPT); - DEFINE_PROPERTY(PROP_SCRIPT_TIMESTAMP, ScriptTimestamp, scriptTimestamp, quint64, ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP); - DEFINE_PROPERTY_REF(PROP_COLLISION_SOUND_URL, CollisionSoundURL, collisionSoundURL, QString, ENTITY_ITEM_DEFAULT_COLLISION_SOUND_URL); - DEFINE_PROPERTY_REF(PROP_COLOR, Color, color, u8vec3Color, particle::DEFAULT_COLOR); - DEFINE_PROPERTY_REF(PROP_COLOR_SPREAD, ColorSpread, colorSpread, u8vec3Color, particle::DEFAULT_COLOR_SPREAD); - DEFINE_PROPERTY_REF(PROP_COLOR_START, ColorStart, colorStart, glm::vec3, particle::DEFAULT_COLOR_UNINITIALIZED); - DEFINE_PROPERTY_REF(PROP_COLOR_FINISH, ColorFinish, colorFinish, glm::vec3, particle::DEFAULT_COLOR_UNINITIALIZED); - DEFINE_PROPERTY(PROP_ALPHA, Alpha, alpha, float, particle::DEFAULT_ALPHA); - DEFINE_PROPERTY(PROP_ALPHA_SPREAD, AlphaSpread, alphaSpread, float, particle::DEFAULT_ALPHA_SPREAD); - DEFINE_PROPERTY(PROP_ALPHA_START, AlphaStart, alphaStart, float, particle::DEFAULT_ALPHA_START); - DEFINE_PROPERTY(PROP_ALPHA_FINISH, AlphaFinish, alphaFinish, float, particle::DEFAULT_ALPHA_FINISH); - DEFINE_PROPERTY_REF(PROP_MODEL_URL, ModelURL, modelURL, QString, ""); - DEFINE_PROPERTY_REF(PROP_COMPOUND_SHAPE_URL, CompoundShapeURL, compoundShapeURL, QString, ""); DEFINE_PROPERTY_REF(PROP_REGISTRATION_POINT, RegistrationPoint, registrationPoint, glm::vec3, ENTITY_ITEM_DEFAULT_REGISTRATION_POINT); - DEFINE_PROPERTY_REF(PROP_ANGULAR_VELOCITY, AngularVelocity, angularVelocity, glm::vec3, ENTITY_ITEM_DEFAULT_ANGULAR_VELOCITY); - DEFINE_PROPERTY(PROP_ANGULAR_DAMPING, AngularDamping, angularDamping, float, ENTITY_ITEM_DEFAULT_ANGULAR_DAMPING); - DEFINE_PROPERTY(PROP_COLLISIONLESS, Collisionless, collisionless, bool, ENTITY_ITEM_DEFAULT_COLLISIONLESS); - DEFINE_PROPERTY(PROP_COLLISION_MASK, CollisionMask, collisionMask, uint16_t, ENTITY_COLLISION_MASK_DEFAULT); - DEFINE_PROPERTY(PROP_DYNAMIC, Dynamic, dynamic, bool, ENTITY_ITEM_DEFAULT_DYNAMIC); - DEFINE_PROPERTY(PROP_IS_SPOTLIGHT, IsSpotlight, isSpotlight, bool, LightEntityItem::DEFAULT_IS_SPOTLIGHT); - DEFINE_PROPERTY(PROP_INTENSITY, Intensity, intensity, float, LightEntityItem::DEFAULT_INTENSITY); - DEFINE_PROPERTY(PROP_FALLOFF_RADIUS, FalloffRadius, falloffRadius, float, LightEntityItem::DEFAULT_FALLOFF_RADIUS); - DEFINE_PROPERTY(PROP_EXPONENT, Exponent, exponent, float, LightEntityItem::DEFAULT_EXPONENT); - DEFINE_PROPERTY(PROP_CUTOFF, Cutoff, cutoff, float, LightEntityItem::DEFAULT_CUTOFF); - DEFINE_PROPERTY(PROP_LOCKED, Locked, locked, bool, ENTITY_ITEM_DEFAULT_LOCKED); - DEFINE_PROPERTY_REF(PROP_TEXTURES, Textures, textures, QString, ""); - DEFINE_PROPERTY_REF(PROP_USER_DATA, UserData, userData, QString, ENTITY_ITEM_DEFAULT_USER_DATA); - DEFINE_PROPERTY_REF(PROP_SIMULATION_OWNER, SimulationOwner, simulationOwner, SimulationOwner, SimulationOwner()); - DEFINE_PROPERTY_REF(PROP_TEXT, Text, text, QString, TextEntityItem::DEFAULT_TEXT); - DEFINE_PROPERTY(PROP_LINE_HEIGHT, LineHeight, lineHeight, float, TextEntityItem::DEFAULT_LINE_HEIGHT); - DEFINE_PROPERTY_REF(PROP_TEXT_COLOR, TextColor, textColor, u8vec3Color, TextEntityItem::DEFAULT_TEXT_COLOR); - DEFINE_PROPERTY_REF(PROP_BACKGROUND_COLOR, BackgroundColor, backgroundColor, u8vec3Color, TextEntityItem::DEFAULT_BACKGROUND_COLOR); - DEFINE_PROPERTY_REF_ENUM(PROP_SHAPE_TYPE, ShapeType, shapeType, ShapeType, SHAPE_TYPE_NONE); - DEFINE_PROPERTY(PROP_MAX_PARTICLES, MaxParticles, maxParticles, quint32, particle::DEFAULT_MAX_PARTICLES); - DEFINE_PROPERTY(PROP_LIFESPAN, Lifespan, lifespan, float, particle::DEFAULT_LIFESPAN); - DEFINE_PROPERTY(PROP_EMITTING_PARTICLES, IsEmitting, isEmitting, bool, true); - DEFINE_PROPERTY(PROP_EMIT_RATE, EmitRate, emitRate, float, particle::DEFAULT_EMIT_RATE); - DEFINE_PROPERTY(PROP_EMIT_SPEED, EmitSpeed, emitSpeed, float, particle::DEFAULT_EMIT_SPEED); - DEFINE_PROPERTY(PROP_SPEED_SPREAD, SpeedSpread, speedSpread, float, particle::DEFAULT_SPEED_SPREAD); - DEFINE_PROPERTY_REF(PROP_EMIT_ORIENTATION, EmitOrientation, emitOrientation, glm::quat, particle::DEFAULT_EMIT_ORIENTATION); - DEFINE_PROPERTY_REF(PROP_EMIT_DIMENSIONS, EmitDimensions, emitDimensions, glm::vec3, particle::DEFAULT_EMIT_DIMENSIONS); - DEFINE_PROPERTY(PROP_EMIT_RADIUS_START, EmitRadiusStart, emitRadiusStart, float, particle::DEFAULT_EMIT_RADIUS_START); - DEFINE_PROPERTY(PROP_POLAR_START, PolarStart, polarStart, float, particle::DEFAULT_POLAR_START); - DEFINE_PROPERTY(PROP_POLAR_FINISH, PolarFinish, polarFinish, float, particle::DEFAULT_POLAR_FINISH); - DEFINE_PROPERTY(PROP_AZIMUTH_START, AzimuthStart, azimuthStart, float, particle::DEFAULT_AZIMUTH_START); - DEFINE_PROPERTY(PROP_AZIMUTH_FINISH, AzimuthFinish, azimuthFinish, float, particle::DEFAULT_AZIMUTH_FINISH); - DEFINE_PROPERTY_REF(PROP_EMIT_ACCELERATION, EmitAcceleration, emitAcceleration, glm::vec3, particle::DEFAULT_EMIT_ACCELERATION); - DEFINE_PROPERTY_REF(PROP_ACCELERATION_SPREAD, AccelerationSpread, accelerationSpread, glm::vec3, particle::DEFAULT_ACCELERATION_SPREAD); - DEFINE_PROPERTY(PROP_PARTICLE_RADIUS, ParticleRadius, particleRadius, float, particle::DEFAULT_PARTICLE_RADIUS); - DEFINE_PROPERTY(PROP_RADIUS_SPREAD, RadiusSpread, radiusSpread, float, particle::DEFAULT_RADIUS_SPREAD); - DEFINE_PROPERTY(PROP_RADIUS_START, RadiusStart, radiusStart, float, particle::DEFAULT_RADIUS_START); - DEFINE_PROPERTY(PROP_RADIUS_FINISH, RadiusFinish, radiusFinish, float, particle::DEFAULT_RADIUS_FINISH); - DEFINE_PROPERTY(PROP_EMITTER_SHOULD_TRAIL, EmitterShouldTrail, emitterShouldTrail, bool, particle::DEFAULT_EMITTER_SHOULD_TRAIL); - DEFINE_PROPERTY_GROUP(KeyLight, keyLight, KeyLightPropertyGroup); - DEFINE_PROPERTY_GROUP(AmbientLight, ambientLight, AmbientLightPropertyGroup); - DEFINE_PROPERTY_REF(PROP_VOXEL_VOLUME_SIZE, VoxelVolumeSize, voxelVolumeSize, glm::vec3, PolyVoxEntityItem::DEFAULT_VOXEL_VOLUME_SIZE); - DEFINE_PROPERTY_REF(PROP_VOXEL_DATA, VoxelData, voxelData, QByteArray, PolyVoxEntityItem::DEFAULT_VOXEL_DATA); - DEFINE_PROPERTY_REF(PROP_VOXEL_SURFACE_STYLE, VoxelSurfaceStyle, voxelSurfaceStyle, uint16_t, PolyVoxEntityItem::DEFAULT_VOXEL_SURFACE_STYLE); - DEFINE_PROPERTY_REF(PROP_NAME, Name, name, QString, ENTITY_ITEM_DEFAULT_NAME); - - DEFINE_PROPERTY_REF_ENUM(PROP_KEY_LIGHT_MODE, KeyLightMode, keyLightMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); - DEFINE_PROPERTY_REF_ENUM(PROP_SKYBOX_MODE, SkyboxMode, skyboxMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); - DEFINE_PROPERTY_REF_ENUM(PROP_AMBIENT_LIGHT_MODE, AmbientLightMode, ambientLightMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); - DEFINE_PROPERTY_REF_ENUM(PROP_HAZE_MODE, HazeMode, hazeMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); - DEFINE_PROPERTY_REF_ENUM(PROP_BLOOM_MODE, BloomMode, bloomMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); - - DEFINE_PROPERTY_GROUP(Skybox, skybox, SkyboxPropertyGroup); - DEFINE_PROPERTY_GROUP(Haze, haze, HazePropertyGroup); - DEFINE_PROPERTY_GROUP(Bloom, bloom, BloomPropertyGroup); - DEFINE_PROPERTY_GROUP(Animation, animation, AnimationPropertyGroup); - DEFINE_PROPERTY_REF(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString, ""); - DEFINE_PROPERTY(PROP_LINE_WIDTH, LineWidth, lineWidth, float, LineEntityItem::DEFAULT_LINE_WIDTH); - DEFINE_PROPERTY_REF(LINE_POINTS, LinePoints, linePoints, QVector<glm::vec3>, ENTITY_ITEM_DEFAULT_EMPTY_VEC3_QVEC); - DEFINE_PROPERTY_REF(PROP_HREF, Href, href, QString, ""); - DEFINE_PROPERTY_REF(PROP_DESCRIPTION, Description, description, QString, ""); - DEFINE_PROPERTY(PROP_FACE_CAMERA, FaceCamera, faceCamera, bool, TextEntityItem::DEFAULT_FACE_CAMERA); - DEFINE_PROPERTY_REF(PROP_ACTION_DATA, ActionData, actionData, QByteArray, QByteArray()); - DEFINE_PROPERTY(PROP_NORMALS, Normals, normals, QVector<glm::vec3>, ENTITY_ITEM_DEFAULT_EMPTY_VEC3_QVEC); - DEFINE_PROPERTY(PROP_STROKE_COLORS, StrokeColors, strokeColors, QVector<glm::vec3>, ENTITY_ITEM_DEFAULT_EMPTY_VEC3_QVEC); - DEFINE_PROPERTY(PROP_STROKE_WIDTHS, StrokeWidths, strokeWidths, QVector<float>, QVector<float>()); - DEFINE_PROPERTY(PROP_IS_UV_MODE_STRETCH, IsUVModeStretch, isUVModeStretch, bool, true); - DEFINE_PROPERTY_REF(PROP_X_TEXTURE_URL, XTextureURL, xTextureURL, QString, ""); - DEFINE_PROPERTY_REF(PROP_Y_TEXTURE_URL, YTextureURL, yTextureURL, QString, ""); - DEFINE_PROPERTY_REF(PROP_Z_TEXTURE_URL, ZTextureURL, zTextureURL, QString, ""); - DEFINE_PROPERTY_REF(PROP_X_N_NEIGHBOR_ID, XNNeighborID, xNNeighborID, EntityItemID, UNKNOWN_ENTITY_ID); - DEFINE_PROPERTY_REF(PROP_Y_N_NEIGHBOR_ID, YNNeighborID, yNNeighborID, EntityItemID, UNKNOWN_ENTITY_ID); - DEFINE_PROPERTY_REF(PROP_Z_N_NEIGHBOR_ID, ZNNeighborID, zNNeighborID, EntityItemID, UNKNOWN_ENTITY_ID); - DEFINE_PROPERTY_REF(PROP_X_P_NEIGHBOR_ID, XPNeighborID, xPNeighborID, EntityItemID, UNKNOWN_ENTITY_ID); - DEFINE_PROPERTY_REF(PROP_Y_P_NEIGHBOR_ID, YPNeighborID, yPNeighborID, EntityItemID, UNKNOWN_ENTITY_ID); - DEFINE_PROPERTY_REF(PROP_Z_P_NEIGHBOR_ID, ZPNeighborID, zPNeighborID, EntityItemID, UNKNOWN_ENTITY_ID); + DEFINE_PROPERTY(PROP_CREATED, Created, created, quint64, UNKNOWN_CREATED_TIME); + DEFINE_PROPERTY_REF(PROP_LAST_EDITED_BY, LastEditedBy, lastEditedBy, QUuid, ENTITY_ITEM_DEFAULT_LAST_EDITED_BY); + DEFINE_PROPERTY_REF_ENUM(PROP_ENTITY_HOST_TYPE, EntityHostType, entityHostType, entity::HostType, entity::HostType::DOMAIN); + DEFINE_PROPERTY_REF_WITH_SETTER(PROP_OWNING_AVATAR_ID, OwningAvatarID, owningAvatarID, QUuid, UNKNOWN_ENTITY_ID); DEFINE_PROPERTY_REF(PROP_PARENT_ID, ParentID, parentID, QUuid, UNKNOWN_ENTITY_ID); DEFINE_PROPERTY_REF(PROP_PARENT_JOINT_INDEX, ParentJointIndex, parentJointIndex, quint16, -1); DEFINE_PROPERTY_REF(PROP_QUERY_AA_CUBE, QueryAACube, queryAACube, AACube, AACube()); - DEFINE_PROPERTY_REF(PROP_SHAPE, Shape, shape, QString, "Sphere"); - - DEFINE_PROPERTY_REF(PROP_MATERIAL_URL, MaterialURL, materialURL, QString, ""); - DEFINE_PROPERTY_REF_ENUM(PROP_MATERIAL_MAPPING_MODE, MaterialMappingMode, materialMappingMode, MaterialMappingMode, UV); - DEFINE_PROPERTY_REF(PROP_MATERIAL_PRIORITY, Priority, priority, quint16, 0); - DEFINE_PROPERTY_REF(PROP_PARENT_MATERIAL_NAME, ParentMaterialName, parentMaterialName, QString, "0"); - DEFINE_PROPERTY_REF(PROP_MATERIAL_MAPPING_POS, MaterialMappingPos, materialMappingPos, glm::vec2, glm::vec2(0.0f)); - DEFINE_PROPERTY_REF(PROP_MATERIAL_MAPPING_SCALE, MaterialMappingScale, materialMappingScale, glm::vec2, glm::vec2(1.0f)); - DEFINE_PROPERTY_REF(PROP_MATERIAL_MAPPING_ROT, MaterialMappingRot, materialMappingRot, float, 0); - DEFINE_PROPERTY_REF(PROP_MATERIAL_DATA, MaterialData, materialData, QString, ""); - DEFINE_PROPERTY_REF(PROP_MATERIAL_REPEAT, MaterialRepeat, materialRepeat, bool, true); - + DEFINE_PROPERTY(PROP_CAN_CAST_SHADOW, CanCastShadow, canCastShadow, bool, ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW); DEFINE_PROPERTY(PROP_VISIBLE_IN_SECONDARY_CAMERA, IsVisibleInSecondaryCamera, isVisibleInSecondaryCamera, bool, ENTITY_ITEM_DEFAULT_VISIBLE_IN_SECONDARY_CAMERA); + DEFINE_PROPERTY_GROUP(Grab, grab, GrabPropertyGroup); - DEFINE_PROPERTY(PROP_PARTICLE_SPIN, ParticleSpin, particleSpin, float, particle::DEFAULT_PARTICLE_SPIN); - DEFINE_PROPERTY(PROP_SPIN_SPREAD, SpinSpread, spinSpread, float, particle::DEFAULT_SPIN_SPREAD); - DEFINE_PROPERTY(PROP_SPIN_START, SpinStart, spinStart, float, particle::DEFAULT_SPIN_START); - DEFINE_PROPERTY(PROP_SPIN_FINISH, SpinFinish, spinFinish, float, particle::DEFAULT_SPIN_FINISH); - DEFINE_PROPERTY(PROP_PARTICLE_ROTATE_WITH_ENTITY, RotateWithEntity, rotateWithEntity, bool, particle::DEFAULT_ROTATE_WITH_ENTITY); + // Physics + DEFINE_PROPERTY(PROP_DENSITY, Density, density, float, ENTITY_ITEM_DEFAULT_DENSITY); + DEFINE_PROPERTY_REF(PROP_VELOCITY, Velocity, velocity, glm::vec3, ENTITY_ITEM_DEFAULT_VELOCITY); + DEFINE_PROPERTY_REF(PROP_ANGULAR_VELOCITY, AngularVelocity, angularVelocity, glm::vec3, ENTITY_ITEM_DEFAULT_ANGULAR_VELOCITY); + DEFINE_PROPERTY_REF(PROP_GRAVITY, Gravity, gravity, glm::vec3, ENTITY_ITEM_DEFAULT_GRAVITY); + DEFINE_PROPERTY_REF(PROP_ACCELERATION, Acceleration, acceleration, glm::vec3, ENTITY_ITEM_DEFAULT_ACCELERATION); + DEFINE_PROPERTY(PROP_DAMPING, Damping, damping, float, ENTITY_ITEM_DEFAULT_DAMPING); + DEFINE_PROPERTY(PROP_ANGULAR_DAMPING, AngularDamping, angularDamping, float, ENTITY_ITEM_DEFAULT_ANGULAR_DAMPING); + DEFINE_PROPERTY(PROP_RESTITUTION, Restitution, restitution, float, ENTITY_ITEM_DEFAULT_RESTITUTION); + DEFINE_PROPERTY(PROP_FRICTION, Friction, friction, float, ENTITY_ITEM_DEFAULT_FRICTION); + DEFINE_PROPERTY(PROP_LIFETIME, Lifetime, lifetime, float, ENTITY_ITEM_DEFAULT_LIFETIME); + DEFINE_PROPERTY(PROP_COLLISIONLESS, Collisionless, collisionless, bool, ENTITY_ITEM_DEFAULT_COLLISIONLESS); + DEFINE_PROPERTY(PROP_COLLISION_MASK, CollisionMask, collisionMask, uint16_t, ENTITY_COLLISION_MASK_DEFAULT); + DEFINE_PROPERTY(PROP_DYNAMIC, Dynamic, dynamic, bool, ENTITY_ITEM_DEFAULT_DYNAMIC); + DEFINE_PROPERTY_REF(PROP_COLLISION_SOUND_URL, CollisionSoundURL, collisionSoundURL, QString, ENTITY_ITEM_DEFAULT_COLLISION_SOUND_URL); + DEFINE_PROPERTY_REF(PROP_ACTION_DATA, ActionData, actionData, QByteArray, QByteArray()); + + // Cloning + DEFINE_PROPERTY(PROP_CLONEABLE, Cloneable, cloneable, bool, ENTITY_ITEM_DEFAULT_CLONEABLE); + DEFINE_PROPERTY(PROP_CLONE_LIFETIME, CloneLifetime, cloneLifetime, float, ENTITY_ITEM_DEFAULT_CLONE_LIFETIME); + DEFINE_PROPERTY(PROP_CLONE_LIMIT, CloneLimit, cloneLimit, float, ENTITY_ITEM_DEFAULT_CLONE_LIMIT); + DEFINE_PROPERTY(PROP_CLONE_DYNAMIC, CloneDynamic, cloneDynamic, bool, ENTITY_ITEM_DEFAULT_CLONE_DYNAMIC); + DEFINE_PROPERTY(PROP_CLONE_AVATAR_ENTITY, CloneAvatarEntity, cloneAvatarEntity, bool, ENTITY_ITEM_DEFAULT_CLONE_AVATAR_ENTITY); + DEFINE_PROPERTY_REF(PROP_CLONE_ORIGIN_ID, CloneOriginID, cloneOriginID, QUuid, ENTITY_ITEM_DEFAULT_CLONE_ORIGIN_ID); + + // Scripts + DEFINE_PROPERTY_REF(PROP_SCRIPT, Script, script, QString, ENTITY_ITEM_DEFAULT_SCRIPT); + DEFINE_PROPERTY(PROP_SCRIPT_TIMESTAMP, ScriptTimestamp, scriptTimestamp, quint64, ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP); + DEFINE_PROPERTY_REF(PROP_SERVER_SCRIPTS, ServerScripts, serverScripts, QString, ENTITY_ITEM_DEFAULT_SERVER_SCRIPTS); // Certifiable Properties - related to Proof of Purchase certificates DEFINE_PROPERTY_REF(PROP_ITEM_NAME, ItemName, itemName, QString, ENTITY_ITEM_DEFAULT_ITEM_NAME); @@ -271,35 +221,142 @@ public: DEFINE_PROPERTY_REF(PROP_LOCAL_ANGULAR_VELOCITY, LocalAngularVelocity, localAngularVelocity, glm::vec3, ENTITY_ITEM_ZERO_VEC3); DEFINE_PROPERTY_REF(PROP_LOCAL_DIMENSIONS, LocalDimensions, localDimensions, glm::vec3, ENTITY_ITEM_ZERO_VEC3); + // Common + DEFINE_PROPERTY_REF_ENUM(PROP_SHAPE_TYPE, ShapeType, shapeType, ShapeType, SHAPE_TYPE_NONE); + DEFINE_PROPERTY_REF(PROP_COMPOUND_SHAPE_URL, CompoundShapeURL, compoundShapeURL, QString, ""); + DEFINE_PROPERTY_REF(PROP_COLOR, Color, color, u8vec3Color, particle::DEFAULT_COLOR); + DEFINE_PROPERTY(PROP_ALPHA, Alpha, alpha, float, particle::DEFAULT_ALPHA); + DEFINE_PROPERTY_REF(PROP_TEXTURES, Textures, textures, QString, ""); + + // Particles + DEFINE_PROPERTY(PROP_MAX_PARTICLES, MaxParticles, maxParticles, quint32, particle::DEFAULT_MAX_PARTICLES); + DEFINE_PROPERTY(PROP_LIFESPAN, Lifespan, lifespan, float, particle::DEFAULT_LIFESPAN); + DEFINE_PROPERTY(PROP_EMITTING_PARTICLES, IsEmitting, isEmitting, bool, true); + DEFINE_PROPERTY(PROP_EMIT_RATE, EmitRate, emitRate, float, particle::DEFAULT_EMIT_RATE); + DEFINE_PROPERTY(PROP_EMIT_SPEED, EmitSpeed, emitSpeed, float, particle::DEFAULT_EMIT_SPEED); + DEFINE_PROPERTY(PROP_SPEED_SPREAD, SpeedSpread, speedSpread, float, particle::DEFAULT_SPEED_SPREAD); + DEFINE_PROPERTY_REF(PROP_EMIT_ORIENTATION, EmitOrientation, emitOrientation, glm::quat, particle::DEFAULT_EMIT_ORIENTATION); + DEFINE_PROPERTY_REF(PROP_EMIT_DIMENSIONS, EmitDimensions, emitDimensions, glm::vec3, particle::DEFAULT_EMIT_DIMENSIONS); + DEFINE_PROPERTY(PROP_EMIT_RADIUS_START, EmitRadiusStart, emitRadiusStart, float, particle::DEFAULT_EMIT_RADIUS_START); + DEFINE_PROPERTY(PROP_POLAR_START, PolarStart, polarStart, float, particle::DEFAULT_POLAR_START); + DEFINE_PROPERTY(PROP_POLAR_FINISH, PolarFinish, polarFinish, float, particle::DEFAULT_POLAR_FINISH); + DEFINE_PROPERTY(PROP_AZIMUTH_START, AzimuthStart, azimuthStart, float, particle::DEFAULT_AZIMUTH_START); + DEFINE_PROPERTY(PROP_AZIMUTH_FINISH, AzimuthFinish, azimuthFinish, float, particle::DEFAULT_AZIMUTH_FINISH); + DEFINE_PROPERTY_REF(PROP_EMIT_ACCELERATION, EmitAcceleration, emitAcceleration, glm::vec3, particle::DEFAULT_EMIT_ACCELERATION); + DEFINE_PROPERTY_REF(PROP_ACCELERATION_SPREAD, AccelerationSpread, accelerationSpread, glm::vec3, particle::DEFAULT_ACCELERATION_SPREAD); + DEFINE_PROPERTY(PROP_PARTICLE_RADIUS, ParticleRadius, particleRadius, float, particle::DEFAULT_PARTICLE_RADIUS); + DEFINE_PROPERTY(PROP_RADIUS_SPREAD, RadiusSpread, radiusSpread, float, particle::DEFAULT_RADIUS_SPREAD); + DEFINE_PROPERTY(PROP_RADIUS_START, RadiusStart, radiusStart, float, particle::DEFAULT_RADIUS_START); + DEFINE_PROPERTY(PROP_RADIUS_FINISH, RadiusFinish, radiusFinish, float, particle::DEFAULT_RADIUS_FINISH); + DEFINE_PROPERTY_REF(PROP_COLOR_SPREAD, ColorSpread, colorSpread, u8vec3Color, particle::DEFAULT_COLOR_SPREAD); + DEFINE_PROPERTY_REF(PROP_COLOR_START, ColorStart, colorStart, glm::vec3, particle::DEFAULT_COLOR_UNINITIALIZED); + DEFINE_PROPERTY_REF(PROP_COLOR_FINISH, ColorFinish, colorFinish, glm::vec3, particle::DEFAULT_COLOR_UNINITIALIZED); + DEFINE_PROPERTY(PROP_ALPHA_SPREAD, AlphaSpread, alphaSpread, float, particle::DEFAULT_ALPHA_SPREAD); + DEFINE_PROPERTY(PROP_ALPHA_START, AlphaStart, alphaStart, float, particle::DEFAULT_ALPHA_START); + DEFINE_PROPERTY(PROP_ALPHA_FINISH, AlphaFinish, alphaFinish, float, particle::DEFAULT_ALPHA_FINISH); + DEFINE_PROPERTY(PROP_EMITTER_SHOULD_TRAIL, EmitterShouldTrail, emitterShouldTrail, bool, particle::DEFAULT_EMITTER_SHOULD_TRAIL); + DEFINE_PROPERTY(PROP_PARTICLE_SPIN, ParticleSpin, particleSpin, float, particle::DEFAULT_PARTICLE_SPIN); + DEFINE_PROPERTY(PROP_SPIN_SPREAD, SpinSpread, spinSpread, float, particle::DEFAULT_SPIN_SPREAD); + DEFINE_PROPERTY(PROP_SPIN_START, SpinStart, spinStart, float, particle::DEFAULT_SPIN_START); + DEFINE_PROPERTY(PROP_SPIN_FINISH, SpinFinish, spinFinish, float, particle::DEFAULT_SPIN_FINISH); + DEFINE_PROPERTY(PROP_PARTICLE_ROTATE_WITH_ENTITY, RotateWithEntity, rotateWithEntity, bool, particle::DEFAULT_ROTATE_WITH_ENTITY); + + // Model + DEFINE_PROPERTY_REF(PROP_MODEL_URL, ModelURL, modelURL, QString, ""); DEFINE_PROPERTY_REF(PROP_JOINT_ROTATIONS_SET, JointRotationsSet, jointRotationsSet, QVector<bool>, QVector<bool>()); DEFINE_PROPERTY_REF(PROP_JOINT_ROTATIONS, JointRotations, jointRotations, QVector<glm::quat>, QVector<glm::quat>()); DEFINE_PROPERTY_REF(PROP_JOINT_TRANSLATIONS_SET, JointTranslationsSet, jointTranslationsSet, QVector<bool>, QVector<bool>()); DEFINE_PROPERTY_REF(PROP_JOINT_TRANSLATIONS, JointTranslations, jointTranslations, QVector<glm::vec3>, ENTITY_ITEM_DEFAULT_EMPTY_VEC3_QVEC); + DEFINE_PROPERTY(PROP_RELAY_PARENT_JOINTS, RelayParentJoints, relayParentJoints, bool, ENTITY_ITEM_DEFAULT_RELAY_PARENT_JOINTS); + DEFINE_PROPERTY_GROUP(Animation, animation, AnimationPropertyGroup); + // Light + DEFINE_PROPERTY(PROP_IS_SPOTLIGHT, IsSpotlight, isSpotlight, bool, LightEntityItem::DEFAULT_IS_SPOTLIGHT); + DEFINE_PROPERTY(PROP_INTENSITY, Intensity, intensity, float, LightEntityItem::DEFAULT_INTENSITY); + DEFINE_PROPERTY(PROP_EXPONENT, Exponent, exponent, float, LightEntityItem::DEFAULT_EXPONENT); + DEFINE_PROPERTY(PROP_CUTOFF, Cutoff, cutoff, float, LightEntityItem::DEFAULT_CUTOFF); + DEFINE_PROPERTY(PROP_FALLOFF_RADIUS, FalloffRadius, falloffRadius, float, LightEntityItem::DEFAULT_FALLOFF_RADIUS); + + // Text + DEFINE_PROPERTY_REF(PROP_TEXT, Text, text, QString, TextEntityItem::DEFAULT_TEXT); + DEFINE_PROPERTY(PROP_LINE_HEIGHT, LineHeight, lineHeight, float, TextEntityItem::DEFAULT_LINE_HEIGHT); + DEFINE_PROPERTY_REF(PROP_TEXT_COLOR, TextColor, textColor, u8vec3Color, TextEntityItem::DEFAULT_TEXT_COLOR); + DEFINE_PROPERTY_REF(PROP_TEXT_ALPHA, TextAlpha, textAlpha, float, TextEntityItem::DEFAULT_TEXT_ALPHA); + DEFINE_PROPERTY_REF(PROP_BACKGROUND_COLOR, BackgroundColor, backgroundColor, u8vec3Color, TextEntityItem::DEFAULT_BACKGROUND_COLOR); + DEFINE_PROPERTY_REF(PROP_BACKGROUND_ALPHA, BackgroundAlpha, backgroundAlpha, float, TextEntityItem::DEFAULT_TEXT_ALPHA); + DEFINE_PROPERTY_REF_ENUM(PROP_BILLBOARD_MODE, BillboardMode, billboardMode, BillboardMode, BillboardMode::NONE); + DEFINE_PROPERTY_REF(PROP_LEFT_MARGIN, LeftMargin, leftMargin, float, TextEntityItem::DEFAULT_MARGIN); + DEFINE_PROPERTY_REF(PROP_RIGHT_MARGIN, RightMargin, rightMargin, float, TextEntityItem::DEFAULT_MARGIN); + DEFINE_PROPERTY_REF(PROP_TOP_MARGIN, TopMargin, topMargin, float, TextEntityItem::DEFAULT_MARGIN); + DEFINE_PROPERTY_REF(PROP_BOTTOM_MARGIN, BottomMargin, bottomMargin, float, TextEntityItem::DEFAULT_MARGIN); + + // Zone + DEFINE_PROPERTY_GROUP(KeyLight, keyLight, KeyLightPropertyGroup); + DEFINE_PROPERTY_GROUP(AmbientLight, ambientLight, AmbientLightPropertyGroup); + DEFINE_PROPERTY_GROUP(Skybox, skybox, SkyboxPropertyGroup); + DEFINE_PROPERTY_GROUP(Haze, haze, HazePropertyGroup); + DEFINE_PROPERTY_GROUP(Bloom, bloom, BloomPropertyGroup); DEFINE_PROPERTY(PROP_FLYING_ALLOWED, FlyingAllowed, flyingAllowed, bool, ZoneEntityItem::DEFAULT_FLYING_ALLOWED); DEFINE_PROPERTY(PROP_GHOSTING_ALLOWED, GhostingAllowed, ghostingAllowed, bool, ZoneEntityItem::DEFAULT_GHOSTING_ALLOWED); DEFINE_PROPERTY(PROP_FILTER_URL, FilterURL, filterURL, QString, ZoneEntityItem::DEFAULT_FILTER_URL); + DEFINE_PROPERTY_REF_ENUM(PROP_KEY_LIGHT_MODE, KeyLightMode, keyLightMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); + DEFINE_PROPERTY_REF_ENUM(PROP_SKYBOX_MODE, SkyboxMode, skyboxMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); + DEFINE_PROPERTY_REF_ENUM(PROP_AMBIENT_LIGHT_MODE, AmbientLightMode, ambientLightMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); + DEFINE_PROPERTY_REF_ENUM(PROP_HAZE_MODE, HazeMode, hazeMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); + DEFINE_PROPERTY_REF_ENUM(PROP_BLOOM_MODE, BloomMode, bloomMode, uint32_t, (uint32_t)COMPONENT_MODE_INHERIT); - DEFINE_PROPERTY_REF_ENUM(PROP_ENTITY_HOST_TYPE, EntityHostType, entityHostType, entity::HostType, entity::HostType::DOMAIN); - DEFINE_PROPERTY_REF(PROP_OWNING_AVATAR_ID, OwningAvatarID, owningAvatarID, QUuid, UNKNOWN_ENTITY_ID); + // Polyvox + DEFINE_PROPERTY_REF(PROP_VOXEL_VOLUME_SIZE, VoxelVolumeSize, voxelVolumeSize, glm::vec3, PolyVoxEntityItem::DEFAULT_VOXEL_VOLUME_SIZE); + DEFINE_PROPERTY_REF(PROP_VOXEL_DATA, VoxelData, voxelData, QByteArray, PolyVoxEntityItem::DEFAULT_VOXEL_DATA); + DEFINE_PROPERTY_REF(PROP_VOXEL_SURFACE_STYLE, VoxelSurfaceStyle, voxelSurfaceStyle, uint16_t, PolyVoxEntityItem::DEFAULT_VOXEL_SURFACE_STYLE); + DEFINE_PROPERTY_REF(PROP_X_TEXTURE_URL, XTextureURL, xTextureURL, QString, ""); + DEFINE_PROPERTY_REF(PROP_Y_TEXTURE_URL, YTextureURL, yTextureURL, QString, ""); + DEFINE_PROPERTY_REF(PROP_Z_TEXTURE_URL, ZTextureURL, zTextureURL, QString, ""); + DEFINE_PROPERTY_REF(PROP_X_N_NEIGHBOR_ID, XNNeighborID, xNNeighborID, EntityItemID, UNKNOWN_ENTITY_ID); + DEFINE_PROPERTY_REF(PROP_Y_N_NEIGHBOR_ID, YNNeighborID, yNNeighborID, EntityItemID, UNKNOWN_ENTITY_ID); + DEFINE_PROPERTY_REF(PROP_Z_N_NEIGHBOR_ID, ZNNeighborID, zNNeighborID, EntityItemID, UNKNOWN_ENTITY_ID); + DEFINE_PROPERTY_REF(PROP_X_P_NEIGHBOR_ID, XPNeighborID, xPNeighborID, EntityItemID, UNKNOWN_ENTITY_ID); + DEFINE_PROPERTY_REF(PROP_Y_P_NEIGHBOR_ID, YPNeighborID, yPNeighborID, EntityItemID, UNKNOWN_ENTITY_ID); + DEFINE_PROPERTY_REF(PROP_Z_P_NEIGHBOR_ID, ZPNeighborID, zPNeighborID, EntityItemID, UNKNOWN_ENTITY_ID); + // Web + DEFINE_PROPERTY_REF(PROP_SOURCE_URL, SourceUrl, sourceUrl, QString, ""); DEFINE_PROPERTY_REF(PROP_DPI, DPI, dpi, uint16_t, ENTITY_ITEM_DEFAULT_DPI); - DEFINE_PROPERTY_REF(PROP_LAST_EDITED_BY, LastEditedBy, lastEditedBy, QUuid, ENTITY_ITEM_DEFAULT_LAST_EDITED_BY); + // Polyline + DEFINE_PROPERTY_REF(PROP_LINE_POINTS, LinePoints, linePoints, QVector<glm::vec3>, ENTITY_ITEM_DEFAULT_EMPTY_VEC3_QVEC); + DEFINE_PROPERTY(PROP_STROKE_WIDTHS, StrokeWidths, strokeWidths, QVector<float>, QVector<float>()); + DEFINE_PROPERTY(PROP_STROKE_NORMALS, Normals, normals, QVector<glm::vec3>, ENTITY_ITEM_DEFAULT_EMPTY_VEC3_QVEC); + DEFINE_PROPERTY(PROP_STROKE_COLORS, StrokeColors, strokeColors, QVector<glm::vec3>, ENTITY_ITEM_DEFAULT_EMPTY_VEC3_QVEC); + DEFINE_PROPERTY(PROP_IS_UV_MODE_STRETCH, IsUVModeStretch, isUVModeStretch, bool, true); + DEFINE_PROPERTY(PROP_LINE_GLOW, Glow, glow, bool, false); + DEFINE_PROPERTY(PROP_LINE_FACE_CAMERA, FaceCamera, faceCamera, bool, false); - DEFINE_PROPERTY_REF(PROP_SERVER_SCRIPTS, ServerScripts, serverScripts, QString, ENTITY_ITEM_DEFAULT_SERVER_SCRIPTS); - DEFINE_PROPERTY(PROP_RELAY_PARENT_JOINTS, RelayParentJoints, relayParentJoints, bool, ENTITY_ITEM_DEFAULT_RELAY_PARENT_JOINTS); + // Shape + DEFINE_PROPERTY_REF(PROP_SHAPE, Shape, shape, QString, "Sphere"); - DEFINE_PROPERTY(PROP_CLONEABLE, Cloneable, cloneable, bool, ENTITY_ITEM_DEFAULT_CLONEABLE); - DEFINE_PROPERTY(PROP_CLONE_LIFETIME, CloneLifetime, cloneLifetime, float, ENTITY_ITEM_DEFAULT_CLONE_LIFETIME); - DEFINE_PROPERTY(PROP_CLONE_LIMIT, CloneLimit, cloneLimit, float, ENTITY_ITEM_DEFAULT_CLONE_LIMIT); - DEFINE_PROPERTY(PROP_CLONE_DYNAMIC, CloneDynamic, cloneDynamic, bool, ENTITY_ITEM_DEFAULT_CLONE_DYNAMIC); - DEFINE_PROPERTY(PROP_CLONE_AVATAR_ENTITY, CloneAvatarEntity, cloneAvatarEntity, bool, ENTITY_ITEM_DEFAULT_CLONE_AVATAR_ENTITY); - DEFINE_PROPERTY_REF(PROP_CLONE_ORIGIN_ID, CloneOriginID, cloneOriginID, QUuid, ENTITY_ITEM_DEFAULT_CLONE_ORIGIN_ID); + // Material + DEFINE_PROPERTY_REF(PROP_MATERIAL_URL, MaterialURL, materialURL, QString, ""); + DEFINE_PROPERTY_REF_ENUM(PROP_MATERIAL_MAPPING_MODE, MaterialMappingMode, materialMappingMode, MaterialMappingMode, UV); + DEFINE_PROPERTY_REF(PROP_MATERIAL_PRIORITY, Priority, priority, quint16, 0); + DEFINE_PROPERTY_REF(PROP_PARENT_MATERIAL_NAME, ParentMaterialName, parentMaterialName, QString, "0"); + DEFINE_PROPERTY_REF(PROP_MATERIAL_MAPPING_POS, MaterialMappingPos, materialMappingPos, glm::vec2, glm::vec2(0.0f)); + DEFINE_PROPERTY_REF(PROP_MATERIAL_MAPPING_SCALE, MaterialMappingScale, materialMappingScale, glm::vec2, glm::vec2(1.0f)); + DEFINE_PROPERTY_REF(PROP_MATERIAL_MAPPING_ROT, MaterialMappingRot, materialMappingRot, float, 0); + DEFINE_PROPERTY_REF(PROP_MATERIAL_DATA, MaterialData, materialData, QString, ""); + DEFINE_PROPERTY_REF(PROP_MATERIAL_REPEAT, MaterialRepeat, materialRepeat, bool, true); - DEFINE_PROPERTY_GROUP(Grab, grab, GrabPropertyGroup); + // Image + DEFINE_PROPERTY_REF(PROP_IMAGE_URL, ImageURL, imageURL, QString, ""); + DEFINE_PROPERTY_REF(PROP_EMISSIVE, Emissive, emissive, bool, false); + DEFINE_PROPERTY_REF(PROP_KEEP_ASPECT_RATIO, KeepAspectRatio, keepAspectRatio, bool, true); + DEFINE_PROPERTY_REF(PROP_SUB_IMAGE, SubImage, subImage, QRect, QRect()); + + // Grid + DEFINE_PROPERTY_REF(PROP_GRID_FOLLOW_CAMERA, FollowCamera, followCamera, bool, true); + DEFINE_PROPERTY(PROP_MAJOR_GRID_EVERY, MajorGridEvery, majorGridEvery, uint32_t, GridEntityItem::DEFAULT_MAJOR_GRID_EVERY); + DEFINE_PROPERTY(PROP_MINOR_GRID_EVERY, MinorGridEvery, minorGridEvery, float, GridEntityItem::DEFAULT_MINOR_GRID_EVERY); - static QString getComponentModeString(uint32_t mode); static QString getComponentModeAsString(uint32_t mode); std::array<ComponentPair, COMPONENT_MODE_ITEM_COUNT>::const_iterator findComponent(const QString& mode); @@ -314,9 +371,6 @@ public: bool containsPositionChange() const { return _positionChanged; } bool containsDimensionsChange() const { return _dimensionsChanged; } - float getLocalRenderAlpha() const { return _localRenderAlpha; } - void setLocalRenderAlpha(float value) { _localRenderAlpha = value; _localRenderAlphaChanged = true; } - static OctreeElement::AppendState encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties, QByteArray& buffer, EntityPropertyFlags requestedProperties, EntityPropertyFlags& didntFitProperties); @@ -327,8 +381,6 @@ public: static bool decodeEntityEditPacket(const unsigned char* data, int bytesToRead, int& processedBytes, EntityItemID& entityID, EntityItemProperties& properties); - bool localRenderAlphaChanged() const { return _localRenderAlphaChanged; } - void clearID() { _id = UNKNOWN_ENTITY_ID; _idSet = false; } void markAllChanged(); @@ -349,9 +401,10 @@ public: void setQueryAACubeDirty() { _queryAACubeChanged = true; } - void setCreated(QDateTime& v); + void setLocationDirty() { _positionChanged = true; _rotationChanged = true; } bool hasTransformOrVelocityChanges() const; + void clearTransformOrVelocityChanges(); bool hasMiscPhysicsChanges() const; bool hasSimulationRestrictedChanges() const; @@ -416,8 +469,6 @@ private: EntityTypes::EntityType _type; void setType(const QString& typeName) { _type = EntityTypes::getEntityTypeFromName(typeName); } - float _localRenderAlpha; - bool _localRenderAlphaChanged; bool _defaultSettings; bool _dimensionsInitialized = true; // Only false if creating an entity locally with no dimensions properties @@ -446,11 +497,26 @@ Q_DECLARE_METATYPE(EntityPropertyFlags); QScriptValue EntityPropertyFlagsToScriptValue(QScriptEngine* engine, const EntityPropertyFlags& flags); void EntityPropertyFlagsFromScriptValue(const QScriptValue& object, EntityPropertyFlags& flags); +Q_DECLARE_METATYPE(EntityPropertyInfo); +QScriptValue EntityPropertyInfoToScriptValue(QScriptEngine* engine, const EntityPropertyInfo& propertyInfo); +void EntityPropertyInfoFromScriptValue(const QScriptValue& object, EntityPropertyInfo& propertyInfo); // define these inline here so the macros work inline void EntityItemProperties::setPosition(const glm::vec3& value) { _position = glm::clamp(value, (float)-HALF_TREE_SCALE, (float)HALF_TREE_SCALE); _positionChanged = true; } +inline void EntityItemProperties::setOwningAvatarID(const QUuid& id) { + _owningAvatarID = id; + if (!_owningAvatarID.isNull()) { + // for AvatarEntities there's no entity-server to tell us we're the simulation owner, + // so always set the simulationOwner to the owningAvatarID and a high priority. + setSimulationOwner(_owningAvatarID, AVATAR_ENTITY_SIMULATION_PRIORITY); + } + _owningAvatarIDChanged = true; +} + +QDebug& operator<<(QDebug& dbg, const EntityPropertyFlags& f); + inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { debug << "EntityItemProperties[" << "\n"; diff --git a/libraries/entities/src/EntityItemPropertiesDefaults.h b/libraries/entities/src/EntityItemPropertiesDefaults.h index 6c39e90c91..a1390b88fc 100644 --- a/libraries/entities/src/EntityItemPropertiesDefaults.h +++ b/libraries/entities/src/EntityItemPropertiesDefaults.h @@ -44,7 +44,6 @@ const QString ENTITY_ITEM_DEFAULT_CERTIFICATE_ID = QString(""); const quint32 ENTITY_ITEM_DEFAULT_STATIC_CERTIFICATE_VERSION = 0; const float ENTITY_ITEM_DEFAULT_ALPHA = 1.0f; -const float ENTITY_ITEM_DEFAULT_LOCAL_RENDER_ALPHA = 1.0f; const bool ENTITY_ITEM_DEFAULT_VISIBLE = true; const bool ENTITY_ITEM_DEFAULT_VISIBLE_IN_SECONDARY_CAMERA = true; const bool ENTITY_ITEM_DEFAULT_CAN_CAST_SHADOW { true }; @@ -53,6 +52,9 @@ const QString ENTITY_ITEM_DEFAULT_SCRIPT = QString(""); const quint64 ENTITY_ITEM_DEFAULT_SCRIPT_TIMESTAMP = 0; const QString ENTITY_ITEM_DEFAULT_SERVER_SCRIPTS = QString(""); const QString ENTITY_ITEM_DEFAULT_COLLISION_SOUND_URL = QString(""); + +const float ENTITY_ITEM_MIN_REGISTRATION_POINT = 0.0f; +const float ENTITY_ITEM_MAX_REGISTRATION_POINT = 1.0f; const glm::vec3 ENTITY_ITEM_DEFAULT_REGISTRATION_POINT = ENTITY_ITEM_HALF_VEC3; // center const float ENTITY_ITEM_IMMORTAL_LIFETIME = -1.0f; /// special lifetime which means the entity lives for ever @@ -75,6 +77,8 @@ const glm::vec3 ENTITY_ITEM_DEFAULT_VELOCITY = ENTITY_ITEM_ZERO_VEC3; const glm::vec3 ENTITY_ITEM_DEFAULT_ANGULAR_VELOCITY = ENTITY_ITEM_ZERO_VEC3; const glm::vec3 ENTITY_ITEM_DEFAULT_GRAVITY = ENTITY_ITEM_ZERO_VEC3; const glm::vec3 ENTITY_ITEM_DEFAULT_ACCELERATION = ENTITY_ITEM_ZERO_VEC3; +const float ENTITY_ITEM_MIN_DAMPING = 0.0f; +const float ENTITY_ITEM_MAX_DAMPING = 1.0f; const float ENTITY_ITEM_DEFAULT_DAMPING = 0.39347f; // approx timescale = 2.0 sec (see damping timescale formula in header) const float ENTITY_ITEM_DEFAULT_ANGULAR_DAMPING = 0.39347f; // approx timescale = 2.0 sec (see damping timescale formula in header) diff --git a/libraries/entities/src/EntityItemPropertiesMacros.h b/libraries/entities/src/EntityItemPropertiesMacros.h index d3a2dc6cec..fa14f52b48 100644 --- a/libraries/entities/src/EntityItemPropertiesMacros.h +++ b/libraries/entities/src/EntityItemPropertiesMacros.h @@ -13,8 +13,6 @@ #ifndef hifi_EntityItemPropertiesMacros_h #define hifi_EntityItemPropertiesMacros_h -#include <QDateTime> - #include "EntityItemID.h" #include <RegisteredMetaTypes.h> @@ -122,6 +120,8 @@ inline QScriptValue convertScriptValue(QScriptEngine* e, const QVector<glm::quat inline QScriptValue convertScriptValue(QScriptEngine* e, const QVector<bool>& v) {return qVectorBoolToScriptValue(e, v); } inline QScriptValue convertScriptValue(QScriptEngine* e, const QVector<float>& v) { return qVectorFloatToScriptValue(e, v); } +inline QScriptValue convertScriptValue(QScriptEngine* e, const QRect& v) { return qRectToScriptValue(e, v); } + inline QScriptValue convertScriptValue(QScriptEngine* e, const QByteArray& v) { QByteArray b64 = v.toBase64(); return QScriptValue(QString(b64)); @@ -224,6 +224,7 @@ inline quint32 quint32_convertFromScriptValue(const QScriptValue& v, bool& isVal } inline quint16 quint16_convertFromScriptValue(const QScriptValue& v, bool& isValid) { return v.toVariant().toInt(&isValid); } inline uint16_t uint16_t_convertFromScriptValue(const QScriptValue& v, bool& isValid) { return v.toVariant().toInt(&isValid); } +inline uint32_t uint32_t_convertFromScriptValue(const QScriptValue& v, bool& isValid) { return v.toVariant().toInt(&isValid); } inline int int_convertFromScriptValue(const QScriptValue& v, bool& isValid) { return v.toVariant().toInt(&isValid); } inline bool bool_convertFromScriptValue(const QScriptValue& v, bool& isValid) { isValid = true; return v.toVariant().toBool(); } inline uint8_t uint8_t_convertFromScriptValue(const QScriptValue& v, bool& isValid) { isValid = true; return (uint8_t)(0xff & v.toVariant().toInt(&isValid)); } @@ -231,14 +232,6 @@ inline QString QString_convertFromScriptValue(const QScriptValue& v, bool& isVal inline QUuid QUuid_convertFromScriptValue(const QScriptValue& v, bool& isValid) { isValid = true; return v.toVariant().toUuid(); } inline EntityItemID EntityItemID_convertFromScriptValue(const QScriptValue& v, bool& isValid) { isValid = true; return v.toVariant().toUuid(); } - -inline QDateTime QDateTime_convertFromScriptValue(const QScriptValue& v, bool& isValid) { - isValid = true; - auto result = QDateTime::fromString(v.toVariant().toString().trimmed(), Qt::ISODate); - // result.setTimeSpec(Qt::OffsetFromUTC); - return result; -} - inline QByteArray QByteArray_convertFromScriptValue(const QScriptValue& v, bool& isValid) { isValid = true; QString b64 = v.toVariant().toString().trimmed(); @@ -323,6 +316,13 @@ inline glm::quat quat_convertFromScriptValue(const QScriptValue& v, bool& isVali return glm::quat(); } +inline QRect QRect_convertFromScriptValue(const QScriptValue& v, bool& isValid) { + isValid = true; + QRect rect; + qRectFromScriptValue(v, rect); + return rect; +} + #define COPY_PROPERTY_IF_CHANGED(P) \ { \ if (other._##P##Changed) { \ @@ -401,10 +401,32 @@ inline glm::quat quat_convertFromScriptValue(const QScriptValue& v, bool& isVali static T _static##N; #define ADD_PROPERTY_TO_MAP(P, N, n, T) \ - _propertyStringsToEnums[#n] = P; + { \ + EntityPropertyInfo propertyInfo = EntityPropertyInfo(P); \ + _propertyInfos[#n] = propertyInfo; \ + _enumsToPropertyStrings[P] = #n; \ + } + +#define ADD_PROPERTY_TO_MAP_WITH_RANGE(P, N, n, T, M, X) \ + { \ + EntityPropertyInfo propertyInfo = EntityPropertyInfo(P, M, X); \ + _propertyInfos[#n] = propertyInfo; \ + _enumsToPropertyStrings[P] = #n; \ + } #define ADD_GROUP_PROPERTY_TO_MAP(P, G, g, N, n) \ - _propertyStringsToEnums[#g "." #n] = P; + { \ + EntityPropertyInfo propertyInfo = EntityPropertyInfo(P); \ + _propertyInfos[#g "." #n] = propertyInfo; \ + _enumsToPropertyStrings[P] = #g "." #n; \ + } + +#define ADD_GROUP_PROPERTY_TO_MAP_WITH_RANGE(P, G, g, N, n, M, X) \ + { \ + EntityPropertyInfo propertyInfo = EntityPropertyInfo(P, M, X); \ + _propertyInfos[#g "." #n] = propertyInfo; \ + _enumsToPropertyStrings[P] = #g "." #n; \ + } #define DEFINE_CORE(N, n, T, V) \ public: \ diff --git a/libraries/entities/src/EntityPropertyFlags.cpp b/libraries/entities/src/EntityPropertyFlags.cpp deleted file mode 100644 index 4977cdc537..0000000000 --- a/libraries/entities/src/EntityPropertyFlags.cpp +++ /dev/null @@ -1,208 +0,0 @@ - -#include "EntityPropertyFlags.h" - - -QDebug& operator<<(QDebug& dbg, const EntityPropertyFlags& f) { - QString result = "[ "; - - result = f.getHasProperty(PROP_PAGED_PROPERTY) ? result + "pagedProperty " : result; - result = f.getHasProperty(PROP_CUSTOM_PROPERTIES_INCLUDED) ? result + "customPropertiesIncluded " : result; - result = f.getHasProperty(PROP_VISIBLE) ? result + "visible " : result; - result = f.getHasProperty(PROP_CAN_CAST_SHADOW) ? result + "canCastShadow " : result; - result = f.getHasProperty(PROP_POSITION) ? result + "position " : result; - result = f.getHasProperty(PROP_DIMENSIONS) ? result + "dimensions " : result; - result = f.getHasProperty(PROP_ROTATION) ? result + "rotation " : result; - result = f.getHasProperty(PROP_DENSITY) ? result + "density " : result; - result = f.getHasProperty(PROP_VELOCITY) ? result + "velocity " : result; - result = f.getHasProperty(PROP_GRAVITY) ? result + "gravity " : result; - result = f.getHasProperty(PROP_DAMPING) ? result + "damping " : result; - result = f.getHasProperty(PROP_LIFETIME) ? result + "lifetime " : result; - result = f.getHasProperty(PROP_SCRIPT) ? result + "script " : result; - result = f.getHasProperty(PROP_COLOR) ? result + "color " : result; - result = f.getHasProperty(PROP_MODEL_URL) ? result + "modelUrl " : result; - result = f.getHasProperty(PROP_ANIMATION_URL) ? result + "animationUrl " : result; - result = f.getHasProperty(PROP_ANIMATION_FPS) ? result + "animationFps " : result; - result = f.getHasProperty(PROP_ANIMATION_FRAME_INDEX) ? result + "animationFrameIndex " : result; - result = f.getHasProperty(PROP_ANIMATION_PLAYING) ? result + "animationPlaying " : result; - result = f.getHasProperty(PROP_ANIMATION_ALLOW_TRANSLATION) ? result + "animationAllowTranslation " : result; - result = f.getHasProperty(PROP_RELAY_PARENT_JOINTS) ? result + "relayParentJoints " : result; - result = f.getHasProperty(PROP_REGISTRATION_POINT) ? result + "registrationPoint " : result; - result = f.getHasProperty(PROP_ANGULAR_VELOCITY) ? result + "angularVelocity " : result; - result = f.getHasProperty(PROP_ANGULAR_DAMPING) ? result + "angularDamping " : result; - result = f.getHasProperty(PROP_COLLISIONLESS) ? result + "collisionless " : result; - result = f.getHasProperty(PROP_DYNAMIC) ? result + "dynamic " : result; - result = f.getHasProperty(PROP_IS_SPOTLIGHT) ? result + "isSpotlight " : result; - result = f.getHasProperty(PROP_DIFFUSE_COLOR) ? result + "diffuseColor " : result; - result = f.getHasProperty(PROP_AMBIENT_COLOR_UNUSED) ? result + "ambientColorUnused " : result; - result = f.getHasProperty(PROP_SPECULAR_COLOR_UNUSED) ? result + "specularColorUnused " : result; - result = f.getHasProperty(PROP_INTENSITY) ? result + "intensity " : result; - result = f.getHasProperty(PROP_LINEAR_ATTENUATION_UNUSED) ? result + "linearAttenuationUnused " : result; - result = f.getHasProperty(PROP_QUADRATIC_ATTENUATION_UNUSED) ? result + "quadraticAttenuationUnused " : result; - result = f.getHasProperty(PROP_EXPONENT) ? result + "exponent " : result; - result = f.getHasProperty(PROP_CUTOFF) ? result + "cutoff " : result; - result = f.getHasProperty(PROP_LOCKED) ? result + "locked " : result; - result = f.getHasProperty(PROP_TEXTURES) ? result + "textures " : result; - result = f.getHasProperty(PROP_ANIMATION_SETTINGS_UNUSED) ? result + "animationSettingsUnused " : result; - result = f.getHasProperty(PROP_USER_DATA) ? result + "userData " : result; - result = f.getHasProperty(PROP_SHAPE_TYPE) ? result + "shapeType " : result; - result = f.getHasProperty(PROP_MAX_PARTICLES) ? result + "maxParticles " : result; - result = f.getHasProperty(PROP_LIFESPAN) ? result + "lifespan " : result; - result = f.getHasProperty(PROP_EMIT_RATE) ? result + "emitRate " : result; - result = f.getHasProperty(PROP_EMIT_SPEED) ? result + "emitSpeed " : result; - result = f.getHasProperty(PROP_EMIT_STRENGTH) ? result + "emitStrength " : result; - result = f.getHasProperty(PROP_EMIT_ACCELERATION) ? result + "emitAcceleration " : result; - result = f.getHasProperty(PROP_PARTICLE_RADIUS) ? result + "particleRadius " : result; - result = f.getHasProperty(PROP_COMPOUND_SHAPE_URL) ? result + "compoundShapeUrl " : result; - result = f.getHasProperty(PROP_MARKETPLACE_ID) ? result + "marketplaceID " : result; - result = f.getHasProperty(PROP_ACCELERATION) ? result + "acceleration " : result; - result = f.getHasProperty(PROP_SIMULATION_OWNER) ? result + "simulationOwner " : result; - result = f.getHasProperty(PROP_NAME) ? result + "name " : result; - result = f.getHasProperty(PROP_COLLISION_SOUND_URL) ? result + "collisionSoundUrl " : result; - result = f.getHasProperty(PROP_RESTITUTION) ? result + "restitution " : result; - result = f.getHasProperty(PROP_FRICTION) ? result + "friction " : result; - result = f.getHasProperty(PROP_VOXEL_VOLUME_SIZE) ? result + "voxelVolumeSize " : result; - result = f.getHasProperty(PROP_VOXEL_DATA) ? result + "voxelData " : result; - result = f.getHasProperty(PROP_VOXEL_SURFACE_STYLE) ? result + "voxelSurfaceStyle " : result; - result = f.getHasProperty(PROP_LINE_WIDTH) ? result + "lineWidth " : result; - result = f.getHasProperty(PROP_LINE_POINTS) ? result + "linePoints " : result; - result = f.getHasProperty(PROP_HREF) ? result + "href " : result; - result = f.getHasProperty(PROP_DESCRIPTION) ? result + "description " : result; - result = f.getHasProperty(PROP_FACE_CAMERA) ? result + "faceCamera " : result; - result = f.getHasProperty(PROP_SCRIPT_TIMESTAMP) ? result + "scriptTimestamp " : result; - result = f.getHasProperty(PROP_ACTION_DATA) ? result + "actionData " : result; - result = f.getHasProperty(PROP_X_TEXTURE_URL) ? result + "xTextureUrl " : result; - result = f.getHasProperty(PROP_Y_TEXTURE_URL) ? result + "yTextureUrl " : result; - result = f.getHasProperty(PROP_Z_TEXTURE_URL) ? result + "zTextureUrl " : result; - result = f.getHasProperty(PROP_NORMALS) ? result + "normals " : result; - result = f.getHasProperty(PROP_STROKE_COLORS) ? result + "strokeColors " : result; - result = f.getHasProperty(PROP_STROKE_WIDTHS) ? result + "strokeWidths " : result; - result = f.getHasProperty(PROP_IS_UV_MODE_STRETCH) ? result + "isUvModeStretch " : result; - result = f.getHasProperty(PROP_SPEED_SPREAD) ? result + "speedSpread " : result; - result = f.getHasProperty(PROP_ACCELERATION_SPREAD) ? result + "accelerationSpread " : result; - result = f.getHasProperty(PROP_X_N_NEIGHBOR_ID) ? result + "xNNeighborID " : result; - result = f.getHasProperty(PROP_Y_N_NEIGHBOR_ID) ? result + "yNNeighborID " : result; - result = f.getHasProperty(PROP_Z_N_NEIGHBOR_ID) ? result + "zNNeighborID " : result; - result = f.getHasProperty(PROP_X_P_NEIGHBOR_ID) ? result + "xPNeighborID " : result; - result = f.getHasProperty(PROP_Y_P_NEIGHBOR_ID) ? result + "yPNeighborID " : result; - result = f.getHasProperty(PROP_Z_P_NEIGHBOR_ID) ? result + "zPNeighborID " : result; - result = f.getHasProperty(PROP_RADIUS_SPREAD) ? result + "radiusSpread " : result; - result = f.getHasProperty(PROP_RADIUS_START) ? result + "radiusStart " : result; - result = f.getHasProperty(PROP_RADIUS_FINISH) ? result + "radiusFinish " : result; - result = f.getHasProperty(PROP_ALPHA) ? result + "alpha " : result; - result = f.getHasProperty(PROP_COLOR_SPREAD) ? result + "colorSpread " : result; - result = f.getHasProperty(PROP_COLOR_START) ? result + "colorStart " : result; - result = f.getHasProperty(PROP_COLOR_FINISH) ? result + "colorFinish " : result; - result = f.getHasProperty(PROP_ALPHA_SPREAD) ? result + "alphaSpread " : result; - result = f.getHasProperty(PROP_ALPHA_START) ? result + "alphaStart " : result; - result = f.getHasProperty(PROP_ALPHA_FINISH) ? result + "alphaFinish " : result; - result = f.getHasProperty(PROP_EMIT_ORIENTATION) ? result + "emitOrientation " : result; - result = f.getHasProperty(PROP_EMIT_DIMENSIONS) ? result + "emitDimensions " : result; - result = f.getHasProperty(PROP_EMIT_RADIUS_START) ? result + "emitRadiusStart " : result; - result = f.getHasProperty(PROP_POLAR_START) ? result + "polarStart " : result; - result = f.getHasProperty(PROP_POLAR_FINISH) ? result + "polarFinish " : result; - result = f.getHasProperty(PROP_AZIMUTH_START) ? result + "azimuthStart " : result; - result = f.getHasProperty(PROP_AZIMUTH_FINISH) ? result + "azimuthFinish " : result; - result = f.getHasProperty(PROP_ANIMATION_LOOP) ? result + "animationLoop " : result; - result = f.getHasProperty(PROP_ANIMATION_FIRST_FRAME) ? result + "animationFirstFrame " : result; - result = f.getHasProperty(PROP_ANIMATION_LAST_FRAME) ? result + "animationLastFrame " : result; - result = f.getHasProperty(PROP_ANIMATION_HOLD) ? result + "animationHold " : result; - result = f.getHasProperty(PROP_ANIMATION_START_AUTOMATICALLY) ? result + "animationStartAutomatically " : result; - result = f.getHasProperty(PROP_EMITTER_SHOULD_TRAIL) ? result + "emitterShouldTrail " : result; - result = f.getHasProperty(PROP_PARENT_ID) ? result + "parentID " : result; - result = f.getHasProperty(PROP_PARENT_JOINT_INDEX) ? result + "parentJointIndex " : result; - result = f.getHasProperty(PROP_LOCAL_POSITION) ? result + "localPosition " : result; - result = f.getHasProperty(PROP_LOCAL_ROTATION) ? result + "localRotation " : result; - result = f.getHasProperty(PROP_QUERY_AA_CUBE) ? result + "queryAaCube " : result; - result = f.getHasProperty(PROP_JOINT_ROTATIONS_SET) ? result + "jointRotationsSet " : result; - result = f.getHasProperty(PROP_JOINT_ROTATIONS) ? result + "jointRotations " : result; - result = f.getHasProperty(PROP_JOINT_TRANSLATIONS_SET) ? result + "jointTranslationsSet " : result; - result = f.getHasProperty(PROP_JOINT_TRANSLATIONS) ? result + "jointTranslations " : result; - result = f.getHasProperty(PROP_COLLISION_MASK) ? result + "collisionMask " : result; - result = f.getHasProperty(PROP_FALLOFF_RADIUS) ? result + "falloffRadius " : result; - result = f.getHasProperty(PROP_FLYING_ALLOWED) ? result + "flyingAllowed " : result; - result = f.getHasProperty(PROP_GHOSTING_ALLOWED) ? result + "ghostingAllowed " : result; - result = f.getHasProperty(PROP_ENTITY_HOST_TYPE) ? result + "entityHostType " : result; - result = f.getHasProperty(PROP_OWNING_AVATAR_ID) ? result + "owningAvatarID " : result; - result = f.getHasProperty(PROP_SHAPE) ? result + "shape " : result; - result = f.getHasProperty(PROP_DPI) ? result + "dpi " : result; - result = f.getHasProperty(PROP_LOCAL_VELOCITY) ? result + "localVelocity " : result; - result = f.getHasProperty(PROP_LOCAL_ANGULAR_VELOCITY) ? result + "localAngularVelocity " : result; - result = f.getHasProperty(PROP_LAST_EDITED_BY) ? result + "lastEditedBy " : result; - result = f.getHasProperty(PROP_SERVER_SCRIPTS) ? result + "serverScripts " : result; - result = f.getHasProperty(PROP_FILTER_URL) ? result + "filterUrl " : result; - result = f.getHasProperty(PROP_ITEM_NAME) ? result + "itemName " : result; - result = f.getHasProperty(PROP_ITEM_DESCRIPTION) ? result + "itemDescription " : result; - result = f.getHasProperty(PROP_ITEM_CATEGORIES) ? result + "itemCategories " : result; - result = f.getHasProperty(PROP_ITEM_ARTIST) ? result + "itemArtist " : result; - result = f.getHasProperty(PROP_ITEM_LICENSE) ? result + "itemLicense " : result; - result = f.getHasProperty(PROP_LIMITED_RUN) ? result + "limitedRun " : result; - result = f.getHasProperty(PROP_EDITION_NUMBER) ? result + "editionNumber " : result; - result = f.getHasProperty(PROP_ENTITY_INSTANCE_NUMBER) ? result + "entityInstanceNumber " : result; - result = f.getHasProperty(PROP_CERTIFICATE_ID) ? result + "certificateID " : result; - result = f.getHasProperty(PROP_STATIC_CERTIFICATE_VERSION) ? result + "staticCertificateVersion " : result; - result = f.getHasProperty(PROP_CLONEABLE) ? result + "cloneable " : result; - result = f.getHasProperty(PROP_CLONE_LIFETIME) ? result + "cloneLifetime " : result; - result = f.getHasProperty(PROP_CLONE_LIMIT) ? result + "cloneLimit " : result; - result = f.getHasProperty(PROP_CLONE_DYNAMIC) ? result + "cloneDynamic " : result; - result = f.getHasProperty(PROP_CLONE_AVATAR_ENTITY) ? result + "cloneAvatarEntity " : result; - result = f.getHasProperty(PROP_CLONE_ORIGIN_ID) ? result + "cloneOriginID " : result; - result = f.getHasProperty(PROP_HAZE_MODE) ? result + "hazeMode " : result; - result = f.getHasProperty(PROP_KEYLIGHT_COLOR) ? result + "keylightColor " : result; - result = f.getHasProperty(PROP_KEYLIGHT_INTENSITY) ? result + "keylightIntensity " : result; - result = f.getHasProperty(PROP_KEYLIGHT_DIRECTION) ? result + "keylightDirection " : result; - result = f.getHasProperty(PROP_KEYLIGHT_CAST_SHADOW) ? result + "keylightCastShadow " : result; - result = f.getHasProperty(PROP_HAZE_RANGE) ? result + "hazeRange " : result; - result = f.getHasProperty(PROP_HAZE_COLOR) ? result + "hazeColor " : result; - result = f.getHasProperty(PROP_HAZE_GLARE_COLOR) ? result + "hazeGlareColor " : result; - result = f.getHasProperty(PROP_HAZE_ENABLE_GLARE) ? result + "hazeEnableGlare " : result; - result = f.getHasProperty(PROP_HAZE_GLARE_ANGLE) ? result + "hazeGlareAngle " : result; - result = f.getHasProperty(PROP_HAZE_ALTITUDE_EFFECT) ? result + "hazeAltitudeEffect " : result; - result = f.getHasProperty(PROP_HAZE_CEILING) ? result + "hazeCeiling " : result; - result = f.getHasProperty(PROP_HAZE_BASE_REF) ? result + "hazeBaseRef " : result; - result = f.getHasProperty(PROP_HAZE_BACKGROUND_BLEND) ? result + "hazeBackgroundBlend " : result; - result = f.getHasProperty(PROP_HAZE_ATTENUATE_KEYLIGHT) ? result + "hazeAttenuateKeylight " : result; - result = f.getHasProperty(PROP_HAZE_KEYLIGHT_RANGE) ? result + "hazeKeylightRange " : result; - result = f.getHasProperty(PROP_HAZE_KEYLIGHT_ALTITUDE) ? result + "hazeKeylightAltitude " : result; - result = f.getHasProperty(PROP_KEY_LIGHT_MODE) ? result + "keyLightMode " : result; - result = f.getHasProperty(PROP_AMBIENT_LIGHT_MODE) ? result + "ambientLightMode " : result; - result = f.getHasProperty(PROP_SKYBOX_MODE) ? result + "skyboxMode " : result; - result = f.getHasProperty(PROP_LOCAL_DIMENSIONS) ? result + "localDimensions " : result; - result = f.getHasProperty(PROP_MATERIAL_URL) ? result + "materialUrl " : result; - result = f.getHasProperty(PROP_MATERIAL_MAPPING_MODE) ? result + "materialMappingMode " : result; - result = f.getHasProperty(PROP_MATERIAL_PRIORITY) ? result + "materialPriority " : result; - result = f.getHasProperty(PROP_PARENT_MATERIAL_NAME) ? result + "parentMaterialName " : result; - result = f.getHasProperty(PROP_MATERIAL_MAPPING_POS) ? result + "materialMappingPos " : result; - result = f.getHasProperty(PROP_MATERIAL_MAPPING_SCALE) ? result + "materialMappingScale " : result; - result = f.getHasProperty(PROP_MATERIAL_MAPPING_ROT) ? result + "materialMappingRot " : result; - result = f.getHasProperty(PROP_MATERIAL_DATA) ? result + "materialData " : result; - result = f.getHasProperty(PROP_MATERIAL_REPEAT) ? result + "materialRepeat " : result; - result = f.getHasProperty(PROP_VISIBLE_IN_SECONDARY_CAMERA) ? result + "visibleInSecondaryCamera " : result; - result = f.getHasProperty(PROP_PARTICLE_SPIN) ? result + "particleSpin " : result; - result = f.getHasProperty(PROP_SPIN_START) ? result + "spinStart " : result; - result = f.getHasProperty(PROP_SPIN_FINISH) ? result + "spinFinish " : result; - result = f.getHasProperty(PROP_SPIN_SPREAD) ? result + "spinSpread " : result; - result = f.getHasProperty(PROP_PARTICLE_ROTATE_WITH_ENTITY) ? result + "particleRotateWithEntity " : result; - result = f.getHasProperty(PROP_BLOOM_INTENSITY) ? result + "bloomIntensity " : result; - result = f.getHasProperty(PROP_BLOOM_THRESHOLD) ? result + "bloomThreshold " : result; - result = f.getHasProperty(PROP_BLOOM_SIZE) ? result + "bloomSize " : result; - result = f.getHasProperty(PROP_GRAB_GRABBABLE) ? result + "grab.Grabbable " : result; - result = f.getHasProperty(PROP_GRAB_KINEMATIC) ? result + "grab.Kinematic " : result; - result = f.getHasProperty(PROP_GRAB_FOLLOWS_CONTROLLER) ? result + "grab.FollowsController " : result; - result = f.getHasProperty(PROP_GRAB_TRIGGERABLE) ? result + "grab.Triggerable " : result; - result = f.getHasProperty(PROP_GRAB_EQUIPPABLE) ? result + "grab.Equippable " : result; - result = - f.getHasProperty(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET) ? result + "grab.LeftEquippablePositionOffset " : result; - result = - f.getHasProperty(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET) ? result + "grab.LeftEquippableRotationOffset " : result; - result = - f.getHasProperty(PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET) ? result + "grab.RightEquippablePositionOffset " : result; - result = - f.getHasProperty(PROP_GRAB_RIGHT_EQUIPPABLE_ROTATION_OFFSET) ? result + "grab.RightEquippableRotationOffset " : result; - result = f.getHasProperty(PROP_GRAB_EQUIPPABLE_INDICATOR_URL) ? result + "grab.EquippableIndicatorURL " : result; - result = f.getHasProperty(PROP_GRAB_EQUIPPABLE_INDICATOR_SCALE) ? result + "grab.EquippableIndicatorScale " : result; - result = f.getHasProperty(PROP_GRAB_EQUIPPABLE_INDICATOR_OFFSET) ? result + "grab.EquippableIndicatorOffset " : result; - - result += "]"; - dbg.nospace() << result; - return dbg; -} diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 49e5980ffe..5e4b27858c 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -18,255 +18,34 @@ enum EntityPropertyList { PROP_PAGED_PROPERTY, PROP_CUSTOM_PROPERTIES_INCLUDED, - // these properties are supported by the EntityItem base class + // Core properties + PROP_SIMULATION_OWNER, PROP_VISIBLE, - PROP_CAN_CAST_SHADOW, + PROP_NAME, + PROP_LOCKED, + PROP_USER_DATA, + PROP_HREF, + PROP_DESCRIPTION, PROP_POSITION, PROP_DIMENSIONS, PROP_ROTATION, - PROP_DENSITY, - PROP_VELOCITY, - PROP_GRAVITY, - PROP_DAMPING, - PROP_LIFETIME, - PROP_SCRIPT, - - // these properties are supported by some derived classes - PROP_COLOR, - - // these are used by models only - PROP_MODEL_URL, - PROP_ANIMATION_URL, - PROP_ANIMATION_FPS, - PROP_ANIMATION_FRAME_INDEX, - PROP_ANIMATION_PLAYING, - PROP_ANIMATION_ALLOW_TRANSLATION, - PROP_RELAY_PARENT_JOINTS, - - // these properties are supported by the EntityItem base class PROP_REGISTRATION_POINT, - PROP_ANGULAR_VELOCITY, - PROP_ANGULAR_DAMPING, - PROP_COLLISIONLESS, - PROP_DYNAMIC, // 24 - - // property used by Light entity - PROP_IS_SPOTLIGHT, - PROP_DIFFUSE_COLOR, - PROP_AMBIENT_COLOR_UNUSED, // FIXME - No longer used, can remove and bump protocol - PROP_SPECULAR_COLOR_UNUSED, // FIXME - No longer used, can remove and bump protocol - PROP_INTENSITY, // Previously PROP_CONSTANT_ATTENUATION - PROP_LINEAR_ATTENUATION_UNUSED, - PROP_QUADRATIC_ATTENUATION_UNUSED, - PROP_EXPONENT, - PROP_CUTOFF, - - // available to all entities - PROP_LOCKED, // 34 - - PROP_TEXTURES, // used by Model entities - PROP_ANIMATION_SETTINGS_UNUSED, // FIXME - No longer used, can remove and bump protocol - PROP_USER_DATA, // all entities -- 37 - PROP_SHAPE_TYPE, // used by Model + zones entities - - // used by ParticleEffect entities - PROP_MAX_PARTICLES, // 39 - PROP_LIFESPAN, // 40 -- used by all entities - PROP_EMIT_RATE, - PROP_EMIT_SPEED, - PROP_EMIT_STRENGTH, - PROP_EMIT_ACCELERATION, // FIXME - doesn't seem to get set in mark all changed???? - PROP_PARTICLE_RADIUS, // 45!! - - PROP_COMPOUND_SHAPE_URL, // used by Model + zones entities - PROP_MARKETPLACE_ID, // all entities - PROP_ACCELERATION, // all entities - PROP_SIMULATION_OWNER, // formerly known as PROP_SIMULATOR_ID - PROP_NAME, // all entities -- 50 - PROP_COLLISION_SOUND_URL, - PROP_RESTITUTION, - PROP_FRICTION, // 53 - - PROP_VOXEL_VOLUME_SIZE, - PROP_VOXEL_DATA, - PROP_VOXEL_SURFACE_STYLE, - - //for lines - PROP_LINE_WIDTH, - PROP_LINE_POINTS, - - // used by hyperlinks - PROP_HREF, - PROP_DESCRIPTION, // 61 - - PROP_FACE_CAMERA, - PROP_SCRIPT_TIMESTAMP, - - PROP_ACTION_DATA, - - PROP_X_TEXTURE_URL, // used by PolyVox - PROP_Y_TEXTURE_URL, // used by PolyVox - PROP_Z_TEXTURE_URL, // used by PolyVox - - // Used by PolyLine entity - PROP_NORMALS, - PROP_STROKE_COLORS, - PROP_STROKE_WIDTHS, - PROP_IS_UV_MODE_STRETCH, - - // used by particles - PROP_SPEED_SPREAD, - PROP_ACCELERATION_SPREAD, - - PROP_X_N_NEIGHBOR_ID, // used by PolyVox - PROP_Y_N_NEIGHBOR_ID, // used by PolyVox - PROP_Z_N_NEIGHBOR_ID, // used by PolyVox - PROP_X_P_NEIGHBOR_ID, // used by PolyVox - PROP_Y_P_NEIGHBOR_ID, // used by PolyVox - PROP_Z_P_NEIGHBOR_ID, // used by PolyVox - - // Used by particles - PROP_RADIUS_SPREAD, - PROP_RADIUS_START, - PROP_RADIUS_FINISH, - - PROP_ALPHA, // Supported by some derived classes - - //Used by particles - PROP_COLOR_SPREAD, - PROP_COLOR_START, - PROP_COLOR_FINISH, - PROP_ALPHA_SPREAD, - PROP_ALPHA_START, - PROP_ALPHA_FINISH, - PROP_EMIT_ORIENTATION, - PROP_EMIT_DIMENSIONS, - PROP_EMIT_RADIUS_START, - PROP_POLAR_START, - PROP_POLAR_FINISH, - PROP_AZIMUTH_START, - PROP_AZIMUTH_FINISH, - - PROP_ANIMATION_LOOP, - PROP_ANIMATION_FIRST_FRAME, - PROP_ANIMATION_LAST_FRAME, - PROP_ANIMATION_HOLD, - PROP_ANIMATION_START_AUTOMATICALLY, - - PROP_EMITTER_SHOULD_TRAIL, - + PROP_CREATED, + PROP_LAST_EDITED_BY, + PROP_ENTITY_HOST_TYPE, // not sent over the wire + PROP_OWNING_AVATAR_ID, // not sent over the wire PROP_PARENT_ID, PROP_PARENT_JOINT_INDEX, - - PROP_LOCAL_POSITION, // only used to convert values to and from scripts - PROP_LOCAL_ROTATION, // only used to convert values to and from scripts - - PROP_QUERY_AA_CUBE, // how the EntityTree considers the size and position on an entity - - // ModelEntity joint state - PROP_JOINT_ROTATIONS_SET, - PROP_JOINT_ROTATIONS, - PROP_JOINT_TRANSLATIONS_SET, - PROP_JOINT_TRANSLATIONS, - - PROP_COLLISION_MASK, // one byte of collision group flags - - PROP_FALLOFF_RADIUS, // for Light entity - - PROP_FLYING_ALLOWED, // can avatars in a zone fly? - PROP_GHOSTING_ALLOWED, // can avatars in a zone turn off physics? - - PROP_ENTITY_HOST_TYPE, // doesn't go over wire - PROP_OWNING_AVATAR_ID, // doesn't go over wire - - PROP_SHAPE, - PROP_DPI, - - PROP_LOCAL_VELOCITY, // only used to convert values to and from scripts - PROP_LOCAL_ANGULAR_VELOCITY, // only used to convert values to and from scripts - - PROP_LAST_EDITED_BY, - - PROP_SERVER_SCRIPTS, - - PROP_FILTER_URL, - - // Certificable Properties - PROP_ITEM_NAME, - PROP_ITEM_DESCRIPTION, - PROP_ITEM_CATEGORIES, - PROP_ITEM_ARTIST, - PROP_ITEM_LICENSE, - PROP_LIMITED_RUN, - // PROP_MARKETPLACE_ID is above - PROP_EDITION_NUMBER, - PROP_ENTITY_INSTANCE_NUMBER, - PROP_CERTIFICATE_ID, - PROP_STATIC_CERTIFICATE_VERSION, - - PROP_CLONEABLE, - PROP_CLONE_LIFETIME, - PROP_CLONE_LIMIT, - PROP_CLONE_DYNAMIC, - PROP_CLONE_AVATAR_ENTITY, - PROP_CLONE_ORIGIN_ID, - - PROP_HAZE_MODE, - - PROP_KEYLIGHT_COLOR, - PROP_KEYLIGHT_INTENSITY, - PROP_KEYLIGHT_DIRECTION, - PROP_KEYLIGHT_CAST_SHADOW, - - PROP_HAZE_RANGE, - PROP_HAZE_COLOR, - PROP_HAZE_GLARE_COLOR, - PROP_HAZE_ENABLE_GLARE, - PROP_HAZE_GLARE_ANGLE, - - PROP_HAZE_ALTITUDE_EFFECT, - PROP_HAZE_CEILING, - PROP_HAZE_BASE_REF, - - PROP_HAZE_BACKGROUND_BLEND, - - PROP_HAZE_ATTENUATE_KEYLIGHT, - PROP_HAZE_KEYLIGHT_RANGE, - PROP_HAZE_KEYLIGHT_ALTITUDE, - - PROP_KEY_LIGHT_MODE, - PROP_AMBIENT_LIGHT_MODE, - PROP_SKYBOX_MODE, - - PROP_LOCAL_DIMENSIONS, // only used to convert values to and from scripts - - PROP_MATERIAL_URL, - PROP_MATERIAL_MAPPING_MODE, - PROP_MATERIAL_PRIORITY, - PROP_PARENT_MATERIAL_NAME, - PROP_MATERIAL_MAPPING_POS, - PROP_MATERIAL_MAPPING_SCALE, - PROP_MATERIAL_MAPPING_ROT, - PROP_MATERIAL_DATA, - - PROP_VISIBLE_IN_SECONDARY_CAMERA, // not sent over the wire, only used locally - - PROP_PARTICLE_SPIN, - PROP_SPIN_START, - PROP_SPIN_FINISH, - PROP_SPIN_SPREAD, - PROP_PARTICLE_ROTATE_WITH_ENTITY, - - PROP_BLOOM_MODE, - PROP_BLOOM_INTENSITY, - PROP_BLOOM_THRESHOLD, - PROP_BLOOM_SIZE, - + PROP_QUERY_AA_CUBE, + PROP_CAN_CAST_SHADOW, + PROP_VISIBLE_IN_SECONDARY_CAMERA, // not sent over the wire + // Grab PROP_GRAB_GRABBABLE, PROP_GRAB_KINEMATIC, PROP_GRAB_FOLLOWS_CONTROLLER, PROP_GRAB_TRIGGERABLE, PROP_GRAB_EQUIPPABLE, + PROP_GRAB_DELEGATE_TO_PARENT, PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET, @@ -275,40 +54,270 @@ enum EntityPropertyList { PROP_GRAB_EQUIPPABLE_INDICATOR_SCALE, PROP_GRAB_EQUIPPABLE_INDICATOR_OFFSET, - PROP_MATERIAL_REPEAT, + // Physics + PROP_DENSITY, + PROP_VELOCITY, + PROP_ANGULAR_VELOCITY, + PROP_GRAVITY, + PROP_ACCELERATION, + PROP_DAMPING, + PROP_ANGULAR_DAMPING, + PROP_RESTITUTION, + PROP_FRICTION, + PROP_LIFETIME, + PROP_COLLISIONLESS, + PROP_COLLISION_MASK, + PROP_DYNAMIC, + PROP_COLLISION_SOUND_URL, + PROP_ACTION_DATA, + + // Cloning + PROP_CLONEABLE, + PROP_CLONE_LIFETIME, + PROP_CLONE_LIMIT, + PROP_CLONE_DYNAMIC, + PROP_CLONE_AVATAR_ENTITY, + PROP_CLONE_ORIGIN_ID, + + // Scripts + PROP_SCRIPT, + PROP_SCRIPT_TIMESTAMP, + PROP_SERVER_SCRIPTS, + + // Certifiable Properties + PROP_ITEM_NAME, + PROP_ITEM_DESCRIPTION, + PROP_ITEM_CATEGORIES, + PROP_ITEM_ARTIST, + PROP_ITEM_LICENSE, + PROP_LIMITED_RUN, + PROP_MARKETPLACE_ID, + PROP_EDITION_NUMBER, + PROP_ENTITY_INSTANCE_NUMBER, + PROP_CERTIFICATE_ID, + PROP_STATIC_CERTIFICATE_VERSION, + + // Used to convert values to and from scripts + PROP_LOCAL_POSITION, + PROP_LOCAL_ROTATION, + PROP_LOCAL_VELOCITY, + PROP_LOCAL_ANGULAR_VELOCITY, + PROP_LOCAL_DIMENSIONS, + + // These properties are used by multiple subtypes but aren't in the base EntityItem + PROP_SHAPE_TYPE, + PROP_COMPOUND_SHAPE_URL, + PROP_COLOR, + PROP_ALPHA, + PROP_TEXTURES, //////////////////////////////////////////////////////////////////////////////////////////////////// - // ATTENTION: add new properties to end of list just ABOVE this line + // ATTENTION: add new shared EntityItem properties to the list ABOVE this line + //////////////////////////////////////////////////////////////////////////////////////////////////// + + // We need as many of these as the number of unique properties of a derived EntityItem class + PROP_DERIVED_0, + PROP_DERIVED_1, + PROP_DERIVED_2, + PROP_DERIVED_3, + PROP_DERIVED_4, + PROP_DERIVED_5, + PROP_DERIVED_6, + PROP_DERIVED_7, + PROP_DERIVED_8, + PROP_DERIVED_9, + PROP_DERIVED_10, + PROP_DERIVED_11, + PROP_DERIVED_12, + PROP_DERIVED_13, + PROP_DERIVED_14, + PROP_DERIVED_15, + PROP_DERIVED_16, + PROP_DERIVED_17, + PROP_DERIVED_18, + PROP_DERIVED_19, + PROP_DERIVED_20, + PROP_DERIVED_21, + PROP_DERIVED_22, + PROP_DERIVED_23, + PROP_DERIVED_24, + PROP_DERIVED_25, + PROP_DERIVED_26, + PROP_DERIVED_27, + PROP_DERIVED_28, + PROP_DERIVED_29, + PROP_DERIVED_30, + PROP_AFTER_LAST_ITEM, - //////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////// - // WARNING! Do not add props here unless you intentionally mean to reuse PROP_ indexes + // WARNING! Do not add props here unless you intentionally mean to reuse PROP_DERIVED_X indexes // - // These properties of TextEntity piggy back off of properties of ModelEntities, the type doesn't matter - // since the derived class knows how to interpret it's own properties and knows the types it expects - PROP_TEXT_COLOR = PROP_COLOR, - PROP_TEXT = PROP_MODEL_URL, - PROP_LINE_HEIGHT = PROP_ANIMATION_URL, - PROP_BACKGROUND_COLOR = PROP_ANIMATION_FPS, - PROP_COLLISION_MODEL_URL_OLD_VERSION = PROP_ANIMATION_FPS + 1, - - // Aliases/Piggyback properties for Zones. These properties intentionally reuse the enum values for - // other properties which will never overlap with each other. We do this so that we don't have to expand + // These properties intentionally reuse the enum values for other properties which will never overlap with each other. We do this so that we don't have to expand // the size of the properties bitflags mask - PROP_SKYBOX_COLOR = PROP_ANIMATION_URL, - PROP_SKYBOX_URL = PROP_ANIMATION_FPS, + // + // Only add properties here that are only used by one subclass. Otherwise, they should go above to prevent collisions - PROP_AMBIENT_LIGHT_INTENSITY = PROP_CUTOFF, - PROP_AMBIENT_LIGHT_URL = PROP_ANIMATION_PLAYING, + // Particles + PROP_MAX_PARTICLES = PROP_DERIVED_0, + PROP_LIFESPAN = PROP_DERIVED_1, + PROP_EMITTING_PARTICLES = PROP_DERIVED_2, + PROP_EMIT_RATE = PROP_DERIVED_3, + PROP_EMIT_SPEED = PROP_DERIVED_4, + PROP_SPEED_SPREAD = PROP_DERIVED_5, + PROP_EMIT_ORIENTATION = PROP_DERIVED_6, + PROP_EMIT_DIMENSIONS = PROP_DERIVED_7, + PROP_ACCELERATION_SPREAD = PROP_DERIVED_8, + PROP_POLAR_START = PROP_DERIVED_9, + PROP_POLAR_FINISH = PROP_DERIVED_10, + PROP_AZIMUTH_START = PROP_DERIVED_11, + PROP_AZIMUTH_FINISH = PROP_DERIVED_12, + PROP_EMIT_RADIUS_START = PROP_DERIVED_13, + PROP_EMIT_ACCELERATION = PROP_DERIVED_14, + PROP_PARTICLE_RADIUS = PROP_DERIVED_15, + PROP_RADIUS_SPREAD = PROP_DERIVED_16, + PROP_RADIUS_START = PROP_DERIVED_17, + PROP_RADIUS_FINISH = PROP_DERIVED_18, + PROP_COLOR_SPREAD = PROP_DERIVED_19, + PROP_COLOR_START = PROP_DERIVED_20, + PROP_COLOR_FINISH = PROP_DERIVED_21, + PROP_ALPHA_SPREAD = PROP_DERIVED_22, + PROP_ALPHA_START = PROP_DERIVED_23, + PROP_ALPHA_FINISH = PROP_DERIVED_24, + PROP_EMITTER_SHOULD_TRAIL = PROP_DERIVED_25, + PROP_PARTICLE_SPIN = PROP_DERIVED_26, + PROP_SPIN_START = PROP_DERIVED_27, + PROP_SPIN_FINISH = PROP_DERIVED_28, + PROP_SPIN_SPREAD = PROP_DERIVED_29, + PROP_PARTICLE_ROTATE_WITH_ENTITY = PROP_DERIVED_30, - // Aliases/Piggyback properties for Web. These properties intentionally reuse the enum values for - // other properties which will never overlap with each other. - PROP_SOURCE_URL = PROP_MODEL_URL, + // Model + PROP_MODEL_URL = PROP_DERIVED_0, + PROP_JOINT_ROTATIONS_SET = PROP_DERIVED_1, + PROP_JOINT_ROTATIONS = PROP_DERIVED_2, + PROP_JOINT_TRANSLATIONS_SET = PROP_DERIVED_3, + PROP_JOINT_TRANSLATIONS = PROP_DERIVED_4, + PROP_RELAY_PARENT_JOINTS = PROP_DERIVED_5, + // Animation + PROP_ANIMATION_URL = PROP_DERIVED_6, + PROP_ANIMATION_ALLOW_TRANSLATION = PROP_DERIVED_7, + PROP_ANIMATION_FPS = PROP_DERIVED_8, + PROP_ANIMATION_FRAME_INDEX = PROP_DERIVED_9, + PROP_ANIMATION_PLAYING = PROP_DERIVED_10, + PROP_ANIMATION_LOOP = PROP_DERIVED_11, + PROP_ANIMATION_FIRST_FRAME = PROP_DERIVED_12, + PROP_ANIMATION_LAST_FRAME = PROP_DERIVED_13, + PROP_ANIMATION_HOLD = PROP_DERIVED_14, - // Aliases/Piggyback properties for Particle Emmitter. These properties intentionally reuse the enum values for - // other properties which will never overlap with each other. - PROP_EMITTING_PARTICLES = PROP_ANIMATION_PLAYING, + // Light + PROP_IS_SPOTLIGHT = PROP_DERIVED_0, + PROP_INTENSITY = PROP_DERIVED_1, + PROP_EXPONENT = PROP_DERIVED_2, + PROP_CUTOFF = PROP_DERIVED_3, + PROP_FALLOFF_RADIUS = PROP_DERIVED_4, + + // Text + PROP_TEXT = PROP_DERIVED_0, + PROP_LINE_HEIGHT = PROP_DERIVED_1, + PROP_TEXT_COLOR = PROP_DERIVED_2, + PROP_TEXT_ALPHA = PROP_DERIVED_3, + PROP_BACKGROUND_COLOR = PROP_DERIVED_4, + PROP_BACKGROUND_ALPHA = PROP_DERIVED_5, + PROP_BILLBOARD_MODE = PROP_DERIVED_6, + PROP_LEFT_MARGIN = PROP_DERIVED_7, + PROP_RIGHT_MARGIN = PROP_DERIVED_8, + PROP_TOP_MARGIN = PROP_DERIVED_9, + PROP_BOTTOM_MARGIN = PROP_DERIVED_10, + + // Zone + // Keylight + PROP_KEYLIGHT_COLOR = PROP_DERIVED_0, + PROP_KEYLIGHT_INTENSITY = PROP_DERIVED_1, + PROP_KEYLIGHT_DIRECTION = PROP_DERIVED_2, + PROP_KEYLIGHT_CAST_SHADOW = PROP_DERIVED_3, + // Ambient light + PROP_AMBIENT_LIGHT_INTENSITY = PROP_DERIVED_4, + PROP_AMBIENT_LIGHT_URL = PROP_DERIVED_5, + // Skybox + PROP_SKYBOX_COLOR = PROP_DERIVED_6, + PROP_SKYBOX_URL = PROP_DERIVED_7, + // Haze + PROP_HAZE_RANGE = PROP_DERIVED_8, + PROP_HAZE_COLOR = PROP_DERIVED_9, + PROP_HAZE_GLARE_COLOR = PROP_DERIVED_10, + PROP_HAZE_ENABLE_GLARE = PROP_DERIVED_11, + PROP_HAZE_GLARE_ANGLE = PROP_DERIVED_12, + PROP_HAZE_ALTITUDE_EFFECT = PROP_DERIVED_13, + PROP_HAZE_CEILING = PROP_DERIVED_14, + PROP_HAZE_BASE_REF = PROP_DERIVED_15, + PROP_HAZE_BACKGROUND_BLEND = PROP_DERIVED_16, + PROP_HAZE_ATTENUATE_KEYLIGHT = PROP_DERIVED_17, + PROP_HAZE_KEYLIGHT_RANGE = PROP_DERIVED_18, + PROP_HAZE_KEYLIGHT_ALTITUDE = PROP_DERIVED_19, + // Bloom + PROP_BLOOM_INTENSITY = PROP_DERIVED_20, + PROP_BLOOM_THRESHOLD = PROP_DERIVED_21, + PROP_BLOOM_SIZE = PROP_DERIVED_22, + PROP_FLYING_ALLOWED = PROP_DERIVED_23, + PROP_GHOSTING_ALLOWED = PROP_DERIVED_24, + PROP_FILTER_URL = PROP_DERIVED_25, + PROP_KEY_LIGHT_MODE = PROP_DERIVED_26, + PROP_AMBIENT_LIGHT_MODE = PROP_DERIVED_27, + PROP_SKYBOX_MODE = PROP_DERIVED_28, + PROP_HAZE_MODE = PROP_DERIVED_29, + PROP_BLOOM_MODE = PROP_DERIVED_30, + + // Polyvox + PROP_VOXEL_VOLUME_SIZE = PROP_DERIVED_0, + PROP_VOXEL_DATA = PROP_DERIVED_1, + PROP_VOXEL_SURFACE_STYLE = PROP_DERIVED_2, + PROP_X_TEXTURE_URL = PROP_DERIVED_3, + PROP_Y_TEXTURE_URL = PROP_DERIVED_4, + PROP_Z_TEXTURE_URL = PROP_DERIVED_5, + PROP_X_N_NEIGHBOR_ID = PROP_DERIVED_6, + PROP_Y_N_NEIGHBOR_ID = PROP_DERIVED_7, + PROP_Z_N_NEIGHBOR_ID = PROP_DERIVED_8, + PROP_X_P_NEIGHBOR_ID = PROP_DERIVED_9, + PROP_Y_P_NEIGHBOR_ID = PROP_DERIVED_10, + PROP_Z_P_NEIGHBOR_ID = PROP_DERIVED_11, + + // Web + PROP_SOURCE_URL = PROP_DERIVED_0, + PROP_DPI = PROP_DERIVED_1, + + // Polyline + PROP_LINE_POINTS = PROP_DERIVED_0, + PROP_STROKE_WIDTHS = PROP_DERIVED_1, + PROP_STROKE_NORMALS = PROP_DERIVED_2, + PROP_STROKE_COLORS = PROP_DERIVED_3, + PROP_IS_UV_MODE_STRETCH = PROP_DERIVED_4, + PROP_LINE_GLOW = PROP_DERIVED_5, + PROP_LINE_FACE_CAMERA = PROP_DERIVED_6, + + // Shape + PROP_SHAPE = PROP_DERIVED_0, + + // Material + PROP_MATERIAL_URL = PROP_DERIVED_0, + PROP_MATERIAL_MAPPING_MODE = PROP_DERIVED_1, + PROP_MATERIAL_PRIORITY = PROP_DERIVED_2, + PROP_PARENT_MATERIAL_NAME = PROP_DERIVED_3, + PROP_MATERIAL_MAPPING_POS = PROP_DERIVED_4, + PROP_MATERIAL_MAPPING_SCALE = PROP_DERIVED_5, + PROP_MATERIAL_MAPPING_ROT = PROP_DERIVED_6, + PROP_MATERIAL_DATA = PROP_DERIVED_7, + PROP_MATERIAL_REPEAT = PROP_DERIVED_8, + + // Image + PROP_IMAGE_URL = PROP_DERIVED_0, + PROP_EMISSIVE = PROP_DERIVED_1, + PROP_KEEP_ASPECT_RATIO = PROP_DERIVED_2, + PROP_SUB_IMAGE = PROP_DERIVED_3, + + // Grid + PROP_GRID_FOLLOW_CAMERA = PROP_DERIVED_0, + PROP_MAJOR_GRID_EVERY = PROP_DERIVED_1, + PROP_MINOR_GRID_EVERY = PROP_DERIVED_2, // WARNING!!! DO NOT ADD PROPS_xxx here unless you really really meant to.... Add them UP above }; @@ -319,10 +328,4 @@ typedef PropertyFlags<EntityPropertyList> EntityPropertyFlags; // one greater than the last item property due to the enum's auto-incrementing. extern EntityPropertyList PROP_LAST_ITEM; -QString EntityPropertyFlagsToString(EntityPropertyFlags propertiesFlags); - - -QDebug& operator<<(QDebug& dbg, const EntityPropertyFlags& f); - - #endif // hifi_EntityPropertyFlags_h diff --git a/libraries/entities/src/EntityPsuedoPropertyFlags.h b/libraries/entities/src/EntityPsuedoPropertyFlags.h index 26c2a14015..d6326ee1f5 100644 --- a/libraries/entities/src/EntityPsuedoPropertyFlags.h +++ b/libraries/entities/src/EntityPsuedoPropertyFlags.h @@ -23,7 +23,6 @@ namespace EntityPsuedoPropertyFlag { FlagsActive, ID, Type, - Created, Age, AgeAsText, LastEdited, @@ -31,9 +30,10 @@ namespace EntityPsuedoPropertyFlag { OriginalTextures, RenderInfo, ClientOnly, - OwningAvatarID, AvatarEntity, LocalEntity, + FaceCamera, + IsFacingAvatar, NumFlags }; diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 7f7a628890..697d583de7 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -492,10 +492,9 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties propertiesWithSimID.setCollisionless(true); } + // the created time will be set in EntityTree::addEntity by recordCreationTime() propertiesWithSimID.setLastEditedBy(sessionID); - propertiesWithSimID.setActionData(QByteArray()); - bool scalesWithParent = propertiesWithSimID.getScalesWithParent(); propertiesWithSimID = convertPropertiesFromScriptSemantics(propertiesWithSimID, scalesWithParent); @@ -676,8 +675,6 @@ QScriptValue EntityScriptingInterface::getMultipleEntityPropertiesInternal(QScri psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::ID); } else if (extendedPropertyString == "type") { psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::Type); - } else if (extendedPropertyString == "created") { - psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::Created); } else if (extendedPropertyString == "age") { psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::Age); } else if (extendedPropertyString == "ageAsText") { @@ -692,12 +689,14 @@ QScriptValue EntityScriptingInterface::getMultipleEntityPropertiesInternal(QScri psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::RenderInfo); } else if (extendedPropertyString == "clientOnly") { psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::ClientOnly); - } else if (extendedPropertyString == "owningAvatarID") { - psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::OwningAvatarID); } else if (extendedPropertyString == "avatarEntity") { psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::AvatarEntity); } else if (extendedPropertyString == "localEntity") { psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::LocalEntity); + } else if (extendedPropertyString == "faceCamera") { + psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::FaceCamera); + } else if (extendedPropertyString == "isFacingAvatar") { + psuedoPropertyFlags.set(EntityPsuedoPropertyFlag::IsFacingAvatar); } }; @@ -812,6 +811,10 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& QString previousUserdata; if (entity) { + if (properties.hasTransformOrVelocityChanges() && entity->hasGrabs()) { + // if an entity is grabbed, the grab will override any position changes + properties.clearTransformOrVelocityChanges(); + } if (properties.hasSimulationRestrictedChanges()) { if (_bidOnSimulationOwnership) { // flag for simulation ownership, or upgrade existing ownership priority @@ -851,8 +854,6 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& } properties.setOwningAvatarID(entity->getOwningAvatarID()); - properties.setActionData(entity->getDynamicData()); - // make sure the properties has a type, so that the encode can know which properties to include properties.setType(entity->getType()); @@ -1081,13 +1082,10 @@ QUuid EntityScriptingInterface::findClosestEntity(const glm::vec3& center, float EntityItemID result; if (_entityTree) { - EntityItemPointer closestEntity; + unsigned int searchFilter = PickFilter::getBitMask(PickFilter::FlagBit::DOMAIN_ENTITIES) | PickFilter::getBitMask(PickFilter::FlagBit::AVATAR_ENTITIES); _entityTree->withReadLock([&] { - closestEntity = _entityTree->findClosestEntity(center, radius); + result = _entityTree->evalClosestEntity(center, radius, PickFilter(searchFilter)); }); - if (closestEntity) { - result = closestEntity->getEntityItemID(); - } } return result; } @@ -1106,14 +1104,10 @@ QVector<QUuid> EntityScriptingInterface::findEntities(const glm::vec3& center, f QVector<QUuid> result; if (_entityTree) { - QVector<EntityItemPointer> entities; + unsigned int searchFilter = PickFilter::getBitMask(PickFilter::FlagBit::DOMAIN_ENTITIES) | PickFilter::getBitMask(PickFilter::FlagBit::AVATAR_ENTITIES); _entityTree->withReadLock([&] { - _entityTree->findEntities(center, radius, entities); + _entityTree->evalEntitiesInSphere(center, radius, PickFilter(searchFilter), result); }); - - foreach (EntityItemPointer entity, entities) { - result << entity->getEntityItemID(); - } } return result; } @@ -1123,15 +1117,11 @@ QVector<QUuid> EntityScriptingInterface::findEntitiesInBox(const glm::vec3& corn QVector<QUuid> result; if (_entityTree) { - QVector<EntityItemPointer> entities; + unsigned int searchFilter = PickFilter::getBitMask(PickFilter::FlagBit::DOMAIN_ENTITIES) | PickFilter::getBitMask(PickFilter::FlagBit::AVATAR_ENTITIES); _entityTree->withReadLock([&] { AABox box(corner, dimensions); - _entityTree->findEntities(box, entities); + _entityTree->evalEntitiesInBox(box, PickFilter(searchFilter), result); }); - - foreach (EntityItemPointer entity, entities) { - result << entity->getEntityItemID(); - } } return result; } @@ -1166,14 +1156,10 @@ QVector<QUuid> EntityScriptingInterface::findEntitiesInFrustum(QVariantMap frust viewFrustum.calculate(); if (_entityTree) { - QVector<EntityItemPointer> entities; + unsigned int searchFilter = PickFilter::getBitMask(PickFilter::FlagBit::DOMAIN_ENTITIES) | PickFilter::getBitMask(PickFilter::FlagBit::AVATAR_ENTITIES); _entityTree->withReadLock([&] { - _entityTree->findEntities(viewFrustum, entities); + _entityTree->evalEntitiesInFrustum(viewFrustum, PickFilter(searchFilter), result); }); - - foreach(EntityItemPointer entity, entities) { - result << entity->getEntityItemID(); - } } } @@ -1185,86 +1171,64 @@ QVector<QUuid> EntityScriptingInterface::findEntitiesByType(const QString entity QVector<QUuid> result; if (_entityTree) { - QVector<EntityItemPointer> entities; + unsigned int searchFilter = PickFilter::getBitMask(PickFilter::FlagBit::DOMAIN_ENTITIES) | PickFilter::getBitMask(PickFilter::FlagBit::AVATAR_ENTITIES); _entityTree->withReadLock([&] { - _entityTree->findEntities(center, radius, entities); + _entityTree->evalEntitiesInSphereWithType(center, radius, type, PickFilter(searchFilter), result); }); - - foreach(EntityItemPointer entity, entities) { - if (entity->getType() == type) { - result << entity->getEntityItemID().toString(); - } - } } return result; } QVector<QUuid> EntityScriptingInterface::findEntitiesByName(const QString entityName, const glm::vec3& center, float radius, bool caseSensitiveSearch) const { - QVector<QUuid> result; if (_entityTree) { - QVector<EntityItemPointer> entities; _entityTree->withReadLock([&] { - _entityTree->findEntities(center, radius, entities); + unsigned int searchFilter = PickFilter::getBitMask(PickFilter::FlagBit::DOMAIN_ENTITIES) | PickFilter::getBitMask(PickFilter::FlagBit::AVATAR_ENTITIES); + _entityTree->evalEntitiesInSphereWithName(center, radius, entityName, caseSensitiveSearch, PickFilter(searchFilter), result); }); - - if (caseSensitiveSearch) { - foreach(EntityItemPointer entity, entities) { - if (entity->getName() == entityName) { - result << entity->getEntityItemID(); - } - } - - } else { - QString entityNameLowerCase = entityName.toLower(); - - foreach(EntityItemPointer entity, entities) { - QString entityItemLowerCase = entity->getName().toLower(); - if (entityItemLowerCase == entityNameLowerCase) { - result << entity->getEntityItemID(); - } - } - } } return result; } -RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersection(const PickRay& ray, bool precisionPicking, - const QScriptValue& entityIdsToInclude, const QScriptValue& entityIdsToDiscard, bool visibleOnly, bool collidableOnly) { +RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersection(const PickRay& ray, bool precisionPicking, + const QScriptValue& entityIdsToInclude, const QScriptValue& entityIdsToDiscard, bool visibleOnly, bool collidableOnly) const { + PROFILE_RANGE(script_entities, __FUNCTION__); QVector<EntityItemID> entitiesToInclude = qVectorEntityItemIDFromScriptValue(entityIdsToInclude); QVector<EntityItemID> entitiesToDiscard = qVectorEntityItemIDFromScriptValue(entityIdsToDiscard); - return findRayIntersectionVector(ray, precisionPicking, entitiesToInclude, entitiesToDiscard, visibleOnly, collidableOnly); + unsigned int searchFilter = PickFilter::getBitMask(PickFilter::FlagBit::DOMAIN_ENTITIES) | PickFilter::getBitMask(PickFilter::FlagBit::AVATAR_ENTITIES); + + if (!precisionPicking) { + searchFilter = searchFilter | PickFilter::getBitMask(PickFilter::FlagBit::COARSE); + } + + if (visibleOnly) { + searchFilter = searchFilter | PickFilter::getBitMask(PickFilter::FlagBit::VISIBLE); + } + + if (collidableOnly) { + searchFilter = searchFilter | PickFilter::getBitMask(PickFilter::FlagBit::COLLIDABLE); + } + + return evalRayIntersectionWorker(ray, Octree::Lock, PickFilter(searchFilter), entitiesToInclude, entitiesToDiscard); } -RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionVector(const PickRay& ray, bool precisionPicking, - const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly) { +RayToEntityIntersectionResult EntityScriptingInterface::evalRayIntersectionVector(const PickRay& ray, PickFilter searchFilter, + const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard) { PROFILE_RANGE(script_entities, __FUNCTION__); - return findRayIntersectionWorker(ray, Octree::Lock, precisionPicking, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly); + return evalRayIntersectionWorker(ray, Octree::Lock, searchFilter, entityIdsToInclude, entityIdsToDiscard); } -// FIXME - we should remove this API and encourage all users to use findRayIntersection() instead. We've changed -// findRayIntersection() to be blocking because it never makes sense for a script to get back a non-answer -RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionBlocking(const PickRay& ray, bool precisionPicking, - const QScriptValue& entityIdsToInclude, const QScriptValue& entityIdsToDiscard) { - - qWarning() << "Entities.findRayIntersectionBlocking() is obsolete, use Entities.findRayIntersection() instead."; - const QVector<EntityItemID>& entitiesToInclude = qVectorEntityItemIDFromScriptValue(entityIdsToInclude); - const QVector<EntityItemID> entitiesToDiscard = qVectorEntityItemIDFromScriptValue(entityIdsToDiscard); - return findRayIntersectionWorker(ray, Octree::Lock, precisionPicking, entitiesToInclude, entitiesToDiscard); -} - -RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionWorker(const PickRay& ray, - Octree::lockType lockType, bool precisionPicking, const QVector<EntityItemID>& entityIdsToInclude, - const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly) { - +RayToEntityIntersectionResult EntityScriptingInterface::evalRayIntersectionWorker(const PickRay& ray, + Octree::lockType lockType, PickFilter searchFilter, const QVector<EntityItemID>& entityIdsToInclude, + const QVector<EntityItemID>& entityIdsToDiscard) const { RayToEntityIntersectionResult result; if (_entityTree) { OctreeElementPointer element; - result.entityID = _entityTree->findRayIntersection(ray.origin, ray.direction, - entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly, precisionPicking, + result.entityID = _entityTree->evalRayIntersection(ray.origin, ray.direction, + entityIdsToInclude, entityIdsToDiscard, searchFilter, element, result.distance, result.face, result.surfaceNormal, result.extraInfo, lockType, &result.accurate); result.intersects = !result.entityID.isNull(); @@ -1275,23 +1239,22 @@ RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersectionWorke return result; } -ParabolaToEntityIntersectionResult EntityScriptingInterface::findParabolaIntersectionVector(const PickParabola& parabola, bool precisionPicking, - const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly) { +ParabolaToEntityIntersectionResult EntityScriptingInterface::evalParabolaIntersectionVector(const PickParabola& parabola, PickFilter searchFilter, + const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard) { PROFILE_RANGE(script_entities, __FUNCTION__); - return findParabolaIntersectionWorker(parabola, Octree::Lock, precisionPicking, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly); + return evalParabolaIntersectionWorker(parabola, Octree::Lock, searchFilter, entityIdsToInclude, entityIdsToDiscard); } -ParabolaToEntityIntersectionResult EntityScriptingInterface::findParabolaIntersectionWorker(const PickParabola& parabola, - Octree::lockType lockType, bool precisionPicking, const QVector<EntityItemID>& entityIdsToInclude, - const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly) { - +ParabolaToEntityIntersectionResult EntityScriptingInterface::evalParabolaIntersectionWorker(const PickParabola& parabola, + Octree::lockType lockType, PickFilter searchFilter, const QVector<EntityItemID>& entityIdsToInclude, + const QVector<EntityItemID>& entityIdsToDiscard) const { ParabolaToEntityIntersectionResult result; if (_entityTree) { OctreeElementPointer element; - result.entityID = _entityTree->findParabolaIntersection(parabola, - entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly, precisionPicking, + result.entityID = _entityTree->evalParabolaIntersection(parabola, + entityIdsToInclude, entityIdsToDiscard, searchFilter, element, result.intersection, result.distance, result.parabolicDistance, result.face, result.surfaceNormal, result.extraInfo, lockType, &result.accurate); result.intersects = !result.entityID.isNull(); @@ -2302,3 +2265,132 @@ bool EntityScriptingInterface::verifyStaticCertificateProperties(const QUuid& en } return result; } + +const EntityPropertyInfo EntityScriptingInterface::getPropertyInfo(const QString& propertyName) const { + EntityPropertyInfo propertyInfo; + EntityItemProperties::getPropertyInfo(propertyName, propertyInfo); + return propertyInfo; +} + +glm::vec3 EntityScriptingInterface::worldToLocalPosition(glm::vec3 worldPosition, const QUuid& parentID, + int parentJointIndex, bool scalesWithParent) { + bool success; + glm::vec3 localPosition = SpatiallyNestable::worldToLocal(worldPosition, parentID, parentJointIndex, + scalesWithParent, success); + if (success) { + return localPosition; + } else { + return glm::vec3(0.0f); + } +} + +glm::quat EntityScriptingInterface::worldToLocalRotation(glm::quat worldRotation, const QUuid& parentID, + int parentJointIndex, bool scalesWithParent) { + bool success; + glm::quat localRotation = SpatiallyNestable::worldToLocal(worldRotation, parentID, parentJointIndex, + scalesWithParent, success); + if (success) { + return localRotation; + } else { + return glm::quat(); + } +} + +glm::vec3 EntityScriptingInterface::worldToLocalVelocity(glm::vec3 worldVelocity, const QUuid& parentID, + int parentJointIndex, bool scalesWithParent) { + bool success; + glm::vec3 localVelocity = SpatiallyNestable::worldToLocalVelocity(worldVelocity, parentID, parentJointIndex, + scalesWithParent, success); + if (success) { + return localVelocity; + } else { + return glm::vec3(0.0f); + } +} + +glm::vec3 EntityScriptingInterface::worldToLocalAngularVelocity(glm::vec3 worldAngularVelocity, const QUuid& parentID, + int parentJointIndex, bool scalesWithParent) { + bool success; + glm::vec3 localAngularVelocity = SpatiallyNestable::worldToLocalAngularVelocity(worldAngularVelocity, parentID, + parentJointIndex, scalesWithParent, + success); + if (success) { + return localAngularVelocity; + } else { + return glm::vec3(0.0f); + } +} + +glm::vec3 EntityScriptingInterface::worldToLocalDimensions(glm::vec3 worldDimensions, const QUuid& parentID, + int parentJointIndex, bool scalesWithParent) { + + bool success; + glm::vec3 localDimensions = SpatiallyNestable::worldToLocalDimensions(worldDimensions, parentID, parentJointIndex, + scalesWithParent, success); + if (success) { + return localDimensions; + } else { + return glm::vec3(0.0f); + } +} + +glm::vec3 EntityScriptingInterface::localToWorldPosition(glm::vec3 localPosition, const QUuid& parentID, + int parentJointIndex, bool scalesWithParent) { + bool success; + glm::vec3 worldPosition = SpatiallyNestable::localToWorld(localPosition, parentID, parentJointIndex, + scalesWithParent, success); + if (success) { + return worldPosition; + } else { + return glm::vec3(0.0f); + } +} + +glm::quat EntityScriptingInterface::localToWorldRotation(glm::quat localRotation, const QUuid& parentID, + int parentJointIndex, bool scalesWithParent) { + bool success; + glm::quat worldRotation = SpatiallyNestable::localToWorld(localRotation, parentID, parentJointIndex, + scalesWithParent, success); + if (success) { + return worldRotation; + } else { + return glm::quat(); + } +} + +glm::vec3 EntityScriptingInterface::localToWorldVelocity(glm::vec3 localVelocity, const QUuid& parentID, + int parentJointIndex, bool scalesWithParent) { + bool success; + glm::vec3 worldVelocity = SpatiallyNestable::localToWorldVelocity(localVelocity, parentID, parentJointIndex, + scalesWithParent, success); + if (success) { + return worldVelocity; + } else { + return glm::vec3(0.0f); + } +} + +glm::vec3 EntityScriptingInterface::localToWorldAngularVelocity(glm::vec3 localAngularVelocity, const QUuid& parentID, + int parentJointIndex, bool scalesWithParent) { + bool success; + glm::vec3 worldAngularVelocity = SpatiallyNestable::localToWorldAngularVelocity(localAngularVelocity, + parentID, parentJointIndex, + scalesWithParent, success); + if (success) { + return worldAngularVelocity; + } else { + return glm::vec3(0.0f); + } +} + +glm::vec3 EntityScriptingInterface::localToWorldDimensions(glm::vec3 localDimensions, const QUuid& parentID, + int parentJointIndex, bool scalesWithParent) { + bool success; + glm::vec3 worldDimensions = SpatiallyNestable::localToWorldDimensions(localDimensions, parentID, parentJointIndex, + scalesWithParent, success); + if (success) { + return worldDimensions; + } else { + return glm::vec3(0.0f); + } +} diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 890c666010..0e96cb2d25 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -24,6 +24,7 @@ #include <OctreeScriptingInterface.h> #include <RegisteredMetaTypes.h> #include <PointerEvent.h> +#include <PickFilter.h> #include "PolyVoxEntityItem.h" #include "LineEntityItem.h" @@ -56,8 +57,7 @@ private: }; /**jsdoc - * The result of a {@link PickRay} search using {@link Entities.findRayIntersection|findRayIntersection} or - * {@link Entities.findRayIntersectionBlocking|findRayIntersectionBlocking}. + * The result of a {@link PickRay} search using {@link Entities.findRayIntersection|findRayIntersection}. * @typedef {object} Entities.RayToEntityIntersectionResult * @property {boolean} intersects - <code>true</code> if the {@link PickRay} intersected an entity, otherwise * <code>false</code>. @@ -119,7 +119,6 @@ public: /// handles scripting of Entity commands from JS passed to assigned clients class EntityScriptingInterface : public OctreeScriptingInterface, public Dependency { Q_OBJECT - Q_PROPERTY(QUuid keyboardFocusEntity READ getKeyboardFocusEntity WRITE setKeyboardFocusEntity) friend EntityPropertyMetadataRequest; @@ -144,10 +143,10 @@ public: void resetActivityTracking(); ActivityTracking getActivityTracking() const { return _activityTracking; } - // TODO: expose to script? - ParabolaToEntityIntersectionResult findParabolaIntersectionVector(const PickParabola& parabola, bool precisionPicking, - const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard, - bool visibleOnly, bool collidableOnly); + RayToEntityIntersectionResult evalRayIntersectionVector(const PickRay& ray, PickFilter searchFilter, + const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard); + ParabolaToEntityIntersectionResult evalParabolaIntersectionVector(const PickParabola& parabola, PickFilter searchFilter, + const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard); /**jsdoc * Get the properties of multiple entities. @@ -394,9 +393,8 @@ public slots: Q_INVOKABLE void callEntityClientMethod(QUuid clientSessionID, QUuid entityID, const QString& method, const QStringList& params = QStringList()); - /**jsdoc - * Find the entity with a position closest to a specified point and within a specified radius. + * Find the non-local entity with a position closest to a specified point and within a specified radius. * @function Entities.findClosestEntity * @param {Vec3} center - The point about which to search. * @param {number} radius - The radius within which to search. @@ -410,7 +408,7 @@ public slots: Q_INVOKABLE QUuid findClosestEntity(const glm::vec3& center, float radius) const; /**jsdoc - * Find all entities that intersect a sphere defined by a center point and radius. + * Find all non-local entities that intersect a sphere defined by a center point and radius. * @function Entities.findEntities * @param {Vec3} center - The point about which to search. * @param {number} radius - The radius within which to search. @@ -424,23 +422,23 @@ public slots: Q_INVOKABLE QVector<QUuid> findEntities(const glm::vec3& center, float radius) const; /**jsdoc - * Find all entities whose axis-aligned boxes intersect a search axis-aligned box defined by its minimum coordinates corner + * Find all non-local entities whose axis-aligned boxes intersect a search axis-aligned box defined by its minimum coordinates corner * and dimensions. * @function Entities.findEntitiesInBox * @param {Vec3} corner - The corner of the search AA box with minimum co-ordinate values. * @param {Vec3} dimensions - The dimensions of the search AA box. - * @returns {Uuid[]} An array of entity IDs whose AA boxes intersect the search AA box. The array is empty if no entities + * @returns {Uuid[]} An array of entity IDs whose AA boxes intersect the search AA box. The array is empty if no entities * could be found. */ /// this function will not find any models in script engine contexts which don't have access to models Q_INVOKABLE QVector<QUuid> findEntitiesInBox(const glm::vec3& corner, const glm::vec3& dimensions) const; /**jsdoc - * Find all entities whose axis-aligned boxes intersect a search frustum. + * Find all non-local entities whose axis-aligned boxes intersect a search frustum. * @function Entities.findEntitiesInFrustum * @param {ViewFrustum} frustum - The frustum to search in. The <code>position</code>, <code>orientation</code>, * <code>projection</code>, and <code>centerRadius</code> properties must be specified. - * @returns {Uuid[]} An array of entity IDs axis-aligned boxes intersect the frustum. The array is empty if no entities + * @returns {Uuid[]} An array of entity IDs axis-aligned boxes intersect the frustum. The array is empty if no entities * could be found. * @example <caption>Report the number of entities in view.</caption> * var entityIDs = Entities.findEntitiesInFrustum(Camera.frustum); @@ -450,12 +448,12 @@ public slots: Q_INVOKABLE QVector<QUuid> findEntitiesInFrustum(QVariantMap frustum) const; /**jsdoc - * Find all entities of a particular type that intersect a sphere defined by a center point and radius. + * Find all non-local entities of a particular type that intersect a sphere defined by a center point and radius. * @function Entities.findEntitiesByType * @param {Entities.EntityType} entityType - The type of entity to search for. * @param {Vec3} center - The point about which to search. * @param {number} radius - The radius within which to search. - * @returns {Uuid[]} An array of entity IDs of the specified type that intersect the search sphere. The array is empty if + * @returns {Uuid[]} An array of entity IDs of the specified type that intersect the search sphere. The array is empty if * no entities could be found. * @example <caption>Report the number of Model entities within 10m of your avatar.</caption> * var entityIDs = Entities.findEntitiesByType("Model", MyAvatar.position, 10); @@ -465,7 +463,7 @@ public slots: Q_INVOKABLE QVector<QUuid> findEntitiesByType(const QString entityType, const glm::vec3& center, float radius) const; /**jsdoc - * Find all entities of a particular name that intersect a sphere defined by a center point and radius. + * Find all non-local entities with a particular name that intersect a sphere defined by a center point and radius. * @function Entities.findEntitiesByName * @param {string} entityName - The name of the entity to search for. * @param {Vec3} center - The point about which to search. @@ -475,13 +473,13 @@ public slots: * if no entities could be found. * @example <caption>Report the number of entities with the name, "Light-Target".</caption> * var entityIDs = Entities.findEntitiesByName("Light-Target", MyAvatar.position, 10, false); - * print("Number of entities with the name "Light-Target": " + entityIDs.length); + * print("Number of entities with the name Light-Target: " + entityIDs.length); */ - Q_INVOKABLE QVector<QUuid> findEntitiesByName(const QString entityName, const glm::vec3& center, float radius, - bool caseSensitiveSearch = false ) const; + Q_INVOKABLE QVector<QUuid> findEntitiesByName(const QString entityName, const glm::vec3& center, float radius, + bool caseSensitiveSearch = false) const; /**jsdoc - * Find the first entity intersected by a {@link PickRay}. <code>Light</code> and <code>Zone</code> entities are not + * Find the first non-local entity intersected by a {@link PickRay}. <code>Light</code> and <code>Zone</code> entities are not * intersected unless they've been configured as pickable using {@link Entities.setLightsArePickable|setLightsArePickable} * and {@link Entities.setZonesArePickable|setZonesArePickable}, respectively.<br /> * @function Entities.findRayIntersection @@ -512,33 +510,8 @@ public slots: /// may be inaccurate if the engine is unable to access the visible entities, in which case result.accurate /// will be false. Q_INVOKABLE RayToEntityIntersectionResult findRayIntersection(const PickRay& ray, bool precisionPicking = false, - const QScriptValue& entityIdsToInclude = QScriptValue(), const QScriptValue& entityIdsToDiscard = QScriptValue(), - bool visibleOnly = false, bool collidableOnly = false); - - /// Same as above but with QVectors - RayToEntityIntersectionResult findRayIntersectionVector(const PickRay& ray, bool precisionPicking, - const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard, - bool visibleOnly, bool collidableOnly); - - /**jsdoc - * Find the first entity intersected by a {@link PickRay}. <code>Light</code> and <code>Zone</code> entities are not - * intersected unless they've been configured as pickable using {@link Entities.setLightsArePickable|setLightsArePickable} - * and {@link Entities.setZonesArePickable|setZonesArePickable}, respectively.<br /> - * This is a synonym for {@link Entities.findRayIntersection|findRayIntersection}. - * @function Entities.findRayIntersectionBlocking - * @param {PickRay} pickRay - The PickRay to use for finding entities. - * @param {boolean} [precisionPicking=false] - If <code>true</code> and the intersected entity is a <code>Model</code> - * entity, the result's <code>extraInfo</code> property includes more information than it otherwise would. - * @param {Uuid[]} [entitiesToInclude=[]] - If not empty then the search is restricted to these entities. - * @param {Uuid[]} [entitiesToDiscard=[]] - Entities to ignore during the search. - * @deprecated This function is deprecated and will soon be removed. Use - * {@link Entities.findRayIntersection|findRayIntersection} instead; it blocks and performs the same function. - */ - /// If the scripting context has visible entities, this will determine a ray intersection, and will block in - /// order to return an accurate result - Q_INVOKABLE RayToEntityIntersectionResult findRayIntersectionBlocking(const PickRay& ray, bool precisionPicking = false, - const QScriptValue& entityIdsToInclude = QScriptValue(), const QScriptValue& entityIdsToDiscard = QScriptValue()); - + const QScriptValue& entityIdsToInclude = QScriptValue(), const QScriptValue& entityIdsToDiscard = QScriptValue(), + bool visibleOnly = false, bool collidableOnly = false) const; /**jsdoc * Reloads an entity's server entity script such that the latest version re-downloaded. @@ -603,9 +576,7 @@ public slots: /**jsdoc * Set whether or not ray picks intersect the bounding box of {@link Entities.EntityType|Light} entities. By default, Light * entities are not intersected. The setting lasts for the Interface session. Ray picks are done using - * {@link Entities.findRayIntersection|findRayIntersection} or - * {@link Entities.findRayIntersectionBlocking|findRayIntersectionBlocking}, or the {@link Picks} and {@link RayPick} - * APIs. + * {@link Entities.findRayIntersection|findRayIntersection}, or the {@link Picks} API. * @function Entities.setLightsArePickable * @param {boolean} value - Set <code>true</code> to make ray picks intersect the bounding box of * {@link Entities.EntityType|Light} entities, otherwise <code>false</code>. @@ -615,9 +586,7 @@ public slots: /**jsdoc * Get whether or not ray picks intersect the bounding box of {@link Entities.EntityType|Light} entities. Ray picks are - * done using {@link Entities.findRayIntersection|findRayIntersection} or - * {@link Entities.findRayIntersectionBlocking|findRayIntersectionBlocking}, or the {@link Picks} and {@link RayPick} - * APIs. + * done using {@link Entities.findRayIntersection|findRayIntersection}, or the {@link Picks} API. * @function Entities.getLightsArePickable * @returns {boolean} <code>true</code> if ray picks intersect the bounding box of {@link Entities.EntityType|Light} * entities, otherwise <code>false</code>. @@ -628,9 +597,7 @@ public slots: /**jsdoc * Set whether or not ray picks intersect the bounding box of {@link Entities.EntityType|Zone} entities. By default, Light * entities are not intersected. The setting lasts for the Interface session. Ray picks are done using - * {@link Entities.findRayIntersection|findRayIntersection} or - * {@link Entities.findRayIntersectionBlocking|findRayIntersectionBlocking}, or the {@link Picks} and {@link RayPick} - * APIs. + * {@link Entities.findRayIntersection|findRayIntersection}, or the {@link Picks} API. * @function Entities.setZonesArePickable * @param {boolean} value - Set <code>true</code> to make ray picks intersect the bounding box of * {@link Entities.EntityType|Zone} entities, otherwise <code>false</code>. @@ -640,9 +607,7 @@ public slots: /**jsdoc * Get whether or not ray picks intersect the bounding box of {@link Entities.EntityType|Zone} entities. Ray picks are - * done using {@link Entities.findRayIntersection|findRayIntersection} or - * {@link Entities.findRayIntersectionBlocking|findRayIntersectionBlocking}, or the {@link Picks} and {@link RayPick} - * APIs. + * done using {@link Entities.findRayIntersection|findRayIntersection}, or the {@link Picks} API. * @function Entities.getZonesArePickable * @returns {boolean} <code>true</code> if ray picks intersect the bounding box of {@link Entities.EntityType|Zone} * entities, otherwise <code>false</code>. @@ -1608,6 +1573,109 @@ public slots: * print("Scale: " + JSON.stringify(Mat4.extractScale(transform))); // { x: 1, y: 1, z: 1 } */ Q_INVOKABLE glm::mat4 getEntityLocalTransform(const QUuid& entityID); + + /**jsdoc + * @function Entities.worldToLocalPosition + * @param {Vec3} worldPosition + * @param {Uuid} parentID + * @param {number} parentJointIndex + * @param {boolean} scalesWithparent + * @returns {Vec3} + */ + Q_INVOKABLE glm::vec3 worldToLocalPosition(glm::vec3 worldPosition, const QUuid& parentID, + int parentJointIndex = -1, bool scalesWithParent = false); + /**jsdoc + * @function Entities.worldToLocalRotation + * @param {Quat} worldRotation + * @param {Uuid} parentID + * @param {number} parentJointIndex + * @param {boolean} scalesWithparent + * @returns {Quat} + */ + Q_INVOKABLE glm::quat worldToLocalRotation(glm::quat worldRotation, const QUuid& parentID, + int parentJointIndex = -1, bool scalesWithParent = false); + /**jsdoc + * @function Entities.worldToLocalVelocity + * @param {Vec3} worldVelocity + * @param {Uuid} parentID + * @param {number} parentJointIndex + * @param {boolean} scalesWithparent + * @returns {Vec3} + */ + Q_INVOKABLE glm::vec3 worldToLocalVelocity(glm::vec3 worldVelocity, const QUuid& parentID, + int parentJointIndex = -1, bool scalesWithParent = false); + /**jsdoc + * @function Entities.worldToLocalAngularVelocity + * @param {Vec3} worldAngularVelocity + * @param {Uuid} parentID + * @param {number} parentJointIndex + * @param {boolean} scalesWithparent + * @returns {Vec3} + */ + Q_INVOKABLE glm::vec3 worldToLocalAngularVelocity(glm::vec3 worldAngularVelocity, const QUuid& parentID, + int parentJointIndex = -1, bool scalesWithParent = false); + /**jsdoc + * @function Entities.worldToLocalDimensions + * @param {Vec3} worldDimensions + * @param {Uuid} parentID + * @param {number} parentJointIndex + * @param {boolean} scalesWithparent + * @returns {Vec3} + */ + Q_INVOKABLE glm::vec3 worldToLocalDimensions(glm::vec3 worldDimensions, const QUuid& parentID, + int parentJointIndex = -1, bool scalesWithParent = false); + /**jsdoc + * @function Entities.localToWorldPosition + * @param {Vec3} localPosition + * @param {Uuid} parentID + * @param {number} parentJointIndex + * @param {boolean} scalesWithparent + * @returns {Vec3} + */ + Q_INVOKABLE glm::vec3 localToWorldPosition(glm::vec3 localPosition, const QUuid& parentID, + int parentJointIndex = -1, bool scalesWithParent = false); + /**jsdoc + * @function Entities.localToWorldRotation + * @param {Quat} localRotation + * @param {Uuid} parentID + * @param {number} parentJointIndex + * @param {boolean} scalesWithparent + * @returns {Quat} + */ + Q_INVOKABLE glm::quat localToWorldRotation(glm::quat localRotation, const QUuid& parentID, + int parentJointIndex = -1, bool scalesWithParent = false); + /**jsdoc + * @function Entities.localToWorldVelocity + * @param {Vec3} localVelocity + * @param {Uuid} parentID + * @param {number} parentJointIndex + * @param {boolean} scalesWithparent + * @returns {Vec3} + */ + Q_INVOKABLE glm::vec3 localToWorldVelocity(glm::vec3 localVelocity, const QUuid& parentID, + int parentJointIndex = -1, bool scalesWithParent = false); + /**jsdoc + * @function Entities.localToWorldAngularVelocity + * @param {Vec3} localAngularVelocity + * @param {Uuid} parentID + * @param {number} parentJointIndex + * @param {boolean} scalesWithparent + * @returns {Vec3} + */ + Q_INVOKABLE glm::vec3 localToWorldAngularVelocity(glm::vec3 localAngularVelocity, const QUuid& parentID, + int parentJointIndex = -1, bool scalesWithParent = false); + /**jsdoc + * @function Entities.localToWorldDimensions + * @param {Vec3} localDimensions + * @param {Uuid} parentID + * @param {number} parentJointIndex + * @param {boolean} scalesWithparent + * @returns {Vec3} + */ + Q_INVOKABLE glm::vec3 localToWorldDimensions(glm::vec3 localDimensions, const QUuid& parentID, + int parentJointIndex = -1, bool scalesWithParent = false); + + /**jsdoc * Get the static certificate for an entity. The static certificate contains static properties of the item which cannot * be altered. @@ -1627,6 +1695,16 @@ public slots: */ Q_INVOKABLE bool verifyStaticCertificateProperties(const QUuid& entityID); + /**jsdoc + * Get information about entity properties including a minimum to maximum range for numerical properties + * as well as property enum value. + * @function Entities.getPropertyInfo + * @param {string} propertyName - The name of the property to get the information for. + * @returns {Entities.EntityPropertyInfo} The information data including propertyEnum, minimum, and maximum + * if the property can be found, otherwise an empty object. + */ + Q_INVOKABLE const EntityPropertyInfo getPropertyInfo(const QString& propertyName) const; + signals: /**jsdoc * Triggered on the client that is the physics simulation owner during the collision of two entities. Note: Isn't triggered @@ -1987,14 +2065,12 @@ private: /// actually does the work of finding the ray intersection, can be called in locking mode or tryLock mode - RayToEntityIntersectionResult findRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType, - bool precisionPicking, const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard, - bool visibleOnly = false, bool collidableOnly = false); + RayToEntityIntersectionResult evalRayIntersectionWorker(const PickRay& ray, Octree::lockType lockType, + PickFilter searchFilter, const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard) const; /// actually does the work of finding the parabola intersection, can be called in locking mode or tryLock mode - ParabolaToEntityIntersectionResult findParabolaIntersectionWorker(const PickParabola& parabola, Octree::lockType lockType, - bool precisionPicking, const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard, - bool visibleOnly = false, bool collidableOnly = false); + ParabolaToEntityIntersectionResult evalParabolaIntersectionWorker(const PickParabola& parabola, Octree::lockType lockType, + PickFilter searchFilter, const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard) const; EntityTreePointer _entityTree; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index dd020da5a0..fb1a11d43f 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -43,50 +43,6 @@ static const quint64 DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER = USECS_PER_MSEC * 50; const float EntityTree::DEFAULT_MAX_TMP_ENTITY_LIFETIME = 60 * 60; // 1 hour -// combines the ray cast arguments into a single object -class RayArgs { -public: - // Inputs - glm::vec3 origin; - glm::vec3 direction; - glm::vec3 invDirection; - const QVector<EntityItemID>& entityIdsToInclude; - const QVector<EntityItemID>& entityIdsToDiscard; - bool visibleOnly; - bool collidableOnly; - bool precisionPicking; - - // Outputs - OctreeElementPointer& element; - float& distance; - BoxFace& face; - glm::vec3& surfaceNormal; - QVariantMap& extraInfo; - EntityItemID entityID; -}; - -class ParabolaArgs { -public: - // Inputs - glm::vec3 origin; - glm::vec3 velocity; - glm::vec3 acceleration; - const QVector<EntityItemID>& entityIdsToInclude; - const QVector<EntityItemID>& entityIdsToDiscard; - bool visibleOnly; - bool collidableOnly; - bool precisionPicking; - - // Outputs - OctreeElementPointer& element; - float& parabolicDistance; - BoxFace& face; - glm::vec3& surfaceNormal; - QVariantMap& extraInfo; - EntityItemID entityID; -}; - - EntityTree::EntityTree(bool shouldReaverage) : Octree(shouldReaverage) { @@ -218,7 +174,7 @@ int EntityTree::readEntityDataFromBuffer(const unsigned char* data, int bytesLef addToNeedsParentFixupList(entity); } } else { - entity = EntityTypes::constructEntityItem(dataAt, bytesLeftToRead, args); + entity = EntityTypes::constructEntityItem(dataAt, bytesLeftToRead); if (entity) { bytesForThisEntity = entity->readEntityDataFromBuffer(dataAt, bytesLeftToRead, args); @@ -534,7 +490,6 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti } EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const EntityItemProperties& properties, bool isClone) { - EntityItemPointer result = NULL; EntityItemProperties props = properties; auto nodeList = DependencyManager::get<NodeList>(); @@ -561,12 +516,12 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti if (containingElement) { qCWarning(entities) << "EntityTree::addEntity() on existing entity item with entityID=" << entityID << "containingElement=" << containingElement.get(); - return result; + return nullptr; } // construct the instance of the entity EntityTypes::EntityType type = props.getType(); - result = EntityTypes::constructEntityItem(type, entityID, props); + EntityItemPointer result = EntityTypes::constructEntityItem(type, entityID, props); if (result) { if (recordCreationTime) { @@ -575,10 +530,6 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti // Recurse the tree and store the entity in the correct tree element AddEntityOperator theOperator(getThisPointer(), result); recurseTreeWithOperator(&theOperator); - if (!result->getParentID().isNull()) { - addToNeedsParentFixupList(result); - } - postAddEntity(result); } return result; @@ -778,59 +729,32 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator) } } - -class FindNearPointArgs { +class RayArgs { public: - glm::vec3 position; - float targetRadius; - bool found; - EntityItemPointer closestEntity; - float closestEntityDistance; + // Inputs + glm::vec3 origin; + glm::vec3 direction; + glm::vec3 invDirection; + const QVector<EntityItemID>& entityIdsToInclude; + const QVector<EntityItemID>& entityIdsToDiscard; + PickFilter searchFilter; + + // Outputs + OctreeElementPointer& element; + float& distance; + BoxFace& face; + glm::vec3& surfaceNormal; + QVariantMap& extraInfo; + EntityItemID entityID; }; - -bool EntityTree::findNearPointOperation(const OctreeElementPointer& element, void* extraData) { - FindNearPointArgs* args = static_cast<FindNearPointArgs*>(extraData); - EntityTreeElementPointer entityTreeElement = std::static_pointer_cast<EntityTreeElement>(element); - - glm::vec3 penetration; - bool sphereIntersection = entityTreeElement->getAACube().findSpherePenetration(args->position, args->targetRadius, penetration); - - // If this entityTreeElement contains the point, then search it... - if (sphereIntersection) { - EntityItemPointer thisClosestEntity = entityTreeElement->getClosestEntity(args->position); - - // we may have gotten NULL back, meaning no entity was available - if (thisClosestEntity) { - glm::vec3 entityPosition = thisClosestEntity->getWorldPosition(); - float distanceFromPointToEntity = glm::distance(entityPosition, args->position); - - // If we're within our target radius - if (distanceFromPointToEntity <= args->targetRadius) { - // we are closer than anything else we've found - if (distanceFromPointToEntity < args->closestEntityDistance) { - args->closestEntity = thisClosestEntity; - args->closestEntityDistance = distanceFromPointToEntity; - args->found = true; - } - } - } - - // we should be able to optimize this... - return true; // keep searching in case children have closer entities - } - - // if this element doesn't contain the point, then none of its children can contain the point, so stop searching - return false; -} - -bool findRayIntersectionOp(const OctreeElementPointer& element, void* extraData) { +bool evalRayIntersectionOp(const OctreeElementPointer& element, void* extraData) { RayArgs* args = static_cast<RayArgs*>(extraData); bool keepSearching = true; EntityTreeElementPointer entityTreeElementPointer = std::static_pointer_cast<EntityTreeElement>(element); - EntityItemID entityID = entityTreeElementPointer->findRayIntersection(args->origin, args->direction, + EntityItemID entityID = entityTreeElementPointer->evalRayIntersection(args->origin, args->direction, args->element, args->distance, args->face, args->surfaceNormal, args->entityIdsToInclude, - args->entityIdsToDiscard, args->visibleOnly, args->collidableOnly, args->extraInfo, args->precisionPicking); + args->entityIdsToDiscard, args->searchFilter, args->extraInfo); if (!entityID.isNull()) { args->entityID = entityID; // We recurse OctreeElements in order, so if we hit something, we can stop immediately @@ -839,7 +763,7 @@ bool findRayIntersectionOp(const OctreeElementPointer& element, void* extraData) return keepSearching; } -float findRayIntersectionSortingOp(const OctreeElementPointer& element, void* extraData) { +float evalRayIntersectionSortingOp(const OctreeElementPointer& element, void* extraData) { RayArgs* args = static_cast<RayArgs*>(extraData); EntityTreeElementPointer entityTreeElementPointer = std::static_pointer_cast<EntityTreeElement>(element); float distance = FLT_MAX; @@ -860,19 +784,18 @@ float findRayIntersectionSortingOp(const OctreeElementPointer& element, void* ex return distance; } -EntityItemID EntityTree::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, +EntityItemID EntityTree::evalRayIntersection(const glm::vec3& origin, const glm::vec3& direction, QVector<EntityItemID> entityIdsToInclude, QVector<EntityItemID> entityIdsToDiscard, - bool visibleOnly, bool collidableOnly, bool precisionPicking, - OctreeElementPointer& element, float& distance, + PickFilter searchFilter, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, Octree::lockType lockType, bool* accurateResult) { RayArgs args = { origin, direction, 1.0f / direction, entityIdsToInclude, entityIdsToDiscard, - visibleOnly, collidableOnly, precisionPicking, element, distance, face, surfaceNormal, extraInfo, EntityItemID() }; + searchFilter, element, distance, face, surfaceNormal, extraInfo, EntityItemID() }; distance = FLT_MAX; bool requireLock = lockType == Octree::Lock; bool lockResult = withReadLock([&]{ - recurseTreeWithOperationSorted(findRayIntersectionOp, findRayIntersectionSortingOp, &args); + recurseTreeWithOperationSorted(evalRayIntersectionOp, evalRayIntersectionSortingOp, &args); }, requireLock); if (accurateResult) { @@ -882,13 +805,32 @@ EntityItemID EntityTree::findRayIntersection(const glm::vec3& origin, const glm: return args.entityID; } -bool findParabolaIntersectionOp(const OctreeElementPointer& element, void* extraData) { +class ParabolaArgs { +public: + // Inputs + glm::vec3 origin; + glm::vec3 velocity; + glm::vec3 acceleration; + const QVector<EntityItemID>& entityIdsToInclude; + const QVector<EntityItemID>& entityIdsToDiscard; + PickFilter searchFilter; + + // Outputs + OctreeElementPointer& element; + float& parabolicDistance; + BoxFace& face; + glm::vec3& surfaceNormal; + QVariantMap& extraInfo; + EntityItemID entityID; +}; + +bool evalParabolaIntersectionOp(const OctreeElementPointer& element, void* extraData) { ParabolaArgs* args = static_cast<ParabolaArgs*>(extraData); bool keepSearching = true; EntityTreeElementPointer entityTreeElementPointer = std::static_pointer_cast<EntityTreeElement>(element); - EntityItemID entityID = entityTreeElementPointer->findParabolaIntersection(args->origin, args->velocity, args->acceleration, + EntityItemID entityID = entityTreeElementPointer->evalParabolaIntersection(args->origin, args->velocity, args->acceleration, args->element, args->parabolicDistance, args->face, args->surfaceNormal, args->entityIdsToInclude, - args->entityIdsToDiscard, args->visibleOnly, args->collidableOnly, args->extraInfo, args->precisionPicking); + args->entityIdsToDiscard, args->searchFilter, args->extraInfo); if (!entityID.isNull()) { args->entityID = entityID; // We recurse OctreeElements in order, so if we hit something, we can stop immediately @@ -897,7 +839,7 @@ bool findParabolaIntersectionOp(const OctreeElementPointer& element, void* extra return keepSearching; } -float findParabolaIntersectionSortingOp(const OctreeElementPointer& element, void* extraData) { +float evalParabolaIntersectionSortingOp(const OctreeElementPointer& element, void* extraData) { ParabolaArgs* args = static_cast<ParabolaArgs*>(extraData); EntityTreeElementPointer entityTreeElementPointer = std::static_pointer_cast<EntityTreeElement>(element); float distance = FLT_MAX; @@ -918,20 +860,20 @@ float findParabolaIntersectionSortingOp(const OctreeElementPointer& element, voi return distance; } -EntityItemID EntityTree::findParabolaIntersection(const PickParabola& parabola, +EntityItemID EntityTree::evalParabolaIntersection(const PickParabola& parabola, QVector<EntityItemID> entityIdsToInclude, QVector<EntityItemID> entityIdsToDiscard, - bool visibleOnly, bool collidableOnly, bool precisionPicking, + PickFilter searchFilter, OctreeElementPointer& element, glm::vec3& intersection, float& distance, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, Octree::lockType lockType, bool* accurateResult) { ParabolaArgs args = { parabola.origin, parabola.velocity, parabola.acceleration, entityIdsToInclude, entityIdsToDiscard, - visibleOnly, collidableOnly, precisionPicking, element, parabolicDistance, face, surfaceNormal, extraInfo, EntityItemID() }; + searchFilter, element, parabolicDistance, face, surfaceNormal, extraInfo, EntityItemID() }; parabolicDistance = FLT_MAX; distance = FLT_MAX; bool requireLock = lockType == Octree::Lock; bool lockResult = withReadLock([&] { - recurseTreeWithOperationSorted(findParabolaIntersectionOp, findParabolaIntersectionSortingOp, &args); + recurseTreeWithOperationSorted(evalParabolaIntersectionOp, evalParabolaIntersectionSortingOp, &args); }, requireLock); if (accurateResult) { @@ -946,33 +888,80 @@ EntityItemID EntityTree::findParabolaIntersection(const PickParabola& parabola, return args.entityID; } - -EntityItemPointer EntityTree::findClosestEntity(const glm::vec3& position, float targetRadius) { - FindNearPointArgs args = { position, targetRadius, false, NULL, FLT_MAX }; - withReadLock([&] { - // NOTE: This should use recursion, since this is a spatial operation - recurseTreeWithOperation(findNearPointOperation, &args); - }); - return args.closestEntity; -} - -class FindAllNearPointArgs { +class FindClosestEntityArgs { public: + // Inputs glm::vec3 position; float targetRadius; - QVector<EntityItemPointer> entities; + PickFilter searchFilter; + + // Outputs + QUuid closestEntity; + float closestEntityDistance; }; -bool EntityTree::findInSphereOperation(const OctreeElementPointer& element, void* extraData) { - FindAllNearPointArgs* args = static_cast<FindAllNearPointArgs*>(extraData); +bool evalClosestEntityOperation(const OctreeElementPointer& element, void* extraData) { + FindClosestEntityArgs* args = static_cast<FindClosestEntityArgs*>(extraData); + EntityTreeElementPointer entityTreeElement = std::static_pointer_cast<EntityTreeElement>(element); + + glm::vec3 penetration; + bool sphereIntersection = entityTreeElement->getAACube().findSpherePenetration(args->position, args->targetRadius, penetration); + + // If this entityTreeElement contains the point, then search it... + if (sphereIntersection) { + float closestDistanceSquared = FLT_MAX; + QUuid thisClosestEntity = entityTreeElement->evalClosetEntity(args->position, args->searchFilter, closestDistanceSquared); + + // we may have gotten NULL back, meaning no entity was available + if (!thisClosestEntity.isNull()) { + float distanceFromPointToEntity = glm::sqrt(closestDistanceSquared); + + // If we're within our target radius + if (distanceFromPointToEntity <= args->targetRadius) { + // we are closer than anything else we've found + if (distanceFromPointToEntity < args->closestEntityDistance) { + args->closestEntity = thisClosestEntity; + args->closestEntityDistance = distanceFromPointToEntity; + } + } + } + + // we should be able to optimize this... + return true; // keep searching in case children have closer entities + } + + // if this element doesn't contain the point, then none of its children can contain the point, so stop searching + return false; +} + +// NOTE: assumes caller has handled locking +QUuid EntityTree::evalClosestEntity(const glm::vec3& position, float targetRadius, PickFilter searchFilter) { + FindClosestEntityArgs args = { position, targetRadius, searchFilter, QUuid(), FLT_MAX }; + recurseTreeWithOperation(evalClosestEntityOperation, &args); + return args.closestEntity; +} + +class FindEntitiesInSphereArgs { +public: + // Inputs + glm::vec3 position; + float targetRadius; + PickFilter searchFilter; + + // Outputs + QVector<QUuid> entities; +}; + +bool evalInSphereOperation(const OctreeElementPointer& element, void* extraData) { + FindEntitiesInSphereArgs* args = static_cast<FindEntitiesInSphereArgs*>(extraData); glm::vec3 penetration; bool sphereIntersection = element->getAACube().findSpherePenetration(args->position, args->targetRadius, penetration); // If this element contains the point, then search it... if (sphereIntersection) { EntityTreeElementPointer entityTreeElement = std::static_pointer_cast<EntityTreeElement>(element); - entityTreeElement->getEntities(args->position, args->targetRadius, args->entities); + entityTreeElement->evalEntitiesInSphere(args->position, args->targetRadius, args->searchFilter, args->entities); return true; // keep searching in case children have closer entities } @@ -981,102 +970,166 @@ bool EntityTree::findInSphereOperation(const OctreeElementPointer& element, void } // NOTE: assumes caller has handled locking -void EntityTree::findEntities(const glm::vec3& center, float radius, QVector<EntityItemPointer>& foundEntities) { - FindAllNearPointArgs args = { center, radius, QVector<EntityItemPointer>() }; - // NOTE: This should use recursion, since this is a spatial operation - recurseTreeWithOperation(findInSphereOperation, &args); +void EntityTree::evalEntitiesInSphere(const glm::vec3& center, float radius, PickFilter searchFilter, QVector<QUuid>& foundEntities) { + FindEntitiesInSphereArgs args = { center, radius, searchFilter, QVector<QUuid>() }; + recurseTreeWithOperation(evalInSphereOperation, &args); + foundEntities.swap(args.entities); +} - // swap the two lists of entity pointers instead of copy +class FindEntitiesInSphereWithTypeArgs { +public: + // Inputs + glm::vec3 position; + float targetRadius; + EntityTypes::EntityType type; + PickFilter searchFilter; + + // Outputs + QVector<QUuid> entities; +}; + +bool evalInSphereWithTypeOperation(const OctreeElementPointer& element, void* extraData) { + FindEntitiesInSphereWithTypeArgs* args = static_cast<FindEntitiesInSphereWithTypeArgs*>(extraData); + glm::vec3 penetration; + bool sphereIntersection = element->getAACube().findSpherePenetration(args->position, args->targetRadius, penetration); + + // If this element contains the point, then search it... + if (sphereIntersection) { + EntityTreeElementPointer entityTreeElement = std::static_pointer_cast<EntityTreeElement>(element); + entityTreeElement->evalEntitiesInSphereWithType(args->position, args->targetRadius, args->type, args->searchFilter, args->entities); + return true; // keep searching in case children have closer entities + } + + // if this element doesn't contain the point, then none of it's children can contain the point, so stop searching + return false; +} + +// NOTE: assumes caller has handled locking +void EntityTree::evalEntitiesInSphereWithType(const glm::vec3& center, float radius, EntityTypes::EntityType type, PickFilter searchFilter, QVector<QUuid>& foundEntities) { + FindEntitiesInSphereWithTypeArgs args = { center, radius, type, searchFilter, QVector<QUuid>() }; + recurseTreeWithOperation(evalInSphereWithTypeOperation, &args); + foundEntities.swap(args.entities); +} + +class FindEntitiesInSphereWithNameArgs { +public: + // Inputs + glm::vec3 position; + float targetRadius; + QString name; + bool caseSensitive; + PickFilter searchFilter; + + // Outputs + QVector<QUuid> entities; +}; + +bool evalInSphereWithNameOperation(const OctreeElementPointer& element, void* extraData) { + FindEntitiesInSphereWithNameArgs* args = static_cast<FindEntitiesInSphereWithNameArgs*>(extraData); + glm::vec3 penetration; + bool sphereIntersection = element->getAACube().findSpherePenetration(args->position, args->targetRadius, penetration); + + // If this element contains the point, then search it... + if (sphereIntersection) { + EntityTreeElementPointer entityTreeElement = std::static_pointer_cast<EntityTreeElement>(element); + entityTreeElement->evalEntitiesInSphereWithName(args->position, args->targetRadius, args->name, args->caseSensitive, args->searchFilter, args->entities); + return true; // keep searching in case children have closer entities + } + + // if this element doesn't contain the point, then none of it's children can contain the point, so stop searching + return false; +} + +// NOTE: assumes caller has handled locking +void EntityTree::evalEntitiesInSphereWithName(const glm::vec3& center, float radius, const QString& name, bool caseSensitive, PickFilter searchFilter, QVector<QUuid>& foundEntities) { + FindEntitiesInSphereWithNameArgs args = { center, radius, name, caseSensitive, searchFilter, QVector<QUuid>() }; + recurseTreeWithOperation(evalInSphereWithNameOperation, &args); foundEntities.swap(args.entities); } class FindEntitiesInCubeArgs { public: - FindEntitiesInCubeArgs(const AACube& cube) - : _cube(cube), _foundEntities() { - } + // Inputs + AACube cube; + PickFilter searchFilter; - AACube _cube; - QVector<EntityItemPointer> _foundEntities; + // Outputs + QVector<QUuid> entities; }; -bool EntityTree::findInCubeOperation(const OctreeElementPointer& element, void* extraData) { +bool findInCubeOperation(const OctreeElementPointer& element, void* extraData) { FindEntitiesInCubeArgs* args = static_cast<FindEntitiesInCubeArgs*>(extraData); - if (element->getAACube().touches(args->_cube)) { + if (element->getAACube().touches(args->cube)) { EntityTreeElementPointer entityTreeElement = std::static_pointer_cast<EntityTreeElement>(element); - entityTreeElement->getEntities(args->_cube, args->_foundEntities); + entityTreeElement->evalEntitiesInCube(args->cube, args->searchFilter, args->entities); return true; } return false; } // NOTE: assumes caller has handled locking -void EntityTree::findEntities(const AACube& cube, QVector<EntityItemPointer>& foundEntities) { - FindEntitiesInCubeArgs args(cube); - // NOTE: This should use recursion, since this is a spatial operation +void EntityTree::evalEntitiesInCube(const AACube& cube, PickFilter searchFilter, QVector<QUuid>& foundEntities) { + FindEntitiesInCubeArgs args { cube, searchFilter, QVector<QUuid>() }; recurseTreeWithOperation(findInCubeOperation, &args); - // swap the two lists of entity pointers instead of copy - foundEntities.swap(args._foundEntities); + foundEntities.swap(args.entities); } class FindEntitiesInBoxArgs { public: - FindEntitiesInBoxArgs(const AABox& box) - : _box(box), _foundEntities() { - } + // Inputs + AABox box; + PickFilter searchFilter; - AABox _box; - QVector<EntityItemPointer> _foundEntities; + // Outputs + QVector<QUuid> entities; }; -bool EntityTree::findInBoxOperation(const OctreeElementPointer& element, void* extraData) { +bool findInBoxOperation(const OctreeElementPointer& element, void* extraData) { FindEntitiesInBoxArgs* args = static_cast<FindEntitiesInBoxArgs*>(extraData); - if (element->getAACube().touches(args->_box)) { + if (element->getAACube().touches(args->box)) { EntityTreeElementPointer entityTreeElement = std::static_pointer_cast<EntityTreeElement>(element); - entityTreeElement->getEntities(args->_box, args->_foundEntities); + entityTreeElement->evalEntitiesInBox(args->box, args->searchFilter, args->entities); return true; } return false; } // NOTE: assumes caller has handled locking -void EntityTree::findEntities(const AABox& box, QVector<EntityItemPointer>& foundEntities) { - FindEntitiesInBoxArgs args(box); +void EntityTree::evalEntitiesInBox(const AABox& box, PickFilter searchFilter, QVector<QUuid>& foundEntities) { + FindEntitiesInBoxArgs args { box, searchFilter, QVector<QUuid>() }; // NOTE: This should use recursion, since this is a spatial operation recurseTreeWithOperation(findInBoxOperation, &args); // swap the two lists of entity pointers instead of copy - foundEntities.swap(args._foundEntities); -} - -class FindInFrustumArgs { -public: - ViewFrustum frustum; - QVector<EntityItemPointer> entities; -}; - -bool EntityTree::findInFrustumOperation(const OctreeElementPointer& element, void* extraData) { - FindInFrustumArgs* args = static_cast<FindInFrustumArgs*>(extraData); - if (element->isInView(args->frustum)) { - EntityTreeElementPointer entityTreeElement = std::static_pointer_cast<EntityTreeElement>(element); - entityTreeElement->getEntities(args->frustum, args->entities); - return true; - } - return false; -} - -// NOTE: assumes caller has handled locking -void EntityTree::findEntities(const ViewFrustum& frustum, QVector<EntityItemPointer>& foundEntities) { - FindInFrustumArgs args = { frustum, QVector<EntityItemPointer>() }; - // NOTE: This should use recursion, since this is a spatial operation - recurseTreeWithOperation(findInFrustumOperation, &args); - // swap the two lists of entity pointers instead of copy foundEntities.swap(args.entities); } +class FindEntitiesInFrustumArgs { +public: + // Inputs + ViewFrustum frustum; + PickFilter searchFilter; + + // Outputs + QVector<QUuid> entities; +}; + +bool findInFrustumOperation(const OctreeElementPointer& element, void* extraData) { + FindEntitiesInFrustumArgs* args = static_cast<FindEntitiesInFrustumArgs*>(extraData); + if (element->isInView(args->frustum)) { + EntityTreeElementPointer entityTreeElement = std::static_pointer_cast<EntityTreeElement>(element); + entityTreeElement->evalEntitiesInFrustum(args->frustum, args->searchFilter, args->entities); + return true; + } + return false; +} + // NOTE: assumes caller has handled locking -void EntityTree::findEntities(RecurseOctreeOperation& elementFilter, - QVector<EntityItemPointer>& foundEntities) { - recurseTreeWithOperation(elementFilter, nullptr); +void EntityTree::evalEntitiesInFrustum(const ViewFrustum& frustum, PickFilter searchFilter, QVector<QUuid>& foundEntities) { + FindEntitiesInFrustumArgs args = { frustum, searchFilter, QVector<QUuid>() }; + // NOTE: This should use recursion, since this is a spatial operation + recurseTreeWithOperation(findInFrustumOperation, &args); + // swap the two lists of entity pointers instead of copy + foundEntities.swap(args.entities); } EntityItemPointer EntityTree::findEntityByID(const QUuid& id) const { @@ -1770,7 +1823,6 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c } // this is a new entity... assign a new entityID - properties.setCreated(properties.getLastEdited()); properties.setLastEditedBy(senderNode->getUUID()); startCreate = usecTimestampNow(); EntityItemPointer newEntity = addEntity(entityItemID, properties); @@ -2528,6 +2580,8 @@ void convertGrabUserDataToProperties(EntityItemProperties& properties) { grabProperties.setEquippable(equippable.toBool()); } + grabProperties.setGrabDelegateToParent(true); + if (grabbableKey["spatialKey"].isObject()) { QJsonObject spatialKey = grabbableKey["spatialKey"].toObject(); grabProperties.setEquippable(true); @@ -2771,6 +2825,13 @@ bool EntityTree::readFromMap(QVariantMap& map) { properties.setColorSpread({0, 0, 0}); } + if (contentVersion < (int)EntityVersion::FixPropertiesFromCleanup) { + if (entityMap.contains("created")) { + quint64 created = QDateTime::fromString(entityMap["created"].toString().trimmed(), Qt::ISODate).toMSecsSinceEpoch() * 1000; + properties.setCreated(created); + } + } + EntityItemPointer entity = addEntity(entityItemID, properties); if (!entity) { qCDebug(entities) << "adding Entity failed:" << entityItemID << properties.getType(); @@ -2898,3 +2959,53 @@ bool EntityTree::removeMaterialFromOverlay(const QUuid& overlayID, graphics::Mat } return false; } + +void EntityTree::updateEntityQueryAACubeWorker(SpatiallyNestablePointer object, EntityEditPacketSender* packetSender, + MovingEntitiesOperator& moveOperator, bool force, bool tellServer) { + // if the queryBox has changed, tell the entity-server + EntityItemPointer entity = std::dynamic_pointer_cast<EntityItem>(object); + if (entity) { + bool tellServerThis = tellServer && (entity->getEntityHostType() != entity::HostType::AVATAR); + if ((entity->updateQueryAACube() || force)) { + 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. We do this for domain-hosted + // entities as well as for avatar-entities; the packet-sender will route the update accordingly + if (tellServerThis && packetSender && (entity->isDomainEntity() || entity->isAvatarEntity())) { + quint64 now = usecTimestampNow(); + EntityItemProperties properties = entity->getProperties(); + properties.setQueryAACubeDirty(); + properties.setLocationDirty(); + properties.setLastEdited(now); + + packetSender->queueEditEntityMessage(PacketType::EntityEdit, getThisPointer(), entity->getID(), properties); + entity->setLastBroadcast(now); // for debug/physics status icons + } + + entity->markDirtyFlags(Simulation::DIRTY_POSITION); + entityChanged(entity); + } + } + + object->forEachDescendant([&](SpatiallyNestablePointer descendant) { + updateEntityQueryAACubeWorker(descendant, packetSender, moveOperator, force, tellServer); + }); +} + +void EntityTree::updateEntityQueryAACube(SpatiallyNestablePointer object, EntityEditPacketSender* packetSender, + bool force, bool tellServer) { + // This is used when something other than a script or physics moves an entity. We need to put it in the + // correct place in our local octree, update its and its children's queryAACubes, and send an edit + // packet to the entity-server. + MovingEntitiesOperator moveOperator; + + updateEntityQueryAACubeWorker(object, packetSender, moveOperator, force, tellServer); + + if (moveOperator.hasMovingEntities()) { + PerformanceTimer perfTimer("recurseTreeWithOperator"); + recurseTreeWithOperator(&moveOperator); + } +} diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index d7f93b1eb2..9181a4851c 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -45,7 +45,6 @@ public: QHash<EntityItemID, EntityItemID>* map; }; - class EntityTree : public Octree, public SpatialParentTree { Q_OBJECT public: @@ -92,18 +91,16 @@ public: virtual void processChallengeOwnershipReplyPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) override; virtual void processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) override; - virtual EntityItemID findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + virtual EntityItemID evalRayIntersection(const glm::vec3& origin, const glm::vec3& direction, QVector<EntityItemID> entityIdsToInclude, QVector<EntityItemID> entityIdsToDiscard, - bool visibleOnly, bool collidableOnly, bool precisionPicking, - OctreeElementPointer& element, float& distance, + PickFilter searchFilter, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, Octree::lockType lockType = Octree::TryLock, bool* accurateResult = NULL); - virtual EntityItemID findParabolaIntersection(const PickParabola& parabola, + virtual EntityItemID evalParabolaIntersection(const PickParabola& parabola, QVector<EntityItemID> entityIdsToInclude, QVector<EntityItemID> entityIdsToDiscard, - bool visibleOnly, bool collidableOnly, bool precisionPicking, - OctreeElementPointer& element, glm::vec3& intersection, float& distance, float& parabolicDistance, - BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, + PickFilter searchFilter, OctreeElementPointer& element, glm::vec3& intersection, + float& distance, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, Octree::lockType lockType = Octree::TryLock, bool* accurateResult = NULL); virtual bool rootElementHasData() const override { return true; } @@ -128,44 +125,19 @@ public: void deleteEntity(const EntityItemID& entityID, bool force = false, bool ignoreWarnings = true); void deleteEntities(QSet<EntityItemID> entityIDs, bool force = false, bool ignoreWarnings = true); - /// \param position point of query in world-frame (meters) - /// \param targetRadius radius of query (meters) - EntityItemPointer findClosestEntity(const glm::vec3& position, float targetRadius); EntityItemPointer findEntityByID(const QUuid& id) const; EntityItemPointer findEntityByEntityItemID(const EntityItemID& entityID) const; virtual SpatiallyNestablePointer findByID(const QUuid& id) const override { return findEntityByID(id); } EntityItemID assignEntityID(const EntityItemID& entityItemID); /// Assigns a known ID for a creator token ID - - /// finds all entities that touch a sphere - /// \param center the center of the sphere in world-frame (meters) - /// \param radius the radius of the sphere in world-frame (meters) - /// \param foundEntities[out] vector of EntityItemPointer - /// \remark Side effect: any initial contents in foundEntities will be lost - void findEntities(const glm::vec3& center, float radius, QVector<EntityItemPointer>& foundEntities); - - /// finds all entities that touch a cube - /// \param cube the query cube in world-frame (meters) - /// \param foundEntities[out] vector of non-EntityItemPointer - /// \remark Side effect: any initial contents in entities will be lost - void findEntities(const AACube& cube, QVector<EntityItemPointer>& foundEntities); - - /// finds all entities that touch a box - /// \param box the query box in world-frame (meters) - /// \param foundEntities[out] vector of non-EntityItemPointer - /// \remark Side effect: any initial contents in entities will be lost - void findEntities(const AABox& box, QVector<EntityItemPointer>& foundEntities); - - /// finds all entities within a frustum - /// \parameter frustum the query frustum - /// \param foundEntities[out] vector of EntityItemPointer - void findEntities(const ViewFrustum& frustum, QVector<EntityItemPointer>& foundEntities); - - /// finds all entities that match scanOperator - /// \parameter scanOperator function that scans entities that match criteria - /// \parameter foundEntities[out] vector of EntityItemPointer - void findEntities(RecurseOctreeOperation& scanOperator, QVector<EntityItemPointer>& foundEntities); + QUuid evalClosestEntity(const glm::vec3& position, float targetRadius, PickFilter searchFilter); + void evalEntitiesInSphere(const glm::vec3& center, float radius, PickFilter searchFilter, QVector<QUuid>& foundEntities); + void evalEntitiesInSphereWithType(const glm::vec3& center, float radius, EntityTypes::EntityType type, PickFilter searchFilter, QVector<QUuid>& foundEntities); + void evalEntitiesInSphereWithName(const glm::vec3& center, float radius, const QString& name, bool caseSensitive, PickFilter searchFilter, QVector<QUuid>& foundEntities); + void evalEntitiesInCube(const AACube& cube, PickFilter searchFilter, QVector<QUuid>& foundEntities); + void evalEntitiesInBox(const AABox& box, PickFilter searchFilter, QVector<QUuid>& foundEntities); + void evalEntitiesInFrustum(const ViewFrustum& frustum, PickFilter searchFilter, QVector<QUuid>& foundEntities); void addNewlyCreatedHook(NewlyCreatedEntityHook* hook); void removeNewlyCreatedHook(NewlyCreatedEntityHook* hook); @@ -305,6 +277,9 @@ public: std::map<QString, QString> getNamedPaths() const { return _namedPaths; } + void updateEntityQueryAACube(SpatiallyNestablePointer object, EntityEditPacketSender* packetSender, + bool force, bool tellServer); + signals: void deletingEntity(const EntityItemID& entityID); void deletingEntityPointer(EntityItem* entityID); @@ -322,11 +297,6 @@ protected: void processRemovedEntities(const DeleteEntityOperator& theOperator); bool updateEntity(EntityItemPointer entity, const EntityItemProperties& properties, const SharedNodePointer& senderNode = SharedNodePointer(nullptr)); - static bool findNearPointOperation(const OctreeElementPointer& element, void* extraData); - static bool findInSphereOperation(const OctreeElementPointer& element, void* extraData); - static bool findInCubeOperation(const OctreeElementPointer& element, void* extraData); - static bool findInBoxOperation(const OctreeElementPointer& element, void* extraData); - static bool findInFrustumOperation(const OctreeElementPointer& element, void* extraData); static bool sendEntitiesOperation(const OctreeElementPointer& element, void* extraData); static void bumpTimestamp(EntityItemProperties& properties); @@ -425,6 +395,9 @@ private: bool _serverlessDomain { false }; std::map<QString, QString> _namedPaths; + + void updateEntityQueryAACubeWorker(SpatiallyNestablePointer object, EntityEditPacketSender* packetSender, + MovingEntitiesOperator& moveOperator, bool force, bool tellServer); }; void convertGrabUserDataToProperties(EntityItemProperties& properties); diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 5b71010f75..2ece6835ea 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -139,10 +139,23 @@ bool EntityTreeElement::bestFitBounds(const glm::vec3& minPoint, const glm::vec3 return false; } -EntityItemID EntityTreeElement::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, - const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard, - bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking) { +bool checkFilterSettings(const EntityItemPointer& entity, PickFilter searchFilter) { + bool visible = entity->isVisible(); + bool collidable = !entity->getCollisionless() && (entity->getShapeType() != SHAPE_TYPE_NONE); + if ((!searchFilter.doesPickVisible() && visible) || (!searchFilter.doesPickInvisible() && !visible) || + (!searchFilter.doesPickCollidable() && collidable) || (!searchFilter.doesPickNonCollidable() && !collidable) || + (!searchFilter.doesPickDomainEntities() && entity->isDomainEntity()) || + (!searchFilter.doesPickAvatarEntities() && entity->isAvatarEntity()) || + (!searchFilter.doesPickLocalEntities() && entity->isLocalEntity())) { + return false; + } + return true; +} + +EntityItemID EntityTreeElement::evalRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, + const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard, + PickFilter searchFilter, QVariantMap& extraInfo) { EntityItemID result; BoxFace localFace { UNKNOWN_FACE }; @@ -154,9 +167,8 @@ EntityItemID EntityTreeElement::findRayIntersection(const glm::vec3& origin, con QVariantMap localExtraInfo; float distanceToElementDetails = distance; - EntityItemID entityID = findDetailedRayIntersection(origin, direction, element, distanceToElementDetails, - localFace, localSurfaceNormal, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly, - localExtraInfo, precisionPicking); + EntityItemID entityID = evalDetailedRayIntersection(origin, direction, element, distanceToElementDetails, + localFace, localSurfaceNormal, entityIdsToInclude, entityIdsToDiscard, searchFilter, localExtraInfo); if (!entityID.isNull() && distanceToElementDetails < distance) { distance = distanceToElementDetails; face = localFace; @@ -167,13 +179,12 @@ EntityItemID EntityTreeElement::findRayIntersection(const glm::vec3& origin, con return result; } -EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, +EntityItemID EntityTreeElement::evalDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIDsToDiscard, - bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking) { + PickFilter searchFilter, QVariantMap& extraInfo) { // only called if we do intersect our bounding cube, but find if we actually intersect with entities... - int entityNumber = 0; EntityItemID entityID; forEachEntity([&](EntityItemPointer entity) { // use simple line-sphere for broadphase check @@ -187,11 +198,9 @@ EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& ori return; } - // check RayPick filter settings - if ((visibleOnly && !entity->isVisible()) - || (collidableOnly && (entity->getCollisionless() || entity->getShapeType() == SHAPE_TYPE_NONE)) - || (entityIdsToInclude.size() > 0 && !entityIdsToInclude.contains(entity->getID())) - || (entityIDsToDiscard.size() > 0 && entityIDsToDiscard.contains(entity->getID())) ) { + if (!checkFilterSettings(entity, searchFilter) || + (entityIdsToInclude.size() > 0 && !entityIdsToInclude.contains(entity->getID())) || + (entityIDsToDiscard.size() > 0 && entityIDsToDiscard.contains(entity->getID())) ) { return; } @@ -222,7 +231,7 @@ EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& ori if (entity->supportsDetailedIntersection()) { QVariantMap localExtraInfo; if (entity->findDetailedRayIntersection(origin, direction, element, localDistance, - localFace, localSurfaceNormal, localExtraInfo, precisionPicking)) { + localFace, localSurfaceNormal, localExtraInfo, searchFilter.isPrecise())) { if (localDistance < distance) { distance = localDistance; face = localFace; @@ -244,7 +253,6 @@ EntityItemID EntityTreeElement::findDetailedRayIntersection(const glm::vec3& ori } } } - entityNumber++; }); return entityID; } @@ -275,11 +283,10 @@ bool EntityTreeElement::findSpherePenetration(const glm::vec3& center, float rad return result; } -EntityItemID EntityTreeElement::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, +EntityItemID EntityTreeElement::evalParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude, - const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly, - QVariantMap& extraInfo, bool precisionPicking) { + const QVector<EntityItemID>& entityIdsToDiscard, PickFilter searchFilter, QVariantMap& extraInfo) { EntityItemID result; BoxFace localFace; @@ -300,9 +307,8 @@ EntityItemID EntityTreeElement::findParabolaIntersection(const glm::vec3& origin } // Get the normal of the plane, the cross product of two vectors on the plane glm::vec3 normal = glm::normalize(glm::cross(vectorOnPlane, acceleration)); - EntityItemID entityID = findDetailedParabolaIntersection(origin, velocity, acceleration, normal, element, distanceToElementDetails, - localFace, localSurfaceNormal, entityIdsToInclude, entityIdsToDiscard, visibleOnly, collidableOnly, - localExtraInfo, precisionPicking); + EntityItemID entityID = evalDetailedParabolaIntersection(origin, velocity, acceleration, normal, element, distanceToElementDetails, + localFace, localSurfaceNormal, entityIdsToInclude, entityIdsToDiscard, searchFilter, localExtraInfo); if (!entityID.isNull() && distanceToElementDetails < parabolicDistance) { parabolicDistance = distanceToElementDetails; face = localFace; @@ -313,13 +319,12 @@ EntityItemID EntityTreeElement::findParabolaIntersection(const glm::vec3& origin return result; } -EntityItemID EntityTreeElement::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, +EntityItemID EntityTreeElement::evalDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::vec3& normal, OctreeElementPointer& element, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIDsToDiscard, - bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking) { + PickFilter searchFilter, QVariantMap& extraInfo) { // only called if we do intersect our bounding cube, but find if we actually intersect with entities... - int entityNumber = 0; EntityItemID entityID; forEachEntity([&](EntityItemPointer entity) { // use simple line-sphere for broadphase check @@ -338,11 +343,9 @@ EntityItemID EntityTreeElement::findDetailedParabolaIntersection(const glm::vec3 return; } - // check RayPick filter settings - if ((visibleOnly && !entity->isVisible()) - || (collidableOnly && (entity->getCollisionless() || entity->getShapeType() == SHAPE_TYPE_NONE)) - || (entityIdsToInclude.size() > 0 && !entityIdsToInclude.contains(entity->getID())) - || (entityIDsToDiscard.size() > 0 && entityIDsToDiscard.contains(entity->getID())) ) { + if (!checkFilterSettings(entity, searchFilter) || + (entityIdsToInclude.size() > 0 && !entityIdsToInclude.contains(entity->getID())) || + (entityIDsToDiscard.size() > 0 && entityIDsToDiscard.contains(entity->getID()))) { return; } @@ -374,7 +377,7 @@ EntityItemID EntityTreeElement::findDetailedParabolaIntersection(const glm::vec3 if (entity->supportsDetailedIntersection()) { QVariantMap localExtraInfo; if (entity->findDetailedParabolaIntersection(origin, velocity, acceleration, element, localDistance, - localFace, localSurfaceNormal, localExtraInfo, precisionPicking)) { + localFace, localSurfaceNormal, localExtraInfo, searchFilter.isPrecise())) { if (localDistance < parabolicDistance) { parabolicDistance = localDistance; face = localFace; @@ -396,55 +399,56 @@ EntityItemID EntityTreeElement::findDetailedParabolaIntersection(const glm::vec3 } } } - entityNumber++; }); return entityID; } -EntityItemPointer EntityTreeElement::getClosestEntity(glm::vec3 position) const { - EntityItemPointer closestEntity = NULL; - float closestEntityDistance = FLT_MAX; - withReadLock([&] { - foreach(EntityItemPointer entity, _entityItems) { - float distanceToEntity = glm::distance2(position, entity->getWorldPosition()); - if (distanceToEntity < closestEntityDistance) { - closestEntity = entity; - } +QUuid EntityTreeElement::evalClosetEntity(const glm::vec3& position, PickFilter searchFilter, float& closestDistanceSquared) const { + QUuid closestEntity; + forEachEntity([&](EntityItemPointer entity) { + if (!checkFilterSettings(entity, searchFilter)) { + return; + } + + float distanceToEntity = glm::distance2(position, entity->getWorldPosition()); + if (distanceToEntity < closestDistanceSquared) { + closestEntity = entity->getID(); + closestDistanceSquared = distanceToEntity; } }); return closestEntity; } -// TODO: change this to use better bounding shape for entity than sphere -void EntityTreeElement::getEntities(const glm::vec3& searchPosition, float searchRadius, QVector<EntityItemPointer>& foundEntities) const { +void EntityTreeElement::evalEntitiesInSphere(const glm::vec3& position, float radius, PickFilter searchFilter, QVector<QUuid>& foundEntities) const { forEachEntity([&](EntityItemPointer entity) { + if (!checkFilterSettings(entity, searchFilter)) { + return; + } bool success; AABox entityBox = entity->getAABox(success); // if the sphere doesn't intersect with our world frame AABox, we don't need to consider the more complex case glm::vec3 penetration; - if (!success || entityBox.findSpherePenetration(searchPosition, searchRadius, penetration)) { + if (success && entityBox.findSpherePenetration(position, radius, penetration)) { glm::vec3 dimensions = entity->getRaycastDimensions(); // FIXME - consider allowing the entity to determine penetration so that - // entities could presumably dull actuall hull testing if they wanted to + // entities could presumably do actual hull testing if they wanted to // FIXME - handle entity->getShapeType() == SHAPE_TYPE_SPHERE case better in particular // can we handle the ellipsoid case better? We only currently handle perfect spheres // with centered registration points - if (entity->getShapeType() == SHAPE_TYPE_SPHERE && - (dimensions.x == dimensions.y && dimensions.y == dimensions.z)) { + if (entity->getShapeType() == SHAPE_TYPE_SPHERE && (dimensions.x == dimensions.y && dimensions.y == dimensions.z)) { // NOTE: entity->getRadius() doesn't return the true radius, it returns the radius of the // maximum bounding sphere, which is actually larger than our actual radius float entityTrueRadius = dimensions.x / 2.0f; bool success; - if (findSphereSpherePenetration(searchPosition, searchRadius, - entity->getCenterPosition(success), entityTrueRadius, penetration)) { + if (findSphereSpherePenetration(position, radius, entity->getCenterPosition(success), entityTrueRadius, penetration)) { if (success) { - foundEntities.push_back(entity); + foundEntities.push_back(entity->getID()); } } } else { @@ -460,17 +464,134 @@ void EntityTreeElement::getEntities(const glm::vec3& searchPosition, float searc AABox entityFrameBox(corner, dimensions); - glm::vec3 entityFrameSearchPosition = glm::vec3(worldToEntityMatrix * glm::vec4(searchPosition, 1.0f)); - if (entityFrameBox.findSpherePenetration(entityFrameSearchPosition, searchRadius, penetration)) { - foundEntities.push_back(entity); + glm::vec3 entityFrameSearchPosition = glm::vec3(worldToEntityMatrix * glm::vec4(position, 1.0f)); + if (entityFrameBox.findSpherePenetration(entityFrameSearchPosition, radius, penetration)) { + foundEntities.push_back(entity->getID()); } } } }); } -void EntityTreeElement::getEntities(const AACube& cube, QVector<EntityItemPointer>& foundEntities) { +void EntityTreeElement::evalEntitiesInSphereWithType(const glm::vec3& position, float radius, EntityTypes::EntityType type, PickFilter searchFilter, QVector<QUuid>& foundEntities) const { forEachEntity([&](EntityItemPointer entity) { + if (!checkFilterSettings(entity, searchFilter) || type != entity->getType()) { + return; + } + + bool success; + AABox entityBox = entity->getAABox(success); + + // if the sphere doesn't intersect with our world frame AABox, we don't need to consider the more complex case + glm::vec3 penetration; + if (success && entityBox.findSpherePenetration(position, radius, penetration)) { + + glm::vec3 dimensions = entity->getRaycastDimensions(); + + // FIXME - consider allowing the entity to determine penetration so that + // entities could presumably do actual hull testing if they wanted to + // FIXME - handle entity->getShapeType() == SHAPE_TYPE_SPHERE case better in particular + // can we handle the ellipsoid case better? We only currently handle perfect spheres + // with centered registration points + if (entity->getShapeType() == SHAPE_TYPE_SPHERE && (dimensions.x == dimensions.y && dimensions.y == dimensions.z)) { + + // NOTE: entity->getRadius() doesn't return the true radius, it returns the radius of the + // maximum bounding sphere, which is actually larger than our actual radius + float entityTrueRadius = dimensions.x / 2.0f; + + bool success; + if (findSphereSpherePenetration(position, radius, entity->getCenterPosition(success), entityTrueRadius, penetration)) { + if (success) { + foundEntities.push_back(entity->getID()); + } + } + } else { + // determine the worldToEntityMatrix that doesn't include scale because + // we're going to use the registration aware aa box in the entity frame + glm::mat4 rotation = glm::mat4_cast(entity->getWorldOrientation()); + glm::mat4 translation = glm::translate(entity->getWorldPosition()); + glm::mat4 entityToWorldMatrix = translation * rotation; + glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); + + glm::vec3 registrationPoint = entity->getRegistrationPoint(); + glm::vec3 corner = -(dimensions * registrationPoint); + + AABox entityFrameBox(corner, dimensions); + + glm::vec3 entityFrameSearchPosition = glm::vec3(worldToEntityMatrix * glm::vec4(position, 1.0f)); + if (entityFrameBox.findSpherePenetration(entityFrameSearchPosition, radius, penetration)) { + foundEntities.push_back(entity->getID()); + } + } + } + }); +} + +void EntityTreeElement::evalEntitiesInSphereWithName(const glm::vec3& position, float radius, const QString& name, bool caseSensitive, PickFilter searchFilter, QVector<QUuid>& foundEntities) const { + forEachEntity([&](EntityItemPointer entity) { + if (!checkFilterSettings(entity, searchFilter)) { + return; + } + + QString entityName = entity->getName(); + if ((caseSensitive && name != entityName) || (!caseSensitive && name.toLower() != entityName.toLower())) { + return; + } + + bool success; + AABox entityBox = entity->getAABox(success); + + // if the sphere doesn't intersect with our world frame AABox, we don't need to consider the more complex case + glm::vec3 penetration; + if (success && entityBox.findSpherePenetration(position, radius, penetration)) { + + glm::vec3 dimensions = entity->getRaycastDimensions(); + + // FIXME - consider allowing the entity to determine penetration so that + // entities could presumably do actual hull testing if they wanted to + // FIXME - handle entity->getShapeType() == SHAPE_TYPE_SPHERE case better in particular + // can we handle the ellipsoid case better? We only currently handle perfect spheres + // with centered registration points + if (entity->getShapeType() == SHAPE_TYPE_SPHERE && (dimensions.x == dimensions.y && dimensions.y == dimensions.z)) { + + // NOTE: entity->getRadius() doesn't return the true radius, it returns the radius of the + // maximum bounding sphere, which is actually larger than our actual radius + float entityTrueRadius = dimensions.x / 2.0f; + + bool success; + if (findSphereSpherePenetration(position, radius, entity->getCenterPosition(success), entityTrueRadius, penetration)) { + if (success) { + foundEntities.push_back(entity->getID()); + } + } + } else { + // determine the worldToEntityMatrix that doesn't include scale because + // we're going to use the registration aware aa box in the entity frame + glm::mat4 rotation = glm::mat4_cast(entity->getWorldOrientation()); + glm::mat4 translation = glm::translate(entity->getWorldPosition()); + glm::mat4 entityToWorldMatrix = translation * rotation; + glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix); + + glm::vec3 registrationPoint = entity->getRegistrationPoint(); + glm::vec3 corner = -(dimensions * registrationPoint); + + AABox entityFrameBox(corner, dimensions); + + glm::vec3 entityFrameSearchPosition = glm::vec3(worldToEntityMatrix * glm::vec4(position, 1.0f)); + if (entityFrameBox.findSpherePenetration(entityFrameSearchPosition, radius, penetration)) { + foundEntities.push_back(entity->getID()); + } + } + } + }); +} + +void EntityTreeElement::evalEntitiesInCube(const AACube& cube, PickFilter searchFilter, QVector<QUuid>& foundEntities) const { + forEachEntity([&](EntityItemPointer entity) { + if (!checkFilterSettings(entity, searchFilter)) { + return; + } + bool success; AABox entityBox = entity->getAABox(success); // FIXME - handle entity->getShapeType() == SHAPE_TYPE_SPHERE case better @@ -489,14 +610,18 @@ void EntityTreeElement::getEntities(const AACube& cube, QVector<EntityItemPointe // // If the entities AABox touches the search cube then consider it to be found - if (!success || entityBox.touches(cube)) { - foundEntities.push_back(entity); + if (success && entityBox.touches(cube)) { + foundEntities.push_back(entity->getID()); } }); } -void EntityTreeElement::getEntities(const AABox& box, QVector<EntityItemPointer>& foundEntities) { +void EntityTreeElement::evalEntitiesInBox(const AABox& box, PickFilter searchFilter, QVector<QUuid>& foundEntities) const { forEachEntity([&](EntityItemPointer entity) { + if (!checkFilterSettings(entity, searchFilter)) { + return; + } + bool success; AABox entityBox = entity->getAABox(success); // FIXME - handle entity->getShapeType() == SHAPE_TYPE_SPHERE case better @@ -515,19 +640,24 @@ void EntityTreeElement::getEntities(const AABox& box, QVector<EntityItemPointer> // // If the entities AABox touches the search cube then consider it to be found - if (!success || entityBox.touches(box)) { - foundEntities.push_back(entity); + if (success && entityBox.touches(box)) { + foundEntities.push_back(entity->getID()); } }); } -void EntityTreeElement::getEntities(const ViewFrustum& frustum, QVector<EntityItemPointer>& foundEntities) { +void EntityTreeElement::evalEntitiesInFrustum(const ViewFrustum& frustum, PickFilter searchFilter, QVector<QUuid>& foundEntities) const { forEachEntity([&](EntityItemPointer entity) { + if (!checkFilterSettings(entity, searchFilter)) { + return; + } + bool success; AABox entityBox = entity->getAABox(success); + // FIXME - See FIXMEs for similar methods above. - if (!success || frustum.boxIntersectsFrustum(entityBox) || frustum.boxIntersectsKeyhole(entityBox)) { - foundEntities.push_back(entity); + if (success && (frustum.boxIntersectsFrustum(entityBox) || frustum.boxIntersectsKeyhole(entityBox))) { + foundEntities.push_back(entity->getID()); } }); } diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index 793340c9a4..aed19eed15 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -19,7 +19,8 @@ #include "EntityEditPacketSender.h" #include "EntityItem.h" -#include "EntityTree.h" + +#include <PickFilter.h> class EntityTree; class EntityTreeElement; @@ -77,7 +78,6 @@ public: EntityEditPacketSender* packetSender; }; - class EntityTreeElement : public OctreeElement, ReadWriteLockable { friend class EntityTree; // to allow createElement to new us... @@ -135,28 +135,25 @@ public: virtual bool deleteApproved() const override { return !hasEntities(); } virtual bool canPickIntersect() const override { return hasEntities(); } - virtual EntityItemID findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + virtual EntityItemID evalRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude, const QVector<EntityItemID>& entityIdsToDiscard, - bool visibleOnly, bool collidableOnly, QVariantMap& extraInfo, bool precisionPicking = false); - virtual EntityItemID findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + PickFilter searchFilter, QVariantMap& extraInfo); + virtual EntityItemID evalDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude, - const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly, - QVariantMap& extraInfo, bool precisionPicking); + const QVector<EntityItemID>& entityIdsToDiscard, PickFilter searchFilter, QVariantMap& extraInfo); virtual bool findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration, void** penetratedObject) const override; - virtual EntityItemID findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + virtual EntityItemID evalParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude, - const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly, - QVariantMap& extraInfo, bool precisionPicking = false); - virtual EntityItemID findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const QVector<EntityItemID>& entityIdsToDiscard, PickFilter searchFilter, QVariantMap& extraInfo); + virtual EntityItemID evalDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& normal, const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal, const QVector<EntityItemID>& entityIdsToInclude, - const QVector<EntityItemID>& entityIdsToDiscard, bool visibleOnly, bool collidableOnly, - QVariantMap& extraInfo, bool precisionPicking); + const QVector<EntityItemID>& entityIdsToDiscard, PickFilter searchFilter, QVariantMap& extraInfo); template <typename F> void forEachEntity(F f) const { @@ -175,28 +172,13 @@ public: void addEntityItem(EntityItemPointer entity); - EntityItemPointer getClosestEntity(glm::vec3 position) const; - - /// finds all entities that touch a sphere - /// \param position the center of the query sphere - /// \param radius the radius of the query sphere - /// \param entities[out] vector of const EntityItemPointer - void getEntities(const glm::vec3& position, float radius, QVector<EntityItemPointer>& foundEntities) const; - - /// finds all entities that touch a box - /// \param box the query box - /// \param entities[out] vector of non-const EntityItemPointer - void getEntities(const AACube& cube, QVector<EntityItemPointer>& foundEntities); - - /// finds all entities that touch a box - /// \param box the query box - /// \param entities[out] vector of non-const EntityItemPointer - void getEntities(const AABox& box, QVector<EntityItemPointer>& foundEntities); - - /// finds all entities that touch a frustum - /// \param frustum the query frustum - /// \param entities[out] vector of non-const EntityItemPointer - void getEntities(const ViewFrustum& frustum, QVector<EntityItemPointer>& foundEntities); + QUuid evalClosetEntity(const glm::vec3& position, PickFilter searchFilter, float& closestDistanceSquared) const; + void evalEntitiesInSphere(const glm::vec3& position, float radius, PickFilter searchFilter, QVector<QUuid>& foundEntities) const; + void evalEntitiesInSphereWithType(const glm::vec3& position, float radius, EntityTypes::EntityType type, PickFilter searchFilter, QVector<QUuid>& foundEntities) const; + void evalEntitiesInSphereWithName(const glm::vec3& position, float radius, const QString& name, bool caseSensitive, PickFilter searchFilter, QVector<QUuid>& foundEntities) const; + void evalEntitiesInCube(const AACube& cube, PickFilter searchFilter, QVector<QUuid>& foundEntities) const; + void evalEntitiesInBox(const AABox& box, PickFilter searchFilter, QVector<QUuid>& foundEntities) const; + void evalEntitiesInFrustum(const ViewFrustum& frustum, PickFilter searchFilter, QVector<QUuid>& foundEntities) const; /// finds all entities that match filter /// \param filter function that adds matching entities to foundEntities diff --git a/libraries/entities/src/EntityTypes.cpp b/libraries/entities/src/EntityTypes.cpp index 9611063f8b..ad078190dd 100644 --- a/libraries/entities/src/EntityTypes.cpp +++ b/libraries/entities/src/EntityTypes.cpp @@ -20,40 +20,48 @@ #include "EntityItemProperties.h" #include "EntitiesLogging.h" -#include "LightEntityItem.h" +#include "ShapeEntityItem.h" #include "ModelEntityItem.h" #include "ParticleEffectEntityItem.h" #include "TextEntityItem.h" +#include "ImageEntityItem.h" #include "WebEntityItem.h" -#include "ZoneEntityItem.h" #include "LineEntityItem.h" -#include "PolyVoxEntityItem.h" #include "PolyLineEntityItem.h" -#include "ShapeEntityItem.h" +#include "PolyVoxEntityItem.h" +#include "GridEntityItem.h" +#include "LightEntityItem.h" +#include "ZoneEntityItem.h" #include "MaterialEntityItem.h" QMap<EntityTypes::EntityType, QString> EntityTypes::_typeToNameMap; QMap<QString, EntityTypes::EntityType> EntityTypes::_nameToTypeMap; -EntityTypeFactory EntityTypes::_factories[EntityTypes::LAST + 1]; +EntityTypeFactory EntityTypes::_factories[EntityTypes::NUM_TYPES]; bool EntityTypes::_factoriesInitialized = false; const QString ENTITY_TYPE_NAME_UNKNOWN = "Unknown"; // Register Entity the default implementations of entity types here... -REGISTER_ENTITY_TYPE(Model) -REGISTER_ENTITY_TYPE(Web) -REGISTER_ENTITY_TYPE(Light) -REGISTER_ENTITY_TYPE(Text) -REGISTER_ENTITY_TYPE(ParticleEffect) -REGISTER_ENTITY_TYPE(Zone) -REGISTER_ENTITY_TYPE(Line) -REGISTER_ENTITY_TYPE(PolyVox) -REGISTER_ENTITY_TYPE(PolyLine) -REGISTER_ENTITY_TYPE(Shape) REGISTER_ENTITY_TYPE_WITH_FACTORY(Box, ShapeEntityItem::boxFactory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Sphere, ShapeEntityItem::sphereFactory) +REGISTER_ENTITY_TYPE(Shape) +REGISTER_ENTITY_TYPE(Model) +REGISTER_ENTITY_TYPE(Text) +REGISTER_ENTITY_TYPE(Image) +REGISTER_ENTITY_TYPE(Web) +REGISTER_ENTITY_TYPE(ParticleEffect) +REGISTER_ENTITY_TYPE(Line) +REGISTER_ENTITY_TYPE(PolyLine) +REGISTER_ENTITY_TYPE(PolyVox) +REGISTER_ENTITY_TYPE(Grid) +REGISTER_ENTITY_TYPE(Light) +REGISTER_ENTITY_TYPE(Zone) REGISTER_ENTITY_TYPE(Material) +bool EntityTypes::typeIsValid(EntityType type) { + return type > EntityType::Unknown && type <= EntityType::NUM_TYPES; +} + const QString& EntityTypes::getEntityTypeName(EntityType entityType) { QMap<EntityType, QString>::iterator matchedTypeName = _typeToNameMap.find(entityType); if (matchedTypeName != _typeToNameMap.end()) { @@ -80,7 +88,7 @@ bool EntityTypes::registerEntityType(EntityType entityType, const char* name, En memset(&_factories,0,sizeof(_factories)); _factoriesInitialized = true; } - if (entityType >= 0 && entityType <= LAST) { + if (entityType >= 0 && entityType < NUM_TYPES) { _factories[entityType] = factoryMethod; return true; } @@ -91,7 +99,7 @@ EntityItemPointer EntityTypes::constructEntityItem(EntityType entityType, const const EntityItemProperties& properties) { EntityItemPointer newEntityItem = NULL; EntityTypeFactory factory = NULL; - if (entityType >= 0 && entityType <= LAST) { + if (entityType >= 0 && entityType < NUM_TYPES) { factory = _factories[entityType]; } if (factory) { @@ -103,8 +111,7 @@ EntityItemPointer EntityTypes::constructEntityItem(EntityType entityType, const return newEntityItem; } -EntityItemPointer EntityTypes::constructEntityItem(const unsigned char* data, int bytesToRead, - ReadBitstreamToTreeParams& args) { +void EntityTypes::extractEntityTypeAndID(const unsigned char* data, int dataLength, EntityTypes::EntityType& typeOut, QUuid& idOut) { // Header bytes // object ID [16 bytes] @@ -115,28 +122,36 @@ EntityItemPointer EntityTypes::constructEntityItem(const unsigned char* data, in // ~27-35 bytes... const int MINIMUM_HEADER_BYTES = 27; - int bytesRead = 0; - if (bytesToRead >= MINIMUM_HEADER_BYTES) { - int originalLength = bytesToRead; - QByteArray originalDataBuffer((const char*)data, originalLength); + if (dataLength >= MINIMUM_HEADER_BYTES) { + int bytesRead = 0; + QByteArray originalDataBuffer = QByteArray::fromRawData((const char*)data, dataLength); // id QByteArray encodedID = originalDataBuffer.mid(bytesRead, NUM_BYTES_RFC4122_UUID); // maximum possible size - QUuid actualID = QUuid::fromRfc4122(encodedID); + idOut = QUuid::fromRfc4122(encodedID); bytesRead += encodedID.size(); // type QByteArray encodedType = originalDataBuffer.mid(bytesRead); // maximum possible size ByteCountCoded<quint32> typeCoder = encodedType; encodedType = typeCoder; // determine true length - bytesRead += encodedType.size(); quint32 type = typeCoder; - EntityTypes::EntityType entityType = (EntityTypes::EntityType)type; - - EntityItemID tempEntityID(actualID); - EntityItemProperties tempProperties; - return constructEntityItem(entityType, tempEntityID, tempProperties); + typeOut = (EntityTypes::EntityType)type; } - - return NULL; +} + +EntityItemPointer EntityTypes::constructEntityItem(const unsigned char* data, int bytesToRead) { + QUuid id; + EntityTypes::EntityType type = EntityTypes::Unknown; + extractEntityTypeAndID(data, bytesToRead, type, id); + if (type > EntityTypes::Unknown && type <= EntityTypes::NUM_TYPES) { + EntityItemID tempEntityID(id); + EntityItemProperties tempProperties; + return constructEntityItem(type, tempEntityID, tempProperties); + } + return nullptr; +} + +EntityItemPointer EntityTypes::constructEntityItem(const QUuid& id, const EntityItemProperties& properties) { + return constructEntityItem(properties.getType(), id, properties); } diff --git a/libraries/entities/src/EntityTypes.h b/libraries/entities/src/EntityTypes.h index 954bdf8f17..2e8914c8a7 100644 --- a/libraries/entities/src/EntityTypes.h +++ b/libraries/entities/src/EntityTypes.h @@ -49,25 +49,6 @@ public: * <code>"Cube"</code>. If an entity of type <code>Shape</code> or <code>Sphere</code> has its <code>shape</code> set * to <code>"Cube"</code> then its <code>type</code> will be reported as <code>"Box"</code>. * <td>{@link Entities.EntityProperties-Box|EntityProperties-Box}</td></tr> - * <tr><td><code>"Light"</code></td><td>A local lighting effect.</td> - * <td>{@link Entities.EntityProperties-Light|EntityProperties-Light}</td></tr> - * <tr><td><code>"Line"</code></td><td>A sequence of one or more simple straight lines.</td> - * <td>{@link Entities.EntityProperties-Line|EntityProperties-Line}</td></tr> - * <tr><td><code>"Material"</code></td><td>Modifies the existing materials on Model entities, Shape entities (albedo - * only), {@link Overlays.OverlayType|model overlays}, and avatars.</td> - * <td>{@link Entities.EntityProperties-Material|EntityProperties-Material}</td></tr> - * <tr><td><code>"Model"</code></td><td>A mesh model from an FBX or OBJ file.</td> - * <td>{@link Entities.EntityProperties-Model|EntityProperties-Model}</td></tr> - * <tr><td><code>"ParticleEffect"</code></td><td>A particle system that can be used to simulate things such as fire, - * smoke, snow, magic spells, etc.</td> - * <td>{@link Entities.EntityProperties-ParticleEffect|EntityProperties-ParticleEffect}</td></tr> - * <tr><td><code>"PolyLine"</code></td><td>A sequence of one or more textured straight lines.</td> - * <td>{@link Entities.EntityProperties-PolyLine|EntityProperties-PolyLine}</td></tr> - * <tr><td><code>"PolyVox"</code></td><td>A set of textured voxels.</td> - * <td>{@link Entities.EntityProperties-PolyVox|EntityProperties-PolyVox}</td></tr> - * <tr><td><code>"Shape"</code></td><td>A basic entity such as a cube. - * See also, the <code>"Box"</code> and <code>"Sphere"</code> entity types.</td> - * <td>{@link Entities.EntityProperties-Shape|EntityProperties-Shape}</td></tr> * <tr><td><code>"Sphere"</code></td><td>A sphere. This is a synonym of <code>"Shape"</code> for the case * where the entity's <code>shape</code> property value is <code>"Sphere"</code>.<br /> * If an entity is created with its <code>type</code> @@ -75,44 +56,72 @@ public: * <code>"Sphere"</code>. If an entity of type <code>Box</code> or <code>Shape</code> has its <code>shape</code> set * to <code>"Sphere"</code> then its <code>type</code> will be reported as <code>"Sphere"</code>. * <td>{@link Entities.EntityProperties-Sphere|EntityProperties-Sphere}</td></tr> + * <tr><td><code>"Shape"</code></td><td>A basic entity such as a cube. + * See also, the <code>"Box"</code> and <code>"Sphere"</code> entity types.</td> + * <td>{@link Entities.EntityProperties-Shape|EntityProperties-Shape}</td></tr> + * <tr><td><code>"Model"</code></td><td>A mesh model from an FBX or OBJ file.</td> + * <td>{@link Entities.EntityProperties-Model|EntityProperties-Model}</td></tr> * <tr><td><code>"Text"</code></td><td>A pane of text oriented in space.</td> * <td>{@link Entities.EntityProperties-Text|EntityProperties-Text}</td></tr> + * <tr><td><code>"Image"</code></td><td>An image oriented in space.</td> + * <td>{@link Entities.EntityProperties-Image|EntityProperties-Image}</td></tr> * <tr><td><code>"Web"</code></td><td>A browsable Web page.</td> * <td>{@link Entities.EntityProperties-Web|EntityProperties-Web}</td></tr> + * <tr><td><code>"ParticleEffect"</code></td><td>A particle system that can be used to simulate things such as fire, + * smoke, snow, magic spells, etc.</td> + * <td>{@link Entities.EntityProperties-ParticleEffect|EntityProperties-ParticleEffect}</td></tr> + * <tr><td><code>"Line"</code></td><td>A sequence of one or more simple straight lines.</td> + * <td>{@link Entities.EntityProperties-Line|EntityProperties-Line}</td></tr> + * <tr><td><code>"PolyLine"</code></td><td>A sequence of one or more textured straight lines.</td> + * <td>{@link Entities.EntityProperties-PolyLine|EntityProperties-PolyLine}</td></tr> + * <tr><td><code>"PolyVox"</code></td><td>A set of textured voxels.</td> + * <td>{@link Entities.EntityProperties-PolyVox|EntityProperties-PolyVox}</td></tr> + * <tr><td><code>"Grid"</code></td><td>A grid of lines in a plane.</td> + * <td>{@link Entities.EntityProperties-Grid|EntityProperties-Grid}</td></tr> + * <tr><td><code>"Light"</code></td><td>A local lighting effect.</td> + * <td>{@link Entities.EntityProperties-Light|EntityProperties-Light}</td></tr> * <tr><td><code>"Zone"</code></td><td>A volume of lighting effects and avatar permissions.</td> * <td>{@link Entities.EntityProperties-Zone|EntityProperties-Zone}</td></tr> + * <tr><td><code>"Material"</code></td><td>Modifies the existing materials on Model entities, Shape entities, + * {@link Overlays.OverlayType|model overlays}, and avatars.</td> + * <td>{@link Entities.EntityProperties-Material|EntityProperties-Material}</td></tr> * </tbody> * </table> * @typedef {string} Entities.EntityType */ typedef enum EntityType_t { Unknown, - Model, Box, Sphere, - Light, - Text, - ParticleEffect, - Zone, - Web, - Line, - PolyVox, - PolyLine, Shape, + Model, + Text, + Image, + Web, + ParticleEffect, + Line, + PolyLine, + PolyVox, + Grid, + Light, + Zone, Material, - LAST = Material + NUM_TYPES } EntityType; + static bool typeIsValid(EntityType type); static const QString& getEntityTypeName(EntityType entityType); static EntityTypes::EntityType getEntityTypeFromName(const QString& name); static bool registerEntityType(EntityType entityType, const char* name, EntityTypeFactory factoryMethod); + static void extractEntityTypeAndID(const unsigned char* data, int dataLength, EntityTypes::EntityType& typeOut, QUuid& idOut); static EntityItemPointer constructEntityItem(EntityType entityType, const EntityItemID& entityID, const EntityItemProperties& properties); - static EntityItemPointer constructEntityItem(const unsigned char* data, int bytesToRead, ReadBitstreamToTreeParams& args); + static EntityItemPointer constructEntityItem(const unsigned char* data, int bytesToRead); + static EntityItemPointer constructEntityItem(const QUuid& id, const EntityItemProperties& properties); private: static QMap<EntityType, QString> _typeToNameMap; static QMap<QString, EntityTypes::EntityType> _nameToTypeMap; - static EntityTypeFactory _factories[LAST + 1]; + static EntityTypeFactory _factories[NUM_TYPES]; static bool _factoriesInitialized; }; diff --git a/libraries/entities/src/GrabPropertyGroup.cpp b/libraries/entities/src/GrabPropertyGroup.cpp index 996eed4720..7a9ba147d9 100644 --- a/libraries/entities/src/GrabPropertyGroup.cpp +++ b/libraries/entities/src/GrabPropertyGroup.cpp @@ -24,6 +24,8 @@ void GrabPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProp COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_GRAB_FOLLOWS_CONTROLLER, Grab, grab, GrabFollowsController, grabFollowsController); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_GRAB_TRIGGERABLE, Grab, grab, Triggerable, triggerable); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_GRAB_EQUIPPABLE, Grab, grab, Equippable, equippable); + COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_GRAB_DELEGATE_TO_PARENT, Grab, grab, + GrabDelegateToParent, grabDelegateToParent); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, Grab, grab, EquippableLeftPosition, equippableLeftPosition); COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, Grab, grab, @@ -47,6 +49,7 @@ void GrabPropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _d COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, grabFollowsController, bool, setGrabFollowsController); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, triggerable, bool, setTriggerable); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, equippable, bool, setEquippable); + COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, grabDelegateToParent, bool, setGrabDelegateToParent); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, equippableLeftPosition, vec3, setEquippableLeftPosition); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, equippableLeftRotation, quat, setEquippableLeftRotation); COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, equippableRightPosition, vec3, setEquippableRightPosition); @@ -62,6 +65,7 @@ void GrabPropertyGroup::merge(const GrabPropertyGroup& other) { COPY_PROPERTY_IF_CHANGED(grabFollowsController); COPY_PROPERTY_IF_CHANGED(triggerable); COPY_PROPERTY_IF_CHANGED(equippable); + COPY_PROPERTY_IF_CHANGED(grabDelegateToParent); COPY_PROPERTY_IF_CHANGED(equippableLeftPosition); COPY_PROPERTY_IF_CHANGED(equippableLeftRotation); COPY_PROPERTY_IF_CHANGED(equippableRightPosition); @@ -104,6 +108,9 @@ void GrabPropertyGroup::listChangedProperties(QList<QString>& out) { if (equippableChanged()) { out << "grab-equippable"; } + if (grabDelegateToParentChanged()) { + out << "grab-grabDelegateToParent"; + } if (equippableLeftPositionChanged()) { out << "grab-equippableLeftPosition"; } @@ -141,6 +148,7 @@ bool GrabPropertyGroup::appendToEditPacket(OctreePacketData* packetData, APPEND_ENTITY_PROPERTY(PROP_GRAB_FOLLOWS_CONTROLLER, getGrabFollowsController()); APPEND_ENTITY_PROPERTY(PROP_GRAB_TRIGGERABLE, getTriggerable()); APPEND_ENTITY_PROPERTY(PROP_GRAB_EQUIPPABLE, getEquippable()); + APPEND_ENTITY_PROPERTY(PROP_GRAB_DELEGATE_TO_PARENT, getGrabDelegateToParent()); APPEND_ENTITY_PROPERTY(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, getEquippableLeftPosition()); APPEND_ENTITY_PROPERTY(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, getEquippableLeftRotation()); APPEND_ENTITY_PROPERTY(PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET, getEquippableRightPosition()); @@ -164,6 +172,7 @@ bool GrabPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFlags, READ_ENTITY_PROPERTY(PROP_GRAB_FOLLOWS_CONTROLLER, bool, setGrabFollowsController); READ_ENTITY_PROPERTY(PROP_GRAB_TRIGGERABLE, bool, setTriggerable); READ_ENTITY_PROPERTY(PROP_GRAB_EQUIPPABLE, bool, setEquippable); + READ_ENTITY_PROPERTY(PROP_GRAB_DELEGATE_TO_PARENT, bool, setGrabDelegateToParent); READ_ENTITY_PROPERTY(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, glm::vec3, setEquippableLeftPosition); READ_ENTITY_PROPERTY(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, glm::quat, setEquippableLeftRotation); READ_ENTITY_PROPERTY(PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET, glm::vec3, setEquippableRightPosition); @@ -177,6 +186,7 @@ bool GrabPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFlags, DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_GRAB_FOLLOWS_CONTROLLER, GrabFollowsController); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_GRAB_TRIGGERABLE, Triggerable); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_GRAB_EQUIPPABLE, Equippable); + DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_GRAB_DELEGATE_TO_PARENT, GrabDelegateToParent); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, EquippableLeftPosition); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, EquippableLeftRotation); DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET, EquippableRightPosition); @@ -198,6 +208,7 @@ void GrabPropertyGroup::markAllChanged() { _grabFollowsControllerChanged = true; _triggerableChanged = true; _equippableChanged = true; + _grabDelegateToParentChanged = true; _equippableLeftPositionChanged = true; _equippableLeftRotationChanged = true; _equippableRightPositionChanged = true; @@ -215,6 +226,7 @@ EntityPropertyFlags GrabPropertyGroup::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_GRAB_FOLLOWS_CONTROLLER, grabFollowsController); CHECK_PROPERTY_CHANGE(PROP_GRAB_TRIGGERABLE, triggerable); CHECK_PROPERTY_CHANGE(PROP_GRAB_EQUIPPABLE, equippable); + CHECK_PROPERTY_CHANGE(PROP_GRAB_DELEGATE_TO_PARENT, grabDelegateToParent); CHECK_PROPERTY_CHANGE(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, equippableLeftPosition); CHECK_PROPERTY_CHANGE(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, equippableLeftRotation); CHECK_PROPERTY_CHANGE(PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET, equippableRightPosition); @@ -232,6 +244,7 @@ void GrabPropertyGroup::getProperties(EntityItemProperties& properties) const { COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Grab, GrabFollowsController, getGrabFollowsController); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Grab, Triggerable, getTriggerable); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Grab, Equippable, getEquippable); + COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Grab, GrabDelegateToParent, getGrabDelegateToParent); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Grab, EquippableLeftPosition, getEquippableLeftPosition); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Grab, EquippableLeftRotation, getEquippableLeftRotation); COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Grab, EquippableRightPosition, getEquippableRightPosition); @@ -249,6 +262,7 @@ bool GrabPropertyGroup::setProperties(const EntityItemProperties& properties) { SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Grab, GrabFollowsController, grabFollowsController, setGrabFollowsController); SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Grab, Triggerable, triggerable, setTriggerable); SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Grab, Equippable, equippable, setEquippable); + SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Grab, GrabDelegateToParent, grabDelegateToParent, setGrabDelegateToParent); SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Grab, EquippableLeftPosition, equippableLeftPosition, setEquippableLeftPosition); SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Grab, EquippableLeftRotation, equippableLeftRotation, setEquippableLeftRotation); SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Grab, EquippableRightPosition, equippableRightPosition, @@ -273,6 +287,7 @@ EntityPropertyFlags GrabPropertyGroup::getEntityProperties(EncodeBitstreamParams requestedProperties += PROP_GRAB_FOLLOWS_CONTROLLER; requestedProperties += PROP_GRAB_TRIGGERABLE; requestedProperties += PROP_GRAB_EQUIPPABLE; + requestedProperties += PROP_GRAB_DELEGATE_TO_PARENT; requestedProperties += PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET; requestedProperties += PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET; requestedProperties += PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET; @@ -299,6 +314,7 @@ void GrabPropertyGroup::appendSubclassData(OctreePacketData* packetData, EncodeB APPEND_ENTITY_PROPERTY(PROP_GRAB_FOLLOWS_CONTROLLER, getGrabFollowsController()); APPEND_ENTITY_PROPERTY(PROP_GRAB_TRIGGERABLE, getTriggerable()); APPEND_ENTITY_PROPERTY(PROP_GRAB_EQUIPPABLE, getEquippable()); + APPEND_ENTITY_PROPERTY(PROP_GRAB_DELEGATE_TO_PARENT, getGrabDelegateToParent()); APPEND_ENTITY_PROPERTY(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, getEquippableLeftPosition()); APPEND_ENTITY_PROPERTY(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, getEquippableLeftRotation()); APPEND_ENTITY_PROPERTY(PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET, getEquippableRightPosition()); @@ -321,6 +337,7 @@ int GrabPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* dat READ_ENTITY_PROPERTY(PROP_GRAB_FOLLOWS_CONTROLLER, bool, setGrabFollowsController); READ_ENTITY_PROPERTY(PROP_GRAB_TRIGGERABLE, bool, setTriggerable); READ_ENTITY_PROPERTY(PROP_GRAB_EQUIPPABLE, bool, setEquippable); + READ_ENTITY_PROPERTY(PROP_GRAB_DELEGATE_TO_PARENT, bool, setGrabDelegateToParent); READ_ENTITY_PROPERTY(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, glm::vec3, setEquippableLeftPosition); READ_ENTITY_PROPERTY(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, glm::quat, setEquippableLeftRotation); READ_ENTITY_PROPERTY(PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET, glm::vec3, setEquippableRightPosition); diff --git a/libraries/entities/src/GrabPropertyGroup.h b/libraries/entities/src/GrabPropertyGroup.h index 37b157de06..33550fef3d 100644 --- a/libraries/entities/src/GrabPropertyGroup.h +++ b/libraries/entities/src/GrabPropertyGroup.h @@ -31,6 +31,7 @@ static const bool INITIAL_KINEMATIC { true }; static const bool INITIAL_FOLLOWS_CONTROLLER { true }; static const bool INITIAL_TRIGGERABLE { false }; static const bool INITIAL_EQUIPPABLE { false }; +static const bool INITIAL_GRAB_DELEGATE_TO_PARENT { true }; static const glm::vec3 INITIAL_LEFT_EQUIPPABLE_POSITION { glm::vec3(0.0f) }; static const glm::quat INITIAL_LEFT_EQUIPPABLE_ROTATION { glm::quat() }; static const glm::vec3 INITIAL_RIGHT_EQUIPPABLE_POSITION { glm::vec3(0.0f) }; @@ -123,6 +124,8 @@ public: INITIAL_FOLLOWS_CONTROLLER); DEFINE_PROPERTY(PROP_GRAB_TRIGGERABLE, Triggerable, triggerable, bool, INITIAL_TRIGGERABLE); DEFINE_PROPERTY(PROP_GRAB_EQUIPPABLE, Equippable, equippable, bool, INITIAL_EQUIPPABLE); + DEFINE_PROPERTY(PROP_GRAB_DELEGATE_TO_PARENT, GrabDelegateToParent, grabDelegateToParent, bool, + INITIAL_GRAB_DELEGATE_TO_PARENT); DEFINE_PROPERTY_REF(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, EquippableLeftPosition, equippableLeftPosition, glm::vec3, INITIAL_LEFT_EQUIPPABLE_POSITION); DEFINE_PROPERTY_REF(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, EquippableLeftRotation, equippableLeftRotation, diff --git a/libraries/entities/src/GridEntityItem.cpp b/libraries/entities/src/GridEntityItem.cpp new file mode 100644 index 0000000000..b4709239ed --- /dev/null +++ b/libraries/entities/src/GridEntityItem.cpp @@ -0,0 +1,178 @@ +// +// Created by Sam Gondelman on 11/29/18 +// 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 "GridEntityItem.h" + +#include "EntityItemProperties.h" + +const uint32_t GridEntityItem::DEFAULT_MAJOR_GRID_EVERY = 5; +const float GridEntityItem::DEFAULT_MINOR_GRID_EVERY = 1.0f; + +EntityItemPointer GridEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { + Pointer entity(new GridEntityItem(entityID), [](EntityItem* ptr) { ptr->deleteLater(); }); + entity->setProperties(properties); + return entity; +} + +// our non-pure virtual subclass for now... +GridEntityItem::GridEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { + _type = EntityTypes::Grid; +} + +void GridEntityItem::setUnscaledDimensions(const glm::vec3& value) { + const float GRID_ENTITY_ITEM_FIXED_DEPTH = 0.01f; + // NOTE: Grid Entities always have a "depth" of 1cm. + EntityItem::setUnscaledDimensions(glm::vec3(value.x, value.y, GRID_ENTITY_ITEM_FIXED_DEPTH)); +} + +EntityItemProperties GridEntityItem::getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const { + EntityItemProperties properties = EntityItem::getProperties(desiredProperties, allowEmptyDesiredProperties); // get the properties from our base class + + COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getColor); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(alpha, getAlpha); + + COPY_ENTITY_PROPERTY_TO_PROPERTIES(followCamera, getFollowCamera); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(majorGridEvery, getMajorGridEvery); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(minorGridEvery, getMinorGridEvery); + + return properties; +} + +bool GridEntityItem::setProperties(const EntityItemProperties& properties) { + bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class + + SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha); + + SET_ENTITY_PROPERTY_FROM_PROPERTIES(followCamera, setFollowCamera); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(majorGridEvery, setMajorGridEvery); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(minorGridEvery, setMinorGridEvery); + + if (somethingChanged) { + bool wantDebug = false; + if (wantDebug) { + uint64_t now = usecTimestampNow(); + int elapsed = now - getLastEdited(); + qCDebug(entities) << "GridEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << + "now=" << now << " getLastEdited()=" << getLastEdited(); + } + setLastEdited(properties.getLastEdited()); + } + return somethingChanged; +} + +int GridEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData, + bool& somethingChanged) { + + int bytesRead = 0; + const unsigned char* dataAt = data; + + READ_ENTITY_PROPERTY(PROP_COLOR, u8vec3Color, setColor); + READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha); + + READ_ENTITY_PROPERTY(PROP_GRID_FOLLOW_CAMERA, bool, setFollowCamera); + READ_ENTITY_PROPERTY(PROP_MAJOR_GRID_EVERY, uint32_t, setMajorGridEvery); + READ_ENTITY_PROPERTY(PROP_MINOR_GRID_EVERY, float, setMinorGridEvery); + + return bytesRead; +} + +EntityPropertyFlags GridEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { + EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); + + requestedProperties += PROP_COLOR; + requestedProperties += PROP_ALPHA; + + requestedProperties += PROP_GRID_FOLLOW_CAMERA; + requestedProperties += PROP_MAJOR_GRID_EVERY; + requestedProperties += PROP_MINOR_GRID_EVERY; + + return requestedProperties; +} + +void GridEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const { + + bool successPropertyFits = true; + + APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); + APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha()); + + APPEND_ENTITY_PROPERTY(PROP_GRID_FOLLOW_CAMERA, getFollowCamera()); + APPEND_ENTITY_PROPERTY(PROP_MAJOR_GRID_EVERY, getMajorGridEvery()); + APPEND_ENTITY_PROPERTY(PROP_MINOR_GRID_EVERY, getMinorGridEvery()); +} + +void GridEntityItem::setColor(const glm::u8vec3& color) { + withWriteLock([&] { + _color = color; + }); +} + +glm::u8vec3 GridEntityItem::getColor() const { + return resultWithReadLock<glm::u8vec3>([&] { + return _color; + }); +} + +void GridEntityItem::setAlpha(float alpha) { + withWriteLock([&] { + _alpha = alpha; + }); +} + +float GridEntityItem::getAlpha() const { + return resultWithReadLock<float>([&] { + return _alpha; + }); +} + +void GridEntityItem::setFollowCamera(bool followCamera) { + withWriteLock([&] { + _followCamera = followCamera; + }); +} + +bool GridEntityItem::getFollowCamera() const { + return resultWithReadLock<bool>([&] { + return _followCamera; + }); +} + +void GridEntityItem::setMajorGridEvery(uint32_t majorGridEvery) { + withWriteLock([&] { + const uint32_t MAJOR_GRID_EVERY_MIN = 1; + _majorGridEvery = std::max(majorGridEvery, MAJOR_GRID_EVERY_MIN); + }); +} + +uint32_t GridEntityItem::getMajorGridEvery() const { + return resultWithReadLock<uint32_t>([&] { + return _majorGridEvery; + }); +} + +void GridEntityItem::setMinorGridEvery(float minorGridEvery) { + withWriteLock([&] { + const float MINOR_GRID_EVERY_MIN = 0.01f; + _minorGridEvery = std::max(minorGridEvery, MINOR_GRID_EVERY_MIN); + }); +} + +float GridEntityItem::getMinorGridEvery() const { + return resultWithReadLock<float>([&] { + return _minorGridEvery; + }); +} \ No newline at end of file diff --git a/libraries/entities/src/GridEntityItem.h b/libraries/entities/src/GridEntityItem.h new file mode 100644 index 0000000000..6209aee73d --- /dev/null +++ b/libraries/entities/src/GridEntityItem.h @@ -0,0 +1,71 @@ +// +// Created by Sam Gondelman on 11/29/18 +// 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 +// + +#ifndef hifi_GridEntityItem_h +#define hifi_GridEntityItem_h + +#include "EntityItem.h" + +class GridEntityItem : public EntityItem { + using Pointer = std::shared_ptr<GridEntityItem>; +public: + static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); + + GridEntityItem(const EntityItemID& entityItemID); + + ALLOW_INSTANTIATION // This class can be instantiated + + virtual void setUnscaledDimensions(const glm::vec3& value) override; + + // methods for getting/setting all properties of an entity + EntityItemProperties getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const override; + bool setProperties(const EntityItemProperties& properties) override; + + EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; + + void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const override; + + int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData, + bool& somethingChanged) override; + + static const uint32_t DEFAULT_MAJOR_GRID_EVERY; + static const float DEFAULT_MINOR_GRID_EVERY; + + void setColor(const glm::u8vec3& color); + glm::u8vec3 getColor() const; + + void setAlpha(float alpha); + float getAlpha() const; + + void setFollowCamera(bool followCamera); + bool getFollowCamera() const; + + void setMajorGridEvery(uint32_t majorGridEvery); + uint32_t getMajorGridEvery() const; + + void setMinorGridEvery(float minorGridEvery); + float getMinorGridEvery() const; + +protected: + glm::u8vec3 _color; + float _alpha; + bool _followCamera { true }; + uint32_t _majorGridEvery { DEFAULT_MAJOR_GRID_EVERY }; + float _minorGridEvery { DEFAULT_MINOR_GRID_EVERY }; + +}; + +#endif // hifi_GridEntityItem_h diff --git a/libraries/entities/src/ImageEntityItem.cpp b/libraries/entities/src/ImageEntityItem.cpp new file mode 100644 index 0000000000..cdff1b5390 --- /dev/null +++ b/libraries/entities/src/ImageEntityItem.cpp @@ -0,0 +1,269 @@ +// +// Created by Sam Gondelman on 11/29/18 +// 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 "ImageEntityItem.h" + +#include "EntityItemProperties.h" + +EntityItemPointer ImageEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { + Pointer entity(new ImageEntityItem(entityID), [](EntityItem* ptr) { ptr->deleteLater(); }); + entity->setProperties(properties); + return entity; +} + +// our non-pure virtual subclass for now... +ImageEntityItem::ImageEntityItem(const EntityItemID& entityItemID) : EntityItem(entityItemID) { + _type = EntityTypes::Image; +} + +void ImageEntityItem::setUnscaledDimensions(const glm::vec3& value) { + const float IMAGE_ENTITY_ITEM_FIXED_DEPTH = 0.01f; + // NOTE: Image Entities always have a "depth" of 1cm. + EntityItem::setUnscaledDimensions(glm::vec3(value.x, value.y, IMAGE_ENTITY_ITEM_FIXED_DEPTH)); +} + +EntityItemProperties ImageEntityItem::getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const { + EntityItemProperties properties = EntityItem::getProperties(desiredProperties, allowEmptyDesiredProperties); // get the properties from our base class + + COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getColor); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(alpha, getAlpha); + + COPY_ENTITY_PROPERTY_TO_PROPERTIES(imageURL, getImageURL); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(emissive, getEmissive); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(keepAspectRatio, getKeepAspectRatio); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(billboardMode, getBillboardMode); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(subImage, getSubImage); + + return properties; +} + +bool ImageEntityItem::setProperties(const EntityItemProperties& properties) { + bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class + + SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha); + + SET_ENTITY_PROPERTY_FROM_PROPERTIES(imageURL, setImageURL); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(emissive, setEmissive); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(keepAspectRatio, setKeepAspectRatio); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(billboardMode, setBillboardMode); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(subImage, setSubImage); + + if (somethingChanged) { + bool wantDebug = false; + if (wantDebug) { + uint64_t now = usecTimestampNow(); + int elapsed = now - getLastEdited(); + qCDebug(entities) << "ImageEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << + "now=" << now << " getLastEdited()=" << getLastEdited(); + } + setLastEdited(properties.getLastEdited()); + } + return somethingChanged; +} + +int ImageEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData, + bool& somethingChanged) { + + int bytesRead = 0; + const unsigned char* dataAt = data; + + READ_ENTITY_PROPERTY(PROP_COLOR, u8vec3Color, setColor); + READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha); + + READ_ENTITY_PROPERTY(PROP_IMAGE_URL, QString, setImageURL); + READ_ENTITY_PROPERTY(PROP_EMISSIVE, bool, setEmissive); + READ_ENTITY_PROPERTY(PROP_KEEP_ASPECT_RATIO, bool, setKeepAspectRatio); + READ_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, BillboardMode, setBillboardMode); + READ_ENTITY_PROPERTY(PROP_SUB_IMAGE, QRect, setSubImage); + + return bytesRead; +} + +EntityPropertyFlags ImageEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { + EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); + + requestedProperties += PROP_COLOR; + requestedProperties += PROP_ALPHA; + + requestedProperties += PROP_IMAGE_URL; + requestedProperties += PROP_EMISSIVE; + requestedProperties += PROP_KEEP_ASPECT_RATIO; + requestedProperties += PROP_BILLBOARD_MODE; + requestedProperties += PROP_SUB_IMAGE; + + return requestedProperties; +} + +void ImageEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const { + + bool successPropertyFits = true; + + APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); + APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha()); + + APPEND_ENTITY_PROPERTY(PROP_IMAGE_URL, getImageURL()); + APPEND_ENTITY_PROPERTY(PROP_EMISSIVE, getEmissive()); + APPEND_ENTITY_PROPERTY(PROP_KEEP_ASPECT_RATIO, getKeepAspectRatio()); + APPEND_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, (uint32_t)getBillboardMode()); + APPEND_ENTITY_PROPERTY(PROP_SUB_IMAGE, getSubImage()); +} + +bool ImageEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + OctreeElementPointer& element, + float& distance, BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { + glm::vec3 dimensions = getScaledDimensions(); + glm::vec2 xyDimensions(dimensions.x, dimensions.y); + glm::quat rotation = getWorldOrientation(); + glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); + + if (findRayRectangleIntersection(origin, direction, rotation, position, xyDimensions, distance)) { + glm::vec3 forward = rotation * Vectors::FRONT; + if (glm::dot(forward, direction) > 0.0f) { + face = MAX_Z_FACE; + surfaceNormal = -forward; + } else { + face = MIN_Z_FACE; + surfaceNormal = forward; + } + return true; + } + return false; +} + +bool ImageEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, + OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const { + glm::vec3 dimensions = getScaledDimensions(); + glm::vec2 xyDimensions(dimensions.x, dimensions.y); + glm::quat rotation = getWorldOrientation(); + glm::vec3 position = getWorldPosition() + rotation * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())); + + glm::quat inverseRot = glm::inverse(rotation); + glm::vec3 localOrigin = inverseRot * (origin - position); + glm::vec3 localVelocity = inverseRot * velocity; + glm::vec3 localAcceleration = inverseRot * acceleration; + + if (findParabolaRectangleIntersection(localOrigin, localVelocity, localAcceleration, xyDimensions, parabolicDistance)) { + float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * parabolicDistance; + glm::vec3 forward = rotation * Vectors::FRONT; + if (localIntersectionVelocityZ > 0.0f) { + face = MIN_Z_FACE; + surfaceNormal = forward; + } else { + face = MAX_Z_FACE; + surfaceNormal = -forward; + } + return true; + } + return false; +} + +QString ImageEntityItem::getImageURL() const { + QString result; + withReadLock([&] { + result = _imageURL; + }); + return result; +} + +void ImageEntityItem::setImageURL(const QString& url) { + withWriteLock([&] { + _imageURL = url; + }); +} + +bool ImageEntityItem::getEmissive() const { + bool result; + withReadLock([&] { + result = _emissive; + }); + return result; +} + +void ImageEntityItem::setEmissive(bool emissive) { + withWriteLock([&] { + _emissive = emissive; + }); +} + +bool ImageEntityItem::getKeepAspectRatio() const { + bool result; + withReadLock([&] { + result = _keepAspectRatio; + }); + return result; +} + +void ImageEntityItem::setKeepAspectRatio(bool keepAspectRatio) { + withWriteLock([&] { + _keepAspectRatio = keepAspectRatio; + }); +} + +BillboardMode ImageEntityItem::getBillboardMode() const { + BillboardMode result; + withReadLock([&] { + result = _billboardMode; + }); + return result; +} + +void ImageEntityItem::setBillboardMode(BillboardMode value) { + withWriteLock([&] { + _billboardMode = value; + }); +} + +QRect ImageEntityItem::getSubImage() const { + QRect result; + withReadLock([&] { + result = _subImage; + }); + return result; +} + +void ImageEntityItem::setSubImage(const QRect& subImage) { + withWriteLock([&] { + _subImage = subImage; + }); +} + +void ImageEntityItem::setColor(const glm::u8vec3& color) { + withWriteLock([&] { + _color = color; + }); +} + +glm::u8vec3 ImageEntityItem::getColor() const { + return resultWithReadLock<glm::u8vec3>([&] { + return _color; + }); +} + +void ImageEntityItem::setAlpha(float alpha) { + withWriteLock([&] { + _alpha = alpha; + }); +} + +float ImageEntityItem::getAlpha() const { + return resultWithReadLock<float>([&] { + return _alpha; + }); +} \ No newline at end of file diff --git a/libraries/entities/src/ImageEntityItem.h b/libraries/entities/src/ImageEntityItem.h new file mode 100644 index 0000000000..228f86ca03 --- /dev/null +++ b/libraries/entities/src/ImageEntityItem.h @@ -0,0 +1,86 @@ +// +// Created by Sam Gondelman on 11/29/18 +// 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 +// + +#ifndef hifi_ImageEntityItem_h +#define hifi_ImageEntityItem_h + +#include "EntityItem.h" + +class ImageEntityItem : public EntityItem { + using Pointer = std::shared_ptr<ImageEntityItem>; +public: + static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); + + ImageEntityItem(const EntityItemID& entityItemID); + + ALLOW_INSTANTIATION // This class can be instantiated + + virtual void setUnscaledDimensions(const glm::vec3& value) override; + + // methods for getting/setting all properties of an entity + EntityItemProperties getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const override; + bool setProperties(const EntityItemProperties& properties) override; + + EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; + + void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeDataPointer modelTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const override; + + int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData, + bool& somethingChanged) override; + + virtual bool supportsDetailedIntersection() const override { return true; } + virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + OctreeElementPointer& element, float& distance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override; + virtual bool findDetailedParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, + const glm::vec3& acceleration, OctreeElementPointer& element, float& parabolicDistance, + BoxFace& face, glm::vec3& surfaceNormal, + QVariantMap& extraInfo, bool precisionPicking) const override; + + void setImageURL(const QString& imageUrl); + QString getImageURL() const; + + void setEmissive(bool emissive); + bool getEmissive() const; + + void setKeepAspectRatio(bool keepAspectRatio); + bool getKeepAspectRatio() const; + + void setBillboardMode(BillboardMode value); + BillboardMode getBillboardMode() const; + + void setSubImage(const QRect& subImage); + QRect getSubImage() const; + + void setColor(const glm::u8vec3& color); + glm::u8vec3 getColor() const; + + void setAlpha(float alpha); + float getAlpha() const; + +protected: + QString _imageURL; + bool _emissive { false }; + bool _keepAspectRatio { true }; + BillboardMode _billboardMode; + QRect _subImage; + + glm::u8vec3 _color; + float _alpha; +}; + +#endif // hifi_ImageEntityItem_h diff --git a/libraries/entities/src/LightEntityItem.cpp b/libraries/entities/src/LightEntityItem.cpp index e3de5f66f8..88aae9691c 100644 --- a/libraries/entities/src/LightEntityItem.cpp +++ b/libraries/entities/src/LightEntityItem.cpp @@ -25,6 +25,8 @@ const bool LightEntityItem::DEFAULT_IS_SPOTLIGHT = false; const float LightEntityItem::DEFAULT_INTENSITY = 1.0f; const float LightEntityItem::DEFAULT_FALLOFF_RADIUS = 0.1f; const float LightEntityItem::DEFAULT_EXPONENT = 0.0f; +const float LightEntityItem::MIN_CUTOFF = 0.0f; +const float LightEntityItem::MAX_CUTOFF = 90.0f; const float LightEntityItem::DEFAULT_CUTOFF = PI / 2.0f; bool LightEntityItem::_lightsArePickable = false; @@ -71,8 +73,8 @@ void LightEntityItem::dimensionsChanged() { EntityItemProperties LightEntityItem::getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const { EntityItemProperties properties = EntityItem::getProperties(desiredProperties, allowEmptyDesiredProperties); // get the properties from our base class - COPY_ENTITY_PROPERTY_TO_PROPERTIES(isSpotlight, getIsSpotlight); COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getColor); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(isSpotlight, getIsSpotlight); COPY_ENTITY_PROPERTY_TO_PROPERTIES(intensity, getIntensity); COPY_ENTITY_PROPERTY_TO_PROPERTIES(exponent, getExponent); COPY_ENTITY_PROPERTY_TO_PROPERTIES(cutoff, getCutoff); @@ -115,7 +117,7 @@ void LightEntityItem::setIsSpotlight(bool value) { } void LightEntityItem::setCutoff(float value) { - value = glm::clamp(value, 0.0f, 90.0f); + value = glm::clamp(value, MIN_CUTOFF, MAX_CUTOFF); if (value == getCutoff()) { return; } @@ -155,8 +157,8 @@ bool LightEntityItem::setProperties(const EntityItemProperties& properties) { bool LightEntityItem::setSubClassProperties(const EntityItemProperties& properties) { bool somethingChanged = EntityItem::setSubClassProperties(properties); // set the properties in our base class - SET_ENTITY_PROPERTY_FROM_PROPERTIES(isSpotlight, setIsSpotlight); SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(isSpotlight, setIsSpotlight); SET_ENTITY_PROPERTY_FROM_PROPERTIES(intensity, setIntensity); SET_ENTITY_PROPERTY_FROM_PROPERTIES(exponent, setExponent); SET_ENTITY_PROPERTY_FROM_PROPERTIES(cutoff, setCutoff); @@ -174,8 +176,8 @@ int LightEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesRead = 0; const unsigned char* dataAt = data; - READ_ENTITY_PROPERTY(PROP_IS_SPOTLIGHT, bool, setIsSpotlight); READ_ENTITY_PROPERTY(PROP_COLOR, glm::u8vec3, setColor); + READ_ENTITY_PROPERTY(PROP_IS_SPOTLIGHT, bool, setIsSpotlight); READ_ENTITY_PROPERTY(PROP_INTENSITY, float, setIntensity); READ_ENTITY_PROPERTY(PROP_EXPONENT, float, setExponent); READ_ENTITY_PROPERTY(PROP_CUTOFF, float, setCutoff); @@ -187,8 +189,8 @@ int LightEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, EntityPropertyFlags LightEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); - requestedProperties += PROP_IS_SPOTLIGHT; requestedProperties += PROP_COLOR; + requestedProperties += PROP_IS_SPOTLIGHT; requestedProperties += PROP_INTENSITY; requestedProperties += PROP_EXPONENT; requestedProperties += PROP_CUTOFF; @@ -205,8 +207,8 @@ void LightEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit OctreeElement::AppendState& appendState) const { bool successPropertyFits = true; - APPEND_ENTITY_PROPERTY(PROP_IS_SPOTLIGHT, getIsSpotlight()); APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); + APPEND_ENTITY_PROPERTY(PROP_IS_SPOTLIGHT, getIsSpotlight()); APPEND_ENTITY_PROPERTY(PROP_INTENSITY, getIntensity()); APPEND_ENTITY_PROPERTY(PROP_EXPONENT, getExponent()); APPEND_ENTITY_PROPERTY(PROP_CUTOFF, getCutoff()); diff --git a/libraries/entities/src/LightEntityItem.h b/libraries/entities/src/LightEntityItem.h index 813333d534..26b74f02cd 100644 --- a/libraries/entities/src/LightEntityItem.h +++ b/libraries/entities/src/LightEntityItem.h @@ -20,6 +20,8 @@ public: static const float DEFAULT_INTENSITY; static const float DEFAULT_FALLOFF_RADIUS; static const float DEFAULT_EXPONENT; + static const float MIN_CUTOFF; + static const float MAX_CUTOFF; static const float DEFAULT_CUTOFF; static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); diff --git a/libraries/entities/src/LineEntityItem.cpp b/libraries/entities/src/LineEntityItem.cpp index 4957c30112..12d1178690 100644 --- a/libraries/entities/src/LineEntityItem.cpp +++ b/libraries/entities/src/LineEntityItem.cpp @@ -21,7 +21,6 @@ #include "EntityTreeElement.h" #include "OctreeConstants.h" -const float LineEntityItem::DEFAULT_LINE_WIDTH = 2.0f; const int LineEntityItem::MAX_POINTS_PER_LINE = 70; @@ -42,7 +41,6 @@ EntityItemProperties LineEntityItem::getProperties(const EntityPropertyFlags& de EntityItemProperties properties = EntityItem::getProperties(desiredProperties, allowEmptyDesiredProperties); // get the properties from our base class COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getColor); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(lineWidth, getLineWidth); COPY_ENTITY_PROPERTY_TO_PROPERTIES(linePoints, getLinePoints); return properties; @@ -53,7 +51,6 @@ bool LineEntityItem::setProperties(const EntityItemProperties& properties) { somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(lineWidth, setLineWidth); SET_ENTITY_PROPERTY_FROM_PROPERTIES(linePoints, setLinePoints); if (somethingChanged) { @@ -115,7 +112,6 @@ int LineEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, const unsigned char* dataAt = data; READ_ENTITY_PROPERTY(PROP_COLOR, glm::u8vec3, setColor); - READ_ENTITY_PROPERTY(PROP_LINE_WIDTH, float, setLineWidth); READ_ENTITY_PROPERTY(PROP_LINE_POINTS, QVector<glm::vec3>, setLinePoints); return bytesRead; @@ -125,7 +121,6 @@ int LineEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, EntityPropertyFlags LineEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_COLOR; - requestedProperties += PROP_LINE_WIDTH; requestedProperties += PROP_LINE_POINTS; return requestedProperties; } @@ -141,7 +136,6 @@ void LineEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits bool successPropertyFits = true; APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); - APPEND_ENTITY_PROPERTY(PROP_LINE_WIDTH, getLineWidth()); APPEND_ENTITY_PROPERTY(PROP_LINE_POINTS, getLinePoints()); } @@ -166,20 +160,6 @@ void LineEntityItem::setColor(const glm::u8vec3& value) { }); } -void LineEntityItem::setLineWidth(float lineWidth) { - withWriteLock([&] { - _lineWidth = lineWidth; - }); -} - -float LineEntityItem::getLineWidth() const { - float result; - withReadLock([&] { - result = _lineWidth; - }); - return result; -} - QVector<glm::vec3> LineEntityItem::getLinePoints() const { QVector<glm::vec3> result; withReadLock([&] { diff --git a/libraries/entities/src/LineEntityItem.h b/libraries/entities/src/LineEntityItem.h index 1ebb248d23..86ca6065bb 100644 --- a/libraries/entities/src/LineEntityItem.h +++ b/libraries/entities/src/LineEntityItem.h @@ -44,9 +44,6 @@ class LineEntityItem : public EntityItem { glm::u8vec3 getColor() const; void setColor(const glm::u8vec3& value); - void setLineWidth(float lineWidth); - float getLineWidth() const; - bool setLinePoints(const QVector<glm::vec3>& points); bool appendPoint(const glm::vec3& point); @@ -69,12 +66,10 @@ class LineEntityItem : public EntityItem { bool pointsChanged() const { return _pointsChanged; } void resetPointsChanged(); virtual void debugDump() const override; - static const float DEFAULT_LINE_WIDTH; static const int MAX_POINTS_PER_LINE; private: glm::u8vec3 _color; - float _lineWidth { DEFAULT_LINE_WIDTH }; QVector<glm::vec3> _points; bool _pointsChanged { true }; }; diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index e68e287ee2..55ae1c6c3b 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -55,11 +55,13 @@ void ModelEntityItem::setTextures(const QString& textures) { EntityItemProperties ModelEntityItem::getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const { EntityItemProperties properties = EntityItem::getProperties(desiredProperties, allowEmptyDesiredProperties); // get the properties from our base class - COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getColor); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(modelURL, getModelURL); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(compoundShapeURL, getCompoundShapeURL); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(textures, getTextures); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(shapeType, getShapeType); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(compoundShapeURL, getCompoundShapeURL); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getColor); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(textures, getTextures); + + COPY_ENTITY_PROPERTY_TO_PROPERTIES(modelURL, getModelURL); COPY_ENTITY_PROPERTY_TO_PROPERTIES(jointRotationsSet, getJointRotationsSet); COPY_ENTITY_PROPERTY_TO_PROPERTIES(jointRotations, getJointRotations); COPY_ENTITY_PROPERTY_TO_PROPERTIES(jointTranslationsSet, getJointTranslationsSet); @@ -75,11 +77,12 @@ bool ModelEntityItem::setProperties(const EntityItemProperties& properties) { bool somethingChanged = false; somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class - SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(modelURL, setModelURL); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(textures, setTextures); SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(textures, setTextures); + + SET_ENTITY_PROPERTY_FROM_PROPERTIES(modelURL, setModelURL); SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointRotationsSet, setJointRotationsSet); SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointRotations, setJointRotations); SET_ENTITY_PROPERTY_FROM_PROPERTIES(jointTranslationsSet, setJointTranslationsSet); @@ -116,10 +119,16 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, const unsigned char* dataAt = data; bool animationPropertiesChanged = false; - READ_ENTITY_PROPERTY(PROP_COLOR, glm::u8vec3, setColor); - READ_ENTITY_PROPERTY(PROP_MODEL_URL, QString, setModelURL); + READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType); READ_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL); + READ_ENTITY_PROPERTY(PROP_COLOR, glm::u8vec3, setColor); READ_ENTITY_PROPERTY(PROP_TEXTURES, QString, setTextures); + + READ_ENTITY_PROPERTY(PROP_MODEL_URL, QString, setModelURL); + READ_ENTITY_PROPERTY(PROP_JOINT_ROTATIONS_SET, QVector<bool>, setJointRotationsSet); + READ_ENTITY_PROPERTY(PROP_JOINT_ROTATIONS, QVector<glm::quat>, setJointRotations); + READ_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS_SET, QVector<bool>, setJointTranslationsSet); + READ_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS, QVector<glm::vec3>, setJointTranslations); READ_ENTITY_PROPERTY(PROP_RELAY_PARENT_JOINTS, bool, setRelayParentJoints); // grab a local copy of _animationProperties to avoid multiple locks @@ -140,30 +149,24 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, bytesRead += bytesFromAnimation; dataAt += bytesFromAnimation; - READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType); - - READ_ENTITY_PROPERTY(PROP_JOINT_ROTATIONS_SET, QVector<bool>, setJointRotationsSet); - READ_ENTITY_PROPERTY(PROP_JOINT_ROTATIONS, QVector<glm::quat>, setJointRotations); - READ_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS_SET, QVector<bool>, setJointTranslationsSet); - READ_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS, QVector<glm::vec3>, setJointTranslations); - return bytesRead; } EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); - requestedProperties += PROP_COLOR; - requestedProperties += PROP_MODEL_URL; - requestedProperties += PROP_COMPOUND_SHAPE_URL; - requestedProperties += PROP_TEXTURES; requestedProperties += PROP_SHAPE_TYPE; - requestedProperties += _animationProperties.getEntityProperties(params); + requestedProperties += PROP_COMPOUND_SHAPE_URL; + requestedProperties += PROP_COLOR; + requestedProperties += PROP_TEXTURES; + + requestedProperties += PROP_MODEL_URL; requestedProperties += PROP_JOINT_ROTATIONS_SET; requestedProperties += PROP_JOINT_ROTATIONS; requestedProperties += PROP_JOINT_TRANSLATIONS_SET; requestedProperties += PROP_JOINT_TRANSLATIONS; requestedProperties += PROP_RELAY_PARENT_JOINTS; + requestedProperties += _animationProperties.getEntityProperties(params); return requestedProperties; } @@ -178,23 +181,22 @@ void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit bool successPropertyFits = true; - APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); - APPEND_ENTITY_PROPERTY(PROP_MODEL_URL, getModelURL()); + APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)getShapeType()); APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, getCompoundShapeURL()); + APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); APPEND_ENTITY_PROPERTY(PROP_TEXTURES, getTextures()); + + APPEND_ENTITY_PROPERTY(PROP_MODEL_URL, getModelURL()); + APPEND_ENTITY_PROPERTY(PROP_JOINT_ROTATIONS_SET, getJointRotationsSet()); + APPEND_ENTITY_PROPERTY(PROP_JOINT_ROTATIONS, getJointRotations()); + APPEND_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS_SET, getJointTranslationsSet()); + APPEND_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS, getJointTranslations()); APPEND_ENTITY_PROPERTY(PROP_RELAY_PARENT_JOINTS, getRelayParentJoints()); withReadLock([&] { _animationProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); }); - - APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)getShapeType()); - - APPEND_ENTITY_PROPERTY(PROP_JOINT_ROTATIONS_SET, getJointRotationsSet()); - APPEND_ENTITY_PROPERTY(PROP_JOINT_ROTATIONS, getJointRotations()); - APPEND_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS_SET, getJointTranslationsSet()); - APPEND_ENTITY_PROPERTY(PROP_JOINT_TRANSLATIONS, getJointTranslations()); } diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index acdeac0e93..7426318979 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -409,11 +409,14 @@ void ParticleEffectEntityItem::computeAndUpdateDimensions() { EntityItemProperties ParticleEffectEntityItem::getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const { EntityItemProperties properties = EntityItem::getProperties(desiredProperties, allowEmptyDesiredProperties); // get the properties from our base class + COPY_ENTITY_PROPERTY_TO_PROPERTIES(shapeType, getShapeType); COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getColor); COPY_ENTITY_PROPERTY_TO_PROPERTIES(alpha, getAlpha); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(shapeType, getShapeType); // FIXME - this doesn't appear to get used + COPY_ENTITY_PROPERTY_TO_PROPERTIES(textures, getTextures); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(maxParticles, getMaxParticles); COPY_ENTITY_PROPERTY_TO_PROPERTIES(lifespan, getLifespan); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(isEmitting, getIsEmitting); COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitRate, getEmitRate); COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitSpeed, getEmitSpeed); @@ -421,24 +424,30 @@ EntityItemProperties ParticleEffectEntityItem::getProperties(const EntityPropert COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitOrientation, getEmitOrientation); COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitDimensions, getEmitDimensions); COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitRadiusStart, getEmitRadiusStart); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(polarStart, getPolarStart); COPY_ENTITY_PROPERTY_TO_PROPERTIES(polarFinish, getPolarFinish); COPY_ENTITY_PROPERTY_TO_PROPERTIES(azimuthStart, getAzimuthStart); COPY_ENTITY_PROPERTY_TO_PROPERTIES(azimuthFinish, getAzimuthFinish); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitAcceleration, getEmitAcceleration); COPY_ENTITY_PROPERTY_TO_PROPERTIES(accelerationSpread, getAccelerationSpread); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(particleRadius, getParticleRadius); COPY_ENTITY_PROPERTY_TO_PROPERTIES(radiusSpread, getRadiusSpread); COPY_ENTITY_PROPERTY_TO_PROPERTIES(radiusStart, getRadiusStart); COPY_ENTITY_PROPERTY_TO_PROPERTIES(radiusFinish, getRadiusFinish); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(colorSpread, getColorSpread); COPY_ENTITY_PROPERTY_TO_PROPERTIES(colorStart, getColorStart); COPY_ENTITY_PROPERTY_TO_PROPERTIES(colorFinish, getColorFinish); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(alphaSpread, getAlphaSpread); COPY_ENTITY_PROPERTY_TO_PROPERTIES(alphaStart, getAlphaStart); COPY_ENTITY_PROPERTY_TO_PROPERTIES(alphaFinish, getAlphaFinish); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(textures, getTextures); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(emitterShouldTrail, getEmitterShouldTrail); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(particleSpin, getParticleSpin); COPY_ENTITY_PROPERTY_TO_PROPERTIES(spinSpread, getSpinSpread); COPY_ENTITY_PROPERTY_TO_PROPERTIES(spinStart, getSpinStart); @@ -451,11 +460,14 @@ EntityItemProperties ParticleEffectEntityItem::getProperties(const EntityPropert bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& properties) { bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class + SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType); SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(textures, setTextures); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(maxParticles, setMaxParticles); SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifespan, setLifespan); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(isEmitting, setIsEmitting); SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitRate, setEmitRate); SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitSpeed, setEmitSpeed); @@ -463,24 +475,30 @@ bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& propert SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitOrientation, setEmitOrientation); SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitDimensions, setEmitDimensions); SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitRadiusStart, setEmitRadiusStart); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(polarStart, setPolarStart); SET_ENTITY_PROPERTY_FROM_PROPERTIES(polarFinish, setPolarFinish); SET_ENTITY_PROPERTY_FROM_PROPERTIES(azimuthStart, setAzimuthStart); SET_ENTITY_PROPERTY_FROM_PROPERTIES(azimuthFinish, setAzimuthFinish); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitAcceleration, setEmitAcceleration); SET_ENTITY_PROPERTY_FROM_PROPERTIES(accelerationSpread, setAccelerationSpread); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(particleRadius, setParticleRadius); SET_ENTITY_PROPERTY_FROM_PROPERTIES(radiusSpread, setRadiusSpread); SET_ENTITY_PROPERTY_FROM_PROPERTIES(radiusStart, setRadiusStart); SET_ENTITY_PROPERTY_FROM_PROPERTIES(radiusFinish, setRadiusFinish); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(colorSpread, setColorSpread); SET_ENTITY_PROPERTY_FROM_PROPERTIES(colorStart, setColorStart); SET_ENTITY_PROPERTY_FROM_PROPERTIES(colorFinish, setColorFinish); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(alphaSpread, setAlphaSpread); SET_ENTITY_PROPERTY_FROM_PROPERTIES(alphaStart, setAlphaStart); SET_ENTITY_PROPERTY_FROM_PROPERTIES(alphaFinish, setAlphaFinish); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(textures, setTextures); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(emitterShouldTrail, setEmitterShouldTrail); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(particleSpin, setParticleSpin); SET_ENTITY_PROPERTY_FROM_PROPERTIES(spinSpread, setSpinSpread); SET_ENTITY_PROPERTY_FROM_PROPERTIES(spinStart, setSpinStart); @@ -514,18 +532,31 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch int bytesRead = 0; const unsigned char* dataAt = data; - READ_ENTITY_PROPERTY(PROP_COLOR, u8vec3Color, setColor); - READ_ENTITY_PROPERTY(PROP_EMITTING_PARTICLES, bool, setIsEmitting); READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType); + READ_ENTITY_PROPERTY(PROP_COLOR, u8vec3Color, setColor); + READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha); + READ_ENTITY_PROPERTY(PROP_TEXTURES, QString, setTextures); + READ_ENTITY_PROPERTY(PROP_MAX_PARTICLES, quint32, setMaxParticles); READ_ENTITY_PROPERTY(PROP_LIFESPAN, float, setLifespan); + + READ_ENTITY_PROPERTY(PROP_EMITTING_PARTICLES, bool, setIsEmitting); READ_ENTITY_PROPERTY(PROP_EMIT_RATE, float, setEmitRate); + READ_ENTITY_PROPERTY(PROP_EMIT_SPEED, float, setEmitSpeed); + READ_ENTITY_PROPERTY(PROP_SPEED_SPREAD, float, setSpeedSpread); + READ_ENTITY_PROPERTY(PROP_EMIT_ORIENTATION, quat, setEmitOrientation); + READ_ENTITY_PROPERTY(PROP_EMIT_DIMENSIONS, glm::vec3, setEmitDimensions); + READ_ENTITY_PROPERTY(PROP_EMIT_RADIUS_START, float, setEmitRadiusStart); + + READ_ENTITY_PROPERTY(PROP_POLAR_START, float, setPolarStart); + READ_ENTITY_PROPERTY(PROP_POLAR_FINISH, float, setPolarFinish); + READ_ENTITY_PROPERTY(PROP_AZIMUTH_START, float, setAzimuthStart); + READ_ENTITY_PROPERTY(PROP_AZIMUTH_FINISH, float, setAzimuthFinish); READ_ENTITY_PROPERTY(PROP_EMIT_ACCELERATION, glm::vec3, setEmitAcceleration); READ_ENTITY_PROPERTY(PROP_ACCELERATION_SPREAD, glm::vec3, setAccelerationSpread); - READ_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, float, setParticleRadius); - READ_ENTITY_PROPERTY(PROP_TEXTURES, QString, setTextures); + READ_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, float, setParticleRadius); READ_ENTITY_PROPERTY(PROP_RADIUS_SPREAD, float, setRadiusSpread); READ_ENTITY_PROPERTY(PROP_RADIUS_START, float, setRadiusStart); READ_ENTITY_PROPERTY(PROP_RADIUS_FINISH, float, setRadiusFinish); @@ -533,21 +564,11 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch READ_ENTITY_PROPERTY(PROP_COLOR_SPREAD, u8vec3Color, setColorSpread); READ_ENTITY_PROPERTY(PROP_COLOR_START, vec3Color, setColorStart); READ_ENTITY_PROPERTY(PROP_COLOR_FINISH, vec3Color, setColorFinish); - READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha); + READ_ENTITY_PROPERTY(PROP_ALPHA_SPREAD, float, setAlphaSpread); READ_ENTITY_PROPERTY(PROP_ALPHA_START, float, setAlphaStart); READ_ENTITY_PROPERTY(PROP_ALPHA_FINISH, float, setAlphaFinish); - READ_ENTITY_PROPERTY(PROP_EMIT_SPEED, float, setEmitSpeed); - READ_ENTITY_PROPERTY(PROP_SPEED_SPREAD, float, setSpeedSpread); - READ_ENTITY_PROPERTY(PROP_EMIT_ORIENTATION, quat, setEmitOrientation); - READ_ENTITY_PROPERTY(PROP_EMIT_DIMENSIONS, glm::vec3, setEmitDimensions); - READ_ENTITY_PROPERTY(PROP_EMIT_RADIUS_START, float, setEmitRadiusStart); - READ_ENTITY_PROPERTY(PROP_POLAR_START, float, setPolarStart); - READ_ENTITY_PROPERTY(PROP_POLAR_FINISH, float, setPolarFinish); - READ_ENTITY_PROPERTY(PROP_AZIMUTH_START, float, setAzimuthStart); - READ_ENTITY_PROPERTY(PROP_AZIMUTH_FINISH, float, setAzimuthFinish); - READ_ENTITY_PROPERTY(PROP_EMITTER_SHOULD_TRAIL, bool, setEmitterShouldTrail); READ_ENTITY_PROPERTY(PROP_PARTICLE_SPIN, float, setParticleSpin); @@ -562,36 +583,45 @@ int ParticleEffectEntityItem::readEntitySubclassDataFromBuffer(const unsigned ch EntityPropertyFlags ParticleEffectEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); - requestedProperties += PROP_COLOR; requestedProperties += PROP_SHAPE_TYPE; + requestedProperties += PROP_COLOR; + requestedProperties += PROP_ALPHA; + requestedProperties += PROP_TEXTURES; + requestedProperties += PROP_MAX_PARTICLES; requestedProperties += PROP_LIFESPAN; + requestedProperties += PROP_EMITTING_PARTICLES; requestedProperties += PROP_EMIT_RATE; - requestedProperties += PROP_EMIT_ACCELERATION; - requestedProperties += PROP_ACCELERATION_SPREAD; - requestedProperties += PROP_PARTICLE_RADIUS; - requestedProperties += PROP_TEXTURES; - requestedProperties += PROP_RADIUS_SPREAD; - requestedProperties += PROP_RADIUS_START; - requestedProperties += PROP_RADIUS_FINISH; - requestedProperties += PROP_COLOR_SPREAD; - requestedProperties += PROP_COLOR_START; - requestedProperties += PROP_COLOR_FINISH; - requestedProperties += PROP_ALPHA; - requestedProperties += PROP_ALPHA_SPREAD; - requestedProperties += PROP_ALPHA_START; - requestedProperties += PROP_ALPHA_FINISH; requestedProperties += PROP_EMIT_SPEED; requestedProperties += PROP_SPEED_SPREAD; requestedProperties += PROP_EMIT_ORIENTATION; requestedProperties += PROP_EMIT_DIMENSIONS; requestedProperties += PROP_EMIT_RADIUS_START; + requestedProperties += PROP_POLAR_START; requestedProperties += PROP_POLAR_FINISH; requestedProperties += PROP_AZIMUTH_START; requestedProperties += PROP_AZIMUTH_FINISH; + + requestedProperties += PROP_EMIT_ACCELERATION; + requestedProperties += PROP_ACCELERATION_SPREAD; + + requestedProperties += PROP_PARTICLE_RADIUS; + requestedProperties += PROP_RADIUS_SPREAD; + requestedProperties += PROP_RADIUS_START; + requestedProperties += PROP_RADIUS_FINISH; + + requestedProperties += PROP_COLOR_SPREAD; + requestedProperties += PROP_COLOR_START; + requestedProperties += PROP_COLOR_FINISH; + + requestedProperties += PROP_ALPHA_SPREAD; + requestedProperties += PROP_ALPHA_START; + requestedProperties += PROP_ALPHA_FINISH; + requestedProperties += PROP_EMITTER_SHOULD_TRAIL; + requestedProperties += PROP_PARTICLE_SPIN; requestedProperties += PROP_SPIN_SPREAD; requestedProperties += PROP_SPIN_START; @@ -610,36 +640,45 @@ void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData, OctreeElement::AppendState& appendState) const { bool successPropertyFits = true; - APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); - APPEND_ENTITY_PROPERTY(PROP_EMITTING_PARTICLES, getIsEmitting()); APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)getShapeType()); + APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); + APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha()); + APPEND_ENTITY_PROPERTY(PROP_TEXTURES, getTextures()); + APPEND_ENTITY_PROPERTY(PROP_MAX_PARTICLES, getMaxParticles()); APPEND_ENTITY_PROPERTY(PROP_LIFESPAN, getLifespan()); + + APPEND_ENTITY_PROPERTY(PROP_EMITTING_PARTICLES, getIsEmitting()); APPEND_ENTITY_PROPERTY(PROP_EMIT_RATE, getEmitRate()); - APPEND_ENTITY_PROPERTY(PROP_EMIT_ACCELERATION, getEmitAcceleration()); - APPEND_ENTITY_PROPERTY(PROP_ACCELERATION_SPREAD, getAccelerationSpread()); - APPEND_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, getParticleRadius()); - APPEND_ENTITY_PROPERTY(PROP_TEXTURES, getTextures()); - APPEND_ENTITY_PROPERTY(PROP_RADIUS_SPREAD, getRadiusSpread()); - APPEND_ENTITY_PROPERTY(PROP_RADIUS_START, getRadiusStart()); - APPEND_ENTITY_PROPERTY(PROP_RADIUS_FINISH, getRadiusFinish()); - APPEND_ENTITY_PROPERTY(PROP_COLOR_SPREAD, getColorSpread()); - APPEND_ENTITY_PROPERTY(PROP_COLOR_START, getColorStart()); - APPEND_ENTITY_PROPERTY(PROP_COLOR_FINISH, getColorFinish()); - APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha()); - APPEND_ENTITY_PROPERTY(PROP_ALPHA_SPREAD, getAlphaSpread()); - APPEND_ENTITY_PROPERTY(PROP_ALPHA_START, getAlphaStart()); - APPEND_ENTITY_PROPERTY(PROP_ALPHA_FINISH, getAlphaFinish()); APPEND_ENTITY_PROPERTY(PROP_EMIT_SPEED, getEmitSpeed()); APPEND_ENTITY_PROPERTY(PROP_SPEED_SPREAD, getSpeedSpread()); APPEND_ENTITY_PROPERTY(PROP_EMIT_ORIENTATION, getEmitOrientation()); APPEND_ENTITY_PROPERTY(PROP_EMIT_DIMENSIONS, getEmitDimensions()); APPEND_ENTITY_PROPERTY(PROP_EMIT_RADIUS_START, getEmitRadiusStart()); + APPEND_ENTITY_PROPERTY(PROP_POLAR_START, getPolarStart()); APPEND_ENTITY_PROPERTY(PROP_POLAR_FINISH, getPolarFinish()); APPEND_ENTITY_PROPERTY(PROP_AZIMUTH_START, getAzimuthStart()); APPEND_ENTITY_PROPERTY(PROP_AZIMUTH_FINISH, getAzimuthFinish()); + + APPEND_ENTITY_PROPERTY(PROP_EMIT_ACCELERATION, getEmitAcceleration()); + APPEND_ENTITY_PROPERTY(PROP_ACCELERATION_SPREAD, getAccelerationSpread()); + + APPEND_ENTITY_PROPERTY(PROP_PARTICLE_RADIUS, getParticleRadius()); + APPEND_ENTITY_PROPERTY(PROP_RADIUS_SPREAD, getRadiusSpread()); + APPEND_ENTITY_PROPERTY(PROP_RADIUS_START, getRadiusStart()); + APPEND_ENTITY_PROPERTY(PROP_RADIUS_FINISH, getRadiusFinish()); + + APPEND_ENTITY_PROPERTY(PROP_COLOR_SPREAD, getColorSpread()); + APPEND_ENTITY_PROPERTY(PROP_COLOR_START, getColorStart()); + APPEND_ENTITY_PROPERTY(PROP_COLOR_FINISH, getColorFinish()); + + APPEND_ENTITY_PROPERTY(PROP_ALPHA_SPREAD, getAlphaSpread()); + APPEND_ENTITY_PROPERTY(PROP_ALPHA_START, getAlphaStart()); + APPEND_ENTITY_PROPERTY(PROP_ALPHA_FINISH, getAlphaFinish()); + APPEND_ENTITY_PROPERTY(PROP_EMITTER_SHOULD_TRAIL, getEmitterShouldTrail()); + APPEND_ENTITY_PROPERTY(PROP_PARTICLE_SPIN, getParticleSpin()); APPEND_ENTITY_PROPERTY(PROP_SPIN_SPREAD, getSpinSpread()); APPEND_ENTITY_PROPERTY(PROP_SPIN_START, getSpinStart()); @@ -647,8 +686,6 @@ void ParticleEffectEntityItem::appendSubclassData(OctreePacketData* packetData, APPEND_ENTITY_PROPERTY(PROP_PARTICLE_ROTATE_WITH_ENTITY, getRotateWithEntity()); } - - void ParticleEffectEntityItem::debugDump() const { quint64 now = usecTimestampNow(); qCDebug(entities) << "PA EFFECT EntityItem id:" << getEntityItemID() << "---------------------------------------------"; @@ -749,4 +786,4 @@ particle::Properties ParticleEffectEntityItem::getParticleProperties() const { } return result; -} +} \ No newline at end of file diff --git a/libraries/entities/src/PolyLineEntityItem.cpp b/libraries/entities/src/PolyLineEntityItem.cpp index c72256822d..6ab885b32b 100644 --- a/libraries/entities/src/PolyLineEntityItem.cpp +++ b/libraries/entities/src/PolyLineEntityItem.cpp @@ -24,7 +24,6 @@ const float PolyLineEntityItem::DEFAULT_LINE_WIDTH = 0.1f; const int PolyLineEntityItem::MAX_POINTS_PER_LINE = 60; - EntityItemPointer PolyLineEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer entity(new PolyLineEntityItem(entityID), [](EntityItem* ptr) { ptr->deleteLater(); }); entity->setProperties(properties); @@ -37,33 +36,36 @@ PolyLineEntityItem::PolyLineEntityItem(const EntityItemID& entityItemID) : Entit } EntityItemProperties PolyLineEntityItem::getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const { - QWriteLocker lock(&_quadReadWriteLock); EntityItemProperties properties = EntityItem::getProperties(desiredProperties, allowEmptyDesiredProperties); // get the properties from our base class COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getColor); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(lineWidth, getLineWidth); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(textures, getTextures); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(linePoints, getLinePoints); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(strokeWidths, getStrokeWidths); COPY_ENTITY_PROPERTY_TO_PROPERTIES(normals, getNormals); COPY_ENTITY_PROPERTY_TO_PROPERTIES(strokeColors, getStrokeColors); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(strokeWidths, getStrokeWidths); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(textures, getTextures); COPY_ENTITY_PROPERTY_TO_PROPERTIES(isUVModeStretch, getIsUVModeStretch); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(glow, getGlow); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(faceCamera, getFaceCamera); + return properties; } bool PolyLineEntityItem::setProperties(const EntityItemProperties& properties) { - QWriteLocker lock(&_quadReadWriteLock); bool somethingChanged = false; somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(lineWidth, setLineWidth); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(textures, setTextures); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(linePoints, setLinePoints); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(strokeWidths, setStrokeWidths); SET_ENTITY_PROPERTY_FROM_PROPERTIES(normals, setNormals); SET_ENTITY_PROPERTY_FROM_PROPERTIES(strokeColors, setStrokeColors); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(strokeWidths, setStrokeWidths); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(textures, setTextures); SET_ENTITY_PROPERTY_FROM_PROPERTIES(isUVModeStretch, setIsUVModeStretch); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(glow, setGlow); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(faceCamera, setFaceCamera); if (somethingChanged) { bool wantDebug = false; @@ -78,136 +80,72 @@ bool PolyLineEntityItem::setProperties(const EntityItemProperties& properties) { return somethingChanged; } - -bool PolyLineEntityItem::appendPoint(const glm::vec3& point) { - if (_points.size() > MAX_POINTS_PER_LINE - 1) { - qCDebug(entities) << "MAX POINTS REACHED!"; - return false; - } - - _points << point; - _pointsChanged = true; - - calculateScaleAndRegistrationPoint(); - - return true; -} - - -bool PolyLineEntityItem::setStrokeWidths(const QVector<float>& strokeWidths) { +void PolyLineEntityItem::setLinePoints(const QVector<glm::vec3>& points) { withWriteLock([&] { - _strokeWidths = strokeWidths; - _strokeWidthsChanged = true; + _points = points; + _pointsChanged = true; }); - return true; + computeAndUpdateDimensionsAndPosition(); } -bool PolyLineEntityItem::setNormals(const QVector<glm::vec3>& normals) { +void PolyLineEntityItem::setStrokeWidths(const QVector<float>& strokeWidths) { + withWriteLock([&] { + _widths = strokeWidths; + _widthsChanged = true; + }); + computeAndUpdateDimensionsAndPosition(); +} + +void PolyLineEntityItem::setNormals(const QVector<glm::vec3>& normals) { withWriteLock([&] { _normals = normals; _normalsChanged = true; }); - return true; } -bool PolyLineEntityItem::setStrokeColors(const QVector<glm::vec3>& strokeColors) { +void PolyLineEntityItem::setStrokeColors(const QVector<glm::vec3>& strokeColors) { withWriteLock([&] { - _strokeColors = strokeColors; - _strokeColorsChanged = true; + _colors = strokeColors; + _colorsChanged = true; }); - return true; } +void PolyLineEntityItem::computeAndUpdateDimensionsAndPosition() { + QVector<glm::vec3> points; + QVector<float> widths; -bool PolyLineEntityItem::setLinePoints(const QVector<glm::vec3>& points) { - if (points.size() > MAX_POINTS_PER_LINE) { - return false; - } - bool result = false; - withWriteLock([&] { - //Check to see if points actually changed. If they haven't, return before doing anything else - if (points.size() != _points.size()) { - _pointsChanged = true; - } else if (points.size() == _points.size()) { - //same number of points, so now compare every point - for (int i = 0; i < points.size(); i++) { - if (points.at(i) != _points.at(i)) { - _pointsChanged = true; - break; - } - } - } - if (!_pointsChanged) { - return; - } - - _points = points; - - result = true; - }); - - if (result) { - calculateScaleAndRegistrationPoint(); - } - - return result; -} - -void PolyLineEntityItem::calculateScaleAndRegistrationPoint() { - glm::vec3 high(0.0f, 0.0f, 0.0f); - glm::vec3 low(0.0f, 0.0f, 0.0f); - int pointCount = 0; - glm::vec3 firstPoint; withReadLock([&] { - pointCount = _points.size(); - if (pointCount > 0) { - firstPoint = _points.at(0); - } - for (int i = 0; i < pointCount; i++) { - const glm::vec3& point = _points.at(i); - high = glm::max(point, high); - low = glm::min(point, low); - } + points = _points; + widths = _widths; }); - float magnitudeSquared = glm::length2(low - high); - vec3 newScale { 1 }; - vec3 newRegistrationPoint { 0.5f }; + glm::vec3 maxHalfDim(0.5f * ENTITY_ITEM_DEFAULT_WIDTH); + float maxWidth = 0.0f; + for (int i = 0; i < points.length(); i++) { + maxHalfDim = glm::max(maxHalfDim, glm::abs(points[i])); + maxWidth = glm::max(maxWidth, i < widths.length() ? widths[i] : DEFAULT_LINE_WIDTH); + } - const float EPSILON = 0.0001f; - const float EPSILON_SQUARED = EPSILON * EPSILON; - const float HALF_LINE_WIDTH = 0.075f; // sadly _strokeWidths() don't seem to correspond to reality, so just use a flat assumption of the stroke width - const vec3 QUARTER_LINE_WIDTH { HALF_LINE_WIDTH * 0.5f }; - if (pointCount > 1 && magnitudeSquared > EPSILON_SQUARED) { - newScale = glm::abs(high) + glm::abs(low) + vec3(HALF_LINE_WIDTH); - // Center the poly line in the bounding box - glm::vec3 startPointInScaleSpace = firstPoint - low; - startPointInScaleSpace += QUARTER_LINE_WIDTH; - newRegistrationPoint = startPointInScaleSpace / newScale; - } - - // if Polyline has only one or fewer points, use default dimension settings - setScaledDimensions(newScale); - EntityItem::setRegistrationPoint(newRegistrationPoint); + setScaledDimensions(2.0f * (maxHalfDim + maxWidth)); } int PolyLineEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, bool& somethingChanged) { - - QWriteLocker lock(&_quadReadWriteLock); int bytesRead = 0; const unsigned char* dataAt = data; READ_ENTITY_PROPERTY(PROP_COLOR, glm::u8vec3, setColor); - READ_ENTITY_PROPERTY(PROP_LINE_WIDTH, float, setLineWidth); - READ_ENTITY_PROPERTY(PROP_LINE_POINTS, QVector<glm::vec3>, setLinePoints); - READ_ENTITY_PROPERTY(PROP_NORMALS, QVector<glm::vec3>, setNormals); - READ_ENTITY_PROPERTY(PROP_STROKE_COLORS, QVector<glm::vec3>, setStrokeColors); - READ_ENTITY_PROPERTY(PROP_STROKE_WIDTHS, QVector<float>, setStrokeWidths); READ_ENTITY_PROPERTY(PROP_TEXTURES, QString, setTextures); + + READ_ENTITY_PROPERTY(PROP_LINE_POINTS, QVector<glm::vec3>, setLinePoints); + READ_ENTITY_PROPERTY(PROP_STROKE_WIDTHS, QVector<float>, setStrokeWidths); + READ_ENTITY_PROPERTY(PROP_STROKE_NORMALS, QVector<glm::vec3>, setNormals); + READ_ENTITY_PROPERTY(PROP_STROKE_COLORS, QVector<glm::vec3>, setStrokeColors); READ_ENTITY_PROPERTY(PROP_IS_UV_MODE_STRETCH, bool, setIsUVModeStretch); + READ_ENTITY_PROPERTY(PROP_LINE_GLOW, bool, setGlow); + READ_ENTITY_PROPERTY(PROP_LINE_FACE_CAMERA, bool, setFaceCamera); return bytesRead; } @@ -215,13 +153,15 @@ int PolyLineEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* da EntityPropertyFlags PolyLineEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); requestedProperties += PROP_COLOR; - requestedProperties += PROP_LINE_WIDTH; - requestedProperties += PROP_LINE_POINTS; - requestedProperties += PROP_NORMALS; - requestedProperties += PROP_STROKE_COLORS; - requestedProperties += PROP_STROKE_WIDTHS; requestedProperties += PROP_TEXTURES; + + requestedProperties += PROP_LINE_POINTS; + requestedProperties += PROP_STROKE_WIDTHS; + requestedProperties += PROP_STROKE_NORMALS; + requestedProperties += PROP_STROKE_COLORS; requestedProperties += PROP_IS_UV_MODE_STRETCH; + requestedProperties += PROP_LINE_GLOW; + requestedProperties += PROP_LINE_FACE_CAMERA; return requestedProperties; } @@ -233,17 +173,18 @@ void PolyLineEntityItem::appendSubclassData(OctreePacketData* packetData, Encode int& propertyCount, OctreeElement::AppendState& appendState) const { - QWriteLocker lock(&_quadReadWriteLock); bool successPropertyFits = true; APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); - APPEND_ENTITY_PROPERTY(PROP_LINE_WIDTH, getLineWidth()); - APPEND_ENTITY_PROPERTY(PROP_LINE_POINTS, getLinePoints()); - APPEND_ENTITY_PROPERTY(PROP_NORMALS, getNormals()); - APPEND_ENTITY_PROPERTY(PROP_STROKE_COLORS, getStrokeColors()); - APPEND_ENTITY_PROPERTY(PROP_STROKE_WIDTHS, getStrokeWidths()); APPEND_ENTITY_PROPERTY(PROP_TEXTURES, getTextures()); + + APPEND_ENTITY_PROPERTY(PROP_LINE_POINTS, getLinePoints()); + APPEND_ENTITY_PROPERTY(PROP_STROKE_WIDTHS, getStrokeWidths()); + APPEND_ENTITY_PROPERTY(PROP_STROKE_NORMALS, getNormals()); + APPEND_ENTITY_PROPERTY(PROP_STROKE_COLORS, getStrokeColors()); APPEND_ENTITY_PROPERTY(PROP_IS_UV_MODE_STRETCH, getIsUVModeStretch()); + APPEND_ENTITY_PROPERTY(PROP_LINE_GLOW, getGlow()); + APPEND_ENTITY_PROPERTY(PROP_LINE_FACE_CAMERA, getFaceCamera()); } void PolyLineEntityItem::debugDump() const { @@ -255,61 +196,49 @@ void PolyLineEntityItem::debugDump() const { qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); } - - QVector<glm::vec3> PolyLineEntityItem::getLinePoints() const { - QVector<glm::vec3> result; - withReadLock([&] { - result = _points; + return resultWithReadLock<QVector<glm::vec3>>([&] { + return _points; }); - return result; } QVector<glm::vec3> PolyLineEntityItem::getNormals() const { - QVector<glm::vec3> result; - withReadLock([&] { - result = _normals; + return resultWithReadLock<QVector<glm::vec3>>([&] { + return _normals; }); - return result; } QVector<glm::vec3> PolyLineEntityItem::getStrokeColors() const { - QVector<glm::vec3> result; - withReadLock([&] { - result = _strokeColors; + return resultWithReadLock<QVector<glm::vec3>>([&] { + return _colors; }); - return result; } QVector<float> PolyLineEntityItem::getStrokeWidths() const { - QVector<float> result; - withReadLock([&] { - result = _strokeWidths; + return resultWithReadLock<QVector<float>>([&] { + return _widths; }); - return result; } QString PolyLineEntityItem::getTextures() const { - QString result; - withReadLock([&] { - result = _textures; + return resultWithReadLock<QString>([&] { + return _textures; }); - return result; } void PolyLineEntityItem::setTextures(const QString& textures) { withWriteLock([&] { if (_textures != textures) { _textures = textures; - _texturesChangedFlag = true; + _texturesChanged = true; } }); } void PolyLineEntityItem::setColor(const glm::u8vec3& value) { withWriteLock([&] { - _strokeColorsChanged = true; _color = value; + _colorsChanged = true; }); } diff --git a/libraries/entities/src/PolyLineEntityItem.h b/libraries/entities/src/PolyLineEntityItem.h index f640bd7a9e..41acc6d6d8 100644 --- a/libraries/entities/src/PolyLineEntityItem.h +++ b/libraries/entities/src/PolyLineEntityItem.h @@ -44,38 +44,40 @@ class PolyLineEntityItem : public EntityItem { glm::u8vec3 getColor() const; void setColor(const glm::u8vec3& value); - void setLineWidth(float lineWidth){ _lineWidth = lineWidth; } - float getLineWidth() const{ return _lineWidth; } - - bool setLinePoints(const QVector<glm::vec3>& points); - bool appendPoint(const glm::vec3& point); + static const int MAX_POINTS_PER_LINE; + void setLinePoints(const QVector<glm::vec3>& points); QVector<glm::vec3> getLinePoints() const; - bool setNormals(const QVector<glm::vec3>& normals); + static const float DEFAULT_LINE_WIDTH; + void setStrokeWidths(const QVector<float>& strokeWidths); + QVector<float> getStrokeWidths() const; + + void setNormals(const QVector<glm::vec3>& normals); QVector<glm::vec3> getNormals() const; - bool setStrokeColors(const QVector<glm::vec3>& strokeColors); + void setStrokeColors(const QVector<glm::vec3>& strokeColors); QVector<glm::vec3> getStrokeColors() const; - bool setStrokeWidths(const QVector<float>& strokeWidths); - QVector<float> getStrokeWidths() const; - void setIsUVModeStretch(bool isUVModeStretch){ _isUVModeStretch = isUVModeStretch; } bool getIsUVModeStretch() const{ return _isUVModeStretch; } QString getTextures() const; void setTextures(const QString& textures); - virtual ShapeType getShapeType() const override { return SHAPE_TYPE_NONE; } + void setGlow(bool glow) { _glow = glow; } + bool getGlow() const { return _glow; } + + void setFaceCamera(bool faceCamera) { _faceCamera = faceCamera; } + bool getFaceCamera() const { return _faceCamera; } bool pointsChanged() const { return _pointsChanged; } bool normalsChanged() const { return _normalsChanged; } - bool strokeColorsChanged() const { return _strokeColorsChanged; } - bool strokeWidthsChanged() const { return _strokeWidthsChanged; } - bool texturesChanged() const { return _texturesChangedFlag; } - void resetTexturesChanged() { _texturesChangedFlag = false; } - void resetPolyLineChanged() { _strokeColorsChanged = _strokeWidthsChanged = _normalsChanged = _pointsChanged = false; } + bool colorsChanged() const { return _colorsChanged; } + bool widthsChanged() const { return _widthsChanged; } + bool texturesChanged() const { return _texturesChanged; } + void resetTexturesChanged() { _texturesChanged = false; } + void resetPolyLineChanged() { _colorsChanged = _widthsChanged = _normalsChanged = _pointsChanged = false; } // never have a ray intersection pick a PolyLineEntityItem. virtual bool supportsDetailedIntersection() const override { return true; } @@ -88,30 +90,26 @@ class PolyLineEntityItem : public EntityItem { BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override { return false; } - // disable these external interfaces as PolyLineEntities caculate their own dimensions based on the points they contain - virtual void setRegistrationPoint(const glm::vec3& value) override {}; // FIXME: this is suspicious! - virtual void debugDump() const override; - static const float DEFAULT_LINE_WIDTH; - static const int MAX_POINTS_PER_LINE; private: - void calculateScaleAndRegistrationPoint(); + void computeAndUpdateDimensionsAndPosition(); protected: glm::u8vec3 _color; - float _lineWidth { DEFAULT_LINE_WIDTH }; - bool _pointsChanged { true }; - bool _normalsChanged { true }; - bool _strokeColorsChanged { true }; - bool _strokeWidthsChanged { true }; QVector<glm::vec3> _points; QVector<glm::vec3> _normals; - QVector<glm::vec3> _strokeColors; - QVector<float> _strokeWidths; + QVector<glm::vec3> _colors; + QVector<float> _widths; QString _textures; bool _isUVModeStretch; - bool _texturesChangedFlag { false }; - mutable QReadWriteLock _quadReadWriteLock; + bool _glow; + bool _faceCamera; + + bool _pointsChanged { false }; + bool _normalsChanged { false }; + bool _colorsChanged { false }; + bool _widthsChanged { false }; + bool _texturesChanged { false }; }; #endif // hifi_PolyLineEntityItem_h diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index f67134da0a..88612c8be6 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -118,10 +118,10 @@ ShapeEntityItem::ShapeEntityItem(const EntityItemID& entityItemID) : EntityItem( EntityItemProperties ShapeEntityItem::getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const { EntityItemProperties properties = EntityItem::getProperties(desiredProperties, allowEmptyDesiredProperties); // get the properties from our base class - properties.setShape(entity::stringFromShape(getShape())); - properties._shapeChanged = false; COPY_ENTITY_PROPERTY_TO_PROPERTIES(color, getColor); COPY_ENTITY_PROPERTY_TO_PROPERTIES(alpha, getAlpha); + properties.setShape(entity::stringFromShape(getShape())); + properties._shapeChanged = false; return properties; } @@ -158,8 +158,8 @@ void ShapeEntityItem::setShape(const entity::Shape& shape) { bool ShapeEntityItem::setProperties(const EntityItemProperties& properties) { bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class - SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha); SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha); SET_ENTITY_PROPERTY_FROM_PROPERTIES(shape, setShape); if (somethingChanged) { @@ -183,18 +183,18 @@ int ShapeEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesRead = 0; const unsigned char* dataAt = data; - READ_ENTITY_PROPERTY(PROP_SHAPE, QString, setShape); READ_ENTITY_PROPERTY(PROP_COLOR, glm::u8vec3, setColor); READ_ENTITY_PROPERTY(PROP_ALPHA, float, setAlpha); + READ_ENTITY_PROPERTY(PROP_SHAPE, QString, setShape); return bytesRead; } EntityPropertyFlags ShapeEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); - requestedProperties += PROP_SHAPE; requestedProperties += PROP_COLOR; requestedProperties += PROP_ALPHA; + requestedProperties += PROP_SHAPE; return requestedProperties; } @@ -207,9 +207,9 @@ void ShapeEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit OctreeElement::AppendState& appendState) const { bool successPropertyFits = true; - APPEND_ENTITY_PROPERTY(PROP_SHAPE, entity::stringFromShape(getShape())); APPEND_ENTITY_PROPERTY(PROP_COLOR, getColor()); APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha()); + APPEND_ENTITY_PROPERTY(PROP_SHAPE, entity::stringFromShape(getShape())); } void ShapeEntityItem::setColor(const glm::u8vec3& value) { diff --git a/libraries/entities/src/SimulationOwner.h b/libraries/entities/src/SimulationOwner.h index 353255728c..bd444d28dd 100644 --- a/libraries/entities/src/SimulationOwner.h +++ b/libraries/entities/src/SimulationOwner.h @@ -96,7 +96,7 @@ const uint8_t RECRUIT_SIMULATION_PRIORITY = VOLUNTEER_SIMULATION_PRIORITY + 1; // When poking objects with scripts an observer will bid at SCRIPT_EDIT priority. const uint8_t SCRIPT_GRAB_SIMULATION_PRIORITY = 128; const uint8_t SCRIPT_POKE_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY - 1; -const uint8_t AVATAR_ENTITY_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY + 1; +const uint8_t AVATAR_ENTITY_SIMULATION_PRIORITY = 255; // PERSONAL priority (needs a better name) is the level at which a simulation observer owns its own avatar // which really just means: things that collide with it will be bid at a priority level one lower diff --git a/libraries/entities/src/TextEntityItem.cpp b/libraries/entities/src/TextEntityItem.cpp index 8dd4877ce2..80c4c896bd 100644 --- a/libraries/entities/src/TextEntityItem.cpp +++ b/libraries/entities/src/TextEntityItem.cpp @@ -26,8 +26,9 @@ const QString TextEntityItem::DEFAULT_TEXT(""); const float TextEntityItem::DEFAULT_LINE_HEIGHT = 0.1f; const glm::u8vec3 TextEntityItem::DEFAULT_TEXT_COLOR = { 255, 255, 255 }; +const float TextEntityItem::DEFAULT_TEXT_ALPHA = 1.0f; const glm::u8vec3 TextEntityItem::DEFAULT_BACKGROUND_COLOR = { 0, 0, 0}; -const bool TextEntityItem::DEFAULT_FACE_CAMERA = false; +const float TextEntityItem::DEFAULT_MARGIN = 0.0f; EntityItemPointer TextEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer entity(new TextEntityItem(entityID), [](EntityItem* ptr) { ptr->deleteLater(); }); @@ -39,9 +40,8 @@ TextEntityItem::TextEntityItem(const EntityItemID& entityItemID) : EntityItem(en _type = EntityTypes::Text; } -const float TEXT_ENTITY_ITEM_FIXED_DEPTH = 0.01f; - void TextEntityItem::setUnscaledDimensions(const glm::vec3& value) { + const float TEXT_ENTITY_ITEM_FIXED_DEPTH = 0.01f; // NOTE: Text Entities always have a "depth" of 1cm. EntityItem::setUnscaledDimensions(glm::vec3(value.x, value.y, TEXT_ENTITY_ITEM_FIXED_DEPTH)); } @@ -52,8 +52,14 @@ EntityItemProperties TextEntityItem::getProperties(const EntityPropertyFlags& de COPY_ENTITY_PROPERTY_TO_PROPERTIES(text, getText); COPY_ENTITY_PROPERTY_TO_PROPERTIES(lineHeight, getLineHeight); COPY_ENTITY_PROPERTY_TO_PROPERTIES(textColor, getTextColor); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(textAlpha, getTextAlpha); COPY_ENTITY_PROPERTY_TO_PROPERTIES(backgroundColor, getBackgroundColor); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(faceCamera, getFaceCamera); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(backgroundAlpha, getBackgroundAlpha); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(billboardMode, getBillboardMode); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(leftMargin, getLeftMargin); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(rightMargin, getRightMargin); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(topMargin, getTopMargin); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(bottomMargin, getBottomMargin); return properties; } @@ -64,8 +70,14 @@ bool TextEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(text, setText); SET_ENTITY_PROPERTY_FROM_PROPERTIES(lineHeight, setLineHeight); SET_ENTITY_PROPERTY_FROM_PROPERTIES(textColor, setTextColor); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(textAlpha, setTextAlpha); SET_ENTITY_PROPERTY_FROM_PROPERTIES(backgroundColor, setBackgroundColor); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(faceCamera, setFaceCamera); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(backgroundAlpha, setBackgroundAlpha); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(billboardMode, setBillboardMode); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(leftMargin, setLeftMargin); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(rightMargin, setRightMargin); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(topMargin, setTopMargin); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(bottomMargin, setBottomMargin); if (somethingChanged) { bool wantDebug = false; @@ -92,8 +104,14 @@ int TextEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_TEXT, QString, setText); READ_ENTITY_PROPERTY(PROP_LINE_HEIGHT, float, setLineHeight); READ_ENTITY_PROPERTY(PROP_TEXT_COLOR, glm::u8vec3, setTextColor); + READ_ENTITY_PROPERTY(PROP_TEXT_ALPHA, float, setTextAlpha); READ_ENTITY_PROPERTY(PROP_BACKGROUND_COLOR, glm::u8vec3, setBackgroundColor); - READ_ENTITY_PROPERTY(PROP_FACE_CAMERA, bool, setFaceCamera); + READ_ENTITY_PROPERTY(PROP_BACKGROUND_ALPHA, float, setBackgroundAlpha); + READ_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, BillboardMode, setBillboardMode); + READ_ENTITY_PROPERTY(PROP_LEFT_MARGIN, float, setLeftMargin); + READ_ENTITY_PROPERTY(PROP_RIGHT_MARGIN, float, setRightMargin); + READ_ENTITY_PROPERTY(PROP_TOP_MARGIN, float, setTopMargin); + READ_ENTITY_PROPERTY(PROP_BOTTOM_MARGIN, float, setBottomMargin); return bytesRead; } @@ -103,8 +121,14 @@ EntityPropertyFlags TextEntityItem::getEntityProperties(EncodeBitstreamParams& p requestedProperties += PROP_TEXT; requestedProperties += PROP_LINE_HEIGHT; requestedProperties += PROP_TEXT_COLOR; + requestedProperties += PROP_TEXT_ALPHA; requestedProperties += PROP_BACKGROUND_COLOR; - requestedProperties += PROP_FACE_CAMERA; + requestedProperties += PROP_BACKGROUND_ALPHA; + requestedProperties += PROP_BILLBOARD_MODE; + requestedProperties += PROP_LEFT_MARGIN; + requestedProperties += PROP_RIGHT_MARGIN; + requestedProperties += PROP_TOP_MARGIN; + requestedProperties += PROP_BOTTOM_MARGIN; return requestedProperties; } @@ -121,8 +145,14 @@ void TextEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits APPEND_ENTITY_PROPERTY(PROP_TEXT, getText()); APPEND_ENTITY_PROPERTY(PROP_LINE_HEIGHT, getLineHeight()); APPEND_ENTITY_PROPERTY(PROP_TEXT_COLOR, getTextColor()); + APPEND_ENTITY_PROPERTY(PROP_TEXT_ALPHA, getTextAlpha()); APPEND_ENTITY_PROPERTY(PROP_BACKGROUND_COLOR, getBackgroundColor()); - APPEND_ENTITY_PROPERTY(PROP_FACE_CAMERA, getFaceCamera()); + APPEND_ENTITY_PROPERTY(PROP_BACKGROUND_ALPHA, getBackgroundAlpha()); + APPEND_ENTITY_PROPERTY(PROP_BILLBOARD_MODE, (uint32_t)getBillboardMode()); + APPEND_ENTITY_PROPERTY(PROP_LEFT_MARGIN, getLeftMargin()); + APPEND_ENTITY_PROPERTY(PROP_RIGHT_MARGIN, getRightMargin()); + APPEND_ENTITY_PROPERTY(PROP_TOP_MARGIN, getTopMargin()); + APPEND_ENTITY_PROPERTY(PROP_BOTTOM_MARGIN, getBottomMargin()); } @@ -218,6 +248,18 @@ glm::u8vec3 TextEntityItem::getTextColor() const { }); } +void TextEntityItem::setTextAlpha(float value) { + withWriteLock([&] { + _textAlpha = value; + }); +} + +float TextEntityItem::getTextAlpha() const { + return resultWithReadLock<float>([&] { + return _textAlpha; + }); +} + void TextEntityItem::setBackgroundColor(const glm::u8vec3& value) { withWriteLock([&] { _backgroundColor = value; @@ -230,17 +272,76 @@ glm::u8vec3 TextEntityItem::getBackgroundColor() const { }); } -bool TextEntityItem::getFaceCamera() const { - bool result; +void TextEntityItem::setBackgroundAlpha(float value) { + withWriteLock([&] { + _backgroundAlpha = value; + }); +} + +float TextEntityItem::getBackgroundAlpha() const { + return resultWithReadLock<float>([&] { + return _backgroundAlpha; + }); +} + +BillboardMode TextEntityItem::getBillboardMode() const { + BillboardMode result; withReadLock([&] { - result = _faceCamera; + result = _billboardMode; }); return result; } -void TextEntityItem::setFaceCamera(bool value) { +void TextEntityItem::setBillboardMode(BillboardMode value) { withWriteLock([&] { - _faceCamera = value; + _billboardMode = value; }); } +void TextEntityItem::setLeftMargin(float value) { + withWriteLock([&] { + _leftMargin = value; + }); +} + +float TextEntityItem::getLeftMargin() const { + return resultWithReadLock<float>([&] { + return _leftMargin; + }); +} + +void TextEntityItem::setRightMargin(float value) { + withWriteLock([&] { + _rightMargin = value; + }); +} + +float TextEntityItem::getRightMargin() const { + return resultWithReadLock<float>([&] { + return _rightMargin; + }); +} + +void TextEntityItem::setTopMargin(float value) { + withWriteLock([&] { + _topMargin = value; + }); +} + +float TextEntityItem::getTopMargin() const { + return resultWithReadLock<float>([&] { + return _topMargin; + }); +} + +void TextEntityItem::setBottomMargin(float value) { + withWriteLock([&] { + _bottomMargin = value; + }); +} + +float TextEntityItem::getBottomMargin() const { + return resultWithReadLock<float>([&] { + return _bottomMargin; + }); +} diff --git a/libraries/entities/src/TextEntityItem.h b/libraries/entities/src/TextEntityItem.h index 357697fdec..5c22d8edf0 100644 --- a/libraries/entities/src/TextEntityItem.h +++ b/libraries/entities/src/TextEntityItem.h @@ -67,20 +67,45 @@ public: glm::u8vec3 getTextColor() const; void setTextColor(const glm::u8vec3& value); + static const float DEFAULT_TEXT_ALPHA; + float getTextAlpha() const; + void setTextAlpha(float value); + static const glm::u8vec3 DEFAULT_BACKGROUND_COLOR; glm::u8vec3 getBackgroundColor() const; void setBackgroundColor(const glm::u8vec3& value); - static const bool DEFAULT_FACE_CAMERA; - bool getFaceCamera() const; - void setFaceCamera(bool value); + float getBackgroundAlpha() const; + void setBackgroundAlpha(float value); + + BillboardMode getBillboardMode() const; + void setBillboardMode(BillboardMode value); + + static const float DEFAULT_MARGIN; + float getLeftMargin() const; + void setLeftMargin(float value); + + float getRightMargin() const; + void setRightMargin(float value); + + float getTopMargin() const; + void setTopMargin(float value); + + float getBottomMargin() const; + void setBottomMargin(float value); private: QString _text; float _lineHeight; glm::u8vec3 _textColor; + float _textAlpha; glm::u8vec3 _backgroundColor; - bool _faceCamera; + float _backgroundAlpha; + BillboardMode _billboardMode; + float _leftMargin; + float _rightMargin; + float _topMargin; + float _bottomMargin; }; #endif // hifi_TextEntityItem_h diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 1b8675caac..7f7f6170d4 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -11,7 +11,9 @@ #include "ZoneEntityItem.h" +#include <glm/gtx/transform.hpp> #include <QDebug> +#include <QUrlQuery> #include <ByteCountCoding.h> @@ -48,6 +50,9 @@ ZoneEntityItem::ZoneEntityItem(const EntityItemID& entityItemID) : EntityItem(en EntityItemProperties ZoneEntityItem::getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const { EntityItemProperties properties = EntityItem::getProperties(desiredProperties, allowEmptyDesiredProperties); // get the properties from our base class + COPY_ENTITY_PROPERTY_TO_PROPERTIES(shapeType, getShapeType); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(compoundShapeURL, getCompoundShapeURL); + // Contain QString properties, must be synchronized withReadLock([&] { _keyLightProperties.getProperties(properties); @@ -57,17 +62,14 @@ EntityItemProperties ZoneEntityItem::getProperties(const EntityPropertyFlags& de _hazeProperties.getProperties(properties); _bloomProperties.getProperties(properties); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(shapeType, getShapeType); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(compoundShapeURL, getCompoundShapeURL); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(flyingAllowed, getFlyingAllowed); COPY_ENTITY_PROPERTY_TO_PROPERTIES(ghostingAllowed, getGhostingAllowed); COPY_ENTITY_PROPERTY_TO_PROPERTIES(filterURL, getFilterURL); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(hazeMode, getHazeMode); COPY_ENTITY_PROPERTY_TO_PROPERTIES(keyLightMode, getKeyLightMode); COPY_ENTITY_PROPERTY_TO_PROPERTIES(ambientLightMode, getAmbientLightMode); COPY_ENTITY_PROPERTY_TO_PROPERTIES(skyboxMode, getSkyboxMode); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(hazeMode, getHazeMode); COPY_ENTITY_PROPERTY_TO_PROPERTIES(bloomMode, getBloomMode); return properties; @@ -94,6 +96,9 @@ bool ZoneEntityItem::setProperties(const EntityItemProperties& properties) { bool ZoneEntityItem::setSubClassProperties(const EntityItemProperties& properties) { bool somethingChanged = EntityItem::setSubClassProperties(properties); // set the properties in our base class + SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL); + // Contains a QString property, must be synchronized withWriteLock([&] { _keyLightPropertiesChanged = _keyLightProperties.setProperties(properties); @@ -103,17 +108,14 @@ bool ZoneEntityItem::setSubClassProperties(const EntityItemProperties& propertie _hazePropertiesChanged = _hazeProperties.setProperties(properties); _bloomPropertiesChanged = _bloomProperties.setProperties(properties); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(flyingAllowed, setFlyingAllowed); SET_ENTITY_PROPERTY_FROM_PROPERTIES(ghostingAllowed, setGhostingAllowed); SET_ENTITY_PROPERTY_FROM_PROPERTIES(filterURL, setFilterURL); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(hazeMode, setHazeMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(keyLightMode, setKeyLightMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(ambientLightMode, setAmbientLightMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(skyboxMode, setSkyboxMode); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(hazeMode, setHazeMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(bloomMode, setBloomMode); somethingChanged = somethingChanged || _keyLightPropertiesChanged || _ambientLightPropertiesChanged || @@ -129,6 +131,9 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesRead = 0; const unsigned char* dataAt = data; + READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType); + READ_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL); + { int bytesFromKeylight; withWriteLock([&] { @@ -178,17 +183,14 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, dataAt += bytesFromBloom; } - READ_ENTITY_PROPERTY(PROP_SHAPE_TYPE, ShapeType, setShapeType); - READ_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, QString, setCompoundShapeURL); - READ_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, bool, setFlyingAllowed); READ_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, bool, setGhostingAllowed); READ_ENTITY_PROPERTY(PROP_FILTER_URL, QString, setFilterURL); - READ_ENTITY_PROPERTY(PROP_HAZE_MODE, uint32_t, setHazeMode); READ_ENTITY_PROPERTY(PROP_KEY_LIGHT_MODE, uint32_t, setKeyLightMode); READ_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_MODE, uint32_t, setAmbientLightMode); READ_ENTITY_PROPERTY(PROP_SKYBOX_MODE, uint32_t, setSkyboxMode); + READ_ENTITY_PROPERTY(PROP_HAZE_MODE, uint32_t, setHazeMode); READ_ENTITY_PROPERTY(PROP_BLOOM_MODE, uint32_t, setBloomMode); return bytesRead; @@ -197,25 +199,23 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, EntityPropertyFlags ZoneEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); - withReadLock([&] { - requestedProperties += _keyLightProperties.getEntityProperties(params); - requestedProperties += _ambientLightProperties.getEntityProperties(params); - requestedProperties += _skyboxProperties.getEntityProperties(params); - }); - requestedProperties += _hazeProperties.getEntityProperties(params); - requestedProperties += _bloomProperties.getEntityProperties(params); - requestedProperties += PROP_SHAPE_TYPE; requestedProperties += PROP_COMPOUND_SHAPE_URL; + requestedProperties += _keyLightProperties.getEntityProperties(params); + requestedProperties += _ambientLightProperties.getEntityProperties(params); + requestedProperties += _skyboxProperties.getEntityProperties(params); + requestedProperties += _hazeProperties.getEntityProperties(params); + requestedProperties += _bloomProperties.getEntityProperties(params); + requestedProperties += PROP_FLYING_ALLOWED; requestedProperties += PROP_GHOSTING_ALLOWED; requestedProperties += PROP_FILTER_URL; - requestedProperties += PROP_HAZE_MODE; requestedProperties += PROP_KEY_LIGHT_MODE; requestedProperties += PROP_AMBIENT_LIGHT_MODE; requestedProperties += PROP_SKYBOX_MODE; + requestedProperties += PROP_HAZE_MODE; requestedProperties += PROP_BLOOM_MODE; return requestedProperties; @@ -231,28 +231,30 @@ void ZoneEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits bool successPropertyFits = true; - _keyLightProperties.appendSubclassData(packetData, params, modelTreeElementExtraEncodeData, requestedProperties, - propertyFlags, propertiesDidntFit, propertyCount, appendState); - _ambientLightProperties.appendSubclassData(packetData, params, modelTreeElementExtraEncodeData, requestedProperties, - propertyFlags, propertiesDidntFit, propertyCount, appendState); - _skyboxProperties.appendSubclassData(packetData, params, modelTreeElementExtraEncodeData, requestedProperties, - propertyFlags, propertiesDidntFit, propertyCount, appendState); + APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)getShapeType()); + APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, getCompoundShapeURL()); + + withReadLock([&] { + _keyLightProperties.appendSubclassData(packetData, params, modelTreeElementExtraEncodeData, requestedProperties, + propertyFlags, propertiesDidntFit, propertyCount, appendState); + _ambientLightProperties.appendSubclassData(packetData, params, modelTreeElementExtraEncodeData, requestedProperties, + propertyFlags, propertiesDidntFit, propertyCount, appendState); + _skyboxProperties.appendSubclassData(packetData, params, modelTreeElementExtraEncodeData, requestedProperties, + propertyFlags, propertiesDidntFit, propertyCount, appendState); + }); _hazeProperties.appendSubclassData(packetData, params, modelTreeElementExtraEncodeData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); _bloomProperties.appendSubclassData(packetData, params, modelTreeElementExtraEncodeData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); - APPEND_ENTITY_PROPERTY(PROP_SHAPE_TYPE, (uint32_t)getShapeType()); - APPEND_ENTITY_PROPERTY(PROP_COMPOUND_SHAPE_URL, getCompoundShapeURL()); - APPEND_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, getFlyingAllowed()); APPEND_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, getGhostingAllowed()); APPEND_ENTITY_PROPERTY(PROP_FILTER_URL, getFilterURL()); - APPEND_ENTITY_PROPERTY(PROP_HAZE_MODE, (uint32_t)getHazeMode()); APPEND_ENTITY_PROPERTY(PROP_KEY_LIGHT_MODE, (uint32_t)getKeyLightMode()); APPEND_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_MODE, (uint32_t)getAmbientLightMode()); APPEND_ENTITY_PROPERTY(PROP_SKYBOX_MODE, (uint32_t)getSkyboxMode()); + APPEND_ENTITY_PROPERTY(PROP_HAZE_MODE, (uint32_t)getHazeMode()); APPEND_ENTITY_PROPERTY(PROP_BLOOM_MODE, (uint32_t)getBloomMode()); } @@ -262,11 +264,11 @@ void ZoneEntityItem::debugDump() const { qCDebug(entities) << " position:" << debugTreeVector(getWorldPosition()); qCDebug(entities) << " dimensions:" << debugTreeVector(getScaledDimensions()); qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); - qCDebug(entities) << " _hazeMode:" << EntityItemProperties::getComponentModeString(_hazeMode); - qCDebug(entities) << " _keyLightMode:" << EntityItemProperties::getComponentModeString(_keyLightMode); - qCDebug(entities) << " _ambientLightMode:" << EntityItemProperties::getComponentModeString(_ambientLightMode); - qCDebug(entities) << " _skyboxMode:" << EntityItemProperties::getComponentModeString(_skyboxMode); - qCDebug(entities) << " _bloomMode:" << EntityItemProperties::getComponentModeString(_bloomMode); + qCDebug(entities) << " _hazeMode:" << EntityItemProperties::getComponentModeAsString(_hazeMode); + qCDebug(entities) << " _keyLightMode:" << EntityItemProperties::getComponentModeAsString(_keyLightMode); + qCDebug(entities) << " _ambientLightMode:" << EntityItemProperties::getComponentModeAsString(_ambientLightMode); + qCDebug(entities) << " _skyboxMode:" << EntityItemProperties::getComponentModeAsString(_skyboxMode); + qCDebug(entities) << " _bloomMode:" << EntityItemProperties::getComponentModeAsString(_bloomMode); _keyLightProperties.debugDump(); _ambientLightProperties.debugDump(); @@ -275,22 +277,52 @@ void ZoneEntityItem::debugDump() const { _bloomProperties.debugDump(); } -ShapeType ZoneEntityItem::getShapeType() const { - // Zones are not allowed to have a SHAPE_TYPE_NONE... they are always at least a SHAPE_TYPE_BOX - if (_shapeType == SHAPE_TYPE_COMPOUND) { - return hasCompoundShapeURL() ? SHAPE_TYPE_COMPOUND : DEFAULT_SHAPE_TYPE; +void ZoneEntityItem::setShapeType(ShapeType type) { + ShapeType oldShapeType = _shapeType; + switch(type) { + case SHAPE_TYPE_NONE: + case SHAPE_TYPE_CAPSULE_X: + case SHAPE_TYPE_CAPSULE_Y: + case SHAPE_TYPE_CAPSULE_Z: + case SHAPE_TYPE_HULL: + case SHAPE_TYPE_PLANE: + case SHAPE_TYPE_SIMPLE_HULL: + case SHAPE_TYPE_SIMPLE_COMPOUND: + case SHAPE_TYPE_STATIC_MESH: + case SHAPE_TYPE_CIRCLE: + // these types are unsupported for ZoneEntity + type = DEFAULT_SHAPE_TYPE; + break; + default: + break; + } + _shapeType = type; + + if (type == SHAPE_TYPE_COMPOUND) { + if (type != oldShapeType) { + fetchCollisionGeometryResource(); + } } else { - return _shapeType == SHAPE_TYPE_NONE ? DEFAULT_SHAPE_TYPE : _shapeType; + _shapeResource.reset(); } } +ShapeType ZoneEntityItem::getShapeType() const { + return _shapeType; +} + void ZoneEntityItem::setCompoundShapeURL(const QString& url) { + QString oldCompoundShapeURL = _compoundShapeURL; withWriteLock([&] { _compoundShapeURL = url; - if (_compoundShapeURL.isEmpty() && _shapeType == SHAPE_TYPE_COMPOUND) { - _shapeType = DEFAULT_SHAPE_TYPE; - } }); + if (oldCompoundShapeURL != url) { + if (_shapeType == SHAPE_TYPE_COMPOUND) { + fetchCollisionGeometryResource(); + } else { + _shapeResource.reset(); + } + } } bool ZoneEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, @@ -307,6 +339,27 @@ bool ZoneEntityItem::findDetailedParabolaIntersection(const glm::vec3& origin, c return _zonesArePickable; } +bool ZoneEntityItem::contains(const glm::vec3& point) const { + GeometryResource::Pointer resource = _shapeResource; + if (_shapeType == SHAPE_TYPE_COMPOUND && resource) { + if (resource->isLoaded()) { + const HFMModel& hfmModel = resource->getHFMModel(); + + Extents meshExtents = hfmModel.getMeshExtents(); + glm::vec3 meshExtentsDiagonal = meshExtents.maximum - meshExtents.minimum; + glm::vec3 offset = -meshExtents.minimum- (meshExtentsDiagonal * getRegistrationPoint()); + glm::vec3 scale(getScaledDimensions() / meshExtentsDiagonal); + + glm::mat4 hfmToEntityMatrix = glm::scale(scale) * glm::translate(offset); + glm::mat4 entityToWorldMatrix = getTransform().getMatrix(); + glm::mat4 worldToHFMMatrix = glm::inverse(entityToWorldMatrix * hfmToEntityMatrix); + + return hfmModel.convexHullContains(glm::vec3(worldToHFMMatrix * glm::vec4(point, 1.0f))); + } + } + return EntityItem::contains(point); +} + void ZoneEntityItem::setFilterURL(QString url) { withWriteLock([&] { _filterURL = url; @@ -326,10 +379,6 @@ QString ZoneEntityItem::getFilterURL() const { return result; } -bool ZoneEntityItem::hasCompoundShapeURL() const { - return !getCompoundShapeURL().isEmpty(); -} - QString ZoneEntityItem::getCompoundShapeURL() const { QString result; withReadLock([&] { @@ -403,3 +452,15 @@ void ZoneEntityItem::setSkyboxMode(const uint32_t value) { uint32_t ZoneEntityItem::getSkyboxMode() const { return _skyboxMode; } + +void ZoneEntityItem::fetchCollisionGeometryResource() { + QUrl hullURL(getCompoundShapeURL()); + if (hullURL.isEmpty()) { + _shapeResource.reset(); + } else { + QUrlQuery queryArgs(hullURL); + queryArgs.addQueryItem("collision-hull", ""); + hullURL.setQuery(queryArgs); + _shapeResource = DependencyManager::get<ModelCache>()->getCollisionGeometryResource(hullURL); + } +} diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index c2f4542aa6..813115add9 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -12,6 +12,9 @@ #ifndef hifi_ZoneEntityItem_h #define hifi_ZoneEntityItem_h +#include <ComponentMode.h> +#include <model-networking/ModelCache.h> + #include "KeyLightPropertyGroup.h" #include "AmbientLightPropertyGroup.h" #include "EntityItem.h" @@ -19,7 +22,6 @@ #include "SkyboxPropertyGroup.h" #include "HazePropertyGroup.h" #include "BloomPropertyGroup.h" -#include <ComponentMode.h> class ZoneEntityItem : public EntityItem { public: @@ -58,10 +60,9 @@ public: static void setDrawZoneBoundaries(bool value) { _drawZoneBoundaries = value; } virtual bool isReadyToComputeShape() const override { return false; } - void setShapeType(ShapeType type) override { withWriteLock([&] { _shapeType = type; }); } + virtual void setShapeType(ShapeType type) override; virtual ShapeType getShapeType() const override; - virtual bool hasCompoundShapeURL() const; QString getCompoundShapeURL() const; virtual void setCompoundShapeURL(const QString& url); @@ -115,6 +116,8 @@ public: BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, bool precisionPicking) const override; + bool contains(const glm::vec3& point) const override; + virtual void debugDump() const override; static const ShapeType DEFAULT_SHAPE_TYPE; @@ -156,6 +159,10 @@ protected: static bool _drawZoneBoundaries; static bool _zonesArePickable; + + void fetchCollisionGeometryResource(); + GeometryResource::Pointer _shapeResource; + }; #endif // hifi_ZoneEntityItem_h diff --git a/libraries/fbx/src/FBX.h b/libraries/fbx/src/FBX.h index 157ca5b282..8ad419c7ec 100644 --- a/libraries/fbx/src/FBX.h +++ b/libraries/fbx/src/FBX.h @@ -19,20 +19,6 @@ #include <glm/glm.hpp> -#if defined(Q_OS_ANDROID) -#define FBX_PACK_NORMALS 0 -#else -#define FBX_PACK_NORMALS 1 -#endif - -#if FBX_PACK_NORMALS -using NormalType = glm::uint32; -#define FBX_NORMAL_ELEMENT gpu::Element::VEC4F_NORMALIZED_XYZ10W2 -#else -using NormalType = glm::vec3; -#define FBX_NORMAL_ELEMENT gpu::Element::VEC3F_XYZ -#endif - // See comment in FBXSerializer::parseFBX(). static const int FBX_HEADER_BYTES_BEFORE_VERSION = 23; static const QByteArray FBX_BINARY_PROLOG("Kaydara FBX Binary "); diff --git a/libraries/fbx/src/FBXSerializer.cpp b/libraries/fbx/src/FBXSerializer.cpp index b425b6795d..68019c2f4b 100644 --- a/libraries/fbx/src/FBXSerializer.cpp +++ b/libraries/fbx/src/FBXSerializer.cpp @@ -11,27 +11,13 @@ #include "FBXSerializer.h" -#include <iostream> #include <QBuffer> -#include <QDataStream> -#include <QIODevice> -#include <QStringList> -#include <QTextStream> -#include <QtDebug> -#include <QtEndian> -#include <QFileInfo> #include <glm/gtc/quaternion.hpp> #include <glm/gtx/quaternion.hpp> #include <glm/gtx/transform.hpp> #include <FaceshiftConstants.h> -#include <GeometryUtil.h> -#include <GLMHelpers.h> -#include <NumericalConstants.h> -#include <OctalCode.h> -#include <gpu/Format.h> -#include <LogHandler.h> #include <hfm/ModelFormatLogging.h> @@ -126,26 +112,6 @@ QString getID(const QVariantList& properties, int index = 0) { return processID(properties.at(index).toString()); } -/// The names of the joints in the Maya HumanIK rig -static const std::array<const char*, 16> HUMANIK_JOINTS = {{ - "RightHand", - "RightForeArm", - "RightArm", - "Head", - "LeftArm", - "LeftForeArm", - "LeftHand", - "Neck", - "Spine", - "Hips", - "RightUpLeg", - "LeftUpLeg", - "RightLeg", - "LeftLeg", - "RightFoot", - "LeftFoot" -}}; - class FBXModel { public: QString name; @@ -478,32 +444,6 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr std::map<QString, HFMLight> lights; QVariantHash joints = mapping.value("joint").toHash(); - QString jointEyeLeftName = "EyeLeft"; - QString jointEyeRightName = "EyeRight"; - QString jointNeckName = "Neck"; - QString jointRootName = "Hips"; - QString jointLeanName = "Spine"; - QString jointHeadName = "Head"; - QString jointLeftHandName = "LeftHand"; - QString jointRightHandName = "RightHand"; - QString jointEyeLeftID; - QString jointEyeRightID; - QString jointNeckID; - QString jointRootID; - QString jointLeanID; - QString jointHeadID; - QString jointLeftHandID; - QString jointRightHandID; - QString jointLeftToeID; - QString jointRightToeID; - - - QVector<QString> humanIKJointNames; - for (int i = 0; i < (int) HUMANIK_JOINTS.size(); i++) { - QByteArray jointName = HUMANIK_JOINTS[i]; - humanIKJointNames.append(processID(getString(joints.value(jointName, jointName)))); - } - QVector<QString> humanIKJointIDs(humanIKJointNames.size()); QVariantHash blendshapeMappings = mapping.value("bs").toHash(); @@ -602,42 +542,6 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr hifiGlobalNodeID = id; } - if (name == jointEyeLeftName || name == "EyeL" || name == "joint_Leye" || (hfmModel.hfmToHifiJointNameMapping.contains(jointEyeLeftName) && (name == hfmModel.hfmToHifiJointNameMapping[jointEyeLeftName]))) { - jointEyeLeftID = getID(object.properties); - - } else if (name == jointEyeRightName || name == "EyeR" || name == "joint_Reye" || (hfmModel.hfmToHifiJointNameMapping.contains(jointEyeRightName) && (name == hfmModel.hfmToHifiJointNameMapping[jointEyeRightName]))) { - jointEyeRightID = getID(object.properties); - - } else if (name == jointNeckName || name == "NeckRot" || name == "joint_neck" || (hfmModel.hfmToHifiJointNameMapping.contains(jointNeckName) && (name == hfmModel.hfmToHifiJointNameMapping[jointNeckName]))) { - jointNeckID = getID(object.properties); - - } else if (name == jointRootName || (hfmModel.hfmToHifiJointNameMapping.contains(jointRootName) && (name == hfmModel.hfmToHifiJointNameMapping[jointRootName]))) { - jointRootID = getID(object.properties); - - } else if (name == jointLeanName || (hfmModel.hfmToHifiJointNameMapping.contains(jointLeanName) && (name == hfmModel.hfmToHifiJointNameMapping[jointLeanName]))) { - jointLeanID = getID(object.properties); - - } else if ((name == jointHeadName) || (hfmModel.hfmToHifiJointNameMapping.contains(jointHeadName) && (name == hfmModel.hfmToHifiJointNameMapping[jointHeadName]))) { - jointHeadID = getID(object.properties); - - } else if (name == jointLeftHandName || name == "LeftHand" || name == "joint_L_hand" || (hfmModel.hfmToHifiJointNameMapping.contains(jointLeftHandName) && (name == hfmModel.hfmToHifiJointNameMapping[jointLeftHandName]))) { - jointLeftHandID = getID(object.properties); - - } else if (name == jointRightHandName || name == "RightHand" || name == "joint_R_hand" || (hfmModel.hfmToHifiJointNameMapping.contains(jointRightHandName) && (name == hfmModel.hfmToHifiJointNameMapping[jointRightHandName]))) { - jointRightHandID = getID(object.properties); - - } else if (name == "LeftToe" || name == "joint_L_toe" || name == "LeftToe_End" || (hfmModel.hfmToHifiJointNameMapping.contains("LeftToe") && (name == hfmModel.hfmToHifiJointNameMapping["LeftToe"]))) { - jointLeftToeID = getID(object.properties); - - } else if (name == "RightToe" || name == "joint_R_toe" || name == "RightToe_End" || (hfmModel.hfmToHifiJointNameMapping.contains("RightToe") && (name == hfmModel.hfmToHifiJointNameMapping["RightToe"]))) { - jointRightToeID = getID(object.properties); - } - - int humanIKJointIndex = humanIKJointNames.indexOf(name); - if (humanIKJointIndex != -1) { - humanIKJointIDs[humanIKJointIndex] = getID(object.properties); - } - glm::vec3 translation; // NOTE: the euler angles as supplied by the FBX file are in degrees glm::vec3 rotationOffset; @@ -1449,28 +1353,6 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr std::vector<ShapeVertices> shapeVertices; shapeVertices.resize(std::max(1, hfmModel.joints.size()) ); - // find our special joints - hfmModel.leftEyeJointIndex = modelIDs.indexOf(jointEyeLeftID); - hfmModel.rightEyeJointIndex = modelIDs.indexOf(jointEyeRightID); - hfmModel.neckJointIndex = modelIDs.indexOf(jointNeckID); - hfmModel.rootJointIndex = modelIDs.indexOf(jointRootID); - hfmModel.leanJointIndex = modelIDs.indexOf(jointLeanID); - hfmModel.headJointIndex = modelIDs.indexOf(jointHeadID); - hfmModel.leftHandJointIndex = modelIDs.indexOf(jointLeftHandID); - hfmModel.rightHandJointIndex = modelIDs.indexOf(jointRightHandID); - hfmModel.leftToeJointIndex = modelIDs.indexOf(jointLeftToeID); - hfmModel.rightToeJointIndex = modelIDs.indexOf(jointRightToeID); - - foreach (const QString& id, humanIKJointIDs) { - hfmModel.humanIKJointIndices.append(modelIDs.indexOf(id)); - } - - // extract the translation component of the neck transform - if (hfmModel.neckJointIndex != -1) { - const glm::mat4& transform = hfmModel.joints.at(hfmModel.neckJointIndex).transform; - hfmModel.neckPivot = glm::vec3(transform[3][0], transform[3][1], transform[3][2]); - } - hfmModel.bindExtents.reset(); hfmModel.meshExtents.reset(); @@ -1744,14 +1626,9 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr } } } - buildModelMesh(extracted.mesh, url); hfmModel.meshes.append(extracted.mesh); int meshIndex = hfmModel.meshes.size() - 1; - if (extracted.mesh._mesh) { - extracted.mesh._mesh->displayName = QString("%1#/mesh/%2").arg(url).arg(meshIndex).toStdString(); - extracted.mesh._mesh->modelName = modelIDsToNames.value(modelID).toStdString(); - } meshIDsToMeshIndices.insert(it.key(), meshIndex); } @@ -1819,22 +1696,6 @@ HFMModel* FBXSerializer::extractHFMModel(const QVariantHash& mapping, const QStr } } } - { - int i = 0; - for (const auto& mesh : hfmModel.meshes) { - auto name = hfmModel.getModelNameOfMesh(i++); - if (!name.isEmpty()) { - if (mesh._mesh) { - mesh._mesh->modelName = name.toStdString(); - if (!mesh._mesh->displayName.size()) { - mesh._mesh->displayName = QString("#%1").arg(name).toStdString(); - } - } else { - qDebug() << "modelName but no mesh._mesh" << name; - } - } - } - } auto offsets = getJointRotationOffsets(mapping); hfmModel.jointRotationOffsets.clear(); diff --git a/libraries/fbx/src/FBXSerializer.h b/libraries/fbx/src/FBXSerializer.h index a76fb8f9bf..31ca301522 100644 --- a/libraries/fbx/src/FBXSerializer.h +++ b/libraries/fbx/src/FBXSerializer.h @@ -96,6 +96,8 @@ class ExtractedMesh; class FBXSerializer : public HFMSerializer { public: + virtual ~FBXSerializer() {} + MediaType getMediaType() const override; std::unique_ptr<hfm::Serializer::Factory> getFactory() const override; @@ -111,9 +113,6 @@ public: static ExtractedMesh extractMesh(const FBXNode& object, unsigned int& meshIndex, bool deduplicate = true); QHash<QString, ExtractedMesh> meshes; - static void buildModelMesh(HFMMesh& extractedMesh, const QString& url); - - static glm::vec3 normalizeDirForPacking(const glm::vec3& dir); HFMTexture getTexture(const QString& textureID); diff --git a/libraries/fbx/src/FBXSerializer_Mesh.cpp b/libraries/fbx/src/FBXSerializer_Mesh.cpp index 38533dbc42..fd1f80425b 100644 --- a/libraries/fbx/src/FBXSerializer_Mesh.cpp +++ b/libraries/fbx/src/FBXSerializer_Mesh.cpp @@ -42,16 +42,6 @@ using vec2h = glm::tvec2<glm::detail::hdata>; -#define HFM_PACK_COLORS 1 - -#if HFM_PACK_COLORS -using ColorType = glm::uint32; -#define FBX_COLOR_ELEMENT gpu::Element::COLOR_RGBA_32 -#else -using ColorType = glm::vec3; -#define FBX_COLOR_ELEMENT gpu::Element::VEC3F_XYZ -#endif - class Vertex { public: int originalIndex; @@ -556,364 +546,3 @@ ExtractedMesh FBXSerializer::extractMesh(const FBXNode& object, unsigned int& me return data.extracted; } - -glm::vec3 FBXSerializer::normalizeDirForPacking(const glm::vec3& dir) { - auto maxCoord = glm::max(fabsf(dir.x), glm::max(fabsf(dir.y), fabsf(dir.z))); - if (maxCoord > 1e-6f) { - return dir / maxCoord; - } - return dir; -} - -void FBXSerializer::buildModelMesh(HFMMesh& extractedMesh, const QString& url) { - unsigned int totalSourceIndices = 0; - foreach(const HFMMeshPart& part, extractedMesh.parts) { - totalSourceIndices += (part.quadTrianglesIndices.size() + part.triangleIndices.size()); - } - - static int repeatMessageID = LogHandler::getInstance().newRepeatedMessageID(); - - if (!totalSourceIndices) { - HIFI_FCDEBUG_ID(modelformat(), repeatMessageID, "buildModelMesh failed -- no indices, url = " << url); - return; - } - - if (extractedMesh.vertices.size() == 0) { - HIFI_FCDEBUG_ID(modelformat(), repeatMessageID, "buildModelMesh failed -- no vertices, url = " << url); - return; - } - - HFMMesh& hfmMesh = extractedMesh; - graphics::MeshPointer mesh(new graphics::Mesh()); - int numVerts = extractedMesh.vertices.size(); - - if (!hfmMesh.normals.empty() && hfmMesh.tangents.empty()) { - // Fill with a dummy value to force tangents to be present if there are normals - hfmMesh.tangents.reserve(hfmMesh.normals.size()); - std::fill_n(std::back_inserter(hfmMesh.tangents), hfmMesh.normals.size(), Vectors::UNIT_X); - } - // Same thing with blend shapes - for (auto& blendShape : hfmMesh.blendshapes) { - if (!blendShape.normals.empty() && blendShape.tangents.empty()) { - // Fill with a dummy value to force tangents to be present if there are normals - blendShape.tangents.reserve(blendShape.normals.size()); - std::fill_n(std::back_inserter(blendShape.tangents), blendShape.normals.size(), Vectors::UNIT_X); - } - } - - // evaluate all attribute elements and data sizes - - // Position is a vec3 - const auto positionElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); - const int positionsSize = numVerts * positionElement.getSize(); - - // Normal and tangent are always there together packed in normalized xyz32bits word (times 2) - const auto normalElement = FBX_NORMAL_ELEMENT; - const int normalsSize = hfmMesh.normals.size() * normalElement.getSize(); - const int tangentsSize = hfmMesh.tangents.size() * normalElement.getSize(); - // If there are normals then there should be tangents - assert(normalsSize <= tangentsSize); - if (tangentsSize > normalsSize) { - qWarning() << "Unexpected tangents in " << url; - } - const auto normalsAndTangentsSize = normalsSize + tangentsSize; - - // Color attrib - const auto colorElement = FBX_COLOR_ELEMENT; - const int colorsSize = hfmMesh.colors.size() * colorElement.getSize(); - - // Texture coordinates are stored in 2 half floats - const auto texCoordsElement = gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV); - const int texCoordsSize = hfmMesh.texCoords.size() * texCoordsElement.getSize(); - const int texCoords1Size = hfmMesh.texCoords1.size() * texCoordsElement.getSize(); - - // Support for 4 skinning clusters: - // 4 Indices are uint8 ideally, uint16 if more than 256. - const auto clusterIndiceElement = (hfmMesh.clusters.size() < UINT8_MAX ? gpu::Element(gpu::VEC4, gpu::UINT8, gpu::XYZW) : gpu::Element(gpu::VEC4, gpu::UINT16, gpu::XYZW)); - // 4 Weights are normalized 16bits - const auto clusterWeightElement = gpu::Element(gpu::VEC4, gpu::NUINT16, gpu::XYZW); - - // Cluster indices and weights must be the same sizes - const int NUM_CLUSTERS_PER_VERT = 4; - const int numVertClusters = (hfmMesh.clusterIndices.size() == hfmMesh.clusterWeights.size() ? hfmMesh.clusterIndices.size() / NUM_CLUSTERS_PER_VERT : 0); - const int clusterIndicesSize = numVertClusters * clusterIndiceElement.getSize(); - const int clusterWeightsSize = numVertClusters * clusterWeightElement.getSize(); - - // Decide on where to put what seequencially in a big buffer: - const int positionsOffset = 0; - const int normalsAndTangentsOffset = positionsOffset + positionsSize; - const int colorsOffset = normalsAndTangentsOffset + normalsAndTangentsSize; - const int texCoordsOffset = colorsOffset + colorsSize; - const int texCoords1Offset = texCoordsOffset + texCoordsSize; - const int clusterIndicesOffset = texCoords1Offset + texCoords1Size; - const int clusterWeightsOffset = clusterIndicesOffset + clusterIndicesSize; - const int totalVertsSize = clusterWeightsOffset + clusterWeightsSize; - - // Copy all vertex data in a single buffer - auto vertBuffer = std::make_shared<gpu::Buffer>(); - vertBuffer->resize(totalVertsSize); - - // First positions - vertBuffer->setSubData(positionsOffset, positionsSize, (const gpu::Byte*) extractedMesh.vertices.data()); - - // Interleave normals and tangents - if (normalsSize > 0) { - std::vector<NormalType> normalsAndTangents; - - normalsAndTangents.reserve(hfmMesh.normals.size() + hfmMesh.tangents.size()); - for (auto normalIt = hfmMesh.normals.constBegin(), tangentIt = hfmMesh.tangents.constBegin(); - normalIt != hfmMesh.normals.constEnd(); - ++normalIt, ++tangentIt) { -#if FBX_PACK_NORMALS - const auto normal = normalizeDirForPacking(*normalIt); - const auto tangent = normalizeDirForPacking(*tangentIt); - const auto packedNormal = glm::packSnorm3x10_1x2(glm::vec4(normal, 0.0f)); - const auto packedTangent = glm::packSnorm3x10_1x2(glm::vec4(tangent, 0.0f)); -#else - const auto packedNormal = *normalIt; - const auto packedTangent = *tangentIt; -#endif - normalsAndTangents.push_back(packedNormal); - normalsAndTangents.push_back(packedTangent); - } - vertBuffer->setSubData(normalsAndTangentsOffset, normalsAndTangentsSize, (const gpu::Byte*) normalsAndTangents.data()); - } - - // Pack colors - if (colorsSize > 0) { -#if HFM_PACK_COLORS - std::vector<ColorType> colors; - - colors.reserve(hfmMesh.colors.size()); - for (const auto& color : hfmMesh.colors) { - colors.push_back(glm::packUnorm4x8(glm::vec4(color, 1.0f))); - } - vertBuffer->setSubData(colorsOffset, colorsSize, (const gpu::Byte*) colors.data()); -#else - vertBuffer->setSubData(colorsOffset, colorsSize, (const gpu::Byte*) hfmMesh.colors.constData()); -#endif - } - - // Pack Texcoords 0 and 1 (if exists) - if (texCoordsSize > 0) { - QVector<vec2h> texCoordData; - texCoordData.reserve(hfmMesh.texCoords.size()); - for (auto& texCoordVec2f : hfmMesh.texCoords) { - vec2h texCoordVec2h; - - texCoordVec2h.x = glm::detail::toFloat16(texCoordVec2f.x); - texCoordVec2h.y = glm::detail::toFloat16(texCoordVec2f.y); - texCoordData.push_back(texCoordVec2h); - } - vertBuffer->setSubData(texCoordsOffset, texCoordsSize, (const gpu::Byte*) texCoordData.constData()); - } - if (texCoords1Size > 0) { - QVector<vec2h> texCoordData; - texCoordData.reserve(hfmMesh.texCoords1.size()); - for (auto& texCoordVec2f : hfmMesh.texCoords1) { - vec2h texCoordVec2h; - - texCoordVec2h.x = glm::detail::toFloat16(texCoordVec2f.x); - texCoordVec2h.y = glm::detail::toFloat16(texCoordVec2f.y); - texCoordData.push_back(texCoordVec2h); - } - vertBuffer->setSubData(texCoords1Offset, texCoords1Size, (const gpu::Byte*) texCoordData.constData()); - } - - // Clusters data - if (clusterIndicesSize > 0) { - if (hfmMesh.clusters.size() < UINT8_MAX) { - // yay! we can fit the clusterIndices within 8-bits - int32_t numIndices = hfmMesh.clusterIndices.size(); - QVector<uint8_t> clusterIndices; - clusterIndices.resize(numIndices); - for (int32_t i = 0; i < numIndices; ++i) { - assert(hfmMesh.clusterIndices[i] <= UINT8_MAX); - clusterIndices[i] = (uint8_t)(hfmMesh.clusterIndices[i]); - } - vertBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) clusterIndices.constData()); - } else { - vertBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) hfmMesh.clusterIndices.constData()); - } - } - if (clusterWeightsSize > 0) { - vertBuffer->setSubData(clusterWeightsOffset, clusterWeightsSize, (const gpu::Byte*) hfmMesh.clusterWeights.constData()); - } - - - // Now we decide on how to interleave the attributes and provide the vertices among bufers: - // Aka the Vertex format and the vertexBufferStream - auto vertexFormat = std::make_shared<gpu::Stream::Format>(); - auto vertexBufferStream = std::make_shared<gpu::BufferStream>(); - - // Decision time: - // if blendshapes then keep position and normals/tangents as separated channel buffers from interleaved attributes - // else everything is interleaved in one buffer - - // Default case is no blend shapes - gpu::BufferPointer attribBuffer; - int totalAttribBufferSize = totalVertsSize; - gpu::uint8 posChannel = 0; - gpu::uint8 tangentChannel = posChannel; - gpu::uint8 attribChannel = posChannel; - bool interleavePositions = true; - bool interleaveNormalsTangents = true; - - // Define the vertex format, compute the offset for each attributes as we append them to the vertex format - gpu::Offset bufOffset = 0; - if (positionsSize) { - vertexFormat->setAttribute(gpu::Stream::POSITION, posChannel, positionElement, bufOffset); - bufOffset += positionElement.getSize(); - if (!interleavePositions) { - bufOffset = 0; - } - } - if (normalsSize) { - vertexFormat->setAttribute(gpu::Stream::NORMAL, tangentChannel, normalElement, bufOffset); - bufOffset += normalElement.getSize(); - vertexFormat->setAttribute(gpu::Stream::TANGENT, tangentChannel, normalElement, bufOffset); - bufOffset += normalElement.getSize(); - if (!interleaveNormalsTangents) { - bufOffset = 0; - } - } - - // Pack normal and Tangent with the rest of atributes if no blend shapes - if (colorsSize) { - vertexFormat->setAttribute(gpu::Stream::COLOR, attribChannel, colorElement, bufOffset); - bufOffset += colorElement.getSize(); - } - if (texCoordsSize) { - vertexFormat->setAttribute(gpu::Stream::TEXCOORD, attribChannel, texCoordsElement, bufOffset); - bufOffset += texCoordsElement.getSize(); - } - if (texCoords1Size) { - vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, attribChannel, texCoordsElement, bufOffset); - bufOffset += texCoordsElement.getSize(); - } else if (texCoordsSize) { - vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, attribChannel, texCoordsElement, bufOffset - texCoordsElement.getSize()); - } - if (clusterIndicesSize) { - vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, attribChannel, clusterIndiceElement, bufOffset); - bufOffset += clusterIndiceElement.getSize(); - } - if (clusterWeightsSize) { - vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, attribChannel, clusterWeightElement, bufOffset); - bufOffset += clusterWeightElement.getSize(); - } - - // Finally, allocate and fill the attribBuffer interleaving the attributes as needed: - { - auto vPositionOffset = 0; - auto vPositionSize = (interleavePositions ? positionsSize / numVerts : 0); - - auto vNormalsAndTangentsOffset = vPositionOffset + vPositionSize; - auto vNormalsAndTangentsSize = (interleaveNormalsTangents ? normalsAndTangentsSize / numVerts : 0); - - auto vColorOffset = vNormalsAndTangentsOffset + vNormalsAndTangentsSize; - auto vColorSize = colorsSize / numVerts; - - auto vTexcoord0Offset = vColorOffset + vColorSize; - auto vTexcoord0Size = texCoordsSize / numVerts; - - auto vTexcoord1Offset = vTexcoord0Offset + vTexcoord0Size; - auto vTexcoord1Size = texCoords1Size / numVerts; - - auto vClusterIndiceOffset = vTexcoord1Offset + vTexcoord1Size; - auto vClusterIndiceSize = clusterIndicesSize / numVerts; - - auto vClusterWeightOffset = vClusterIndiceOffset + vClusterIndiceSize; - auto vClusterWeightSize = clusterWeightsSize / numVerts; - - auto vStride = vClusterWeightOffset + vClusterWeightSize; - - std::vector<gpu::Byte> dest; - dest.resize(totalAttribBufferSize); - auto vDest = dest.data(); - - auto source = vertBuffer->getData(); - - for (int i = 0; i < numVerts; i++) { - - if (vPositionSize) memcpy(vDest + vPositionOffset, source + positionsOffset + i * vPositionSize, vPositionSize); - if (vNormalsAndTangentsSize) memcpy(vDest + vNormalsAndTangentsOffset, source + normalsAndTangentsOffset + i * vNormalsAndTangentsSize, vNormalsAndTangentsSize); - if (vColorSize) memcpy(vDest + vColorOffset, source + colorsOffset + i * vColorSize, vColorSize); - if (vTexcoord0Size) memcpy(vDest + vTexcoord0Offset, source + texCoordsOffset + i * vTexcoord0Size, vTexcoord0Size); - if (vTexcoord1Size) memcpy(vDest + vTexcoord1Offset, source + texCoords1Offset + i * vTexcoord1Size, vTexcoord1Size); - if (vClusterIndiceSize) memcpy(vDest + vClusterIndiceOffset, source + clusterIndicesOffset + i * vClusterIndiceSize, vClusterIndiceSize); - if (vClusterWeightSize) memcpy(vDest + vClusterWeightOffset, source + clusterWeightsOffset + i * vClusterWeightSize, vClusterWeightSize); - - vDest += vStride; - } - - auto attribBuffer = std::make_shared<gpu::Buffer>(); - attribBuffer->setData(totalAttribBufferSize, dest.data()); - vertexBufferStream->addBuffer(attribBuffer, 0, vStride); - } - - // Mesh vertex format and vertex stream is ready - mesh->setVertexFormatAndStream(vertexFormat, vertexBufferStream); - - // Index and Part Buffers - unsigned int totalIndices = 0; - foreach(const HFMMeshPart& part, extractedMesh.parts) { - totalIndices += (part.quadTrianglesIndices.size() + part.triangleIndices.size()); - } - - if (! totalIndices) { - qCDebug(modelformat) << "buildModelMesh failed -- no indices, url = " << url; - return; - } - - auto indexBuffer = std::make_shared<gpu::Buffer>(); - indexBuffer->resize(totalIndices * sizeof(int)); - - int indexNum = 0; - int offset = 0; - - std::vector< graphics::Mesh::Part > parts; - if (extractedMesh.parts.size() > 1) { - indexNum = 0; - } - foreach(const HFMMeshPart& part, extractedMesh.parts) { - graphics::Mesh::Part modelPart(indexNum, 0, 0, graphics::Mesh::TRIANGLES); - - if (part.quadTrianglesIndices.size()) { - indexBuffer->setSubData(offset, - part.quadTrianglesIndices.size() * sizeof(int), - (gpu::Byte*) part.quadTrianglesIndices.constData()); - offset += part.quadTrianglesIndices.size() * sizeof(int); - indexNum += part.quadTrianglesIndices.size(); - modelPart._numIndices += part.quadTrianglesIndices.size(); - } - - if (part.triangleIndices.size()) { - indexBuffer->setSubData(offset, - part.triangleIndices.size() * sizeof(int), - (gpu::Byte*) part.triangleIndices.constData()); - offset += part.triangleIndices.size() * sizeof(int); - indexNum += part.triangleIndices.size(); - modelPart._numIndices += part.triangleIndices.size(); - } - - parts.push_back(modelPart); - } - - gpu::BufferView indexBufferView(indexBuffer, gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::XYZ)); - mesh->setIndexBuffer(indexBufferView); - - if (parts.size()) { - auto pb = std::make_shared<gpu::Buffer>(); - pb->setData(parts.size() * sizeof(graphics::Mesh::Part), (const gpu::Byte*) parts.data()); - gpu::BufferView pbv(pb, gpu::Element(gpu::VEC4, gpu::UINT32, gpu::XYZW)); - mesh->setPartBuffer(pbv); - } else { - qCDebug(modelformat) << "buildModelMesh failed -- no parts, url = " << url; - return; - } - - // graphics::Box box = - mesh->evalPartBound(0); - - extractedMesh._mesh = mesh; -} diff --git a/libraries/fbx/src/FST.cpp b/libraries/fbx/src/FST.cpp new file mode 100644 index 0000000000..7828037c74 --- /dev/null +++ b/libraries/fbx/src/FST.cpp @@ -0,0 +1,190 @@ +// +// FST.cpp +// +// Created by Ryan Huffman on 12/11/15. +// 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 "FST.h" + +#include <QDir> +#include <QFileInfo> +#include <hfm/HFM.h> + +constexpr float DEFAULT_SCALE { 1.0f }; + +FST::FST(QString fstPath, QVariantHash data) : _fstPath(std::move(fstPath)) { + + auto setValueFromFSTData = [&data] (const QString& propertyID, auto &targetProperty) mutable { + if (data.contains(propertyID)) { + targetProperty = data[propertyID].toString(); + data.remove(propertyID); + } + }; + setValueFromFSTData(NAME_FIELD, _name); + setValueFromFSTData(FILENAME_FIELD, _modelPath); + setValueFromFSTData(MARKETPLACE_ID_FIELD, _marketplaceID); + + if (data.contains(SCRIPT_FIELD)) { + QVariantList scripts = data.values(SCRIPT_FIELD); + for (const auto& script : scripts) { + _scriptPaths.push_back(script.toString()); + } + data.remove(SCRIPT_FIELD); + } + + _other = data; +} + +FST* FST::createFSTFromModel(const QString& fstPath, const QString& modelFilePath, const hfm::Model& hfmModel) { + QVariantHash mapping; + + // mixamo files - in the event that a mixamo file was edited by some other tool, it's likely the applicationName will + // be rewritten, so we detect the existence of several different blendshapes which indicate we're likely a mixamo file + bool likelyMixamoFile = hfmModel.applicationName == "mixamo.com" || + (hfmModel.blendshapeChannelNames.contains("BrowsDown_Right") && + hfmModel.blendshapeChannelNames.contains("MouthOpen") && + hfmModel.blendshapeChannelNames.contains("Blink_Left") && + hfmModel.blendshapeChannelNames.contains("Blink_Right") && + hfmModel.blendshapeChannelNames.contains("Squint_Right")); + + mapping.insert(NAME_FIELD, QFileInfo(fstPath).baseName()); + mapping.insert(FILENAME_FIELD, QFileInfo(modelFilePath).fileName()); + mapping.insert(TEXDIR_FIELD, "textures"); + + // mixamo/autodesk defaults + mapping.insert(SCALE_FIELD, DEFAULT_SCALE); + QVariantHash joints = mapping.value(JOINT_FIELD).toHash(); + joints.insert("jointEyeLeft", hfmModel.jointIndices.contains("jointEyeLeft") ? "jointEyeLeft" : + (hfmModel.jointIndices.contains("EyeLeft") ? "EyeLeft" : "LeftEye")); + + joints.insert("jointEyeRight", hfmModel.jointIndices.contains("jointEyeRight") ? "jointEyeRight" : + hfmModel.jointIndices.contains("EyeRight") ? "EyeRight" : "RightEye"); + + joints.insert("jointNeck", hfmModel.jointIndices.contains("jointNeck") ? "jointNeck" : "Neck"); + joints.insert("jointRoot", "Hips"); + joints.insert("jointLean", "Spine"); + joints.insert("jointLeftHand", "LeftHand"); + joints.insert("jointRightHand", "RightHand"); + + const char* topName = likelyMixamoFile ? "HeadTop_End" : "HeadEnd"; + joints.insert("jointHead", hfmModel.jointIndices.contains(topName) ? topName : "Head"); + + mapping.insert(JOINT_FIELD, joints); + + QVariantHash jointIndices; + for (int i = 0; i < hfmModel.joints.size(); i++) { + jointIndices.insert(hfmModel.joints.at(i).name, QString::number(i)); + } + mapping.insert(JOINT_INDEX_FIELD, jointIndices); + + mapping.insertMulti(FREE_JOINT_FIELD, "LeftArm"); + mapping.insertMulti(FREE_JOINT_FIELD, "LeftForeArm"); + mapping.insertMulti(FREE_JOINT_FIELD, "RightArm"); + mapping.insertMulti(FREE_JOINT_FIELD, "RightForeArm"); + + + // If there are no blendshape mappings, and we detect that this is likely a mixamo file, + // then we can add the default mixamo to "faceshift" mappings + if (likelyMixamoFile) { + QVariantHash blendshapes; + blendshapes.insertMulti("BrowsD_L", QVariantList() << "BrowsDown_Left" << 1.0); + blendshapes.insertMulti("BrowsD_R", QVariantList() << "BrowsDown_Right" << 1.0); + blendshapes.insertMulti("BrowsU_C", QVariantList() << "BrowsUp_Left" << 1.0); + blendshapes.insertMulti("BrowsU_C", QVariantList() << "BrowsUp_Right" << 1.0); + blendshapes.insertMulti("BrowsU_L", QVariantList() << "BrowsUp_Left" << 1.0); + blendshapes.insertMulti("BrowsU_R", QVariantList() << "BrowsUp_Right" << 1.0); + blendshapes.insertMulti("ChinLowerRaise", QVariantList() << "Jaw_Up" << 1.0); + blendshapes.insertMulti("ChinUpperRaise", QVariantList() << "UpperLipUp_Left" << 0.5); + blendshapes.insertMulti("ChinUpperRaise", QVariantList() << "UpperLipUp_Right" << 0.5); + blendshapes.insertMulti("EyeBlink_L", QVariantList() << "Blink_Left" << 1.0); + blendshapes.insertMulti("EyeBlink_R", QVariantList() << "Blink_Right" << 1.0); + blendshapes.insertMulti("EyeOpen_L", QVariantList() << "EyesWide_Left" << 1.0); + blendshapes.insertMulti("EyeOpen_R", QVariantList() << "EyesWide_Right" << 1.0); + blendshapes.insertMulti("EyeSquint_L", QVariantList() << "Squint_Left" << 1.0); + blendshapes.insertMulti("EyeSquint_R", QVariantList() << "Squint_Right" << 1.0); + blendshapes.insertMulti("JawFwd", QVariantList() << "JawForeward" << 1.0); + blendshapes.insertMulti("JawLeft", QVariantList() << "JawRotateY_Left" << 0.5); + blendshapes.insertMulti("JawOpen", QVariantList() << "MouthOpen" << 0.7); + blendshapes.insertMulti("JawRight", QVariantList() << "Jaw_Right" << 1.0); + blendshapes.insertMulti("LipsFunnel", QVariantList() << "JawForeward" << 0.39); + blendshapes.insertMulti("LipsFunnel", QVariantList() << "Jaw_Down" << 0.36); + blendshapes.insertMulti("LipsFunnel", QVariantList() << "MouthNarrow_Left" << 1.0); + blendshapes.insertMulti("LipsFunnel", QVariantList() << "MouthNarrow_Right" << 1.0); + blendshapes.insertMulti("LipsFunnel", QVariantList() << "MouthWhistle_NarrowAdjust_Left" << 0.5); + blendshapes.insertMulti("LipsFunnel", QVariantList() << "MouthWhistle_NarrowAdjust_Right" << 0.5); + blendshapes.insertMulti("LipsFunnel", QVariantList() << "TongueUp" << 1.0); + blendshapes.insertMulti("LipsLowerClose", QVariantList() << "LowerLipIn" << 1.0); + blendshapes.insertMulti("LipsLowerDown", QVariantList() << "LowerLipDown_Left" << 0.7); + blendshapes.insertMulti("LipsLowerDown", QVariantList() << "LowerLipDown_Right" << 0.7); + blendshapes.insertMulti("LipsLowerOpen", QVariantList() << "LowerLipOut" << 1.0); + blendshapes.insertMulti("LipsPucker", QVariantList() << "MouthNarrow_Left" << 1.0); + blendshapes.insertMulti("LipsPucker", QVariantList() << "MouthNarrow_Right" << 1.0); + blendshapes.insertMulti("LipsUpperClose", QVariantList() << "UpperLipIn" << 1.0); + blendshapes.insertMulti("LipsUpperOpen", QVariantList() << "UpperLipOut" << 1.0); + blendshapes.insertMulti("LipsUpperUp", QVariantList() << "UpperLipUp_Left" << 0.7); + blendshapes.insertMulti("LipsUpperUp", QVariantList() << "UpperLipUp_Right" << 0.7); + blendshapes.insertMulti("MouthDimple_L", QVariantList() << "Smile_Left" << 0.25); + blendshapes.insertMulti("MouthDimple_R", QVariantList() << "Smile_Right" << 0.25); + blendshapes.insertMulti("MouthFrown_L", QVariantList() << "Frown_Left" << 1.0); + blendshapes.insertMulti("MouthFrown_R", QVariantList() << "Frown_Right" << 1.0); + blendshapes.insertMulti("MouthLeft", QVariantList() << "Midmouth_Left" << 1.0); + blendshapes.insertMulti("MouthRight", QVariantList() << "Midmouth_Right" << 1.0); + blendshapes.insertMulti("MouthSmile_L", QVariantList() << "Smile_Left" << 1.0); + blendshapes.insertMulti("MouthSmile_R", QVariantList() << "Smile_Right" << 1.0); + blendshapes.insertMulti("Puff", QVariantList() << "CheekPuff_Left" << 1.0); + blendshapes.insertMulti("Puff", QVariantList() << "CheekPuff_Right" << 1.0); + blendshapes.insertMulti("Sneer", QVariantList() << "NoseScrunch_Left" << 0.75); + blendshapes.insertMulti("Sneer", QVariantList() << "NoseScrunch_Right" << 0.75); + blendshapes.insertMulti("Sneer", QVariantList() << "Squint_Left" << 0.5); + blendshapes.insertMulti("Sneer", QVariantList() << "Squint_Right" << 0.5); + mapping.insert(BLENDSHAPE_FIELD, blendshapes); + } + return new FST(fstPath, mapping); +} + +QString FST::absoluteModelPath() const { + QFileInfo fileInfo{ _fstPath }; + QDir dir{ fileInfo.absoluteDir() }; + return dir.absoluteFilePath(_modelPath); +} + +void FST::setName(const QString& name) { + _name = name; + emit nameChanged(name); +} + +void FST::setModelPath(const QString& modelPath) { + _modelPath = modelPath; + emit modelPathChanged(modelPath); +} + +QVariantHash FST::getMapping() const { + QVariantHash mapping; + mapping.unite(_other); + mapping.insert(NAME_FIELD, _name); + mapping.insert(FILENAME_FIELD, _modelPath); + mapping.insert(MARKETPLACE_ID_FIELD, _marketplaceID); + for (const auto& scriptPath : _scriptPaths) { + mapping.insertMulti(SCRIPT_FIELD, scriptPath); + } + return mapping; +} + +bool FST::write() { + QFile fst(_fstPath); + if (!fst.open(QIODevice::WriteOnly)) { + return false; + } + fst.write(FSTReader::writeMapping(getMapping())); + return true; +} + +void FST::setMarketplaceID(QUuid marketplaceID) { + _marketplaceID = marketplaceID; + emit marketplaceIDChanged(); +} diff --git a/libraries/fbx/src/FST.h b/libraries/fbx/src/FST.h new file mode 100644 index 0000000000..0f4c1ecd3a --- /dev/null +++ b/libraries/fbx/src/FST.h @@ -0,0 +1,71 @@ +// +// FST.h +// +// Created by Ryan Huffman on 12/11/15. +// 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 +// + +#ifndef hifi_FST_h +#define hifi_FST_h + +#include <QVariantHash> +#include <QUuid> +#include "FSTReader.h" + +namespace hfm { + class Model; +}; + +class FST : public QObject { + Q_OBJECT + Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameChanged) + Q_PROPERTY(QString modelPath READ getModelPath WRITE setModelPath NOTIFY modelPathChanged) + Q_PROPERTY(QUuid marketplaceID READ getMarketplaceID) + Q_PROPERTY(bool hasMarketplaceID READ getHasMarketplaceID NOTIFY marketplaceIDChanged) +public: + FST(QString fstPath, QVariantHash data); + + static FST* createFSTFromModel(const QString& fstPath, const QString& modelFilePath, const hfm::Model& hfmModel); + + QString absoluteModelPath() const; + + QString getName() const { return _name; } + void setName(const QString& name); + + QString getModelPath() const { return _modelPath; } + void setModelPath(const QString& modelPath); + + Q_INVOKABLE bool getHasMarketplaceID() const { return !_marketplaceID.isNull(); } + QUuid getMarketplaceID() const { return _marketplaceID; } + void setMarketplaceID(QUuid marketplaceID); + + QStringList getScriptPaths() const { return _scriptPaths; } + void setScriptPaths(QStringList scriptPaths) { _scriptPaths = scriptPaths; } + + QString getPath() const { return _fstPath; } + + QVariantHash getMapping() const; + + bool write(); + +signals: + void nameChanged(const QString& name); + void modelPathChanged(const QString& modelPath); + void marketplaceIDChanged(); + +private: + QString _fstPath; + + QString _name{}; + QString _modelPath{}; + QUuid _marketplaceID{}; + + QStringList _scriptPaths{}; + + QVariantHash _other{}; +}; + +#endif // hifi_FST_h diff --git a/libraries/fbx/src/FSTReader.cpp b/libraries/fbx/src/FSTReader.cpp index 75596862d2..43806560dc 100644 --- a/libraries/fbx/src/FSTReader.cpp +++ b/libraries/fbx/src/FSTReader.cpp @@ -84,7 +84,7 @@ void FSTReader::writeVariant(QBuffer& buffer, QVariantHash::const_iterator& it) QByteArray FSTReader::writeMapping(const QVariantHash& mapping) { static const QStringList PREFERED_ORDER = QStringList() << NAME_FIELD << TYPE_FIELD << SCALE_FIELD << FILENAME_FIELD - << TEXDIR_FIELD << SCRIPT_FIELD << JOINT_FIELD << FREE_JOINT_FIELD + << MARKETPLACE_ID_FIELD << TEXDIR_FIELD << SCRIPT_FIELD << JOINT_FIELD << FREE_JOINT_FIELD << BLENDSHAPE_FIELD << JOINT_INDEX_FIELD; QBuffer buffer; buffer.open(QIODevice::WriteOnly); diff --git a/libraries/fbx/src/FSTReader.h b/libraries/fbx/src/FSTReader.h index 00244877b3..993d7c3148 100644 --- a/libraries/fbx/src/FSTReader.h +++ b/libraries/fbx/src/FSTReader.h @@ -18,6 +18,7 @@ static const QString NAME_FIELD = "name"; static const QString TYPE_FIELD = "type"; static const QString FILENAME_FIELD = "filename"; +static const QString MARKETPLACE_ID_FIELD = "marketplaceID"; static const QString TEXDIR_FIELD = "texdir"; static const QString LOD_FIELD = "lod"; static const QString JOINT_INDEX_FIELD = "jointIndex"; diff --git a/libraries/fbx/src/GLTFSerializer.cpp b/libraries/fbx/src/GLTFSerializer.cpp index e254a91eb0..96c236f703 100644 --- a/libraries/fbx/src/GLTFSerializer.cpp +++ b/libraries/fbx/src/GLTFSerializer.cpp @@ -533,14 +533,13 @@ bool GLTFSerializer::parseGLTF(const QByteArray& data) { QJsonDocument d = QJsonDocument::fromJson(data); QJsonObject jsFile = d.object(); - - bool isvalid = setAsset(jsFile); - if (isvalid) { + bool success = setAsset(jsFile); + if (success) { QJsonArray accessors; if (getObjectArrayVal(jsFile, "accessors", accessors, _file.defined)) { foreach(const QJsonValue & accVal, accessors) { if (accVal.isObject()) { - addAccessor(accVal.toObject()); + success = success && addAccessor(accVal.toObject()); } } } @@ -549,7 +548,7 @@ bool GLTFSerializer::parseGLTF(const QByteArray& data) { if (getObjectArrayVal(jsFile, "animations", animations, _file.defined)) { foreach(const QJsonValue & animVal, accessors) { if (animVal.isObject()) { - addAnimation(animVal.toObject()); + success = success && addAnimation(animVal.toObject()); } } } @@ -558,7 +557,7 @@ bool GLTFSerializer::parseGLTF(const QByteArray& data) { if (getObjectArrayVal(jsFile, "bufferViews", bufferViews, _file.defined)) { foreach(const QJsonValue & bufviewVal, bufferViews) { if (bufviewVal.isObject()) { - addBufferView(bufviewVal.toObject()); + success = success && addBufferView(bufviewVal.toObject()); } } } @@ -567,7 +566,7 @@ bool GLTFSerializer::parseGLTF(const QByteArray& data) { if (getObjectArrayVal(jsFile, "buffers", buffers, _file.defined)) { foreach(const QJsonValue & bufVal, buffers) { if (bufVal.isObject()) { - addBuffer(bufVal.toObject()); + success = success && addBuffer(bufVal.toObject()); } } } @@ -576,7 +575,7 @@ bool GLTFSerializer::parseGLTF(const QByteArray& data) { if (getObjectArrayVal(jsFile, "cameras", cameras, _file.defined)) { foreach(const QJsonValue & camVal, cameras) { if (camVal.isObject()) { - addCamera(camVal.toObject()); + success = success && addCamera(camVal.toObject()); } } } @@ -585,7 +584,7 @@ bool GLTFSerializer::parseGLTF(const QByteArray& data) { if (getObjectArrayVal(jsFile, "images", images, _file.defined)) { foreach(const QJsonValue & imgVal, images) { if (imgVal.isObject()) { - addImage(imgVal.toObject()); + success = success && addImage(imgVal.toObject()); } } } @@ -594,7 +593,7 @@ bool GLTFSerializer::parseGLTF(const QByteArray& data) { if (getObjectArrayVal(jsFile, "materials", materials, _file.defined)) { foreach(const QJsonValue & matVal, materials) { if (matVal.isObject()) { - addMaterial(matVal.toObject()); + success = success && addMaterial(matVal.toObject()); } } } @@ -603,7 +602,7 @@ bool GLTFSerializer::parseGLTF(const QByteArray& data) { if (getObjectArrayVal(jsFile, "meshes", meshes, _file.defined)) { foreach(const QJsonValue & meshVal, meshes) { if (meshVal.isObject()) { - addMesh(meshVal.toObject()); + success = success && addMesh(meshVal.toObject()); } } } @@ -612,7 +611,7 @@ bool GLTFSerializer::parseGLTF(const QByteArray& data) { if (getObjectArrayVal(jsFile, "nodes", nodes, _file.defined)) { foreach(const QJsonValue & nodeVal, nodes) { if (nodeVal.isObject()) { - addNode(nodeVal.toObject()); + success = success && addNode(nodeVal.toObject()); } } } @@ -621,7 +620,7 @@ bool GLTFSerializer::parseGLTF(const QByteArray& data) { if (getObjectArrayVal(jsFile, "samplers", samplers, _file.defined)) { foreach(const QJsonValue & samVal, samplers) { if (samVal.isObject()) { - addSampler(samVal.toObject()); + success = success && addSampler(samVal.toObject()); } } } @@ -630,7 +629,7 @@ bool GLTFSerializer::parseGLTF(const QByteArray& data) { if (getObjectArrayVal(jsFile, "scenes", scenes, _file.defined)) { foreach(const QJsonValue & sceneVal, scenes) { if (sceneVal.isObject()) { - addScene(sceneVal.toObject()); + success = success && addScene(sceneVal.toObject()); } } } @@ -639,7 +638,7 @@ bool GLTFSerializer::parseGLTF(const QByteArray& data) { if (getObjectArrayVal(jsFile, "skins", skins, _file.defined)) { foreach(const QJsonValue & skinVal, skins) { if (skinVal.isObject()) { - addSkin(skinVal.toObject()); + success = success && addSkin(skinVal.toObject()); } } } @@ -648,15 +647,12 @@ bool GLTFSerializer::parseGLTF(const QByteArray& data) { if (getObjectArrayVal(jsFile, "textures", textures, _file.defined)) { foreach(const QJsonValue & texVal, textures) { if (texVal.isObject()) { - addTexture(texVal.toObject()); + success = success && addTexture(texVal.toObject()); } } } - } else { - qCDebug(modelformat) << "Error parsing GLTF file."; - return false; - } - return true; + } + return success; } glm::mat4 GLTFSerializer::getModelTransform(const GLTFNode& node) { @@ -894,7 +890,6 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const QUrl& url) { } mesh.meshIndex = hfmModel.meshes.size(); - FBXSerializer::buildModelMesh(mesh, url.toString()); } } @@ -927,16 +922,20 @@ HFMModel::Pointer GLTFSerializer::read(const QByteArray& data, const QVariantHas _url = QUrl(QFileInfo(localFileName).absoluteFilePath()); } - parseGLTF(data); - //_file.dump(); - auto hfmModelPtr = std::make_shared<HFMModel>(); - HFMModel& hfmModel = *hfmModelPtr; + if (parseGLTF(data)) { + //_file.dump(); + auto hfmModelPtr = std::make_shared<HFMModel>(); + HFMModel& hfmModel = *hfmModelPtr; - buildGeometry(hfmModel, _url); - - //hfmDebugDump(data); - return hfmModelPtr; - + buildGeometry(hfmModel, _url); + + //hfmDebugDump(data); + return hfmModelPtr; + } else { + qCDebug(modelformat) << "Error parsing GLTF file."; + } + + return nullptr; } bool GLTFSerializer::readBinary(const QString& url, QByteArray& outdata) { @@ -1188,19 +1187,6 @@ void GLTFSerializer::hfmDebugDump(const HFMModel& hfmModel) { qCDebug(modelformat) << " hasSkeletonJoints =" << hfmModel.hasSkeletonJoints; qCDebug(modelformat) << " offset =" << hfmModel.offset; - qCDebug(modelformat) << " leftEyeJointIndex =" << hfmModel.leftEyeJointIndex; - qCDebug(modelformat) << " rightEyeJointIndex =" << hfmModel.rightEyeJointIndex; - qCDebug(modelformat) << " neckJointIndex =" << hfmModel.neckJointIndex; - qCDebug(modelformat) << " rootJointIndex =" << hfmModel.rootJointIndex; - qCDebug(modelformat) << " leanJointIndex =" << hfmModel.leanJointIndex; - qCDebug(modelformat) << " headJointIndex =" << hfmModel.headJointIndex; - qCDebug(modelformat) << " leftHandJointIndex" << hfmModel.leftHandJointIndex; - qCDebug(modelformat) << " rightHandJointIndex" << hfmModel.rightHandJointIndex; - qCDebug(modelformat) << " leftToeJointIndex" << hfmModel.leftToeJointIndex; - qCDebug(modelformat) << " rightToeJointIndex" << hfmModel.rightToeJointIndex; - qCDebug(modelformat) << " leftEyeSize = " << hfmModel.leftEyeSize; - qCDebug(modelformat) << " rightEyeSize = " << hfmModel.rightEyeSize; - qCDebug(modelformat) << " palmDirection = " << hfmModel.palmDirection; qCDebug(modelformat) << " neckPivot = " << hfmModel.neckPivot; diff --git a/libraries/fbx/src/OBJSerializer.cpp b/libraries/fbx/src/OBJSerializer.cpp index 9c92466565..9d4b1f16a1 100644 --- a/libraries/fbx/src/OBJSerializer.cpp +++ b/libraries/fbx/src/OBJSerializer.cpp @@ -831,9 +831,6 @@ HFMModel::Pointer OBJSerializer::read(const QByteArray& data, const QVariantHash hfmModel.meshExtents.addPoint(vertex); } - // Build the single mesh. - FBXSerializer::buildModelMesh(mesh, _url.toString()); - // hfmDebugDump(hfmModel); } catch(const std::exception& e) { qCDebug(modelformat) << "OBJSerializer fail: " << e.what(); diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendTransform.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendTransform.cpp index 2c2a4e254c..555a0a1e41 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendTransform.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendTransform.cpp @@ -29,9 +29,9 @@ void GLBackend::do_setProjectionTransform(const Batch& batch, size_t paramOffset } void GLBackend::do_setProjectionJitter(const Batch& batch, size_t paramOffset) { - _transform._projectionJitter.x = batch._params[paramOffset]._float; - _transform._projectionJitter.y = batch._params[paramOffset+1]._float; - _transform._invalidProj = true; + _transform._projectionJitter.x = batch._params[paramOffset]._float; + _transform._projectionJitter.y = batch._params[paramOffset+1]._float; + _transform._invalidProj = true; } void GLBackend::do_setViewportTransform(const Batch& batch, size_t paramOffset) { diff --git a/libraries/gpu/src/gpu/Pipeline.h b/libraries/gpu/src/gpu/Pipeline.h index 28f7fe106e..b46226182a 100755 --- a/libraries/gpu/src/gpu/Pipeline.h +++ b/libraries/gpu/src/gpu/Pipeline.h @@ -38,8 +38,6 @@ protected: StatePointer _state; Pipeline(); - Pipeline(const Pipeline& pipeline); // deep copy of the sysmem shader - Pipeline& operator=(const Pipeline& pipeline); // deep copy of the sysmem texture }; typedef Pipeline::Pointer PipelinePointer; diff --git a/libraries/hfm/src/hfm/HFM.cpp b/libraries/hfm/src/hfm/HFM.cpp index 8f01956f17..b9e630456d 100644 --- a/libraries/hfm/src/hfm/HFM.cpp +++ b/libraries/hfm/src/hfm/HFM.cpp @@ -189,20 +189,17 @@ bool HFMModel::hasBlendedMeshes() const { } Extents HFMModel::getUnscaledMeshExtents() const { - const Extents& extents = meshExtents; - // even though our caller asked for "unscaled" we need to include any fst scaling, translation, and rotation, which // is captured in the offset matrix - glm::vec3 minimum = glm::vec3(offset * glm::vec4(extents.minimum, 1.0f)); - glm::vec3 maximum = glm::vec3(offset * glm::vec4(extents.maximum, 1.0f)); + glm::vec3 minimum = glm::vec3(offset * glm::vec4(meshExtents.minimum, 1.0f)); + glm::vec3 maximum = glm::vec3(offset * glm::vec4(meshExtents.maximum, 1.0f)); Extents scaledExtents = { minimum, maximum }; - return scaledExtents; } // TODO: Move to graphics::Mesh when Sam's ready bool HFMModel::convexHullContains(const glm::vec3& point) const { - if (!getUnscaledMeshExtents().containsPoint(point)) { + if (!meshExtents.containsPoint(point)) { return false; } diff --git a/libraries/hfm/src/hfm/HFM.h b/libraries/hfm/src/hfm/HFM.h index de58d864b3..78f608d72e 100644 --- a/libraries/hfm/src/hfm/HFM.h +++ b/libraries/hfm/src/hfm/HFM.h @@ -25,6 +25,30 @@ #include <graphics/Geometry.h> #include <graphics/Material.h> +#if defined(Q_OS_ANDROID) +#define HFM_PACK_NORMALS 0 +#else +#define HFM_PACK_NORMALS 1 +#endif + +#if HFM_PACK_NORMALS +using NormalType = glm::uint32; +#define HFM_NORMAL_ELEMENT gpu::Element::VEC4F_NORMALIZED_XYZ10W2 +#else +using NormalType = glm::vec3; +#define HFM_NORMAL_ELEMENT gpu::Element::VEC3F_XYZ +#endif + +#define HFM_PACK_COLORS 1 + +#if HFM_PACK_COLORS +using ColorType = glm::uint32; +#define HFM_COLOR_ELEMENT gpu::Element::COLOR_RGBA_32 +#else +using ColorType = glm::vec3; +#define HFM_COLOR_ELEMENT gpu::Element::VEC3F_XYZ +#endif + const int MAX_NUM_PIXELS_FOR_FBX_TEXTURE = 2048 * 2048; // High Fidelity Model namespace @@ -270,22 +294,6 @@ public: glm::mat4 offset; // This includes offset, rotation, and scale as specified by the FST file - int leftEyeJointIndex = -1; - int rightEyeJointIndex = -1; - int neckJointIndex = -1; - int rootJointIndex = -1; - int leanJointIndex = -1; - int headJointIndex = -1; - int leftHandJointIndex = -1; - int rightHandJointIndex = -1; - int leftToeJointIndex = -1; - int rightToeJointIndex = -1; - - float leftEyeSize = 0.0f; // Maximum mesh extents dimension - float rightEyeSize = 0.0f; - - QVector<int> humanIKJointIndices; - glm::vec3 palmDirection; glm::vec3 neckPivot; @@ -302,6 +310,7 @@ public: /// Returns the unscaled extents of the model's mesh Extents getUnscaledMeshExtents() const; + const Extents& getMeshExtents() const { return meshExtents; } bool convexHullContains(const glm::vec3& point) const; diff --git a/libraries/hfm/src/hfm/HFMSerializer.h b/libraries/hfm/src/hfm/HFMSerializer.h index 868ec3dd45..d0be588d60 100644 --- a/libraries/hfm/src/hfm/HFMSerializer.h +++ b/libraries/hfm/src/hfm/HFMSerializer.h @@ -23,6 +23,7 @@ class Serializer { public: class Factory { public: + virtual ~Factory() {} virtual std::shared_ptr<Serializer> get() = 0; }; diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp index ddd51e9b9b..3383d71a52 100755 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.cpp @@ -21,14 +21,27 @@ const char* KeyboardMouseDevice::NAME = "Keyboard/Mouse"; bool KeyboardMouseDevice::_enableTouch = true; +void KeyboardMouseDevice::updateDeltaAxisValue(int channel, float value) { + // Associate timestamps with non-zero delta values so that consecutive identical values can be output. + _inputDevice->_axisStateMap[channel] = { value, value != 0.0f ? usecTimestampNow() : 0 }; +} + void KeyboardMouseDevice::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { auto userInputMapper = DependencyManager::get<controller::UserInputMapper>(); userInputMapper->withLock([&, this]() { _inputDevice->update(deltaTime, inputCalibrationData); eraseMouseClicked(); - _inputDevice->_axisStateMap[MOUSE_AXIS_X] = _lastCursor.x(); - _inputDevice->_axisStateMap[MOUSE_AXIS_Y] = _lastCursor.y(); + _inputDevice->_axisStateMap[MOUSE_AXIS_X].value = _lastCursor.x(); + _inputDevice->_axisStateMap[MOUSE_AXIS_Y].value = _lastCursor.y(); + + QPoint currentMove = _lastCursor - _previousCursor; + updateDeltaAxisValue(MOUSE_AXIS_X_POS, currentMove.x() > 0 ? currentMove.x() : 0.0f); + updateDeltaAxisValue(MOUSE_AXIS_X_NEG, currentMove.x() < 0 ? -currentMove.x() : 0.0f); + // Y mouse is inverted positive is pointing up the screen + updateDeltaAxisValue(MOUSE_AXIS_Y_POS, currentMove.y() < 0 ? -currentMove.y() : 0.0f); + updateDeltaAxisValue(MOUSE_AXIS_Y_NEG, currentMove.y() > 0 ? currentMove.y() : 0.0f); + _previousCursor = _lastCursor; }); // For touch event, we need to check that the last event is not too long ago @@ -73,7 +86,6 @@ void KeyboardMouseDevice::mousePressEvent(QMouseEvent* event) { } _lastCursor = event->pos(); _mousePressTime = usecTimestampNow(); - _mouseMoved = false; _mousePressPos = event->pos(); _clickDeadspotActive = true; @@ -102,21 +114,12 @@ void KeyboardMouseDevice::eraseMouseClicked() { void KeyboardMouseDevice::mouseMoveEvent(QMouseEvent* event) { QPoint currentPos = event->pos(); - QPoint currentMove = currentPos - _lastCursor; - - _inputDevice->_axisStateMap[MOUSE_AXIS_X_POS] = (currentMove.x() > 0 ? currentMove.x() : 0.0f); - _inputDevice->_axisStateMap[MOUSE_AXIS_X_NEG] = (currentMove.x() < 0 ? -currentMove.x() : 0.0f); - // Y mouse is inverted positive is pointing up the screen - _inputDevice->_axisStateMap[MOUSE_AXIS_Y_POS] = (currentMove.y() < 0 ? -currentMove.y() : 0.0f); - _inputDevice->_axisStateMap[MOUSE_AXIS_Y_NEG] = (currentMove.y() > 0 ? currentMove.y() : 0.0f); // FIXME - this has the characteristic that it will show large jumps when you move the cursor // outside of the application window, because we don't get MouseEvents when the cursor is outside // of the application window. _lastCursor = currentPos; - _mouseMoved = true; - const int CLICK_EVENT_DEADSPOT = 6; // pixels if (_clickDeadspotActive && (_mousePressPos - currentPos).manhattanLength() > CLICK_EVENT_DEADSPOT) { _clickDeadspotActive = false; @@ -125,11 +128,10 @@ void KeyboardMouseDevice::mouseMoveEvent(QMouseEvent* event) { void KeyboardMouseDevice::wheelEvent(QWheelEvent* event) { auto currentMove = event->angleDelta() / 120.0f; - - _inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_X_POS).getChannel()] = (currentMove.x() > 0 ? currentMove.x() : 0.0f); - _inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_X_NEG).getChannel()] = (currentMove.x() < 0 ? -currentMove.x() : 0.0f); - _inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_Y_POS).getChannel()] = (currentMove.y() > 0 ? currentMove.y() : 0.0f); - _inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_Y_NEG).getChannel()] = (currentMove.y() < 0 ? -currentMove.y() : 0.0f); + _inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_X_POS).getChannel()].value = currentMove.x() > 0 ? currentMove.x() : 0; + _inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_X_NEG).getChannel()].value = currentMove.x() < 0 ? -currentMove.x() : 0; + _inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_Y_POS).getChannel()].value = currentMove.y() > 0 ? currentMove.y() : 0; + _inputDevice->_axisStateMap[_inputDevice->makeInput(MOUSE_AXIS_WHEEL_Y_NEG).getChannel()].value = currentMove.y() < 0 ? -currentMove.y() : 0; } glm::vec2 evalAverageTouchPoints(const QList<QTouchEvent::TouchPoint>& points) { @@ -168,12 +170,11 @@ void KeyboardMouseDevice::touchUpdateEvent(const QTouchEvent* event) { _isTouching = event->touchPointStates().testFlag(Qt::TouchPointPressed); } else { auto currentMove = currentPos - _lastTouch; - - _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_POS).getChannel()] = (currentMove.x > 0 ? currentMove.x : 0.0f); - _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_NEG).getChannel()] = (currentMove.x < 0 ? -currentMove.x : 0.0f); + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_POS).getChannel()].value = (currentMove.x > 0 ? currentMove.x : 0.0f); + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_NEG).getChannel()].value = (currentMove.x < 0 ? -currentMove.x : 0.0f); // Y mouse is inverted positive is pointing up the screen - _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_POS).getChannel()] = (currentMove.y < 0 ? -currentMove.y : 0.0f); - _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_NEG).getChannel()] = (currentMove.y > 0 ? currentMove.y : 0.0f); + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_POS).getChannel()].value = (currentMove.y < 0 ? -currentMove.y : 0.0f); + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_NEG).getChannel()].value = (currentMove.y > 0 ? currentMove.y : 0.0f); } _lastTouch = currentPos; @@ -247,16 +248,23 @@ controller::Input KeyboardMouseDevice::InputDevice::makeInput(KeyboardMouseDevic * </td></tr> * <tr><td><code>PgDown</code></td><td>number</td><td>number</td><td>The page down key on the keyboard or keypad is pressed. * </td></tr> - * <tr><td><code>LeftMouseButton</code></td><td>number</td><td>number</td><td>The left mouse button pressed.</td></tr> - * <tr><td><code>MiddleMouseButton</code></td><td>number</td><td>number</td><td>The middle mouse button pressed.</td></tr> - * <tr><td><code>RightMouseButton</code></td><td>number</td><td>number</td><td>The right mouse button pressed.</td></tr> - * <tr><td><code>LeftMouseClicked</code></td><td>number</td><td>number</td><td>The left mouse button clicked.</td></tr> - * <tr><td><code>MiddleMouseClicked</code></td><td>number</td><td>number</td><td>The middle mouse button clicked.</td></tr> - * <tr><td><code>RightMouseClicked</code></td><td>number</td><td>number</td><td>The right mouse button clicked.</td></tr> - * <tr><td><code>MouseMoveRight</code></td><td>number</td><td>number</td><td>The mouse moved right.</td></tr> - * <tr><td><code>MouseMoveLeft</code></td><td>number</td><td>number</td><td>The mouse moved left.</td></tr> - * <tr><td><code>MouseMoveUp</code></td><td>number</td><td>number</td><td>The mouse moved up.</td></tr> - * <tr><td><code>MouseMoveDown</code></td><td>number</td><td>number</td><td>The mouse moved down.</td></tr> + * <tr><td><code>LeftMouseButton</code></td><td>number</td><td>number</td><td>The left mouse button is pressed.</td></tr> + * <tr><td><code>MiddleMouseButton</code></td><td>number</td><td>number</td><td>The middle mouse button is pressed. + * </td></tr> + * <tr><td><code>RightMouseButton</code></td><td>number</td><td>number</td><td>The right mouse button is pressed.</td></tr> + * <tr><td><code>LeftMouseClicked</code></td><td>number</td><td>number</td><td>The left mouse button was clicked.</td></tr> + * <tr><td><code>MiddleMouseClicked</code></td><td>number</td><td>number</td><td>The middle mouse button was clicked. + * </td></tr> + * <tr><td><code>RightMouseClicked</code></td><td>number</td><td>number</td><td>The right mouse button was clicked. + * </td></tr> + * <tr><td><code>MouseMoveRight</code></td><td>number</td><td>number</td><td>The mouse moved right. The data value is how + * far it moved.</td></tr> + * <tr><td><code>MouseMoveLeft</code></td><td>number</td><td>number</td><td>The mouse moved left. The data value is how far + * it moved.</td></tr> + * <tr><td><code>MouseMoveUp</code></td><td>number</td><td>number</td><td>The mouse moved up. The data value is how far it + * moved.</td></tr> + * <tr><td><code>MouseMoveDown</code></td><td>number</td><td>number</td><td>The mouse moved down. The data value is how far + * it moved.</td></tr> * <tr><td><code>MouseX</code></td><td>number</td><td>number</td><td>The mouse x-coordinate changed. The data value is its * new x-coordinate value.</td></tr> * <tr><td><code>MouseY</code></td><td>number</td><td>number</td><td>The mouse y-coordinate changed. The data value is its diff --git a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h index b94acb8b71..f6921c8e23 100644 --- a/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h +++ b/libraries/input-plugins/src/input-plugins/KeyboardMouseDevice.h @@ -118,9 +118,9 @@ public: protected: QPoint _lastCursor; + QPoint _previousCursor; QPoint _mousePressPos; quint64 _mousePressTime; - bool _mouseMoved; bool _clickDeadspotActive; glm::vec2 _lastTouch; std::shared_ptr<InputDevice> _inputDevice { std::make_shared<InputDevice>() }; @@ -130,6 +130,9 @@ protected: std::chrono::high_resolution_clock::time_point _lastTouchTime; static bool _enableTouch; + +private: + void updateDeltaAxisValue(int channel, float value); }; #endif // hifi_KeyboardMouseDevice_h diff --git a/libraries/input-plugins/src/input-plugins/TouchscreenDevice.cpp b/libraries/input-plugins/src/input-plugins/TouchscreenDevice.cpp index 20952df4d7..807d7f0ef9 100644 --- a/libraries/input-plugins/src/input-plugins/TouchscreenDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/TouchscreenDevice.cpp @@ -42,24 +42,24 @@ void TouchscreenDevice::pluginUpdate(float deltaTime, const controller::InputCal if (_touchPointCount == 1) { if (_firstTouchVec.x < _currentTouchVec.x) { distanceScaleX = (_currentTouchVec.x - _firstTouchVec.x) / _screenDPIScale.x; - _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_POS).getChannel()] = distanceScaleX; + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_POS).getChannel()].value = distanceScaleX; } else if (_firstTouchVec.x > _currentTouchVec.x) { distanceScaleX = (_firstTouchVec.x - _currentTouchVec.x) / _screenDPIScale.x; - _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_NEG).getChannel()] = distanceScaleX; + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_X_NEG).getChannel()].value = distanceScaleX; } // Y axis is inverted, positive is pointing up the screen if (_firstTouchVec.y > _currentTouchVec.y) { distanceScaleY = (_firstTouchVec.y - _currentTouchVec.y) / _screenDPIScale.y; - _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_POS).getChannel()] = distanceScaleY; + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_POS).getChannel()].value = distanceScaleY; } else if (_firstTouchVec.y < _currentTouchVec.y) { distanceScaleY = (_currentTouchVec.y - _firstTouchVec.y) / _screenDPIScale.y; - _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_NEG).getChannel()] = distanceScaleY; + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_AXIS_Y_NEG).getChannel()].value = distanceScaleY; } } else if (_touchPointCount == 2) { if (_pinchScale > _lastPinchScale && _pinchScale != 0) { - _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_GESTURE_PINCH_POS).getChannel()] = 1.0f; + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_GESTURE_PINCH_POS).getChannel()].value = 1.0f; } else if (_pinchScale != 0) { - _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_GESTURE_PINCH_NEG).getChannel()] = 1.0f; + _inputDevice->_axisStateMap[_inputDevice->makeInput(TOUCH_GESTURE_PINCH_NEG).getChannel()].value = 1.0; } _lastPinchScale = _pinchScale; } diff --git a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp index 32194e1b84..247f484c95 100644 --- a/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp +++ b/libraries/input-plugins/src/input-plugins/TouchscreenVirtualPadDevice.cpp @@ -135,8 +135,8 @@ glm::vec2 TouchscreenVirtualPadDevice::clippedPointInCircle(float radius, glm::v void TouchscreenVirtualPadDevice::processInputDeviceForMove(VirtualPad::Manager& virtualPadManager) { vec2 clippedPoint = clippedPointInCircle(_fixedRadiusForCalc, _moveRefTouchPoint, _moveCurrentTouchPoint); - _inputDevice->_axisStateMap[controller::LX] = (clippedPoint.x - _moveRefTouchPoint.x) / _fixedRadiusForCalc; - _inputDevice->_axisStateMap[controller::LY] = (clippedPoint.y - _moveRefTouchPoint.y) / _fixedRadiusForCalc; + _inputDevice->_axisStateMap[controller::LX].value = (clippedPoint.x - _moveRefTouchPoint.x) / _fixedRadiusForCalc; + _inputDevice->_axisStateMap[controller::LY].value = (clippedPoint.y - _moveRefTouchPoint.y) / _fixedRadiusForCalc; virtualPadManager.getLeftVirtualPad()->setFirstTouch(_moveRefTouchPoint); virtualPadManager.getLeftVirtualPad()->setCurrentTouch(clippedPoint); @@ -148,8 +148,10 @@ void TouchscreenVirtualPadDevice::processInputDeviceForView() { // We use average across how many times we've got touchUpdate events. // Using the average instead of the full deltaX and deltaY, makes deltaTime in MyAvatar dont't accelerate rotation when there is a low touchUpdate rate (heavier domains). // (Because it multiplies this input value by deltaTime (with a coefficient)). - _inputDevice->_axisStateMap[controller::RX] = _viewTouchUpdateCount == 0 ? 0 : (_viewCurrentTouchPoint.x - _viewRefTouchPoint.x) / _viewTouchUpdateCount; - _inputDevice->_axisStateMap[controller::RY] = _viewTouchUpdateCount == 0 ? 0 : (_viewCurrentTouchPoint.y - _viewRefTouchPoint.y) / _viewTouchUpdateCount; + _inputDevice->_axisStateMap[controller::RX].value = + _viewTouchUpdateCount == 0 ? 0 : (_viewCurrentTouchPoint.x - _viewRefTouchPoint.x) / _viewTouchUpdateCount; + _inputDevice->_axisStateMap[controller::RY].value = + _viewTouchUpdateCount == 0 ? 0 : (_viewCurrentTouchPoint.y - _viewRefTouchPoint.y) / _viewTouchUpdateCount; // after use, save last touch point as ref _viewRefTouchPoint = _viewCurrentTouchPoint; @@ -442,8 +444,8 @@ void TouchscreenVirtualPadDevice::moveTouchUpdate(glm::vec2 touchPoint) { void TouchscreenVirtualPadDevice::moveTouchEnd() { if (_moveHasValidTouch) { // do stuff once _moveHasValidTouch = false; - _inputDevice->_axisStateMap[controller::LX] = 0; - _inputDevice->_axisStateMap[controller::LY] = 0; + _inputDevice->_axisStateMap[controller::LX].value = 0; + _inputDevice->_axisStateMap[controller::LY].value = 0; } } @@ -465,8 +467,8 @@ void TouchscreenVirtualPadDevice::viewTouchUpdate(glm::vec2 touchPoint) { void TouchscreenVirtualPadDevice::viewTouchEnd() { if (_viewHasValidTouch) { // do stuff once _viewHasValidTouch = false; - _inputDevice->_axisStateMap[controller::RX] = 0; - _inputDevice->_axisStateMap[controller::RY] = 0; + _inputDevice->_axisStateMap[controller::RX].value = 0; + _inputDevice->_axisStateMap[controller::RY].value = 0; } } diff --git a/libraries/model-baker/CMakeLists.txt b/libraries/model-baker/CMakeLists.txt index 58d9d1e60e..50b113976f 100644 --- a/libraries/model-baker/CMakeLists.txt +++ b/libraries/model-baker/CMakeLists.txt @@ -1,8 +1,6 @@ set(TARGET_NAME model-baker) setup_hifi_library() -link_hifi_libraries(shared task) +link_hifi_libraries(shared task gpu graphics) -include_hifi_library_headers(gpu) -include_hifi_library_headers(graphics) include_hifi_library_headers(hfm) diff --git a/libraries/model-baker/src/model-baker/Baker.cpp b/libraries/model-baker/src/model-baker/Baker.cpp index 14ec62413e..bf2b21993c 100644 --- a/libraries/model-baker/src/model-baker/Baker.cpp +++ b/libraries/model-baker/src/model-baker/Baker.cpp @@ -11,17 +11,89 @@ #include "Baker.h" +#include <shared/HifiTypes.h> + +#include "BakerTypes.h" +#include "BuildGraphicsMeshTask.h" + namespace baker { + class GetModelPartsTask { + public: + using Input = hfm::Model::Pointer; + using Output = VaryingSet3<std::vector<hfm::Mesh>, hifi::URL, MeshIndicesToModelNames>; + using JobModel = Job::ModelIO<GetModelPartsTask, Input, Output>; + + void run(const BakeContextPointer& context, const Input& input, Output& output) { + auto& hfmModelIn = input; + output.edit0() = hfmModelIn->meshes.toStdVector(); + output.edit1() = hfmModelIn->originalURL; + output.edit2() = hfmModelIn->meshIndicesToModelNames; + } + }; + + class BuildMeshesTask { + public: + using Input = VaryingSet4<std::vector<hfm::Mesh>, std::vector<graphics::MeshPointer>, TangentsPerMesh, BlendshapesPerMesh>; + using Output = std::vector<hfm::Mesh>; + using JobModel = Job::ModelIO<BuildMeshesTask, Input, Output>; + + void run(const BakeContextPointer& context, const Input& input, Output& output) { + auto& meshesIn = input.get0(); + int numMeshes = (int)meshesIn.size(); + auto& graphicsMeshesIn = input.get1(); + auto& tangentsPerMeshIn = input.get2(); + auto& blendshapesPerMeshIn = input.get3(); + + auto meshesOut = meshesIn; + for (int i = 0; i < numMeshes; i++) { + auto& meshOut = meshesOut[i]; + meshOut._mesh = graphicsMeshesIn[i]; + meshOut.tangents = QVector<glm::vec3>::fromStdVector(tangentsPerMeshIn[i]); + meshOut.blendshapes = QVector<hfm::Blendshape>::fromStdVector(blendshapesPerMeshIn[i]); + } + output = meshesOut; + } + }; + + class BuildModelTask { + public: + using Input = VaryingSet2<hfm::Model::Pointer, std::vector<hfm::Mesh>>; + using Output = hfm::Model::Pointer; + using JobModel = Job::ModelIO<BuildModelTask, Input, Output>; + + void run(const BakeContextPointer& context, const Input& input, Output& output) { + auto hfmModelOut = input.get0(); + hfmModelOut->meshes = QVector<hfm::Mesh>::fromStdVector(input.get1()); + output = hfmModelOut; + } + }; + class BakerEngineBuilder { public: - using Unused = int; - using Input = hfm::Model::Pointer; using Output = hfm::Model::Pointer; using JobModel = Task::ModelIO<BakerEngineBuilder, Input, Output>; - void build(JobModel& model, const Varying& in, Varying& out) { - out = in; + void build(JobModel& model, const Varying& hfmModelIn, Varying& hfmModelOut) { + // Split up the inputs from hfm::Model + const auto modelPartsIn = model.addJob<GetModelPartsTask>("GetModelParts", hfmModelIn); + const auto meshesIn = modelPartsIn.getN<GetModelPartsTask::Output>(0); + const auto url = modelPartsIn.getN<GetModelPartsTask::Output>(1); + const auto meshIndicesToModelNames = modelPartsIn.getN<GetModelPartsTask::Output>(2); + + // Build the graphics::MeshPointer for each hfm::Mesh + const auto buildGraphicsMeshInputs = BuildGraphicsMeshTask::Input(meshesIn, url, meshIndicesToModelNames).asVarying(); + const auto buildGraphicsMeshOutputs = model.addJob<BuildGraphicsMeshTask>("BuildGraphicsMesh", buildGraphicsMeshInputs); + const auto graphicsMeshes = buildGraphicsMeshOutputs.getN<BuildGraphicsMeshTask::Output>(0); + // TODO: Move tangent/blendshape validation/calculation to an earlier step + const auto tangentsPerMesh = buildGraphicsMeshOutputs.getN<BuildGraphicsMeshTask::Output>(1); + const auto blendshapesPerMesh = buildGraphicsMeshOutputs.getN<BuildGraphicsMeshTask::Output>(2); + + // Combine the outputs into a new hfm::Model + const auto buildMeshesInputs = BuildMeshesTask::Input(meshesIn, graphicsMeshes, tangentsPerMesh, blendshapesPerMesh).asVarying(); + const auto meshesOut = model.addJob<BuildMeshesTask>("BuildMeshes", buildMeshesInputs); + const auto buildModelInputs = BuildModelTask::Input(hfmModelIn, meshesOut).asVarying(); + hfmModelOut = model.addJob<BuildModelTask>("BuildModel", buildModelInputs); } }; diff --git a/libraries/model-baker/src/model-baker/Baker.h b/libraries/model-baker/src/model-baker/Baker.h index ba233243b3..7fb3f420e0 100644 --- a/libraries/model-baker/src/model-baker/Baker.h +++ b/libraries/model-baker/src/model-baker/Baker.h @@ -17,7 +17,6 @@ #include "Engine.h" namespace baker { - class Baker { public: Baker(const hfm::Model::Pointer& hfmModel); diff --git a/libraries/model-baker/src/model-baker/BakerTypes.h b/libraries/model-baker/src/model-baker/BakerTypes.h new file mode 100644 index 0000000000..8615c1b17c --- /dev/null +++ b/libraries/model-baker/src/model-baker/BakerTypes.h @@ -0,0 +1,25 @@ +// +// BakerTypes.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2018/12/10. +// 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 +// + +#ifndef hifi_BakerTypes_h +#define hifi_BakerTypes_h + +#include <hfm/HFM.h> + +namespace baker { + using MeshTangents = std::vector<glm::vec3>; + using TangentsPerMesh = std::vector<std::vector<glm::vec3>>; + using Blendshapes = std::vector<hfm::Blendshape>; + using BlendshapesPerMesh = std::vector<std::vector<hfm::Blendshape>>; + using MeshIndicesToModelNames = QHash<int, QString>; +}; + +#endif // hifi_BakerTypes_h diff --git a/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.cpp b/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.cpp new file mode 100644 index 0000000000..6d351a99e9 --- /dev/null +++ b/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.cpp @@ -0,0 +1,412 @@ +// +// BuildGraphicsMeshTask.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2018/12/06. +// 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 "BuildGraphicsMeshTask.h" + +#include <glm/gtc/packing.hpp> + +#include <LogHandler.h> +#include "ModelBakerLogging.h" + +using vec2h = glm::tvec2<glm::detail::hdata>; + +glm::vec3 normalizeDirForPacking(const glm::vec3& dir) { + auto maxCoord = glm::max(fabsf(dir.x), glm::max(fabsf(dir.y), fabsf(dir.z))); + if (maxCoord > 1e-6f) { + return dir / maxCoord; + } + return dir; +} + +void buildGraphicsMesh(const hfm::Mesh& hfmMesh, graphics::MeshPointer& graphicsMeshPointer, baker::MeshTangents& meshTangents, baker::Blendshapes& blendshapes) { + auto graphicsMesh = std::make_shared<graphics::Mesh>(); + + unsigned int totalSourceIndices = 0; + foreach(const HFMMeshPart& part, hfmMesh.parts) { + totalSourceIndices += (part.quadTrianglesIndices.size() + part.triangleIndices.size()); + } + + static int repeatMessageID = LogHandler::getInstance().newRepeatedMessageID(); + + if (!totalSourceIndices) { + HIFI_FCDEBUG_ID(model_baker(), repeatMessageID, "BuildGraphicsMeshTask failed -- no indices"); + return; + } + + if (hfmMesh.vertices.size() == 0) { + HIFI_FCDEBUG_ID(model_baker(), repeatMessageID, "BuildGraphicsMeshTask failed -- no vertices"); + return; + } + + int numVerts = hfmMesh.vertices.size(); + + if (!hfmMesh.normals.empty() && hfmMesh.tangents.empty()) { + // Fill with a dummy value to force tangents to be present if there are normals + meshTangents.reserve(hfmMesh.normals.size()); + std::fill_n(std::back_inserter(meshTangents), hfmMesh.normals.size(), Vectors::UNIT_X); + } else { + meshTangents = hfmMesh.tangents.toStdVector(); + } + // Same thing with blend shapes + blendshapes = hfmMesh.blendshapes.toStdVector(); + for (auto& blendShape : blendshapes) { + if (!blendShape.normals.empty() && blendShape.tangents.empty()) { + // Fill with a dummy value to force tangents to be present if there are normals + blendShape.tangents.reserve(blendShape.normals.size()); + std::fill_n(std::back_inserter(blendShape.tangents), blendShape.normals.size(), Vectors::UNIT_X); + } + } + + // evaluate all attribute elements and data sizes + + // Position is a vec3 + const auto positionElement = gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ); + const int positionsSize = numVerts * positionElement.getSize(); + + // Normal and tangent are always there together packed in normalized xyz32bits word (times 2) + const auto normalElement = HFM_NORMAL_ELEMENT; + const int normalsSize = hfmMesh.normals.size() * normalElement.getSize(); + const int tangentsSize = (int)meshTangents.size() * normalElement.getSize(); + // If there are normals then there should be tangents + assert(normalsSize <= tangentsSize); + if (tangentsSize > normalsSize) { + HIFI_FCDEBUG_ID(model_baker(), repeatMessageID, "BuildGraphicsMeshTask -- Unexpected tangents in file"); + } + const auto normalsAndTangentsSize = normalsSize + tangentsSize; + + // Color attrib + const auto colorElement = HFM_COLOR_ELEMENT; + const int colorsSize = hfmMesh.colors.size() * colorElement.getSize(); + + // Texture coordinates are stored in 2 half floats + const auto texCoordsElement = gpu::Element(gpu::VEC2, gpu::HALF, gpu::UV); + const int texCoordsSize = hfmMesh.texCoords.size() * texCoordsElement.getSize(); + const int texCoords1Size = hfmMesh.texCoords1.size() * texCoordsElement.getSize(); + + // Support for 4 skinning clusters: + // 4 Indices are uint8 ideally, uint16 if more than 256. + const auto clusterIndiceElement = (hfmMesh.clusters.size() < UINT8_MAX ? gpu::Element(gpu::VEC4, gpu::UINT8, gpu::XYZW) : gpu::Element(gpu::VEC4, gpu::UINT16, gpu::XYZW)); + // 4 Weights are normalized 16bits + const auto clusterWeightElement = gpu::Element(gpu::VEC4, gpu::NUINT16, gpu::XYZW); + + // Cluster indices and weights must be the same sizes + const int NUM_CLUSTERS_PER_VERT = 4; + const int numVertClusters = (hfmMesh.clusterIndices.size() == hfmMesh.clusterWeights.size() ? hfmMesh.clusterIndices.size() / NUM_CLUSTERS_PER_VERT : 0); + const int clusterIndicesSize = numVertClusters * clusterIndiceElement.getSize(); + const int clusterWeightsSize = numVertClusters * clusterWeightElement.getSize(); + + // Decide on where to put what seequencially in a big buffer: + const int positionsOffset = 0; + const int normalsAndTangentsOffset = positionsOffset + positionsSize; + const int colorsOffset = normalsAndTangentsOffset + normalsAndTangentsSize; + const int texCoordsOffset = colorsOffset + colorsSize; + const int texCoords1Offset = texCoordsOffset + texCoordsSize; + const int clusterIndicesOffset = texCoords1Offset + texCoords1Size; + const int clusterWeightsOffset = clusterIndicesOffset + clusterIndicesSize; + const int totalVertsSize = clusterWeightsOffset + clusterWeightsSize; + + // Copy all vertex data in a single buffer + auto vertBuffer = std::make_shared<gpu::Buffer>(); + vertBuffer->resize(totalVertsSize); + + // First positions + vertBuffer->setSubData(positionsOffset, positionsSize, (const gpu::Byte*) hfmMesh.vertices.data()); + + // Interleave normals and tangents + if (normalsSize > 0) { + std::vector<NormalType> normalsAndTangents; + + normalsAndTangents.reserve(hfmMesh.normals.size() + (int)meshTangents.size()); + auto normalIt = hfmMesh.normals.constBegin(); + auto tangentIt = meshTangents.cbegin(); + for (; + normalIt != hfmMesh.normals.constEnd(); + ++normalIt, ++tangentIt) { +#if HFM_PACK_NORMALS + const auto normal = normalizeDirForPacking(*normalIt); + const auto tangent = normalizeDirForPacking(*tangentIt); + const auto packedNormal = glm::packSnorm3x10_1x2(glm::vec4(normal, 0.0f)); + const auto packedTangent = glm::packSnorm3x10_1x2(glm::vec4(tangent, 0.0f)); +#else + const auto packedNormal = *normalIt; + const auto packedTangent = *tangentIt; +#endif + normalsAndTangents.push_back(packedNormal); + normalsAndTangents.push_back(packedTangent); + } + vertBuffer->setSubData(normalsAndTangentsOffset, normalsAndTangentsSize, (const gpu::Byte*) normalsAndTangents.data()); + } + + // Pack colors + if (colorsSize > 0) { +#if HFM_PACK_COLORS + std::vector<ColorType> colors; + + colors.reserve(hfmMesh.colors.size()); + for (const auto& color : hfmMesh.colors) { + colors.push_back(glm::packUnorm4x8(glm::vec4(color, 1.0f))); + } + vertBuffer->setSubData(colorsOffset, colorsSize, (const gpu::Byte*) colors.data()); +#else + vertBuffer->setSubData(colorsOffset, colorsSize, (const gpu::Byte*) hfmMesh.colors.constData()); +#endif + } + + // Pack Texcoords 0 and 1 (if exists) + if (texCoordsSize > 0) { + QVector<vec2h> texCoordData; + texCoordData.reserve(hfmMesh.texCoords.size()); + for (auto& texCoordVec2f : hfmMesh.texCoords) { + vec2h texCoordVec2h; + + texCoordVec2h.x = glm::detail::toFloat16(texCoordVec2f.x); + texCoordVec2h.y = glm::detail::toFloat16(texCoordVec2f.y); + texCoordData.push_back(texCoordVec2h); + } + vertBuffer->setSubData(texCoordsOffset, texCoordsSize, (const gpu::Byte*) texCoordData.constData()); + } + if (texCoords1Size > 0) { + QVector<vec2h> texCoordData; + texCoordData.reserve(hfmMesh.texCoords1.size()); + for (auto& texCoordVec2f : hfmMesh.texCoords1) { + vec2h texCoordVec2h; + + texCoordVec2h.x = glm::detail::toFloat16(texCoordVec2f.x); + texCoordVec2h.y = glm::detail::toFloat16(texCoordVec2f.y); + texCoordData.push_back(texCoordVec2h); + } + vertBuffer->setSubData(texCoords1Offset, texCoords1Size, (const gpu::Byte*) texCoordData.constData()); + } + + // Clusters data + if (clusterIndicesSize > 0) { + if (hfmMesh.clusters.size() < UINT8_MAX) { + // yay! we can fit the clusterIndices within 8-bits + int32_t numIndices = hfmMesh.clusterIndices.size(); + QVector<uint8_t> clusterIndices; + clusterIndices.resize(numIndices); + for (int32_t i = 0; i < numIndices; ++i) { + assert(hfmMesh.clusterIndices[i] <= UINT8_MAX); + clusterIndices[i] = (uint8_t)(hfmMesh.clusterIndices[i]); + } + vertBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) clusterIndices.constData()); + } else { + vertBuffer->setSubData(clusterIndicesOffset, clusterIndicesSize, (const gpu::Byte*) hfmMesh.clusterIndices.constData()); + } + } + if (clusterWeightsSize > 0) { + vertBuffer->setSubData(clusterWeightsOffset, clusterWeightsSize, (const gpu::Byte*) hfmMesh.clusterWeights.constData()); + } + + + // Now we decide on how to interleave the attributes and provide the vertices among bufers: + // Aka the Vertex format and the vertexBufferStream + auto vertexFormat = std::make_shared<gpu::Stream::Format>(); + auto vertexBufferStream = std::make_shared<gpu::BufferStream>(); + + // Decision time: + // if blendshapes then keep position and normals/tangents as separated channel buffers from interleaved attributes + // else everything is interleaved in one buffer + + // Default case is no blend shapes + gpu::BufferPointer attribBuffer; + int totalAttribBufferSize = totalVertsSize; + gpu::uint8 posChannel = 0; + gpu::uint8 tangentChannel = posChannel; + gpu::uint8 attribChannel = posChannel; + bool interleavePositions = true; + bool interleaveNormalsTangents = true; + + // Define the vertex format, compute the offset for each attributes as we append them to the vertex format + gpu::Offset bufOffset = 0; + if (positionsSize) { + vertexFormat->setAttribute(gpu::Stream::POSITION, posChannel, positionElement, bufOffset); + bufOffset += positionElement.getSize(); + if (!interleavePositions) { + bufOffset = 0; + } + } + if (normalsSize) { + vertexFormat->setAttribute(gpu::Stream::NORMAL, tangentChannel, normalElement, bufOffset); + bufOffset += normalElement.getSize(); + vertexFormat->setAttribute(gpu::Stream::TANGENT, tangentChannel, normalElement, bufOffset); + bufOffset += normalElement.getSize(); + if (!interleaveNormalsTangents) { + bufOffset = 0; + } + } + + // Pack normal and Tangent with the rest of atributes if no blend shapes + if (colorsSize) { + vertexFormat->setAttribute(gpu::Stream::COLOR, attribChannel, colorElement, bufOffset); + bufOffset += colorElement.getSize(); + } + if (texCoordsSize) { + vertexFormat->setAttribute(gpu::Stream::TEXCOORD, attribChannel, texCoordsElement, bufOffset); + bufOffset += texCoordsElement.getSize(); + } + if (texCoords1Size) { + vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, attribChannel, texCoordsElement, bufOffset); + bufOffset += texCoordsElement.getSize(); + } else if (texCoordsSize) { + vertexFormat->setAttribute(gpu::Stream::TEXCOORD1, attribChannel, texCoordsElement, bufOffset - texCoordsElement.getSize()); + } + if (clusterIndicesSize) { + vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_INDEX, attribChannel, clusterIndiceElement, bufOffset); + bufOffset += clusterIndiceElement.getSize(); + } + if (clusterWeightsSize) { + vertexFormat->setAttribute(gpu::Stream::SKIN_CLUSTER_WEIGHT, attribChannel, clusterWeightElement, bufOffset); + bufOffset += clusterWeightElement.getSize(); + } + + // Finally, allocate and fill the attribBuffer interleaving the attributes as needed: + { + auto vPositionOffset = 0; + auto vPositionSize = (interleavePositions ? positionsSize / numVerts : 0); + + auto vNormalsAndTangentsOffset = vPositionOffset + vPositionSize; + auto vNormalsAndTangentsSize = (interleaveNormalsTangents ? normalsAndTangentsSize / numVerts : 0); + + auto vColorOffset = vNormalsAndTangentsOffset + vNormalsAndTangentsSize; + auto vColorSize = colorsSize / numVerts; + + auto vTexcoord0Offset = vColorOffset + vColorSize; + auto vTexcoord0Size = texCoordsSize / numVerts; + + auto vTexcoord1Offset = vTexcoord0Offset + vTexcoord0Size; + auto vTexcoord1Size = texCoords1Size / numVerts; + + auto vClusterIndiceOffset = vTexcoord1Offset + vTexcoord1Size; + auto vClusterIndiceSize = clusterIndicesSize / numVerts; + + auto vClusterWeightOffset = vClusterIndiceOffset + vClusterIndiceSize; + auto vClusterWeightSize = clusterWeightsSize / numVerts; + + auto vStride = vClusterWeightOffset + vClusterWeightSize; + + std::vector<gpu::Byte> dest; + dest.resize(totalAttribBufferSize); + auto vDest = dest.data(); + + auto source = vertBuffer->getData(); + + for (int i = 0; i < numVerts; i++) { + + if (vPositionSize) memcpy(vDest + vPositionOffset, source + positionsOffset + i * vPositionSize, vPositionSize); + if (vNormalsAndTangentsSize) memcpy(vDest + vNormalsAndTangentsOffset, source + normalsAndTangentsOffset + i * vNormalsAndTangentsSize, vNormalsAndTangentsSize); + if (vColorSize) memcpy(vDest + vColorOffset, source + colorsOffset + i * vColorSize, vColorSize); + if (vTexcoord0Size) memcpy(vDest + vTexcoord0Offset, source + texCoordsOffset + i * vTexcoord0Size, vTexcoord0Size); + if (vTexcoord1Size) memcpy(vDest + vTexcoord1Offset, source + texCoords1Offset + i * vTexcoord1Size, vTexcoord1Size); + if (vClusterIndiceSize) memcpy(vDest + vClusterIndiceOffset, source + clusterIndicesOffset + i * vClusterIndiceSize, vClusterIndiceSize); + if (vClusterWeightSize) memcpy(vDest + vClusterWeightOffset, source + clusterWeightsOffset + i * vClusterWeightSize, vClusterWeightSize); + + vDest += vStride; + } + + auto attribBuffer = std::make_shared<gpu::Buffer>(); + attribBuffer->setData(totalAttribBufferSize, dest.data()); + vertexBufferStream->addBuffer(attribBuffer, 0, vStride); + } + + // Mesh vertex format and vertex stream is ready + graphicsMesh->setVertexFormatAndStream(vertexFormat, vertexBufferStream); + + // Index and Part Buffers + unsigned int totalIndices = 0; + foreach(const HFMMeshPart& part, hfmMesh.parts) { + totalIndices += (part.quadTrianglesIndices.size() + part.triangleIndices.size()); + } + + if (!totalIndices) { + HIFI_FCDEBUG_ID(model_baker(), repeatMessageID, "BuildGraphicsMeshTask failed -- no indices"); + return; + } + + auto indexBuffer = std::make_shared<gpu::Buffer>(); + indexBuffer->resize(totalIndices * sizeof(int)); + + int indexNum = 0; + int offset = 0; + + std::vector< graphics::Mesh::Part > parts; + if (hfmMesh.parts.size() > 1) { + indexNum = 0; + } + foreach(const HFMMeshPart& part, hfmMesh.parts) { + graphics::Mesh::Part modelPart(indexNum, 0, 0, graphics::Mesh::TRIANGLES); + + if (part.quadTrianglesIndices.size()) { + indexBuffer->setSubData(offset, + part.quadTrianglesIndices.size() * sizeof(int), + (gpu::Byte*) part.quadTrianglesIndices.constData()); + offset += part.quadTrianglesIndices.size() * sizeof(int); + indexNum += part.quadTrianglesIndices.size(); + modelPart._numIndices += part.quadTrianglesIndices.size(); + } + + if (part.triangleIndices.size()) { + indexBuffer->setSubData(offset, + part.triangleIndices.size() * sizeof(int), + (gpu::Byte*) part.triangleIndices.constData()); + offset += part.triangleIndices.size() * sizeof(int); + indexNum += part.triangleIndices.size(); + modelPart._numIndices += part.triangleIndices.size(); + } + + parts.push_back(modelPart); + } + + gpu::BufferView indexBufferView(indexBuffer, gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::XYZ)); + graphicsMesh->setIndexBuffer(indexBufferView); + + if (parts.size()) { + auto pb = std::make_shared<gpu::Buffer>(); + pb->setData(parts.size() * sizeof(graphics::Mesh::Part), (const gpu::Byte*) parts.data()); + gpu::BufferView pbv(pb, gpu::Element(gpu::VEC4, gpu::UINT32, gpu::XYZW)); + graphicsMesh->setPartBuffer(pbv); + } else { + HIFI_FCDEBUG_ID(model_baker(), repeatMessageID, "BuildGraphicsMeshTask failed -- no parts"); + return; + } + + graphicsMesh->evalPartBound(0); + + graphicsMeshPointer = graphicsMesh; +} + +void BuildGraphicsMeshTask::run(const baker::BakeContextPointer& context, const Input& input, Output& output) { + auto& meshes = input.get0(); + auto& url = input.get1(); + auto& meshIndicesToModelNames = input.get2(); + + auto& graphicsMeshes = output.edit0(); + auto& tangentsPerMesh = output.edit1(); + auto& blendshapesPerMesh = output.edit2(); + int n = (int)meshes.size(); + for (int i = 0; i < n; i++) { + graphicsMeshes.emplace_back(); + auto& graphicsMesh = graphicsMeshes[i]; + tangentsPerMesh.emplace_back(); + blendshapesPerMesh.emplace_back(); + + // Try to create the graphics::Mesh + buildGraphicsMesh(meshes[i], graphicsMesh, tangentsPerMesh[i], blendshapesPerMesh[i]); + + // Choose a name for the mesh + if (graphicsMesh) { + graphicsMesh->displayName = url.toString().toStdString() + "#/mesh/" + std::to_string(i); + if (meshIndicesToModelNames.find(i) != meshIndicesToModelNames.cend()) { + graphicsMesh->modelName = meshIndicesToModelNames[i].toStdString(); + } + } + } +} diff --git a/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.h b/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.h new file mode 100644 index 0000000000..3c8985ef9a --- /dev/null +++ b/libraries/model-baker/src/model-baker/BuildGraphicsMeshTask.h @@ -0,0 +1,30 @@ +// +// BuildGraphicsMeshTask.h +// model-baker/src/model-baker +// +// Created by Sabrina Shanman on 2018/12/06. +// 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 +// + +#ifndef hifi_BuildGraphicsMeshTask_h +#define hifi_BuildGraphicsMeshTask_h + +#include <hfm/HFM.h> +#include <shared/HifiTypes.h> + +#include "Engine.h" +#include "BakerTypes.h" + +class BuildGraphicsMeshTask { +public: + using Input = baker::VaryingSet3<std::vector<hfm::Mesh>, hifi::URL, baker::MeshIndicesToModelNames>; + using Output = baker::VaryingSet3<std::vector<graphics::MeshPointer>, std::vector<baker::MeshTangents>, std::vector<baker::Blendshapes>>; + using JobModel = baker::Job::ModelIO<BuildGraphicsMeshTask, Input, Output>; + + void run(const baker::BakeContextPointer& context, const Input& input, Output& output); +}; + +#endif // hifi_BuildGraphicsMeshTask_h diff --git a/libraries/model-baker/src/model-baker/ModelBakerLogging.cpp b/libraries/model-baker/src/model-baker/ModelBakerLogging.cpp new file mode 100644 index 0000000000..0293f5e65b --- /dev/null +++ b/libraries/model-baker/src/model-baker/ModelBakerLogging.cpp @@ -0,0 +1,14 @@ +// +// ModelBakerLogging.cpp +// libraries/baker/src/baker +// +// Created by Sabrina Shanman on 2018/12/12. +// 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 "ModelBakerLogging.h" + +Q_LOGGING_CATEGORY(model_baker, "hifi.model_baker") diff --git a/libraries/model-baker/src/model-baker/ModelBakerLogging.h b/libraries/model-baker/src/model-baker/ModelBakerLogging.h new file mode 100644 index 0000000000..a872fe0682 --- /dev/null +++ b/libraries/model-baker/src/model-baker/ModelBakerLogging.h @@ -0,0 +1,19 @@ +// +// ModelBakerLogging.h +// libraries/baker/src/baker +// +// Created by Sabrina Shanman on 2018/12/06. +// 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 +// + +#ifndef hifi_ModelBakerLogging_h +#define hifi_ModelBakerLogging_h + +#include <QLoggingCategory> + +Q_DECLARE_LOGGING_CATEGORY(model_baker) + +#endif // hifi_ModelBakerLogging_h diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 7a5b1aa30c..38ec9d21d0 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -208,6 +208,44 @@ void AccountManager::setSessionID(const QUuid& sessionID) { } } +QNetworkRequest AccountManager::createRequest(QString path, AccountManagerAuth::Type authType) { + QNetworkRequest networkRequest; + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + networkRequest.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter()); + + networkRequest.setRawHeader(METAVERSE_SESSION_ID_HEADER, + uuidStringWithoutCurlyBraces(_sessionID).toLocal8Bit()); + + QUrl requestURL = _authURL; + + if (requestURL.isEmpty()) { // Assignment client doesn't set _authURL. + requestURL = getMetaverseServerURL(); + } + + if (path.startsWith("/")) { + requestURL.setPath(path); + } else { + requestURL.setPath("/" + path); + } + + if (authType != AccountManagerAuth::None ) { + if (hasValidAccessToken()) { + networkRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, + _accountInfo.getAccessToken().authorizationHeaderValue()); + } else { + if (authType == AccountManagerAuth::Required) { + qCDebug(networking) << "No valid access token present. Bailing on invoked request to" + << path << "that requires authentication"; + return QNetworkRequest(); + } + } + } + + networkRequest.setUrl(requestURL); + + return networkRequest; +} + void AccountManager::sendRequest(const QString& path, AccountManagerAuth::Type authType, QNetworkAccessManager::Operation operation, @@ -231,46 +269,10 @@ void AccountManager::sendRequest(const QString& path, QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - QNetworkRequest networkRequest; - networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); - networkRequest.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter()); - - networkRequest.setRawHeader(METAVERSE_SESSION_ID_HEADER, - uuidStringWithoutCurlyBraces(_sessionID).toLocal8Bit()); - - QUrl requestURL = _authURL; - - if (requestURL.isEmpty()) { // Assignment client doesn't set _authURL. - requestURL = getMetaverseServerURL(); - } - - if (path.startsWith("/")) { - requestURL.setPath(path); - } else { - requestURL.setPath("/" + path); - } - - if (!query.isEmpty()) { - requestURL.setQuery(query); - } - - if (authType != AccountManagerAuth::None ) { - if (hasValidAccessToken()) { - networkRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, - _accountInfo.getAccessToken().authorizationHeaderValue()); - } else { - if (authType == AccountManagerAuth::Required) { - qCDebug(networking) << "No valid access token present. Bailing on invoked request to" - << path << "that requires authentication"; - return; - } - } - } - - networkRequest.setUrl(requestURL); + QNetworkRequest networkRequest = createRequest(path, authType); if (VERBOSE_HTTP_REQUEST_DEBUGGING) { - qCDebug(networking) << "Making a request to" << qPrintable(requestURL.toString()); + qCDebug(networking) << "Making a request to" << qPrintable(networkRequest.url().toString()); if (!dataByteArray.isEmpty()) { qCDebug(networking) << "The POST/PUT body -" << QString(dataByteArray); diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index 9ef8d615db..e21a9a44a4 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -28,7 +28,8 @@ class JSONCallbackParameters { public: - JSONCallbackParameters(QObject* callbackReceiver = nullptr, const QString& jsonCallbackMethod = QString(), + JSONCallbackParameters(QObject* callbackReceiver = nullptr, + const QString& jsonCallbackMethod = QString(), const QString& errorCallbackMethod = QString()); bool isEmpty() const { return !callbackReceiver; } @@ -39,11 +40,11 @@ public: }; namespace AccountManagerAuth { - enum Type { - None, - Required, - Optional - }; +enum Type { + None, + Required, + Optional, +}; } Q_DECLARE_METATYPE(AccountManagerAuth::Type); @@ -60,6 +61,7 @@ class AccountManager : public QObject, public Dependency { public: AccountManager(UserAgentGetter userAgentGetter = DEFAULT_USER_AGENT_GETTER); + QNetworkRequest createRequest(QString path, AccountManagerAuth::Type authType); Q_INVOKABLE void sendRequest(const QString& path, AccountManagerAuth::Type authType, QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation, @@ -84,7 +86,7 @@ public: void requestProfile(); DataServerAccountInfo& getAccountInfo() { return _accountInfo; } - void setAccountInfo(const DataServerAccountInfo &newAccountInfo); + void setAccountInfo(const DataServerAccountInfo& newAccountInfo); static QJsonObject dataObjectFromResponse(QNetworkReply* requestReply); @@ -105,7 +107,10 @@ public slots: void requestAccessToken(const QString& login, const QString& password); void requestAccessTokenWithSteam(QByteArray authSessionTicket); void requestAccessTokenWithOculus(const QString& nonce, const QString& userID, const QString& oculusID); - void requestAccessTokenWithAuthCode(const QString& authCode, const QString& clientId, const QString& clientSecret, const QString& redirectUri); + void requestAccessTokenWithAuthCode(const QString& authCode, + const QString& clientId, + const QString& clientSecret, + const QString& redirectUri); void refreshAccessToken(); void requestAccessTokenFinished(); @@ -160,4 +165,4 @@ private: bool _limitedCommerce { false }; }; -#endif // hifi_AccountManager_h +#endif // hifi_AccountManager_h diff --git a/libraries/networking/src/BandwidthRecorder.cpp b/libraries/networking/src/BandwidthRecorder.cpp deleted file mode 100644 index 80276dba5a..0000000000 --- a/libraries/networking/src/BandwidthRecorder.cpp +++ /dev/null @@ -1,190 +0,0 @@ -// -// BandwidthMeter.cpp -// interface/src/ui -// -// Created by Seth Alves on 2015-1-30 -// Copyright 2015 High Fidelity, Inc. -// -// Based on code by Tobias Schwinger -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "BandwidthRecorder.h" - -#include <QDateTime> - -BandwidthRecorder::Channel::Channel() { -} - -float BandwidthRecorder::Channel::getAverageInputPacketsPerSecond() const { - float averageTimeBetweenPackets = _input.getEventDeltaAverage(); - if (averageTimeBetweenPackets > 0.0f) { - return (1.0f / averageTimeBetweenPackets); - } - return 0.0f; -} - -float BandwidthRecorder::Channel::getAverageOutputPacketsPerSecond() const { - float averageTimeBetweenPackets = _output.getEventDeltaAverage(); - if (averageTimeBetweenPackets > 0.0f) { - return (1.0f / averageTimeBetweenPackets); - } - return 0.0f; -} - -float BandwidthRecorder::Channel::getAverageInputKilobitsPerSecond() const { - return (_input.getAverageSampleValuePerSecond() * (8.0f / 1000)); -} - -float BandwidthRecorder::Channel::getAverageOutputKilobitsPerSecond() const { - return (_output.getAverageSampleValuePerSecond() * (8.0f / 1000)); -} - - -void BandwidthRecorder::Channel::updateInputAverage(const float sample) { - _input.updateAverage(sample); -} - -void BandwidthRecorder::Channel::updateOutputAverage(const float sample) { - _output.updateAverage(sample); -} - -BandwidthRecorder::BandwidthRecorder() { - for (uint i=0; i<CHANNEL_COUNT; i++) { - _channels[ i ] = NULL; - } -} - -BandwidthRecorder::~BandwidthRecorder() { - for (uint i=0; i<CHANNEL_COUNT; i++) { - delete _channels[ i ]; - } -} - -void BandwidthRecorder::updateInboundData(const quint8 channelType, const int sample) { - if (! _channels[channelType]) { - _channels[channelType] = new Channel(); - } - _channels[channelType]->updateInputAverage(sample); -} - -void BandwidthRecorder::updateOutboundData(const quint8 channelType, const int sample) { - if (! _channels[channelType]) { - _channels[channelType] = new Channel(); - } - _channels[channelType]->updateOutputAverage(sample); -} - -float BandwidthRecorder::getAverageInputPacketsPerSecond(const quint8 channelType) const { - if (! _channels[channelType]) { - return 0.0f; - } - return _channels[channelType]->getAverageInputPacketsPerSecond(); -} - -float BandwidthRecorder::getAverageOutputPacketsPerSecond(const quint8 channelType) const { - if (! _channels[channelType]) { - return 0.0f; - } - return _channels[channelType]->getAverageOutputPacketsPerSecond(); -} - -float BandwidthRecorder::getAverageInputKilobitsPerSecond(const quint8 channelType) const { - if (! _channels[channelType]) { - return 0.0f; - } - return _channels[channelType]->getAverageInputKilobitsPerSecond(); -} - -float BandwidthRecorder::getAverageOutputKilobitsPerSecond(const quint8 channelType) const { - if (! _channels[channelType]) { - return 0.0f; - } - return _channels[channelType]->getAverageOutputKilobitsPerSecond(); -} - -float BandwidthRecorder::getTotalAverageInputPacketsPerSecond() const { - float result = 0.0f; - for (uint i=0; i<CHANNEL_COUNT; i++) { - if (_channels[i]) { - result += _channels[i]->getAverageInputPacketsPerSecond(); - } - } - return result; -} - -float BandwidthRecorder::getTotalAverageOutputPacketsPerSecond() const { - float result = 0.0f; - for (uint i=0; i<CHANNEL_COUNT; i++) { - if (_channels[i]) { - result += _channels[i]->getAverageOutputPacketsPerSecond(); - } - } - return result; -} - -float BandwidthRecorder::getTotalAverageInputKilobitsPerSecond() const { - float result = 0.0f; - for (uint i=0; i<CHANNEL_COUNT; i++) { - if (_channels[i]) { - result += _channels[i]->getAverageInputKilobitsPerSecond(); - } - } - return result; -} - -float BandwidthRecorder::getTotalAverageOutputKilobitsPerSecond() const { - float result = 0.0f; - for (uint i=0; i<CHANNEL_COUNT; i++) { - if (_channels[i]) { - result += _channels[i]->getAverageOutputKilobitsPerSecond(); - } - } - return result; -} - -float BandwidthRecorder::getCachedTotalAverageInputPacketsPerSecond() const { - static qint64 lastCalculated = 0; - static float cachedValue = 0.0f; - qint64 now = QDateTime::currentMSecsSinceEpoch(); - if (now - lastCalculated > 1000.0f) { - lastCalculated = now; - cachedValue = getTotalAverageInputPacketsPerSecond(); - } - return cachedValue; -} - -float BandwidthRecorder::getCachedTotalAverageOutputPacketsPerSecond() const { - static qint64 lastCalculated = 0; - static float cachedValue = 0.0f; - qint64 now = QDateTime::currentMSecsSinceEpoch(); - if (now - lastCalculated > 1000.0f) { - lastCalculated = now; - cachedValue = getTotalAverageOutputPacketsPerSecond(); - } - return cachedValue; -} - -float BandwidthRecorder::getCachedTotalAverageInputKilobitsPerSecond() const { - static qint64 lastCalculated = 0; - static float cachedValue = 0.0f; - qint64 now = QDateTime::currentMSecsSinceEpoch(); - if (now - lastCalculated > 1000.0f) { - lastCalculated = now; - cachedValue = getTotalAverageInputKilobitsPerSecond(); - } - return cachedValue; -} - -float BandwidthRecorder::getCachedTotalAverageOutputKilobitsPerSecond() const { - static qint64 lastCalculated = 0; - static float cachedValue = 0.0f; - qint64 now = QDateTime::currentMSecsSinceEpoch(); - if (now - lastCalculated > 1000.0f) { - lastCalculated = now; - cachedValue = getTotalAverageOutputKilobitsPerSecond(); - } - return cachedValue; -} diff --git a/libraries/networking/src/BandwidthRecorder.h b/libraries/networking/src/BandwidthRecorder.h deleted file mode 100644 index b1cee570f2..0000000000 --- a/libraries/networking/src/BandwidthRecorder.h +++ /dev/null @@ -1,75 +0,0 @@ -// -// BandwidthRecorder.h -// -// Created by Seth Alves on 2015-1-30 -// Copyright 2015 High Fidelity, Inc. -// -// Based on code by Tobias Schwinger -// -// 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_BandwidthRecorder_h -#define hifi_BandwidthRecorder_h - -#include <QObject> -#include <QElapsedTimer> -#include "DependencyManager.h" -#include "SimpleMovingAverage.h" - - -class BandwidthRecorder : public QObject, public Dependency { - Q_OBJECT - SINGLETON_DEPENDENCY - -public: - BandwidthRecorder(); - ~BandwidthRecorder(); - - // keep track of data rate in two directions as well as units and style to use during display - class Channel { - public: - Channel(); - float getAverageInputPacketsPerSecond() const; - float getAverageOutputPacketsPerSecond() const; - float getAverageInputKilobitsPerSecond() const; - float getAverageOutputKilobitsPerSecond() const; - - void updateInputAverage(const float sample); - void updateOutputAverage(const float sample); - - private: - SimpleMovingAverage _input; - SimpleMovingAverage _output; - }; - - float getAverageInputPacketsPerSecond(const quint8 channelType) const; - float getAverageOutputPacketsPerSecond(const quint8 channelType) const; - float getAverageInputKilobitsPerSecond(const quint8 channelType) const; - float getAverageOutputKilobitsPerSecond(const quint8 channelType) const; - - float getTotalAverageInputPacketsPerSecond() const; - float getTotalAverageOutputPacketsPerSecond() const; - float getTotalAverageInputKilobitsPerSecond() const; - float getTotalAverageOutputKilobitsPerSecond() const; - - float getCachedTotalAverageInputPacketsPerSecond() const; - float getCachedTotalAverageOutputPacketsPerSecond() const; - float getCachedTotalAverageInputKilobitsPerSecond() const; - float getCachedTotalAverageOutputKilobitsPerSecond() const; - - -private: - // one for each possible Node type - static const unsigned int CHANNEL_COUNT = 256; - Channel* _channels[CHANNEL_COUNT]; - - -public slots: - void updateInboundData(const quint8 channelType, const int bytes); - void updateOutboundData(const quint8 channelType, const int bytes); -}; - -#endif diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 37b914143e..8b9e37569c 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -83,6 +83,11 @@ LimitedNodeList::LimitedNodeList(int socketListenPort, int dtlsListenPort) : connect(silentNodeTimer, &QTimer::timeout, this, &LimitedNodeList::removeSilentNodes); silentNodeTimer->start(NODE_SILENCE_THRESHOLD_MSECS); + const int CONNECTION_STATS_SAMPLE_INTERVAL_MSECS = 1000; + QTimer* statsSampleTimer = new QTimer(this); + connect(statsSampleTimer, &QTimer::timeout, this, &LimitedNodeList::sampleConnectionStats); + statsSampleTimer->start(CONNECTION_STATS_SAMPLE_INTERVAL_MSECS); + // check the local socket right now updateLocalSocket(); @@ -108,8 +113,6 @@ LimitedNodeList::LimitedNodeList(int socketListenPort, int dtlsListenPort) : // handle when a socket connection has its receiver side reset - might need to emit clientConnectionToNodeReset connect(&_nodeSocket, &udt::Socket::clientHandshakeRequestComplete, this, &LimitedNodeList::clientConnectionToSockAddrReset); - _packetStatTimer.start(); - if (_stunSockAddr.getAddress().isNull()) { // we don't know the stun server socket yet, add it to unfiltered once known connect(&_stunSockAddr, &HifiSockAddr::lookupCompleted, this, &LimitedNodeList::addSTUNHandlerToUnfiltered); @@ -295,17 +298,14 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe }); if (sendingNodeType != NodeType::Unassigned) { - emit dataReceived(sendingNodeType, packet.getPayloadSize()); return true; } else { HIFI_FCDEBUG(networking(), "Replicated packet of type" << headerType << "received from unknown upstream" << packet.getSenderSockAddr()); - + return false; } - } else { - emit dataReceived(NodeType::Unassigned, packet.getPayloadSize()); return true; } } else { @@ -319,7 +319,7 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe SharedNodePointer matchingNode = nodeWithLocalID(sourceLocalID); sourceNode = matchingNode.data(); } - + QUuid sourceID = sourceNode ? sourceNode->getUUID() : QUuid(); if (!sourceNode && @@ -328,8 +328,6 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe packet.getSenderSockAddr() == getDomainSockAddr() && PacketTypeEnum::getDomainSourcedPackets().contains(headerType)) { // This is a packet sourced by the domain server - - emit dataReceived(NodeType::Unassigned, packet.getPayloadSize()); return true; } @@ -367,8 +365,6 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe // from this sending node sourceNode->setLastHeardMicrostamp(usecTimestampNow()); - emit dataReceived(sourceNode->getType(), packet.getPayloadSize()); - return true; } else { @@ -380,12 +376,6 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe return false; } -void LimitedNodeList::collectPacketStats(const NLPacket& packet) { - // stat collection for packets - ++_numCollectedPackets; - _numCollectedBytes += packet.getDataSize(); -} - void LimitedNodeList::fillPacketHeader(const NLPacket& packet, HMACAuth* hmacAuth) { if (!PacketTypeEnum::getNonSourcedPackets().contains(packet.getType())) { packet.writeSourceID(getSessionLocalID()); @@ -407,9 +397,6 @@ qint64 LimitedNodeList::sendUnreliablePacket(const NLPacket& packet, const Node& return 0; } - emit dataSent(destinationNode.getType(), packet.getDataSize()); - destinationNode.recordBytesSent(packet.getDataSize()); - return sendUnreliablePacket(packet, *destinationNode.getActiveSocket(), destinationNode.getAuthenticateHash()); } @@ -419,7 +406,6 @@ qint64 LimitedNodeList::sendUnreliablePacket(const NLPacket& packet, const HifiS Q_ASSERT_X(!packet.isReliable(), "LimitedNodeList::sendUnreliablePacket", "Trying to send a reliable packet unreliably."); - collectPacketStats(packet); fillPacketHeader(packet, hmacAuth); return _nodeSocket.writePacket(packet, sockAddr); @@ -430,9 +416,6 @@ qint64 LimitedNodeList::sendPacket(std::unique_ptr<NLPacket> packet, const Node& auto activeSocket = destinationNode.getActiveSocket(); if (activeSocket) { - emit dataSent(destinationNode.getType(), packet->getDataSize()); - destinationNode.recordBytesSent(packet->getDataSize()); - return sendPacket(std::move(packet), *activeSocket, destinationNode.getAuthenticateHash()); } else { qCDebug(networking) << "LimitedNodeList::sendPacket called without active socket for node" << destinationNode << "- not sending"; @@ -444,7 +427,6 @@ qint64 LimitedNodeList::sendPacket(std::unique_ptr<NLPacket> packet, const HifiS HMACAuth* hmacAuth) { Q_ASSERT(!packet->isPartOfMessage()); if (packet->isReliable()) { - collectPacketStats(*packet); fillPacketHeader(*packet, hmacAuth); auto size = packet->getDataSize(); @@ -470,8 +452,6 @@ qint64 LimitedNodeList::sendUnreliableUnorderedPacketList(NLPacketList& packetLi bytesSent += sendPacket(packetList.takeFront<NLPacket>(), *activeSocket, connectionHash); } - - emit dataSent(destinationNode.getType(), bytesSent); return bytesSent; } else { qCDebug(networking) << "LimitedNodeList::sendPacketList called without active socket for node" << destinationNode @@ -500,7 +480,6 @@ qint64 LimitedNodeList::sendPacketList(std::unique_ptr<NLPacketList> packetList, for (std::unique_ptr<udt::Packet>& packet : packetList->_packets) { NLPacket* nlPacket = static_cast<NLPacket*>(packet.get()); - collectPacketStats(*nlPacket); fillPacketHeader(*nlPacket); } @@ -515,7 +494,6 @@ qint64 LimitedNodeList::sendPacketList(std::unique_ptr<NLPacketList> packetList, for (std::unique_ptr<udt::Packet>& packet : packetList->_packets) { NLPacket* nlPacket = static_cast<NLPacket*>(packet.get()); - collectPacketStats(*nlPacket); fillPacketHeader(*nlPacket, destinationNode.getAuthenticateHash()); } @@ -842,23 +820,6 @@ SharedNodePointer LimitedNodeList::soloNodeOfType(NodeType_t nodeType) { }); } -void LimitedNodeList::getPacketStats(float& packetsInPerSecond, float& bytesInPerSecond, float& packetsOutPerSecond, float& bytesOutPerSecond) { - packetsInPerSecond = (float) getPacketReceiver().getInPacketCount() / ((float) _packetStatTimer.elapsed() / 1000.0f); - bytesInPerSecond = (float) getPacketReceiver().getInByteCount() / ((float) _packetStatTimer.elapsed() / 1000.0f); - - packetsOutPerSecond = (float) _numCollectedPackets / ((float) _packetStatTimer.elapsed() / 1000.0f); - bytesOutPerSecond = (float) _numCollectedBytes / ((float) _packetStatTimer.elapsed() / 1000.0f); -} - -void LimitedNodeList::resetPacketStats() { - getPacketReceiver().resetCounters(); - - _numCollectedPackets = 0; - _numCollectedBytes = 0; - - _packetStatTimer.restart(); -} - void LimitedNodeList::removeSilentNodes() { QSet<SharedNodePointer> killedNodes; @@ -887,10 +848,56 @@ void LimitedNodeList::removeSilentNodes() { } } +void LimitedNodeList::sampleConnectionStats() { + uint32_t packetsIn { 0 }; + uint32_t packetsOut { 0 }; + uint64_t bytesIn { 0 }; + uint64_t bytesOut { 0 }; + int elapsedSum { 0 }; + int elapsedCount { 0 }; + + auto allStats = _nodeSocket.sampleStatsForAllConnections(); + for (const auto& stats : allStats) { + auto node = findNodeWithAddr(stats.first); + if (node && node->getActiveSocket() && + *node->getActiveSocket() == stats.first) { + node->updateStats(stats.second); + } + + packetsIn += stats.second.receivedPackets; + packetsIn += stats.second.receivedUnreliablePackets; + packetsOut += stats.second.sentPackets; + packetsOut += stats.second.sentUnreliablePackets; + bytesIn += stats.second.receivedBytes; + bytesIn += stats.second.receivedUnreliableBytes; + bytesOut += stats.second.sentBytes; + bytesOut += stats.second.sentUnreliableBytes; + elapsedSum += (stats.second.endTime - stats.second.startTime).count(); + elapsedCount++; + } + + if (elapsedCount > 0) { + float elapsedAvg = (float)elapsedSum / elapsedCount; + float factor = USECS_PER_SECOND / elapsedAvg; + + float kilobitsReceived = (float)bytesIn * BITS_IN_BYTE / BYTES_PER_KILOBYTE; + float kilobitsSent = (float)bytesOut * BITS_IN_BYTE / BYTES_PER_KILOBYTE; + + _inboundPPS = packetsIn * factor; + _outboundPPS = packetsOut * factor; + _inboundKbps = kilobitsReceived * factor; + _outboundKbps = kilobitsSent * factor; + } else { + _inboundPPS = 0; + _outboundPPS = 0; + _inboundKbps = 0.0f; + _outboundKbps = 0.0f; + } +} + const uint32_t RFC_5389_MAGIC_COOKIE = 0x2112A442; const int NUM_BYTES_STUN_HEADER = 20; - void LimitedNodeList::makeSTUNRequestPacket(char* stunRequestPacket) { int packetIndex = 0; @@ -1261,6 +1268,10 @@ void LimitedNodeList::flagTimeForConnectionStep(ConnectionStep connectionStep) { } void LimitedNodeList::flagTimeForConnectionStep(ConnectionStep connectionStep, quint64 timestamp) { + if (!_flagTimeForConnectionStep) { + // this is only true in interface + return; + } if (connectionStep == ConnectionStep::LookupAddress) { QWriteLocker writeLock(&_connectionTimeLock); diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index dacefa8e40..450fad96a9 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -122,7 +122,7 @@ public: bool getThisNodeCanWriteAssets() const { return _permissions.can(NodePermissions::Permission::canWriteToAssetServer); } bool getThisNodeCanKick() const { return _permissions.can(NodePermissions::Permission::canKick); } bool getThisNodeCanReplaceContent() const { return _permissions.can(NodePermissions::Permission::canReplaceDomainContent); } - + quint16 getSocketLocalPort() const { return _nodeSocket.localPort(); } Q_INVOKABLE void setSocketLocalPort(quint16 socketLocalPort); @@ -183,9 +183,6 @@ public: unsigned int broadcastToNodes(std::unique_ptr<NLPacket> packet, const NodeSet& destinationNodeTypes); SharedNodePointer soloNodeOfType(NodeType_t nodeType); - void getPacketStats(float& packetsInPerSecond, float& bytesInPerSecond, float& packetsOutPerSecond, float& bytesOutPerSecond); - void resetPacketStats(); - std::unique_ptr<NLPacket> constructPingPacket(const QUuid& nodeId, PingType_t pingType = PingType::Agnostic); std::unique_ptr<NLPacket> constructPingReplyPacket(ReceivedMessage& message); @@ -204,9 +201,9 @@ public: // This allows multiple threads (i.e. a thread pool) to share a lock // without deadlocking when a dying node attempts to acquire a write lock template<typename NestedNodeLambda> - void nestedEach(NestedNodeLambda functor, - int* lockWaitOut = nullptr, - int* nodeTransformOut = nullptr, + void nestedEach(NestedNodeLambda functor, + int* lockWaitOut = nullptr, + int* nodeTransformOut = nullptr, int* functorOut = nullptr) { auto start = usecTimestampNow(); { @@ -310,12 +307,20 @@ public: void setAuthenticatePackets(bool useAuthentication) { _useAuthentication = useAuthentication; } bool getAuthenticatePackets() const { return _useAuthentication; } + void setFlagTimeForConnectionStep(bool flag) { _flagTimeForConnectionStep = flag; } + bool isFlagTimeForConnectionStep() { return _flagTimeForConnectionStep; } + static void makeSTUNRequestPacket(char* stunRequestPacket); #if (PR_BUILD || DEV_BUILD) void sendFakedHandshakeRequestToNode(SharedNodePointer node); #endif + int getInboundPPS() const { return _inboundPPS; } + int getOutboundPPS() const { return _outboundPPS; } + float getInboundKbps() const { return _inboundKbps; } + float getOutboundKbps() const { return _outboundKbps; } + public slots: void reset(); void eraseAllNodes(); @@ -329,10 +334,10 @@ public slots: bool killNodeWithUUID(const QUuid& nodeUUID, ConnectionID newConnectionID = NULL_CONNECTION_ID); -signals: - void dataSent(quint8 channelType, int bytes); - void dataReceived(quint8 channelType, int bytes); +private slots: + void sampleConnectionStats(); +signals: // QUuid might be zero for non-sourced packet types. void packetVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID); @@ -369,9 +374,6 @@ protected: qint64 sendPacket(std::unique_ptr<NLPacket> packet, const Node& destinationNode, const HifiSockAddr& overridenSockAddr); - qint64 writePacket(const NLPacket& packet, const HifiSockAddr& destinationSockAddr, - const QUuid& connectionSecret = QUuid()); - void collectPacketStats(const NLPacket& packet); void fillPacketHeader(const NLPacket& packet, HMACAuth* hmacAuth = nullptr); void setLocalSocket(const HifiSockAddr& sockAddr); @@ -400,10 +402,6 @@ protected: PacketReceiver* _packetReceiver; - std::atomic<int> _numCollectedPackets { 0 }; - std::atomic<int> _numCollectedBytes { 0 }; - - QElapsedTimer _packetStatTimer; NodePermissions _permissions; QPointer<QTimer> _initialSTUNTimer; @@ -440,6 +438,12 @@ private: using LocalIDMapping = tbb::concurrent_unordered_map<Node::LocalID, SharedNodePointer>; LocalIDMapping _localIDMap; Node::LocalID _sessionLocalID { 0 }; + bool _flagTimeForConnectionStep { false }; // only keep track in interface + + int _inboundPPS { 0 }; + int _outboundPPS { 0 }; + float _inboundKbps { 0.0f }; + float _outboundKbps { 0.0f }; }; #endif // hifi_LimitedNodeList_h diff --git a/libraries/networking/src/NetworkPeer.cpp b/libraries/networking/src/NetworkPeer.cpp index 35956c4789..4e0a82ba0e 100644 --- a/libraries/networking/src/NetworkPeer.cpp +++ b/libraries/networking/src/NetworkPeer.cpp @@ -228,19 +228,3 @@ QDebug operator<<(QDebug debug, const NetworkPeer &peer) { << "- local:" << peer.getLocalSocket(); return debug; } - -void NetworkPeer::recordBytesSent(int count) const { - _bandwidthRecorder.updateOutboundData(0, count); -} - -void NetworkPeer::recordBytesReceived(int count) const { - _bandwidthRecorder.updateInboundData(0, count); -} - -float NetworkPeer::getOutboundBandwidth() const { - return _bandwidthRecorder.getAverageOutputKilobitsPerSecond(0); -} - -float NetworkPeer::getInboundBandwidth() const { - return _bandwidthRecorder.getAverageInputKilobitsPerSecond(0); -} diff --git a/libraries/networking/src/NetworkPeer.h b/libraries/networking/src/NetworkPeer.h index 4688498a96..b75d2f8b86 100644 --- a/libraries/networking/src/NetworkPeer.h +++ b/libraries/networking/src/NetworkPeer.h @@ -18,7 +18,6 @@ #include <QtCore/QTimer> #include <QtCore/QUuid> -#include "BandwidthRecorder.h" #include "HifiSockAddr.h" #include "UUID.h" @@ -78,12 +77,6 @@ public: void incrementConnectionAttempts() { ++_connectionAttempts; } void resetConnectionAttempts() { _connectionAttempts = 0; } - void recordBytesSent(int count) const; - void recordBytesReceived(int count) const; - - float getOutboundBandwidth() const; // in kbps - float getInboundBandwidth() const; // in kbps - // Typically the LimitedNodeList removes nodes after they are "silent" // meaning that we have not received any packets (including simple keepalive pings) from them for a set interval. // The _isForcedNeverSilent flag tells the LimitedNodeList that a Node should never be killed by removeSilentNodes() @@ -114,8 +107,6 @@ protected: HifiSockAddr _symmetricSocket; HifiSockAddr* _activeSocket; - mutable BandwidthRecorder _bandwidthRecorder; - quint64 _wakeTimestamp; std::atomic_ullong _lastHeardMicrostamp; diff --git a/libraries/networking/src/NetworkingConstants.h b/libraries/networking/src/NetworkingConstants.h index 302e0efa02..117a41c976 100644 --- a/libraries/networking/src/NetworkingConstants.h +++ b/libraries/networking/src/NetworkingConstants.h @@ -33,6 +33,7 @@ namespace NetworkingConstants { const QString HIFI_URL_SCHEME_ABOUT = "about"; const QString URL_SCHEME_HIFI = "hifi"; const QString URL_SCHEME_HIFIAPP = "hifiapp"; +const QString URL_SCHEME_DATA = "data"; const QString URL_SCHEME_QRC = "qrc"; const QString HIFI_URL_SCHEME_FILE = "file"; const QString HIFI_URL_SCHEME_HTTP = "http"; diff --git a/libraries/networking/src/Node.cpp b/libraries/networking/src/Node.cpp index 9421e1da44..a2bd60914a 100644 --- a/libraries/networking/src/Node.cpp +++ b/libraries/networking/src/Node.cpp @@ -219,3 +219,37 @@ void Node::setConnectionSecret(const QUuid& connectionSecret) { _connectionSecret = connectionSecret; _authenticateHash->setKey(_connectionSecret); } + +void Node::updateStats(Stats stats) { + _stats = stats; +} + +const Node::Stats& Node::getConnectionStats() const { + return _stats; +} + +float Node::getInboundKbps() const { + float bitsReceived = (_stats.receivedBytes + _stats.receivedUnreliableBytes) * BITS_IN_BYTE; + auto elapsed = _stats.endTime - _stats.startTime; + auto bps = (bitsReceived * USECS_PER_SECOND) / elapsed.count(); + return bps / BYTES_PER_KILOBYTE; +} + +float Node::getOutboundKbps() const { + float bitsSent = (_stats.sentBytes + _stats.sentUnreliableBytes) * BITS_IN_BYTE; + auto elapsed = _stats.endTime - _stats.startTime; + auto bps = (bitsSent * USECS_PER_SECOND) / elapsed.count(); + return bps / BYTES_PER_KILOBYTE; +} + +int Node::getInboundPPS() const { + float packetsReceived = _stats.receivedPackets + _stats.receivedUnreliablePackets; + auto elapsed = _stats.endTime - _stats.startTime; + return (packetsReceived * USECS_PER_SECOND) / elapsed.count(); +} + +int Node::getOutboundPPS() const { + float packetsSent = _stats.sentPackets + _stats.sentUnreliablePackets; + auto elapsed = _stats.endTime - _stats.startTime; + return (packetsSent * USECS_PER_SECOND) / elapsed.count(); +} diff --git a/libraries/networking/src/Node.h b/libraries/networking/src/Node.h index 6c5a56c94e..fe3177d785 100644 --- a/libraries/networking/src/Node.h +++ b/libraries/networking/src/Node.h @@ -35,10 +35,13 @@ #include "MovingPercentile.h" #include "NodePermissions.h" #include "HMACAuth.h" +#include "udt/ConnectionStats.h" +#include "NumericalConstants.h" class Node : public NetworkPeer { Q_OBJECT public: + using Stats = udt::ConnectionStats::Stats; Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, @@ -94,6 +97,14 @@ public: friend QDataStream& operator<<(QDataStream& out, const Node& node); friend QDataStream& operator>>(QDataStream& in, Node& node); + void updateStats(Stats stats); + const Stats& getConnectionStats() const; + + int getInboundPPS() const; + int getOutboundPPS() const; + float getInboundKbps() const; + float getOutboundKbps() const; + private: // privatize copy and assignment operator to disallow Node copying Node(const Node &otherNode); @@ -115,6 +126,8 @@ private: IgnoredNodeIDs _ignoredNodeIDs; mutable QReadWriteLock _ignoredNodeIDSetLock; std::vector<QString> _replicatedUsernames { }; + + Stats _stats; }; Q_DECLARE_METATYPE(Node*) diff --git a/libraries/networking/src/PacketReceiver.cpp b/libraries/networking/src/PacketReceiver.cpp index fe2a273d61..962ceab00f 100644 --- a/libraries/networking/src/PacketReceiver.cpp +++ b/libraries/networking/src/PacketReceiver.cpp @@ -212,18 +212,12 @@ void PacketReceiver::handleVerifiedPacket(std::unique_ptr<udt::Packet> packet) { auto nlPacket = NLPacket::fromBase(std::move(packet)); auto receivedMessage = QSharedPointer<ReceivedMessage>::create(*nlPacket); - _inPacketCount += 1; - _inByteCount += nlPacket->size(); - handleVerifiedMessage(receivedMessage, true); } void PacketReceiver::handleVerifiedMessagePacket(std::unique_ptr<udt::Packet> packet) { auto nlPacket = NLPacket::fromBase(std::move(packet)); - _inPacketCount += 1; - _inByteCount += nlPacket->size(); - auto key = std::pair<HifiSockAddr, udt::Packet::MessageNumber>(nlPacket->getSenderSockAddr(), nlPacket->getMessageNumber()); auto it = _pendingMessages.find(key); QSharedPointer<ReceivedMessage> message; @@ -284,10 +278,6 @@ void PacketReceiver::handleVerifiedMessage(QSharedPointer<ReceivedMessage> recei connectionType = _directlyConnectedObjects.contains(listener.object) ? Qt::DirectConnection : Qt::AutoConnection; } - if (matchingNode) { - matchingNode->recordBytesReceived(receivedMessage->getSize()); - } - QMetaMethod metaMethod = listener.method; static const QByteArray QSHAREDPOINTER_NODE_NORMALIZED = QMetaObject::normalizedType("QSharedPointer<Node>"); diff --git a/libraries/networking/src/PacketReceiver.h b/libraries/networking/src/PacketReceiver.h index 4b4d260409..e29a0d6e5a 100644 --- a/libraries/networking/src/PacketReceiver.h +++ b/libraries/networking/src/PacketReceiver.h @@ -49,13 +49,8 @@ public: PacketReceiver(const PacketReceiver&) = delete; PacketReceiver& operator=(const PacketReceiver&) = delete; - - int getInPacketCount() const { return _inPacketCount; } - int getInByteCount() const { return _inByteCount; } void setShouldDropPackets(bool shouldDropPackets) { _shouldDropPackets = shouldDropPackets; } - - void resetCounters() { _inPacketCount = 0; _inByteCount = 0; } // If deliverPending is false, ReceivedMessage will only be delivered once all packets for the message have // been received. If deliverPending is true, ReceivedMessage will be delivered as soon as the first packet @@ -87,8 +82,7 @@ private: QMutex _packetListenerLock; QHash<PacketType, Listener> _messageListenerMap; - int _inPacketCount = 0; - int _inByteCount = 0; + bool _shouldDropPackets = false; QMutex _directConnectSetMutex; QSet<QObject*> _directlyConnectedObjects; diff --git a/libraries/networking/src/ThreadedAssignment.cpp b/libraries/networking/src/ThreadedAssignment.cpp index 13d4e0bf8b..bdba47f0ed 100644 --- a/libraries/networking/src/ThreadedAssignment.cpp +++ b/libraries/networking/src/ThreadedAssignment.cpp @@ -94,15 +94,11 @@ void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeTy void ThreadedAssignment::addPacketStatsAndSendStatsPacket(QJsonObject statsObject) { auto nodeList = DependencyManager::get<NodeList>(); - float packetsInPerSecond, bytesInPerSecond, packetsOutPerSecond, bytesOutPerSecond; - nodeList->getPacketStats(packetsInPerSecond, bytesInPerSecond, packetsOutPerSecond, bytesOutPerSecond); - nodeList->resetPacketStats(); - QJsonObject ioStats; - ioStats["inbound_bytes_per_s"] = bytesInPerSecond; - ioStats["inbound_packets_per_s"] = packetsInPerSecond; - ioStats["outbound_bytes_per_s"] = bytesOutPerSecond; - ioStats["outbound_packets_per_s"] = packetsOutPerSecond; + ioStats["inbound_kbps"] = nodeList->getInboundKbps(); + ioStats["inbound_pps"] = nodeList->getInboundPPS(); + ioStats["outbound_kbps"] = nodeList->getOutboundKbps(); + ioStats["outbound_pps"] = nodeList->getOutboundPPS(); statsObject["io_stats"] = ioStats; diff --git a/libraries/networking/src/UserActivityLogger.cpp b/libraries/networking/src/UserActivityLogger.cpp index c05daf0217..948acd3d78 100644 --- a/libraries/networking/src/UserActivityLogger.cpp +++ b/libraries/networking/src/UserActivityLogger.cpp @@ -139,11 +139,10 @@ void UserActivityLogger::connectedDevice(QString typeOfDevice, QString deviceNam "NullDisplayPlugin", "3D TV - Side by Side Stereo", "3D TV - Interleaved", - "Keyboard/Mouse" }; - if (DEVICE_BLACKLIST.contains(deviceName)) { + if (DEVICE_BLACKLIST.contains(deviceName) || deviceName.isEmpty()) { return; } diff --git a/libraries/networking/src/udt/Connection.cpp b/libraries/networking/src/udt/Connection.cpp index 4798288a18..418dc8f417 100644 --- a/libraries/networking/src/udt/Connection.cpp +++ b/libraries/networking/src/udt/Connection.cpp @@ -192,12 +192,21 @@ void Connection::recordSentPackets(int wireSize, int payloadSize, _congestionControl->onPacketSent(wireSize, seqNum, timePoint); } -void Connection::recordRetransmission(int wireSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint) { - _stats.record(ConnectionStats::Stats::Retransmission); +void Connection::recordRetransmission(int wireSize, int payloadSize, + SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint) { + _stats.recordRetransmittedPackets(payloadSize, wireSize); _congestionControl->onPacketReSent(wireSize, seqNum, timePoint); } +void Connection::recordSentUnreliablePackets(int wireSize, int payloadSize) { + _stats.recordUnreliableSentPackets(payloadSize, wireSize); +} + +void Connection::recordReceivedUnreliablePackets(int wireSize, int payloadSize) { + _stats.recordUnreliableReceivedPackets(payloadSize, wireSize); +} + void Connection::sendACK() { SequenceNumber nextACKNumber = nextACK(); @@ -212,7 +221,7 @@ void Connection::sendACK() { // have the socket send off our packet _parentSocket->writeBasePacket(*_ackPacket, _destination); - _stats.record(ConnectionStats::Stats::SentACK); + _stats.recordSentACK(_ackPacket->getWireSize()); } SequenceNumber Connection::nextACK() const { @@ -270,7 +279,7 @@ bool Connection::processReceivedSequenceNumber(SequenceNumber sequenceNumber, in sendACK(); if (wasDuplicate) { - _stats.record(ConnectionStats::Stats::Duplicate); + _stats.recordDuplicatePackets(payloadSize, packetSize); } else { _stats.recordReceivedPackets(payloadSize, packetSize); } @@ -318,7 +327,7 @@ void Connection::processACK(ControlPacketPointer controlPacket) { controlPacket->readPrimitive(&ack); // update the total count of received ACKs - _stats.record(ConnectionStats::Stats::ReceivedACK); + _stats.recordReceivedACK(controlPacket->getWireSize()); // validate that this isn't a BS ACK if (ack > getSendQueue().getCurrentSequenceNumber()) { diff --git a/libraries/networking/src/udt/Connection.h b/libraries/networking/src/udt/Connection.h index 17e8a9b1f9..938ec36860 100644 --- a/libraries/networking/src/udt/Connection.h +++ b/libraries/networking/src/udt/Connection.h @@ -73,6 +73,9 @@ public: void setMaxBandwidth(int maxBandwidth); void sendHandshakeRequest(); + + void recordSentUnreliablePackets(int wireSize, int payloadSize); + void recordReceivedUnreliablePackets(int wireSize, int payloadSize); signals: void packetSent(); @@ -80,7 +83,8 @@ signals: private slots: void recordSentPackets(int wireSize, int payloadSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint); - void recordRetransmission(int wireSize, SequenceNumber sequenceNumber, p_high_resolution_clock::time_point timePoint); + void recordRetransmission(int wireSize, int payloadSize, SequenceNumber sequenceNumber, p_high_resolution_clock::time_point timePoint); + void queueInactive(); void queueTimeout(); diff --git a/libraries/networking/src/udt/ConnectionStats.cpp b/libraries/networking/src/udt/ConnectionStats.cpp index e30c588dba..188cc3114d 100644 --- a/libraries/networking/src/udt/ConnectionStats.cpp +++ b/libraries/networking/src/udt/ConnectionStats.cpp @@ -19,7 +19,6 @@ using namespace std::chrono; ConnectionStats::ConnectionStats() { auto now = duration_cast<microseconds>(system_clock::now().time_since_epoch()); _currentSample.startTime = now; - _total.startTime = now; } ConnectionStats::Stats ConnectionStats::sample() { @@ -35,79 +34,60 @@ ConnectionStats::Stats ConnectionStats::sample() { void ConnectionStats::record(Stats::Event event) { ++_currentSample.events[(int) event]; - ++_total.events[(int) event]; +} + +void ConnectionStats::recordSentACK(int size) { + record(Stats::SentACK); + recordSentPackets(0, size); +} + +void ConnectionStats::recordReceivedACK(int size) { + record(Stats::ReceivedACK); + recordReceivedPackets(0, size); } void ConnectionStats::recordSentPackets(int payload, int total) { ++_currentSample.sentPackets; - ++_total.sentPackets; - _currentSample.sentUtilBytes += payload; - _total.sentUtilBytes += payload; - _currentSample.sentBytes += total; - _total.sentBytes += total; } void ConnectionStats::recordReceivedPackets(int payload, int total) { ++_currentSample.receivedPackets; - ++_total.receivedPackets; - _currentSample.receivedUtilBytes += payload; - _total.receivedUtilBytes += payload; - _currentSample.receivedBytes += total; - _total.receivedBytes += total; +} + +void ConnectionStats::recordRetransmittedPackets(int payload, int total) { + ++_currentSample.retransmittedPackets; + _currentSample.retransmittedUtilBytes += payload; + _currentSample.retransmittedBytes += total; +} + +void ConnectionStats::recordDuplicatePackets(int payload, int total) { + ++_currentSample.duplicatePackets; + _currentSample.duplicateUtilBytes += payload; + _currentSample.duplicateBytes += total; } void ConnectionStats::recordUnreliableSentPackets(int payload, int total) { ++_currentSample.sentUnreliablePackets; - ++_total.sentUnreliablePackets; - _currentSample.sentUnreliableUtilBytes += payload; - _total.sentUnreliableUtilBytes += payload; - _currentSample.sentUnreliableBytes += total; - _total.sentUnreliableBytes += total; } void ConnectionStats::recordUnreliableReceivedPackets(int payload, int total) { ++_currentSample.receivedUnreliablePackets; - ++_total.receivedUnreliablePackets; - _currentSample.receivedUnreliableUtilBytes += payload; - _total.receivedUnreliableUtilBytes += payload; - - _currentSample.sentUnreliableBytes += total; - _total.receivedUnreliableBytes += total; -} - -static const double EWMA_CURRENT_SAMPLE_WEIGHT = 0.125; -static const double EWMA_PREVIOUS_SAMPLES_WEIGHT = 1.0 - EWMA_CURRENT_SAMPLE_WEIGHT; - -void ConnectionStats::recordSendRate(int sample) { - _currentSample.sendRate = sample; - _total.sendRate = (int)((_total.sendRate * EWMA_PREVIOUS_SAMPLES_WEIGHT) + (sample * EWMA_CURRENT_SAMPLE_WEIGHT)); -} - -void ConnectionStats::recordReceiveRate(int sample) { - _currentSample.receiveRate = sample; - _total.receiveRate = (int)((_total.receiveRate * EWMA_PREVIOUS_SAMPLES_WEIGHT) + (sample * EWMA_CURRENT_SAMPLE_WEIGHT)); -} - -void ConnectionStats::recordRTT(int sample) { - _currentSample.rtt = sample; - _total.rtt = (int)((_total.rtt * EWMA_PREVIOUS_SAMPLES_WEIGHT) + (sample * EWMA_CURRENT_SAMPLE_WEIGHT)); + _currentSample.receivedUnreliableBytes += total; } void ConnectionStats::recordCongestionWindowSize(int sample) { _currentSample.congestionWindowSize = sample; - _total.congestionWindowSize = (int)((_total.congestionWindowSize * EWMA_PREVIOUS_SAMPLES_WEIGHT) + (sample * EWMA_CURRENT_SAMPLE_WEIGHT)); } void ConnectionStats::recordPacketSendPeriod(int sample) { _currentSample.packetSendPeriod = sample; - _total.packetSendPeriod = (int)((_total.packetSendPeriod * EWMA_PREVIOUS_SAMPLES_WEIGHT) + (sample * EWMA_CURRENT_SAMPLE_WEIGHT)); } QDebug& operator<<(QDebug&& debug, const udt::ConnectionStats::Stats& stats) { @@ -117,13 +97,13 @@ QDebug& operator<<(QDebug&& debug, const udt::ConnectionStats::Stats& stats) { HIFI_LOG_EVENT(SentACK) HIFI_LOG_EVENT(ReceivedACK) HIFI_LOG_EVENT(ProcessedACK) - HIFI_LOG_EVENT(Retransmission) - HIFI_LOG_EVENT(Duplicate) ; #undef HIFI_LOG_EVENT debug << " Sent packets: " << stats.sentPackets; + debug << "\n Retransmitted packets: " << stats.retransmittedPackets; debug << "\n Received packets: " << stats.receivedPackets; + debug << "\n Duplicate packets: " << stats.duplicatePackets; debug << "\n Sent util bytes: " << stats.sentUtilBytes; debug << "\n Sent bytes: " << stats.sentBytes; debug << "\n Received bytes: " << stats.receivedBytes << "\n"; diff --git a/libraries/networking/src/udt/ConnectionStats.h b/libraries/networking/src/udt/ConnectionStats.h index 0fdd1636b3..8ff0ec90fd 100644 --- a/libraries/networking/src/udt/ConnectionStats.h +++ b/libraries/networking/src/udt/ConnectionStats.h @@ -14,6 +14,7 @@ #include <chrono> #include <array> +#include <stdint.h> namespace udt { @@ -24,8 +25,6 @@ public: SentACK, ReceivedACK, ProcessedACK, - Retransmission, - Duplicate, NumEvents }; @@ -40,19 +39,27 @@ public: Events events; // packet counts and sizes - int sentPackets { 0 }; - int receivedPackets { 0 }; - int sentUtilBytes { 0 }; - int receivedUtilBytes { 0 }; - int sentBytes { 0 }; - int receivedBytes { 0 }; + uint32_t sentPackets { 0 }; + uint32_t receivedPackets { 0 }; + uint32_t retransmittedPackets { 0 }; + uint32_t duplicatePackets { 0 }; + + uint64_t sentUtilBytes { 0 }; + uint64_t receivedUtilBytes { 0 }; + uint64_t retransmittedUtilBytes { 0 }; + uint64_t duplicateUtilBytes { 0 }; + + uint64_t sentBytes { 0 }; + uint64_t receivedBytes { 0 }; + uint64_t retransmittedBytes { 0 }; + uint64_t duplicateBytes { 0 }; - int sentUnreliablePackets { 0 }; - int receivedUnreliablePackets { 0 }; - int sentUnreliableUtilBytes { 0 }; - int receivedUnreliableUtilBytes { 0 }; - int sentUnreliableBytes { 0 }; - int receivedUnreliableBytes { 0 }; + uint32_t sentUnreliablePackets { 0 }; + uint32_t receivedUnreliablePackets { 0 }; + uint64_t sentUnreliableUtilBytes { 0 }; + uint64_t receivedUnreliableUtilBytes { 0 }; + uint64_t sentUnreliableBytes { 0 }; + uint64_t receivedUnreliableBytes { 0 }; // the following stats are trailing averages in the result, not totals int sendRate { 0 }; @@ -69,25 +76,26 @@ public: ConnectionStats(); Stats sample(); - Stats getTotalStats(); void record(Stats::Event event); - + + void recordSentACK(int size); + void recordReceivedACK(int size); + void recordSentPackets(int payload, int total); void recordReceivedPackets(int payload, int total); + + void recordRetransmittedPackets(int payload, int total); + void recordDuplicatePackets(int payload, int total); void recordUnreliableSentPackets(int payload, int total); void recordUnreliableReceivedPackets(int payload, int total); - - void recordSendRate(int sample); - void recordReceiveRate(int sample); - void recordRTT(int sample); + void recordCongestionWindowSize(int sample); void recordPacketSendPeriod(int sample); private: Stats _currentSample; - Stats _total; }; } diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index fad38d565b..642914cd56 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -33,14 +33,15 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityEdit: case PacketType::EntityData: case PacketType::EntityPhysics: - return static_cast<PacketVersion>(EntityVersion::EntityHostTypes); + return static_cast<PacketVersion>(EntityVersion::FixProtocolVersionBumpMismatch); case PacketType::EntityQuery: return static_cast<PacketVersion>(EntityQueryPacketVersion::ConicalFrustums); case PacketType::AvatarIdentity: case PacketType::AvatarData: + return static_cast<PacketVersion>(AvatarMixerPacketVersion::CollisionFlag); case PacketType::BulkAvatarData: case PacketType::KillAvatar: - return static_cast<PacketVersion>(AvatarMixerPacketVersion::JointTransScaled); + return static_cast<PacketVersion>(AvatarMixerPacketVersion::FasterAvatarEntities); case PacketType::MessagesData: return static_cast<PacketVersion>(MessageDataVersion::TextOrBinaryData); // ICE packets @@ -96,6 +97,9 @@ PacketVersion versionForPacketType(PacketType packetType) { return 22; case PacketType::EntityQueryInitialResultsComplete: return static_cast<PacketVersion>(EntityVersion::ParticleSpin); + case PacketType::BulkAvatarTraitsAck: + case PacketType::BulkAvatarTraits: + return static_cast<PacketVersion>(AvatarMixerPacketVersion::AvatarTraitsAck); default: return 22; } diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index b46eb3e9e4..f53a287d71 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -133,7 +133,7 @@ public: EntityQueryInitialResultsComplete, BulkAvatarTraits, AudioSoloRequest, - + BulkAvatarTraitsAck, NUM_PACKET_TYPE }; @@ -246,7 +246,16 @@ enum class EntityVersion : PacketVersion { ScriptGlmVectors, FixedLightSerialization, MaterialRepeat, - EntityHostTypes + EntityHostTypes, + CleanupProperties, + ImageEntities, + GridEntities, + MissingTextProperties, + GrabTraits, + MorePropertiesCleanup, + FixPropertiesFromCleanup, + UpdatedPolyLines, + FixProtocolVersionBumpMismatch }; enum class EntityScriptCallMethodVersion : PacketVersion { @@ -299,7 +308,11 @@ enum class AvatarMixerPacketVersion : PacketVersion { MigrateSkeletonURLToTraits, MigrateAvatarEntitiesToTraits, FarGrabJointsRedux, - JointTransScaled + JointTransScaled, + GrabTraits, + CollisionFlag, + AvatarTraitsAck, + FasterAvatarEntities }; enum class DomainConnectRequestVersion : PacketVersion { diff --git a/libraries/networking/src/udt/PacketQueue.cpp b/libraries/networking/src/udt/PacketQueue.cpp index 0560855ecb..b2e3000f74 100644 --- a/libraries/networking/src/udt/PacketQueue.cpp +++ b/libraries/networking/src/udt/PacketQueue.cpp @@ -16,7 +16,8 @@ using namespace udt; PacketQueue::PacketQueue(MessageNumber messageNumber) : _currentMessageNumber(messageNumber) { - _channels.emplace_back(new std::list<PacketPointer>()); + _channels.emplace_front(new std::list<PacketPointer>()); + _currentChannel = _channels.begin(); } MessageNumber PacketQueue::getNextMessageNumber() { @@ -27,21 +28,28 @@ MessageNumber PacketQueue::getNextMessageNumber() { bool PacketQueue::isEmpty() const { LockGuard locker(_packetsLock); + // Only the main channel and it is empty - return (_channels.size() == 1) && _channels.front()->empty(); + return _channels.size() == 1 && _channels.front()->empty(); } PacketQueue::PacketPointer PacketQueue::takePacket() { LockGuard locker(_packetsLock); + if (isEmpty()) { return PacketPointer(); } - // Find next non empty channel - if (_channels[nextIndex()]->empty()) { - nextIndex(); + // handle the case where we are looking at the first channel and it is empty + if (_currentChannel == _channels.begin() && (*_currentChannel)->empty()) { + ++_currentChannel; } - auto& channel = _channels[_currentIndex]; + + // at this point the current channel should always not be at the end and should also not be empty + Q_ASSERT(_currentChannel != _channels.end()); + + auto& channel = *_currentChannel; + Q_ASSERT(!channel->empty()); // Take front packet @@ -49,20 +57,28 @@ PacketQueue::PacketPointer PacketQueue::takePacket() { channel->pop_front(); // Remove now empty channel (Don't remove the main channel) - if (channel->empty() && _currentIndex != 0) { - channel->swap(*_channels.back()); - _channels.pop_back(); - --_currentIndex; + if (channel->empty() && _currentChannel != _channels.begin()) { + // erase the current channel and slide the iterator to the next channel + _currentChannel = _channels.erase(_currentChannel); + } else { + ++_currentChannel; + } + + // push forward our number of channels taken from + ++_channelsVisitedCount; + + // check if we need to restart back at the front channel (main) + // to respect our capped number of channels considered concurrently + static const int MAX_CHANNELS_SENT_CONCURRENTLY = 16; + + if (_currentChannel == _channels.end() || _channelsVisitedCount >= MAX_CHANNELS_SENT_CONCURRENTLY) { + _channelsVisitedCount = 0; + _currentChannel = _channels.begin(); } return packet; } -unsigned int PacketQueue::nextIndex() { - _currentIndex = (_currentIndex + 1) % _channels.size(); - return _currentIndex; -} - void PacketQueue::queuePacket(PacketPointer packet) { LockGuard locker(_packetsLock); _channels.front()->push_back(std::move(packet)); diff --git a/libraries/networking/src/udt/PacketQueue.h b/libraries/networking/src/udt/PacketQueue.h index bc4c5e3432..f696864e6b 100644 --- a/libraries/networking/src/udt/PacketQueue.h +++ b/libraries/networking/src/udt/PacketQueue.h @@ -30,8 +30,9 @@ class PacketQueue { using LockGuard = std::lock_guard<Mutex>; using PacketPointer = std::unique_ptr<Packet>; using PacketListPointer = std::unique_ptr<PacketList>; - using Channel = std::unique_ptr<std::list<PacketPointer>>; - using Channels = std::vector<Channel>; + using RawChannel = std::list<PacketPointer>; + using Channel = std::unique_ptr<RawChannel>; + using Channels = std::list<Channel>; public: PacketQueue(MessageNumber messageNumber = 0); @@ -47,16 +48,17 @@ public: private: MessageNumber getNextMessageNumber(); - unsigned int nextIndex(); - + MessageNumber _currentMessageNumber { 0 }; mutable Mutex _packetsLock; // Protects the packets to be sent. Channels _channels; // One channel per packet list + Main channel - unsigned int _currentIndex { 0 }; + + Channels::iterator _currentChannel; + unsigned int _channelsVisitedCount { 0 }; }; } -#endif // hifi_PacketQueue_h \ No newline at end of file +#endif // hifi_PacketQueue_h diff --git a/libraries/networking/src/udt/SendQueue.cpp b/libraries/networking/src/udt/SendQueue.cpp index 3178217a36..15841b5c21 100644 --- a/libraries/networking/src/udt/SendQueue.cpp +++ b/libraries/networking/src/udt/SendQueue.cpp @@ -61,6 +61,9 @@ private: Mutex2& _mutex2; }; +const microseconds SendQueue::MAXIMUM_ESTIMATED_TIMEOUT = seconds(5); +const microseconds SendQueue::MINIMUM_ESTIMATED_TIMEOUT = milliseconds(10); + std::unique_ptr<SendQueue> SendQueue::create(Socket* socket, HifiSockAddr destination, SequenceNumber currentSequenceNumber, MessageNumber currentMessageNumber, bool hasReceivedHandshakeACK) { Q_ASSERT_X(socket, "SendQueue::create", "Must be called with a valid Socket*"); @@ -404,6 +407,7 @@ bool SendQueue::maybeResendPacket() { Packet::ObfuscationLevel level = (Packet::ObfuscationLevel)(entry.first < 2 ? 0 : (entry.first - 2) % 4); auto wireSize = resendPacket.getWireSize(); + auto payloadSize = resendPacket.getPayloadSize(); auto sequenceNumber = it->first; if (level != Packet::NoObfuscation) { @@ -439,7 +443,8 @@ bool SendQueue::maybeResendPacket() { sentLocker.unlock(); } - emit packetRetransmitted(wireSize, sequenceNumber, p_high_resolution_clock::now()); + emit packetRetransmitted(wireSize, payloadSize, sequenceNumber, + p_high_resolution_clock::now()); // Signal that we did resend a packet return true; @@ -505,12 +510,8 @@ bool SendQueue::isInactive(bool attemptedToSendPacket) { auto estimatedTimeout = std::chrono::microseconds(_estimatedTimeout); - // cap our maximum estimated timeout to the already unreasonable 5 seconds - const auto MAXIMUM_ESTIMATED_TIMEOUT = std::chrono::seconds(5); - - if (estimatedTimeout > MAXIMUM_ESTIMATED_TIMEOUT) { - estimatedTimeout = MAXIMUM_ESTIMATED_TIMEOUT; - } + // Clamp timeout beween 10 ms and 5 s + estimatedTimeout = std::min(MAXIMUM_ESTIMATED_TIMEOUT, std::max(MINIMUM_ESTIMATED_TIMEOUT, estimatedTimeout)); // use our condition_variable_any to wait auto cvStatus = _emptyCondition.wait_for(locker, estimatedTimeout); diff --git a/libraries/networking/src/udt/SendQueue.h b/libraries/networking/src/udt/SendQueue.h index c1faac3b22..c1a2b59075 100644 --- a/libraries/networking/src/udt/SendQueue.h +++ b/libraries/networking/src/udt/SendQueue.h @@ -78,7 +78,7 @@ public slots: signals: void packetSent(int wireSize, int payloadSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint); - void packetRetransmitted(int wireSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint); + void packetRetransmitted(int wireSize, int payloadSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint); void queueInactive(); @@ -140,6 +140,9 @@ private: std::condition_variable_any _emptyCondition; std::chrono::high_resolution_clock::time_point _lastPacketSentAt; + + static const std::chrono::microseconds MAXIMUM_ESTIMATED_TIMEOUT; + static const std::chrono::microseconds MINIMUM_ESTIMATED_TIMEOUT; }; } diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index 25e6fae023..358acce694 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -129,6 +129,12 @@ qint64 Socket::writePacket(const Packet& packet, const HifiSockAddr& sockAddr) { sequenceNumber = ++_unreliableSequenceNumbers[sockAddr]; } + auto connection = findOrCreateConnection(sockAddr, true); + if (connection) { + connection->recordSentUnreliablePackets(packet.getWireSize(), + packet.getPayloadSize()); + } + // write the correct sequence number to the Packet here packet.writeSequenceNumber(sequenceNumber); @@ -392,9 +398,10 @@ void Socket::readPendingDatagrams() { // call our verification operator to see if this packet is verified if (!_packetFilterOperator || _packetFilterOperator(*packet)) { + auto connection = findOrCreateConnection(senderSockAddr, true); + if (packet->isReliable()) { // if this was a reliable packet then signal the matching connection with the sequence number - auto connection = findOrCreateConnection(senderSockAddr, true); if (!connection || !connection->processReceivedSequenceNumber(packet->getSequenceNumber(), packet->getDataSize(), @@ -406,6 +413,9 @@ void Socket::readPendingDatagrams() { #endif continue; } + } else if (connection) { + connection->recordReceivedUnreliablePackets(packet->getWireSize(), + packet->getPayloadSize()); } if (packet->isPartOfMessage()) { diff --git a/libraries/octree/src/OctreeEntitiesFileParser.cpp b/libraries/octree/src/OctreeEntitiesFileParser.cpp index 962f744c34..2f03eda286 100644 --- a/libraries/octree/src/OctreeEntitiesFileParser.cpp +++ b/libraries/octree/src/OctreeEntitiesFileParser.cpp @@ -9,13 +9,15 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "OctreeEntitiesFileParser.h" + #include <sstream> #include <cctype> + #include <QUuid> #include <QJsonDocument> #include <QJsonObject> -#include "OctreeEntitiesFileParser.h" using std::string; diff --git a/libraries/octree/src/OctreePacketData.cpp b/libraries/octree/src/OctreePacketData.cpp index 88e83c01c8..fd57f2fa3a 100644 --- a/libraries/octree/src/OctreePacketData.cpp +++ b/libraries/octree/src/OctreePacketData.cpp @@ -38,7 +38,11 @@ void OctreePacketData::changeSettings(bool enableCompression, unsigned int targe _enableCompression = enableCompression; _targetSize = targetSize; _uncompressedByteArray.resize(_targetSize); - _compressedByteArray.resize(_targetSize); + if (_enableCompression) { + _compressedByteArray.resize(_targetSize); + } else { + _compressedByteArray.resize(0); + } _uncompressed = (unsigned char*)_uncompressedByteArray.data(); _compressed = (unsigned char*)_compressedByteArray.data(); @@ -546,6 +550,17 @@ bool OctreePacketData::appendValue(const AACube& aaCube) { return success; } +bool OctreePacketData::appendValue(const QRect& value) { + const unsigned char* data = (const unsigned char*)&value; + int length = sizeof(QRect); + bool success = append(data, length); + if (success) { + _bytesOfValues += length; + _totalBytesOfValues += length; + } + return success; +} + bool OctreePacketData::appendPosition(const glm::vec3& value) { const unsigned char* data = (const unsigned char*)&value; int length = sizeof(value); @@ -575,13 +590,10 @@ bool OctreePacketData::appendRawData(QByteArray data) { AtomicUIntStat OctreePacketData::_compressContentTime { 0 }; AtomicUIntStat OctreePacketData::_compressContentCalls { 0 }; -bool OctreePacketData::compressContent() { +bool OctreePacketData::compressContent() { PerformanceWarning warn(false, "OctreePacketData::compressContent()", false, &_compressContentTime, &_compressContentCalls); - - // without compression, we always pass... - if (!_enableCompression) { - return true; - } + assert(_dirty); + assert(_enableCompression); _bytesInUseLastCheck = _bytesInUse; @@ -594,13 +606,13 @@ bool OctreePacketData::compressContent() { QByteArray compressedData = qCompress(uncompressedData, uncompressedSize, MAX_COMPRESSION); - if (compressedData.size() < (int)MAX_OCTREE_PACKET_DATA_SIZE) { + if (compressedData.size() < _compressedByteArray.size()) { _compressedBytes = compressedData.size(); memcpy(_compressed, compressedData.constData(), _compressedBytes); _dirty = false; success = true; } else { - qCWarning(octree) << "OctreePacketData::compressContent -- compressedData.size >= MAX_OCTREE_PACKET_DATA_SIZE"; + qCWarning(octree) << "OctreePacketData::compressContent -- compressedData.size >= " << _compressedByteArray.size(); assert(false); } return success; @@ -633,8 +645,7 @@ void OctreePacketData::loadFinalizedContent(const unsigned char* data, int lengt memcpy(_uncompressed, uncompressedData.constData(), _bytesInUse); } else { memcpy(_uncompressed, data, length); - memcpy(_compressed, data, length); - _bytesInUse = _compressedBytes = length; + _bytesInUse = length; } } else { if (_debug) { @@ -804,3 +815,8 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, AACube result = AACube(cube.corner, cube.scale); return sizeof(aaCubeData); } + +int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QRect& result) { + memcpy(&result, dataBytes, sizeof(result)); + return sizeof(result); +} \ No newline at end of file diff --git a/libraries/octree/src/OctreePacketData.h b/libraries/octree/src/OctreePacketData.h index 46726d83a6..bd1abf8744 100644 --- a/libraries/octree/src/OctreePacketData.h +++ b/libraries/octree/src/OctreePacketData.h @@ -34,6 +34,7 @@ #include <udt/PacketHeaders.h> #include "MaterialMappingMode.h" +#include "BillboardMode.h" #include "OctreeConstants.h" #include "OctreeElement.h" @@ -197,6 +198,9 @@ public: /// appends an AACube value to the end of the stream, may fail if new data stream is too long to fit in packet bool appendValue(const AACube& aaCube); + /// appends an QRect value to the end of the stream, may fail if new data stream is too long to fit in packet + bool appendValue(const QRect& rect); + /// appends a position to the end of the stream, may fail if new data stream is too long to fit in packet bool appendPosition(const glm::vec3& value); @@ -258,6 +262,7 @@ public: static int unpackDataFromBytes(const unsigned char* dataBytes, glm::quat& result) { int bytes = unpackOrientationQuatFromBytes(dataBytes, result); return bytes; } static int unpackDataFromBytes(const unsigned char* dataBytes, ShapeType& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } static int unpackDataFromBytes(const unsigned char* dataBytes, MaterialMappingMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } + static int unpackDataFromBytes(const unsigned char* dataBytes, BillboardMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } static int unpackDataFromBytes(const unsigned char* dataBytes, glm::vec2& result); static int unpackDataFromBytes(const unsigned char* dataBytes, glm::vec3& result); static int unpackDataFromBytes(const unsigned char* dataBytes, glm::u8vec3& result); @@ -269,6 +274,7 @@ public: static int unpackDataFromBytes(const unsigned char* dataBytes, QVector<bool>& result); static int unpackDataFromBytes(const unsigned char* dataBytes, QByteArray& result); static int unpackDataFromBytes(const unsigned char* dataBytes, AACube& result); + static int unpackDataFromBytes(const unsigned char* dataBytes, QRect& result); private: /// appends raw bytes, might fail if byte would cause packet to be too large diff --git a/libraries/physics/CMakeLists.txt b/libraries/physics/CMakeLists.txt index 96a55c8477..5249ed2950 100644 --- a/libraries/physics/CMakeLists.txt +++ b/libraries/physics/CMakeLists.txt @@ -1,11 +1,16 @@ set(TARGET_NAME physics) setup_hifi_library() -link_hifi_libraries(shared task workload fbx entities graphics) +link_hifi_libraries(shared task workload fbx entities graphics shaders) include_hifi_library_headers(networking) include_hifi_library_headers(gpu) include_hifi_library_headers(avatars) include_hifi_library_headers(audio) include_hifi_library_headers(octree) include_hifi_library_headers(animation) +include_hifi_library_headers(model-networking) +include_hifi_library_headers(image) +include_hifi_library_headers(ktx) +include_hifi_library_headers(gpu) +include_hifi_library_headers(hfm) target_bullet() diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 4b635ef0be..9814e358c3 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -91,7 +91,6 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer _serverRotation = localTransform.getRotation(); _serverAcceleration = _entity->getAcceleration(); _serverActionData = _entity->getDynamicData(); - } EntityMotionState::~EntityMotionState() { @@ -307,6 +306,8 @@ const btCollisionShape* EntityMotionState::computeNewShape() { return getShapeManager()->getShape(shapeInfo); } +const uint8_t MAX_NUM_INACTIVE_UPDATES = 20; + bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { // NOTE: this method is only ever called when the entity simulation is locally owned DETAILED_PROFILE_RANGE(simulation_physics, "CheckOutOfSync"); @@ -316,15 +317,10 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { // TODO: need to be able to detect when logic dictates we *decrease* priority // WIP: print info whenever _bidPriority mismatches what is known to the entity - if (_entity->dynamicDataNeedsTransmit()) { - return true; - } - int numSteps = simulationStep - _lastStep; float dt = (float)(numSteps) * PHYSICS_ENGINE_FIXED_SUBSTEP; if (_numInactiveUpdates > 0) { - const uint8_t MAX_NUM_INACTIVE_UPDATES = 20; if (_numInactiveUpdates > MAX_NUM_INACTIVE_UPDATES) { // clear local ownership (stop sending updates) and let the server clear itself _entity->clearSimulationOwnership(); @@ -452,8 +448,13 @@ void EntityMotionState::updateSendVelocities() { if (!_body->isKinematicObject()) { clearObjectVelocities(); } - // we pretend we sent the inactive update for this object - _numInactiveUpdates = 1; + if (_entity->getEntityHostType() == entity::HostType::AVATAR) { + // AvatarEntities only ever need to send one update (their updates are sent over a lossless protocol) + // so we set the count to the max to prevent resends + _numInactiveUpdates = MAX_NUM_INACTIVE_UPDATES; + } else { + ++_numInactiveUpdates; + } } else { glm::vec3 gravity = _entity->getGravity(); diff --git a/libraries/physics/src/ObjectActionTractor.cpp b/libraries/physics/src/ObjectActionTractor.cpp index c5641ad347..a46aac3f29 100644 --- a/libraries/physics/src/ObjectActionTractor.cpp +++ b/libraries/physics/src/ObjectActionTractor.cpp @@ -33,36 +33,53 @@ ObjectActionTractor::ObjectActionTractor(const QUuid& id, EntityItemPointer owne _rotationalTargetSet(true), _linearVelocityTarget(0.0f) { - #if WANT_DEBUG +#if WANT_DEBUG qCDebug(physics) << "ObjectActionTractor::ObjectActionTractor"; - #endif +#endif } ObjectActionTractor::~ObjectActionTractor() { - #if WANT_DEBUG +#if WANT_DEBUG qCDebug(physics) << "ObjectActionTractor::~ObjectActionTractor"; - #endif +#endif } bool ObjectActionTractor::getTarget(float deltaTimeStep, glm::quat& rotation, glm::vec3& position, - glm::vec3& linearVelocity, glm::vec3& angularVelocity, - float& linearTimeScale, float& angularTimeScale) { - bool success { true }; - EntityItemPointer other = std::dynamic_pointer_cast<EntityItem>(getOther()); - withReadLock([&]{ + glm::vec3& linearVelocity, glm::vec3& angularVelocity, + float& linearTimeScale, float& angularTimeScale) { + SpatiallyNestablePointer other = getOther(); + return resultWithReadLock<bool>([&]{ linearTimeScale = _linearTimeScale; angularTimeScale = _angularTimeScale; if (!_otherID.isNull()) { - if (other && other->isReadyToComputeShape()) { - rotation = _desiredRotationalTarget * other->getWorldOrientation(); - position = other->getWorldOrientation() * _desiredPositionalTarget + other->getWorldPosition(); + bool otherIsReady { true }; + if (other && other->getNestableType() == NestableType::Entity) { + EntityItemPointer otherEntity = std::static_pointer_cast<EntityItem>(other); + otherIsReady = otherEntity->isReadyToComputeShape(); + } + if (other && otherIsReady) { + bool success; + glm::vec3 otherWorldPosition = other->getWorldPosition(_otherJointIndex, success); + if (!success) { + linearTimeScale = FLT_MAX; + angularTimeScale = FLT_MAX; + return false; + } + glm::quat otherWorldOrientation = other->getWorldOrientation(_otherJointIndex, success); + if (!success) { + linearTimeScale = FLT_MAX; + angularTimeScale = FLT_MAX; + return false; + } + rotation = otherWorldOrientation * _desiredRotationalTarget; + position = otherWorldOrientation * _desiredPositionalTarget + otherWorldPosition; } else { // we should have an "other" but can't find it, or its collision shape isn't loaded, // so disable the tractor. linearTimeScale = FLT_MAX; angularTimeScale = FLT_MAX; - success = false; + return false; } } else { rotation = _desiredRotationalTarget; @@ -70,8 +87,8 @@ bool ObjectActionTractor::getTarget(float deltaTimeStep, glm::quat& rotation, gl } linearVelocity = glm::vec3(); angularVelocity = glm::vec3(); + return true; }); - return success; } bool ObjectActionTractor::prepareForTractorUpdate(btScalar deltaTimeStep) { @@ -108,9 +125,9 @@ bool ObjectActionTractor::prepareForTractorUpdate(btScalar deltaTimeStep) { float linearTimeScale; float angularTimeScale; bool success = tractorAction->getTarget(deltaTimeStep, - rotationForAction, positionForAction, - linearVelocityForAction, angularVelocityForAction, - linearTimeScale, angularTimeScale); + rotationForAction, positionForAction, + linearVelocityForAction, angularVelocityForAction, + linearTimeScale, angularTimeScale); if (success) { if (angularTimeScale < MAX_TRACTOR_TIMESCALE) { angularTractorCount++; @@ -239,7 +256,7 @@ bool ObjectActionTractor::updateArguments(QVariantMap arguments) { glm::quat rotationalTarget; float angularTimeScale; QUuid otherID; - + int otherJointIndex; bool needUpdate = false; bool somethingChanged = ObjectDynamic::updateArguments(arguments); @@ -274,18 +291,25 @@ bool ObjectActionTractor::updateArguments(QVariantMap arguments) { } ok = true; - otherID = QUuid(EntityDynamicInterface::extractStringArgument("tractor action", - arguments, "otherID", ok, false)); + otherID = QUuid(EntityDynamicInterface::extractStringArgument("tractor action", arguments, "otherID", ok, false)); if (!ok) { otherID = _otherID; } + ok = true; + otherJointIndex = EntityDynamicInterface::extractIntegerArgument("tractor action", arguments, + "otherJointIndex", ok, false); + if (!ok) { + otherJointIndex = _otherJointIndex; + } + if (somethingChanged || positionalTarget != _desiredPositionalTarget || linearTimeScale != _linearTimeScale || rotationalTarget != _desiredRotationalTarget || angularTimeScale != _angularTimeScale || - otherID != _otherID) { + otherID != _otherID || + otherJointIndex != _otherJointIndex) { // something changed needUpdate = true; } @@ -298,6 +322,7 @@ bool ObjectActionTractor::updateArguments(QVariantMap arguments) { _desiredRotationalTarget = rotationalTarget; _angularTimeScale = glm::max(MIN_TIMESCALE, glm::abs(angularTimeScale)); _otherID = otherID; + _otherJointIndex = otherJointIndex; _active = true; auto ownerEntity = _ownerEntity.lock(); @@ -313,20 +338,22 @@ bool ObjectActionTractor::updateArguments(QVariantMap arguments) { } /**jsdoc - * The <code>"tractor"</code> {@link Entities.ActionType|ActionType} moves and rotates an entity to a target position and + * The <code>"tractor"</code> {@link Entities.ActionType|ActionType} moves and rotates an entity to a target position and * orientation, optionally relative to another entity. * It has arguments in addition to the common {@link Entities.ActionArguments|ActionArguments}. * * @typedef {object} Entities.ActionArguments-Tractor * @property {Vec3} targetPosition=0,0,0 - The target position. * @property {Quat} targetRotation=0,0,0,1 - The target rotation. - * @property {Uuid} otherID=null - If an entity ID, the <code>targetPosition</code> and <code>targetRotation</code> are + * @property {Uuid} otherID=null - If an entity ID, the <code>targetPosition</code> and <code>targetRotation</code> are * relative to this entity's position and rotation. + * @property {Uuid} otherJointIndex=null - If an entity JointIndex, the <code>targetPosition</code> and + * <code>targetRotation</code> are relative to this entity's joint's position and rotation. * @property {number} linearTimeScale=3.4e+38 - Controls how long it takes for the entity's position to catch up with the - * target position. The value is the time for the action to catch up to 1/e = 0.368 of the target value, where the action + * target position. The value is the time for the action to catch up to 1/e = 0.368 of the target value, where the action * is applied using an exponential decay. * @property {number} angularTimeScale=3.4e+38 - Controls how long it takes for the entity's orientation to catch up with the - * target orientation. The value is the time for the action to catch up to 1/e = 0.368 of the target value, where the + * target orientation. The value is the time for the action to catch up to 1/e = 0.368 of the target value, where the * action is applied using an exponential decay. */ QVariantMap ObjectActionTractor::getArguments() { @@ -339,6 +366,7 @@ QVariantMap ObjectActionTractor::getArguments() { arguments["angularTimeScale"] = _angularTimeScale; arguments["otherID"] = _otherID; + arguments["otherJointIndex"] = _otherJointIndex; }); return arguments; } @@ -354,6 +382,7 @@ void ObjectActionTractor::serializeParameters(QDataStream& dataStream) const { dataStream << localTimeToServerTime(_expires); dataStream << _tag; dataStream << _otherID; + dataStream << _otherJointIndex; }); } @@ -387,6 +416,7 @@ void ObjectActionTractor::deserializeParameters(QByteArray serializedArguments, dataStream >> _tag; dataStream >> _otherID; + dataStream >> _otherJointIndex; _active = true; }); diff --git a/libraries/physics/src/ObjectDynamic.h b/libraries/physics/src/ObjectDynamic.h index bfee79aca9..f41d6cd51d 100644 --- a/libraries/physics/src/ObjectDynamic.h +++ b/libraries/physics/src/ObjectDynamic.h @@ -66,6 +66,7 @@ protected: EntityItemID _otherID; SpatiallyNestableWeakPointer _other; SpatiallyNestablePointer getOther(); + int _otherJointIndex { -1 }; private: qint64 getEntityServerClockSkew() const; diff --git a/libraries/plugins/src/plugins/PluginManager.cpp b/libraries/plugins/src/plugins/PluginManager.cpp index 5473ab723d..a546f69d23 100644 --- a/libraries/plugins/src/plugins/PluginManager.cpp +++ b/libraries/plugins/src/plugins/PluginManager.cpp @@ -44,33 +44,24 @@ PluginManagerPointer PluginManager::getInstance() { return DependencyManager::get<PluginManager>(); } -QString getPluginNameFromMetaData(QJsonObject object) { +QString getPluginNameFromMetaData(const QJsonObject& object) { static const char* METADATA_KEY = "MetaData"; static const char* NAME_KEY = "name"; - - if (!object.contains(METADATA_KEY) || !object[METADATA_KEY].isObject()) { - return QString(); - } - - auto metaDataObject = object[METADATA_KEY].toObject(); - - if (!metaDataObject.contains(NAME_KEY) || !metaDataObject[NAME_KEY].isString()) { - return QString(); - } - - return metaDataObject[NAME_KEY].toString(); + return object[METADATA_KEY][NAME_KEY].toString(""); } -QString getPluginIIDFromMetaData(QJsonObject object) { +QString getPluginIIDFromMetaData(const QJsonObject& object) { static const char* IID_KEY = "IID"; - - if (!object.contains(IID_KEY) || !object[IID_KEY].isString()) { - return QString(); - } - - return object[IID_KEY].toString(); + return object[IID_KEY].toString(""); } +int getPluginInterfaceVersionFromMetaData(const QJsonObject& object) { + static const QString METADATA_KEY = "MetaData"; + static const QString NAME_KEY = "version"; + return object[METADATA_KEY][NAME_KEY].toInt(0); +} + + QStringList preferredDisplayPlugins; QStringList disabledDisplays; QStringList disabledInputs; @@ -117,10 +108,16 @@ const LoaderList& getLoadedPlugins() { QSharedPointer<QPluginLoader> loader(new QPluginLoader(pluginPath + plugin)); if (isDisabled(loader->metaData())) { - qWarning() << "Plugin" << qPrintable(plugin) << "is disabled"; + qCWarning(plugins) << "Plugin" << qPrintable(plugin) << "is disabled"; // Skip this one, it's disabled continue; } + if (getPluginInterfaceVersionFromMetaData(loader->metaData()) != HIFI_PLUGIN_INTERFACE_VERSION) { + qCWarning(plugins) << "Plugin" << qPrintable(plugin) << "interface version doesn't match, not loading:" + << getPluginInterfaceVersionFromMetaData(loader->metaData()) + << "doesn't match" << HIFI_PLUGIN_INTERFACE_VERSION; + continue; + } if (loader->load()) { qCDebug(plugins) << "Plugin" << qPrintable(plugin) << "loaded successfully"; diff --git a/libraries/plugins/src/plugins/PluginManager.h b/libraries/plugins/src/plugins/PluginManager.h index dc3e3fd856..593daeca11 100644 --- a/libraries/plugins/src/plugins/PluginManager.h +++ b/libraries/plugins/src/plugins/PluginManager.h @@ -62,3 +62,12 @@ private: DisplayPluginList _displayPlugins; InputPluginList _inputPlugins; }; + +// TODO: we should define this value in CMake, and then use CMake +// templating to generate the individual plugin.json files, so that we +// don't have to update every plugin.json file whenever we update this +// value. The value should match "version" in +// plugins/*/src/plugin.json +// plugins/oculus/src/oculus.json +// etc +static const int HIFI_PLUGIN_INTERFACE_VERSION = 1; diff --git a/libraries/pointers/src/Pick.cpp b/libraries/pointers/src/Pick.cpp index 7ac53a2643..e95721c04b 100644 --- a/libraries/pointers/src/Pick.cpp +++ b/libraries/pointers/src/Pick.cpp @@ -7,8 +7,6 @@ // #include "Pick.h" -const PickFilter PickFilter::NOTHING; - int pickTypeMetaTypeId = qRegisterMetaType<PickQuery::PickType>("PickType"); PickQuery::PickQuery(const PickFilter& filter, const float maxDistance, const bool enabled) : @@ -41,7 +39,8 @@ bool PickQuery::isEnabled() const { void PickQuery::setPrecisionPicking(bool precisionPicking) { withWriteLock([&] { - _filter.setFlag(PickFilter::PICK_COARSE, !precisionPicking); + _filter.setFlag(PickFilter::PRECISE, precisionPicking); + _filter.setFlag(PickFilter::COARSE, !precisionPicking); }); } diff --git a/libraries/pointers/src/Pick.h b/libraries/pointers/src/Pick.h index 9ca0f14c5f..857a72caa8 100644 --- a/libraries/pointers/src/Pick.h +++ b/libraries/pointers/src/Pick.h @@ -18,6 +18,7 @@ #include <shared/ReadWriteLockable.h> #include <TransformNode.h> +#include <PickFilter.h> enum IntersectionType { NONE = 0, @@ -27,84 +28,6 @@ enum IntersectionType { HUD }; -class PickFilter { -public: - enum FlagBit { - PICK_ENTITIES = 0, - PICK_OVERLAYS, - PICK_AVATARS, - PICK_HUD, - - PICK_COARSE, // if not set, does precise intersection, otherwise, doesn't - - PICK_INCLUDE_INVISIBLE, // if not set, will not intersect invisible elements, otherwise, intersects both visible and invisible elements - PICK_INCLUDE_NONCOLLIDABLE, // if not set, will not intersect noncollidable elements, otherwise, intersects both collidable and noncollidable elements - - // NOT YET IMPLEMENTED - PICK_ALL_INTERSECTIONS, // if not set, returns closest intersection, otherwise, returns list of all intersections - - NUM_FLAGS, // Not a valid flag - }; - typedef std::bitset<NUM_FLAGS> Flags; - - // The key is the Flags - Flags _flags; - - PickFilter() {} - PickFilter(const Flags& flags) : _flags(flags) {} - - bool operator== (const PickFilter& rhs) const { return _flags == rhs._flags; } - bool operator!= (const PickFilter& rhs) const { return _flags != rhs._flags; } - - void setFlag(FlagBit flag, bool value) { _flags[flag] = value; } - - bool doesPickNothing() const { return _flags == NOTHING._flags; } - bool doesPickEntities() const { return _flags[PICK_ENTITIES]; } - bool doesPickOverlays() const { return _flags[PICK_OVERLAYS]; } - bool doesPickAvatars() const { return _flags[PICK_AVATARS]; } - bool doesPickHUD() const { return _flags[PICK_HUD]; } - - bool doesPickCoarse() const { return _flags[PICK_COARSE]; } - bool doesPickInvisible() const { return _flags[PICK_INCLUDE_INVISIBLE]; } - bool doesPickNonCollidable() const { return _flags[PICK_INCLUDE_NONCOLLIDABLE]; } - - bool doesWantAllIntersections() const { return _flags[PICK_ALL_INTERSECTIONS]; } - - // Helpers for RayPickManager - Flags getEntityFlags() const { - unsigned int toReturn = getBitMask(PICK_ENTITIES); - if (doesPickInvisible()) { - toReturn |= getBitMask(PICK_INCLUDE_INVISIBLE); - } - if (doesPickNonCollidable()) { - toReturn |= getBitMask(PICK_INCLUDE_NONCOLLIDABLE); - } - if (doesPickCoarse()) { - toReturn |= getBitMask(PICK_COARSE); - } - return Flags(toReturn); - } - Flags getOverlayFlags() const { - unsigned int toReturn = getBitMask(PICK_OVERLAYS); - if (doesPickInvisible()) { - toReturn |= getBitMask(PICK_INCLUDE_INVISIBLE); - } - if (doesPickNonCollidable()) { - toReturn |= getBitMask(PICK_INCLUDE_NONCOLLIDABLE); - } - if (doesPickCoarse()) { - toReturn |= getBitMask(PICK_COARSE); - } - return Flags(toReturn); - } - Flags getAvatarFlags() const { return Flags(getBitMask(PICK_AVATARS)); } - Flags getHUDFlags() const { return Flags(getBitMask(PICK_HUD)); } - - static constexpr unsigned int getBitMask(FlagBit bit) { return 1 << bit; } - - static const PickFilter NOTHING; -}; - class PickResult { public: PickResult() {} diff --git a/libraries/pointers/src/PickCacheOptimizer.h b/libraries/pointers/src/PickCacheOptimizer.h index c74e84937b..e91283f02c 100644 --- a/libraries/pointers/src/PickCacheOptimizer.h +++ b/libraries/pointers/src/PickCacheOptimizer.h @@ -85,10 +85,10 @@ QVector4D PickCacheOptimizer<T>::update(std::unordered_map<uint32_t, std::shared T mathematicalPick = pick->getMathematicalPick(); PickResultPointer res = pick->getDefaultResult(mathematicalPick.toVariantMap()); - if (!pick->isEnabled() || pick->getFilter().doesPickNothing() || pick->getMaxDistance() < 0.0f || !mathematicalPick) { + if (!pick->isEnabled() || pick->getMaxDistance() < 0.0f || !mathematicalPick) { pick->setPickResult(res); } else { - if (pick->getFilter().doesPickEntities()) { + if (pick->getFilter().doesPickDomainEntities() || pick->getFilter().doesPickAvatarEntities()) { PickCacheKey entityKey = { pick->getFilter().getEntityFlags(), pick->getIncludeItems(), pick->getIgnoreItems() }; if (!checkAndCompareCachedResults(mathematicalPick, results, res, entityKey)) { PickResultPointer entityRes = pick->getEntityIntersection(mathematicalPick); @@ -99,7 +99,7 @@ QVector4D PickCacheOptimizer<T>::update(std::unordered_map<uint32_t, std::shared } } - if (pick->getFilter().doesPickOverlays()) { + if (pick->getFilter().doesPickLocalEntities()) { PickCacheKey overlayKey = { pick->getFilter().getOverlayFlags(), pick->getIncludeItems(), pick->getIgnoreItems() }; if (!checkAndCompareCachedResults(mathematicalPick, results, res, overlayKey)) { PickResultPointer overlayRes = pick->getOverlayIntersection(mathematicalPick); diff --git a/libraries/procedural/src/procedural/Procedural.cpp b/libraries/procedural/src/procedural/Procedural.cpp index 7095732d53..05cbde374d 100644 --- a/libraries/procedural/src/procedural/Procedural.cpp +++ b/libraries/procedural/src/procedural/Procedural.cpp @@ -39,8 +39,10 @@ static const std::string PROCEDURAL_BLOCK = "//PROCEDURAL_BLOCK"; static const std::string PROCEDURAL_VERSION = "//PROCEDURAL_VERSION"; bool operator==(const ProceduralData& a, const ProceduralData& b) { - return ((a.version == b.version) && (a.shaderUrl == b.shaderUrl) && (a.uniforms == b.uniforms) && - (a.channels == b.channels)); + return ((a.version == b.version) && + (a.shaderUrl == b.shaderUrl) && + (a.uniforms == b.uniforms) && + (a.channels == b.channels)); } QJsonValue ProceduralData::getProceduralData(const QString& proceduralJson) { @@ -57,9 +59,9 @@ QJsonValue ProceduralData::getProceduralData(const QString& proceduralJson) { return doc.object()[PROCEDURAL_USER_DATA_KEY]; } -ProceduralData ProceduralData::parse(const QString& userDataJson) { +ProceduralData ProceduralData::parse(const QString& proceduralData) { ProceduralData result; - result.parse(getProceduralData(userDataJson).toObject()); + result.parse(getProceduralData(proceduralData).toObject()); return result; } @@ -73,7 +75,7 @@ void ProceduralData::parse(const QJsonObject& proceduralData) { if (versionJson.isDouble()) { version = (uint8_t)(floor(versionJson.toDouble())); // invalid version - if (!(version == 1 || version == 2)) { + if (!(version == 1 || version == 2 || version == 3 || version == 4)) { return; } } else { @@ -102,20 +104,27 @@ void ProceduralData::parse(const QJsonObject& proceduralData) { //} Procedural::Procedural() { - _transparentState->setCullMode(gpu::State::CULL_NONE); + _opaqueState->setCullMode(gpu::State::CULL_BACK); + _opaqueState->setDepthTest(true, true, gpu::LESS_EQUAL); + _opaqueState->setBlendFunction(false, + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + + _transparentState->setCullMode(gpu::State::CULL_BACK); _transparentState->setDepthTest(true, true, gpu::LESS_EQUAL); - _transparentState->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + _transparentState->setBlendFunction(true, + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); _standardInputsBuffer = std::make_shared<gpu::Buffer>(sizeof(StandardInputs), nullptr); } void Procedural::setProceduralData(const ProceduralData& proceduralData) { + std::lock_guard<std::mutex> lock(_mutex); if (proceduralData == _data) { return; } - _dirty = true; _enabled = false; if (proceduralData.version != _data.version) { @@ -124,6 +133,10 @@ void Procedural::setProceduralData(const ProceduralData& proceduralData) { } if (proceduralData.uniforms != _data.uniforms) { + // If the uniform keys changed, we need to recreate the whole shader to handle the reflection + if (proceduralData.uniforms.keys() != _data.uniforms.keys()) { + _shaderDirty = true; + } _data.uniforms = proceduralData.uniforms; _uniformsDirty = true; } @@ -147,16 +160,14 @@ void Procedural::setProceduralData(const ProceduralData& proceduralData) { if (proceduralData.shaderUrl != _data.shaderUrl) { _data.shaderUrl = proceduralData.shaderUrl; - _shaderDirty = true; const auto& shaderUrl = _data.shaderUrl; + + _shaderDirty = true; _networkShader.reset(); _shaderPath.clear(); + _shaderSource.clear(); - if (shaderUrl.isEmpty()) { - return; - } - - if (!shaderUrl.isValid()) { + if (shaderUrl.isEmpty() || !shaderUrl.isValid()) { return; } @@ -180,6 +191,8 @@ bool Procedural::isReady() const { return false; #endif + std::lock_guard<std::mutex> lock(_mutex); + if (!_enabled) { return false; } @@ -209,10 +222,11 @@ bool Procedural::isReady() const { } void Procedural::prepare(gpu::Batch& batch, - const glm::vec3& position, - const glm::vec3& size, - const glm::quat& orientation, - const glm::vec4& color) { + const glm::vec3& position, + const glm::vec3& size, + const glm::quat& orientation, + const ProceduralProgramKey key) { + std::lock_guard<std::mutex> lock(_mutex); _entityDimensions = size; _entityPosition = position; _entityOrientation = glm::mat3_cast(orientation); @@ -225,62 +239,56 @@ void Procedural::prepare(gpu::Batch& batch, _shaderDirty = true; _shaderModified = lastModified; } - } else if (_networkShader && _networkShader->isLoaded()) { + } else if (_shaderSource.isEmpty() && _networkShader && _networkShader->isLoaded()) { _shaderSource = _networkShader->_source; + _shaderDirty = true; } - if (!_opaquePipeline || !_transparentPipeline || _shaderDirty) { + if (_shaderDirty) { + _proceduralPipelines.clear(); + } + + auto pipeline = _proceduralPipelines.find(key); + bool recompiledShader = false; + if (pipeline == _proceduralPipelines.end()) { if (!_vertexShader) { _vertexShader = gpu::Shader::createVertex(_vertexSource); } + gpu::Shader::Source& fragmentSource = (key.isTransparent() && _transparentFragmentSource.valid()) ? _transparentFragmentSource : _opaqueFragmentSource; + // Build the fragment shader - _opaqueFragmentSource.replacements.clear(); - if (_data.version == 1) { - _opaqueFragmentSource.replacements[PROCEDURAL_VERSION] = "#define PROCEDURAL_V1 1"; - } else if (_data.version == 2) { - _opaqueFragmentSource.replacements[PROCEDURAL_VERSION] = "#define PROCEDURAL_V2 1"; - } - _opaqueFragmentSource.replacements[PROCEDURAL_BLOCK] = _shaderSource.toStdString(); - _transparentFragmentSource.replacements = _opaqueFragmentSource.replacements; + fragmentSource.replacements.clear(); + fragmentSource.replacements[PROCEDURAL_VERSION] = "#define PROCEDURAL_V" + std::to_string(_data.version); + fragmentSource.replacements[PROCEDURAL_BLOCK] = _shaderSource.toStdString(); // Set any userdata specified uniforms int customSlot = procedural::slot::uniform::Custom; for (const auto& key : _data.uniforms.keys()) { std::string uniformName = key.toLocal8Bit().data(); - _opaqueFragmentSource.reflection.uniforms[uniformName] = customSlot; - _transparentFragmentSource.reflection.uniforms[uniformName] = customSlot; + fragmentSource.reflection.uniforms[uniformName] = customSlot; ++customSlot; } // Leave this here for debugging - // qCDebug(procedural) << "FragmentShader:\n" << fragmentShaderSource.c_str(); + //qCDebug(proceduralLog) << "FragmentShader:\n" << fragmentSource.getSource(shader::Dialect::glsl450, shader::Variant::Mono).c_str(); + + gpu::ShaderPointer fragmentShader = gpu::Shader::createPixel(fragmentSource); + gpu::ShaderPointer program = gpu::Shader::createProgram(_vertexShader, fragmentShader); + + _proceduralPipelines[key] = gpu::Pipeline::create(program, key.isTransparent() ? _transparentState : _opaqueState); - // TODO: THis is a simple fix, we need a cleaner way to provide the "hosting" program for procedural custom shaders to be defined together with the required bindings. - _opaqueFragmentShader = gpu::Shader::createPixel(_opaqueFragmentSource); - _opaqueShader = gpu::Shader::createProgram(_vertexShader, _opaqueFragmentShader); - _opaquePipeline = gpu::Pipeline::create(_opaqueShader, _opaqueState); - if (_transparentFragmentSource.valid()) { - _transparentFragmentShader = gpu::Shader::createPixel(_transparentFragmentSource); - _transparentShader = gpu::Shader::createProgram(_vertexShader, _transparentFragmentShader); - _transparentPipeline = gpu::Pipeline::create(_transparentShader, _transparentState); - } else { - _transparentFragmentShader = _opaqueFragmentShader; - _transparentShader = _opaqueShader; - _transparentPipeline = _opaquePipeline; - } _start = usecTimestampNow(); _frameCount = 0; + recompiledShader = true; } - bool transparent = color.a < 1.0f; - batch.setPipeline(transparent ? _transparentPipeline : _opaquePipeline); + batch.setPipeline(recompiledShader ? _proceduralPipelines[key] : pipeline->second); - if (_shaderDirty || _uniformsDirty || _prevTransparent != transparent) { - setupUniforms(transparent); + if (_shaderDirty || _uniformsDirty) { + setupUniforms(); } - _prevTransparent = transparent; _shaderDirty = _uniformsDirty = false; for (auto lambda : _uniforms) { @@ -290,8 +298,7 @@ void Procedural::prepare(gpu::Batch& batch, static gpu::Sampler sampler; static std::once_flag once; std::call_once(once, [&] { - gpu::Sampler::Desc desc; - desc._filter = gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR; + sampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR); }); for (size_t i = 0; i < MAX_PROCEDURAL_TEXTURE_CHANNELS; ++i) { @@ -301,19 +308,17 @@ void Procedural::prepare(gpu::Batch& batch, gpuTexture->setSampler(sampler); gpuTexture->setAutoGenerateMips(true); } - batch.setResourceTexture((gpu::uint32)i, gpuTexture); + batch.setResourceTexture((gpu::uint32)(procedural::slot::texture::Channel0 + i), gpuTexture); } } } -void Procedural::setupUniforms(bool transparent) { +void Procedural::setupUniforms() { _uniforms.clear(); - auto customUniformCount = _data.uniforms.keys().size(); // Set any userdata specified uniforms - for (int i = 0; i < customUniformCount; ++i) { - int slot = procedural::slot::uniform::Custom + i; - QString key = _data.uniforms.keys().at(i); + int slot = procedural::slot::uniform::Custom; + for (const auto& key : _data.uniforms.keys()) { std::string uniformName = key.toLocal8Bit().data(); QJsonValue value = _data.uniforms[key]; if (value.isDouble()) { @@ -360,6 +365,7 @@ void Procedural::setupUniforms(bool transparent) { } } } + slot++; } _uniforms.push_back([=](gpu::Batch& batch) { @@ -398,7 +404,7 @@ void Procedural::setupUniforms(bool transparent) { }); } -glm::vec4 Procedural::getColor(const glm::vec4& entityColor) { +glm::vec4 Procedural::getColor(const glm::vec4& entityColor) const { if (_data.version == 1) { return glm::vec4(1); } diff --git a/libraries/procedural/src/procedural/Procedural.h b/libraries/procedural/src/procedural/Procedural.h index 781ac25249..3e10678ba7 100644 --- a/libraries/procedural/src/procedural/Procedural.h +++ b/libraries/procedural/src/procedural/Procedural.h @@ -7,8 +7,6 @@ // #pragma once -#ifndef hifi_RenderableProcedrualItem_h -#define hifi_RenderableProcedrualItem_h #include <atomic> @@ -32,7 +30,6 @@ const size_t MAX_PROCEDURAL_TEXTURE_CHANNELS{ 4 }; struct ProceduralData { static QJsonValue getProceduralData(const QString& proceduralJson); static ProceduralData parse(const QString& userDataJson); - // This should only be called from the render thread, as it shares data with Procedural::prepare void parse(const QJsonObject&); // Rendering object descriptions, from userData @@ -42,10 +39,40 @@ struct ProceduralData { QJsonArray channels; }; +class ProceduralProgramKey { +public: + enum FlagBit { + IS_TRANSPARENT = 0, + NUM_FLAGS + }; + + typedef std::bitset<NUM_FLAGS> Flags; + + Flags _flags; + + bool isTransparent() const { return _flags[IS_TRANSPARENT]; } + + ProceduralProgramKey(bool transparent = false) { + if (transparent) { + _flags.set(IS_TRANSPARENT); + } + } +}; +namespace std { + template <> + struct hash<ProceduralProgramKey> { + size_t operator()(const ProceduralProgramKey& key) const { + return std::hash<std::bitset<ProceduralProgramKey::FlagBit::NUM_FLAGS>>()(key._flags); + } + }; +} +inline bool operator==(const ProceduralProgramKey& a, const ProceduralProgramKey& b) { + return a._flags == b._flags; +} +inline bool operator!=(const ProceduralProgramKey& a, const ProceduralProgramKey& b) { + return a._flags != b._flags; +} -// WARNING with threaded rendering it is the RESPONSIBILITY OF THE CALLER to ensure that -// calls to `setProceduralData` happen on the main thread and that calls to `ready` and `prepare` -// are treated atomically, and that they cannot happen concurrently with calls to `setProceduralData` // FIXME better encapsulation // FIXME better mechanism for extending to things rendered using shaders other than simple.slv struct Procedural { @@ -55,10 +82,9 @@ public: bool isReady() const; bool isEnabled() const { return _enabled; } - void prepare(gpu::Batch& batch, const glm::vec3& position, const glm::vec3& size, const glm::quat& orientation, const glm::vec4& color = glm::vec4(1)); - const gpu::ShaderPointer& getOpaqueShader() const { return _opaqueShader; } + void prepare(gpu::Batch& batch, const glm::vec3& position, const glm::vec3& size, const glm::quat& orientation, const ProceduralProgramKey key = ProceduralProgramKey()); - glm::vec4 getColor(const glm::vec4& entityColor); + glm::vec4 getColor(const glm::vec4& entityColor) const; quint64 getFadeStartTime() const { return _fadeStartTime; } bool isFading() const { return _doesFade && _isFading; } void setIsFading(bool isFading) { _isFading = isFading; } @@ -108,22 +134,19 @@ protected: QString _shaderPath; quint64 _shaderModified { 0 }; NetworkShaderPointer _networkShader; - bool _dirty { false }; bool _shaderDirty { true }; bool _uniformsDirty { true }; // Rendering objects UniformLambdas _uniforms; NetworkTexturePointer _channels[MAX_PROCEDURAL_TEXTURE_CHANNELS]; - gpu::PipelinePointer _opaquePipeline; - gpu::PipelinePointer _transparentPipeline; + + std::unordered_map<ProceduralProgramKey, gpu::PipelinePointer> _proceduralPipelines; + + gpu::ShaderPointer _vertexShader; + StandardInputs _standardInputs; gpu::BufferPointer _standardInputsBuffer; - gpu::ShaderPointer _vertexShader; - gpu::ShaderPointer _opaqueFragmentShader; - gpu::ShaderPointer _transparentFragmentShader; - gpu::ShaderPointer _opaqueShader; - gpu::ShaderPointer _transparentShader; // Entity metadata glm::vec3 _entityDimensions; @@ -131,14 +154,11 @@ protected: glm::mat3 _entityOrientation; private: - // This should only be called from the render thread, as it shares data with Procedural::prepare - void setupUniforms(bool transparent); + void setupUniforms(); mutable quint64 _fadeStartTime { 0 }; mutable bool _hasStartedFade { false }; mutable bool _isFading { false }; bool _doesFade { true }; - bool _prevTransparent { false }; + mutable std::mutex _mutex; }; - -#endif diff --git a/libraries/procedural/src/procedural/ProceduralCommon.slh b/libraries/procedural/src/procedural/ProceduralCommon.slh index d515a79e22..bd894a9e92 100644 --- a/libraries/procedural/src/procedural/ProceduralCommon.slh +++ b/libraries/procedural/src/procedural/ProceduralCommon.slh @@ -8,11 +8,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -<@include gpu/Transform.slh@> <@include gpu/Noise.slh@> <@include procedural/ShaderConstants.h@> - -<$declareStandardCameraTransform()$> LAYOUT(binding=PROCEDURAL_TEXTURE_CHANNEL0) uniform sampler2D iChannel0; LAYOUT(binding=PROCEDURAL_TEXTURE_CHANNEL1) uniform sampler2D iChannel1; @@ -59,6 +56,32 @@ LAYOUT_STD140(binding=0) uniform standardInputsBuffer { #define iChannelResolution standardInputs.channelResolution #define iWorldOrientation standardInputs.worldOrientation +struct ProceduralFragment { + vec3 normal; + vec3 diffuse; + vec3 specular; + vec3 emissive; + float alpha; + float roughness; + float metallic; + float occlusion; + float scattering; +}; + +// Same as ProceduralFragment but with position +struct ProceduralFragmentWithPosition { + vec3 position; + vec3 normal; + vec3 diffuse; + vec3 specular; + vec3 emissive; + float alpha; + float roughness; + float metallic; + float occlusion; + float scattering; +}; + // Unimplemented uniforms // Resolution doesn't make sense in the VR context const vec3 iResolution = vec3(1.0); @@ -69,8 +92,6 @@ const float iSampleRate = 1.0; // No support for video input const vec4 iChannelTime = vec4(0.0); -#define PROCEDURAL 1 - //PROCEDURAL_VERSION // hack comment for extra whitespace diff --git a/libraries/procedural/src/procedural/ProceduralSkybox.cpp b/libraries/procedural/src/procedural/ProceduralSkybox.cpp index ea5be23eb8..211f6ca0a2 100644 --- a/libraries/procedural/src/procedural/ProceduralSkybox.cpp +++ b/libraries/procedural/src/procedural/ProceduralSkybox.cpp @@ -35,7 +35,6 @@ bool ProceduralSkybox::empty() { void ProceduralSkybox::clear() { // Parse and prepare a procedural with no shaders to release textures parse(QString()); - _procedural.isReady(); Skybox::clear(); } diff --git a/libraries/render-utils/src/AmbientOcclusionEffect.cpp b/libraries/render-utils/src/AmbientOcclusionEffect.cpp index bbb370010e..b1ca24de1f 100644 --- a/libraries/render-utils/src/AmbientOcclusionEffect.cpp +++ b/libraries/render-utils/src/AmbientOcclusionEffect.cpp @@ -303,8 +303,6 @@ AmbientOcclusionEffect::AmbientOcclusionEffect() { } void AmbientOcclusionEffect::configure(const Config& config) { - DependencyManager::get<DeferredLightingEffect>()->setAmbientOcclusionEnabled(config.isEnabled()); - bool shouldUpdateBlurs = false; bool shouldUpdateTechnique = false; @@ -591,14 +589,21 @@ void AmbientOcclusionEffect::updateJitterSamples() { } } -void AmbientOcclusionEffect::run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs) { +void AmbientOcclusionEffect::run(const render::RenderContextPointer& renderContext, const Input& input, Output& output) { assert(renderContext->args); assert(renderContext->args->hasViewFrustum()); RenderArgs* args = renderContext->args; - const auto& frameTransform = inputs.get0(); - const auto& linearDepthFramebuffer = inputs.get2(); + const auto& lightingModel = input.get0(); + + if (!lightingModel->isAmbientOcclusionEnabled()) { + output.edit0().reset(); + return; + } + + const auto& frameTransform = input.get1(); + const auto& linearDepthFramebuffer = input.get3(); const int resolutionLevel = _aoParametersBuffer->getResolutionLevel(); const auto depthResolutionLevel = getDepthResolutionLevel(); @@ -631,8 +636,8 @@ void AmbientOcclusionEffect::run(const render::RenderContextPointer& renderConte auto occlusionFBO = _framebuffer->getOcclusionFramebuffer(); auto occlusionBlurredFBO = _framebuffer->getOcclusionBlurredFramebuffer(); - outputs.edit0() = _framebuffer; - outputs.edit1() = _aoParametersBuffer; + output.edit0() = _framebuffer; + output.edit1() = _aoParametersBuffer; auto occlusionPipeline = getOcclusionPipeline(); auto bilateralBlurPipeline = getBilateralBlurPipeline(); diff --git a/libraries/render-utils/src/AmbientOcclusionEffect.h b/libraries/render-utils/src/AmbientOcclusionEffect.h index af6f6b21a3..65fc09a814 100644 --- a/libraries/render-utils/src/AmbientOcclusionEffect.h +++ b/libraries/render-utils/src/AmbientOcclusionEffect.h @@ -17,6 +17,7 @@ #include "render/DrawTask.h" +#include "LightingModel.h" #include "DeferredFrameTransform.h" #include "DeferredFramebuffer.h" #include "SurfaceGeometryPass.h" @@ -152,15 +153,15 @@ signals: class AmbientOcclusionEffect { public: - using Inputs = render::VaryingSet3<DeferredFrameTransformPointer, DeferredFramebufferPointer, LinearDepthFramebufferPointer>; - using Outputs = render::VaryingSet2<AmbientOcclusionFramebufferPointer, gpu::BufferView>; + using Input = render::VaryingSet4<LightingModelPointer, DeferredFrameTransformPointer, DeferredFramebufferPointer, LinearDepthFramebufferPointer>; + using Output = render::VaryingSet2<AmbientOcclusionFramebufferPointer, gpu::BufferView>; using Config = AmbientOcclusionEffectConfig; - using JobModel = render::Job::ModelIO<AmbientOcclusionEffect, Inputs, Outputs, Config>; + using JobModel = render::Job::ModelIO<AmbientOcclusionEffect, Input, Output, Config>; AmbientOcclusionEffect(); void configure(const Config& config); - void run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& outputs); + void run(const render::RenderContextPointer& renderContext, const Input& input, Output& output); // Class describing the uniform buffer with all the parameters common to the AO shaders class AOParameters : public AmbientOcclusionParams { diff --git a/libraries/render-utils/src/AssembleLightingStageTask.cpp b/libraries/render-utils/src/AssembleLightingStageTask.cpp new file mode 100644 index 0000000000..589cdb31ea --- /dev/null +++ b/libraries/render-utils/src/AssembleLightingStageTask.cpp @@ -0,0 +1,50 @@ +// +// Created by Samuel Gateau on 2018/12/06 +// Copyright 2013-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 "AssembleLightingStageTask.h" + +#include <render/DrawTask.h> + +void FetchCurrentFrames::run(const render::RenderContextPointer& renderContext, Output& output) { + auto lightStage = renderContext->_scene->getStage<LightStage>(); + assert(lightStage); + output.edit0() = std::make_shared<LightStage::Frame>(lightStage->_currentFrame); + + auto backgroundStage = renderContext->_scene->getStage<BackgroundStage>(); + assert(backgroundStage); + output.edit1() = std::make_shared<BackgroundStage::Frame>(backgroundStage->_currentFrame); + + auto hazeStage = renderContext->_scene->getStage<HazeStage>(); + assert(hazeStage); + output.edit2() = std::make_shared<HazeStage::Frame>(hazeStage->_currentFrame); + + auto bloomStage = renderContext->_scene->getStage<BloomStage>(); + assert(bloomStage); + output.edit3() = std::make_shared<BloomStage::Frame>(bloomStage->_currentFrame); +} + + +void AssembleLightingStageTask::build(JobModel& task, const render::Varying& input, render::Varying& output) { + const auto& fetchCullSortOut = input.get<Input>(); + const auto& items = fetchCullSortOut.get0(); + //const auto& items = input.get<Input>(); + + const auto& lights = items[RenderFetchCullSortTask::LIGHT]; + const auto& metas = items[RenderFetchCullSortTask::META]; + + // Clear Light, Haze, Bloom, and Skybox Stages and render zones from the general metas bucket + const auto zones = task.addJob<ZoneRendererTask>("ZoneRenderer", metas); + + // Draw Lights just add the lights to the current list of lights to deal with. NOt really gpu job for now. + task.addJob<render::DrawLight>("DrawLight", lights); + + // Fetch the current frame stacks from all the stages + const auto currentStageFrames = task.addJob<FetchCurrentFrames>("FetchCurrentFrames"); + + output = Output(currentStageFrames, zones); +} + diff --git a/libraries/render-utils/src/AssembleLightingStageTask.h b/libraries/render-utils/src/AssembleLightingStageTask.h new file mode 100644 index 0000000000..9770af473c --- /dev/null +++ b/libraries/render-utils/src/AssembleLightingStageTask.h @@ -0,0 +1,44 @@ +// +// Created by Samuel Gateau on 2018/12/06 +// Copyright 2013-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 +// + +#ifndef hifi_AssembleLightingStageTask_h +#define hifi_AssembleLightingStageTask_h + +#include <render/RenderFetchCullSortTask.h> +#include "LightingModel.h" + +#include "LightStage.h" +#include "BackgroundStage.h" +#include "HazeStage.h" +#include "BloomStage.h" + +#include "ZoneRenderer.h" + + +class FetchCurrentFrames { +public: + using Output = render::VaryingSet4<LightStage::FramePointer, BackgroundStage::FramePointer, HazeStage::FramePointer, BloomStage::FramePointer>; + using JobModel = render::Job::ModelO<FetchCurrentFrames, Output>; + + FetchCurrentFrames() {} + + void run(const render::RenderContextPointer& renderContext, Output& output); +}; + +class AssembleLightingStageTask { +public: + using Input = RenderFetchCullSortTask::Output; + using Output = render::VaryingSet2<FetchCurrentFrames::Output, ZoneRendererTask::Output>; + using JobModel = render::Task::ModelIO<AssembleLightingStageTask, Input, Output, render::Task::Config>; + + AssembleLightingStageTask() {} + + void build(JobModel& task, const render::Varying& input, render::Varying& output); +}; + +#endif diff --git a/libraries/render-utils/src/CauterizedModel.cpp b/libraries/render-utils/src/CauterizedModel.cpp index 86d4793aa5..3e32d19b49 100644 --- a/libraries/render-utils/src/CauterizedModel.cpp +++ b/libraries/render-utils/src/CauterizedModel.cpp @@ -137,7 +137,7 @@ void CauterizedModel::updateClusterMatrices() { // as an optimization, don't build cautrizedClusterMatrices if the boneSet is empty. if (!_cauterizeBoneSet.empty()) { - AnimPose cauterizePose = _rig.getJointPose(hfmModel.neckJointIndex); + AnimPose cauterizePose = _rig.getJointPose(_rig.indexOfJoint("Neck")); cauterizePose.scale() = glm::vec3(0.0001f, 0.0001f, 0.0001f); static const glm::mat4 zeroScale( @@ -145,7 +145,7 @@ void CauterizedModel::updateClusterMatrices() { glm::vec4(0.0f, 0.0001f, 0.0f, 0.0f), glm::vec4(0.0f, 0.0f, 0.0001f, 0.0f), glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)); - auto cauterizeMatrix = _rig.getJointTransform(hfmModel.neckJointIndex) * zeroScale; + auto cauterizeMatrix = _rig.getJointTransform(_rig.indexOfJoint("Neck")) * zeroScale; for (int i = 0; i < _cauterizeMeshStates.size(); i++) { Model::MeshState& state = _cauterizeMeshStates[i]; diff --git a/libraries/render-utils/src/DebugDeferredBuffer.cpp b/libraries/render-utils/src/DebugDeferredBuffer.cpp index 9bdfdbcda6..01a9c055cc 100644 --- a/libraries/render-utils/src/DebugDeferredBuffer.cpp +++ b/libraries/render-utils/src/DebugDeferredBuffer.cpp @@ -403,7 +403,7 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I auto& ambientOcclusionFramebuffer = inputs.get3(); auto& velocityFramebuffer = inputs.get4(); auto& frameTransform = inputs.get5(); - auto& lightFrame = inputs.get6(); + auto& shadowFrame = inputs.get6(); gpu::doInBatch("DebugDeferredBuffer::run", args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); @@ -439,16 +439,14 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I batch.setResourceTexture(Textures::DebugTexture0, velocityFramebuffer->getVelocityTexture()); } - auto lightStage = renderContext->_scene->getStage<LightStage>(); - assert(lightStage); - assert(lightStage->getNumLights() > 0); - auto lightAndShadow = lightStage->getCurrentKeyLightAndShadow(*lightFrame); - const auto& globalShadow = lightAndShadow.second; - if (globalShadow) { - batch.setResourceTexture(Textures::Shadow, globalShadow->map); - batch.setUniformBuffer(UBOs::ShadowParams, globalShadow->getBuffer()); - batch.setUniformBuffer(UBOs::DeferredFrameTransform, frameTransform->getFrameTransformBuffer()); - batch.setUniformBuffer(UBOs::DebugDeferredParams, _parameters); + if (!shadowFrame->_objects.empty()) { + const auto& globalShadow = shadowFrame->_objects[0]; + if (globalShadow) { + batch.setResourceTexture(Textures::Shadow, globalShadow->map); + batch.setUniformBuffer(UBOs::ShadowParams, globalShadow->getBuffer()); + batch.setUniformBuffer(UBOs::DeferredFrameTransform, frameTransform->getFrameTransformBuffer()); + batch.setUniformBuffer(UBOs::DebugDeferredParams, _parameters); + } } if (linearDepthTarget) { diff --git a/libraries/render-utils/src/DebugDeferredBuffer.h b/libraries/render-utils/src/DebugDeferredBuffer.h index 8afccfca13..5ff3ab28c9 100644 --- a/libraries/render-utils/src/DebugDeferredBuffer.h +++ b/libraries/render-utils/src/DebugDeferredBuffer.h @@ -46,7 +46,7 @@ public: AmbientOcclusionFramebufferPointer, VelocityFramebufferPointer, DeferredFrameTransformPointer, - LightStage::FramePointer>; + LightStage::ShadowFramePointer>; using Config = DebugDeferredBufferConfig; using JobModel = render::Job::ModelI<DebugDeferredBuffer, Inputs, Config>; diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index e0fd9e970d..ab9dea2325 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -374,11 +374,11 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, const DeferredFramebufferPointer& deferredFramebuffer, const LightingModelPointer& lightingModel, const LightStage::FramePointer& lightFrame, + const LightStage::ShadowFramePointer& shadowFrame, const HazeStage::FramePointer& hazeFrame, const SurfaceGeometryFramebufferPointer& surfaceGeometryFramebuffer, const AmbientOcclusionFramebufferPointer& ambientOcclusionFramebuffer, - const SubsurfaceScatteringResourcePointer& subsurfaceScatteringResource, - bool renderShadows) { + const SubsurfaceScatteringResourcePointer& subsurfaceScatteringResource) { auto args = renderContext->args; auto& batch = (*args->_batch); @@ -404,7 +404,7 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, batch.setResourceTexture(ru::Texture::DeferredDepth, deferredFramebuffer->getPrimaryDepthTexture()); // FIXME: Different render modes should have different tasks - if (args->_renderMode == RenderArgs::DEFAULT_RENDER_MODE && deferredLightingEffect->isAmbientOcclusionEnabled() && ambientOcclusionFramebuffer) { + if (lightingModel->isAmbientOcclusionEnabled() && ambientOcclusionFramebuffer) { batch.setResourceTexture(ru::Texture::DeferredObscurance, ambientOcclusionFramebuffer->getOcclusionTexture()); } else { // need to assign the white texture if ao is off @@ -429,24 +429,23 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, batch.setResourceTexture(ru::Texture::SsscSpecularBeckmann, subsurfaceScatteringResource->getScatteringSpecular()); } - // Global directional light and ambient pass - + // Global directional light, maybe shadow and ambient pass auto lightStage = renderContext->_scene->getStage<LightStage>(); assert(lightStage); assert(lightStage->getNumLights() > 0); - auto lightAndShadow = lightStage->getCurrentKeyLightAndShadow(*lightFrame); - const auto& globalShadow = lightAndShadow.second; + auto keyLight = lightStage->getCurrentKeyLight(*lightFrame); - // Bind the shadow buffers - if (globalShadow) { - batch.setResourceTexture(ru::Texture::Shadow, globalShadow->map); + // Check if keylight casts shadows + bool keyLightCastShadows{ false }; + LightStage::ShadowPointer globalShadow; + if (lightingModel->isShadowEnabled() && shadowFrame && !shadowFrame->_objects.empty()) { + globalShadow = shadowFrame->_objects.front(); + if (globalShadow) { + keyLightCastShadows = true; + } } - auto program = deferredLightingEffect->_directionalSkyboxLight; - LightLocationsPtr locations = deferredLightingEffect->_directionalSkyboxLightLocations; - - auto keyLight = lightAndShadow.first; - + // Global Ambient light graphics::LightPointer ambientLight; if (lightStage && lightFrame->_ambientLights.size()) { ambientLight = lightStage->getLight(lightFrame->_ambientLights.front()); @@ -454,18 +453,10 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, bool hasAmbientMap = (ambientLight != nullptr); // Setup the global directional pass pipeline + auto program = deferredLightingEffect->_directionalSkyboxLight; + LightLocationsPtr locations = deferredLightingEffect->_directionalSkyboxLightLocations; { - // Check if keylight casts shadows - bool keyLightCastShadows { false }; - - if (renderShadows && lightStage && lightFrame->_sunLights.size()) { - graphics::LightPointer keyLight = lightStage->getLight(lightFrame->_sunLights.front()); - if (keyLight) { - keyLightCastShadows = keyLight->getCastShadows(); - } - } - - if (deferredLightingEffect->_shadowMapEnabled && keyLightCastShadows) { + if (keyLightCastShadows) { // If the keylight has an ambient Map then use the Skybox version of the pass // otherwise use the ambient sphere version @@ -488,7 +479,8 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, } } - if (locations->shadowTransform && globalShadow) { + if (keyLightCastShadows && globalShadow) { + batch.setResourceTexture(ru::Texture::Shadow, globalShadow->map); batch.setUniformBuffer(ru::Buffer::ShadowParams, globalShadow->getBuffer()); } @@ -510,10 +502,7 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, batch.draw(gpu::TRIANGLE_STRIP, 4); deferredLightingEffect->unsetKeyLightBatch(batch); - - for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) { - batch.setResourceTexture(ru::Texture::Shadow +i, nullptr); - } + batch.setResourceTexture(ru::Texture::Shadow, nullptr); } } @@ -606,9 +595,7 @@ void RenderDeferredCleanup::run(const render::RenderContextPointer& renderContex } } -RenderDeferred::RenderDeferred(bool renderShadows): - _renderShadows(renderShadows) -{ +RenderDeferred::RenderDeferred() { DependencyManager::get<DeferredLightingEffect>()->init(); } @@ -616,18 +603,21 @@ void RenderDeferred::configure(const Config& config) { } void RenderDeferred::run(const RenderContextPointer& renderContext, const Inputs& inputs) { + auto args = renderContext->args; auto deferredTransform = inputs.get0(); auto deferredFramebuffer = inputs.get1(); - auto lightingModel = inputs.get2(); - auto surfaceGeometryFramebuffer = inputs.get3(); - auto ssaoFramebuffer = inputs.get4(); - auto subsurfaceScatteringResource = inputs.get5(); - auto lightClusters = inputs.get6(); - auto args = renderContext->args; + auto extraRenderBuffers = inputs.get2(); + auto surfaceGeometryFramebuffer = extraRenderBuffers.get0(); + auto ssaoFramebuffer = extraRenderBuffers.get1(); + auto subsurfaceScatteringResource = extraRenderBuffers.get2(); - const auto& lightFrame = inputs.get7(); - const auto& hazeFrame = inputs.get8(); + auto lightingModel = inputs.get3(); + auto lightClusters = inputs.get4(); + + const auto& lightFrame = inputs.get5(); + const auto& shadowFrame = inputs.get6(); + const auto& hazeFrame = inputs.get7(); if (!_gpuTimer) { _gpuTimer = std::make_shared < gpu::RangeTimer>(__FUNCTION__); @@ -638,7 +628,7 @@ void RenderDeferred::run(const RenderContextPointer& renderContext, const Inputs args->_batch = &batch; _gpuTimer->begin(batch); - setupJob.run(renderContext, deferredTransform, deferredFramebuffer, lightingModel, lightFrame, hazeFrame, surfaceGeometryFramebuffer, ssaoFramebuffer, subsurfaceScatteringResource, _renderShadows); + setupJob.run(renderContext, deferredTransform, deferredFramebuffer, lightingModel, lightFrame, shadowFrame, hazeFrame, surfaceGeometryFramebuffer, ssaoFramebuffer, subsurfaceScatteringResource); lightsJob.run(renderContext, deferredTransform, deferredFramebuffer, lightingModel, surfaceGeometryFramebuffer, lightClusters); @@ -696,7 +686,6 @@ void DefaultLightingSetup::run(const RenderContextPointer& renderContext) { // Add the global light to the light stage (for later shadow rendering) // Set this light to be the default _defaultLightID = lightStage->addLight(lp, true); - lightStage->addShadow(_defaultLightID); } auto backgroundStage = renderContext->_scene->getStage<BackgroundStage>(); diff --git a/libraries/render-utils/src/DeferredLightingEffect.h b/libraries/render-utils/src/DeferredLightingEffect.h index 9fd9554d31..f4935000ef 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.h +++ b/libraries/render-utils/src/DeferredLightingEffect.h @@ -55,16 +55,9 @@ public: static void setupLocalLightsBatch(gpu::Batch& batch, const LightClustersPointer& lightClusters); static void unsetLocalLightsBatch(gpu::Batch& batch); - void setShadowMapEnabled(bool enable) { _shadowMapEnabled = enable; }; - void setAmbientOcclusionEnabled(bool enable) { _ambientOcclusionEnabled = enable; } - bool isAmbientOcclusionEnabled() const { return _ambientOcclusionEnabled; } - private: DeferredLightingEffect() = default; - bool _shadowMapEnabled{ true }; // note that this value is overwritten in the ::configure method - bool _ambientOcclusionEnabled{ false }; - graphics::MeshPointer _pointLightMesh; graphics::MeshPointer getPointLightMesh(); graphics::MeshPointer _spotLightMesh; @@ -146,11 +139,11 @@ public: const DeferredFramebufferPointer& deferredFramebuffer, const LightingModelPointer& lightingModel, const LightStage::FramePointer& lightFrame, + const LightStage::ShadowFramePointer& shadowFrame, const HazeStage::FramePointer& hazeFrame, const SurfaceGeometryFramebufferPointer& surfaceGeometryFramebuffer, const AmbientOcclusionFramebufferPointer& ambientOcclusionFramebuffer, - const SubsurfaceScatteringResourcePointer& subsurfaceScatteringResource, - bool renderShadows); + const SubsurfaceScatteringResourcePointer& subsurfaceScatteringResource); }; class RenderDeferredLocals { @@ -167,7 +160,6 @@ public: gpu::BufferView _localLightsBuffer; RenderDeferredLocals(); - }; @@ -182,14 +174,14 @@ using RenderDeferredConfig = render::GPUJobConfig; class RenderDeferred { public: - using Inputs = render::VaryingSet9< - DeferredFrameTransformPointer, DeferredFramebufferPointer, LightingModelPointer, SurfaceGeometryFramebufferPointer, - AmbientOcclusionFramebufferPointer, SubsurfaceScatteringResourcePointer, LightClustersPointer, LightStage::FramePointer, HazeStage::FramePointer>; + using ExtraDeferredBuffer = render::VaryingSet3<SurfaceGeometryFramebufferPointer, AmbientOcclusionFramebufferPointer, SubsurfaceScatteringResourcePointer>; + using Inputs = render::VaryingSet8< + DeferredFrameTransformPointer, DeferredFramebufferPointer, ExtraDeferredBuffer, LightingModelPointer, LightClustersPointer, LightStage::FramePointer, LightStage::ShadowFramePointer, HazeStage::FramePointer>; using Config = RenderDeferredConfig; using JobModel = render::Job::ModelI<RenderDeferred, Inputs, Config>; - RenderDeferred(bool renderShadows = false); + RenderDeferred(); void configure(const Config& config); @@ -203,7 +195,6 @@ protected: gpu::RangeTimerPointer _gpuTimer; private: - bool _renderShadows { false }; }; class DefaultLightingSetup { diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 38c3106af6..d6d6f4903e 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -944,42 +944,44 @@ void GeometryCache::renderWireSphere(gpu::Batch& batch, const glm::vec4& color) } void GeometryCache::renderGrid(gpu::Batch& batch, const glm::vec2& minCorner, const glm::vec2& maxCorner, - int majorRows, int majorCols, float majorEdge, - int minorRows, int minorCols, float minorEdge, - const glm::vec4& color, bool isLayered, int id) { - static const glm::vec2 MIN_TEX_COORD(0.0f, 0.0f); - static const glm::vec2 MAX_TEX_COORD(1.0f, 1.0f); - - bool registered = (id != UNKNOWN_ID); + int majorRows, int majorCols, float majorEdge, int minorRows, int minorCols, float minorEdge, + const glm::vec4& color, int id) { Vec2FloatPair majorKey(glm::vec2(majorRows, majorCols), majorEdge); Vec2FloatPair minorKey(glm::vec2(minorRows, minorCols), minorEdge); Vec2FloatPairPair key(majorKey, minorKey); // Make the gridbuffer - if (registered && (!_registeredGridBuffers.contains(id) || _lastRegisteredGridBuffer[id] != key)) { - GridSchema gridSchema; - GridBuffer gridBuffer = std::make_shared<gpu::Buffer>(sizeof(GridSchema), (const gpu::Byte*) &gridSchema); - - if (registered && _registeredGridBuffers.contains(id)) { - gridBuffer = _registeredGridBuffers[id]; + GridBuffer gridBuffer; + if (id != UNKNOWN_ID) { + auto gridBufferIter = _registeredGridBuffers.find(id); + bool hadGridBuffer = gridBufferIter != _registeredGridBuffers.end(); + if (hadGridBuffer) { + gridBuffer = gridBufferIter.value(); + } else { + GridSchema gridSchema; + gridBuffer = std::make_shared<gpu::Buffer>(sizeof(GridSchema), (const gpu::Byte*)&gridSchema); } - _registeredGridBuffers[id] = gridBuffer; - _lastRegisteredGridBuffer[id] = key; + if (!hadGridBuffer || _lastRegisteredGridBuffer[id] != key) { + _registeredGridBuffers[id] = gridBuffer; + _lastRegisteredGridBuffer[id] = key; - gridBuffer.edit<GridSchema>().period = glm::vec4(majorRows, majorCols, minorRows, minorCols); - gridBuffer.edit<GridSchema>().offset.x = -(majorEdge / majorRows) / 2; - gridBuffer.edit<GridSchema>().offset.y = -(majorEdge / majorCols) / 2; - gridBuffer.edit<GridSchema>().offset.z = -(minorEdge / minorRows) / 2; - gridBuffer.edit<GridSchema>().offset.w = -(minorEdge / minorCols) / 2; - gridBuffer.edit<GridSchema>().edge = glm::vec4(glm::vec2(majorEdge), - // If rows or columns are not set, do not draw minor gridlines - glm::vec2((minorRows != 0 && minorCols != 0) ? minorEdge : 0.0f)); + gridBuffer.edit<GridSchema>().period = glm::vec4(majorRows, majorCols, minorRows, minorCols); + gridBuffer.edit<GridSchema>().offset.x = -(majorEdge / majorRows) / 2; + gridBuffer.edit<GridSchema>().offset.y = -(majorEdge / majorCols) / 2; + gridBuffer.edit<GridSchema>().offset.z = -(minorEdge / minorRows) / 2; + gridBuffer.edit<GridSchema>().offset.w = -(minorEdge / minorCols) / 2; + gridBuffer.edit<GridSchema>().edge = glm::vec4(glm::vec2(majorEdge), + // If rows or columns are not set, do not draw minor gridlines + glm::vec2((minorRows != 0 && minorCols != 0) ? minorEdge : 0.0f)); + } } // Set the grid pipeline - useGridPipeline(batch, _registeredGridBuffers[id], isLayered); + useGridPipeline(batch, gridBuffer, color.a < 1.0f); + static const glm::vec2 MIN_TEX_COORD(0.0f, 0.0f); + static const glm::vec2 MAX_TEX_COORD(1.0f, 1.0f); renderQuad(batch, minCorner, maxCorner, MIN_TEX_COORD, MAX_TEX_COORD, color, id); } @@ -2115,26 +2117,40 @@ void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) { } } -void GeometryCache::useGridPipeline(gpu::Batch& batch, GridBuffer gridBuffer, bool isLayered) { - if (!_gridPipeline) { - auto program = gpu::Shader::createProgram(shader::render_utils::program::grid); - _gridSlot = 0; - auto stateLayered = std::make_shared<gpu::State>(); - stateLayered->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA); - PrepareStencil::testMask(*stateLayered); - _gridPipelineLayered = gpu::Pipeline::create(program, stateLayered); - - auto state = std::make_shared<gpu::State>(stateLayered->getValues()); +void GeometryCache::useGridPipeline(gpu::Batch& batch, GridBuffer gridBuffer, bool transparent) { + if (!_gridPipelineOpaque || !_gridPipelineTransparent) { const float DEPTH_BIAS = 0.001f; - state->setDepthBias(DEPTH_BIAS); - state->setDepthTest(true, false, gpu::LESS_EQUAL); - PrepareStencil::testMaskDrawShape(*state); - _gridPipeline = gpu::Pipeline::create(program, state); + + // FIXME: need forward pipelines + { + auto program = gpu::Shader::createProgram(shader::render_utils::program::grid); + auto state = std::make_shared<gpu::State>(); + state->setDepthTest(true, true, gpu::LESS_EQUAL); + state->setBlendFunction(false, + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + PrepareStencil::testMaskDrawShape(*state); + state->setCullMode(gpu::State::CULL_NONE); + state->setDepthBias(DEPTH_BIAS); + _gridPipelineOpaque = gpu::Pipeline::create(program, state); + } + + { + auto program = gpu::Shader::createProgram(shader::render_utils::program::grid_translucent); + auto state = std::make_shared<gpu::State>(); + state->setDepthTest(true, true, gpu::LESS_EQUAL); + state->setBlendFunction(true, + gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, + gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); + PrepareStencil::testMask(*state); + state->setCullMode(gpu::State::CULL_NONE); + state->setDepthBias(DEPTH_BIAS); + _gridPipelineTransparent = gpu::Pipeline::create(program, state); + } } - gpu::PipelinePointer pipeline = isLayered ? _gridPipelineLayered : _gridPipeline; - batch.setPipeline(pipeline); - batch.setUniformBuffer(_gridSlot, gridBuffer); + batch.setPipeline(transparent ? _gridPipelineTransparent : _gridPipelineOpaque); + batch.setUniformBuffer(0, gridBuffer); } diff --git a/libraries/render-utils/src/GeometryCache.h b/libraries/render-utils/src/GeometryCache.h index fcbf5ee128..589be4dcc1 100644 --- a/libraries/render-utils/src/GeometryCache.h +++ b/libraries/render-utils/src/GeometryCache.h @@ -270,11 +270,7 @@ public: void renderGrid(gpu::Batch& batch, const glm::vec2& minCorner, const glm::vec2& maxCorner, int majorRows, int majorCols, float majorEdge, int minorRows, int minorCols, float minorEdge, - const glm::vec4& color, bool isLayered, int id); - void renderGrid(gpu::Batch& batch, const glm::vec2& minCorner, const glm::vec2& maxCorner, - int rows, int cols, float edge, const glm::vec4& color, bool isLayered, int id) { - renderGrid(batch, minCorner, maxCorner, rows, cols, edge, 0, 0, 0.0f, color, isLayered, id); - } + const glm::vec4& color, int id); void renderBevelCornersRect(gpu::Batch& batch, int x, int y, int width, int height, int bevelDistance, const glm::vec4& color, int id); @@ -414,9 +410,8 @@ private: }; using GridBuffer = gpu::BufferView; void useGridPipeline(gpu::Batch& batch, GridBuffer gridBuffer, bool isLayered); - gpu::PipelinePointer _gridPipeline; - gpu::PipelinePointer _gridPipelineLayered; - int _gridSlot; + gpu::PipelinePointer _gridPipelineOpaque; + gpu::PipelinePointer _gridPipelineTransparent; class BatchItemDetails { public: diff --git a/libraries/render-utils/src/LightClusters.cpp b/libraries/render-utils/src/LightClusters.cpp index 252416ea9d..3dc826d56c 100644 --- a/libraries/render-utils/src/LightClusters.cpp +++ b/libraries/render-utils/src/LightClusters.cpp @@ -543,7 +543,7 @@ void LightClusteringPass::configure(const Config& config) { _freeze = config.freeze; } -void LightClusteringPass::run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& output) { +void LightClusteringPass::run(const render::RenderContextPointer& renderContext, const Input& inputs, Output& output) { auto args = renderContext->args; auto deferredTransform = inputs.get0(); @@ -638,10 +638,9 @@ void DebugLightClusters::run(const render::RenderContextPointer& renderContext, } auto deferredTransform = inputs.get0(); - auto deferredFramebuffer = inputs.get1(); - auto lightingModel = inputs.get2(); - auto linearDepthTarget = inputs.get3(); - auto lightClusters = inputs.get4(); + auto lightingModel = inputs.get1(); + auto linearDepthTarget = inputs.get2(); + auto lightClusters = inputs.get3(); auto args = renderContext->args; diff --git a/libraries/render-utils/src/LightClusters.h b/libraries/render-utils/src/LightClusters.h index e109e43aaa..60978e76e8 100644 --- a/libraries/render-utils/src/LightClusters.h +++ b/libraries/render-utils/src/LightClusters.h @@ -167,16 +167,16 @@ protected: class LightClusteringPass { public: - using Inputs = render::VaryingSet4<DeferredFrameTransformPointer, LightingModelPointer, LightStage::FramePointer, LinearDepthFramebufferPointer>; - using Outputs = LightClustersPointer; + using Input = render::VaryingSet4<DeferredFrameTransformPointer, LightingModelPointer, LightStage::FramePointer, LinearDepthFramebufferPointer>; + using Output = LightClustersPointer; using Config = LightClusteringPassConfig; - using JobModel = render::Job::ModelIO<LightClusteringPass, Inputs, Outputs, Config>; + using JobModel = render::Job::ModelIO<LightClusteringPass, Input, Output, Config>; LightClusteringPass(); void configure(const Config& config); - void run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& output); + void run(const render::RenderContextPointer& renderContext, const Input& input, Output& output); protected: LightClustersPointer _lightClusters; @@ -213,7 +213,7 @@ protected: class DebugLightClusters { public: - using Inputs = render::VaryingSet5 < DeferredFrameTransformPointer, DeferredFramebufferPointer, LightingModelPointer, LinearDepthFramebufferPointer, LightClustersPointer>; + using Inputs = render::VaryingSet4 < DeferredFrameTransformPointer, LightingModelPointer, LinearDepthFramebufferPointer, LightClustersPointer>; using Config = DebugLightClustersConfig; using JobModel = render::Job::ModelI<DebugLightClusters, Inputs, Config>; diff --git a/libraries/render-utils/src/LightStage.cpp b/libraries/render-utils/src/LightStage.cpp index d5e1ca4644..6913949286 100644 --- a/libraries/render-utils/src/LightStage.cpp +++ b/libraries/render-utils/src/LightStage.cpp @@ -152,6 +152,11 @@ LightStage::Shadow::Shadow(graphics::LightPointer light, float maxDistance, unsi setMaxDistance(maxDistance); } +void LightStage::Shadow::setLight(graphics::LightPointer light) { + _light = light; +} + + void LightStage::Shadow::setMaxDistance(float value) { // This overlaping factor isn't really used directly for blending of shadow cascades. It // just there to be sure the cascades do overlap. The blending width used is relative @@ -345,27 +350,9 @@ LightStage::Index LightStage::addLight(const LightPointer& light, const bool sho return lightId; } -LightStage::Index LightStage::addShadow(Index lightIndex, float maxDistance, unsigned int cascadeCount) { - auto light = getLight(lightIndex); - Index shadowId = INVALID_INDEX; - if (light) { - assert(_descs[lightIndex].shadowId == INVALID_INDEX); - shadowId = _shadows.newElement(std::make_shared<Shadow>(light, maxDistance, cascadeCount)); - _descs[lightIndex].shadowId = shadowId; - } - return shadowId; -} - LightStage::LightPointer LightStage::removeLight(Index index) { LightPointer removedLight = _lights.freeElement(index); if (removedLight) { - auto shadowId = _descs[index].shadowId; - // Remove shadow if one exists for this light - if (shadowId != INVALID_INDEX) { - auto removedShadow = _shadows.freeElement(shadowId); - assert(removedShadow); - assert(removedShadow->getLight() == removedLight); - } _lightMap.erase(removedLight); _descs[index] = Desc(); } @@ -389,35 +376,6 @@ LightStage::LightPointer LightStage::getCurrentAmbientLight(const LightStage::Fr return _lights.get(keyLightId); } -LightStage::ShadowPointer LightStage::getCurrentKeyShadow(const LightStage::Frame& frame) const { - Index keyLightId { _defaultLightId }; - if (!frame._sunLights.empty()) { - keyLightId = frame._sunLights.front(); - } - auto shadow = getShadow(keyLightId); - assert(shadow == nullptr || shadow->getLight() == getLight(keyLightId)); - return shadow; -} - -LightStage::LightAndShadow LightStage::getCurrentKeyLightAndShadow(const LightStage::Frame& frame) const { - Index keyLightId { _defaultLightId }; - if (!frame._sunLights.empty()) { - keyLightId = frame._sunLights.front(); - } - auto shadow = getShadow(keyLightId); - auto light = getLight(keyLightId); - assert(shadow == nullptr || shadow->getLight() == light); - return LightAndShadow(light, shadow); -} - -LightStage::Index LightStage::getShadowId(Index lightId) const { - if (checkLightId(lightId)) { - return _descs[lightId].shadowId; - } else { - return INVALID_INDEX; - } -} - void LightStage::updateLightArrayBuffer(Index lightId) { auto lightSize = sizeof(graphics::Light::LightSchema); if (!_lightArrayBuffer) { diff --git a/libraries/render-utils/src/LightStage.h b/libraries/render-utils/src/LightStage.h index 5e5b6cf4fa..1fb1754862 100644 --- a/libraries/render-utils/src/LightStage.h +++ b/libraries/render-utils/src/LightStage.h @@ -76,6 +76,8 @@ public: Shadow(graphics::LightPointer light, float maxDistance, unsigned int cascadeCount = 1); + void setLight(graphics::LightPointer light); + void setKeylightFrustum(const ViewFrustum& viewFrustum, float nearDepth = 1.0f, float farDepth = 1000.0f); void setKeylightCascadeFrustum(unsigned int cascadeIndex, const ViewFrustum& viewFrustum, @@ -93,10 +95,15 @@ public: const graphics::LightPointer& getLight() const { return _light; } gpu::TexturePointer map; +#include "Shadows_shared.slh" + class Schema : public ShadowParameters { + public: + Schema(); + + }; protected: -#include "Shadows_shared.slh" using Cascades = std::vector<Cascade>; @@ -106,25 +113,17 @@ public: float _maxDistance; Cascades _cascades; - class Schema : public ShadowParameters { - public: - Schema(); - - }; UniformBufferView _schemaBuffer = nullptr; }; using ShadowPointer = std::shared_ptr<Shadow>; - using Shadows = render::indexed_container::IndexedPointerVector<Shadow>; Index findLight(const LightPointer& light) const; Index addLight(const LightPointer& light, const bool shouldSetAsDefault = false); Index getDefaultLight() { return _defaultLightId; } - Index addShadow(Index lightIndex, float maxDistance = 20.0f, unsigned int cascadeCount = 1U); - LightPointer removeLight(Index index); bool checkLightId(Index index) const { return _lights.checkIndex(index); } @@ -133,23 +132,7 @@ public: Index getNumFreeLights() const { return _lights.getNumFreeIndices(); } Index getNumAllocatedLights() const { return _lights.getNumAllocatedIndices(); } - LightPointer getLight(Index lightId) const { - return _lights.get(lightId); - } - - Index getShadowId(Index lightId) const; - - ShadowPointer getShadow(Index lightId) const { - return _shadows.get(getShadowId(lightId)); - } - - using LightAndShadow = std::pair<LightPointer, ShadowPointer>; - LightAndShadow getLightAndShadow(Index lightId) const { - auto light = getLight(lightId); - auto shadow = getShadow(lightId); - assert(shadow == nullptr || shadow->getLight() == light); - return LightAndShadow(light, shadow); - } + LightPointer getLight(Index lightId) const { return _lights.get(lightId); } LightStage(); @@ -182,6 +165,24 @@ public: }; using FramePointer = std::shared_ptr<Frame>; + class ShadowFrame { + public: + ShadowFrame() {} + + void clear() {} + + using Object = ShadowPointer; + using Objects = std::vector<Object>; + + void pushShadow(const ShadowPointer& shadow) { + _objects.emplace_back(shadow); + } + + + Objects _objects; + }; + using ShadowFramePointer = std::shared_ptr<ShadowFrame>; + Frame _currentFrame; Index getAmbientOffLight() { return _ambientOffLightId; } @@ -191,8 +192,6 @@ public: LightPointer getCurrentKeyLight(const LightStage::Frame& frame) const; LightPointer getCurrentAmbientLight(const LightStage::Frame& frame) const; - ShadowPointer getCurrentKeyShadow(const LightStage::Frame& frame) const; - LightAndShadow getCurrentKeyLightAndShadow(const LightStage::Frame& frame) const; protected: @@ -204,7 +203,6 @@ protected: gpu::BufferPointer _lightArrayBuffer; Lights _lights; - Shadows _shadows; Descs _descs; LightMap _lightMap; diff --git a/libraries/render-utils/src/LightingModel.cpp b/libraries/render-utils/src/LightingModel.cpp index 05002a5daf..2a85fcd960 100644 --- a/libraries/render-utils/src/LightingModel.cpp +++ b/libraries/render-utils/src/LightingModel.cpp @@ -187,6 +187,24 @@ bool LightingModel::isBlendshapeEnabled() const { return (bool)_parametersBuffer.get<Parameters>().enableBlendshape; } +void LightingModel::setAmbientOcclusion(bool enable) { + if (enable != isAmbientOcclusionEnabled()) { + _parametersBuffer.edit<Parameters>().enableAmbientOcclusion = (float)enable; + } +} +bool LightingModel::isAmbientOcclusionEnabled() const { + return (bool)_parametersBuffer.get<Parameters>().enableAmbientOcclusion; +} + +void LightingModel::setShadow(bool enable) { + if (enable != isShadowEnabled()) { + _parametersBuffer.edit<Parameters>().enableShadow = (float)enable; + } +} +bool LightingModel::isShadowEnabled() const { + return (bool)_parametersBuffer.get<Parameters>().enableShadow; +} + MakeLightingModel::MakeLightingModel() { _lightingModel = std::make_shared<LightingModel>(); } @@ -218,6 +236,9 @@ void MakeLightingModel::configure(const Config& config) { _lightingModel->setSkinning(config.enableSkinning); _lightingModel->setBlendshape(config.enableBlendshape); + + _lightingModel->setAmbientOcclusion(config.enableAmbientOcclusion); + _lightingModel->setShadow(config.enableShadow); } void MakeLightingModel::run(const render::RenderContextPointer& renderContext, LightingModelPointer& lightingModel) { diff --git a/libraries/render-utils/src/LightingModel.h b/libraries/render-utils/src/LightingModel.h index de7ff63972..571eadb60b 100644 --- a/libraries/render-utils/src/LightingModel.h +++ b/libraries/render-utils/src/LightingModel.h @@ -76,6 +76,12 @@ public: void setBlendshape(bool enable); bool isBlendshapeEnabled() const; + + void setAmbientOcclusion(bool enable); + bool isAmbientOcclusionEnabled() const; + void setShadow(bool enable); + bool isShadowEnabled() const; + UniformBufferView getParametersBuffer() const { return _parametersBuffer; } protected: @@ -112,6 +118,11 @@ protected: float enableSkinning{ 1.0f }; float enableBlendshape{ 1.0f }; + float enableAmbientOcclusion{ 0.0f }; + float enableShadow{ 1.0f }; + float spare1{ 1.0f }; + float spare2{ 1.0f }; + Parameters() {} }; UniformBufferView _parametersBuffer; @@ -152,6 +163,10 @@ class MakeLightingModelConfig : public render::Job::Config { Q_PROPERTY(bool enableSkinning MEMBER enableSkinning NOTIFY dirty) Q_PROPERTY(bool enableBlendshape MEMBER enableBlendshape NOTIFY dirty) + Q_PROPERTY(bool enableAmbientOcclusion READ isAmbientOcclusionEnabled WRITE setAmbientOcclusion NOTIFY dirty) + Q_PROPERTY(bool enableShadow READ isShadowEnabled WRITE setShadow NOTIFY dirty) + + public: MakeLightingModelConfig() : render::Job::Config() {} // Make Lighting Model is always on @@ -181,6 +196,17 @@ public: bool enableSkinning{ true }; bool enableBlendshape{ true }; + bool enableAmbientOcclusion{ true }; + bool enableShadow{ true }; + + + void setAmbientOcclusion(bool enable) { enableAmbientOcclusion = enable; emit dirty();} + bool isAmbientOcclusionEnabled() const { return enableAmbientOcclusion; } + void setShadow(bool enable) { + enableShadow = enable; emit dirty(); + } + bool isShadowEnabled() const { return enableShadow; } + signals: void dirty(); }; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 9cefbf65a8..ec29fb009e 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -614,58 +614,11 @@ bool Model::findParabolaIntersectionAgainstSubMeshes(const glm::vec3& origin, co return intersectedSomething; } -bool Model::convexHullContains(glm::vec3 point) { - // if we aren't active, we can't compute that yet... - if (!isActive()) { - return false; - } - - // extents is the entity relative, scaled, centered extents of the entity - glm::vec3 position = _translation; - glm::mat4 rotation = glm::mat4_cast(_rotation); - glm::mat4 translation = glm::translate(position); - glm::mat4 modelToWorldMatrix = translation * rotation; - glm::mat4 worldToModelMatrix = glm::inverse(modelToWorldMatrix); - - Extents modelExtents = getMeshExtents(); // NOTE: unrotated - - glm::vec3 dimensions = modelExtents.maximum - modelExtents.minimum; - glm::vec3 corner = -(dimensions * _registrationPoint); - AABox modelFrameBox(corner, dimensions); - - glm::vec3 modelFramePoint = glm::vec3(worldToModelMatrix * glm::vec4(point, 1.0f)); - - // we can use the AABox's contains() by mapping our point into the model frame - // and testing there. - if (modelFrameBox.contains(modelFramePoint)){ - QMutexLocker locker(&_mutex); - - if (!_triangleSetsValid) { - calculateTriangleSets(getHFMModel()); - } - - // If we are inside the models box, then consider the submeshes... - glm::mat4 meshToModelMatrix = glm::scale(_scale) * glm::translate(_offset); - glm::mat4 meshToWorldMatrix = createMatFromQuatAndPos(_rotation, _translation) * meshToModelMatrix; - glm::mat4 worldToMeshMatrix = glm::inverse(meshToWorldMatrix); - glm::vec3 meshFramePoint = glm::vec3(worldToMeshMatrix * glm::vec4(point, 1.0f)); - - for (auto& meshTriangleSets : _modelSpaceMeshTriangleSets) { - for (auto &partTriangleSet : meshTriangleSets) { - const AABox& box = partTriangleSet.getBounds(); - if (box.contains(meshFramePoint)) { - if (partTriangleSet.convexHullContains(meshFramePoint)) { - // It's inside this mesh, return true. - return true; - } - } - } - } - - - } - // It wasn't in any mesh, return false. - return false; +glm::mat4 Model::getWorldToHFMMatrix() const { + glm::mat4 hfmToModelMatrix = glm::scale(_scale) * glm::translate(_offset); + glm::mat4 modelToWorldMatrix = createMatFromQuatAndPos(_rotation, _translation); + glm::mat4 worldToHFMMatrix = glm::inverse(modelToWorldMatrix * hfmToModelMatrix); + return worldToHFMMatrix; } // TODO: deprecate and remove diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 93a0626d28..0f8eb782c3 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -192,7 +192,7 @@ public: bool didVisualGeometryRequestFail() const { return _visualGeometryRequestFailed; } bool didCollisionGeometryRequestFail() const { return _collisionGeometryRequestFailed; } - bool convexHullContains(glm::vec3 point); + glm::mat4 getWorldToHFMMatrix() const; QStringList getJointNames() const; diff --git a/libraries/render-utils/src/RenderCommonTask.cpp b/libraries/render-utils/src/RenderCommonTask.cpp index 4422d15d5c..40724cbf5a 100644 --- a/libraries/render-utils/src/RenderCommonTask.cpp +++ b/libraries/render-utils/src/RenderCommonTask.cpp @@ -203,7 +203,7 @@ void ExtractFrustums::run(const render::RenderContextPointer& renderContext, con RenderArgs* args = renderContext->args; - const auto& lightFrame = inputs; + const auto& shadowFrame = inputs; // Return view frustum auto& viewFrustum = output[VIEW_FRUSTUM].edit<ViewFrustumPointer>(); @@ -214,38 +214,18 @@ void ExtractFrustums::run(const render::RenderContextPointer& renderContext, con } // Return shadow frustum - auto lightStage = args->_scene->getStage<LightStage>(LightStage::getName()); + LightStage::ShadowPointer globalShadow; + if (shadowFrame && !shadowFrame->_objects.empty() && shadowFrame->_objects[0]) { + globalShadow = shadowFrame->_objects[0]; + } for (auto i = 0; i < SHADOW_CASCADE_FRUSTUM_COUNT; i++) { auto& shadowFrustum = output[SHADOW_CASCADE0_FRUSTUM+i].edit<ViewFrustumPointer>(); - if (lightStage) { - auto globalShadow = lightStage->getCurrentKeyShadow(*lightFrame); - - if (globalShadow && i<(int)globalShadow->getCascadeCount()) { - auto& cascade = globalShadow->getCascade(i); - shadowFrustum = cascade.getFrustum(); - } else { - shadowFrustum.reset(); - } + if (globalShadow && i<(int)globalShadow->getCascadeCount()) { + auto& cascade = globalShadow->getCascade(i); + shadowFrustum = cascade.getFrustum(); } else { shadowFrustum.reset(); } } } -void FetchCurrentFrames::run(const render::RenderContextPointer& renderContext, Outputs& outputs) { - auto lightStage = renderContext->_scene->getStage<LightStage>(); - assert(lightStage); - outputs.edit0() = std::make_shared<LightStage::Frame>(lightStage->_currentFrame); - - auto backgroundStage = renderContext->_scene->getStage<BackgroundStage>(); - assert(backgroundStage); - outputs.edit1() = std::make_shared<BackgroundStage::Frame>(backgroundStage->_currentFrame); - - auto hazeStage = renderContext->_scene->getStage<HazeStage>(); - assert(hazeStage); - outputs.edit2() = std::make_shared<HazeStage::Frame>(hazeStage->_currentFrame); - - auto bloomStage = renderContext->_scene->getStage<BloomStage>(); - assert(bloomStage); - outputs.edit3() = std::make_shared<BloomStage::Frame>(bloomStage->_currentFrame); -} diff --git a/libraries/render-utils/src/RenderCommonTask.h b/libraries/render-utils/src/RenderCommonTask.h index 9b611bc38d..29f195ffff 100644 --- a/libraries/render-utils/src/RenderCommonTask.h +++ b/libraries/render-utils/src/RenderCommonTask.h @@ -10,13 +10,8 @@ #define hifi_RenderCommonTask_h #include <gpu/Pipeline.h> -#include <render/RenderFetchCullSortTask.h> -#include "LightingModel.h" - #include "LightStage.h" -#include "BackgroundStage.h" -#include "HazeStage.h" -#include "BloomStage.h" +#include "LightingModel.h" class BeginGPURangeTimer { public: @@ -111,22 +106,11 @@ public: FRUSTUM_COUNT }; - using Inputs = LightStage::FramePointer; + using Inputs = LightStage::ShadowFramePointer; using Outputs = render::VaryingArray<ViewFrustumPointer, FRUSTUM_COUNT>; using JobModel = render::Job::ModelIO<ExtractFrustums, Inputs, Outputs>; void run(const render::RenderContextPointer& renderContext, const Inputs& inputs, Outputs& output); }; - -class FetchCurrentFrames { -public: - using Outputs = render::VaryingSet4<LightStage::FramePointer, BackgroundStage::FramePointer, HazeStage::FramePointer, BloomStage::FramePointer>; - using JobModel = render::Job::ModelO<FetchCurrentFrames, Outputs>; - - FetchCurrentFrames() {} - - void run(const render::RenderContextPointer& renderContext, Outputs& outputs); -}; - #endif // hifi_RenderDeferredTask_h diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index 4052b6bd5a..a685f3998e 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -72,6 +72,23 @@ namespace gr { } +class RenderDeferredTaskDebug { +public: + using ExtraBuffers = render::VaryingSet6<LinearDepthFramebufferPointer, SurfaceGeometryFramebufferPointer, AmbientOcclusionFramebufferPointer, gpu::BufferView, SubsurfaceScatteringResourcePointer, VelocityFramebufferPointer>; + using Input = render::VaryingSet9<RenderFetchCullSortTask::Output, RenderShadowTask::Output, + AssembleLightingStageTask::Output, LightClusteringPass::Output, + PrepareDeferred::Outputs, ExtraBuffers, GenerateDeferredFrameTransform::Output, + JitterSample::Output, LightingModel>; + + using JobModel = render::Task::ModelI<RenderDeferredTaskDebug, Input>; + + RenderDeferredTaskDebug(); + + void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs); +private: +}; + + RenderDeferredTask::RenderDeferredTask() { } @@ -86,37 +103,45 @@ void RenderDeferredTask::configure(const Config& config) { upsamplePrimaryBufferConfig->setProperty("factor", 1.0f / config.resolutionScale); } -const render::Varying RenderDeferredTask::addSelectItemJobs(JobModel& task, const char* selectionName, - const render::Varying& metas, - const render::Varying& opaques, - const render::Varying& transparents) { - const auto selectMetaInput = SelectItems::Inputs(metas, Varying(), std::string()).asVarying(); - const auto selectedMetas = task.addJob<SelectItems>("MetaSelection", selectMetaInput, selectionName); - const auto selectMetaAndOpaqueInput = SelectItems::Inputs(opaques, selectedMetas, std::string()).asVarying(); - const auto selectedMetasAndOpaques = task.addJob<SelectItems>("OpaqueSelection", selectMetaAndOpaqueInput, selectionName); - const auto selectItemInput = SelectItems::Inputs(transparents, selectedMetasAndOpaques, std::string()).asVarying(); - return task.addJob<SelectItems>("TransparentSelection", selectItemInput, selectionName); -} - -void RenderDeferredTask::build(JobModel& task, const render::Varying& input, render::Varying& output, bool renderShadows) { - const auto& inputs = input.get<Input>(); - const auto& items = inputs.get0(); - +void RenderDeferredTask::build(JobModel& task, const render::Varying& input, render::Varying& output) { auto fadeEffect = DependencyManager::get<FadeEffect>(); - // Prepare the ShapePipelines ShapePlumberPointer shapePlumber = std::make_shared<ShapePlumber>(); initDeferredPipelines(*shapePlumber, fadeEffect->getBatchSetter(), fadeEffect->getItemUniformSetter()); - // Extract opaques / transparents / lights / metas / overlays / background - const auto& opaques = items.get0()[RenderFetchCullSortTask::OPAQUE_SHAPE]; - const auto& transparents = items.get0()[RenderFetchCullSortTask::TRANSPARENT_SHAPE]; - const auto& lights = items.get0()[RenderFetchCullSortTask::LIGHT]; - const auto& metas = items.get0()[RenderFetchCullSortTask::META]; - const auto& overlayOpaques = items.get0()[RenderFetchCullSortTask::OVERLAY_OPAQUE_SHAPE]; - const auto& overlayTransparents = items.get0()[RenderFetchCullSortTask::OVERLAY_TRANSPARENT_SHAPE]; - //const auto& background = items.get0()[RenderFetchCullSortTask::BACKGROUND]; - const auto& spatialSelection = items[1]; + const auto& inputs = input.get<Input>(); + + // Separate the fetched items + const auto& fetchedItems = inputs.get0(); + + const auto& items = fetchedItems.get0(); + + // Extract opaques / transparents / lights / metas / overlays / background + const auto& opaques = items[RenderFetchCullSortTask::OPAQUE_SHAPE]; + const auto& transparents = items[RenderFetchCullSortTask::TRANSPARENT_SHAPE]; + const auto& overlaysInFrontOpaque = items[RenderFetchCullSortTask::LAYER_FRONT_OPAQUE_SHAPE]; + const auto& overlaysInFrontTransparent = items[RenderFetchCullSortTask::LAYER_FRONT_TRANSPARENT_SHAPE]; + const auto& overlaysHUDOpaque = items[RenderFetchCullSortTask::LAYER_HUD_OPAQUE_SHAPE]; + const auto& overlaysHUDTransparent = items[RenderFetchCullSortTask::LAYER_HUD_TRANSPARENT_SHAPE]; + + // Lighting model comes next, the big configuration of the view + const auto& lightingModel = inputs[1]; + + // Extract the Lighting Stages Current frame ( and zones) + const auto& lightingStageInputs = inputs.get2(); + // Fetch the current frame stacks from all the stages + const auto currentStageFrames = lightingStageInputs.get0(); + const auto lightFrame = currentStageFrames[0]; + const auto backgroundFrame = currentStageFrames[1]; + const auto& hazeFrame = currentStageFrames[2]; + const auto& bloomFrame = currentStageFrames[3]; + + // Shadow Task Outputs + const auto& shadowTaskOutputs = inputs.get3(); + + // Shadow Stage Frame + const auto shadowFrame = shadowTaskOutputs[1]; + fadeEffect->build(task, opaques); @@ -127,7 +152,6 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren // Prepare deferred, generate the shared Deferred Frame Transform. Only valid with the scaled frame buffer const auto deferredFrameTransform = task.addJob<GenerateDeferredFrameTransform>("DeferredFrameTransform", jitter); - const auto lightingModel = task.addJob<MakeLightingModel>("LightingModel"); const auto opaqueRangeTimer = task.addJob<BeginGPURangeTimer>("BeginOpaqueRangeTimer", "DrawOpaques"); @@ -164,38 +188,25 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren const auto scatteringResource = task.addJob<SubsurfaceScattering>("Scattering"); // AO job - const auto ambientOcclusionInputs = AmbientOcclusionEffect::Inputs(deferredFrameTransform, deferredFramebuffer, linearDepthTarget).asVarying(); + const auto ambientOcclusionInputs = AmbientOcclusionEffect::Input(lightingModel, deferredFrameTransform, deferredFramebuffer, linearDepthTarget).asVarying(); const auto ambientOcclusionOutputs = task.addJob<AmbientOcclusionEffect>("AmbientOcclusion", ambientOcclusionInputs); - const auto ambientOcclusionFramebuffer = ambientOcclusionOutputs.getN<AmbientOcclusionEffect::Outputs>(0); - const auto ambientOcclusionUniforms = ambientOcclusionOutputs.getN<AmbientOcclusionEffect::Outputs>(1); + const auto ambientOcclusionFramebuffer = ambientOcclusionOutputs.getN<AmbientOcclusionEffect::Output>(0); + const auto ambientOcclusionUniforms = ambientOcclusionOutputs.getN<AmbientOcclusionEffect::Output>(1); // Velocity const auto velocityBufferInputs = VelocityBufferPass::Inputs(deferredFrameTransform, deferredFramebuffer).asVarying(); const auto velocityBufferOutputs = task.addJob<VelocityBufferPass>("VelocityBuffer", velocityBufferInputs); const auto velocityBuffer = velocityBufferOutputs.getN<VelocityBufferPass::Outputs>(0); - // Clear Light, Haze, Bloom, and Skybox Stages and render zones from the general metas bucket - const auto zones = task.addJob<ZoneRendererTask>("ZoneRenderer", metas); - - // Draw Lights just add the lights to the current list of lights to deal with. NOt really gpu job for now. - task.addJob<DrawLight>("DrawLight", lights); - - // Fetch the current frame stacks from all the stages - const auto currentFrames = task.addJob<FetchCurrentFrames>("FetchCurrentFrames"); - const auto lightFrame = currentFrames.getN<FetchCurrentFrames::Outputs>(0); - const auto backgroundFrame = currentFrames.getN<FetchCurrentFrames::Outputs>(1); - const auto hazeFrame = currentFrames.getN<FetchCurrentFrames::Outputs>(2); - const auto bloomFrame = currentFrames.getN<FetchCurrentFrames::Outputs>(3); - // Light Clustering // Create the cluster grid of lights, cpu job for now - const auto lightClusteringPassInputs = LightClusteringPass::Inputs(deferredFrameTransform, lightingModel, lightFrame, linearDepthTarget).asVarying(); + const auto lightClusteringPassInputs = LightClusteringPass::Input(deferredFrameTransform, lightingModel, lightFrame, linearDepthTarget).asVarying(); const auto lightClusters = task.addJob<LightClusteringPass>("LightClustering", lightClusteringPassInputs); // DeferredBuffer is complete, now let's shade it into the LightingBuffer - const auto deferredLightingInputs = RenderDeferred::Inputs(deferredFrameTransform, deferredFramebuffer, lightingModel, - surfaceGeometryFramebuffer, ambientOcclusionFramebuffer, scatteringResource, lightClusters, lightFrame, hazeFrame).asVarying(); - task.addJob<RenderDeferred>("RenderDeferred", deferredLightingInputs, renderShadows); + const auto extraDeferredBuffer = RenderDeferred::ExtraDeferredBuffer(surfaceGeometryFramebuffer, ambientOcclusionFramebuffer, scatteringResource).asVarying(); + const auto deferredLightingInputs = RenderDeferred::Inputs(deferredFrameTransform, deferredFramebuffer, extraDeferredBuffer, lightingModel, lightClusters, lightFrame, shadowFrame, hazeFrame).asVarying(); + task.addJob<RenderDeferred>("RenderDeferred", deferredLightingInputs); // Similar to light stage, background stage has been filled by several potential render items and resolved for the frame in this job const auto backgroundInputs = DrawBackgroundStage::Inputs(lightingModel, backgroundFrame).asVarying(); @@ -205,43 +216,22 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob<DrawHaze>("DrawHazeDeferred", drawHazeInputs); // Render transparent objects forward in LightingBuffer - const auto transparentsInputs = DrawDeferred::Inputs(transparents, hazeFrame, lightFrame, lightingModel, lightClusters, jitter).asVarying(); + const auto transparentsInputs = DrawDeferred::Inputs(transparents, hazeFrame, lightFrame, lightingModel, lightClusters, shadowFrame, jitter).asVarying(); task.addJob<DrawDeferred>("DrawTransparentDeferred", transparentsInputs, shapePlumber); - // Light Cluster Grid Debuging job - { - const auto debugLightClustersInputs = DebugLightClusters::Inputs(deferredFrameTransform, deferredFramebuffer, lightingModel, linearDepthTarget, lightClusters).asVarying(); - task.addJob<DebugLightClusters>("DebugLightClusters", debugLightClustersInputs); - } - const auto outlineRangeTimer = task.addJob<BeginGPURangeTimer>("BeginHighlightRangeTimer", "Highlight"); - // Select items that need to be outlined - const auto selectionBaseName = "contextOverlayHighlightList"; - const auto selectedItems = addSelectItemJobs(task, selectionBaseName, metas, opaques, transparents); - const auto outlineInputs = DrawHighlightTask::Inputs(items.get0(), deferredFramebuffer, lightingFramebuffer, deferredFrameTransform, jitter).asVarying(); + const auto outlineInputs = DrawHighlightTask::Inputs(items, deferredFramebuffer, lightingFramebuffer, deferredFrameTransform, jitter).asVarying(); task.addJob<DrawHighlightTask>("DrawHighlight", outlineInputs); task.addJob<EndGPURangeTimer>("HighlightRangeTimer", outlineRangeTimer); - const auto overlaysInFrontRangeTimer = task.addJob<BeginGPURangeTimer>("BeginOverlaysInFrontRangeTimer", "BeginOverlaysInFrontRangeTimer"); - - // Layered Overlays - const auto filteredOverlaysOpaque = task.addJob<FilterLayeredItems>("FilterOverlaysLayeredOpaque", overlayOpaques, render::hifi::LAYER_3D_FRONT); - const auto filteredOverlaysTransparent = task.addJob<FilterLayeredItems>("FilterOverlaysLayeredTransparent", overlayTransparents, render::hifi::LAYER_3D_FRONT); - const auto overlaysInFrontOpaque = filteredOverlaysOpaque.getN<FilterLayeredItems::Outputs>(0); - const auto overlaysInFrontTransparent = filteredOverlaysTransparent.getN<FilterLayeredItems::Outputs>(0); - - // We don't want the overlay to clear the deferred frame buffer depth because we would like to keep it for debugging visualisation - // task.addJob<SetSeparateDeferredDepthBuffer>("SeparateDepthForOverlay", deferredFramebuffer); - + // Layered Over (in front) const auto overlayInFrontOpaquesInputs = DrawOverlay3D::Inputs(overlaysInFrontOpaque, lightingModel, jitter).asVarying(); const auto overlayInFrontTransparentsInputs = DrawOverlay3D::Inputs(overlaysInFrontTransparent, lightingModel, jitter).asVarying(); task.addJob<DrawOverlay3D>("DrawOverlayInFrontOpaque", overlayInFrontOpaquesInputs, true); task.addJob<DrawOverlay3D>("DrawOverlayInFrontTransparent", overlayInFrontTransparentsInputs, false); - task.addJob<EndGPURangeTimer>("OverlaysInFrontRangeTimer", overlaysInFrontRangeTimer); - const auto toneAndPostRangeTimer = task.addJob<BeginGPURangeTimer>("BeginToneAndPostRangeTimer", "PostToneOverlaysAntialiasing"); // AA job before bloom to limit flickering @@ -256,14 +246,115 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren const auto toneMappingInputs = ToneMappingDeferred::Inputs(lightingFramebuffer, scaledPrimaryFramebuffer).asVarying(); task.addJob<ToneMappingDeferred>("ToneMapping", toneMappingInputs); + // Debugging task is happening in the "over" layer after tone mapping and just before HUD + { // Debug the bounds of the rendered items, still look at the zbuffer + const auto extraDebugBuffers = RenderDeferredTaskDebug::ExtraBuffers(linearDepthTarget, surfaceGeometryFramebuffer, ambientOcclusionFramebuffer, ambientOcclusionFramebuffer, scatteringResource, velocityBuffer); + const auto debugInputs = RenderDeferredTaskDebug::Input(fetchedItems, shadowTaskOutputs, lightingStageInputs, lightClusters, prepareDeferredOutputs, extraDebugBuffers, + deferredFrameTransform, jitter, lightingModel).asVarying(); + task.addJob<RenderDeferredTaskDebug>("DebugRenderDeferredTask", debugInputs); + } + + // Upscale to finale resolution + const auto primaryFramebuffer = task.addJob<render::Upsample>("PrimaryBufferUpscale", scaledPrimaryFramebuffer); + + // Composite the HUD and HUD overlays + task.addJob<CompositeHUD>("HUD"); + + const auto nullJitter = Varying(glm::vec2(0.0f, 0.0f)); + const auto overlayHUDOpaquesInputs = DrawOverlay3D::Inputs(overlaysHUDOpaque, lightingModel, nullJitter).asVarying(); + const auto overlayHUDTransparentsInputs = DrawOverlay3D::Inputs(overlaysHUDTransparent, lightingModel, nullJitter).asVarying(); + task.addJob<DrawOverlay3D>("DrawOverlayHUDOpaque", overlayHUDOpaquesInputs, true); + task.addJob<DrawOverlay3D>("DrawOverlayHUDTransparent", overlayHUDTransparentsInputs, false); + + task.addJob<EndGPURangeTimer>("ToneAndPostRangeTimer", toneAndPostRangeTimer); + + // Blit! + task.addJob<Blit>("Blit", primaryFramebuffer); +} + +RenderDeferredTaskDebug::RenderDeferredTaskDebug() { +} + +void RenderDeferredTaskDebug::build(JobModel& task, const render::Varying& input, render::Varying& outputs) { + + const auto& inputs = input.get<Input>(); + + // RenderFetchCullSortTask out + const auto& fetchCullSortTaskOut = inputs.get0(); + const auto& items = fetchCullSortTaskOut.get0(); + + // Extract opaques / transparents / lights / metas / overlays InFront and HUD / background + const auto& opaques = items[RenderFetchCullSortTask::OPAQUE_SHAPE]; + const auto& transparents = items[RenderFetchCullSortTask::TRANSPARENT_SHAPE]; + const auto& lights = items[RenderFetchCullSortTask::LIGHT]; + const auto& metas = items[RenderFetchCullSortTask::META]; + const auto& overlaysInFrontOpaque = items[RenderFetchCullSortTask::LAYER_FRONT_OPAQUE_SHAPE]; + const auto& overlaysInFrontTransparent = items[RenderFetchCullSortTask::LAYER_FRONT_TRANSPARENT_SHAPE]; + const auto& overlaysHUDOpaque = items[RenderFetchCullSortTask::LAYER_HUD_OPAQUE_SHAPE]; + const auto& overlaysHUDTransparent = items[RenderFetchCullSortTask::LAYER_HUD_TRANSPARENT_SHAPE]; + + const auto& spatialSelection = fetchCullSortTaskOut[1]; + + // RenderShadowTask out + const auto& shadowOut = inputs.get1(); + + const auto& renderShadowTaskOut = shadowOut[0]; + const auto& shadowFrame = shadowOut[1]; + + // Extract the Lighting Stages Current frame ( and zones) + const auto lightingStageInputs = inputs.get2(); + // Fetch the current frame stacks from all the stages + const auto stageCurrentFrames = lightingStageInputs.get0(); + const auto lightFrame = stageCurrentFrames[0]; + const auto backgroundFrame = stageCurrentFrames[1]; + const auto hazeFrame = stageCurrentFrames[2]; + const auto bloomFrame = stageCurrentFrames[3]; + + // Zones + const auto& zones = lightingStageInputs[1]; + + // Light CLuster + const auto& lightClusters = inputs[3]; + + // PrepareDeferred out + const auto& prepareDeferredOutputs = inputs.get4(); + const auto& deferredFramebuffer = prepareDeferredOutputs[0]; + + // extraDeferredBuffer + const auto& extraDeferredBuffer = inputs.get5(); + const auto& linearDepthTarget = extraDeferredBuffer[0]; + const auto& surfaceGeometryFramebuffer = extraDeferredBuffer[1]; + const auto& ambientOcclusionFramebuffer = extraDeferredBuffer[2]; + const auto& ambientOcclusionUniforms = extraDeferredBuffer[3]; + const auto& scatteringResource = extraDeferredBuffer[4]; + const auto& velocityBuffer = extraDeferredBuffer[5]; + + // GenerateDeferredFrameTransform out + const auto& deferredFrameTransform = inputs[6]; + + // Jitter out + const auto& jitter = inputs[7]; + + // Lighting Model out + const auto& lightingModel = inputs[8]; + + + + // Light Cluster Grid Debuging job + { + const auto debugLightClustersInputs = DebugLightClusters::Inputs(deferredFrameTransform, lightingModel, linearDepthTarget, lightClusters).asVarying(); + task.addJob<DebugLightClusters>("DebugLightClusters", debugLightClustersInputs); + } + + { // Debug the bounds of the rendered items, still look at the zbuffer task.addJob<DrawBounds>("DrawMetaBounds", metas); task.addJob<DrawBounds>("DrawOpaqueBounds", opaques); task.addJob<DrawBounds>("DrawTransparentBounds", transparents); - + task.addJob<DrawBounds>("DrawLightBounds", lights); task.addJob<DrawBounds>("DrawZones", zones); - const auto frustums = task.addJob<ExtractFrustums>("ExtractFrustums", lightFrame); + const auto frustums = task.addJob<ExtractFrustums>("ExtractFrustums", shadowFrame); const auto viewFrustum = frustums.getN<ExtractFrustums::Outputs>(ExtractFrustums::VIEW_FRUSTUM); task.addJob<DrawFrustum>("DrawViewFrustum", viewFrustum, glm::vec3(0.0f, 1.0f, 0.0f)); for (auto i = 0; i < ExtractFrustums::SHADOW_CASCADE_FRUSTUM_COUNT; i++) { @@ -272,13 +363,26 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren char jobName[64]; sprintf(jobName, "DrawShadowFrustum%d", i); task.addJob<DrawFrustum>(jobName, shadowFrustum, glm::vec3(0.0f, tint, 1.0f)); - if (!inputs[1].isNull()) { - const auto& shadowCascadeSceneBBoxes = inputs.get1(); + if (!renderShadowTaskOut.isNull()) { + const auto& shadowCascadeSceneBBoxes = renderShadowTaskOut; const auto shadowBBox = shadowCascadeSceneBBoxes[ExtractFrustums::SHADOW_CASCADE0_FRUSTUM + i]; sprintf(jobName, "DrawShadowBBox%d", i); task.addJob<DrawAABox>(jobName, shadowBBox, glm::vec3(1.0f, tint, 0.0f)); } } + } + + { // Debug Selection... + // TODO: It s busted + // Select items that need to be outlined and show them + const auto selectionBaseName = "contextOverlayHighlightList"; + + const auto selectMetaInput = SelectItems::Inputs(metas, Varying(), std::string()).asVarying(); + const auto selectedMetas = task.addJob<SelectItems>("MetaSelection", selectMetaInput, selectionBaseName); + const auto selectMetaAndOpaqueInput = SelectItems::Inputs(opaques, selectedMetas, std::string()).asVarying(); + const auto selectedMetasAndOpaques = task.addJob<SelectItems>("OpaqueSelection", selectMetaAndOpaqueInput, selectionBaseName); + const auto selectItemInput = SelectItems::Inputs(transparents, selectedMetasAndOpaques, std::string()).asVarying(); + const auto selectedItems = task.addJob<SelectItems>("TransparentSelection", selectItemInput, selectionBaseName); // Render.getConfig("RenderMainView.DrawSelectionBounds").enabled = true task.addJob<DrawBounds>("DrawSelectionBounds", selectedItems); @@ -289,10 +393,16 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren task.addJob<DrawBounds>("DrawOverlayInFrontTransparentBounds", overlaysInFrontTransparent); } + { // Debug the bounds of the rendered Overlay items that are marked drawHUDLayer, still look at the zbuffer + task.addJob<DrawBounds>("DrawOverlayHUDOpaqueBounds", overlaysHUDOpaque); + task.addJob<DrawBounds>("DrawOverlayHUDTransparentBounds", overlaysHUDTransparent); + } + // Debugging stages { + // Debugging Deferred buffer job - const auto debugFramebuffers = render::Varying(DebugDeferredBuffer::Inputs(deferredFramebuffer, linearDepthTarget, surfaceGeometryFramebuffer, ambientOcclusionFramebuffer, velocityBuffer, deferredFrameTransform, lightFrame)); + const auto debugFramebuffers = DebugDeferredBuffer::Inputs(deferredFramebuffer, linearDepthTarget, surfaceGeometryFramebuffer, ambientOcclusionFramebuffer, velocityBuffer, deferredFrameTransform, shadowFrame).asVarying(); task.addJob<DebugDeferredBuffer>("DebugDeferredBuffer", debugFramebuffers); const auto debugSubsurfaceScatteringInputs = DebugSubsurfaceScattering::Inputs(deferredFrameTransform, deferredFramebuffer, lightingModel, @@ -310,7 +420,7 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren // Status icon rendering job { - // Grab a texture map representing the different status icons and assign that to the drawStatsuJob + // Grab a texture map representing the different status icons and assign that to the drawStatusJob auto iconMapPath = PathUtils::resourcesPath() + "icons/statusIconAtlas.svg"; auto statusIconMap = DependencyManager::get<TextureCache>()->getImageTexture(iconMapPath, image::TextureUsage::STRICT_TEXTURE); const auto drawStatusInputs = DrawStatus::Input(opaques, jitter).asVarying(); @@ -319,34 +429,13 @@ void RenderDeferredTask::build(JobModel& task, const render::Varying& input, ren const auto debugZoneInputs = DebugZoneLighting::Inputs(deferredFrameTransform, lightFrame, backgroundFrame).asVarying(); task.addJob<DebugZoneLighting>("DrawZoneStack", debugZoneInputs); + } - // Upscale to finale resolution - const auto primaryFramebuffer = task.addJob<render::Upsample>("PrimaryBufferUpscale", scaledPrimaryFramebuffer); - // Composite the HUD and HUD overlays - task.addJob<CompositeHUD>("HUD"); - - const auto overlaysHUDOpaque = filteredOverlaysOpaque.getN<FilterLayeredItems::Outputs>(1); - const auto overlaysHUDTransparent = filteredOverlaysTransparent.getN<FilterLayeredItems::Outputs>(1); - const auto nullJitter = Varying(glm::vec2(0.0f, 0.0f)); - - const auto overlayHUDOpaquesInputs = DrawOverlay3D::Inputs(overlaysHUDOpaque, lightingModel, nullJitter).asVarying(); - const auto overlayHUDTransparentsInputs = DrawOverlay3D::Inputs(overlaysHUDTransparent, lightingModel, nullJitter).asVarying(); - task.addJob<DrawOverlay3D>("DrawOverlayHUDOpaque", overlayHUDOpaquesInputs, true); - task.addJob<DrawOverlay3D>("DrawOverlayHUDTransparent", overlayHUDTransparentsInputs, false); - - { // Debug the bounds of the rendered Overlay items that are marked drawHUDLayer, still look at the zbuffer - task.addJob<DrawBounds>("DrawOverlayHUDOpaqueBounds", overlaysHUDOpaque); - task.addJob<DrawBounds>("DrawOverlayHUDTransparentBounds", overlaysHUDTransparent); - } - - task.addJob<EndGPURangeTimer>("ToneAndPostRangeTimer", toneAndPostRangeTimer); - - // Blit! - task.addJob<Blit>("Blit", primaryFramebuffer); } + void DrawDeferred::run(const RenderContextPointer& renderContext, const Inputs& inputs) { assert(renderContext->args); assert(renderContext->args->hasViewFrustum()); @@ -358,7 +447,8 @@ void DrawDeferred::run(const RenderContextPointer& renderContext, const Inputs& const auto& lightFrame = inputs.get2(); const auto& lightingModel = inputs.get3(); const auto& lightClusters = inputs.get4(); - const auto jitter = inputs.get5(); + // Not needed yet: const auto& shadowFrame = inputs.get5(); + const auto jitter = inputs.get6(); auto deferredLightingEffect = DependencyManager::get<DeferredLightingEffect>(); RenderArgs* args = renderContext->args; diff --git a/libraries/render-utils/src/RenderDeferredTask.h b/libraries/render-utils/src/RenderDeferredTask.h index c18daa6d3d..0a188ec3a6 100644 --- a/libraries/render-utils/src/RenderDeferredTask.h +++ b/libraries/render-utils/src/RenderDeferredTask.h @@ -14,10 +14,10 @@ #include <gpu/Pipeline.h> #include <render/RenderFetchCullSortTask.h> +#include "AssembleLightingStageTask.h" #include "LightingModel.h" #include "LightClusters.h" #include "RenderShadowTask.h" -#include "HazeStage.h" class DrawDeferredConfig : public render::Job::Config { Q_OBJECT @@ -43,7 +43,7 @@ protected: class DrawDeferred { public: - using Inputs = render::VaryingSet6<render::ItemBounds, HazeStage::FramePointer, LightStage::FramePointer, LightingModelPointer, LightClustersPointer, glm::vec2>; + using Inputs = render::VaryingSet7<render::ItemBounds, HazeStage::FramePointer, LightStage::FramePointer, LightingModelPointer, LightClustersPointer, LightStage::ShadowFramePointer, glm::vec2>; using Config = DrawDeferredConfig; using JobModel = render::Job::ModelI<DrawDeferred, Inputs, Config>; @@ -137,21 +137,16 @@ signals: class RenderDeferredTask { public: - using Input = render::VaryingSet2<RenderFetchCullSortTask::Output, RenderShadowTask::Output>; + using Input = render::VaryingSet4<RenderFetchCullSortTask::Output, LightingModelPointer, AssembleLightingStageTask::Output, RenderShadowTask::Output>; using Config = RenderDeferredTaskConfig; using JobModel = render::Task::ModelI<RenderDeferredTask, Input, Config>; RenderDeferredTask(); void configure(const Config& config); - void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, bool renderShadows); + void build(JobModel& task, const render::Varying& input, render::Varying& output); private: - static const render::Varying addSelectItemJobs(JobModel& task, - const char* selectionName, - const render::Varying& metas, - const render::Varying& opaques, - const render::Varying& transparents); }; #endif // hifi_RenderDeferredTask_h diff --git a/libraries/render-utils/src/RenderForwardTask.cpp b/libraries/render-utils/src/RenderForwardTask.cpp index 53f89aaec3..ffdbc1c4b1 100755 --- a/libraries/render-utils/src/RenderForwardTask.cpp +++ b/libraries/render-utils/src/RenderForwardTask.cpp @@ -48,39 +48,46 @@ using namespace render; extern void initForwardPipelines(ShapePlumber& plumber); void RenderForwardTask::build(JobModel& task, const render::Varying& input, render::Varying& output) { - auto items = input.get<Input>(); - auto fadeEffect = DependencyManager::get<FadeEffect>(); - // Prepare the ShapePipelines + auto fadeEffect = DependencyManager::get<FadeEffect>(); ShapePlumberPointer shapePlumber = std::make_shared<ShapePlumber>(); initForwardPipelines(*shapePlumber); - // Extract opaques / transparents / lights / metas / overlays / background - const auto& opaques = items.get0()[RenderFetchCullSortTask::OPAQUE_SHAPE]; - const auto& transparents = items.get0()[RenderFetchCullSortTask::TRANSPARENT_SHAPE]; - //const auto& lights = items.get0()[RenderFetchCullSortTask::LIGHT]; - const auto& metas = items.get0()[RenderFetchCullSortTask::META]; - const auto& overlayOpaques = items.get0()[RenderFetchCullSortTask::OVERLAY_OPAQUE_SHAPE]; - const auto& overlayTransparents = items.get0()[RenderFetchCullSortTask::OVERLAY_TRANSPARENT_SHAPE]; + // Unpack inputs + const auto& inputs = input.get<Input>(); + + // Separate the fetched items + const auto& fetchedItems = inputs.get0(); - //const auto& background = items.get0()[RenderFetchCullSortTask::BACKGROUND]; - //const auto& spatialSelection = items[1]; + const auto& items = fetchedItems.get0(); + // Extract opaques / transparents / lights / metas / overlays / background + const auto& opaques = items[RenderFetchCullSortTask::OPAQUE_SHAPE]; + const auto& transparents = items[RenderFetchCullSortTask::TRANSPARENT_SHAPE]; + const auto& metas = items[RenderFetchCullSortTask::META]; + const auto& overlaysInFrontOpaque = items[RenderFetchCullSortTask::LAYER_FRONT_OPAQUE_SHAPE]; + const auto& overlaysInFrontTransparent = items[RenderFetchCullSortTask::LAYER_FRONT_TRANSPARENT_SHAPE]; + // TODO: Re enable the rendering of the HUD overlayes + // const auto& overlaysHUDOpaque = items[RenderFetchCullSortTask::LAYER_HUD_OPAQUE_SHAPE]; + // const auto& overlaysHUDTransparent = items[RenderFetchCullSortTask::LAYER_HUD_TRANSPARENT_SHAPE]; + + // Lighting model comes next, the big configuration of the view + const auto& lightingModel = inputs[1]; + + // Extract the Lighting Stages Current frame ( and zones) + const auto& lightingStageInputs = inputs.get2(); + // Fetch the current frame stacks from all the stages + const auto currentStageFrames = lightingStageInputs.get0(); + const auto lightFrame = currentStageFrames[0]; + const auto backgroundFrame = currentStageFrames[1]; + + const auto& zones = lightingStageInputs[1]; + + // First job, alter faded fadeEffect->build(task, opaques); // Prepare objects shared by several jobs const auto deferredFrameTransform = task.addJob<GenerateDeferredFrameTransform>("DeferredFrameTransform"); - const auto lightingModel = task.addJob<MakeLightingModel>("LightingModel"); - - // Filter zones from the general metas bucket - const auto zones = task.addJob<ZoneRendererTask>("ZoneRenderer", metas); - - // Fetch the current frame stacks from all the stages - const auto currentFrames = task.addJob<FetchCurrentFrames>("FetchCurrentFrames"); - const auto lightFrame = currentFrames.getN<FetchCurrentFrames::Outputs>(0); - const auto backgroundFrame = currentFrames.getN<FetchCurrentFrames::Outputs>(1); - //const auto hazeFrame = currentFrames.getN<FetchCurrentFrames::Outputs>(2); - //const auto bloomFrame = currentFrames.getN<FetchCurrentFrames::Outputs>(3); // GPU jobs: Start preparing the main framebuffer const auto framebuffer = task.addJob<PrepareFramebuffer>("PrepareFramebuffer"); @@ -91,12 +98,9 @@ void RenderForwardTask::build(JobModel& task, const render::Varying& input, rend task.addJob<PrepareStencil>("PrepareStencil", framebuffer); // Layered Overlays - const auto filteredOverlaysOpaque = task.addJob<FilterLayeredItems>("FilterOverlaysLayeredOpaque", overlayOpaques, render::hifi::LAYER_3D_FRONT); - const auto filteredOverlaysTransparent = task.addJob<FilterLayeredItems>("FilterOverlaysLayeredTransparent", overlayTransparents, render::hifi::LAYER_3D_FRONT); - const auto overlaysInFrontOpaque = filteredOverlaysOpaque.getN<FilterLayeredItems::Outputs>(0); - const auto overlaysInFrontTransparent = filteredOverlaysTransparent.getN<FilterLayeredItems::Outputs>(0); const auto nullJitter = Varying(glm::vec2(0.0f, 0.0f)); + // Layered Over (in front) const auto overlayInFrontOpaquesInputs = DrawOverlay3D::Inputs(overlaysInFrontOpaque, lightingModel, nullJitter).asVarying(); const auto overlayInFrontTransparentsInputs = DrawOverlay3D::Inputs(overlaysInFrontTransparent, lightingModel, nullJitter).asVarying(); task.addJob<DrawOverlay3D>("DrawOverlayInFrontOpaque", overlayInFrontOpaquesInputs, true); diff --git a/libraries/render-utils/src/RenderForwardTask.h b/libraries/render-utils/src/RenderForwardTask.h index 54341d1ded..e6a6008319 100755 --- a/libraries/render-utils/src/RenderForwardTask.h +++ b/libraries/render-utils/src/RenderForwardTask.h @@ -14,17 +14,17 @@ #include <gpu/Pipeline.h> #include <render/RenderFetchCullSortTask.h> +#include "AssembleLightingStageTask.h" #include "LightingModel.h" -#include "LightStage.h" class RenderForwardTask { public: - using Input = RenderFetchCullSortTask::Output; + using Input = render::VaryingSet3<RenderFetchCullSortTask::Output, LightingModelPointer, AssembleLightingStageTask::Output>; using JobModel = render::Task::ModelI<RenderForwardTask, Input>; RenderForwardTask() {} - void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs); + void build(JobModel& task, const render::Varying& input, render::Varying& output); }; class PrepareFramebuffer { diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index b5b3e9c5b9..5456453d8a 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -25,6 +25,7 @@ #include "RenderUtilsLogging.h" #include "RenderCommonTask.h" +#include "AssembleLightingStageTask.h" #include "FadeEffect.h" @@ -32,13 +33,15 @@ // but are readjusted afterwards #define SHADOW_FRUSTUM_NEAR 1.0f #define SHADOW_FRUSTUM_FAR 500.0f +static const unsigned int SHADOW_CASCADE_COUNT{ 4 }; +static const float SHADOW_MAX_DISTANCE{ 40.0f }; using namespace render; extern void initZPassPipelines(ShapePlumber& plumber, gpu::StatePointer state, const render::ShapePipeline::BatchSetter& batchSetter, const render::ShapePipeline::ItemSetter& itemSetter); void RenderShadowTask::configure(const Config& configuration) { - DependencyManager::get<DeferredLightingEffect>()->setShadowMapEnabled(configuration.isEnabled()); + //DependencyManager::get<DeferredLightingEffect>()->setShadowMapEnabled(configuration.isEnabled()); // This is a task, so must still propogate configure() to its Jobs // Task::configure(configuration); } @@ -57,11 +60,12 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende // FIXME: calling this here before the zones/lights are drawn during the deferred/forward passes means we're actually using the frames from the previous draw // Fetch the current frame stacks from all the stages - const auto currentFrames = task.addJob<FetchCurrentFrames>("FetchCurrentFrames"); - const auto lightFrame = currentFrames.getN<FetchCurrentFrames::Outputs>(0); + // Starting with the Light Frame genreated in previous tasks - const auto setupOutput = task.addJob<RenderShadowSetup>("ShadowSetup", lightFrame); - const auto queryResolution = setupOutput.getN<RenderShadowSetup::Outputs>(1); + const auto setupOutput = task.addJob<RenderShadowSetup>("ShadowSetup", input); + const auto queryResolution = setupOutput.getN<RenderShadowSetup::Output>(1); + const auto shadowFrame = setupOutput.getN<RenderShadowSetup::Output>(3); + const auto currentKeyLight = setupOutput.getN<RenderShadowSetup::Output>(4); // Fetch and cull the items from the scene static const auto shadowCasterReceiverFilter = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(tagBits, tagMask); @@ -73,7 +77,7 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende // Cull objects that are not visible in camera view. Hopefully the cull functor only performs LOD culling, not // frustum culling or this will make shadow casters out of the camera frustum disappear. - const auto cameraFrustum = setupOutput.getN<RenderShadowSetup::Outputs>(2); + const auto cameraFrustum = setupOutput.getN<RenderShadowSetup::Output>(2); const auto applyFunctorInputs = ApplyCullFunctorOnItemBounds::Inputs(shadowItems, cameraFrustum).asVarying(); const auto culledShadowItems = task.addJob<ApplyCullFunctorOnItemBounds>("ShadowCullCamera", applyFunctorInputs, cameraCullFunctor); @@ -90,12 +94,12 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende #endif }; - Output cascadeSceneBBoxes; + render::VaryingArray<AABox,4> cascadeSceneBBoxes; for (auto i = 0; i < SHADOW_CASCADE_MAX_COUNT; i++) { char jobName[64]; sprintf(jobName, "ShadowCascadeSetup%d", i); - const auto cascadeSetupOutput = task.addJob<RenderShadowCascadeSetup>(jobName, lightFrame, i, tagBits, tagMask); + const auto cascadeSetupOutput = task.addJob<RenderShadowCascadeSetup>(jobName, shadowFrame, i, tagBits, tagMask); const auto shadowFilter = cascadeSetupOutput.getN<RenderShadowCascadeSetup::Outputs>(0); auto antiFrustum = render::Varying(ViewFrustumPointer()); cascadeFrustums[i] = cascadeSetupOutput.getN<RenderShadowCascadeSetup::Outputs>(1); @@ -103,24 +107,25 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende antiFrustum = cascadeFrustums[i - 2]; } - const auto cullInputs = CullShadowBounds::Inputs(sortedShapes, shadowFilter, antiFrustum, lightFrame, cascadeSetupOutput.getN<RenderShadowCascadeSetup::Outputs>(2)).asVarying(); + const auto cullInputs = CullShadowBounds::Inputs(sortedShapes, shadowFilter, antiFrustum, currentKeyLight, cascadeSetupOutput.getN<RenderShadowCascadeSetup::Outputs>(2)).asVarying(); sprintf(jobName, "CullShadowCascade%d", i); const auto culledShadowItemsAndBounds = task.addJob<CullShadowBounds>(jobName, cullInputs); // GPU jobs: Render to shadow map sprintf(jobName, "RenderShadowMap%d", i); const auto shadowInputs = RenderShadowMap::Inputs(culledShadowItemsAndBounds.getN<CullShadowBounds::Outputs>(0), - culledShadowItemsAndBounds.getN<CullShadowBounds::Outputs>(1), lightFrame).asVarying(); + culledShadowItemsAndBounds.getN<CullShadowBounds::Outputs>(1), shadowFrame).asVarying(); task.addJob<RenderShadowMap>(jobName, shadowInputs, shapePlumber, i); sprintf(jobName, "ShadowCascadeTeardown%d", i); task.addJob<RenderShadowCascadeTeardown>(jobName, shadowFilter); cascadeSceneBBoxes[i] = culledShadowItemsAndBounds.getN<CullShadowBounds::Outputs>(1); } - - output = render::Varying(cascadeSceneBBoxes); - task.addJob<RenderShadowTeardown>("ShadowTeardown", setupOutput); + + + output = Output(cascadeSceneBBoxes, setupOutput.getN<RenderShadowSetup::Output>(3)); + } static void computeNearFar(const Triangle& triangle, const Plane shadowClipPlanes[4], float& near, float& far) { @@ -211,12 +216,12 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con const auto& inShapes = inputs.get0(); const auto& inShapeBounds = inputs.get1(); - const auto& lightFrame = inputs.get2(); + const auto& shadowFrame = inputs.get2(); - auto lightStage = renderContext->_scene->getStage<LightStage>(); - assert(lightStage); - - auto shadow = lightStage->getCurrentKeyShadow(*lightFrame); + LightStage::ShadowPointer shadow; + if (shadowFrame && !shadowFrame->_objects.empty()) { + shadow = shadowFrame->_objects.front(); + } if (!shadow || _cascadeIndex >= shadow->getCascadeCount()) { return; } @@ -314,7 +319,7 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con RenderShadowSetup::RenderShadowSetup() : _cameraFrustum{ std::make_shared<ViewFrustum>() }, _coarseShadowFrustum{ std::make_shared<ViewFrustum>() } { - + _shadowFrameCache = std::make_shared<LightStage::ShadowFrame>(); } void RenderShadowSetup::configure(const Config& configuration) { @@ -338,123 +343,141 @@ void RenderShadowSetup::setSlopeBias(int cascadeIndex, float value) { _bias[cascadeIndex]._slope = value * value * value * 0.01f; } -void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, const Inputs& input, Outputs& output) { +void RenderShadowSetup::run(const render::RenderContextPointer& renderContext, const Input& input, Output& output) { // Abort all jobs if not casting shadows auto lightStage = renderContext->_scene->getStage<LightStage>(); - auto lightFrame = *input; assert(lightStage); - if (!lightStage->getCurrentKeyLight(lightFrame) || !lightStage->getCurrentKeyLight(lightFrame)->getCastShadows()) { + + const auto lightFrame = *input.get0(); + const auto lightingModel = input.get1(); + + // Clear previous shadow frame always + _shadowFrameCache->_objects.clear(); + output.edit3() = _shadowFrameCache; + + const auto currentKeyLight = lightStage->getCurrentKeyLight(lightFrame); + if (!lightingModel->isShadowEnabled() || !currentKeyLight || !currentKeyLight->getCastShadows()) { renderContext->taskFlow.abortTask(); return; } + output.edit4() = currentKeyLight; // Cache old render args RenderArgs* args = renderContext->args; output.edit0() = args->_renderMode; - output.edit1() = glm::ivec2(0, 0); // Save main camera frustum *_cameraFrustum = args->getViewFrustum(); output.edit2() = _cameraFrustum; - const auto globalShadow = lightStage->getCurrentKeyShadow(lightFrame); - if (globalShadow) { - globalShadow->setKeylightFrustum(args->getViewFrustum(), SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR); - - auto& firstCascade = globalShadow->getCascade(0); - auto& firstCascadeFrustum = firstCascade.getFrustum(); - unsigned int cascadeIndex; - - // Adjust each cascade frustum - for (cascadeIndex = 0; cascadeIndex < globalShadow->getCascadeCount(); ++cascadeIndex) { - auto& bias = _bias[cascadeIndex]; - globalShadow->setKeylightCascadeFrustum(cascadeIndex, args->getViewFrustum(), - SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR, - bias._constant, bias._slope); - } - - // Now adjust coarse frustum bounds - auto frustumPosition = firstCascadeFrustum->getPosition(); - auto farTopLeft = firstCascadeFrustum->getFarTopLeft() - frustumPosition; - auto farBottomRight = firstCascadeFrustum->getFarBottomRight() - frustumPosition; - - auto left = glm::dot(farTopLeft, firstCascadeFrustum->getRight()); - auto right = glm::dot(farBottomRight, firstCascadeFrustum->getRight()); - auto top = glm::dot(farTopLeft, firstCascadeFrustum->getUp()); - auto bottom = glm::dot(farBottomRight, firstCascadeFrustum->getUp()); - auto near = firstCascadeFrustum->getNearClip(); - auto far = firstCascadeFrustum->getFarClip(); - - for (cascadeIndex = 1; cascadeIndex < globalShadow->getCascadeCount(); ++cascadeIndex) { - auto& cascadeFrustum = globalShadow->getCascade(cascadeIndex).getFrustum(); - - farTopLeft = cascadeFrustum->getFarTopLeft() - frustumPosition; - farBottomRight = cascadeFrustum->getFarBottomRight() - frustumPosition; - - auto cascadeLeft = glm::dot(farTopLeft, cascadeFrustum->getRight()); - auto cascadeRight = glm::dot(farBottomRight, cascadeFrustum->getRight()); - auto cascadeTop = glm::dot(farTopLeft, cascadeFrustum->getUp()); - auto cascadeBottom = glm::dot(farBottomRight, cascadeFrustum->getUp()); - auto cascadeNear = cascadeFrustum->getNearClip(); - auto cascadeFar = cascadeFrustum->getFarClip(); - left = glm::min(left, cascadeLeft); - right = glm::max(right, cascadeRight); - bottom = glm::min(bottom, cascadeBottom); - top = glm::max(top, cascadeTop); - near = glm::min(near, cascadeNear); - far = glm::max(far, cascadeFar); - } - - _coarseShadowFrustum->setPosition(firstCascadeFrustum->getPosition()); - _coarseShadowFrustum->setOrientation(firstCascadeFrustum->getOrientation()); - _coarseShadowFrustum->setProjection(glm::ortho<float>(left, right, bottom, top, near, far)); - _coarseShadowFrustum->calculate(); - - // Push frustum for further culling and selection - args->pushViewFrustum(*_coarseShadowFrustum); - - args->_renderMode = RenderArgs::SHADOW_RENDER_MODE; - - // We want for the octree query enough resolution to catch the details in the lowest cascade. So compute - // the desired resolution for the first cascade frustum and extrapolate it to the coarse frustum. - glm::ivec2 queryResolution = firstCascade.framebuffer->getSize(); - queryResolution.x = int(queryResolution.x * _coarseShadowFrustum->getWidth() / firstCascadeFrustum->getWidth()); - queryResolution.y = int(queryResolution.y * _coarseShadowFrustum->getHeight() / firstCascadeFrustum->getHeight()); - output.edit1() = queryResolution; + if (!_globalShadowObject) { + _globalShadowObject = std::make_shared<LightStage::Shadow>(graphics::LightPointer(), SHADOW_MAX_DISTANCE, SHADOW_CASCADE_COUNT); } + + _globalShadowObject->setLight(currentKeyLight); + _globalShadowObject->setKeylightFrustum(args->getViewFrustum(), SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR); + + auto& firstCascade = _globalShadowObject->getCascade(0); + auto& firstCascadeFrustum = firstCascade.getFrustum(); + unsigned int cascadeIndex; + + // Adjust each cascade frustum + for (cascadeIndex = 0; cascadeIndex < _globalShadowObject->getCascadeCount(); ++cascadeIndex) { + auto& bias = _bias[cascadeIndex]; + _globalShadowObject->setKeylightCascadeFrustum(cascadeIndex, args->getViewFrustum(), + SHADOW_FRUSTUM_NEAR, SHADOW_FRUSTUM_FAR, + bias._constant, bias._slope); + } + + _shadowFrameCache->pushShadow(_globalShadowObject); + + // Now adjust coarse frustum bounds + auto frustumPosition = firstCascadeFrustum->getPosition(); + auto farTopLeft = firstCascadeFrustum->getFarTopLeft() - frustumPosition; + auto farBottomRight = firstCascadeFrustum->getFarBottomRight() - frustumPosition; + + auto left = glm::dot(farTopLeft, firstCascadeFrustum->getRight()); + auto right = glm::dot(farBottomRight, firstCascadeFrustum->getRight()); + auto top = glm::dot(farTopLeft, firstCascadeFrustum->getUp()); + auto bottom = glm::dot(farBottomRight, firstCascadeFrustum->getUp()); + auto near = firstCascadeFrustum->getNearClip(); + auto far = firstCascadeFrustum->getFarClip(); + + for (cascadeIndex = 1; cascadeIndex < _globalShadowObject->getCascadeCount(); ++cascadeIndex) { + auto& cascadeFrustum = _globalShadowObject->getCascade(cascadeIndex).getFrustum(); + + farTopLeft = cascadeFrustum->getFarTopLeft() - frustumPosition; + farBottomRight = cascadeFrustum->getFarBottomRight() - frustumPosition; + + auto cascadeLeft = glm::dot(farTopLeft, cascadeFrustum->getRight()); + auto cascadeRight = glm::dot(farBottomRight, cascadeFrustum->getRight()); + auto cascadeTop = glm::dot(farTopLeft, cascadeFrustum->getUp()); + auto cascadeBottom = glm::dot(farBottomRight, cascadeFrustum->getUp()); + auto cascadeNear = cascadeFrustum->getNearClip(); + auto cascadeFar = cascadeFrustum->getFarClip(); + left = glm::min(left, cascadeLeft); + right = glm::max(right, cascadeRight); + bottom = glm::min(bottom, cascadeBottom); + top = glm::max(top, cascadeTop); + near = glm::min(near, cascadeNear); + far = glm::max(far, cascadeFar); + } + + _coarseShadowFrustum->setPosition(firstCascadeFrustum->getPosition()); + _coarseShadowFrustum->setOrientation(firstCascadeFrustum->getOrientation()); + _coarseShadowFrustum->setProjection(glm::ortho<float>(left, right, bottom, top, near, far)); + _coarseShadowFrustum->calculate(); + + // Push frustum for further culling and selection + args->pushViewFrustum(*_coarseShadowFrustum); + + args->_renderMode = RenderArgs::SHADOW_RENDER_MODE; + + // We want for the octree query enough resolution to catch the details in the lowest cascade. So compute + // the desired resolution for the first cascade frustum and extrapolate it to the coarse frustum. + glm::ivec2 queryResolution = firstCascade.framebuffer->getSize(); + queryResolution.x = int(queryResolution.x * _coarseShadowFrustum->getWidth() / firstCascadeFrustum->getWidth()); + queryResolution.y = int(queryResolution.y * _coarseShadowFrustum->getHeight() / firstCascadeFrustum->getHeight()); + output.edit1() = queryResolution; } void RenderShadowCascadeSetup::run(const render::RenderContextPointer& renderContext, const Inputs& input, Outputs& output) { - auto lightStage = renderContext->_scene->getStage<LightStage>(); - const auto& lightFrame = *input; - assert(lightStage); + const auto shadowFrame = input; // Cache old render args RenderArgs* args = renderContext->args; RenderShadowTask::CullFunctor cullFunctor; + if (shadowFrame && !shadowFrame->_objects.empty() && shadowFrame->_objects[0]) { + const auto globalShadow = shadowFrame->_objects[0]; - const auto globalShadow = lightStage->getCurrentKeyShadow(lightFrame); - if (globalShadow && _cascadeIndex < globalShadow->getCascadeCount()) { - // Second item filter is to filter items to keep in shadow frustum computation (here we need to keep shadow receivers) - output.edit0() = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(_tagBits, _tagMask); + if (globalShadow && _cascadeIndex < globalShadow->getCascadeCount()) { + // Second item filter is to filter items to keep in shadow frustum computation (here we need to keep shadow receivers) + output.edit0() = ItemFilter::Builder::visibleWorldItems().withTypeShape().withOpaque().withoutLayered().withTagBits(_tagBits, _tagMask); - // Set the keylight render args - auto& cascade = globalShadow->getCascade(_cascadeIndex); - auto& cascadeFrustum = cascade.getFrustum(); - args->pushViewFrustum(*cascadeFrustum); - auto texelSize = glm::min(cascadeFrustum->getHeight(), cascadeFrustum->getWidth()) / cascade.framebuffer->getSize().x; - // Set the cull threshold to 24 shadow texels. This is totally arbitrary - const auto minTexelCount = 24.0f; - // TODO : maybe adapt that with LOD management system? - texelSize *= minTexelCount; - cullFunctor._minSquareSize = texelSize * texelSize; + // Set the keylight render args + auto& cascade = globalShadow->getCascade(_cascadeIndex); + auto& cascadeFrustum = cascade.getFrustum(); + args->pushViewFrustum(*cascadeFrustum); + auto texelSize = glm::min(cascadeFrustum->getHeight(), cascadeFrustum->getWidth()) / cascade.framebuffer->getSize().x; + // Set the cull threshold to 24 shadow texels. This is totally arbitrary + const auto minTexelCount = 24.0f; + // TODO : maybe adapt that with LOD management system? + texelSize *= minTexelCount; + cullFunctor._minSquareSize = texelSize * texelSize; - output.edit1() = cascadeFrustum; - } else { + output.edit1() = cascadeFrustum; + + } else { + output.edit0() = ItemFilter::Builder::nothing(); + output.edit1() = ViewFrustumPointer(); + } + } + else { output.edit0() = ItemFilter::Builder::nothing(); output.edit1() = ViewFrustumPointer(); } + output.edit2() = cullFunctor; } @@ -513,20 +536,20 @@ void CullShadowBounds::run(const render::RenderContextPointer& renderContext, co outShapes.clear(); outBounds = AABox(); - const auto& lightFrame = *inputs.get3(); + const auto currentKeyLight = inputs.get3(); auto cullFunctor = inputs.get4(); render::CullFunctor shadowCullFunctor = [cullFunctor](const RenderArgs* args, const AABox& bounds) { return cullFunctor(args, bounds); }; - if (!filter.selectsNothing()) { + if (!filter.selectsNothing() && currentKeyLight) { auto& details = args->_details.edit(RenderDetails::SHADOW); render::CullTest test(shadowCullFunctor, args, details, antiFrustum); auto scene = args->_scene; auto lightStage = renderContext->_scene->getStage<LightStage>(); assert(lightStage); - const auto globalLightDir = lightStage->getCurrentKeyLight(lightFrame)->getDirection(); + const auto globalLightDir = currentKeyLight->getDirection(); auto castersFilter = render::ItemFilter::Builder(filter).withShadowCaster().build(); const auto& receiversFilter = filter; diff --git a/libraries/render-utils/src/RenderShadowTask.h b/libraries/render-utils/src/RenderShadowTask.h index e90725d66d..7e7d59763e 100644 --- a/libraries/render-utils/src/RenderShadowTask.h +++ b/libraries/render-utils/src/RenderShadowTask.h @@ -19,13 +19,14 @@ #include "Shadows_shared.slh" +#include "LightingModel.h" #include "LightStage.h" class ViewFrustum; class RenderShadowMap { public: - using Inputs = render::VaryingSet3<render::ShapeBounds, AABox, LightStage::FramePointer>; + using Inputs = render::VaryingSet3<render::ShapeBounds, AABox, LightStage::ShadowFramePointer>; using JobModel = render::Job::ModelI<RenderShadowMap, Inputs>; RenderShadowMap(render::ShapePlumberPointer shapePlumber, unsigned int cascadeIndex) : _shapePlumber{ shapePlumber }, _cascadeIndex{ cascadeIndex } {} @@ -36,10 +37,12 @@ protected: unsigned int _cascadeIndex; }; -class RenderShadowTaskConfig : public render::Task::Config::Persistent { +//class RenderShadowTaskConfig : public render::Task::Config::Persistent { +class RenderShadowTaskConfig : public render::Task::Config { Q_OBJECT public: - RenderShadowTaskConfig() : render::Task::Config::Persistent(QStringList() << "Render" << "Engine" << "Shadows", true) {} + // RenderShadowTaskConfig() : render::Task::Config::Persistent(QStringList() << "Render" << "Engine" << "Shadows", true) {} + RenderShadowTaskConfig() {} signals: void dirty(); @@ -49,9 +52,10 @@ class RenderShadowTask { public: // There is one AABox per shadow cascade - using Output = render::VaryingArray<AABox, SHADOW_CASCADE_MAX_COUNT>; + using Input = render::VaryingSet2<LightStage::FramePointer, LightingModelPointer>; + using Output = render::VaryingSet2<render::VaryingArray<AABox, SHADOW_CASCADE_MAX_COUNT>, LightStage::ShadowFramePointer>; using Config = RenderShadowTaskConfig; - using JobModel = render::Task::ModelO<RenderShadowTask, Output, Config>; + using JobModel = render::Task::ModelIO<RenderShadowTask, Input, Output, Config>; RenderShadowTask() {} void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cameraCullFunctor, uint8_t tagBits = 0x00, uint8_t tagMask = 0x00); @@ -99,14 +103,14 @@ signals: class RenderShadowSetup { public: - using Inputs = LightStage::FramePointer; - using Outputs = render::VaryingSet3<RenderArgs::RenderMode, glm::ivec2, ViewFrustumPointer>; + using Input = RenderShadowTask::Input; + using Output = render::VaryingSet5<RenderArgs::RenderMode, glm::ivec2, ViewFrustumPointer, LightStage::ShadowFramePointer, graphics::LightPointer>; using Config = RenderShadowSetupConfig; - using JobModel = render::Job::ModelIO<RenderShadowSetup, Inputs, Outputs, Config>; + using JobModel = render::Job::ModelIO<RenderShadowSetup, Input, Output, Config>; RenderShadowSetup(); void configure(const Config& configuration); - void run(const render::RenderContextPointer& renderContext, const Inputs& input, Outputs& output); + void run(const render::RenderContextPointer& renderContext, const Input& input, Output& output); private: @@ -117,13 +121,16 @@ private: float _slope; } _bias[SHADOW_CASCADE_MAX_COUNT]; + LightStage::ShadowFrame::Object _globalShadowObject; + LightStage::ShadowFramePointer _shadowFrameCache; + void setConstantBias(int cascadeIndex, float value); void setSlopeBias(int cascadeIndex, float value); }; class RenderShadowCascadeSetup { public: - using Inputs = LightStage::FramePointer; + using Inputs = LightStage::ShadowFramePointer; using Outputs = render::VaryingSet3<render::ItemFilter, ViewFrustumPointer, RenderShadowTask::CullFunctor>; using JobModel = render::Job::ModelIO<RenderShadowCascadeSetup, Inputs, Outputs>; @@ -147,14 +154,14 @@ public: class RenderShadowTeardown { public: - using Input = RenderShadowSetup::Outputs; + using Input = RenderShadowSetup::Output; using JobModel = render::Job::ModelI<RenderShadowTeardown, Input>; void run(const render::RenderContextPointer& renderContext, const Input& input); }; class CullShadowBounds { public: - using Inputs = render::VaryingSet5<render::ShapeBounds, render::ItemFilter, ViewFrustumPointer, LightStage::FramePointer, RenderShadowTask::CullFunctor>; + using Inputs = render::VaryingSet5<render::ShapeBounds, render::ItemFilter, ViewFrustumPointer, graphics::LightPointer, RenderShadowTask::CullFunctor>; using Outputs = render::VaryingSet2<render::ShapeBounds, AABox>; using JobModel = render::Job::ModelIO<CullShadowBounds, Inputs, Outputs>; diff --git a/libraries/render-utils/src/RenderViewTask.cpp b/libraries/render-utils/src/RenderViewTask.cpp index 2e03082ff4..7230635060 100644 --- a/libraries/render-utils/src/RenderViewTask.cpp +++ b/libraries/render-utils/src/RenderViewTask.cpp @@ -10,24 +10,32 @@ // #include "RenderViewTask.h" +#include "AssembleLightingStageTask.h" #include "RenderShadowTask.h" #include "RenderDeferredTask.h" #include "RenderForwardTask.h" void RenderViewTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, bool isDeferred, uint8_t tagBits, uint8_t tagMask) { - // auto items = input.get<Input>(); - const auto items = task.addJob<RenderFetchCullSortTask>("FetchCullSort", cullFunctor, tagBits, tagMask); assert(items.canCast<RenderFetchCullSortTask::Output>()); + // Issue the lighting model, aka the big global settings for the view + const auto lightingModel = task.addJob<MakeLightingModel>("LightingModel"); + + // Assemble the lighting stages current frames + const auto lightingStageFramesAndZones = task.addJob<AssembleLightingStageTask>("AssembleStages", items); + if (isDeferred) { // Warning : the cull functor passed to the shadow pass should only be testing for LOD culling. If frustum culling // is performed, then casters not in the view frustum will be removed, which is not what we wish. - const auto cascadeSceneBBoxes = task.addJob<RenderShadowTask>("RenderShadowTask", cullFunctor, tagBits, tagMask); - const auto renderInput = RenderDeferredTask::Input(items, cascadeSceneBBoxes).asVarying(); - task.addJob<RenderDeferredTask>("RenderDeferredTask", renderInput, true); + const auto shadowTaskIn = RenderShadowTask::Input(lightingStageFramesAndZones.get<AssembleLightingStageTask::Output>().get0()[0], lightingModel).asVarying(); + const auto shadowTaskOut = task.addJob<RenderShadowTask>("RenderShadowTask", shadowTaskIn, cullFunctor, tagBits, tagMask); + + const auto renderInput = RenderDeferredTask::Input(items, lightingModel, lightingStageFramesAndZones, shadowTaskOut).asVarying(); + task.addJob<RenderDeferredTask>("RenderDeferredTask", renderInput); } else { - task.addJob<RenderForwardTask>("Forward", items); + const auto renderInput = RenderForwardTask::Input(items, lightingModel, lightingStageFramesAndZones).asVarying(); + task.addJob<RenderForwardTask>("Forward", renderInput); } } diff --git a/libraries/render-utils/src/TextRenderer3D.cpp b/libraries/render-utils/src/TextRenderer3D.cpp index 8ef0dc0d73..2d507ba21b 100644 --- a/libraries/render-utils/src/TextRenderer3D.cpp +++ b/libraries/render-utils/src/TextRenderer3D.cpp @@ -67,11 +67,11 @@ float TextRenderer3D::getFontSize() const { } void TextRenderer3D::draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color, - const glm::vec2& bounds, bool layered) { + const glm::vec2& bounds) { // The font does all the OpenGL work if (_font) { _color = color; - _font->drawString(batch, _drawInfo, str, _color, _effectType, { x, y }, bounds, layered); + _font->drawString(batch, _drawInfo, str, _color, _effectType, { x, y }, bounds); } } diff --git a/libraries/render-utils/src/TextRenderer3D.h b/libraries/render-utils/src/TextRenderer3D.h index 6c91411e1d..97b74fcabd 100644 --- a/libraries/render-utils/src/TextRenderer3D.h +++ b/libraries/render-utils/src/TextRenderer3D.h @@ -39,7 +39,7 @@ public: float getFontSize() const; // Pixel size void draw(gpu::Batch& batch, float x, float y, const QString& str, const glm::vec4& color = glm::vec4(1.0f), - const glm::vec2& bounds = glm::vec2(-1.0f), bool layered = false); + const glm::vec2& bounds = glm::vec2(-1.0f)); private: TextRenderer3D(const char* family, float pointSize, int weight = -1, bool italic = false, diff --git a/libraries/render-utils/src/ZoneRenderer.cpp b/libraries/render-utils/src/ZoneRenderer.cpp index 4ffc8730a7..0e9672a7f2 100644 --- a/libraries/render-utils/src/ZoneRenderer.cpp +++ b/libraries/render-utils/src/ZoneRenderer.cpp @@ -51,7 +51,7 @@ void ZoneRendererTask::build(JobModel& task, const Varying& input, Varying& outp output = zoneItems; } -void SetupZones::run(const RenderContextPointer& context, const Inputs& inputs) { +void SetupZones::run(const RenderContextPointer& context, const Input& input) { // Grab light, background, haze, and bloom stages and clear them auto lightStage = context->_scene->getStage<LightStage>(); assert(lightStage); @@ -70,7 +70,7 @@ void SetupZones::run(const RenderContextPointer& context, const Inputs& inputs) bloomStage->_currentFrame.clear(); // call render over the zones to grab their components in the correct order first... - render::renderItems(context, inputs); + render::renderItems(context, input); // Finally add the default lights and background: lightStage->_currentFrame.pushSunLight(lightStage->getDefaultLight()); diff --git a/libraries/render-utils/src/ZoneRenderer.h b/libraries/render-utils/src/ZoneRenderer.h index 1646c5977d..6eeee1293a 100644 --- a/libraries/render-utils/src/ZoneRenderer.h +++ b/libraries/render-utils/src/ZoneRenderer.h @@ -21,12 +21,12 @@ class SetupZones { public: - using Inputs = render::ItemBounds; - using JobModel = render::Job::ModelI<SetupZones, Inputs>; + using Input = render::ItemBounds; + using JobModel = render::Job::ModelI<SetupZones, Input>; SetupZones() {} - void run(const render::RenderContextPointer& context, const Inputs& inputs); + void run(const render::RenderContextPointer& context, const Input& input); }; class ZoneRendererConfig : public render::Task::Config { @@ -51,13 +51,14 @@ public: static const render::Selection::Name ZONES_SELECTION; - using Inputs = render::ItemBounds; + using Input = render::ItemBounds; + using Output = render::ItemBounds; using Config = ZoneRendererConfig; - using JobModel = render::Task::ModelI<ZoneRendererTask, Inputs, Config>; + using JobModel = render::Task::ModelIO<ZoneRendererTask, Input, Output, Config>; ZoneRendererTask() {} - void build(JobModel& task, const render::Varying& inputs, render::Varying& output); + void build(JobModel& task, const render::Varying& input, render::Varying& output); void configure(const Config& config) { _maxDrawn = config.maxDrawn; } diff --git a/libraries/render-utils/src/forward_simple.slf b/libraries/render-utils/src/forward_simple.slf index 9c86f9dff1..677c369033 100644 --- a/libraries/render-utils/src/forward_simple.slf +++ b/libraries/render-utils/src/forward_simple.slf @@ -14,9 +14,9 @@ <@include DefaultMaterials.slh@> <@include ForwardGlobalLight.slh@> -<@include gpu/Transform.slh@> - <$declareEvalSkyboxGlobalColor()$> + +<@include gpu/Transform.slh@> <$declareStandardCameraTransform()$> // the interpolated normal diff --git a/libraries/render-utils/src/glowLine.slv b/libraries/render-utils/src/glowLine.slv index 3471bc2f98..1bcb25817c 100644 --- a/libraries/render-utils/src/glowLine.slv +++ b/libraries/render-utils/src/glowLine.slv @@ -49,7 +49,7 @@ void main(void) { vec3 orthogonal = cross(v1, v2) * _lineData.width; // Deteremine which end to emit based on the vertex id (even / odd) - vec4 eye = (0 == gl_VertexID % 2) ? p1eye : p2eye; + vec4 eye = mix(p2eye, p1eye, float(gl_VertexID % 2 == 0)); // Add or subtract the orthogonal vector based on a different vertex ID // calculation diff --git a/libraries/render-utils/src/grid.slf b/libraries/render-utils/src/grid.slf index 408a9ca190..50c420bc10 100644 --- a/libraries/render-utils/src/grid.slf +++ b/libraries/render-utils/src/grid.slf @@ -1,8 +1,6 @@ <@include gpu/Config.slh@> <$VERSION_HEADER$> // Generated on <$_SCRIBE_DATE$> -// grid.frag -// fragment shader // // Created by Zach Pomerantz on 2/16/2016. // Copyright 2016 High Fidelity, Inc. @@ -11,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +<@include DeferredBufferWrite.slh@> + <@include gpu/ShaderConstants.h@> <@include gpu/Paint.slh@> @@ -21,26 +21,20 @@ struct Grid { }; LAYOUT(binding=0) uniform gridBuffer { - Grid grid; + Grid grid; }; -Grid getGrid() { return grid; } - layout(location=GPU_ATTR_TEXCOORD0) in vec2 varTexCoord0; layout(location=GPU_ATTR_COLOR) in vec4 varColor; -layout(location=0) out vec4 outFragColor; - void main(void) { - Grid grid = getGrid(); - float alpha = mix(paintGridMajorMinor(varTexCoord0, grid.offset, grid.period, grid.edge), paintGrid(varTexCoord0, grid.offset.xy, grid.period.xy, grid.edge.xy), float(grid.edge.z == 0.0)); - if (alpha == 0.0) { + if (alpha < 0.0001) { discard; } - outFragColor = vec4(varColor.xyz, varColor.w * alpha); + packDeferredFragmentUnlit(vec3(1.0, 0.0, 0.0), 1.0, varColor.xyz); } diff --git a/libraries/render-utils/src/grid_translucent.slf b/libraries/render-utils/src/grid_translucent.slf new file mode 100644 index 0000000000..bb61126991 --- /dev/null +++ b/libraries/render-utils/src/grid_translucent.slf @@ -0,0 +1,41 @@ +<@include gpu/Config.slh@> +<$VERSION_HEADER$> +// Generated on <$_SCRIBE_DATE$> +// +// Created by Sam Gondelman on 12/22/18 +// 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 DeferredBufferWrite.slh@> + +<@include gpu/ShaderConstants.h@> +<@include gpu/Paint.slh@> + +struct Grid { + vec4 period; + vec4 offset; + vec4 edge; +}; + +LAYOUT(binding=0) uniform gridBuffer { + Grid grid; +}; + +layout(location=GPU_ATTR_TEXCOORD0) in vec2 varTexCoord0; +layout(location=GPU_ATTR_COLOR) in vec4 varColor; + +void main(void) { + float alpha = mix(paintGridMajorMinor(varTexCoord0, grid.offset, grid.period, grid.edge), + paintGrid(varTexCoord0, grid.offset.xy, grid.period.xy, grid.edge.xy), + float(grid.edge.z == 0.0)); + alpha *= varColor.w; + + if (alpha < 0.0001) { + discard; + } + + packDeferredFragmentTranslucent(vec3(1.0, 0.0, 0.0), alpha, varColor.xyz, DEFAULT_ROUGHNESS); +} diff --git a/libraries/render-utils/src/render-utils/grid_translucent.slp b/libraries/render-utils/src/render-utils/grid_translucent.slp new file mode 100644 index 0000000000..c81b208f63 --- /dev/null +++ b/libraries/render-utils/src/render-utils/grid_translucent.slp @@ -0,0 +1 @@ +VERTEX standardTransformPNTC diff --git a/libraries/render-utils/src/simple.slf b/libraries/render-utils/src/simple.slf index d2ddaa8e55..582549ade1 100644 --- a/libraries/render-utils/src/simple.slf +++ b/libraries/render-utils/src/simple.slf @@ -14,6 +14,9 @@ <@include DeferredBufferWrite.slh@> +<@include gpu/Transform.slh@> +<$declareStandardCameraTransform()$> + <@include render-utils/ShaderConstants.h@> // the interpolated normal @@ -45,25 +48,76 @@ float getProceduralColors(inout vec3 diffuse, inout vec3 specular, inout float s return 1.0; } +float getProceduralFragment(inout ProceduralFragment proceduralData) { + return 1.0; +} + +float getProceduralFragmentWithPosition(inout ProceduralFragmentWithPosition proceduralData) { + return 1.0; +} + //PROCEDURAL_BLOCK_END #line 2030 void main(void) { vec3 normal = normalize(_normalWS.xyz); vec3 diffuse = _color.rgb; - vec3 specular = DEFAULT_SPECULAR; - float shininess = DEFAULT_SHININESS; + float roughness = DEFAULT_ROUGHNESS; + float metallic = DEFAULT_METALLIC; + vec3 emissive = DEFAULT_EMISSIVE; + float occlusion = DEFAULT_OCCLUSION; + float scattering = DEFAULT_SCATTERING; + float emissiveAmount = 0.0; -#ifdef PROCEDURAL - -#ifdef PROCEDURAL_V1 +#if defined(PROCEDURAL_V1) diffuse = getProceduralColor().rgb; - // Procedural Shaders are expected to be Gamma corrected so let's bring back the RGB in linear space for the rest of the pipeline - //diffuse = pow(diffuse, vec3(2.2)); emissiveAmount = 1.0; -#else + emissive = vec3(1.0); +#elif defined(PROCEDURAL_V2) + vec3 specular = DEFAULT_SPECULAR; + float shininess = DEFAULT_SHININESS; emissiveAmount = getProceduralColors(diffuse, specular, shininess); + roughness = max(0.0, 1.0 - shininess / 128.0); + metallic = length(specular); + emissive = vec3(clamp(emissiveAmount, 0.0, 1.0)); +#elif defined(PROCEDURAL_V3) || defined(PROCEDURAL_V4) +#if defined(PROCEDURAL_V3) + ProceduralFragment proceduralData = ProceduralFragment( +#else + TransformCamera cam = getTransformCamera(); + vec4 position = cam._viewInverse * _positionES; + ProceduralFragmentWithPosition proceduralData = ProceduralFragmentWithPosition( + position.xyz, +#endif + normal, + vec3(0.0), + DEFAULT_SPECULAR, + DEFAULT_EMISSIVE, + 1.0, + DEFAULT_ROUGHNESS, + DEFAULT_METALLIC, + DEFAULT_OCCLUSION, + DEFAULT_SCATTERING + ); + +#if defined(PROCEDURAL_V3) + emissiveAmount = getProceduralFragment(proceduralData); +#else + emissiveAmount = getProceduralFragmentWithPosition(proceduralData); +#endif + normal = proceduralData.normal; + diffuse = proceduralData.diffuse; + roughness = proceduralData.roughness; + metallic = proceduralData.metallic; + emissive = proceduralData.emissive; + occlusion = proceduralData.occlusion; + scattering = proceduralData.scattering; + +#if defined(PROCEDURAL_V4) + position = vec4(proceduralData.position, 1.0); + vec4 posClip = cam._projection * (cam._view * position); + gl_FragDepth = 0.5 * (posClip.z / posClip.w + 1.0); #endif #endif @@ -73,18 +127,18 @@ void main(void) { normal, 1.0, diffuse, - max(0.0, 1.0 - shininess / 128.0), - DEFAULT_METALLIC, - vec3(clamp(emissiveAmount, 0.0, 1.0))); + roughness, + metallic, + emissive); } else { packDeferredFragment( normal, 1.0, diffuse, - max(0.0, 1.0 - shininess / 128.0), - length(specular), - DEFAULT_EMISSIVE, - DEFAULT_OCCLUSION, - DEFAULT_SCATTERING); + roughness, + metallic, + emissive, + occlusion, + scattering); } } diff --git a/libraries/render-utils/src/simple_transparent.slf b/libraries/render-utils/src/simple_transparent.slf index 0e29ed7470..ea444d6113 100644 --- a/libraries/render-utils/src/simple_transparent.slf +++ b/libraries/render-utils/src/simple_transparent.slf @@ -16,6 +16,9 @@ <@include DeferredGlobalLight.slh@> <$declareEvalGlobalLightingAlphaBlendedWithHaze()$> +<@include gpu/Transform.slh@> +<$declareStandardCameraTransform()$> + <@include render-utils/ShaderConstants.h@> // the interpolated normal @@ -50,46 +53,100 @@ float getProceduralColors(inout vec3 diffuse, inout vec3 specular, inout float s return 1.0; } +float getProceduralFragment(inout ProceduralFragment proceduralData) { + return 1.0; +} + +float getProceduralFragmentWithPosition(inout ProceduralFragmentWithPosition proceduralData) { + return 1.0; +} + //PROCEDURAL_BLOCK_END #line 2030 void main(void) { vec3 normal = normalize(_normalWS.xyz); vec3 diffuse = _color.rgb; - vec3 specular = DEFAULT_SPECULAR; - float shininess = DEFAULT_SHININESS; + float alpha = _color.a; + float occlusion = DEFAULT_OCCLUSION; + vec3 fresnel = DEFAULT_FRESNEL; + float metallic = DEFAULT_METALLIC; + vec3 emissive = DEFAULT_EMISSIVE; + float roughness = DEFAULT_ROUGHNESS; + float emissiveAmount = 0.0; - -#ifdef PROCEDURAL + + TransformCamera cam = getTransformCamera(); + vec3 posEye = _positionES.xyz; #ifdef PROCEDURAL_V1 diffuse = getProceduralColor().rgb; - // Procedural Shaders are expected to be Gamma corrected so let's bring back the RGB in linear space for the rest of the pipeline - //diffuse = pow(diffuse, vec3(2.2)); emissiveAmount = 1.0; -#else + emissive = vec3(1.0); +#elif defined(PROCEDURAL_V2) + vec3 specular = DEFAULT_SPECULAR; + float shininess = DEFAULT_SHININESS; emissiveAmount = getProceduralColors(diffuse, specular, shininess); + roughness = max(0.0, 1.0 - shininess / 128.0); + metallic = length(specular); + emissive = vec3(clamp(emissiveAmount, 0.0, 1.0)); +#elif defined(PROCEDURAL_V3) || defined(PROCEDURAL_V4) +#if defined(PROCEDURAL_V3) + ProceduralFragment proceduralData = { +#else + vec4 position = cam._viewInverse * _positionES; + ProceduralFragmentWithPosition proceduralData = { + position.xyz, +#endif + normal, + vec3(0.0), + DEFAULT_SPECULAR, + DEFAULT_EMISSIVE, + 1.0, + DEFAULT_ROUGHNESS, + DEFAULT_METALLIC, + DEFAULT_OCCLUSION, + DEFAULT_SCATTERING + }; + +#if defined(PROCEDURAL_V3) + emissiveAmount = getProceduralFragment(proceduralData); +#else + emissiveAmount = getProceduralFragmentWithPosition(proceduralData); +#endif + occlusion = proceduralData.occlusion; + normal = proceduralData.normal; + diffuse = proceduralData.diffuse; + fresnel = proceduralData.specular; + metallic = proceduralData.metallic; + emissive = proceduralData.emissive; + roughness = proceduralData.roughness; + alpha = proceduralData.alpha; + +#if defined(PROCEDURAL_V4) + position = vec4(proceduralData.position, 1.0); + vec4 posEye4 = cam._view * position; + posEye = posEye4.xyz; + vec4 posClip = cam._projection * posEye4; + gl_FragDepth = 0.5 * (posClip.z / posClip.w + 1.0); #endif #endif - TransformCamera cam = getTransformCamera(); - vec3 fragPosition = _positionES.xyz; - if (emissiveAmount > 0.0) { - _fragColor0 = vec4(diffuse, _color.a); + _fragColor0 = vec4(diffuse, alpha); } else { _fragColor0 = vec4(evalGlobalLightingAlphaBlendedWithHaze( cam._viewInverse, 1.0, - DEFAULT_OCCLUSION, - fragPosition, + occlusion, + posEye, normal, diffuse, - DEFAULT_FRESNEL, - length(specular), - DEFAULT_EMISSIVE, - max(0.0, 1.0 - shininess / 128.0), _color.a), - _color.a); + fresnel, + metallic, + emissive, + roughness, alpha), + alpha); } } diff --git a/libraries/render-utils/src/text/Font.cpp b/libraries/render-utils/src/text/Font.cpp index 68a1e59dc3..cc451eeedc 100644 --- a/libraries/render-utils/src/text/Font.cpp +++ b/libraries/render-utils/src/text/Font.cpp @@ -222,7 +222,6 @@ void Font::setupGPU() { // Setup render pipeline { gpu::ShaderPointer program = gpu::Shader::createProgram(shader::render_utils::program::sdf_text3D); - gpu::ShaderPointer programTransparent = gpu::Shader::createProgram(shader::render_utils::program::sdf_text3D_transparent); auto state = std::make_shared<gpu::State>(); state->setCullMode(gpu::State::CULL_BACK); state->setDepthTest(true, true, gpu::LESS_EQUAL); @@ -232,13 +231,14 @@ void Font::setupGPU() { PrepareStencil::testMaskDrawShape(*state); _pipeline = gpu::Pipeline::create(program, state); + gpu::ShaderPointer programTransparent = gpu::Shader::createProgram(shader::render_utils::program::sdf_text3D_transparent); auto transparentState = std::make_shared<gpu::State>(); transparentState->setCullMode(gpu::State::CULL_BACK); transparentState->setDepthTest(true, true, gpu::LESS_EQUAL); transparentState->setBlendFunction(true, gpu::State::SRC_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::INV_SRC_ALPHA, gpu::State::FACTOR_ALPHA, gpu::State::BLEND_OP_ADD, gpu::State::ONE); - PrepareStencil::testMaskDrawShape(*transparentState); + PrepareStencil::testMask(*transparentState); _transparentPipeline = gpu::Pipeline::create(programTransparent, transparentState); } @@ -289,7 +289,7 @@ void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm break; } } - if ((bounds.y != -1) && (advance.y - _fontSize < -origin.y - bounds.y)) { + if ((bounds.y != -1) && (advance.y - _fontSize < origin.y - bounds.y)) { // We are out of the y bound, stop drawing break; } @@ -343,7 +343,7 @@ void Font::buildVertices(Font::DrawInfo& drawInfo, const QString& str, const glm } void Font::drawString(gpu::Batch& batch, Font::DrawInfo& drawInfo, const QString& str, const glm::vec4& color, - EffectType effectType, const glm::vec2& origin, const glm::vec2& bounds, bool layered) { + EffectType effectType, const glm::vec2& origin, const glm::vec2& bounds) { if (str == "") { return; } @@ -370,7 +370,7 @@ void Font::drawString(gpu::Batch& batch, Font::DrawInfo& drawInfo, const QString } // need the gamma corrected color here - batch.setPipeline((color.a < 1.0f || layered) ? _transparentPipeline : _pipeline); + batch.setPipeline((color.a < 1.0f) ? _transparentPipeline : _pipeline); batch.setInputFormat(_format); batch.setInputBuffer(0, drawInfo.verticesBuffer, 0, _format->getChannels().at(0)._stride); batch.setResourceTexture(render_utils::slot::texture::TextFont, _texture); diff --git a/libraries/render-utils/src/text/Font.h b/libraries/render-utils/src/text/Font.h index 9a0a9b4734..85f692f5d8 100644 --- a/libraries/render-utils/src/text/Font.h +++ b/libraries/render-utils/src/text/Font.h @@ -46,7 +46,7 @@ public: // Render string to batch void drawString(gpu::Batch& batch, DrawInfo& drawInfo, const QString& str, const glm::vec4& color, EffectType effectType, - const glm::vec2& origin, const glm::vec2& bound, bool layered = false); + const glm::vec2& origin, const glm::vec2& bound); static Pointer load(const QString& family); diff --git a/libraries/render/src/render/FilterTask.h b/libraries/render/src/render/FilterTask.h index 9b40728b00..c2244e5f57 100644 --- a/libraries/render/src/render/FilterTask.h +++ b/libraries/render/src/render/FilterTask.h @@ -114,7 +114,8 @@ namespace render { class SelectItems { public: using Inputs = VaryingSet3<ItemBounds, ItemBounds, std::string>; - using JobModel = Job::ModelIO<SelectItems, Inputs, ItemBounds>; + using Outputs = ItemBounds; + using JobModel = Job::ModelIO<SelectItems, Inputs, Outputs>; std::string _name; SelectItems() {} diff --git a/libraries/render/src/render/RenderFetchCullSortTask.cpp b/libraries/render/src/render/RenderFetchCullSortTask.cpp index 7b9765dca1..324f1d879f 100644 --- a/libraries/render/src/render/RenderFetchCullSortTask.cpp +++ b/libraries/render/src/render/RenderFetchCullSortTask.cpp @@ -70,5 +70,13 @@ void RenderFetchCullSortTask::build(JobModel& task, const Varying& input, Varyin const auto overlayTransparents = task.addJob<DepthSortItems>("DepthSortOverlayTransparent", filteredNonspatialBuckets[TRANSPARENT_SHAPE_BUCKET], DepthSortItems(false)); const auto background = filteredNonspatialBuckets[BACKGROUND_BUCKET]; - output = Output(BucketList{ opaques, transparents, lights, metas, overlayOpaques, overlayTransparents, background }, spatialSelection); + // split up the overlays into 3D front, hud + const auto filteredOverlaysOpaque = task.addJob<FilterLayeredItems>("FilterOverlaysLayeredOpaque", overlayOpaques, ItemKey::Layer::LAYER_1); + const auto filteredOverlaysTransparent = task.addJob<FilterLayeredItems>("FilterOverlaysLayeredTransparent", overlayTransparents, ItemKey::Layer::LAYER_1); + + + output = Output(BucketList{ opaques, transparents, lights, metas, overlayOpaques, overlayTransparents, + filteredOverlaysOpaque.getN<FilterLayeredItems::Outputs>(0), filteredOverlaysTransparent.getN<FilterLayeredItems::Outputs>(0), + filteredOverlaysOpaque.getN<FilterLayeredItems::Outputs>(1), filteredOverlaysTransparent.getN<FilterLayeredItems::Outputs>(1), + background }, spatialSelection); } diff --git a/libraries/render/src/render/RenderFetchCullSortTask.h b/libraries/render/src/render/RenderFetchCullSortTask.h index 8c9f2e7304..a75c814d91 100644 --- a/libraries/render/src/render/RenderFetchCullSortTask.h +++ b/libraries/render/src/render/RenderFetchCullSortTask.h @@ -25,6 +25,11 @@ public: META, OVERLAY_OPAQUE_SHAPE, OVERLAY_TRANSPARENT_SHAPE, + LAYER_FRONT_OPAQUE_SHAPE, + LAYER_FRONT_TRANSPARENT_SHAPE, + LAYER_HUD_OPAQUE_SHAPE, + LAYER_HUD_TRANSPARENT_SHAPE, + BACKGROUND, NUM_BUCKETS diff --git a/libraries/script-engine/src/ScriptCache.cpp b/libraries/script-engine/src/ScriptCache.cpp index 9922b125f4..b23aa48762 100644 --- a/libraries/script-engine/src/ScriptCache.cpp +++ b/libraries/script-engine/src/ScriptCache.cpp @@ -87,7 +87,7 @@ void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailable if (_scriptCache.contains(url) && !forceDownload) { auto scriptContent = _scriptCache[url]; lock.unlock(); - qCDebug(scriptengine) << "Found script in cache:" << url.toString(); + qCDebug(scriptengine) << "Found script in cache:" << url.fileName(); contentAvailable(url.toString(), scriptContent, true, true, STATUS_CACHED); } else { auto& scriptRequest = _activeScriptRequests[url]; diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 6f98dd2864..47ce29f9a3 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -164,7 +164,9 @@ ScriptEnginePointer scriptEngineFactory(ScriptEngine::Context context, const QString& fileNameString) { ScriptEngine* engine = new ScriptEngine(context, scriptContents, fileNameString); ScriptEnginePointer engineSP = ScriptEnginePointer(engine); - DependencyManager::get<ScriptEngines>()->addScriptEngine(qSharedPointerCast<ScriptEngine>(engineSP)); + auto scriptEngines = DependencyManager::get<ScriptEngines>(); + scriptEngines->addScriptEngine(qSharedPointerCast<ScriptEngine>(engineSP)); + engine->setScriptEngines(scriptEngines); return engineSP; } @@ -259,7 +261,7 @@ bool ScriptEngine::isDebugMode() const { } ScriptEngine::~ScriptEngine() { - auto scriptEngines = DependencyManager::get<ScriptEngines>(); + QSharedPointer<ScriptEngines> scriptEngines(_scriptEngines); if (scriptEngines) { scriptEngines->removeScriptEngine(qSharedPointerCast<ScriptEngine>(sharedFromThis())); } @@ -555,6 +557,10 @@ using ScriptableResourceRawPtr = ScriptableResource*; static QScriptValue scriptableResourceToScriptValue(QScriptEngine* engine, const ScriptableResourceRawPtr& resource) { + if (!resource) { + return QScriptValue(); // probably shutting down + } + // The first script to encounter this resource will track its memory. // In this way, it will be more likely to GC. // This fails in the case that the resource is used across many scripts, but @@ -670,6 +676,7 @@ void ScriptEngine::init() { qScriptRegisterMetaType(this, EntityPropertyFlagsToScriptValue, EntityPropertyFlagsFromScriptValue); qScriptRegisterMetaType(this, EntityItemPropertiesToScriptValue, EntityItemPropertiesFromScriptValueHonorReadOnly); + qScriptRegisterMetaType(this, EntityPropertyInfoToScriptValue, EntityPropertyInfoFromScriptValue); qScriptRegisterMetaType(this, EntityItemIDtoScriptValue, EntityItemIDfromScriptValue); qScriptRegisterMetaType(this, RayToEntityIntersectionResultToScriptValue, RayToEntityIntersectionResultFromScriptValue); qScriptRegisterMetaType(this, RayToAvatarIntersectionResultToScriptValue, RayToAvatarIntersectionResultFromScriptValue); @@ -1011,7 +1018,8 @@ QScriptValue ScriptEngine::evaluateInClosure(const QScriptValue& closure, const } QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fileName, int lineNumber) { - if (DependencyManager::get<ScriptEngines>()->isStopped()) { + QSharedPointer<ScriptEngines> scriptEngines(_scriptEngines); + if (!scriptEngines || scriptEngines->isStopped()) { return QScriptValue(); // bail early } @@ -1061,7 +1069,8 @@ void ScriptEngine::run() { auto name = filenameParts.size() > 0 ? filenameParts[filenameParts.size() - 1] : "unknown"; PROFILE_SET_THREAD_NAME("Script: " + name); - if (DependencyManager::get<ScriptEngines>()->isStopped()) { + QSharedPointer<ScriptEngines> scriptEngines(_scriptEngines); + if (!scriptEngines || scriptEngines->isStopped()) { return; // bail early - avoid setting state in init(), as evaluate() will bail too } @@ -1318,8 +1327,8 @@ void ScriptEngine::updateMemoryCost(const qint64& deltaSize) { void ScriptEngine::timerFired() { { - auto engine = DependencyManager::get<ScriptEngines>(); - if (!engine || engine->isStopped()) { + QSharedPointer<ScriptEngines> scriptEngines(_scriptEngines); + if (!scriptEngines || scriptEngines->isStopped()) { scriptWarningMessage("Script.timerFired() while shutting down is ignored... parent script:" + getFilename()); return; // bail early } @@ -1372,7 +1381,8 @@ QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int } QObject* ScriptEngine::setInterval(const QScriptValue& function, int intervalMS) { - if (DependencyManager::get<ScriptEngines>()->isStopped()) { + QSharedPointer<ScriptEngines> scriptEngines(_scriptEngines); + if (!scriptEngines || scriptEngines->isStopped()) { scriptWarningMessage("Script.setInterval() while shutting down is ignored... parent script:" + getFilename()); return NULL; // bail early } @@ -1381,7 +1391,8 @@ QObject* ScriptEngine::setInterval(const QScriptValue& function, int intervalMS) } QObject* ScriptEngine::setTimeout(const QScriptValue& function, int timeoutMS) { - if (DependencyManager::get<ScriptEngines>()->isStopped()) { + QSharedPointer<ScriptEngines> scriptEngines(_scriptEngines); + if (!scriptEngines || scriptEngines->isStopped()) { scriptWarningMessage("Script.setTimeout() while shutting down is ignored... parent script:" + getFilename()); return NULL; // bail early } @@ -1594,7 +1605,7 @@ QScriptValue ScriptEngine::newModule(const QString& modulePath, const QScriptVal auto closure = newObject(); auto exports = newObject(); auto module = newObject(); - qCDebug(scriptengine_module) << "newModule" << modulePath << parent.property("filename").toString(); + qCDebug(scriptengine_module) << "newModule" << parent.property("filename").toString(); closure.setProperty("module", module, READONLY_PROP_FLAGS); @@ -1812,7 +1823,8 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { return; } - if (DependencyManager::get<ScriptEngines>()->isStopped()) { + QSharedPointer<ScriptEngines> scriptEngines(_scriptEngines); + if (!scriptEngines || scriptEngines->isStopped()) { scriptWarningMessage("Script.include() while shutting down is ignored... includeFiles:" + includeFiles.join(",") + "parent script:" + getFilename()); return; // bail early @@ -1906,7 +1918,8 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac } void ScriptEngine::include(const QString& includeFile, QScriptValue callback) { - if (DependencyManager::get<ScriptEngines>()->isStopped()) { + QSharedPointer<ScriptEngines> scriptEngines(_scriptEngines); + if (!scriptEngines || scriptEngines->isStopped()) { scriptWarningMessage("Script.include() while shutting down is ignored... includeFile:" + includeFile + "parent script:" + getFilename()); return; // bail early @@ -1924,7 +1937,8 @@ void ScriptEngine::load(const QString& loadFile) { if (!IS_THREADSAFE_INVOCATION(thread(), __FUNCTION__)) { return; } - if (DependencyManager::get<ScriptEngines>()->isStopped()) { + QSharedPointer<ScriptEngines> scriptEngines(_scriptEngines); + if (!scriptEngines || scriptEngines->isStopped()) { scriptWarningMessage("Script.load() while shutting down is ignored... loadFile:" + loadFile + "parent script:" + getFilename()); return; // bail early @@ -2072,10 +2086,11 @@ void ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& } PROFILE_RANGE(script, __FUNCTION__); - if (isStopping() || DependencyManager::get<ScriptEngines>()->isStopped()) { + QSharedPointer<ScriptEngines> scriptEngines(_scriptEngines); + if (isStopping() || !scriptEngines || scriptEngines->isStopped()) { qCDebug(scriptengine) << "loadEntityScript.start " << entityID.toString() << " but isStopping==" << isStopping() - << " || engines->isStopped==" << DependencyManager::get<ScriptEngines>()->isStopped(); + << " || engines->isStopped==" << scriptEngines->isStopped(); return; } diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 8fe50aee78..0d6d45e594 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -563,6 +563,8 @@ public: bool getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const; bool hasEntityScriptDetails(const EntityItemID& entityID) const; + void setScriptEngines(QSharedPointer<ScriptEngines>& scriptEngines) { _scriptEngines = scriptEngines; } + public slots: /**jsdoc @@ -814,6 +816,8 @@ protected: static const QString _SETTINGS_ENABLE_EXTENDED_EXCEPTIONS; Setting::Handle<bool> _enableExtendedJSExceptions { _SETTINGS_ENABLE_EXTENDED_EXCEPTIONS, true }; + + QWeakPointer<ScriptEngines> _scriptEngines; }; ScriptEnginePointer scriptEngineFactory(ScriptEngine::Context context, diff --git a/libraries/shared/src/AABox.cpp b/libraries/shared/src/AABox.cpp index ff6c2a4e6e..6ff290f9f2 100644 --- a/libraries/shared/src/AABox.cpp +++ b/libraries/shared/src/AABox.cpp @@ -480,7 +480,7 @@ void AABox::embiggen(const glm::vec3& scale) { } void AABox::setScaleStayCentered(const glm::vec3& scale) { - _corner += -0.5f * scale; + _corner -= 0.5f * (scale - _scale); _scale = scale; } diff --git a/libraries/shared/src/BillboardMode.cpp b/libraries/shared/src/BillboardMode.cpp new file mode 100644 index 0000000000..56251f53f2 --- /dev/null +++ b/libraries/shared/src/BillboardMode.cpp @@ -0,0 +1,25 @@ +// +// Created by Sam Gondelman on 11/30/18 +// 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 "BillboardMode.h" + +const char* billboardModeNames[] = { + "none", + "yaw", + "full" +}; + +static const size_t MATERIAL_MODE_NAMES = (sizeof(billboardModeNames) / sizeof((billboardModeNames)[0])); + +QString BillboardModeHelpers::getNameForBillboardMode(BillboardMode mode) { + if (((int)mode <= 0) || ((int)mode >= (int)MATERIAL_MODE_NAMES)) { + mode = (BillboardMode)0; + } + + return billboardModeNames[(int)mode]; +} \ No newline at end of file diff --git a/libraries/shared/src/BillboardMode.h b/libraries/shared/src/BillboardMode.h new file mode 100644 index 0000000000..050f939941 --- /dev/null +++ b/libraries/shared/src/BillboardMode.h @@ -0,0 +1,41 @@ +// +// Created by Sam Gondelman on 11/30/18. +// 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 +// + +#ifndef hifi_BillboardMode_h +#define hifi_BillboardMode_h + +#include "QString" + +/**jsdoc + * <p>How an entity is billboarded.</p> + * <table> + * <thead> + * <tr><th>Value</th><th>Description</th></tr> + * </thead> + * <tbody> + * <tr><td><code>none</code></td><td>The entity will not be billboarded.</td></tr> + * <tr><td><code>yaw</code></td><td>The entity will yaw, but not pitch, to face the camera. Its actual rotation will be ignored.</td></tr> + * <tr><td><code>full</code></td><td>The entity will be billboarded to face the camera. Its actual rotation will be ignored.</td></tr> + * </tbody> + * </table> + * @typedef {string} BillboardMode + */ + +enum class BillboardMode { + NONE = 0, + YAW, + FULL +}; + +class BillboardModeHelpers { +public: + static QString getNameForBillboardMode(BillboardMode mode); +}; + +#endif // hifi_BillboardMode_h + diff --git a/libraries/shared/src/GLMHelpers.cpp b/libraries/shared/src/GLMHelpers.cpp index 905bf3ccfd..790623ad8e 100644 --- a/libraries/shared/src/GLMHelpers.cpp +++ b/libraries/shared/src/GLMHelpers.cpp @@ -9,9 +9,12 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include <limits> #include "GLMHelpers.h" + +#include <limits> + #include <glm/gtc/matrix_transform.hpp> + #include "NumericalConstants.h" const vec3 Vectors::UNIT_X{ 1.0f, 0.0f, 0.0f }; diff --git a/libraries/shared/src/Grab.cpp b/libraries/shared/src/Grab.cpp new file mode 100644 index 0000000000..195bc61f66 --- /dev/null +++ b/libraries/shared/src/Grab.cpp @@ -0,0 +1,70 @@ +// +// Grab.cpp +// libraries/avatars/src +// +// Created by Seth Alves on 2018-9-1. +// 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 "Grab.h" + +QByteArray Grab::toByteArray() { + QByteArray ba; + QDataStream dataStream(&ba, QIODevice::WriteOnly); + const int dataEncodingVersion = 1; + dataStream << dataEncodingVersion << _ownerID << _targetID << _parentJointIndex + << _hand << _positionalOffset << _rotationalOffset; + return ba; +} + +bool Grab::fromByteArray(const QByteArray& grabData) { + QDataStream dataStream(grabData); + + int dataEncodingVersion; + QUuid newOwnerID { QUuid() }; + QUuid newTargetID { QUuid() }; + int newParentJointIndex { -1 }; + QString newHand { "none" }; + glm::vec3 newPositionalOffset { glm::vec3(0.0f) }; + glm::quat newRotationalOffset { glm::quat() }; + + dataStream >> dataEncodingVersion; + assert(dataEncodingVersion == 1); + dataStream >> newOwnerID; + dataStream >> newTargetID; + dataStream >> newParentJointIndex; + dataStream >> newHand; + dataStream >> newPositionalOffset; + dataStream >> newRotationalOffset; + + bool somethingChanged { false }; + if (_ownerID != newOwnerID) { + _ownerID = newOwnerID; + somethingChanged = true; + } + if (_targetID != newTargetID) { + _targetID = newTargetID; + somethingChanged = true; + } + if (_parentJointIndex != newParentJointIndex) { + _parentJointIndex = newParentJointIndex; + somethingChanged = true; + } + if (_hand != newHand) { + _hand = newHand; + somethingChanged = true; + } + if (_positionalOffset != newPositionalOffset) { + _positionalOffset = newPositionalOffset; + somethingChanged = true; + } + if (_rotationalOffset != newRotationalOffset) { + _rotationalOffset = newRotationalOffset; + somethingChanged = true; + } + + return somethingChanged; +} diff --git a/libraries/shared/src/Grab.h b/libraries/shared/src/Grab.h new file mode 100644 index 0000000000..5765d6fd0e --- /dev/null +++ b/libraries/shared/src/Grab.h @@ -0,0 +1,99 @@ +// +// Grab.h +// libraries/avatars/src +// +// Created by Seth Alves on 2018-9-1. +// 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 +// + +#ifndef hifi_Grab_h +#define hifi_Grab_h + +#include <QUuid> +#include <QByteArray> +#include "GLMHelpers.h" +#include "StreamUtils.h" + +class Grab; +using GrabPointer = std::shared_ptr<Grab>; +using GrabWeakPointer = std::weak_ptr<Grab>; + +class GrabLocationAccumulator { +public: + void accumulate(glm::vec3 position, glm::quat orientation) { + _position += position; + _orientation = orientation; // XXX + count++; + } + + glm::vec3 finalizePosition() { return count > 0 ? _position * (1.0f / count) : glm::vec3(0.0f); } + glm::quat finalizeOrientation() { return _orientation; } // XXX + +protected: + glm::vec3 _position; + glm::quat _orientation; + int count { 0 }; +}; + +class Grab { +public: + Grab() {}; + Grab(const QUuid& newOwnerID, const QUuid& newTargetID, int newParentJointIndex, const QString& newHand, + glm::vec3 newPositionalOffset, glm::quat newRotationalOffset) : + _ownerID(newOwnerID), + _targetID(newTargetID), + _parentJointIndex(newParentJointIndex), + _hand(newHand), + _positionalOffset(newPositionalOffset), + _rotationalOffset(newRotationalOffset) {} + + QByteArray toByteArray(); + bool fromByteArray(const QByteArray& grabData); + + Grab& operator=(const GrabPointer& other) { + _ownerID = other->_ownerID; + _targetID = other->_targetID; + _parentJointIndex = other->_parentJointIndex; + _hand = other->_hand; + _positionalOffset = other->_positionalOffset; + _rotationalOffset = other->_rotationalOffset; + _actionID = other->_actionID; + return *this; + } + + QUuid getActionID() const { return _actionID; } + void setActionID(const QUuid& actionID) { _actionID = actionID; } + + QUuid getOwnerID() const { return _ownerID; } + void setOwnerID(QUuid ownerID) { _ownerID = ownerID; } + + QUuid getTargetID() const { return _targetID; } + void setTargetID(QUuid targetID) { _targetID = targetID; } + + int getParentJointIndex() const { return _parentJointIndex; } + void setParentJointIndex(int parentJointIndex) { _parentJointIndex = parentJointIndex; } + + QString getHand() const { return _hand; } + void setHand(QString hand) { _hand = hand; } + + glm::vec3 getPositionalOffset() const { return _positionalOffset; } + void setPositionalOffset(glm::vec3 positionalOffset) { _positionalOffset = positionalOffset; } + + glm::quat getRotationalOffset() const { return _rotationalOffset; } + void setRotationalOffset(glm::quat rotationalOffset) { _rotationalOffset = rotationalOffset; } + +protected: + QUuid _actionID; // if an action is created in bullet for this grab, this is the ID + QUuid _ownerID; // avatar ID of grabber + QUuid _targetID; // SpatiallyNestable ID of grabbed + int _parentJointIndex { -1 }; // which avatar joint is being used to grab + QString _hand; // "left" or "right" + glm::vec3 _positionalOffset; // relative to joint + glm::quat _rotationalOffset; // relative to joint +}; + + +#endif // hifi_Grab_h diff --git a/libraries/shared/src/PickFilter.h b/libraries/shared/src/PickFilter.h new file mode 100644 index 0000000000..2efd408e4f --- /dev/null +++ b/libraries/shared/src/PickFilter.h @@ -0,0 +1,99 @@ +// +// Created by Sam Gondelman on 12/7/18. +// 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 +// + +#ifndef hifi_PickFilter_h +#define hifi_PickFilter_h + +#include <bitset> + +class PickFilter { +public: + enum FlagBit { + DOMAIN_ENTITIES = 0, + AVATAR_ENTITIES, + LOCAL_ENTITIES, + AVATARS, + HUD, + + VISIBLE, + INVISIBLE, + + COLLIDABLE, + NONCOLLIDABLE, + + PRECISE, + COARSE, + + // NOT YET IMPLEMENTED + PICK_ALL_INTERSECTIONS, // if not set, returns closest intersection, otherwise, returns list of all intersections + + NUM_FLAGS, // Not a valid flag + }; + typedef std::bitset<NUM_FLAGS> Flags; + + // The key is the Flags + Flags _flags; + + PickFilter() {} + PickFilter(const Flags& flags) : _flags(flags) {} + + bool operator==(const PickFilter& rhs) const { return _flags == rhs._flags; } + bool operator!=(const PickFilter& rhs) const { return _flags != rhs._flags; } + + void setFlag(FlagBit flag, bool value) { _flags[flag] = value; } + + // There are different groups of related flags. If none of the flags in a group are set, the search filter includes them all. + bool doesPickDomainEntities() const { return _flags[DOMAIN_ENTITIES] || !(_flags[AVATAR_ENTITIES] || _flags[LOCAL_ENTITIES] || _flags[AVATARS] || _flags[HUD]); } + bool doesPickAvatarEntities() const { return _flags[AVATAR_ENTITIES] || !(_flags[DOMAIN_ENTITIES] || _flags[LOCAL_ENTITIES] || _flags[AVATARS] || _flags[HUD]); } + bool doesPickLocalEntities() const { return _flags[LOCAL_ENTITIES] || !(_flags[DOMAIN_ENTITIES] || _flags[AVATAR_ENTITIES] || _flags[AVATARS] || _flags[HUD]); } + bool doesPickAvatars() const { return _flags[AVATARS] || !(_flags[DOMAIN_ENTITIES] || _flags[AVATAR_ENTITIES] || _flags[LOCAL_ENTITIES] || _flags[HUD]); } + bool doesPickHUD() const { return _flags[HUD] || !(_flags[DOMAIN_ENTITIES] || _flags[AVATAR_ENTITIES] || _flags[LOCAL_ENTITIES] || _flags[AVATARS]); } + + bool doesPickVisible() const { return _flags[VISIBLE] || !_flags[INVISIBLE]; } + bool doesPickInvisible() const { return _flags[INVISIBLE] || !_flags[VISIBLE]; } + + bool doesPickCollidable() const { return _flags[COLLIDABLE] || !_flags[NONCOLLIDABLE]; } + bool doesPickNonCollidable() const { return _flags[NONCOLLIDABLE] || !_flags[COLLIDABLE]; } + + bool isPrecise() const { return _flags[PRECISE] || !_flags[COARSE]; } + bool isCoarse() const { return _flags[COARSE]; } + + bool doesWantAllIntersections() const { return _flags[PICK_ALL_INTERSECTIONS]; } + + // Helpers for RayPickManager + Flags getEntityFlags() const { + unsigned int toReturn = 0; + for (int i = DOMAIN_ENTITIES; i < LOCAL_ENTITIES; i++) { + if (_flags[i]) { + toReturn |= getBitMask(FlagBit(i)); + } + } + for (int i = HUD + 1; i < NUM_FLAGS; i++) { + if (_flags[i]) { + toReturn |= getBitMask(FlagBit(i)); + } + } + return Flags(toReturn); + } + Flags getOverlayFlags() const { + unsigned int toReturn = getBitMask(LOCAL_ENTITIES); + for (int i = HUD + 1; i < NUM_FLAGS; i++) { + if (_flags[i]) { + toReturn |= getBitMask(FlagBit(i)); + } + } + return Flags(toReturn); + } + Flags getAvatarFlags() const { return Flags(getBitMask(AVATARS)); } + Flags getHUDFlags() const { return Flags(getBitMask(HUD)); } + + static constexpr unsigned int getBitMask(FlagBit bit) { return 1 << bit; } +}; + +#endif // hifi_PickFilter_h + diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index dc84afff93..5394a0f448 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -24,6 +24,7 @@ #include <QtNetwork/QAbstractSocket> #include <QtScript/QScriptValue> #include <QtScript/QScriptValueIterator> +#include <QJsonDocument> int vec2MetaTypeId = qRegisterMetaType<glm::vec2>(); int u8vec3MetaTypeId = qRegisterMetaType<u8vec3>(); @@ -1245,3 +1246,31 @@ void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector<MeshFace> result << meshFace; } } + +QVariantMap parseTexturesToMap(QString textures, const QVariantMap& defaultTextures) { + // If textures are unset, revert to original textures + if (textures.isEmpty()) { + return defaultTextures; + } + + // Legacy: a ,\n-delimited list of filename:"texturepath" + if (*textures.cbegin() != '{') { + textures = "{\"" + textures.replace(":\"", "\":\"").replace(",\n", ",\"") + "}"; + } + + QJsonParseError error; + QJsonDocument texturesJson = QJsonDocument::fromJson(textures.toUtf8(), &error); + // If textures are invalid, revert to original textures + if (error.error != QJsonParseError::NoError) { + qWarning() << "Could not evaluate textures property value:" << textures; + return defaultTextures; + } + + QVariantMap texturesMap = texturesJson.toVariant().toMap(); + // If textures are unset, revert to original textures + if (texturesMap.isEmpty()) { + return defaultTextures; + } + + return texturesJson.toVariant().toMap(); +} \ No newline at end of file diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index ed637fe771..9d5bca6b9f 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -705,5 +705,7 @@ void meshFaceFromScriptValue(const QScriptValue &object, MeshFace& meshFaceResul QScriptValue qVectorMeshFaceToScriptValue(QScriptEngine* engine, const QVector<MeshFace>& vector); void qVectorMeshFaceFromScriptValue(const QScriptValue& array, QVector<MeshFace>& result); +QVariantMap parseTexturesToMap(QString textures, const QVariantMap& defaultTextures); + #endif // hifi_RegisteredMetaTypes_h diff --git a/libraries/shared/src/ResourceRequestObserver.cpp b/libraries/shared/src/ResourceRequestObserver.cpp index 5e0925520a..608d6905c5 100644 --- a/libraries/shared/src/ResourceRequestObserver.cpp +++ b/libraries/shared/src/ResourceRequestObserver.cpp @@ -1,6 +1,6 @@ // -// ResourceAccessMonitor.h -// libraries/networking/src +// ResourceRequestObserver.cpp +// libraries/shared/src // // Created by Kerry Ivan Kurian on 9/27/18. // Copyright 2018 High Fidelity, Inc. @@ -9,12 +9,12 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "ResourceRequestObserver.h" #include <QJsonArray> #include <QJsonObject> #include <QString> #include <QUrl> -#include "ResourceRequestObserver.h" void ResourceRequestObserver::update(const QUrl& requestUrl, const qint64 callerId, diff --git a/libraries/shared/src/ResourceRequestObserver.h b/libraries/shared/src/ResourceRequestObserver.h index 1b1bc322e5..edf3c617cb 100644 --- a/libraries/shared/src/ResourceRequestObserver.h +++ b/libraries/shared/src/ResourceRequestObserver.h @@ -1,6 +1,6 @@ // // ResourceRequestObserver.h -// libraries/commerce/src +// libraries/shared/src // // Created by Kerry Ivan Kurian on 9/27/18. // Copyright 2018 High Fidelity, Inc. diff --git a/libraries/shared/src/ShapeInfo.cpp b/libraries/shared/src/ShapeInfo.cpp index df8e61114d..3426a79782 100644 --- a/libraries/shared/src/ShapeInfo.cpp +++ b/libraries/shared/src/ShapeInfo.cpp @@ -58,7 +58,8 @@ const char* shapeTypeNames[] = { "compound", "simple-hull", "simple-compound", - "static-mesh" + "static-mesh", + "ellipsoid" }; static const size_t SHAPETYPE_NAME_COUNT = (sizeof(shapeTypeNames) / sizeof((shapeTypeNames)[0])); @@ -250,50 +251,6 @@ float ShapeInfo::computeVolume() const { return volume; } -bool ShapeInfo::contains(const glm::vec3& point) const { - switch(_type) { - case SHAPE_TYPE_SPHERE: - return glm::length(point) <= _halfExtents.x; - case SHAPE_TYPE_CYLINDER_X: - return glm::length(glm::vec2(point.y, point.z)) <= _halfExtents.z; - case SHAPE_TYPE_CYLINDER_Y: - return glm::length(glm::vec2(point.x, point.z)) <= _halfExtents.x; - case SHAPE_TYPE_CYLINDER_Z: - return glm::length(glm::vec2(point.x, point.y)) <= _halfExtents.y; - case SHAPE_TYPE_CAPSULE_X: { - if (glm::abs(point.x) <= _halfExtents.x - _halfExtents.y) { - return glm::length(glm::vec2(point.y, point.z)) <= _halfExtents.y; - } else { - glm::vec3 absPoint = glm::abs(point) - glm::vec3(_halfExtents.x, 0.0f, 0.0f); - return glm::length(absPoint) <= _halfExtents.y; - } - } - case SHAPE_TYPE_CAPSULE_Y: { - if (glm::abs(point.y) <= _halfExtents.y - _halfExtents.z) { - return glm::length(glm::vec2(point.x, point.z)) <= _halfExtents.z; - } else { - glm::vec3 absPoint = glm::abs(point) - glm::vec3(0.0f, _halfExtents.y, 0.0f); - return glm::length(absPoint) <= _halfExtents.z; - } - } - case SHAPE_TYPE_CAPSULE_Z: { - if (glm::abs(point.z) <= _halfExtents.z - _halfExtents.x) { - return glm::length(glm::vec2(point.x, point.y)) <= _halfExtents.x; - } else { - glm::vec3 absPoint = glm::abs(point) - glm::vec3(0.0f, 0.0f, _halfExtents.z); - return glm::length(absPoint) <= _halfExtents.x; - } - } - case SHAPE_TYPE_BOX: - default: { - glm::vec3 absPoint = glm::abs(point); - return absPoint.x <= _halfExtents.x - && absPoint.y <= _halfExtents.y - && absPoint.z <= _halfExtents.z; - } - } -} - const HashKey& ShapeInfo::getHash() const { // NOTE: we cache the key so we only ever need to compute it once for any valid ShapeInfo instance. if (_hashKey.isNull() && _type != SHAPE_TYPE_NONE) { diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h index 16e260d9db..a2092c7f00 100644 --- a/libraries/shared/src/ShapeInfo.h +++ b/libraries/shared/src/ShapeInfo.h @@ -86,10 +86,6 @@ public: float computeVolume() const; - /// Returns whether point is inside the shape - /// For compound shapes it will only return whether it is inside the bounding box - bool contains(const glm::vec3& point) const; - const HashKey& getHash() const; protected: diff --git a/libraries/shared/src/SpatiallyNestable.cpp b/libraries/shared/src/SpatiallyNestable.cpp index d704498143..8805205361 100644 --- a/libraries/shared/src/SpatiallyNestable.cpp +++ b/libraries/shared/src/SpatiallyNestable.cpp @@ -481,27 +481,29 @@ void SpatiallyNestable::setWorldTransform(const glm::vec3& position, const glm:: return; } - bool changed = false; bool success = true; Transform parentTransform = getParentTransform(success); - _transformLock.withWriteLock([&] { - Transform myWorldTransform; - Transform::mult(myWorldTransform, parentTransform, _transform); - if (myWorldTransform.getRotation() != orientation) { - changed = true; - myWorldTransform.setRotation(orientation); - } - if (myWorldTransform.getTranslation() != position) { - changed = true; - myWorldTransform.setTranslation(position); - } + if (success) { + bool changed = false; + _transformLock.withWriteLock([&] { + Transform myWorldTransform; + Transform::mult(myWorldTransform, parentTransform, _transform); + if (myWorldTransform.getRotation() != orientation) { + changed = true; + myWorldTransform.setRotation(orientation); + } + if (myWorldTransform.getTranslation() != position) { + changed = true; + myWorldTransform.setTranslation(position); + } + if (changed) { + Transform::inverseMult(_transform, parentTransform, myWorldTransform); + _translationChanged = usecTimestampNow(); + } + }); if (changed) { - Transform::inverseMult(_transform, parentTransform, myWorldTransform); - _translationChanged = usecTimestampNow(); + locationChanged(false); } - }); - if (success && changed) { - locationChanged(false); } } @@ -788,19 +790,21 @@ void SpatiallyNestable::setTransform(const Transform& transform, bool& success) return; } - bool changed = false; Transform parentTransform = getParentTransform(success); - _transformLock.withWriteLock([&] { - Transform beforeTransform = _transform; - Transform::inverseMult(_transform, parentTransform, transform); - if (_transform != beforeTransform) { - changed = true; - _translationChanged = usecTimestampNow(); - _rotationChanged = usecTimestampNow(); + if (success) { + bool changed = false; + _transformLock.withWriteLock([&] { + Transform beforeTransform = _transform; + Transform::inverseMult(_transform, parentTransform, transform); + if (_transform != beforeTransform) { + changed = true; + _translationChanged = usecTimestampNow(); + _rotationChanged = usecTimestampNow(); + } + }); + if (changed) { + locationChanged(); } - }); - if (success && changed) { - locationChanged(); } } @@ -1369,3 +1373,43 @@ bool SpatiallyNestable::isParentPathComplete(int depth) const { return parent->isParentPathComplete(depth + 1); } + +void SpatiallyNestable::addGrab(GrabPointer grab) { + _grabsLock.withWriteLock([&] { + _grabs.insert(grab); + }); +} + +void SpatiallyNestable::removeGrab(GrabPointer grab) { + _grabsLock.withWriteLock([&] { + _grabs.remove(grab); + }); +} + +bool SpatiallyNestable::hasGrabs() { + bool result { false }; + _grabsLock.withReadLock([&] { + result = !_grabs.isEmpty(); + }); + return result; +} + +QUuid SpatiallyNestable::getEditSenderID() { + // if more than one avatar is grabbing something, decide which one should tell the enity-server about it + QUuid editSenderID; + bool editSenderIDSet { false }; + _grabsLock.withReadLock([&] { + foreach (const GrabPointer &grab, _grabs) { + QUuid ownerID = grab->getOwnerID(); + if (!editSenderIDSet) { + editSenderID = ownerID; + editSenderIDSet = true; + } else { + if (ownerID < editSenderID) { + editSenderID = ownerID; + } + } + } + }); + return editSenderID; +} diff --git a/libraries/shared/src/SpatiallyNestable.h b/libraries/shared/src/SpatiallyNestable.h index cf2e304d19..a55f32896b 100644 --- a/libraries/shared/src/SpatiallyNestable.h +++ b/libraries/shared/src/SpatiallyNestable.h @@ -18,6 +18,7 @@ #include "AACube.h" #include "SpatialParentFinder.h" #include "shared/ReadWriteLockable.h" +#include "Grab.h" class SpatiallyNestable; using SpatiallyNestableWeakPointer = std::weak_ptr<SpatiallyNestable>; @@ -213,6 +214,11 @@ public: virtual void dimensionsChanged() { _queryAACubeSet = false; } // called when a this object's dimensions have changed virtual void parentDeleted() { } // called on children of a deleted parent + virtual void addGrab(GrabPointer grab); + virtual void removeGrab(GrabPointer grab); + bool hasGrabs(); + virtual QUuid getEditSenderID(); + protected: QUuid _id; mutable SpatiallyNestableWeakPointer _parent; @@ -232,6 +238,9 @@ protected: quint64 _translationChanged { 0 }; quint64 _rotationChanged { 0 }; + mutable ReadWriteLockable _grabsLock; + QSet<GrabPointer> _grabs; + private: SpatiallyNestable() = delete; const NestableType _nestableType; // EntityItem or an AvatarData diff --git a/libraries/task/src/task/Task.cpp b/libraries/task/src/task/Task.cpp index 9123ef8b04..2d6493fbc5 100644 --- a/libraries/task/src/task/Task.cpp +++ b/libraries/task/src/task/Task.cpp @@ -29,5 +29,3 @@ void TaskFlow::abortTask() { bool TaskFlow::doAbortTask() const { return _doAbortTask; } - - diff --git a/libraries/task/src/task/Task.h b/libraries/task/src/task/Task.h index fb7012b16c..632e8a222e 100644 --- a/libraries/task/src/task/Task.h +++ b/libraries/task/src/task/Task.h @@ -146,7 +146,7 @@ public: Concept(name, config), _data(Data(std::forward<A>(args)...)), _input(input), - _output(Output()) { + _output(Output(), name + ".o") { applyConfiguration(); } @@ -419,6 +419,7 @@ protected: template < typename T0, typename T1, typename T2, typename T3, typename T4, typename T5, typename T6 > using VaryingSet7 = task::VaryingSet7<T0, T1, T2, T3, T4, T5, T6>; \ template < typename T0, typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7 > using VaryingSet8 = task::VaryingSet8<T0, T1, T2, T3, T4, T5, T6, T7>; \ template < typename T0, typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8 > using VaryingSet9 = task::VaryingSet9<T0, T1, T2, T3, T4, T5, T6, T7, T8>; \ + template < typename T0, typename T1, typename T2, typename T3, typename T4, typename T5, typename T6, typename T7, typename T8, typename T9 > using VaryingSet10 = task::VaryingSet10<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9>; \ template < class T, int NUM > using VaryingArray = task::VaryingArray<T, NUM>; diff --git a/libraries/task/src/task/Varying.h b/libraries/task/src/task/Varying.h index 9536db2799..686a00446b 100644 --- a/libraries/task/src/task/Varying.h +++ b/libraries/task/src/task/Varying.h @@ -12,10 +12,13 @@ #ifndef hifi_task_Varying_h #define hifi_task_Varying_h +#include <type_traits> #include <tuple> #include <array> namespace task { +class Varying; + // A varying piece of data, to be used as Job/Task I/O class Varying { @@ -23,15 +26,16 @@ public: Varying() {} Varying(const Varying& var) : _concept(var._concept) {} Varying& operator=(const Varying& var) { - _concept = var._concept; + _concept = var._concept; return (*this); } - template <class T> Varying(const T& data) : _concept(std::make_shared<Model<T>>(data)) {} + template <class T> Varying(const T& data, const std::string& name = "noname") : _concept(std::make_shared<Model<T>>(data, name)) {} template <class T> bool canCast() const { return !!std::dynamic_pointer_cast<Model<T>>(_concept); } template <class T> const T& get() const { return std::static_pointer_cast<const Model<T>>(_concept)->_data; } template <class T> T& edit() { return std::static_pointer_cast<Model<T>>(_concept)->_data; } + const std::string name() const { return _concept->name(); } // access potential sub varyings contained in this one. Varying operator[] (uint8_t index) const { return (*_concept)[index]; } @@ -45,23 +49,30 @@ public: protected: class Concept { public: + Concept(const std::string& name) : _name(name) {} + virtual ~Concept() = default; virtual Varying operator[] (uint8_t index) const = 0; virtual uint8_t length() const = 0; + + const std::string name() { return _name; } + + const std::string _name; }; template <class T> class Model : public Concept { public: using Data = T; - Model(const Data& data) : _data(data) {} + Model(const Data& data, const std::string& name) : Concept(name), _data(data) {} virtual ~Model() = default; virtual Varying operator[] (uint8_t index) const override { - Varying var; - return var; + return Varying(); + } + virtual uint8_t length() const override { + return 0; } - virtual uint8_t length() const override { return 0; } Data _data; }; @@ -69,11 +80,10 @@ protected: std::shared_ptr<Concept> _concept; }; -using VaryingPairBase = std::pair<Varying, Varying>; template < typename T0, typename T1 > -class VaryingSet2 : public VaryingPairBase { +class VaryingSet2 : public std::pair<Varying, Varying> { public: - using Parent = VaryingPairBase; + using Parent = std::pair<Varying, Varying>; typedef void is_proxy_tag; VaryingSet2() : Parent(Varying(T0()), Varying(T1())) {} @@ -98,7 +108,6 @@ public: Varying asVarying() const { return Varying((*this)); } }; - template <class T0, class T1, class T2> class VaryingSet3 : public std::tuple<Varying, Varying,Varying>{ public: @@ -168,7 +177,6 @@ public: Varying asVarying() const { return Varying((*this)); } }; - template <class T0, class T1, class T2, class T3, class T4> class VaryingSet5 : public std::tuple<Varying, Varying, Varying, Varying, Varying>{ public: @@ -289,6 +297,26 @@ public: const T6& get6() const { return std::get<6>((*this)).template get<T6>(); } T6& edit6() { return std::get<6>((*this)).template edit<T6>(); } + virtual Varying operator[] (uint8_t index) const { + switch (index) { + default: + return std::get<0>((*this)); + case 1: + return std::get<1>((*this)); + case 2: + return std::get<2>((*this)); + case 3: + return std::get<3>((*this)); + case 4: + return std::get<4>((*this)); + case 5: + return std::get<5>((*this)); + case 6: + return std::get<6>((*this)); + }; + } + virtual uint8_t length() const { return 7; } + Varying asVarying() const { return Varying((*this)); } }; @@ -325,6 +353,28 @@ public: const T7& get7() const { return std::get<7>((*this)).template get<T7>(); } T7& edit7() { return std::get<7>((*this)).template edit<T7>(); } + virtual Varying operator[] (uint8_t index) const { + switch (index) { + default: + return std::get<0>((*this)); + case 1: + return std::get<1>((*this)); + case 2: + return std::get<2>((*this)); + case 3: + return std::get<3>((*this)); + case 4: + return std::get<4>((*this)); + case 5: + return std::get<5>((*this)); + case 6: + return std::get<6>((*this)); + case 7: + return std::get<7>((*this)); + }; + } + virtual uint8_t length() const { return 8; } + Varying asVarying() const { return Varying((*this)); } }; @@ -363,6 +413,98 @@ public: const T8& get8() const { return std::get<8>((*this)).template get<T8>(); } T8& edit8() { return std::get<8>((*this)).template edit<T8>(); } + virtual Varying operator[] (uint8_t index) const { + switch (index) { + default: + return std::get<0>((*this)); + case 1: + return std::get<1>((*this)); + case 2: + return std::get<2>((*this)); + case 3: + return std::get<3>((*this)); + case 4: + return std::get<4>((*this)); + case 5: + return std::get<5>((*this)); + case 6: + return std::get<6>((*this)); + case 7: + return std::get<7>((*this)); + case 8: + return std::get<8>((*this)); + }; + } + virtual uint8_t length() const { return 9; } + + Varying asVarying() const { return Varying((*this)); } +}; + + +template <class T0, class T1, class T2, class T3, class T4, class T5, class T6, class T7, class T8, class T9> +class VaryingSet10 : public std::tuple<Varying, Varying, Varying, Varying, Varying, Varying, Varying, Varying, Varying, Varying> { +public: + using Parent = std::tuple<Varying, Varying, Varying, Varying, Varying, Varying, Varying, Varying, Varying, Varying>; + + VaryingSet10() : Parent(Varying(T0()), Varying(T1()), Varying(T2()), Varying(T3()), Varying(T4()), Varying(T5()), Varying(T6()), Varying(T7()), Varying(T8()), Varying(T9())) {} + VaryingSet10(const VaryingSet10& src) : Parent(std::get<0>(src), std::get<1>(src), std::get<2>(src), std::get<3>(src), std::get<4>(src), std::get<5>(src), std::get<6>(src), std::get<7>(src), std::get<8>(src), std::get<9>(src)) {} + VaryingSet10(const Varying& first, const Varying& second, const Varying& third, const Varying& fourth, const Varying& fifth, const Varying& sixth, const Varying& seventh, const Varying& eighth, const Varying& nine, const Varying& ten) : Parent(first, second, third, fourth, fifth, sixth, seventh, eighth, nine, ten) {} + + const T0& get0() const { return std::get<0>((*this)).template get<T0>(); } + T0& edit0() { return std::get<0>((*this)).template edit<T0>(); } + + const T1& get1() const { return std::get<1>((*this)).template get<T1>(); } + T1& edit1() { return std::get<1>((*this)).template edit<T1>(); } + + const T2& get2() const { return std::get<2>((*this)).template get<T2>(); } + T2& edit2() { return std::get<2>((*this)).template edit<T2>(); } + + const T3& get3() const { return std::get<3>((*this)).template get<T3>(); } + T3& edit3() { return std::get<3>((*this)).template edit<T3>(); } + + const T4& get4() const { return std::get<4>((*this)).template get<T4>(); } + T4& edit4() { return std::get<4>((*this)).template edit<T4>(); } + + const T5& get5() const { return std::get<5>((*this)).template get<T5>(); } + T5& edit5() { return std::get<5>((*this)).template edit<T5>(); } + + const T6& get6() const { return std::get<6>((*this)).template get<T6>(); } + T6& edit6() { return std::get<6>((*this)).template edit<T6>(); } + + const T7& get7() const { return std::get<7>((*this)).template get<T7>(); } + T7& edit7() { return std::get<7>((*this)).template edit<T7>(); } + + const T8& get8() const { return std::get<8>((*this)).template get<T8>(); } + T8& edit8() { return std::get<8>((*this)).template edit<T8>(); } + + const T9& get9() const { return std::get<9>((*this)).template get<T9>(); } + T9& edit9() { return std::get<9>((*this)).template edit<T9>(); } + + virtual Varying operator[] (uint8_t index) const { + switch (index) { + default: + return std::get<0>((*this)); + case 1: + return std::get<1>((*this)); + case 2: + return std::get<2>((*this)); + case 3: + return std::get<3>((*this)); + case 4: + return std::get<4>((*this)); + case 5: + return std::get<5>((*this)); + case 6: + return std::get<6>((*this)); + case 7: + return std::get<7>((*this)); + case 8: + return std::get<8>((*this)); + case 9: + return std::get<9>((*this)); + }; + } + virtual uint8_t length() const { return 10; } Varying asVarying() const { return Varying((*this)); } }; @@ -381,6 +523,7 @@ public: std::copy(list.begin(), list.end(), std::array<Varying, NUM>::begin()); } }; + } #endif // hifi_task_Varying_h diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index 13b0498e76..e75687c512 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -377,6 +377,7 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { QObject::connect(quickItem, SIGNAL(windowClosed()), this, SLOT(desktopWindowClosed())); QObject::connect(tabletRootWindow, SIGNAL(webEventReceived(QVariant)), this, SLOT(emitWebEvent(QVariant)), Qt::DirectConnection); + QObject::connect(quickItem, SIGNAL(screenChanged(QVariant, QVariant)), this, SIGNAL(screenChanged(QVariant, QVariant)), Qt::DirectConnection); // forward qml surface events to interface js connect(tabletRootWindow, &QmlWindowClass::fromQml, this, &TabletProxy::fromQml); @@ -488,6 +489,7 @@ void TabletProxy::setQmlTabletRoot(OffscreenQmlSurface* qmlOffscreenSurface) { _qmlTabletRoot = qmlOffscreenSurface ? qmlOffscreenSurface->getRootItem() : nullptr; if (_qmlTabletRoot && _qmlOffscreenSurface) { QObject::connect(_qmlOffscreenSurface, SIGNAL(webEventReceived(QVariant)), this, SLOT(emitWebEvent(QVariant))); + QObject::connect(_qmlTabletRoot, SIGNAL(screenChanged(QVariant, QVariant)), this, SIGNAL(screenChanged(QVariant, QVariant))); // forward qml surface events to interface js connect(_qmlOffscreenSurface, &OffscreenQmlSurface::fromQml, [this](QVariant message) { @@ -570,7 +572,6 @@ void TabletProxy::gotoMenuScreen(const QString& submenu) { QMetaObject::invokeMethod(root, "setMenuProperties", Q_ARG(QVariant, QVariant::fromValue(menu)), Q_ARG(const QVariant&, QVariant(submenu))); QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, QVariant(VRMENU_SOURCE_URL))); _state = State::Menu; - emit screenChanged(QVariant("Menu"), QVariant(VRMENU_SOURCE_URL)); _currentPathLoaded = VRMENU_SOURCE_URL; QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true))); if (_toolbarMode && _desktopWindow) { @@ -640,9 +641,6 @@ void TabletProxy::loadQMLSource(const QVariant& path, bool resizable) { if (root) { QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, path)); _state = State::QML; - if (path != _currentPathLoaded) { - emit screenChanged(QVariant("QML"), path); - } _currentPathLoaded = path; QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true))); if (_toolbarMode && _desktopWindow) { @@ -749,7 +747,6 @@ void TabletProxy::loadHomeScreen(bool forceOntoHomeScreen) { } } _state = State::Home; - emit screenChanged(QVariant("Home"), QVariant(TABLET_HOME_SOURCE_URL)); _currentPathLoaded = TABLET_HOME_SOURCE_URL; } } @@ -810,7 +807,6 @@ void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaS QMetaObject::invokeMethod(root, "setResizable", Q_ARG(const QVariant&, QVariant(false))); } _state = State::Web; - emit screenChanged(QVariant("Web"), QVariant(url)); _currentPathLoaded = QVariant(url); } else { // tablet is not initialized yet, save information and load when diff --git a/plugins/hifiCodec/src/plugin.json b/plugins/hifiCodec/src/plugin.json index df26a67ea8..27391a484d 100644 --- a/plugins/hifiCodec/src/plugin.json +++ b/plugins/hifiCodec/src/plugin.json @@ -1 +1,4 @@ -{"name":"HiFi 4:1 Audio Codec"} +{ + "name":"HiFi 4:1 Audio Codec", + "version":1 +} diff --git a/plugins/hifiKinect/src/plugin.json b/plugins/hifiKinect/src/plugin.json index daa3a668dd..b401bb8c83 100644 --- a/plugins/hifiKinect/src/plugin.json +++ b/plugins/hifiKinect/src/plugin.json @@ -1 +1,4 @@ -{"name":"Kinect"} +{ + "name":"Kinect", + "version":1 +} diff --git a/plugins/hifiLeapMotion/src/plugin.json b/plugins/hifiLeapMotion/src/plugin.json index 2e867d96e4..92e410ef11 100644 --- a/plugins/hifiLeapMotion/src/plugin.json +++ b/plugins/hifiLeapMotion/src/plugin.json @@ -1 +1,4 @@ -{"name":"Leap Motion"} +{ + "name":"Leap Motion", + "version":1 +} diff --git a/plugins/hifiNeuron/src/plugin.json b/plugins/hifiNeuron/src/plugin.json index d153b5cebd..7059248934 100644 --- a/plugins/hifiNeuron/src/plugin.json +++ b/plugins/hifiNeuron/src/plugin.json @@ -1 +1,4 @@ -{"name":"Neuron"} +{ + "name":"Neuron", + "version":1 +} diff --git a/plugins/hifiSdl2/src/Joystick.cpp b/plugins/hifiSdl2/src/Joystick.cpp index be4ad6e4ee..ad0b459544 100644 --- a/plugins/hifiSdl2/src/Joystick.cpp +++ b/plugins/hifiSdl2/src/Joystick.cpp @@ -46,8 +46,8 @@ void Joystick::closeJoystick() { void Joystick::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) { for (auto axisState : _axisStateMap) { - if (fabsf(axisState.second) < CONTROLLER_THRESHOLD) { - _axisStateMap[axisState.first] = 0.0f; + if (fabsf(axisState.second.value) < CONTROLLER_THRESHOLD) { + _axisStateMap[axisState.first].value = 0.0f; } } } @@ -59,7 +59,7 @@ void Joystick::focusOutEvent() { void Joystick::handleAxisEvent(const SDL_ControllerAxisEvent& event) { SDL_GameControllerAxis axis = (SDL_GameControllerAxis) event.axis; - _axisStateMap[makeInput((controller::StandardAxisChannel)axis).getChannel()] = (float)event.value / MAX_AXIS; + _axisStateMap[makeInput((controller::StandardAxisChannel)axis).getChannel()].value = (float)event.value / MAX_AXIS; } void Joystick::handleButtonEvent(const SDL_ControllerButtonEvent& event) { diff --git a/plugins/hifiSdl2/src/plugin.json b/plugins/hifiSdl2/src/plugin.json index a65846ecab..e1963f9995 100644 --- a/plugins/hifiSdl2/src/plugin.json +++ b/plugins/hifiSdl2/src/plugin.json @@ -1 +1,4 @@ -{"name":"SDL2"} +{ + "name":"SDL2", + "version":1 +} diff --git a/plugins/hifiSixense/src/plugin.json b/plugins/hifiSixense/src/plugin.json index 9e6e15a354..a56a1ba384 100644 --- a/plugins/hifiSixense/src/plugin.json +++ b/plugins/hifiSixense/src/plugin.json @@ -1 +1,4 @@ -{"name":"Sixense"} +{ + "name":"Sixense", + "version":1 +} diff --git a/plugins/hifiSpacemouse/src/plugin.json b/plugins/hifiSpacemouse/src/plugin.json index 294f436039..6eac13ac66 100644 --- a/plugins/hifiSpacemouse/src/plugin.json +++ b/plugins/hifiSpacemouse/src/plugin.json @@ -1 +1,4 @@ -{"name":"Spacemouse"} +{ + "name":"Spacemouse", + "version":1 +} diff --git a/plugins/oculus/src/OculusControllerManager.cpp b/plugins/oculus/src/OculusControllerManager.cpp index 392d990638..76ff4a1755 100644 --- a/plugins/oculus/src/OculusControllerManager.cpp +++ b/plugins/oculus/src/OculusControllerManager.cpp @@ -258,15 +258,15 @@ void OculusControllerManager::TouchDevice::update(float deltaTime, using namespace controller; // Axes const auto& inputState = _parent._touchInputState; - _axisStateMap[LX] = inputState.Thumbstick[ovrHand_Left].x; - _axisStateMap[LY] = inputState.Thumbstick[ovrHand_Left].y; - _axisStateMap[LT] = inputState.IndexTrigger[ovrHand_Left]; - _axisStateMap[LEFT_GRIP] = inputState.HandTrigger[ovrHand_Left]; + _axisStateMap[LX].value = inputState.Thumbstick[ovrHand_Left].x; + _axisStateMap[LY].value = inputState.Thumbstick[ovrHand_Left].y; + _axisStateMap[LT].value = inputState.IndexTrigger[ovrHand_Left]; + _axisStateMap[LEFT_GRIP].value = inputState.HandTrigger[ovrHand_Left]; - _axisStateMap[RX] = inputState.Thumbstick[ovrHand_Right].x; - _axisStateMap[RY] = inputState.Thumbstick[ovrHand_Right].y; - _axisStateMap[RT] = inputState.IndexTrigger[ovrHand_Right]; - _axisStateMap[RIGHT_GRIP] = inputState.HandTrigger[ovrHand_Right]; + _axisStateMap[RX].value = inputState.Thumbstick[ovrHand_Right].x; + _axisStateMap[RY].value = inputState.Thumbstick[ovrHand_Right].y; + _axisStateMap[RT].value = inputState.IndexTrigger[ovrHand_Right]; + _axisStateMap[RIGHT_GRIP].value = inputState.HandTrigger[ovrHand_Right]; // Buttons for (const auto& pair : BUTTON_MAP) { diff --git a/plugins/oculus/src/oculus.json b/plugins/oculus/src/oculus.json index 86546c8dd5..0043a8b50f 100644 --- a/plugins/oculus/src/oculus.json +++ b/plugins/oculus/src/oculus.json @@ -1 +1,4 @@ -{"name":"Oculus Rift"} +{ + "name":"Oculus Rift", + "version":1 +} diff --git a/plugins/oculusLegacy/src/oculus.json b/plugins/oculusLegacy/src/oculus.json index 86546c8dd5..0043a8b50f 100644 --- a/plugins/oculusLegacy/src/oculus.json +++ b/plugins/oculusLegacy/src/oculus.json @@ -1 +1,4 @@ -{"name":"Oculus Rift"} +{ + "name":"Oculus Rift", + "version":1 +} diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 11ef222172..11d941dcd0 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -671,8 +671,6 @@ void OpenVrDisplayPlugin::postPreview() { PoseData nextRender, nextSim; nextRender.frameIndex = presentCount(); - _hmdActivityLevel = _system->GetTrackedDeviceActivityLevel(vr::k_unTrackedDeviceIndex_Hmd); - if (!_threadedSubmit) { vr::VRCompositor()->WaitGetPoses(nextRender.vrPoses, vr::k_unMaxTrackedDeviceCount, nextSim.vrPoses, vr::k_unMaxTrackedDeviceCount); @@ -692,7 +690,7 @@ void OpenVrDisplayPlugin::postPreview() { } bool OpenVrDisplayPlugin::isHmdMounted() const { - return _hmdActivityLevel == vr::k_EDeviceActivityLevel_UserInteraction; + return isHeadInHeadset(); } void OpenVrDisplayPlugin::updatePresentPose() { diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.h b/plugins/openvr/src/OpenVrDisplayPlugin.h index 5585957031..265f328920 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.h +++ b/plugins/openvr/src/OpenVrDisplayPlugin.h @@ -37,6 +37,7 @@ class OpenVrDisplayPlugin : public HmdDisplayPlugin { public: bool isSupported() const override; const QString getName() const override; + bool getSupportsAutoSwitch() override final { return true; } glm::mat4 getEyeProjection(Eye eye, const glm::mat4& baseProjection) const override; glm::mat4 getCullingProjection(const glm::mat4& baseProjection) const override; @@ -78,7 +79,6 @@ protected: private: vr::IVRSystem* _system { nullptr }; - std::atomic<vr::EDeviceActivityLevel> _hmdActivityLevel { vr::k_EDeviceActivityLevel_Unknown }; std::atomic<uint32_t> _keyboardSupressionCount{ 0 }; vr::HmdMatrix34_t _lastGoodHMDPose; diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index 9a57413f95..108431a8ac 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -37,6 +37,11 @@ static int refCount { 0 }; static Mutex mutex; static vr::IVRSystem* activeHmd { nullptr }; static bool _openVrQuitRequested { false }; +static bool _headInHeadset { false }; + +bool isHeadInHeadset() { + return _headInHeadset; +} bool openVrQuitRequested() { return _openVrQuitRequested; @@ -272,6 +277,15 @@ void handleOpenVrEvents() { default: break; } + if (event.data.controller.button == vr::k_EButton_ProximitySensor) { + vr::VRControllerState_t controllerState = vr::VRControllerState_t(); + if (activeHmd->GetControllerState(vr::k_unTrackedDeviceIndex_Hmd, &controllerState, sizeof(vr::VRControllerState_t))) { + ulong promitySensorFlag = (1UL << ((int)vr::k_EButton_ProximitySensor)); + _headInHeadset = (controllerState.ulButtonPressed & promitySensorFlag) == promitySensorFlag; + } + + } + #if DEV_BUILD qDebug() << "OpenVR: Event " << activeHmd->GetEventTypeNameFromEnum((vr::EVREventType)event.eventType) << "(" << event.eventType << ")"; #endif diff --git a/plugins/openvr/src/OpenVrHelpers.h b/plugins/openvr/src/OpenVrHelpers.h index 833e5ba65d..b0960a03eb 100644 --- a/plugins/openvr/src/OpenVrHelpers.h +++ b/plugins/openvr/src/OpenVrHelpers.h @@ -23,6 +23,7 @@ vr::IVRSystem* acquireOpenVrSystem(); void releaseOpenVrSystem(); void handleOpenVrEvents(); bool openVrQuitRequested(); +bool isHeadInHeadset(); void enableOpenVrKeyboard(PluginContainer* container); void disableOpenVrKeyboard(); bool isOpenVrKeyboardShown(); diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index 53c23403a5..3aea5f1ce0 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -928,8 +928,8 @@ void ViveControllerManager::InputDevice::partitionTouchpad(int sButton, int xAxi const float CENTER_DEADBAND = 0.6f; const float DIAGONAL_DIVIDE_IN_RADIANS = PI / 4.0f; if (_buttonPressedMap.find(sButton) != _buttonPressedMap.end()) { - float absX = abs(_axisStateMap[xAxis]); - float absY = abs(_axisStateMap[yAxis]); + float absX = abs(_axisStateMap[xAxis].value); + float absY = abs(_axisStateMap[yAxis].value); glm::vec2 cartesianQuadrantI(absX, absY); float angle = glm::atan(cartesianQuadrantI.y / cartesianQuadrantI.x); float radius = glm::length(cartesianQuadrantI); @@ -956,10 +956,10 @@ void ViveControllerManager::InputDevice::handleAxisEvent(float deltaTime, uint32 } else { stick = _filteredRightStick.process(deltaTime, stick); } - _axisStateMap[isLeftHand ? LX : RX] = stick.x; - _axisStateMap[isLeftHand ? LY : RY] = stick.y; + _axisStateMap[isLeftHand ? LX : RX].value = stick.x; + _axisStateMap[isLeftHand ? LY : RY].value = stick.y; } else if (axis == vr::k_EButton_SteamVR_Trigger) { - _axisStateMap[isLeftHand ? LT : RT] = x; + _axisStateMap[isLeftHand ? LT : RT].value = x; // The click feeling on the Vive controller trigger represents a value of *precisely* 1.0, // so we can expose that as an additional button if (x >= 1.0f) { @@ -1000,7 +1000,7 @@ void ViveControllerManager::InputDevice::handleButtonEvent(float deltaTime, uint if (button == vr::k_EButton_ApplicationMenu) { _buttonPressedMap.insert(isLeftHand ? LEFT_APP_MENU : RIGHT_APP_MENU); } else if (button == vr::k_EButton_Grip) { - _axisStateMap[isLeftHand ? LEFT_GRIP : RIGHT_GRIP] = 1.0f; + _axisStateMap[isLeftHand ? LEFT_GRIP : RIGHT_GRIP].value = 1.0f; } else if (button == vr::k_EButton_SteamVR_Trigger) { _buttonPressedMap.insert(isLeftHand ? LT : RT); } else if (button == vr::k_EButton_SteamVR_Touchpad) { @@ -1008,7 +1008,7 @@ void ViveControllerManager::InputDevice::handleButtonEvent(float deltaTime, uint } } else { if (button == vr::k_EButton_Grip) { - _axisStateMap[isLeftHand ? LEFT_GRIP : RIGHT_GRIP] = 0.0f; + _axisStateMap[isLeftHand ? LEFT_GRIP : RIGHT_GRIP].value = 0.0f; } } diff --git a/plugins/openvr/src/plugin.json b/plugins/openvr/src/plugin.json index d68c8e68d3..763414cd8b 100644 --- a/plugins/openvr/src/plugin.json +++ b/plugins/openvr/src/plugin.json @@ -1 +1,4 @@ -{"name":"OpenVR (Vive)"} +{ + "name":"OpenVR (Vive)", + "version":1 +} diff --git a/plugins/pcmCodec/src/plugin.json b/plugins/pcmCodec/src/plugin.json index 2d86251845..525124592b 100644 --- a/plugins/pcmCodec/src/plugin.json +++ b/plugins/pcmCodec/src/plugin.json @@ -1 +1,4 @@ -{"name":"PCM Codec"} +{ + "name":"PCM Codec", + "version":1 +} diff --git a/plugins/steamClient/src/plugin.json b/plugins/steamClient/src/plugin.json index dfe37917d2..ce4647188f 100644 --- a/plugins/steamClient/src/plugin.json +++ b/plugins/steamClient/src/plugin.json @@ -1 +1,4 @@ -{"name":"Steam Client"} +{ + "name":"Steam Client", + "version":1 +} diff --git a/scripts/developer/debugging/queryAACubeInspector.js b/scripts/developer/debugging/queryAACubeInspector.js index d8a87c3cf5..d6adcf02b7 100644 --- a/scripts/developer/debugging/queryAACubeInspector.js +++ b/scripts/developer/debugging/queryAACubeInspector.js @@ -40,7 +40,8 @@ function updateOverlay(entityID, queryAACube) { blue: 255 }, alpha: 1, - solid: false + solid: false, + grabbable: false }); } } @@ -60,4 +61,4 @@ function cleanup() { } } -Script.scriptEnding.connect(cleanup); \ No newline at end of file +Script.scriptEnding.connect(cleanup); diff --git a/scripts/developer/tests/avatarToWorldTests.js b/scripts/developer/tests/avatarToWorldTests.js index c2da49cbd4..e27c3c4b5b 100644 --- a/scripts/developer/tests/avatarToWorldTests.js +++ b/scripts/developer/tests/avatarToWorldTests.js @@ -42,52 +42,6 @@ function jointToWorldPointTest_update(deltaTime) { Entities.editEntity(jointToWorldPointTest_sphereEntity, newProperties); } -//jointToWorldDirection -// create line in world space -// each frame calculate world space direction of players head z axis -// update line to match -var jointToWorldDirectionTest_lineEntity; -function jointToWorldDirectionTest() { - var jointIndex = MyAvatar.getJointIndex("Head"); - var avatarPos = MyAvatar.getJointPosition(jointIndex); - - var jointDir = { x: 1, y: 0, z: 1 }; - var worldDir = MyAvatar.jointToWorldDirection(jointDir, jointIndex); - print(worldDir.x); - print(worldDir.y); - print(worldDir.z); - jointToWorldDirectionTest_lineEntity = Entities.addEntity({ - type: "Line", - color: {red: 250, green: 0, blue: 0}, - dimensions: {x: 5, y: 5, z: 5}, - lifetime: 10.0, - linePoints: [{ - x: 0, - y: 0, - z: 0 - }, worldDir - ], - position : avatarPos, - }); -} -function jointToWorldDirection_update(deltaTime) { - var jointIndex = MyAvatar.getJointIndex("Head"); - var avatarPos = MyAvatar.getJointPosition(jointIndex); - var jointDir = { x: 1, y: 0, z: 0 }; - var worldDir = MyAvatar.jointToWorldDirection(jointDir, jointIndex); - var newProperties = { - linePoints: [{ - x: 0, - y: 0, - z: 0 - }, worldDir - ], - position : avatarPos - }; - - Entities.editEntity(jointToWorldDirectionTest_lineEntity, newProperties); -} - //jointToWorldRotation // create box in world space // each frame calculate world space rotation of players head diff --git a/scripts/developer/tests/worldToAvatarTests.js b/scripts/developer/tests/worldToAvatarTests.js index 6f0b19dc2d..e368a5e455 100644 --- a/scripts/developer/tests/worldToAvatarTests.js +++ b/scripts/developer/tests/worldToAvatarTests.js @@ -49,53 +49,6 @@ function worldToJointPointTest() { Entities.addEntity(worldSphereProps); } -//worldToJointDirection -// create line and attach to avatars head -// each frame calculate direction of world x axis in joint space of players head -// update arrow orientation to match -var worldToJointDirectionTest_lineEntity; -function worldToJointDirectionTest() { - var jointIndex = MyAvatar.getJointIndex("Head"); - - var jointPosition_WorldSpace = MyAvatar.getJointPosition(jointIndex); - var jointOffset_WorldSpace = { x: 0, y: 0, z: 0 }; - var jointPosition_WorldSpaceOffset = Vec3.sum(jointPosition_WorldSpace, jointOffset_WorldSpace); - var jointPosition_JointSpaceOffset = MyAvatar.worldToJointPoint(jointPosition_WorldSpaceOffset, jointIndex); - - var worldDir = { x: 1, y: 0, z: 0 }; - var avatarDir = MyAvatar.worldToJointDirection(worldDir, jointIndex); - - worldToJointDirectionTest_lineEntity = Entities.addEntity({ - type: "Line", - color: {red: 200, green: 250, blue: 0}, - dimensions: {x: 5, y: 5, z: 5}, - lifetime: 10.0, - linePoints: [{ - x: 0, - y: 0, - z: 0 - }, avatarDir - ], - localPosition : jointOffset_WorldSpace, - parentID : AVATAR_SELF_ID, - parentJointIndex : jointIndex - }); -} - -function worldToJointDirectionTest_update(deltaTime) { - var jointIndex = MyAvatar.getJointIndex("Head"); - var worldDir = { x: 1, y: 0, z: 0 }; - var avatarDir = MyAvatar.worldToJointDirection(worldDir, jointIndex); - var newProperties = { linePoints: [{ - x: 0, - y: 0, - z: 0 - }, avatarDir - ]}; - - Entities.editEntity(worldToJointDirectionTest_lineEntity, newProperties); -} - //worldToJointRotation // create box and parent to some player joint // convert world identity rotation to joint space rotation diff --git a/scripts/developer/utilities/lib/jet/jet.js b/scripts/developer/utilities/lib/jet/jet.js index 71fb3e1f70..40563e4b2c 100644 --- a/scripts/developer/utilities/lib/jet/jet.js +++ b/scripts/developer/utilities/lib/jet/jet.js @@ -45,6 +45,24 @@ function job_propKeys(job) { return propKeys; } +// Access job inputs +// return all the inputs of a job +function job_inoutKeys(job) { + var keys = Object.keys(job) + var inoutKeys = []; + for (var k=0; k < keys.length;k++) { + // Filter for relevant property + var key = keys[k] + if ((typeof job[key]) !== "function") { + if ((key == "input") || (key == "output")) { + inoutKeys.push(keys[k]); + } + } + } + + return inoutKeys; +} + // Use this function to create a functor that will fill the specifed array with one entry name per task and job and it s rank function job_list_functor(jobList, maxDepth) { if (maxDepth === undefined) maxDepth = 100 @@ -55,7 +73,7 @@ function job_list_functor(jobList, maxDepth) { } // Use this function to create a functor that will print the content of the Job visited calling the specified 'printout' function -function job_print_functor(printout, showProps, maxDepth) { +function job_print_functor(printout, showProps, showInOuts, maxDepth) { if (maxDepth === undefined) maxDepth = 100 return function (job, depth, index) { var tab = " " @@ -69,6 +87,14 @@ function job_print_functor(printout, showProps, maxDepth) { printout(depthTab + tab + tab + typeof prop + " " + keys[p] + " " + prop); } } + if (showInOuts) { + printout("jsdkfkjdskflj") + var inouts = job_inoutKeys(job); + for (var p=0; p < inouts.length;p++) { + var prop = job[inouts[p]] + printout(depthTab + tab + tab + typeof prop + " " + inouts[p] + " " + prop); + } + } return depth < maxDepth; } } diff --git a/scripts/developer/utilities/lib/jet/qml/TaskList.qml b/scripts/developer/utilities/lib/jet/qml/TaskList.qml index 166f604666..e4b0267d3f 100644 --- a/scripts/developer/utilities/lib/jet/qml/TaskList.qml +++ b/scripts/developer/utilities/lib/jet/qml/TaskList.qml @@ -32,7 +32,7 @@ Rectangle { Component.onCompleted: { var message = "" - var functor = Jet.job_print_functor(function (line) { message += line + "\n"; }, false); + var functor = Jet.job_print_functor(function (line) { message += line + "\n"; }, false, true); Jet.task_traverseTree(rootConfig, functor); textArea.append(message); } diff --git a/scripts/developer/utilities/render/deferredLighting.qml b/scripts/developer/utilities/render/deferredLighting.qml index 78edf7939f..9737fb7f1a 100644 --- a/scripts/developer/utilities/render/deferredLighting.qml +++ b/scripts/developer/utilities/render/deferredLighting.qml @@ -47,8 +47,8 @@ Rectangle { "Emissive:LightingModel:enableEmissive", "Lightmap:LightingModel:enableLightmap", "Background:LightingModel:enableBackground", - "Haze:LightingModel:enableHaze", - "ssao:AmbientOcclusion:enabled", + "Haze:LightingModel:enableHaze", + "ssao:LightingModel:enableAmbientOcclusion", "Textures:LightingModel:enableMaterialTexturing" ] HifiControls.CheckBox { @@ -93,7 +93,7 @@ Rectangle { "Spot:LightingModel:enableSpotLight", "Light Contour:LightingModel:showLightContour", "Zone Stack:DrawZoneStack:enabled", - "Shadow:RenderShadowTask:enabled" + "Shadow:LightingModel:enableShadow" ] HifiControls.CheckBox { boxSize: 20 diff --git a/scripts/developer/utilities/render/engineList.js b/scripts/developer/utilities/render/engineList.js new file mode 100644 index 0000000000..85028ded53 --- /dev/null +++ b/scripts/developer/utilities/render/engineList.js @@ -0,0 +1,13 @@ + function openEngineTaskView() { + // Set up the qml ui + var qml = Script.resolvePath('engineList.qml'); + var window = new OverlayWindow({ + title: 'Render Engine', + source: qml, + width: 300, + height: 400 + }); + window.setPosition(200, 50); + //window.closed.connect(function() { Script.stop(); }); + } + openEngineTaskView(); \ No newline at end of file diff --git a/scripts/developer/utilities/render/engineList.qml b/scripts/developer/utilities/render/engineList.qml new file mode 100644 index 0000000000..bf5c2a13a8 --- /dev/null +++ b/scripts/developer/utilities/render/engineList.qml @@ -0,0 +1,30 @@ +// +// engineList.qml +// +// Created by Sam Gateau on 12/3/2018 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.7 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.3 + +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls + +import "../lib/jet/qml" as Jet + +Item { + HifiConstants { id: hifi;} + id: render; + anchors.fill: parent + + property var mainViewTask: Render.getConfig("RenderMainView") + + Jet.TaskList { + rootConfig: Render + anchors.fill: render + } +} \ No newline at end of file diff --git a/scripts/developer/utilities/tests/entityPerfTest.js b/scripts/developer/utilities/tests/entityPerfTest.js index 181afcf360..d3846baba9 100644 --- a/scripts/developer/utilities/tests/entityPerfTest.js +++ b/scripts/developer/utilities/tests/entityPerfTest.js @@ -87,14 +87,6 @@ function makeEntity () { .withRay(function(){ Entities.findRayIntersection(this.ray, false); }); - entityTests.addTestCase('findRayIntersectionBlocking, precisionPicking=true') - .withRay(function(){ - Entities.findRayIntersectionBlocking(this.ray, true); - }) - entityTests.addTestCase('findRayIntersectionBlocking, precisionPicking=false') - .withRay(function(){ - Entities.findRayIntersectionBlocking(this.ray, false); - }) entityTests.addTestCase('no-op') .run(function(){}); diff --git a/scripts/developer/utilities/workload/avatars.js b/scripts/developer/utilities/workload/avatars.js new file mode 100644 index 0000000000..3080ef09db --- /dev/null +++ b/scripts/developer/utilities/workload/avatars.js @@ -0,0 +1,129 @@ +"use strict"; + +// +// Avatars.js +// tablet-engine app +// +// 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 +// + +(function() { + var TABLET_BUTTON_NAME = "Avatars"; + var QMLAPP_URL = Script.resolvePath("./avatars.qml"); + var ICON_URL = Script.resolvePath("../../../system/assets/images/lod-i.svg"); + var ACTIVE_ICON_URL = Script.resolvePath("../../../system/assets/images/lod-a.svg"); + + var onTablet = false; // set this to true to use the tablet, false use a floating window + + var onAppScreen = false; + + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + var button = tablet.addButton({ + text: TABLET_BUTTON_NAME, + icon: ICON_URL, + activeIcon: ACTIVE_ICON_URL + }); + + var hasEventBridge = false; + + var onScreen = false; + var window; + + function onClicked() { + if (onTablet) { + if (onAppScreen) { + tablet.gotoHomeScreen(); + } else { + tablet.loadQMLSource(QMLAPP_URL); + } + } else { + if (onScreen) { + killWindow() + } else { + createWindow() + } + } + } + + function createWindow() { + var qml = Script.resolvePath(QMLAPP_URL); + window = Desktop.createWindow(Script.resolvePath(QMLAPP_URL), { + title: TABLET_BUTTON_NAME, + flags: Desktop.ALWAYS_ON_TOP, + presentationMode: Desktop.PresentationMode.NATIVE, + size: {x: 400, y: 600} + }); + window.closed.connect(killWindow); + window.fromQml.connect(fromQml); + onScreen = true + button.editProperties({isActive: true}); + } + + function killWindow() { + if (window !== undefined) { + window.closed.disconnect(killWindow); + window.fromQml.disconnect(fromQml); + window.close() + window = undefined + } + onScreen = false + button.editProperties({isActive: false}) + } + + function wireEventBridge(on) { + if (!tablet) { + print("Warning in wireEventBridge(): 'tablet' undefined!"); + return; + } + if (on) { + if (!hasEventBridge) { + tablet.fromQml.connect(fromQml); + hasEventBridge = true; + } + } else { + if (hasEventBridge) { + tablet.fromQml.disconnect(fromQml); + hasEventBridge = false; + } + } + } + + function onScreenChanged(type, url) { + if (onTablet) { + onAppScreen = (url === QMLAPP_URL); + + button.editProperties({isActive: onAppScreen}); + wireEventBridge(onAppScreen); + } + } + + button.clicked.connect(onClicked); + tablet.screenChanged.connect(onScreenChanged); + + Script.scriptEnding.connect(function () { + killWindow() + if (onAppScreen) { + tablet.gotoHomeScreen(); + } + button.clicked.disconnect(onClicked); + tablet.screenChanged.disconnect(onScreenChanged); + tablet.removeButton(button); + }); + + function fromQml(message) { + } + + function sendToQml(message) { + if (onTablet) { + tablet.sendToQml(message); + } else { + if (window) { + window.sendToQml(message); + } + } + } + +}()); diff --git a/scripts/developer/utilities/workload/avatars.qml b/scripts/developer/utilities/workload/avatars.qml new file mode 100644 index 0000000000..5951e72c31 --- /dev/null +++ b/scripts/developer/utilities/workload/avatars.qml @@ -0,0 +1,78 @@ +// +// avatars.qml +// scripts/developer/utilities/workload +// +// Created by Sam Gateau on 2018.11.28 +// Copyright 2018 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html +// +import QtQuick 2.5 +import QtQuick.Controls 1.4 + +import stylesUit 1.0 +import controlsUit 1.0 as HifiControls + +import "../lib/plotperf" +import "../render/configSlider" + +Item { + id: root + anchors.fill:parent + + Component.onCompleted: { + } + + Component.onDestruction: { + } + + Column { + id: topHeader + spacing: 8 + anchors.right: parent.right + anchors.left: parent.left + } + + Column { + id: stats + spacing: 4 + anchors.right: parent.right + anchors.left: parent.left + anchors.top: topHeader.bottom + anchors.bottom: parent.bottom + + function evalEvenHeight() { + // Why do we have to do that manually ? cannot seem to find a qml / anchor / layout mode that does that ? + var numPlots = (children.length + - 2) + return (height - topLine.height - bottomLine.height - spacing * (numPlots - 1)) / (numPlots) + } + + Separator { + id: topLine + } + + PlotPerf { + title: "Avatars" + height: parent.evalEvenHeight() + object: Stats + valueScale: 1 + valueUnit: "num" + plots: [ + { + prop: "updatedAvatarCount", + label: "updatedAvatarCount", + color: "#FFFF00" + }, + { + prop: "notUpdatedAvatarCount", + label: "notUpdatedAvatarCount", + color: "#00FF00" + } + ] + } + Separator { + id: bottomLine + } + } +} diff --git a/scripts/system/assets/data/createAppTooltips.json b/scripts/system/assets/data/createAppTooltips.json index 9f77a86dc9..cb194c9d66 100644 --- a/scripts/system/assets/data/createAppTooltips.json +++ b/scripts/system/assets/data/createAppTooltips.json @@ -11,14 +11,33 @@ "textColor": { "tooltip": "The color of the text." }, + "textAlpha": { + "tooltip": "The alpha of the text." + }, "backgroundColor": { "tooltip": "The color of the background." }, + "backgroundAlpha": { + "tooltip": "The alpha of the background." + }, "lineHeight": { "tooltip": "The height of each line of text. This determines the size of the text." }, - "faceCamera": { - "tooltip": "If enabled, the entity follows the camera of each user, creating a billboard effect." + "textBillboardMode": { + "tooltip": "If enabled, determines how the entity will face the camera.", + "jsPropertyName": "billboardMode" + }, + "topMargin": { + "tooltip": "The top margin, in meters." + }, + "rightMargin": { + "tooltip": "The right margin, in meters." + }, + "bottomMargin": { + "tooltip": "The bottom margin, in meters." + }, + "leftMargin": { + "tooltip": "The left margin, in meters." }, "flyingAllowed": { "tooltip": "If enabled, users can fly in the zone." @@ -149,9 +168,25 @@ "originalTextures": { "tooltip": "A JSON string containing the original texture used on the model." }, - "image": { - "tooltip": "The URL for the image source.", - "jsPropertyName": "textures" + "imageURL": { + "tooltip": "The URL for the image source." + }, + "imageColor": { + "tooltip": "The tint to be applied to the image.", + "jsPropertyName": "color" + }, + "emissive": { + "tooltip": "If enabled, the image will display at full brightness." + }, + "subImage": { + "tooltip": "The area of the image that is displayed." + }, + "imageBillboardMode": { + "tooltip": "If enabled, determines how the entity will face the camera.", + "jsPropertyName": "billboardMode" + }, + "keepAspectRatio": { + "tooltip": "If enabled, the image will maintain its original aspect ratio." }, "sourceUrl": { "tooltip": "The URL for the web page source." @@ -336,6 +371,15 @@ "materialMappingRot": { "tooltip": "How much to rotate the material within the parent's UV-space, in degrees." }, + "followCamera": { + "tooltip": "If enabled, the grid is always visible even as the camera moves to another position." + }, + "majorGridEvery": { + "tooltip": "The number of \"Minor Grid Every\" intervals at which to draw a thick grid line." + }, + "minorGridEvery": { + "tooltip": "The real number of meters at which to draw thin grid lines." + }, "id": { "tooltip": "The unique identifier of this entity." }, diff --git a/scripts/system/avatarapp.js b/scripts/system/avatarapp.js index 65abf791a5..2b9a738202 100644 --- a/scripts/system/avatarapp.js +++ b/scripts/system/avatarapp.js @@ -62,7 +62,8 @@ function getMyAvatar() { function getMyAvatarSettings() { return { dominantHand: MyAvatar.getDominantHand(), - collisionsEnabled : MyAvatar.getCollisionsEnabled(), + collisionsEnabled: MyAvatar.getCollisionsEnabled(), + otherAvatarsCollisionsEnabled: MyAvatar.getOtherAvatarsCollisionsEnabled(), collisionSoundUrl : MyAvatar.collisionSoundURL, animGraphUrl: MyAvatar.getAnimGraphUrl(), animGraphOverrideUrl : MyAvatar.getAnimGraphOverrideUrl(), @@ -135,6 +136,13 @@ function onCollisionsEnabledChanged(enabled) { } } +function onOtherAvatarsCollisionsEnabledChanged(enabled) { + if (currentAvatarSettings.otherAvatarsCollisionsEnabled !== enabled) { + currentAvatarSettings.otherAvatarsCollisionsEnabled = enabled; + sendToQml({ 'method': 'settingChanged', 'name': 'otherAvatarsCollisionsEnabled', 'value': enabled }) + } +} + function onNewCollisionSoundUrl(url) { if(currentAvatarSettings.collisionSoundUrl !== url) { currentAvatarSettings.collisionSoundUrl = url; @@ -323,6 +331,7 @@ function fromQml(message) { // messages are {method, params}, like json-rpc. See currentAvatar.avatarScale = message.avatarScale; MyAvatar.setDominantHand(message.settings.dominantHand); + MyAvatar.setOtherAvatarsCollisionsEnabled(message.settings.otherAvatarsCollisionsEnabled); MyAvatar.setCollisionsEnabled(message.settings.collisionsEnabled); MyAvatar.collisionSoundURL = message.settings.collisionSoundUrl; MyAvatar.setAnimGraphOverrideUrl(message.settings.animGraphOverrideUrl); @@ -513,6 +522,7 @@ function off() { MyAvatar.skeletonModelURLChanged.disconnect(onSkeletonModelURLChanged); MyAvatar.dominantHandChanged.disconnect(onDominantHandChanged); MyAvatar.collisionsEnabledChanged.disconnect(onCollisionsEnabledChanged); + MyAvatar.otherAvatarsCollisionsEnabledChanged.disconnect(onOtherAvatarsCollisionsEnabledChanged); MyAvatar.newCollisionSoundURL.disconnect(onNewCollisionSoundUrl); MyAvatar.animGraphUrlChanged.disconnect(onAnimGraphUrlChanged); MyAvatar.targetScaleChanged.disconnect(onTargetScaleChanged); @@ -533,6 +543,7 @@ function on() { MyAvatar.skeletonModelURLChanged.connect(onSkeletonModelURLChanged); MyAvatar.dominantHandChanged.connect(onDominantHandChanged); MyAvatar.collisionsEnabledChanged.connect(onCollisionsEnabledChanged); + MyAvatar.otherAvatarsCollisionsEnabledChanged.connect(onOtherAvatarsCollisionsEnabledChanged); MyAvatar.newCollisionSoundURL.connect(onNewCollisionSoundUrl); MyAvatar.animGraphUrlChanged.connect(onAnimGraphUrlChanged); MyAvatar.targetScaleChanged.connect(onTargetScaleChanged); diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index adc09b507f..19efdc042c 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -433,7 +433,6 @@ function fromQml(message) { } break; case 'needsLogIn_loginClicked': - ui.close(); openLoginWindow(); break; case 'disableHmdPreview': diff --git a/scripts/system/controllers/controllerModules/farGrabEntity.js b/scripts/system/controllers/controllerModules/farGrabEntity.js new file mode 100644 index 0000000000..dab1aa97af --- /dev/null +++ b/scripts/system/controllers/controllerModules/farGrabEntity.js @@ -0,0 +1,497 @@ +"use strict"; + +// farGrabEntity.js +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + +/* jslint bitwise: true */ + +/* global Script, Controller, RIGHT_HAND, LEFT_HAND, Mat4, MyAvatar, Vec3, Quat, getEnabledModuleByName, makeRunningValues, + Entities, enableDispatcherModule, disableDispatcherModule, entityIsGrabbable, makeDispatcherModuleParameters, MSECS_PER_SEC, + HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC, + projectOntoEntityXYPlane, ContextOverlay, HMD, Picks, makeLaserLockInfo, makeLaserParams, AddressManager, + getEntityParents, Selection, DISPATCHER_HOVERING_LIST, unhighlightTargetEntity, Messages, findGroupParent, + worldPositionToRegistrationFrameMatrix, DISPATCHER_PROPERTIES +*/ + +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/controllers.js"); + +(function() { + var MARGIN = 25; + + function TargetObject(entityID, entityProps) { + this.entityID = entityID; + this.entityProps = entityProps; + this.targetEntityID = null; + this.targetEntityProps = null; + + this.getTargetEntity = function() { + var parentPropsLength = this.parentProps.length; + if (parentPropsLength !== 0) { + var targetEntity = { + id: this.parentProps[parentPropsLength - 1].id, + props: this.parentProps[parentPropsLength - 1]}; + this.targetEntityID = targetEntity.id; + this.targetEntityProps = targetEntity.props; + return targetEntity; + } + this.targetEntityID = this.entityID; + this.targetEntityProps = this.entityProps; + return { + id: this.entityID, + props: this.entityProps}; + }; + } + + function FarGrabEntity(hand) { + this.hand = hand; + this.grabbing = false; + this.targetEntityID = null; + this.targetObject = null; + this.previouslyUnhooked = {}; + this.potentialEntityWithContextOverlay = false; + this.entityWithContextOverlay = false; + this.contextOverlayTimer = false; + this.reticleMinX = MARGIN; + this.reticleMaxX = 0; + this.reticleMinY = MARGIN; + this.reticleMaxY = 0; + this.endedGrab = 0; + this.MIN_HAPTIC_PULSE_INTERVAL = 500; // ms + + var FAR_GRAB_JOINTS = [65527, 65528]; // FARGRAB_LEFTHAND_INDEX, FARGRAB_RIGHTHAND_INDEX + + var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand and object + var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position + var DISTANCE_HOLDING_UNITY_MASS = 1200; // The mass at which the distance holding action timeframe is unmodified + var DISTANCE_HOLDING_UNITY_DISTANCE = 6; // The distance at which the distance holding action timeframe is unmodified + + this.parameters = makeDispatcherModuleParameters( + 540, + this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], + [], + 100, + makeLaserParams(this.hand, false)); + + + this.handToController = function() { + return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; + }; + + this.distanceGrabTimescale = function(mass, distance) { + var timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME * mass / + DISTANCE_HOLDING_UNITY_MASS * distance / + DISTANCE_HOLDING_UNITY_DISTANCE; + if (timeScale < DISTANCE_HOLDING_ACTION_TIMEFRAME) { + timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME; + } + return timeScale; + }; + + this.getMass = function(dimensions, density) { + return (dimensions.x * dimensions.y * dimensions.z) * density; + }; + + this.startFarGrabEntity = function (controllerData, targetProps) { + var controllerLocation = controllerData.controllerLocations[this.hand]; + var worldControllerPosition = controllerLocation.position; + var worldControllerRotation = controllerLocation.orientation; + // transform the position into room space + var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); + var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); + + var now = Date.now(); + + // add the action and initialize some variables + this.currentObjectPosition = targetProps.position; + this.currentObjectRotation = targetProps.rotation; + this.currentObjectTime = now; + + this.grabRadius = this.grabbedDistance; + this.grabRadialVelocity = 0.0; + + // offset between controller vector at the grab radius and the entity position + var targetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); + targetPosition = Vec3.sum(targetPosition, worldControllerPosition); + this.offsetPosition = Vec3.subtract(this.currentObjectPosition, targetPosition); + + // compute a constant based on the initial conditions which we use below to exaggerate hand motion + // onto the held object + this.radiusScalar = Math.log(this.grabRadius + 1.0); + if (this.radiusScalar < 1.0) { + this.radiusScalar = 1.0; + } + + // compute the mass for the purpose of energy and how quickly to move object + this.mass = this.getMass(targetProps.dimensions, targetProps.density); + + // Debounce haptic pules. Can occur as near grab controller module vacillates between being ready or not due to + // changing positions and floating point rounding. + if (Date.now() - this.endedGrab > this.MIN_HAPTIC_PULSE_INTERVAL) { + Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); + } + + unhighlightTargetEntity(this.targetEntityID); + var message = { + hand: this.hand, + entityID: this.targetEntityID + }; + + Messages.sendLocalMessage('Hifi-unhighlight-entity', JSON.stringify(message)); + + var newTargetPosLocal = MyAvatar.worldToJointPoint(targetProps.position); + MyAvatar.setJointTranslation(FAR_GRAB_JOINTS[this.hand], newTargetPosLocal); + MyAvatar.setJointRotation(FAR_GRAB_JOINTS[this.hand], { x: 0, y: 0, z: 0, w: 1 }); + + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(targetProps.id, "startDistanceGrab", args); + + this.targetEntityID = targetProps.id; + + + if (this.grabID) { + MyAvatar.releaseGrab(this.grabID); + } + var farJointIndex = FAR_GRAB_JOINTS[this.hand]; + this.grabID = MyAvatar.grab(targetProps.id, farJointIndex, + Entities.worldToLocalPosition(targetProps.position, MyAvatar.SELF_ID, farJointIndex), + Entities.worldToLocalRotation(targetProps.rotation, MyAvatar.SELF_ID, farJointIndex)); + + Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + action: 'grab', + grabbedEntity: targetProps.id, + joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" + })); + this.grabbing = true; + + this.previousRoomControllerPosition = roomControllerPosition; + }; + + this.continueDistanceHolding = function(controllerData) { + var controllerLocation = controllerData.controllerLocations[this.hand]; + var worldControllerPosition = controllerLocation.position; + var worldControllerRotation = controllerLocation.orientation; + + // also transform the position into room space + var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix()); + var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition); + + var targetProps = Entities.getEntityProperties(this.targetEntityID, DISPATCHER_PROPERTIES); + var now = Date.now(); + var deltaObjectTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds + this.currentObjectTime = now; + + // the action was set up when this.distanceHolding was called. update the targets. + var radius = Vec3.distance(this.currentObjectPosition, worldControllerPosition) * + this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR; + if (radius < 1.0) { + radius = 1.0; + } + + var roomHandDelta = Vec3.subtract(roomControllerPosition, this.previousRoomControllerPosition); + var worldHandDelta = Mat4.transformVector(MyAvatar.getSensorToWorldMatrix(), roomHandDelta); + var handMoved = Vec3.multiply(worldHandDelta, radius); + this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, handMoved); + + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.targetEntityID, "continueDistanceGrab", args); + + // Update radialVelocity + var lastVelocity = Vec3.multiply(worldHandDelta, 1.0 / deltaObjectTime); + var delta = Vec3.normalize(Vec3.subtract(targetProps.position, worldControllerPosition)); + var newRadialVelocity = Vec3.dot(lastVelocity, delta); + + var VELOCITY_AVERAGING_TIME = 0.016; + var blendFactor = deltaObjectTime / VELOCITY_AVERAGING_TIME; + if (blendFactor < 0.0) { + blendFactor = 0.0; + } else if (blendFactor > 1.0) { + blendFactor = 1.0; + } + this.grabRadialVelocity = blendFactor * newRadialVelocity + (1.0 - blendFactor) * this.grabRadialVelocity; + + var RADIAL_GRAB_AMPLIFIER = 10.0; + if (Math.abs(this.grabRadialVelocity) > 0.0) { + this.grabRadius = this.grabRadius + (this.grabRadialVelocity * deltaObjectTime * + this.grabRadius * RADIAL_GRAB_AMPLIFIER); + } + + // don't let grabRadius go all the way to zero, because it can't come back from that + var MINIMUM_GRAB_RADIUS = 0.1; + if (this.grabRadius < MINIMUM_GRAB_RADIUS) { + this.grabRadius = MINIMUM_GRAB_RADIUS; + } + var newTargetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation)); + newTargetPosition = Vec3.sum(newTargetPosition, worldControllerPosition); + newTargetPosition = Vec3.sum(newTargetPosition, this.offsetPosition); + + // MyAvatar.setJointTranslation(FAR_GRAB_JOINTS[this.hand], MyAvatar.worldToJointPoint(newTargetPosition)); + + // var newTargetPosLocal = Mat4.transformPoint(MyAvatar.getSensorToWorldMatrix(), newTargetPosition); + var newTargetPosLocal = MyAvatar.worldToJointPoint(newTargetPosition); + MyAvatar.setJointTranslation(FAR_GRAB_JOINTS[this.hand], newTargetPosLocal); + MyAvatar.setJointRotation(FAR_GRAB_JOINTS[this.hand], { x: 0, y: 0, z: 0, w: 1 }); + + this.previousRoomControllerPosition = roomControllerPosition; + }; + + this.endFarGrabEntity = function (controllerData) { + if (this.grabID) { + MyAvatar.releaseGrab(this.grabID); + this.grabID = null; + } + + this.endedGrab = Date.now(); + + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.targetEntityID, "releaseGrab", args); + Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + action: 'release', + grabbedEntity: this.targetEntityID, + joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" + })); + unhighlightTargetEntity(this.targetEntityID); + this.grabbing = false; + this.targetEntityID = null; + this.potentialEntityWithContextOverlay = false; + MyAvatar.clearJointData(FAR_GRAB_JOINTS[this.hand]); + }; + + this.updateRecommendedArea = function() { + var dims = Controller.getViewportDimensions(); + this.reticleMaxX = dims.x - MARGIN; + this.reticleMaxY = dims.y - MARGIN; + }; + + this.calculateNewReticlePosition = function(intersection) { + this.updateRecommendedArea(); + var point2d = HMD.overlayFromWorldPoint(intersection); + point2d.x = Math.max(this.reticleMinX, Math.min(point2d.x, this.reticleMaxX)); + point2d.y = Math.max(this.reticleMinY, Math.min(point2d.y, this.reticleMaxY)); + return point2d; + }; + + this.notPointingAtEntity = function(controllerData) { + var intersection = controllerData.rayPicks[this.hand]; + var entityProperty = Entities.getEntityProperties(intersection.objectID, DISPATCHER_PROPERTIES); + var entityType = entityProperty.type; + var hudRayPick = controllerData.hudRayPicks[this.hand]; + var point2d = this.calculateNewReticlePosition(hudRayPick.intersection); + if ((intersection.type === Picks.INTERSECTED_ENTITY && entityType === "Web") || + intersection.type === Picks.INTERSECTED_OVERLAY || Window.isPointOnDesktopWindow(point2d)) { + return true; + } + return false; + }; + + this.destroyContextOverlay = function(controllerData) { + if (this.entityWithContextOverlay) { + ContextOverlay.destroyContextOverlay(this.entityWithContextOverlay); + this.entityWithContextOverlay = false; + this.potentialEntityWithContextOverlay = false; + } + }; + + this.targetIsNull = function() { + var properties = Entities.getEntityProperties(this.targetEntityID, DISPATCHER_PROPERTIES); + if (Object.keys(properties).length === 0 && this.distanceHolding) { + return true; + } + return false; + }; + + this.getTargetProps = function (controllerData) { + var targetEntity = controllerData.rayPicks[this.hand].objectID; + if (targetEntity) { + var gtProps = Entities.getEntityProperties(targetEntity, DISPATCHER_PROPERTIES); + if (entityIsGrabbable(gtProps)) { + // if we've attempted to grab a child, roll up to the root of the tree + var groupRootProps = findGroupParent(controllerData, gtProps); + if (entityIsGrabbable(groupRootProps)) { + return groupRootProps; + } + return gtProps; + } + } + return null; + }; + + this.isReady = function (controllerData) { + if (HMD.active) { + if (this.notPointingAtEntity(controllerData)) { + return makeRunningValues(false, [], []); + } + + this.distanceHolding = false; + + if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) { + return makeRunningValues(true, [], []); + } else { + this.destroyContextOverlay(); + } + } + return makeRunningValues(false, [], []); + }; + + this.run = function (controllerData) { + if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE || this.targetIsNull()) { + this.endFarGrabEntity(controllerData); + return makeRunningValues(false, [], []); + } + this.intersectionDistance = controllerData.rayPicks[this.hand].distance; + + // gather up the readiness of the near-grab modules + var nearGrabNames = [ + this.hand === RIGHT_HAND ? "RightScaleAvatar" : "LeftScaleAvatar", + this.hand === RIGHT_HAND ? "RightFarTriggerEntity" : "LeftFarTriggerEntity", + this.hand === RIGHT_HAND ? "RightNearGrabEntity" : "LeftNearGrabEntity" + ]; + if (!this.grabbing) { + nearGrabNames.push(this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay"); + nearGrabNames.push(this.hand === RIGHT_HAND ? "RightNearTabletHighlight" : "LeftNearTabletHighlight"); + } + + var nearGrabReadiness = []; + for (var i = 0; i < nearGrabNames.length; i++) { + var nearGrabModule = getEnabledModuleByName(nearGrabNames[i]); + var ready = nearGrabModule ? nearGrabModule.isReady(controllerData) : makeRunningValues(false, [], []); + nearGrabReadiness.push(ready); + } + + if (this.targetEntityID) { + // if we are doing a distance grab and the object gets close enough to the controller, + // stop the far-grab so the near-grab or equip can take over. + for (var k = 0; k < nearGrabReadiness.length; k++) { + if (nearGrabReadiness[k].active && (nearGrabReadiness[k].targets[0] === this.targetEntityID)) { + this.endFarGrabEntity(controllerData); + return makeRunningValues(false, [], []); + } + } + + this.continueDistanceHolding(controllerData); + } else { + // if we are doing a distance search and this controller moves into a position + // where it could near-grab something, stop searching. + for (var j = 0; j < nearGrabReadiness.length; j++) { + if (nearGrabReadiness[j].active) { + this.endFarGrabEntity(controllerData); + return makeRunningValues(false, [], []); + } + } + + var rayPickInfo = controllerData.rayPicks[this.hand]; + if (rayPickInfo.type === Picks.INTERSECTED_ENTITY) { + if (controllerData.triggerClicks[this.hand]) { + var entityID = rayPickInfo.objectID; + var targetProps = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES); + if (targetProps.href !== "") { + AddressManager.handleLookupString(targetProps.href); + return makeRunningValues(false, [], []); + } + + this.targetObject = new TargetObject(entityID, targetProps); + this.targetObject.parentProps = getEntityParents(targetProps); + + if (this.contextOverlayTimer) { + Script.clearTimeout(this.contextOverlayTimer); + } + this.contextOverlayTimer = false; + if (entityID === this.entityWithContextOverlay) { + this.destroyContextOverlay(); + } else { + Selection.removeFromSelectedItemsList("contextOverlayHighlightList", "entity", entityID); + } + + var targetEntity = this.targetObject.getTargetEntity(); + entityID = targetEntity.id; + targetProps = targetEntity.props; + + if (entityIsGrabbable(targetProps) || entityIsGrabbable(this.targetObject.entityProps)) { + + this.targetEntityID = entityID; + this.grabbedDistance = rayPickInfo.distance; + this.distanceHolding = true; + this.startFarGrabEntity(controllerData, targetProps); + } + } else if (!this.entityWithContextOverlay) { + var _this = this; + + if (_this.potentialEntityWithContextOverlay !== rayPickInfo.objectID) { + if (_this.contextOverlayTimer) { + Script.clearTimeout(_this.contextOverlayTimer); + } + _this.contextOverlayTimer = false; + _this.potentialEntityWithContextOverlay = rayPickInfo.objectID; + } + + if (!_this.contextOverlayTimer) { + _this.contextOverlayTimer = Script.setTimeout(function () { + if (!_this.entityWithContextOverlay && + _this.contextOverlayTimer && + _this.potentialEntityWithContextOverlay === rayPickInfo.objectID) { + var cotProps = Entities.getEntityProperties(rayPickInfo.objectID, + DISPATCHER_PROPERTIES); + var pointerEvent = { + type: "Move", + id: _this.hand + 1, // 0 is reserved for hardware mouse + pos2D: projectOntoEntityXYPlane(rayPickInfo.objectID, + rayPickInfo.intersection, cotProps), + pos3D: rayPickInfo.intersection, + normal: rayPickInfo.surfaceNormal, + direction: Vec3.subtract(ZERO_VEC, rayPickInfo.surfaceNormal), + button: "Secondary" + }; + if (ContextOverlay.createOrDestroyContextOverlay(rayPickInfo.objectID, pointerEvent)) { + _this.entityWithContextOverlay = rayPickInfo.objectID; + } + } + _this.contextOverlayTimer = false; + }, 500); + } + } + } + } + return this.exitIfDisabled(controllerData); + }; + + this.exitIfDisabled = function(controllerData) { + var moduleName = this.hand === RIGHT_HAND ? "RightDisableModules" : "LeftDisableModules"; + var disableModule = getEnabledModuleByName(moduleName); + if (disableModule) { + if (disableModule.disableModules) { + this.endFarGrabEntity(controllerData); + Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity); + this.highlightedEntity = null; + return makeRunningValues(false, [], []); + } + } + var grabbedThing = this.distanceHolding ? this.targetObject.entityID : null; + var offset = this.calculateOffset(controllerData); + var laserLockInfo = makeLaserLockInfo(grabbedThing, false, this.hand, offset); + return makeRunningValues(true, [], [], laserLockInfo); + }; + + this.calculateOffset = function(controllerData) { + if (this.distanceHolding) { + var targetProps = Entities.getEntityProperties(this.targetObject.entityID, + [ "position", "rotation", "registrationPoint", "dimensions" ]); + return worldPositionToRegistrationFrameMatrix(targetProps, controllerData.rayPicks[this.hand].intersection); + } + return undefined; + }; + } + + var leftFarGrabEntity = new FarGrabEntity(LEFT_HAND); + var rightFarGrabEntity = new FarGrabEntity(RIGHT_HAND); + + enableDispatcherModule("LeftFarGrabEntity", leftFarGrabEntity); + enableDispatcherModule("RightFarGrabEntity", rightFarGrabEntity); + + function cleanup() { + disableDispatcherModule("LeftFarGrabEntity"); + disableDispatcherModule("RightFarGrabEntity"); + } + Script.scriptEnding.connect(cleanup); +}()); diff --git a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js index bb563a269c..ddff35b9e7 100644 --- a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js @@ -8,10 +8,10 @@ /* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, getControllerJointIndex, getGrabbableData, enableDispatcherModule, disableDispatcherModule, propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, entityIsGrabbable, - Quat, Vec3, MSECS_PER_SEC, getControllerWorldLocation, makeDispatcherModuleParameters, makeRunningValues, + MSECS_PER_SEC, makeDispatcherModuleParameters, makeRunningValues, TRIGGER_OFF_VALUE, NEAR_GRAB_RADIUS, findGroupParent, entityIsCloneable, propsAreCloneDynamic, cloneEntity, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE, unhighlightTargetEntity, Uuid, - DISPATCHER_PROPERTIES + DISPATCHER_PROPERTIES, HMD */ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); @@ -65,25 +65,14 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); this.grabFollowsController = grabbableData.grabFollowsController; this.kinematicGrab = grabbableData.grabKinematic; - var handRotation; - var handPosition; - if (this.grabFollowsController) { - var controllerID = - (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - var controllerLocation = getControllerWorldLocation(controllerID, false); - handRotation = controllerLocation.orientation; - handPosition = controllerLocation.position; + var handJointIndex; + if (HMD.mounted && HMD.isHandControllerAvailable() && grabbableData.grabFollowsController) { + handJointIndex = getControllerJointIndex(this.hand); } else { - handRotation = this.getHandRotation(); - handPosition = this.getHandPosition(); + handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); } - - var objectRotation = targetProps.rotation; - this.offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation); - - var currentObjectPosition = targetProps.position; - var offset = Vec3.subtract(currentObjectPosition, handPosition); - this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset); + this.offsetPosition = Entities.worldToLocalPosition(targetProps.position, MyAvatar.SELF_ID, handJointIndex); + this.offsetRotation = Entities.worldToLocalRotation(targetProps.rotation, MyAvatar.SELF_ID, handJointIndex); var now = Date.now(); this.actionTimeout = now + (ACTION_TTL * MSECS_PER_SEC); diff --git a/scripts/system/controllers/controllerModules/nearGrabEntity.js b/scripts/system/controllers/controllerModules/nearGrabEntity.js new file mode 100644 index 0000000000..60a5781ca4 --- /dev/null +++ b/scripts/system/controllers/controllerModules/nearGrabEntity.js @@ -0,0 +1,225 @@ +"use strict"; + +// nearGrabEntity.js +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html + + +/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, getControllerJointIndex, enableDispatcherModule, + disableDispatcherModule, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, TRIGGER_OFF_VALUE, + makeDispatcherModuleParameters, entityIsGrabbable, makeRunningValues, NEAR_GRAB_RADIUS, findGroupParent, Vec3, cloneEntity, + entityIsCloneable, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE, distanceBetweenPointAndEntityBoundingBox, + getGrabbableData, getEnabledModuleByName, DISPATCHER_PROPERTIES, HMD, NEAR_GRAB_DISTANCE +*/ + +Script.include("/~/system/libraries/controllerDispatcherUtils.js"); +Script.include("/~/system/libraries/cloneEntityUtils.js"); +Script.include("/~/system/libraries/controllers.js"); + +(function() { + + function NearGrabEntity(hand) { + this.hand = hand; + this.targetEntityID = null; + this.grabbing = false; + this.cloneAllowed = true; + this.grabID = null; + + this.parameters = makeDispatcherModuleParameters( + 500, + this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], + [], + 100); + + this.startGrab = function (targetProps) { + if (this.grabID) { + MyAvatar.releaseGrab(this.grabID); + } + + var grabData = getGrabbableData(targetProps); + + var handJointIndex; + if (HMD.mounted && HMD.isHandControllerAvailable() && grabData.grabFollowsController) { + handJointIndex = getControllerJointIndex(this.hand); + } else { + handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"); + } + + this.targetEntityID = targetProps.id; + + var relativePosition = Entities.worldToLocalPosition(targetProps.position, MyAvatar.SELF_ID, handJointIndex); + var relativeRotation = Entities.worldToLocalRotation(targetProps.rotation, MyAvatar.SELF_ID, handJointIndex); + this.grabID = MyAvatar.grab(targetProps.id, handJointIndex, relativePosition, relativeRotation); + }; + + this.startNearGrabEntity = function (targetProps) { + Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand); + + this.startGrab(targetProps); + + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(targetProps.id, "startNearGrab", args); + + Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + action: 'grab', + grabbedEntity: targetProps.id, + joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" + })); + + this.grabbing = true; + }; + + this.endGrab = function () { + if (this.grabID) { + MyAvatar.releaseGrab(this.grabID); + this.grabID = null; + } + }; + + this.endNearGrabEntity = function () { + this.endGrab(); + + this.grabbing = false; + this.targetEntityID = null; + + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.targetEntityID, "releaseGrab", args); + Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({ + action: 'release', + grabbedEntity: this.targetEntityID, + joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand" + })); + }; + + this.getTargetProps = function (controllerData) { + // nearbyEntityProperties is already sorted by length from controller + var nearbyEntityProperties = controllerData.nearbyEntityProperties[this.hand]; + var sensorScaleFactor = MyAvatar.sensorToWorldScale; + var nearGrabDistance = NEAR_GRAB_DISTANCE * sensorScaleFactor; + var nearGrabRadius = NEAR_GRAB_RADIUS * sensorScaleFactor; + for (var i = 0; i < nearbyEntityProperties.length; i++) { + var props = nearbyEntityProperties[i]; + var grabPosition = controllerData.controllerLocations[this.hand].position; // Is offset from hand position. + var dist = distanceBetweenPointAndEntityBoundingBox(grabPosition, props); + var distance = Vec3.distance(grabPosition, props.position); + if ((dist > nearGrabDistance) || + (distance > nearGrabRadius)) { // Only smallish entities can be near grabbed. + continue; + } + if (entityIsGrabbable(props) || entityIsCloneable(props)) { + if (!entityIsCloneable(props)) { + // if we've attempted to grab a non-cloneable child, roll up to the root of the tree + var groupRootProps = findGroupParent(controllerData, props); + if (entityIsGrabbable(groupRootProps)) { + return groupRootProps; + } + } + return props; + } + } + return null; + }; + + this.isReady = function (controllerData, deltaTime) { + this.targetEntityID = null; + this.grabbing = false; + + if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE && + controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) { + this.cloneAllowed = true; + return makeRunningValues(false, [], []); + } + + var scaleModuleName = this.hand === RIGHT_HAND ? "RightScaleEntity" : "LeftScaleEntity"; + var scaleModule = getEnabledModuleByName(scaleModuleName); + if (scaleModule.grabbedThingID || scaleModule.isReady(controllerData).active) { + // we're rescaling -- don't start a grab. + return makeRunningValues(false, [], []); + } + + var targetProps = this.getTargetProps(controllerData); + if (targetProps) { + this.targetEntityID = targetProps.id; + return makeRunningValues(true, [this.targetEntityID], []); + } else { + return makeRunningValues(false, [], []); + } + }; + + this.run = function (controllerData, deltaTime) { + + if (this.grabbing) { + if (controllerData.triggerClicks[this.hand] < TRIGGER_OFF_VALUE && + controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) { + this.endNearGrabEntity(); + return makeRunningValues(false, [], []); + } + + var props = controllerData.nearbyEntityPropertiesByID[this.targetEntityID]; + if (!props) { + props = Entities.getEntityProperties(this.targetEntityID, DISPATCHER_PROPERTIES); + if (!props) { + // entity was deleted + this.grabbing = false; + this.targetEntityID = null; + return makeRunningValues(false, [], []); + } + } + + var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID]; + Entities.callEntityMethod(this.targetEntityID, "continueNearGrab", args); + } else { + // still searching + var readiness = this.isReady(controllerData); + if (!readiness.active) { + return readiness; + } + if (controllerData.triggerClicks[this.hand] || controllerData.secondaryValues[this.hand] > BUMPER_ON_VALUE) { + // switch to grab + var targetProps = this.getTargetProps(controllerData); + var targetCloneable = entityIsCloneable(targetProps); + + if (targetCloneable) { + if (this.cloneAllowed) { + var cloneID = cloneEntity(targetProps); + if (cloneID !== null) { + var cloneProps = Entities.getEntityProperties(cloneID, DISPATCHER_PROPERTIES); + cloneProps.id = cloneID; + this.grabbing = true; + this.targetEntityID = cloneID; + this.startNearGrabEntity(cloneProps); + this.cloneAllowed = false; // prevent another clone call until inputs released + } + } + } else if (targetProps) { + this.grabbing = true; + this.startNearGrabEntity(targetProps); + } + } + } + + return makeRunningValues(true, [this.targetEntityID], []); + }; + + this.cleanup = function () { + if (this.targetEntityID) { + this.endNearGrabEntity(); + } + }; + } + + var leftNearGrabEntity = new NearGrabEntity(LEFT_HAND); + var rightNearGrabEntity = new NearGrabEntity(RIGHT_HAND); + + enableDispatcherModule("LeftNearGrabEntity", leftNearGrabEntity); + enableDispatcherModule("RightNearGrabEntity", rightNearGrabEntity); + + function cleanup() { + leftNearGrabEntity.cleanup(); + rightNearGrabEntity.cleanup(); + disableDispatcherModule("LeftNearGrabEntity"); + disableDispatcherModule("RightNearGrabEntity"); + } + Script.scriptEnding.connect(cleanup); +}()); diff --git a/scripts/system/controllers/controllerModules/scaleEntity.js b/scripts/system/controllers/controllerModules/scaleEntity.js index 24c05a1394..50b6c5b853 100644 --- a/scripts/system/controllers/controllerModules/scaleEntity.js +++ b/scripts/system/controllers/controllerModules/scaleEntity.js @@ -7,7 +7,7 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/* global Script, Vec3, MyAvatar, Entities, RIGHT_HAND */ +/* global Script, Vec3, MyAvatar, Entities, RIGHT_HAND, entityIsGrabbable */ (function() { var dispatcherUtils = Script.require("/~/system/libraries/controllerDispatcherUtils.js"); @@ -62,14 +62,19 @@ var otherHandTargetProps = otherModule.getTargetProps(controllerData); if (thisHandTargetProps && otherHandTargetProps) { if (thisHandTargetProps.id === otherHandTargetProps.id) { + if (!entityIsGrabbable(thisHandTargetProps)) { + return dispatcherUtils.makeRunningValues(false, [], []); + } this.grabbedThingID = thisHandTargetProps.id; - this.scalingStartDistance = Vec3.length(Vec3.subtract(controllerData.controllerLocations[this.hand].position, - controllerData.controllerLocations[this.otherHand()].position)); + this.scalingStartDistance = + Vec3.length(Vec3.subtract(controllerData.controllerLocations[this.hand].position, + controllerData.controllerLocations[this.otherHand()].position)); this.scalingStartDimensions = thisHandTargetProps.dimensions; return dispatcherUtils.makeRunningValues(true, [], []); } } } + this.grabbedThingID = false; return dispatcherUtils.makeRunningValues(false, [], []); }; @@ -82,10 +87,11 @@ controllerData.controllerLocations[this.otherHand()].position)); var currentRescale = scalingCurrentDistance / this.scalingStartDistance; var newDimensions = Vec3.multiply(currentRescale, this.scalingStartDimensions); - Entities.editEntity(this.grabbedThingID, { dimensions: newDimensions }); + Entities.editEntity(this.grabbedThingID, { localDimensions: newDimensions }); } return dispatcherUtils.makeRunningValues(true, [], []); } + this.grabbedThingID = false; return dispatcherUtils.makeRunningValues(false, [], []); }; } diff --git a/scripts/system/controllers/controllerScripts.js b/scripts/system/controllers/controllerScripts.js index 83fa455519..2114f2c0b2 100644 --- a/scripts/system/controllers/controllerScripts.js +++ b/scripts/system/controllers/controllerScripts.js @@ -18,11 +18,7 @@ var CONTOLLER_SCRIPTS = [ "toggleAdvancedMovementForHandControllers.js", "handTouch.js", "controllerDispatcher.js", - "controllerModules/nearParentGrabEntity.js", "controllerModules/nearParentGrabOverlay.js", - "controllerModules/nearActionGrabEntity.js", - "controllerModules/farActionGrabEntityDynOnly.js", - "controllerModules/farParentGrabEntity.js", "controllerModules/stylusInput.js", "controllerModules/equipEntity.js", "controllerModules/nearTrigger.js", @@ -39,6 +35,16 @@ var CONTOLLER_SCRIPTS = [ "controllerModules/nearTabletHighlight.js" ]; +if (Settings.getValue("useTraitsGrab", true)) { + CONTOLLER_SCRIPTS.push("controllerModules/nearGrabEntity.js"); + CONTOLLER_SCRIPTS.push("controllerModules/farGrabEntity.js"); +} else { + CONTOLLER_SCRIPTS.push("controllerModules/nearParentGrabEntity.js"); + CONTOLLER_SCRIPTS.push("controllerModules/nearActionGrabEntity.js"); + CONTOLLER_SCRIPTS.push("controllerModules/farActionGrabEntityDynOnly.js"); + CONTOLLER_SCRIPTS.push("controllerModules/farParentGrabEntity.js"); +} + var DEBUG_MENU_ITEM = "Debug defaultScripts.js"; diff --git a/scripts/system/edit.js b/scripts/system/edit.js index f9ebe36025..84143f4c25 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -42,7 +42,6 @@ var TITLE_OFFSET = 60; var CREATE_TOOLS_WIDTH = 490; var MAX_DEFAULT_ENTITY_LIST_HEIGHT = 942; -var IMAGE_MODEL = "https://hifi-content.s3.amazonaws.com/DomainContent/production/default-image-model.fbx"; var DEFAULT_IMAGE = "https://hifi-content.s3.amazonaws.com/DomainContent/production/no-image.jpg"; var createToolsWindow = new CreateWindow( @@ -373,6 +372,7 @@ const DEFAULT_ENTITY_PROPERTIES = { blue: 179 }, }, + shapeType: "box", bloomMode: "inherit" }, Model: { @@ -398,8 +398,8 @@ const DEFAULT_ENTITY_PROPERTIES = { }, shapeType: "box", collisionless: true, - modelURL: IMAGE_MODEL, - textures: JSON.stringify({ "tex.picture": "" }) + keepAspectRatio: false, + imageURL: DEFAULT_IMAGE }, Web: { dimensions: { @@ -422,7 +422,6 @@ const DEFAULT_ENTITY_PROPERTIES = { emitterShouldTrail: true, particleRadius: 0.25, radiusStart: 0, - radiusFinish: 0.1, radiusSpread: 0, particleColor: { red: 255, @@ -436,7 +435,6 @@ const DEFAULT_ENTITY_PROPERTIES = { }, alpha: 0, alphaStart: 1, - alphaFinish: 0, alphaSpread: 0, emitAcceleration: { x: 0, @@ -449,12 +447,10 @@ const DEFAULT_ENTITY_PROPERTIES = { z: 0 }, particleSpin: 0, - spinStart: 0, - spinFinish: 0, spinSpread: 0, rotateWithEntity: false, polarStart: 0, - polarFinish: 0, + polarFinish: Math.PI, azimuthStart: -Math.PI, azimuthFinish: Math.PI }, @@ -495,9 +491,6 @@ var toolBar = (function () { var type = requestedProperties.type; if (type === "Box" || type === "Sphere") { applyProperties(properties, DEFAULT_ENTITY_PROPERTIES.Shape); - } else if (type === "Image") { - requestedProperties.type = "Model"; - applyProperties(properties, DEFAULT_ENTITY_PROPERTIES.Image); } else { applyProperties(properties, DEFAULT_ENTITY_PROPERTIES[type]); } @@ -515,7 +508,7 @@ var toolBar = (function () { } direction = Vec3.multiplyQbyV(direction, Vec3.UNIT_Z); - var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Web", "Material"]; + var PRE_ADJUST_ENTITY_TYPES = ["Box", "Sphere", "Shape", "Text", "Image", "Web", "Material"]; if (PRE_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) { // Adjust position of entity per bounding box prior to creating it. @@ -2483,6 +2476,15 @@ var PropertiesTool = function (opts) { tooltips: Script.require('./assets/data/createAppTooltips.json'), hmdActive: HMD.active, }); + } else if (data.type === "propertyRangeRequest") { + var propertyRanges = {}; + data.properties.forEach(function (property) { + propertyRanges[property] = Entities.getPropertyInfo(property); + }); + emitScriptEvent({ + type: 'propertyRangeReply', + propertyRanges: propertyRanges, + }); } }; diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index c5979c41e2..6a0e7c8343 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -956,12 +956,12 @@ div.refresh input[type="button"] { } .draggable-number .left-arrow { top: 3px; - left: 0px; + left: 0; transform: rotate(180deg); } .draggable-number .right-arrow { top: 3px; - right: 0px; + right: 0; } .draggable-number input[type=number] { position: absolute; @@ -995,6 +995,10 @@ div.refresh input[type="button"] { font-size: 15px; } +.rect .rect-row { + margin-bottom: 8px; +} + .row .property { width: auto; display: inline-block; @@ -1602,10 +1606,10 @@ input.rename-entity { margin-left: 4px; margin-right: 10px; } -.fstuple label.red, .fstuple label.x { +.fstuple label.red, .fstuple label.x, .fstuple label.w { color: #C62147; } -.fstuple label.green, .fstuple label.y { +.fstuple label.green, .fstuple label.y, .fstuple label.h { color: #359D85; } .fstuple label.blue, .fstuple label.z { diff --git a/scripts/system/html/entityList.html b/scripts/system/html/entityList.html index f9948bc8b0..f525c84cbc 100644 --- a/scripts/system/html/entityList.html +++ b/scripts/system/html/entityList.html @@ -19,6 +19,7 @@ <script type="text/javascript" src="js/listView.js"></script> <script type="text/javascript" src="js/entityListContextMenu.js"></script> <script type="text/javascript" src="js/utils.js"></script> + <script type="text/javascript" src="js/includes.js"></script> <script type="text/javascript" src="js/entityList.js"></script> </head> <body onload='loaded();' id="entity-list-body"> diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index a8bcabbfea..67f03a33a2 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -24,6 +24,7 @@ <script type="text/javascript" src="js/createAppTooltip.js"></script> <script type="text/javascript" src="js/draggableNumber.js"></script> <script type="text/javascript" src="js/utils.js"></script> + <script type="text/javascript" src="js/includes.js"></script> <script type="text/javascript" src="js/entityProperties.js"></script> <script src="js/jsoneditor.min.js"></script> </head> diff --git a/scripts/system/html/js/draggableNumber.js b/scripts/system/html/js/draggableNumber.js index 4e3a32b0f2..0d6af01ebd 100644 --- a/scripts/system/html/js/draggableNumber.js +++ b/scripts/system/html/js/draggableNumber.js @@ -106,7 +106,7 @@ DraggableNumber.prototype = { stepUp: function() { if (!this.isDisabled()) { - this.elInput.stepUp(); + this.elInput.value = parseFloat(this.elInput.value) + this.step; this.inputChange(); if (this.valueChangeFunction) { this.valueChangeFunction(); @@ -116,7 +116,7 @@ DraggableNumber.prototype = { stepDown: function() { if (!this.isDisabled()) { - this.elInput.stepDown(); + this.elInput.value = parseFloat(this.elInput.value) - this.step; this.inputChange(); if (this.valueChangeFunction) { this.valueChangeFunction(); @@ -139,7 +139,14 @@ DraggableNumber.prototype = { }, inputChange: function() { - this.setValue(this.elInput.value); + let value = this.elInput.value; + if (this.max !== undefined) { + value = Math.min(this.max, value); + } + if (this.min !== undefined) { + value = Math.max(this.min, value); + } + this.setValue(value); }, inputBlur: function(ev) { @@ -156,6 +163,17 @@ DraggableNumber.prototype = { return this.elText.getAttribute("disabled") === "disabled"; }, + updateMinMax: function(min, max) { + this.min = min; + this.max = max; + if (this.min !== undefined) { + this.elInput.setAttribute("min", this.min); + } + if (this.max !== undefined) { + this.elInput.setAttribute("max", this.max); + } + }, + initialize: function() { this.onMouseDown = this.mouseDown.bind(this); this.onMouseUp = this.mouseUp.bind(this); @@ -189,12 +207,7 @@ DraggableNumber.prototype = { this.elInput = document.createElement('input'); this.elInput.className = "input"; this.elInput.setAttribute("type", "number"); - if (this.min !== undefined) { - this.elInput.setAttribute("min", this.min); - } - if (this.max !== undefined) { - this.elInput.setAttribute("max", this.max); - } + this.updateMinMax(this.min, this.max); if (this.step !== undefined) { this.elInput.setAttribute("step", this.step); } diff --git a/scripts/system/html/js/entityList.js b/scripts/system/html/js/entityList.js index 89b56c7f7b..fdf0993235 100644 --- a/scripts/system/html/js/entityList.js +++ b/scripts/system/html/js/entityList.js @@ -11,7 +11,6 @@ const DESCENDING_SORT = -1; const ASCENDING_STRING = '▴'; const DESCENDING_STRING = '▾'; const BYTES_PER_MEGABYTE = 1024 * 1024; -const IMAGE_MODEL_NAME = 'default-image-model.fbx'; const COLLAPSE_EXTRA_INFO = "E"; const EXPAND_EXTRA_INFO = "D"; const FILTER_IN_VIEW_ATTRIBUTE = "pressed"; @@ -25,6 +24,19 @@ const DELTA_X_MOVE_COLUMNS_THRESHOLD = 2; const DELTA_X_COLUMN_SWAP_POSITION = 5; const CERTIFIED_PLACEHOLDER = "** Certified **"; +function decimalMegabytes(number) { + return number ? (number / BYTES_PER_MEGABYTE).toFixed(1) : ""; +} + +function displayIfNonZero(number) { + return number ? number : ""; +} + +function getFilename(url) { + let urlParts = url.split('/'); + return urlParts[urlParts.length - 1]; +} + const COLUMNS = { type: { columnHeader: "Type", @@ -80,6 +92,7 @@ const COLUMNS = { dropdownLabel: "Texture Size", propertyID: "texturesSize", initialWidth: 0.10, + format: decimalMegabytes }, hasTransparent: { columnHeader: "", @@ -140,22 +153,9 @@ const FILTER_TYPES = [ "PolyLine", "PolyVox", "Text", + "Grid", ]; -const ICON_FOR_TYPE = { - Shape: "n", - Model: "", - Image: "", - Light: "p", - Zone: "o", - Web: "q", - Material: "", - ParticleEffect: "", - PolyLine: "", - PolyVox: "", - Text: "l", -}; - const DOUBLE_CLICK_TIMEOUT = 300; // ms const RENAME_COOLDOWN = 400; // ms @@ -312,7 +312,7 @@ function loaded() { let elSpan = document.createElement('span'); elSpan.setAttribute("class", "typeIcon"); - elSpan.innerHTML = ICON_FOR_TYPE[type]; + elSpan.innerHTML = ENTITY_TYPE_ICON[type]; elLabel.insertBefore(elSpan, elLabel.childNodes[0]); @@ -606,19 +606,6 @@ function loaded() { })); } - function decimalMegabytes(number) { - return number ? (number / BYTES_PER_MEGABYTE).toFixed(1) : ""; - } - - function displayIfNonZero(number) { - return number ? number : ""; - } - - function getFilename(url) { - let urlParts = url.split('/'); - return urlParts[urlParts.length - 1]; - } - function updateEntityData(entityData) { entities = []; entitiesByID = {}; @@ -628,9 +615,6 @@ function loaded() { entityData.forEach(function(entity) { let type = entity.type; let filename = getFilename(entity.url); - if (filename === IMAGE_MODEL_NAME) { - type = "Image"; - } let entityData = { id: entity.id, @@ -643,7 +627,7 @@ function loaded() { certificateID: entity.certificateID, verticesCount: displayIfNonZero(entity.verticesCount), texturesCount: displayIfNonZero(entity.texturesCount), - texturesSize: decimalMegabytes(entity.texturesSize), + texturesSize: entity.texturesSize, hasTransparent: entity.hasTransparent, isBaked: entity.isBaked, drawCalls: displayIfNonZero(entity.drawCalls), @@ -878,7 +862,11 @@ function loaded() { if (column.data.glyph) { elCell.innerHTML = itemData[column.data.propertyID] ? column.data.columnHeader : null; } else { - elCell.innerHTML = itemData[column.data.propertyID]; + let value = itemData[column.data.propertyID]; + if (column.data.format) { + value = column.data.format(value); + } + elCell.innerHTML = value; } elCell.style = "min-width:" + column.widthPx + "px;" + "max-width:" + column.widthPx + "px;"; elCell.className = createColumnClassName(column.columnID); diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 4e4e73df92..8b7264eeb1 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -9,23 +9,6 @@ /* global alert, augmentSpinButtons, clearTimeout, console, document, Element, EventBridge, JSONEditor, openEventBridge, setTimeout, window, _ $ */ - -const ICON_FOR_TYPE = { - Box: "V", - Sphere: "n", - Shape: "n", - ParticleEffect: "", - Model: "", - Web: "q", - Image: "", - Text: "l", - Light: "p", - Zone: "o", - PolyVox: "", - Multiple: "", - PolyLine: "", - Material: "" -}; const DEGREES_TO_RADIANS = Math.PI / 180.0; @@ -44,7 +27,7 @@ const GROUPS = [ { label: NO_SELECTION, type: "icon", - icons: ICON_FOR_TYPE, + icons: ENTITY_TYPE_ICON, propertyID: "type", replaceID: "placeholder-property-type", }, @@ -75,7 +58,7 @@ const GROUPS = [ }, { label: "Parent Joint Index", - type: "number-draggable", + type: "number", propertyID: "parentJointIndex", }, { @@ -128,24 +111,72 @@ const GROUPS = [ type: "color", propertyID: "textColor", }, + { + label: "Text Alpha", + type: "number-draggable", + min: 0, + max: 1, + step: 0.01, + decimals: 2, + propertyID: "textAlpha", + }, { label: "Background Color", type: "color", propertyID: "backgroundColor", }, + { + label: "Background Alpha", + type: "number-draggable", + min: 0, + max: 1, + step: 0.01, + decimals: 2, + propertyID: "backgroundAlpha", + }, { label: "Line Height", type: "number-draggable", min: 0, - step: 0.005, + step: 0.001, decimals: 4, unit: "m", - propertyID: "lineHeight" + propertyID: "lineHeight", }, { - label: "Face Camera", - type: "bool", - propertyID: "faceCamera" + label: "Billboard Mode", + type: "dropdown", + options: { none: "None", yaw: "Yaw", full: "Full"}, + propertyID: "textBillboardMode", + propertyName: "billboardMode", // actual entity property name + }, + { + label: "Top Margin", + type: "number-draggable", + step: 0.01, + decimals: 2, + propertyID: "topMargin", + }, + { + label: "Right Margin", + type: "number-draggable", + step: 0.01, + decimals: 2, + propertyID: "rightMargin", + }, + { + label: "Bottom Margin", + type: "number-draggable", + step: 0.01, + decimals: 2, + propertyID: "bottomMargin", + }, + { + label: "Left Margin", + type: "number-draggable", + step: 0.01, + decimals: 2, + propertyID: "leftMargin", }, ] }, @@ -185,8 +216,8 @@ const GROUPS = [ label: "Light Intensity", type: "number-draggable", min: 0, - max: 10, - step: 0.1, + max: 40, + step: 0.01, decimals: 2, propertyID: "keyLight.intensity", showPropertyRule: { "keyLightMode": "enabled" }, @@ -194,6 +225,7 @@ const GROUPS = [ { label: "Light Horizontal Angle", type: "number-draggable", + step: 0.1, multiplier: DEGREES_TO_RADIANS, decimals: 2, unit: "deg", @@ -203,6 +235,7 @@ const GROUPS = [ { label: "Light Vertical Angle", type: "number-draggable", + step: 0.1, multiplier: DEGREES_TO_RADIANS, decimals: 2, unit: "deg", @@ -243,7 +276,7 @@ const GROUPS = [ label: "Ambient Intensity", type: "number-draggable", min: 0, - max: 10, + max: 200, step: 0.1, decimals: 2, propertyID: "ambientLight.ambientIntensity", @@ -271,9 +304,9 @@ const GROUPS = [ { label: "Range", type: "number-draggable", - min: 5, + min: 1, max: 10000, - step: 5, + step: 1, decimals: 0, unit: "m", propertyID: "haze.hazeRange", @@ -290,7 +323,7 @@ const GROUPS = [ type: "number-draggable", min: -1000, max: 1000, - step: 10, + step: 1, decimals: 0, unit: "m", propertyID: "haze.hazeBaseRef", @@ -301,7 +334,7 @@ const GROUPS = [ type: "number-draggable", min: -1000, max: 5000, - step: 10, + step: 1, decimals: 0, unit: "m", propertyID: "haze.hazeCeiling", @@ -318,8 +351,8 @@ const GROUPS = [ type: "number-draggable", min: 0, max: 1, - step: 0.01, - decimals: 2, + step: 0.001, + decimals: 3, propertyID: "haze.hazeBackgroundBlend", showPropertyRule: { "hazeMode": "enabled" }, }, @@ -356,8 +389,8 @@ const GROUPS = [ type: "number-draggable", min: 0, max: 1, - step: 0.01, - decimals: 2, + step: 0.001, + decimals: 3, propertyID: "bloom.bloomIntensity", showPropertyRule: { "bloomMode": "enabled" }, }, @@ -366,8 +399,8 @@ const GROUPS = [ type: "number-draggable", min: 0, max: 1, - step: 0.01, - decimals: 2, + step: 0.001, + decimals: 3, propertyID: "bloom.bloomThreshold", showPropertyRule: { "bloomMode": "enabled" }, }, @@ -376,8 +409,8 @@ const GROUPS = [ type: "number-draggable", min: 0, max: 2, - step: 0.01, - decimals: 2, + step: 0.001, + decimals: 3, propertyID: "bloom.bloomSize", showPropertyRule: { "bloomMode": "enabled" }, }, @@ -476,7 +509,38 @@ const GROUPS = [ label: "Image", type: "string", placeholder: "URL", - propertyID: "image", + propertyID: "imageURL", + }, + { + label: "Color", + type: "color", + propertyID: "imageColor", + propertyName: "color", // actual entity property name + }, + { + label: "Emissive", + type: "bool", + propertyID: "emissive", + }, + { + label: "Sub Image", + type: "rect", + min: 0, + step: 1, + subLabels: [ "x", "y", "w", "h" ], + propertyID: "subImage", + }, + { + label: "Billboard Mode", + type: "dropdown", + options: { none: "None", yaw: "Yaw", full: "Full"}, + propertyID: "imageBillboardMode", + propertyName: "billboardMode", // actual entity property name + }, + { + label: "Keep Aspect Ratio", + type: "bool", + propertyID: "keepAspectRatio", }, ] }, @@ -510,16 +574,18 @@ const GROUPS = [ label: "Intensity", type: "number-draggable", min: 0, + max: 10000, step: 0.1, - decimals: 1, + decimals: 2, propertyID: "intensity", }, { label: "Fall-Off Radius", type: "number-draggable", min: 0, + max: 10000, step: 0.1, - decimals: 1, + decimals: 2, unit: "m", propertyID: "falloffRadius", }, @@ -531,6 +597,7 @@ const GROUPS = [ { label: "Spotlight Exponent", type: "number-draggable", + min: 0, step: 0.01, decimals: 2, propertyID: "exponent", @@ -630,6 +697,39 @@ const GROUPS = [ }, ] }, + { + id: "grid", + addToGroup: "base", + properties: [ + { + label: "Color", + type: "color", + propertyID: "gridColor", + propertyName: "color", // actual entity property name + }, + { + label: "Follow Camera", + type: "bool", + propertyID: "followCamera", + }, + { + label: "Major Grid Every", + type: "number-draggable", + min: 0, + step: 1, + decimals: 0, + propertyID: "majorGridEvery", + }, + { + label: "Minor Grid Every", + type: "number-draggable", + min: 0, + step: 0.01, + decimals: 2, + propertyID: "minorGridEvery", + }, + ] + }, { id: "particles", addToGroup: "base", @@ -643,8 +743,6 @@ const GROUPS = [ label: "Lifespan", type: "number-draggable", unit: "s", - min: 0.01, - max: 10, step: 0.01, decimals: 2, propertyID: "lifespan", @@ -652,8 +750,6 @@ const GROUPS = [ { label: "Max Particles", type: "number-draggable", - min: 1, - max: 10000, step: 1, propertyID: "maxParticles", }, @@ -673,26 +769,20 @@ const GROUPS = [ { label: "Emit Rate", type: "number-draggable", - min: 1, - max: 1000, step: 1, propertyID: "emitRate", }, { label: "Emit Speed", type: "number-draggable", - min: 0, - max: 5, - step: 0.01, + step: 0.1, decimals: 2, propertyID: "emitSpeed", }, { label: "Speed Spread", type: "number-draggable", - min: 0, - max: 5, - step: 0.01, + step: 0.1, decimals: 2, propertyID: "speedSpread", }, @@ -700,7 +790,6 @@ const GROUPS = [ label: "Emit Dimensions", type: "vec3", vec3Type: "xyz", - min: 0, step: 0.01, round: 100, subLabels: [ "x", "y", "z" ], @@ -709,10 +798,8 @@ const GROUPS = [ { label: "Emit Radius Start", type: "number-draggable", - min: 0, - max: 1, - step: 0.01, - decimals: 2, + step: 0.001, + decimals: 3, propertyID: "emitRadiusStart" }, { @@ -745,8 +832,6 @@ const GROUPS = [ { label: "Start", type: "number-draggable", - min: 0, - max: 4, step: 0.01, decimals: 2, propertyID: "radiusStart", @@ -755,8 +840,6 @@ const GROUPS = [ { label: "Middle", type: "number-draggable", - min: 0, - max: 4, step: 0.01, decimals: 2, propertyID: "particleRadius", @@ -764,8 +847,6 @@ const GROUPS = [ { label: "Finish", type: "number-draggable", - min: 0, - max: 4, step: 0.01, decimals: 2, propertyID: "radiusFinish", @@ -776,8 +857,6 @@ const GROUPS = [ { label: "Size Spread", type: "number-draggable", - min: 0, - max: 4, step: 0.01, decimals: 2, propertyID: "radiusSpread", @@ -834,29 +913,23 @@ const GROUPS = [ { label: "Start", type: "number-draggable", - min: 0, - max: 1, - step: 0.01, - decimals: 2, + step: 0.001, + decimals: 3, propertyID: "alphaStart", fallbackProperty: "alpha", }, { label: "Middle", type: "number-draggable", - min: 0, - max: 1, - step: 0.01, - decimals: 2, + step: 0.001, + decimals: 3, propertyID: "alpha", }, { label: "Finish", type: "number-draggable", - min: 0, - max: 1, - step: 0.01, - decimals: 2, + step: 0.001, + decimals: 3, propertyID: "alphaFinish", fallbackProperty: "alpha", }, @@ -865,10 +938,8 @@ const GROUPS = [ { label: "Alpha Spread", type: "number-draggable", - min: 0, - max: 1, - step: 0.01, - decimals: 2, + step: 0.001, + decimals: 3, propertyID: "alphaSpread", }, ] @@ -911,10 +982,8 @@ const GROUPS = [ { label: "Start", type: "number-draggable", - min: -360, - max: 360, - step: 1, - decimals: 0, + step: 0.1, + decimals: 2, multiplier: DEGREES_TO_RADIANS, unit: "deg", propertyID: "spinStart", @@ -923,10 +992,8 @@ const GROUPS = [ { label: "Middle", type: "number-draggable", - min: -360, - max: 360, - step: 1, - decimals: 0, + step: 0.1, + decimals: 2, multiplier: DEGREES_TO_RADIANS, unit: "deg", propertyID: "particleSpin", @@ -934,10 +1001,8 @@ const GROUPS = [ { label: "Finish", type: "number-draggable", - min: -360, - max: 360, - step: 1, - decimals: 0, + step: 0.1, + decimals: 2, multiplier: DEGREES_TO_RADIANS, unit: "deg", propertyID: "spinFinish", @@ -948,10 +1013,8 @@ const GROUPS = [ { label: "Spin Spread", type: "number-draggable", - min: 0, - max: 360, - step: 1, - decimals: 0, + step: 0.1, + decimals: 2, multiplier: DEGREES_TO_RADIANS, unit: "deg", propertyID: "spinSpread", @@ -976,10 +1039,8 @@ const GROUPS = [ { label: "Start", type: "number-draggable", - min: 0, - max: 180, - step: 1, - decimals: 0, + step: 0.1, + decimals: 2, multiplier: DEGREES_TO_RADIANS, unit: "deg", propertyID: "polarStart", @@ -987,10 +1048,8 @@ const GROUPS = [ { label: "Finish", type: "number-draggable", - min: 0, - max: 180, - step: 1, - decimals: 0, + step: 0.1, + decimals: 2, multiplier: DEGREES_TO_RADIANS, unit: "deg", propertyID: "polarFinish", @@ -1005,10 +1064,8 @@ const GROUPS = [ { label: "Start", type: "number-draggable", - min: -180, - max: 180, - step: 1, - decimals: 0, + step: 0.1, + decimals: 2, multiplier: DEGREES_TO_RADIANS, unit: "deg", propertyID: "azimuthStart", @@ -1016,10 +1073,8 @@ const GROUPS = [ { label: "Finish", type: "number-draggable", - min: -180, - max: 180, - step: 1, - decimals: 0, + step: 0.1, + decimals: 2, multiplier: DEGREES_TO_RADIANS, unit: "deg", propertyID: "azimuthFinish", @@ -1036,6 +1091,7 @@ const GROUPS = [ label: "Position", type: "vec3", vec3Type: "xyz", + step: 0.1, decimals: 4, subLabels: [ "x", "y", "z" ], unit: "m", @@ -1046,6 +1102,7 @@ const GROUPS = [ label: "Local Position", type: "vec3", vec3Type: "xyz", + step: 0.1, decimals: 4, subLabels: [ "x", "y", "z" ], unit: "m", @@ -1078,8 +1135,7 @@ const GROUPS = [ label: "Dimensions", type: "vec3", vec3Type: "xyz", - min: 0, - step: 0.1, + step: 0.01, decimals: 4, subLabels: [ "x", "y", "z" ], unit: "m", @@ -1090,8 +1146,7 @@ const GROUPS = [ label: "Local Dimensions", type: "vec3", vec3Type: "xyz", - min: 0, - step: 0.1, + step: 0.01, decimals: 4, subLabels: [ "x", "y", "z" ], unit: "m", @@ -1111,7 +1166,7 @@ const GROUPS = [ label: "Pivot", type: "vec3", vec3Type: "xyz", - step: 0.1, + step: 0.001, decimals: 4, subLabels: [ "x", "y", "z" ], unit: "(ratio of dimension)", @@ -1143,6 +1198,7 @@ const GROUPS = [ { label: "Clone Lifetime", type: "number-draggable", + min: -1, unit: "s", propertyID: "cloneLifetime", showPropertyRule: { "cloneable": "true" }, @@ -1150,6 +1206,7 @@ const GROUPS = [ { label: "Clone Limit", type: "number-draggable", + min: 0, propertyID: "cloneLimit", showPropertyRule: { "cloneable": "true" }, }, @@ -1288,6 +1345,24 @@ const GROUPS = [ }, ] }, + { + id: "zone_shape", + label: "ZONE SHAPE", + properties: [ + { + label: "Shape Type", + type: "dropdown", + options: { "box": "Box", "sphere": "Sphere", "ellipsoid": "Ellipsoid", + "cylinder-y": "Cylinder", "compound": "Use Compound Shape URL" }, + propertyID: "shapeType", + }, + { + label: "Compound Shape URL", + type: "string", + propertyID: "compoundShapeURL", + }, + ] + }, { id: "physics", label: "PHYSICS", @@ -1296,7 +1371,7 @@ const GROUPS = [ label: "Linear Velocity", type: "vec3", vec3Type: "xyz", - step: 0.1, + step: 0.01, decimals: 4, subLabels: [ "x", "y", "z" ], unit: "m/s", @@ -1307,8 +1382,8 @@ const GROUPS = [ type: "number-draggable", min: 0, max: 1, - step: 0.01, - decimals: 2, + step: 0.001, + decimals: 4, propertyID: "damping", }, { @@ -1326,33 +1401,27 @@ const GROUPS = [ type: "number-draggable", min: 0, max: 1, - step: 0.01, + step: 0.001, decimals: 4, propertyID: "angularDamping", }, { label: "Bounciness", type: "number-draggable", - min: 0, - max: 1, - step: 0.01, + step: 0.001, decimals: 4, propertyID: "restitution", }, { label: "Friction", type: "number-draggable", - min: 0, - max: 10, - step: 0.1, + step: 0.01, decimals: 4, propertyID: "friction", }, { label: "Density", type: "number-draggable", - min: 100, - max: 10000, step: 1, decimals: 4, propertyID: "density", @@ -1372,6 +1441,7 @@ const GROUPS = [ type: "vec3", vec3Type: "xyz", subLabels: [ "x", "y", "z" ], + step: 0.1, decimals: 4, unit: "m/s<sup>2</sup>", propertyID: "acceleration", @@ -1384,7 +1454,7 @@ const GROUPS_PER_TYPE = { None: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ], Shape: [ 'base', 'shape', 'spatial', 'behavior', 'collision', 'physics' ], Text: [ 'base', 'text', 'spatial', 'behavior', 'collision', 'physics' ], - Zone: [ 'base', 'zone', 'spatial', 'behavior', 'collision', 'physics' ], + Zone: [ 'base', 'zone', 'spatial', 'behavior', 'zone_shape', 'physics' ], Model: [ 'base', 'model', 'spatial', 'behavior', 'collision', 'physics' ], Image: [ 'base', 'image', 'spatial', 'behavior', 'collision', 'physics' ], Web: [ 'base', 'web', 'spatial', 'behavior', 'collision', 'physics' ], @@ -1394,6 +1464,7 @@ const GROUPS_PER_TYPE = { 'particles_acceleration', 'particles_spin', 'particles_constraints', 'spatial', 'behavior', 'physics' ], PolyLine: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ], PolyVox: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ], + Grid: [ 'base', 'grid', 'spatial', 'behavior', 'physics' ], Multiple: [ 'base', 'spatial', 'behavior', 'collision', 'physics' ], }; @@ -1424,6 +1495,13 @@ const PROPERTY_NAME_DIVISION = { SUBPROPERTY: 2, }; +const RECT_ELEMENTS = { + X_NUMBER: 0, + Y_NUMBER: 1, + WIDTH_NUMBER: 2, + HEIGHT_NUMBER: 3, +}; + const VECTOR_ELEMENTS = { X_NUMBER: 0, Y_NUMBER: 1, @@ -1446,6 +1524,7 @@ const JSON_EDITOR_ROW_DIV_INDEX = 2; let elGroups = {}; let properties = {}; +let propertyRangeRequests = []; let colorPickers = {}; let particlePropertyUpdates = {}; let selectedEntityProperties; @@ -1475,6 +1554,13 @@ function getPropertyInputElement(propertyID) { return property.elInput; case 'number-draggable': return property.elNumber.elInput; + case 'rect': + return { + x: property.elNumberX.elInput, + y: property.elNumberY.elInput, + width: property.elNumberWidth.elInput, + height: property.elNumberHeight.elInput + }; case 'vec3': case 'vec2': return { x: property.elNumberX.elInput, y: property.elNumberY.elInput, z: property.elNumberZ.elInput }; @@ -1564,6 +1650,13 @@ function resetProperties() { } break; } + case 'rect': { + property.elNumberX.setValue(""); + property.elNumberY.setValue(""); + property.elNumberWidth.setValue(""); + property.elNumberHeight.setValue(""); + break; + } case 'vec3': case 'vec2': { property.elNumberX.setValue(""); @@ -1748,7 +1841,7 @@ function createDragStartFunction(property) { function createDragEndFunction(property) { return function() { property.dragging = false; - // send an additonal update post-dragging to consider whole property change from dragStart to dragEnd to be 1 action + // send an additional update post-dragging to consider whole property change from dragStart to dragEnd to be 1 action this.valueChangeFunction(); }; } @@ -1793,6 +1886,18 @@ function createEmitVec3PropertyUpdateFunction(property) { }; } +function createEmitRectPropertyUpdateFunction(property) { + return function() { + let newValue = { + x: property.elNumberX.elInput.value, + y: property.elNumberY.elInput.value, + width: property.elNumberWidth.elInput.value, + height: property.elNumberHeight.elInput.value, + }; + updateProperty(property.name, newValue, property.isParticleProperty); + }; +} + function createEmitColorPropertyUpdateFunction(property) { return function() { emitColorPropertyUpdate(property.name, property.elNumberR.elInput.value, property.elNumberG.elInput.value, @@ -1821,14 +1926,6 @@ function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElemen updateProperty(propertyName, propertyValue, isParticleProperty); } -function createImageURLUpdateFunction(property) { - return function () { - let newTextures = JSON.stringify({ "tex.picture": this.value }); - updateProperty(property.name, newTextures, property.isParticleProperty); - }; -} - - /** * PROPERTY ELEMENT CREATION FUNCTIONS */ @@ -1930,6 +2027,18 @@ function createNumberProperty(property, elProperty) { return elInput; } +function updateNumberMinMax(property) { + let elInput = property.elInput; + let min = property.data.min; + let max = property.data.max; + if (min !== undefined) { + elInput.setAttribute("min", min); + } + if (max !== undefined) { + elInput.setAttribute("max", max); + } +} + function createNumberDraggableProperty(property, elProperty) { let elementID = property.elementID; let propertyData = property.data; @@ -1959,6 +2068,58 @@ function createNumberDraggableProperty(property, elProperty) { return elDraggableNumber; } +function updateNumberDraggableMinMax(property) { + let propertyData = property.data; + property.elNumber.updateMinMax(propertyData.min, propertyData.max); +} + +function createRectProperty(property, elProperty) { + let propertyData = property.data; + + elProperty.className = "rect"; + + let elXYRow = document.createElement('div'); + elXYRow.className = "rect-row fstuple"; + elProperty.appendChild(elXYRow); + + let elWidthHeightRow = document.createElement('div'); + elWidthHeightRow.className = "rect-row fstuple"; + elProperty.appendChild(elWidthHeightRow); + + + let elNumberX = createTupleNumberInput(property, propertyData.subLabels[RECT_ELEMENTS.X_NUMBER]); + let elNumberY = createTupleNumberInput(property, propertyData.subLabels[RECT_ELEMENTS.Y_NUMBER]); + let elNumberWidth = createTupleNumberInput(property, propertyData.subLabels[RECT_ELEMENTS.WIDTH_NUMBER]); + let elNumberHeight = createTupleNumberInput(property, propertyData.subLabels[RECT_ELEMENTS.HEIGHT_NUMBER]); + + elXYRow.appendChild(elNumberX.elDiv); + elXYRow.appendChild(elNumberY.elDiv); + elWidthHeightRow.appendChild(elNumberWidth.elDiv); + elWidthHeightRow.appendChild(elNumberHeight.elDiv); + + let valueChangeFunction = createEmitRectPropertyUpdateFunction(property); + elNumberX.setValueChangeFunction(valueChangeFunction); + elNumberY.setValueChangeFunction(valueChangeFunction); + elNumberWidth.setValueChangeFunction(valueChangeFunction); + elNumberHeight.setValueChangeFunction(valueChangeFunction); + + let elResult = []; + elResult[RECT_ELEMENTS.X_NUMBER] = elNumberX; + elResult[RECT_ELEMENTS.Y_NUMBER] = elNumberY; + elResult[RECT_ELEMENTS.WIDTH_NUMBER] = elNumberWidth; + elResult[RECT_ELEMENTS.HEIGHT_NUMBER] = elNumberHeight; + return elResult; +} + +function updateRectMinMax(property) { + let min = property.data.min; + let max = property.data.max; + property.elNumberX.updateMinMax(min, max); + property.elNumberY.updateMinMax(min, max); + property.elNumberWidth.updateMinMax(min, max); + property.elNumberHeight.updateMinMax(min, max); +} + function createVec3Property(property, elProperty) { let propertyData = property.data; @@ -2008,6 +2169,16 @@ function createVec2Property(property, elProperty) { return elResult; } +function updateVectorMinMax(property) { + let min = property.data.min; + let max = property.data.max; + property.elNumberX.updateMinMax(min, max); + property.elNumberY.updateMinMax(min, max); + if (property.elNumberZ) { + property.elNumberZ.updateMinMax(min, max); + } +} + function createColorProperty(property, elProperty) { let propertyName = property.name; let elementID = property.elementID; @@ -2055,7 +2226,6 @@ function createColorProperty(property, elProperty) { submit: false, // We don't want to have a submission button onShow: function(colpick) { console.log("Showing"); - $(colorPickerID).attr('active', 'true'); // The original color preview within the picker needs to be updated on show because // prior to the picker being shown we don't have access to the selections' starting color. colorPickers[colorPickerID].colpickSetColor({ @@ -2063,13 +2233,18 @@ function createColorProperty(property, elProperty) { "g": elNumberG.elInput.value, "b": elNumberB.elInput.value }); + + // Set the color picker active after setting the color, otherwise an update will be sent on open. + $(colorPickerID).attr('active', 'true'); }, onHide: function(colpick) { $(colorPickerID).attr('active', 'false'); }, onChange: function(hsb, hex, rgb, el) { $(el).css('background-color', '#' + hex); - emitColorPropertyUpdate(propertyName, rgb.r, rgb.g, rgb.b); + if ($(colorPickerID).attr('active') === 'true') { + emitColorPropertyUpdate(propertyName, rgb.r, rgb.g, rgb.b); + } } }); @@ -2281,6 +2456,14 @@ function createProperty(propertyData, propertyElementID, propertyName, propertyI property.elNumber = createNumberDraggableProperty(property, elProperty); break; } + case 'rect': { + let elRect = createRectProperty(property, elProperty); + property.elNumberX = elRect[RECT_ELEMENTS.X_NUMBER]; + property.elNumberY = elRect[RECT_ELEMENTS.Y_NUMBER]; + property.elNumberWidth = elRect[RECT_ELEMENTS.WIDTH_NUMBER]; + property.elNumberHeight = elRect[RECT_ELEMENTS.HEIGHT_NUMBER]; + break; + } case 'vec3': { let elVec3 = createVec3Property(property, elProperty); property.elNumberX = elVec3[VECTOR_ELEMENTS.X_NUMBER]; @@ -2994,6 +3177,9 @@ function loaded() { if (property.type !== 'placeholder') { properties[propertyID] = property; } + if (innerPropertyData.type === 'number' || innerPropertyData.type === 'number-draggable') { + propertyRangeRequests.push(propertyID); + } } } else { let property = createProperty(propertyData, propertyElementID, propertyName, propertyID, elProperty); @@ -3003,6 +3189,10 @@ function loaded() { if (property.type !== 'placeholder') { properties[propertyID] = property; + } + if (propertyData.type === 'number' || propertyData.type === 'number-draggable' || + propertyData.type === 'vec2' || propertyData.type === 'vec3' || propertyData.type === 'rect') { + propertyRangeRequests.push(propertyID); } let showPropertyRule = propertyData.showPropertyRule; @@ -3132,17 +3322,6 @@ function loaded() { // the event bridge and json parsing handle our avatar id string differently. lastEntityID = '"' + selectedEntityProperties.id + '"'; - // HTML workaround since image is not yet a separate entity type - let IMAGE_MODEL_NAME = 'default-image-model.fbx'; - if (selectedEntityProperties.type === "Model") { - let urlParts = selectedEntityProperties.modelURL.split('/'); - let propsFilename = urlParts[urlParts.length - 1]; - - if (propsFilename === IMAGE_MODEL_NAME) { - selectedEntityProperties.type = "Image"; - } - } - showGroupsForType(selectedEntityProperties.type); if (selectedEntityProperties.locked) { @@ -3179,6 +3358,7 @@ function loaded() { case 'number-draggable': isPropertyNotNumber = isNaN(propertyValue) || propertyValue === null; break; + case 'rect': case 'vec3': case 'vec2': isPropertyNotNumber = isNaN(propertyValue.x) || propertyValue.x === null; @@ -3221,6 +3401,12 @@ function loaded() { property.elNumber.setValue(value); break; } + case 'rect': + property.elNumberX.setValue(propertyValue.x); + property.elNumberY.setValue(propertyValue.y); + property.elNumberWidth.setValue(propertyValue.width); + property.elNumberHeight.setValue(propertyValue.height); + break; case 'vec3': case 'vec2': { let multiplier = propertyData.multiplier !== undefined ? propertyData.multiplier : 1; @@ -3251,6 +3437,18 @@ function loaded() { property.elColorPicker.style.backgroundColor = "rgb(" + propertyValue.red + "," + propertyValue.green + "," + propertyValue.blue + ")"; + if ($(property.elColorPicker).attr('active') === 'true') { + // Set the color picker inactive before setting the color, + // otherwise an update will be sent directly after setting it here. + $(property.elColorPicker).attr('active', 'false'); + colorPickers['#' + property.elementID].colpickSetColor({ + "r": propertyValue.red, + "g": propertyValue.green, + "b": propertyValue.blue + }); + $(property.elColorPicker).attr('active', 'true'); + } + property.elNumberR.setValue(propertyValue.red); property.elNumberG.setValue(propertyValue.green); property.elNumberB.setValue(propertyValue.blue); @@ -3290,10 +3488,7 @@ function loaded() { updateVisibleSpaceModeProperties(); - if (selectedEntityProperties.type === "Image") { - let imageLink = JSON.parse(selectedEntityProperties.textures)["tex.picture"]; - getPropertyInputElement("image").value = imageLink; - } else if (selectedEntityProperties.type === "Material") { + if (selectedEntityProperties.type === "Material") { let elParentMaterialNameString = getPropertyInputElement("materialNameToReplace"); let elParentMaterialNameNumber = getPropertyInputElement("submeshToReplace"); let elParentMaterialNameCheckbox = getPropertyInputElement("selectSubmesh"); @@ -3368,11 +3563,48 @@ function loaded() { } else if (data.type === 'setSpaceMode') { currentSpaceMode = data.spaceMode === "local" ? PROPERTY_SPACE_MODE.LOCAL : PROPERTY_SPACE_MODE.WORLD; updateVisibleSpaceModeProperties(); + } else if (data.type === 'propertyRangeReply') { + let propertyRanges = data.propertyRanges; + for (let property in propertyRanges) { + let propertyRange = propertyRanges[property]; + if (propertyRange !== undefined) { + let propertyData = properties[property].data; + let multiplier = propertyData.multiplier; + if (propertyData.min === undefined && propertyRange.minimum != "") { + propertyData.min = propertyRange.minimum; + if (multiplier !== undefined) { + propertyData.min /= multiplier; + } + } + if (propertyData.max === undefined && propertyRange.maximum != "") { + propertyData.max = propertyRange.maximum; + if (multiplier !== undefined) { + propertyData.max /= multiplier; + } + } + switch (propertyData.type) { + case 'number': + updateNumberMinMax(properties[property]); + break; + case 'number-draggable': + updateNumberDraggableMinMax(properties[property]); + break; + case 'vec3': + case 'vec2': + updateVectorMinMax(properties[property]); + break; + case 'rect': + updateRectMinMax(properties[property]); + break; + } + } + } } }); - // Request tooltips as soon as we can process a reply: + // Request tooltips and property ranges as soon as we can process a reply: EventBridge.emitWebEvent(JSON.stringify({ type: 'tooltipsRequest' })); + EventBridge.emitWebEvent(JSON.stringify({ type: 'propertyRangeRequest', properties: propertyRangeRequests })); } // Server Script Status @@ -3456,8 +3688,6 @@ function loaded() { } }); - getPropertyInputElement("image").addEventListener('change', createImageURLUpdateFunction(properties['textures'])); - // Collapsible sections let elCollapsible = document.getElementsByClassName("collapse-icon"); diff --git a/scripts/system/html/js/includes.js b/scripts/system/html/js/includes.js new file mode 100644 index 0000000000..c604115f91 --- /dev/null +++ b/scripts/system/html/js/includes.js @@ -0,0 +1,27 @@ +// +// includes.js +// +// Created by David Back on 3 Jan 2019 +// Copyright 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 +// + +const ENTITY_TYPE_ICON = { + Box: "m", + Grid: "", + Image: "", + Light: "p", + Material: "", + Model: "", + ParticleEffect: "", + PolyVox: "", + PolyLine: "", + Shape: "n", + Sphere: "n", + Text: "l", + Web: "q", + Zone: "o", + Multiple: "", +}; diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index 070997d479..221af07474 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -5,7 +5,7 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -/* global module, Camera, HMD, MyAvatar, controllerDispatcherPlugins:true, Quat, Vec3, Overlays, Xform, Mat4, +/* global module, HMD, MyAvatar, controllerDispatcherPlugins:true, Quat, Vec3, Overlays, Xform, Mat4, Selection, Uuid, MSECS_PER_SEC:true , LEFT_HAND:true, RIGHT_HAND:true, FORBIDDEN_GRAB_TYPES:true, HAPTIC_PULSE_STRENGTH:true, HAPTIC_PULSE_DURATION:true, ZERO_VEC:true, ONE_VEC:true, @@ -31,6 +31,8 @@ disableDispatcherModule:true, getEnabledModuleByName:true, getGrabbableData:true, + isAnothersAvatarEntity:true, + isAnothersChildEntity:true, entityIsGrabbable:true, entityIsDistanceGrabbable:true, getControllerJointIndexCacheTime:true, @@ -104,7 +106,7 @@ TEAR_AWAY_CHECK_TIME = 0.15; // seconds, duration between checks NEAR_GRAB_DISTANCE = 0.14; // Grab an entity if its bounding box is within this distance. // Smaller than TEAR_AWAY_DISTANCE for hysteresis. -DISPATCHER_HOVERING_LIST = "dispactherHoveringList"; +DISPATCHER_HOVERING_LIST = "dispatcherHoveringList"; DISPATCHER_HOVERING_STYLE = { isOutlineSmooth: true, outlineWidth: 0, @@ -144,6 +146,7 @@ DISPATCHER_PROPERTIES = [ "grab.grabFollowsController", "grab.triggerable", "grab.equippable", + "grab.grabDelegateToParent", "grab.equippableLeftPosition", "grab.equippableLeftRotation", "grab.equippableRightPosition", @@ -151,7 +154,9 @@ DISPATCHER_PROPERTIES = [ "grab.equippableIndicatorURL", "grab.equippableIndicatorScale", "grab.equippableIndicatorOffset", - "userData" + "userData", + "avatarEntity", + "owningAvatarID" ]; // priority -- a lower priority means the module will be asked sooner than one with a higher priority in a given update step @@ -286,10 +291,44 @@ getGrabbableData = function (ggdProps) { return grabbableData; }; +isAnothersAvatarEntity = function (iaaeProps) { + if (!iaaeProps.avatarEntity) { + return false; + } + if (iaaeProps.owningAvatarID === MyAvatar.sessionUUID) { + return false; + } + if (iaaeProps.owningAvatarID === MyAvatar.SELF_ID) { + return false; + } + return true; +}; + +isAnothersChildEntity = function (iaceProps) { + while (iaceProps.parentID && iaceProps.parentID !== Uuid.NULL) { + if (Entities.getNestableType(iaceProps.parentID) == "avatar") { + if (iaceProps.parentID == MyAvatar.SELF_ID || iaceProps.parentID == MyAvatar.sessionUUID) { + return false; // not another's, it's mine. + } + return true; + } + // Entities.getNestableType(iaceProps.parentID) == "entity") { + var parentProps = Entities.getEntityProperties(iaceProps.parentID, DISPATCHER_PROPERTIES); + if (!parentProps) { + break; + } + parentProps.id = iaceProps.parentID; + iaceProps = parentProps; + } + return false; +}; + entityIsGrabbable = function (eigProps) { var grabbable = getGrabbableData(eigProps).grabbable; if (!grabbable || eigProps.locked || + isAnothersAvatarEntity(eigProps) || + isAnothersChildEntity(eigProps) || FORBIDDEN_GRAB_TYPES.indexOf(eigProps.type) >= 0) { return false; } @@ -331,17 +370,9 @@ getControllerJointIndex = function (hand) { var now = Date.now(); if (now - getControllerJointIndexCacheTime[hand] > GET_CONTROLLERJOINTINDEX_CACHE_REFRESH_TIME) { if (HMD.isHandControllerAvailable()) { - var controllerJointIndex = -1; - if (Camera.mode === "first person") { - controllerJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ? + var controllerJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ? "_CONTROLLER_RIGHTHAND" : "_CONTROLLER_LEFTHAND"); - } else if (Camera.mode === "third person") { - controllerJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ? - "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : - "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"); - } - getControllerJointIndexCacheTime[hand] = now; getControllerJointIndexCache[hand] = controllerJointIndex; return controllerJointIndex; @@ -409,7 +440,8 @@ ensureDynamic = function (entityID) { }; findGroupParent = function (controllerData, targetProps) { - while (targetProps.parentID && + while (targetProps.grab.grabDelegateToParent && + targetProps.parentID && targetProps.parentID !== Uuid.NULL && Entities.getNestableType(targetProps.parentID) == "entity") { var parentProps = Entities.getEntityProperties(targetProps.parentID, DISPATCHER_PROPERTIES); diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index f27ab6caf2..d19da2b342 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -164,7 +164,7 @@ EntityListTool = function(shouldUseEditTabletApp) { var cameraPosition = Camera.position; PROFILE("getMultipleProperties", function () { var multipleProperties = Entities.getMultipleEntityProperties(ids, ['name', 'type', 'locked', - 'visible', 'renderInfo', 'modelURL', 'materialURL', 'script', 'certificateID']); + 'visible', 'renderInfo', 'modelURL', 'materialURL', 'imageURL', 'script', 'certificateID']); for (var i = 0; i < multipleProperties.length; i++) { var properties = multipleProperties[i]; @@ -174,6 +174,8 @@ EntityListTool = function(shouldUseEditTabletApp) { url = properties.modelURL; } else if (properties.type === "Material") { url = properties.materialURL; + } else if (properties.type === "Image") { + url = properties.imageURL; } entities.push({ id: ids[i], diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 164899ef86..83cd010337 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -599,12 +599,10 @@ SelectionDisplay = (function() { const STRETCH_CUBE_OFFSET = 0.06; const STRETCH_CUBE_CAMERA_DISTANCE_MULTIPLE = 0.02; - const STRETCH_MINIMUM_DIMENSION = 0.001; const STRETCH_PANEL_WIDTH = 0.01; const SCALE_OVERLAY_CAMERA_DISTANCE_MULTIPLE = 0.02; const SCALE_DIMENSIONS_CAMERA_DISTANCE_MULTIPLE = 0.5; - const SCALE_MINIMUM_DIMENSION = 0.01; const BOUNDING_EDGE_OFFSET = 0.5; @@ -1543,7 +1541,7 @@ SelectionDisplay = (function() { print(" SpaceMode: " + spaceMode); print(" DisplayMode: " + getMode()); } - + if (SelectionManager.selections.length === 0) { that.setOverlaysVisible(false); that.clearDebugPickPlane(); @@ -1574,7 +1572,7 @@ SelectionDisplay = (function() { dimensions: dimensions }; var isCameraInsideBox = isPointInsideBox(Camera.position, selectionBoxGeometry); - + // in HMD if outside the bounding box clamp the overlays to the bounding box for now so lasers can hit them var maxHandleDimension = 0; if (HMD.active && !isCameraInsideBox) { @@ -2515,7 +2513,7 @@ SelectionDisplay = (function() { var newDimensions = Vec3.sum(initialDimensions, changeInDimensions); - var minimumDimension = STRETCH_MINIMUM_DIMENSION; + var minimumDimension = Entities.getPropertyInfo("dimensions").minimum; if (newDimensions.x < minimumDimension) { newDimensions.x = minimumDimension; changeInDimensions.x = minimumDimension - initialDimensions.x; @@ -2634,7 +2632,7 @@ SelectionDisplay = (function() { newDimensions.y = Math.abs(newDimensions.y); newDimensions.z = Math.abs(newDimensions.z); - var minimumDimension = SCALE_MINIMUM_DIMENSION; + var minimumDimension = Entities.getPropertyInfo("dimensions").minimum; if (newDimensions.x < minimumDimension) { newDimensions.x = minimumDimension; changeInDimensions.x = minimumDimension - initialDimensions.x; diff --git a/server-console/resources/console-beta.icns b/server-console/resources/console-beta.icns index 70a00698bd..a7317c863a 100644 Binary files a/server-console/resources/console-beta.icns and b/server-console/resources/console-beta.icns differ diff --git a/server-console/resources/console-beta.ico b/server-console/resources/console-beta.ico index 57df32f878..c551fc5e37 100644 Binary files a/server-console/resources/console-beta.ico and b/server-console/resources/console-beta.ico differ diff --git a/server-console/resources/console-beta.png b/server-console/resources/console-beta.png index c2df15832d..288ca42bb3 100644 Binary files a/server-console/resources/console-beta.png and b/server-console/resources/console-beta.png differ diff --git a/server-console/resources/console.icns b/server-console/resources/console.icns index 8810f804c3..61e8108c95 100644 Binary files a/server-console/resources/console.icns and b/server-console/resources/console.icns differ diff --git a/server-console/resources/console.ico b/server-console/resources/console.ico index c5a4bb0381..007c97f92a 100644 Binary files a/server-console/resources/console.ico and b/server-console/resources/console.ico differ diff --git a/server-console/resources/console.png b/server-console/resources/console.png index 9fa1b843ec..cfb848d47b 100644 Binary files a/server-console/resources/console.png and b/server-console/resources/console.png differ diff --git a/tools/dissectors/1-hfudt.lua b/tools/dissectors/1-hfudt.lua index fe8a72682d..de99c1ce3c 100644 --- a/tools/dissectors/1-hfudt.lua +++ b/tools/dissectors/1-hfudt.lua @@ -151,13 +151,16 @@ local packet_types = { [96] = "OctreeDataFileReply", [97] = "OctreeDataPersist", [98] = "EntityClone", - [99] = "EntityQueryInitialResultsComplete" + [99] = "EntityQueryInitialResultsComplete", + [100] = "BulkAvatarTraits" } local unsourced_packet_types = { ["DomainList"] = true } +local fragments = {} + function p_hfudt.dissector(buf, pinfo, tree) -- make sure this isn't a STUN packet - those don't follow HFUDT format @@ -235,6 +238,10 @@ function p_hfudt.dissector(buf, pinfo, tree) local payload_offset = 4 + local message_number = 0 + local message_part_number = 0 + local message_position = 0 + -- if the message bit is set, handle the second word if message_bit == 1 then payload_offset = 12 @@ -242,7 +249,7 @@ function p_hfudt.dissector(buf, pinfo, tree) local second_word = buf(4, 4):le_uint() -- read message position from upper 2 bits - local message_position = bit32.rshift(second_word, 30) + message_position = bit32.rshift(second_word, 30) local position = subtree:add(f_message_position, message_position) if message_positions[message_position] ~= nil then @@ -251,10 +258,12 @@ function p_hfudt.dissector(buf, pinfo, tree) end -- read message number from lower 30 bits - subtree:add(f_message_number, bit32.band(second_word, 0x3FFFFFFF)) + message_number = bit32.band(second_word, 0x3FFFFFFF) + subtree:add(f_message_number, message_number) -- read the message part number - subtree:add(f_message_part_number, buf(8, 4):le_uint()) + message_part_number = buf(8, 4):le_uint() + subtree:add(f_message_part_number, message_part_number) end if obfuscation_bits ~= 0 then @@ -288,25 +297,85 @@ function p_hfudt.dissector(buf, pinfo, tree) i = i + 16 end - -- Domain packets - if packet_type_text == "DomainList" then - Dissector.get("hf-domain"):call(buf(i):tvb(), pinfo, tree) + local payload_to_dissect = nil + + -- check if we have part of a message that we need to re-assemble + -- before it can be dissected + if obfuscation_bits == 0 then + if message_bit == 1 and message_position ~= 0 then + if fragments[message_number] == nil then + fragments[message_number] = {} + end + + if fragments[message_number][message_part_number] == nil then + fragments[message_number][message_part_number] = {} + end + + -- set the properties for this fragment + fragments[message_number][message_part_number] = { + payload = buf(i):bytes() + } + + -- if this is the last part, set our maximum part number + if message_position == 1 then + fragments[message_number].last_part_number = message_part_number + end + + -- if we have the last part + -- enumerate our parts for this message and see if everything is present + if fragments[message_number].last_part_number ~= nil then + local i = 0 + local has_all = true + + local finalMessage = ByteArray.new() + local message_complete = true + + while i <= fragments[message_number].last_part_number do + if fragments[message_number][i] ~= nil then + finalMessage = finalMessage .. fragments[message_number][i].payload + else + -- missing this part, have to break until we have it + message_complete = false + end + + i = i + 1 + end + + if message_complete then + debug("Message " .. message_number .. " is " .. finalMessage:len()) + payload_to_dissect = ByteArray.tvb(finalMessage, message_number) + end + end + + else + payload_to_dissect = buf(i):tvb() + end end - -- AvatarData or BulkAvatarDataPacket - if packet_type_text == "AvatarData" or packet_type_text == "BulkAvatarData" then - Dissector.get("hf-avatar"):call(buf(i):tvb(), pinfo, tree) + if payload_to_dissect ~= nil then + -- Domain packets + if packet_type_text == "DomainList" then + Dissector.get("hf-domain"):call(payload_to_dissect, pinfo, tree) + end + + -- AvatarData or BulkAvatarDataPacket + if packet_type_text == "AvatarData" or + packet_type_text == "BulkAvatarData" or + packet_type_text == "BulkAvatarTraits" then + Dissector.get("hf-avatar"):call(payload_to_dissect, pinfo, tree) + end + + if packet_type_text == "EntityEdit" then + Dissector.get("hf-entity"):call(payload_to_dissect, pinfo, tree) + end + + if packet_types[packet_type] == "MicrophoneAudioNoEcho" or + packet_types[packet_type] == "MicrophoneAudioWithEcho" or + packet_types[packet_type] == "SilentAudioFrame" then + Dissector.get("hf-audio"):call(payload_to_dissect, pinfo, tree) + end end - if packet_type_text == "EntityEdit" then - Dissector.get("hf-entity"):call(buf(i):tvb(), pinfo, tree) - end - - if packet_types[packet_type] == "MicrophoneAudioNoEcho" or - packet_types[packet_type] == "MicrophoneAudioWithEcho" or - packet_types[packet_type] == "SilentAudioFrame" then - Dissector.get("hf-audio"):call(buf(i):tvb(), pinfo, tree) - end end -- return the size of the header diff --git a/tools/dissectors/3-hf-avatar.lua b/tools/dissectors/3-hf-avatar.lua index 0fa551c6f8..f4172b01cb 100644 --- a/tools/dissectors/3-hf-avatar.lua +++ b/tools/dissectors/3-hf-avatar.lua @@ -21,13 +21,31 @@ local f_avatar_data_default_rotations = ProtoField.string("hf_avatar.avatar_data local f_avatar_data_default_translations = ProtoField.string("hf_avatar.avatar_data_default_translations", "Valid Default") local f_avatar_data_sizes = ProtoField.string("hf_avatar.avatar_sizes", "Sizes") +-- avatar trait data fields +local f_avatar_trait_data = ProtoField.bytes("hf_avatar.avatar_trait_data", "Avatar Trait Data") + +local f_avatar_trait_id = ProtoField.guid("hf_avatar.trait_avatar_id", "Trait Avatar ID") +local f_avatar_trait_type = ProtoField.int8("hf_avatar.trait_type", "Trait Type") +local f_avatar_trait_version = ProtoField.int32("hf_avatar.trait_version", "Trait Version") +local f_avatar_trait_instance_id = ProtoField.guid("hf_avatar.trait_instance_id", "Trait Instance ID") +local f_avatar_trait_binary = ProtoField.bytes("hf_avatar.trait_binary", "Trait Binary Data") p_hf_avatar.fields = { - f_avatar_id, f_avatar_data_parent_id + f_avatar_id, f_avatar_data_parent_id, + f_avatar_trait_data, + f_avatar_trait_type, f_avatar_trait_id, + f_avatar_trait_version, f_avatar_trait_binary, + f_avatar_trait_instance_id } local packet_type_extractor = Field.new('hfudt.type') +INSTANCED_TYPES = { + [1] = true +} + +TOTAL_TRAIT_TYPES = 2 + function p_hf_avatar.dissector(buf, pinfo, tree) pinfo.cols.protocol = p_hf_avatar.name @@ -52,7 +70,7 @@ function p_hf_avatar.dissector(buf, pinfo, tree) add_avatar_data_subtrees(avatar_data) - else + elseif packet_type == 11 then -- BulkAvatarData packet while i < buf:len() do -- avatar_id is first 16 bytes @@ -65,9 +83,88 @@ function p_hf_avatar.dissector(buf, pinfo, tree) add_avatar_data_subtrees(avatar_data) end + elseif packet_type == 100 then + -- BulkAvatarTraits packet + + -- loop over the packet until we're done reading it + while i < buf:len() do + i = i + read_avatar_trait_data(buf(i)) + end end end +function read_avatar_trait_data(buf) + local i = 0 + + -- avatar_id is first 16 bytes + local avatar_id_bytes = buf(i, 16) + i = i + 16 + + local traits_map = {} + + -- loop over all of the traits for this avatar + while i < buf:len() do + -- pull out this trait type + local trait_type = buf(i, 1):le_int() + i = i + 1 + + debug("The trait type is " .. trait_type) + + -- bail on our while if the trait type is null (-1) + if trait_type == -1 then break end + + local trait_map = {} + + -- pull out the trait version + trait_map.version = buf(i, 4):le_int() + i = i + 4 + + if INSTANCED_TYPES[trait_type] ~= nil then + -- pull out the trait instance ID + trait_map.instance_ID = buf(i, 16) + i = i + 16 + end + + -- pull out the trait binary size + trait_map.binary_size = buf(i, 2):le_int() + i = i + 2 + + -- unpack the binary data as long as this wasn't a delete + if trait_map.binary_size ~= -1 then + -- pull out the binary data for the trait + trait_map.binary_data = buf(i, trait_map.binary_size) + i = i + trait_map.binary_size + end + + traits_map[trait_type] = trait_map + end + + -- add a subtree including all of the data for this avatar + debug("Adding trait data of " .. i .. " bytes to the avatar tree") + local this_avatar_tree = avatar_subtree:add(f_avatar_trait_data, buf(0, i)) + + this_avatar_tree:add(f_avatar_trait_id, avatar_id_bytes) + + -- enumerate the pulled traits and add them to the tree + local trait_type = 0 + while trait_type < TOTAL_TRAIT_TYPES do + trait = traits_map[trait_type] + + if trait ~= nil then + this_avatar_tree:add(f_avatar_trait_type, trait_type) + this_avatar_tree:add(f_avatar_trait_version, trait.version) + this_avatar_tree:add(f_avatar_trait_binary, trait.binary_data) + + if trait.instance_ID ~= nil then + this_avatar_tree:add(f_avatar_trait_instance_id, trait.instance_ID) + end + end + + trait_type = trait_type + 1 + end + + return i +end function add_avatar_data_subtrees(avatar_data) if avatar_data["has_flags"] then diff --git a/tools/jsdoc/gravPrep.js b/tools/jsdoc/gravPrep.js index 849837bae0..00dd4850ea 100644 --- a/tools/jsdoc/gravPrep.js +++ b/tools/jsdoc/gravPrep.js @@ -25,7 +25,7 @@ let dir_css = path.join(dir_grav, 'css'); let dir_js = path.join(dir_grav, 'js'); let dir_template = path.join(dir_grav, 'templates'); -let dir_md = path.join(dir_grav, '06.api-reference'); +let dir_md = path.join(dir_grav, '08.api-reference'); let dir_md_objects = path.join(dir_md, '02.Objects'); let dir_md_namespaces = path.join(dir_md, '01.Namespaces'); let dir_md_globals = path.join(dir_md, '03.Globals'); @@ -662,7 +662,7 @@ if (copyLocal){ copyFolderRecursiveSync(dir_template, targetTemplateDirectory); // Copy files to the Md Directory - let baseMdRefDir = path.join(targetMDDirectory,"06.api-reference"); + let baseMdRefDir = path.join(targetMDDirectory,"08.api-reference"); // Remove existing MD directory if (fs.existsSync(baseMdRefDir)){ rimraf.sync(baseMdRefDir); diff --git a/tools/nitpick/src/AWSInterface.cpp b/tools/nitpick/src/AWSInterface.cpp index 0b93ce44e5..59be26c383 100644 --- a/tools/nitpick/src/AWSInterface.cpp +++ b/tools/nitpick/src/AWSInterface.cpp @@ -476,24 +476,33 @@ void AWSInterface::updateAWS() { QStringList parts = nextDirectory.split('/'); QString filename = parts[parts.length() - 3] + "/" + parts[parts.length() - 2] + "/" + parts[parts.length() - 1]; - stream << "data = open('" << _workingDirectory << "/" << filename << "/" - << "Actual Image.png" - << "', 'rb')\n"; - - stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Actual Image.png" << "', Body=data)\n\n"; - - stream << "data = open('" << _workingDirectory << "/" << filename << "/" - << "Expected Image.png" - << "', 'rb')\n"; - - stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Expected Image.png" << "', Body=data)\n\n"; - - if (QFile::exists(_htmlFailuresFolder + "/" + parts[parts.length() - 1] + "/Difference Image.png")) { + // The directory may contain either 'Result.txt', or 3 images (and a text file named 'TestResults.txt' that is not used) + if (QFile::exists(_htmlFailuresFolder + "/" + parts[parts.length() - 1] + "/Result.txt")) { stream << "data = open('" << _workingDirectory << "/" << filename << "/" - << "Difference Image.png" - << "', 'rb')\n"; + << "Result.txt" + << "', 'rb')\n"; - stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Difference Image.png" << "', Body=data)\n\n"; + stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Result.txt" << "', Body=data)\n\n"; + } else { + stream << "data = open('" << _workingDirectory << "/" << filename << "/" + << "Actual Image.png" + << "', 'rb')\n"; + + stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Actual Image.png" << "', Body=data)\n\n"; + + stream << "data = open('" << _workingDirectory << "/" << filename << "/" + << "Expected Image.png" + << "', 'rb')\n"; + + stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Expected Image.png" << "', Body=data)\n\n"; + + if (QFile::exists(_htmlFailuresFolder + "/" + parts[parts.length() - 1] + "/Difference Image.png")) { + stream << "data = open('" << _workingDirectory << "/" << filename << "/" + << "Difference Image.png" + << "', 'rb')\n"; + + stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Difference Image.png" << "', Body=data)\n\n"; + } } } @@ -510,31 +519,39 @@ void AWSInterface::updateAWS() { // We need to concatenate the last 3 components, to get `TestResults--2018-10-02_16-54-11(9426)[DESKTOP-PMKNLSQ]/successes/engine.render.effect.bloom.00000` QStringList parts = nextDirectory.split('/'); QString filename = parts[parts.length() - 3] + "/" + parts[parts.length() - 2] + "/" + parts[parts.length() - 1]; - - stream << "data = open('" << _workingDirectory << "/" << filename << "/" - << "Actual Image.png" - << "', 'rb')\n"; - - stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Actual Image.png" << "', Body=data)\n\n"; - - stream << "data = open('" << _workingDirectory << "/" << filename << "/" - << "Expected Image.png" - << "', 'rb')\n"; - - stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Expected Image.png" << "', Body=data)\n\n"; - - if (QFile::exists(_htmlSuccessesFolder + "/" + parts[parts.length() - 1] + "/Difference Image.png")) { + // The directory may contain either 'Result.txt', or 3 images (and a text file named 'TestResults.txt' that is not used) + if (QFile::exists(_htmlSuccessesFolder + "/" + parts[parts.length() - 1] + "/Result.txt")) { stream << "data = open('" << _workingDirectory << "/" << filename << "/" - << "Difference Image.png" - << "', 'rb')\n"; + << "Result.txt" + << "', 'rb')\n"; - stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Difference Image.png" << "', Body=data)\n\n"; + stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Result.txt" << "', Body=data)\n\n"; + } else { + stream << "data = open('" << _workingDirectory << "/" << filename << "/" + << "Actual Image.png" + << "', 'rb')\n"; + + stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Actual Image.png" << "', Body=data)\n\n"; + + stream << "data = open('" << _workingDirectory << "/" << filename << "/" + << "Expected Image.png" + << "', 'rb')\n"; + + stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Expected Image.png" << "', Body=data)\n\n"; + + if (QFile::exists(_htmlSuccessesFolder + "/" + parts[parts.length() - 1] + "/Difference Image.png")) { + stream << "data = open('" << _workingDirectory << "/" << filename << "/" + << "Difference Image.png" + << "', 'rb')\n"; + + stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << filename << "/" << "Difference Image.png" << "', Body=data)\n\n"; + } } } stream << "data = open('" << _workingDirectory << "/" << _resultsFolder << "/" << HTML_FILENAME << "', 'rb')\n"; stream << "s3.Bucket('hifi-content').put_object(Bucket='" << AWS_BUCKET << "', Key='" << _resultsFolder << "/" - << HTML_FILENAME << "', Body=data, ContentType='text/html')\n"; + << HTML_FILENAME << "', Body=data, ContentType='text/html')\n"; file.close(); @@ -548,7 +565,7 @@ void AWSInterface::updateAWS() { connect(process, &QProcess::started, this, [=]() { _busyWindow.exec(); }); connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater())); connect(process, static_cast<void (QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished), this, - [=](int exitCode, QProcess::ExitStatus exitStatus) { _busyWindow.hide(); }); + [=](int exitCode, QProcess::ExitStatus exitStatus) { _busyWindow.hide(); }); #ifdef Q_OS_WIN QStringList parameters = QStringList() << filename; diff --git a/tools/nitpick/src/TestRunner.cpp b/tools/nitpick/src/TestRunner.cpp index 9b99e114a7..9aca2bf3e6 100644 --- a/tools/nitpick/src/TestRunner.cpp +++ b/tools/nitpick/src/TestRunner.cpp @@ -469,12 +469,28 @@ void TestRunner::runInterfaceWithTestScript() { url = "hifi://localhost"; } + QString deleteScript = + QString("https://raw.githubusercontent.com/") + _user + "/hifi_tests/" + _branch + "/tests/utils/deleteNearbyEntities.js"; + QString testScript = QString("https://raw.githubusercontent.com/") + _user + "/hifi_tests/" + _branch + "/tests/testRecursive.js"; QString commandLine; #ifdef Q_OS_WIN - QString exeFile = QString("\"") + QDir::toNativeSeparators(_installationFolder) + "\\interface.exe\""; + QString exeFile; + // First, run script to delete any entities in test area + // Note that this will run to completion before continuing + exeFile = QString("\"") + QDir::toNativeSeparators(_installationFolder) + "\\interface.exe\""; + commandLine = "start /wait \"\" " + exeFile + + " --url " + url + + " --no-updater" + + " --no-login-suggestion" + " --testScript " + deleteScript + " quitWhenFinished"; + + system(commandLine.toStdString().c_str()); + + // Now run the test suite + exeFile = QString("\"") + QDir::toNativeSeparators(_installationFolder) + "\\interface.exe\""; commandLine = exeFile + " --url " + url + " --no-updater" + @@ -485,10 +501,6 @@ void TestRunner::runInterfaceWithTestScript() { _interfaceWorker->setCommandLine(commandLine); emit startInterface(); #elif defined Q_OS_MAC - // On The Mac, we need to resize Interface. The Interface window opens a few seconds after the process - // has started. - // Before starting interface, start a process that will resize interface 10s after it opens - // This is performed by creating a bash script that runs to processes QFile script; script.setFileName(_workingFolder + "/runInterfaceTests.sh"); if (!script.open(QIODevice::WriteOnly | QIODevice::Text)) { @@ -498,7 +510,20 @@ void TestRunner::runInterfaceWithTestScript() { } script.write("#!/bin/sh\n\n"); - + + // First, run script to delete any entities in test area + commandLine = + "open -W \"" +_installationFolder + "/interface.app\" --args" + + " --url " + url + + " --no-updater" + + " --no-login-suggestion" + " --testScript " + deleteScript + " quitWhenFinished\n"; + + script.write(commandLine.toStdString().c_str()); + + // On The Mac, we need to resize Interface. The Interface window opens a few seconds after the process + // has started. + // Before starting interface, start a process that will resize interface 10s after it opens commandLine = _workingFolder +"/waitForStart.sh interface && sleep 10 && " + _workingFolder +"/setInterfaceSizeAndPosition.sh &\n"; script.write(commandLine.toStdString().c_str()); @@ -509,7 +534,7 @@ void TestRunner::runInterfaceWithTestScript() { " --no-login-suggestion" " --testScript " + testScript + " quitWhenFinished" + " --testResultsLocation " + _snapshotFolder + - " && " + _workingFolder +"/waitForFinish.sh interface"; + " && " + _workingFolder +"/waitForFinish.sh interface\n"; script.write(commandLine.toStdString().c_str()); diff --git a/tools/nitpick/src/ui/HelpWindow.cpp b/tools/nitpick/src/ui/HelpWindow.cpp deleted file mode 100644 index 21c5d9d375..0000000000 --- a/tools/nitpick/src/ui/HelpWindow.cpp +++ /dev/null @@ -1,14 +0,0 @@ -// -// HelpWindow.cpp -// -// Created by Nissim Hadar on 8 Aug 2017. -// Copyright 2013 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 "HelpWindow.h" - -HelpWindow::HelpWindow(QWidget *parent) { - setupUi(this); -} diff --git a/tools/nitpick/src/ui/HelpWindow.h b/tools/nitpick/src/ui/HelpWindow.h deleted file mode 100644 index 5ce91b360d..0000000000 --- a/tools/nitpick/src/ui/HelpWindow.h +++ /dev/null @@ -1,22 +0,0 @@ -// -// HelpWindow.h -// -// Created by Nissim Hadar on 8 Aug 2017. -// Copyright 2013 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_HelpWindow_h -#define hifi_HelpWindow_h - -#include "ui_HelpWindow.h" - -class HelpWindow : public QDialog, public Ui::HelpWindow { - Q_OBJECT - -public: - HelpWindow(QWidget* parent = Q_NULLPTR); -}; - -#endif \ No newline at end of file diff --git a/tools/nitpick/src/ui/HelpWindow.ui b/tools/nitpick/src/ui/HelpWindow.ui deleted file mode 100644 index 1ce6e8c321..0000000000 --- a/tools/nitpick/src/ui/HelpWindow.ui +++ /dev/null @@ -1,46 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>HelpWindow</class> - <widget class="QDialog" name="HelpWindow"> - <property name="windowModality"> - <enum>Qt::ApplicationModal</enum> - </property> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>696</width> - <height>546</height> - </rect> - </property> - <property name="windowTitle"> - <string>Nitpick Help</string> - </property> - <widget class="QTextEdit" name="textEdit"> - <property name="geometry"> - <rect> - <x>50</x> - <y>50</y> - <width>581</width> - <height>381</height> - </rect> - </property> - </widget> - <widget class="QPushButton" name="pushButton"> - <property name="geometry"> - <rect> - <x>300</x> - <y>460</y> - <width>93</width> - <height>28</height> - </rect> - </property> - <property name="text"> - <string>Close</string> - </property> - </widget> - </widget> - <layoutdefault spacing="6" margin="11"/> - <resources/> - <connections/> -</ui> diff --git a/tools/nitpick/src/ui/Nitpick.cpp b/tools/nitpick/src/ui/Nitpick.cpp index cdd2ff89d9..0bd397715b 100644 --- a/tools/nitpick/src/ui/Nitpick.cpp +++ b/tools/nitpick/src/ui/Nitpick.cpp @@ -15,6 +15,8 @@ #include <shellapi.h> #endif +#include <QDesktopServices> + Nitpick::Nitpick(QWidget* parent) : QMainWindow(parent) { _ui.setupUi(this); @@ -36,10 +38,7 @@ Nitpick::Nitpick(QWidget* parent) : QMainWindow(parent) { _ui.statusLabel->setText(""); _ui.plainTextEdit->setReadOnly(true); - setWindowTitle("Nitpick - v1.2"); - - // Coming soon to a nitpick near you... - //// _helpWindow.textBrowser->setText() + setWindowTitle("Nitpick - v1.3.2"); } Nitpick::~Nitpick() { @@ -287,7 +286,7 @@ void Nitpick::about() { } void Nitpick::content() { - _helpWindow.show(); + QDesktopServices::openUrl(QUrl("https://github.com/highfidelity/hifi/blob/master/tools/nitpick/README.md")); } void Nitpick::setUserText(const QString& user) { diff --git a/tools/nitpick/src/ui/Nitpick.h b/tools/nitpick/src/ui/Nitpick.h index 21b917654b..08e41e0a90 100644 --- a/tools/nitpick/src/ui/Nitpick.h +++ b/tools/nitpick/src/ui/Nitpick.h @@ -18,7 +18,6 @@ #include "../Downloader.h" #include "../Test.h" -#include "HelpWindow.h" #include "../TestRunner.h" #include "../AWSInterface.h" @@ -116,8 +115,6 @@ private: bool _isRunningFromCommandline{ false }; - HelpWindow _helpWindow; - void* _caller; }; diff --git a/tools/nitpick/src/ui/Nitpick.ui b/tools/nitpick/src/ui/Nitpick.ui index 5e20e75553..78f7dcf2bf 100644 --- a/tools/nitpick/src/ui/Nitpick.ui +++ b/tools/nitpick/src/ui/Nitpick.ui @@ -803,7 +803,7 @@ <x>0</x> <y>0</y> <width>720</width> - <height>22</height> + <height>21</height> </rect> </property> <widget class="QMenu" name="menuFile"> @@ -843,7 +843,7 @@ </action> <action name="actionContent"> <property name="text"> - <string>Content</string> + <string>Online readme</string> </property> </action> </widget> diff --git a/tools/udt-test/src/UDTTest.cpp b/tools/udt-test/src/UDTTest.cpp index 46e7ed0be0..65bf2c7ebd 100644 --- a/tools/udt-test/src/UDTTest.cpp +++ b/tools/udt-test/src/UDTTest.cpp @@ -386,7 +386,7 @@ void UDTTest::sampleStats() { QString::number(stats.events[udt::ConnectionStats::Stats::ReceivedACK]).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), QString::number(stats.events[udt::ConnectionStats::Stats::ProcessedACK]).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), QString::number(stats.sentPackets).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()), - QString::number(stats.events[udt::ConnectionStats::Stats::Retransmission]).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()) + QString::number(stats.retransmittedPackets).rightJustified(CLIENT_STATS_TABLE_HEADERS[++headerIndex].size()) }; // output this line of values diff --git a/tools/unity-avatar-exporter/Assets/Editor.meta b/tools/unity-avatar-exporter/Assets/Editor.meta index aac82b4258..cf7dcf12dd 100644 --- a/tools/unity-avatar-exporter/Assets/Editor.meta +++ b/tools/unity-avatar-exporter/Assets/Editor.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 51b3237a2992bd449a58ade16e52d0e0 +guid: 02111c50e71dd664da8ad5c6a6eca767 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs index 60b5e0e643..b6470a7551 100644 --- a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs +++ b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs @@ -12,8 +12,8 @@ using System; using System.IO; using System.Collections.Generic; -public class AvatarExporter : MonoBehaviour { - public static Dictionary<string, string> UNITY_TO_HIFI_JOINT_NAME = new Dictionary<string, string> { +class AvatarExporter : MonoBehaviour { + static readonly Dictionary<string, string> HUMANOID_TO_HIFI_JOINT_NAME = new Dictionary<string, string> { {"Chest", "Spine1"}, {"Head", "Head"}, {"Hips", "Hips"}, @@ -70,138 +70,533 @@ public class AvatarExporter : MonoBehaviour { {"UpperChest", "Spine2"}, }; - public static string exportedPath = String.Empty; + static readonly Dictionary<string, Quaternion> referenceAbsoluteRotations = new Dictionary<string, Quaternion> { + {"Head", new Quaternion(-2.509889e-9f, -3.379446e-12f, 2.306033e-13f, 1f)}, + {"Hips", new Quaternion(-3.043941e-10f, -1.573706e-7f, 5.112975e-6f, 1f)}, + {"LeftHandIndex3", new Quaternion(-0.5086057f, 0.4908088f, -0.4912299f, -0.5090388f)}, + {"LeftHandIndex2", new Quaternion(-0.4934928f, 0.5062312f, -0.5064303f, -0.4936835f)}, + {"LeftHandIndex1", new Quaternion(-0.4986293f, 0.5017503f, -0.5013659f, -0.4982448f)}, + {"LeftHandPinky3", new Quaternion(-0.490056f, 0.5143053f, -0.5095307f, -0.4855038f)}, + {"LeftHandPinky2", new Quaternion(-0.5083722f, 0.4954255f, -0.4915887f, -0.5044324f)}, + {"LeftHandPinky1", new Quaternion(-0.5062528f, 0.497324f, -0.4937346f, -0.5025966f)}, + {"LeftHandMiddle3", new Quaternion(-0.4871885f, 0.5123404f, -0.5125002f, -0.4873383f)}, + {"LeftHandMiddle2", new Quaternion(-0.5171652f, 0.4827828f, -0.4822642f, -0.5166069f)}, + {"LeftHandMiddle1", new Quaternion(-0.4955998f, 0.5041052f, -0.5043675f, -0.4958555f)}, + {"LeftHandRing3", new Quaternion(-0.4936301f, 0.5097645f, -0.5061787f, -0.4901562f)}, + {"LeftHandRing2", new Quaternion(-0.5089865f, 0.4943658f, -0.4909532f, -0.5054707f)}, + {"LeftHandRing1", new Quaternion(-0.5020972f, 0.5005084f, -0.4979034f, -0.4994819f)}, + {"LeftHandThumb3", new Quaternion(-0.6617184f, 0.2884935f, -0.3604706f, -0.5907297f)}, + {"LeftHandThumb2", new Quaternion(-0.6935627f, 0.1995147f, -0.2805665f, -0.6328092f)}, + {"LeftHandThumb1", new Quaternion(-0.6663674f, 0.278572f, -0.3507071f, -0.5961183f)}, + {"LeftEye", new Quaternion(-2.509889e-9f, -3.379446e-12f, 2.306033e-13f, 1f)}, + {"LeftFoot", new Quaternion(0.009215056f, 0.3612514f, 0.9323555f, -0.01121602f)}, + {"LeftHand", new Quaternion(-0.4797408f, 0.5195366f, -0.5279632f, -0.4703038f)}, + {"LeftForeArm", new Quaternion(-0.4594738f, 0.4594729f, -0.5374805f, -0.5374788f)}, + {"LeftLeg", new Quaternion(-0.0005380471f, -0.03154583f, 0.9994993f, 0.002378627f)}, + {"LeftShoulder", new Quaternion(-0.3840606f, 0.525857f, -0.5957767f, -0.47013f)}, + {"LeftToeBase", new Quaternion(-0.0002536641f, 0.7113448f, 0.7027079f, -0.01379319f)}, + {"LeftArm", new Quaternion(-0.4591927f, 0.4591916f, -0.5377204f, -0.5377193f)}, + {"LeftUpLeg", new Quaternion(-0.0006682819f, 0.0006864658f, 0.9999968f, -0.002333928f)}, + {"Neck", new Quaternion(-2.509889e-9f, -3.379446e-12f, 2.306033e-13f, 1f)}, + {"RightHandIndex3", new Quaternion(0.5083892f, 0.4911618f, -0.4914584f, 0.5086939f)}, + {"RightHandIndex2", new Quaternion(0.4931984f, 0.5065879f, -0.5067145f, 0.4933202f)}, + {"RightHandIndex1", new Quaternion(0.4991491f, 0.5012957f, -0.5008481f, 0.4987026f)}, + {"RightHandPinky3", new Quaternion(0.4890696f, 0.5154139f, -0.5104482f, 0.4843578f)}, + {"RightHandPinky2", new Quaternion(0.5084175f, 0.495413f, -0.4915423f, 0.5044444f)}, + {"RightHandPinky1", new Quaternion(0.5069782f, 0.4965974f, -0.4930001f, 0.5033045f)}, + {"RightHandMiddle3", new Quaternion(0.4867662f, 0.5129694f, -0.5128888f, 0.4866894f)}, + {"RightHandMiddle2", new Quaternion(0.5167004f, 0.4833596f, -0.4827653f, 0.5160643f)}, + {"RightHandMiddle1", new Quaternion(0.4965845f, 0.5031784f, -0.5033959f, 0.4967981f)}, + {"RightHandRing3", new Quaternion(0.4933217f, 0.5102056f, -0.5064691f, 0.4897075f)}, + {"RightHandRing2", new Quaternion(0.5085972f, 0.494844f, -0.4913519f, 0.505007f)}, + {"RightHandRing1", new Quaternion(0.502959f, 0.4996676f, -0.4970418f, 0.5003144f)}, + {"RightHandThumb3", new Quaternion(0.6611374f, 0.2896575f, -0.3616535f, 0.5900872f)}, + {"RightHandThumb2", new Quaternion(0.6937408f, 0.1986776f, -0.279922f, 0.6331626f)}, + {"RightHandThumb1", new Quaternion(0.6664271f, 0.2783172f, -0.3505667f, 0.596253f)}, + {"RightEye", new Quaternion(-2.509889e-9f, -3.379446e-12f, 2.306033e-13f, 1f)}, + {"RightFoot", new Quaternion(-0.009482829f, 0.3612484f, 0.9323512f, 0.01144584f)}, + {"RightHand", new Quaternion(0.4797273f, 0.5195542f, -0.5279628f, 0.4702987f)}, + {"RightForeArm", new Quaternion(0.4594217f, 0.4594215f, -0.5375242f, 0.5375237f)}, + {"RightLeg", new Quaternion(0.0005446263f, -0.03177159f, 0.9994922f, -0.002395923f)}, + {"RightShoulder", new Quaternion(0.3841222f, 0.5257177f, -0.5957286f, 0.4702966f)}, + {"RightToeBase", new Quaternion(0.0001034f, 0.7113398f, 0.7027067f, 0.01411251f)}, + {"RightArm", new Quaternion(0.4591419f, 0.4591423f, -0.537763f, 0.5377624f)}, + {"RightUpLeg", new Quaternion(0.0006750703f, 0.0008973633f, 0.9999966f, 0.002352045f)}, + {"Spine", new Quaternion(-0.05427956f, 1.508558e-7f, -2.775203e-6f, 0.9985258f)}, + {"Spine1", new Quaternion(-0.0824653f, 1.25274e-7f, -6.75759e-6f, 0.996594f)}, + {"Spine2", new Quaternion(-0.0824653f, 1.25274e-7f, -6.75759e-6f, 0.996594f)}, + }; + + static Dictionary<string, string> userBoneToHumanoidMappings = new Dictionary<string, string>(); + static Dictionary<string, string> userParentNames = new Dictionary<string, string>(); + static Dictionary<string, Quaternion> userAbsoluteRotations = new Dictionary<string, Quaternion>(); + + static string assetPath = ""; + static string assetName = ""; + static HumanDescription humanDescription; [MenuItem("High Fidelity/Export New Avatar")] - public static void ExportNewAvatar() { + static void ExportNewAvatar() { ExportSelectedAvatar(false); } - - [MenuItem("High Fidelity/Export New Avatar", true)] - private static bool ExportNewAvatarValidator() { - // only enable Export New Avatar option if we have an asset selected - string[] guids = Selection.assetGUIDs; - return guids.Length > 0; - } - - [MenuItem("High Fidelity/Update Avatar")] - public static void UpdateAvatar() { + + [MenuItem("High Fidelity/Update Existing Avatar")] + static void UpdateAvatar() { ExportSelectedAvatar(true); } - [MenuItem("High Fidelity/Update Avatar", true)] - private static bool UpdateAvatarValidation() { - // only enable Update Avatar option if the selected avatar is the last one that was exported - if (exportedPath != String.Empty) { - string[] guids = Selection.assetGUIDs; - if (guids.Length > 0) { - string selectedAssetPath = AssetDatabase.GUIDToAssetPath(guids[0]); - string selectedAsset = Path.GetFileNameWithoutExtension(selectedAssetPath); - string exportedAsset = Path.GetFileNameWithoutExtension(exportedPath); - return exportedAsset == selectedAsset; + static void ExportSelectedAvatar(bool updateAvatar) { + string[] guids = Selection.assetGUIDs; + if (guids.Length != 1) { + if (guids.Length == 0) { + EditorUtility.DisplayDialog("Error", "Please select an asset to export.", "Ok"); + } else { + EditorUtility.DisplayDialog("Error", "Please select a single asset to export.", "Ok"); } - } - return false; - } - - public static void ExportSelectedAvatar(bool usePreviousPath) { - string assetPath = AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[0]); - if (assetPath.LastIndexOf(".fbx") == -1) { - EditorUtility.DisplayDialog("Error", "Please select an fbx avatar to export", "Ok"); return; } - ModelImporter importer = ModelImporter.GetAtPath(assetPath) as ModelImporter; - if (importer == null) { - EditorUtility.DisplayDialog("Error", "Please select a model", "Ok"); + assetPath = AssetDatabase.GUIDToAssetPath(Selection.assetGUIDs[0]); + assetName = Path.GetFileNameWithoutExtension(assetPath); + ModelImporter modelImporter = ModelImporter.GetAtPath(assetPath) as ModelImporter; + if (Path.GetExtension(assetPath).ToLower() != ".fbx" || modelImporter == null) { + EditorUtility.DisplayDialog("Error", "Please select an .fbx model asset to export.", "Ok"); return; } - if (importer.animationType != ModelImporterAnimationType.Human) { - EditorUtility.DisplayDialog("Error", "Please set model's Animation Type to Humanoid", "Ok"); + if (modelImporter.animationType != ModelImporterAnimationType.Human) { + EditorUtility.DisplayDialog("Error", "Please set model's Animation Type to Humanoid in the Rig section of it's Inspector window.", "Ok"); return; } - - // store joint mappings only for joints that exist in hifi and verify missing joints - HumanDescription humanDescription = importer.humanDescription; - HumanBone[] boneMap = humanDescription.human; - Dictionary<string, string> jointMappings = new Dictionary<string, string>(); - foreach (HumanBone bone in boneMap) { - string humanBone = bone.humanName; - string hifiJointName; - if (UNITY_TO_HIFI_JOINT_NAME.TryGetValue(humanBone, out hifiJointName)) { - jointMappings.Add(hifiJointName, bone.boneName); - } - } - if (!jointMappings.ContainsKey("Hips")) { - EditorUtility.DisplayDialog("Error", "There is no Hips bone in selected avatar", "Ok"); + + humanDescription = modelImporter.humanDescription; + if (!SetJointMappingsAndParentNames()) { return; } - if (!jointMappings.ContainsKey("Spine")) { - EditorUtility.DisplayDialog("Error", "There is no Spine bone in selected avatar", "Ok"); - return; - } - if (!jointMappings.ContainsKey("Spine1")) { - EditorUtility.DisplayDialog("Error", "There is no Chest bone in selected avatar", "Ok"); - return; - } - if (!jointMappings.ContainsKey("Spine2")) { - // if there is no UpperChest (Spine2) bone then we remap Chest (Spine1) to Spine2 in hifi and skip Spine1 - jointMappings["Spine2"] = jointMappings["Spine1"]; - jointMappings.Remove("Spine1"); - } - - // open folder explorer defaulting to user documents folder to select target path if exporting new avatar, - // otherwise use previously exported path if updating avatar - string directoryPath; - string assetName = Path.GetFileNameWithoutExtension(assetPath); - if (!usePreviousPath || exportedPath == String.Empty) { - string documentsFolder = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments); - if (!SelectExportFolder(assetName, documentsFolder, out directoryPath)) { - return; - } - } else { - directoryPath = Path.GetDirectoryName(exportedPath) + "/"; - } - Directory.CreateDirectory(directoryPath); - - // delete any existing fst since we agreed to overwrite it - string fstPath = directoryPath + assetName + ".fst"; - if (File.Exists(fstPath)) { - File.Delete(fstPath); - } - - // write out core fields to top of fst file - File.WriteAllText(fstPath, "name = " + assetName + "\ntype = body+head\nscale = 1\nfilename = " + - assetName + ".fbx\n" + "texdir = textures\n"); - - // write out joint mappings to fst file - foreach (var jointMapping in jointMappings) { - File.AppendAllText(fstPath, "jointMap = " + jointMapping.Key + " = " + jointMapping.Value + "\n"); - } - // delete any existing fbx since we agreed to overwrite it, and copy fbx over - string targetAssetPath = directoryPath + assetName + ".fbx"; - if (File.Exists(targetAssetPath)) { - File.Delete(targetAssetPath); + string documentsFolder = System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments); + string hifiFolder = documentsFolder + "\\High Fidelity Projects"; + if (updateAvatar) { // Update Existing Avatar menu option + bool copyModelToExport = false; + string initialPath = Directory.Exists(hifiFolder) ? hifiFolder : documentsFolder; + + // open file explorer defaulting to hifi projects folder in user documents to select target fst to update + string exportFstPath = EditorUtility.OpenFilePanel("Select .fst to update", initialPath, "fst"); + if (exportFstPath.Length == 0) { // file selection cancelled + return; + } + exportFstPath = exportFstPath.Replace('/', '\\'); + + // lookup the project name field from the fst file to update + string projectName = ""; + try { + string[] lines = File.ReadAllLines(exportFstPath); + foreach (string line in lines) { + int separatorIndex = line.IndexOf("="); + if (separatorIndex >= 0) { + string key = line.Substring(0, separatorIndex).Trim(); + if (key == "name") { + projectName = line.Substring(separatorIndex + 1).Trim(); + break; + } + } + } + } catch { + EditorUtility.DisplayDialog("Error", "Failed to read from existing file " + exportFstPath + + ". Please check the file and try again.", "Ok"); + return; + } + + string exportModelPath = Path.GetDirectoryName(exportFstPath) + "\\" + assetName + ".fbx"; + if (File.Exists(exportModelPath)) { + // if the fbx in Unity Assets is newer than the fbx in the target export + // folder or vice-versa then ask to replace the older fbx with the newer fbx + DateTime assetModelWriteTime = File.GetLastWriteTime(assetPath); + DateTime targetModelWriteTime = File.GetLastWriteTime(exportModelPath); + if (assetModelWriteTime > targetModelWriteTime) { + int option = EditorUtility.DisplayDialogComplex("Error", "The " + assetName + + ".fbx model in the Unity Assets folder is newer than the " + exportModelPath + + " model.\n\nDo you want to replace the older .fbx with the newer .fbx?", + "Yes", "No", "Cancel"); + if (option == 2) { // Cancel + return; + } + copyModelToExport = option == 0; // Yes + } else if (assetModelWriteTime < targetModelWriteTime) { + int option = EditorUtility.DisplayDialogComplex("Error", "The " + exportModelPath + + " model is newer than the " + assetName + ".fbx model in the Unity Assets folder." + + "\n\nDo you want to replace the older .fbx with the newer .fbx and re-import it?", + "Yes", "No" , "Cancel"); + if (option == 2) { // Cancel + return; + } else if (option == 0) { // Yes - copy model to Unity project + // copy the fbx from the project folder to Unity Assets, overwriting the existing fbx, and re-import it + File.Copy(exportModelPath, assetPath, true); + AssetDatabase.ImportAsset(assetPath); + + // set model to Humanoid animation type and force another refresh on it to process Humanoid + modelImporter = ModelImporter.GetAtPath(assetPath) as ModelImporter; + modelImporter.animationType = ModelImporterAnimationType.Human; + EditorUtility.SetDirty(modelImporter); + modelImporter.SaveAndReimport(); + humanDescription = modelImporter.humanDescription; + + // redo joint mappings and parent names due to the fbx change + SetJointMappingsAndParentNames(); + } + } + } else { + // if no matching fbx exists in the target export folder then ask to copy fbx over + int option = EditorUtility.DisplayDialogComplex("Error", "There is no existing " + exportModelPath + + " model.\n\nDo you want to copy over the " + assetName + + ".fbx model from the Unity Assets folder?", "Yes", "No", "Cancel"); + if (option == 2) { // Cancel + return; + } + copyModelToExport = option == 0; // Yes + } + + // copy asset fbx over deleting any existing fbx if we agreed to overwrite it + if (copyModelToExport) { + try { + File.Copy(assetPath, exportModelPath, true); + } catch { + EditorUtility.DisplayDialog("Error", "Failed to copy existing file " + assetPath + " to " + exportModelPath + + ". Please check the location and try again.", "Ok"); + return; + } + } + + // delete existing fst file since we will write a new file + // TODO: updating fst should only rewrite joint mappings and joint rotation offsets to existing file + try { + File.Delete(exportFstPath); + } catch { + EditorUtility.DisplayDialog("Error", "Failed to overwrite existing file " + exportFstPath + + ". Please check the file and try again.", "Ok"); + return; + } + + // write out a new fst file in place of the old file + WriteFST(exportFstPath, projectName); + } else { // Export New Avatar menu option + // create High Fidelity Projects folder in user documents folder if it doesn't exist + if (!Directory.Exists(hifiFolder)) { + Directory.CreateDirectory(hifiFolder); + } + + // open a popup window to enter new export project name and project location + ExportProjectWindow window = ScriptableObject.CreateInstance<ExportProjectWindow>(); + window.Init(hifiFolder, OnExportProjectWindowClose); } - File.Copy(assetPath, targetAssetPath); - - exportedPath = targetAssetPath; } - public static bool SelectExportFolder(string assetName, string initialPath, out string directoryPath) { - string selectedPath = EditorUtility.OpenFolderPanel("Select export location", initialPath, ""); - if (selectedPath.Length == 0) { // folder selection cancelled - directoryPath = ""; + static void OnExportProjectWindowClose(string projectDirectory, string projectName) { + // copy the fbx from the Unity Assets folder to the project directory + string exportModelPath = projectDirectory + assetName + ".fbx"; + File.Copy(assetPath, exportModelPath); + + // create empty Textures and Scripts folders in the project directory + string texturesDirectory = projectDirectory + "\\textures"; + string scriptsDirectory = projectDirectory + "\\scripts"; + Directory.CreateDirectory(texturesDirectory); + Directory.CreateDirectory(scriptsDirectory); + + // write out the avatar.fst file to the project directory + string exportFstPath = projectDirectory + "avatar.fst"; + WriteFST(exportFstPath, projectName); + + // remove any double slashes in texture directory path and warn user to copy external textures over + texturesDirectory = texturesDirectory.Replace("\\\\", "\\"); + EditorUtility.DisplayDialog("Warning", "If you are using any external textures with your model, " + + "please copy those textures to " + texturesDirectory, "Ok"); + } + + static bool SetJointMappingsAndParentNames() { + userParentNames.Clear(); + userBoneToHumanoidMappings.Clear(); + + // instantiate a game object of the user avatar to save out bone parents then destroy it + UnityEngine.Object avatarResource = AssetDatabase.LoadAssetAtPath(assetPath, typeof(UnityEngine.Object)); + GameObject assetGameObject = (GameObject)Instantiate(avatarResource); + SetParentNames(assetGameObject.transform, userParentNames); + DestroyImmediate(assetGameObject); + + // store joint mappings only for joints that exist in hifi and verify missing required joints + HumanBone[] boneMap = humanDescription.human; + string chestUserBone = ""; + string neckUserBone = ""; + foreach (HumanBone bone in boneMap) { + string humanName = bone.humanName; + string boneName = bone.boneName; + string hifiJointName; + if (HUMANOID_TO_HIFI_JOINT_NAME.TryGetValue(humanName, out hifiJointName)) { + userBoneToHumanoidMappings.Add(boneName, humanName); + if (humanName == "Chest") { + chestUserBone = boneName; + } else if (humanName == "Neck") { + neckUserBone = boneName; + } + } + + } + if (!userBoneToHumanoidMappings.ContainsValue("Hips")) { + EditorUtility.DisplayDialog("Error", "There is no Hips bone in selected avatar", "Ok"); return false; } - directoryPath = selectedPath + "/" + assetName + "/"; - if (Directory.Exists(directoryPath)) { - bool overwrite = EditorUtility.DisplayDialog("Error", "Directory " + assetName + - " already exists here, would you like to overwrite it?", "Yes", "No"); - if (!overwrite) { - SelectExportFolder(assetName, selectedPath, out directoryPath); + if (!userBoneToHumanoidMappings.ContainsValue("Spine")) { + EditorUtility.DisplayDialog("Error", "There is no Spine bone in selected avatar", "Ok"); + return false; + } + if (!userBoneToHumanoidMappings.ContainsValue("Chest")) { + // check to see if there is a child of Spine that could be mapped to Chest + string spineChild = ""; + foreach (var parentRelation in userParentNames) { + string humanName; + if (userBoneToHumanoidMappings.TryGetValue(parentRelation.Value, out humanName) && humanName == "Spine") { + if (spineChild == "") { + spineChild = parentRelation.Key; + } else { + // found more than one Spine child so we can't choose one to remap + spineChild = ""; + break; + } + } + } + if (spineChild != "" && !userBoneToHumanoidMappings.ContainsKey(spineChild)) { + // use child of Spine as Chest + userBoneToHumanoidMappings.Add(spineChild, "Chest"); + chestUserBone = spineChild; + } else { + EditorUtility.DisplayDialog("Error", "There is no Chest bone in selected avatar", "Ok"); + return false; } } + if (!userBoneToHumanoidMappings.ContainsValue("UpperChest")) { + //if parent of Neck is not Chest then map the parent to UpperChest + if (neckUserBone != "") { + string neckParentUserBone, neckParentHuman; + userParentNames.TryGetValue(neckUserBone, out neckParentUserBone); + userBoneToHumanoidMappings.TryGetValue(neckParentUserBone, out neckParentHuman); + if (neckParentHuman != "Chest" && !userBoneToHumanoidMappings.ContainsKey(neckParentUserBone)) { + userBoneToHumanoidMappings.Add(neckParentUserBone, "UpperChest"); + } + } + // if there is still no UpperChest bone but there is a Chest bone then we remap Chest to UpperChest + if (!userBoneToHumanoidMappings.ContainsValue("UpperChest") && chestUserBone != "") { + userBoneToHumanoidMappings[chestUserBone] = "UpperChest"; + } + } + return true; } + + static void WriteFST(string exportFstPath, string projectName) { + userAbsoluteRotations.Clear(); + + // write out core fields to top of fst file + try { + File.WriteAllText(exportFstPath, "name = " + projectName + "\ntype = body+head\nscale = 1\nfilename = " + + assetName + ".fbx\n" + "texdir = textures\n"); + } catch { + EditorUtility.DisplayDialog("Error", "Failed to write file " + exportFstPath + + ". Please check the location and try again.", "Ok"); + return; + } + + // write out joint mappings to fst file + foreach (var jointMapping in userBoneToHumanoidMappings) { + string hifiJointName = HUMANOID_TO_HIFI_JOINT_NAME[jointMapping.Value]; + File.AppendAllText(exportFstPath, "jointMap = " + hifiJointName + " = " + jointMapping.Key + "\n"); + } + + // calculate and write out joint rotation offsets to fst file + SkeletonBone[] skeletonMap = humanDescription.skeleton; + foreach (SkeletonBone userBone in skeletonMap) { + string userBoneName = userBone.name; + Quaternion userBoneRotation = userBone.rotation; + + string parentName; + userParentNames.TryGetValue(userBoneName, out parentName); + if (parentName == "root") { + // if the parent is root then use bone's rotation + userAbsoluteRotations.Add(userBoneName, userBoneRotation); + } else { + // otherwise multiply bone's rotation by parent bone's absolute rotation + userAbsoluteRotations.Add(userBoneName, userAbsoluteRotations[parentName] * userBoneRotation); + } + + // generate joint rotation offsets for both humanoid-mapped bones as well as extra unmapped bones in user avatar + Quaternion jointOffset = new Quaternion(); + string humanName, outputJointName = ""; + if (userBoneToHumanoidMappings.TryGetValue(userBoneName, out humanName)) { + outputJointName = HUMANOID_TO_HIFI_JOINT_NAME[humanName]; + Quaternion rotation = referenceAbsoluteRotations[outputJointName]; + jointOffset = Quaternion.Inverse(userAbsoluteRotations[userBoneName]) * rotation; + } else if (userAbsoluteRotations.ContainsKey(userBoneName)) { + outputJointName = userBoneName; + string lastRequiredParent = FindLastRequiredParentBone(userBoneName); + if (lastRequiredParent == "root") { + jointOffset = Quaternion.Inverse(userAbsoluteRotations[userBoneName]); + } else { + // take the previous offset and multiply it by the current local when we have an extra joint + string lastRequiredParentHifiName = HUMANOID_TO_HIFI_JOINT_NAME[userBoneToHumanoidMappings[lastRequiredParent]]; + Quaternion lastRequiredParentRotation = referenceAbsoluteRotations[lastRequiredParentHifiName]; + jointOffset = Quaternion.Inverse(userAbsoluteRotations[userBoneName]) * lastRequiredParentRotation; + } + } + + // swap from left-handed (Unity) to right-handed (HiFi) coordinates and write out joint rotation offset to fst + if (outputJointName != "") { + jointOffset = new Quaternion(-jointOffset.x, jointOffset.y, jointOffset.z, -jointOffset.w); + File.AppendAllText(exportFstPath, "jointRotationOffset = " + outputJointName + " = (" + jointOffset.x + ", " + + jointOffset.y + ", " + jointOffset.z + ", " + jointOffset.w + ")\n"); + } + } + + // open File Explorer to the project directory once finished + System.Diagnostics.Process.Start("explorer.exe", "/select," + exportFstPath); + } + + static void SetParentNames(Transform modelBone, Dictionary<string, string> parentNames) { + for (int i = 0; i < modelBone.childCount; i++) { + SetParentNames(modelBone.GetChild(i), parentNames); + } + if (modelBone.parent != null) { + parentNames.Add(modelBone.name, modelBone.parent.name); + } else { + parentNames.Add(modelBone.name, "root"); + } + } + + static string FindLastRequiredParentBone(string currentBone) { + string result = currentBone; + while (result != "root" && !userBoneToHumanoidMappings.ContainsKey(result)) { + result = userParentNames[result]; + } + return result; + } +} + +class ExportProjectWindow : EditorWindow { + const int MIN_WIDTH = 450; + const int MIN_HEIGHT = 250; + const int BUTTON_FONT_SIZE = 16; + const int LABEL_FONT_SIZE = 16; + const int TEXT_FIELD_FONT_SIZE = 14; + const int TEXT_FIELD_HEIGHT = 20; + const int ERROR_FONT_SIZE = 12; + + string projectName = ""; + string projectLocation = ""; + string projectDirectory = ""; + string errorLabel = "\n"; + + public delegate void OnCloseDelegate(string projectDirectory, string projectName); + OnCloseDelegate onCloseCallback; + + public void Init(string initialPath, OnCloseDelegate closeCallback) { + minSize = new Vector2(MIN_WIDTH, MIN_HEIGHT); + titleContent.text = "Export New Avatar"; + projectLocation = initialPath; + onCloseCallback = closeCallback; + ShowUtility(); + } + + void OnGUI() { + // define UI styles for all GUI elements to be created + GUIStyle buttonStyle = new GUIStyle(GUI.skin.button); + buttonStyle.fontSize = BUTTON_FONT_SIZE; + GUIStyle labelStyle = new GUIStyle(GUI.skin.label); + labelStyle.fontSize = LABEL_FONT_SIZE; + GUIStyle textStyle = new GUIStyle(GUI.skin.textField); + textStyle.fontSize = TEXT_FIELD_FONT_SIZE; + textStyle.fixedHeight = TEXT_FIELD_HEIGHT; + GUIStyle errorStyle = new GUIStyle(GUI.skin.label); + errorStyle.fontSize = ERROR_FONT_SIZE; + errorStyle.normal.textColor = Color.red; + + GUILayout.Space(10); + + // Project name label and input text field + GUILayout.Label("Export project name:", labelStyle); + projectName = GUILayout.TextField(projectName, textStyle); + + GUILayout.Space(10); + + // Project location label and input text field + GUILayout.Label("Export project location:", labelStyle); + projectLocation = GUILayout.TextField(projectLocation, textStyle); + + // Browse button to open folder explorer that starts at project location path and then updates project location + if (GUILayout.Button("Browse", buttonStyle)) { + string result = EditorUtility.OpenFolderPanel("Select export location", projectLocation, ""); + if (result.Length > 0) { // folder selection not cancelled + projectLocation = result.Replace('/', '\\'); + } + } + + // Red error label text to display any issues under text fields and Browse button + GUILayout.Label(errorLabel, errorStyle); + + GUILayout.Space(20); + + // Export button which will verify project folder can actually be created + // before closing popup window and calling back to initiate the export + bool export = false; + if (GUILayout.Button("Export", buttonStyle)) { + export = true; + if (!CheckForErrors(true)) { + Close(); + onCloseCallback(projectDirectory, projectName); + } + } + + // Cancel button just closes the popup window without callback + if (GUILayout.Button("Cancel", buttonStyle)) { + Close(); + } + + // When either text field changes check for any errors if we didn't just check errors from clicking Export above + if (GUI.changed && !export) { + CheckForErrors(false); + } + } + + bool CheckForErrors(bool exporting) { + errorLabel = "\n"; // default to no error + projectDirectory = projectLocation + "\\" + projectName + "\\"; + if (projectName.Length > 0) { + // new project must have a unique folder name since the folder will be created for it + if (Directory.Exists(projectDirectory)) { + errorLabel = "A folder with the name " + projectName + + " already exists at that location.\nPlease choose a different project name or location."; + return true; + } + } + if (projectLocation.Length > 0) { + // before clicking Export we can verify that the project location at least starts with a drive + if (!Char.IsLetter(projectLocation[0]) || projectLocation.Length == 1 || projectLocation[1] != ':') { + errorLabel = "Project location is invalid. Please choose a different project location.\n"; + return true; + } + } + if (exporting) { + // when exporting, project name and location must both be defined, and project location must + // be valid and accessible (we attempt to create the project folder at this time to verify this) + if (projectName.Length == 0) { + errorLabel = "Please define a project name.\n"; + return true; + } else if (projectLocation.Length == 0) { + errorLabel = "Please define a project location.\n"; + return true; + } else { + try { + Directory.CreateDirectory(projectDirectory); + } catch { + errorLabel = "Project location is invalid. Please choose a different project location.\n"; + return true; + } + } + } + return false; + } } diff --git a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs.meta b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs.meta index c71e4c396d..373aecc6a8 100644 --- a/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs.meta +++ b/tools/unity-avatar-exporter/Assets/Editor/AvatarExporter.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: c7a34be82b3ae554ea097963914b083f +guid: 00403fdc52187214c8418bc0a7f387e2 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/tools/unity-avatar-exporter/Assets/README.txt b/tools/unity-avatar-exporter/Assets/README.txt new file mode 100644 index 0000000000..3ca4dbb1ee --- /dev/null +++ b/tools/unity-avatar-exporter/Assets/README.txt @@ -0,0 +1,19 @@ +Note: It is recommended to use Unity versions between 2017.4.17f1 and 2018.2.12f1 for this Avatar Exporter. + +To create a new avatar project: +1. Import your .fbx avatar model into your Unity project's Assets by either dragging and dropping the file into the Assets window or by using Assets menu > Import New Assets. +2. Select the .fbx avatar that you imported in step 1 in the Assets window, and in the Rig section of the Inspector window set the Animation Type to Humanoid and choose Apply. +3. With the .fbx avatar still selected in the Assets window, choose High Fidelity menu > Export New Avatar. +4. Select a name for your avatar project (this will be used to create a directory with that name), as well as the target location for your project folder. +5. Once it is exported, your project directory will open in File Explorer. + +To update an existing avatar project: +1. Select the existing .fbx avatar in the Assets window that you would like to re-export. +2. Choose High Fidelity menu > Update Existing Avatar and browse to the .fst file you would like to update. +3. If the .fbx file in your Unity Assets folder is newer than the existing .fbx file in your selected avatar project or vice-versa, you will be prompted if you wish to replace the older file with the newer file before performing the update. +4. Once it is updated, your project directory will open in File Explorer. + +* WARNING * +If you are using any external textures as part of your .fbx model, be sure they are copied into the textures folder that is created in the project folder after exporting a new avatar. + +For further details including troubleshooting tips, see the full documentation at https://docs.highfidelity.com/create-and-explore/avatars/create-avatars/unity-extension diff --git a/tools/unity-avatar-exporter/Assets/README.txt.meta b/tools/unity-avatar-exporter/Assets/README.txt.meta new file mode 100644 index 0000000000..148fd21fdd --- /dev/null +++ b/tools/unity-avatar-exporter/Assets/README.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 30b2b6221fd08234eb07c4d6d525d32e +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/tools/unity-avatar-exporter/avatarExporter.unitypackage b/tools/unity-avatar-exporter/avatarExporter.unitypackage index bb25cb4072..28052efea5 100644 Binary files a/tools/unity-avatar-exporter/avatarExporter.unitypackage and b/tools/unity-avatar-exporter/avatarExporter.unitypackage differ diff --git a/tools/unity-avatar-exporter/packager.bat b/tools/unity-avatar-exporter/packager.bat index 99932f1ead..66629783af 100644 --- a/tools/unity-avatar-exporter/packager.bat +++ b/tools/unity-avatar-exporter/packager.bat @@ -1 +1 @@ -"C:\Program Files\Unity\Editor\Unity.exe" -quit -batchmode -projectPath %CD% -exportPackage "Assets/Editor" "avatarExporter.unitypackage" +Unity -quit -batchmode -projectPath %CD% -exportPackage "Assets" "avatarExporter.unitypackage" diff --git a/unpublishedScripts/DomainContent/Toybox/pistol/pistol.js b/unpublishedScripts/DomainContent/Toybox/pistol/pistol.js index b408e4f464..038c53054c 100644 --- a/unpublishedScripts/DomainContent/Toybox/pistol/pistol.js +++ b/unpublishedScripts/DomainContent/Toybox/pistol/pistol.js @@ -137,7 +137,7 @@ direction: this.firingDirection }; this.createGunFireEffect(this.barrelPoint) - var intersection = Entities.findRayIntersectionBlocking(pickRay, true); + var intersection = Entities.findRayIntersection(pickRay, true); if (intersection.intersects) { this.createEntityHitEffect(intersection.intersection); if (Math.random() < this.playRichochetSoundChance) {