diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index e7323bb272..b5faf71bde 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -143,6 +143,8 @@ void AudioMixer::queueReplicatedAudioPacket(QSharedPointer mess rewrittenType = PacketType::InjectAudio; } else if (message->getType() == PacketType::ReplicatedSilentAudioFrame) { rewrittenType = PacketType::SilentAudioFrame; + } else { + return; } auto replicatedMessage = QSharedPointer::create(audioData, rewrittenType, diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index d4d098133d..7641000a7a 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -691,9 +691,10 @@ bool AudioMixerClientData::shouldIgnore(const SharedNodePointer self, const Shar } void AudioMixerClientData::setupCodecForReplicatedAgent(QSharedPointer message) { - // pull the codec string from the packet + // hop past the sequence number that leads the packet message->seek(sizeof(quint16)); + // pull the codec string from the packet auto codecString = message->readString(); qDebug() << "Manually setting codec for replicated agent" << uuidStringWithoutCurlyBraces(getNodeID()) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 4b85e6b667..e9885621db 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -85,7 +85,7 @@ SharedNodePointer addOrUpdateReplicatedNode(const QUuid& nodeID, const HifiSockA void AvatarMixer::handleReplicatedPackets(QSharedPointer message) { auto nodeList = DependencyManager::get(); - auto nodeID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID)); + auto nodeID = QUuid::fromRfc4122(message->peek(NUM_BYTES_RFC4122_UUID)); auto replicatedNode = addOrUpdateReplicatedNode(nodeID, message->getSenderSockAddr()); @@ -516,12 +516,9 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer mes AvatarData& avatar = nodeData->getAvatar(); // parse the identity packet and update the change timestamp if appropriate - AvatarData::Identity identity; - AvatarData::parseAvatarIdentityPacket(message->getMessage(), identity); - bool identityChanged = false; bool displayNameChanged = false; - avatar.processAvatarIdentity(identity, identityChanged, displayNameChanged); + avatar.processAvatarIdentity(message->getMessage(), identityChanged, displayNameChanged); if (identityChanged) { QMutexLocker nodeDataLocker(&nodeData->getMutex()); nodeData->flagIdentityChange(); diff --git a/assignment-client/src/avatars/AvatarMixerSlave.cpp b/assignment-client/src/avatars/AvatarMixerSlave.cpp index 398b28f7a6..95a3a5034d 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlave.cpp @@ -67,7 +67,7 @@ void AvatarMixerSlave::processIncomingPackets(const SharedNodePointer& node) { int AvatarMixerSlave::sendIdentityPacket(const AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode) { if (destinationNode->getType() == NodeType::Agent && !destinationNode->isUpstream()) { - QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray(); + QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray(true); individualData.replace(0, NUM_BYTES_RFC4122_UUID, nodeData->getNodeID().toRfc4122()); // FIXME, this looks suspicious auto identityPackets = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true); identityPackets->write(individualData); @@ -81,7 +81,7 @@ int AvatarMixerSlave::sendIdentityPacket(const AvatarMixerClientData* nodeData, int AvatarMixerSlave::sendReplicatedIdentityPacket(const AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode) { if (destinationNode->getType() == NodeType::DownstreamAvatarMixer) { - QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray(); + QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray(true); individualData.replace(0, NUM_BYTES_RFC4122_UUID, nodeData->getNodeID().toRfc4122()); // FIXME, this looks suspicious auto identityPacket = NLPacket::create(PacketType::ReplicatedAvatarIdentity); identityPacket->write(individualData); @@ -204,216 +204,212 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node) ViewFrustum cameraView = nodeData->getViewFrustom(); std::priority_queue sortedAvatars; AvatarData::sortAvatars(avatarList, cameraView, sortedAvatars, - [&](AvatarSharedPointer avatar)->uint64_t{ - auto avatarNode = avatarDataToNodes[avatar]; - assert(avatarNode); // we can't have gotten here without the avatarData being a valid key in the map - return nodeData->getLastBroadcastTime(avatarNode->getUUID()); - }, + [&](AvatarSharedPointer avatar)->uint64_t { + auto avatarNode = avatarDataToNodes[avatar]; + assert(avatarNode); // we can't have gotten here without the avatarData being a valid key in the map + return nodeData->getLastBroadcastTime(avatarNode->getUUID()); + }, [&](AvatarSharedPointer avatar)->float{ + glm::vec3 nodeBoxHalfScale = (avatar->getPosition() - avatar->getGlobalBoundingBoxCorner()); + return glm::max(nodeBoxHalfScale.x, glm::max(nodeBoxHalfScale.y, nodeBoxHalfScale.z)); + }, [&](AvatarSharedPointer avatar)->bool { + if (avatar == thisAvatar) { + return true; // ignore ourselves... + } - [&](AvatarSharedPointer avatar)->float{ - glm::vec3 nodeBoxHalfScale = (avatar->getPosition() - avatar->getGlobalBoundingBoxCorner()); - return glm::max(nodeBoxHalfScale.x, glm::max(nodeBoxHalfScale.y, nodeBoxHalfScale.z)); - }, + bool shouldIgnore = false; - [&](AvatarSharedPointer avatar)->bool{ - if (avatar == thisAvatar) { - return true; // ignore ourselves... - } + // We will also ignore other nodes for a couple of different reasons: + // 1) ignore bubbles and ignore specific node + // 2) the node hasn't really updated it's frame data recently, this can + // happen if for example the avatar is connected on a desktop and sending + // updates at ~30hz. So every 3 frames we skip a frame. + auto avatarNode = avatarDataToNodes[avatar]; - bool shouldIgnore = false; + assert(avatarNode); // we can't have gotten here without the avatarData being a valid key in the map - // We will also ignore other nodes for a couple of different reasons: - // 1) ignore bubbles and ignore specific node - // 2) the node hasn't really updated it's frame data recently, this can - // happen if for example the avatar is connected on a desktop and sending - // updates at ~30hz. So every 3 frames we skip a frame. - auto avatarNode = avatarDataToNodes[avatar]; + const AvatarMixerClientData* avatarNodeData = reinterpret_cast(avatarNode->getLinkedData()); + assert(avatarNodeData); // we can't have gotten here without avatarNode having valid data + quint64 startIgnoreCalculation = usecTimestampNow(); - assert(avatarNode); // we can't have gotten here without the avatarData being a valid key in the map + // make sure we have data for this avatar, that it isn't the same node, + // and isn't an avatar that the viewing node has ignored + // or that has ignored the viewing node + if (!avatarNode->getLinkedData() + || avatarNode->getUUID() == node->getUUID() + || (node->isIgnoringNodeWithID(avatarNode->getUUID()) && !PALIsOpen) + || (avatarNode->isIgnoringNodeWithID(node->getUUID()) && !getsAnyIgnored)) { + shouldIgnore = true; + } else { - const AvatarMixerClientData* avatarNodeData = reinterpret_cast(avatarNode->getLinkedData()); - assert(avatarNodeData); // we can't have gotten here without avatarNode having valid data - quint64 startIgnoreCalculation = usecTimestampNow(); + // Check to see if the space bubble is enabled + // Don't bother with these checks if the other avatar has their bubble enabled and we're gettingAnyIgnored + if (node->isIgnoreRadiusEnabled() || (avatarNode->isIgnoreRadiusEnabled() && !getsAnyIgnored)) { - // make sure we have data for this avatar, that it isn't the same node, - // and isn't an avatar that the viewing node has ignored - // or that has ignored the viewing node - if (!avatarNode->getLinkedData() - || avatarNode->getUUID() == node->getUUID() - || (node->isIgnoringNodeWithID(avatarNode->getUUID()) && !PALIsOpen) - || (avatarNode->isIgnoringNodeWithID(node->getUUID()) && !getsAnyIgnored)) { - shouldIgnore = true; - } else { - - // Check to see if the space bubble is enabled - // Don't bother with these checks if the other avatar has their bubble enabled and we're gettingAnyIgnored - if (node->isIgnoreRadiusEnabled() || (avatarNode->isIgnoreRadiusEnabled() && !getsAnyIgnored)) { - - // Define the scale of the box for the current other node - glm::vec3 otherNodeBoxScale = (avatarNodeData->getPosition() - avatarNodeData->getGlobalBoundingBoxCorner()) * 2.0f; - // Set up the bounding box for the current other node - AABox otherNodeBox(avatarNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale); - // Clamp the size of the bounding box to a minimum scale - if (glm::any(glm::lessThan(otherNodeBoxScale, minBubbleSize))) { - otherNodeBox.setScaleStayCentered(minBubbleSize); - } - // Quadruple the scale of both bounding boxes - otherNodeBox.embiggen(4.0f); - - // Perform the collision check between the two bounding boxes - if (nodeBox.touches(otherNodeBox)) { - nodeData->ignoreOther(node, avatarNode); - shouldIgnore = !getsAnyIgnored; - } + // Define the scale of the box for the current other node + glm::vec3 otherNodeBoxScale = (avatarNodeData->getPosition() - avatarNodeData->getGlobalBoundingBoxCorner()) * 2.0f; + // Set up the bounding box for the current other node + AABox otherNodeBox(avatarNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale); + // Clamp the size of the bounding box to a minimum scale + if (glm::any(glm::lessThan(otherNodeBoxScale, minBubbleSize))) { + otherNodeBox.setScaleStayCentered(minBubbleSize); } - // Not close enough to ignore - if (!shouldIgnore) { - nodeData->removeFromRadiusIgnoringSet(node, avatarNode->getUUID()); + // Quadruple the scale of both bounding boxes + otherNodeBox.embiggen(4.0f); + + // Perform the collision check between the two bounding boxes + if (nodeBox.touches(otherNodeBox)) { + nodeData->ignoreOther(node, avatarNode); + shouldIgnore = !getsAnyIgnored; } } - quint64 endIgnoreCalculation = usecTimestampNow(); - _stats.ignoreCalculationElapsedTime += (endIgnoreCalculation - startIgnoreCalculation); - + // Not close enough to ignore if (!shouldIgnore) { - AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(avatarNode->getUUID()); - AvatarDataSequenceNumber lastSeqFromSender = avatarNodeData->getLastReceivedSequenceNumber(); - - // FIXME - This code does appear to be working. But it seems brittle. - // It supports determining if the frame of data for this "other" - // avatar has already been sent to the reciever. This has been - // verified to work on a desktop display that renders at 60hz and - // therefore sends to mixer at 30hz. Each second you'd expect to - // have 15 (45hz-30hz) duplicate frames. In this case, the stat - // avg_other_av_skips_per_second does report 15. - // - // make sure we haven't already sent this data from this sender to this receiver - // or that somehow we haven't sent - if (lastSeqToReceiver == lastSeqFromSender && lastSeqToReceiver != 0) { - ++numAvatarsHeldBack; - shouldIgnore = true; - } else if (lastSeqFromSender - lastSeqToReceiver > 1) { - // this is a skip - we still send the packet but capture the presence of the skip so we see it happening - ++numAvatarsWithSkippedFrames; - } + nodeData->removeFromRadiusIgnoringSet(node, avatarNode->getUUID()); } - return shouldIgnore; - }); + } + quint64 endIgnoreCalculation = usecTimestampNow(); + _stats.ignoreCalculationElapsedTime += (endIgnoreCalculation - startIgnoreCalculation); - // loop through our sorted avatars and allocate our bandwidth to them accordingly - int avatarRank = 0; + if (!shouldIgnore) { + AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(avatarNode->getUUID()); + AvatarDataSequenceNumber lastSeqFromSender = avatarNodeData->getLastReceivedSequenceNumber(); - // this is overly conservative, because it includes some avatars we might not consider - int remainingAvatars = (int)sortedAvatars.size(); - - while (!sortedAvatars.empty()) { - AvatarPriority sortData = sortedAvatars.top(); - sortedAvatars.pop(); - const auto& avatarData = sortData.avatar; - avatarRank++; - remainingAvatars--; - - auto otherNode = avatarDataToNodes[avatarData]; - assert(otherNode); // we can't have gotten here without the avatarData being a valid key in the map - - // NOTE: Here's where we determine if we are over budget and drop to bare minimum data - int minimRemainingAvatarBytes = minimumBytesPerAvatar * remainingAvatars; - bool overBudget = (identityBytesSent + numAvatarDataBytes + minimRemainingAvatarBytes) > maxAvatarBytesPerFrame; - - quint64 startAvatarDataPacking = usecTimestampNow(); - - ++numOtherAvatars; - - const AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); - - // If the time that the mixer sent AVATAR DATA about Avatar B to Avatar A is BEFORE OR EQUAL TO - // the time that Avatar B flagged an IDENTITY DATA change, send IDENTITY DATA about Avatar B to Avatar A. - if (nodeData->getLastBroadcastTime(otherNode->getUUID()) <= otherNodeData->getIdentityChangeTimestamp()) { - identityBytesSent += sendIdentityPacket(otherNodeData, node); + // FIXME - This code does appear to be working. But it seems brittle. + // It supports determining if the frame of data for this "other" + // avatar has already been sent to the reciever. This has been + // verified to work on a desktop display that renders at 60hz and + // therefore sends to mixer at 30hz. Each second you'd expect to + // have 15 (45hz-30hz) duplicate frames. In this case, the stat + // avg_other_av_skips_per_second does report 15. + // + // make sure we haven't already sent this data from this sender to this receiver + // or that somehow we haven't sent + if (lastSeqToReceiver == lastSeqFromSender && lastSeqToReceiver != 0) { + ++numAvatarsHeldBack; + shouldIgnore = true; + } else if (lastSeqFromSender - lastSeqToReceiver > 1) { + // this is a skip - we still send the packet but capture the presence of the skip so we see it happening + ++numAvatarsWithSkippedFrames; } + } + return shouldIgnore; + }); - const AvatarData* otherAvatar = otherNodeData->getConstAvatarData(); - glm::vec3 otherPosition = otherAvatar->getClientGlobalPosition(); + // loop through our sorted avatars and allocate our bandwidth to them accordingly + int avatarRank = 0; - // determine if avatar is in view, to determine how much data to include... - glm::vec3 otherNodeBoxScale = (otherPosition - otherNodeData->getGlobalBoundingBoxCorner()) * 2.0f; - AABox otherNodeBox(otherNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale); - bool isInView = nodeData->otherAvatarInView(otherNodeBox); + // this is overly conservative, because it includes some avatars we might not consider + int remainingAvatars = (int)sortedAvatars.size(); - // start a new segment in the PacketList for this avatar - avatarPacketList->startSegment(); + while (!sortedAvatars.empty()) { + AvatarPriority sortData = sortedAvatars.top(); + sortedAvatars.pop(); + const auto& avatarData = sortData.avatar; + avatarRank++; + remainingAvatars--; - AvatarData::AvatarDataDetail detail; + auto otherNode = avatarDataToNodes[avatarData]; + assert(otherNode); // we can't have gotten here without the avatarData being a valid key in the map - if (overBudget) { - overBudgetAvatars++; - _stats.overBudgetAvatars++; - detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::NoData; - } else if (!isInView) { - detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::MinimumData; - nodeData->incrementAvatarOutOfView(); - } else { - detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO - ? AvatarData::SendAllData : AvatarData::CullSmallData; - nodeData->incrementAvatarInView(); - } + // NOTE: Here's where we determine if we are over budget and drop to bare minimum data + int minimRemainingAvatarBytes = minimumBytesPerAvatar * remainingAvatars; + bool overBudget = (identityBytesSent + numAvatarDataBytes + minimRemainingAvatarBytes) > maxAvatarBytesPerFrame; - bool includeThisAvatar = true; - auto lastEncodeForOther = nodeData->getLastOtherAvatarEncodeTime(otherNode->getUUID()); - QVector& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getUUID()); - bool distanceAdjust = true; - glm::vec3 viewerPosition = myPosition; - AvatarDataPacket::HasFlags hasFlagsOut; // the result of the toByteArray - bool dropFaceTracking = false; + quint64 startAvatarDataPacking = usecTimestampNow(); - quint64 start = usecTimestampNow(); - QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, - hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther); - quint64 end = usecTimestampNow(); - _stats.toByteArrayElapsedTime += (end - start); + ++numOtherAvatars; + + const AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); + + // If the time that the mixer sent AVATAR DATA about Avatar B to Avatar A is BEFORE OR EQUAL TO + // the time that Avatar B flagged an IDENTITY DATA change, send IDENTITY DATA about Avatar B to Avatar A. + if (nodeData->getLastBroadcastTime(otherNode->getUUID()) <= otherNodeData->getIdentityChangeTimestamp()) { + identityBytesSent += sendIdentityPacket(otherNodeData, node); + } + + const AvatarData* otherAvatar = otherNodeData->getConstAvatarData(); + glm::vec3 otherPosition = otherAvatar->getClientGlobalPosition(); + + // determine if avatar is in view, to determine how much data to include... + glm::vec3 otherNodeBoxScale = (otherPosition - otherNodeData->getGlobalBoundingBoxCorner()) * 2.0f; + AABox otherNodeBox(otherNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale); + bool isInView = nodeData->otherAvatarInView(otherNodeBox); + + // start a new segment in the PacketList for this avatar + avatarPacketList->startSegment(); + + AvatarData::AvatarDataDetail detail; + + if (overBudget) { + overBudgetAvatars++; + _stats.overBudgetAvatars++; + detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::NoData; + } else if (!isInView) { + detail = PALIsOpen ? AvatarData::PALMinimum : AvatarData::MinimumData; + nodeData->incrementAvatarOutOfView(); + } else { + detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO + ? AvatarData::SendAllData : AvatarData::CullSmallData; + nodeData->incrementAvatarInView(); + } + + bool includeThisAvatar = true; + auto lastEncodeForOther = nodeData->getLastOtherAvatarEncodeTime(otherNode->getUUID()); + QVector& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getUUID()); + bool distanceAdjust = true; + glm::vec3 viewerPosition = myPosition; + AvatarDataPacket::HasFlags hasFlagsOut; // the result of the toByteArray + bool dropFaceTracking = false; + + quint64 start = usecTimestampNow(); + QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, + hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther); + quint64 end = usecTimestampNow(); + _stats.toByteArrayElapsedTime += (end - start); + + static const int MAX_ALLOWED_AVATAR_DATA = (1400 - NUM_BYTES_RFC4122_UUID); + if (bytes.size() > MAX_ALLOWED_AVATAR_DATA) { + qCWarning(avatars) << "otherAvatar.toByteArray() resulted in very large buffer:" << bytes.size() << "... attempt to drop facial data"; + + dropFaceTracking = true; // first try dropping the facial data + bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, + hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther); - static const int MAX_ALLOWED_AVATAR_DATA = (1400 - NUM_BYTES_RFC4122_UUID); if (bytes.size() > MAX_ALLOWED_AVATAR_DATA) { - qCWarning(avatars) << "otherAvatar.toByteArray() resulted in very large buffer:" << bytes.size() << "... attempt to drop facial data"; - - dropFaceTracking = true; // first try dropping the facial data - bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther, + qCWarning(avatars) << "otherAvatar.toByteArray() without facial data resulted in very large buffer:" << bytes.size() << "... reduce to MinimumData"; + bytes = otherAvatar->toByteArray(AvatarData::MinimumData, lastEncodeForOther, lastSentJointsForOther, hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther); - - if (bytes.size() > MAX_ALLOWED_AVATAR_DATA) { - qCWarning(avatars) << "otherAvatar.toByteArray() without facial data resulted in very large buffer:" << bytes.size() << "... reduce to MinimumData"; - bytes = otherAvatar->toByteArray(AvatarData::MinimumData, lastEncodeForOther, lastSentJointsForOther, - hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther); - } - - if (bytes.size() > MAX_ALLOWED_AVATAR_DATA) { - qCWarning(avatars) << "otherAvatar.toByteArray() MinimumData resulted in very large buffer:" << bytes.size() << "... FAIL!!"; - includeThisAvatar = false; - } } - if (includeThisAvatar) { - numAvatarDataBytes += avatarPacketList->write(otherNode->getUUID().toRfc4122()); - numAvatarDataBytes += avatarPacketList->write(bytes); - - if (detail != AvatarData::NoData) { - _stats.numOthersIncluded++; - - // increment the number of avatars sent to this reciever - nodeData->incrementNumAvatarsSentLastFrame(); - - // set the last sent sequence number for this sender on the receiver - nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(), - otherNodeData->getLastReceivedSequenceNumber()); - - // remember the last time we sent details about this other node to the receiver - nodeData->setLastBroadcastTime(otherNode->getUUID(), start); - } + if (bytes.size() > MAX_ALLOWED_AVATAR_DATA) { + qCWarning(avatars) << "otherAvatar.toByteArray() MinimumData resulted in very large buffer:" << bytes.size() << "... FAIL!!"; + includeThisAvatar = false; } + } - avatarPacketList->endSegment(); + if (includeThisAvatar) { + numAvatarDataBytes += avatarPacketList->write(otherNode->getUUID().toRfc4122()); + numAvatarDataBytes += avatarPacketList->write(bytes); - quint64 endAvatarDataPacking = usecTimestampNow(); - _stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); + if (detail != AvatarData::NoData) { + _stats.numOthersIncluded++; + + // increment the number of avatars sent to this reciever + nodeData->incrementNumAvatarsSentLastFrame(); + + // set the last sent sequence number for this sender on the receiver + nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(), + otherNodeData->getLastReceivedSequenceNumber()); + + // remember the last time we sent details about this other node to the receiver + nodeData->setLastBroadcastTime(otherNode->getUUID(), start); + } + } + + avatarPacketList->endSegment(); + + quint64 endAvatarDataPacking = usecTimestampNow(); + _stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking); }; quint64 startPacketSending = usecTimestampNow(); diff --git a/assignment-client/src/avatars/AvatarMixerSlavePool.cpp b/assignment-client/src/avatars/AvatarMixerSlavePool.cpp index 07d4fa8851..cb5ae7735a 100644 --- a/assignment-client/src/avatars/AvatarMixerSlavePool.cpp +++ b/assignment-client/src/avatars/AvatarMixerSlavePool.cpp @@ -76,8 +76,8 @@ void AvatarMixerSlavePool::processIncomingPackets(ConstIter begin, ConstIter end } void AvatarMixerSlavePool::broadcastAvatarData(ConstIter begin, ConstIter end, - p_high_resolution_clock::time_point lastFrameTimestamp, - float maxKbpsPerNode, float throttlingRatio) { + p_high_resolution_clock::time_point lastFrameTimestamp, + float maxKbpsPerNode, float throttlingRatio) { _function = &AvatarMixerSlave::broadcastAvatarData; _configure = [&](AvatarMixerSlave& slave) { slave.configureBroadcast(begin, end, lastFrameTimestamp, maxKbpsPerNode, throttlingRatio); diff --git a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp index d2fef4dfbd..04409b3b21 100644 --- a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp +++ b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp @@ -47,7 +47,7 @@ void OctreeInboundPacketProcessor::resetStats() { _singleSenderStats.clear(); } -unsigned long OctreeInboundPacketProcessor::getMaxWait() const { +uint32_t OctreeInboundPacketProcessor::getMaxWait() const { // calculate time until next sendNackPackets() quint64 nextNackTime = _lastNackTime + TOO_LONG_SINCE_LAST_NACK; quint64 now = usecTimestampNow(); diff --git a/assignment-client/src/octree/OctreeInboundPacketProcessor.h b/assignment-client/src/octree/OctreeInboundPacketProcessor.h index 4611fcada0..a7fa297d24 100644 --- a/assignment-client/src/octree/OctreeInboundPacketProcessor.h +++ b/assignment-client/src/octree/OctreeInboundPacketProcessor.h @@ -80,7 +80,7 @@ protected: virtual void processPacket(QSharedPointer message, SharedNodePointer sendingNode) override; - virtual unsigned long getMaxWait() const override; + virtual uint32_t getMaxWait() const override; virtual void preProcess() override; virtual void midProcess() override; diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 0143414db8..e9327d6a66 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1360,8 +1360,19 @@ { "name": "server_type", "label": "Server Type", + "type": "select", "placeholder": "Audio Mixer", - "can_set": true + "can_set": true, + "options": [ + { + "value": "Audio Mixer", + "label": "Audio Mixer" + }, + { + "value": "Avatar Mixer", + "label": "Avatar Mixer" + } + ] } ] } diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 8066223318..c48096c097 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -223,6 +223,14 @@ $(document).ready(function(){ // set focus to the first input in the new row $target.closest('table').find('tr.inputs input:first').focus(); } + + var tableRows = sibling.parent(); + var tableBody = tableRows.parent(); + + // if theres no more siblings, we should jump to a new row + if (sibling.next().length == 0 && tableRows.nextAll().length == 1) { + tableBody.find("." + Settings.ADD_ROW_BUTTON_CLASS).click(); + } } } else if ($target.is('input')) { @@ -1281,6 +1289,17 @@ function makeTableHiddenInputs(setting, initialValues, categoryValue) { "" + ""; + } else if (col.type === "select") { + html += "" + html += ""; + html += ""; } else { html += " 1 ? "." + key : ""); if (isCheckbox) { - input.attr("name", setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : "")) + input.attr("name", newName) } else { - input.attr("name", setting_name + "[" + row_index + "]" + (num_columns > 1 ? "." + key : "")) + if (isDropdown) { + $(element).children("select").attr("data-hidden-input", newName); + } + input.attr("name", newName); } } else { // because the name of the setting in question requires the key @@ -1435,6 +1459,12 @@ function addTableRow(row) { input.focus(); focusChanged = true; } + + // if we are adding a dropdown, we should go ahead and make its select + // element is visible + if (isDropdown) { + $(element).children("select").attr("style", ""); + } if (isCheckbox) { $(input).find("input").attr("data-changed", "true"); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 24ae106b90..32ffd626e8 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -2322,6 +2322,11 @@ void DomainServer::updateReplicatedNodes() { qDebug() << "Setting node to NOT be replicated:" << otherNode->getUUID(); } else if (!isReplicated && shouldReplicate) { qDebug() << "Setting node to replicated:" << otherNode->getUUID(); + qDebug() << "Setting node to NOT be replicated:" + << otherNode->getPermissions().getVerifiedUserName() << otherNode->getUUID(); + } else if (!isReplicated && shouldReplicate) { + qDebug() << "Setting node to replicated:" + << otherNode->getPermissions().getVerifiedUserName() << otherNode->getUUID(); } otherNode->setIsReplicated(shouldReplicate); } @@ -2330,7 +2335,7 @@ void DomainServer::updateReplicatedNodes() { bool DomainServer::shouldReplicateNode(const Node& node) { QString verifiedUsername = node.getPermissions().getVerifiedUserName(); - // Both he verified username and usernames in _replicatedUsernames are lowercase, so + // Both the verified username and usernames in _replicatedUsernames are lowercase, so // comparisons here are case-insensitive. auto it = find(_replicatedUsernames.cbegin(), _replicatedUsernames.cend(), verifiedUsername); return it != _replicatedUsernames.end() && node.getType() == NodeType::Agent; diff --git a/interface/resources/config/render.json b/interface/resources/config/render.json index 414d94e11e..b5b72d7d07 100644 --- a/interface/resources/config/render.json +++ b/interface/resources/config/render.json @@ -1,14 +1,16 @@ { - "RenderShadowTask": { - "Enabled": { - "enabled": true - } - }, - "RenderDeferredTask": { - "AmbientOcclusion": { + "RenderMainView": { + "RenderShadowTask": { "Enabled": { "enabled": true } + }, + "RenderDeferredTask": { + "AmbientOcclusion": { + "Enabled": { + "enabled": true + } + } } } } diff --git a/interface/resources/html/createGlobalEventBridge.js b/interface/resources/html/createGlobalEventBridge.js index 027d6fe8db..4a0de464c3 100644 --- a/interface/resources/html/createGlobalEventBridge.js +++ b/interface/resources/html/createGlobalEventBridge.js @@ -32,7 +32,7 @@ var EventBridge; var webChannel = new QWebChannel(qt.webChannelTransport, function (channel) { // replace the TempEventBridge with the real one. var tempEventBridge = EventBridge; - EventBridge = channel.objects.eventBridgeWrapper.eventBridge; + EventBridge = channel.objects.eventBridge; tempEventBridge._callbacks.forEach(function (callback) { EventBridge.scriptEventReceived.connect(callback); }); diff --git a/interface/resources/meshes/Jointy3/Jointy3.fbx b/interface/resources/meshes/Jointy3/Jointy3.fbx deleted file mode 100644 index 9df7b17eac..0000000000 Binary files a/interface/resources/meshes/Jointy3/Jointy3.fbx and /dev/null differ diff --git a/interface/resources/meshes/defaultAvatar_full.fst b/interface/resources/meshes/defaultAvatar_full.fst index 3abbaf9ff7..eb8e356196 100644 --- a/interface/resources/meshes/defaultAvatar_full.fst +++ b/interface/resources/meshes/defaultAvatar_full.fst @@ -1,85 +1,89 @@ -name = Jointy3 +name = mannequin type = body+head scale = 1 -filename = Jointy3/Jointy3.fbx -texdir = Jointy3/textures +filename = mannequin/mannequin.baked.fbx +joint = jointEyeLeft = LeftEye +joint = jointRightHand = RightHand +joint = jointHead = Head +joint = jointEyeRight = RightEye +joint = jointLean = Spine joint = jointNeck = Neck joint = jointLeftHand = LeftHand -joint = jointEyeRight = RightEye -joint = jointHead = Head -joint = jointRightHand = RightHand joint = jointRoot = Hips -joint = jointLean = Spine -joint = jointEyeLeft = LeftEye freeJoint = LeftArm freeJoint = LeftForeArm freeJoint = RightArm freeJoint = RightForeArm -jointIndex = RightHand = 17 -jointIndex = LeftHandIndex3 = 56 -jointIndex = Hips = 0 -jointIndex = LeftHandRing2 = 47 -jointIndex = LeftHandThumb3 = 60 -jointIndex = RightShoulder = 14 -jointIndex = RightHandRing1 = 30 -jointIndex = RightHandRing3 = 32 -jointIndex = LeftHandPinky4 = 45 -jointIndex = LeftHandRing1 = 46 -jointIndex = LeftFoot = 8 -jointIndex = RightHandIndex2 = 23 -jointIndex = RightToeBase = 4 -jointIndex = RightHandMiddle4 = 29 -jointIndex = RightHandPinky4 = 37 -jointIndex = LeftToe_End = 10 -jointIndex = RightEye = 66 -jointIndex = RightHandPinky2 = 35 -jointIndex = RightHandRing2 = 31 -jointIndex = LeftHand = 41 -jointIndex = RightToe_End = 5 -jointIndex = LeftEye = 65 -jointIndex = LeftHandThumb2 = 59 -jointIndex = pCylinder73Shape1 = 67 -jointIndex = LeftShoulder = 38 -jointIndex = LeftHandIndex2 = 55 -jointIndex = RightForeArm = 16 -jointIndex = LeftHandMiddle2 = 51 -jointIndex = RightHandRing4 = 33 -jointIndex = LeftLeg = 7 -jointIndex = LeftHandThumb4 = 61 -jointIndex = LeftForeArm = 40 -jointIndex = HeadTop_End = 64 -jointIndex = RightHandPinky1 = 34 -jointIndex = RightHandIndex1 = 22 -jointIndex = LeftHandIndex1 = 54 -jointIndex = RightLeg = 2 -jointIndex = RightHandIndex4 = 25 -jointIndex = Neck = 62 -jointIndex = LeftHandMiddle1 = 50 -jointIndex = RightHandPinky3 = 36 -jointIndex = LeftHandPinky2 = 43 -jointIndex = RightHandMiddle3 = 28 -jointIndex = RightHandThumb4 = 21 -jointIndex = LeftUpLeg = 6 -jointIndex = RightFoot = 3 -jointIndex = LeftHandThumb1 = 58 -jointIndex = LeftArm = 39 -jointIndex = RightHandMiddle1 = 26 -jointIndex = LeftHandRing3 = 48 -jointIndex = LeftHandMiddle4 = 53 -jointIndex = RightUpLeg = 1 -jointIndex = RightHandMiddle2 = 27 -jointIndex = LeftToeBase = 9 -jointIndex = RightHandThumb2 = 19 -jointIndex = Spine2 = 13 -jointIndex = Spine = 11 -jointIndex = LeftHandRing4 = 49 -jointIndex = Head = 63 -jointIndex = LeftHandPinky3 = 44 +bs = EyeBlink_L = blink = 1 +bs = JawOpen = mouth_Open = 1 +bs = LipsFunnel = Oo = 1 +bs = BrowsU_L = brow_Up = 1 +jointIndex = RightHandIndex2 = 27 +jointIndex = LeftHandIndex2 = 51 +jointIndex = RightUpLeg = 6 +jointIndex = RightToe_End = 10 +jointIndex = RightEye = 65 jointIndex = LeftHandPinky1 = 42 -jointIndex = RightHandThumb1 = 18 -jointIndex = LeftHandIndex4 = 57 -jointIndex = LeftHandMiddle3 = 52 -jointIndex = RightHandIndex3 = 24 -jointIndex = Spine1 = 12 +jointIndex = RightHandRing1 = 22 +jointIndex = face = 67 +jointIndex = LeftUpLeg = 1 +jointIndex = LeftHand = 41 +jointIndex = LeftHandMiddle1 = 58 +jointIndex = LeftHandIndex1 = 50 +jointIndex = LeftEye = 64 +jointIndex = RightHandIndex1 = 26 +jointIndex = LeftHandPinky4 = 45 jointIndex = RightArm = 15 -jointIndex = RightHandThumb3 = 20 +jointIndex = LeftShoulder = 38 +jointIndex = RightHandPinky2 = 19 +jointIndex = RightHandThumb1 = 30 +jointIndex = RightForeArm = 16 +jointIndex = LeftHandMiddle3 = 60 +jointIndex = Neck = 62 +jointIndex = LeftHandThumb1 = 54 +jointIndex = RightHandMiddle2 = 35 +jointIndex = LeftHandMiddle4 = 61 +jointIndex = mannequin = 68 +jointIndex = Spine1 = 12 +jointIndex = RightFoot = 8 +jointIndex = RightHand = 17 +jointIndex = LeftHandIndex3 = 52 +jointIndex = RightHandIndex3 = 28 +jointIndex = RightHandMiddle4 = 37 +jointIndex = LeftLeg = 2 +jointIndex = RightHandMiddle1 = 34 +jointIndex = Spine2 = 13 +jointIndex = LeftHandMiddle2 = 59 +jointIndex = LeftHandPinky3 = 44 +jointIndex = LeftHandThumb3 = 56 +jointIndex = LeftHandRing4 = 49 +jointIndex = RightHandThumb2 = 31 +jointIndex = LeftHandRing3 = 48 +jointIndex = HeadTop_End = 66 +jointIndex = LeftHandThumb4 = 57 +jointIndex = RightHandThumb3 = 32 +jointIndex = RightHandPinky1 = 18 +jointIndex = RightLeg = 7 +jointIndex = RightHandMiddle3 = 36 +jointIndex = RightHandPinky3 = 20 +jointIndex = LeftToeBase = 4 +jointIndex = LeftForeArm = 40 +jointIndex = RightShoulder = 14 +jointIndex = LeftHandRing2 = 47 +jointIndex = LeftHandThumb2 = 55 +jointIndex = Head = 63 +jointIndex = RightHandRing4 = 25 +jointIndex = LeftHandRing1 = 46 +jointIndex = LeftFoot = 3 +jointIndex = RightHandRing3 = 24 +jointIndex = RightHandThumb4 = 33 +jointIndex = LeftArm = 39 +jointIndex = LeftToe_End = 5 +jointIndex = RightToeBase = 9 +jointIndex = RightHandPinky4 = 21 +jointIndex = Spine = 11 +jointIndex = LeftHandIndex4 = 53 +jointIndex = LeftHandPinky2 = 43 +jointIndex = RightHandIndex4 = 29 +jointIndex = Hips = 0 +jointIndex = RightHandRing2 = 23 diff --git a/interface/resources/meshes/mannequin/Eyes.ktx b/interface/resources/meshes/mannequin/Eyes.ktx new file mode 100755 index 0000000000..4da922936b Binary files /dev/null and b/interface/resources/meshes/mannequin/Eyes.ktx differ diff --git a/interface/resources/meshes/mannequin/lambert1_Base_Color.ktx b/interface/resources/meshes/mannequin/lambert1_Base_Color.ktx new file mode 100755 index 0000000000..fcca382445 Binary files /dev/null and b/interface/resources/meshes/mannequin/lambert1_Base_Color.ktx differ diff --git a/interface/resources/meshes/mannequin/lambert1_Normal_OpenGL.ktx b/interface/resources/meshes/mannequin/lambert1_Normal_OpenGL.ktx new file mode 100755 index 0000000000..3ae717c5d7 Binary files /dev/null and b/interface/resources/meshes/mannequin/lambert1_Normal_OpenGL.ktx differ diff --git a/interface/resources/meshes/mannequin/lambert1_Roughness.ktx b/interface/resources/meshes/mannequin/lambert1_Roughness.ktx new file mode 100755 index 0000000000..fe9b42a54b Binary files /dev/null and b/interface/resources/meshes/mannequin/lambert1_Roughness.ktx differ diff --git a/interface/resources/meshes/mannequin/mannequin.baked.fbx b/interface/resources/meshes/mannequin/mannequin.baked.fbx new file mode 100755 index 0000000000..b405b4cfbb Binary files /dev/null and b/interface/resources/meshes/mannequin/mannequin.baked.fbx differ diff --git a/interface/resources/qml/Browser.qml b/interface/resources/qml/Browser.qml index 4f7639dd0e..55927fda24 100644 --- a/interface/resources/qml/Browser.qml +++ b/interface/resources/qml/Browser.qml @@ -21,8 +21,6 @@ ScrollingWindow { property alias url: webview.url property alias webView: webview - property alias eventBridge: eventBridgeWrapper.eventBridge - signal loadingChanged(int status) x: 100 @@ -210,17 +208,6 @@ ScrollingWindow { url: "https://highfidelity.com/" profile: FileTypeProfile; - property alias eventBridgeWrapper: eventBridgeWrapper - - QtObject { - id: eventBridgeWrapper - WebChannel.id: "eventBridgeWrapper" - property var eventBridge; - } - - - webChannel.registeredObjects: [eventBridgeWrapper] - // Create a global EventBridge object for raiseAndLowerKeyboard. WebEngineScript { id: createGlobalEventBridge @@ -267,6 +254,8 @@ ScrollingWindow { } Component.onCompleted: { + webChannel.registerObject("eventBridge", eventBridge); + webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); desktop.initWebviewProfileHandlers(webview.profile); } } diff --git a/interface/resources/qml/QmlWebWindow.qml b/interface/resources/qml/QmlWebWindow.qml index d40b1595ba..d2daf0fa1d 100644 --- a/interface/resources/qml/QmlWebWindow.qml +++ b/interface/resources/qml/QmlWebWindow.qml @@ -26,15 +26,8 @@ Windows.ScrollingWindow { // Don't destroy on close... otherwise the JS/C++ will have a dangling pointer destroyOnCloseButton: false property alias source: webview.url - property alias eventBridge: eventBridgeWrapper.eventBridge; property alias scriptUrl: webview.userScriptUrl - QtObject { - id: eventBridgeWrapper - WebChannel.id: "eventBridgeWrapper" - property var eventBridge; - } - // This is for JS/QML communication, which is unused in a WebWindow, // but not having this here results in spurious warnings about a // missing signal @@ -70,7 +63,6 @@ Windows.ScrollingWindow { url: "about:blank" anchors.fill: parent focus: true - webChannel.registeredObjects: [eventBridgeWrapper] property string userScriptUrl: "" @@ -107,6 +99,8 @@ Windows.ScrollingWindow { } Component.onCompleted: { + webChannel.registerObject("eventBridge", eventBridge); + webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); eventBridge.webEventReceived.connect(onWebEventReceived); } } diff --git a/interface/resources/qml/QmlWindow.qml b/interface/resources/qml/QmlWindow.qml index ac18d36ce6..9ef151b32e 100644 --- a/interface/resources/qml/QmlWindow.qml +++ b/interface/resources/qml/QmlWindow.qml @@ -30,15 +30,6 @@ Windows.Window { property bool keyboardRaised: false property bool punctuationMode: false - // JavaScript event bridge object in case QML content includes Web content. - property alias eventBridge: eventBridgeWrapper.eventBridge; - - QtObject { - id: eventBridgeWrapper - WebChannel.id: "eventBridgeWrapper" - property var eventBridge; - } - onSourceChanged: { if (dynamicContent) { dynamicContent.destroy(); diff --git a/interface/resources/qml/TabletBrowser.qml b/interface/resources/qml/TabletBrowser.qml index d89aa8626f..c3d879c513 100644 --- a/interface/resources/qml/TabletBrowser.qml +++ b/interface/resources/qml/TabletBrowser.qml @@ -18,7 +18,6 @@ Item { property variant permissionsBar: {'securityOrigin':'none','feature':'none'} property alias url: webview.url property WebEngineView webView: webview - property alias eventBridge: eventBridgeWrapper.eventBridge property bool canGoBack: webview.canGoBack property bool canGoForward: webview.canGoForward @@ -32,12 +31,6 @@ Item { webview.profile = profile; } - QtObject { - id: eventBridgeWrapper - WebChannel.id: "eventBridgeWrapper" - property var eventBridge; - } - WebEngineView { id: webview objectName: "webEngineView" @@ -78,9 +71,10 @@ Item { property string newUrl: "" - webChannel.registeredObjects: [eventBridgeWrapper] - Component.onCompleted: { + webChannel.registerObject("eventBridge", eventBridge); + webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); + // Ensure the JS from the web-engine makes it to our logging webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); diff --git a/interface/resources/qml/ToolWindow.qml b/interface/resources/qml/ToolWindow.qml index 9c0b0a8c1a..b1120058f9 100644 --- a/interface/resources/qml/ToolWindow.qml +++ b/interface/resources/qml/ToolWindow.qml @@ -79,15 +79,11 @@ ScrollingWindow { id: webView anchors.fill: parent enabled: false - property alias eventBridgeWrapper: eventBridgeWrapper - - QtObject { - id: eventBridgeWrapper - WebChannel.id: "eventBridgeWrapper" - property var eventBridge + Component.onCompleted: { + webChannel.registerObject("eventBridge", eventBridge); + webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); } - webChannel.registeredObjects: [eventBridgeWrapper] onEnabledChanged: toolWindow.updateVisiblity() } } @@ -251,12 +247,9 @@ ScrollingWindow { tab.enabled = true; tab.originalUrl = properties.source; - var eventBridge = properties.eventBridge; - var result = tab.item; result.enabled = true; tabView.tabCount++; - result.eventBridgeWrapper.eventBridge = eventBridge; result.url = properties.source; return result; } diff --git a/interface/resources/qml/controls/TabletWebScreen.qml b/interface/resources/qml/controls/TabletWebScreen.qml index 0b265f6fbb..68f8226e21 100644 --- a/interface/resources/qml/controls/TabletWebScreen.qml +++ b/interface/resources/qml/controls/TabletWebScreen.qml @@ -6,7 +6,6 @@ import "../controls-uit" as HiFiControls Item { property alias url: root.url property alias scriptURL: root.userScriptUrl - property alias eventBridge: eventBridgeWrapper.eventBridge property alias canGoBack: root.canGoBack; property var goBack: root.goBack; property alias urlTag: root.urlTag @@ -22,12 +21,6 @@ Item { } */ - QtObject { - id: eventBridgeWrapper - WebChannel.id: "eventBridgeWrapper" - property var eventBridge; - } - property alias viewProfile: root.profile WebEngineView { @@ -71,10 +64,11 @@ Item { userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ] property string newUrl: "" - - webChannel.registeredObjects: [eventBridgeWrapper] + Component.onCompleted: { + webChannel.registerObject("eventBridge", eventBridge); + webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); // Ensure the JS from the web-engine makes it to our logging root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); diff --git a/interface/resources/qml/controls/TabletWebView.qml b/interface/resources/qml/controls/TabletWebView.qml index 3b23cbc19e..0a5a68717e 100644 --- a/interface/resources/qml/controls/TabletWebView.qml +++ b/interface/resources/qml/controls/TabletWebView.qml @@ -17,7 +17,6 @@ Item { property int headerHeight: 70 property string url property string scriptURL - property alias eventBridge: eventBridgeWrapper.eventBridge property bool keyboardEnabled: false property bool keyboardRaised: false property bool punctuationMode: false @@ -135,12 +134,6 @@ Item { loadUrl(url); } - QtObject { - id: eventBridgeWrapper - WebChannel.id: "eventBridgeWrapper" - property var eventBridge; - } - WebEngineView { id: webview objectName: "webEngineView" @@ -182,9 +175,9 @@ Item { property string newUrl: "" - webChannel.registeredObjects: [eventBridgeWrapper] - Component.onCompleted: { + webChannel.registerObject("eventBridge", eventBridge); + webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); // Ensure the JS from the web-engine makes it to our logging webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index d08562eea3..38136c7eec 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -6,7 +6,6 @@ import "../controls-uit" as HiFiControls Item { property alias url: root.url property alias scriptURL: root.userScriptUrl - property alias eventBridge: eventBridgeWrapper.eventBridge property alias canGoBack: root.canGoBack; property var goBack: root.goBack; property alias urlTag: root.urlTag @@ -22,12 +21,6 @@ Item { } */ - QtObject { - id: eventBridgeWrapper - WebChannel.id: "eventBridgeWrapper" - property var eventBridge; - } - property alias viewProfile: root.profile WebEngineView { @@ -72,9 +65,9 @@ Item { property string newUrl: "" - webChannel.registeredObjects: [eventBridgeWrapper] - Component.onCompleted: { + webChannel.registerObject("eventBridge", eventBridge); + webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); // Ensure the JS from the web-engine makes it to our logging root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message); diff --git a/interface/resources/qml/dialogs/TabletLoginDialog.qml b/interface/resources/qml/dialogs/TabletLoginDialog.qml index 30c3e678b4..36ca480b24 100644 --- a/interface/resources/qml/dialogs/TabletLoginDialog.qml +++ b/interface/resources/qml/dialogs/TabletLoginDialog.qml @@ -20,7 +20,6 @@ TabletModalWindow { id: loginDialogRoot objectName: "LoginDialog" - property var eventBridge; signal sendToScript(var message); property bool isHMD: false property bool gotoPreviousApp: false; diff --git a/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml b/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml index 652e02b6b9..e2a012ad46 100644 --- a/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml +++ b/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml @@ -24,8 +24,6 @@ Window { resizable: true modality: Qt.ApplicationModal - property alias eventBridge: eventBridgeWrapper.eventBridge - Item { anchors.fill: parent @@ -45,16 +43,6 @@ Window { bottom: keyboard.top } - property alias eventBridgeWrapper: eventBridgeWrapper - - QtObject { - id: eventBridgeWrapper - WebChannel.id: "eventBridgeWrapper" - property var eventBridge; - } - - webChannel.registeredObjects: [eventBridgeWrapper] - // Create a global EventBridge object for raiseAndLowerKeyboard. WebEngineScript { id: createGlobalEventBridge @@ -73,6 +61,10 @@ Window { userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ] + Component.onCompleted: { + webChannel.registerObject("eventBridge", eventBridge); + webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); + } } Keyboard { diff --git a/interface/resources/qml/dialogs/preferences/AvatarPreference.qml b/interface/resources/qml/dialogs/preferences/AvatarPreference.qml index bcc5a1d9e6..b27827d9d7 100644 --- a/interface/resources/qml/dialogs/preferences/AvatarPreference.qml +++ b/interface/resources/qml/dialogs/preferences/AvatarPreference.qml @@ -116,9 +116,7 @@ Preference { Component { id: tabletAvatarBrowserBuilder; - TabletAvatarBrowser { - eventBridge: tabletRoot.eventBridge - } + TabletAvatarBrowser { } } } diff --git a/interface/resources/qml/hifi/Audio.qml b/interface/resources/qml/hifi/Audio.qml index 46cec99e69..48de891733 100644 --- a/interface/resources/qml/hifi/Audio.qml +++ b/interface/resources/qml/hifi/Audio.qml @@ -31,7 +31,6 @@ Rectangle { HifiConstants { id: hifi; } objectName: "AudioWindow" - property var eventBridge; property string title: "Audio Options" signal sendToScript(var message); diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index 2d6b21b219..bbb42e61ac 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -44,7 +44,6 @@ Rectangle { property var activeTab: "nearbyTab"; property bool currentlyEditingDisplayName: false property bool punctuationMode: false; - property var eventBridge; HifiConstants { id: hifi; } @@ -129,7 +128,7 @@ Rectangle { pal.sendToScript({method: 'refreshNearby', params: params}); } - Item { + Rectangle { id: palTabContainer; // Anchors anchors { @@ -138,6 +137,7 @@ Rectangle { left: parent.left; right: parent.right; } + color: "white"; Rectangle { id: tabSelectorContainer; // Anchors @@ -1043,7 +1043,6 @@ Rectangle { } // Keyboard HifiControls.TabletWebView { - eventBridge: pal.eventBridge; id: userInfoViewer; anchors { top: parent.top; diff --git a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml index 6e0263787b..47c9af1f57 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAssetServer.qml @@ -24,7 +24,6 @@ Rectangle { property string title: "Asset Browser" property bool keyboardRaised: false - property var eventBridge; signal sendToScript(var message); property bool isHMD: false diff --git a/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml b/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml index b33b957e15..0f363d1be9 100644 --- a/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml +++ b/interface/resources/qml/hifi/dialogs/TabletDCDialog.qml @@ -20,7 +20,6 @@ Rectangle { id: root objectName: "DCConectionTiming" - property var eventBridge; signal sendToScript(var message); property bool isHMD: false diff --git a/interface/resources/qml/hifi/dialogs/TabletDebugWindow.qml b/interface/resources/qml/hifi/dialogs/TabletDebugWindow.qml index d4bbe0af04..22e9dc07a2 100644 --- a/interface/resources/qml/hifi/dialogs/TabletDebugWindow.qml +++ b/interface/resources/qml/hifi/dialogs/TabletDebugWindow.qml @@ -19,7 +19,6 @@ Rectangle { id: root objectName: "DebugWindow" - property var eventBridge; property var title: "Debug Window" property bool isHMD: false diff --git a/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml b/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml index 35ee58be0c..da295917a0 100644 --- a/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml +++ b/interface/resources/qml/hifi/dialogs/TabletEntityStatistics.qml @@ -20,7 +20,6 @@ Rectangle { id: root objectName: "EntityStatistics" - property var eventBridge; signal sendToScript(var message); property bool isHMD: false diff --git a/interface/resources/qml/hifi/dialogs/TabletLODTools.qml b/interface/resources/qml/hifi/dialogs/TabletLODTools.qml index 26e9759e0d..2291a42bf6 100644 --- a/interface/resources/qml/hifi/dialogs/TabletLODTools.qml +++ b/interface/resources/qml/hifi/dialogs/TabletLODTools.qml @@ -20,7 +20,6 @@ Rectangle { id: root objectName: "LODTools" - property var eventBridge; signal sendToScript(var message); property bool isHMD: false diff --git a/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml b/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml index d826b40ad1..11643ae1f1 100644 --- a/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml +++ b/interface/resources/qml/hifi/dialogs/TabletRunningScripts.qml @@ -23,7 +23,6 @@ Rectangle { property string title: "Running Scripts" HifiConstants { id: hifi } signal sendToScript(var message); - property var eventBridge; property var scripts: ScriptDiscoveryService; property var scriptsModel: scripts.scriptsModelFilter property var runningScriptsModel: ListModel { } diff --git a/interface/resources/qml/hifi/tablet/Edit.qml b/interface/resources/qml/hifi/tablet/Edit.qml index ea31eb26d8..e2e8c4362e 100644 --- a/interface/resources/qml/hifi/tablet/Edit.qml +++ b/interface/resources/qml/hifi/tablet/Edit.qml @@ -7,14 +7,12 @@ StackView { objectName: "stack" initialItem: Qt.resolvedUrl('EditTabView.qml') - property var eventBridge; signal sendToScript(var message); HifiConstants { id: hifi } function pushSource(path) { editRoot.push(Qt.resolvedUrl(path)); - editRoot.currentItem.eventBridge = editRoot.eventBridge; editRoot.currentItem.sendToScript.connect(editRoot.sendToScript); } diff --git a/interface/resources/qml/hifi/tablet/EditTabView.qml b/interface/resources/qml/hifi/tablet/EditTabView.qml index d084f1c7b3..e4a20a0316 100644 --- a/interface/resources/qml/hifi/tablet/EditTabView.qml +++ b/interface/resources/qml/hifi/tablet/EditTabView.qml @@ -181,7 +181,6 @@ TabView { WebView { id: entityListToolWebView url: "../../../../../scripts/system/html/entityList.html" - eventBridge: editRoot.eventBridge anchors.fill: parent enabled: true } @@ -196,7 +195,6 @@ TabView { WebView { id: entityPropertiesWebView url: "../../../../../scripts/system/html/entityProperties.html" - eventBridge: editRoot.eventBridge anchors.fill: parent enabled: true } @@ -211,7 +209,6 @@ TabView { WebView { id: gridControlsWebView url: "../../../../../scripts/system/html/gridControls.html" - eventBridge: editRoot.eventBridge anchors.fill: parent enabled: true } @@ -226,7 +223,6 @@ TabView { WebView { id: particleExplorerWebView url: "../../../../../scripts/system/particle_explorer/particleExplorer.html" - eventBridge: editRoot.eventBridge anchors.fill: parent enabled: true } @@ -289,7 +285,7 @@ TabView { editTabView.currentIndex = id; } else { console.warn('Attempt to switch to invalid tab:', id); - } + } } else if (typeof id === 'string'){ switch (id.toLowerCase()) { case 'create': diff --git a/interface/resources/qml/hifi/tablet/InputRecorder.qml b/interface/resources/qml/hifi/tablet/InputRecorder.qml index 76b122d07d..292deb751e 100644 --- a/interface/resources/qml/hifi/tablet/InputRecorder.qml +++ b/interface/resources/qml/hifi/tablet/InputRecorder.qml @@ -18,7 +18,6 @@ import "../../dialogs" Rectangle { id: inputRecorder - property var eventBridge; HifiConstants { id: hifi } signal sendToScript(var message); color: hifi.colors.baseGray; diff --git a/interface/resources/qml/hifi/tablet/NewModelDialog.qml b/interface/resources/qml/hifi/tablet/NewModelDialog.qml index 2d9d121209..5040c043f4 100644 --- a/interface/resources/qml/hifi/tablet/NewModelDialog.qml +++ b/interface/resources/qml/hifi/tablet/NewModelDialog.qml @@ -20,7 +20,6 @@ Rectangle { // height: parent.height HifiConstants { id: hifi } color: hifi.colors.baseGray; - property var eventBridge; signal sendToScript(var message); property bool keyboardEnabled: false property bool punctuationMode: false diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 7159b078ee..073f143dbe 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -29,7 +29,6 @@ StackView { initialItem: addressBarDialog width: parent !== null ? parent.width : undefined height: parent !== null ? parent.height : undefined - property var eventBridge; property int cardWidth: 212; property int cardHeight: 152; property string metaverseBase: addressBarDialog.metaverseServerUrl + "/api/v1/"; @@ -80,7 +79,6 @@ StackView { var card = tabletWebView.createObject(); card.url = addressBarDialog.metaverseServerUrl + targetString; card.parentStackItem = root; - card.eventBridge = root.eventBridge; root.push(card); return; } diff --git a/interface/resources/qml/hifi/tablet/TabletAttachmentsDialog.qml b/interface/resources/qml/hifi/tablet/TabletAttachmentsDialog.qml index 634c9d41ec..19548365aa 100644 --- a/interface/resources/qml/hifi/tablet/TabletAttachmentsDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAttachmentsDialog.qml @@ -25,7 +25,6 @@ Item { property bool keyboardRaised: false property bool punctuationMode: false - property var eventBridge; signal sendToScript(var message); anchors.fill: parent diff --git a/interface/resources/qml/hifi/tablet/TabletAudioPreferences.qml b/interface/resources/qml/hifi/tablet/TabletAudioPreferences.qml index 2046071e4c..b30e741be9 100644 --- a/interface/resources/qml/hifi/tablet/TabletAudioPreferences.qml +++ b/interface/resources/qml/hifi/tablet/TabletAudioPreferences.qml @@ -19,7 +19,6 @@ StackView { objectName: "stack" property string title: "Audio Settings" - property var eventBridge; signal sendToScript(var message); function pushSource(path) { diff --git a/interface/resources/qml/hifi/tablet/TabletAvatarPreferences.qml b/interface/resources/qml/hifi/tablet/TabletAvatarPreferences.qml index 75973f32ae..94fb29c6a1 100644 --- a/interface/resources/qml/hifi/tablet/TabletAvatarPreferences.qml +++ b/interface/resources/qml/hifi/tablet/TabletAvatarPreferences.qml @@ -19,7 +19,6 @@ StackView { objectName: "stack" property string title: "Avatar Settings" - property var eventBridge; signal sendToScript(var message); function pushSource(path) { diff --git a/interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml b/interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml index 17d3f1b959..fe043f6ac7 100644 --- a/interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml +++ b/interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml @@ -19,7 +19,6 @@ StackView { objectName: "stack" property string title: "General Settings" property alias gotoPreviousApp: root.gotoPreviousApp; - property var eventBridge; signal sendToScript(var message); function pushSource(path) { diff --git a/interface/resources/qml/hifi/tablet/TabletGraphicsPreferences.qml b/interface/resources/qml/hifi/tablet/TabletGraphicsPreferences.qml index 95ee2c3a72..25b5be05f2 100644 --- a/interface/resources/qml/hifi/tablet/TabletGraphicsPreferences.qml +++ b/interface/resources/qml/hifi/tablet/TabletGraphicsPreferences.qml @@ -19,7 +19,6 @@ StackView { objectName: "stack" property string title: "Graphics Settings" - property var eventBridge; signal sendToScript(var message); function pushSource(path) { diff --git a/interface/resources/qml/hifi/tablet/TabletLodPreferences.qml b/interface/resources/qml/hifi/tablet/TabletLodPreferences.qml index 6f38fee8b9..b502c26245 100644 --- a/interface/resources/qml/hifi/tablet/TabletLodPreferences.qml +++ b/interface/resources/qml/hifi/tablet/TabletLodPreferences.qml @@ -19,7 +19,6 @@ StackView { objectName: "stack" property string title: "LOD Settings" - property var eventBridge; signal sendToScript(var message); function pushSource(path) { diff --git a/interface/resources/qml/hifi/tablet/TabletMenu.qml b/interface/resources/qml/hifi/tablet/TabletMenu.qml index 62b61d129b..457fe84c3a 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenu.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenu.qml @@ -21,7 +21,6 @@ FocusScope { property var point: Qt.point(50, 50); TabletMenuStack { id: menuPopperUpper } property string subMenu: "" - property var eventBridge; signal sendToScript(var message); Rectangle { diff --git a/interface/resources/qml/hifi/tablet/TabletMenuStack.qml b/interface/resources/qml/hifi/tablet/TabletMenuStack.qml index bacc11228e..2fd33e9cbc 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuStack.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuStack.qml @@ -49,7 +49,6 @@ Item { function pushSource(path) { d.push(Qt.resolvedUrl(path)); - d.currentItem.eventBridge = tabletMenu.eventBridge d.currentItem.sendToScript.connect(tabletMenu.sendToScript); d.currentItem.focus = true; d.currentItem.forceActiveFocus(); diff --git a/interface/resources/qml/hifi/tablet/TabletNetworkingPreferences.qml b/interface/resources/qml/hifi/tablet/TabletNetworkingPreferences.qml index 7184d91044..91d6140fc3 100644 --- a/interface/resources/qml/hifi/tablet/TabletNetworkingPreferences.qml +++ b/interface/resources/qml/hifi/tablet/TabletNetworkingPreferences.qml @@ -19,7 +19,6 @@ StackView { objectName: "stack" property var title: "Networking Settings" - property var eventBridge; signal sendToScript(var message); function pushSource(path) { diff --git a/interface/resources/qml/hifi/tablet/TabletRoot.qml b/interface/resources/qml/hifi/tablet/TabletRoot.qml index e7654d9ff1..b6cdce0853 100644 --- a/interface/resources/qml/hifi/tablet/TabletRoot.qml +++ b/interface/resources/qml/hifi/tablet/TabletRoot.qml @@ -8,7 +8,6 @@ Item { id: tabletRoot objectName: "tabletRoot" property string username: "Unknown user" - property var eventBridge; property var rootMenu; property var openModal: null; property var openMessage: null; @@ -111,7 +110,6 @@ Item { function openBrowserWindow(request, profile) { var component = Qt.createComponent("../../controls/TabletWebView.qml"); var newWindow = component.createObject(tabletRoot); - newWindow.eventBridge = tabletRoot.eventBridge; newWindow.remove = true; newWindow.profile = profile; request.openIn(newWindow.webView); @@ -175,7 +173,7 @@ Item { // Hook up callback for clara.io download from the marketplace. Connections { id: eventBridgeConnection - target: null + target: eventBridge onWebEventReceived: { if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") { ApplicationInterface.addAssetToWorldFromURL(message.slice(18)); @@ -184,10 +182,6 @@ Item { } onLoaded: { - if (loader.item.hasOwnProperty("eventBridge")) { - loader.item.eventBridge = eventBridge; - eventBridgeConnection.target = eventBridge - } if (loader.item.hasOwnProperty("sendToScript")) { loader.item.sendToScript.connect(tabletRoot.sendToScript); } diff --git a/interface/resources/qml/hifi/tablet/WindowRoot.qml b/interface/resources/qml/hifi/tablet/WindowRoot.qml index ee8dbbff59..12f302d60a 100644 --- a/interface/resources/qml/hifi/tablet/WindowRoot.qml +++ b/interface/resources/qml/hifi/tablet/WindowRoot.qml @@ -18,7 +18,6 @@ Windows.ScrollingWindow { id: tabletRoot objectName: "tabletRoot" property string username: "Unknown user" - property var eventBridge; property var rootMenu; property string subMenu: "" @@ -93,7 +92,7 @@ Windows.ScrollingWindow { // Hook up callback for clara.io download from the marketplace. Connections { id: eventBridgeConnection - target: null + target: eventBridge onWebEventReceived: { if (message.slice(0, 17) === "CLARA.IO DOWNLOAD") { ApplicationInterface.addAssetToWorldFromURL(message.slice(18)); @@ -102,10 +101,6 @@ Windows.ScrollingWindow { } onLoaded: { - if (loader.item.hasOwnProperty("eventBridge")) { - loader.item.eventBridge = eventBridge; - eventBridgeConnection.target = eventBridge - } if (loader.item.hasOwnProperty("sendToScript")) { loader.item.sendToScript.connect(tabletRoot.sendToScript); } diff --git a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/TabletAvatarBrowser.qml b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/TabletAvatarBrowser.qml index 029cf7d46b..cab76bf818 100644 --- a/interface/resources/qml/hifi/tablet/tabletWindows/preferences/TabletAvatarBrowser.qml +++ b/interface/resources/qml/hifi/tablet/tabletWindows/preferences/TabletAvatarBrowser.qml @@ -27,8 +27,6 @@ Item { property bool keyboardRaised: false property bool punctuationMode: false - property alias eventBridge: eventBridgeWrapper.eventBridge - anchors.fill: parent BaseWebView { @@ -43,14 +41,6 @@ Item { bottom: footer.top } - QtObject { - id: eventBridgeWrapper - WebChannel.id: "eventBridgeWrapper" - property var eventBridge; - } - - webChannel.registeredObjects: [eventBridgeWrapper] - // Create a global EventBridge object for raiseAndLowerKeyboard. WebEngineScript { id: createGlobalEventBridge @@ -68,6 +58,11 @@ Item { } userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ] + + Component.onCompleted: { + webChannel.registerObject("eventBridge", eventBridge); + webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); + } } Rectangle { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 890b5cb455..9ce6cc9b25 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -114,6 +114,7 @@ #include #include #include +#include #include #include #include @@ -1866,15 +1867,9 @@ void Application::initializeGL() { // Set up the render engine render::CullFunctor cullFunctor = LODManager::shouldRender; - _renderEngine->addJob("RenderShadowTask", cullFunctor); - const auto items = _renderEngine->addJob("FetchCullSort", cullFunctor); - assert(items.canCast()); static const QString RENDER_FORWARD = "HIFI_RENDER_FORWARD"; - if (QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD)) { - _renderEngine->addJob("Forward", items); - } else { - _renderEngine->addJob("RenderDeferredTask", items); - } + bool isDeferred = !QProcessEnvironment::systemEnvironment().contains(RENDER_FORWARD); + _renderEngine->addJob("RenderMainView", cullFunctor, isDeferred); _renderEngine->load(); _renderEngine->registerScene(_main3DScene); @@ -5076,7 +5071,7 @@ namespace render { template <> const ItemKey payloadGetKey(const WorldBoxRenderData::Pointer& stuff) { return ItemKey::Builder::opaqueShape(); } template <> const Item::Bound payloadGetBound(const WorldBoxRenderData::Pointer& stuff) { return Item::Bound(); } template <> void payloadRender(const WorldBoxRenderData::Pointer& stuff, RenderArgs* args) { - if (args->_renderMode != RenderArgs::MIRROR_RENDER_MODE && Menu::getInstance()->isOptionChecked(MenuOption::WorldAxes)) { + if (Menu::getInstance()->isOptionChecked(MenuOption::WorldAxes)) { PerformanceTimer perfTimer("worldBox"); auto& batch = *args->_batch; diff --git a/interface/src/CrashHandler.cpp b/interface/src/CrashHandler.cpp index 19e887eb6e..f7d71de3fe 100644 --- a/interface/src/CrashHandler.cpp +++ b/interface/src/CrashHandler.cpp @@ -73,7 +73,7 @@ CrashHandler::Action CrashHandler::promptUserForAction(bool showCrashMessage) { layout->addWidget(label); QRadioButton* option1 = new QRadioButton("Reset all my settings"); - QRadioButton* option2 = new QRadioButton("Reset my settings but retain avatar info."); + QRadioButton* option2 = new QRadioButton("Reset my settings but keep essential info"); QRadioButton* option3 = new QRadioButton("Continue with my current settings"); option3->setChecked(true); layout->addWidget(option1); @@ -95,7 +95,7 @@ CrashHandler::Action CrashHandler::promptUserForAction(bool showCrashMessage) { return CrashHandler::DELETE_INTERFACE_INI; } if (option2->isChecked()) { - return CrashHandler::RETAIN_AVATAR_INFO; + return CrashHandler::RETAIN_IMPORTANT_INFO; } } @@ -104,7 +104,7 @@ CrashHandler::Action CrashHandler::promptUserForAction(bool showCrashMessage) { } void CrashHandler::handleCrash(CrashHandler::Action action) { - if (action != CrashHandler::DELETE_INTERFACE_INI && action != CrashHandler::RETAIN_AVATAR_INFO) { + if (action != CrashHandler::DELETE_INTERFACE_INI && action != CrashHandler::RETAIN_IMPORTANT_INFO) { // CrashHandler::DO_NOTHING or unexpected value return; } @@ -116,12 +116,15 @@ void CrashHandler::handleCrash(CrashHandler::Action action) { const QString DISPLAY_NAME_KEY = "displayName"; const QString FULL_AVATAR_URL_KEY = "fullAvatarURL"; const QString FULL_AVATAR_MODEL_NAME_KEY = "fullAvatarModelName"; + const QString TUTORIAL_COMPLETE_FLAG_KEY = "tutorialComplete"; + QString displayName; QUrl fullAvatarURL; QString fullAvatarModelName; QUrl address; + bool tutorialComplete = false; - if (action == CrashHandler::RETAIN_AVATAR_INFO) { + if (action == CrashHandler::RETAIN_IMPORTANT_INFO) { // Read avatar info // Location and orientation @@ -135,6 +138,9 @@ void CrashHandler::handleCrash(CrashHandler::Action action) { fullAvatarURL = settings.value(FULL_AVATAR_URL_KEY).toUrl(); fullAvatarModelName = settings.value(FULL_AVATAR_MODEL_NAME_KEY).toString(); settings.endGroup(); + + // Tutorial complete + tutorialComplete = settings.value(TUTORIAL_COMPLETE_FLAG_KEY).toBool(); } // Delete Interface.ini @@ -143,7 +149,7 @@ void CrashHandler::handleCrash(CrashHandler::Action action) { settingsFile.remove(); } - if (action == CrashHandler::RETAIN_AVATAR_INFO) { + if (action == CrashHandler::RETAIN_IMPORTANT_INFO) { // Write avatar info // Location and orientation @@ -157,6 +163,9 @@ void CrashHandler::handleCrash(CrashHandler::Action action) { settings.setValue(FULL_AVATAR_URL_KEY, fullAvatarURL); settings.setValue(FULL_AVATAR_MODEL_NAME_KEY, fullAvatarModelName); settings.endGroup(); + + // Tutorial complete + settings.setValue(TUTORIAL_COMPLETE_FLAG_KEY, tutorialComplete); } } diff --git a/interface/src/CrashHandler.h b/interface/src/CrashHandler.h index da2e1575db..bff8bba6dd 100644 --- a/interface/src/CrashHandler.h +++ b/interface/src/CrashHandler.h @@ -22,7 +22,7 @@ public: private: enum Action { DELETE_INTERFACE_INI, - RETAIN_AVATAR_INFO, + RETAIN_IMPORTANT_INFO, DO_NOTHING }; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index dea2404cee..24a25f314d 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -794,6 +794,77 @@ controller::Pose MyAvatar::getRightHandTipPose() const { return pose; } +glm::vec3 MyAvatar::worldToJointPoint(const glm::vec3& position, const int jointIndex) const { + glm::vec3 jointPos = getPosition();//default value if no or invalid joint specified + glm::quat jointRot = getRotation();//default value if no or invalid joint specified + if (jointIndex != -1) { + if (_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPos)) { + _skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot); + } else { + qWarning() << "Invalid joint index specified: " << jointIndex; + } + } + glm::vec3 modelOffset = position - jointPos; + glm::vec3 jointSpacePosition = glm::inverse(jointRot) * modelOffset; + + return jointSpacePosition; +} + +glm::vec3 MyAvatar::worldToJointDirection(const glm::vec3& worldDir, const int jointIndex) const { + glm::quat jointRot = getRotation();//default value if no or invalid joint specified + if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) { + qWarning() << "Invalid joint index specified: " << jointIndex; + } + + glm::vec3 jointSpaceDir = glm::inverse(jointRot) * worldDir; + return jointSpaceDir; +} + +glm::quat MyAvatar::worldToJointRotation(const glm::quat& worldRot, const int jointIndex) const { + glm::quat jointRot = getRotation();//default value if no or invalid joint specified + if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) { + qWarning() << "Invalid joint index specified: " << jointIndex; + } + glm::quat jointSpaceRot = glm::inverse(jointRot) * worldRot; + return jointSpaceRot; +} + +glm::vec3 MyAvatar::jointToWorldPoint(const glm::vec3& jointSpacePos, const int jointIndex) const { + glm::vec3 jointPos = getPosition();//default value if no or invalid joint specified + glm::quat jointRot = getRotation();//default value if no or invalid joint specified + + if (jointIndex != -1) { + if (_skeletonModel->getJointPositionInWorldFrame(jointIndex, jointPos)) { + _skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot); + } else { + qWarning() << "Invalid joint index specified: " << jointIndex; + } + } + + glm::vec3 worldOffset = jointRot * jointSpacePos; + glm::vec3 worldPos = jointPos + worldOffset; + + return worldPos; +} + +glm::vec3 MyAvatar::jointToWorldDirection(const glm::vec3& jointSpaceDir, const int jointIndex) const { + glm::quat jointRot = getRotation();//default value if no or invalid joint specified + if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) { + qWarning() << "Invalid joint index specified: " << jointIndex; + } + glm::vec3 worldDir = jointRot * jointSpaceDir; + return worldDir; +} + +glm::quat MyAvatar::jointToWorldRotation(const glm::quat& jointSpaceRot, const int jointIndex) const { + glm::quat jointRot = getRotation();//default value if no or invalid joint specified + if ((jointIndex != -1) && (!_skeletonModel->getJointRotationInWorldFrame(jointIndex, jointRot))) { + qWarning() << "Invalid joint index specified: " << jointIndex; + } + glm::quat worldRot = jointRot * jointSpaceRot; + return worldRot; +} + // virtual void MyAvatar::render(RenderArgs* renderArgs) { // don't render if we've been asked to disable local rendering diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index fb11705a9c..cfe66eb10e 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -378,6 +378,15 @@ public: Q_INVOKABLE controller::Pose getLeftHandTipPose() const; Q_INVOKABLE controller::Pose getRightHandTipPose() const; + // world-space to avatar-space rigconversion functions + Q_INVOKABLE glm::vec3 worldToJointPoint(const glm::vec3& position, const int jointIndex = -1) const; + Q_INVOKABLE glm::vec3 worldToJointDirection(const glm::vec3& direction, const int jointIndex = -1) const; + Q_INVOKABLE glm::quat worldToJointRotation(const glm::quat& rotation, const int jointIndex = -1) const; + + Q_INVOKABLE glm::vec3 jointToWorldPoint(const glm::vec3& position, const int jointIndex = -1) const; + Q_INVOKABLE glm::vec3 jointToWorldDirection(const glm::vec3& direction, const int jointIndex = -1) const; + Q_INVOKABLE glm::quat jointToWorldRotation(const glm::quat& rotation, const int jointIndex = -1) const; + AvatarWeakPointer getLookAtTargetAvatar() const { return _lookAtTargetAvatar; } void updateLookAtTargetAvatar(); void clearLookAtTargetAvatar(); diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 2f812724c5..0bc72ae689 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -184,6 +184,8 @@ AudioClient::AudioClient() : checkDevices(); }); }); + const unsigned long DEVICE_CHECK_INTERVAL_MSECS = 2 * 1000; + _checkDevicesTimer->start(DEVICE_CHECK_INTERVAL_MSECS); configureReverb(); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 4407e12295..3c048e590d 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1473,27 +1473,6 @@ QStringList AvatarData::getJointNames() const { return _jointNames; } -void AvatarData::parseAvatarIdentityPacket(const QByteArray& data, Identity& identityOut) { - QDataStream packetStream(data); - - packetStream >> identityOut.uuid - >> identityOut.skeletonModelURL - >> identityOut.attachmentData - >> identityOut.displayName - >> identityOut.sessionDisplayName - >> identityOut.avatarEntityData - >> identityOut.sequenceId; - -#ifdef WANT_DEBUG - qCDebug(avatars) << __FUNCTION__ - << "identityOut.uuid:" << identityOut.uuid - << "identityOut.skeletonModelURL:" << identityOut.skeletonModelURL - << "identityOut.displayName:" << identityOut.displayName - << "identityOut.sessionDisplayName:" << identityOut.sessionDisplayName; -#endif - -} - glm::quat AvatarData::getOrientationOutbound() const { return (getLocalOrientation()); } @@ -1504,61 +1483,106 @@ QUrl AvatarData::cannonicalSkeletonModelURL(const QUrl& emptyURL) const { return _skeletonModelURL.scheme() == "file" ? emptyURL : _skeletonModelURL; } -void AvatarData::processAvatarIdentity(const Identity& identity, bool& identityChanged, bool& displayNameChanged) { +void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged) { - if (identity.sequenceId < _identitySequenceId) { - qCDebug(avatars) << "Ignoring older identity packet for avatar" << getSessionUUID() - << "_identitySequenceId (" << _identitySequenceId << ") is greater than" << identity.sequenceId; - return; + QDataStream packetStream(identityData); + + QUuid avatarSessionID; + + // peek the sequence number, this will tell us if we should be processing this identity packet at all + udt::SequenceNumber::Type incomingSequenceNumberType; + packetStream >> avatarSessionID >> incomingSequenceNumberType; + udt::SequenceNumber incomingSequenceNumber(incomingSequenceNumberType); + + if (!_hasProcessedFirstIdentity) { + _lastIncomingSequenceNumber = incomingSequenceNumber - 1; + _hasProcessedFirstIdentity = true; + qCDebug(avatars) << "Processing first identity packet for" << avatarSessionID << "-" + << (udt::SequenceNumber::Type) incomingSequenceNumber; } - // otherwise, set the identitySequenceId to match the incoming identity - _identitySequenceId = identity.sequenceId; - if (_firstSkeletonCheck || (identity.skeletonModelURL != cannonicalSkeletonModelURL(emptyURL))) { - setSkeletonModelURL(identity.skeletonModelURL); - identityChanged = true; - if (_firstSkeletonCheck) { + if (incomingSequenceNumber > _lastIncomingSequenceNumber) { + Identity identity; + + packetStream >> identity.skeletonModelURL + >> identity.attachmentData + >> identity.displayName + >> identity.sessionDisplayName + >> identity.avatarEntityData; + + // set the store identity sequence number to match the incoming identity + _lastIncomingSequenceNumber = incomingSequenceNumber; + + if (_firstSkeletonCheck || (identity.skeletonModelURL != cannonicalSkeletonModelURL(emptyURL))) { + setSkeletonModelURL(identity.skeletonModelURL); + identityChanged = true; + if (_firstSkeletonCheck) { + displayNameChanged = true; + } + _firstSkeletonCheck = false; + } + + if (identity.displayName != _displayName) { + _displayName = identity.displayName; + identityChanged = true; displayNameChanged = true; } - _firstSkeletonCheck = false; - } + maybeUpdateSessionDisplayNameFromTransport(identity.sessionDisplayName); - if (identity.displayName != _displayName) { - _displayName = identity.displayName; - identityChanged = true; - displayNameChanged = true; - } - maybeUpdateSessionDisplayNameFromTransport(identity.sessionDisplayName); + if (identity.attachmentData != _attachmentData) { + setAttachmentData(identity.attachmentData); + identityChanged = true; + } - if (identity.attachmentData != _attachmentData) { - setAttachmentData(identity.attachmentData); - identityChanged = true; - } + bool avatarEntityDataChanged = false; + _avatarEntitiesLock.withReadLock([&] { + avatarEntityDataChanged = (identity.avatarEntityData != _avatarEntityData); + }); + + if (avatarEntityDataChanged) { + setAvatarEntityData(identity.avatarEntityData); + identityChanged = true; + } - bool avatarEntityDataChanged = false; - _avatarEntitiesLock.withReadLock([&] { - avatarEntityDataChanged = (identity.avatarEntityData != _avatarEntityData); - }); - if (avatarEntityDataChanged) { - setAvatarEntityData(identity.avatarEntityData); - identityChanged = true; - } +#ifdef WANT_DEBUG + qCDebug(avatars) << __FUNCTION__ + << "identity.uuid:" << identity.uuid + << "identity.skeletonModelURL:" << identity.skeletonModelURL + << "identity.displayName:" << identity.displayName + << "identity.sessionDisplayName:" << identity.sessionDisplayName; +#endif + } else { +#ifdef WANT_DEBUG + qCDebug(avatars) << "Refusing to process identity for" << uuidStringWithoutCurlyBraces(avatarSessionID) << "since" + << (udt::SequenceNumber::Type) _lastIncomingSequenceNumber + << "is >=" << (udt::SequenceNumber::Type) incomingSequenceNumber; +#endif + } } -QByteArray AvatarData::identityByteArray() const { +QByteArray AvatarData::identityByteArray(bool shouldForwardIncomingSequenceNumber) const { QByteArray identityData; QDataStream identityStream(&identityData, QIODevice::Append); const QUrl& urlToSend = cannonicalSkeletonModelURL(emptyURL); // depends on _skeletonModelURL + // we use the boolean flag to determine if this is an identity byte array for a mixer to send to an agent + // or an agent to send to a mixer + + // when mixers send identity packets to agents, they simply forward along the last incoming sequence number they received + // whereas agents send a fresh outgoing sequence number when identity data has changed + + udt::SequenceNumber identitySequenceNumber = + shouldForwardIncomingSequenceNumber ? _lastIncomingSequenceNumber : _lastOutgoingSequenceNumber; + _avatarEntitiesLock.withReadLock([&] { identityStream << getSessionUUID() - << urlToSend - << _attachmentData - << _displayName - << getSessionDisplayNameForTransport() // depends on _sessionDisplayName - << _avatarEntityData - << _identitySequenceId; + << (udt::SequenceNumber::Type) identitySequenceNumber + << urlToSend + << _attachmentData + << _displayName + << getSessionDisplayNameForTransport() // depends on _sessionDisplayName + << _avatarEntityData; }); return identityData; @@ -1734,6 +1758,12 @@ void AvatarData::sendAvatarDataPacket() { void AvatarData::sendIdentityPacket() { auto nodeList = DependencyManager::get(); + + if (_identityDataChanged) { + // if the identity data has changed, push the sequence number forwards + ++_lastOutgoingSequenceNumber; + } + QByteArray identityData = identityByteArray(); auto packetList = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true); @@ -1744,7 +1774,7 @@ void AvatarData::sendIdentityPacket() { }, [&](const SharedNodePointer& node) { nodeList->sendPacketList(std::move(packetList), *node); - }); + }); _avatarEntityDataLocallyEdited = false; _identityDataChanged = false; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 4104615cfe..2d43c4d412 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -52,15 +52,16 @@ typedef unsigned long long quint64; #include #include #include -#include -#include -#include #include #include -#include +#include #include -#include +#include +#include +#include #include +#include +#include #include "AABox.h" #include "HeadData.h" @@ -525,22 +526,18 @@ public: const HeadData* getHeadData() const { return _headData; } struct Identity { - QUuid uuid; QUrl skeletonModelURL; QVector attachmentData; QString displayName; QString sessionDisplayName; AvatarEntityMap avatarEntityData; - quint64 sequenceId; }; - static void parseAvatarIdentityPacket(const QByteArray& data, Identity& identityOut); - // identityChanged returns true if identity has changed, false otherwise. // displayNameChanged returns true if displayName has changed, false otherwise. - void processAvatarIdentity(const Identity& identity, bool& identityChanged, bool& displayNameChanged); + void processAvatarIdentity(const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged); - QByteArray identityByteArray() const; + QByteArray identityByteArray(bool shouldForwardIncomingSequenceNumber = false) const; const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; } const QString& getDisplayName() const { return _displayName; } @@ -624,10 +621,7 @@ public: static float _avatarSortCoefficientAge; bool getIdentityDataChanged() const { return _identityDataChanged; } // has the identity data changed since the last time sendIdentityPacket() was called - void markIdentityDataChanged() { - _identityDataChanged = true; - _identitySequenceId++; - } + void markIdentityDataChanged() { _identityDataChanged = true; } float getDensity() const { return _density; } @@ -786,7 +780,9 @@ protected: float _audioAverageLoudness { 0.0f }; bool _identityDataChanged { false }; - quint64 _identitySequenceId { 0 }; + udt::SequenceNumber _lastIncomingSequenceNumber { 0 }; + udt::SequenceNumber _lastOutgoingSequenceNumber { 0 }; + bool _hasProcessedFirstIdentity { false }; float _density; private: diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 2ccc64fee2..cfbf2a8806 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -126,8 +126,9 @@ AvatarSharedPointer AvatarHashMap::parseAvatarData(QSharedPointer message, SharedNodePointer sendingNode) { - AvatarData::Identity identity; - AvatarData::parseAvatarIdentityPacket(message->getMessage(), identity); + + // peek the avatar UUID from the incoming packet + QUuid identityUUID = message->peek(NUM_BYTES_RFC4122_UUID); // make sure this isn't for an ignored avatar auto nodeList = DependencyManager::get(); @@ -136,20 +137,21 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer { QReadLocker locker(&_hashLock); auto me = _avatarHash.find(EMPTY); - if ((me != _avatarHash.end()) && (identity.uuid == me.value()->getSessionUUID())) { + if ((me != _avatarHash.end()) && (identityUUID == me.value()->getSessionUUID())) { // We add MyAvatar to _avatarHash with an empty UUID. Code relies on this. In order to correctly handle an // identity packet for ourself (such as when we are assigned a sessionDisplayName by the mixer upon joining), // we make things match here. - identity.uuid = EMPTY; + identityUUID = EMPTY; } } - if (!nodeList->isIgnoringNode(identity.uuid) || nodeList->getRequestsDomainListData()) { + + if (!nodeList->isIgnoringNode(identityUUID) || nodeList->getRequestsDomainListData()) { // mesh URL for a UUID, find avatar in our list - auto avatar = newOrExistingAvatar(identity.uuid, sendingNode); + auto avatar = newOrExistingAvatar(identityUUID, sendingNode); bool identityChanged = false; bool displayNameChanged = false; // In this case, the "sendingNode" is the Avatar Mixer. - avatar->processAvatarIdentity(identity, identityChanged, displayNameChanged); + avatar->processAvatarIdentity(message->getMessage(), identityChanged, displayNameChanged); } } diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index eab0e3011e..e89646d838 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -605,7 +605,7 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori QString extraInfo; return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance, - face, surfaceNormal, extraInfo, precisionPicking, precisionPicking); + face, surfaceNormal, extraInfo, precisionPicking, false); } void RenderableModelEntityItem::getCollisionGeometryResource() { diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 236daf6443..6d4f586c52 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -460,17 +460,11 @@ FBXLight extractLight(const FBXNode& object) { } QByteArray fileOnUrl(const QByteArray& filepath, const QString& url) { - QString path = QFileInfo(url).path(); - QByteArray filename = filepath; - QFileInfo checkFile(path + "/" + filepath); + // in order to match the behaviour when loading models from remote URLs + // we assume that all external textures are right beside the loaded model + // ignoring any relative paths or absolute paths inside of models - // check if the file exists at the RelativeFilename - if (!(checkFile.exists() && checkFile.isFile())) { - // if not, assume it is in the fbx directory - filename = filename.mid(filename.lastIndexOf('/') + 1); - } - - return filename; + return filepath.mid(filepath.lastIndexOf('/') + 1); } FBXGeometry* FBXReader::extractFBXGeometry(const QVariantHash& mapping, const QString& url) { diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.cpp b/libraries/gl/src/gl/OffscreenQmlSurface.cpp index 65c311424f..9dcc1d7991 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.cpp +++ b/libraries/gl/src/gl/OffscreenQmlSurface.cpp @@ -277,6 +277,23 @@ QString getEventBridgeJavascript() { return javaScriptToInject; } +class EventBridgeWrapper : public QObject { + Q_OBJECT + Q_PROPERTY(QObject* eventBridge READ getEventBridge CONSTANT); + +public: + EventBridgeWrapper(QObject* eventBridge, QObject* parent = nullptr) : QObject(parent), _eventBridge(eventBridge) { + } + + QObject* getEventBridge() { + return _eventBridge; + } + +private: + QObject* _eventBridge; +}; + + QQmlEngine* acquireEngine(QQuickWindow* window) { Q_ASSERT(QThread::currentThread() == qApp->thread()); @@ -430,7 +447,6 @@ OffscreenQmlSurface::~OffscreenQmlSurface() { _canvas->deleteLater(); _rootItem->deleteLater(); - _qmlComponent->deleteLater(); _quickWindow->deleteLater(); releaseEngine(); } @@ -473,11 +489,12 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) { _qmlContext = new QQmlContext(qmlEngine->rootContext()); _qmlContext->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow())); - _qmlContext->setContextProperty("globalEventBridge", this); + _qmlContext->setContextProperty("eventBridge", this); _qmlContext->setContextProperty("webEntity", this); - _qmlComponent = new QQmlComponent(qmlEngine); - + // FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper + // Find a way to flag older scripts using this mechanism and wanr that this is deprecated + _qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(this, _qmlContext)); if (!_canvas->makeCurrent()) { qWarning("Failed to make context current for QML Renderer"); @@ -577,71 +594,79 @@ void OffscreenQmlSurface::setBaseUrl(const QUrl& baseUrl) { _qmlContext->setBaseUrl(baseUrl); } -QObject* OffscreenQmlSurface::load(const QUrl& qmlSource, std::function f) { +QObject* OffscreenQmlSurface::load(const QUrl& qmlSource, bool createNewContext, std::function f) { // Synchronous loading may take a while; restart the deadlock timer QMetaObject::invokeMethod(qApp, "updateHeartbeat", Qt::DirectConnection); - if ((qmlSource.isRelative() && !qmlSource.isEmpty()) || qmlSource.scheme() == QLatin1String("file")) { - _qmlComponent->loadUrl(_qmlContext->resolvedUrl(qmlSource), QQmlComponent::PreferSynchronous); - } else { - _qmlComponent->loadUrl(qmlSource, QQmlComponent::PreferSynchronous); + QQmlContext* targetContext = _qmlContext; + if (_rootItem && createNewContext) { + targetContext = new QQmlContext(targetContext); } + QUrl finalQmlSource = qmlSource; + if ((qmlSource.isRelative() && !qmlSource.isEmpty()) || qmlSource.scheme() == QLatin1String("file")) { + finalQmlSource = _qmlContext->resolvedUrl(qmlSource); + } - if (_qmlComponent->isLoading()) { - connect(_qmlComponent, &QQmlComponent::statusChanged, this, - [this, f](QQmlComponent::Status){ - finishQmlLoad(f); - }); + auto qmlComponent = new QQmlComponent(_qmlContext->engine(), finalQmlSource, QQmlComponent::PreferSynchronous); + if (qmlComponent->isLoading()) { + connect(qmlComponent, &QQmlComponent::statusChanged, this, + [this, qmlComponent, targetContext, f](QQmlComponent::Status) { + finishQmlLoad(qmlComponent, targetContext, f); + }); return nullptr; } - return finishQmlLoad(f); + return finishQmlLoad(qmlComponent, targetContext, f); +} + +QObject* OffscreenQmlSurface::loadInNewContext(const QUrl& qmlSource, std::function f) { + return load(qmlSource, true, f); +} + +QObject* OffscreenQmlSurface::load(const QUrl& qmlSource, std::function f) { + return load(qmlSource, false, f); } void OffscreenQmlSurface::clearCache() { _qmlContext->engine()->clearComponentCache(); } -QObject* OffscreenQmlSurface::finishQmlLoad(std::function f) { -#if 0 - if (!_rootItem) { - QQmlComponent component(_qmlContext->engine()); - component.setData(R"QML( -import QtQuick 2.0 -import QtWebChannel 1.0 -Item { Component.onCompleted: globalEventBridge.WebChannel.id = "globalEventBridge"; } -)QML", QUrl()); - QObject *helper = component.create(_qmlContext); - qDebug() << "Created helper"; - } -#endif - disconnect(_qmlComponent, &QQmlComponent::statusChanged, this, 0); - if (_qmlComponent->isError()) { - QList errorList = _qmlComponent->errors(); - foreach(const QQmlError& error, errorList) { - qWarning() << error.url() << error.line() << error; +QObject* OffscreenQmlSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, std::function f) { + disconnect(qmlComponent, &QQmlComponent::statusChanged, this, 0); + if (qmlComponent->isError()) { + for (const auto& error : qmlComponent->errors()) { + qCWarning(glLogging) << error.url() << error.line() << error; } + qmlComponent->deleteLater(); return nullptr; } - QObject* newObject = _qmlComponent->beginCreate(_qmlContext); - if (_qmlComponent->isError()) { - QList errorList = _qmlComponent->errors(); - foreach(const QQmlError& error, errorList) + QObject* newObject = qmlComponent->beginCreate(qmlContext); + if (qmlComponent->isError()) { + for (const auto& error : qmlComponent->errors()) { qCWarning(glLogging) << error.url() << error.line() << error; + } if (!_rootItem) { qFatal("Unable to finish loading QML root"); } + qmlComponent->deleteLater(); return nullptr; } - _qmlContext->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership); - newObject->setProperty("eventBridge", QVariant::fromValue(this)); + qmlContext->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership); + f(qmlContext, newObject); - f(_qmlContext, newObject); - _qmlComponent->completeCreate(); + QObject* eventBridge = qmlContext->contextProperty("eventBridge").value(); + if (qmlContext != _qmlContext && eventBridge && eventBridge != this) { + // FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper + // Find a way to flag older scripts using this mechanism and wanr that this is deprecated + qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(eventBridge, qmlContext)); + } + + qmlComponent->completeCreate(); + qmlComponent->deleteLater(); // All quick items should be focusable diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.h b/libraries/gl/src/gl/OffscreenQmlSurface.h index 2a078d2b4f..ae81ae48b4 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.h +++ b/libraries/gl/src/gl/OffscreenQmlSurface.h @@ -48,6 +48,8 @@ public: void resize(const QSize& size, bool forceResize = false); QSize size() const; + Q_INVOKABLE QObject* load(const QUrl& qmlSource, bool createNewContext, std::function f = [](QQmlContext*, QObject*) {}); + Q_INVOKABLE QObject* loadInNewContext(const QUrl& qmlSource, std::function f = [](QQmlContext*, QObject*) {}); Q_INVOKABLE QObject* load(const QUrl& qmlSource, std::function f = [](QQmlContext*, QObject*) {}); Q_INVOKABLE QObject* load(const QString& qmlSourceFile, std::function f = [](QQmlContext*, QObject*) {}) { return load(QUrl(qmlSourceFile), f); @@ -118,7 +120,7 @@ protected: void setFocusText(bool newFocusText); private: - QObject* finishQmlLoad(std::function f); + QObject* finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, std::function f); QPointF mapWindowToUi(const QPointF& sourcePosition, QObject* sourceObject); void setupFbo(); bool allowNewFrame(uint8_t fps); @@ -134,7 +136,6 @@ private: QQuickWindow* _quickWindow { nullptr }; QMyQuickRenderControl* _renderControl{ nullptr }; QQmlContext* _qmlContext { nullptr }; - QQmlComponent* _qmlComponent { nullptr }; QQuickItem* _rootItem { nullptr }; OffscreenGLCanvas* _canvas { nullptr }; diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackendOutput.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackendOutput.cpp index 1e6691538b..5c6a18d7af 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackendOutput.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackendOutput.cpp @@ -63,11 +63,17 @@ void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) { int useScissor = batch._params[paramOffset + 0]._int; GLuint glmask = 0; + bool restoreStencilMask = false; + uint8_t cacheStencilMask = 0xFF; if (masks & Framebuffer::BUFFER_STENCIL) { glClearStencil(stencil); glmask |= GL_STENCIL_BUFFER_BIT; - // TODO: we will probably need to also check the write mask of stencil like we do - // for depth buffer, but as would say a famous Fez owner "We'll cross that bridge when we come to it" + + cacheStencilMask = _pipeline._stateCache.stencilActivation.getWriteMaskFront(); + if (cacheStencilMask != 0xFF) { + restoreStencilMask = true; + glStencilMask( 0xFF); + } } bool restoreDepthMask = false; @@ -121,6 +127,11 @@ void GLBackend::do_clearFramebuffer(const Batch& batch, size_t paramOffset) { glDisable(GL_SCISSOR_TEST); } + // Restore Stencil write mask + if (restoreStencilMask) { + glStencilMask(cacheStencilMask); + } + // Restore write mask meaning turn back off if (restoreDepthMask) { glDepthMask(GL_FALSE); diff --git a/libraries/networking/src/ReceivedPacketProcessor.h b/libraries/networking/src/ReceivedPacketProcessor.h index 5b54d4f309..f71abce1f1 100644 --- a/libraries/networking/src/ReceivedPacketProcessor.h +++ b/libraries/networking/src/ReceivedPacketProcessor.h @@ -20,7 +20,7 @@ class ReceivedPacketProcessor : public GenericThread { Q_OBJECT public: - static const unsigned long MAX_WAIT_TIME { 100 }; + static const uint64_t MAX_WAIT_TIME { 100 }; // Max wait time in ms ReceivedPacketProcessor(); @@ -66,7 +66,7 @@ protected: virtual bool process() override; /// Determines the timeout of the wait when there are no packets to process. Default value is 100ms to allow for regular event processing. - virtual unsigned long getMaxWait() const { return MAX_WAIT_TIME; } + virtual uint32_t getMaxWait() const { return MAX_WAIT_TIME; } /// Override to do work before the packets processing loop. Default does nothing. virtual void preProcess() { } diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index fc6251566e..7985df58bf 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -59,7 +59,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::AvatarData: case PacketType::BulkAvatarData: case PacketType::KillAvatar: - return static_cast(AvatarMixerPacketVersion::AvatarIdentitySequenceId); + return static_cast(AvatarMixerPacketVersion::AvatarIdentitySequenceFront); case PacketType::MessagesData: return static_cast(MessageDataVersion::TextOrBinaryData); case PacketType::ICEServerHeartbeat: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 9b37a7d76d..c080ab8e19 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -243,7 +243,9 @@ enum class AvatarMixerPacketVersion : PacketVersion { AvatarAsChildFixes, StickAndBallDefaultAvatar, IdentityPacketsIncludeUpdateTime, - AvatarIdentitySequenceId + AvatarIdentitySequenceId, + MannequinDefaultAvatar, + AvatarIdentitySequenceFront }; enum class DomainConnectRequestVersion : PacketVersion { diff --git a/libraries/networking/src/udt/SequenceNumber.h b/libraries/networking/src/udt/SequenceNumber.h index 3abc80bdd8..2c82eccfa3 100644 --- a/libraries/networking/src/udt/SequenceNumber.h +++ b/libraries/networking/src/udt/SequenceNumber.h @@ -35,8 +35,8 @@ public: explicit SequenceNumber(char* value) { _value = (*reinterpret_cast(value)) & MAX; } explicit SequenceNumber(Type value) { _value = (value <= MAX) ? ((value >= 0) ? value : 0) : MAX; } explicit SequenceNumber(UType value) { _value = (value <= MAX) ? value : MAX; } - explicit operator Type() { return _value; } - explicit operator UType() { return static_cast(_value); } + explicit operator Type() const { return _value; } + explicit operator UType() const { return static_cast(_value); } inline SequenceNumber& operator++() { _value = (_value + 1) % (MAX + 1); diff --git a/libraries/octree/src/JurisdictionListener.cpp b/libraries/octree/src/JurisdictionListener.cpp index dbbd146f4e..76c5069006 100644 --- a/libraries/octree/src/JurisdictionListener.cpp +++ b/libraries/octree/src/JurisdictionListener.cpp @@ -35,14 +35,13 @@ void JurisdictionListener::nodeKilled(SharedNodePointer node) { } bool JurisdictionListener::queueJurisdictionRequest() { - auto packet = NLPacket::create(PacketType::JurisdictionRequest, 0); - auto nodeList = DependencyManager::get(); int nodeCount = 0; nodeList->eachNode([&](const SharedNodePointer& node) { if (node->getType() == getNodeType() && node->getActiveSocket()) { + auto packet = NLPacket::create(PacketType::JurisdictionRequest, 0); _packetSender.queuePacketForSending(node, std::move(packet)); nodeCount++; } diff --git a/libraries/octree/src/JurisdictionSender.cpp b/libraries/octree/src/JurisdictionSender.cpp index ed3d59cebc..dfe1a6d872 100644 --- a/libraries/octree/src/JurisdictionSender.cpp +++ b/libraries/octree/src/JurisdictionSender.cpp @@ -41,8 +41,6 @@ bool JurisdictionSender::process() { // call our ReceivedPacketProcessor base class process so we'll get any pending packets if (continueProcessing && (continueProcessing = ReceivedPacketProcessor::process())) { - auto packet = (_jurisdictionMap) ? _jurisdictionMap->packIntoPacket() - : JurisdictionMap::packEmptyJurisdictionIntoMessage(getNodeType()); int nodeCount = 0; lockRequestingNodes(); @@ -53,6 +51,8 @@ bool JurisdictionSender::process() { SharedNodePointer node = DependencyManager::get()->nodeWithUUID(nodeUUID); if (node && node->getActiveSocket()) { + auto packet = (_jurisdictionMap) ? _jurisdictionMap->packIntoPacket() + : JurisdictionMap::packEmptyJurisdictionIntoMessage(getNodeType()); _packetSender.queuePacketForSending(node, std::move(packet)); nodeCount++; } diff --git a/libraries/render-utils/src/AntialiasingEffect.cpp b/libraries/render-utils/src/AntialiasingEffect.cpp index 139f1ae091..55a46a526f 100644 --- a/libraries/render-utils/src/AntialiasingEffect.cpp +++ b/libraries/render-utils/src/AntialiasingEffect.cpp @@ -19,7 +19,6 @@ #include "AntialiasingEffect.h" #include "StencilMaskPass.h" #include "TextureCache.h" -#include "FramebufferCache.h" #include "DependencyManager.h" #include "ViewFrustum.h" #include "GeometryCache.h" @@ -40,9 +39,9 @@ Antialiasing::~Antialiasing() { } } -const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() { - int width = DependencyManager::get()->getFrameBufferSize().width(); - int height = DependencyManager::get()->getFrameBufferSize().height(); +const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline(RenderArgs* args) { + int width = args->_viewport.z; + int height = args->_viewport.w; if (_antialiasingBuffer && _antialiasingBuffer->getSize() != uvec2(width, height)) { _antialiasingBuffer.reset(); @@ -51,7 +50,7 @@ const gpu::PipelinePointer& Antialiasing::getAntialiasingPipeline() { if (!_antialiasingBuffer) { // Link the antialiasing FBO to texture _antialiasingBuffer = gpu::FramebufferPointer(gpu::Framebuffer::create("antialiasing")); - auto format = gpu::Element::COLOR_SRGBA_32; // DependencyManager::get()->getLightingTexture()->getTexelFormat(); + auto format = gpu::Element::COLOR_SRGBA_32; auto defaultSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_POINT); _antialiasingTexture = gpu::Texture::createRenderBuffer(format, width, height, gpu::Texture::SINGLE_MIP, defaultSampler); _antialiasingBuffer->setRenderBuffer(0, _antialiasingTexture); @@ -110,19 +109,13 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const RenderArgs* args = renderContext->args; - if (args->_renderMode == RenderArgs::MIRROR_RENDER_MODE) { - return; - } - gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { batch.enableStereo(false); batch.setViewportTransform(args->_viewport); // FIXME: NEED to simplify that code to avoid all the GeometryCahce call, this is purely pixel manipulation - auto framebufferCache = DependencyManager::get(); - QSize framebufferSize = framebufferCache->getFrameBufferSize(); - float fbWidth = framebufferSize.width(); - float fbHeight = framebufferSize.height(); + float fbWidth = renderContext->args->_viewport.z; + float fbHeight = renderContext->args->_viewport.w; // float sMin = args->_viewport.x / fbWidth; // float sWidth = args->_viewport.z / fbWidth; // float tMin = args->_viewport.y / fbHeight; @@ -137,10 +130,10 @@ void Antialiasing::run(const render::RenderContextPointer& renderContext, const batch.setModelTransform(Transform()); // FXAA step - getAntialiasingPipeline(); + auto pipeline = getAntialiasingPipeline(renderContext->args); batch.setResourceTexture(0, sourceBuffer->getRenderBuffer(0)); batch.setFramebuffer(_antialiasingBuffer); - batch.setPipeline(getAntialiasingPipeline()); + batch.setPipeline(pipeline); // initialize the view-space unpacking uniforms using frustum data float left, right, bottom, top, nearVal, farVal; diff --git a/libraries/render-utils/src/AntialiasingEffect.h b/libraries/render-utils/src/AntialiasingEffect.h index e403032b4e..cec2554a3b 100644 --- a/libraries/render-utils/src/AntialiasingEffect.h +++ b/libraries/render-utils/src/AntialiasingEffect.h @@ -33,7 +33,7 @@ public: void configure(const Config& config) {} void run(const render::RenderContextPointer& renderContext, const gpu::FramebufferPointer& sourceBuffer); - const gpu::PipelinePointer& getAntialiasingPipeline(); + const gpu::PipelinePointer& getAntialiasingPipeline(RenderArgs* args); const gpu::PipelinePointer& getBlendPipeline(); private: diff --git a/libraries/render-utils/src/DebugDeferredBuffer.cpp b/libraries/render-utils/src/DebugDeferredBuffer.cpp index a67d20c6b0..3359b3a12d 100644 --- a/libraries/render-utils/src/DebugDeferredBuffer.cpp +++ b/libraries/render-utils/src/DebugDeferredBuffer.cpp @@ -19,7 +19,6 @@ #include #include "GeometryCache.h" -#include "FramebufferCache.h" #include "TextureCache.h" #include "DeferredLightingEffect.h" @@ -410,7 +409,6 @@ void DebugDeferredBuffer::run(const RenderContextPointer& renderContext, const I batch.setViewportTransform(args->_viewport); const auto geometryBuffer = DependencyManager::get(); - const auto framebufferCache = DependencyManager::get(); const auto textureCache = DependencyManager::get(); glm::mat4 projMat; diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 36a9401d00..0b4eee125b 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -418,10 +418,7 @@ model::MeshPointer DeferredLightingEffect::getSpotLightMesh() { } void PreparePrimaryFramebuffer::run(const RenderContextPointer& renderContext, gpu::FramebufferPointer& primaryFramebuffer) { - - auto framebufferCache = DependencyManager::get(); - auto framebufferSize = framebufferCache->getFrameBufferSize(); - glm::uvec2 frameSize(framebufferSize.width(), framebufferSize.height()); + glm::uvec2 frameSize(renderContext->args->_viewport.z, renderContext->args->_viewport.w); // Resizing framebuffers instead of re-building them seems to cause issues with threaded // rendering @@ -504,10 +501,7 @@ void RenderDeferredSetup::run(const render::RenderContextPointer& renderContext, { // Framebuffer copy operations cannot function as multipass stereo operations. batch.enableStereo(false); - - // perform deferred lighting, rendering to free fbo - auto framebufferCache = DependencyManager::get(); - + auto textureCache = DependencyManager::get(); auto deferredLightingEffect = DependencyManager::get(); diff --git a/libraries/render-utils/src/LightClusters.h b/libraries/render-utils/src/LightClusters.h index 105d6fb139..f495dabebb 100644 --- a/libraries/render-utils/src/LightClusters.h +++ b/libraries/render-utils/src/LightClusters.h @@ -152,9 +152,9 @@ public: int numInputLights { 0 }; int numClusteredLights { 0 }; - void setNumClusteredLightReferences(int numRefs) { numClusteredLightReferences = numRefs; emit dirty(); } - void setNumInputLights(int numLights) { numInputLights = numLights; emit dirty(); } - void setNumClusteredLights(int numLights) { numClusteredLights = numLights; emit dirty(); } + void setNumClusteredLightReferences(int numRefs) { numClusteredLightReferences = numRefs; } + void setNumInputLights(int numLights) { numInputLights = numLights; } + void setNumClusteredLights(int numLights) { numClusteredLights = numLights; } int numSceneLights { 0 }; int numFreeSceneLights { 0 }; diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index d7ec087174..ddb64bc69e 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -48,6 +48,7 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, RenderArgs* args = renderContext->args; gpu::doInBatch(args->_context, [&](gpu::Batch& batch) { args->_batch = &batch; + batch.enableStereo(false); glm::ivec4 viewport{0, 0, fbo->getWidth(), fbo->getHeight()}; batch.setViewportTransform(viewport); @@ -114,7 +115,7 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende skinProgram, state); } - const auto cachedMode = task.addJob("Setup"); + const auto cachedMode = task.addJob("ShadowSetup"); // CPU jobs: // Fetch and cull the items from the scene @@ -129,7 +130,7 @@ void RenderShadowTask::build(JobModel& task, const render::Varying& input, rende // GPU jobs: Render to shadow map task.addJob("RenderShadowMap", sortedShapes, shapePlumber); - task.addJob("Teardown", cachedMode); + task.addJob("ShadowTeardown", cachedMode); } void RenderShadowTask::configure(const Config& configuration) { diff --git a/libraries/render-utils/src/RenderViewTask.cpp b/libraries/render-utils/src/RenderViewTask.cpp new file mode 100644 index 0000000000..fceaf7b5b9 --- /dev/null +++ b/libraries/render-utils/src/RenderViewTask.cpp @@ -0,0 +1,33 @@ +// +// RenderViewTask.cpp +// render-utils/src/ +// +// Created by Sam Gateau on 5/25/2017. +// 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 "RenderViewTask.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) { + // auto items = input.get(); + + task.addJob("RenderShadowTask", cullFunctor); + + const auto items = task.addJob("FetchCullSort", cullFunctor); + assert(items.canCast()); + + if (isDeferred) { + task.addJob("RenderDeferredTask", items); + } else { + task.addJob("Forward", items); + } +} + diff --git a/libraries/render-utils/src/RenderViewTask.h b/libraries/render-utils/src/RenderViewTask.h new file mode 100644 index 0000000000..eb61f56eab --- /dev/null +++ b/libraries/render-utils/src/RenderViewTask.h @@ -0,0 +1,31 @@ +// +// RenderViewTask.h +// render-utils/src/ +// +// Created by Sam Gateau on 5/25/2017. +// 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 +// +#pragma once +#ifndef hifi_RenderViewTask_h +#define hifi_RenderViewTask_h + +#include +#include + + +class RenderViewTask { +public: + using Input = RenderFetchCullSortTask::Output; + using JobModel = render::Task::ModelI; + + RenderViewTask() {} + + void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs, render::CullFunctor cullFunctor, bool isDeferred); + +}; + + +#endif // hifi_RenderViewTask_h diff --git a/libraries/render-utils/src/text/Font.cpp b/libraries/render-utils/src/text/Font.cpp index c6a7da3a1a..f51a779066 100644 --- a/libraries/render-utils/src/text/Font.cpp +++ b/libraries/render-utils/src/text/Font.cpp @@ -14,6 +14,8 @@ #include "../RenderUtilsLogging.h" #include "FontFamilies.h" +static std::mutex fontMutex; + struct TextureVertex { glm::vec2 pos; glm::vec2 tex; @@ -56,6 +58,7 @@ Font::Pointer Font::load(QIODevice& fontFile) { } Font::Pointer Font::load(const QString& family) { + std::lock_guard lock(fontMutex); if (!LOADED_FONTS.contains(family)) { static const QString SDFF_COURIER_PRIME_FILENAME{ ":/CourierPrime.sdff" }; diff --git a/libraries/render-utils/src/text/Font.h b/libraries/render-utils/src/text/Font.h index 5b6b4f2a43..2b61f19492 100644 --- a/libraries/render-utils/src/text/Font.h +++ b/libraries/render-utils/src/text/Font.h @@ -31,10 +31,10 @@ public: const glm::vec4* color, EffectType effectType, const glm::vec2& bound, bool layered = false); - static Pointer load(QIODevice& fontFile); static Pointer load(const QString& family); private: + static Pointer load(QIODevice& fontFile); QStringList tokenizeForWrapping(const QString& str) const; QStringList splitLines(const QString& str) const; glm::vec2 computeTokenExtent(const QString& str) const; diff --git a/libraries/render/src/render/EngineStats.cpp b/libraries/render/src/render/EngineStats.cpp index 9e45be5dbd..ae1467ac0f 100644 --- a/libraries/render/src/render/EngineStats.cpp +++ b/libraries/render/src/render/EngineStats.cpp @@ -63,6 +63,4 @@ void EngineStats::run(const RenderContextPointer& renderContext) { config->frameSetPipelineCount = _gpuStats._PSNumSetPipelines; config->frameSetInputFormatCount = _gpuStats._ISNumFormatChanges; - - config->emitDirty(); } diff --git a/libraries/render/src/task/Config.cpp b/libraries/render/src/task/Config.cpp index cb2c4f1e3c..0e630311f6 100644 --- a/libraries/render/src/task/Config.cpp +++ b/libraries/render/src/task/Config.cpp @@ -34,6 +34,7 @@ void TaskConfig::connectChildConfig(QConfigPointer childConfig, const std::strin if (childConfig->metaObject()->indexOfSignal(DIRTY_SIGNAL) != -1) { // Connect dirty->refresh if defined QObject::connect(childConfig.get(), SIGNAL(dirty()), this, SLOT(refresh())); + QObject::connect(childConfig.get(), SIGNAL(dirtyEnabled()), this, SLOT(refresh())); } } @@ -50,6 +51,7 @@ void TaskConfig::transferChildrenConfigs(QConfigPointer source) { if (child->metaObject()->indexOfSignal(DIRTY_SIGNAL) != -1) { // Connect dirty->refresh if defined QObject::connect(child, SIGNAL(dirty()), this, SLOT(refresh())); + QObject::connect(child, SIGNAL(dirtyEnabled()), this, SLOT(refresh())); } } } diff --git a/libraries/render/src/task/Config.h b/libraries/render/src/task/Config.h index c78a3f3bfe..40a3abbd18 100644 --- a/libraries/render/src/task/Config.h +++ b/libraries/render/src/task/Config.h @@ -89,7 +89,7 @@ protected: class JobConfig : public QObject { Q_OBJECT Q_PROPERTY(double cpuRunTime READ getCPURunTime NOTIFY newStats()) //ms - Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled) + Q_PROPERTY(bool enabled READ isEnabled WRITE setEnabled NOTIFY dirtyEnabled()) double _msCPURunTime{ 0.0 }; public: @@ -99,7 +99,7 @@ public: JobConfig(bool enabled) : alwaysEnabled{ false }, enabled{ enabled } {} bool isEnabled() { return alwaysEnabled || enabled; } - void setEnabled(bool enable) { enabled = alwaysEnabled || enable; } + void setEnabled(bool enable) { enabled = alwaysEnabled || enable; emit dirtyEnabled(); } bool alwaysEnabled{ true }; bool enabled{ true }; @@ -121,6 +121,7 @@ public slots: signals: void loaded(); void newStats(); + void dirtyEnabled(); }; class TaskConfig : public JobConfig { diff --git a/libraries/render/src/task/Task.h b/libraries/render/src/task/Task.h index ed335150a7..f76ba92546 100644 --- a/libraries/render/src/task/Task.h +++ b/libraries/render/src/task/Task.h @@ -170,6 +170,7 @@ protected: std::string _name = ""; }; + // A task is a specialized job to run a collection of other jobs // It can be created on any type T by aliasing the type JobModel in the class T // using JobModel = Task::Model diff --git a/libraries/script-engine/src/TabletScriptingInterface.cpp b/libraries/script-engine/src/TabletScriptingInterface.cpp index 644f1e6f0c..dadf436ea5 100644 --- a/libraries/script-engine/src/TabletScriptingInterface.cpp +++ b/libraries/script-engine/src/TabletScriptingInterface.cpp @@ -540,7 +540,7 @@ void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaS QObject* TabletProxy::addButton(const QVariant& properties) { auto tabletButtonProxy = QSharedPointer(new TabletButtonProxy(properties.toMap())); - std::lock_guard guard(_tabletMutex); + std::unique_lock guard(_tabletMutex); _tabletButtonProxies.push_back(tabletButtonProxy); if (!_toolbarMode && _qmlTabletRoot) { auto tablet = getQmlTablet(); @@ -550,7 +550,6 @@ QObject* TabletProxy::addButton(const QVariant& properties) { qCCritical(scriptengine) << "Could not find tablet in TabletRoot.qml"; } } else if (_toolbarMode) { - auto tabletScriptingInterface = DependencyManager::get(); QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy(); @@ -559,6 +558,8 @@ QObject* TabletProxy::addButton(const QVariant& properties) { connectionType = Qt::BlockingQueuedConnection; } + guard.unlock(); + // copy properties from tablet button proxy to toolbar button proxy. QObject* toolbarButtonProxy = nullptr; bool hasResult = QMetaObject::invokeMethod(toolbarProxy, "addButton", connectionType, Q_RETURN_ARG(QObject*, toolbarButtonProxy), Q_ARG(QVariant, tabletButtonProxy->getProperties())); @@ -576,31 +577,38 @@ bool TabletProxy::onHomeScreen() { } void TabletProxy::removeButton(QObject* tabletButtonProxy) { - std::lock_guard guard(_tabletMutex); + std::unique_lock guard(_tabletMutex); auto tablet = getQmlTablet(); if (!tablet) { qCCritical(scriptengine) << "Could not find tablet in TabletRoot.qml"; } - auto iter = std::find(_tabletButtonProxies.begin(), _tabletButtonProxies.end(), tabletButtonProxy); - if (iter != _tabletButtonProxies.end()) { - if (!_toolbarMode && _qmlTabletRoot) { - (*iter)->setQmlButton(nullptr); - if (tablet) { - QMetaObject::invokeMethod(tablet, "removeButtonProxy", Qt::AutoConnection, Q_ARG(QVariant, (*iter)->getProperties())); - } - } else if (_toolbarMode) { - auto tabletScriptingInterface = DependencyManager::get(); - QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy(); - - // remove button from toolbarProxy - QMetaObject::invokeMethod(toolbarProxy, "removeButton", Qt::AutoConnection, Q_ARG(QVariant, (*iter)->getUuid().toString())); - (*iter)->setToolbarButtonProxy(nullptr); + QSharedPointer buttonProxy; + { + auto iter = std::find(_tabletButtonProxies.begin(), _tabletButtonProxies.end(), tabletButtonProxy); + if (iter == _tabletButtonProxies.end()) { + qCWarning(scriptengine) << "TabletProxy::removeButton() could not find button " << tabletButtonProxy; + return; } + buttonProxy = *iter; _tabletButtonProxies.erase(iter); - } else { - qCWarning(scriptengine) << "TabletProxy::removeButton() could not find button " << tabletButtonProxy; + } + + if (!_toolbarMode && _qmlTabletRoot) { + buttonProxy->setQmlButton(nullptr); + if (tablet) { + guard.unlock(); + QMetaObject::invokeMethod(tablet, "removeButtonProxy", Qt::AutoConnection, Q_ARG(QVariant, buttonProxy->getProperties())); + } + } else if (_toolbarMode) { + auto tabletScriptingInterface = DependencyManager::get(); + QObject* toolbarProxy = tabletScriptingInterface->getSystemToolbarProxy(); + + // remove button from toolbarProxy + guard.unlock(); + QMetaObject::invokeMethod(toolbarProxy, "removeButton", Qt::AutoConnection, Q_ARG(QVariant, buttonProxy->getUuid().toString())); + buttonProxy->setToolbarButtonProxy(nullptr); } } diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp index c0e94058ae..58d39448ac 100644 --- a/libraries/ui/src/QmlWindowClass.cpp +++ b/libraries/ui/src/QmlWindowClass.cpp @@ -104,9 +104,9 @@ void QmlWindowClass::initQml(QVariantMap properties) { Q_ASSERT(invokeResult); } else { // Build the event bridge and wrapper on the main thread - offscreenUi->load(qmlSource(), [&](QQmlContext* context, QObject* object) { + offscreenUi->loadInNewContext(qmlSource(), [&](QQmlContext* context, QObject* object) { _qmlWindow = object; - _qmlWindow->setProperty("eventBridge", QVariant::fromValue(this)); + context->setContextProperty("eventBridge", this); context->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership); context->engine()->setObjectOwnership(object, QQmlEngine::CppOwnership); if (properties.contains(TITLE_PROPERTY)) { diff --git a/plugins/openvr/CMakeLists.txt b/plugins/openvr/CMakeLists.txt index af65f3dbf7..6a95ef6d76 100644 --- a/plugins/openvr/CMakeLists.txt +++ b/plugins/openvr/CMakeLists.txt @@ -10,10 +10,10 @@ if (WIN32) # we're using static GLEW, so define GLEW_STATIC add_definitions(-DGLEW_STATIC) set(TARGET_NAME openvr) - setup_hifi_plugin(OpenGL Script Qml Widgets) + setup_hifi_plugin(OpenGL Script Qml Widgets Multimedia) link_hifi_libraries(shared gl networking controllers ui plugins display-plugins ui-plugins input-plugins script-engine - render-utils model gpu gpu-gl render model-networking fbx ktx image procedural) + audio-client render-utils model gpu gpu-gl render model-networking fbx ktx image procedural) include_hifi_library_headers(octree) @@ -21,4 +21,5 @@ if (WIN32) find_package(OpenVR REQUIRED) target_include_directories(${TARGET_NAME} PRIVATE ${OPENVR_INCLUDE_DIRS}) target_link_libraries(${TARGET_NAME} ${OPENVR_LIBRARIES}) + target_link_libraries(${TARGET_NAME} Winmm.lib) endif() diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.cpp b/plugins/openvr/src/OpenVrDisplayPlugin.cpp index 8105de7a13..15fb7d72c9 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.cpp +++ b/plugins/openvr/src/OpenVrDisplayPlugin.cpp @@ -7,6 +7,9 @@ // #include "OpenVrDisplayPlugin.h" +// Odd ordering of header is required to avoid 'macro redinition warnings' +#include + #include #include #include @@ -713,3 +716,30 @@ bool OpenVrDisplayPlugin::isKeyboardVisible() { int OpenVrDisplayPlugin::getRequiredThreadCount() const { return Parent::getRequiredThreadCount() + (_threadedSubmit ? 1 : 0); } + +QString OpenVrDisplayPlugin::getPreferredAudioInDevice() const { + QString device = getVrSettingString(vr::k_pch_audio_Section, vr::k_pch_audio_OnPlaybackDevice_String); + if (!device.isEmpty()) { + static const WCHAR INIT = 0; + size_t size = device.size() + 1; + std::vector deviceW; + deviceW.assign(size, INIT); + device.toWCharArray(deviceW.data()); + device = AudioClient::friendlyNameForAudioDevice(deviceW.data()); + } + return device; +} + +QString OpenVrDisplayPlugin::getPreferredAudioOutDevice() const { + QString device = getVrSettingString(vr::k_pch_audio_Section, vr::k_pch_audio_OnRecordDevice_String); + if (!device.isEmpty()) { + static const WCHAR INIT = 0; + size_t size = device.size() + 1; + std::vector deviceW; + deviceW.assign(size, INIT); + device.toWCharArray(deviceW.data()); + device = AudioClient::friendlyNameForAudioDevice(deviceW.data()); + } + return device; +} + diff --git a/plugins/openvr/src/OpenVrDisplayPlugin.h b/plugins/openvr/src/OpenVrDisplayPlugin.h index 01e02c9892..a1bbed8754 100644 --- a/plugins/openvr/src/OpenVrDisplayPlugin.h +++ b/plugins/openvr/src/OpenVrDisplayPlugin.h @@ -58,6 +58,9 @@ public: // Possibly needs an additional thread for VR submission int getRequiredThreadCount() const override; + QString getPreferredAudioInDevice() const override; + QString getPreferredAudioOutDevice() const override; + protected: bool internalActivate() override; void internalDeactivate() override; diff --git a/plugins/openvr/src/OpenVrHelpers.cpp b/plugins/openvr/src/OpenVrHelpers.cpp index d9db757b2f..7e287a16c3 100644 --- a/plugins/openvr/src/OpenVrHelpers.cpp +++ b/plugins/openvr/src/OpenVrHelpers.cpp @@ -72,6 +72,21 @@ bool openVrSupported() { return (enableDebugOpenVR || !isOculusPresent()) && vr::VR_IsHmdPresent(); } +QString getVrSettingString(const char* section, const char* setting) { + QString result; + static const uint32_t BUFFER_SIZE = 1024; + static char BUFFER[BUFFER_SIZE]; + vr::IVRSettings * vrSettings = vr::VRSettings(); + if (vrSettings) { + vr::EVRSettingsError error = vr::VRSettingsError_None; + vrSettings->GetString(vr::k_pch_audio_Section, vr::k_pch_audio_OnPlaybackDevice_String, BUFFER, BUFFER_SIZE, &error); + if (error == vr::VRSettingsError_None) { + result = BUFFER; + } + } + return result; +} + vr::IVRSystem* acquireOpenVrSystem() { bool hmdPresent = vr::VR_IsHmdPresent(); if (hmdPresent) { @@ -82,6 +97,7 @@ vr::IVRSystem* acquireOpenVrSystem() { #endif vr::EVRInitError eError = vr::VRInitError_None; activeHmd = vr::VR_Init(&eError, vr::VRApplication_Scene); + #if DEV_BUILD qCDebug(displayplugins) << "OpenVR display: HMD is " << activeHmd << " error is " << eError; #endif diff --git a/plugins/openvr/src/OpenVrHelpers.h b/plugins/openvr/src/OpenVrHelpers.h index f00cd9e117..f4253899a2 100644 --- a/plugins/openvr/src/OpenVrHelpers.h +++ b/plugins/openvr/src/OpenVrHelpers.h @@ -25,6 +25,7 @@ bool openVrQuitRequested(); void enableOpenVrKeyboard(PluginContainer* container); void disableOpenVrKeyboard(); bool isOpenVrKeyboardShown(); +QString getVrSettingString(const char* section, const char* setting); template diff --git a/plugins/openvr/src/ViveControllerManager.cpp b/plugins/openvr/src/ViveControllerManager.cpp index b5fa7cadad..7330daf228 100644 --- a/plugins/openvr/src/ViveControllerManager.cpp +++ b/plugins/openvr/src/ViveControllerManager.cpp @@ -123,15 +123,18 @@ bool ViveControllerManager::isSupported() const { bool ViveControllerManager::activate() { InputPlugin::activate(); - _container->addMenu(MENU_PATH); - _container->addMenuItem(PluginType::INPUT_PLUGIN, MENU_PATH, RENDER_CONTROLLERS, - [this] (bool clicked) { this->setRenderControllers(clicked); }, - true, true); - if (!_system) { _system = acquireOpenVrSystem(); } - Q_ASSERT(_system); + + if (!_system) { + return false; + } + + _container->addMenu(MENU_PATH); + _container->addMenuItem(PluginType::INPUT_PLUGIN, MENU_PATH, RENDER_CONTROLLERS, + [this](bool clicked) { this->setRenderControllers(clicked); }, + true, true); enableOpenVrKeyboard(_container); diff --git a/scripts/developer/tests/avatarToWorldTests.js b/scripts/developer/tests/avatarToWorldTests.js new file mode 100644 index 0000000000..c6e23fc81b --- /dev/null +++ b/scripts/developer/tests/avatarToWorldTests.js @@ -0,0 +1,127 @@ +var AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}"; + +var debugSphereBaseProperties = { + type: "Sphere", + dimensions: { x: 0.2, y: 0.2, z: 0.2 }, + dynamic: false, + collisionless: true, + gravity: { x: 0, y: 0, z: 0 }, + lifetime: 10.0, + userData: "{ \"grabbableKey\": { \"grabbable\": false, \"kinematic\": false } }" +}; + +var debugBoxBaseProperties = { + type: "Box", + dimensions: { x: 0.2, y: 0.2, z: 0.2 }, + dynamic: false, + collisionless: true, + gravity: { x: 0, y: 0, z: 0 }, + lifetime: 10.0, + userData: "{ \"grabbableKey\": { \"grabbable\": false, \"kinematic\": false } }" +}; + +//jointToWorldPoint +// create sphere for finger on left hand +// each frame, calculate world position of finger, with some offset +// update sphere to match this position +var jointToWorldPointTest_sphereEntity; +function jointToWorldPointTest() { + var jointIndex = MyAvatar.getJointIndex("LeftHandPinky4"); + var jointOffset_WorldSpace = { x: 0.1, y: 0, z: 0 }; + var worldPos = MyAvatar.jointToWorldPoint(jointOffset_WorldSpace, jointIndex); + + var jointSphereProps = Object.create(debugSphereBaseProperties); + jointSphereProps.name = "jointToWorldPointTest_Sphere"; + jointSphereProps.color = { blue: 240, green: 150, red: 150 }; + jointSphereProps.position = worldPos; + jointToWorldPointTest_sphereEntity = Entities.addEntity(jointSphereProps); +} +function jointToWorldPointTest_update(deltaTime) { + var jointIndex = MyAvatar.getJointIndex("LeftHandPinky4"); + var jointOffset_WorldSpace = { x: 0.1, y: 0, z: 0 }; + var worldPos = MyAvatar.jointToWorldPoint(jointOffset_WorldSpace, jointIndex); + var newProperties = { position: worldPos }; + 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 +// update box rotation to match +var jointToWorldRotationTest_boxEntity; +function jointToWorldRotationTest() { + var jointIndex = MyAvatar.getJointIndex("Head"); + var jointPosition_WorldSpace = MyAvatar.getJointPosition(jointIndex); + var jointRot = MyAvatar.getJointRotation(jointIndex); + var jointRot_WorldSpace = MyAvatar.jointToWorldRotation(jointRot, jointIndex); + + var boxProps = Object.create(debugBoxBaseProperties); + boxProps.name = "jointToWorldRotationTest_Box"; + boxProps.color = { blue: 250, green: 250, red: 250 }; + boxProps.position = jointPosition_WorldSpace; + boxProps.rotation = jointRot_WorldSpace; + jointToWorldRotationTest_boxEntity = Entities.addEntity(boxProps); +} +function jointToWorldRotationTest_update(deltaTime) { + var jointIndex = MyAvatar.getJointIndex("Head"); + var jointPosition_WorldSpace = MyAvatar.getJointPosition(jointIndex); + var jointRot = MyAvatar.getJointRotation(jointIndex); + var jointRot_WorldSpace = MyAvatar.jointToWorldRotation(jointRot, jointIndex); + var newProperties = { position: jointPosition_WorldSpace, rotation: jointRot_WorldSpace }; + Entities.editEntity(jointToWorldRotationTest_boxEntity, newProperties); +} + +jointToWorldPointTest(); +Script.update.connect(jointToWorldPointTest_update); + +jointToWorldDirectionTest(); +Script.update.connect(jointToWorldDirection_update); + +jointToWorldRotationTest(); +Script.update.connect(jointToWorldRotationTest_update); diff --git a/scripts/developer/tests/worldToAvatarTests.js b/scripts/developer/tests/worldToAvatarTests.js new file mode 100644 index 0000000000..6f0b19dc2d --- /dev/null +++ b/scripts/developer/tests/worldToAvatarTests.js @@ -0,0 +1,134 @@ +var AVATAR_SELF_ID = "{00000000-0000-0000-0000-000000000001}"; + +var debugSphereBaseProperties = { + type: "Sphere", + dimensions: { x: 0.2, y: 0.2, z: 0.2 }, + dynamic: false, + collisionless: true, + gravity: { x: 0, y: 0, z: 0 }, + lifetime: 10.0, + userData: "{ \"grabbableKey\": { \"grabbable\": false, \"kinematic\": false } }" +}; + +var debugBoxBaseProperties = { + type: "Box", + dimensions: { x: 0.2, y: 0.2, z: 0.2 }, + dynamic: false, + collisionless: true, + gravity: { x: 0, y: 0, z: 0 }, + lifetime: 10.0, + userData: "{ \"grabbableKey\": { \"grabbable\": false, \"kinematic\": false } }" +}; + +//worldToJointPoint +// calculate position offset from joint using getJointPosition +// pass through worldToJointPoint to get offset in joint space of players joint +// create a blue sphere and attach it to players joint using the joint space offset +// The two spheres should appear in the same place, but the blue sphere will follow the avatar +function worldToJointPointTest() { + var jointIndex = MyAvatar.getJointIndex("LeftHandPinky4"); + var avatarPos = MyAvatar.position; + + var jointPosition_WorldSpace = MyAvatar.getJointPosition(jointIndex); + var jointOffset_WorldSpace = { x: 0.1, y: 0, z: 0 }; + var jointPosition_WorldSpaceOffset = Vec3.sum(jointPosition_WorldSpace, jointOffset_WorldSpace); + var jointPosition_JointSpaceOffset = MyAvatar.worldToJointPoint(jointPosition_WorldSpaceOffset, jointIndex); + + var jointSphereProps = Object.create(debugSphereBaseProperties); + jointSphereProps.name = "worldToJointPointTest_Joint"; + jointSphereProps.color = { blue: 240, green: 150, red: 150 }; + jointSphereProps.localPosition = jointPosition_JointSpaceOffset; + jointSphereProps.parentID = AVATAR_SELF_ID; + jointSphereProps.parentJointIndex = jointIndex; + Entities.addEntity(jointSphereProps); + + var worldSphereProps = Object.create(debugSphereBaseProperties); + worldSphereProps.name = "worldToJointPointTest_World"; + worldSphereProps.color = { blue: 150, green: 250, red: 150 }; + worldSphereProps.position = jointPosition_WorldSpaceOffset; + 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 +// each frame, update box with new orientation +var worldToJointRotationTest_boxEntity; +function worldToJointRotationTest() { + var jointIndex = MyAvatar.getJointIndex("RightHandPinky4"); + var avatarPos = MyAvatar.position; + + var jointPosition_WorldSpace = MyAvatar.getJointPosition(jointIndex); + var jointOffset_WorldSpace = { x: 0.0, y: 0, z: 0 }; + var jointPosition_WorldSpaceOffset = Vec3.sum(jointPosition_WorldSpace, jointOffset_WorldSpace); + var jointPosition_JointSpaceOffset = MyAvatar.worldToJointPoint(jointPosition_WorldSpaceOffset, jointIndex); + + var jointBoxProps = Object.create(debugBoxBaseProperties); + jointBoxProps.name = "worldToJointRotationTest_Box"; + jointBoxProps.color = { blue: 0, green: 0, red: 250 }; + jointBoxProps.localPosition = jointPosition_JointSpaceOffset; + jointBoxProps.parentID = AVATAR_SELF_ID; + jointBoxProps.parentJointIndex = jointIndex; + worldToJointRotationTest_boxEntity = Entities.addEntity(jointBoxProps); +} +function worldToJointRotationTest_update(deltaTime) { + var jointIndex = MyAvatar.getJointIndex("RightHandPinky4"); + var worldRot = Quat.fromPitchYawRollDegrees(0,0,0); + var avatarRot = MyAvatar.worldToJointRotation(worldRot, jointIndex); + var newProperties = { localRotation: avatarRot }; + Entities.editEntity(worldToJointRotationTest_boxEntity, newProperties); +} + +worldToJointPointTest(); +worldToJointDirectionTest(); +worldToJointRotationTest(); + +Script.update.connect(worldToJointDirectionTest_update); +Script.update.connect(worldToJointRotationTest_update); diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 2c3cc496dc..a3583e7808 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -12,7 +12,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global Script, SelectionDisplay, LightOverlayManager, CameraManager, Grid, GridTool, EntityListTool, Vec3, SelectionManager, Overlays, OverlayWebWindow, UserActivityLogger, +/* global Script, SelectionDisplay, LightOverlayManager, CameraManager, Grid, GridTool, EntityListTool, Vec3, SelectionManager, Overlays, OverlayWebWindow, UserActivityLogger, Settings, Entities, Tablet, Toolbars, Messages, Menu, Camera, progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, ParticleExplorerTool */ (function() { // BEGIN LOCAL_SCOPE @@ -80,27 +80,7 @@ selectionManager.addEventListener(function () { } var type = Entities.getEntityProperties(selectedEntityID, "type").type; if (type === "ParticleEffect") { - // Destroy the old particles web view first - particleExplorerTool.destroyWebView(); - particleExplorerTool.createWebView(); - var properties = Entities.getEntityProperties(selectedEntityID); - var particleData = { - messageType: "particle_settings", - currentProperties: properties - }; - selectedParticleEntityID = selectedEntityID; - particleExplorerTool.setActiveParticleEntity(selectedParticleEntityID); - - particleExplorerTool.webView.webEventReceived.connect(function (data) { - data = JSON.parse(data); - if (data.messageType === "page_loaded") { - particleExplorerTool.webView.emitScriptEvent(JSON.stringify(particleData)); - } - }); - - // Switch to particle explorer - var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); - tablet.sendToQml({method: 'selectTab', params: {id: 'particle'}}); + selectParticleEntity(selectedEntityID); } else { needToDestroyParticleExplorer = true; } @@ -218,7 +198,7 @@ function hideMarketplace() { // } function adjustPositionPerBoundingBox(position, direction, registration, dimensions, orientation) { - // Adjust the position such that the bounding box (registration, dimenions, and orientation) lies behind the original + // Adjust the position such that the bounding box (registration, dimenions, and orientation) lies behind the original // position in the given direction. var CORNERS = [ { x: 0, y: 0, z: 0 }, @@ -1373,7 +1353,7 @@ function parentSelectedEntities() { } }); - if(parentCheck) { + if (parentCheck) { Window.notify("Entities parented"); }else { Window.notify("Entities are already parented to last"); @@ -1575,7 +1555,7 @@ function importSVO(importURL) { var properties = Entities.getEntityProperties(pastedEntityIDs[0], ["position", "dimensions", "registrationPoint"]); var position = Vec3.sum(deltaPosition, properties.position); - position = grid.snapToSurface(grid.snapToGrid(position, false, properties.dimensions, + position = grid.snapToSurface(grid.snapToGrid(position, false, properties.dimensions, properties.registrationPoint), properties.dimensions, properties.registrationPoint); deltaPosition = Vec3.subtract(position, properties.position); } @@ -1907,11 +1887,11 @@ var PropertiesTool = function (opts) { } pushCommandForSelections(); selectionManager._update(); - } else if(data.type === 'parent') { + } else if (data.type === 'parent') { parentSelectedEntities(); - } else if(data.type === 'unparent') { + } else if (data.type === 'unparent') { unparentSelectedEntities(); - } else if(data.type === 'saveUserData'){ + } else if (data.type === 'saveUserData'){ //the event bridge and json parsing handle our avatar id string differently. var actualID = data.id.split('"')[1]; Entities.editEntity(actualID, data.properties); @@ -2203,6 +2183,10 @@ var selectedParticleEntityID = null; function selectParticleEntity(entityID) { var properties = Entities.getEntityProperties(entityID); + + if (properties.emitOrientation) { + properties.emitOrientation = Quat.safeEulerAngles(properties.emitOrientation); + } var particleData = { messageType: "particle_settings", currentProperties: properties @@ -2212,6 +2196,7 @@ function selectParticleEntity(entityID) { selectedParticleEntity = entityID; particleExplorerTool.setActiveParticleEntity(entityID); + particleExplorerTool.webView.emitScriptEvent(JSON.stringify(particleData)); // Switch to particle explorer @@ -2229,7 +2214,7 @@ entityListTool.webView.webEventReceived.connect(function (data) { if (data.type === 'parent') { parentSelectedEntities(); - } else if(data.type === 'unparent') { + } else if (data.type === 'unparent') { unparentSelectedEntities(); } else if (data.type === "selectionUpdate") { var ids = data.entityIds; @@ -2250,4 +2235,3 @@ entityListTool.webView.webEventReceived.connect(function (data) { }); }()); // END LOCAL_SCOPE - diff --git a/scripts/system/html/css/hifi-style.css b/scripts/system/html/css/hifi-style.css index ec6cd1a402..e1e4f67723 100644 --- a/scripts/system/html/css/hifi-style.css +++ b/scripts/system/html/css/hifi-style.css @@ -172,4 +172,4 @@ input[type=radio]:active + label > span > span{ } .blueButton:disabled { background-image: linear-gradient(#FFFFFF, #AFAFAF); -} \ No newline at end of file +} diff --git a/scripts/system/html/js/eventBridgeLoader.js b/scripts/system/html/js/eventBridgeLoader.js index 0e95345b40..411780853b 100644 --- a/scripts/system/html/js/eventBridgeLoader.js +++ b/scripts/system/html/js/eventBridgeLoader.js @@ -13,7 +13,7 @@ var WebChannel; openEventBridge = function(callback) { WebChannel = new QWebChannel(qt.webChannelTransport, function (channel) { - EventBridge = WebChannel.objects.eventBridgeWrapper.eventBridge; + EventBridge = WebChannel.objects.eventBridge; callback(EventBridge); }); } diff --git a/scripts/system/particle_explorer/dat.gui.min.js b/scripts/system/particle_explorer/dat.gui.min.js deleted file mode 100644 index 8ea141a966..0000000000 --- a/scripts/system/particle_explorer/dat.gui.min.js +++ /dev/null @@ -1,95 +0,0 @@ -/** - * dat-gui JavaScript Controller Library - * http://code.google.com/p/dat-gui - * - * Copyright 2011 Data Arts Team, Google Creative Lab - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - */ -var dat=dat||{};dat.gui=dat.gui||{};dat.utils=dat.utils||{};dat.controllers=dat.controllers||{};dat.dom=dat.dom||{};dat.color=dat.color||{};dat.utils.css=function(){return{load:function(f,a){a=a||document;var d=a.createElement("link");d.type="text/css";d.rel="stylesheet";d.href=f;a.getElementsByTagName("head")[0].appendChild(d)},inject:function(f,a){a=a||document;var d=document.createElement("style");d.type="text/css";d.innerHTML=f;a.getElementsByTagName("head")[0].appendChild(d)}}}(); -dat.utils.common=function(){var f=Array.prototype.forEach,a=Array.prototype.slice;return{BREAK:{},extend:function(d){this.each(a.call(arguments,1),function(a){for(var c in a)this.isUndefined(a[c])||(d[c]=a[c])},this);return d},defaults:function(d){this.each(a.call(arguments,1),function(a){for(var c in a)this.isUndefined(d[c])&&(d[c]=a[c])},this);return d},compose:function(){var d=a.call(arguments);return function(){for(var e=a.call(arguments),c=d.length-1;0<=c;c--)e=[d[c].apply(this,e)];return e[0]}}, -each:function(a,e,c){if(a)if(f&&a.forEach&&a.forEach===f)a.forEach(e,c);else if(a.length===a.length+0)for(var b=0,p=a.length;bthis.__max&&(a=this.__max);void 0!==this.__step&&0!=a%this.__step&&(a=Math.round(a/this.__step)*this.__step);return e.superclass.prototype.setValue.call(this,a)},min:function(a){this.__min=a;return this},max:function(a){this.__max=a;return this},step:function(a){this.__impliedStep=this.__step=a;this.__precision=d(a);return this}});return e}(dat.controllers.Controller,dat.utils.common); -dat.controllers.NumberControllerBox=function(f,a,d){var e=function(c,b,f){function q(){var a=parseFloat(n.__input.value);d.isNaN(a)||n.setValue(a)}function l(a){var b=u-a.clientY;n.setValue(n.getValue()+b*n.__impliedStep);u=a.clientY}function r(){a.unbind(window,"mousemove",l);a.unbind(window,"mouseup",r)}this.__truncationSuspended=!1;e.superclass.call(this,c,b,f);var n=this,u;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"change",q);a.bind(this.__input, -"blur",function(){q();n.__onFinishChange&&n.__onFinishChange.call(n,n.getValue())});a.bind(this.__input,"mousedown",function(b){a.bind(window,"mousemove",l);a.bind(window,"mouseup",r);u=b.clientY});a.bind(this.__input,"keydown",function(a){13===a.keyCode&&(n.__truncationSuspended=!0,this.blur(),n.__truncationSuspended=!1)});this.updateDisplay();this.domElement.appendChild(this.__input)};e.superclass=f;d.extend(e.prototype,f.prototype,{updateDisplay:function(){var a=this.__input,b;if(this.__truncationSuspended)b= -this.getValue();else{b=this.getValue();var d=Math.pow(10,this.__precision);b=Math.round(b*d)/d}a.value=b;return e.superclass.prototype.updateDisplay.call(this)}});return e}(dat.controllers.NumberController,dat.dom.dom,dat.utils.common); -dat.controllers.NumberControllerSlider=function(f,a,d,e,c){function b(a,b,c,e,d){return e+(a-b)/(c-b)*(d-e)}var p=function(c,e,d,f,u){function A(c){c.preventDefault();var e=a.getOffset(k.__background),d=a.getWidth(k.__background);k.setValue(b(c.clientX,e.left,e.left+d,k.__min,k.__max));return!1}function g(){a.unbind(window,"mousemove",A);a.unbind(window,"mouseup",g);k.__onFinishChange&&k.__onFinishChange.call(k,k.getValue())}p.superclass.call(this,c,e,{min:d,max:f,step:u});var k=this;this.__background= -document.createElement("div");this.__foreground=document.createElement("div");a.bind(this.__background,"mousedown",function(b){a.bind(window,"mousemove",A);a.bind(window,"mouseup",g);A(b)});a.addClass(this.__background,"slider");a.addClass(this.__foreground,"slider-fg");this.updateDisplay();this.__background.appendChild(this.__foreground);this.domElement.appendChild(this.__background)};p.superclass=f;p.useDefaultStyles=function(){d.inject(c)};e.extend(p.prototype,f.prototype,{updateDisplay:function(){var a= -(this.getValue()-this.__min)/(this.__max-this.__min);this.__foreground.style.width=100*a+"%";return p.superclass.prototype.updateDisplay.call(this)}});return p}(dat.controllers.NumberController,dat.dom.dom,dat.utils.css,dat.utils.common,"/**\n * dat-gui JavaScript Controller Library\n * http://code.google.com/p/dat-gui\n *\n * Copyright 2011 Data Arts Team, Google Creative Lab\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n */\n\n.slider {\n box-shadow: inset 0 2px 4px rgba(0,0,0,0.15);\n height: 1em;\n border-radius: 1em;\n background-color: #eee;\n padding: 0 0.5em;\n overflow: hidden;\n}\n\n.slider-fg {\n padding: 1px 0 2px 0;\n background-color: #aaa;\n height: 1em;\n margin-left: -0.5em;\n padding-right: 0.5em;\n border-radius: 1em 0 0 1em;\n}\n\n.slider-fg:after {\n display: inline-block;\n border-radius: 1em;\n background-color: #fff;\n border: 1px solid #aaa;\n content: '';\n float: right;\n margin-right: -1em;\n margin-top: -1px;\n height: 0.9em;\n width: 0.9em;\n}"); -dat.controllers.FunctionController=function(f,a,d){var e=function(c,b,d){e.superclass.call(this,c,b);var f=this;this.__button=document.createElement("div");this.__button.innerHTML=void 0===d?"Fire":d;a.bind(this.__button,"click",function(a){a.preventDefault();f.fire();return!1});a.addClass(this.__button,"button");this.domElement.appendChild(this.__button)};e.superclass=f;d.extend(e.prototype,f.prototype,{fire:function(){this.__onChange&&this.__onChange.call(this);this.getValue().call(this.object); -this.__onFinishChange&&this.__onFinishChange.call(this,this.getValue())}});return e}(dat.controllers.Controller,dat.dom.dom,dat.utils.common); -dat.controllers.BooleanController=function(f,a,d){var e=function(c,b){e.superclass.call(this,c,b);var d=this;this.__prev=this.getValue();this.__checkbox=document.createElement("input");this.__checkbox.setAttribute("type","checkbox");a.bind(this.__checkbox,"change",function(){d.setValue(!d.__prev)},!1);this.domElement.appendChild(this.__checkbox);this.updateDisplay()};e.superclass=f;d.extend(e.prototype,f.prototype,{setValue:function(a){a=e.superclass.prototype.setValue.call(this,a);this.__onFinishChange&& -this.__onFinishChange.call(this,this.getValue());this.__prev=this.getValue();return a},updateDisplay:function(){!0===this.getValue()?(this.__checkbox.setAttribute("checked","checked"),this.__checkbox.checked=!0):this.__checkbox.checked=!1;return e.superclass.prototype.updateDisplay.call(this)}});return e}(dat.controllers.Controller,dat.dom.dom,dat.utils.common); -dat.color.toString=function(f){return function(a){if(1==a.a||f.isUndefined(a.a)){for(a=a.hex.toString(16);6>a.length;)a="0"+a;return"#"+a}return"rgba("+Math.round(a.r)+","+Math.round(a.g)+","+Math.round(a.b)+","+a.a+")"}}(dat.utils.common); -dat.color.interpret=function(f,a){var d,e,c=[{litmus:a.isString,conversions:{THREE_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9])([A-F0-9])([A-F0-9])$/i);return null===a?!1:{space:"HEX",hex:parseInt("0x"+a[1].toString()+a[1].toString()+a[2].toString()+a[2].toString()+a[3].toString()+a[3].toString())}},write:f},SIX_CHAR_HEX:{read:function(a){a=a.match(/^#([A-F0-9]{6})$/i);return null===a?!1:{space:"HEX",hex:parseInt("0x"+a[1].toString())}},write:f},CSS_RGB:{read:function(a){a=a.match(/^rgb\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/); -return null===a?!1:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3])}},write:f},CSS_RGBA:{read:function(a){a=a.match(/^rgba\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\,\s*(.+)\s*\)/);return null===a?!1:{space:"RGB",r:parseFloat(a[1]),g:parseFloat(a[2]),b:parseFloat(a[3]),a:parseFloat(a[4])}},write:f}}},{litmus:a.isNumber,conversions:{HEX:{read:function(a){return{space:"HEX",hex:a,conversionName:"HEX"}},write:function(a){return a.hex}}}},{litmus:a.isArray,conversions:{RGB_ARRAY:{read:function(a){return 3!= -a.length?!1:{space:"RGB",r:a[0],g:a[1],b:a[2]}},write:function(a){return[a.r,a.g,a.b]}},RGBA_ARRAY:{read:function(a){return 4!=a.length?!1:{space:"RGB",r:a[0],g:a[1],b:a[2],a:a[3]}},write:function(a){return[a.r,a.g,a.b,a.a]}}}},{litmus:a.isObject,conversions:{RGBA_OBJ:{read:function(b){return a.isNumber(b.r)&&a.isNumber(b.g)&&a.isNumber(b.b)&&a.isNumber(b.a)?{space:"RGB",r:b.r,g:b.g,b:b.b,a:b.a}:!1},write:function(a){return{r:a.r,g:a.g,b:a.b,a:a.a}}},RGB_OBJ:{read:function(b){return a.isNumber(b.r)&& -a.isNumber(b.g)&&a.isNumber(b.b)?{space:"RGB",r:b.r,g:b.g,b:b.b}:!1},write:function(a){return{r:a.r,g:a.g,b:a.b}}},HSVA_OBJ:{read:function(b){return a.isNumber(b.h)&&a.isNumber(b.s)&&a.isNumber(b.v)&&a.isNumber(b.a)?{space:"HSV",h:b.h,s:b.s,v:b.v,a:b.a}:!1},write:function(a){return{h:a.h,s:a.s,v:a.v,a:a.a}}},HSV_OBJ:{read:function(b){return a.isNumber(b.h)&&a.isNumber(b.s)&&a.isNumber(b.v)?{space:"HSV",h:b.h,s:b.s,v:b.v}:!1},write:function(a){return{h:a.h,s:a.s,v:a.v}}}}}];return function(){e=!1; -var b=1\n\n Here\'s the new load parameter for your GUI\'s constructor:\n\n \n\n
\n\n Automatically save\n values to localStorage on exit.\n\n
The values saved to localStorage will\n override those passed to dat.GUI\'s constructor. This makes it\n easier to work incrementally, but localStorage is fragile,\n and your friends may not see the same values you do.\n \n
\n \n
\n\n', -".dg {\n /** Clear list styles */\n /* Auto-place container */\n /* Auto-placed GUI's */\n /* Line items that don't contain folders. */\n /** Folder names */\n /** Hides closed items */\n /** Controller row */\n /** Name-half (left) */\n /** Controller-half (right) */\n /** Controller placement */\n /** Shorter number boxes when slider is present. */\n /** Ensure the entire boolean and function row shows a hand */ }\n .dg ul {\n list-style: none;\n margin: 0;\n padding: 0;\n width: 100%;\n clear: both; }\n .dg.ac {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n height: 0;\n z-index: 0; }\n .dg:not(.ac) .main {\n /** Exclude mains in ac so that we don't hide close button */\n overflow: hidden; }\n .dg.main {\n -webkit-transition: opacity 0.1s linear;\n -o-transition: opacity 0.1s linear;\n -moz-transition: opacity 0.1s linear;\n transition: opacity 0.1s linear; }\n .dg.main.taller-than-window {\n overflow-y: auto; }\n .dg.main.taller-than-window .close-button {\n opacity: 1;\n /* TODO, these are style notes */\n margin-top: -1px;\n border-top: 1px solid #2c2c2c; }\n .dg.main ul.closed .close-button {\n opacity: 1 !important; }\n .dg.main:hover .close-button,\n .dg.main .close-button.drag {\n opacity: 1; }\n .dg.main .close-button {\n /*opacity: 0;*/\n -webkit-transition: opacity 0.1s linear;\n -o-transition: opacity 0.1s linear;\n -moz-transition: opacity 0.1s linear;\n transition: opacity 0.1s linear;\n border: 0;\n position: absolute;\n line-height: 19px;\n height: 20px;\n /* TODO, these are style notes */\n cursor: pointer;\n text-align: center;\n background-color: #000; }\n .dg.main .close-button:hover {\n background-color: #111; }\n .dg.a {\n float: right;\n margin-right: 15px;\n overflow-x: hidden; }\n .dg.a.has-save > ul {\n margin-top: 27px; }\n .dg.a.has-save > ul.closed {\n margin-top: 0; }\n .dg.a .save-row {\n position: fixed;\n top: 0;\n z-index: 1002; }\n .dg li {\n -webkit-transition: height 0.1s ease-out;\n -o-transition: height 0.1s ease-out;\n -moz-transition: height 0.1s ease-out;\n transition: height 0.1s ease-out; }\n .dg li:not(.folder) {\n cursor: auto;\n height: 27px;\n line-height: 27px;\n overflow: hidden;\n padding: 0 4px 0 5px; }\n .dg li.folder {\n padding: 0;\n border-left: 4px solid rgba(0, 0, 0, 0); }\n .dg li.title {\n cursor: pointer;\n margin-left: -4px; }\n .dg .closed li:not(.title),\n .dg .closed ul li,\n .dg .closed ul li > * {\n height: 0;\n overflow: hidden;\n border: 0; }\n .dg .cr {\n clear: both;\n padding-left: 3px;\n height: 27px; }\n .dg .property-name {\n cursor: default;\n float: left;\n clear: left;\n width: 40%;\n overflow: hidden;\n text-overflow: ellipsis; }\n .dg .c {\n float: left;\n width: 60%; }\n .dg .c input[type=text] {\n border: 0;\n margin-top: 4px;\n padding: 3px;\n width: 100%;\n float: right; }\n .dg .has-slider input[type=text] {\n width: 30%;\n /*display: none;*/\n margin-left: 0; }\n .dg .slider {\n float: left;\n width: 66%;\n margin-left: -5px;\n margin-right: 0;\n height: 19px;\n margin-top: 4px; }\n .dg .slider-fg {\n height: 100%; }\n .dg .c input[type=checkbox] {\n margin-top: 9px; }\n .dg .c select {\n margin-top: 5px; }\n .dg .cr.function,\n .dg .cr.function .property-name,\n .dg .cr.function *,\n .dg .cr.boolean,\n .dg .cr.boolean * {\n cursor: pointer; }\n .dg .selector {\n display: none;\n position: absolute;\n margin-left: -9px;\n margin-top: 23px;\n z-index: 10; }\n .dg .c:hover .selector,\n .dg .selector.drag {\n display: block; }\n .dg li.save-row {\n padding: 0; }\n .dg li.save-row .button {\n display: inline-block;\n padding: 0px 6px; }\n .dg.dialogue {\n background-color: #222;\n width: 460px;\n padding: 15px;\n font-size: 13px;\n line-height: 15px; }\n\n/* TODO Separate style and structure */\n#dg-new-constructor {\n padding: 10px;\n color: #222;\n font-family: Monaco, monospace;\n font-size: 10px;\n border: 0;\n resize: none;\n box-shadow: inset 1px 1px 1px #888;\n word-wrap: break-word;\n margin: 12px 0;\n display: block;\n width: 440px;\n overflow-y: scroll;\n height: 100px;\n position: relative; }\n\n#dg-local-explain {\n display: none;\n font-size: 11px;\n line-height: 17px;\n border-radius: 3px;\n background-color: #333;\n padding: 8px;\n margin-top: 10px; }\n #dg-local-explain code {\n font-size: 10px; }\n\n#dat-gui-save-locally {\n display: none; }\n\n/** Main type */\n.dg {\n color: #eee;\n font: 11px 'Lucida Grande', sans-serif;\n text-shadow: 0 -1px 0 #111;\n /** Auto place */\n /* Controller row,
  • */\n /** Controllers */ }\n .dg.main {\n /** Scrollbar */ }\n .dg.main::-webkit-scrollbar {\n width: 5px;\n background: #1a1a1a; }\n .dg.main::-webkit-scrollbar-corner {\n height: 0;\n display: none; }\n .dg.main::-webkit-scrollbar-thumb {\n border-radius: 5px;\n background: #676767; }\n .dg li:not(.folder) {\n background: #1a1a1a;\n border-bottom: 1px solid #2c2c2c; }\n .dg li.save-row {\n line-height: 25px;\n background: #dad5cb;\n border: 0; }\n .dg li.save-row select {\n margin-left: 5px;\n width: 108px; }\n .dg li.save-row .button {\n margin-left: 5px;\n margin-top: 1px;\n border-radius: 2px;\n font-size: 9px;\n line-height: 7px;\n padding: 4px 4px 5px 4px;\n background: #c5bdad;\n color: #fff;\n text-shadow: 0 1px 0 #b0a58f;\n box-shadow: 0 -1px 0 #b0a58f;\n cursor: pointer; }\n .dg li.save-row .button.gears {\n background: #c5bdad url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAsAAAANCAYAAAB/9ZQ7AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQJJREFUeNpiYKAU/P//PwGIC/ApCABiBSAW+I8AClAcgKxQ4T9hoMAEUrxx2QSGN6+egDX+/vWT4e7N82AMYoPAx/evwWoYoSYbACX2s7KxCxzcsezDh3evFoDEBYTEEqycggWAzA9AuUSQQgeYPa9fPv6/YWm/Acx5IPb7ty/fw+QZblw67vDs8R0YHyQhgObx+yAJkBqmG5dPPDh1aPOGR/eugW0G4vlIoTIfyFcA+QekhhHJhPdQxbiAIguMBTQZrPD7108M6roWYDFQiIAAv6Aow/1bFwXgis+f2LUAynwoIaNcz8XNx3Dl7MEJUDGQpx9gtQ8YCueB+D26OECAAQDadt7e46D42QAAAABJRU5ErkJggg==) 2px 1px no-repeat;\n height: 7px;\n width: 8px; }\n .dg li.save-row .button:hover {\n background-color: #bab19e;\n box-shadow: 0 -1px 0 #b0a58f; }\n .dg li.folder {\n border-bottom: 0; }\n .dg li.title {\n padding-left: 16px;\n background: black url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlI+hKgFxoCgAOw==) 6px 10px no-repeat;\n cursor: pointer;\n border-bottom: 1px solid rgba(255, 255, 255, 0.2); }\n .dg .closed li.title {\n background-image: url(data:image/gif;base64,R0lGODlhBQAFAJEAAP////Pz8////////yH5BAEAAAIALAAAAAAFAAUAAAIIlGIWqMCbWAEAOw==); }\n .dg .cr.boolean {\n border-left: 3px solid #806787; }\n .dg .cr.function {\n border-left: 3px solid #e61d5f; }\n .dg .cr.number {\n border-left: 3px solid #2fa1d6; }\n .dg .cr.number input[type=text] {\n color: #2fa1d6; }\n .dg .cr.string {\n border-left: 3px solid #1ed36f; }\n .dg .cr.string input[type=text] {\n color: #1ed36f; }\n .dg .cr.function:hover, .dg .cr.boolean:hover {\n background: #111; }\n .dg .c input[type=text] {\n background: #303030;\n outline: none; }\n .dg .c input[type=text]:hover {\n background: #3c3c3c; }\n .dg .c input[type=text]:focus {\n background: #494949;\n color: #fff; }\n .dg .c .slider {\n background: #303030;\n cursor: ew-resize; }\n .dg .c .slider-fg {\n background: #2fa1d6; }\n .dg .c .slider:hover {\n background: #3c3c3c; }\n .dg .c .slider:hover .slider-fg {\n background: #44abda; }\n", -dat.controllers.factory=function(f,a,d,e,c,b,p){return function(q,l,r,n){var u=q[l];if(p.isArray(r)||p.isObject(r))return new f(q,l,r);if(p.isNumber(u))return p.isNumber(r)&&p.isNumber(n)?new d(q,l,r,n):new a(q,l,{min:r,max:n});if(p.isString(u))return new e(q,l);if(p.isFunction(u))return new c(q,l,"");if(p.isBoolean(u))return new b(q,l)}}(dat.controllers.OptionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.StringController=function(f,a,d){var e= -function(c,b){function d(){f.setValue(f.__input.value)}e.superclass.call(this,c,b);var f=this;this.__input=document.createElement("input");this.__input.setAttribute("type","text");a.bind(this.__input,"keyup",d);a.bind(this.__input,"change",d);a.bind(this.__input,"blur",function(){f.__onFinishChange&&f.__onFinishChange.call(f,f.getValue())});a.bind(this.__input,"keydown",function(a){13===a.keyCode&&this.blur()});this.updateDisplay();this.domElement.appendChild(this.__input)};e.superclass=f;d.extend(e.prototype, -f.prototype,{updateDisplay:function(){a.isActive(this.__input)||(this.__input.value=this.getValue());return e.superclass.prototype.updateDisplay.call(this)}});return e}(dat.controllers.Controller,dat.dom.dom,dat.utils.common),dat.controllers.FunctionController,dat.controllers.BooleanController,dat.utils.common),dat.controllers.Controller,dat.controllers.BooleanController,dat.controllers.FunctionController,dat.controllers.NumberControllerBox,dat.controllers.NumberControllerSlider,dat.controllers.OptionController, -dat.controllers.ColorController=function(f,a,d,e,c){function b(a,b,d,e){a.style.background="";c.each(l,function(c){a.style.cssText+="background: "+c+"linear-gradient("+b+", "+d+" 0%, "+e+" 100%); "})}function p(a){a.style.background="";a.style.cssText+="background: -moz-linear-gradient(top, #ff0000 0%, #ff00ff 17%, #0000ff 34%, #00ffff 50%, #00ff00 67%, #ffff00 84%, #ff0000 100%);";a.style.cssText+="background: -webkit-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"; -a.style.cssText+="background: -o-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";a.style.cssText+="background: -ms-linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);";a.style.cssText+="background: linear-gradient(top, #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);"}var q=function(f,n){function u(b){v(b);a.bind(window,"mousemove",v);a.bind(window, -"mouseup",l)}function l(){a.unbind(window,"mousemove",v);a.unbind(window,"mouseup",l)}function g(){var a=e(this.value);!1!==a?(t.__color.__state=a,t.setValue(t.__color.toOriginal())):this.value=t.__color.toString()}function k(){a.unbind(window,"mousemove",w);a.unbind(window,"mouseup",k)}function v(b){b.preventDefault();var c=a.getWidth(t.__saturation_field),d=a.getOffset(t.__saturation_field),e=(b.clientX-d.left+document.body.scrollLeft)/c;b=1-(b.clientY-d.top+document.body.scrollTop)/c;1 -b&&(b=0);1e&&(e=0);t.__color.v=b;t.__color.s=e;t.setValue(t.__color.toOriginal());return!1}function w(b){b.preventDefault();var c=a.getHeight(t.__hue_field),d=a.getOffset(t.__hue_field);b=1-(b.clientY-d.top+document.body.scrollTop)/c;1b&&(b=0);t.__color.h=360*b;t.setValue(t.__color.toOriginal());return!1}q.superclass.call(this,f,n);this.__color=new d(this.getValue());this.__temp=new d(0);var t=this;this.domElement=document.createElement("div");a.makeSelectable(this.domElement,!1); -this.__selector=document.createElement("div");this.__selector.className="selector";this.__saturation_field=document.createElement("div");this.__saturation_field.className="saturation-field";this.__field_knob=document.createElement("div");this.__field_knob.className="field-knob";this.__field_knob_border="2px solid ";this.__hue_knob=document.createElement("div");this.__hue_knob.className="hue-knob";this.__hue_field=document.createElement("div");this.__hue_field.className="hue-field";this.__input=document.createElement("input"); -this.__input.type="text";this.__input_textShadow="0 1px 1px ";a.bind(this.__input,"keydown",function(a){13===a.keyCode&&g.call(this)});a.bind(this.__input,"blur",g);a.bind(this.__selector,"mousedown",function(b){a.addClass(this,"drag").bind(window,"mouseup",function(b){a.removeClass(t.__selector,"drag")})});var y=document.createElement("div");c.extend(this.__selector.style,{width:"122px",height:"102px",padding:"3px",backgroundColor:"#222",boxShadow:"0px 1px 3px rgba(0,0,0,0.3)"});c.extend(this.__field_knob.style, -{position:"absolute",width:"12px",height:"12px",border:this.__field_knob_border+(.5>this.__color.v?"#fff":"#000"),boxShadow:"0px 1px 3px rgba(0,0,0,0.5)",borderRadius:"12px",zIndex:1});c.extend(this.__hue_knob.style,{position:"absolute",width:"15px",height:"2px",borderRight:"4px solid #fff",zIndex:1});c.extend(this.__saturation_field.style,{width:"100px",height:"100px",border:"1px solid #555",marginRight:"3px",display:"inline-block",cursor:"pointer"});c.extend(y.style,{width:"100%",height:"100%", -background:"none"});b(y,"top","rgba(0,0,0,0)","#000");c.extend(this.__hue_field.style,{width:"15px",height:"100px",display:"inline-block",border:"1px solid #555",cursor:"ns-resize"});p(this.__hue_field);c.extend(this.__input.style,{outline:"none",textAlign:"center",color:"#fff",border:0,fontWeight:"bold",textShadow:this.__input_textShadow+"rgba(0,0,0,0.7)"});a.bind(this.__saturation_field,"mousedown",u);a.bind(this.__field_knob,"mousedown",u);a.bind(this.__hue_field,"mousedown",function(b){w(b);a.bind(window, -"mousemove",w);a.bind(window,"mouseup",k)});this.__saturation_field.appendChild(y);this.__selector.appendChild(this.__field_knob);this.__selector.appendChild(this.__saturation_field);this.__selector.appendChild(this.__hue_field);this.__hue_field.appendChild(this.__hue_knob);this.domElement.appendChild(this.__input);this.domElement.appendChild(this.__selector);this.updateDisplay()};q.superclass=f;c.extend(q.prototype,f.prototype,{updateDisplay:function(){var a=e(this.getValue());if(!1!==a){var f=!1; -c.each(d.COMPONENTS,function(b){if(!c.isUndefined(a[b])&&!c.isUndefined(this.__color.__state[b])&&a[b]!==this.__color.__state[b])return f=!0,{}},this);f&&c.extend(this.__color.__state,a)}c.extend(this.__temp.__state,this.__color.__state);this.__temp.a=1;var l=.5>this.__color.v||.5a&&(a+=1);return{h:360*a,s:c/b,v:b/255}},rgb_to_hex:function(a,d,e){a=this.hex_with_component(0,2,a);a=this.hex_with_component(a,1,d);return a=this.hex_with_component(a,0,e)},component_from_hex:function(a,d){return a>>8*d&255},hex_with_component:function(a,d,e){return e<<(f=8*d)|a&~(255< 0) { + json[key] = document.getElementById(key) + .value; + } + } + + return json; + }, + fillFields: function (currentProperties) { + var self = this; + var fields = document.getElementsByTagName("input"); + + if (!currentProperties.locked) { + for (var i = 0; i < fields.length; i++) { + fields[i].removeAttribute("disabled"); + } + } + if (self.onSelect) { + self.onSelect(); + } + var keys = Object.keys(currentProperties); + + + for (var e in keys) { + if (keys.hasOwnProperty(e)) { + var value = keys[e]; + + var property = currentProperties[value]; + var field = self.builtRows[value]; + if (field) { + var el = document.getElementById(value); + + if (field.className.indexOf("radian") !== -1) { + el.value = property / RADIANS_PER_DEGREE; + el.onchange({ + target: el + }); + } else if (field.className.indexOf("range") !== -1 || field.className.indexOf("texture") !== -1) { + el.value = property; + el.onchange({ + target: el + }); + } else if (field.className.indexOf("checkbox") !== -1) { + if (property) { + el.setAttribute("checked", property); + } else { + el.removeAttribute("checked"); + } + } else if (field.className.indexOf("vector-section") !== -1) { + if (field.className.indexOf("rgb") !== -1) { + var red = document.getElementById(value + "-red"); + var blue = document.getElementById(value + "-blue"); + var green = document.getElementById(value + "-green"); + red.value = parseInt(property.red); + blue.value = parseInt(property.blue); + green.value = parseInt(property.green); + + red.oninput({ + target: red + }); + } else if (field.className.indexOf("xyz") !== -1) { + var x = document.getElementById(value + "-x"); + var y = document.getElementById(value + "-y"); + var z = document.getElementById(value + "-z"); + + x.value = roundFloat(property.x, 100); + y.value = roundFloat(property.y, 100); + z.value = roundFloat(property.z, 100); + } else if (field.className.indexOf("pyr") !== -1) { + var pitch = document.getElementById(value + "-Pitch"); + var yaw = document.getElementById(value + "-Yaw"); + var roll = document.getElementById(value + "-Roll"); + + pitch.value = roundFloat(property.x, 100); + yaw.value = roundFloat(property.y, 100); + roll.value = roundFloat(property.z, 100); + + } + } + } + } + } + }, + connect: function (EventBridge) { + this.EventBridge = EventBridge; + + var self = this; + + EventBridge.emitWebEvent(JSON.stringify({ + messageType: 'page_loaded' + })); + + EventBridge.scriptEventReceived.connect(function (data) { + data = JSON.parse(data); + + if (data.messageType === 'particle_settings') { + // Update settings + var currentProperties = data.currentProperties; + self.fillFields(currentProperties); + // Do expected property match with structure; + } else if (data.messageType === 'particle_close') { + self.disableFields(); + } + }); + }, + build: function () { + var self = this; + var sections = Object.keys(this.structure); + this.builtRows = {}; + sections.forEach(function (section, index) { + var properties = self.structure[section]; + self.addSection(self.parent, section, properties, index); + }); + }, + addSection: function (parent, section, properties, index) { + var self = this; + + var sectionDivHeader = document.createElement("div"); + var title = document.createElement("label"); + var dropDown = document.createElement("span"); + + dropDown.className = "arrow"; + sectionDivHeader.className = "section-header"; + title.innerHTML = section; + sectionDivHeader.appendChild(title); + sectionDivHeader.appendChild(dropDown); + var collapsed = index !== 0; + + dropDown.innerHTML = collapsed ? "L" : "M"; + sectionDivHeader.setAttribute("collapsed", collapsed); + parent.appendChild(sectionDivHeader); + + var sectionDivBody = document.createElement("div"); + sectionDivBody.className = "property-group"; + + var animationWrapper = document.createElement("div"); + animationWrapper.className = "section-wrap"; + + for (var property in properties) { + if (properties.hasOwnProperty(property)) { + var builtRow = self.addElement(animationWrapper, properties[property]); + var id = properties[property].id; + if (id) { + self.builtRows[id] = builtRow; + } + } + } + sectionDivBody.appendChild(animationWrapper); + parent.appendChild(sectionDivBody); + _.defer(function () { + var height = (animationWrapper.clientHeight) + "px"; + if (collapsed) { + sectionDivBody.classList.remove("visible"); + sectionDivBody.style.maxHeight = "0px"; + } else { + sectionDivBody.classList.add("visible"); + sectionDivBody.style.maxHeight = height; + } + + sectionDivHeader.onclick = function () { + collapsed = !collapsed; + if (collapsed) { + sectionDivBody.classList.remove("visible"); + sectionDivBody.style.maxHeight = "0px"; + } else { + sectionDivBody.classList.add("visible"); + sectionDivBody.style.maxHeight = (animationWrapper.clientHeight) + "px"; + } + // sectionDivBody.style.display = collapsed ? "none": "block"; + dropDown.innerHTML = collapsed ? "L" : "M"; + sectionDivHeader.setAttribute("collapsed", collapsed); + }; + }); + }, + addLabel: function (parent, group) { + var label = document.createElement("label"); + label.innerHTML = group.name; + parent.appendChild(label); + if (group.unit) { + var span = document.createElement("span"); + span.innerHTML = group.unit; + span.className = "unit"; + label.appendChild(span); + } + return label; + }, + addVector: function (parent, group, labels, domArray) { + var self = this; + var inputs = labels ? labels : ["x", "y", "z"]; + domArray = domArray ? domArray : []; + parent.id = group.id; + for (var index in inputs) { + var element = document.createElement("input"); + + element.setAttribute("type", "number"); + element.className = inputs[index]; + element.id = group.id + "-" + inputs[index]; + + if (group.defaultRange) { + if (group.defaultRange.min) { + element.setAttribute("min", group.defaultRange.min); + } + if (group.defaultRange.max) { + element.setAttribute("max", group.defaultRange.max); + } + if (group.defaultRange.step) { + element.setAttribute("step", group.defaultRange.step); + } + } + if (group.oninput) { + element.oninput = group.oninput; + } else { + element.oninput = function (event) { + self.webBridgeSync(group.id, { + x: domArray[0].value, + y: domArray[1].value, + z: domArray[2].value + }); + }; + } + element.onchange = element.oninput; + domArray.push(element); + } + + this.addLabel(parent, group); + var className = ""; + for (var i = 0; i < inputs.length; i++) { + className += inputs[i].charAt(0) + .toLowerCase(); + } + parent.className += " property vector-section " + className; + + // Add Tuple and the rest + var tupleContainer = document.createElement("div"); + tupleContainer.className = "tuple"; + for (var domIndex in domArray) { + var container = domArray[domIndex]; + var div = document.createElement("div"); + var label = document.createElement("label"); + label.innerHTML = inputs[domIndex] + ":"; + label.setAttribute("for", container.id); + div.appendChild(container); + div.appendChild(label); + tupleContainer.appendChild(div); + } + parent.appendChild(tupleContainer); + }, + addVectorQuaternion: function (parent, group) { + this.addVector(parent, group, ["Pitch", "Yaw", "Roll"]); + }, + addColorPicker: function (parent, group) { + var self = this; + var $colPickContainer = $('
    ', { + id: group.id, + class: "color-picker" + }); + var updateColors = function (red, green, blue) { + $colPickContainer.css('background-color', "rgb(" + + red + "," + + green + "," + + blue + ")"); + }; + + var inputs = ["red", "green", "blue"]; + var domArray = []; + group.oninput = function (event) { + $colPickContainer.colpickSetColor( + { + r: domArray[0].value, + g: domArray[1].value, + b: domArray[2].value + }, + true); + }; + group.defaultRange = { + min: 0, + max: 255, + step: 1 + }; + + parent.appendChild($colPickContainer[0]); + self.addVector(parent, group, inputs, domArray); + + updateColors(domArray[0].value, domArray[1].value, domArray[2].value); + + // Could probably write a custom one for this to completely write out jquery, + // but for now, using the same as earlier. + + /* Color Picker Logic Here */ + + + $colPickContainer.colpick({ + colorScheme: 'dark', + layout: 'hex', + color: { + r: domArray[0].value, + g: domArray[1].value, + b: domArray[2].value + }, + onChange: function (hsb, hex, rgb, el) { + updateColors(rgb.r, rgb.g, rgb.b); + + domArray[0].value = rgb.r; + domArray[1].value = rgb.g; + domArray[2].value = rgb.b; + self.webBridgeSync(group.id, { + red: rgb.r, + green: rgb.g, + blue: rgb.b + }); + }, + onSubmit: function (hsb, hex, rgb, el) { + $(el) + .css('background-color', '#' + hex); + $(el) + .colpickHide(); + domArray[0].value = rgb.r; + domArray[1].value = rgb.g; + domArray[2].value = rgb.b; + self.webBridgeSync(group.id, { + red: rgb.r, + green: rgb.g, + blue: rgb.b + }); + } + }); + }, + addTextureField: function (parent, group) { + var self = this; + this.addLabel(parent, group); + parent.className += " property texture"; + var textureImage = document.createElement("div"); + var textureUrl = document.createElement("input"); + textureUrl.setAttribute("type", "text"); + textureUrl.id = group.id; + textureImage.className = "texture-image no-texture"; + var image = document.createElement("img"); + var imageLoad = _.debounce(function (url) { + if (url.length > 0) { + textureImage.classList.remove("no-texture"); + textureImage.classList.add("with-texture"); + image.src = url; + image.style.display = "block"; + } else { + image.src = ""; + image.style.display = "none"; + textureImage.classList.add("no-texture"); + } + self.webBridgeSync(group.id, url); + }, 250); + + textureUrl.oninput = function (event) { + // Add throttle + var url = event.target.value; + imageLoad(url); + }; + textureUrl.onchange = textureUrl.oninput; + textureImage.appendChild(image); + parent.appendChild(textureImage); + parent.appendChild(textureUrl); + }, + addSlider: function (parent, group) { + var self = this; + this.addLabel(parent, group); + parent.className += " property range"; + var container = document.createElement("div"); + container.className = "slider-wrapper"; + var slider = document.createElement("input"); + slider.setAttribute("type", "range"); + + var inputField = document.createElement("input"); + inputField.setAttribute("type", "number"); + + container.appendChild(slider); + container.appendChild(inputField); + parent.appendChild(container); + + if (group.type === "SliderInteger") { + inputField.setAttribute("min", group.min !== undefined ? group.min : 0); + inputField.setAttribute("step", 1); + + slider.setAttribute("min", group.min !== undefined ? group.min : 0); + slider.setAttribute("max", group.max !== undefined ? group.max : 10000); + slider.setAttribute("step", 1); + + inputField.oninput = function (event) { + + if (parseInt(event.target.value) > parseInt(slider.getAttribute("max")) && group.max !== 1) { + slider.setAttribute("max", event.target.value); + } + slider.value = event.target.value; + + self.webBridgeSync(group.id, slider.value); + }; + inputField.onchange = inputField.oninput; + slider.oninput = function (event) { + inputField.value = event.target.value; + self.webBridgeSync(group.id, slider.value); + }; + + inputField.id = group.id; + } else if (group.type === "SliderRadian") { + slider.setAttribute("min", group.min !== undefined ? group.min : 0); + slider.setAttribute("max", group.max !== undefined ? group.max : 180); + slider.setAttribute("step", 1); + parent.className += " radian"; + inputField.setAttribute("min", (group.min !== undefined ? group.min : 0)); + inputField.setAttribute("max", (group.max !== undefined ? group.max : 180)); + + inputField.oninput = function (event) { + slider.value = event.target.value; + self.webBridgeSync(group.id, slider.value * RADIANS_PER_DEGREE); + }; + inputField.onchange = inputField.oninput; + + inputField.id = group.id; + slider.oninput = function (event) { + if (event.target.value > 0) { + inputField.value = Math.floor(event.target.value); + } else { + inputField.value = Math.ceil(event.target.value); + } + self.webBridgeSync(group.id, slider.value * RADIANS_PER_DEGREE); + }; + var degrees = document.createElement("label"); + degrees.innerHTML = "°"; + degrees.style.fontSize = "1.4rem"; + degrees.style.display = "inline"; + degrees.style.verticalAlign = "top"; + degrees.style.paddingLeft = "0.4rem"; + container.appendChild(degrees); + + } else { + // Must then be Float + inputField.setAttribute("min", group.min !== undefined ? group.min : 0); + slider.setAttribute("step", 0.01); + + slider.setAttribute("min", group.min !== undefined ? group.min : 0); + slider.setAttribute("max", group.max !== undefined ? group.max : 1); + slider.setAttribute("step", 0.01); + + inputField.oninput = function (event) { + if (parseFloat(event.target.value) > parseFloat(slider.getAttribute("max")) && group.max !== 1) { + slider.setAttribute("max", event.target.value); + } + + slider.value = event.target.value; + self.webBridgeSync(group.id, slider.value); + // bind web sock update here. + }; + inputField.onchange = inputField.oninput; + slider.oninput = function (event) { + inputField.value = event.target.value; + self.webBridgeSync(group.id, inputField.value); + }; + + inputField.id = group.id; + } + + // UpdateBinding + }, + addCheckBox: function (parent, group) { + var checkBox = document.createElement("input"); + checkBox.setAttribute("type", "checkbox"); + var self = this; + checkBox.onchange = function (event) { + self.webBridgeSync(group.id, event.target.checked); + }; + checkBox.id = group.id; + parent.appendChild(checkBox); + var label = this.addLabel(parent, group); + label.setAttribute("for", checkBox.id); + parent.className += " property checkbox"; + }, + addElement: function (parent, group) { + var self = this; + var property = document.createElement("div"); + property.id = group.id; + + var row = document.createElement("div"); + switch (group.type) { + case "Button": + var button = document.createElement("input"); + button.setAttribute("type", "button"); + button.id = group.id; + if (group.disabled) { + button.disabled = group.disabled; + } + button.className = group.class; + button.value = group.name; + + button.onclick = group.callback; + parent.appendChild(button); + break; + case "Row": + var hr = document.createElement("hr"); + hr.className = "splitter"; + if (group.id) { + hr.id = group.id; + } + parent.appendChild(hr); + break; + case "Boolean": + self.addCheckBox(row, group); + parent.appendChild(row); + break; + case "SliderFloat": + case "SliderInteger": + case "SliderRadian": + self.addSlider(row, group); + parent.appendChild(row); + break; + case "Texture": + self.addTextureField(row, group); + parent.appendChild(row); + break; + case "Color": + self.addColorPicker(row, group); + parent.appendChild(row); + break; + case "Vector": + self.addVector(row, group); + parent.appendChild(row); + break; + case "VectorQuaternion": + self.addVectorQuaternion(row, group); + parent.appendChild(row); + break; + default: + console.log("not defined"); + } + return row; + } +}; diff --git a/scripts/system/particle_explorer/particle-style.css b/scripts/system/particle_explorer/particle-style.css new file mode 100644 index 0000000000..e8b71fdba0 --- /dev/null +++ b/scripts/system/particle_explorer/particle-style.css @@ -0,0 +1,124 @@ +/* +// particle-style.css +// +// Created by Matti 'Menithal' Lahtinen on 21 May 2017 +// 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 +*/ + + +.property-group { + max-height: 0; + -webkit-transition: max-height 0.15s ease-out; + transition: max-height 0.15s ease-out; + overflow: hidden; +} +.property-group.visible { + transition: max-height 0.25s ease-in; +} +.section-wrap { + width: 100%; +} +.property { + padding: 0.4rem 0; + margin: 0; +} +.property.checkbox { + margin: 0; +} +.property.range label{ + display: block; +} + +input[type="button"] { + margin: 0.4rem; + min-width: 6rem; +} +input[type="text"] { + margin: 0; +} +.property.range input[type=number]{ + margin-left: 0.8rem; + width: 5.4rem; + height: 1.8rem; +} +input[type=range] { + -webkit-appearance: none; + background: #2e2e2e; + height: 1.8rem; + border-radius: 1rem; +} +input[type=range]::-webkit-slider-thumb { + -webkit-appearance:none; + width: 0.6rem; + height: 1.8rem; + padding:0; + margin: 0; + background-color: #696969; + border-radius: 1rem; +} +input[type=range]::-webkit-slider-thumb:hover { + background-color: white; +} +input[type=range]:focus { /*#252525*/ + outline: none; +} +.tuple label { + text-transform: capitalize; +} +.slider-wrapper { + display: table; + padding: 0.4rem 0; +} +hr.splitter{ + width: 100%; + padding: 0.2rem 0 0 0; + margin: 0; + position: relative; + clear: both; +} +hr.splitter:last-of-type{ + padding:0; +} +#rem { + height: 1rem; + width: 1rem; +} +.property { + min-height: 2rem; +} +.property.vector-section{ + + width: 24rem; +} + +.property.texture { + display: block; +} +.property.texture input{ + margin: 0.4rem 0; +} +.texture-image img{ + padding: 0; + margin: 0; + width: 100%; + height: 100%; + display: none; +} +.texture-image { + display: block; + position: relative; + background-repeat: no-repeat; + background-position: center; + background-size: 100% 100%; + margin-top: 0.4rem; + height:128px; + width: 128px; + background-image: url('data:image/png;base64,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'); +} + +.texture-image.no-texture{ + background-image: url('data:image/png;base64,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'); +} diff --git a/scripts/system/particle_explorer/particleExplorer.html b/scripts/system/particle_explorer/particleExplorer.html index d0d86d79da..0f014e9fa8 100644 --- a/scripts/system/particle_explorer/particleExplorer.html +++ b/scripts/system/particle_explorer/particleExplorer.html @@ -1,95 +1,42 @@ +// + --> - - - - - - - - - - + + + - -
    - -
    -
    -
    -
    -
    - + +
    +
    + +
    + +
    +
    + diff --git a/scripts/system/particle_explorer/particleExplorer.js b/scripts/system/particle_explorer/particleExplorer.js index 5f66fe7ae6..ca6a873b73 100644 --- a/scripts/system/particle_explorer/particleExplorer.js +++ b/scripts/system/particle_explorer/particleExplorer.js @@ -2,550 +2,410 @@ // particleExplorer.js // // Created by James B. Pollack @imgntn on 9/26/2015 -// Copyright 2015 High Fidelity, Inc. +// Copyright 2017 High Fidelity, Inc. +// +// Reworked by Menithal on 20/5/2017 +// // Web app side of the App - contains GUI. -// This is an example of a new, easy way to do two way bindings between dynamically created GUI and in-world entities. +// This is an example of a new, easy way to do two way bindings between dynamically created GUI and in-world entities. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/* global window, alert, EventBridge, dat, listenForSettingsUpdates, createVec3Folder, createQuatFolder, writeVec3ToInterface, writeDataToInterface, - $, document, _, openEventBridge */ +/* global HifiEntityUI, openEventBridge, console, EventBridge, document, window */ +/* eslint no-console: 0, no-global-assign: 0 */ -var Settings = function () { - this.exportSettings = function () { - // copyExportSettingsToClipboard(); - showPreselectedPrompt(); - }; - this.importSettings = function () { - importSettings(); - }; -}; +(function () { -// 2-way bindings-aren't quite ready yet. see bottom of file. -var AUTO_UPDATE = false; -var UPDATE_ALL_FREQUENCY = 100; + var root = document.getElementById("particle-explorer"); -var controllers = []; -var colpickKeys = []; -var folders = []; -var gui = null; -var settings = new Settings(); -var updateInterval; - -var active = false; - -var currentInputField; -var storedController; -// CHANGE TO WHITELIST -var keysToAllow = [ - 'isEmitting', - 'maxParticles', - 'lifespan', - 'emitRate', - 'emitSpeed', - 'speedSpread', - 'emitOrientation', - 'emitDimensions', - 'polarStart', - 'polarFinish', - 'azimuthStart', - 'azimuthFinish', - 'emitAcceleration', - 'accelerationSpread', - 'particleRadius', - 'radiusSpread', - 'radiusStart', - 'radiusFinish', - 'color', - 'colorSpread', - 'colorStart', - 'colorFinish', - 'alpha', - 'alphaSpread', - 'alphaStart', - 'alphaFinish', - 'emitterShouldTrail', - 'textures' -]; - -var individualKeys = []; -var vec3Keys = []; -var quatKeys = []; -var colorKeys = []; - -window.onload = function () { - openEventBridge(function () { - var stringifiedData = JSON.stringify({ - messageType: 'page_loaded' + window.onload = function () { + var ui = new HifiEntityUI(root); + var textarea = document.createElement("textarea"); + var properties = ""; + var menuStructure = { + General: [ + { + type: "Row", + id: "export-import-field" + }, + { + id: "show-properties-button", + name: "Show Properties", + type: "Button", + class: "blue", + disabled: true, + callback: function (event) { + var insertZone = document.getElementById("export-import-field"); + var json = ui.getSettings(); + properties = JSON.stringify(json); + textarea.value = properties; + if (!insertZone.contains(textarea)) { + insertZone.appendChild(textarea); + insertZone.parentNode.parentNode.style.maxHeight = + insertZone.parentNode.clientHeight + "px"; + document.getElementById("export-properties-button").removeAttribute("disabled"); + textarea.onchange = function (e) { + if (e.target.value !== properties) { + document.getElementById("import-properties-button").removeAttribute("disabled"); + } + }; + textarea.oninput = textarea.onchange; + } else { + textarea.onchange = function () {}; + textarea.oninput = textarea.onchange; + textarea.value = ""; + textarea.remove(); + insertZone.parentNode.parentNode.style.maxHeight = + insertZone.parentNode.clientHeight + "px"; + document.getElementById("export-properties-button").setAttribute("disabled", true); + document.getElementById("import-properties-button").setAttribute("disabled", true); + } + } + }, + { + id: "import-properties-button", + name: "Import", + type: "Button", + class: "blue", + disabled: true, + callback: function (event) { + ui.fillFields(JSON.parse(textarea.value)); + ui.submitChanges(JSON.parse(textarea.value)); + } + }, + { + id: "export-properties-button", + name: "Export", + type: "Button", + class: "red", + disabled: true, + callback: function (event) { + textarea.select(); + try { + var success = document.execCommand('copy'); + if (!success) { + throw "Not success :("; + } + } catch (e) { + print("couldnt copy field"); + } + } + }, + { + type: "Row" + }, + { + id: "isEmitting", + name: "Is Emitting", + type: "Boolean" + }, + { + type: "Row" + }, + { + id: "lifespan", + name: "Lifespan", + type: "SliderFloat", + min: 0.01, + max: 10 + }, + { + type: "Row" + }, + { + id: "maxParticles", + name: "Max Particles", + type: "SliderInteger", + min: 1, + max: 10000 + }, + { + type: "Row" + }, + { + id: "textures", + name: "Textures", + type: "Texture" + }, + { + type: "Row" + } + ], + Emit: [ + { + id: "emitRate", + name: "Emit Rate", + type: "SliderInteger", + max: 1000, + min: 1 + }, + { + type: "Row" + }, + { + id: "emitSpeed", + name: "Emit Speed", + type: "SliderFloat", + max: 5 + }, + { + type: "Row" + }, + { + id: "emitDimensions", + name: "Emit Dimension", + type: "Vector", + defaultRange: { + min: 0, + step: 0.01 + } + }, + { + type: "Row" + }, + { + id: "emitOrientation", + unit: "deg", + name: "Emit Orientation", + type: "VectorQuaternion", + defaultRange: { + min: 0, + step: 0.01 + } + }, + { + type: "Row" + }, + { + id: "emitShouldTrail", + name: "Emit Should Trail", + type: "Boolean" + }, + { + type: "Row" + } + ], + Radius: [ + { + id: "particleRadius", + name: "Particle Radius", + type: "SliderFloat", + max: 4.0 + }, + { + type: "Row" + }, + { + id: "radiusSpread", + name: "Radius Spread", + type: "SliderFloat", + max: 4.0 + }, + { + type: "Row" + }, + { + id: "radiusStart", + name: "Radius Start", + type: "SliderFloat", + max: 4.0 + }, + { + type: "Row" + }, + { + id: "radiusFinish", + name: "Radius Finish", + type: "SliderFloat", + max: 4.0 + }, + { + type: "Row" + } + ], + Color: [ + { + id: "color", + name: "Color", + type: "Color", + defaultColor: { + red: 255, + green: 255, + blue: 255 + } + }, + { + type: "Row" + }, + { + id: "colorSpread", + name: "Color Spread", + type: "Color", + defaultColor: { + red: 0, + green: 0, + blue: 0 + } + }, + { + type: "Row" + }, + { + id: "colorStart", + name: "Color Start", + type: "Color", + defaultColor: { + red: 255, + green: 255, + blue: 255 + } + }, + { + type: "Row" + }, + { + id: "colorFinish", + name: "Color Finish", + type: "Color", + defaultColor: { + red: 255, + green: 255, + blue: 255 + } + }, + { + type: "Row" + } + ], + Acceleration: [ + { + id: "emitAcceleration", + name: "Emit Acceleration", + type: "Vector", + defaultRange: { + step: 0.01 + } + }, + { + type: "Row" + }, + { + id: "accelerationSpread", + name: "Acceleration Spread", + type: "Vector", + defaultRange: { + step: 0.01 + } + }, + { + type: "Row" + } + ], + Alpha: [ + { + id: "alpha", + name: "Alpha", + type: "SliderFloat" + }, + { + type: "Row" + }, + { + id: "alphaSpread", + name: "Alpha Spread", + type: "SliderFloat" + }, + { + type: "Row" + }, + { + id: "alphaStart", + name: "Alpha Start", + type: "SliderFloat" + }, + { + type: "Row" + }, + { + id: "alphaFinish", + name: "Alpha Finish", + type: "SliderFloat" + }, + { + type: "Row" + } + ], + Polar: [ + { + id: "polarStart", + name: "Polar Start", + unit: "deg", + type: "SliderRadian" + }, + { + type: "Row" + }, + { + id: "polarFinish", + name: "Polar Finish", + unit: "deg", + type: "SliderRadian" + }, + { + type: "Row" + } + ], + Azimuth: [ + { + id: "azimuthStart", + name: "Azimuth Start", + unit: "deg", + type: "SliderRadian", + min: -180, + max: 0 + }, + { + type: "Row" + }, + { + id: "azimuthFinish", + name: "Azimuth Finish", + unit: "deg", + type: "SliderRadian" + }, + { + type: "Row" + } + ] + }; + ui.setUI(menuStructure); + ui.setOnSelect(function () { + document.getElementById("show-properties-button").removeAttribute("disabled"); + document.getElementById("export-properties-button").setAttribute("disabled", true); + document.getElementById("import-properties-button").setAttribute("disabled", true); }); + ui.build(); + var overrideLoad = false; + if (openEventBridge === undefined) { + overrideLoad = true, + openEventBridge = function (callback) { + callback({ + emitWebEvent: function () {}, + submitChanges: function () {}, + scriptEventReceived: { + connect: function () { - EventBridge.emitWebEvent( - stringifiedData - ); - - listenForSettingsUpdates(); - window.onresize = setGUIWidthToWindowWidth; - }); - -}; - -function loadGUI() { - // whether or not to autoplace - gui = new dat.GUI({ - autoPlace: false - }); - - // if not autoplacing, put gui in a custom container - if (gui.autoPlace === false) { - var customContainer = document.getElementById('my-gui-container'); - customContainer.appendChild(gui.domElement); - } - - // presets for the GUI itself. a little confusing and import/export is mostly what we want to do at the moment. - // gui.remember(settings); - - colpickKeys = []; - var keys = _.keys(settings); - - _.each(keys, function(key) { - var shouldAllow = _.contains(keysToAllow, key); - - if (shouldAllow) { - var subKeys = _.keys(settings[key]); - var hasX = _.contains(subKeys, 'x'); - var hasY = _.contains(subKeys, 'y'); - var hasZ = _.contains(subKeys, 'z'); - var hasW = _.contains(subKeys, 'w'); - var hasRed = _.contains(subKeys, 'red'); - var hasGreen = _.contains(subKeys, 'green'); - var hasBlue = _.contains(subKeys, 'blue'); - - if ((hasX && hasY && hasZ) && hasW === false) { - vec3Keys.push(key); - } else if (hasX && hasY && hasZ && hasW) { - quatKeys.push(key); - } else if (hasRed || hasGreen || hasBlue) { - colorKeys.push(key); - - } else { - individualKeys.push(key); - } + } + } + }); + }; } - }); - - // alphabetize our keys - individualKeys.sort(); - vec3Keys.sort(); - quatKeys.sort(); - colorKeys.sort(); - - // add to gui in the order they should appear - gui.add(settings, 'importSettings'); - gui.add(settings, 'exportSettings'); - addIndividualKeys(); - addFolders(); - - // set the gui width to match the web window width - gui.width = window.innerWidth; - - // 2-way binding stuff - // if (AUTO_UPDATE) { - // setInterval(manuallyUpdateDisplay, UPDATE_ALL_FREQUENCY); - // registerDOMElementsForListenerBlocking(); - // } - -} - -function addIndividualKeys() { - _.each(individualKeys, function(key) { - // temporary patch for not crashing when this goes below 0 - var controller; - - if (key.indexOf('emitRate') > -1) { - controller = gui.add(settings, key).min(0); - } else { - controller = gui.add(settings, key); - } - - // 2-way - need to fix not being able to input exact values if constantly listening - // controller.listen(); - - // keep track of our controller - controllers.push(controller); - - // hook into change events for this gui controller - controller.onChange(function(value) { - // Fires on every change, drag, keypress, etc. - writeDataToInterface(this.property, value); + openEventBridge(function (EventBridge) { + ui.connect(EventBridge); }); - - }); -} - -function addFolders() { - _.each(colorKeys, function(key) { - createColorPicker(key); - }); - _.each(vec3Keys, function(key) { - createVec3Folder(key); - }); - _.each(quatKeys, function(key) { - createQuatFolder(key); - }); -} - -function createColorPicker(key) { - var colorObject = settings[key]; - - // Embed colpick's color picker into dat.GUI - var name = document.createElement('span'); - name.className = 'property-name'; - name.innerHTML = key; - - var container = document.createElement('div'); - container.appendChild(name); - - var $colPickContainer = $('
    ', { - id: key.toString(), - class: "color-box" - }); - $colPickContainer.css('background-color', "rgb(" + colorObject.red + "," + colorObject.green + "," + colorObject.blue + ")"); - container.appendChild($colPickContainer[0]); - - var $li = $('
  • ', { class: 'cr object color' }); - $li.append(container); - gui.__ul.appendChild($li[0]); - gui.onResize(); - - $colPickContainer.colpick({ - colorScheme: 'dark', - layout: 'hex', - color: { r: colorObject.red, g: colorObject.green, b: colorObject.blue }, - onSubmit: function (hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - $(el).colpickHide(); - - var obj = {}; - obj[key] = { red: rgb.r, green: rgb.g, blue: rgb.b }; - writeVec3ToInterface(obj); + if (overrideLoad) { + openEventBridge(); } - }); - - colpickKeys.push(key); -} - -function createVec3Folder(category) { - var folder = gui.addFolder(category); - - folder.add(settings[category], 'x').step(0.1).onChange(function(value) { - // Fires when a controller loses focus. - var obj = {}; - obj[category] = {}; - obj[category][this.property] = value; - obj[category].y = settings[category].y; - obj[category].z = settings[category].z; - writeVec3ToInterface(obj); - }); - - folder.add(settings[category], 'y').step(0.1).onChange(function(value) { - // Fires when a controller loses focus. - var obj = {}; - obj[category] = {}; - obj[category].x = settings[category].x; - obj[category][this.property] = value; - obj[category].z = settings[category].z; - writeVec3ToInterface(obj); - }); - - folder.add(settings[category], 'z').step(0.1).onChange(function(value) { - // Fires when a controller loses focus. - var obj = {}; - obj[category] = {}; - obj[category].y = settings[category].y; - obj[category].x = settings[category].x; - obj[category][this.property] = value; - writeVec3ToInterface(obj); - }); - - folders.push(folder); - folder.open(); -} - -function createQuatFolder(category) { - var folder = gui.addFolder(category); - - folder.add(settings[category], 'x').step(0.1).onChange(function(value) { - // Fires when a controller loses focus. - var obj = {}; - obj[category] = {}; - obj[category][this.property] = value; - obj[category].y = settings[category].y; - obj[category].z = settings[category].z; - obj[category].w = settings[category].w; - writeVec3ToInterface(obj); - }); - - folder.add(settings[category], 'y').step(0.1).onChange(function(value) { - // Fires when a controller loses focus. - var obj = {}; - obj[category] = {}; - obj[category].x = settings[category].x; - obj[category][this.property] = value; - obj[category].z = settings[category].z; - obj[category].w = settings[category].w; - writeVec3ToInterface(obj); - }); - - folder.add(settings[category], 'z').step(0.1).onChange(function(value) { - // Fires when a controller loses focus. - var obj = {}; - obj[category] = {}; - obj[category].x = settings[category].x; - obj[category].y = settings[category].y; - obj[category][this.property] = value; - obj[category].w = settings[category].w; - writeVec3ToInterface(obj); - }); - - folder.add(settings[category], 'w').step(0.1).onChange(function(value) { - // Fires when a controller loses focus. - var obj = {}; - obj[category] = {}; - obj[category].x = settings[category].x; - obj[category].y = settings[category].y; - obj[category].z = settings[category].z; - obj[category][this.property] = value; - writeVec3ToInterface(obj); - }); - - folders.push(folder); - folder.open(); -} - -function convertColorObjectToArray(colorObject) { - var colorArray = []; - - _.each(colorObject, function(singleColor) { - colorArray.push(singleColor); - }); - - return colorArray; -} - -function convertColorArrayToObject(colorArray) { - var colorObject = { - red: colorArray[0], - green: colorArray[1], - blue: colorArray[2] }; - - return colorObject; -} - -function writeDataToInterface(property, value) { - var data = {}; - data[property] = value; - - var sendData = { - messageType: "settings_update", - updatedSettings: data - }; - - var stringifiedData = JSON.stringify(sendData); - - EventBridge.emitWebEvent(stringifiedData); -} - -function writeVec3ToInterface(obj) { - var sendData = { - messageType: "settings_update", - updatedSettings: obj - }; - - var stringifiedData = JSON.stringify(sendData); - - EventBridge.emitWebEvent(stringifiedData); -} - -function listenForSettingsUpdates() { - EventBridge.scriptEventReceived.connect(function(data) { - data = JSON.parse(data); - if (data.messageType === 'particle_settings') { - _.each(data.currentProperties, function(value, key) { - settings[key] = {}; - settings[key] = value; - }); - - if (gui) { - manuallyUpdateDisplay(); - } else { - loadGUI(); - } - if (!active) { - // gui.toggleHide(); - gui.closed = false; - } - active = true; - - } else if (data.messageType === "particle_close") { - // none of this seems to work. - // if (active) { - // gui.toggleHide(); - // } - active = false; - gui.closed = true; - } - }); -} - -function manuallyUpdateDisplay() { - // Iterate over all controllers - // this is expensive, write a method for indiviudal controllers and use it when the value is different than a cached value, perhaps. - var i; - for (i in gui.__controllers) { - gui.__controllers[i].updateDisplay(); - } - - // Update color pickers - for (i in colpickKeys) { - var color = settings[colpickKeys[i]]; - var $object = $('#' + colpickKeys[i]); - $object.css('background-color', "rgb(" + color.red + "," + color.green + "," + color.blue + ")"); - $object.colpickSetColor({ r: color.red, g: color.green, b: color.blue }, true); - } -} - -function setGUIWidthToWindowWidth() { - if (gui !== null) { - gui.width = window.innerWidth; - } -} - -function handleInputKeyPress(e) { - if (e.keyCode === 13) { - importSettings(); - } - return false; -} - -function importSettings() { - var importInput = document.getElementById('importer-input'); - - try { - var importedSettings = JSON.parse(importInput.value); - - var keys = _.keys(importedSettings); - - _.each(keys, function(key) { - var shouldAllow = _.contains(keysToAllow, key); - - if (!shouldAllow) { - return; - } - - settings[key] = importedSettings[key]; - }); - - writeVec3ToInterface(settings); - - manuallyUpdateDisplay(); - } catch (e) { - alert('Not properly formatted JSON'); - } -} - -function prepareSettingsForExport() { - var keys = _.keys(settings); - - var exportSettings = {}; - - _.each(keys, function(key) { - var shouldAllow = _.contains(keysToAllow, key); - - if (!shouldAllow) { - return; - } - - if (key.indexOf('color') > -1) { - var colorObject = convertColorArrayToObject(settings[key]); - settings[key] = colorObject; - } - - exportSettings[key] = settings[key]; - }); - - return JSON.stringify(exportSettings, null, 4); -} - -function showPreselectedPrompt() { - var elem = document.getElementById("exported-props"); - var exportSettings = prepareSettingsForExport(); - elem.innerHTML = ""; - var buttonnode = document.createElement('input'); - buttonnode.setAttribute('type', 'button'); - buttonnode.setAttribute('value', 'close'); - elem.appendChild(document.createTextNode("COPY THE BELOW FIELD TO CLIPBOARD:")); - elem.appendChild(document.createElement("br")); - var textAreaNode = document.createElement("textarea"); - textAreaNode.value = exportSettings; - elem.appendChild(textAreaNode); - elem.appendChild(document.createElement("br")); - elem.appendChild(buttonnode); - - buttonnode.onclick = function() { - console.log("click"); - elem.innerHTML = ""; - }; - - // window.alert("Ctrl-C to copy, then Enter.", prepareSettingsForExport()); -} - -function removeContainerDomElement() { - var elem = document.getElementById("my-gui-container"); - elem.parentNode.removeChild(elem); -} - -function removeListenerFromGUI(key) { - _.each(gui.__listening, function(controller, index) { - if (controller.property === key) { - storedController = controller; - gui.__listening.splice(index, 1); - } - }); -} - -// the section below is to try to work at achieving two way bindings; - -function addListenersBackToGUI() { - gui.__listening.push(storedController); - storedController = null; -} - -function registerDOMElementsForListenerBlocking() { - _.each(gui.__controllers, function(controller) { - var input = controller.domElement.childNodes[0]; - input.addEventListener('focus', function() { - console.log('INPUT ELEMENT GOT FOCUS!' + controller.property); - removeListenerFromGUI(controller.property); - }); - }); - - _.each(gui.__controllers, function(controller) { - var input = controller.domElement.childNodes[0]; - input.addEventListener('blur', function() { - console.log('INPUT ELEMENT GOT BLUR!' + controller.property); - addListenersBackToGUI(); - }); - }); - - // also listen to inputs inside of folders - _.each(gui.__folders, function(folder) { - _.each(folder.__controllers, function(controller) { - var input = controller.__input; - input.addEventListener('focus', function() { - console.log('FOLDER ELEMENT GOT FOCUS!' + controller.property); - }); - }); - }); -} +})(); diff --git a/scripts/system/particle_explorer/particleExplorerTool.js b/scripts/system/particle_explorer/particleExplorerTool.js index b3db475ab0..d85fc169b1 100644 --- a/scripts/system/particle_explorer/particleExplorerTool.js +++ b/scripts/system/particle_explorer/particleExplorerTool.js @@ -4,23 +4,22 @@ // Created by Eric Levin on 2/15/16 // Copyright 2016 High Fidelity, Inc. // Adds particleExplorer tool to the edit panel when a user selects a particle entity from the edit tool window -// This is an example of a new, easy way to do two way bindings between dynamically created GUI and in-world entities. +// This is an example of a new, easy way to do two way bindings between dynamically created GUI and in-world entities. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -/*global window, alert, EventBridge, dat, listenForSettingsUpdates,createVec3Folder,createQuatFolder,writeVec3ToInterface,writeDataToInterface*/ +/* global window, alert, ParticleExplorerTool, EventBridge, dat, listenForSettingsUpdates,createVec3Folder,createQuatFolder,writeVec3ToInterface,writeDataToInterface*/ var PARTICLE_EXPLORER_HTML_URL = Script.resolvePath('particleExplorer.html'); ParticleExplorerTool = function() { var that = {}; - that.createWebView = function() { that.webView = Tablet.getTablet("com.highfidelity.interface.tablet.system"); that.webView.setVisible = function(value) {}; - that.webView.webEventReceived.connect(that.webEventReceived); + that.webView.webEventReceived.connect(that.webEventReceived); } that.destroyWebView = function() { @@ -38,6 +37,9 @@ ParticleExplorerTool = function() { that.webEventReceived = function(data) { var data = JSON.parse(data); if (data.messageType === "settings_update") { + if (data.updatedSettings.emitOrientation) { + data.updatedSettings.emitOrientation = Quat.fromVec3Degrees(data.updatedSettings.emitOrientation); + } Entities.editEntity(that.activeParticleEntity, data.updatedSettings); } }