mirror of
https://github.com/overte-org/overte.git
synced 2025-04-17 06:26:28 +02:00
Merge branch 'master' of github.com:highfidelity/hifi into black-bis
This commit is contained in:
commit
592c2b267d
93 changed files with 7069 additions and 5991 deletions
CMakeLists.txt
assignment-client/src/avatars
AvatarMixer.cppAvatarMixerClientData.cppAvatarMixerClientData.hAvatarMixerSlave.cppAvatarMixerSlave.h
interface
resources
src
libraries
animation/src
avatars-renderer/src/avatars-renderer
avatars/src
controllers/src/controllers/impl
Filter.cpp
filters
display-plugins/src/display-plugins
entities-renderer/src
entities/src
EntityItemProperties.cppEntityItemProperties.hEntityScriptingInterface.cppEntityTree.cppEntityTree.hGrabPropertyGroup.cppParticleEffectEntityItem.cppParticleEffectEntityItem.h
gl/src/gl
Context.cppContext.hContextQt.cppGLHelpers.cppGLHelpers.hGLWidget.cppGLWidget.hGLWindow.cppOffscreenGLCanvas.cppOffscreenGLCanvas.hQOpenGLContextWrapper.cppQOpenGLContextWrapper.h
gpu-gl-common/src/gpu/gl
physics/src
qml/src/qml/impl
render-utils/src
shared/src
plugins/openvr/src
scripts
defaultScripts.js
developer
system
tests-manual/qml
|
@ -66,7 +66,7 @@ if (ANDROID)
|
|||
set(GLES_OPTION ON)
|
||||
set(PLATFORM_QT_COMPONENTS AndroidExtras WebView)
|
||||
else ()
|
||||
set(PLATFORM_QT_COMPONENTS WebEngine WebEngineWidgets)
|
||||
set(PLATFORM_QT_COMPONENTS WebEngine)
|
||||
endif ()
|
||||
|
||||
if (USE_GLES AND (NOT ANDROID))
|
||||
|
|
|
@ -541,7 +541,7 @@ void AvatarMixer::handleRequestsDomainListDataPacket(QSharedPointer<ReceivedMess
|
|||
// ...For those nodes, reset the lastBroadcastTime to 0
|
||||
// so that the AvatarMixer will send Identity data to us
|
||||
[&](const SharedNodePointer& node) {
|
||||
nodeData->setLastBroadcastTime(node->getUUID(), 0);
|
||||
nodeData->setLastBroadcastTime(node->getLocalID(), 0);
|
||||
nodeData->resetSentTraitData(node->getLocalID());
|
||||
}
|
||||
);
|
||||
|
@ -565,7 +565,8 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer<ReceivedMessage> mes
|
|||
// parse the identity packet and update the change timestamp if appropriate
|
||||
bool identityChanged = false;
|
||||
bool displayNameChanged = false;
|
||||
avatar.processAvatarIdentity(message->getMessage(), identityChanged, displayNameChanged);
|
||||
QDataStream avatarIdentityStream(message->getMessage());
|
||||
avatar.processAvatarIdentity(avatarIdentityStream, identityChanged, displayNameChanged);
|
||||
|
||||
if (identityChanged) {
|
||||
QMutexLocker nodeDataLocker(&nodeData->getMutex());
|
||||
|
@ -637,7 +638,7 @@ void AvatarMixer::handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage>
|
|||
// Reset the lastBroadcastTime for the ignored avatar to 0
|
||||
// so the AvatarMixer knows it'll have to send identity data about the ignored avatar
|
||||
// to the ignorer if the ignorer unignores.
|
||||
nodeData->setLastBroadcastTime(ignoredUUID, 0);
|
||||
nodeData->setLastBroadcastTime(ignoredNode->getLocalID(), 0);
|
||||
nodeData->resetSentTraitData(ignoredNode->getLocalID());
|
||||
}
|
||||
|
||||
|
@ -647,7 +648,7 @@ void AvatarMixer::handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage>
|
|||
// to the ignored if the ignorer unignores.
|
||||
AvatarMixerClientData* ignoredNodeData = reinterpret_cast<AvatarMixerClientData*>(ignoredNode->getLinkedData());
|
||||
if (ignoredNodeData) {
|
||||
ignoredNodeData->setLastBroadcastTime(senderNode->getUUID(), 0);
|
||||
ignoredNodeData->setLastBroadcastTime(senderNode->getLocalID(), 0);
|
||||
ignoredNodeData->resetSentTraitData(senderNode->getLocalID());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,20 +26,20 @@ AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID, Node::LocalID
|
|||
_avatar->setID(nodeID);
|
||||
}
|
||||
|
||||
uint64_t AvatarMixerClientData::getLastOtherAvatarEncodeTime(QUuid otherAvatar) const {
|
||||
std::unordered_map<QUuid, uint64_t>::const_iterator itr = _lastOtherAvatarEncodeTime.find(otherAvatar);
|
||||
uint64_t AvatarMixerClientData::getLastOtherAvatarEncodeTime(NLPacket::LocalID otherAvatar) const {
|
||||
const auto itr = _lastOtherAvatarEncodeTime.find(otherAvatar);
|
||||
if (itr != _lastOtherAvatarEncodeTime.end()) {
|
||||
return itr->second;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AvatarMixerClientData::setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, uint64_t time) {
|
||||
std::unordered_map<QUuid, uint64_t>::iterator itr = _lastOtherAvatarEncodeTime.find(otherAvatar);
|
||||
void AvatarMixerClientData::setLastOtherAvatarEncodeTime(NLPacket::LocalID otherAvatar, uint64_t time) {
|
||||
auto itr = _lastOtherAvatarEncodeTime.find(otherAvatar);
|
||||
if (itr != _lastOtherAvatarEncodeTime.end()) {
|
||||
itr->second = time;
|
||||
} else {
|
||||
_lastOtherAvatarEncodeTime.emplace(std::pair<QUuid, uint64_t>(otherAvatar, time));
|
||||
_lastOtherAvatarEncodeTime.emplace(std::pair<NLPacket::LocalID, uint64_t>(otherAvatar, time));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -220,7 +220,7 @@ void AvatarMixerClientData::checkSkeletonURLAgainstWhitelist(const SlaveSharedDa
|
|||
}
|
||||
}
|
||||
|
||||
uint64_t AvatarMixerClientData::getLastBroadcastTime(const QUuid& nodeUUID) const {
|
||||
uint64_t AvatarMixerClientData::getLastBroadcastTime(NLPacket::LocalID nodeUUID) const {
|
||||
// return the matching PacketSequenceNumber, or the default if we don't have it
|
||||
auto nodeMatch = _lastBroadcastTimes.find(nodeUUID);
|
||||
if (nodeMatch != _lastBroadcastTimes.end()) {
|
||||
|
@ -229,9 +229,9 @@ uint64_t AvatarMixerClientData::getLastBroadcastTime(const QUuid& nodeUUID) cons
|
|||
return 0;
|
||||
}
|
||||
|
||||
uint16_t AvatarMixerClientData::getLastBroadcastSequenceNumber(const QUuid& nodeUUID) const {
|
||||
uint16_t AvatarMixerClientData::getLastBroadcastSequenceNumber(NLPacket::LocalID nodeID) const {
|
||||
// return the matching PacketSequenceNumber, or the default if we don't have it
|
||||
auto nodeMatch = _lastBroadcastSequenceNumbers.find(nodeUUID);
|
||||
auto nodeMatch = _lastBroadcastSequenceNumbers.find(nodeID);
|
||||
if (nodeMatch != _lastBroadcastSequenceNumbers.end()) {
|
||||
return nodeMatch->second;
|
||||
}
|
||||
|
@ -252,7 +252,7 @@ void AvatarMixerClientData::ignoreOther(const Node* self, const Node* other) {
|
|||
} else {
|
||||
killPacket->writePrimitive(KillAvatarReason::YourAvatarEnteredTheirBubble);
|
||||
}
|
||||
setLastBroadcastTime(other->getUUID(), 0);
|
||||
setLastBroadcastTime(other->getLocalID(), 0);
|
||||
|
||||
resetSentTraitData(other->getLocalID());
|
||||
|
||||
|
@ -331,9 +331,9 @@ AvatarMixerClientData::TraitsCheckTimestamp AvatarMixerClientData::getLastOtherA
|
|||
}
|
||||
}
|
||||
|
||||
void AvatarMixerClientData::cleanupKilledNode(const QUuid& nodeUUID, Node::LocalID nodeLocalID) {
|
||||
removeLastBroadcastSequenceNumber(nodeUUID);
|
||||
removeLastBroadcastTime(nodeUUID);
|
||||
void AvatarMixerClientData::cleanupKilledNode(const QUuid&, Node::LocalID nodeLocalID) {
|
||||
removeLastBroadcastSequenceNumber(nodeLocalID);
|
||||
removeLastBroadcastTime(nodeLocalID);
|
||||
_lastSentTraitsTimestamps.erase(nodeLocalID);
|
||||
_sentTraitVersions.erase(nodeLocalID);
|
||||
}
|
||||
|
|
|
@ -49,17 +49,16 @@ public:
|
|||
const AvatarData* getConstAvatarData() const { return _avatar.get(); }
|
||||
AvatarSharedPointer getAvatarSharedPointer() const { return _avatar; }
|
||||
|
||||
uint16_t getLastBroadcastSequenceNumber(NLPacket::LocalID nodeID) const;
|
||||
void setLastBroadcastSequenceNumber(NLPacket::LocalID nodeID, uint16_t sequenceNumber)
|
||||
{ _lastBroadcastSequenceNumbers[nodeID] = sequenceNumber; }
|
||||
Q_INVOKABLE void removeLastBroadcastSequenceNumber(NLPacket::LocalID nodeID) { _lastBroadcastSequenceNumbers.erase(nodeID); }
|
||||
bool isIgnoreRadiusEnabled() const { return _isIgnoreRadiusEnabled; }
|
||||
void setIsIgnoreRadiusEnabled(bool enabled) { _isIgnoreRadiusEnabled = enabled; }
|
||||
|
||||
uint16_t getLastBroadcastSequenceNumber(const QUuid& nodeUUID) const;
|
||||
void setLastBroadcastSequenceNumber(const QUuid& nodeUUID, uint16_t sequenceNumber)
|
||||
{ _lastBroadcastSequenceNumbers[nodeUUID] = sequenceNumber; }
|
||||
Q_INVOKABLE void removeLastBroadcastSequenceNumber(const QUuid& nodeUUID) { _lastBroadcastSequenceNumbers.erase(nodeUUID); }
|
||||
|
||||
uint64_t getLastBroadcastTime(const QUuid& nodeUUID) const;
|
||||
void setLastBroadcastTime(const QUuid& nodeUUID, uint64_t broadcastTime) { _lastBroadcastTimes[nodeUUID] = broadcastTime; }
|
||||
Q_INVOKABLE void removeLastBroadcastTime(const QUuid& nodeUUID) { _lastBroadcastTimes.erase(nodeUUID); }
|
||||
uint64_t getLastBroadcastTime(NLPacket::LocalID nodeUUID) const;
|
||||
void setLastBroadcastTime(NLPacket::LocalID nodeUUID, uint64_t broadcastTime) { _lastBroadcastTimes[nodeUUID] = broadcastTime; }
|
||||
Q_INVOKABLE void removeLastBroadcastTime(NLPacket::LocalID nodeUUID) { _lastBroadcastTimes.erase(nodeUUID); }
|
||||
|
||||
Q_INVOKABLE void cleanupKilledNode(const QUuid& nodeUUID, Node::LocalID nodeLocalID);
|
||||
|
||||
|
@ -93,7 +92,7 @@ public:
|
|||
|
||||
void loadJSONStats(QJsonObject& jsonObject) const;
|
||||
|
||||
glm::vec3 getPosition() const { return _avatar ? _avatar->getWorldPosition() : glm::vec3(0); }
|
||||
glm::vec3 getPosition() const { return _avatar ? _avatar->getClientGlobalPosition() : glm::vec3(0); }
|
||||
bool isRadiusIgnoring(const QUuid& other) const;
|
||||
void addToRadiusIgnoringSet(const QUuid& other);
|
||||
void removeFromRadiusIgnoringSet(const QUuid& other);
|
||||
|
@ -114,10 +113,10 @@ public:
|
|||
|
||||
const ConicalViewFrustums& getViewFrustums() const { return _currentViewFrustums; }
|
||||
|
||||
uint64_t getLastOtherAvatarEncodeTime(QUuid otherAvatar) const;
|
||||
void setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, uint64_t time);
|
||||
uint64_t getLastOtherAvatarEncodeTime(NLPacket::LocalID otherAvatar) const;
|
||||
void setLastOtherAvatarEncodeTime(NLPacket::LocalID otherAvatar, uint64_t time);
|
||||
|
||||
QVector<JointData>& getLastOtherAvatarSentJoints(QUuid otherAvatar) { return _lastOtherAvatarSentJoints[otherAvatar]; }
|
||||
QVector<JointData>& getLastOtherAvatarSentJoints(NLPacket::LocalID otherAvatar) { return _lastOtherAvatarSentJoints[otherAvatar]; }
|
||||
|
||||
void queuePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer node);
|
||||
int processPackets(const SlaveSharedData& slaveSharedData); // returns number of packets processed
|
||||
|
@ -150,13 +149,13 @@ private:
|
|||
AvatarSharedPointer _avatar { new AvatarData() };
|
||||
|
||||
uint16_t _lastReceivedSequenceNumber { 0 };
|
||||
std::unordered_map<QUuid, uint16_t> _lastBroadcastSequenceNumbers;
|
||||
std::unordered_map<QUuid, uint64_t> _lastBroadcastTimes;
|
||||
std::unordered_map<NLPacket::LocalID, uint16_t> _lastBroadcastSequenceNumbers;
|
||||
std::unordered_map<NLPacket::LocalID, uint64_t> _lastBroadcastTimes;
|
||||
|
||||
// this is a map of the last time we encoded an "other" avatar for
|
||||
// sending to "this" node
|
||||
std::unordered_map<QUuid, uint64_t> _lastOtherAvatarEncodeTime;
|
||||
std::unordered_map<QUuid, QVector<JointData>> _lastOtherAvatarSentJoints;
|
||||
std::unordered_map<NLPacket::LocalID, uint64_t> _lastOtherAvatarEncodeTime;
|
||||
std::unordered_map<NLPacket::LocalID, QVector<JointData>> _lastOtherAvatarSentJoints;
|
||||
|
||||
uint64_t _identityChangeTimestamp;
|
||||
bool _avatarSessionDisplayNameMustChange{ true };
|
||||
|
|
|
@ -68,13 +68,11 @@ void AvatarMixerSlave::processIncomingPackets(const SharedNodePointer& node) {
|
|||
_stats.processIncomingPacketsElapsedTime += (end - start);
|
||||
}
|
||||
|
||||
int AvatarMixerSlave::sendIdentityPacket(const AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode) {
|
||||
if (destinationNode->getType() == NodeType::Agent && !destinationNode->isUpstream()) {
|
||||
int AvatarMixerSlave::sendIdentityPacket(NLPacketList& packetList, const AvatarMixerClientData* nodeData, const Node& destinationNode) {
|
||||
if (destinationNode.getType() == NodeType::Agent && !destinationNode.isUpstream()) {
|
||||
QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray();
|
||||
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);
|
||||
DependencyManager::get<NodeList>()->sendPacketList(std::move(identityPackets), *destinationNode);
|
||||
packetList.write(individualData);
|
||||
_stats.numIdentityPackets++;
|
||||
return individualData.size();
|
||||
} else {
|
||||
|
@ -247,12 +245,12 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
// reset the internal state for correct random number distribution
|
||||
distribution.reset();
|
||||
|
||||
// Estimate number to sort on number sent last frame (with min. of 20).
|
||||
const int numToSendEst = std::max(int(nodeData->getNumAvatarsSentLastFrame() * 2.5f), 20);
|
||||
|
||||
// reset the number of sent avatars
|
||||
nodeData->resetNumAvatarsSentLastFrame();
|
||||
|
||||
// keep a counter of the number of considered avatars
|
||||
int numOtherAvatars = 0;
|
||||
|
||||
// keep track of outbound data rate specifically for avatar data
|
||||
int numAvatarDataBytes = 0;
|
||||
int identityBytesSent = 0;
|
||||
|
@ -261,7 +259,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
// max number of avatarBytes per frame
|
||||
int maxAvatarBytesPerFrame = int(_maxKbpsPerNode * BYTES_PER_KILOBIT / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND);
|
||||
|
||||
|
||||
// keep track of the number of other avatars held back in this frame
|
||||
int numAvatarsHeldBack = 0;
|
||||
|
||||
|
@ -279,10 +276,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
int minimumBytesPerAvatar = PALIsOpen ? AvatarDataPacket::AVATAR_HAS_FLAGS_SIZE + NUM_BYTES_RFC4122_UUID +
|
||||
sizeof(AvatarDataPacket::AvatarGlobalPosition) + sizeof(AvatarDataPacket::AudioLoudness) : 0;
|
||||
|
||||
// setup a PacketList for the avatarPackets
|
||||
auto avatarPacketList = NLPacketList::create(PacketType::BulkAvatarData);
|
||||
static auto maxAvatarDataBytes = avatarPacketList->getMaxSegmentSize() - NUM_BYTES_RFC4122_UUID;
|
||||
|
||||
// compute node bounding box
|
||||
const float MY_AVATAR_BUBBLE_EXPANSION_FACTOR = 4.0f; // magic number determined emperically
|
||||
AABox nodeBox = computeBubbleBox(avatar, MY_AVATAR_BUBBLE_EXPANSION_FACTOR);
|
||||
|
@ -350,8 +343,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
// Don't bother with these checks if the other avatar has their bubble enabled and we're gettingAnyIgnored
|
||||
if (nodeData->isIgnoreRadiusEnabled() || (avatarClientNodeData->isIgnoreRadiusEnabled() && !getsAnyIgnored)) {
|
||||
// Perform the collision check between the two bounding boxes
|
||||
const float OTHER_AVATAR_BUBBLE_EXPANSION_FACTOR = 2.4f; // magic number determined empirically
|
||||
AABox otherNodeBox = computeBubbleBox(avatarClientNodeData->getAvatar(), OTHER_AVATAR_BUBBLE_EXPANSION_FACTOR);
|
||||
AABox otherNodeBox = avatarClientNodeData->getAvatar().getDefaultBubbleBox();
|
||||
if (nodeBox.touches(otherNodeBox)) {
|
||||
nodeData->ignoreOther(destinationNode, avatarNode);
|
||||
shouldIgnore = !getsAnyIgnored;
|
||||
|
@ -364,7 +356,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
}
|
||||
|
||||
if (!shouldIgnore) {
|
||||
AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(avatarNode->getUUID());
|
||||
AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(avatarNode->getLocalID());
|
||||
AvatarDataSequenceNumber lastSeqFromSender = avatarClientNodeData->getLastReceivedSequenceNumber();
|
||||
|
||||
// FIXME - This code does appear to be working. But it seems brittle.
|
||||
|
@ -396,7 +388,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
if (!shouldIgnore) {
|
||||
// sort this one for later
|
||||
const AvatarData* avatarNodeData = avatarClientNodeData->getConstAvatarData();
|
||||
auto lastEncodeTime = nodeData->getLastOtherAvatarEncodeTime(avatarNodeData->getSessionUUID());
|
||||
auto lastEncodeTime = nodeData->getLastOtherAvatarEncodeTime(avatarNode->getLocalID());
|
||||
|
||||
sortedAvatars.push(SortableAvatar(avatarNodeData, avatarNode, lastEncodeTime));
|
||||
}
|
||||
|
@ -406,8 +398,13 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
|
||||
int remainingAvatars = (int)sortedAvatars.size();
|
||||
auto traitsPacketList = NLPacketList::create(PacketType::BulkAvatarTraits, QByteArray(), true, true);
|
||||
auto avatarPacket = NLPacket::create(PacketType::BulkAvatarData);
|
||||
const int avatarPacketCapacity = avatarPacket->getPayloadCapacity();
|
||||
int avatarSpaceAvailable = avatarPacketCapacity;
|
||||
int numPacketsSent = 0;
|
||||
auto identityPacketList = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true);
|
||||
|
||||
const auto& sortedAvatarVector = sortedAvatars.getSortedVector();
|
||||
const auto& sortedAvatarVector = sortedAvatars.getSortedVector(numToSendEst);
|
||||
for (const auto& sortedAvatar : sortedAvatarVector) {
|
||||
const Node* otherNode = sortedAvatar.getNode();
|
||||
auto lastEncodeForOther = sortedAvatar.getTimestamp();
|
||||
|
@ -432,21 +429,9 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
|
||||
auto startAvatarDataPacking = chrono::high_resolution_clock::now();
|
||||
|
||||
++numOtherAvatars;
|
||||
|
||||
const AvatarMixerClientData* otherNodeData = reinterpret_cast<const AvatarMixerClientData*>(otherNode->getLinkedData());
|
||||
const AvatarData* otherAvatar = otherNodeData->getConstAvatarData();
|
||||
|
||||
// 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 (otherAvatar->hasProcessedFirstIdentity()
|
||||
&& nodeData->getLastBroadcastTime(otherNode->getUUID()) <= otherNodeData->getIdentityChangeTimestamp()) {
|
||||
identityBytesSent += sendIdentityPacket(otherNodeData, node);
|
||||
|
||||
// remember the last time we sent identity details about this other node to the receiver
|
||||
nodeData->setLastBroadcastTime(otherNode->getUUID(), usecTimestampNow());
|
||||
}
|
||||
|
||||
// Typically all out-of-view avatars but such avatars' priorities will rise with time:
|
||||
bool isLowerPriority = sortedAvatar.getPriority() <= OUT_OF_VIEW_THRESHOLD;
|
||||
|
||||
|
@ -456,71 +441,56 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
} else if (!overBudget) {
|
||||
detail = distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO ? AvatarData::SendAllData : AvatarData::CullSmallData;
|
||||
nodeData->incrementAvatarInView();
|
||||
}
|
||||
|
||||
bool includeThisAvatar = true;
|
||||
QVector<JointData>& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getUUID());
|
||||
// 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 (otherAvatar->hasProcessedFirstIdentity()
|
||||
&& nodeData->getLastBroadcastTime(otherNode->getLocalID()) <= otherNodeData->getIdentityChangeTimestamp()) {
|
||||
identityBytesSent += sendIdentityPacket(*identityPacketList, otherNodeData, *destinationNode);
|
||||
|
||||
lastSentJointsForOther.resize(otherAvatar->getJointCount());
|
||||
|
||||
bool distanceAdjust = true;
|
||||
glm::vec3 viewerPosition = myPosition;
|
||||
AvatarDataPacket::HasFlags hasFlagsOut; // the result of the toByteArray
|
||||
bool dropFaceTracking = false;
|
||||
|
||||
auto startSerialize = chrono::high_resolution_clock::now();
|
||||
QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther,
|
||||
hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition,
|
||||
&lastSentJointsForOther);
|
||||
auto endSerialize = chrono::high_resolution_clock::now();
|
||||
_stats.toByteArrayElapsedTime +=
|
||||
(quint64) chrono::duration_cast<chrono::microseconds>(endSerialize - startSerialize).count();
|
||||
|
||||
if (bytes.size() > maxAvatarDataBytes) {
|
||||
qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID()
|
||||
<< "resulted in very large buffer of" << bytes.size() << "bytes - dropping facial data";
|
||||
|
||||
dropFaceTracking = true; // first try dropping the facial data
|
||||
bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther,
|
||||
hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther);
|
||||
|
||||
if (bytes.size() > maxAvatarDataBytes) {
|
||||
qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID()
|
||||
<< "without facial data resulted in very large buffer of" << bytes.size()
|
||||
<< "bytes - reducing to MinimumData";
|
||||
bytes = otherAvatar->toByteArray(AvatarData::MinimumData, lastEncodeForOther, lastSentJointsForOther,
|
||||
hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther);
|
||||
|
||||
if (bytes.size() > maxAvatarDataBytes) {
|
||||
qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID()
|
||||
<< "MinimumData resulted in very large buffer of" << bytes.size()
|
||||
<< "bytes - refusing to send avatar";
|
||||
includeThisAvatar = false;
|
||||
}
|
||||
// remember the last time we sent identity details about this other node to the receiver
|
||||
nodeData->setLastBroadcastTime(otherNode->getLocalID(), usecTimestampNow());
|
||||
}
|
||||
}
|
||||
|
||||
if (includeThisAvatar) {
|
||||
// start a new segment in the PacketList for this avatar
|
||||
avatarPacketList->startSegment();
|
||||
numAvatarDataBytes += avatarPacketList->write(otherNode->getUUID().toRfc4122());
|
||||
numAvatarDataBytes += avatarPacketList->write(bytes);
|
||||
avatarPacketList->endSegment();
|
||||
QVector<JointData>& lastSentJointsForOther = nodeData->getLastOtherAvatarSentJoints(otherNode->getLocalID());
|
||||
|
||||
if (detail != AvatarData::NoData) {
|
||||
_stats.numOthersIncluded++;
|
||||
const bool distanceAdjust = true;
|
||||
const bool dropFaceTracking = false;
|
||||
AvatarDataPacket::SendStatus sendStatus;
|
||||
sendStatus.sendUUID = true;
|
||||
|
||||
// increment the number of avatars sent to this reciever
|
||||
nodeData->incrementNumAvatarsSentLastFrame();
|
||||
do {
|
||||
auto startSerialize = chrono::high_resolution_clock::now();
|
||||
QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther,
|
||||
sendStatus, dropFaceTracking, distanceAdjust, myPosition,
|
||||
&lastSentJointsForOther, avatarSpaceAvailable);
|
||||
auto endSerialize = chrono::high_resolution_clock::now();
|
||||
_stats.toByteArrayElapsedTime +=
|
||||
(quint64)chrono::duration_cast<chrono::microseconds>(endSerialize - startSerialize).count();
|
||||
|
||||
// set the last sent sequence number for this sender on the receiver
|
||||
nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(),
|
||||
otherNodeData->getLastReceivedSequenceNumber());
|
||||
nodeData->setLastOtherAvatarEncodeTime(otherNode->getUUID(), usecTimestampNow());
|
||||
avatarPacket->write(bytes);
|
||||
avatarSpaceAvailable -= bytes.size();
|
||||
numAvatarDataBytes += bytes.size();
|
||||
if (!sendStatus || avatarSpaceAvailable < (int)AvatarDataPacket::MIN_BULK_PACKET_SIZE) {
|
||||
// Weren't able to fit everything.
|
||||
nodeList->sendPacket(std::move(avatarPacket), *destinationNode);
|
||||
++numPacketsSent;
|
||||
avatarPacket = NLPacket::create(PacketType::BulkAvatarData);
|
||||
avatarSpaceAvailable = avatarPacketCapacity;
|
||||
}
|
||||
} else {
|
||||
// TODO? this avatar is not included now, and will probably not be included next frame.
|
||||
// It would be nice if we could tweak its future sort priority to put it at the back of the list.
|
||||
} while (!sendStatus);
|
||||
|
||||
if (detail != AvatarData::NoData) {
|
||||
_stats.numOthersIncluded++;
|
||||
|
||||
// increment the number of avatars sent to this receiver
|
||||
nodeData->incrementNumAvatarsSentLastFrame();
|
||||
|
||||
// set the last sent sequence number for this sender on the receiver
|
||||
nodeData->setLastBroadcastSequenceNumber(otherNode->getLocalID(),
|
||||
otherNodeData->getLastReceivedSequenceNumber());
|
||||
nodeData->setLastOtherAvatarEncodeTime(otherNode->getLocalID(), usecTimestampNow());
|
||||
}
|
||||
|
||||
auto endAvatarDataPacking = chrono::high_resolution_clock::now();
|
||||
|
@ -532,17 +502,21 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
remainingAvatars--;
|
||||
}
|
||||
|
||||
if (nodeData->getNumAvatarsSentLastFrame() > numToSendEst) {
|
||||
qCWarning(avatars) << "More avatars sent than upper estimate" << nodeData->getNumAvatarsSentLastFrame()
|
||||
<< " / " << numToSendEst;
|
||||
}
|
||||
|
||||
quint64 startPacketSending = usecTimestampNow();
|
||||
|
||||
// close the current packet so that we're always sending something
|
||||
avatarPacketList->closeCurrentPacket(true);
|
||||
if (avatarPacket->getPayloadSize() != 0) {
|
||||
nodeList->sendPacket(std::move(avatarPacket), *destinationNode);
|
||||
++numPacketsSent;
|
||||
}
|
||||
|
||||
_stats.numPacketsSent += (int)avatarPacketList->getNumPackets();
|
||||
_stats.numPacketsSent += numPacketsSent;
|
||||
_stats.numBytesSent += numAvatarDataBytes;
|
||||
|
||||
// send the avatar data PacketList
|
||||
nodeList->sendPacketList(std::move(avatarPacketList), *destinationNode);
|
||||
|
||||
// record the bytes sent for other avatar data in the AvatarMixerClientData
|
||||
nodeData->recordSentAvatarData(numAvatarDataBytes);
|
||||
|
||||
|
@ -554,6 +528,12 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
nodeList->sendPacketList(std::move(traitsPacketList), *destinationNode);
|
||||
}
|
||||
|
||||
// Send any AvatarIdentity packets:
|
||||
identityPacketList->closeCurrentPacket();
|
||||
if (identityBytesSent > 0) {
|
||||
nodeList->sendPacketList(std::move(identityPacketList), *destinationNode);
|
||||
}
|
||||
|
||||
// record the number of avatars held back this frame
|
||||
nodeData->recordNumOtherAvatarStarves(numAvatarsHeldBack);
|
||||
nodeData->recordNumOtherAvatarSkips(numAvatarsWithSkippedFrames);
|
||||
|
@ -599,20 +579,20 @@ void AvatarMixerSlave::broadcastAvatarDataToDownstreamMixer(const SharedNodePoin
|
|||
// so we always send a full update for this avatar
|
||||
|
||||
quint64 start = usecTimestampNow();
|
||||
AvatarDataPacket::HasFlags flagsOut;
|
||||
AvatarDataPacket::SendStatus sendStatus;
|
||||
|
||||
QVector<JointData> emptyLastJointSendData { otherAvatar->getJointCount() };
|
||||
|
||||
QByteArray avatarByteArray = otherAvatar->toByteArray(AvatarData::SendAllData, 0, emptyLastJointSendData,
|
||||
flagsOut, false, false, glm::vec3(0), nullptr);
|
||||
sendStatus, false, false, glm::vec3(0), nullptr, 0);
|
||||
quint64 end = usecTimestampNow();
|
||||
_stats.toByteArrayElapsedTime += (end - start);
|
||||
|
||||
auto lastBroadcastTime = nodeData->getLastBroadcastTime(agentNode->getUUID());
|
||||
auto lastBroadcastTime = nodeData->getLastBroadcastTime(agentNode->getLocalID());
|
||||
if (lastBroadcastTime <= agentNodeData->getIdentityChangeTimestamp()
|
||||
|| (start - lastBroadcastTime) >= REBROADCAST_IDENTITY_TO_DOWNSTREAM_EVERY_US) {
|
||||
sendReplicatedIdentityPacket(*agentNode, agentNodeData, *node);
|
||||
nodeData->setLastBroadcastTime(agentNode->getUUID(), start);
|
||||
nodeData->setLastBroadcastTime(agentNode->getLocalID(), start);
|
||||
}
|
||||
|
||||
// figure out how large our avatar byte array can be to fit in the packet list
|
||||
|
@ -630,14 +610,14 @@ void AvatarMixerSlave::broadcastAvatarDataToDownstreamMixer(const SharedNodePoin
|
|||
<< "-" << avatarByteArray.size() << "bytes";
|
||||
|
||||
avatarByteArray = otherAvatar->toByteArray(AvatarData::SendAllData, 0, emptyLastJointSendData,
|
||||
flagsOut, true, false, glm::vec3(0), nullptr);
|
||||
sendStatus, true, false, glm::vec3(0), nullptr, 0);
|
||||
|
||||
if (avatarByteArray.size() > maxAvatarByteArraySize) {
|
||||
qCWarning(avatars) << "Replicated avatar data without facial data still too large for"
|
||||
<< otherAvatar->getSessionUUID() << "-" << avatarByteArray.size() << "bytes";
|
||||
|
||||
avatarByteArray = otherAvatar->toByteArray(AvatarData::MinimumData, 0, emptyLastJointSendData,
|
||||
flagsOut, true, false, glm::vec3(0), nullptr);
|
||||
sendStatus, true, false, glm::vec3(0), nullptr, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -646,7 +626,7 @@ void AvatarMixerSlave::broadcastAvatarDataToDownstreamMixer(const SharedNodePoin
|
|||
nodeData->incrementNumAvatarsSentLastFrame();
|
||||
|
||||
// set the last sent sequence number for this sender on the receiver
|
||||
nodeData->setLastBroadcastSequenceNumber(agentNode->getUUID(),
|
||||
nodeData->setLastBroadcastSequenceNumber(agentNode->getLocalID(),
|
||||
agentNodeData->getLastReceivedSequenceNumber());
|
||||
|
||||
// increment the number of avatars sent to this reciever
|
||||
|
|
|
@ -101,7 +101,7 @@ public:
|
|||
void harvestStats(AvatarMixerSlaveStats& stats);
|
||||
|
||||
private:
|
||||
int sendIdentityPacket(const AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode);
|
||||
int sendIdentityPacket(NLPacketList& packet, const AvatarMixerClientData* nodeData, const Node& destinationNode);
|
||||
int sendReplicatedIdentityPacket(const Node& agentNode, const AvatarMixerClientData* nodeData, const Node& destinationNode);
|
||||
|
||||
qint64 addChangedTraitsToBulkPacket(AvatarMixerClientData* listeningNodeData,
|
||||
|
|
|
@ -51,32 +51,34 @@
|
|||
{ "from": "Vive.RSCenter", "to": "Standard.RightPrimaryThumb" },
|
||||
{ "from": "Vive.RightApplicationMenu", "to": "Standard.RightSecondaryThumb" },
|
||||
|
||||
{ "from": "Vive.LeftHand", "to": "Standard.LeftHand"},
|
||||
{ "from": "Vive.RightHand", "to": "Standard.RightHand"},
|
||||
{ "from": "Vive.LeftHand", "to": "Standard.LeftHand" },
|
||||
{ "from": "Vive.RightHand", "to": "Standard.RightHand" },
|
||||
{ "from": "Vive.Head", "to" : "Standard.Head" },
|
||||
|
||||
{
|
||||
"from": "Vive.LeftFoot", "to" : "Standard.LeftFoot",
|
||||
"filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.3}]
|
||||
"filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.15}]
|
||||
},
|
||||
|
||||
{
|
||||
"from": "Vive.RightFoot", "to" : "Standard.RightFoot",
|
||||
"filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.3}]
|
||||
"filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.15}]
|
||||
},
|
||||
|
||||
{
|
||||
"from": "Vive.Hips", "to" : "Standard.Hips",
|
||||
"filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.3}]
|
||||
"filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.15}]
|
||||
},
|
||||
|
||||
{
|
||||
"from": "Vive.Spine2", "to" : "Standard.Spine2",
|
||||
"filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.3}]
|
||||
"filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.15}]
|
||||
},
|
||||
{
|
||||
"from": "Vive.RightArm", "to" : "Standard.RightArm",
|
||||
"filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.15}]
|
||||
},
|
||||
{
|
||||
"from": "Vive.LeftArm", "to" : "Standard.LeftArm",
|
||||
"filters" : [{"type" : "exponentialSmoothing", "rotation" : 0.15, "translation": 0.15}]
|
||||
},
|
||||
|
||||
{ "from": "Vive.Head", "to" : "Standard.Head"},
|
||||
{ "from": "Vive.RightArm", "to" : "Standard.RightArm" },
|
||||
{ "from": "Vive.LeftArm", "to" : "Standard.LeftArm" },
|
||||
|
||||
{ "from": "Vive.TrackedObject00", "to" : "Standard.TrackedObject00" },
|
||||
{ "from": "Vive.TrackedObject01", "to" : "Standard.TrackedObject01" },
|
||||
|
|
|
@ -175,7 +175,7 @@ TabBar {
|
|||
method: "newEntityButtonClicked",
|
||||
params: { buttonName: "newParticleButton" }
|
||||
});
|
||||
editTabView.currentIndex = 4
|
||||
editTabView.currentIndex = 2
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -279,21 +279,6 @@ TabBar {
|
|||
}
|
||||
}
|
||||
|
||||
EditTabButton {
|
||||
title: "P"
|
||||
active: true
|
||||
enabled: true
|
||||
property string originalUrl: ""
|
||||
|
||||
property Component visualItem: Component {
|
||||
WebView {
|
||||
id: particleExplorerWebView
|
||||
url: Paths.defaultScripts + "/system/particle_explorer/particleExplorer.html"
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function fromScript(message) {
|
||||
switch (message.method) {
|
||||
case 'selectTab':
|
||||
|
@ -326,9 +311,6 @@ TabBar {
|
|||
case 'grid':
|
||||
editTabView.currentIndex = 3;
|
||||
break;
|
||||
case 'particle':
|
||||
editTabView.currentIndex = 4;
|
||||
break;
|
||||
default:
|
||||
console.warn('Attempt to switch to invalid tab:', id);
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ TabBar {
|
|||
readonly property int create: 0
|
||||
readonly property int properties: 1
|
||||
readonly property int grid: 2
|
||||
readonly property int particle: 3
|
||||
}
|
||||
|
||||
readonly property HifiConstants hifi: HifiConstants {}
|
||||
|
@ -182,7 +181,7 @@ TabBar {
|
|||
method: "newEntityButtonClicked",
|
||||
params: { buttonName: "newParticleButton" }
|
||||
});
|
||||
editTabView.currentIndex = tabIndex.particle
|
||||
editTabView.currentIndex = tabIndex.properties
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -271,21 +270,6 @@ TabBar {
|
|||
}
|
||||
}
|
||||
|
||||
EditTabButton {
|
||||
title: "P"
|
||||
active: true
|
||||
enabled: true
|
||||
property string originalUrl: ""
|
||||
|
||||
property Component visualItem: Component {
|
||||
WebView {
|
||||
id: particleExplorerWebView
|
||||
url: Paths.defaultScripts + "/system/particle_explorer/particleExplorer.html"
|
||||
enabled: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function fromScript(message) {
|
||||
switch (message.method) {
|
||||
case 'selectTab':
|
||||
|
@ -299,7 +283,7 @@ TabBar {
|
|||
// Changes the current tab based on tab index or title as input
|
||||
function selectTab(id) {
|
||||
if (typeof id === 'number') {
|
||||
if (id >= tabIndex.create && id <= tabIndex.particle) {
|
||||
if (id >= tabIndex.create && id <= tabIndex.grid) {
|
||||
editTabView.currentIndex = id;
|
||||
} else {
|
||||
console.warn('Attempt to switch to invalid tab:', id);
|
||||
|
@ -315,9 +299,6 @@ TabBar {
|
|||
case 'grid':
|
||||
editTabView.currentIndex = tabIndex.grid;
|
||||
break;
|
||||
case 'particle':
|
||||
editTabView.currentIndex = tabIndex.particle;
|
||||
break;
|
||||
default:
|
||||
console.warn('Attempt to switch to invalid tab:', id);
|
||||
}
|
||||
|
|
|
@ -822,11 +822,44 @@ Flickable {
|
|||
}
|
||||
}
|
||||
|
||||
Row {
|
||||
id: outOfRangeDataStrategyRow
|
||||
anchors.top: viveInDesktop.bottom
|
||||
anchors.topMargin: 5
|
||||
anchors.left: openVrConfiguration.left
|
||||
anchors.leftMargin: leftMargin + 10
|
||||
spacing: 15
|
||||
|
||||
RalewayRegular {
|
||||
id: outOfRangeDataStrategyLabel
|
||||
size: 12
|
||||
text: "Out Of Range Data Strategy:"
|
||||
color: hifi.colors.lightGrayText
|
||||
topPadding: 5
|
||||
}
|
||||
|
||||
HifiControls.ComboBox {
|
||||
id: outOfRangeDataStrategyComboBox
|
||||
|
||||
height: 25
|
||||
width: 100
|
||||
|
||||
editable: true
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
model: ["None", "Freeze", "Drop"]
|
||||
label: ""
|
||||
|
||||
onCurrentIndexChanged: {
|
||||
sendConfigurationSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RalewayBold {
|
||||
id: viveDesktopText
|
||||
size: 10
|
||||
size: 12
|
||||
text: "Use " + stack.selectedPlugin + " devices in desktop mode"
|
||||
color: hifi.colors.white
|
||||
color: hifi.colors.lightGrayText
|
||||
|
||||
anchors {
|
||||
left: viveInDesktop.right
|
||||
|
@ -946,6 +979,7 @@ Flickable {
|
|||
|
||||
viveInDesktop.checked = desktopMode;
|
||||
hmdInDesktop.checked = hmdDesktopPosition;
|
||||
outOfRangeDataStrategyComboBox.currentIndex = outOfRangeDataStrategyComboBox.model.indexOf(settings.outOfRangeDataStrategy);
|
||||
|
||||
initializeButtonState();
|
||||
updateCalibrationText();
|
||||
|
@ -1107,7 +1141,8 @@ Flickable {
|
|||
"armCircumference": armCircumference.realValue,
|
||||
"shoulderWidth": shoulderWidth.realValue,
|
||||
"desktopMode": viveInDesktop.checked,
|
||||
"hmdDesktopTracking": hmdInDesktop.checked
|
||||
"hmdDesktopTracking": hmdInDesktop.checked,
|
||||
"outOfRangeDataStrategy": outOfRangeDataStrategyComboBox.model[outOfRangeDataStrategyComboBox.currentIndex]
|
||||
}
|
||||
|
||||
return settingsObject;
|
||||
|
|
|
@ -52,6 +52,8 @@
|
|||
#include <QTemporaryDir>
|
||||
|
||||
#include <gl/QOpenGLContextWrapper.h>
|
||||
#include <gl/GLWindow.h>
|
||||
#include <gl/GLHelpers.h>
|
||||
|
||||
#include <shared/FileUtils.h>
|
||||
#include <shared/QtHelpers.h>
|
||||
|
@ -971,9 +973,11 @@ OffscreenGLCanvas* _qmlShareContext { nullptr };
|
|||
// and manually set THAT to be the shared context for the Chromium helper
|
||||
#if !defined(DISABLE_QML)
|
||||
OffscreenGLCanvas* _chromiumShareContext { nullptr };
|
||||
Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context);
|
||||
#endif
|
||||
|
||||
Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context);
|
||||
Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context();
|
||||
|
||||
Setting::Handle<int> sessionRunTime{ "sessionRunTime", 0 };
|
||||
|
||||
const float DEFAULT_HMD_TABLET_SCALE_PERCENT = 60.0f;
|
||||
|
@ -1370,7 +1374,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
_glWidget->setMouseTracking(true);
|
||||
// Make sure the window is set to the correct size by processing the pending events
|
||||
QCoreApplication::processEvents();
|
||||
_glWidget->createContext();
|
||||
|
||||
// Create the main thread context, the GPU backend
|
||||
initializeGL();
|
||||
|
@ -2727,46 +2730,66 @@ void Application::initializeGL() {
|
|||
_isGLInitialized = true;
|
||||
}
|
||||
|
||||
if (!_glWidget->makeCurrent()) {
|
||||
qCWarning(interfaceapp, "Unable to make window context current");
|
||||
}
|
||||
_glWidget->windowHandle()->setFormat(getDefaultOpenGLSurfaceFormat());
|
||||
|
||||
// When loading QtWebEngineWidgets, it creates a global share context on startup.
|
||||
// We have to account for this possibility by checking here for an existing
|
||||
// global share context
|
||||
auto globalShareContext = qt_gl_global_share_context();
|
||||
|
||||
#if !defined(DISABLE_QML)
|
||||
// Build a shared canvas / context for the Chromium processes
|
||||
{
|
||||
// Disable signed distance field font rendering on ATI/AMD GPUs, due to
|
||||
// https://highfidelity.manuscript.com/f/cases/13677/Text-showing-up-white-on-Marketplace-app
|
||||
std::string vendor{ (const char*)glGetString(GL_VENDOR) };
|
||||
if ((vendor.find("AMD") != std::string::npos) || (vendor.find("ATI") != std::string::npos)) {
|
||||
qputenv("QTWEBENGINE_CHROMIUM_FLAGS", QByteArray("--disable-distance-field-text"));
|
||||
}
|
||||
|
||||
if (!globalShareContext) {
|
||||
// Chromium rendering uses some GL functions that prevent nSight from capturing
|
||||
// frames, so we only create the shared context if nsight is NOT active.
|
||||
if (!nsightActive()) {
|
||||
_chromiumShareContext = new OffscreenGLCanvas();
|
||||
_chromiumShareContext->setObjectName("ChromiumShareContext");
|
||||
_chromiumShareContext->create(_glWidget->qglContext());
|
||||
auto format =QSurfaceFormat::defaultFormat();
|
||||
#ifdef Q_OS_MAC
|
||||
// On mac, the primary shared OpenGL context must be a 3.2 core context,
|
||||
// or chromium flips out and spews error spam (but renders fine)
|
||||
format.setMajorVersion(3);
|
||||
format.setMinorVersion(2);
|
||||
#endif
|
||||
_chromiumShareContext->setFormat(format);
|
||||
_chromiumShareContext->create();
|
||||
if (!_chromiumShareContext->makeCurrent()) {
|
||||
qCWarning(interfaceapp, "Unable to make chromium shared context current");
|
||||
}
|
||||
qt_gl_set_global_share_context(_chromiumShareContext->getContext());
|
||||
globalShareContext = _chromiumShareContext->getContext();
|
||||
qt_gl_set_global_share_context(globalShareContext);
|
||||
_chromiumShareContext->doneCurrent();
|
||||
// Restore the GL widget context
|
||||
if (!_glWidget->makeCurrent()) {
|
||||
qCWarning(interfaceapp, "Unable to make window context current");
|
||||
}
|
||||
} else {
|
||||
qCWarning(interfaceapp) << "nSight detected, disabling chrome rendering";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
_glWidget->createContext(globalShareContext);
|
||||
|
||||
if (!_glWidget->makeCurrent()) {
|
||||
qCWarning(interfaceapp, "Unable to make window context current");
|
||||
}
|
||||
|
||||
#if !defined(DISABLE_QML)
|
||||
// Disable signed distance field font rendering on ATI/AMD GPUs, due to
|
||||
// https://highfidelity.manuscript.com/f/cases/13677/Text-showing-up-white-on-Marketplace-app
|
||||
std::string vendor{ (const char*)glGetString(GL_VENDOR) };
|
||||
if ((vendor.find("AMD") != std::string::npos) || (vendor.find("ATI") != std::string::npos)) {
|
||||
qputenv("QTWEBENGINE_CHROMIUM_FLAGS", QByteArray("--disable-distance-field-text"));
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!globalShareContext) {
|
||||
globalShareContext = _glWidget->qglContext();
|
||||
qt_gl_set_global_share_context(globalShareContext);
|
||||
}
|
||||
|
||||
// Build a shared canvas / context for the QML rendering
|
||||
{
|
||||
_qmlShareContext = new OffscreenGLCanvas();
|
||||
_qmlShareContext->setObjectName("QmlShareContext");
|
||||
_qmlShareContext->create(_glWidget->qglContext());
|
||||
_qmlShareContext->create(globalShareContext);
|
||||
if (!_qmlShareContext->makeCurrent()) {
|
||||
qCWarning(interfaceapp, "Unable to make QML shared context current");
|
||||
}
|
||||
|
@ -5815,6 +5838,42 @@ void Application::update(float deltaTime) {
|
|||
controller::Pose pose = userInputMapper->getPoseState(action);
|
||||
myAvatar->setControllerPoseInSensorFrame(action, pose.transform(avatarToSensorMatrix));
|
||||
}
|
||||
|
||||
static const std::vector<QString> trackedObjectStringLiterals = {
|
||||
QStringLiteral("_TrackedObject00"), QStringLiteral("_TrackedObject01"), QStringLiteral("_TrackedObject02"), QStringLiteral("_TrackedObject03"),
|
||||
QStringLiteral("_TrackedObject04"), QStringLiteral("_TrackedObject05"), QStringLiteral("_TrackedObject06"), QStringLiteral("_TrackedObject07"),
|
||||
QStringLiteral("_TrackedObject08"), QStringLiteral("_TrackedObject09"), QStringLiteral("_TrackedObject10"), QStringLiteral("_TrackedObject11"),
|
||||
QStringLiteral("_TrackedObject12"), QStringLiteral("_TrackedObject13"), QStringLiteral("_TrackedObject14"), QStringLiteral("_TrackedObject15")
|
||||
};
|
||||
|
||||
// Controlled by the Developer > Avatar > Show Tracked Objects menu.
|
||||
if (_showTrackedObjects) {
|
||||
static const std::vector<controller::Action> trackedObjectActions = {
|
||||
controller::Action::TRACKED_OBJECT_00, controller::Action::TRACKED_OBJECT_01, controller::Action::TRACKED_OBJECT_02, controller::Action::TRACKED_OBJECT_03,
|
||||
controller::Action::TRACKED_OBJECT_04, controller::Action::TRACKED_OBJECT_05, controller::Action::TRACKED_OBJECT_06, controller::Action::TRACKED_OBJECT_07,
|
||||
controller::Action::TRACKED_OBJECT_08, controller::Action::TRACKED_OBJECT_09, controller::Action::TRACKED_OBJECT_10, controller::Action::TRACKED_OBJECT_11,
|
||||
controller::Action::TRACKED_OBJECT_12, controller::Action::TRACKED_OBJECT_13, controller::Action::TRACKED_OBJECT_14, controller::Action::TRACKED_OBJECT_15
|
||||
};
|
||||
|
||||
int i = 0;
|
||||
glm::vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
for (auto& action : trackedObjectActions) {
|
||||
controller::Pose pose = userInputMapper->getPoseState(action);
|
||||
if (pose.valid) {
|
||||
glm::vec3 pos = transformPoint(myAvatarMatrix, pose.translation);
|
||||
glm::quat rot = glmExtractRotation(myAvatarMatrix) * pose.rotation;
|
||||
DebugDraw::getInstance().addMarker(trackedObjectStringLiterals[i], rot, pos, BLUE);
|
||||
} else {
|
||||
DebugDraw::getInstance().removeMarker(trackedObjectStringLiterals[i]);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
} else if (_prevShowTrackedObjects) {
|
||||
for (auto& key : trackedObjectStringLiterals) {
|
||||
DebugDraw::getInstance().removeMarker(key);
|
||||
}
|
||||
}
|
||||
_prevShowTrackedObjects = _showTrackedObjects;
|
||||
}
|
||||
|
||||
updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process...
|
||||
|
@ -5981,7 +6040,9 @@ void Application::update(float deltaTime) {
|
|||
{
|
||||
PROFILE_RANGE_EX(app, "Overlays", 0xffff0000, (uint64_t)getActiveDisplayPlugin()->presentCount());
|
||||
PerformanceTimer perfTimer("overlays");
|
||||
_overlays.update(deltaTime);
|
||||
if (qApp->shouldPaint()) {
|
||||
_overlays.update(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
// Update _viewFrustum with latest camera and view frustum data...
|
||||
|
@ -6066,8 +6127,10 @@ void Application::update(float deltaTime) {
|
|||
PROFILE_RANGE_EX(app, "PostUpdateLambdas", 0xffff0000, (uint64_t)0);
|
||||
PerformanceTimer perfTimer("postUpdateLambdas");
|
||||
std::unique_lock<std::mutex> guard(_postUpdateLambdasLock);
|
||||
for (auto& iter : _postUpdateLambdas) {
|
||||
iter.second();
|
||||
if (qApp->shouldPaint()) {
|
||||
for (auto& iter : _postUpdateLambdas) {
|
||||
iter.second();
|
||||
}
|
||||
}
|
||||
_postUpdateLambdas.clear();
|
||||
}
|
||||
|
@ -8311,6 +8374,10 @@ void Application::setShowBulletConstraintLimits(bool value) {
|
|||
_physicsEngine->setShowBulletConstraintLimits(value);
|
||||
}
|
||||
|
||||
void Application::setShowTrackedObjects(bool value) {
|
||||
_showTrackedObjects = value;
|
||||
}
|
||||
|
||||
void Application::startHMDStandBySession() {
|
||||
_autoSwitchDisplayModeSupportedHMDPlugin->startStandBySession();
|
||||
}
|
||||
|
|
|
@ -498,6 +498,8 @@ private slots:
|
|||
void setShowBulletConstraints(bool value);
|
||||
void setShowBulletConstraintLimits(bool value);
|
||||
|
||||
void setShowTrackedObjects(bool value);
|
||||
|
||||
private:
|
||||
void init();
|
||||
bool handleKeyEventForFocusedEntityOrOverlay(QEvent* event);
|
||||
|
@ -779,5 +781,8 @@ private:
|
|||
std::atomic<bool> _pendingRenderEvent { true };
|
||||
|
||||
bool quitWhenFinished { false };
|
||||
|
||||
bool _showTrackedObjects { false };
|
||||
bool _prevShowTrackedObjects { false };
|
||||
};
|
||||
#endif // hifi_Application_h
|
||||
|
|
|
@ -273,15 +273,71 @@ Menu::Menu() {
|
|||
|
||||
// Developer menu ----------------------------------
|
||||
MenuWrapper* developerMenu = addMenu("Developer", "Developer");
|
||||
|
||||
// Developer > Scripting >>>
|
||||
MenuWrapper* scriptingOptionsMenu = developerMenu->addMenu("Scripting");
|
||||
|
||||
// Developer > Scripting > Console...
|
||||
addActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::Console, Qt::CTRL | Qt::ALT | Qt::Key_J,
|
||||
DependencyManager::get<StandAloneJSConsole>().data(),
|
||||
SLOT(toggleConsole()),
|
||||
QAction::NoRole,
|
||||
UNSPECIFIED_POSITION);
|
||||
|
||||
// Developer > Scripting > API Debugger
|
||||
action = addActionToQMenuAndActionHash(scriptingOptionsMenu, "API Debugger");
|
||||
connect(action, &QAction::triggered, [] {
|
||||
QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation();
|
||||
defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/utilities/tools/currentAPI.js");
|
||||
DependencyManager::get<ScriptEngines>()->loadScript(defaultScriptsLoc.toString());
|
||||
});
|
||||
|
||||
// Developer > Scripting > Entity Script Server Log
|
||||
auto essLogAction = addActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::EntityScriptServerLog, 0,
|
||||
qApp, SLOT(toggleEntityScriptServerLogDialog()));
|
||||
{
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
QObject::connect(nodeList.data(), &NodeList::canRezChanged, essLogAction, [essLogAction] {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
essLogAction->setEnabled(nodeList->getThisNodeCanRez());
|
||||
});
|
||||
essLogAction->setEnabled(nodeList->getThisNodeCanRez());
|
||||
}
|
||||
|
||||
// Developer > Scripting > Script Log (HMD friendly)...
|
||||
addActionToQMenuAndActionHash(scriptingOptionsMenu, "Script Log (HMD friendly)...", Qt::NoButton,
|
||||
qApp, SLOT(showScriptLogs()));
|
||||
|
||||
// Developer > Scripting > Verbose Logging
|
||||
addCheckableActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::VerboseLogging, 0, false,
|
||||
qApp, SLOT(updateVerboseLogging()));
|
||||
|
||||
// Developer > Scripting > Enable Speech Control API
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||
auto speechRecognizer = DependencyManager::get<SpeechRecognizer>();
|
||||
QAction* speechRecognizerAction = addCheckableActionToQMenuAndActionHash(scriptingOptionsMenu, MenuOption::ControlWithSpeech,
|
||||
Qt::CTRL | Qt::SHIFT | Qt::Key_C,
|
||||
speechRecognizer->getEnabled(),
|
||||
speechRecognizer.data(),
|
||||
SLOT(setEnabled(bool)),
|
||||
UNSPECIFIED_POSITION);
|
||||
connect(speechRecognizer.data(), SIGNAL(enabledUpdated(bool)), speechRecognizerAction, SLOT(setChecked(bool)));
|
||||
#endif
|
||||
|
||||
// Developer > UI >>>
|
||||
MenuWrapper* uiOptionsMenu = developerMenu->addMenu("UI");
|
||||
action = addCheckableActionToQMenuAndActionHash(uiOptionsMenu, MenuOption::DesktopTabletToToolbar, 0,
|
||||
qApp->getDesktopTabletBecomesToolbarSetting());
|
||||
|
||||
// Developer > UI > Show Overlays
|
||||
addCheckableActionToQMenuAndActionHash(uiOptionsMenu, MenuOption::Overlays, 0, true);
|
||||
|
||||
// Developer > UI > Desktop Tablet Becomes Toolbar
|
||||
connect(action, &QAction::triggered, [action] {
|
||||
qApp->setDesktopTabletBecomesToolbarSetting(action->isChecked());
|
||||
});
|
||||
|
||||
|
||||
// Developer > UI > HMD Tablet Becomes Toolbar
|
||||
action = addCheckableActionToQMenuAndActionHash(uiOptionsMenu, MenuOption::HMDTabletToToolbar, 0,
|
||||
qApp->getHmdTabletBecomesToolbarSetting());
|
||||
connect(action, &QAction::triggered, [action] {
|
||||
|
@ -570,6 +626,8 @@ Menu::Menu() {
|
|||
avatar.get(), SLOT(updateMotionBehaviorFromMenu()),
|
||||
UNSPECIFIED_POSITION, "Developer");
|
||||
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowTrackedObjects, 0, false, qApp, SLOT(setShowTrackedObjects(bool)));
|
||||
|
||||
// Developer > Hands >>>
|
||||
MenuWrapper* handOptionsMenu = developerMenu->addMenu("Hands");
|
||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false,
|
||||
|
@ -684,10 +742,11 @@ Menu::Menu() {
|
|||
addCheckableActionToQMenuAndActionHash(pickingOptionsMenu, MenuOption::ForceCoarsePicking, 0, false,
|
||||
DependencyManager::get<PickManager>().data(), SLOT(setForceCoarsePicking(bool)));
|
||||
|
||||
// Developer > Display Crash Options
|
||||
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::DisplayCrashOptions, 0, true);
|
||||
// Developer > Crash >>>
|
||||
MenuWrapper* crashMenu = developerMenu->addMenu("Crash");
|
||||
|
||||
// Developer > Crash > Display Crash Options
|
||||
addCheckableActionToQMenuAndActionHash(crashMenu, MenuOption::DisplayCrashOptions, 0, true);
|
||||
|
||||
addActionToQMenuAndActionHash(crashMenu, MenuOption::DeadlockInterface, 0, qApp, SLOT(deadlockApplication()));
|
||||
addActionToQMenuAndActionHash(crashMenu, MenuOption::UnresponsiveInterface, 0, qApp, SLOT(unresponsiveApplication()));
|
||||
|
@ -722,59 +781,15 @@ Menu::Menu() {
|
|||
action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNewFaultThreaded);
|
||||
connect(action, &QAction::triggered, qApp, []() { std::thread(crash::newFault).join(); });
|
||||
|
||||
// Developer > Stats
|
||||
// Developer > Show Statistics
|
||||
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Stats);
|
||||
|
||||
// Developer > Show Animation Statistics
|
||||
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::AnimStats);
|
||||
|
||||
// Settings > Enable Speech Control API
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||
auto speechRecognizer = DependencyManager::get<SpeechRecognizer>();
|
||||
QAction* speechRecognizerAction = addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::ControlWithSpeech,
|
||||
Qt::CTRL | Qt::SHIFT | Qt::Key_C,
|
||||
speechRecognizer->getEnabled(),
|
||||
speechRecognizer.data(),
|
||||
SLOT(setEnabled(bool)),
|
||||
UNSPECIFIED_POSITION);
|
||||
connect(speechRecognizer.data(), SIGNAL(enabledUpdated(bool)), speechRecognizerAction, SLOT(setChecked(bool)));
|
||||
#endif
|
||||
|
||||
// console
|
||||
addActionToQMenuAndActionHash(developerMenu, MenuOption::Console, Qt::CTRL | Qt::ALT | Qt::Key_J,
|
||||
DependencyManager::get<StandAloneJSConsole>().data(),
|
||||
SLOT(toggleConsole()),
|
||||
QAction::NoRole,
|
||||
UNSPECIFIED_POSITION);
|
||||
|
||||
// Developer > API Debugger
|
||||
action = addActionToQMenuAndActionHash(developerMenu, "API Debugger");
|
||||
connect(action, &QAction::triggered, [] {
|
||||
QUrl defaultScriptsLoc = PathUtils::defaultScriptsLocation();
|
||||
defaultScriptsLoc.setPath(defaultScriptsLoc.path() + "developer/utilities/tools/currentAPI.js");
|
||||
DependencyManager::get<ScriptEngines>()->loadScript(defaultScriptsLoc.toString());
|
||||
});
|
||||
|
||||
// Developer > Log...
|
||||
// Developer > Log
|
||||
addActionToQMenuAndActionHash(developerMenu, MenuOption::Log, Qt::CTRL | Qt::SHIFT | Qt::Key_L,
|
||||
qApp, SLOT(toggleLogDialog()));
|
||||
auto essLogAction = addActionToQMenuAndActionHash(developerMenu, MenuOption::EntityScriptServerLog, 0,
|
||||
qApp, SLOT(toggleEntityScriptServerLogDialog()));
|
||||
{
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
QObject::connect(nodeList.data(), &NodeList::canRezChanged, essLogAction, [essLogAction] {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
essLogAction->setEnabled(nodeList->getThisNodeCanRez());
|
||||
});
|
||||
essLogAction->setEnabled(nodeList->getThisNodeCanRez());
|
||||
}
|
||||
|
||||
addActionToQMenuAndActionHash(developerMenu, "Script Log (HMD friendly)...", Qt::NoButton,
|
||||
qApp, SLOT(showScriptLogs()));
|
||||
|
||||
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::VerboseLogging, 0, false,
|
||||
qApp, SLOT(updateVerboseLogging()));
|
||||
|
||||
// Developer > Show Overlays
|
||||
addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Overlays, 0, true);
|
||||
|
||||
#if 0 /// -------------- REMOVED FOR NOW --------------
|
||||
addDisabledActionAndSeparator(navigateMenu, "History");
|
||||
|
|
|
@ -183,6 +183,7 @@ namespace MenuOption {
|
|||
const QString RunClientScriptTests = "Run Client Script Tests";
|
||||
const QString RunTimingTests = "Run Timing Tests";
|
||||
const QString ScriptedMotorControl = "Enable Scripted Motor Control";
|
||||
const QString ShowTrackedObjects = "Show Tracked Objects";
|
||||
const QString SendWrongDSConnectVersion = "Send wrong DS connect version";
|
||||
const QString SendWrongProtocolVersion = "Send wrong protocol version";
|
||||
const QString SetHomeLocation = "Set Home Location";
|
||||
|
|
|
@ -261,7 +261,9 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
|||
for (auto it = sortedAvatarVector.begin(); it != sortedAvatarVector.end(); ++it) {
|
||||
const SortableAvatar& sortData = *it;
|
||||
const auto avatar = std::static_pointer_cast<OtherAvatar>(sortData.getAvatar());
|
||||
|
||||
if (!avatar->_isClientAvatar) {
|
||||
avatar->setIsClientAvatar(true);
|
||||
}
|
||||
// TODO: to help us scale to more avatars it would be nice to not have to poll this stuff every update
|
||||
if (avatar->getSkeletonModel()->isLoaded()) {
|
||||
// remove the orb if it is there
|
||||
|
|
|
@ -36,6 +36,7 @@ Rig::CharacterControllerState convertCharacterControllerState(CharacterControlle
|
|||
static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) {
|
||||
glm::mat4 worldToSensorMat = glm::inverse(myAvatar->getSensorToWorldMatrix());
|
||||
|
||||
|
||||
// check for pinned hips.
|
||||
auto hipsIndex = myAvatar->getJointIndex("Hips");
|
||||
if (myAvatar->isJointPinned(hipsIndex)) {
|
||||
|
@ -199,49 +200,38 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
if (avatarHeadPose.isValid() && !(params.primaryControllerFlags[Rig::PrimaryControllerType_Hips] & (uint8_t)Rig::ControllerFlags::Enabled)) {
|
||||
bool isFlying = (myAvatar->getCharacterController()->getState() == CharacterController::State::Hover || myAvatar->getCharacterController()->computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS);
|
||||
|
||||
if (!_prevHipsValid) {
|
||||
AnimPose hips = computeHipsInSensorFrame(myAvatar, isFlying);
|
||||
_prevHips = hips;
|
||||
}
|
||||
|
||||
AnimPose hips = computeHipsInSensorFrame(myAvatar, isFlying);
|
||||
|
||||
// timescale in seconds
|
||||
const float TRANS_HORIZ_TIMESCALE = 0.15f;
|
||||
const float TRANS_VERT_TIMESCALE = 0.01f; // We want the vertical component of the hips to follow quickly to prevent spine squash/stretch.
|
||||
const float ROT_TIMESCALE = 0.15f;
|
||||
const float FLY_IDLE_TRANSITION_TIMESCALE = 0.25f;
|
||||
|
||||
float transHorizAlpha, transVertAlpha, rotAlpha;
|
||||
if (_flyIdleTimer < 0.0f) {
|
||||
transHorizAlpha = glm::min(deltaTime / TRANS_HORIZ_TIMESCALE, 1.0f);
|
||||
transVertAlpha = glm::min(deltaTime / TRANS_VERT_TIMESCALE, 1.0f);
|
||||
rotAlpha = glm::min(deltaTime / ROT_TIMESCALE, 1.0f);
|
||||
_smoothHipsHelper.setHorizontalTranslationTimescale(TRANS_HORIZ_TIMESCALE);
|
||||
_smoothHipsHelper.setVerticalTranslationTimescale(TRANS_VERT_TIMESCALE);
|
||||
_smoothHipsHelper.setRotationTimescale(ROT_TIMESCALE);
|
||||
} else {
|
||||
transHorizAlpha = glm::min(deltaTime / FLY_IDLE_TRANSITION_TIMESCALE, 1.0f);
|
||||
transVertAlpha = glm::min(deltaTime / FLY_IDLE_TRANSITION_TIMESCALE, 1.0f);
|
||||
rotAlpha = glm::min(deltaTime / FLY_IDLE_TRANSITION_TIMESCALE, 1.0f);
|
||||
_smoothHipsHelper.setHorizontalTranslationTimescale(FLY_IDLE_TRANSITION_TIMESCALE);
|
||||
_smoothHipsHelper.setVerticalTranslationTimescale(FLY_IDLE_TRANSITION_TIMESCALE);
|
||||
_smoothHipsHelper.setRotationTimescale(FLY_IDLE_TRANSITION_TIMESCALE);
|
||||
}
|
||||
|
||||
// smootly lerp hips, in sensorframe, with different coeff for horiz and vertical translation.
|
||||
float hipsY = hips.trans().y;
|
||||
hips.trans() = lerp(_prevHips.trans(), hips.trans(), transHorizAlpha);
|
||||
hips.trans().y = lerp(_prevHips.trans().y, hipsY, transVertAlpha);
|
||||
hips.rot() = safeLerp(_prevHips.rot(), hips.rot(), rotAlpha);
|
||||
|
||||
_prevHips = hips;
|
||||
_prevHipsValid = true;
|
||||
AnimPose sensorHips = computeHipsInSensorFrame(myAvatar, isFlying);
|
||||
if (!_prevIsEstimatingHips) {
|
||||
_smoothHipsHelper.teleport(sensorHips);
|
||||
}
|
||||
sensorHips = _smoothHipsHelper.update(sensorHips, deltaTime);
|
||||
|
||||
glm::mat4 invRigMat = glm::inverse(myAvatar->getTransform().getMatrix() * Matrices::Y_180);
|
||||
AnimPose sensorToRigPose(invRigMat * myAvatar->getSensorToWorldMatrix());
|
||||
|
||||
params.primaryControllerPoses[Rig::PrimaryControllerType_Hips] = sensorToRigPose * hips;
|
||||
params.primaryControllerPoses[Rig::PrimaryControllerType_Hips] = sensorToRigPose * sensorHips;
|
||||
params.primaryControllerFlags[Rig::PrimaryControllerType_Hips] = (uint8_t)Rig::ControllerFlags::Enabled | (uint8_t)Rig::ControllerFlags::Estimated;
|
||||
|
||||
// set spine2 if we have hand controllers
|
||||
if (myAvatar->getControllerPoseInAvatarFrame(controller::Action::RIGHT_HAND).isValid() &&
|
||||
myAvatar->getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() &&
|
||||
!(params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] & (uint8_t)Rig::ControllerFlags::Enabled)) {
|
||||
myAvatar->getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND).isValid() &&
|
||||
!(params.primaryControllerFlags[Rig::PrimaryControllerType_Spine2] & (uint8_t)Rig::ControllerFlags::Enabled)) {
|
||||
|
||||
AnimPose currentSpine2Pose;
|
||||
AnimPose currentHeadPose;
|
||||
|
@ -268,8 +258,9 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
}
|
||||
}
|
||||
|
||||
_prevIsEstimatingHips = true;
|
||||
} else {
|
||||
_prevHipsValid = false;
|
||||
_prevIsEstimatingHips = false;
|
||||
}
|
||||
|
||||
params.isTalking = head->getTimeWithoutTalking() <= 1.5f;
|
||||
|
@ -299,7 +290,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
auto velocity = myAvatar->getLocalVelocity() / myAvatar->getSensorToWorldScale();
|
||||
auto position = myAvatar->getLocalPosition();
|
||||
auto orientation = myAvatar->getLocalOrientation();
|
||||
_rig.computeMotionAnimationState(deltaTime, position, velocity, orientation, ccState);
|
||||
_rig.computeMotionAnimationState(deltaTime, position, velocity, orientation, ccState, myAvatar->getSensorToWorldScale());
|
||||
|
||||
// evaluate AnimGraph animation and update jointStates.
|
||||
Model::updateRig(deltaTime, parentTransform);
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#define hifi_MySkeletonModel_h
|
||||
|
||||
#include <avatars-renderer/SkeletonModel.h>
|
||||
#include <AnimUtil.h>
|
||||
#include "MyAvatar.h"
|
||||
|
||||
/// A skeleton loaded from a model.
|
||||
|
@ -26,11 +27,12 @@ public:
|
|||
private:
|
||||
void updateFingers();
|
||||
|
||||
AnimPose _prevHips; // sensor frame
|
||||
bool _prevHipsValid { false };
|
||||
CriticallyDampedSpringPoseHelper _smoothHipsHelper; // sensor frame
|
||||
bool _prevIsFlying { false };
|
||||
float _flyIdleTimer { 0.0f };
|
||||
|
||||
float _prevIsEstimatingHips { false };
|
||||
|
||||
std::map<int, int> _jointRotationFrameOffsetMap;
|
||||
};
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include <SandboxUtils.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <NetworkAccessManager.h>
|
||||
#include <gl/GLHelpers.h>
|
||||
|
||||
#include "AddressManager.h"
|
||||
#include "Application.h"
|
||||
|
@ -40,6 +41,18 @@ extern "C" {
|
|||
#endif
|
||||
|
||||
int main(int argc, const char* argv[]) {
|
||||
#ifdef Q_OS_MAC
|
||||
auto format = getDefaultOpenGLSurfaceFormat();
|
||||
// Deal with some weirdness in the chromium context sharing on Mac.
|
||||
// The primary share context needs to be 3.2, so that the Chromium will
|
||||
// succeed in it's creation of it's command stub contexts.
|
||||
format.setVersion(3, 2);
|
||||
// This appears to resolve the issues with corrupted fonts on OSX. No
|
||||
// idea why.
|
||||
qputenv("QT_ENABLE_GLYPH_CACHE_WORKAROUND", "true");
|
||||
// https://i.kym-cdn.com/entries/icons/original/000/008/342/ihave.jpg
|
||||
QSurfaceFormat::setDefaultFormat(format);
|
||||
#endif
|
||||
setupHifiApplication(BuildInfo::INTERFACE_NAME);
|
||||
|
||||
QStringList arguments;
|
||||
|
|
|
@ -115,6 +115,7 @@ void ApplicationOverlay::renderQmlUi(RenderArgs* renderArgs) {
|
|||
batch.resetViewTransform();
|
||||
batch.setResourceTexture(0, _uiTexture);
|
||||
geometryCache->renderUnitQuad(batch, glm::vec4(1), _qmlGeometryId);
|
||||
batch.setResourceTexture(0, nullptr);
|
||||
}
|
||||
|
||||
void ApplicationOverlay::renderOverlays(RenderArgs* renderArgs) {
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
#include "AnimUtil.h"
|
||||
|
||||
static const int MAX_TARGET_MARKERS = 30;
|
||||
static const float JOINT_CHAIN_INTERP_TIME = 0.25f;
|
||||
static const float JOINT_CHAIN_INTERP_TIME = 0.5f;
|
||||
|
||||
static void lookupJointInfo(const AnimInverseKinematics::JointChainInfo& jointChainInfo,
|
||||
int indexA, int indexB,
|
||||
|
@ -253,11 +253,25 @@ void AnimInverseKinematics::solve(const AnimContext& context, const std::vector<
|
|||
if (numLoops == MAX_IK_LOOPS) {
|
||||
for (size_t i = 0; i < _prevJointChainInfoVec.size(); i++) {
|
||||
if (_prevJointChainInfoVec[i].timer > 0.0f) {
|
||||
|
||||
float alpha = (JOINT_CHAIN_INTERP_TIME - _prevJointChainInfoVec[i].timer) / JOINT_CHAIN_INTERP_TIME;
|
||||
|
||||
// ease in expo
|
||||
alpha = 1.0f - powf(2.0f, -10.0f * alpha);
|
||||
|
||||
size_t chainSize = std::min(_prevJointChainInfoVec[i].jointInfoVec.size(), jointChainInfoVec[i].jointInfoVec.size());
|
||||
for (size_t j = 0; j < chainSize; j++) {
|
||||
jointChainInfoVec[i].jointInfoVec[j].rot = safeMix(_prevJointChainInfoVec[i].jointInfoVec[j].rot, jointChainInfoVec[i].jointInfoVec[j].rot, alpha);
|
||||
jointChainInfoVec[i].jointInfoVec[j].trans = lerp(_prevJointChainInfoVec[i].jointInfoVec[j].trans, jointChainInfoVec[i].jointInfoVec[j].trans, alpha);
|
||||
|
||||
if (jointChainInfoVec[i].target.getType() != IKTarget::Type::Unknown) {
|
||||
// if we are interping into an enabled target type, i.e. not off, lerp the rot and the trans.
|
||||
for (size_t j = 0; j < chainSize; j++) {
|
||||
jointChainInfoVec[i].jointInfoVec[j].rot = safeMix(_prevJointChainInfoVec[i].jointInfoVec[j].rot, jointChainInfoVec[i].jointInfoVec[j].rot, alpha);
|
||||
jointChainInfoVec[i].jointInfoVec[j].trans = lerp(_prevJointChainInfoVec[i].jointInfoVec[j].trans, jointChainInfoVec[i].jointInfoVec[j].trans, alpha);
|
||||
}
|
||||
} else {
|
||||
// if we are interping into a disabled target type, keep the rot & trans the same, but lerp the weight down to zero.
|
||||
jointChainInfoVec[i].target.setType((int)_prevJointChainInfoVec[i].target.getType());
|
||||
jointChainInfoVec[i].target.setWeight(_prevJointChainInfoVec[i].target.getWeight() * (1.0f - alpha));
|
||||
jointChainInfoVec[i].jointInfoVec = _prevJointChainInfoVec[i].jointInfoVec;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -201,14 +201,17 @@ const AnimPoseVec& AnimTwoBoneIK::evaluate(const AnimVariantMap& animVars, const
|
|||
if (_interpType != InterpType::None) {
|
||||
_interpAlpha += _interpAlphaVel * dt;
|
||||
|
||||
// ease in expo
|
||||
float easeInAlpha = 1.0f - powf(2.0f, -10.0f * _interpAlpha);
|
||||
|
||||
if (_interpAlpha < 1.0f) {
|
||||
AnimChain interpChain;
|
||||
if (_interpType == InterpType::SnapshotToUnderPoses) {
|
||||
interpChain = underChain;
|
||||
interpChain.blend(_snapshotChain, _interpAlpha);
|
||||
interpChain.blend(_snapshotChain, easeInAlpha);
|
||||
} else if (_interpType == InterpType::SnapshotToSolve) {
|
||||
interpChain = ikChain;
|
||||
interpChain.blend(_snapshotChain, _interpAlpha);
|
||||
interpChain.blend(_snapshotChain, easeInAlpha);
|
||||
}
|
||||
// copy interpChain into _poses
|
||||
interpChain.outputRelativePoses(_poses);
|
||||
|
|
|
@ -38,4 +38,94 @@ AnimPose boneLookAt(const glm::vec3& target, const AnimPose& bone);
|
|||
// and returns a bodyRot that is also z-forward and y-up
|
||||
glm::quat computeBodyFacingFromHead(const glm::quat& headRot, const glm::vec3& up);
|
||||
|
||||
|
||||
// Uses a approximation of a critically damped spring to smooth full AnimPoses.
|
||||
// It provides seperate timescales for horizontal, vertical and rotation components.
|
||||
// The timescale is roughly how much time it will take the spring will reach halfway toward it's target.
|
||||
class CriticallyDampedSpringPoseHelper {
|
||||
public:
|
||||
CriticallyDampedSpringPoseHelper() : _prevPoseValid(false) {}
|
||||
|
||||
void setHorizontalTranslationTimescale(float timescale) {
|
||||
_horizontalTranslationTimescale = timescale;
|
||||
}
|
||||
void setVerticalTranslationTimescale(float timescale) {
|
||||
_verticalTranslationTimescale = timescale;
|
||||
}
|
||||
void setRotationTimescale(float timescale) {
|
||||
_rotationTimescale = timescale;
|
||||
}
|
||||
|
||||
AnimPose update(const AnimPose& pose, float deltaTime) {
|
||||
if (!_prevPoseValid) {
|
||||
_prevPose = pose;
|
||||
_prevPoseValid = true;
|
||||
}
|
||||
|
||||
const float horizontalTranslationAlpha = glm::min(deltaTime / _horizontalTranslationTimescale, 1.0f);
|
||||
const float verticalTranslationAlpha = glm::min(deltaTime / _verticalTranslationTimescale, 1.0f);
|
||||
const float rotationAlpha = glm::min(deltaTime / _rotationTimescale, 1.0f);
|
||||
|
||||
const float poseY = pose.trans().y;
|
||||
AnimPose newPose = _prevPose;
|
||||
newPose.trans() = lerp(_prevPose.trans(), pose.trans(), horizontalTranslationAlpha);
|
||||
newPose.trans().y = lerp(_prevPose.trans().y, poseY, verticalTranslationAlpha);
|
||||
newPose.rot() = safeLerp(_prevPose.rot(), pose.rot(), rotationAlpha);
|
||||
|
||||
_prevPose = newPose;
|
||||
_prevPoseValid = true;
|
||||
|
||||
return newPose;
|
||||
}
|
||||
|
||||
void teleport(const AnimPose& pose) {
|
||||
_prevPoseValid = true;
|
||||
_prevPose = pose;
|
||||
}
|
||||
|
||||
protected:
|
||||
AnimPose _prevPose;
|
||||
float _horizontalTranslationTimescale { 0.15f };
|
||||
float _verticalTranslationTimescale { 0.15f };
|
||||
float _rotationTimescale { 0.15f };
|
||||
bool _prevPoseValid;
|
||||
};
|
||||
|
||||
class SnapshotBlendPoseHelper {
|
||||
public:
|
||||
SnapshotBlendPoseHelper() : _snapshotValid(false) {}
|
||||
|
||||
void setBlendDuration(float duration) {
|
||||
_duration = duration;
|
||||
}
|
||||
|
||||
void setSnapshot(const AnimPose& pose) {
|
||||
_snapshotValid = true;
|
||||
_snapshotPose = pose;
|
||||
_timer = _duration;
|
||||
}
|
||||
|
||||
AnimPose update(const AnimPose& targetPose, float deltaTime) {
|
||||
_timer -= deltaTime;
|
||||
if (_timer > 0.0f) {
|
||||
float alpha = (_duration - _timer) / _duration;
|
||||
|
||||
// ease in expo
|
||||
alpha = 1.0f - powf(2.0f, -10.0f * alpha);
|
||||
|
||||
AnimPose newPose = targetPose;
|
||||
newPose.blend(_snapshotPose, alpha);
|
||||
return newPose;
|
||||
} else {
|
||||
return targetPose;
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
AnimPose _snapshotPose;
|
||||
float _duration { 1.0f };
|
||||
float _timer { 0.0f };
|
||||
bool _snapshotValid { false };
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include "AnimOverlay.h"
|
||||
#include "AnimSkeleton.h"
|
||||
#include "AnimUtil.h"
|
||||
#include "AvatarConstants.h"
|
||||
#include "IKTarget.h"
|
||||
#include "PathUtils.h"
|
||||
|
||||
|
@ -692,7 +693,8 @@ bool Rig::getRelativeDefaultJointTranslation(int index, glm::vec3& translationOu
|
|||
}
|
||||
}
|
||||
|
||||
void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation, CharacterControllerState ccState) {
|
||||
void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity,
|
||||
const glm::quat& worldRotation, CharacterControllerState ccState, float sensorToWorldScale) {
|
||||
|
||||
glm::vec3 forward = worldRotation * IDENTITY_FORWARD;
|
||||
glm::vec3 workingVelocity = worldVelocity;
|
||||
|
@ -987,9 +989,15 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos
|
|||
}
|
||||
_animVars.set("isNotInAir", false);
|
||||
|
||||
// compute blend based on velocity
|
||||
const float JUMP_SPEED = 3.5f;
|
||||
float alpha = glm::clamp(-workingVelocity.y / JUMP_SPEED, -1.0f, 1.0f) + 1.0f;
|
||||
// We want to preserve the apparent jump height in sensor space.
|
||||
const float jumpHeight = std::max(sensorToWorldScale * DEFAULT_AVATAR_JUMP_HEIGHT, DEFAULT_AVATAR_MIN_JUMP_HEIGHT);
|
||||
|
||||
// convert jump height to a initial jump speed with the given gravity.
|
||||
const float jumpSpeed = sqrtf(2.0f * -DEFAULT_AVATAR_GRAVITY * jumpHeight);
|
||||
|
||||
// compute inAirAlpha blend based on velocity
|
||||
float alpha = glm::clamp((-workingVelocity.y * sensorToWorldScale) / jumpSpeed, -1.0f, 1.0f) + 1.0f;
|
||||
|
||||
_animVars.set("inAirAlpha", alpha);
|
||||
}
|
||||
|
||||
|
@ -1667,6 +1675,7 @@ glm::vec3 Rig::calculateKneePoleVector(int footJointIndex, int kneeIndex, int up
|
|||
|
||||
void Rig::updateFromControllerParameters(const ControllerParameters& params, float dt) {
|
||||
if (!_animSkeleton || !_animNode) {
|
||||
_previousControllerParameters = params;
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1677,7 +1686,9 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo
|
|||
bool leftHandEnabled = params.primaryControllerFlags[PrimaryControllerType_LeftHand] & (uint8_t)ControllerFlags::Enabled;
|
||||
bool rightHandEnabled = params.primaryControllerFlags[PrimaryControllerType_RightHand] & (uint8_t)ControllerFlags::Enabled;
|
||||
bool hipsEnabled = params.primaryControllerFlags[PrimaryControllerType_Hips] & (uint8_t)ControllerFlags::Enabled;
|
||||
bool prevHipsEnabled = _previousControllerParameters.primaryControllerFlags[PrimaryControllerType_Hips] & (uint8_t)ControllerFlags::Enabled;
|
||||
bool hipsEstimated = params.primaryControllerFlags[PrimaryControllerType_Hips] & (uint8_t)ControllerFlags::Estimated;
|
||||
bool prevHipsEstimated = _previousControllerParameters.primaryControllerFlags[PrimaryControllerType_Hips] & (uint8_t)ControllerFlags::Estimated;
|
||||
bool leftFootEnabled = params.primaryControllerFlags[PrimaryControllerType_LeftFoot] & (uint8_t)ControllerFlags::Enabled;
|
||||
bool rightFootEnabled = params.primaryControllerFlags[PrimaryControllerType_RightFoot] & (uint8_t)ControllerFlags::Enabled;
|
||||
bool spine2Enabled = params.primaryControllerFlags[PrimaryControllerType_Spine2] & (uint8_t)ControllerFlags::Enabled;
|
||||
|
@ -1716,9 +1727,26 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo
|
|||
}
|
||||
|
||||
if (hipsEnabled) {
|
||||
|
||||
// Apply a bit of smoothing when the hips toggle between estimated and non-estimated poses.
|
||||
// This should help smooth out problems with the vive tracker when the sensor is occluded.
|
||||
if (prevHipsEnabled && hipsEstimated != prevHipsEstimated) {
|
||||
// blend from a snapshot of the previous hips.
|
||||
const float HIPS_BLEND_DURATION = 0.5f;
|
||||
_hipsBlendHelper.setBlendDuration(HIPS_BLEND_DURATION);
|
||||
_hipsBlendHelper.setSnapshot(_previousControllerParameters.primaryControllerPoses[PrimaryControllerType_Hips]);
|
||||
} else if (!prevHipsEnabled) {
|
||||
// we have no sensible value to blend from.
|
||||
const float HIPS_BLEND_DURATION = 0.0f;
|
||||
_hipsBlendHelper.setBlendDuration(HIPS_BLEND_DURATION);
|
||||
_hipsBlendHelper.setSnapshot(params.primaryControllerPoses[PrimaryControllerType_Hips]);
|
||||
}
|
||||
|
||||
AnimPose hips = _hipsBlendHelper.update(params.primaryControllerPoses[PrimaryControllerType_Hips], dt);
|
||||
|
||||
_animVars.set("hipsType", (int)IKTarget::Type::RotationAndPosition);
|
||||
_animVars.set("hipsPosition", params.primaryControllerPoses[PrimaryControllerType_Hips].trans());
|
||||
_animVars.set("hipsRotation", params.primaryControllerPoses[PrimaryControllerType_Hips].rot());
|
||||
_animVars.set("hipsPosition", hips.trans());
|
||||
_animVars.set("hipsRotation", hips.rot());
|
||||
} else {
|
||||
_animVars.set("hipsType", (int)IKTarget::Type::Unknown);
|
||||
}
|
||||
|
@ -1758,6 +1786,8 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
_previousControllerParameters = params;
|
||||
}
|
||||
|
||||
void Rig::initAnimGraph(const QUrl& url) {
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "AnimNode.h"
|
||||
#include "AnimNodeLoader.h"
|
||||
#include "SimpleMovingAverage.h"
|
||||
#include "AnimUtil.h"
|
||||
|
||||
class Rig;
|
||||
class AnimInverseKinematics;
|
||||
|
@ -175,7 +176,8 @@ public:
|
|||
AnimPose getJointPose(int jointIndex) const;
|
||||
|
||||
// Start or stop animations as needed.
|
||||
void computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation, CharacterControllerState ccState);
|
||||
void computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity,
|
||||
const glm::quat& worldRotation, CharacterControllerState ccState, float sensorToWorldScale);
|
||||
|
||||
// Regardless of who started the animations or how many, update the joints.
|
||||
void updateAnimations(float deltaTime, const glm::mat4& rootTransform, const glm::mat4& rigToWorldTransform);
|
||||
|
@ -413,6 +415,9 @@ protected:
|
|||
|
||||
AnimContext _lastContext;
|
||||
AnimVariantMap _lastAnimVars;
|
||||
|
||||
SnapshotBlendPoseHelper _hipsBlendHelper;
|
||||
ControllerParameters _previousControllerParameters;
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__Rig__) */
|
||||
|
|
|
@ -707,10 +707,10 @@ static TextRenderer3D* textRenderer(TextRendererType type) {
|
|||
return displayNameRenderer;
|
||||
}
|
||||
|
||||
void Avatar::metaBlendshapeOperator(int blendshapeNumber, const QVector<BlendshapeOffset>& blendshapeOffsets, const QVector<int>& blendedMeshSizes,
|
||||
const render::ItemIDs& subItemIDs) {
|
||||
void Avatar::metaBlendshapeOperator(render::ItemID renderItemID, int blendshapeNumber, const QVector<BlendshapeOffset>& blendshapeOffsets,
|
||||
const QVector<int>& blendedMeshSizes, const render::ItemIDs& subItemIDs) {
|
||||
render::Transaction transaction;
|
||||
transaction.updateItem<AvatarData>(_renderItemID, [blendshapeNumber, blendshapeOffsets, blendedMeshSizes,
|
||||
transaction.updateItem<AvatarData>(renderItemID, [blendshapeNumber, blendshapeOffsets, blendedMeshSizes,
|
||||
subItemIDs](AvatarData& avatar) {
|
||||
auto avatarPtr = dynamic_cast<Avatar*>(&avatar);
|
||||
if (avatarPtr) {
|
||||
|
@ -730,7 +730,7 @@ void Avatar::addToScene(AvatarSharedPointer self, const render::ScenePointer& sc
|
|||
_renderBound = getBounds();
|
||||
transaction.resetItem(_renderItemID, avatarPayloadPointer);
|
||||
using namespace std::placeholders;
|
||||
_skeletonModel->addToScene(scene, transaction, std::bind(&Avatar::metaBlendshapeOperator, this, _1, _2, _3, _4));
|
||||
_skeletonModel->addToScene(scene, transaction, std::bind(&Avatar::metaBlendshapeOperator, _renderItemID, _1, _2, _3, _4));
|
||||
_skeletonModel->setTagMask(render::hifi::TAG_ALL_VIEWS);
|
||||
_skeletonModel->setGroupCulled(true);
|
||||
_skeletonModel->setCanCastShadow(true);
|
||||
|
@ -954,7 +954,7 @@ void Avatar::fixupModelsInScene(const render::ScenePointer& scene) {
|
|||
if (_skeletonModel->isRenderable() && _skeletonModel->needsFixupInScene()) {
|
||||
_skeletonModel->removeFromScene(scene, transaction);
|
||||
using namespace std::placeholders;
|
||||
_skeletonModel->addToScene(scene, transaction, std::bind(&Avatar::metaBlendshapeOperator, this, _1, _2, _3, _4));
|
||||
_skeletonModel->addToScene(scene, transaction, std::bind(&Avatar::metaBlendshapeOperator, _renderItemID, _1, _2, _3, _4));
|
||||
|
||||
_skeletonModel->setTagMask(render::hifi::TAG_ALL_VIEWS);
|
||||
_skeletonModel->setGroupCulled(true);
|
||||
|
|
|
@ -626,8 +626,8 @@ protected:
|
|||
|
||||
LoadingStatus _loadingStatus { LoadingStatus::NoModel };
|
||||
|
||||
void metaBlendshapeOperator(int blendshapeNumber, const QVector<BlendshapeOffset>& blendshapeOffsets, const QVector<int>& blendedMeshSizes,
|
||||
const render::ItemIDs& subItemIDs);
|
||||
static void metaBlendshapeOperator(render::ItemID renderItemID, int blendshapeNumber, const QVector<BlendshapeOffset>& blendshapeOffsets,
|
||||
const QVector<int>& blendedMeshSizes, const render::ItemIDs& subItemIDs);
|
||||
};
|
||||
|
||||
#endif // hifi_Avatar_h
|
||||
|
|
|
@ -66,7 +66,7 @@ size_t AvatarDataPacket::maxFaceTrackerInfoSize(size_t numBlendshapeCoefficients
|
|||
}
|
||||
|
||||
size_t AvatarDataPacket::maxJointDataSize(size_t numJoints, bool hasGrabJoints) {
|
||||
const size_t validityBitsSize = (size_t)std::ceil(numJoints / (float)BITS_IN_BYTE);
|
||||
const size_t validityBitsSize = calcBitVectorSize((int)numJoints);
|
||||
|
||||
size_t totalSize = sizeof(uint8_t); // numJoints
|
||||
|
||||
|
@ -228,18 +228,18 @@ float AvatarData::getDistanceBasedMinTranslationDistance(glm::vec3 viewerPositio
|
|||
|
||||
// we want to track outbound data in this case...
|
||||
QByteArray AvatarData::toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) {
|
||||
AvatarDataPacket::HasFlags hasFlagsOut;
|
||||
auto lastSentTime = _lastToByteArray;
|
||||
_lastToByteArray = usecTimestampNow();
|
||||
return AvatarData::toByteArray(dataDetail, lastSentTime, getLastSentJointData(),
|
||||
hasFlagsOut, dropFaceTracking, false, glm::vec3(0), nullptr,
|
||||
&_outboundDataRate);
|
||||
AvatarDataPacket::SendStatus sendStatus;
|
||||
auto avatarByteArray = AvatarData::toByteArray(dataDetail, lastSentTime, getLastSentJointData(),
|
||||
sendStatus, dropFaceTracking, false, glm::vec3(0), nullptr, 0, &_outboundDataRate);
|
||||
return avatarByteArray;
|
||||
}
|
||||
|
||||
QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime,
|
||||
const QVector<JointData>& lastSentJointData,
|
||||
AvatarDataPacket::HasFlags& hasFlagsOut, bool dropFaceTracking, bool distanceAdjust,
|
||||
glm::vec3 viewerPosition, QVector<JointData>* sentJointDataOut, AvatarDataRate* outboundDataRateOut) const {
|
||||
AvatarDataPacket::SendStatus& sendStatus, bool dropFaceTracking, bool distanceAdjust,
|
||||
glm::vec3 viewerPosition, QVector<JointData>* sentJointDataOut, int maxDataSize, AvatarDataRate* outboundDataRateOut) const {
|
||||
|
||||
bool cullSmallChanges = (dataDetail == CullSmallData);
|
||||
bool sendAll = (dataDetail == SendAllData);
|
||||
|
@ -247,11 +247,23 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
bool sendPALMinimum = (dataDetail == PALMinimum);
|
||||
|
||||
lazyInitHeadData();
|
||||
ASSERT(maxDataSize == 0 || (size_t)maxDataSize >= AvatarDataPacket::MIN_BULK_PACKET_SIZE);
|
||||
|
||||
// Leading flags, to indicate how much data is actually included in the packet...
|
||||
AvatarDataPacket::HasFlags wantedFlags = 0;
|
||||
AvatarDataPacket::HasFlags includedFlags = 0;
|
||||
AvatarDataPacket::HasFlags extraReturnedFlags = 0; // For partial joint data.
|
||||
|
||||
// special case, if we were asked for no data, then just include the flags all set to nothing
|
||||
if (dataDetail == NoData) {
|
||||
AvatarDataPacket::HasFlags packetStateFlags = 0;
|
||||
QByteArray avatarDataByteArray(reinterpret_cast<char*>(&packetStateFlags), sizeof(packetStateFlags));
|
||||
sendStatus.itemFlags = wantedFlags;
|
||||
|
||||
QByteArray avatarDataByteArray;
|
||||
if (sendStatus.sendUUID) {
|
||||
avatarDataByteArray.append(getSessionUUID().toRfc4122().data(), NUM_BYTES_RFC4122_UUID);
|
||||
}
|
||||
|
||||
avatarDataByteArray.append((char*) &wantedFlags, sizeof wantedFlags);
|
||||
return avatarDataByteArray;
|
||||
}
|
||||
|
||||
|
@ -274,109 +286,141 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
// 3 translations * 6 bytes = 6.48kbps
|
||||
//
|
||||
|
||||
auto parentID = getParentID();
|
||||
|
||||
bool hasAvatarGlobalPosition = true; // always include global position
|
||||
bool hasAvatarOrientation = false;
|
||||
bool hasAvatarBoundingBox = false;
|
||||
bool hasAvatarScale = false;
|
||||
bool hasLookAtPosition = false;
|
||||
bool hasAudioLoudness = false;
|
||||
bool hasSensorToWorldMatrix = false;
|
||||
bool hasAdditionalFlags = false;
|
||||
|
||||
// local position, and parent info only apply to avatars that are parented. The local position
|
||||
// and the parent info can change independently though, so we track their "changed since"
|
||||
// separately
|
||||
bool hasParentInfo = false;
|
||||
bool hasAvatarLocalPosition = false;
|
||||
|
||||
bool hasFaceTrackerInfo = false;
|
||||
bool hasJointData = false;
|
||||
bool hasJointDefaultPoseFlags = false;
|
||||
bool hasGrabJoints = false;
|
||||
QUuid parentID;
|
||||
|
||||
glm::mat4 leftFarGrabMatrix;
|
||||
glm::mat4 rightFarGrabMatrix;
|
||||
glm::mat4 mouseFarGrabMatrix;
|
||||
|
||||
if (sendPALMinimum) {
|
||||
hasAudioLoudness = true;
|
||||
} else {
|
||||
hasAvatarOrientation = sendAll || rotationChangedSince(lastSentTime);
|
||||
hasAvatarBoundingBox = sendAll || avatarBoundingBoxChangedSince(lastSentTime);
|
||||
hasAvatarScale = sendAll || avatarScaleChangedSince(lastSentTime);
|
||||
hasLookAtPosition = sendAll || lookAtPositionChangedSince(lastSentTime);
|
||||
hasAudioLoudness = sendAll || audioLoudnessChangedSince(lastSentTime);
|
||||
hasSensorToWorldMatrix = sendAll || sensorToWorldMatrixChangedSince(lastSentTime);
|
||||
hasAdditionalFlags = sendAll || additionalFlagsChangedSince(lastSentTime);
|
||||
hasParentInfo = sendAll || parentInfoChangedSince(lastSentTime);
|
||||
hasAvatarLocalPosition = hasParent() && (sendAll ||
|
||||
tranlationChangedSince(lastSentTime) ||
|
||||
parentInfoChangedSince(lastSentTime));
|
||||
if (sendStatus.itemFlags == 0) {
|
||||
// New avatar ...
|
||||
bool hasAvatarGlobalPosition = true; // always include global position
|
||||
bool hasAvatarOrientation = false;
|
||||
bool hasAvatarBoundingBox = false;
|
||||
bool hasAvatarScale = false;
|
||||
bool hasLookAtPosition = false;
|
||||
bool hasAudioLoudness = false;
|
||||
bool hasSensorToWorldMatrix = false;
|
||||
bool hasJointData = false;
|
||||
bool hasJointDefaultPoseFlags = false;
|
||||
bool hasAdditionalFlags = false;
|
||||
|
||||
hasFaceTrackerInfo = !dropFaceTracking && (hasFaceTracker() || getHasScriptedBlendshapes()) &&
|
||||
(sendAll || faceTrackerInfoChangedSince(lastSentTime));
|
||||
hasJointData = sendAll || !sendMinimum;
|
||||
hasJointDefaultPoseFlags = hasJointData;
|
||||
if (hasJointData) {
|
||||
bool leftValid;
|
||||
leftFarGrabMatrix = _farGrabLeftMatrixCache.get(leftValid);
|
||||
if (!leftValid) {
|
||||
leftFarGrabMatrix = glm::mat4();
|
||||
}
|
||||
bool rightValid;
|
||||
rightFarGrabMatrix = _farGrabRightMatrixCache.get(rightValid);
|
||||
if (!rightValid) {
|
||||
rightFarGrabMatrix = glm::mat4();
|
||||
}
|
||||
bool mouseValid;
|
||||
mouseFarGrabMatrix = _farGrabMouseMatrixCache.get(mouseValid);
|
||||
if (!mouseValid) {
|
||||
mouseFarGrabMatrix = glm::mat4();
|
||||
}
|
||||
hasGrabJoints = (leftValid || rightValid || mouseValid);
|
||||
// local position, and parent info only apply to avatars that are parented. The local position
|
||||
// and the parent info can change independently though, so we track their "changed since"
|
||||
// separately
|
||||
bool hasParentInfo = false;
|
||||
bool hasAvatarLocalPosition = false;
|
||||
|
||||
bool hasFaceTrackerInfo = false;
|
||||
|
||||
if (sendPALMinimum) {
|
||||
hasAudioLoudness = true;
|
||||
} else {
|
||||
hasAvatarOrientation = sendAll || rotationChangedSince(lastSentTime);
|
||||
hasAvatarBoundingBox = sendAll || avatarBoundingBoxChangedSince(lastSentTime);
|
||||
hasAvatarScale = sendAll || avatarScaleChangedSince(lastSentTime);
|
||||
hasLookAtPosition = sendAll || lookAtPositionChangedSince(lastSentTime);
|
||||
hasAudioLoudness = sendAll || audioLoudnessChangedSince(lastSentTime);
|
||||
hasSensorToWorldMatrix = sendAll || sensorToWorldMatrixChangedSince(lastSentTime);
|
||||
hasAdditionalFlags = sendAll || additionalFlagsChangedSince(lastSentTime);
|
||||
hasParentInfo = sendAll || parentInfoChangedSince(lastSentTime);
|
||||
hasAvatarLocalPosition = hasParent() && (sendAll ||
|
||||
tranlationChangedSince(lastSentTime) ||
|
||||
parentInfoChangedSince(lastSentTime));
|
||||
|
||||
hasFaceTrackerInfo = !dropFaceTracking && (hasFaceTracker() || getHasScriptedBlendshapes()) &&
|
||||
(sendAll || faceTrackerInfoChangedSince(lastSentTime));
|
||||
hasJointData = !sendMinimum;
|
||||
hasJointDefaultPoseFlags = hasJointData;
|
||||
}
|
||||
|
||||
wantedFlags =
|
||||
(hasAvatarGlobalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_GLOBAL_POSITION : 0)
|
||||
| (hasAvatarBoundingBox ? AvatarDataPacket::PACKET_HAS_AVATAR_BOUNDING_BOX : 0)
|
||||
| (hasAvatarOrientation ? AvatarDataPacket::PACKET_HAS_AVATAR_ORIENTATION : 0)
|
||||
| (hasAvatarScale ? AvatarDataPacket::PACKET_HAS_AVATAR_SCALE : 0)
|
||||
| (hasLookAtPosition ? AvatarDataPacket::PACKET_HAS_LOOK_AT_POSITION : 0)
|
||||
| (hasAudioLoudness ? AvatarDataPacket::PACKET_HAS_AUDIO_LOUDNESS : 0)
|
||||
| (hasSensorToWorldMatrix ? AvatarDataPacket::PACKET_HAS_SENSOR_TO_WORLD_MATRIX : 0)
|
||||
| (hasAdditionalFlags ? AvatarDataPacket::PACKET_HAS_ADDITIONAL_FLAGS : 0)
|
||||
| (hasParentInfo ? AvatarDataPacket::PACKET_HAS_PARENT_INFO : 0)
|
||||
| (hasAvatarLocalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_LOCAL_POSITION : 0)
|
||||
| (hasFaceTrackerInfo ? AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO : 0)
|
||||
| (hasJointData ? AvatarDataPacket::PACKET_HAS_JOINT_DATA : 0)
|
||||
| (hasJointDefaultPoseFlags ? AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS : 0)
|
||||
| (hasJointData ? AvatarDataPacket::PACKET_HAS_GRAB_JOINTS : 0);
|
||||
|
||||
sendStatus.itemFlags = wantedFlags;
|
||||
sendStatus.rotationsSent = 0;
|
||||
sendStatus.translationsSent = 0;
|
||||
} else { // Continuing avatar ...
|
||||
wantedFlags = sendStatus.itemFlags;
|
||||
if (wantedFlags & AvatarDataPacket::PACKET_HAS_GRAB_JOINTS) {
|
||||
// Must send joints for grab joints -
|
||||
wantedFlags |= AvatarDataPacket::PACKET_HAS_JOINT_DATA;
|
||||
}
|
||||
}
|
||||
|
||||
if (wantedFlags & AvatarDataPacket::PACKET_HAS_GRAB_JOINTS) {
|
||||
bool leftValid;
|
||||
leftFarGrabMatrix = _farGrabLeftMatrixCache.get(leftValid);
|
||||
if (!leftValid) {
|
||||
leftFarGrabMatrix = glm::mat4();
|
||||
}
|
||||
bool rightValid;
|
||||
rightFarGrabMatrix = _farGrabRightMatrixCache.get(rightValid);
|
||||
if (!rightValid) {
|
||||
rightFarGrabMatrix = glm::mat4();
|
||||
}
|
||||
bool mouseValid;
|
||||
mouseFarGrabMatrix = _farGrabMouseMatrixCache.get(mouseValid);
|
||||
if (!mouseValid) {
|
||||
mouseFarGrabMatrix = glm::mat4();
|
||||
}
|
||||
if (!(leftValid || rightValid || mouseValid)) {
|
||||
wantedFlags &= ~AvatarDataPacket::PACKET_HAS_GRAB_JOINTS;
|
||||
}
|
||||
}
|
||||
if (wantedFlags & (AvatarDataPacket::PACKET_HAS_ADDITIONAL_FLAGS | AvatarDataPacket::PACKET_HAS_PARENT_INFO)) {
|
||||
parentID = getParentID();
|
||||
}
|
||||
|
||||
const size_t byteArraySize = AvatarDataPacket::MAX_CONSTANT_HEADER_SIZE +
|
||||
(hasFaceTrackerInfo ? AvatarDataPacket::maxFaceTrackerInfoSize(_headData->getBlendshapeCoefficients().size()) : 0) +
|
||||
(hasJointData ? AvatarDataPacket::maxJointDataSize(_jointData.size(), hasGrabJoints) : 0) +
|
||||
(hasJointDefaultPoseFlags ? AvatarDataPacket::maxJointDefaultPoseFlagsSize(_jointData.size()) : 0);
|
||||
const size_t byteArraySize = AvatarDataPacket::MAX_CONSTANT_HEADER_SIZE + NUM_BYTES_RFC4122_UUID +
|
||||
AvatarDataPacket::maxFaceTrackerInfoSize(_headData->getBlendshapeCoefficients().size()) +
|
||||
AvatarDataPacket::maxJointDataSize(_jointData.size(), true) +
|
||||
AvatarDataPacket::maxJointDefaultPoseFlagsSize(_jointData.size());
|
||||
|
||||
if (maxDataSize == 0) {
|
||||
maxDataSize = (int)byteArraySize;
|
||||
}
|
||||
|
||||
QByteArray avatarDataByteArray((int)byteArraySize, 0);
|
||||
unsigned char* destinationBuffer = reinterpret_cast<unsigned char*>(avatarDataByteArray.data());
|
||||
unsigned char* startPosition = destinationBuffer;
|
||||
|
||||
// Leading flags, to indicate how much data is actually included in the packet...
|
||||
AvatarDataPacket::HasFlags packetStateFlags =
|
||||
(hasAvatarGlobalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_GLOBAL_POSITION : 0)
|
||||
| (hasAvatarBoundingBox ? AvatarDataPacket::PACKET_HAS_AVATAR_BOUNDING_BOX : 0)
|
||||
| (hasAvatarOrientation ? AvatarDataPacket::PACKET_HAS_AVATAR_ORIENTATION : 0)
|
||||
| (hasAvatarScale ? AvatarDataPacket::PACKET_HAS_AVATAR_SCALE : 0)
|
||||
| (hasLookAtPosition ? AvatarDataPacket::PACKET_HAS_LOOK_AT_POSITION : 0)
|
||||
| (hasAudioLoudness ? AvatarDataPacket::PACKET_HAS_AUDIO_LOUDNESS : 0)
|
||||
| (hasSensorToWorldMatrix ? AvatarDataPacket::PACKET_HAS_SENSOR_TO_WORLD_MATRIX : 0)
|
||||
| (hasAdditionalFlags ? AvatarDataPacket::PACKET_HAS_ADDITIONAL_FLAGS : 0)
|
||||
| (hasParentInfo ? AvatarDataPacket::PACKET_HAS_PARENT_INFO : 0)
|
||||
| (hasAvatarLocalPosition ? AvatarDataPacket::PACKET_HAS_AVATAR_LOCAL_POSITION : 0)
|
||||
| (hasFaceTrackerInfo ? AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO : 0)
|
||||
| (hasJointData ? AvatarDataPacket::PACKET_HAS_JOINT_DATA : 0)
|
||||
| (hasJointDefaultPoseFlags ? AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS : 0)
|
||||
| (hasGrabJoints ? AvatarDataPacket::PACKET_HAS_GRAB_JOINTS : 0);
|
||||
|
||||
memcpy(destinationBuffer, &packetStateFlags, sizeof(packetStateFlags));
|
||||
destinationBuffer += sizeof(packetStateFlags);
|
||||
const unsigned char* const startPosition = destinationBuffer;
|
||||
const unsigned char* const packetEnd = destinationBuffer + maxDataSize;
|
||||
|
||||
#define AVATAR_MEMCPY(src) \
|
||||
memcpy(destinationBuffer, &(src), sizeof(src)); \
|
||||
destinationBuffer += sizeof(src);
|
||||
|
||||
if (hasAvatarGlobalPosition) {
|
||||
auto startSection = destinationBuffer;
|
||||
AVATAR_MEMCPY(_globalPosition);
|
||||
// If we want an item and there's sufficient space:
|
||||
#define IF_AVATAR_SPACE(flag, space) \
|
||||
if ((wantedFlags & AvatarDataPacket::flag) \
|
||||
&& (packetEnd - destinationBuffer) >= (ptrdiff_t)(space) \
|
||||
&& (includedFlags |= AvatarDataPacket::flag))
|
||||
|
||||
if (sendStatus.sendUUID) {
|
||||
memcpy(destinationBuffer, getSessionUUID().toRfc4122(), NUM_BYTES_RFC4122_UUID);
|
||||
destinationBuffer += NUM_BYTES_RFC4122_UUID;
|
||||
}
|
||||
|
||||
unsigned char * packetFlagsLocation = destinationBuffer;
|
||||
destinationBuffer += sizeof(wantedFlags);
|
||||
|
||||
IF_AVATAR_SPACE(PACKET_HAS_AVATAR_GLOBAL_POSITION, sizeof _globalPosition) {
|
||||
auto startSection = destinationBuffer;
|
||||
AVATAR_MEMCPY(_globalPosition);
|
||||
|
||||
int numBytes = destinationBuffer - startSection;
|
||||
|
||||
if (outboundDataRateOut) {
|
||||
|
@ -384,7 +428,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
}
|
||||
}
|
||||
|
||||
if (hasAvatarBoundingBox) {
|
||||
IF_AVATAR_SPACE(PACKET_HAS_AVATAR_BOUNDING_BOX, sizeof _globalBoundingBoxDimensions + sizeof _globalBoundingBoxOffset) {
|
||||
auto startSection = destinationBuffer;
|
||||
AVATAR_MEMCPY(_globalBoundingBoxDimensions);
|
||||
AVATAR_MEMCPY(_globalBoundingBoxOffset);
|
||||
|
@ -395,7 +439,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
}
|
||||
}
|
||||
|
||||
if (hasAvatarOrientation) {
|
||||
IF_AVATAR_SPACE(PACKET_HAS_AVATAR_ORIENTATION, sizeof(AvatarDataPacket::SixByteQuat)) {
|
||||
auto startSection = destinationBuffer;
|
||||
auto localOrientation = getOrientationOutbound();
|
||||
destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, localOrientation);
|
||||
|
@ -406,7 +450,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
}
|
||||
}
|
||||
|
||||
if (hasAvatarScale) {
|
||||
IF_AVATAR_SPACE(PACKET_HAS_AVATAR_SCALE, sizeof(AvatarDataPacket::AvatarScale)) {
|
||||
auto startSection = destinationBuffer;
|
||||
auto data = reinterpret_cast<AvatarDataPacket::AvatarScale*>(destinationBuffer);
|
||||
auto scale = getDomainLimitedScale();
|
||||
|
@ -419,7 +463,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
}
|
||||
}
|
||||
|
||||
if (hasLookAtPosition) {
|
||||
IF_AVATAR_SPACE(PACKET_HAS_LOOK_AT_POSITION, sizeof(_headData->getLookAtPosition()) ) {
|
||||
auto startSection = destinationBuffer;
|
||||
AVATAR_MEMCPY(_headData->getLookAtPosition());
|
||||
int numBytes = destinationBuffer - startSection;
|
||||
|
@ -428,7 +472,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
}
|
||||
}
|
||||
|
||||
if (hasAudioLoudness) {
|
||||
IF_AVATAR_SPACE(PACKET_HAS_AUDIO_LOUDNESS, sizeof(AvatarDataPacket::AudioLoudness)) {
|
||||
auto startSection = destinationBuffer;
|
||||
auto data = reinterpret_cast<AvatarDataPacket::AudioLoudness*>(destinationBuffer);
|
||||
data->audioLoudness = packFloatGainToByte(getAudioLoudness() / AUDIO_LOUDNESS_SCALE);
|
||||
|
@ -440,7 +484,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
}
|
||||
}
|
||||
|
||||
if (hasSensorToWorldMatrix) {
|
||||
IF_AVATAR_SPACE(PACKET_HAS_SENSOR_TO_WORLD_MATRIX, sizeof(AvatarDataPacket::SensorToWorldMatrix)) {
|
||||
auto startSection = destinationBuffer;
|
||||
auto data = reinterpret_cast<AvatarDataPacket::SensorToWorldMatrix*>(destinationBuffer);
|
||||
glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix();
|
||||
|
@ -458,7 +502,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
}
|
||||
}
|
||||
|
||||
if (hasAdditionalFlags) {
|
||||
IF_AVATAR_SPACE(PACKET_HAS_ADDITIONAL_FLAGS, sizeof (uint16_t)) {
|
||||
auto startSection = destinationBuffer;
|
||||
auto data = reinterpret_cast<AvatarDataPacket::AdditionalFlags*>(destinationBuffer);
|
||||
|
||||
|
@ -506,7 +550,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
}
|
||||
}
|
||||
|
||||
if (hasParentInfo) {
|
||||
IF_AVATAR_SPACE(PACKET_HAS_PARENT_INFO, sizeof(AvatarDataPacket::ParentInfo)) {
|
||||
auto startSection = destinationBuffer;
|
||||
auto parentInfo = reinterpret_cast<AvatarDataPacket::ParentInfo*>(destinationBuffer);
|
||||
QByteArray referentialAsBytes = parentID.toRfc4122();
|
||||
|
@ -520,7 +564,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
}
|
||||
}
|
||||
|
||||
if (hasAvatarLocalPosition) {
|
||||
IF_AVATAR_SPACE(PACKET_HAS_AVATAR_LOCAL_POSITION, sizeof(getLocalPosition()) ) {
|
||||
auto startSection = destinationBuffer;
|
||||
const auto localPosition = getLocalPosition();
|
||||
AVATAR_MEMCPY(localPosition);
|
||||
|
@ -531,11 +575,11 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
}
|
||||
}
|
||||
|
||||
const auto& blendshapeCoefficients = _headData->getBlendshapeCoefficients();
|
||||
// If it is connected, pack up the data
|
||||
if (hasFaceTrackerInfo) {
|
||||
IF_AVATAR_SPACE(PACKET_HAS_FACE_TRACKER_INFO, sizeof(AvatarDataPacket::FaceTrackerInfo) + (size_t)blendshapeCoefficients.size() * sizeof(float)) {
|
||||
auto startSection = destinationBuffer;
|
||||
auto faceTrackerInfo = reinterpret_cast<AvatarDataPacket::FaceTrackerInfo*>(destinationBuffer);
|
||||
const auto& blendshapeCoefficients = _headData->getBlendshapeCoefficients();
|
||||
// note: we don't use the blink and average loudness, we just use the numBlendShapes and
|
||||
// compute the procedural info on the client side.
|
||||
faceTrackerInfo->leftEyeBlink = _headData->_leftEyeBlink;
|
||||
|
@ -555,125 +599,125 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
}
|
||||
|
||||
QVector<JointData> jointData;
|
||||
if (hasJointData || hasJointDefaultPoseFlags) {
|
||||
if (wantedFlags & (AvatarDataPacket::PACKET_HAS_JOINT_DATA | AvatarDataPacket::PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS)) {
|
||||
QReadLocker readLock(&_jointDataLock);
|
||||
jointData = _jointData;
|
||||
}
|
||||
const int numJoints = jointData.size();
|
||||
assert(numJoints <= 255);
|
||||
const int jointBitVectorSize = calcBitVectorSize(numJoints);
|
||||
|
||||
// If it is connected, pack up the data
|
||||
if (hasJointData) {
|
||||
// Start joints if room for at least the faux joints.
|
||||
IF_AVATAR_SPACE(PACKET_HAS_JOINT_DATA, 1 + 2 * jointBitVectorSize + AvatarDataPacket::FAUX_JOINTS_SIZE) {
|
||||
// Allow for faux joints + translation bit-vector:
|
||||
const ptrdiff_t minSizeForJoint = sizeof(AvatarDataPacket::SixByteQuat)
|
||||
+ jointBitVectorSize + AvatarDataPacket::FAUX_JOINTS_SIZE;
|
||||
auto startSection = destinationBuffer;
|
||||
|
||||
// joint rotation data
|
||||
int numJoints = jointData.size();
|
||||
*destinationBuffer++ = (uint8_t)numJoints;
|
||||
|
||||
unsigned char* validityPosition = destinationBuffer;
|
||||
unsigned char validity = 0;
|
||||
int validityBit = 0;
|
||||
int numValidityBytes = calcBitVectorSize(numJoints);
|
||||
memset(validityPosition, 0, jointBitVectorSize);
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
int rotationSentCount = 0;
|
||||
unsigned char* beforeRotations = destinationBuffer;
|
||||
#endif
|
||||
|
||||
destinationBuffer += numValidityBytes; // Move pointer past the validity bytes
|
||||
destinationBuffer += jointBitVectorSize; // Move pointer past the validity bytes
|
||||
|
||||
// sentJointDataOut and lastSentJointData might be the same vector
|
||||
if (sentJointDataOut) {
|
||||
sentJointDataOut->resize(numJoints); // Make sure the destination is resized before using it
|
||||
}
|
||||
const JointData *const joints = jointData.data();
|
||||
JointData *const sentJoints = sentJointDataOut ? sentJointDataOut->data() : nullptr;
|
||||
|
||||
float minRotationDOT = (distanceAdjust && cullSmallChanges) ? getDistanceBasedMinRotationDOT(viewerPosition) : AVATAR_MIN_ROTATION_DOT;
|
||||
|
||||
for (int i = 0; i < jointData.size(); i++) {
|
||||
const JointData& data = jointData[i];
|
||||
int i = sendStatus.rotationsSent;
|
||||
for (; i < numJoints; ++i) {
|
||||
const JointData& data = joints[i];
|
||||
const JointData& last = lastSentJointData[i];
|
||||
|
||||
if (!data.rotationIsDefaultPose) {
|
||||
// The dot product for larger rotations is a lower number.
|
||||
// So if the dot() is less than the value, then the rotation is a larger angle of rotation
|
||||
if (sendAll || last.rotationIsDefaultPose || (!cullSmallChanges && last.rotation != data.rotation)
|
||||
|| (cullSmallChanges && fabsf(glm::dot(last.rotation, data.rotation)) < minRotationDOT) ) {
|
||||
validity |= (1 << validityBit);
|
||||
if (packetEnd - destinationBuffer >= minSizeForJoint) {
|
||||
if (!data.rotationIsDefaultPose) {
|
||||
// The dot product for larger rotations is a lower number,
|
||||
// so if the dot() is less than the value, then the rotation is a larger angle of rotation
|
||||
if (sendAll || last.rotationIsDefaultPose || (!cullSmallChanges && last.rotation != data.rotation)
|
||||
|| (cullSmallChanges && fabsf(glm::dot(last.rotation, data.rotation)) < minRotationDOT)) {
|
||||
validityPosition[i / BITS_IN_BYTE] |= 1 << (i % BITS_IN_BYTE);
|
||||
#ifdef WANT_DEBUG
|
||||
rotationSentCount++;
|
||||
rotationSentCount++;
|
||||
#endif
|
||||
destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation);
|
||||
destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, data.rotation);
|
||||
|
||||
if (sentJointDataOut) {
|
||||
(*sentJointDataOut)[i].rotation = data.rotation;
|
||||
if (sentJoints) {
|
||||
sentJoints[i].rotation = data.rotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
if (sentJointDataOut) {
|
||||
(*sentJointDataOut)[i].rotationIsDefaultPose = data.rotationIsDefaultPose;
|
||||
if (sentJoints) {
|
||||
sentJoints[i].rotationIsDefaultPose = data.rotationIsDefaultPose;
|
||||
}
|
||||
|
||||
if (++validityBit == BITS_IN_BYTE) {
|
||||
*validityPosition++ = validity;
|
||||
validityBit = validity = 0;
|
||||
}
|
||||
}
|
||||
if (validityBit != 0) {
|
||||
*validityPosition++ = validity;
|
||||
}
|
||||
sendStatus.rotationsSent = i;
|
||||
|
||||
// joint translation data
|
||||
validityPosition = destinationBuffer;
|
||||
validity = 0;
|
||||
validityBit = 0;
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
int translationSentCount = 0;
|
||||
unsigned char* beforeTranslations = destinationBuffer;
|
||||
#endif
|
||||
|
||||
destinationBuffer += numValidityBytes; // Move pointer past the validity bytes
|
||||
memset(destinationBuffer, 0, jointBitVectorSize);
|
||||
destinationBuffer += jointBitVectorSize; // Move pointer past the validity bytes
|
||||
|
||||
float minTranslation = (distanceAdjust && cullSmallChanges) ? getDistanceBasedMinTranslationDistance(viewerPosition) : AVATAR_MIN_TRANSLATION;
|
||||
|
||||
float maxTranslationDimension = 0.0;
|
||||
for (int i = 0; i < jointData.size(); i++) {
|
||||
const JointData& data = jointData[i];
|
||||
i = sendStatus.translationsSent;
|
||||
for (; i < numJoints; ++i) {
|
||||
const JointData& data = joints[i];
|
||||
const JointData& last = lastSentJointData[i];
|
||||
|
||||
if (!data.translationIsDefaultPose) {
|
||||
if (sendAll || last.translationIsDefaultPose || (!cullSmallChanges && last.translation != data.translation)
|
||||
|| (cullSmallChanges && glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation)) {
|
||||
|
||||
validity |= (1 << validityBit);
|
||||
if (packetEnd - destinationBuffer >= minSizeForJoint) {
|
||||
if (!data.translationIsDefaultPose) {
|
||||
if (sendAll || last.translationIsDefaultPose || (!cullSmallChanges && last.translation != data.translation)
|
||||
|| (cullSmallChanges && glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation)) {
|
||||
validityPosition[i / BITS_IN_BYTE] |= 1 << (i % BITS_IN_BYTE);
|
||||
#ifdef WANT_DEBUG
|
||||
translationSentCount++;
|
||||
translationSentCount++;
|
||||
#endif
|
||||
maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension);
|
||||
maxTranslationDimension = glm::max(fabsf(data.translation.y), maxTranslationDimension);
|
||||
maxTranslationDimension = glm::max(fabsf(data.translation.z), maxTranslationDimension);
|
||||
maxTranslationDimension = glm::max(fabsf(data.translation.x), maxTranslationDimension);
|
||||
maxTranslationDimension = glm::max(fabsf(data.translation.y), maxTranslationDimension);
|
||||
maxTranslationDimension = glm::max(fabsf(data.translation.z), maxTranslationDimension);
|
||||
|
||||
destinationBuffer +=
|
||||
packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX);
|
||||
destinationBuffer +=
|
||||
packFloatVec3ToSignedTwoByteFixed(destinationBuffer, data.translation, TRANSLATION_COMPRESSION_RADIX);
|
||||
|
||||
if (sentJointDataOut) {
|
||||
(*sentJointDataOut)[i].translation = data.translation;
|
||||
if (sentJoints) {
|
||||
sentJoints[i].translation = data.translation;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
if (sentJointDataOut) {
|
||||
(*sentJointDataOut)[i].translationIsDefaultPose = data.translationIsDefaultPose;
|
||||
if (sentJoints) {
|
||||
sentJoints[i].translationIsDefaultPose = data.translationIsDefaultPose;
|
||||
}
|
||||
|
||||
if (++validityBit == BITS_IN_BYTE) {
|
||||
*validityPosition++ = validity;
|
||||
validityBit = validity = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (validityBit != 0) {
|
||||
*validityPosition++ = validity;
|
||||
}
|
||||
sendStatus.translationsSent = i;
|
||||
|
||||
// faux joints
|
||||
Transform controllerLeftHandTransform = Transform(getControllerLeftHandMatrix());
|
||||
|
@ -686,7 +730,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
destinationBuffer += packFloatVec3ToSignedTwoByteFixed(destinationBuffer, controllerRightHandTransform.getTranslation(),
|
||||
TRANSLATION_COMPRESSION_RADIX);
|
||||
|
||||
if (hasGrabJoints) {
|
||||
IF_AVATAR_SPACE(PACKET_HAS_GRAB_JOINTS, sizeof (AvatarDataPacket::FarGrabJoints)) {
|
||||
// the far-grab joints may range further than 3 meters, so we can't use packFloatVec3ToSignedTwoByteFixed etc
|
||||
auto startSection = destinationBuffer;
|
||||
|
||||
|
@ -728,18 +772,20 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
}
|
||||
#endif
|
||||
|
||||
if (sendStatus.rotationsSent != numJoints || sendStatus.translationsSent != numJoints) {
|
||||
extraReturnedFlags |= AvatarDataPacket::PACKET_HAS_JOINT_DATA;
|
||||
}
|
||||
|
||||
int numBytes = destinationBuffer - startSection;
|
||||
if (outboundDataRateOut) {
|
||||
outboundDataRateOut->jointDataRate.increment(numBytes);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (hasJointDefaultPoseFlags) {
|
||||
|
||||
IF_AVATAR_SPACE(PACKET_HAS_JOINT_DEFAULT_POSE_FLAGS, 1 + 2 * jointBitVectorSize) {
|
||||
auto startSection = destinationBuffer;
|
||||
|
||||
// write numJoints
|
||||
int numJoints = jointData.size();
|
||||
*destinationBuffer++ = (uint8_t)numJoints;
|
||||
|
||||
// write rotationIsDefaultPose bits
|
||||
|
@ -758,6 +804,10 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
}
|
||||
}
|
||||
|
||||
memcpy(packetFlagsLocation, &includedFlags, sizeof(includedFlags));
|
||||
// Return dropped items.
|
||||
sendStatus.itemFlags = (wantedFlags & ~includedFlags) | extraReturnedFlags;
|
||||
|
||||
int avatarDataSize = destinationBuffer - startPosition;
|
||||
|
||||
if (avatarDataSize > (int)byteArraySize) {
|
||||
|
@ -766,6 +816,9 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
}
|
||||
|
||||
return avatarDataByteArray.left(avatarDataSize);
|
||||
|
||||
#undef AVATAR_MEMCPY
|
||||
#undef IF_AVATAR_SPACE
|
||||
}
|
||||
|
||||
// NOTE: This is never used in a "distanceAdjust" mode, so it's ok that it doesn't use a variable minimum rotation/translation
|
||||
|
@ -883,17 +936,27 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
}
|
||||
|
||||
_serverPosition = glm::vec3(data->globalPosition[0], data->globalPosition[1], data->globalPosition[2]) + offset;
|
||||
auto oneStepDistance = glm::length(_globalPosition - _serverPosition);
|
||||
if (oneStepDistance <= AVATAR_TRANSIT_MIN_TRIGGER_DISTANCE || oneStepDistance >= AVATAR_TRANSIT_MAX_TRIGGER_DISTANCE) {
|
||||
_globalPosition = _serverPosition;
|
||||
// if we don't have a parent, make sure to also set our local position
|
||||
if (_isClientAvatar) {
|
||||
auto oneStepDistance = glm::length(_globalPosition - _serverPosition);
|
||||
if (oneStepDistance <= AVATAR_TRANSIT_MIN_TRIGGER_DISTANCE || oneStepDistance >= AVATAR_TRANSIT_MAX_TRIGGER_DISTANCE) {
|
||||
_globalPosition = _serverPosition;
|
||||
// if we don't have a parent, make sure to also set our local position
|
||||
if (!hasParent()) {
|
||||
setLocalPosition(_serverPosition);
|
||||
}
|
||||
}
|
||||
if (_globalPosition != _serverPosition) {
|
||||
_globalPositionChanged = now;
|
||||
}
|
||||
} else {
|
||||
if (_globalPosition != _serverPosition) {
|
||||
_globalPosition = _serverPosition;
|
||||
_globalPositionChanged = now;
|
||||
}
|
||||
if (!hasParent()) {
|
||||
setLocalPosition(_serverPosition);
|
||||
}
|
||||
}
|
||||
if (_globalPosition != _serverPosition) {
|
||||
_globalPositionChanged = now;
|
||||
}
|
||||
sourceBuffer += sizeof(AvatarDataPacket::AvatarGlobalPosition);
|
||||
int numBytesRead = sourceBuffer - startSection;
|
||||
_globalPositionRate.increment(numBytesRead);
|
||||
|
@ -918,6 +981,8 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
_avatarBoundingBoxChanged = now;
|
||||
}
|
||||
|
||||
_defaultBubbleBox = computeBubbleBox();
|
||||
|
||||
sourceBuffer += sizeof(AvatarDataPacket::AvatarBoundingBox);
|
||||
int numBytesRead = sourceBuffer - startSection;
|
||||
_avatarBoundingBoxRate.increment(numBytesRead);
|
||||
|
@ -1727,11 +1792,9 @@ glm::quat AvatarData::getOrientationOutbound() const {
|
|||
return (getLocalOrientation());
|
||||
}
|
||||
|
||||
void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& identityChanged,
|
||||
void AvatarData::processAvatarIdentity(QDataStream& packetStream, bool& identityChanged,
|
||||
bool& displayNameChanged) {
|
||||
|
||||
QDataStream packetStream(identityData);
|
||||
|
||||
QUuid avatarSessionID;
|
||||
|
||||
// peek the sequence number, this will tell us if we should be processing this identity packet at all
|
||||
|
@ -1746,17 +1809,18 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide
|
|||
<< (udt::SequenceNumber::Type) incomingSequenceNumber;
|
||||
}
|
||||
|
||||
if (incomingSequenceNumber > _identitySequenceNumber) {
|
||||
Identity identity;
|
||||
Identity identity;
|
||||
|
||||
packetStream
|
||||
>> identity.attachmentData
|
||||
>> identity.displayName
|
||||
>> identity.sessionDisplayName
|
||||
>> identity.isReplicated
|
||||
>> identity.lookAtSnappingEnabled
|
||||
packetStream
|
||||
>> identity.attachmentData
|
||||
>> identity.displayName
|
||||
>> identity.sessionDisplayName
|
||||
>> identity.isReplicated
|
||||
>> identity.lookAtSnappingEnabled
|
||||
;
|
||||
|
||||
if (incomingSequenceNumber > _identitySequenceNumber) {
|
||||
|
||||
// set the store identity sequence number to match the incoming identity
|
||||
_identitySequenceNumber = incomingSequenceNumber;
|
||||
|
||||
|
@ -2902,3 +2966,21 @@ void AvatarEntityMapFromScriptValue(const QScriptValue& object, AvatarEntityMap&
|
|||
value[EntityID] = binaryEntityProperties;
|
||||
}
|
||||
}
|
||||
|
||||
const float AvatarData::DEFAULT_BUBBLE_SCALE = 2.4f; // magic number determined empirically
|
||||
|
||||
AABox AvatarData::computeBubbleBox(float bubbleScale) const {
|
||||
AABox box = AABox(_globalBoundingBoxOffset - _globalBoundingBoxDimensions, _globalBoundingBoxDimensions);
|
||||
glm::vec3 size = box.getScale();
|
||||
size *= bubbleScale;
|
||||
const glm::vec3 MIN_BUBBLE_SCALE(0.3f, 1.3f, 0.3);
|
||||
size= glm::max(size, MIN_BUBBLE_SCALE);
|
||||
box.setScaleStayCentered(size);
|
||||
return box;
|
||||
}
|
||||
|
||||
AABox AvatarData::getDefaultBubbleBox() const {
|
||||
AABox bubbleBox(_defaultBubbleBox);
|
||||
bubbleBox.translate(_globalPosition);
|
||||
return bubbleBox;
|
||||
}
|
||||
|
|
|
@ -296,6 +296,17 @@ namespace AvatarDataPacket {
|
|||
} PACKED_END;
|
||||
const size_t FAR_GRAB_JOINTS_SIZE = 84;
|
||||
static_assert(sizeof(FarGrabJoints) == FAR_GRAB_JOINTS_SIZE, "AvatarDataPacket::FarGrabJoints size doesn't match.");
|
||||
|
||||
static const size_t MIN_BULK_PACKET_SIZE = NUM_BYTES_RFC4122_UUID + HEADER_SIZE;
|
||||
static const size_t FAUX_JOINTS_SIZE = 2 * (sizeof(SixByteQuat) + sizeof(SixByteTrans));
|
||||
|
||||
struct SendStatus {
|
||||
HasFlags itemFlags { 0 };
|
||||
bool sendUUID { false };
|
||||
int rotationsSent { 0 }; // ie: index of next unsent joint
|
||||
int translationsSent { 0 };
|
||||
operator bool() { return itemFlags == 0; }
|
||||
};
|
||||
}
|
||||
|
||||
const float MAX_AUDIO_LOUDNESS = 1000.0f; // close enough for mouth animation
|
||||
|
@ -463,8 +474,8 @@ public:
|
|||
virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking = false);
|
||||
|
||||
virtual QByteArray toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector<JointData>& lastSentJointData,
|
||||
AvatarDataPacket::HasFlags& hasFlagsOut, bool dropFaceTracking, bool distanceAdjust, glm::vec3 viewerPosition,
|
||||
QVector<JointData>* sentJointDataOut, AvatarDataRate* outboundDataRateOut = nullptr) const;
|
||||
AvatarDataPacket::SendStatus& sendStatus, bool dropFaceTracking, bool distanceAdjust, glm::vec3 viewerPosition,
|
||||
QVector<JointData>* sentJointDataOut, int maxDataSize = 0, AvatarDataRate* outboundDataRateOut = nullptr) const;
|
||||
|
||||
virtual void doneEncoding(bool cullSmallChanges);
|
||||
|
||||
|
@ -971,7 +982,7 @@ public:
|
|||
|
||||
// identityChanged returns true if identity has changed, false otherwise.
|
||||
// identityChanged returns true if identity has changed, false otherwise. Similarly for displayNameChanged and skeletonModelUrlChange.
|
||||
void processAvatarIdentity(const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged);
|
||||
void processAvatarIdentity(QDataStream& packetStream, bool& identityChanged, bool& displayNameChanged);
|
||||
|
||||
qint64 packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice& destination,
|
||||
AvatarTraits::TraitVersion traitVersion = AvatarTraits::NULL_TRAIT_VERSION);
|
||||
|
@ -1112,6 +1123,7 @@ public:
|
|||
|
||||
glm::vec3 getClientGlobalPosition() const { return _globalPosition; }
|
||||
AABox getGlobalBoundingBox() const { return AABox(_globalPosition + _globalBoundingBoxOffset - _globalBoundingBoxDimensions, _globalBoundingBoxDimensions); }
|
||||
AABox getDefaultBubbleBox() const;
|
||||
|
||||
/**jsdoc
|
||||
* @function MyAvatar.getAvatarEntityData
|
||||
|
@ -1204,8 +1216,12 @@ public:
|
|||
void setReplicaIndex(int replicaIndex) { _replicaIndex = replicaIndex; }
|
||||
int getReplicaIndex() { return _replicaIndex; }
|
||||
|
||||
static const float DEFAULT_BUBBLE_SCALE; /* = 2.4 */
|
||||
AABox computeBubbleBox(float bubbleScale = DEFAULT_BUBBLE_SCALE) const;
|
||||
|
||||
void setIsNewAvatar(bool isNewAvatar) { _isNewAvatar = isNewAvatar; }
|
||||
bool getIsNewAvatar() { return _isNewAvatar; }
|
||||
void setIsClientAvatar(bool isClientAvatar) { _isClientAvatar = isClientAvatar; }
|
||||
|
||||
signals:
|
||||
|
||||
|
@ -1440,6 +1456,8 @@ protected:
|
|||
glm::vec3 _globalBoundingBoxDimensions;
|
||||
glm::vec3 _globalBoundingBoxOffset;
|
||||
|
||||
AABox _defaultBubbleBox;
|
||||
|
||||
mutable ReadWriteLockable _avatarEntitiesLock;
|
||||
AvatarEntityIDs _avatarEntityDetached; // recently detached from this avatar
|
||||
AvatarEntityIDs _avatarEntityForRecording; // create new entities id for avatar recording
|
||||
|
@ -1469,6 +1487,7 @@ protected:
|
|||
float _density;
|
||||
int _replicaIndex { 0 };
|
||||
bool _isNewAvatar { true };
|
||||
bool _isClientAvatar { false };
|
||||
|
||||
// null unless MyAvatar or ScriptableAvatar sending traits data to mixer
|
||||
std::unique_ptr<ClientTraitsHandler> _clientTraitsHandler;
|
||||
|
|
|
@ -85,8 +85,9 @@ std::vector<AvatarSharedPointer> AvatarReplicas::takeReplicas(const QUuid& paren
|
|||
void AvatarReplicas::processAvatarIdentity(const QUuid& parentID, const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged) {
|
||||
if (_replicasMap.find(parentID) != _replicasMap.end()) {
|
||||
auto &replicas = _replicasMap[parentID];
|
||||
QDataStream identityDataStream(identityData);
|
||||
for (auto avatar : replicas) {
|
||||
avatar->processAvatarIdentity(identityData, identityChanged, displayNameChanged);
|
||||
avatar->processAvatarIdentity(identityDataStream, identityChanged, displayNameChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -284,39 +285,45 @@ AvatarSharedPointer AvatarHashMap::parseAvatarData(QSharedPointer<ReceivedMessag
|
|||
}
|
||||
|
||||
void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||
QDataStream avatarIdentityStream(message->getMessage());
|
||||
|
||||
// peek the avatar UUID from the incoming packet
|
||||
QUuid identityUUID = QUuid::fromRfc4122(message->peek(NUM_BYTES_RFC4122_UUID));
|
||||
while (!avatarIdentityStream.atEnd()) {
|
||||
// peek the avatar UUID from the incoming packet
|
||||
avatarIdentityStream.startTransaction();
|
||||
QUuid identityUUID;
|
||||
avatarIdentityStream >> identityUUID;
|
||||
avatarIdentityStream.rollbackTransaction();
|
||||
|
||||
if (identityUUID.isNull()) {
|
||||
qCDebug(avatars) << "Refusing to process identity packet for null avatar ID";
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure this isn't for an ignored avatar
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
static auto EMPTY = QUuid();
|
||||
|
||||
{
|
||||
QReadLocker locker(&_hashLock);
|
||||
auto me = _avatarHash.find(EMPTY);
|
||||
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.
|
||||
identityUUID = EMPTY;
|
||||
if (identityUUID.isNull()) {
|
||||
qCDebug(avatars) << "Refusing to process identity packet for null avatar ID";
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure this isn't for an ignored avatar
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
static auto EMPTY = QUuid();
|
||||
|
||||
{
|
||||
QReadLocker locker(&_hashLock);
|
||||
auto me = _avatarHash.find(EMPTY);
|
||||
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.
|
||||
identityUUID = EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
if (!nodeList->isIgnoringNode(identityUUID) || nodeList->getRequestsDomainListData()) {
|
||||
// mesh URL for a UUID, find avatar in our list
|
||||
bool isNewAvatar;
|
||||
auto avatar = newOrExistingAvatar(identityUUID, sendingNode, isNewAvatar);
|
||||
bool identityChanged = false;
|
||||
bool displayNameChanged = false;
|
||||
// In this case, the "sendingNode" is the Avatar Mixer.
|
||||
avatar->processAvatarIdentity(avatarIdentityStream, identityChanged, displayNameChanged);
|
||||
_replicas.processAvatarIdentity(identityUUID, message->getMessage(), identityChanged, displayNameChanged);
|
||||
}
|
||||
}
|
||||
|
||||
if (!nodeList->isIgnoringNode(identityUUID) || nodeList->getRequestsDomainListData()) {
|
||||
// mesh URL for a UUID, find avatar in our list
|
||||
bool isNewAvatar;
|
||||
auto avatar = newOrExistingAvatar(identityUUID, sendingNode, isNewAvatar);
|
||||
bool identityChanged = false;
|
||||
bool displayNameChanged = false;
|
||||
// In this case, the "sendingNode" is the Avatar Mixer.
|
||||
avatar->processAvatarIdentity(message->getMessage(), identityChanged, displayNameChanged);
|
||||
_replicas.processAvatarIdentity(identityUUID, message->getMessage(), identityChanged, displayNameChanged);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include "filters/RotateFilter.h"
|
||||
#include "filters/LowVelocityFilter.h"
|
||||
#include "filters/ExponentialSmoothingFilter.h"
|
||||
#include "filters/AccelerationLimiterFilter.h"
|
||||
|
||||
using namespace controller;
|
||||
|
||||
|
@ -51,6 +52,7 @@ REGISTER_FILTER_CLASS_INSTANCE(PostTransformFilter, "postTransform")
|
|||
REGISTER_FILTER_CLASS_INSTANCE(RotateFilter, "rotate")
|
||||
REGISTER_FILTER_CLASS_INSTANCE(LowVelocityFilter, "lowVelocity")
|
||||
REGISTER_FILTER_CLASS_INSTANCE(ExponentialSmoothingFilter, "exponentialSmoothing")
|
||||
REGISTER_FILTER_CLASS_INSTANCE(AccelerationLimiterFilter, "accelerationLimiter")
|
||||
|
||||
const QString JSON_FILTER_TYPE = QStringLiteral("type");
|
||||
const QString JSON_FILTER_PARAMS = QStringLiteral("params");
|
||||
|
|
|
@ -0,0 +1,192 @@
|
|||
//
|
||||
// Created by Anthony Thibault 2018/11/09
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
|
||||
#include "AccelerationLimiterFilter.h"
|
||||
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QJsonArray>
|
||||
#include "../../UserInputMapper.h"
|
||||
#include "../../Input.h"
|
||||
#include <DependencyManager.h>
|
||||
#include <QDebug>
|
||||
#include <StreamUtils.h>
|
||||
|
||||
static const QString JSON_ROTATION_ACCELERATION_LIMIT = QStringLiteral("rotationAccelerationLimit");
|
||||
static const QString JSON_TRANSLATION_ACCELERATION_LIMIT = QStringLiteral("translationAccelerationLimit");
|
||||
static const QString JSON_TRANSLATION_SNAP_THRESHOLD = QStringLiteral("translationSnapThreshold");
|
||||
static const QString JSON_ROTATION_SNAP_THRESHOLD = QStringLiteral("rotationSnapThreshold");
|
||||
|
||||
static glm::vec3 angularVelFromDeltaRot(const glm::quat& deltaQ, float dt) {
|
||||
// Measure the angular velocity of a delta rotation quaternion by using quaternion logarithm.
|
||||
// The logarithm of a unit quternion returns the axis of rotation with a length of one half the angle of rotation in the imaginary part.
|
||||
// The real part will be 0. Then we multiply it by 2 / dt. turning it into the angular velocity, (except for the extra w = 0 part).
|
||||
glm::quat omegaQ((2.0f / dt) * glm::log(deltaQ));
|
||||
return glm::vec3(omegaQ.x, omegaQ.y, omegaQ.z);
|
||||
}
|
||||
|
||||
static glm::quat deltaRotFromAngularVel(const glm::vec3& omega, float dt) {
|
||||
// Convert angular velocity into a delta quaternion by using quaternion exponent.
|
||||
// The exponent of quaternion will return a delta rotation around the axis of the imaginary part, by twice the angle as determined by the length of that imaginary part.
|
||||
// It is the inverse of the logarithm step in angularVelFromDeltaRot
|
||||
glm::quat omegaQ(0.0f, omega.x, omega.y, omega.z);
|
||||
return glm::exp((dt / 2.0f) * omegaQ);
|
||||
}
|
||||
|
||||
static glm::vec3 filterTranslation(const glm::vec3& x0, const glm::vec3& x1, const glm::vec3& x2, const glm::vec3& x3,
|
||||
float dt, float accLimit, float snapThreshold) {
|
||||
|
||||
// measure the linear velocities of this step and the previoius step
|
||||
glm::vec3 v1 = (x3 - x1) / (2.0f * dt);
|
||||
glm::vec3 v0 = (x2 - x0) / (2.0f * dt);
|
||||
|
||||
// compute the acceleration
|
||||
const glm::vec3 a = (v1 - v0) / dt;
|
||||
|
||||
// clamp the acceleration if it is over the limit
|
||||
float aLen = glm::length(a);
|
||||
|
||||
// pick limit based on if we are moving faster then our target
|
||||
float distToTarget = glm::length(x3 - x2);
|
||||
if (aLen > accLimit && distToTarget > snapThreshold) {
|
||||
// Solve for a new `v1`, such that `a` does not exceed `aLimit`
|
||||
// This combines two steps:
|
||||
// 1) Computing a limited accelration in the direction of `a`, but with a magnitute of `aLimit`:
|
||||
// `newA = a * (aLimit / aLen)`
|
||||
// 2) Computing new `v1`
|
||||
// `v1 = newA * dt + v0`
|
||||
// We combine the scalars from step 1 and step 2 into a single term to avoid having to do multiple scalar-vec3 multiplies.
|
||||
v1 = a * ((accLimit * dt) / aLen) + v0;
|
||||
|
||||
// apply limited v1 to compute filtered x3
|
||||
return v1 * dt + x2;
|
||||
} else {
|
||||
// did not exceed limit, no filtering necesary
|
||||
return x3;
|
||||
}
|
||||
}
|
||||
|
||||
static glm::quat filterRotation(const glm::quat& q0In, const glm::quat& q1In, const glm::quat& q2In, const glm::quat& q3In,
|
||||
float dt, float accLimit, float snapThreshold) {
|
||||
|
||||
// ensure quaternions have the same polarity
|
||||
glm::quat q0 = q0In;
|
||||
glm::quat q1 = glm::dot(q0In, q1In) < 0.0f ? -q1In : q1In;
|
||||
glm::quat q2 = glm::dot(q1In, q2In) < 0.0f ? -q2In : q2In;
|
||||
glm::quat q3 = glm::dot(q2In, q3In) < 0.0f ? -q3In : q3In;
|
||||
|
||||
// measure the angular velocities of this step and the previous step
|
||||
glm::vec3 w1 = angularVelFromDeltaRot(q3 * glm::inverse(q1), 2.0f * dt);
|
||||
glm::vec3 w0 = angularVelFromDeltaRot(q2 * glm::inverse(q0), 2.0f * dt);
|
||||
|
||||
const glm::vec3 a = (w1 - w0) / dt;
|
||||
float aLen = glm::length(a);
|
||||
|
||||
// clamp the acceleration if it is over the limit
|
||||
float angleToTarget = glm::angle(q3 * glm::inverse(q2));
|
||||
if (aLen > accLimit && angleToTarget > snapThreshold) {
|
||||
// solve for a new w1, such that a does not exceed the accLimit
|
||||
w1 = a * ((accLimit * dt) / aLen) + w0;
|
||||
|
||||
// apply limited w1 to compute filtered q3
|
||||
return deltaRotFromAngularVel(w1, dt) * q2;
|
||||
} else {
|
||||
// did not exceed limit, no filtering necesary
|
||||
return q3;
|
||||
}
|
||||
}
|
||||
|
||||
namespace controller {
|
||||
|
||||
Pose AccelerationLimiterFilter::apply(Pose value) const {
|
||||
|
||||
if (value.isValid()) {
|
||||
|
||||
// to perform filtering in sensor space, we need to compute the transformations.
|
||||
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
||||
const InputCalibrationData calibrationData = userInputMapper->getInputCalibrationData();
|
||||
glm::mat4 sensorToAvatarMat = glm::inverse(calibrationData.avatarMat) * calibrationData.sensorToWorldMat;
|
||||
glm::mat4 avatarToSensorMat = glm::inverse(calibrationData.sensorToWorldMat) * calibrationData.avatarMat;
|
||||
|
||||
// transform pose into sensor space.
|
||||
Pose sensorValue = value.transform(avatarToSensorMat);
|
||||
|
||||
if (_prevValid) {
|
||||
|
||||
const float DELTA_TIME = 0.01111111f;
|
||||
|
||||
glm::vec3 unfilteredTranslation = sensorValue.translation;
|
||||
sensorValue.translation = filterTranslation(_prevPos[0], _prevPos[1], _prevPos[2], sensorValue.translation,
|
||||
DELTA_TIME, _translationAccelerationLimit, _translationSnapThreshold);
|
||||
glm::quat unfilteredRot = sensorValue.rotation;
|
||||
sensorValue.rotation = filterRotation(_prevRot[0], _prevRot[1], _prevRot[2], sensorValue.rotation,
|
||||
DELTA_TIME, _rotationAccelerationLimit, _rotationSnapThreshold);
|
||||
|
||||
// remember previous values.
|
||||
_prevPos[0] = _prevPos[1];
|
||||
_prevPos[1] = _prevPos[2];
|
||||
_prevPos[2] = sensorValue.translation;
|
||||
_prevRot[0] = _prevRot[1];
|
||||
_prevRot[1] = _prevRot[2];
|
||||
_prevRot[2] = sensorValue.rotation;
|
||||
|
||||
_unfilteredPrevPos[0] = _unfilteredPrevPos[1];
|
||||
_unfilteredPrevPos[1] = _unfilteredPrevPos[2];
|
||||
_unfilteredPrevPos[2] = unfilteredTranslation;
|
||||
_unfilteredPrevRot[0] = _unfilteredPrevRot[1];
|
||||
_unfilteredPrevRot[1] = _unfilteredPrevRot[2];
|
||||
_unfilteredPrevRot[2] = unfilteredRot;
|
||||
|
||||
// transform back into avatar space
|
||||
return sensorValue.transform(sensorToAvatarMat);
|
||||
} else {
|
||||
// initialize previous values with the current sample.
|
||||
_prevPos[0] = sensorValue.translation;
|
||||
_prevPos[1] = sensorValue.translation;
|
||||
_prevPos[2] = sensorValue.translation;
|
||||
_prevRot[0] = sensorValue.rotation;
|
||||
_prevRot[1] = sensorValue.rotation;
|
||||
_prevRot[2] = sensorValue.rotation;
|
||||
|
||||
_unfilteredPrevPos[0] = sensorValue.translation;
|
||||
_unfilteredPrevPos[1] = sensorValue.translation;
|
||||
_unfilteredPrevPos[2] = sensorValue.translation;
|
||||
_unfilteredPrevRot[0] = sensorValue.rotation;
|
||||
_unfilteredPrevRot[1] = sensorValue.rotation;
|
||||
_unfilteredPrevRot[2] = sensorValue.rotation;
|
||||
|
||||
_prevValid = true;
|
||||
|
||||
// no previous value to smooth with, so return value unchanged
|
||||
return value;
|
||||
}
|
||||
} else {
|
||||
// mark previous poses as invalid.
|
||||
_prevValid = false;
|
||||
|
||||
// return invalid value unchanged
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
bool AccelerationLimiterFilter::parseParameters(const QJsonValue& parameters) {
|
||||
if (parameters.isObject()) {
|
||||
auto obj = parameters.toObject();
|
||||
if (obj.contains(JSON_ROTATION_ACCELERATION_LIMIT) && obj.contains(JSON_TRANSLATION_ACCELERATION_LIMIT) &&
|
||||
obj.contains(JSON_ROTATION_SNAP_THRESHOLD) && obj.contains(JSON_TRANSLATION_SNAP_THRESHOLD)) {
|
||||
_rotationAccelerationLimit = (float)obj[JSON_ROTATION_ACCELERATION_LIMIT].toDouble();
|
||||
_translationAccelerationLimit = (float)obj[JSON_TRANSLATION_ACCELERATION_LIMIT].toDouble();
|
||||
_rotationSnapThreshold = (float)obj[JSON_ROTATION_SNAP_THRESHOLD].toDouble();
|
||||
_translationSnapThreshold = (float)obj[JSON_TRANSLATION_SNAP_THRESHOLD].toDouble();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// Created by Anthony Thibault 2018/11/09
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_Controllers_Filters_Acceleration_Limiter_h
|
||||
#define hifi_Controllers_Filters_Acceleration_Limiter_h
|
||||
|
||||
#include "../Filter.h"
|
||||
|
||||
namespace controller {
|
||||
|
||||
class AccelerationLimiterFilter : public Filter {
|
||||
REGISTER_FILTER_CLASS(AccelerationLimiterFilter);
|
||||
|
||||
public:
|
||||
AccelerationLimiterFilter() {}
|
||||
|
||||
float apply(float value) const override { return value; }
|
||||
Pose apply(Pose value) const override;
|
||||
bool parseParameters(const QJsonValue& parameters) override;
|
||||
|
||||
private:
|
||||
float _rotationAccelerationLimit { FLT_MAX };
|
||||
float _translationAccelerationLimit { FLT_MAX };
|
||||
float _rotationSnapThreshold { 0.0f };
|
||||
float _translationSnapThreshold { 0.0f };
|
||||
|
||||
mutable glm::vec3 _prevPos[3]; // sensor space
|
||||
mutable glm::quat _prevRot[3]; // sensor space
|
||||
mutable glm::vec3 _unfilteredPrevPos[3]; // sensor space
|
||||
mutable glm::quat _unfilteredPrevRot[3]; // sensor space
|
||||
mutable bool _prevValid { false };
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
|
@ -35,7 +35,7 @@ namespace controller {
|
|||
if (_prevSensorValue.isValid()) {
|
||||
// exponential smoothing filter
|
||||
sensorValue.translation = _translationConstant * sensorValue.getTranslation() + (1.0f - _translationConstant) * _prevSensorValue.getTranslation();
|
||||
sensorValue.rotation = safeMix(sensorValue.getRotation(), _prevSensorValue.getRotation(), _rotationConstant);
|
||||
sensorValue.rotation = safeMix(sensorValue.getRotation(), _prevSensorValue.getRotation(), (1.0f - _rotationConstant));
|
||||
|
||||
// remember previous sensor space value.
|
||||
_prevSensorValue = sensorValue;
|
||||
|
|
|
@ -88,6 +88,7 @@ public:
|
|||
// Move the OpenGL context to the present thread
|
||||
// Extra code because of the widget 'wrapper' context
|
||||
_context = context;
|
||||
_context->doneCurrent();
|
||||
_context->moveToThread(this);
|
||||
}
|
||||
|
||||
|
@ -179,7 +180,9 @@ public:
|
|||
_context->makeCurrent();
|
||||
{
|
||||
PROFILE_RANGE(render, "PluginPresent")
|
||||
gl::globalLock();
|
||||
currentPlugin->present();
|
||||
gl::globalRelease(false);
|
||||
CHECK_GL_ERROR();
|
||||
}
|
||||
_context->doneCurrent();
|
||||
|
|
|
@ -261,6 +261,7 @@ void WebEntityRenderer::doRender(RenderArgs* args) {
|
|||
DependencyManager::get<GeometryCache>()->bindWebBrowserProgram(batch, fadeRatio < OPAQUE_ALPHA_THRESHOLD);
|
||||
DependencyManager::get<GeometryCache>()->renderQuad(batch, topLeft, bottomRight, texMin, texMax, glm::vec4(1.0f, 1.0f, 1.0f, fadeRatio), _geometryId);
|
||||
batch.popProjectionJitter();
|
||||
batch.setResourceTexture(0, nullptr);
|
||||
}
|
||||
|
||||
bool WebEntityRenderer::hasWebSurface() {
|
||||
|
|
|
@ -3207,6 +3207,8 @@ void EntityItemProperties::markAllChanged() {
|
|||
|
||||
_queryAACubeChanged = true;
|
||||
|
||||
_shapeChanged = true;
|
||||
|
||||
_flyingAllowedChanged = true;
|
||||
_ghostingAllowedChanged = true;
|
||||
_filterURLChanged = true;
|
||||
|
@ -3803,6 +3805,16 @@ bool EntityItemProperties::queryAACubeRelatedPropertyChanged() const {
|
|||
return parentRelatedPropertyChanged() || dimensionsChanged();
|
||||
}
|
||||
|
||||
bool EntityItemProperties::grabbingRelatedPropertyChanged() const {
|
||||
const GrabPropertyGroup& grabProperties = getGrab();
|
||||
return grabProperties.triggerableChanged() || grabProperties.grabbableChanged() ||
|
||||
grabProperties.grabFollowsControllerChanged() || grabProperties.grabKinematicChanged() ||
|
||||
grabProperties.equippableChanged() || grabProperties.equippableLeftPositionChanged() ||
|
||||
grabProperties.equippableRightPositionChanged() || grabProperties.equippableLeftRotationChanged() ||
|
||||
grabProperties.equippableRightRotationChanged() || grabProperties.equippableIndicatorURLChanged() ||
|
||||
grabProperties.equippableIndicatorScaleChanged() || grabProperties.equippableIndicatorOffsetChanged();
|
||||
}
|
||||
|
||||
// Checking Certifiable Properties
|
||||
#define ADD_STRING_PROPERTY(n, N) if (!get##N().isEmpty()) json[#n] = get##N()
|
||||
#define ADD_ENUM_PROPERTY(n, N) json[#n] = get##N##AsString()
|
||||
|
|
|
@ -108,6 +108,7 @@ public:
|
|||
bool getScalesWithParent() const;
|
||||
bool parentRelatedPropertyChanged() const;
|
||||
bool queryAACubeRelatedPropertyChanged() const;
|
||||
bool grabbingRelatedPropertyChanged() const;
|
||||
|
||||
AABox getAABox() const;
|
||||
|
||||
|
|
|
@ -16,6 +16,9 @@
|
|||
|
||||
#include <QFutureWatcher>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
|
||||
#include <shared/QtHelpers.h>
|
||||
#include <VariantMapToScriptValue.h>
|
||||
|
@ -37,6 +40,7 @@
|
|||
#include "WebEntityItem.h"
|
||||
#include <EntityScriptClient.h>
|
||||
#include <Profile.h>
|
||||
#include "GrabPropertyGroup.h"
|
||||
|
||||
const QString GRABBABLE_USER_DATA = "{\"grabbableKey\":{\"grabbable\":true}}";
|
||||
const QString NOT_GRABBABLE_USER_DATA = "{\"grabbableKey\":{\"grabbable\":false}}";
|
||||
|
@ -237,6 +241,235 @@ EntityItemProperties convertPropertiesFromScriptSemantics(const EntityItemProper
|
|||
}
|
||||
|
||||
|
||||
void synchronizeSpatialKey(const GrabPropertyGroup& grabProperties, QJsonObject& grabbableKey, bool& userDataChanged) {
|
||||
if (grabProperties.equippableLeftPositionChanged() ||
|
||||
grabProperties.equippableRightPositionChanged() ||
|
||||
grabProperties.equippableRightRotationChanged() ||
|
||||
grabProperties.equippableIndicatorURLChanged() ||
|
||||
grabProperties.equippableIndicatorScaleChanged() ||
|
||||
grabProperties.equippableIndicatorOffsetChanged()) {
|
||||
|
||||
QJsonObject spatialKey = grabbableKey["spatialKey"].toObject();
|
||||
|
||||
if (grabProperties.equippableLeftPositionChanged()) {
|
||||
if (grabProperties.getEquippableLeftPosition() == INITIAL_LEFT_EQUIPPABLE_POSITION) {
|
||||
spatialKey.remove("leftRelativePosition");
|
||||
} else {
|
||||
spatialKey["leftRelativePosition"] =
|
||||
QJsonValue::fromVariant(vec3ToQMap(grabProperties.getEquippableLeftPosition()));
|
||||
}
|
||||
}
|
||||
if (grabProperties.equippableRightPositionChanged()) {
|
||||
if (grabProperties.getEquippableRightPosition() == INITIAL_RIGHT_EQUIPPABLE_POSITION) {
|
||||
spatialKey.remove("rightRelativePosition");
|
||||
} else {
|
||||
spatialKey["rightRelativePosition"] =
|
||||
QJsonValue::fromVariant(vec3ToQMap(grabProperties.getEquippableRightPosition()));
|
||||
}
|
||||
}
|
||||
if (grabProperties.equippableLeftRotationChanged()) {
|
||||
spatialKey["relativeRotation"] =
|
||||
QJsonValue::fromVariant(quatToQMap(grabProperties.getEquippableLeftRotation()));
|
||||
} else if (grabProperties.equippableRightRotationChanged()) {
|
||||
spatialKey["relativeRotation"] =
|
||||
QJsonValue::fromVariant(quatToQMap(grabProperties.getEquippableRightRotation()));
|
||||
}
|
||||
|
||||
grabbableKey["spatialKey"] = spatialKey;
|
||||
userDataChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void synchronizeGrabbableKey(const GrabPropertyGroup& grabProperties, QJsonObject& userData, bool& userDataChanged) {
|
||||
if (grabProperties.triggerableChanged() ||
|
||||
grabProperties.grabbableChanged() ||
|
||||
grabProperties.grabFollowsControllerChanged() ||
|
||||
grabProperties.grabKinematicChanged() ||
|
||||
grabProperties.equippableChanged() ||
|
||||
grabProperties.equippableLeftPositionChanged() ||
|
||||
grabProperties.equippableRightPositionChanged() ||
|
||||
grabProperties.equippableRightRotationChanged()) {
|
||||
|
||||
QJsonObject grabbableKey = userData["grabbableKey"].toObject();
|
||||
|
||||
if (grabProperties.triggerableChanged()) {
|
||||
if (grabProperties.getTriggerable()) {
|
||||
grabbableKey["triggerable"] = true;
|
||||
} else {
|
||||
grabbableKey.remove("triggerable");
|
||||
}
|
||||
}
|
||||
if (grabProperties.grabbableChanged()) {
|
||||
if (grabProperties.getGrabbable()) {
|
||||
grabbableKey.remove("grabbable");
|
||||
} else {
|
||||
grabbableKey["grabbable"] = false;
|
||||
}
|
||||
}
|
||||
if (grabProperties.grabFollowsControllerChanged()) {
|
||||
if (grabProperties.getGrabFollowsController()) {
|
||||
grabbableKey.remove("ignoreIK");
|
||||
} else {
|
||||
grabbableKey["ignoreIK"] = false;
|
||||
}
|
||||
}
|
||||
if (grabProperties.grabKinematicChanged()) {
|
||||
if (grabProperties.getGrabKinematic()) {
|
||||
grabbableKey.remove("kinematic");
|
||||
} else {
|
||||
grabbableKey["kinematic"] = false;
|
||||
}
|
||||
}
|
||||
if (grabProperties.equippableChanged()) {
|
||||
if (grabProperties.getEquippable()) {
|
||||
grabbableKey["equippable"] = true;
|
||||
} else {
|
||||
grabbableKey.remove("equippable");
|
||||
}
|
||||
}
|
||||
|
||||
if (grabbableKey.contains("spatialKey")) {
|
||||
synchronizeSpatialKey(grabProperties, grabbableKey, userDataChanged);
|
||||
}
|
||||
|
||||
userData["grabbableKey"] = grabbableKey;
|
||||
userDataChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
void synchronizeGrabJoints(const GrabPropertyGroup& grabProperties, QJsonObject& joints) {
|
||||
QJsonArray rightHand = joints["RightHand"].toArray();
|
||||
QJsonObject rightHandPosition = rightHand.size() > 0 ? rightHand[0].toObject() : QJsonObject();
|
||||
QJsonObject rightHandRotation = rightHand.size() > 1 ? rightHand[1].toObject() : QJsonObject();
|
||||
QJsonArray leftHand = joints["LeftHand"].toArray();
|
||||
QJsonObject leftHandPosition = leftHand.size() > 0 ? leftHand[0].toObject() : QJsonObject();
|
||||
QJsonObject leftHandRotation = leftHand.size() > 1 ? leftHand[1].toObject() : QJsonObject();
|
||||
|
||||
if (grabProperties.equippableLeftPositionChanged()) {
|
||||
leftHandPosition =
|
||||
QJsonValue::fromVariant(vec3ToQMap(grabProperties.getEquippableLeftPosition())).toObject();
|
||||
}
|
||||
if (grabProperties.equippableRightPositionChanged()) {
|
||||
rightHandPosition =
|
||||
QJsonValue::fromVariant(vec3ToQMap(grabProperties.getEquippableRightPosition())).toObject();
|
||||
}
|
||||
if (grabProperties.equippableLeftRotationChanged()) {
|
||||
leftHandRotation =
|
||||
QJsonValue::fromVariant(quatToQMap(grabProperties.getEquippableLeftRotation())).toObject();
|
||||
}
|
||||
if (grabProperties.equippableRightRotationChanged()) {
|
||||
rightHandRotation =
|
||||
QJsonValue::fromVariant(quatToQMap(grabProperties.getEquippableRightRotation())).toObject();
|
||||
}
|
||||
|
||||
rightHand = QJsonArray();
|
||||
rightHand.append(rightHandPosition);
|
||||
rightHand.append(rightHandRotation);
|
||||
joints["RightHand"] = rightHand;
|
||||
leftHand = QJsonArray();
|
||||
leftHand.append(leftHandPosition);
|
||||
leftHand.append(leftHandRotation);
|
||||
joints["LeftHand"] = leftHand;
|
||||
}
|
||||
|
||||
void synchronizeEquipHotspot(const GrabPropertyGroup& grabProperties, QJsonObject& userData, bool& userDataChanged) {
|
||||
if (grabProperties.equippableLeftPositionChanged() ||
|
||||
grabProperties.equippableRightPositionChanged() ||
|
||||
grabProperties.equippableRightRotationChanged() ||
|
||||
grabProperties.equippableIndicatorURLChanged() ||
|
||||
grabProperties.equippableIndicatorScaleChanged() ||
|
||||
grabProperties.equippableIndicatorOffsetChanged()) {
|
||||
|
||||
QJsonArray equipHotspots = userData["equipHotspots"].toArray();
|
||||
QJsonObject equipHotspot = equipHotspots[0].toObject();
|
||||
QJsonObject joints = equipHotspot["joints"].toObject();
|
||||
|
||||
synchronizeGrabJoints(grabProperties, joints);
|
||||
|
||||
if (grabProperties.equippableIndicatorURLChanged()) {
|
||||
equipHotspot["modelURL"] = grabProperties.getEquippableIndicatorURL();
|
||||
}
|
||||
if (grabProperties.equippableIndicatorScaleChanged()) {
|
||||
QJsonObject scale =
|
||||
QJsonValue::fromVariant(vec3ToQMap(grabProperties.getEquippableIndicatorScale())).toObject();
|
||||
equipHotspot["radius"] = scale;
|
||||
equipHotspot["modelScale"] = scale;
|
||||
|
||||
}
|
||||
if (grabProperties.equippableIndicatorOffsetChanged()) {
|
||||
equipHotspot["position"] =
|
||||
QJsonValue::fromVariant(vec3ToQMap(grabProperties.getEquippableIndicatorOffset())).toObject();
|
||||
}
|
||||
|
||||
equipHotspot["joints"] = joints;
|
||||
equipHotspots = QJsonArray();
|
||||
equipHotspots.append(equipHotspot);
|
||||
userData["equipHotspots"] = equipHotspots;
|
||||
userDataChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
void synchronizeWearable(const GrabPropertyGroup& grabProperties, QJsonObject& userData, bool& userDataChanged) {
|
||||
if (grabProperties.equippableLeftPositionChanged() ||
|
||||
grabProperties.equippableRightPositionChanged() ||
|
||||
grabProperties.equippableRightRotationChanged() ||
|
||||
grabProperties.equippableIndicatorURLChanged() ||
|
||||
grabProperties.equippableIndicatorScaleChanged() ||
|
||||
grabProperties.equippableIndicatorOffsetChanged()) {
|
||||
|
||||
QJsonObject wearable = userData["wearable"].toObject();
|
||||
QJsonObject joints = wearable["joints"].toObject();
|
||||
|
||||
synchronizeGrabJoints(grabProperties, joints);
|
||||
|
||||
wearable["joints"] = joints;
|
||||
userData["wearable"] = wearable;
|
||||
userDataChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
void synchronizeEditedGrabProperties(EntityItemProperties& properties, const QString& previousUserdata) {
|
||||
// After sufficient warning to content creators, we should be able to remove this.
|
||||
|
||||
if (properties.grabbingRelatedPropertyChanged()) {
|
||||
// This edit touches a new-style grab property, so make userData agree...
|
||||
GrabPropertyGroup& grabProperties = properties.getGrab();
|
||||
|
||||
bool userDataChanged { false };
|
||||
|
||||
// if the edit changed userData, use the updated version coming along with the edit. If not, use
|
||||
// what was already in the entity.
|
||||
QByteArray userDataString;
|
||||
if (properties.userDataChanged()) {
|
||||
userDataString = properties.getUserData().toUtf8();
|
||||
} else {
|
||||
userDataString = previousUserdata.toUtf8();;
|
||||
}
|
||||
QJsonObject userData = QJsonDocument::fromJson(userDataString).object();
|
||||
|
||||
if (userData.contains("grabbableKey")) {
|
||||
synchronizeGrabbableKey(grabProperties, userData, userDataChanged);
|
||||
}
|
||||
if (userData.contains("equipHotspots")) {
|
||||
synchronizeEquipHotspot(grabProperties, userData, userDataChanged);
|
||||
}
|
||||
if (userData.contains("wearable")) {
|
||||
synchronizeWearable(grabProperties, userData, userDataChanged);
|
||||
}
|
||||
|
||||
if (userDataChanged) {
|
||||
properties.setUserData(QJsonDocument(userData).toJson());
|
||||
}
|
||||
|
||||
} else if (properties.userDataChanged()) {
|
||||
// This edit touches userData (and doesn't touch a new-style grab property). Check for grabbableKey in the
|
||||
// userdata and make the new-style grab properties agree
|
||||
convertGrabUserDataToProperties(properties);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties, bool clientOnly) {
|
||||
PROFILE_RANGE(script_entities, __FUNCTION__);
|
||||
|
||||
|
@ -257,6 +490,7 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties
|
|||
|
||||
propertiesWithSimID = convertPropertiesFromScriptSemantics(propertiesWithSimID, scalesWithParent);
|
||||
propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged());
|
||||
synchronizeEditedGrabProperties(propertiesWithSimID, QString());
|
||||
|
||||
EntityItemID id;
|
||||
// If we have a local entity tree set, then also update it.
|
||||
|
@ -559,6 +793,7 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
|
|||
simulationOwner = entity->getSimulationOwner();
|
||||
});
|
||||
|
||||
QString previousUserdata;
|
||||
if (entity) {
|
||||
if (properties.hasSimulationRestrictedChanges()) {
|
||||
if (_bidOnSimulationOwnership) {
|
||||
|
@ -597,6 +832,8 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
|
|||
|
||||
// make sure the properties has a type, so that the encode can know which properties to include
|
||||
properties.setType(entity->getType());
|
||||
|
||||
previousUserdata = entity->getUserData();
|
||||
} else if (_bidOnSimulationOwnership) {
|
||||
// bail when simulation participants don't know about entity
|
||||
return QUuid();
|
||||
|
@ -605,6 +842,7 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
|
|||
// How to check for this cheaply?
|
||||
|
||||
properties = convertPropertiesFromScriptSemantics(properties, properties.getScalesWithParent());
|
||||
synchronizeEditedGrabProperties(properties, previousUserdata);
|
||||
properties.setLastEditedBy(sessionID);
|
||||
|
||||
// done reading and modifying properties --> start write
|
||||
|
|
|
@ -2493,6 +2493,118 @@ bool EntityTree::writeToMap(QVariantMap& entityDescription, OctreeElementPointer
|
|||
return true;
|
||||
}
|
||||
|
||||
void convertGrabUserDataToProperties(EntityItemProperties& properties) {
|
||||
GrabPropertyGroup& grabProperties = properties.getGrab();
|
||||
QJsonObject userData = QJsonDocument::fromJson(properties.getUserData().toUtf8()).object();
|
||||
|
||||
QJsonValue grabbableKeyValue = userData["grabbableKey"];
|
||||
if (grabbableKeyValue.isObject()) {
|
||||
QJsonObject grabbableKey = grabbableKeyValue.toObject();
|
||||
|
||||
QJsonValue wantsTrigger = grabbableKey["wantsTrigger"];
|
||||
if (wantsTrigger.isBool()) {
|
||||
grabProperties.setTriggerable(wantsTrigger.toBool());
|
||||
}
|
||||
QJsonValue triggerable = grabbableKey["triggerable"];
|
||||
if (triggerable.isBool()) {
|
||||
grabProperties.setTriggerable(triggerable.toBool());
|
||||
}
|
||||
QJsonValue grabbable = grabbableKey["grabbable"];
|
||||
if (grabbable.isBool()) {
|
||||
grabProperties.setGrabbable(grabbable.toBool());
|
||||
}
|
||||
QJsonValue ignoreIK = grabbableKey["ignoreIK"];
|
||||
if (ignoreIK.isBool()) {
|
||||
grabProperties.setGrabFollowsController(ignoreIK.toBool());
|
||||
}
|
||||
QJsonValue kinematic = grabbableKey["kinematic"];
|
||||
if (kinematic.isBool()) {
|
||||
grabProperties.setGrabKinematic(kinematic.toBool());
|
||||
}
|
||||
QJsonValue equippable = grabbableKey["equippable"];
|
||||
if (equippable.isBool()) {
|
||||
grabProperties.setEquippable(equippable.toBool());
|
||||
}
|
||||
|
||||
if (grabbableKey["spatialKey"].isObject()) {
|
||||
QJsonObject spatialKey = grabbableKey["spatialKey"].toObject();
|
||||
grabProperties.setEquippable(true);
|
||||
if (spatialKey["leftRelativePosition"].isObject()) {
|
||||
grabProperties.setEquippableLeftPosition(qMapToVec3(spatialKey["leftRelativePosition"].toVariant()));
|
||||
}
|
||||
if (spatialKey["rightRelativePosition"].isObject()) {
|
||||
grabProperties.setEquippableRightPosition(qMapToVec3(spatialKey["rightRelativePosition"].toVariant()));
|
||||
}
|
||||
if (spatialKey["relativeRotation"].isObject()) {
|
||||
grabProperties.setEquippableLeftRotation(qMapToQuat(spatialKey["relativeRotation"].toVariant()));
|
||||
grabProperties.setEquippableRightRotation(qMapToQuat(spatialKey["relativeRotation"].toVariant()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QJsonValue wearableValue = userData["wearable"];
|
||||
if (wearableValue.isObject()) {
|
||||
QJsonObject wearable = wearableValue.toObject();
|
||||
QJsonObject joints = wearable["joints"].toObject();
|
||||
if (joints["LeftHand"].isArray()) {
|
||||
QJsonArray leftHand = joints["LeftHand"].toArray();
|
||||
if (leftHand.size() == 2) {
|
||||
grabProperties.setEquippable(true);
|
||||
grabProperties.setEquippableLeftPosition(qMapToVec3(leftHand[0].toVariant()));
|
||||
grabProperties.setEquippableLeftRotation(qMapToQuat(leftHand[1].toVariant()));
|
||||
}
|
||||
}
|
||||
if (joints["RightHand"].isArray()) {
|
||||
QJsonArray rightHand = joints["RightHand"].toArray();
|
||||
if (rightHand.size() == 2) {
|
||||
grabProperties.setEquippable(true);
|
||||
grabProperties.setEquippableRightPosition(qMapToVec3(rightHand[0].toVariant()));
|
||||
grabProperties.setEquippableRightRotation(qMapToQuat(rightHand[1].toVariant()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QJsonValue equipHotspotsValue = userData["equipHotspots"];
|
||||
if (equipHotspotsValue.isArray()) {
|
||||
QJsonArray equipHotspots = equipHotspotsValue.toArray();
|
||||
if (equipHotspots.size() > 0) {
|
||||
// just take the first one
|
||||
QJsonObject firstHotSpot = equipHotspots[0].toObject();
|
||||
QJsonObject joints = firstHotSpot["joints"].toObject();
|
||||
if (joints["LeftHand"].isArray()) {
|
||||
QJsonArray leftHand = joints["LeftHand"].toArray();
|
||||
if (leftHand.size() == 2) {
|
||||
grabProperties.setEquippableLeftPosition(qMapToVec3(leftHand[0].toVariant()));
|
||||
grabProperties.setEquippableLeftRotation(qMapToQuat(leftHand[1].toVariant()));
|
||||
}
|
||||
}
|
||||
if (joints["RightHand"].isArray()) {
|
||||
QJsonArray rightHand = joints["RightHand"].toArray();
|
||||
if (rightHand.size() == 2) {
|
||||
grabProperties.setEquippable(true);
|
||||
grabProperties.setEquippableRightPosition(qMapToVec3(rightHand[0].toVariant()));
|
||||
grabProperties.setEquippableRightRotation(qMapToQuat(rightHand[1].toVariant()));
|
||||
}
|
||||
}
|
||||
QJsonValue indicatorURL = firstHotSpot["modelURL"];
|
||||
if (indicatorURL.isString()) {
|
||||
grabProperties.setEquippableIndicatorURL(indicatorURL.toString());
|
||||
}
|
||||
QJsonValue indicatorScale = firstHotSpot["modelScale"];
|
||||
if (indicatorScale.isDouble()) {
|
||||
grabProperties.setEquippableIndicatorScale(glm::vec3((float)indicatorScale.toDouble()));
|
||||
} else if (indicatorScale.isObject()) {
|
||||
grabProperties.setEquippableIndicatorScale(qMapToVec3(indicatorScale.toVariant()));
|
||||
}
|
||||
QJsonValue indicatorOffset = firstHotSpot["position"];
|
||||
if (indicatorOffset.isObject()) {
|
||||
grabProperties.setEquippableIndicatorOffset(qMapToVec3(indicatorOffset.toVariant()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool EntityTree::readFromMap(QVariantMap& map) {
|
||||
// These are needed to deal with older content (before adding inheritance modes)
|
||||
int contentVersion = map["Version"].toInt();
|
||||
|
@ -2639,104 +2751,7 @@ bool EntityTree::readFromMap(QVariantMap& map) {
|
|||
|
||||
// convert old grab-related userData to new grab properties
|
||||
if (contentVersion < (int)EntityVersion::GrabProperties) {
|
||||
QJsonObject userData = QJsonDocument::fromJson(properties.getUserData().toUtf8()).object();
|
||||
QJsonObject grabbableKey = userData["grabbableKey"].toObject();
|
||||
QJsonValue wantsTrigger = grabbableKey["wantsTrigger"];
|
||||
|
||||
GrabPropertyGroup& grabProperties = properties.getGrab();
|
||||
|
||||
if (wantsTrigger.isBool()) {
|
||||
grabProperties.setTriggerable(wantsTrigger.toBool());
|
||||
}
|
||||
QJsonValue triggerable = grabbableKey["triggerable"];
|
||||
if (triggerable.isBool()) {
|
||||
grabProperties.setTriggerable(triggerable.toBool());
|
||||
}
|
||||
QJsonValue grabbable = grabbableKey["grabbable"];
|
||||
if (grabbable.isBool()) {
|
||||
grabProperties.setGrabbable(grabbable.toBool());
|
||||
}
|
||||
QJsonValue ignoreIK = grabbableKey["ignoreIK"];
|
||||
if (ignoreIK.isBool()) {
|
||||
grabProperties.setGrabFollowsController(ignoreIK.toBool());
|
||||
}
|
||||
QJsonValue kinematic = grabbableKey["kinematic"];
|
||||
if (kinematic.isBool()) {
|
||||
grabProperties.setGrabKinematic(kinematic.toBool());
|
||||
}
|
||||
|
||||
if (grabbableKey["spatialKey"].isObject()) {
|
||||
QJsonObject spatialKey = grabbableKey["spatialKey"].toObject();
|
||||
grabProperties.setEquippable(true);
|
||||
if (spatialKey["leftRelativePosition"].isObject()) {
|
||||
grabProperties.setEquippableLeftPosition(qMapToVec3(spatialKey["leftRelativePosition"].toVariant()));
|
||||
}
|
||||
if (spatialKey["rightRelativePosition"].isObject()) {
|
||||
grabProperties.setEquippableRightPosition(qMapToVec3(spatialKey["rightRelativePosition"].toVariant()));
|
||||
}
|
||||
if (spatialKey["relativeRotation"].isObject()) {
|
||||
grabProperties.setEquippableLeftRotation(qMapToQuat(spatialKey["relativeRotation"].toVariant()));
|
||||
grabProperties.setEquippableRightRotation(qMapToQuat(spatialKey["relativeRotation"].toVariant()));
|
||||
}
|
||||
}
|
||||
|
||||
QJsonObject wearable = userData["wearable"].toObject();
|
||||
QJsonObject joints = wearable["joints"].toObject();
|
||||
if (joints["LeftHand"].isArray()) {
|
||||
QJsonArray leftHand = joints["LeftHand"].toArray();
|
||||
if (leftHand.size() == 2) {
|
||||
grabProperties.setEquippable(true);
|
||||
grabProperties.setEquippableLeftPosition(qMapToVec3(leftHand[0].toVariant()));
|
||||
grabProperties.setEquippableLeftRotation(qMapToQuat(leftHand[1].toVariant()));
|
||||
}
|
||||
}
|
||||
if (joints["RightHand"].isArray()) {
|
||||
QJsonArray rightHand = joints["RightHand"].toArray();
|
||||
if (rightHand.size() == 2) {
|
||||
grabProperties.setEquippable(true);
|
||||
grabProperties.setEquippableRightPosition(qMapToVec3(rightHand[0].toVariant()));
|
||||
grabProperties.setEquippableRightRotation(qMapToQuat(rightHand[1].toVariant()));
|
||||
}
|
||||
}
|
||||
|
||||
if (userData["equipHotspots"].isArray()) {
|
||||
QJsonArray equipHotspots = userData["equipHotspots"].toArray();
|
||||
if (equipHotspots.size() > 0) {
|
||||
// just take the first one
|
||||
QJsonObject firstHotSpot = equipHotspots[0].toObject();
|
||||
QJsonObject joints = firstHotSpot["joints"].toObject();
|
||||
if (joints["LeftHand"].isArray()) {
|
||||
QJsonArray leftHand = joints["LeftHand"].toArray();
|
||||
if (leftHand.size() == 2) {
|
||||
grabProperties.setEquippable(true);
|
||||
grabProperties.setEquippableLeftPosition(qMapToVec3(leftHand[0].toVariant()));
|
||||
grabProperties.setEquippableLeftRotation(qMapToQuat(leftHand[1].toVariant()));
|
||||
}
|
||||
}
|
||||
if (joints["RightHand"].isArray()) {
|
||||
QJsonArray rightHand = joints["RightHand"].toArray();
|
||||
if (rightHand.size() == 2) {
|
||||
grabProperties.setEquippable(true);
|
||||
grabProperties.setEquippableRightPosition(qMapToVec3(rightHand[0].toVariant()));
|
||||
grabProperties.setEquippableRightRotation(qMapToQuat(rightHand[1].toVariant()));
|
||||
}
|
||||
}
|
||||
QJsonValue indicatorURL = firstHotSpot["modelURL"];
|
||||
if (indicatorURL.isString()) {
|
||||
grabProperties.setEquippableIndicatorURL(indicatorURL.toString());
|
||||
}
|
||||
QJsonValue indicatorScale = firstHotSpot["modelScale"];
|
||||
if (indicatorScale.isDouble()) {
|
||||
grabProperties.setEquippableIndicatorScale(glm::vec3((float)indicatorScale.toDouble()));
|
||||
} else if (indicatorScale.isObject()) {
|
||||
grabProperties.setEquippableIndicatorScale(qMapToVec3(indicatorScale.toVariant()));
|
||||
}
|
||||
QJsonValue indicatorOffset = firstHotSpot["position"];
|
||||
if (indicatorOffset.isObject()) {
|
||||
grabProperties.setEquippableIndicatorOffset(qMapToVec3(indicatorOffset.toVariant()));
|
||||
}
|
||||
}
|
||||
}
|
||||
convertGrabUserDataToProperties(properties);
|
||||
}
|
||||
|
||||
// Zero out the spread values that were fixed in version ParticleEntityFix so they behave the same as before
|
||||
|
|
|
@ -424,4 +424,6 @@ private:
|
|||
std::map<QString, QString> _namedPaths;
|
||||
};
|
||||
|
||||
void convertGrabUserDataToProperties(EntityItemProperties& properties);
|
||||
|
||||
#endif // hifi_EntityTree_h
|
||||
|
|
|
@ -51,6 +51,9 @@ void GrabPropertyGroup::copyFromScriptValue(const QScriptValue& object, bool& _d
|
|||
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, equippableLeftRotation, quat, setEquippableLeftRotation);
|
||||
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, equippableRightPosition, vec3, setEquippableRightPosition);
|
||||
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, equippableRightRotation, quat, setEquippableRightRotation);
|
||||
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, equippableIndicatorURL, QString, setEquippableIndicatorURL);
|
||||
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, equippableIndicatorScale, vec3, setEquippableIndicatorScale);
|
||||
COPY_GROUP_PROPERTY_FROM_QSCRIPTVALUE(grab, equippableIndicatorOffset, vec3, setEquippableIndicatorOffset);
|
||||
}
|
||||
|
||||
void GrabPropertyGroup::merge(const GrabPropertyGroup& other) {
|
||||
|
@ -63,6 +66,9 @@ void GrabPropertyGroup::merge(const GrabPropertyGroup& other) {
|
|||
COPY_PROPERTY_IF_CHANGED(equippableLeftRotation);
|
||||
COPY_PROPERTY_IF_CHANGED(equippableRightPosition);
|
||||
COPY_PROPERTY_IF_CHANGED(equippableRightRotation);
|
||||
COPY_PROPERTY_IF_CHANGED(equippableIndicatorURL);
|
||||
COPY_PROPERTY_IF_CHANGED(equippableIndicatorScale);
|
||||
COPY_PROPERTY_IF_CHANGED(equippableIndicatorOffset);
|
||||
}
|
||||
|
||||
void GrabPropertyGroup::debugDump() const {
|
||||
|
@ -77,6 +83,9 @@ void GrabPropertyGroup::debugDump() const {
|
|||
qCDebug(entities) << " _equippableLeftRotation:" << _equippableLeftRotation;
|
||||
qCDebug(entities) << " _equippableRightPosition:" << _equippableRightPosition;
|
||||
qCDebug(entities) << " _equippableRightRotation:" << _equippableRightRotation;
|
||||
qCDebug(entities) << " _equippableIndicatorURL:" << _equippableIndicatorURL;
|
||||
qCDebug(entities) << " _equippableIndicatorScale:" << _equippableIndicatorScale;
|
||||
qCDebug(entities) << " _equippableIndicatorOffset:" << _equippableIndicatorOffset;
|
||||
}
|
||||
|
||||
void GrabPropertyGroup::listChangedProperties(QList<QString>& out) {
|
||||
|
@ -107,6 +116,15 @@ void GrabPropertyGroup::listChangedProperties(QList<QString>& out) {
|
|||
if (equippableRightRotationChanged()) {
|
||||
out << "grab-equippableRightRotation";
|
||||
}
|
||||
if (equippableIndicatorURLChanged()) {
|
||||
out << "grab-equippableIndicatorURL";
|
||||
}
|
||||
if (equippableIndicatorScaleChanged()) {
|
||||
out << "grab-equippableIndicatorScale";
|
||||
}
|
||||
if (equippableIndicatorOffsetChanged()) {
|
||||
out << "grab-equippableIndicatorOffset";
|
||||
}
|
||||
}
|
||||
|
||||
bool GrabPropertyGroup::appendToEditPacket(OctreePacketData* packetData,
|
||||
|
@ -184,6 +202,9 @@ void GrabPropertyGroup::markAllChanged() {
|
|||
_equippableLeftRotationChanged = true;
|
||||
_equippableRightPositionChanged = true;
|
||||
_equippableRightRotationChanged = true;
|
||||
_equippableIndicatorURLChanged = true;
|
||||
_equippableIndicatorScaleChanged = true;
|
||||
_equippableIndicatorOffsetChanged = true;
|
||||
}
|
||||
|
||||
EntityPropertyFlags GrabPropertyGroup::getChangedProperties() const {
|
||||
|
@ -215,6 +236,9 @@ void GrabPropertyGroup::getProperties(EntityItemProperties& properties) const {
|
|||
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Grab, EquippableLeftRotation, getEquippableLeftRotation);
|
||||
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Grab, EquippableRightPosition, getEquippableRightPosition);
|
||||
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Grab, EquippableRightRotation, getEquippableRightRotation);
|
||||
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Grab, EquippableIndicatorURL, getEquippableIndicatorURL);
|
||||
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Grab, EquippableIndicatorScale, getEquippableIndicatorScale);
|
||||
COPY_ENTITY_GROUP_PROPERTY_TO_PROPERTIES(Grab, EquippableIndicatorOffset, getEquippableIndicatorOffset);
|
||||
}
|
||||
|
||||
bool GrabPropertyGroup::setProperties(const EntityItemProperties& properties) {
|
||||
|
@ -231,6 +255,12 @@ bool GrabPropertyGroup::setProperties(const EntityItemProperties& properties) {
|
|||
setEquippableRightPosition);
|
||||
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Grab, EquippableRightRotation, equippableRightRotation,
|
||||
setEquippableRightRotation);
|
||||
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Grab, EquippableIndicatorURL, equippableIndicatorURL,
|
||||
setEquippableIndicatorURL);
|
||||
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Grab, EquippableIndicatorScale, equippableIndicatorScale,
|
||||
setEquippableIndicatorScale);
|
||||
SET_ENTITY_GROUP_PROPERTY_FROM_PROPERTIES(Grab, EquippableIndicatorOffset, equippableIndicatorOffset,
|
||||
setEquippableIndicatorOffset);
|
||||
|
||||
return somethingChanged;
|
||||
}
|
||||
|
|
|
@ -90,10 +90,10 @@ bool operator==(const Properties& a, const Properties& b) {
|
|||
return
|
||||
(a.color == b.color) &&
|
||||
(a.alpha == b.alpha) &&
|
||||
(a.radiusStart == b.radiusStart) &&
|
||||
(a.radius == b.radius) &&
|
||||
(a.spin == b.spin) &&
|
||||
(a.rotateWithEntity == b.rotateWithEntity) &&
|
||||
(a.radiusStart == b.radiusStart) &&
|
||||
(a.lifespan == b.lifespan) &&
|
||||
(a.maxParticles == b.maxParticles) &&
|
||||
(a.emission == b.emission) &&
|
||||
|
@ -117,18 +117,7 @@ bool Properties::valid() const {
|
|||
(alpha.range.start == glm::clamp(alpha.range.start, MINIMUM_ALPHA, MAXIMUM_ALPHA)) &&
|
||||
(alpha.range.finish == glm::clamp(alpha.range.finish, MINIMUM_ALPHA, MAXIMUM_ALPHA)) &&
|
||||
(alpha.gradient.spread == glm::clamp(alpha.gradient.spread, MINIMUM_ALPHA, MAXIMUM_ALPHA)) &&
|
||||
(lifespan == glm::clamp(lifespan, MINIMUM_LIFESPAN, MAXIMUM_LIFESPAN)) &&
|
||||
(emission.rate == glm::clamp(emission.rate, MINIMUM_EMIT_RATE, MAXIMUM_EMIT_RATE)) &&
|
||||
(emission.speed.target == glm::clamp(emission.speed.target, MINIMUM_EMIT_SPEED, MAXIMUM_EMIT_SPEED)) &&
|
||||
(emission.speed.spread == glm::clamp(emission.speed.spread, MINIMUM_EMIT_SPEED, MAXIMUM_EMIT_SPEED)) &&
|
||||
(emission.dimensions == glm::clamp(emission.dimensions, vec3(MINIMUM_EMIT_DIMENSION), vec3(MAXIMUM_EMIT_DIMENSION))) &&
|
||||
(radiusStart == glm::clamp(radiusStart, MINIMUM_EMIT_RADIUS_START, MAXIMUM_EMIT_RADIUS_START)) &&
|
||||
(polar.start == glm::clamp(polar.start, MINIMUM_POLAR, MAXIMUM_POLAR)) &&
|
||||
(polar.finish == glm::clamp(polar.finish, MINIMUM_POLAR, MAXIMUM_POLAR)) &&
|
||||
(azimuth.start == glm::clamp(azimuth.start, MINIMUM_AZIMUTH, MAXIMUM_AZIMUTH)) &&
|
||||
(azimuth.finish == glm::clamp(azimuth.finish, MINIMUM_AZIMUTH, MAXIMUM_AZIMUTH)) &&
|
||||
(emission.acceleration.target == glm::clamp(emission.acceleration.target, vec3(MINIMUM_EMIT_ACCELERATION), vec3(MAXIMUM_EMIT_ACCELERATION))) &&
|
||||
(emission.acceleration.spread == glm::clamp(emission.acceleration.spread, vec3(MINIMUM_ACCELERATION_SPREAD), vec3(MAXIMUM_ACCELERATION_SPREAD))) &&
|
||||
(radius.gradient.target == glm::clamp(radius.gradient.target, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)) &&
|
||||
(radius.range.start == glm::clamp(radius.range.start, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)) &&
|
||||
(radius.range.finish == glm::clamp(radius.range.finish, MINIMUM_PARTICLE_RADIUS, MAXIMUM_PARTICLE_RADIUS)) &&
|
||||
|
@ -136,7 +125,19 @@ bool Properties::valid() const {
|
|||
(spin.gradient.target == glm::clamp(spin.gradient.target, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)) &&
|
||||
(spin.range.start == glm::clamp(spin.range.start, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)) &&
|
||||
(spin.range.finish == glm::clamp(spin.range.finish, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)) &&
|
||||
(spin.gradient.spread == glm::clamp(spin.gradient.spread, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN));
|
||||
(spin.gradient.spread == glm::clamp(spin.gradient.spread, MINIMUM_PARTICLE_SPIN, MAXIMUM_PARTICLE_SPIN)) &&
|
||||
(lifespan == glm::clamp(lifespan, MINIMUM_LIFESPAN, MAXIMUM_LIFESPAN)) &&
|
||||
(maxParticles == glm::clamp(maxParticles, MINIMUM_MAX_PARTICLES, MAXIMUM_MAX_PARTICLES)) &&
|
||||
(emission.rate == glm::clamp(emission.rate, MINIMUM_EMIT_RATE, MAXIMUM_EMIT_RATE)) &&
|
||||
(emission.speed.target == glm::clamp(emission.speed.target, MINIMUM_EMIT_SPEED, MAXIMUM_EMIT_SPEED)) &&
|
||||
(emission.speed.spread == glm::clamp(emission.speed.spread, MINIMUM_EMIT_SPEED, MAXIMUM_EMIT_SPEED)) &&
|
||||
(emission.acceleration.target == glm::clamp(emission.acceleration.target, vec3(MINIMUM_EMIT_ACCELERATION), vec3(MAXIMUM_EMIT_ACCELERATION))) &&
|
||||
(emission.acceleration.spread == glm::clamp(emission.acceleration.spread, vec3(MINIMUM_ACCELERATION_SPREAD), vec3(MAXIMUM_ACCELERATION_SPREAD)) &&
|
||||
(emission.dimensions == glm::clamp(emission.dimensions, vec3(MINIMUM_EMIT_DIMENSION), vec3(MAXIMUM_EMIT_DIMENSION))) &&
|
||||
(polar.start == glm::clamp(polar.start, MINIMUM_POLAR, MAXIMUM_POLAR)) &&
|
||||
(polar.finish == glm::clamp(polar.finish, MINIMUM_POLAR, MAXIMUM_POLAR)) &&
|
||||
(azimuth.start == glm::clamp(azimuth.start, MINIMUM_AZIMUTH, MAXIMUM_AZIMUTH)) &&
|
||||
(azimuth.finish == glm::clamp(azimuth.finish, MINIMUM_AZIMUTH, MAXIMUM_AZIMUTH)));
|
||||
}
|
||||
|
||||
bool Properties::emitting() const {
|
||||
|
|
|
@ -177,9 +177,10 @@ namespace particle {
|
|||
Properties& operator =(const Properties& other) {
|
||||
color = other.color;
|
||||
alpha = other.alpha;
|
||||
radiusStart = other.radiusStart;
|
||||
radius = other.radius;
|
||||
spin = other.spin;
|
||||
rotateWithEntity = other.rotateWithEntity;
|
||||
radius = other.radius;
|
||||
lifespan = other.lifespan;
|
||||
maxParticles = other.maxParticles;
|
||||
emission = other.emission;
|
||||
|
|
|
@ -25,25 +25,24 @@
|
|||
#include "GLLogging.h"
|
||||
#include "Config.h"
|
||||
#include "GLHelpers.h"
|
||||
#include "QOpenGLContextWrapper.h"
|
||||
|
||||
using namespace gl;
|
||||
|
||||
|
||||
bool Context::enableDebugLogger() {
|
||||
#if defined(Q_OS_MAC)
|
||||
// OSX does not support GL_KHR_debug or GL_ARB_debug_output
|
||||
return false;
|
||||
#else
|
||||
#if defined(DEBUG) || defined(USE_GLES)
|
||||
static bool enableDebugLogger = true;
|
||||
#else
|
||||
static const QString DEBUG_FLAG("HIFI_DEBUG_OPENGL");
|
||||
static bool enableDebugLogger = QProcessEnvironment::systemEnvironment().contains(DEBUG_FLAG);
|
||||
#endif
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [&] {
|
||||
// If the previous run crashed, force GL debug logging on
|
||||
if (qApp->property(hifi::properties::CRASHED).toBool()) {
|
||||
enableDebugLogger = true;
|
||||
}
|
||||
});
|
||||
return enableDebugLogger;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
@ -68,8 +67,6 @@ void Context::updateSwapchainMemoryUsage(size_t prevSize, size_t newSize) {
|
|||
}
|
||||
|
||||
|
||||
Context* Context::PRIMARY = nullptr;
|
||||
|
||||
Context::Context() {}
|
||||
|
||||
Context::Context(QWindow* window) {
|
||||
|
@ -97,9 +94,6 @@ void Context::release() {
|
|||
_context = nullptr;
|
||||
#endif
|
||||
_window = nullptr;
|
||||
if (PRIMARY == this) {
|
||||
PRIMARY = nullptr;
|
||||
}
|
||||
updateSwapchainMemoryCounter();
|
||||
}
|
||||
|
||||
|
@ -235,16 +229,9 @@ typedef HGLRC(APIENTRYP PFNWGLCREATECONTEXTATTRIBSARBPROC)(HDC hDC, HGLRC hShare
|
|||
GLAPI PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB;
|
||||
GLAPI PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB;
|
||||
|
||||
Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context();
|
||||
|
||||
void Context::create() {
|
||||
if (!PRIMARY) {
|
||||
PRIMARY = static_cast<Context*>(qApp->property(hifi::properties::gl::PRIMARY_CONTEXT).value<void*>());
|
||||
}
|
||||
|
||||
if (PRIMARY) {
|
||||
_version = PRIMARY->_version;
|
||||
}
|
||||
|
||||
void Context::create(QOpenGLContext* shareContext) {
|
||||
assert(0 != _hwnd);
|
||||
assert(0 == _hdc);
|
||||
auto hwnd = _hwnd;
|
||||
|
@ -338,7 +325,10 @@ void Context::create() {
|
|||
contextAttribs.push_back(0);
|
||||
}
|
||||
contextAttribs.push_back(0);
|
||||
auto shareHglrc = PRIMARY ? PRIMARY->_hglrc : 0;
|
||||
if (!shareContext) {
|
||||
shareContext = qt_gl_global_share_context();
|
||||
}
|
||||
HGLRC shareHglrc = (HGLRC)QOpenGLContextWrapper::nativeContext(shareContext);
|
||||
_hglrc = wglCreateContextAttribsARB(_hdc, shareHglrc, &contextAttribs[0]);
|
||||
}
|
||||
|
||||
|
@ -346,11 +336,6 @@ void Context::create() {
|
|||
throw std::runtime_error("Could not create GL context");
|
||||
}
|
||||
|
||||
if (!PRIMARY) {
|
||||
PRIMARY = this;
|
||||
qApp->setProperty(hifi::properties::gl::PRIMARY_CONTEXT, QVariant::fromValue((void*)PRIMARY));
|
||||
}
|
||||
|
||||
if (!makeCurrent()) {
|
||||
throw std::runtime_error("Could not make context current");
|
||||
}
|
||||
|
@ -363,12 +348,11 @@ void Context::create() {
|
|||
|
||||
#endif
|
||||
|
||||
|
||||
OffscreenContext::~OffscreenContext() {
|
||||
_window->deleteLater();
|
||||
}
|
||||
|
||||
void OffscreenContext::create() {
|
||||
void OffscreenContext::create(QOpenGLContext* shareContext) {
|
||||
if (!_window) {
|
||||
_window = new QWindow();
|
||||
_window->setFlags(Qt::MSWindowsOwnDC);
|
||||
|
@ -379,5 +363,5 @@ void OffscreenContext::create() {
|
|||
qCDebug(glLogging) << "New Offscreen GLContext, window size = " << windowSize.width() << " , " << windowSize.height();
|
||||
QGuiApplication::processEvents();
|
||||
}
|
||||
Parent::create();
|
||||
Parent::create(shareContext);
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ class QSurface;
|
|||
class QWindow;
|
||||
class QOpenGLContext;
|
||||
class QThread;
|
||||
class QOpenGLDebugMessage;
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
#define GL_CUSTOM_CONTEXT
|
||||
|
@ -30,7 +31,6 @@ namespace gl {
|
|||
class Context {
|
||||
protected:
|
||||
QWindow* _window { nullptr };
|
||||
static Context* PRIMARY;
|
||||
static void destroyContext(QOpenGLContext* context);
|
||||
#if defined(GL_CUSTOM_CONTEXT)
|
||||
uint32_t _version { 0x0401 };
|
||||
|
@ -48,6 +48,9 @@ namespace gl {
|
|||
|
||||
public:
|
||||
static bool enableDebugLogger();
|
||||
static void debugMessageHandler(const QOpenGLDebugMessage &debugMessage);
|
||||
static void setupDebugLogging(QOpenGLContext* context);
|
||||
|
||||
Context();
|
||||
Context(QWindow* window);
|
||||
void release();
|
||||
|
@ -59,14 +62,14 @@ namespace gl {
|
|||
static void makeCurrent(QOpenGLContext* context, QSurface* surface);
|
||||
void swapBuffers();
|
||||
void doneCurrent();
|
||||
virtual void create();
|
||||
virtual void create(QOpenGLContext* shareContext = nullptr);
|
||||
QOpenGLContext* qglContext();
|
||||
void moveToThread(QThread* thread);
|
||||
|
||||
static size_t evalSurfaceMemoryUsage(uint32_t width, uint32_t height, uint32_t pixelSize);
|
||||
static size_t getSwapchainMemoryUsage();
|
||||
static void updateSwapchainMemoryUsage(size_t prevSize, size_t newSize);
|
||||
|
||||
|
||||
private:
|
||||
static std::atomic<size_t> _totalSwapchainMemoryUsage;
|
||||
|
||||
|
@ -81,7 +84,7 @@ namespace gl {
|
|||
QWindow* _window { nullptr };
|
||||
public:
|
||||
virtual ~OffscreenContext();
|
||||
void create() override;
|
||||
void create(QOpenGLContext* shareContext = nullptr) override;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -17,6 +17,8 @@
|
|||
#include <QtPlatformHeaders/QWGLNativeContext>
|
||||
#endif
|
||||
|
||||
#include <QtGui/QOpenGLDebugMessage>
|
||||
|
||||
#include "GLHelpers.h"
|
||||
|
||||
using namespace gl;
|
||||
|
@ -47,6 +49,32 @@ void Context::moveToThread(QThread* thread) {
|
|||
qglContext()->moveToThread(thread);
|
||||
}
|
||||
|
||||
void Context::debugMessageHandler(const QOpenGLDebugMessage& debugMessage) {
|
||||
auto severity = debugMessage.severity();
|
||||
switch (severity) {
|
||||
case QOpenGLDebugMessage::NotificationSeverity:
|
||||
case QOpenGLDebugMessage::LowSeverity:
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
qDebug(glLogging) << debugMessage;
|
||||
return;
|
||||
}
|
||||
|
||||
void Context::setupDebugLogging(QOpenGLContext *context) {
|
||||
QOpenGLDebugLogger *logger = new QOpenGLDebugLogger(context);
|
||||
QObject::connect(logger, &QOpenGLDebugLogger::messageLogged, nullptr, [](const QOpenGLDebugMessage& message){
|
||||
Context::debugMessageHandler(message);
|
||||
});
|
||||
if (logger->initialize()) {
|
||||
logger->enableMessages();
|
||||
logger->startLogging(QOpenGLDebugLogger::SynchronousLogging);
|
||||
} else {
|
||||
qCWarning(glLogging) << "OpenGL context does not support debugging";
|
||||
}
|
||||
}
|
||||
|
||||
#ifndef GL_CUSTOM_CONTEXT
|
||||
bool Context::makeCurrent() {
|
||||
updateSwapchainMemoryCounter();
|
||||
|
@ -65,21 +93,29 @@ void Context::doneCurrent() {
|
|||
}
|
||||
}
|
||||
|
||||
Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context();
|
||||
const QSurfaceFormat& getDefaultOpenGLSurfaceFormat();
|
||||
|
||||
|
||||
void Context::create() {
|
||||
void Context::create(QOpenGLContext* shareContext) {
|
||||
_context = new QOpenGLContext();
|
||||
if (PRIMARY) {
|
||||
_context->setShareContext(PRIMARY->qglContext());
|
||||
} else {
|
||||
PRIMARY = this;
|
||||
_context->setFormat(_window->format());
|
||||
if (!shareContext) {
|
||||
shareContext = qt_gl_global_share_context();
|
||||
}
|
||||
_context->setFormat(getDefaultOpenGLSurfaceFormat());
|
||||
_context->create();
|
||||
|
||||
_context->setShareContext(shareContext);
|
||||
_context->create();
|
||||
_swapchainPixelSize = evalGLFormatSwapchainPixelSize(_context->format());
|
||||
updateSwapchainMemoryCounter();
|
||||
|
||||
if (!makeCurrent()) {
|
||||
throw std::runtime_error("Could not make context current");
|
||||
}
|
||||
if (enableDebugLogger()) {
|
||||
setupDebugLogging(_context);
|
||||
}
|
||||
doneCurrent();
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
#include <QtGui/QOpenGLContext>
|
||||
#include <QtGui/QOpenGLDebugLogger>
|
||||
|
||||
#include "Context.h"
|
||||
|
||||
size_t evalGLFormatSwapchainPixelSize(const QSurfaceFormat& format) {
|
||||
size_t pixelSize = format.redBufferSize() + format.greenBufferSize() + format.blueBufferSize() + format.alphaBufferSize();
|
||||
// We don't apply the length of the swap chain into this pixelSize since it is not vsible for the Process (on windows).
|
||||
|
@ -34,14 +36,54 @@ bool gl::disableGl45() {
|
|||
#endif
|
||||
}
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
#define SERIALIZE_GL_RENDERING
|
||||
#endif
|
||||
|
||||
#ifdef SERIALIZE_GL_RENDERING
|
||||
|
||||
// This terrible terrible hack brought to you by the complete lack of reasonable
|
||||
// OpenGL debugging tools on OSX. Without this serialization code, the UI textures
|
||||
// frequently become 'glitchy' and get composited onto the main scene in what looks
|
||||
// like a partially rendered state.
|
||||
// This looks very much like either state bleeding across the contexts, or bad
|
||||
// synchronization for the shared OpenGL textures. However, previous attempts to resolve
|
||||
// it, even with gratuitous use of glFinish hasn't improved the situation
|
||||
|
||||
static std::mutex _globalOpenGLLock;
|
||||
|
||||
void gl::globalLock() {
|
||||
_globalOpenGLLock.lock();
|
||||
}
|
||||
|
||||
void gl::globalRelease(bool finish) {
|
||||
if (finish) {
|
||||
glFinish();
|
||||
}
|
||||
_globalOpenGLLock.unlock();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void gl::globalLock() {}
|
||||
void gl::globalRelease(bool finish) {}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
void gl::getTargetVersion(int& major, int& minor) {
|
||||
#if defined(USE_GLES)
|
||||
major = 3;
|
||||
minor = 2;
|
||||
#else
|
||||
#if defined(Q_OS_MAC)
|
||||
major = 4;
|
||||
minor = 1;
|
||||
#else
|
||||
major = 4;
|
||||
minor = disableGl45() ? 1 : 5;
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() {
|
||||
|
@ -57,6 +99,9 @@ const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() {
|
|||
#else
|
||||
format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile);
|
||||
#endif
|
||||
if (gl::Context::enableDebugLogger()) {
|
||||
format.setOption(QSurfaceFormat::DebugContext);
|
||||
}
|
||||
// Qt Quick may need a depth and stencil buffer. Always make sure these are available.
|
||||
format.setDepthBufferSize(DEFAULT_GL_DEPTH_BUFFER_BITS);
|
||||
format.setStencilBufferSize(DEFAULT_GL_STENCIL_BUFFER_BITS);
|
||||
|
@ -64,7 +109,6 @@ const QSurfaceFormat& getDefaultOpenGLSurfaceFormat() {
|
|||
::gl::getTargetVersion(major, minor);
|
||||
format.setMajorVersion(major);
|
||||
format.setMinorVersion(minor);
|
||||
QSurfaceFormat::setDefaultFormat(format);
|
||||
});
|
||||
return format;
|
||||
}
|
||||
|
|
|
@ -35,6 +35,9 @@ int glVersionToInteger(QString glVersion);
|
|||
bool isRenderThread();
|
||||
|
||||
namespace gl {
|
||||
void globalLock();
|
||||
void globalRelease(bool finish = true);
|
||||
|
||||
void withSavedContext(const std::function<void()>& f);
|
||||
|
||||
bool checkGLError(const char* name);
|
||||
|
|
|
@ -63,10 +63,10 @@ int GLWidget::getDeviceHeight() const {
|
|||
return height() * (windowHandle() ? (float)windowHandle()->devicePixelRatio() : 1.0f);
|
||||
}
|
||||
|
||||
void GLWidget::createContext() {
|
||||
void GLWidget::createContext(QOpenGLContext* shareContext) {
|
||||
_context = new gl::Context();
|
||||
_context->setWindow(windowHandle());
|
||||
_context->create();
|
||||
_context->create(shareContext);
|
||||
_context->makeCurrent();
|
||||
_context->clear();
|
||||
_context->doneCurrent();
|
||||
|
|
|
@ -29,7 +29,7 @@ public:
|
|||
int getDeviceHeight() const;
|
||||
QSize getDeviceSize() const { return QSize(getDeviceWidth(), getDeviceHeight()); }
|
||||
QPaintEngine* paintEngine() const override;
|
||||
void createContext();
|
||||
void createContext(QOpenGLContext* shareContext = nullptr);
|
||||
bool makeCurrent();
|
||||
void doneCurrent();
|
||||
void swapBuffers();
|
||||
|
|
|
@ -22,7 +22,7 @@ void GLWindow::createContext(QOpenGLContext* shareContext) {
|
|||
void GLWindow::createContext(const QSurfaceFormat& format, QOpenGLContext* shareContext) {
|
||||
_context = new gl::Context();
|
||||
_context->setWindow(this);
|
||||
_context->create();
|
||||
_context->create(shareContext);
|
||||
_context->makeCurrent();
|
||||
_context->clear();
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ OffscreenGLCanvas::OffscreenGLCanvas() :
|
|||
_context(new QOpenGLContext),
|
||||
_offscreenSurface(new QOffscreenSurface)
|
||||
{
|
||||
setFormat(getDefaultOpenGLSurfaceFormat());
|
||||
}
|
||||
|
||||
OffscreenGLCanvas::~OffscreenGLCanvas() {
|
||||
|
@ -49,12 +50,15 @@ OffscreenGLCanvas::~OffscreenGLCanvas() {
|
|||
|
||||
}
|
||||
|
||||
void OffscreenGLCanvas::setFormat(const QSurfaceFormat& format) {
|
||||
_context->setFormat(format);
|
||||
}
|
||||
|
||||
bool OffscreenGLCanvas::create(QOpenGLContext* sharedContext) {
|
||||
if (nullptr != sharedContext) {
|
||||
sharedContext->doneCurrent();
|
||||
_context->setShareContext(sharedContext);
|
||||
}
|
||||
_context->setFormat(getDefaultOpenGLSurfaceFormat());
|
||||
if (!_context->create()) {
|
||||
qFatal("Failed to create OffscreenGLCanvas context");
|
||||
}
|
||||
|
@ -69,38 +73,16 @@ bool OffscreenGLCanvas::create(QOpenGLContext* sharedContext) {
|
|||
if (!_context->makeCurrent(_offscreenSurface)) {
|
||||
qFatal("Unable to make offscreen surface current");
|
||||
}
|
||||
_context->doneCurrent();
|
||||
#else
|
||||
if (!_offscreenSurface->isValid()) {
|
||||
qFatal("Offscreen surface is invalid");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (gl::Context::enableDebugLogger()) {
|
||||
_context->makeCurrent(_offscreenSurface);
|
||||
QOpenGLDebugLogger *logger = new QOpenGLDebugLogger(this);
|
||||
connect(logger, &QOpenGLDebugLogger::messageLogged, this, &OffscreenGLCanvas::onMessageLogged);
|
||||
logger->initialize();
|
||||
logger->enableMessages();
|
||||
logger->startLogging(QOpenGLDebugLogger::SynchronousLogging);
|
||||
_context->doneCurrent();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OffscreenGLCanvas::onMessageLogged(const QOpenGLDebugMessage& debugMessage) {
|
||||
auto severity = debugMessage.severity();
|
||||
switch (severity) {
|
||||
case QOpenGLDebugMessage::NotificationSeverity:
|
||||
case QOpenGLDebugMessage::LowSeverity:
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
qDebug(glLogging) << debugMessage;
|
||||
return;
|
||||
}
|
||||
|
||||
bool OffscreenGLCanvas::makeCurrent() {
|
||||
bool result = _context->makeCurrent(_offscreenSurface);
|
||||
if (glGetString) {
|
||||
|
|
|
@ -18,11 +18,13 @@
|
|||
class QOpenGLContext;
|
||||
class QOffscreenSurface;
|
||||
class QOpenGLDebugMessage;
|
||||
class QSurfaceFormat;
|
||||
|
||||
class OffscreenGLCanvas : public QObject {
|
||||
public:
|
||||
OffscreenGLCanvas();
|
||||
~OffscreenGLCanvas();
|
||||
void setFormat(const QSurfaceFormat& format);
|
||||
bool create(QOpenGLContext* sharedContext = nullptr);
|
||||
bool makeCurrent();
|
||||
void doneCurrent();
|
||||
|
@ -35,9 +37,6 @@ public:
|
|||
void setThreadContext();
|
||||
static bool restoreThreadContext();
|
||||
|
||||
private slots:
|
||||
void onMessageLogged(const QOpenGLDebugMessage &debugMessage);
|
||||
|
||||
protected:
|
||||
void clearThreadContext();
|
||||
|
||||
|
|
|
@ -13,6 +13,10 @@
|
|||
|
||||
#include <QOpenGLContext>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <QtPlatformHeaders/QWGLNativeContext>
|
||||
#endif
|
||||
|
||||
uint32_t QOpenGLContextWrapper::currentContextVersion() {
|
||||
QOpenGLContext* context = QOpenGLContext::currentContext();
|
||||
if (!context) {
|
||||
|
@ -45,6 +49,19 @@ void QOpenGLContextWrapper::setFormat(const QSurfaceFormat& format) {
|
|||
_context->setFormat(format);
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
void* QOpenGLContextWrapper::nativeContext(QOpenGLContext* context) {
|
||||
HGLRC result = 0;
|
||||
if (context != nullptr) {
|
||||
auto nativeHandle = context->nativeHandle();
|
||||
if (nativeHandle.canConvert<QWGLNativeContext>()) {
|
||||
result = nativeHandle.value<QWGLNativeContext>().context();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool QOpenGLContextWrapper::create() {
|
||||
return _context->create();
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#define hifi_QOpenGLContextWrapper_h
|
||||
|
||||
#include <stdint.h>
|
||||
#include <QtGlobal>
|
||||
|
||||
class QOpenGLContext;
|
||||
class QSurface;
|
||||
|
@ -21,6 +22,10 @@ class QThread;
|
|||
|
||||
class QOpenGLContextWrapper {
|
||||
public:
|
||||
#ifdef Q_OS_WIN
|
||||
static void* nativeContext(QOpenGLContext* context);
|
||||
#endif
|
||||
|
||||
QOpenGLContextWrapper();
|
||||
QOpenGLContextWrapper(QOpenGLContext* context);
|
||||
virtual ~QOpenGLContextWrapper();
|
||||
|
|
|
@ -708,37 +708,37 @@ void GLBackend::do_glColor4f(const Batch& batch, size_t paramOffset) {
|
|||
|
||||
void GLBackend::releaseBuffer(GLuint id, Size size) const {
|
||||
Lock lock(_trashMutex);
|
||||
_buffersTrash.push_back({ id, size });
|
||||
_currentFrameTrash.buffersTrash.push_back({ id, size });
|
||||
}
|
||||
|
||||
void GLBackend::releaseExternalTexture(GLuint id, const Texture::ExternalRecycler& recycler) const {
|
||||
Lock lock(_trashMutex);
|
||||
_externalTexturesTrash.push_back({ id, recycler });
|
||||
_currentFrameTrash.externalTexturesTrash.push_back({ id, recycler });
|
||||
}
|
||||
|
||||
void GLBackend::releaseTexture(GLuint id, Size size) const {
|
||||
Lock lock(_trashMutex);
|
||||
_texturesTrash.push_back({ id, size });
|
||||
_currentFrameTrash.texturesTrash.push_back({ id, size });
|
||||
}
|
||||
|
||||
void GLBackend::releaseFramebuffer(GLuint id) const {
|
||||
Lock lock(_trashMutex);
|
||||
_framebuffersTrash.push_back(id);
|
||||
_currentFrameTrash.framebuffersTrash.push_back(id);
|
||||
}
|
||||
|
||||
void GLBackend::releaseShader(GLuint id) const {
|
||||
Lock lock(_trashMutex);
|
||||
_shadersTrash.push_back(id);
|
||||
_currentFrameTrash.shadersTrash.push_back(id);
|
||||
}
|
||||
|
||||
void GLBackend::releaseProgram(GLuint id) const {
|
||||
Lock lock(_trashMutex);
|
||||
_programsTrash.push_back(id);
|
||||
_currentFrameTrash.programsTrash.push_back(id);
|
||||
}
|
||||
|
||||
void GLBackend::releaseQuery(GLuint id) const {
|
||||
Lock lock(_trashMutex);
|
||||
_queriesTrash.push_back(id);
|
||||
_currentFrameTrash.queriesTrash.push_back(id);
|
||||
}
|
||||
|
||||
void GLBackend::queueLambda(const std::function<void()> lambda) const {
|
||||
|
@ -746,6 +746,81 @@ void GLBackend::queueLambda(const std::function<void()> lambda) const {
|
|||
_lambdaQueue.push_back(lambda);
|
||||
}
|
||||
|
||||
void GLBackend::FrameTrash::cleanup() {
|
||||
glWaitSync(fence, 0, GL_TIMEOUT_IGNORED);
|
||||
glDeleteSync(fence);
|
||||
|
||||
{
|
||||
std::vector<GLuint> ids;
|
||||
ids.reserve(buffersTrash.size());
|
||||
for (auto pair : buffersTrash) {
|
||||
ids.push_back(pair.first);
|
||||
}
|
||||
if (!ids.empty()) {
|
||||
glDeleteBuffers((GLsizei)ids.size(), ids.data());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<GLuint> ids;
|
||||
ids.reserve(framebuffersTrash.size());
|
||||
for (auto id : framebuffersTrash) {
|
||||
ids.push_back(id);
|
||||
}
|
||||
if (!ids.empty()) {
|
||||
glDeleteFramebuffers((GLsizei)ids.size(), ids.data());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<GLuint> ids;
|
||||
ids.reserve(texturesTrash.size());
|
||||
for (auto pair : texturesTrash) {
|
||||
ids.push_back(pair.first);
|
||||
}
|
||||
if (!ids.empty()) {
|
||||
glDeleteTextures((GLsizei)ids.size(), ids.data());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if (!externalTexturesTrash.empty()) {
|
||||
std::vector<GLsync> fences;
|
||||
fences.resize(externalTexturesTrash.size());
|
||||
for (size_t i = 0; i < externalTexturesTrash.size(); ++i) {
|
||||
fences[i] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
}
|
||||
// External texture fences will be read in another thread/context, so we need a flush
|
||||
glFlush();
|
||||
size_t index = 0;
|
||||
for (auto pair : externalTexturesTrash) {
|
||||
auto fence = fences[index++];
|
||||
pair.second(pair.first, fence);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto id : programsTrash) {
|
||||
glDeleteProgram(id);
|
||||
}
|
||||
|
||||
for (auto id : shadersTrash) {
|
||||
glDeleteShader(id);
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<GLuint> ids;
|
||||
ids.reserve(queriesTrash.size());
|
||||
for (auto id : queriesTrash) {
|
||||
ids.push_back(id);
|
||||
}
|
||||
if (!ids.empty()) {
|
||||
glDeleteQueries((GLsizei)ids.size(), ids.data());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void GLBackend::recycle() const {
|
||||
PROFILE_RANGE(render_gpu_gl, __FUNCTION__)
|
||||
{
|
||||
|
@ -759,112 +834,16 @@ void GLBackend::recycle() const {
|
|||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<GLuint> ids;
|
||||
std::list<std::pair<GLuint, Size>> buffersTrash;
|
||||
{
|
||||
Lock lock(_trashMutex);
|
||||
std::swap(_buffersTrash, buffersTrash);
|
||||
}
|
||||
ids.reserve(buffersTrash.size());
|
||||
for (auto pair : buffersTrash) {
|
||||
ids.push_back(pair.first);
|
||||
}
|
||||
if (!ids.empty()) {
|
||||
glDeleteBuffers((GLsizei)ids.size(), ids.data());
|
||||
}
|
||||
while (!_previousFrameTrashes.empty()) {
|
||||
_previousFrameTrashes.front().cleanup();
|
||||
_previousFrameTrashes.pop_front();
|
||||
}
|
||||
|
||||
_previousFrameTrashes.emplace_back();
|
||||
{
|
||||
std::vector<GLuint> ids;
|
||||
std::list<GLuint> framebuffersTrash;
|
||||
{
|
||||
Lock lock(_trashMutex);
|
||||
std::swap(_framebuffersTrash, framebuffersTrash);
|
||||
}
|
||||
ids.reserve(framebuffersTrash.size());
|
||||
for (auto id : framebuffersTrash) {
|
||||
ids.push_back(id);
|
||||
}
|
||||
if (!ids.empty()) {
|
||||
glDeleteFramebuffers((GLsizei)ids.size(), ids.data());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<GLuint> ids;
|
||||
std::list<std::pair<GLuint, Size>> texturesTrash;
|
||||
{
|
||||
Lock lock(_trashMutex);
|
||||
std::swap(_texturesTrash, texturesTrash);
|
||||
}
|
||||
ids.reserve(texturesTrash.size());
|
||||
for (auto pair : texturesTrash) {
|
||||
ids.push_back(pair.first);
|
||||
}
|
||||
if (!ids.empty()) {
|
||||
glDeleteTextures((GLsizei)ids.size(), ids.data());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::list<std::pair<GLuint, Texture::ExternalRecycler>> externalTexturesTrash;
|
||||
{
|
||||
Lock lock(_trashMutex);
|
||||
std::swap(_externalTexturesTrash, externalTexturesTrash);
|
||||
}
|
||||
if (!externalTexturesTrash.empty()) {
|
||||
std::vector<GLsync> fences;
|
||||
fences.resize(externalTexturesTrash.size());
|
||||
for (size_t i = 0; i < externalTexturesTrash.size(); ++i) {
|
||||
fences[i] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
}
|
||||
// External texture fences will be read in another thread/context, so we need a flush
|
||||
glFlush();
|
||||
size_t index = 0;
|
||||
for (auto pair : externalTexturesTrash) {
|
||||
auto fence = fences[index++];
|
||||
pair.second(pair.first, fence);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::list<GLuint> programsTrash;
|
||||
{
|
||||
Lock lock(_trashMutex);
|
||||
std::swap(_programsTrash, programsTrash);
|
||||
}
|
||||
for (auto id : programsTrash) {
|
||||
glDeleteProgram(id);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::list<GLuint> shadersTrash;
|
||||
{
|
||||
Lock lock(_trashMutex);
|
||||
std::swap(_shadersTrash, shadersTrash);
|
||||
}
|
||||
for (auto id : shadersTrash) {
|
||||
glDeleteShader(id);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::vector<GLuint> ids;
|
||||
std::list<GLuint> queriesTrash;
|
||||
{
|
||||
Lock lock(_trashMutex);
|
||||
std::swap(_queriesTrash, queriesTrash);
|
||||
}
|
||||
ids.reserve(queriesTrash.size());
|
||||
for (auto id : queriesTrash) {
|
||||
ids.push_back(id);
|
||||
}
|
||||
if (!ids.empty()) {
|
||||
glDeleteQueries((GLsizei)ids.size(), ids.data());
|
||||
}
|
||||
Lock lock(_trashMutex);
|
||||
_previousFrameTrashes.back().swap(_currentFrameTrash);
|
||||
_previousFrameTrashes.back().fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
}
|
||||
|
||||
_textureManagement._transferEngine->manageMemory();
|
||||
|
|
|
@ -419,16 +419,34 @@ protected:
|
|||
static const size_t INVALID_OFFSET = (size_t)-1;
|
||||
bool _inRenderTransferPass{ false };
|
||||
int _currentDraw{ -1 };
|
||||
|
||||
std::list<std::string> profileRanges;
|
||||
|
||||
struct FrameTrash {
|
||||
GLsync fence = nullptr;
|
||||
std::list<std::pair<GLuint, Size>> buffersTrash;
|
||||
std::list<std::pair<GLuint, Size>> texturesTrash;
|
||||
std::list<std::pair<GLuint, Texture::ExternalRecycler>> externalTexturesTrash;
|
||||
std::list<GLuint> framebuffersTrash;
|
||||
std::list<GLuint> shadersTrash;
|
||||
std::list<GLuint> programsTrash;
|
||||
std::list<GLuint> queriesTrash;
|
||||
|
||||
void swap(FrameTrash& other) {
|
||||
buffersTrash.swap(other.buffersTrash);
|
||||
texturesTrash.swap(other.texturesTrash);
|
||||
externalTexturesTrash.swap(other.externalTexturesTrash);
|
||||
framebuffersTrash.swap(other.framebuffersTrash);
|
||||
shadersTrash.swap(other.shadersTrash);
|
||||
programsTrash.swap(other.programsTrash);
|
||||
queriesTrash.swap(other.queriesTrash);
|
||||
}
|
||||
|
||||
void cleanup();
|
||||
};
|
||||
|
||||
mutable Mutex _trashMutex;
|
||||
mutable std::list<std::pair<GLuint, Size>> _buffersTrash;
|
||||
mutable std::list<std::pair<GLuint, Size>> _texturesTrash;
|
||||
mutable std::list<std::pair<GLuint, Texture::ExternalRecycler>> _externalTexturesTrash;
|
||||
mutable std::list<GLuint> _framebuffersTrash;
|
||||
mutable std::list<GLuint> _shadersTrash;
|
||||
mutable std::list<GLuint> _programsTrash;
|
||||
mutable std::list<GLuint> _queriesTrash;
|
||||
mutable FrameTrash _currentFrameTrash;
|
||||
mutable std::list<FrameTrash> _previousFrameTrashes;
|
||||
std::list<std::string> profileRanges;
|
||||
mutable std::list<std::function<void()>> _lambdaQueue;
|
||||
|
||||
void renderPassTransfer(const Batch& batch);
|
||||
|
|
|
@ -766,14 +766,16 @@ void CharacterController::updateState() {
|
|||
SET_STATE(State::InAir, "takeoff done");
|
||||
|
||||
// compute jumpSpeed based on the scaled jump height for the default avatar in default gravity.
|
||||
float jumpSpeed = sqrtf(2.0f * DEFAULT_AVATAR_GRAVITY * _scaleFactor * DEFAULT_AVATAR_JUMP_HEIGHT);
|
||||
const float jumpHeight = std::max(_scaleFactor * DEFAULT_AVATAR_JUMP_HEIGHT, DEFAULT_AVATAR_MIN_JUMP_HEIGHT);
|
||||
const float jumpSpeed = sqrtf(2.0f * -DEFAULT_AVATAR_GRAVITY * jumpHeight);
|
||||
velocity += jumpSpeed * _currentUp;
|
||||
_rigidBody->setLinearVelocity(velocity);
|
||||
}
|
||||
break;
|
||||
case State::InAir: {
|
||||
const float JUMP_SPEED = _scaleFactor * DEFAULT_AVATAR_JUMP_SPEED;
|
||||
if ((velocity.dot(_currentUp) <= (JUMP_SPEED / 2.0f)) && ((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport)) {
|
||||
const float jumpHeight = std::max(_scaleFactor * DEFAULT_AVATAR_JUMP_HEIGHT, DEFAULT_AVATAR_MIN_JUMP_HEIGHT);
|
||||
const float jumpSpeed = sqrtf(2.0f * -DEFAULT_AVATAR_GRAVITY * jumpHeight);
|
||||
if ((velocity.dot(_currentUp) <= (jumpSpeed / 2.0f)) && ((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport)) {
|
||||
SET_STATE(State::Ground, "hit ground");
|
||||
} else if (_flyingAllowed) {
|
||||
btVector3 desiredVelocity = _targetVelocity;
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include <gl/Config.h>
|
||||
#include <gl/QOpenGLContextWrapper.h>
|
||||
#include <gl/GLHelpers.h>
|
||||
|
||||
#include <QtQuick/QQuickWindow>
|
||||
|
||||
|
@ -114,6 +115,7 @@ void RenderEventHandler::onRender() {
|
|||
|
||||
PROFILE_RANGE(render_qml_gl, __FUNCTION__);
|
||||
|
||||
gl::globalLock();
|
||||
if (!_shared->preRender()) {
|
||||
return;
|
||||
}
|
||||
|
@ -139,11 +141,12 @@ void RenderEventHandler::onRender() {
|
|||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
auto fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
// Fence will be used in another thread / context, so a flush is required
|
||||
glFlush();
|
||||
_shared->updateTextureAndFence({ texture, fence });
|
||||
// Fence will be used in another thread / context, so a flush is required
|
||||
_shared->_quickWindow->resetOpenGLState();
|
||||
}
|
||||
gl::globalRelease();
|
||||
}
|
||||
|
||||
void RenderEventHandler::onQuit() {
|
||||
|
@ -167,4 +170,5 @@ void RenderEventHandler::onQuit() {
|
|||
moveToThread(qApp->thread());
|
||||
QThread::currentThread()->quit();
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
|
@ -51,8 +51,10 @@ uint32_t TextureCache::acquireTexture(const QSize& size) {
|
|||
if (!textureSet.returnedTextures.empty()) {
|
||||
auto textureAndFence = textureSet.returnedTextures.front();
|
||||
textureSet.returnedTextures.pop_front();
|
||||
glWaitSync((GLsync)textureAndFence.second, 0, GL_TIMEOUT_IGNORED);
|
||||
glDeleteSync((GLsync)textureAndFence.second);
|
||||
if (textureAndFence.second) {
|
||||
glWaitSync((GLsync)textureAndFence.second, 0, GL_TIMEOUT_IGNORED);
|
||||
glDeleteSync((GLsync)textureAndFence.second);
|
||||
}
|
||||
return textureAndFence.first;
|
||||
}
|
||||
return createTexture(size);
|
||||
|
@ -101,9 +103,11 @@ void TextureCache::destroyTexture(uint32_t texture) {
|
|||
|
||||
void TextureCache::destroy(const Value& textureAndFence) {
|
||||
const auto& fence = textureAndFence.second;
|
||||
// FIXME prevents crash on shutdown, but we should migrate to a global functions object owned by the shared context.
|
||||
glWaitSync((GLsync)fence, 0, GL_TIMEOUT_IGNORED);
|
||||
glDeleteSync((GLsync)fence);
|
||||
if (fence) {
|
||||
// FIXME prevents crash on shutdown, but we should migrate to a global functions object owned by the shared context.
|
||||
glWaitSync((GLsync)fence, 0, GL_TIMEOUT_IGNORED);
|
||||
glDeleteSync((GLsync)fence);
|
||||
}
|
||||
destroyTexture(textureAndFence.first);
|
||||
}
|
||||
|
||||
|
|
|
@ -242,9 +242,13 @@ ModelMeshPartPayload::ModelMeshPartPayload(ModelPointer model, int meshIndex, in
|
|||
#ifdef Q_OS_MAC
|
||||
// On mac AMD, we specifically need to have a _meshBlendshapeBuffer bound when using a deformed mesh pipeline
|
||||
// it cannot be null otherwise we crash in the drawcall using a deformed pipeline with a skinned only (not blendshaped) mesh
|
||||
if ((_isBlendShaped || _isSkinned)) {
|
||||
glm::vec4 data;
|
||||
_meshBlendshapeBuffer = std::make_shared<gpu::Buffer>(sizeof(glm::vec4), reinterpret_cast<const gpu::Byte*>(&data));
|
||||
if (_isBlendShaped) {
|
||||
std::vector<BlendshapeOffset> data(_meshNumVertices);
|
||||
const auto blendShapeBufferSize = _meshNumVertices * sizeof(BlendshapeOffset);
|
||||
_meshBlendshapeBuffer = std::make_shared<gpu::Buffer>(blendShapeBufferSize, reinterpret_cast<const gpu::Byte*>(data.data()), blendShapeBufferSize);
|
||||
} else if (_isSkinned) {
|
||||
BlendshapeOffset data;
|
||||
_meshBlendshapeBuffer = std::make_shared<gpu::Buffer>(sizeof(BlendshapeOffset), reinterpret_cast<const gpu::Byte*>(&data), sizeof(BlendshapeOffset));
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
|
@ -70,9 +70,10 @@ const float DEFAULT_AVATAR_MAX_WALKING_BACKWARD_SPEED = 2.2f; // meters / second
|
|||
const float DEFAULT_AVATAR_MAX_FLYING_SPEED = 30.0f; // meters / second
|
||||
const float DEFAULT_AVATAR_WALK_SPEED_THRESHOLD = 0.15f;
|
||||
|
||||
const float DEFAULT_AVATAR_GRAVITY = -5.0f; // meters / second^2
|
||||
const float DEFAULT_AVATAR_JUMP_SPEED = 3.5f; // meters / second
|
||||
const float DEFAULT_AVATAR_JUMP_HEIGHT = (DEFAULT_AVATAR_JUMP_SPEED * DEFAULT_AVATAR_JUMP_SPEED) / (2.0f * DEFAULT_AVATAR_GRAVITY); // meters
|
||||
const float DEFAULT_AVATAR_GRAVITY = -5.0f; // meters / second^2 (world)
|
||||
const float DEFAULT_AVATAR_JUMP_SPEED = 3.5f; // meters / second (sensor)
|
||||
const float DEFAULT_AVATAR_JUMP_HEIGHT = (DEFAULT_AVATAR_JUMP_SPEED * DEFAULT_AVATAR_JUMP_SPEED) / (2.0f * -DEFAULT_AVATAR_GRAVITY); // meters (sensor)
|
||||
const float DEFAULT_AVATAR_MIN_JUMP_HEIGHT = 0.25f; // meters (world) // hack
|
||||
|
||||
const float DEFAULT_AVATAR_FALL_HEIGHT = 20.0f; // meters
|
||||
const float DEFAULT_AVATAR_MIN_HOVER_HEIGHT = 2.5f; // meters
|
||||
|
|
|
@ -68,8 +68,14 @@ namespace PrioritySortUtil {
|
|||
void reserve(size_t num) {
|
||||
_vector.reserve(num);
|
||||
}
|
||||
const std::vector<T>& getSortedVector() {
|
||||
std::sort(_vector.begin(), _vector.end(), [](const T& left, const T& right) { return left.getPriority() > right.getPriority(); });
|
||||
const std::vector<T>& getSortedVector(int numToSort = 0) {
|
||||
if (numToSort == 0 || numToSort >= (int)_vector.size()) {
|
||||
std::sort(_vector.begin(), _vector.end(),
|
||||
[](const T& left, const T& right) { return left.getPriority() > right.getPriority(); });
|
||||
} else {
|
||||
std::partial_sort(_vector.begin(), _vector.begin() + numToSort, _vector.end(),
|
||||
[](const T& left, const T& right) { return left.getPriority() > right.getPriority(); });
|
||||
}
|
||||
return _vector;
|
||||
}
|
||||
|
||||
|
@ -99,6 +105,9 @@ namespace PrioritySortUtil {
|
|||
float radius = glm::max(thing.getRadius(), MIN_RADIUS);
|
||||
// Other item's angle from view centre:
|
||||
float cosineAngle = glm::dot(offset, view.getDirection()) / distance;
|
||||
if (cosineAngle > 0.0f) {
|
||||
cosineAngle = std::sqrt(cosineAngle);
|
||||
}
|
||||
float age = float((_usecCurrentTime - thing.getTimestamp()) / USECS_PER_SECOND);
|
||||
|
||||
// the "age" term accumulates at the sum of all weights
|
||||
|
|
|
@ -129,6 +129,28 @@ static glm::mat4 calculateResetMat() {
|
|||
return glm::mat4();
|
||||
}
|
||||
|
||||
static QString outOfRangeDataStrategyToString(ViveControllerManager::OutOfRangeDataStrategy strategy) {
|
||||
switch (strategy) {
|
||||
default:
|
||||
case ViveControllerManager::OutOfRangeDataStrategy::None:
|
||||
return "None";
|
||||
case ViveControllerManager::OutOfRangeDataStrategy::Freeze:
|
||||
return "Freeze";
|
||||
case ViveControllerManager::OutOfRangeDataStrategy::Drop:
|
||||
return "Drop";
|
||||
}
|
||||
}
|
||||
|
||||
static ViveControllerManager::OutOfRangeDataStrategy stringToOutOfRangeDataStrategy(const QString& string) {
|
||||
if (string == "Drop") {
|
||||
return ViveControllerManager::OutOfRangeDataStrategy::Drop;
|
||||
} else if (string == "Freeze") {
|
||||
return ViveControllerManager::OutOfRangeDataStrategy::Freeze;
|
||||
} else {
|
||||
return ViveControllerManager::OutOfRangeDataStrategy::None;
|
||||
}
|
||||
}
|
||||
|
||||
bool ViveControllerManager::isDesktopMode() {
|
||||
if (_container) {
|
||||
return !_container->getActiveDisplayPlugin()->isHmd();
|
||||
|
@ -288,8 +310,10 @@ void ViveControllerManager::loadSettings() {
|
|||
if (_inputDevice) {
|
||||
const double DEFAULT_ARM_CIRCUMFERENCE = 0.33;
|
||||
const double DEFAULT_SHOULDER_WIDTH = 0.48;
|
||||
const QString DEFAULT_OUT_OF_RANGE_STRATEGY = "Drop";
|
||||
_inputDevice->_armCircumference = settings.value("armCircumference", QVariant(DEFAULT_ARM_CIRCUMFERENCE)).toDouble();
|
||||
_inputDevice->_shoulderWidth = settings.value("shoulderWidth", QVariant(DEFAULT_SHOULDER_WIDTH)).toDouble();
|
||||
_inputDevice->_outOfRangeDataStrategy = stringToOutOfRangeDataStrategy(settings.value("outOfRangeDataStrategy", QVariant(DEFAULT_OUT_OF_RANGE_STRATEGY)).toString());
|
||||
}
|
||||
}
|
||||
settings.endGroup();
|
||||
|
@ -303,6 +327,7 @@ void ViveControllerManager::saveSettings() const {
|
|||
if (_inputDevice) {
|
||||
settings.setValue(QString("armCircumference"), _inputDevice->_armCircumference);
|
||||
settings.setValue(QString("shoulderWidth"), _inputDevice->_shoulderWidth);
|
||||
settings.setValue(QString("outOfRangeDataStrategy"), outOfRangeDataStrategyToString(_inputDevice->_outOfRangeDataStrategy));
|
||||
}
|
||||
}
|
||||
settings.endGroup();
|
||||
|
@ -446,6 +471,8 @@ void ViveControllerManager::InputDevice::configureCalibrationSettings(const QJso
|
|||
hmdDesktopTracking = iter.value().toBool();
|
||||
} else if (iter.key() == "desktopMode") {
|
||||
hmdDesktopMode = iter.value().toBool();
|
||||
} else if (iter.key() == "outOfRangeDataStrategy") {
|
||||
_outOfRangeDataStrategy = stringToOutOfRangeDataStrategy(iter.value().toString());
|
||||
}
|
||||
iter++;
|
||||
}
|
||||
|
@ -468,6 +495,7 @@ QJsonObject ViveControllerManager::InputDevice::configurationSettings() {
|
|||
configurationSettings["puckCount"] = (int)_validTrackedObjects.size();
|
||||
configurationSettings["armCircumference"] = (double)_armCircumference * M_TO_CM;
|
||||
configurationSettings["shoulderWidth"] = (double)_shoulderWidth * M_TO_CM;
|
||||
configurationSettings["outOfRangeDataStrategy"] = outOfRangeDataStrategyToString(_outOfRangeDataStrategy);
|
||||
return configurationSettings;
|
||||
}
|
||||
|
||||
|
@ -484,6 +512,10 @@ void ViveControllerManager::InputDevice::emitCalibrationStatus() {
|
|||
emit inputConfiguration->calibrationStatus(status);
|
||||
}
|
||||
|
||||
static controller::Pose buildPose(const glm::mat4& mat, const glm::vec3& linearVelocity, const glm::vec3& angularVelocity) {
|
||||
return controller::Pose(extractTranslation(mat), glmExtractRotation(mat), linearVelocity, angularVelocity);
|
||||
}
|
||||
|
||||
void ViveControllerManager::InputDevice::handleTrackedObject(uint32_t deviceIndex, const controller::InputCalibrationData& inputCalibrationData) {
|
||||
uint32_t poseIndex = controller::TRACKED_OBJECT_00 + deviceIndex;
|
||||
printDeviceTrackingResultChange(deviceIndex);
|
||||
|
@ -492,35 +524,48 @@ void ViveControllerManager::InputDevice::handleTrackedObject(uint32_t deviceInde
|
|||
_nextSimPoseData.vrPoses[deviceIndex].bPoseIsValid &&
|
||||
poseIndex <= controller::TRACKED_OBJECT_15) {
|
||||
|
||||
mat4& mat = mat4();
|
||||
vec3 linearVelocity = vec3();
|
||||
vec3 angularVelocity = vec3();
|
||||
// check if the device is tracking out of range, then process the correct pose depending on the result.
|
||||
if (_nextSimPoseData.vrPoses[deviceIndex].eTrackingResult != vr::TrackingResult_Running_OutOfRange) {
|
||||
mat = _nextSimPoseData.poses[deviceIndex];
|
||||
linearVelocity = _nextSimPoseData.linearVelocities[deviceIndex];
|
||||
angularVelocity = _nextSimPoseData.angularVelocities[deviceIndex];
|
||||
} else {
|
||||
mat = _lastSimPoseData.poses[deviceIndex];
|
||||
linearVelocity = _lastSimPoseData.linearVelocities[deviceIndex];
|
||||
angularVelocity = _lastSimPoseData.angularVelocities[deviceIndex];
|
||||
|
||||
// make sure that we do not overwrite the pose in the _lastSimPose with incorrect data.
|
||||
_nextSimPoseData.poses[deviceIndex] = _lastSimPoseData.poses[deviceIndex];
|
||||
_nextSimPoseData.linearVelocities[deviceIndex] = _lastSimPoseData.linearVelocities[deviceIndex];
|
||||
_nextSimPoseData.angularVelocities[deviceIndex] = _lastSimPoseData.angularVelocities[deviceIndex];
|
||||
controller::Pose pose;
|
||||
switch (_outOfRangeDataStrategy) {
|
||||
case OutOfRangeDataStrategy::Drop:
|
||||
default:
|
||||
// Drop - Mark all non Running_OK results as invald
|
||||
if (_nextSimPoseData.vrPoses[deviceIndex].eTrackingResult == vr::TrackingResult_Running_OK) {
|
||||
pose = buildPose(_nextSimPoseData.poses[deviceIndex], _nextSimPoseData.linearVelocities[deviceIndex], _nextSimPoseData.angularVelocities[deviceIndex]);
|
||||
} else {
|
||||
pose.valid = false;
|
||||
}
|
||||
break;
|
||||
case OutOfRangeDataStrategy::None:
|
||||
// None - Ignore eTrackingResult all together
|
||||
pose = buildPose(_nextSimPoseData.poses[deviceIndex], _nextSimPoseData.linearVelocities[deviceIndex], _nextSimPoseData.angularVelocities[deviceIndex]);
|
||||
break;
|
||||
case OutOfRangeDataStrategy::Freeze:
|
||||
// Freeze - Dont invalide non Running_OK poses, instead just return the last good pose.
|
||||
if (_nextSimPoseData.vrPoses[deviceIndex].eTrackingResult == vr::TrackingResult_Running_OK) {
|
||||
pose = buildPose(_nextSimPoseData.poses[deviceIndex], _nextSimPoseData.linearVelocities[deviceIndex], _nextSimPoseData.angularVelocities[deviceIndex]);
|
||||
} else {
|
||||
pose = buildPose(_lastSimPoseData.poses[deviceIndex], _lastSimPoseData.linearVelocities[deviceIndex], _lastSimPoseData.angularVelocities[deviceIndex]);
|
||||
|
||||
// make sure that we do not overwrite the pose in the _lastSimPose with incorrect data.
|
||||
_nextSimPoseData.poses[deviceIndex] = _lastSimPoseData.poses[deviceIndex];
|
||||
_nextSimPoseData.linearVelocities[deviceIndex] = _lastSimPoseData.linearVelocities[deviceIndex];
|
||||
_nextSimPoseData.angularVelocities[deviceIndex] = _lastSimPoseData.angularVelocities[deviceIndex];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
controller::Pose pose(extractTranslation(mat), glmExtractRotation(mat), linearVelocity, angularVelocity);
|
||||
if (pose.valid) {
|
||||
// transform into avatar frame
|
||||
glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat;
|
||||
_poseStateMap[poseIndex] = pose.transform(controllerToAvatar);
|
||||
|
||||
// transform into avatar frame
|
||||
glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat;
|
||||
_poseStateMap[poseIndex] = pose.transform(controllerToAvatar);
|
||||
|
||||
// but _validTrackedObjects remain in sensor frame
|
||||
_validTrackedObjects.push_back(std::make_pair(poseIndex, pose));
|
||||
_trackedControllers++;
|
||||
// but _validTrackedObjects remain in sensor frame
|
||||
_validTrackedObjects.push_back(std::make_pair(poseIndex, pose));
|
||||
_trackedControllers++;
|
||||
} else {
|
||||
// insert invalid pose into state map
|
||||
_poseStateMap[poseIndex] = pose;
|
||||
}
|
||||
} else {
|
||||
controller::Pose invalidPose;
|
||||
_poseStateMap[poseIndex] = invalidPose;
|
||||
|
|
|
@ -60,11 +60,18 @@ public:
|
|||
virtual void saveSettings() const override;
|
||||
virtual void loadSettings() override;
|
||||
|
||||
enum class OutOfRangeDataStrategy {
|
||||
None,
|
||||
Freeze,
|
||||
Drop
|
||||
};
|
||||
|
||||
private:
|
||||
class InputDevice : public controller::InputDevice {
|
||||
public:
|
||||
InputDevice(vr::IVRSystem*& system);
|
||||
bool isHeadControllerMounted() const { return _overrideHead; }
|
||||
|
||||
private:
|
||||
// Device functions
|
||||
controller::Input::NamedVector getAvailableInputs() const override;
|
||||
|
@ -162,6 +169,7 @@ private:
|
|||
FilteredStick _filteredLeftStick;
|
||||
FilteredStick _filteredRightStick;
|
||||
std::string _headsetName {""};
|
||||
OutOfRangeDataStrategy _outOfRangeDataStrategy { OutOfRangeDataStrategy::Drop };
|
||||
|
||||
std::vector<PuckPosePair> _validTrackedObjects;
|
||||
std::map<uint32_t, glm::mat4> _pucksPostOffset;
|
||||
|
|
|
@ -45,7 +45,7 @@ if (Window.interstitialModeEnabled) {
|
|||
}
|
||||
|
||||
// add a menu item for debugging
|
||||
var MENU_CATEGORY = "Developer";
|
||||
var MENU_CATEGORY = "Developer > Scripting";
|
||||
var MENU_ITEM = "Debug defaultScripts.js";
|
||||
|
||||
var SETTINGS_KEY = '_debugDefaultScriptsIsChecked';
|
||||
|
|
222
scripts/developer/accelerationFilterApp.js
Normal file
222
scripts/developer/accelerationFilterApp.js
Normal file
|
@ -0,0 +1,222 @@
|
|||
var LEFT_HAND_INDEX = 0;
|
||||
var RIGHT_HAND_INDEX = 1;
|
||||
var LEFT_FOOT_INDEX = 2;
|
||||
var RIGHT_FOOT_INDEX = 3;
|
||||
var HIPS_INDEX = 4;
|
||||
var SPINE2_INDEX = 5;
|
||||
|
||||
var mappingJson = {
|
||||
name: "com.highfidelity.testing.accelerationTest",
|
||||
channels: [
|
||||
{
|
||||
from: "Standard.LeftHand",
|
||||
to: "Actions.LeftHand",
|
||||
filters: [
|
||||
{
|
||||
type: "accelerationLimiter",
|
||||
rotationAccelerationLimit: 2000.0,
|
||||
translationAccelerationLimit: 100.0,
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
from: "Standard.RightHand",
|
||||
to: "Actions.RightHand",
|
||||
filters: [
|
||||
{
|
||||
type: "accelerationLimiter",
|
||||
rotationAccelerationLimit: 2000.0,
|
||||
translationAccelerationLimit: 100.0,
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
from: "Standard.LeftFoot",
|
||||
to: "Actions.LeftFoot",
|
||||
filters: [
|
||||
{
|
||||
type: "accelerationLimiter",
|
||||
rotationAccelerationLimit: 2000.0,
|
||||
translationAccelerationLimit: 100.0,
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
from: "Standard.RightFoot",
|
||||
to: "Actions.RightFoot",
|
||||
filters: [
|
||||
{
|
||||
type: "accelerationLimiter",
|
||||
rotationAccelerationLimit: 2000.0,
|
||||
translationAccelerationLimit: 100.0,
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
from: "Standard.Hips",
|
||||
to: "Actions.Hips",
|
||||
filters: [
|
||||
{
|
||||
type: "accelerationLimiter",
|
||||
rotationAccelerationLimit: 2000.0,
|
||||
translationAccelerationLimit: 100.0,
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
from: "Standard.Spine2",
|
||||
to: "Actions.Spine2",
|
||||
filters: [
|
||||
{
|
||||
type: "accelerationLimiter",
|
||||
rotationAccelerationLimit: 2000.0,
|
||||
translationAccelerationLimit: 100.0,
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
//
|
||||
// tablet app boiler plate
|
||||
//
|
||||
|
||||
var TABLET_BUTTON_NAME = "ACCFILT";
|
||||
var HTML_URL = "https://s3.amazonaws.com/hifi-public/tony/html/accelerationFilterApp.html?2";
|
||||
|
||||
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
var tabletButton = tablet.addButton({
|
||||
text: TABLET_BUTTON_NAME,
|
||||
icon: "https://s3.amazonaws.com/hifi-public/tony/icons/tpose-i.svg",
|
||||
activeIcon: "https://s3.amazonaws.com/hifi-public/tony/icons/tpose-a.svg"
|
||||
});
|
||||
|
||||
tabletButton.clicked.connect(function () {
|
||||
if (shown) {
|
||||
tablet.gotoHomeScreen();
|
||||
} else {
|
||||
tablet.gotoWebScreen(HTML_URL);
|
||||
}
|
||||
});
|
||||
|
||||
var shown = false;
|
||||
|
||||
function onScreenChanged(type, url) {
|
||||
if (type === "Web" && url === HTML_URL) {
|
||||
tabletButton.editProperties({isActive: true});
|
||||
if (!shown) {
|
||||
// hook up to event bridge
|
||||
tablet.webEventReceived.connect(onWebEventReceived);
|
||||
shownChanged(true);
|
||||
}
|
||||
shown = true;
|
||||
} else {
|
||||
tabletButton.editProperties({isActive: false});
|
||||
if (shown) {
|
||||
// disconnect from event bridge
|
||||
tablet.webEventReceived.disconnect(onWebEventReceived);
|
||||
shownChanged(false);
|
||||
}
|
||||
shown = false;
|
||||
}
|
||||
}
|
||||
|
||||
function getTranslationAccelerationLimit(i) {
|
||||
return mappingJson.channels[i].filters[0].translationAccelerationLimit;
|
||||
}
|
||||
function setTranslationAccelerationLimit(i, value) {
|
||||
mappingJson.channels[i].filters[0].translationAccelerationLimit = value;
|
||||
mappingChanged();
|
||||
}
|
||||
function getRotationAccelerationLimit(i) {
|
||||
return mappingJson.channels[i].filters[0].rotationAccelerationLimit;
|
||||
}
|
||||
function setRotationAccelerationLimit(i, value) {
|
||||
mappingJson.channels[i].filters[0].rotationAccelerationLimit = value; mappingChanged();
|
||||
}
|
||||
|
||||
function onWebEventReceived(msg) {
|
||||
if (msg.name === "init-complete") {
|
||||
var values = [
|
||||
{name: "left-hand-translation-acceleration-limit", val: getTranslationAccelerationLimit(LEFT_HAND_INDEX), checked: false},
|
||||
{name: "left-hand-rotation-acceleration-limit", val: getRotationAccelerationLimit(LEFT_HAND_INDEX), checked: false},
|
||||
{name: "right-hand-translation-acceleration-limit", val: getTranslationAccelerationLimit(RIGHT_HAND_INDEX), checked: false},
|
||||
{name: "right-hand-rotation-acceleration-limit", val: getRotationAccelerationLimit(RIGHT_HAND_INDEX), checked: false},
|
||||
{name: "left-foot-translation-acceleration-limit", val: getTranslationAccelerationLimit(LEFT_FOOT_INDEX), checked: false},
|
||||
{name: "left-foot-rotation-acceleration-limit", val: getRotationAccelerationLimit(LEFT_FOOT_INDEX), checked: false},
|
||||
{name: "right-foot-translation-acceleration-limit", val: getTranslationAccelerationLimit(RIGHT_FOOT_INDEX), checked: false},
|
||||
{name: "right-foot-rotation-acceleration-limit", val: getRotationAccelerationLimit(RIGHT_FOOT_INDEX), checked: false},
|
||||
{name: "hips-translation-acceleration-limit", val: getTranslationAccelerationLimit(HIPS_INDEX), checked: false},
|
||||
{name: "hips-rotation-acceleration-limit", val: getRotationAccelerationLimit(HIPS_INDEX), checked: false},
|
||||
{name: "spine2-translation-acceleration-limit", val: getTranslationAccelerationLimit(SPINE2_INDEX), checked: false},
|
||||
{name: "spine2-rotation-acceleration-limit", val: getRotationAccelerationLimit(SPINE2_INDEX), checked: false}
|
||||
];
|
||||
tablet.emitScriptEvent(JSON.stringify(values));
|
||||
} else if (msg.name === "left-hand-translation-acceleration-limit") {
|
||||
setTranslationAccelerationLimit(LEFT_HAND_INDEX, parseInt(msg.val, 10));
|
||||
} else if (msg.name === "left-hand-rotation-acceleration-limit") {
|
||||
setRotationAccelerationLimit(LEFT_HAND_INDEX, parseInt(msg.val, 10));
|
||||
} else if (msg.name === "right-hand-translation-acceleration-limit") {
|
||||
setTranslationAccelerationLimit(RIGHT_HAND_INDEX, parseInt(msg.val, 10));
|
||||
} else if (msg.name === "right-hand-rotation-acceleration-limit") {
|
||||
setRotationAccelerationLimit(RIGHT_HAND_INDEX, parseInt(msg.val, 10));
|
||||
} else if (msg.name === "left-foot-translation-acceleration-limit") {
|
||||
setTranslationAccelerationLimit(LEFT_FOOT_INDEX, parseInt(msg.val, 10));
|
||||
} else if (msg.name === "left-foot-rotation-acceleration-limit") {
|
||||
setRotationAccelerationLimit(LEFT_FOOT_INDEX, parseInt(msg.val, 10));
|
||||
} else if (msg.name === "right-foot-translation-acceleration-limit") {
|
||||
setTranslationAccelerationLimit(RIGHT_FOOT_INDEX, parseInt(msg.val, 10));
|
||||
} else if (msg.name === "right-foot-rotation-acceleration-limit") {
|
||||
setRotationAccelerationLimit(RIGHT_FOOT_INDEX, parseInt(msg.val, 10));
|
||||
} else if (msg.name === "hips-translation-acceleration-limit") {
|
||||
setTranslationAccelerationLimit(HIPS_INDEX, parseInt(msg.val, 10));
|
||||
} else if (msg.name === "hips-rotation-acceleration-limit") {
|
||||
setRotationAccelerationLimit(HIPS_INDEX, parseInt(msg.val, 10));
|
||||
} else if (msg.name === "spine2-translation-acceleration-limit") {
|
||||
setTranslationAccelerationLimit(SPINE2_INDEX, parseInt(msg.val, 10));
|
||||
} else if (msg.name === "spine2-rotation-acceleration-limit") {
|
||||
setRotationAccelerationLimit(SPINE2_INDEX, parseInt(msg.val, 10));
|
||||
}
|
||||
}
|
||||
|
||||
tablet.screenChanged.connect(onScreenChanged);
|
||||
|
||||
function shutdownTabletApp() {
|
||||
tablet.removeButton(tabletButton);
|
||||
if (shown) {
|
||||
tablet.webEventReceived.disconnect(onWebEventReceived);
|
||||
tablet.gotoHomeScreen();
|
||||
}
|
||||
tablet.screenChanged.disconnect(onScreenChanged);
|
||||
}
|
||||
|
||||
//
|
||||
// end tablet app boiler plate
|
||||
//
|
||||
|
||||
var mapping;
|
||||
function mappingChanged() {
|
||||
if (mapping) {
|
||||
mapping.disable();
|
||||
}
|
||||
mapping = Controller.parseMapping(JSON.stringify(mappingJson));
|
||||
mapping.enable();
|
||||
}
|
||||
|
||||
function shownChanged(newShown) {
|
||||
if (newShown) {
|
||||
mappingChanged();
|
||||
} else {
|
||||
mapping.disable();
|
||||
}
|
||||
}
|
||||
|
||||
mappingChanged();
|
||||
|
||||
Script.scriptEnding.connect(function() {
|
||||
if (mapping) {
|
||||
mapping.disable();
|
||||
}
|
||||
tablet.removeButton(tabletButton);
|
||||
});
|
||||
|
|
@ -19,6 +19,23 @@ if (scripts.length >= 2) {
|
|||
return;
|
||||
}
|
||||
|
||||
var SUPPRESS_DEFAULT_SCRIPTS_MENU_NAME = "Developer"
|
||||
var SUPPRESS_DEFAULT_SCRIPTS_ITEM_NAME = "Suppress messages from default scripts in Debug Window";
|
||||
var DEBUG_WINDOW_SUPPRESS_DEFAULTS_SCRIPTS = 'debugWindowSuppressDefaultScripts';
|
||||
var suppressDefaultScripts = Settings.getValue(DEBUG_WINDOW_SUPPRESS_DEFAULTS_SCRIPTS, false)
|
||||
Menu.addMenuItem({
|
||||
menuName: SUPPRESS_DEFAULT_SCRIPTS_MENU_NAME,
|
||||
menuItemName: SUPPRESS_DEFAULT_SCRIPTS_ITEM_NAME,
|
||||
isCheckable: true,
|
||||
isChecked: suppressDefaultScripts
|
||||
});
|
||||
|
||||
Menu.menuItemEvent.connect(function(menuItem) {
|
||||
if (menuItem === SUPPRESS_DEFAULT_SCRIPTS_ITEM_NAME) {
|
||||
suppressDefaultScripts = Menu.isOptionChecked(SUPPRESS_DEFAULT_SCRIPTS_ITEM_NAME);
|
||||
}
|
||||
});
|
||||
|
||||
// Set up the qml ui
|
||||
var qml = Script.resolvePath('debugWindow.qml');
|
||||
|
||||
|
@ -61,17 +78,24 @@ window.visibleChanged.connect(function() {
|
|||
|
||||
window.closed.connect(function () { Script.stop(); });
|
||||
|
||||
function shouldLogMessage(scriptFileName) {
|
||||
return !suppressDefaultScripts
|
||||
|| (scriptFileName !== "defaultScripts.js" && scriptFileName != "controllerScripts.js");
|
||||
}
|
||||
|
||||
var getFormattedDate = function() {
|
||||
var date = new Date();
|
||||
return date.getMonth() + "/" + date.getDate() + " " + date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds();
|
||||
};
|
||||
|
||||
var sendToLogWindow = function(type, message, scriptFileName) {
|
||||
var typeFormatted = "";
|
||||
if (type) {
|
||||
typeFormatted = type + " - ";
|
||||
if (shouldLogMessage(scriptFileName)) {
|
||||
var typeFormatted = "";
|
||||
if (type) {
|
||||
typeFormatted = type + " - ";
|
||||
}
|
||||
window.sendToQml("[" + getFormattedDate() + "] " + "[" + scriptFileName + "] " + typeFormatted + message);
|
||||
}
|
||||
window.sendToQml("[" + getFormattedDate() + "] " + "[" + scriptFileName + "] " + typeFormatted + message);
|
||||
};
|
||||
|
||||
ScriptDiscoveryService.printedMessage.connect(function(message, scriptFileName) {
|
||||
|
@ -95,6 +119,10 @@ ScriptDiscoveryService.clearDebugWindow.connect(function() {
|
|||
});
|
||||
|
||||
Script.scriptEnding.connect(function () {
|
||||
Settings.setValue(DEBUG_WINDOW_SUPPRESS_DEFAULTS_SCRIPTS,
|
||||
Menu.isOptionChecked(SUPPRESS_DEFAULT_SCRIPTS_ITEM_NAME));
|
||||
Menu.removeMenuItem(SUPPRESS_DEFAULT_SCRIPTS_MENU_NAME, SUPPRESS_DEFAULT_SCRIPTS_ITEM_NAME);
|
||||
|
||||
var geometry = JSON.stringify({
|
||||
x: window.position.x,
|
||||
y: window.position.y,
|
||||
|
|
240
scripts/developer/exponentialFilterApp.js
Normal file
240
scripts/developer/exponentialFilterApp.js
Normal file
|
@ -0,0 +1,240 @@
|
|||
var LEFT_HAND_INDEX = 0;
|
||||
var RIGHT_HAND_INDEX = 1;
|
||||
var LEFT_FOOT_INDEX = 2;
|
||||
var RIGHT_FOOT_INDEX = 3;
|
||||
var HIPS_INDEX = 4;
|
||||
var SPINE2_INDEX = 5;
|
||||
|
||||
var HAND_SMOOTHING_TRANSLATION = 0.3;
|
||||
var HAND_SMOOTHING_ROTATION = 0.15;
|
||||
var FOOT_SMOOTHING_TRANSLATION = 0.3;
|
||||
var FOOT_SMOOTHING_ROTATION = 0.15;
|
||||
var TORSO_SMOOTHING_TRANSLATION = 0.3;
|
||||
var TORSO_SMOOTHING_ROTATION = 0.16;
|
||||
|
||||
var mappingJson = {
|
||||
name: "com.highfidelity.testing.exponentialFilterApp",
|
||||
channels: [
|
||||
{
|
||||
from: "Standard.LeftHand",
|
||||
to: "Actions.LeftHand",
|
||||
filters: [
|
||||
{
|
||||
type: "exponentialSmoothing",
|
||||
translation: HAND_SMOOTHING_TRANSLATION,
|
||||
rotation: HAND_SMOOTHING_ROTATION
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
from: "Standard.RightHand",
|
||||
to: "Actions.RightHand",
|
||||
filters: [
|
||||
{
|
||||
type: "exponentialSmoothing",
|
||||
translation: HAND_SMOOTHING_TRANSLATION,
|
||||
rotation: HAND_SMOOTHING_ROTATION
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
from: "Standard.LeftFoot",
|
||||
to: "Actions.LeftFoot",
|
||||
filters: [
|
||||
{
|
||||
type: "exponentialSmoothing",
|
||||
translation: FOOT_SMOOTHING_TRANSLATION,
|
||||
rotation: FOOT_SMOOTHING_ROTATION
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
from: "Standard.RightFoot",
|
||||
to: "Actions.RightFoot",
|
||||
filters: [
|
||||
{
|
||||
type: "exponentialSmoothing",
|
||||
translation: FOOT_SMOOTHING_TRANSLATION,
|
||||
rotation: FOOT_SMOOTHING_ROTATION
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
from: "Standard.Hips",
|
||||
to: "Actions.Hips",
|
||||
filters: [
|
||||
{
|
||||
type: "exponentialSmoothing",
|
||||
translation: TORSO_SMOOTHING_TRANSLATION,
|
||||
rotation: TORSO_SMOOTHING_ROTATION
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
from: "Standard.Spine2",
|
||||
to: "Actions.Spine2",
|
||||
filters: [
|
||||
{
|
||||
type: "exponentialSmoothing",
|
||||
translation: TORSO_SMOOTHING_TRANSLATION,
|
||||
rotation: TORSO_SMOOTHING_ROTATION
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
//
|
||||
// tablet app boiler plate
|
||||
//
|
||||
|
||||
var TABLET_BUTTON_NAME = "EXPFILT";
|
||||
var HTML_URL = "https://s3.amazonaws.com/hifi-public/tony/html/exponentialFilterApp.html?7";
|
||||
|
||||
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
var tabletButton = tablet.addButton({
|
||||
text: TABLET_BUTTON_NAME,
|
||||
icon: "https://s3.amazonaws.com/hifi-public/tony/icons/tpose-i.svg",
|
||||
activeIcon: "https://s3.amazonaws.com/hifi-public/tony/icons/tpose-a.svg"
|
||||
});
|
||||
|
||||
tabletButton.clicked.connect(function () {
|
||||
if (shown) {
|
||||
tablet.gotoHomeScreen();
|
||||
} else {
|
||||
tablet.gotoWebScreen(HTML_URL);
|
||||
}
|
||||
});
|
||||
|
||||
var shown = false;
|
||||
|
||||
function onScreenChanged(type, url) {
|
||||
if (type === "Web" && url === HTML_URL) {
|
||||
tabletButton.editProperties({isActive: true});
|
||||
if (!shown) {
|
||||
// hook up to event bridge
|
||||
tablet.webEventReceived.connect(onWebEventReceived);
|
||||
shownChanged(true);
|
||||
}
|
||||
shown = true;
|
||||
} else {
|
||||
tabletButton.editProperties({isActive: false});
|
||||
if (shown) {
|
||||
// disconnect from event bridge
|
||||
tablet.webEventReceived.disconnect(onWebEventReceived);
|
||||
shownChanged(false);
|
||||
}
|
||||
shown = false;
|
||||
}
|
||||
}
|
||||
|
||||
function getTranslation(i) {
|
||||
return mappingJson.channels[i].filters[0].translation;
|
||||
}
|
||||
function setTranslation(i, value) {
|
||||
mappingJson.channels[i].filters[0].translation = value;
|
||||
mappingChanged();
|
||||
}
|
||||
function getRotation(i) {
|
||||
return mappingJson.channels[i].filters[0].rotation;
|
||||
}
|
||||
function setRotation(i, value) {
|
||||
mappingJson.channels[i].filters[0].rotation = value; mappingChanged();
|
||||
}
|
||||
|
||||
function onWebEventReceived(msg) {
|
||||
if (msg.name === "init-complete") {
|
||||
var values = [
|
||||
{name: "enable-filtering", val: filterEnabled ? "on" : "off", checked: false},
|
||||
{name: "left-hand-translation", val: getTranslation(LEFT_HAND_INDEX), checked: false},
|
||||
{name: "left-hand-rotation", val: getRotation(LEFT_HAND_INDEX), checked: false},
|
||||
{name: "right-hand-translation", val: getTranslation(RIGHT_HAND_INDEX), checked: false},
|
||||
{name: "right-hand-rotation", val: getRotation(RIGHT_HAND_INDEX), checked: false},
|
||||
{name: "left-foot-translation", val: getTranslation(LEFT_FOOT_INDEX), checked: false},
|
||||
{name: "left-foot-rotation", val: getRotation(LEFT_FOOT_INDEX), checked: false},
|
||||
{name: "right-foot-translation", val: getTranslation(RIGHT_FOOT_INDEX), checked: false},
|
||||
{name: "right-foot-rotation", val: getRotation(RIGHT_FOOT_INDEX), checked: false},
|
||||
{name: "hips-translation", val: getTranslation(HIPS_INDEX), checked: false},
|
||||
{name: "hips-rotation", val: getRotation(HIPS_INDEX), checked: false},
|
||||
{name: "spine2-translation", val: getTranslation(SPINE2_INDEX), checked: false},
|
||||
{name: "spine2-rotation", val: getRotation(SPINE2_INDEX), checked: false}
|
||||
];
|
||||
tablet.emitScriptEvent(JSON.stringify(values));
|
||||
} else if (msg.name === "enable-filtering") {
|
||||
if (msg.val === "on") {
|
||||
filterEnabled = true;
|
||||
} else if (msg.val === "off") {
|
||||
filterEnabled = false;
|
||||
}
|
||||
mappingChanged();
|
||||
} else if (msg.name === "left-hand-translation") {
|
||||
setTranslation(LEFT_HAND_INDEX, Number(msg.val));
|
||||
} else if (msg.name === "left-hand-rotation") {
|
||||
setRotation(LEFT_HAND_INDEX, Number(msg.val));
|
||||
} else if (msg.name === "right-hand-translation") {
|
||||
setTranslation(RIGHT_HAND_INDEX, Number(msg.val));
|
||||
} else if (msg.name === "right-hand-rotation") {
|
||||
setRotation(RIGHT_HAND_INDEX, Number(msg.val));
|
||||
} else if (msg.name === "left-foot-translation") {
|
||||
setTranslation(LEFT_FOOT_INDEX, Number(msg.val));
|
||||
} else if (msg.name === "left-foot-rotation") {
|
||||
setRotation(LEFT_FOOT_INDEX, Number(msg.val));
|
||||
} else if (msg.name === "right-foot-translation") {
|
||||
setTranslation(RIGHT_FOOT_INDEX, Number(msg.val));
|
||||
} else if (msg.name === "right-foot-rotation") {
|
||||
setRotation(RIGHT_FOOT_INDEX, Number(msg.val));
|
||||
} else if (msg.name === "hips-translation") {
|
||||
setTranslation(HIPS_INDEX, Number(msg.val));
|
||||
} else if (msg.name === "hips-rotation") {
|
||||
setRotation(HIPS_INDEX, Number(msg.val));
|
||||
} else if (msg.name === "spine2-translation") {
|
||||
setTranslation(SPINE2_INDEX, Number(msg.val));
|
||||
} else if (msg.name === "spine2-rotation") {
|
||||
setRotation(SPINE2_INDEX, Number(msg.val));
|
||||
}
|
||||
}
|
||||
|
||||
tablet.screenChanged.connect(onScreenChanged);
|
||||
|
||||
function shutdownTabletApp() {
|
||||
tablet.removeButton(tabletButton);
|
||||
if (shown) {
|
||||
tablet.webEventReceived.disconnect(onWebEventReceived);
|
||||
tablet.gotoHomeScreen();
|
||||
}
|
||||
tablet.screenChanged.disconnect(onScreenChanged);
|
||||
}
|
||||
|
||||
//
|
||||
// end tablet app boiler plate
|
||||
//
|
||||
|
||||
var filterEnabled = true;
|
||||
var mapping;
|
||||
function mappingChanged() {
|
||||
if (mapping) {
|
||||
mapping.disable();
|
||||
}
|
||||
if (filterEnabled) {
|
||||
mapping = Controller.parseMapping(JSON.stringify(mappingJson));
|
||||
mapping.enable();
|
||||
}
|
||||
}
|
||||
|
||||
function shownChanged(newShown) {
|
||||
if (newShown) {
|
||||
mappingChanged();
|
||||
} else {
|
||||
mapping.disable();
|
||||
}
|
||||
}
|
||||
|
||||
mappingChanged();
|
||||
|
||||
Script.scriptEnding.connect(function() {
|
||||
if (mapping) {
|
||||
mapping.disable();
|
||||
}
|
||||
tablet.removeButton(tabletButton);
|
||||
});
|
||||
|
|
@ -21,16 +21,14 @@ function shutdown() {
|
|||
var BLUE = {x: 0, y: 0, z: 1, w: 1};
|
||||
|
||||
function update(dt) {
|
||||
if (Controller.Hardware.Vive) {
|
||||
TRACKED_OBJECT_POSES.forEach(function (key) {
|
||||
var pose = Controller.getPoseValue(Controller.Standard[key]);
|
||||
if (pose.valid) {
|
||||
DebugDraw.addMyAvatarMarker(key, pose.rotation, pose.translation, BLUE);
|
||||
} else {
|
||||
DebugDraw.removeMyAvatarMarker(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
TRACKED_OBJECT_POSES.forEach(function (key) {
|
||||
var pose = Controller.getPoseValue(Controller.Standard[key]);
|
||||
if (pose.valid) {
|
||||
DebugDraw.addMyAvatarMarker(key, pose.rotation, pose.translation, BLUE);
|
||||
} else {
|
||||
DebugDraw.removeMyAvatarMarker(key);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
init();
|
438
scripts/developer/tests/filtered-puck-attach.js
Normal file
438
scripts/developer/tests/filtered-puck-attach.js
Normal file
|
@ -0,0 +1,438 @@
|
|||
//
|
||||
// Created by Anthony J. Thibault on 2017/06/20
|
||||
// Modified by Robbie Uvanni to support multiple pucks and easier placement of pucks on entities, on 2017/08/01
|
||||
// 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
|
||||
//
|
||||
// When this script is running, a new app button, named "PUCKTACH", will be added to the toolbar/tablet.
|
||||
// Click this app to bring up the puck attachment panel.
|
||||
//
|
||||
|
||||
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
|
||||
/* global Xform */
|
||||
Script.include("/~/system/libraries/Xform.js");
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
|
||||
var TABLET_BUTTON_NAME = "PUCKATTACH";
|
||||
var TABLET_APP_URL = "https://s3.amazonaws.com/hifi-public/tony/html/filtered-puck-attach.html?2";
|
||||
var NUM_TRACKED_OBJECTS = 16;
|
||||
|
||||
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
var tabletButton = tablet.addButton({
|
||||
text: TABLET_BUTTON_NAME,
|
||||
icon: "https://s3.amazonaws.com/hifi-public/tony/icons/puck-i.svg",
|
||||
activeIcon: "https://s3.amazonaws.com/hifi-public/tony/icons/puck-a.svg"
|
||||
});
|
||||
|
||||
var shown = false;
|
||||
function onScreenChanged(type, url) {
|
||||
if (type === "Web" && url === TABLET_APP_URL) {
|
||||
tabletButton.editProperties({isActive: true});
|
||||
if (!shown) {
|
||||
// hook up to event bridge
|
||||
tablet.webEventReceived.connect(onWebEventReceived);
|
||||
shownChanged(true);
|
||||
}
|
||||
shown = true;
|
||||
} else {
|
||||
tabletButton.editProperties({isActive: false});
|
||||
if (shown) {
|
||||
// disconnect from event bridge
|
||||
tablet.webEventReceived.disconnect(onWebEventReceived);
|
||||
shownChanged(false);
|
||||
}
|
||||
shown = false;
|
||||
}
|
||||
}
|
||||
tablet.screenChanged.connect(onScreenChanged);
|
||||
|
||||
function pad(num, size) {
|
||||
var tempString = "000000000" + num;
|
||||
return tempString.substr(tempString.length - size);
|
||||
}
|
||||
function indexToTrackedObjectName(index) {
|
||||
return "TrackedObject" + pad(index, 2);
|
||||
}
|
||||
function getAvailableTrackedObjects() {
|
||||
var available = [];
|
||||
var i;
|
||||
for (i = 0; i < NUM_TRACKED_OBJECTS; i++) {
|
||||
var key = indexToTrackedObjectName(i);
|
||||
var pose = Controller.getPoseValue(Controller.Standard[key]);
|
||||
if (pose && pose.valid) {
|
||||
available.push(i);
|
||||
}
|
||||
}
|
||||
return available;
|
||||
}
|
||||
function sendAvailableTrackedObjects() {
|
||||
tablet.emitScriptEvent(JSON.stringify({
|
||||
pucks: getAvailableTrackedObjects(),
|
||||
selectedPuck: ((lastPuck === undefined) ? -1 : lastPuck.name)
|
||||
}));
|
||||
}
|
||||
|
||||
function getRelativePosition(origin, rotation, offset) {
|
||||
var relativeOffset = Vec3.multiplyQbyV(rotation, offset);
|
||||
var worldPosition = Vec3.sum(origin, relativeOffset);
|
||||
return worldPosition;
|
||||
}
|
||||
function getPropertyForEntity(entityID, propertyName) {
|
||||
return Entities.getEntityProperties(entityID, [propertyName])[propertyName];
|
||||
}
|
||||
function entityExists(entityID) {
|
||||
return Object.keys(Entities.getEntityProperties(entityID)).length > 0;
|
||||
}
|
||||
|
||||
var VIVE_PUCK_MODEL = "https://s3.amazonaws.com/hifi-public/tony/vive_tracker_puck_y180z180.obj";
|
||||
var VIVE_PUCK_DIMENSIONS = { x: 0.0945, y: 0.0921, z: 0.0423 }; // 1/1000th scale of model
|
||||
var VIVE_PUCK_SEARCH_DISTANCE = 1.5; // metres
|
||||
var VIVE_PUCK_SPAWN_DISTANCE = 0.5; // metres
|
||||
var VIVE_PUCK_TRACKED_OBJECT_MAX_DISTANCE = 10.0; // metres
|
||||
var VIVE_PUCK_NAME = "Tracked Puck";
|
||||
|
||||
var trackedPucks = { };
|
||||
var lastPuck;
|
||||
|
||||
var DEFAULT_ROTATION_SMOOTHING_CONSTANT = 1.0; // no smoothing
|
||||
var DEFAULT_TRANSLATION_SMOOTHING_CONSTANT = 1.0; // no smoothing
|
||||
var DEFAULT_TRANSLATION_ACCELERATION_LIMIT = 1000; // only extreme accelerations are smoothed
|
||||
var DEFAULT_ROTATION_ACCELERATION_LIMIT = 10000; // only extreme accelerations are smoothed
|
||||
var DEFAULT_TRANSLATION_SNAP_THRESHOLD = 0; // no snapping
|
||||
var DEFAULT_ROTATION_SNAP_THRESHOLD = 0; // no snapping
|
||||
|
||||
function buildMappingJson() {
|
||||
var obj = {name: "com.highfidelity.testing.filteredPuckAttach", channels: []};
|
||||
var i;
|
||||
for (i = 0; i < NUM_TRACKED_OBJECTS; i++) {
|
||||
obj.channels.push({
|
||||
from: "Vive." + indexToTrackedObjectName(i),
|
||||
to: "Standard." + indexToTrackedObjectName(i),
|
||||
filters: [
|
||||
{
|
||||
type: "accelerationLimiter",
|
||||
translationAccelerationLimit: DEFAULT_TRANSLATION_ACCELERATION_LIMIT,
|
||||
rotationAccelerationLimit: DEFAULT_ROTATION_ACCELERATION_LIMIT,
|
||||
translationSnapThreshold: DEFAULT_TRANSLATION_SNAP_THRESHOLD,
|
||||
rotationSnapThreshold: DEFAULT_ROTATION_SNAP_THRESHOLD,
|
||||
},
|
||||
{
|
||||
type: "exponentialSmoothing",
|
||||
translation: DEFAULT_TRANSLATION_SMOOTHING_CONSTANT,
|
||||
rotation: DEFAULT_ROTATION_SMOOTHING_CONSTANT
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
var mappingJson = buildMappingJson();
|
||||
|
||||
var mapping;
|
||||
function mappingChanged() {
|
||||
if (mapping) {
|
||||
mapping.disable();
|
||||
}
|
||||
mapping = Controller.parseMapping(JSON.stringify(mappingJson));
|
||||
mapping.enable();
|
||||
}
|
||||
|
||||
function shownChanged(newShown) {
|
||||
if (newShown) {
|
||||
mappingChanged();
|
||||
} else {
|
||||
mapping.disable();
|
||||
}
|
||||
}
|
||||
|
||||
function setTranslationAccelerationLimit(value) {
|
||||
var i;
|
||||
for (i = 0; i < NUM_TRACKED_OBJECTS; i++) {
|
||||
mappingJson.channels[i].filters[0].translationAccelerationLimit = value;
|
||||
}
|
||||
mappingChanged();
|
||||
}
|
||||
|
||||
function setTranslationSnapThreshold(value) {
|
||||
// convert from mm
|
||||
var MM_PER_M = 1000;
|
||||
var meters = value / MM_PER_M;
|
||||
var i;
|
||||
for (i = 0; i < NUM_TRACKED_OBJECTS; i++) {
|
||||
mappingJson.channels[i].filters[0].translationSnapThreshold = meters;
|
||||
}
|
||||
mappingChanged();
|
||||
}
|
||||
|
||||
function setRotationAccelerationLimit(value) {
|
||||
var i;
|
||||
for (i = 0; i < NUM_TRACKED_OBJECTS; i++) {
|
||||
mappingJson.channels[i].filters[0].rotationAccelerationLimit = value;
|
||||
}
|
||||
mappingChanged();
|
||||
}
|
||||
|
||||
function setRotationSnapThreshold(value) {
|
||||
// convert from degrees
|
||||
var PI_IN_DEGREES = 180;
|
||||
var radians = value * (Math.pi / PI_IN_DEGREES);
|
||||
var i;
|
||||
for (i = 0; i < NUM_TRACKED_OBJECTS; i++) {
|
||||
mappingJson.channels[i].filters[0].translationSnapThreshold = radians;
|
||||
}
|
||||
mappingChanged();
|
||||
}
|
||||
|
||||
function setTranslationSmoothingConstant(value) {
|
||||
var i;
|
||||
for (i = 0; i < NUM_TRACKED_OBJECTS; i++) {
|
||||
mappingJson.channels[i].filters[1].translation = value;
|
||||
}
|
||||
mappingChanged();
|
||||
}
|
||||
|
||||
function setRotationSmoothingConstant(value) {
|
||||
var i;
|
||||
for (i = 0; i < NUM_TRACKED_OBJECTS; i++) {
|
||||
mappingJson.channels[i].filters[1].rotation = value;
|
||||
}
|
||||
mappingChanged();
|
||||
}
|
||||
|
||||
|
||||
function createPuck(puck) {
|
||||
// create a puck entity and add it to our list of pucks
|
||||
var action = indexToTrackedObjectName(puck.puckno);
|
||||
var pose = Controller.getPoseValue(Controller.Standard[action]);
|
||||
|
||||
if (pose && pose.valid) {
|
||||
var spawnOffset = Vec3.multiply(Vec3.FRONT, VIVE_PUCK_SPAWN_DISTANCE);
|
||||
var spawnPosition = getRelativePosition(MyAvatar.position, MyAvatar.orientation, spawnOffset);
|
||||
|
||||
// should be an overlay
|
||||
var puckEntityProperties = {
|
||||
name: "Tracked Puck",
|
||||
type: "Model",
|
||||
modelURL: VIVE_PUCK_MODEL,
|
||||
dimensions: VIVE_PUCK_DIMENSIONS,
|
||||
position: spawnPosition,
|
||||
userData: '{ "grabbableKey": { "grabbable": true, "kinematic": false } }'
|
||||
};
|
||||
|
||||
var puckEntityID = Entities.addEntity(puckEntityProperties);
|
||||
|
||||
// if we've already created this puck, destroy it
|
||||
if (trackedPucks.hasOwnProperty(puck.puckno)) {
|
||||
destroyPuck(puck.puckno);
|
||||
}
|
||||
// if we had an unfinalized puck, destroy it
|
||||
if (lastPuck !== undefined) {
|
||||
destroyPuck(lastPuck.name);
|
||||
}
|
||||
// create our new unfinalized puck
|
||||
trackedPucks[puck.puckno] = {
|
||||
puckEntityID: puckEntityID,
|
||||
trackedEntityID: ""
|
||||
};
|
||||
lastPuck = trackedPucks[puck.puckno];
|
||||
lastPuck.name = Number(puck.puckno);
|
||||
}
|
||||
}
|
||||
function finalizePuck(puckName) {
|
||||
// find nearest entity and change its parent to the puck
|
||||
|
||||
if (!trackedPucks.hasOwnProperty(puckName)) {
|
||||
print('2');
|
||||
return;
|
||||
}
|
||||
if (lastPuck === undefined) {
|
||||
print('3');
|
||||
return;
|
||||
}
|
||||
if (lastPuck.name !== Number(puckName)) {
|
||||
print('1');
|
||||
return;
|
||||
}
|
||||
|
||||
var puckPosition = getPropertyForEntity(lastPuck.puckEntityID, "position");
|
||||
var foundEntities = Entities.findEntities(puckPosition, VIVE_PUCK_SEARCH_DISTANCE);
|
||||
|
||||
var foundEntity;
|
||||
var leastDistance = Number.MAX_VALUE;
|
||||
|
||||
for (var i = 0; i < foundEntities.length; i++) {
|
||||
var entity = foundEntities[i];
|
||||
|
||||
if (getPropertyForEntity(entity, "name") !== VIVE_PUCK_NAME) {
|
||||
var entityPosition = getPropertyForEntity(entity, "position");
|
||||
var d = Vec3.distance(entityPosition, puckPosition);
|
||||
|
||||
if (d < leastDistance) {
|
||||
leastDistance = d;
|
||||
foundEntity = entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (foundEntity) {
|
||||
lastPuck.trackedEntityID = foundEntity;
|
||||
// remember the userdata and collisionless flag for the tracked entity since
|
||||
// we're about to remove it and make it ungrabbable and collisionless
|
||||
lastPuck.trackedEntityUserData = getPropertyForEntity(foundEntity, "userData");
|
||||
lastPuck.trackedEntityCollisionFlag = getPropertyForEntity(foundEntity, "collisionless");
|
||||
// update properties of the tracked entity
|
||||
Entities.editEntity(lastPuck.trackedEntityID, {
|
||||
"parentID": lastPuck.puckEntityID,
|
||||
"userData": '{ "grabbableKey": { "grabbable": false } }',
|
||||
"collisionless": 1
|
||||
});
|
||||
// remove reference to puck since it is now calibrated and finalized
|
||||
lastPuck = undefined;
|
||||
}
|
||||
}
|
||||
function updatePucks() {
|
||||
// for each puck, update its position and orientation
|
||||
for (var puckName in trackedPucks) {
|
||||
if (!trackedPucks.hasOwnProperty(puckName)) {
|
||||
continue;
|
||||
}
|
||||
var action = indexToTrackedObjectName(puckName);
|
||||
var pose = Controller.getPoseValue(Controller.Standard[action]);
|
||||
if (pose && pose.valid) {
|
||||
var puck = trackedPucks[puckName];
|
||||
if (puck.trackedEntityID) {
|
||||
if (entityExists(puck.trackedEntityID)) {
|
||||
var avatarXform = new Xform(MyAvatar.orientation, MyAvatar.position);
|
||||
var puckXform = new Xform(pose.rotation, pose.translation);
|
||||
var finalXform = Xform.mul(avatarXform, puckXform);
|
||||
|
||||
var d = Vec3.distance(MyAvatar.position, finalXform.pos);
|
||||
if (d > VIVE_PUCK_TRACKED_OBJECT_MAX_DISTANCE) {
|
||||
print('tried to move tracked object too far away: ' + d);
|
||||
return;
|
||||
}
|
||||
|
||||
Entities.editEntity(puck.puckEntityID, {
|
||||
position: finalXform.pos,
|
||||
rotation: finalXform.rot
|
||||
});
|
||||
|
||||
// in case someone grabbed both entities and destroyed the
|
||||
// child/parent relationship
|
||||
Entities.editEntity(puck.trackedEntityID, {
|
||||
parentID: puck.puckEntityID
|
||||
});
|
||||
} else {
|
||||
destroyPuck(puckName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
function destroyPuck(puckName) {
|
||||
// unparent entity and delete its parent
|
||||
if (!trackedPucks.hasOwnProperty(puckName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var puck = trackedPucks[puckName];
|
||||
var puckEntityID = puck.puckEntityID;
|
||||
var trackedEntityID = puck.trackedEntityID;
|
||||
|
||||
// remove the puck as a parent entity and restore the tracked entities
|
||||
// former userdata and collision flag
|
||||
Entities.editEntity(trackedEntityID, {
|
||||
"parentID": "{00000000-0000-0000-0000-000000000000}",
|
||||
"userData": puck.trackedEntityUserData,
|
||||
"collisionless": puck.trackedEntityCollisionFlag
|
||||
});
|
||||
|
||||
delete trackedPucks[puckName];
|
||||
|
||||
// in some cases, the entity deletion may occur before the parent change
|
||||
// has been processed, resulting in both the puck and the tracked entity
|
||||
// to be deleted so we wait 100ms before deleting the puck, assuming
|
||||
// that the parent change has occured
|
||||
var DELETE_TIMEOUT = 100; // ms
|
||||
Script.setTimeout(function() {
|
||||
// delete the puck
|
||||
Entities.deleteEntity(puckEntityID);
|
||||
}, DELETE_TIMEOUT);
|
||||
}
|
||||
function destroyPucks() {
|
||||
// remove all pucks and unparent entities
|
||||
for (var puckName in trackedPucks) {
|
||||
if (trackedPucks.hasOwnProperty(puckName)) {
|
||||
destroyPuck(puckName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onWebEventReceived(msg) {
|
||||
var obj = {};
|
||||
|
||||
try {
|
||||
obj = JSON.parse(msg);
|
||||
} catch (err) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (obj.cmd) {
|
||||
case "ready":
|
||||
sendAvailableTrackedObjects();
|
||||
break;
|
||||
case "create":
|
||||
createPuck(obj);
|
||||
break;
|
||||
case "finalize":
|
||||
finalizePuck(obj.puckno);
|
||||
break;
|
||||
case "destroy":
|
||||
destroyPuck(obj.puckno);
|
||||
break;
|
||||
case "translation-acceleration-limit":
|
||||
setTranslationAccelerationLimit(Number(obj.val));
|
||||
break;
|
||||
case "translation-snap-threshold":
|
||||
setTranslationSnapThreshold(Number(obj.val));
|
||||
break;
|
||||
case "rotation-acceleration-limit":
|
||||
setRotationAccelerationLimit(Number(obj.val));
|
||||
break;
|
||||
case "rotation-snap-threshold":
|
||||
setRotationSnapThreshold(Number(obj.val));
|
||||
break;
|
||||
case "translation-smoothing-constant":
|
||||
setTranslationSmoothingConstant(Number(obj.val));
|
||||
break;
|
||||
case "rotation-smoothing-constant":
|
||||
setRotationSmoothingConstant(Number(obj.val));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Script.update.connect(updatePucks);
|
||||
Script.scriptEnding.connect(function () {
|
||||
tablet.removeButton(tabletButton);
|
||||
destroyPucks();
|
||||
if (shown) {
|
||||
tablet.webEventReceived.disconnect(onWebEventReceived);
|
||||
tablet.gotoHomeScreen();
|
||||
}
|
||||
tablet.screenChanged.disconnect(onScreenChanged);
|
||||
if (mapping) {
|
||||
mapping.disable();
|
||||
}
|
||||
});
|
||||
tabletButton.clicked.connect(function () {
|
||||
if (shown) {
|
||||
tablet.gotoHomeScreen();
|
||||
} else {
|
||||
tablet.gotoWebScreen(TABLET_APP_URL);
|
||||
}
|
||||
});
|
||||
}()); // END LOCAL_SCOPE
|
|
@ -85,7 +85,7 @@ function entityExists(entityID) {
|
|||
return Object.keys(Entities.getEntityProperties(entityID)).length > 0;
|
||||
}
|
||||
|
||||
var VIVE_PUCK_MODEL = "http://content.highfidelity.com/seefo/production/puck-attach/vive_tracker_puck.obj";
|
||||
var VIVE_PUCK_MODEL = "https://s3.amazonaws.com/hifi-public/tony/vive_tracker_puck_y180z180.obj";
|
||||
var VIVE_PUCK_DIMENSIONS = { x: 0.0945, y: 0.0921, z: 0.0423 }; // 1/1000th scale of model
|
||||
var VIVE_PUCK_SEARCH_DISTANCE = 1.5; // metres
|
||||
var VIVE_PUCK_SPAWN_DISTANCE = 0.5; // metres
|
||||
|
@ -304,4 +304,4 @@ tabletButton.clicked.connect(function () {
|
|||
tablet.gotoWebScreen(TABLET_APP_URL);
|
||||
}
|
||||
});
|
||||
}()); // END LOCAL_SCOPE
|
||||
}()); // END LOCAL_SCOPE
|
||||
|
|
500
scripts/system/assets/data/createAppTooltips.json
Normal file
500
scripts/system/assets/data/createAppTooltips.json
Normal file
|
@ -0,0 +1,500 @@
|
|||
{
|
||||
"shape": {
|
||||
"tooltip": "The shape of this entity's geometry."
|
||||
},
|
||||
"color": {
|
||||
"tooltip": "The RGB value of this entity."
|
||||
},
|
||||
"text": {
|
||||
"tooltip": "The text to display on the entity."
|
||||
},
|
||||
"textColor": {
|
||||
"tooltip": "The color of the text."
|
||||
},
|
||||
"backgroundColor": {
|
||||
"tooltip": "The color of the background."
|
||||
},
|
||||
"lineHeight": {
|
||||
"tooltip": "The height of each line of text. This determines the size of the text."
|
||||
},
|
||||
"faceCamera": {
|
||||
"tooltip": "If enabled, the entity follows the camera of each user, creating a billboard effect."
|
||||
},
|
||||
"flyingAllowed": {
|
||||
"tooltip": "If enabled, users can fly in the zone."
|
||||
},
|
||||
"ghostingAllowed": {
|
||||
"tooltip": "If enabled, users with avatar collisions turned off will not collide with content in the zone."
|
||||
},
|
||||
"filterURL": {
|
||||
"tooltip": "The URL of a JS file that checks for changes to entity properties within the zone. Runs periodically."
|
||||
},
|
||||
"keyLightMode": {
|
||||
"tooltip": "Configures the key light in the zone. This light is directional."
|
||||
},
|
||||
"keyLight.color": {
|
||||
"tooltip": "The color of the key light."
|
||||
},
|
||||
"keyLight.intensity": {
|
||||
"tooltip": "The intensity of the key light."
|
||||
},
|
||||
"keyLight.direction.y": {
|
||||
"tooltip": "The angle in deg at which light emits. Starts in the entity's -z direction, and rotates around its y axis."
|
||||
},
|
||||
"keyLight.direction.x": {
|
||||
"tooltip": "The angle in deg at which light emits. Starts in the entity's -z direction, and rotates around its x axis."
|
||||
},
|
||||
"keyLight.castShadows": {
|
||||
"tooltip": "If enabled, shadows are cast. The entity or avatar casting the shadow must also have Cast Shadows enabled."
|
||||
},
|
||||
"skyboxMode": {
|
||||
"tooltip": "Configures the skybox in the zone. The skybox is a cube map image."
|
||||
},
|
||||
"skybox.color": {
|
||||
"tooltip": "If the URL is blank, this changes the color of the sky, otherwise it modifies the color of the skybox."
|
||||
},
|
||||
"skybox.url": {
|
||||
"tooltip": "A cube map image that is used to render the sky."
|
||||
},
|
||||
"ambientLightMode": {
|
||||
"tooltip": "Configures the ambient light in the zone. Use this if you want your skybox to reflect light on the content."
|
||||
},
|
||||
"ambientLight.ambientIntensity": {
|
||||
"tooltip": "The intensity of the ambient light."
|
||||
},
|
||||
"ambientLight.ambientURL": {
|
||||
"tooltip": "A cube map image that defines the color of the light coming from each direction."
|
||||
},
|
||||
"hazeMode": {
|
||||
"tooltip": "Configures the haze in the scene."
|
||||
},
|
||||
"haze.hazeRange": {
|
||||
"tooltip": "How far the haze extends out. This is measured in meters."
|
||||
},
|
||||
"haze.hazeAltitudeEffect": {
|
||||
"tooltip": "If enabled, this adjusts the haze intensity as it gets higher."
|
||||
},
|
||||
"haze.hazeBaseRef": {
|
||||
"tooltip": "The base of the altitude range. Measured in entity space."
|
||||
},
|
||||
"haze.hazeCeiling": {
|
||||
"tooltip": "The ceiling of the altitude range. Measured in entity space."
|
||||
},
|
||||
"haze.hazeColor": {
|
||||
"tooltip": "The color of the haze."
|
||||
},
|
||||
"haze.hazeBackgroundBlend": {
|
||||
"tooltip": "How much the skybox shows through the haze. The higher the value, the more it shows through."
|
||||
},
|
||||
"haze.hazeEnableGlare": {
|
||||
"tooltip": "If enabled, a glare is enabled on the skybox, based on the key light."
|
||||
},
|
||||
"haze.hazeGlareColor": {
|
||||
"tooltip": "The color of the glare based on the key light."
|
||||
},
|
||||
"haze.hazeGlareAngle": {
|
||||
"tooltip": "The angular size of the glare and how much it encompasses the skybox, based on the key light."
|
||||
},
|
||||
"bloomMode": {
|
||||
"tooltip": "Configures how much bright areas of the scene glow."
|
||||
},
|
||||
"bloom.bloomIntensity": {
|
||||
"tooltip": "The intensity, or brightness, of the bloom effect."
|
||||
},
|
||||
"bloom.bloomThreshold": {
|
||||
"tooltip": "The cutoff of the bloom. The higher the value, the more only bright areas of the scene will glow."
|
||||
},
|
||||
"bloom.bloomSize": {
|
||||
"tooltip": "The radius of bloom. The higher the value, the larger the bloom."
|
||||
},
|
||||
"modelURL": {
|
||||
"tooltip": "A mesh model from an FBX or OBJ file."
|
||||
},
|
||||
"shapeType": {
|
||||
"tooltip": "The shape of the collision hull used if collisions are enabled. This affects how an entity collides."
|
||||
},
|
||||
"compoundShapeURL": {
|
||||
"tooltip": "The OBJ file to use for the compound shape if Collision Shape is \"compound\"."
|
||||
},
|
||||
"animation.url": {
|
||||
"tooltip": "An animation to play on the model."
|
||||
},
|
||||
"animation.running": {
|
||||
"tooltip": "If enabled, the animation on the model will play automatically."
|
||||
},
|
||||
"animation.allowTranslation": {
|
||||
"tooltip": "If enabled, this allows an entity to move in space during an animation."
|
||||
},
|
||||
"animation.loop": {
|
||||
"tooltip": "If enabled, then the animation will continuously repeat."
|
||||
},
|
||||
"animation.hold": {
|
||||
"tooltip": "If enabled, then rotations and translations of the last frame played are maintained when the animation stops."
|
||||
},
|
||||
"animation.currentFrame": {
|
||||
"tooltip": "The current frame being played in the animation."
|
||||
},
|
||||
"animation.firstFrame": {
|
||||
"tooltip": "The first frame to play in the animation."
|
||||
},
|
||||
"animation.lastFrame": {
|
||||
"tooltip": "The last frame to play in the animation."
|
||||
},
|
||||
"animation.fps": {
|
||||
"tooltip": "The speed of the animation."
|
||||
},
|
||||
"textures": {
|
||||
"tooltip": "A JSON string containing a texture. Use a name from the Original Texture property to override it."
|
||||
},
|
||||
"originalTextures": {
|
||||
"tooltip": "A JSON string containing the original texture used on the model."
|
||||
},
|
||||
"image": {
|
||||
"tooltip": "The URL for the image source.",
|
||||
"jsPropertyName": "textures"
|
||||
},
|
||||
"sourceUrl": {
|
||||
"tooltip": "The URL for the web page source."
|
||||
},
|
||||
"dpi": {
|
||||
"tooltip": "The resolution to display the page at, in pixels per inch. Use this to resize your web source in the frame."
|
||||
},
|
||||
"isEmitting": {
|
||||
"tooltip": "If enabled, then particles are emitted."
|
||||
},
|
||||
"lifespan": {
|
||||
"tooltip": "How long each particle lives, measured in seconds."
|
||||
},
|
||||
"maxParticles": {
|
||||
"tooltip": "The maximum number of particles to render at one time. Older particles are swapped out for new ones."
|
||||
},
|
||||
"particleTextures": {
|
||||
"tooltip": "The URL of a JPG or PNG image file to display for each particle.",
|
||||
"jsPropertyName": "textures"
|
||||
},
|
||||
"emitRate": {
|
||||
"tooltip": "The number of particles per second to emit."
|
||||
},
|
||||
"emitSpeed": {
|
||||
"tooltip": "The speed that each particle is emitted at, measured in m/s."
|
||||
},
|
||||
"speedSpread": {
|
||||
"tooltip": "The spread in speeds at which particles are emitted at, resulting in a variety of speeds."
|
||||
},
|
||||
"emitDimensions": {
|
||||
"tooltip": "The outer limit radius in dimensions that the particles can be emitted from."
|
||||
},
|
||||
"emitOrientation": {
|
||||
"tooltip": "The orientation of particle emission relative to the entity's axes."
|
||||
},
|
||||
"emitRadiusStart": {
|
||||
"tooltip": "The inner limit radius in dimensions that the particles start emitting from."
|
||||
},
|
||||
"emitterShouldTrail": {
|
||||
"tooltip": "If enabled, then particles are \"left behind\" as the emitter moves, otherwise they are not."
|
||||
},
|
||||
"particleRadius": {
|
||||
"tooltip": "The size of each particle."
|
||||
},
|
||||
"radiusStart": {
|
||||
"tooltip": ""
|
||||
},
|
||||
"radiusFinish": {
|
||||
"tooltip": ""
|
||||
},
|
||||
"radiusSpread": {
|
||||
"tooltip": "The spread in size that each particle is given, resulting in a variety of sizes."
|
||||
},
|
||||
"particleColor": {
|
||||
"tooltip": "The color of each particle.",
|
||||
"jsPropertyName": "color"
|
||||
},
|
||||
"colorSpread": {
|
||||
"tooltip": "The spread in color that each particle is given, resulting in a variety of colors."
|
||||
},
|
||||
"alpha": {
|
||||
"tooltip": "The alpha of each particle."
|
||||
},
|
||||
"alphaStart": {
|
||||
"tooltip": ""
|
||||
},
|
||||
"alphaFinish": {
|
||||
"tooltip": ""
|
||||
},
|
||||
"alphaSpread": {
|
||||
"tooltip": "The spread in alpha that each particle is given, resulting in a variety of alphas."
|
||||
},
|
||||
"emitAcceleration": {
|
||||
"tooltip": "The acceleration that is applied to each particle during its lifetime."
|
||||
},
|
||||
"accelerationSpread": {
|
||||
"tooltip": "The spread in accelerations that each particle is given, resulting in a variety of accelerations."
|
||||
},
|
||||
"particleSpin": {
|
||||
"tooltip": "The spin of each particle in the system."
|
||||
},
|
||||
"spinStart": {
|
||||
"tooltip": ""
|
||||
},
|
||||
"spinFinish": {
|
||||
"tooltip": ""
|
||||
},
|
||||
"spinSpread": {
|
||||
"tooltip": "The spread in spin that each particle is given, resulting in a variety of spins."
|
||||
},
|
||||
"rotateWithEntity": {
|
||||
"tooltip": "If enabled, each particle will spin relative to the roation of the entity as a whole."
|
||||
},
|
||||
"polarStart": {
|
||||
"tooltip": "The angle in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis."
|
||||
},
|
||||
"polarFinish": {
|
||||
"tooltip": ""
|
||||
},
|
||||
"azimuthStart": {
|
||||
"tooltip": "The angle in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis."
|
||||
},
|
||||
"azimuthFinish": {
|
||||
"tooltip": ""
|
||||
},
|
||||
"lightColor": {
|
||||
"tooltip": "The color of the light emitted.",
|
||||
"jsPropertyName": "color"
|
||||
},
|
||||
"intensity": {
|
||||
"tooltip": "The brightness of the light."
|
||||
},
|
||||
"falloffRadius": {
|
||||
"tooltip": "The distance from the light's center where the intensity is reduced."
|
||||
},
|
||||
"isSpotlight": {
|
||||
"tooltip": "If enabled, then the light is directional, otherwise the light is a point light which emits light in all directions."
|
||||
},
|
||||
"exponent": {
|
||||
"tooltip": "Affects the softness of the spotlight beam; the higher the value, the softer the beam."
|
||||
},
|
||||
"cutoff": {
|
||||
"tooltip": "Affects the size of the spotlight beam; the higher the value, the larger the beam."
|
||||
},
|
||||
"materialURL": {
|
||||
"tooltip": "The URL to an external JSON file or \"materialData\", \"materialData?<material name> to use Material Data."
|
||||
},
|
||||
"materialData": {
|
||||
"tooltip": "Can be used instead of a JSON file when material set to materialData."
|
||||
},
|
||||
"materialNameToReplace": {
|
||||
"tooltip": "Material name of parent entity to map this material entity on.",
|
||||
"jsPropertyName": "parentMaterialName"
|
||||
},
|
||||
"submeshToReplace": {
|
||||
"tooltip": "Submesh index of the parent entity to map this material on.",
|
||||
"jsPropertyName": "parentMaterialName"
|
||||
},
|
||||
"selectSubmesh": {
|
||||
"tooltip": "If enabled, \"Select Submesh\" property will show up, otherwise \"Material Name to Replace\" will be shown.",
|
||||
"skipJSProperty": true
|
||||
},
|
||||
"priority": {
|
||||
"tooltip": "The priority of the material, where a larger number means higher priority. Original materials = 0."
|
||||
},
|
||||
"materialMappingPos": {
|
||||
"tooltip": "The offset position of the bottom left of the material within the parent's UV space."
|
||||
},
|
||||
"materialMappingScale": {
|
||||
"tooltip": "How many times the material will repeat in each direction within the parent's UV space."
|
||||
},
|
||||
"materialMappingRot": {
|
||||
"tooltip": "How much to rotate the material within the parent's UV-space, in degrees."
|
||||
},
|
||||
"id": {
|
||||
"tooltip": "The unique identifier of this entity."
|
||||
},
|
||||
"name": {
|
||||
"tooltip": "The name of this entity."
|
||||
},
|
||||
"description": {
|
||||
"tooltip": "Use this field to describe the entity."
|
||||
},
|
||||
"position": {
|
||||
"tooltip": "The global position of this entity."
|
||||
},
|
||||
"rotation": {
|
||||
"tooltip": "The rotation of the entity with respect to world coordinates."
|
||||
},
|
||||
"dimensions": {
|
||||
"tooltip": "The global dimensions of this entity."
|
||||
},
|
||||
"scale": {
|
||||
"tooltip": "The global scaling of this entity.",
|
||||
"skipJSProperty": true
|
||||
},
|
||||
"registrationPoint": {
|
||||
"tooltip": "The point in the entity at which the entity is rotated about."
|
||||
},
|
||||
"visible": {
|
||||
"tooltip": "If enabled, this entity will be visible."
|
||||
},
|
||||
"locked": {
|
||||
"tooltip": "If enabled, this entity will be locked."
|
||||
},
|
||||
"collisionless": {
|
||||
"tooltip": "If enabled, this entity will collide with other entities or avatars."
|
||||
},
|
||||
"dynamic": {
|
||||
"tooltip": "If enabled, this entity has collisions associated with it that can affect its movement."
|
||||
},
|
||||
"collidesWithStatic": {
|
||||
"tooltip": "If enabled, this entity will collide with other non-moving, static entities.",
|
||||
"jsPropertyName": "collidesWith"
|
||||
},
|
||||
"collidesWithDynamic": {
|
||||
"tooltip": "If enabled, this entity will collide with other dynamic entities.",
|
||||
"jsPropertyName": "collidesWith"
|
||||
},
|
||||
"collidesWithKinematic": {
|
||||
"tooltip": "If enabled, this entity will collide with other kinematic entities (they have velocity but are not dynamic).",
|
||||
"jsPropertyName": "collidesWith"
|
||||
},
|
||||
"collidesWithOtherAvatar": {
|
||||
"tooltip": "If enabled, this entity will collide with other user's avatars.",
|
||||
"jsPropertyName": "collidesWith"
|
||||
},
|
||||
"collidesWithMyAvatar": {
|
||||
"tooltip": "If enabled, this entity will collide with your own avatar.",
|
||||
"jsPropertyName": "collidesWith"
|
||||
},
|
||||
"collisionSoundURL": {
|
||||
"tooltip": "The URL of a sound to play when the entity collides with something else."
|
||||
},
|
||||
"grab.grabbable": {
|
||||
"tooltip": "If enabled, this entity will allow grabbing input and will be moveable."
|
||||
},
|
||||
"grab.triggerable": {
|
||||
"tooltip": "If enabled, the collider on this entity is used for triggering events."
|
||||
},
|
||||
"cloneable": {
|
||||
"tooltip": "If enabled, this entity can be duplicated."
|
||||
},
|
||||
"cloneLifetime": {
|
||||
"tooltip": "The lifetime for clones of this entity."
|
||||
},
|
||||
"cloneLimit": {
|
||||
"tooltip": "The total number of clones of this entity that can exist in the domain at any given time."
|
||||
},
|
||||
"cloneDynamic": {
|
||||
"tooltip": "If enabled, then clones created from this entity will be dynamic, allowing the clone to collide."
|
||||
},
|
||||
"cloneAvatarEntity": {
|
||||
"tooltip": "If enabled, then clones created from this entity will be created as avatar entities."
|
||||
},
|
||||
"grab.grabFollowsController": {
|
||||
"tooltip": "If enabled, grabbed entities will follow the movements of your hand controller instead of your avatar's hand."
|
||||
},
|
||||
"canCastShadow": {
|
||||
"tooltip": "If enabled, this geometry of this entity casts shadows when a shadow-casting light source shines on it."
|
||||
},
|
||||
"parentID": {
|
||||
"tooltip": "The ID of the entity or avatar that this entity is parented to."
|
||||
},
|
||||
"parentJointIndex": {
|
||||
"tooltip": "If the entity is parented to an avatar, this joint defines where on the avatar the entity is parented."
|
||||
},
|
||||
"href": {
|
||||
"tooltip": "The URL that will be opened when a user clicks on this entity. Useful for web pages and portals."
|
||||
},
|
||||
"script": {
|
||||
"tooltip": "The URL to an external JS file to add behaviors to the client."
|
||||
},
|
||||
"serverScripts": {
|
||||
"tooltip": "The URL to an external JS file to add behaviors to the server."
|
||||
},
|
||||
"serverScriptsStatus": {
|
||||
"tooltip": "The status of the server script, if provided. This shows if it's running or has an error.",
|
||||
"skipJSProperty": true
|
||||
},
|
||||
"hasLifetime": {
|
||||
"tooltip": "If enabled, the entity will disappear after a certain amount of time specified by Lifetime.",
|
||||
"jsPropertyName": "lifetime"
|
||||
},
|
||||
"lifetime": {
|
||||
"tooltip": "The time this entity will exist in the environment for."
|
||||
},
|
||||
"userData": {
|
||||
"tooltip": "Used to store extra data about the entity in JSON format."
|
||||
},
|
||||
"velocity": {
|
||||
"tooltip": "The linear velocity vector of the entity. The velocity at which this entity moves forward in space."
|
||||
},
|
||||
"damping": {
|
||||
"tooltip": "The linear damping to slow down the linear velocity of an entity over time."
|
||||
},
|
||||
"angularVelocity": {
|
||||
"tooltip": "The angular velocity of the entity in rad/s with respect to its axes, about its pivot point."
|
||||
},
|
||||
"angularDamping": {
|
||||
"tooltip": "The angular damping to slow down the angular velocity of an entity over time."
|
||||
},
|
||||
"restitution": {
|
||||
"tooltip": "If enabled, the entity can bounce against other objects that also have Bounciness."
|
||||
},
|
||||
"friction": {
|
||||
"tooltip": "The friction applied to slow down an entity when it's moving against another entity."
|
||||
},
|
||||
"density": {
|
||||
"tooltip": "The density of the entity. The higher the density, the harder the entity is to move."
|
||||
},
|
||||
"gravity": {
|
||||
"tooltip": "The acceleration due to gravity that the entity should move with, in world space."
|
||||
},
|
||||
"acceleration": {
|
||||
"tooltip": "A acceleration that the entity should move with, in world space."
|
||||
},
|
||||
"alignToGrid": {
|
||||
"tooltip": "Used to align entities to the grid, or floor of the environment.",
|
||||
"skipJSProperty": true
|
||||
},
|
||||
"createModel": {
|
||||
"tooltip": "An entity that is based on a custom mesh created from an .OBJ or .FBX.",
|
||||
"skipJSProperty": true
|
||||
},
|
||||
"createShape": {
|
||||
"tooltip": "An entity that has many different primitive shapes.",
|
||||
"skipJSProperty": true
|
||||
},
|
||||
"createLight": {
|
||||
"tooltip": "An entity that emits light.",
|
||||
"skipJSProperty": true
|
||||
},
|
||||
"createText": {
|
||||
"tooltip": "An entity that displays text on a panel.",
|
||||
"skipJSProperty": true
|
||||
},
|
||||
"createImage": {
|
||||
"tooltip": "An entity that displays an image on a panel.",
|
||||
"skipJSProperty": true
|
||||
},
|
||||
"createWeb": {
|
||||
"tooltip": "An entity that displays a web page on a panel.",
|
||||
"skipJSProperty": true
|
||||
},
|
||||
"createZone": {
|
||||
"tooltip": "An entity that can be used for skyboxes, lighting, and can constrain or change avatar behaviors.",
|
||||
"skipJSProperty": true
|
||||
},
|
||||
"createParticle": {
|
||||
"tooltip": "An entity that emits particles.",
|
||||
"skipJSProperty": true
|
||||
},
|
||||
"createMaterial": {
|
||||
"tooltip": "An entity that creates a material that can be attached to a Shape or Model.",
|
||||
"skipJSProperty": true
|
||||
},
|
||||
"useAssetServer": {
|
||||
"tooltip": "A server that hosts content and assets. You can't take items that are hosted here into other domains.",
|
||||
"skipJSProperty": true
|
||||
},
|
||||
"importNewEntity": {
|
||||
"tooltip": "Import a local or hosted file that can be used across domains.",
|
||||
"skipJSProperty": true
|
||||
}
|
||||
}
|
|
@ -167,16 +167,6 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa
|
|||
};
|
||||
|
||||
|
||||
var alreadyWarned = {};
|
||||
function warnAboutUserData(props) {
|
||||
if (alreadyWarned[props.id]) {
|
||||
return;
|
||||
}
|
||||
print("Warning -- overriding grab properties with userData for " + props.id + " / " + props.name);
|
||||
alreadyWarned[props.id] = true;
|
||||
}
|
||||
|
||||
|
||||
(function() {
|
||||
|
||||
var ATTACH_POINT_SETTINGS = "io.highfidelity.attachPoints";
|
||||
|
@ -200,7 +190,6 @@ function warnAboutUserData(props) {
|
|||
|
||||
function getWearableData(props) {
|
||||
if (props.grab.equippable) {
|
||||
// if equippable is true, we know this was already converted from the old userData style to properties
|
||||
return {
|
||||
joints: {
|
||||
LeftHand: [ props.grab.equippableLeftPosition, props.grab.equippableLeftRotation ],
|
||||
|
@ -211,67 +200,7 @@ function warnAboutUserData(props) {
|
|||
indicatorOffset: props.grab.equippableIndicatorOffset
|
||||
};
|
||||
} else {
|
||||
// check for old userData equippability. The JSON reader will convert userData to properties
|
||||
// in EntityTree.cpp, but this won't catch things created from scripts or some items in
|
||||
// the market. Eventually we'll remove this section.
|
||||
try {
|
||||
if (!props.userDataParsed) {
|
||||
props.userDataParsed = JSON.parse(props.userData);
|
||||
}
|
||||
var userDataParsed = props.userDataParsed;
|
||||
|
||||
// userData: { wearable: { joints: { LeftHand: {...}, RightHand: {...} } } }
|
||||
if (userDataParsed.wearable && userDataParsed.wearable.joints) {
|
||||
warnAboutUserData(props);
|
||||
userDataParsed.wearable.indicatorURL = "";
|
||||
userDataParsed.wearable.indicatorScale = { x: 1, y: 1, z: 1 };
|
||||
userDataParsed.wearable.indicatorOffset = { x: 0, y: 0, z: 0 };
|
||||
return userDataParsed.wearable;
|
||||
}
|
||||
|
||||
// userData: { equipHotspots: { joints: { LeftHand: {...}, RightHand: {...} } } }
|
||||
// https://highfidelity.atlassian.net/wiki/spaces/HOME/pages/51085337/Authoring+Equippable+Entities
|
||||
if (userDataParsed.equipHotspots &&
|
||||
userDataParsed.equipHotspots.length > 0 &&
|
||||
userDataParsed.equipHotspots[0].joints) {
|
||||
warnAboutUserData(props);
|
||||
var hotSpot = userDataParsed.equipHotspots[0];
|
||||
|
||||
var indicatorScale = { x: hotSpot.radius, y: hotSpot.radius, z: hotSpot.radius };
|
||||
if (hotSpot.modelURL && hotSpot.modelURL !== "") {
|
||||
indicatorScale = hotSpot.modelScale;
|
||||
}
|
||||
|
||||
return {
|
||||
joints: hotSpot.joints,
|
||||
indicatorURL: hotSpot.modelURL,
|
||||
indicatorScale: indicatorScale,
|
||||
indicatorOffset: hotSpot.position,
|
||||
};
|
||||
}
|
||||
|
||||
// userData:{grabbableKey:{spatialKey:{leftRelativePosition:{...},rightRelativePosition:{...}}}}
|
||||
if (userDataParsed.grabbableKey &&
|
||||
userDataParsed.grabbableKey.spatialKey) {
|
||||
warnAboutUserData(props);
|
||||
var joints = {};
|
||||
joints.LeftHand = [ { x: 0, y: 0, z: 0 }, { x: 0, y: 0, z: 0, w: 1 } ];
|
||||
joints.RightHand = [ { x: 0, y: 0, z: 0 }, { x: 0, y: 0, z: 0, w: 1 } ];
|
||||
if (userDataParsed.grabbableKey.spatialKey.leftRelativePosition) {
|
||||
joints.LeftHand = [userDataParsed.grabbableKey.spatialKey.leftRelativePosition,
|
||||
userDataParsed.grabbableKey.spatialKey.relativeRotation];
|
||||
}
|
||||
if (userDataParsed.grabbableKey.spatialKey.rightRelativePosition) {
|
||||
joints.RightHand = [userDataParsed.grabbableKey.spatialKey.rightRelativePosition,
|
||||
userDataParsed.grabbableKey.spatialKey.relativeRotation];
|
||||
}
|
||||
return { joints: joints };
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
// don't spam logs
|
||||
}
|
||||
return null;
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
/* 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, OverlaySystemWindow */
|
||||
progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, OverlaySystemWindow */
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
|
||||
|
@ -32,7 +32,6 @@ Script.include([
|
|||
"libraries/gridTool.js",
|
||||
"libraries/entityList.js",
|
||||
"libraries/utils.js",
|
||||
"particle_explorer/particleExplorerTool.js",
|
||||
"libraries/entityIconOverlayManager.js"
|
||||
]);
|
||||
|
||||
|
@ -112,28 +111,6 @@ var entityListTool = new EntityListTool(shouldUseEditTabletApp);
|
|||
selectionManager.addEventListener(function () {
|
||||
selectionDisplay.updateHandles();
|
||||
entityIconOverlayManager.updatePositions();
|
||||
|
||||
// Update particle explorer
|
||||
var needToDestroyParticleExplorer = false;
|
||||
if (selectionManager.selections.length === 1) {
|
||||
var selectedEntityID = selectionManager.selections[0];
|
||||
if (selectedEntityID === selectedParticleEntityID) {
|
||||
return;
|
||||
}
|
||||
var type = Entities.getEntityProperties(selectedEntityID, "type").type;
|
||||
if (type === "ParticleEffect") {
|
||||
selectParticleEntity(selectedEntityID);
|
||||
} else {
|
||||
needToDestroyParticleExplorer = true;
|
||||
}
|
||||
} else {
|
||||
needToDestroyParticleExplorer = true;
|
||||
}
|
||||
|
||||
if (needToDestroyParticleExplorer && selectedParticleEntityID !== null) {
|
||||
selectedParticleEntityID = null;
|
||||
particleExplorerTool.destroyWebView();
|
||||
}
|
||||
});
|
||||
|
||||
var KEY_P = 80; //Key code for letter p used for Parenting hotkey.
|
||||
|
@ -580,10 +557,6 @@ var toolBar = (function () {
|
|||
properties: properties
|
||||
}], [], true);
|
||||
|
||||
if (properties.type === "ParticleEffect") {
|
||||
selectParticleEntity(entityID);
|
||||
}
|
||||
|
||||
var POST_ADJUST_ENTITY_TYPES = ["Model"];
|
||||
if (POST_ADJUST_ENTITY_TYPES.indexOf(properties.type) !== -1) {
|
||||
// Adjust position of entity per bounding box after it has been created and auto-resized.
|
||||
|
@ -1314,13 +1287,6 @@ function mouseClickEvent(event) {
|
|||
orientation = MyAvatar.orientation;
|
||||
intersection = rayPlaneIntersection(pickRay, P, Quat.getForward(orientation));
|
||||
|
||||
if (event.isShifted) {
|
||||
particleExplorerTool.destroyWebView();
|
||||
}
|
||||
if (properties.type !== "ParticleEffect") {
|
||||
particleExplorerTool.destroyWebView();
|
||||
}
|
||||
|
||||
if (!event.isShifted) {
|
||||
selectionManager.setSelections([foundEntity], this);
|
||||
} else {
|
||||
|
@ -1739,8 +1705,6 @@ function deleteSelectedEntities() {
|
|||
if (SelectionManager.hasSelection()) {
|
||||
var deletedIDs = [];
|
||||
|
||||
selectedParticleEntityID = null;
|
||||
particleExplorerTool.destroyWebView();
|
||||
SelectionManager.saveProperties();
|
||||
var savedProperties = [];
|
||||
var newSortedSelection = sortSelectedEntities(selectionManager.selections);
|
||||
|
@ -2347,9 +2311,11 @@ var PropertiesTool = function (opts) {
|
|||
if (entity.properties.rotation !== undefined) {
|
||||
entity.properties.rotation = Quat.safeEulerAngles(entity.properties.rotation);
|
||||
}
|
||||
if (entity.properties.emitOrientation !== undefined) {
|
||||
entity.properties.emitOrientation = Quat.safeEulerAngles(entity.properties.emitOrientation);
|
||||
}
|
||||
if (entity.properties.keyLight !== undefined && entity.properties.keyLight.direction !== undefined) {
|
||||
entity.properties.keyLight.direction = Vec3.multiply(RADIANS_TO_DEGREES,
|
||||
Vec3.toPolar(entity.properties.keyLight.direction));
|
||||
entity.properties.keyLight.direction = Vec3.toPolar(entity.properties.keyLight.direction);
|
||||
entity.properties.keyLight.direction.z = 0.0;
|
||||
}
|
||||
selections.push(entity);
|
||||
|
@ -2385,18 +2351,24 @@ var PropertiesTool = function (opts) {
|
|||
data.properties.angularVelocity = Vec3.ZERO;
|
||||
}
|
||||
if (data.properties.rotation !== undefined) {
|
||||
var rotation = data.properties.rotation;
|
||||
data.properties.rotation = Quat.fromPitchYawRollDegrees(rotation.x, rotation.y, rotation.z);
|
||||
data.properties.rotation = Quat.fromVec3Degrees(data.properties.rotation);
|
||||
}
|
||||
if (data.properties.emitOrientation !== undefined) {
|
||||
data.properties.emitOrientation = Quat.fromVec3Degrees(data.properties.emitOrientation);
|
||||
}
|
||||
if (data.properties.keyLight !== undefined && data.properties.keyLight.direction !== undefined) {
|
||||
data.properties.keyLight.direction = Vec3.fromPolar(
|
||||
data.properties.keyLight.direction.x * DEGREES_TO_RADIANS,
|
||||
data.properties.keyLight.direction.y * DEGREES_TO_RADIANS
|
||||
);
|
||||
var currentKeyLightDirection = Vec3.toPolar(Entities.getEntityProperties(selectionManager.selections[0], ['keyLight.direction']).keyLight.direction);
|
||||
if (data.properties.keyLight.direction.x === undefined) {
|
||||
data.properties.keyLight.direction.x = currentKeyLightDirection.x;
|
||||
}
|
||||
if (data.properties.keyLight.direction.y === undefined) {
|
||||
data.properties.keyLight.direction.y = currentKeyLightDirection.y;
|
||||
}
|
||||
data.properties.keyLight.direction = Vec3.fromPolar(data.properties.keyLight.direction.x, data.properties.keyLight.direction.y);
|
||||
}
|
||||
Entities.editEntity(selectionManager.selections[0], data.properties);
|
||||
if (data.properties.name !== undefined || data.properties.modelURL !== undefined || data.properties.materialURL !== undefined ||
|
||||
data.properties.visible !== undefined || data.properties.locked !== undefined) {
|
||||
data.properties.visible !== undefined || data.properties.locked !== undefined) {
|
||||
entityListTool.sendUpdate();
|
||||
}
|
||||
}
|
||||
|
@ -2509,6 +2481,12 @@ var PropertiesTool = function (opts) {
|
|||
}
|
||||
} else if (data.type === "propertiesPageReady") {
|
||||
updateSelections(true);
|
||||
} else if (data.type === "tooltipsRequest") {
|
||||
emitScriptEvent({
|
||||
type: 'tooltipsReply',
|
||||
tooltips: Script.require('./assets/data/createAppTooltips.json'),
|
||||
hmdActive: HMD.active,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -2742,31 +2720,6 @@ propertyMenu.onSelectMenuItem = function (name) {
|
|||
var showMenuItem = propertyMenu.addMenuItem("Show in Marketplace");
|
||||
|
||||
var propertiesTool = new PropertiesTool();
|
||||
var particleExplorerTool = new ParticleExplorerTool(createToolsWindow);
|
||||
var selectedParticleEntityID = null;
|
||||
|
||||
function selectParticleEntity(entityID) {
|
||||
selectedParticleEntityID = entityID;
|
||||
|
||||
var properties = Entities.getEntityProperties(entityID);
|
||||
if (properties.emitOrientation) {
|
||||
properties.emitOrientation = Quat.safeEulerAngles(properties.emitOrientation);
|
||||
}
|
||||
|
||||
particleExplorerTool.destroyWebView();
|
||||
particleExplorerTool.createWebView();
|
||||
|
||||
particleExplorerTool.setActiveParticleEntity(entityID);
|
||||
|
||||
// Switch to particle explorer
|
||||
var selectTabMethod = { method: 'selectTab', params: { id: 'particle' } };
|
||||
if (shouldUseEditTabletApp()) {
|
||||
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
tablet.sendToQml(selectTabMethod);
|
||||
} else {
|
||||
createToolsWindow.sendToQml(selectTabMethod);
|
||||
}
|
||||
}
|
||||
|
||||
entityListTool.webView.webEventReceived.connect(function(data) {
|
||||
try {
|
||||
|
@ -2780,20 +2733,6 @@ entityListTool.webView.webEventReceived.connect(function(data) {
|
|||
parentSelectedEntities();
|
||||
} else if (data.type === 'unparent') {
|
||||
unparentSelectedEntities();
|
||||
} else if (data.type === "selectionUpdate") {
|
||||
var ids = data.entityIds;
|
||||
if (ids.length === 1) {
|
||||
if (Entities.getEntityProperties(ids[0], "type").type === "ParticleEffect") {
|
||||
if (JSON.stringify(selectedParticleEntityID) === JSON.stringify(ids[0])) {
|
||||
// This particle entity is already selected, so return
|
||||
return;
|
||||
}
|
||||
// Destroy the old particles web view first
|
||||
} else {
|
||||
selectedParticleEntityID = 0;
|
||||
particleExplorerTool.destroyWebView();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,4 +1,4 @@
|
|||
<!--
|
||||
<!--
|
||||
// entityProperties.html
|
||||
//
|
||||
// Created by Ryan Huffman on 13 Nov 2014
|
||||
|
@ -20,882 +20,15 @@
|
|||
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
|
||||
<script type="text/javascript" src="js/eventBridgeLoader.js"></script>
|
||||
<script type="text/javascript" src="js/spinButtons.js"></script>
|
||||
<script type="text/javascript" src="js/underscore-min.js"></script>
|
||||
<script type="text/javascript" src="js/createAppTooltip.js"></script>
|
||||
<script type="text/javascript" src="js/entityProperties.js"></script>
|
||||
<script src="js/jsoneditor.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body onload='loaded();'>
|
||||
<div id="properties-list">
|
||||
|
||||
<fieldset id="properties-header">
|
||||
<div id="type" class="property value">
|
||||
<span id="type-icon"></span><label id="property-type"><i>No selection</i></label>
|
||||
</div>
|
||||
<div class="property checkbox">
|
||||
<input type="checkbox" id="property-locked">
|
||||
<label for="property-locked"><span></span> Locked</label>
|
||||
</div>
|
||||
<div class="property checkbox">
|
||||
<input type="checkbox" id="property-visible">
|
||||
<label for="property-visible"><span></span> Visible</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="general" class="major">
|
||||
<div class="shape-group shape-section property dropdown" id="shape-list">
|
||||
<label for="property-shape">Shape</label>
|
||||
<select name="SelectShape" id="property-shape">
|
||||
<option value="Cube">Box</option>
|
||||
<option value="Sphere">Sphere</option>
|
||||
<option value="Tetrahedron">Tetrahedron</option>
|
||||
<option value="Octahedron">Octahedron</option>
|
||||
<option value="Icosahedron">Icosahedron</option>
|
||||
<option value="Dodecahedron">Dodecahedron</option>
|
||||
<option value="Hexagon">Hexagon</option>
|
||||
<option value="Triangle">Triangle</option>
|
||||
<option value="Octagon">Octagon</option>
|
||||
<option value="Cylinder">Cylinder</option>
|
||||
<option value="Cone">Cone</option>
|
||||
<option value="Circle">Circle</option>
|
||||
<option value="Quad">Quad</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="property text">
|
||||
<label for="property-name">Name</label>
|
||||
<input type="text" id="property-name">
|
||||
</div>
|
||||
<div class="physical-group color-section property rgb fstuple" id="base-color-section">
|
||||
<div class="color-picker" id="property-color-control2"></div>
|
||||
<label>Entity color</label>
|
||||
<div class="tuple">
|
||||
<div><input type="number" class="red" id="property-color-red"><label for="property-color-red">Red:</label></div>
|
||||
<div><input type="number" class="green" id="property-color-green"><label for="property-color-green">Green:</label></div>
|
||||
<div><input type="number" class="blue" id="property-color-blue"><label for="property-color-blue">Blue:</label></div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="collision-info" class="major">
|
||||
<legend class="section-header"> Collision<span class=".collapse-icon">M</span> </legend>
|
||||
<fieldset class="minor">
|
||||
<div class="behavior-group property checkbox">
|
||||
<input type="checkbox" id="property-collisionless">
|
||||
<label for="property-collisionless">Collisionless</label>
|
||||
</div>
|
||||
<div class="behavior-group property checkbox">
|
||||
<input type="checkbox" id="property-dynamic">
|
||||
<label for="property-dynamic">Dynamic</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="minor">
|
||||
<div class="behavior-group two-column">
|
||||
<fieldset class="column">
|
||||
<legend class="sub-section-header">
|
||||
Collides With
|
||||
</legend>
|
||||
<div class="checkbox-sub-props">
|
||||
<div class="property checkbox">
|
||||
<input type="checkbox" id="property-collide-static">
|
||||
<label for="property-collide-static">Static entities</label>
|
||||
</div>
|
||||
<div class="property checkbox">
|
||||
<input type="checkbox" id="property-collide-dynamic">
|
||||
<label for="property-collide-dynamic">Dynamic entities</label>
|
||||
</div>
|
||||
<div class="property checkbox">
|
||||
<input type="checkbox" id="property-collide-kinematic">
|
||||
<label for="property-collide-kinematic">Kinematic entities</label>
|
||||
</div>
|
||||
<div class="property checkbox">
|
||||
<input type="checkbox" id="property-collide-myAvatar">
|
||||
<label for="property-collide-myAvatar">My avatar</label>
|
||||
</div>
|
||||
<div class="property checkbox">
|
||||
<input type="checkbox" id="property-collide-otherAvatar">
|
||||
<label for="property-collide-otherAvatar">Other avatars</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="column">
|
||||
<legend class="sub-section-header">
|
||||
Grabbing
|
||||
</legend>
|
||||
<div class="checkbox-sub-props">
|
||||
<div class="property checkbox">
|
||||
<input type="checkbox" id="property-grabbable">
|
||||
<label for="property-grabbable">Grabbable</label>
|
||||
</div>
|
||||
<div class="property checkbox">
|
||||
<input type="checkbox" id="property-triggerable">
|
||||
<label for="property-triggerable">Triggerable</label>
|
||||
</div>
|
||||
<div class="property checkbox">
|
||||
<input type="checkbox" id="property-cloneable">
|
||||
<label for="property-cloneable">Cloneable</label>
|
||||
</div>
|
||||
<div class="property checkbox">
|
||||
<input type="checkbox" id="property-grab-follows-controller">
|
||||
<label for="property-grab-follows-controller">Follow Controller</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset class="column" id="group-cloneable-group" style="display:none;">
|
||||
<legend class="sub-section-header">
|
||||
<span>Cloneable Settings</span>
|
||||
</legend>
|
||||
<fieldset class="minor">
|
||||
<div><label>Clone Lifetime</label><input type="number" data-user-data-type="cloneLifetime" id="property-cloneable-lifetime"></div>
|
||||
</fieldset>
|
||||
<fieldset class="minor">
|
||||
<div><label>Clone Limit</label><input type="number" data-user-data-type="cloneLimit" id="property-cloneable-limit"></div>
|
||||
</fieldset>
|
||||
<div class="property checkbox">
|
||||
<input type="checkbox" id="property-cloneable-dynamic">
|
||||
<label for="property-cloneable-dynamic">Clone Dynamic</label>
|
||||
</div>
|
||||
<div class="property checkbox">
|
||||
<input type="checkbox" id="property-cloneable-avatarEntity">
|
||||
<label for="property-cloneable-avatarEntity">Clone Avatar Entity</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
</fieldset>
|
||||
</fieldset>
|
||||
|
||||
|
||||
<fieldset id="physical" class="major">
|
||||
<legend class="section-header physical-group">
|
||||
Physical<span class=".collapse-icon"> M</span>
|
||||
</legend>
|
||||
<fieldset class="minor">
|
||||
<fieldset class="physical-group property xyz fstuple">
|
||||
<legend>Linear velocity <span class="unit">m/s</span></legend>
|
||||
<div class="tuple">
|
||||
<div><input type="number" class="x" id="property-lvel-x"><label for="property-lvel-x">X:</label></div>
|
||||
<div><input type="number" class="y" id="property-lvel-y"><label for="property-lvel-y">Y:</label></div>
|
||||
<div><input type="number" class="z" id="property-lvel-z"><label for="property-lvel-z">Z:</label></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="physical-group property number">
|
||||
<label>Linear damping</label>
|
||||
<input type="number" id="property-ldamping">
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="minor">
|
||||
<fieldset class="physical-group property pyr fstuple">
|
||||
<legend>Angular velocity <span class="unit">deg/s</span></legend>
|
||||
<div class="tuple">
|
||||
<div><input type="number" class="pitch" id="property-avel-x"><label for="property-avel-x">Pitch:</label></div>
|
||||
<div><input type="number" class="yaw" id="property-avel-y"><label for="property-avel-y">Yaw:</label></div>
|
||||
<div><input type="number" class="roll" id="property-avel-z"><label for="property-avel-z">Roll:</label></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="physical-group property number">
|
||||
<label>Angular damping</label>
|
||||
<input type="number" id="property-adamping">
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="physical-group property gen fstuple">
|
||||
<div class="tuple">
|
||||
<div><label>Restitution</label><input type="number" id="property-restitution"></div>
|
||||
<div><label>Friction</label><input type="number" id="property-friction"></div>
|
||||
<div><label>Density</label><input type="number" id="property-density"></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="minor">
|
||||
<fieldset class="physical-group property xyz fstuple">
|
||||
<legend>Gravity <span class="unit">m/s<sup>2</sup></span></legend>
|
||||
<div class="tuple">
|
||||
<div><input type="number" class="x" id="property-grav-x"><label for="property-grav-x">X:</label></div>
|
||||
<div><input type="number" class="y" id="property-grav-y"><label for="property-grav-y">Y:</label></div>
|
||||
<div><input type="number" class="z" id="property-grav-z"><label for="property-grav-z">Z:</label></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="physical-group property xyz fstuple">
|
||||
<legend>Acceleration <span class="unit">m/s<sup>2</sup></span></legend>
|
||||
<div class="tuple">
|
||||
<div><input type="number" class="x" id="property-lacc-x"><label for="property-lacc-x">X:</label></div>
|
||||
<div><input type="number" class="y" id="property-lacc-y"><label for="property-lacc-y">Y:</label></div>
|
||||
<div><input type="number" class="z" id="property-lacc-z"><label for="property-lacc-z">Z:</label></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</fieldset>
|
||||
</fieldset>
|
||||
|
||||
|
||||
<fieldset id="spatial" class="major">
|
||||
<legend class="section-header spatial-group">
|
||||
Spatial<span class=".collapse-icon" >M</span>
|
||||
</legend>
|
||||
<fieldset class="spatial-group property xyz fstuple">
|
||||
<legend>Position <span class="unit">m</span></legend>
|
||||
<div class="tuple">
|
||||
<div><input type="number" class="x" id="property-pos-x"><label for="property-pos-x">X:</label></div>
|
||||
<div><input type="number" class="y" id="property-pos-y"><label for="property-pos-y">Y:</label></div>
|
||||
<div><input type="number" class="z" id="property-pos-z"><label for="property-pos-z">Z:</label></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="spatial-group property pyr fstuple">
|
||||
<legend>Rotation <span class="unit">deg</span></legend>
|
||||
<div class="tuple">
|
||||
<div><input type="number" class="pitch" id="property-rot-x" step="0.1"><label for="property-rot-x">Pitch:</label></div>
|
||||
<div><input type="number" class="yaw" id="property-rot-y" step="0.1"><label for="property-rot-y">Yaw:</label></div>
|
||||
<div><input type="number" class="roll" id="property-rot-z" step="0.1"><label for="property-rot-z">Roll:</label></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="spatial-group property xyz fstuple">
|
||||
<legend>Dimensions <span class="unit">m</span></legend>
|
||||
<div class="tuple">
|
||||
<div><input type="number" class="x" id="property-dim-x" step="0.1"><label for="property-dim-x">X:</label></div>
|
||||
<div><input type="number" class="y" id="property-dim-y" step="0.1"><label for="property-dim-y">Y:</label></div>
|
||||
<div><input type="number" class="z" id="property-dim-z" step="0.1"><label for="property-dim-z">Z:</label></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="spatial-group property xyz fstuple">
|
||||
<legend>Registration <span class="unit">(pivot offset as ratio of dimension)</span></legend>
|
||||
<div class="tuple">
|
||||
<div><input type="number" class="x" id="property-reg-x" step="0.1"><label for="property-reg-x">X:</label></div>
|
||||
<div><input type="number" class="y" id="property-reg-y" step="0.1"><label for="property-reg-y">Y:</label></div>
|
||||
<div><input type="number" class="z" id="property-reg-z" step="0.1"><label for="property-reg-z">Z:</label></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="spatial-group property gen fsrow">
|
||||
<legend>Scale <span class="unit">%</span></legend>
|
||||
<div class="row">
|
||||
<input type="number" id="dimension-rescale-pct" value=100>
|
||||
<input type="button" class="blue" id="dimension-rescale-button" value="Rescale">
|
||||
<input type="button" class="red" id="reset-to-natural-dimensions" value="Reset Dimensions">
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="spatial-group row">
|
||||
<div class="property text">
|
||||
<label for="property-parent-id">Parent ID</label>
|
||||
<input type="text" id="property-parent-id">
|
||||
</div>
|
||||
<div class="property number">
|
||||
<label for="property-parent-joint-index">Parent joint index</label>
|
||||
<input type="number" id="property-parent-joint-index">
|
||||
</div>
|
||||
</div>
|
||||
<div class="spatial-group ">
|
||||
<div class="property text">
|
||||
<label>Align</label>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="buttons">
|
||||
<input type="button" id="move-selection-to-grid" value="Selection to Grid">
|
||||
<input type="button" id="move-all-to-grid" value="All to Grid">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="behavior" class="major">
|
||||
<legend class="section-header behavior-group">
|
||||
Behavior<span class=".collapse-icon">M</span>
|
||||
</legend>
|
||||
<div class="property textarea">
|
||||
<label for="property-user-data">User data</label>
|
||||
<br>
|
||||
<div class="row">
|
||||
<input type="button" class="red" id="userdata-clear" value="Clear User Data">
|
||||
<input type="button" class="blue" id="userdata-new-editor" value="Edit as JSON">
|
||||
<input disabled type="button" class="black" id="userdata-save" value="Save User Data">
|
||||
<span id="userdata-saved">Saved!</span>
|
||||
</div>
|
||||
<div id="static-userdata"></div>
|
||||
<div id="userdata-editor"></div>
|
||||
<textarea id="property-user-data"></textarea>
|
||||
</div>
|
||||
<div id="id" class="property value">
|
||||
<label>ID:</label>
|
||||
<input type="text" id="property-id" readonly>
|
||||
</div>
|
||||
<div class="can-cast-shadow-section property checkbox">
|
||||
<input type="checkbox" id="property-can-cast-shadow">
|
||||
<label for="property-can-cast-shadow">Can cast shadow</label>
|
||||
</div>
|
||||
|
||||
<fieldset class="minor">
|
||||
<div class="behavior-group property url ">
|
||||
<label for="property-collision-sound-url">Collision sound URL</label>
|
||||
<input type="text" id="property-collision-sound-url">
|
||||
</div>
|
||||
<div class="behavior-group property number">
|
||||
<label>Lifetime <span class="unit">s</span></label>
|
||||
<input type="number" id="property-lifetime">
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="minor">
|
||||
<div class="behavior-group property url refresh">
|
||||
<input type="hidden" id="property-script-timestamp" class="value">
|
||||
<label for="property-script-url">Script URL</label>
|
||||
<input type="text" id="property-script-url">
|
||||
<input type="button" id="reload-script-button" class="glyph" value="F">
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="minor">
|
||||
<div class="behavior-group property url refresh">
|
||||
<label for="property-server-scripts">Server Script URL</label>
|
||||
<input type="text" id="property-server-scripts">
|
||||
<input type="button" id="reload-server-scripts-button" class="glyph" value="F">
|
||||
</div>
|
||||
<div class="behavior-group property">
|
||||
<label for="server-script-status">Server Script Status</label>
|
||||
<span id="server-script-status"></span>
|
||||
</div>
|
||||
<div class="behavior-group property">
|
||||
<textarea id="server-script-error"></textarea>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="property text">
|
||||
<label for="property-description">Description</label>
|
||||
<input type="text" id="property-description">
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
|
||||
<fieldset id="hyperlink" class="major">
|
||||
<legend class="section-header hyperlink-group hyperlink-section">
|
||||
Hyperlink<span class=".collapse-icon">M</span>
|
||||
</legend>
|
||||
<div class="hyperlink-group hyperlink-section property url">
|
||||
<label for="property-hyperlink-href">Href - hifi://address</label>
|
||||
<input type="text" id="property-hyperlink-href">
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="light" class="major">
|
||||
<legend class="section-header light-group light-section">
|
||||
Light<span class=".collapse-icon">M</span>
|
||||
</legend>
|
||||
<fieldset class="light-group light-section property rgb fstuple">
|
||||
<div class="color-picker" id="property-light-color"></div>
|
||||
<legend>Light color</legend>
|
||||
<div class="tuple">
|
||||
<div><input type="number" class="red" id="property-light-color-red"><label for="property-light-color-red">Red:</label></div>
|
||||
<div><input type="number" class="green" id="property-light-color-green"><label for="property-light-color-green">Green:</label></div>
|
||||
<div><input type="number" class="blue" id="property-light-color-blue"><label for="property-light-color-blue">Blue:</label></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="light-group light-section property gen fstuple">
|
||||
<div class="tuple">
|
||||
<div><label>Intensity</label><input type="number" id="property-light-intensity" min="0" step="0.1"></div>
|
||||
<div><label>Fall-off radius <span class="unit">m</span></label><input type="number" id="property-light-falloff-radius" min="0" step="0.1"></div>
|
||||
<div></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="light-group light-section property checkbox">
|
||||
<input type="checkbox" id="property-light-spot-light">
|
||||
<label for="property-light-spot-light">Spotlight</label>
|
||||
</div>
|
||||
<fieldset class="light-group light-section property gen fstuple">
|
||||
<div class="tuple">
|
||||
<div><label>Spotlight exponent</label><input type="number" id="property-light-exponent" step="0.01"></div>
|
||||
<div><label>Spotlight cut-off</label><input type="number" id="property-light-cutoff" step="0.01"></div>
|
||||
<div></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="model" class="major">
|
||||
<legend class="section-header model-group model-section zone-section">
|
||||
Model<span class=".collapse-icon">M</span>
|
||||
</legend>
|
||||
<fieldset class="minor">
|
||||
<div class="model-group model-section property url ">
|
||||
<label for="property-model-url">Model URL</label>
|
||||
<input type="text" id="property-model-url">
|
||||
</div>
|
||||
<div class="model-group model-section zone-section property dropdown">
|
||||
<label>Collision shape type</label>
|
||||
<select name="SelectShapeType" id="property-shape-type">
|
||||
<option value="none">No Collision</option>
|
||||
<option value="box">Box</option>
|
||||
<option value="sphere">Sphere</option>
|
||||
<option value="compound">Compound</option>
|
||||
<option value="simple-hull">Basic - Whole model</option>
|
||||
<option value="simple-compound">Good - Sub-meshes</option>
|
||||
<option value="static-mesh">Exact - All polygons (non-dynamic only)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="model-group model-section zone-section property url ">
|
||||
<label for="property-compound-shape-url">Compound shape URL</label>
|
||||
<input type="text" id="property-compound-shape-url">
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="minor">
|
||||
<div class="model-group model-section property url ">
|
||||
<label for="property-model-animation-url">Animation URL</label>
|
||||
<input type="text" id="property-model-animation-url">
|
||||
</div>
|
||||
|
||||
<div class="model-group model-section two-column">
|
||||
<div class="column">
|
||||
<div class="property checkbox">
|
||||
<input type="checkbox" id="property-model-animation-playing">
|
||||
<label for="property-model-animation-playing">Animation playing</label>
|
||||
</div>
|
||||
<div class="property checkbox indent">
|
||||
<input type="checkbox" id="property-model-animation-loop">
|
||||
<label for="property-model-animation-loop">Animation loop</label>
|
||||
</div>
|
||||
<div class="property checkbox indent">
|
||||
<input type="checkbox" id="property-model-animation-hold">
|
||||
<label for="property-model-animation-hold">Animation hold</label>
|
||||
</div>
|
||||
<div class="property checkbox indent">
|
||||
<input type="checkbox" id="property-model-animation-allow-translation">
|
||||
<label for="property-model-animation-allow-translation">Animation Allow Translation</label>
|
||||
</div>
|
||||
<div id="animation-fps" class="property number">
|
||||
<label>Animation FPS</label>
|
||||
<input type="number" id="property-model-animation-fps">
|
||||
</div>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div class="property number">
|
||||
<label>Animation frame</label>
|
||||
<input type="number" id="property-model-animation-frame">
|
||||
</div>
|
||||
<div class="property number">
|
||||
<label>First frame</label>
|
||||
<input type="number" id="property-model-animation-first-frame">
|
||||
</div>
|
||||
<div class="property number">
|
||||
<label>Last frame</label>
|
||||
<input type="number" id="property-model-animation-last-frame">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="minor">
|
||||
<div class="model-group model-section property textarea">
|
||||
<label for="property-model-textures">Textures</label>
|
||||
<textarea id="property-model-textures"></textarea>
|
||||
</div>
|
||||
<div class="model-group model-section property textarea">
|
||||
<label for="property-model-original-textures">Original textures</label>
|
||||
<textarea id="property-model-original-textures" readonly></textarea>
|
||||
</div>
|
||||
</fieldset>
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="zone" class="major">
|
||||
<legend class="section-header zone-group zone-section">
|
||||
Zone<span class=".collapse-icon">M</span>
|
||||
</legend>
|
||||
<fieldset class="minor">
|
||||
<div class="zone-group zone-section property checkbox">
|
||||
<input type="checkbox" id="property-zone-flying-allowed">
|
||||
<label for="property-zone-flying-allowed">Flying allowed</label>
|
||||
</div>
|
||||
<div class="zone-group zone-section property checkbox">
|
||||
<input type="checkbox" id="property-zone-ghosting-allowed">
|
||||
<label for="property-zone-ghosting-allowed">Ghosting allowed</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="minor">
|
||||
<div class="zone-group zone-section property url ">
|
||||
<label for="property-zone-filter-url">Filter URL</label>
|
||||
<input type="text" id="property-zone-filter-url">
|
||||
</div>
|
||||
<div class="sub-section-header zone-group zone-section">
|
||||
<label>Key Light</label>
|
||||
</div>
|
||||
<form>
|
||||
<input type="radio" name="keyLightMode" value="inherit" id="property-zone-key-light-mode-inherit" checked> Inherit
|
||||
<input type="radio" name="keyLightMode" value="disabled" id="property-zone-key-light-mode-disabled"> Off
|
||||
<input type="radio" name="keyLightMode" value="enabled" id="property-zone-key-light-mode-enabled"> On
|
||||
</form>
|
||||
<div class="zone-section keylight-section zone-group property rgb">
|
||||
<div class="color-picker" id="property-zone-key-light-color"></div>
|
||||
<label>Key light color</label>
|
||||
<div class="tuple">
|
||||
<div><input type="number" class="red" id="property-zone-key-light-color-red" min="0" max="255" step="1"><label for="property-zone-key-light-color-red">Red:</label></div>
|
||||
<div><input type="number" class="green" id="property-zone-key-light-color-green" min="0" max="255" step="1"><label for="property-zone-key-light-color-green">Green:</label></div>
|
||||
<div><input type="number" class="blue" id="property-zone-key-light-color-blue" min="0" max="255" step="1"><label for="property-zone-key-light-color-blue">Blue:</label></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="zone-section keylight-section zone-group property number">
|
||||
<label>Light intensity</label>
|
||||
<input type="number" id="property-zone-key-intensity" min="0" max="10" step="0.1">
|
||||
</div>
|
||||
<div class="zone-group zone-section keylight-section property gen">
|
||||
<div class="tuple">
|
||||
<div><label>Light altitude <span class="unit">deg</span></label><input type="number" id="property-zone-key-light-direction-x"></div>
|
||||
<div><label>Light azimuth <span class="unit">deg</span></label><input type="number" id="property-zone-key-light-direction-y"></div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="zone-group zone-section keylight-section property checkbox">
|
||||
<input type="checkbox" id="property-zone-key-light-cast-shadows">
|
||||
<label for="property-zone-key-light-cast-shadows">Cast Shadows</label>
|
||||
</div>
|
||||
<fieldset class="minor">
|
||||
<legend class="sub-section-header zone-group zone-section background-section">
|
||||
Skybox
|
||||
</legend>
|
||||
<form>
|
||||
<input type="radio" name="skyboxMode" value="inherit" id="property-zone-skybox-mode-inherit" checked> Inherit
|
||||
<input type="radio" name="skyboxMode" value="disabled" id="property-zone-skybox-mode-disabled"> Off
|
||||
<input type="radio" name="skyboxMode" value="enabled" id="property-zone-skybox-mode-enabled"> On
|
||||
</form>
|
||||
<fieldset class="zone-group zone-section skybox-section property rgb fstuple">
|
||||
<div class="color-picker" id="property-zone-skybox-color"></div>
|
||||
<legend>Skybox color</legend>
|
||||
<div class="tuple">
|
||||
<div>
|
||||
<input type="number" class="red" id="property-zone-skybox-color-red">
|
||||
<label for="property-zone-skybox-color-red">Red:</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="number" class="green" id="property-zone-skybox-color-green">
|
||||
<label for="property-zone-skybox-color-green">Green:</label>
|
||||
</div>
|
||||
<div>
|
||||
<input type="number" class="blue" id="property-zone-skybox-color-blue">
|
||||
<label for="property-zone-skybox-color-blue">Blue:</label>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="zone-group zone-section skybox-section property url ">
|
||||
<br>
|
||||
<label for="property-zone-skybox-url" width="80">Skybox URL</label>
|
||||
<input type="text" id="property-zone-skybox-url">
|
||||
<br>
|
||||
<br>
|
||||
<input type="button" id="copy-skybox-url-to-ambient-url" value="Copy URL to Ambient">
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="sub-section-header zone-group zone-section">
|
||||
<label>Ambient Light</label>
|
||||
</div>
|
||||
<form>
|
||||
<input type="radio" name="ambientLightMode" value="inherit" id="property-zone-ambient-light-mode-inherit" checked> Inherit
|
||||
<input type="radio" name="ambientLightMode" value="disabled" id="property-zone-ambient-light-mode-disabled"> Off
|
||||
<input type="radio" name="ambientLightMode" value="enabled" id="property-zone-ambient-light-mode-enabled"> On
|
||||
</form>
|
||||
<div class="zone-group zone-section ambient-section property number">
|
||||
<label>Ambient intensity</label>
|
||||
<input type="number" id="property-zone-key-ambient-intensity" min="0" max="10" step="0.1">
|
||||
</div>
|
||||
<div class="zone-group zone-section ambient-section property url ">
|
||||
<label for="property-zone-key-ambient-url">Ambient URL</label>
|
||||
<input type="text" id="property-zone-key-ambient-url">
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="minor">
|
||||
<legend class="sub-section-header zone-group zone-section">
|
||||
Haze
|
||||
</legend>
|
||||
<form>
|
||||
<input type="radio" name="hazeMode" value="inherit" id="property-zone-haze-mode-inherit" checked> Inherit
|
||||
<input type="radio" name="hazeMode" value="disabled" id="property-zone-haze-mode-disabled"> Off
|
||||
<input type="radio" name="hazeMode" value="enabled" id="property-zone-haze-mode-enabled"> On
|
||||
</form>
|
||||
<fieldset class="zone-group zone-section haze-section property gen fstuple">
|
||||
<div class="tuple">
|
||||
<div>
|
||||
<label>Range<span class="unit">m</span></label>
|
||||
<input type="number" id="property-zone-haze-range" min="5" max="10000" step="5">
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="zone-group zone-section haze-section property checkbox">
|
||||
<input type="checkbox" id="property-zone-haze-altitude-effect">
|
||||
<label for="property-zone-haze-altitude-effect">Use Altitude</label>
|
||||
</div>
|
||||
<fieldset class="zone-group zone-section haze-section property gen fstuple">
|
||||
<div class="tuple">
|
||||
<div><label>Base<span class="unit">m</span></label><input type="number" id="property-zone-haze-base" min="-1000" max="1000" step="10"></div>
|
||||
<div><label>Ceiling<span class="unit">m</span></label><input type="number" id="property-zone-haze-ceiling" min="-1000" max="5000" step="10"></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="zone-group zone-section haze-section property rgb fstuple">
|
||||
<div class="color-picker" id="property-zone-haze-color"></div>
|
||||
<legend>Haze Color</legend>
|
||||
<div class="tuple">
|
||||
<div><input type="number" class="red" id="property-zone-haze-color-red" min="0" max="255" step="1">
|
||||
<label for="property-zone-haze-blend-in-color-red">Red:</label></div>
|
||||
<div><input type="number" class="green" id="property-zone-haze-color-green" min="0" max="255" step="1">
|
||||
<label for="property-zone-haze-blend-in-color-green">Green:</label></div>
|
||||
<div><input type="number" class="blue" id="property-zone-haze-color-blue" min="0" max="255" step="1">
|
||||
<label for="property-zone-haze-blend-in-color-blue">Blue:</label></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="zone-group zone-section haze-section property gen fstuple">
|
||||
<div class="tuple">
|
||||
<div>
|
||||
<br>
|
||||
<table>
|
||||
<td><label>Background Blend 0.0</label></td>
|
||||
<td><input type="range" class="slider" id="property-zone-haze-background-blend" min="0.0" max="1.0" step="0.01" value="0.0"></td>
|
||||
<td><label>1.0</label></td>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="zone-group zone-section haze-section property checkbox">
|
||||
<input type="checkbox" id="property-zone-haze-enable-light-blend">
|
||||
<label for="property-zone-haze-enable-light-blend">Enable Glare</label>
|
||||
</div>
|
||||
<fieldset class="zone-group zone-section haze-section property rgb fstuple">
|
||||
<div class="color-picker" id="property-zone-haze-glare-color"></div>
|
||||
<legend>Glare Color</legend>
|
||||
<div class="tuple">
|
||||
<div><input type="number" class="red" id="property-zone-haze-glare-color-red" min="0" max="255" step="1">
|
||||
<label for="property-zone-haze-blend-out-color-red">Red:</label></div>
|
||||
<div><input type="number" class="green" id="property-zone-haze-glare-color-green" min="0" max="255" step="1">
|
||||
<label for="property-zone-haze-blend-out-color-green">Green:</label></div>
|
||||
<div><input type="number" class="blue" id="property-zone-haze-glare-color-blue" min="0" max="255" step="1">
|
||||
<label for="property-zone-haze-blend-out-color-blue">Blue:</label></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="zone-group zone-section haze-section property gen fstuple">
|
||||
<div class="tuple">
|
||||
<div>
|
||||
<br>
|
||||
<table>
|
||||
<td><label>Glare Angle 0</label></td>
|
||||
<td><input type="range" class="slider" id="property-zone-haze-blend-angle" min="0" max="180" step="1"></td>
|
||||
<td><label>180</label>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<!--div class="zone-group zone-section haze-section property checkbox">
|
||||
<input type="checkbox" id="property-zone-haze-attenuate-keylight">
|
||||
<label for="property-zone-haze-attenuate-keylight">Attenuate Keylight</label>
|
||||
</div>
|
||||
<fieldset class="zone-group zone-section haze-section property gen fstuple">
|
||||
<div class="tuple">
|
||||
<div><label>Range<span class="unit">m</span></label><input type="number" id="property-zone-haze-keylight-range"
|
||||
min="5" max="1000000" step="5"></div>
|
||||
<div><label>Altitude<span class="unit">m</span></label><input type="number" id="property-zone-haze-keylight-altitude"
|
||||
min="-1000" max="50000" step="10"></div>
|
||||
</div>
|
||||
</fieldset-->
|
||||
</fieldset>
|
||||
<fieldset class="minor">
|
||||
<legend class="sub-section-header zone-group zone-section">
|
||||
Bloom
|
||||
</legend>
|
||||
<form>
|
||||
<input type="radio" name="bloomMode" value="inherit" id="property-zone-bloom-mode-inherit" checked> Inherit
|
||||
<input type="radio" name="bloomMode" value="disabled" id="property-zone-bloom-mode-disabled"> Off
|
||||
<input type="radio" name="bloomMode" value="enabled" id="property-zone-bloom-mode-enabled"> On
|
||||
</form>
|
||||
<fieldset class="zone-group zone-section bloom-section property gen fstuple">
|
||||
<div class="tuple">
|
||||
<div>
|
||||
<br>
|
||||
<table>
|
||||
<td><label>Bloom Intensity 0</label></td>
|
||||
<td><input type="range" class="slider" id="property-zone-bloom-intensity" min="0" max="1" step="0.01"></td>
|
||||
<td><label>1</label>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="zone-group zone-section bloom-section property gen fstuple">
|
||||
<div class="tuple">
|
||||
<div>
|
||||
<br>
|
||||
<table>
|
||||
<td><label>Bloom Threshold 0</label></td>
|
||||
<td><input type="range" class="slider" id="property-zone-bloom-threshold" min="0" max="1" step="0.01"></td>
|
||||
<td><label>1</label>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="zone-group zone-section bloom-section property gen fstuple">
|
||||
<div class="tuple">
|
||||
<div>
|
||||
<br>
|
||||
<table>
|
||||
<td><label>Bloom Size 0</label></td>
|
||||
<td><input type="range" class="slider" id="property-zone-bloom-size" min="0" max="2" step="0.01"></td>
|
||||
<td><label>2</label>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</fieldset>
|
||||
</fieldset>
|
||||
<fieldset id="text" class="major">
|
||||
<legend class="section-header text-group text-section">
|
||||
Text<span class=".collapse-icon">M</span>
|
||||
</legend>
|
||||
<div class="text-group text-section property text">
|
||||
<label for="property-text-text">Text content</label>
|
||||
<input type="text" id="property-text-text">
|
||||
</div>
|
||||
<div class="text-group text-section property checkbox">
|
||||
<input type="checkbox" id="property-text-face-camera">
|
||||
<label for="property-text-face-camera"> Face Camera</label>
|
||||
</div>
|
||||
<div class="text-group text-section property number">
|
||||
<label>Line height <span class="unit">m</span></label>
|
||||
<input type="number" id="property-text-line-height" min="0" step="0.005">
|
||||
</div>
|
||||
<div class="text-group text-section property rgb">
|
||||
<div class="color-picker" id="property-text-text-color"></div>
|
||||
<label>Text color</label>
|
||||
<div class="tuple">
|
||||
<div><input type="number" class="red" id="property-text-text-color-red"><label for="property-text-text-color-red">Red:</label></div>
|
||||
<div><input type="number" class="green" id="property-text-text-color-green"><label for="property-text-text-color-green">Green:</label></div>
|
||||
<div><input type="number" class="blue" id="property-text-text-color-blue"><label for="property-text-text-color-blue">Blue:</label></div>
|
||||
</div>
|
||||
</div>
|
||||
<fieldset class="text-group text-section property rgb fstuple">
|
||||
<div class="color-picker" id="property-text-background-color"></div>
|
||||
<legend>Background color</legend>
|
||||
<div class="tuple">
|
||||
<div><input type="number" class="red" id="property-text-background-color-red"><label for="roperty-text-background-color-red">Red:</label></div>
|
||||
<div><input type="number" class="green" id="property-text-background-color-green"><label for="property-text-background-color-green">Green:</label></div>
|
||||
<div><input type="number" class="blue" id="property-text-background-color-blue"><label for="property-text-background-color-blue">Blue:</label></div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="image" class="major">
|
||||
<legend class="section-header image-group image-section">
|
||||
Image<span>M</span>
|
||||
</legend>
|
||||
<div class="image-group image-section property url ">
|
||||
<label for="property-image-url">Image URL</label>
|
||||
<input type="text" id="property-image-url">
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="web" class="major">
|
||||
<legend class="section-header web-group web-section">
|
||||
Web<span class=".collapse-icon">M</span>
|
||||
</legend>
|
||||
<div class="web-group web-section property url ">
|
||||
<label for="property-web-source-url">Source URL</label>
|
||||
<input type="text" id="property-web-source-url">
|
||||
</div>
|
||||
<div class="web-group web-section property dpi ">
|
||||
<label for="property-web-dpi">Resolution (DPI)</label>
|
||||
<input type="number" id="property-web-dpi">
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="polyvox" class="major">
|
||||
<legend class="section-header spatial-group poly-vox-section property xyz">
|
||||
Voxel volume size <span>m</span>
|
||||
</legend>
|
||||
<div class="tuple">
|
||||
<div><input type="number" class="x" id="property-voxel-volume-size-x"><label for="property-voxel-volume-size-x">X:</label></div>
|
||||
<div><input type="number" class="y" id="property-voxel-volume-size-y"><label for="property-voxel-volume-size-y">Y:</label></div>
|
||||
<div><input type="number" class="z" id="property-voxel-volume-size-z"><label for="property-voxel-volume-size-z">Z:</label></div>
|
||||
</div>
|
||||
<div class="spatial-group poly-vox-section property dropdown">
|
||||
<label>Surface extractor</label>
|
||||
<select name="SelectVoxelSurfaceStyle" id="property-voxel-surface-style">
|
||||
<option value="0">Marching cubes</option>
|
||||
<option value="1">Cubic</option>
|
||||
<option value="2">Edged cubic</option>
|
||||
<option value="3">Edged marching cubes</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="spatial-group poly-vox-section property url ">
|
||||
<label for="property-x-texture-url">X-axis texture URL</label>
|
||||
<input type="text" id="property-x-texture-url">
|
||||
</div>
|
||||
<div class="spatial-group poly-vox-section property url ">
|
||||
<label for="property-y-texture-url">Y-axis texture URL</label>
|
||||
<input type="text" id="property-y-texture-url">
|
||||
</div>
|
||||
<div class="spatial-group poly-vox-section property url ">
|
||||
<label for="property-z-texture-url">Z-axis texture URL</label>
|
||||
<input type="text" id="property-z-texture-url">
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<fieldset id="material" class="major">
|
||||
<legend class="section-header material-group material-section">
|
||||
Material<span>M</span>
|
||||
</legend>
|
||||
<fieldset class="minor">
|
||||
<div class="material-group material-section property url">
|
||||
<label for="property-material-url">Material URL</label>
|
||||
<input type="text" id="property-material-url">
|
||||
</div>
|
||||
|
||||
<div class="property textarea">
|
||||
<label for="property-material-data">Material data</label>
|
||||
<br><br>
|
||||
<div class="row">
|
||||
<input type="button" class="red" id="materialdata-clear" value="Clear Material Data">
|
||||
<input type="button" class="blue" id="materialdata-new-editor" value="Edit as JSON">
|
||||
<input disabled type="button" class="black" id="materialdata-save" value="Save Material Data">
|
||||
<span id="materialdata-saved" visible="false">Saved!</span>
|
||||
</div>
|
||||
<div id="static-naterialdata"></div>
|
||||
<div id="materialdata-editor"></div>
|
||||
<textarea id="property-material-data"></textarea>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="minor">
|
||||
<div class="material-group material-section property text" id="property-parent-material-id-string-container">
|
||||
<label for="property-parent-material-id-string">Material Name to Replace </label>
|
||||
<input type="text" id="property-parent-material-id-string">
|
||||
</div>
|
||||
|
||||
<div class="material-group material-section property number" id="property-parent-material-id-number-container">
|
||||
<label for="property-parent-material-id-number">Submesh to Replace </label>
|
||||
<input type="number" min="0" step="1" value="0" id="property-parent-material-id-number">
|
||||
</div>
|
||||
|
||||
<div class="material-group material-section property checkbox">
|
||||
<input type="checkbox" id="property-parent-material-id-checkbox">
|
||||
<label for="property-parent-material-id-checkbox">Select Submesh</label>
|
||||
</div>
|
||||
|
||||
<div class="material-group material-section property number">
|
||||
<label>Priority </label>
|
||||
<input type="number" id="property-priority" min="0">
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<!-- TODO: support 3D projected materials
|
||||
<div class="material-group material-section property dropdown">
|
||||
<label>Material mode </label>
|
||||
<select name="SelectMaterialMode" id="property-material-mapping-mode">
|
||||
<option value="uv">UV space material</option>
|
||||
<option value="projected">3D projected material</option>
|
||||
</select>
|
||||
</div>
|
||||
-->
|
||||
|
||||
<div class="material-group material-section property xy fstuple">
|
||||
<label>Material Position </label>
|
||||
<div class="tuple">
|
||||
<div><input type="number" class="x" id="property-material-mapping-pos-x" min="0" max="1" step="0.1"><label for="property-material-mapping-pos-x">X:</label></div>
|
||||
<div><input type="number" class="y" id="property-material-mapping-pos-y" min="0" max="1" step="0.1"><label for="property-material-mapping-pos-y">Y:</label></div>
|
||||
</div>
|
||||
|
||||
<div class="material-group material-section property wh fstuple">
|
||||
<label>Material Scale </label>
|
||||
<div class="tuple">
|
||||
<div><input type="number" class="x" id="property-material-mapping-scale-x" min="0" step="0.1"><label for="property-material-mapping-scale-x">Width:</label></div>
|
||||
<div><input type="number" class="y" id="property-material-mapping-scale-y" min="0" step="0.1"><label for="property-material-mapping-scale-y">Height:</label></div>
|
||||
</div>
|
||||
|
||||
<div class="material-group material-section property number">
|
||||
<label>Material Rotation <span class="unit">deg</span></label>
|
||||
<input type="number" id="property-material-mapping-rot" step="0.1">
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
</fieldset>
|
||||
</fieldset>
|
||||
|
||||
<!-- each property is added at runtime in entityProperties -->
|
||||
</div>
|
||||
</body>
|
||||
|
||||
|
|
116
scripts/system/html/js/createAppTooltip.js
Normal file
116
scripts/system/html/js/createAppTooltip.js
Normal file
|
@ -0,0 +1,116 @@
|
|||
// createAppTooltip.js
|
||||
//
|
||||
// Created by Thijs Wenker on 17 Oct 2018
|
||||
// Copyright 2018 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
const CREATE_APP_TOOLTIP_OFFSET = 20;
|
||||
const TOOLTIP_DELAY = 500; // ms
|
||||
const TOOLTIP_DEBUG = false;
|
||||
|
||||
function CreateAppTooltip() {
|
||||
this._tooltipData = null;
|
||||
this._tooltipDiv = null;
|
||||
this._delayTimeout = null;
|
||||
this._isEnabled = false;
|
||||
}
|
||||
|
||||
CreateAppTooltip.prototype = {
|
||||
_tooltipData: null,
|
||||
_tooltipDiv: null,
|
||||
_delayTimeout: null,
|
||||
_isEnabled: null,
|
||||
|
||||
_removeTooltipIfExists: function() {
|
||||
if (this._delayTimeout !== null) {
|
||||
window.clearTimeout(this._delayTimeout);
|
||||
this._delayTimeout = null;
|
||||
}
|
||||
|
||||
if (this._tooltipDiv !== null) {
|
||||
this._tooltipDiv.remove();
|
||||
this._tooltipDiv = null;
|
||||
}
|
||||
},
|
||||
|
||||
setIsEnabled: function(isEnabled) {
|
||||
this._isEnabled = isEnabled;
|
||||
},
|
||||
|
||||
setTooltipData: function(tooltipData) {
|
||||
this._tooltipData = tooltipData;
|
||||
},
|
||||
|
||||
registerTooltipElement: function(element, tooltipID) {
|
||||
element.addEventListener("mouseover", function() {
|
||||
if (!this._isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._removeTooltipIfExists();
|
||||
|
||||
this._delayTimeout = window.setTimeout(function() {
|
||||
let tooltipData = this._tooltipData[tooltipID];
|
||||
|
||||
if (!tooltipData || tooltipData.tooltip === "") {
|
||||
if (!TOOLTIP_DEBUG) {
|
||||
return;
|
||||
}
|
||||
tooltipData = {tooltip: 'PLEASE SET THIS TOOLTIP'};
|
||||
}
|
||||
|
||||
let elementRect = element.getBoundingClientRect();
|
||||
let elTip = document.createElement("div");
|
||||
elTip.className = "createAppTooltip";
|
||||
|
||||
let elTipDescription = document.createElement("div");
|
||||
elTipDescription.className = "createAppTooltipDescription";
|
||||
elTipDescription.innerText = tooltipData.tooltip;
|
||||
elTip.appendChild(elTipDescription);
|
||||
|
||||
let jsAttribute = tooltipID;
|
||||
if (tooltipData.jsPropertyName) {
|
||||
jsAttribute = tooltipData.jsPropertyName;
|
||||
}
|
||||
|
||||
if (!tooltipData.skipJSProperty) {
|
||||
let elTipJSAttribute = document.createElement("div");
|
||||
elTipJSAttribute.className = "createAppTooltipJSAttribute";
|
||||
elTipJSAttribute.innerText = `JS Attribute: ${jsAttribute}`;
|
||||
elTip.appendChild(elTipJSAttribute);
|
||||
}
|
||||
|
||||
document.body.appendChild(elTip);
|
||||
|
||||
let elementTop = window.pageYOffset + elementRect.top;
|
||||
|
||||
let desiredTooltipTop = elementTop + element.clientHeight + CREATE_APP_TOOLTIP_OFFSET;
|
||||
let desiredTooltipLeft = window.pageXOffset + elementRect.left;
|
||||
|
||||
if ((window.innerHeight + window.pageYOffset) < (desiredTooltipTop + elTip.clientHeight)) {
|
||||
// show above when otherwise out of bounds
|
||||
elTip.style.top = elementTop - CREATE_APP_TOOLTIP_OFFSET - elTip.clientHeight;
|
||||
} else {
|
||||
// show tooltip on below by default
|
||||
elTip.style.top = desiredTooltipTop;
|
||||
}
|
||||
if ((window.innerWidth + window.pageXOffset) < (desiredTooltipLeft + elTip.clientWidth)) {
|
||||
elTip.style.left = document.body.clientWidth + window.pageXOffset - elTip.offsetWidth;
|
||||
} else {
|
||||
elTip.style.left = desiredTooltipLeft;
|
||||
}
|
||||
|
||||
this._tooltipDiv = elTip;
|
||||
}.bind(this), TOOLTIP_DELAY);
|
||||
}.bind(this), false);
|
||||
element.addEventListener("mouseout", function() {
|
||||
if (!this._isEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._removeTooltipIfExists();
|
||||
}.bind(this), false);
|
||||
}
|
||||
};
|
File diff suppressed because it is too large
Load diff
|
@ -1,709 +0,0 @@
|
|||
/* global window, document, print, alert, console,setTimeout, clearTimeout, _ $ */
|
||||
/* eslint no-console: 0 */
|
||||
|
||||
/**
|
||||
UI Builder V1.0
|
||||
|
||||
Created by Matti 'Menithal' Lahtinen
|
||||
24/5/2017
|
||||
Copyright 2017 High Fidelity, Inc.
|
||||
|
||||
This can eventually be expanded to all of Edit, for now, starting
|
||||
with Particles Only.
|
||||
|
||||
This is created for the sole purpose of streamliming the bridge, and to simplify
|
||||
the logic between an inputfield in WebView and Entities in High Fidelity.
|
||||
|
||||
We also do not need anything as heavy as jquery or any other platform,
|
||||
as we are mostly only building for QT (while, all the other JS frameworks usually do alot of polyfilling)
|
||||
|
||||
Available Types:
|
||||
|
||||
JSONInputField - Accepts JSON input, once one presses Save, it will be propegated.
|
||||
Button- A Button that listens for a custom event as defined by callback
|
||||
Boolean - Creates a checkbox that the user can either check or uncheck
|
||||
SliderFloat - Creates a slider (with input) that has Float values from min to max.
|
||||
Default is min 0, max 1
|
||||
SliderInteger - Creates a slider (with input) that has a Integer value from min to max.
|
||||
Default is min 1, max 10000
|
||||
SliderRadian - Creates a slider (with input) that has Float values in degrees,
|
||||
that are converted to radians. default is min 0, max Math.PI.
|
||||
Texture - Creates a Image with an url input field that points to texture.
|
||||
If image cannot form, show "cannot find image"
|
||||
VecQuaternion - Creates a 3D Vector field that converts to quaternions.
|
||||
Checkbox exists to show quaternions instead.
|
||||
Color - Create field color button, that when pressed, opens the color picker.
|
||||
Vector - Create a 3D Vector field that has one to one correspondence.
|
||||
|
||||
The script will use this structure to build a UI that is connected The
|
||||
id fields within High Fidelity
|
||||
|
||||
This should make editing, and everything related much more simpler to maintain,
|
||||
and If there is any changes to either the Entities or properties of
|
||||
|
||||
**/
|
||||
|
||||
var RADIANS_PER_DEGREE = Math.PI / 180;
|
||||
var DEBOUNCE_TIMEOUT = 125;
|
||||
|
||||
var roundFloat = function (input, round) {
|
||||
round = round ? round : 1000;
|
||||
var sanitizedInput;
|
||||
if (typeof input === "string") {
|
||||
sanitizedInput = parseFloat(input);
|
||||
} else {
|
||||
sanitizedInput = input;
|
||||
}
|
||||
return Math.round(sanitizedInput * round) / round;
|
||||
};
|
||||
|
||||
function HifiEntityUI(parent) {
|
||||
this.parent = parent;
|
||||
|
||||
var self = this;
|
||||
this.sendPackage = {};
|
||||
this.settingsUpdateLock = false;
|
||||
this.webBridgeSync = function(id, val) {
|
||||
if (!this.settingsUpdateLock) {
|
||||
this.sendPackage[id] = val;
|
||||
this.webBridgeSyncDebounce();
|
||||
}
|
||||
};
|
||||
this.webBridgeSyncDebounce = _.debounce(function () {
|
||||
if (self.EventBridge) {
|
||||
self.submitChanges(self.sendPackage);
|
||||
self.sendPackage = {};
|
||||
}
|
||||
}, DEBOUNCE_TIMEOUT);
|
||||
}
|
||||
|
||||
HifiEntityUI.prototype = {
|
||||
setOnSelect: function (callback) {
|
||||
this.onSelect = callback;
|
||||
},
|
||||
submitChanges: function (structure) {
|
||||
var message = {
|
||||
messageType: "settings_update",
|
||||
updatedSettings: structure
|
||||
};
|
||||
this.EventBridge.emitWebEvent(JSON.stringify(message));
|
||||
},
|
||||
setUI: function (structure) {
|
||||
this.structure = structure;
|
||||
},
|
||||
disableFields: function () {
|
||||
var fields = document.getElementsByTagName("input");
|
||||
for (var i = 0; i < fields.length; i++) {
|
||||
if (fields[i].getAttribute("type") !== "button") {
|
||||
fields[i].value = "";
|
||||
}
|
||||
|
||||
fields[i].setAttribute("disabled", true);
|
||||
}
|
||||
var textures = document.getElementsByTagName("img");
|
||||
for (i = 0; i < textures.length; i++) {
|
||||
textures[i].src = "";
|
||||
}
|
||||
|
||||
textures = document.getElementsByClassName("with-texture");
|
||||
for (i = 0; i < textures.length; i++) {
|
||||
textures[i].classList.remove("with-textures");
|
||||
textures[i].classList.add("no-texture");
|
||||
}
|
||||
|
||||
var textareas = document.getElementsByTagName("textarea");
|
||||
for (var x = 0; x < textareas.length; x++) {
|
||||
textareas[x].remove();
|
||||
}
|
||||
},
|
||||
getSettings: function () {
|
||||
var self = this;
|
||||
var json = {};
|
||||
var keys = Object.keys(self.builtRows);
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var key = keys[i];
|
||||
var el = self.builtRows[key];
|
||||
if (el.className.indexOf("checkbox") !== -1) {
|
||||
json[key] = document.getElementById(key)
|
||||
.checked ? true : false;
|
||||
} else if (el.className.indexOf("vector-section") !== -1) {
|
||||
var vector = {};
|
||||
if (el.className.indexOf("rgb") !== -1) {
|
||||
var red = document.getElementById(key + "-red");
|
||||
var blue = document.getElementById(key + "-blue");
|
||||
var green = document.getElementById(key + "-green");
|
||||
vector.red = red.value;
|
||||
vector.blue = blue.value;
|
||||
vector.green = green.value;
|
||||
} else if (el.className.indexOf("pyr") !== -1) {
|
||||
var p = document.getElementById(key + "-Pitch");
|
||||
var y = document.getElementById(key + "-Yaw");
|
||||
var r = document.getElementById(key + "-Roll");
|
||||
vector.x = p.value;
|
||||
vector.y = y.value;
|
||||
vector.z = r.value;
|
||||
} else {
|
||||
var x = document.getElementById(key + "-x");
|
||||
var ey = document.getElementById(key + "-y");
|
||||
var z = document.getElementById(key + "-z");
|
||||
vector.x = x.value;
|
||||
vector.y = ey.value;
|
||||
vector.z = z.value;
|
||||
}
|
||||
json[key] = vector;
|
||||
} else if (el.className.indexOf("radian") !== -1) {
|
||||
json[key] = document.getElementById(key).value * RADIANS_PER_DEGREE;
|
||||
} else if (el.className.length > 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 (fields[i].hasAttribute("data-max")) {
|
||||
// Reset Max to original max
|
||||
fields[i].setAttribute("max", fields[i].getAttribute("data-max"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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') {
|
||||
self.settingsUpdateLock = true;
|
||||
self.fillFields(data.currentProperties);
|
||||
self.settingsUpdateLock = false;
|
||||
// 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("fieldset");
|
||||
var title = document.createElement("legend");
|
||||
var dropDown = document.createElement("span");
|
||||
|
||||
dropDown.className = "arrow";
|
||||
sectionDivHeader.className = "major";
|
||||
title.className = "section-header";
|
||||
title.id = section + "-section";
|
||||
title.innerHTML = section;
|
||||
title.appendChild(dropDown);
|
||||
sectionDivHeader.appendChild(title);
|
||||
|
||||
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);
|
||||
sectionDivHeader.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;
|
||||
}
|
||||
|
||||
title.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";
|
||||
title.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 = $('<div>', {
|
||||
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: (group.layoutColorScheme === undefined ? 'dark' : group.layoutColorScheme),
|
||||
layout: (group.layoutType === undefined ? 'hex' : group.layoutType),
|
||||
submit: (group.useSubmitButton === undefined ? true : group.useSubmitButton),
|
||||
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.slice(0, 5).toLowerCase() === "atp:/") {
|
||||
image.src = "";
|
||||
image.style.display = "none";
|
||||
textureImage.classList.remove("with-texture");
|
||||
textureImage.classList.remove("no-texture");
|
||||
textureImage.classList.add("no-preview");
|
||||
} else if (url.length > 0) {
|
||||
textureImage.classList.remove("no-texture");
|
||||
textureImage.classList.remove("no-preview");
|
||||
textureImage.classList.add("with-texture");
|
||||
image.src = url;
|
||||
image.style.display = "block";
|
||||
} else {
|
||||
image.src = "";
|
||||
image.style.display = "none";
|
||||
textureImage.classList.remove("with-texture");
|
||||
textureImage.classList.remove("no-preview");
|
||||
textureImage.classList.add("no-texture");
|
||||
}
|
||||
}, DEBOUNCE_TIMEOUT * 2);
|
||||
|
||||
textureUrl.oninput = function (event) {
|
||||
// Add throttle
|
||||
var url = event.target.value;
|
||||
imageLoad(url);
|
||||
self.webBridgeSync(group.id, 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("data-max", group.max !== undefined ? group.max : 10000);
|
||||
slider.setAttribute("step", 1);
|
||||
|
||||
inputField.oninput = function (event) {
|
||||
// TODO: Remove this functionality? Alan finds it confusing
|
||||
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, inputField.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, inputField.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("data-max", group.max !== undefined ? group.max : 1);
|
||||
slider.setAttribute("step", 0.01);
|
||||
|
||||
inputField.oninput = function (event) {
|
||||
// TODO: Remove this functionality? Alan finds it confusing
|
||||
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;
|
||||
}
|
||||
};
|
File diff suppressed because one or more lines are too long
|
@ -1,43 +0,0 @@
|
|||
<!--
|
||||
// particleExplorer.hml
|
||||
//
|
||||
//
|
||||
// Created by James B. Pollack @imgntn on 9/26/2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
|
||||
// Reworked by Menithal on 20/5/2017
|
||||
// Using a custom built system for High Fidelity
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" type="text/css" href="../html/css/colpick.css">
|
||||
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
|
||||
<script type="text/javascript" src="../html/js/eventBridgeLoader.js"></script>
|
||||
<!---->
|
||||
<script type="text/javascript" src="../html/js/jquery-2.1.4.min.js"></script>
|
||||
<script type="text/javascript" src="../html/js/colpick.js"></script>
|
||||
|
||||
<script type="text/javascript" src="underscore-min.js"></script>
|
||||
<script type="text/javascript" src="hifi-entity-ui.js?v1"></script>
|
||||
|
||||
<link rel="stylesheet" type="text/css" href="../html/css/hifi-style.css">
|
||||
<link rel="stylesheet" type="text/css" href="../html/css/edit-style.css">
|
||||
<link rel="stylesheet" type="text/css" href="../html/css/colpick.css">
|
||||
<link rel="stylesheet" type="text/css" href="particle-style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="properties-list">
|
||||
<div class="section-header" id="main-header">
|
||||
<label> Particle Explorer </label>
|
||||
</div>
|
||||
<!-- This will be filled by the script! -->
|
||||
</div>
|
||||
<div id="rem"></div>
|
||||
<script type="text/javascript" src="particleExplorer.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,485 +0,0 @@
|
|||
//
|
||||
// particleExplorer.js
|
||||
//
|
||||
// Created by James B. Pollack @imgntn on 9/26/2015
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Reworked by Menithal on 20/5/2017
|
||||
// Reworked by Daniela Fontes and Artur Gomes (Mimicry) on 12/18/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.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
/* global HifiEntityUI, openEventBridge, console, EventBridge, document, window */
|
||||
/* eslint no-console: 0, no-global-assign: 0 */
|
||||
|
||||
(function () {
|
||||
|
||||
var root = document.getElementById("properties-list");
|
||||
|
||||
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;
|
||||
document.getElementById("show-properties-button").value = "Hide Properties";
|
||||
} 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);
|
||||
document.getElementById("show-properties-button").value = "Show Properties";
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
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
|
||||
},
|
||||
{
|
||||
id: "speedSpread",
|
||||
name: "Speed Spread",
|
||||
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: "emitterShouldTrail",
|
||||
name: "Emitter 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
|
||||
},
|
||||
layoutType: "hex",
|
||||
layoutColorScheme: "dark",
|
||||
useSubmitButton: false
|
||||
},
|
||||
{
|
||||
type: "Row"
|
||||
},
|
||||
{
|
||||
id: "colorSpread",
|
||||
name: "Color Spread",
|
||||
type: "Color",
|
||||
defaultColor: {
|
||||
red: 0,
|
||||
green: 0,
|
||||
blue: 0
|
||||
},
|
||||
layoutType: "hex",
|
||||
layoutColorScheme: "dark",
|
||||
useSubmitButton: false
|
||||
},
|
||||
{
|
||||
type: "Row"
|
||||
},
|
||||
{
|
||||
id: "colorStart",
|
||||
name: "Color Start",
|
||||
type: "Color",
|
||||
defaultColor: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255
|
||||
},
|
||||
layoutType: "hex",
|
||||
layoutColorScheme: "dark",
|
||||
useSubmitButton: false
|
||||
},
|
||||
{
|
||||
type: "Row"
|
||||
},
|
||||
{
|
||||
id: "colorFinish",
|
||||
name: "Color Finish",
|
||||
type: "Color",
|
||||
defaultColor: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255
|
||||
},
|
||||
layoutType: "hex",
|
||||
layoutColorScheme: "dark",
|
||||
useSubmitButton: false
|
||||
},
|
||||
{
|
||||
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",
|
||||
max: 1.0
|
||||
},
|
||||
{
|
||||
type: "Row"
|
||||
},
|
||||
{
|
||||
id: "alphaSpread",
|
||||
name: "Alpha Spread",
|
||||
type: "SliderFloat",
|
||||
max: 1.0
|
||||
},
|
||||
{
|
||||
type: "Row"
|
||||
},
|
||||
{
|
||||
id: "alphaStart",
|
||||
name: "Alpha Start",
|
||||
type: "SliderFloat",
|
||||
max: 1.0
|
||||
},
|
||||
{
|
||||
type: "Row"
|
||||
},
|
||||
{
|
||||
id: "alphaFinish",
|
||||
name: "Alpha Finish",
|
||||
type: "SliderFloat",
|
||||
max: 1.0
|
||||
},
|
||||
{
|
||||
type: "Row"
|
||||
}
|
||||
],
|
||||
Spin: [
|
||||
{
|
||||
id: "particleSpin",
|
||||
name: "Particle Spin",
|
||||
type: "SliderRadian",
|
||||
min: -360.0,
|
||||
max: 360.0
|
||||
},
|
||||
{
|
||||
type: "Row"
|
||||
},
|
||||
{
|
||||
id: "spinSpread",
|
||||
name: "Spin Spread",
|
||||
type: "SliderRadian",
|
||||
max: 360.0
|
||||
},
|
||||
{
|
||||
type: "Row"
|
||||
},
|
||||
{
|
||||
id: "spinStart",
|
||||
name: "Spin Start",
|
||||
type: "SliderRadian",
|
||||
min: -360.0,
|
||||
max: 360.0
|
||||
},
|
||||
{
|
||||
type: "Row"
|
||||
},
|
||||
{
|
||||
id: "spinFinish",
|
||||
name: "Spin Finish",
|
||||
type: "SliderRadian",
|
||||
min: -360.0,
|
||||
max: 360.0
|
||||
},
|
||||
{
|
||||
type: "Row"
|
||||
},
|
||||
{
|
||||
id: "rotateWithEntity",
|
||||
name: "Rotate with Entity",
|
||||
type: "Boolean"
|
||||
},
|
||||
{
|
||||
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 () {
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
openEventBridge(function (EventBridge) {
|
||||
ui.connect(EventBridge);
|
||||
});
|
||||
if (overrideLoad) {
|
||||
openEventBridge();
|
||||
}
|
||||
};
|
||||
})();
|
|
@ -1,144 +0,0 @@
|
|||
//
|
||||
// particleExplorerTool.js
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
/* global ParticleExplorerTool */
|
||||
|
||||
|
||||
var PARTICLE_EXPLORER_HTML_URL = Script.resolvePath('particleExplorer.html');
|
||||
|
||||
ParticleExplorerTool = function(createToolsWindow) {
|
||||
var that = {};
|
||||
that.activeParticleEntity = 0;
|
||||
that.updatedActiveParticleProperties = {};
|
||||
|
||||
that.createWebView = function() {
|
||||
that.webView = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
that.webView.setVisible = function(value) {};
|
||||
that.webView.webEventReceived.connect(that.webEventReceived);
|
||||
createToolsWindow.webEventReceived.addListener(this, that.webEventReceived);
|
||||
};
|
||||
|
||||
function emitScriptEvent(data) {
|
||||
var messageData = JSON.stringify(data);
|
||||
that.webView.emitScriptEvent(messageData);
|
||||
createToolsWindow.emitScriptEvent(messageData);
|
||||
}
|
||||
|
||||
that.destroyWebView = function() {
|
||||
if (!that.webView) {
|
||||
return;
|
||||
}
|
||||
that.activeParticleEntity = 0;
|
||||
that.updatedActiveParticleProperties = {};
|
||||
|
||||
emitScriptEvent({
|
||||
messageType: "particle_close"
|
||||
});
|
||||
};
|
||||
|
||||
function sendParticleProperties(properties) {
|
||||
emitScriptEvent({
|
||||
messageType: "particle_settings",
|
||||
currentProperties: properties
|
||||
});
|
||||
}
|
||||
|
||||
function sendActiveParticleProperties() {
|
||||
var properties = Entities.getEntityProperties(that.activeParticleEntity);
|
||||
if (properties.emitOrientation) {
|
||||
properties.emitOrientation = Quat.safeEulerAngles(properties.emitOrientation);
|
||||
}
|
||||
// Update uninitialized variables
|
||||
if (isNaN(properties.alphaStart)) {
|
||||
properties.alphaStart = properties.alpha;
|
||||
}
|
||||
if (isNaN(properties.alphaFinish)) {
|
||||
properties.alphaFinish = properties.alpha;
|
||||
}
|
||||
if (isNaN(properties.radiusStart)) {
|
||||
properties.radiusStart = properties.particleRadius;
|
||||
}
|
||||
if (isNaN(properties.radiusFinish)) {
|
||||
properties.radiusFinish = properties.particleRadius;
|
||||
}
|
||||
if (isNaN(properties.colorStart.red)) {
|
||||
properties.colorStart = properties.color;
|
||||
}
|
||||
if (isNaN(properties.colorFinish.red)) {
|
||||
properties.colorFinish = properties.color;
|
||||
}
|
||||
if (isNaN(properties.spinStart)) {
|
||||
properties.spinStart = properties.particleSpin;
|
||||
}
|
||||
if (isNaN(properties.spinFinish)) {
|
||||
properties.spinFinish = properties.particleSpin;
|
||||
}
|
||||
sendParticleProperties(properties);
|
||||
}
|
||||
|
||||
function sendUpdatedActiveParticleProperties() {
|
||||
sendParticleProperties(that.updatedActiveParticleProperties);
|
||||
that.updatedActiveParticleProperties = {};
|
||||
}
|
||||
|
||||
that.webEventReceived = function(message) {
|
||||
var data = JSON.parse(message);
|
||||
if (data.messageType === "settings_update") {
|
||||
var updatedSettings = data.updatedSettings;
|
||||
|
||||
var optionalProps = ["alphaStart", "alphaFinish", "radiusStart", "radiusFinish", "colorStart", "colorFinish", "spinStart", "spinFinish"];
|
||||
var fallbackProps = ["alpha", "particleRadius", "color", "particleSpin"];
|
||||
for (var i = 0; i < optionalProps.length; i++) {
|
||||
var fallbackProp = fallbackProps[Math.floor(i / 2)];
|
||||
var optionalValue = updatedSettings[optionalProps[i]];
|
||||
var fallbackValue = updatedSettings[fallbackProp];
|
||||
if (optionalValue && fallbackValue) {
|
||||
delete updatedSettings[optionalProps[i]];
|
||||
}
|
||||
}
|
||||
|
||||
if (updatedSettings.emitOrientation) {
|
||||
updatedSettings.emitOrientation = Quat.fromVec3Degrees(updatedSettings.emitOrientation);
|
||||
}
|
||||
|
||||
Entities.editEntity(that.activeParticleEntity, updatedSettings);
|
||||
|
||||
var entityProps = Entities.getEntityProperties(that.activeParticleEntity, optionalProps);
|
||||
|
||||
var needsUpdate = false;
|
||||
for (var i = 0; i < optionalProps.length; i++) {
|
||||
var fallbackProp = fallbackProps[Math.floor(i / 2)];
|
||||
var fallbackValue = updatedSettings[fallbackProp];
|
||||
if (fallbackValue) {
|
||||
var optionalProp = optionalProps[i];
|
||||
if ((fallbackProp !== "color" && isNaN(entityProps[optionalProp])) || (fallbackProp === "color" && isNaN(entityProps[optionalProp].red))) {
|
||||
that.updatedActiveParticleProperties[optionalProp] = fallbackValue;
|
||||
needsUpdate = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (needsUpdate) {
|
||||
sendUpdatedActiveParticleProperties();
|
||||
}
|
||||
|
||||
} else if (data.messageType === "page_loaded") {
|
||||
sendActiveParticleProperties();
|
||||
}
|
||||
};
|
||||
|
||||
that.setActiveParticleEntity = function(id) {
|
||||
that.activeParticleEntity = id;
|
||||
sendActiveParticleProperties();
|
||||
};
|
||||
|
||||
return that;
|
||||
};
|
77
tests-manual/qml/qml/MacQml.qml
Normal file
77
tests-manual/qml/qml/MacQml.qml
Normal file
|
@ -0,0 +1,77 @@
|
|||
/****************************************************************************
|
||||
**
|
||||
** Copyright (C) 2016 The Qt Company Ltd.
|
||||
** Contact: https://www.qt.io/licensing/
|
||||
**
|
||||
** This file is part of the examples of the Qt Toolkit.
|
||||
**
|
||||
** $QT_BEGIN_LICENSE:BSD$
|
||||
** Commercial License Usage
|
||||
** Licensees holding valid commercial Qt licenses may use this file in
|
||||
** accordance with the commercial license agreement provided with the
|
||||
** Software or, alternatively, in accordance with the terms contained in
|
||||
** a written agreement between you and The Qt Company. For licensing terms
|
||||
** and conditions see https://www.qt.io/terms-conditions. For further
|
||||
** information use the contact form at https://www.qt.io/contact-us.
|
||||
**
|
||||
** BSD License Usage
|
||||
** Alternatively, you may use this file under the terms of the BSD license
|
||||
** as follows:
|
||||
**
|
||||
** "Redistribution and use in source and binary forms, with or without
|
||||
** modification, are permitted provided that the following conditions are
|
||||
** met:
|
||||
** * Redistributions of source code must retain the above copyright
|
||||
** notice, this list of conditions and the following disclaimer.
|
||||
** * Redistributions in binary form must reproduce the above copyright
|
||||
** notice, this list of conditions and the following disclaimer in
|
||||
** the documentation and/or other materials provided with the
|
||||
** distribution.
|
||||
** * Neither the name of The Qt Company Ltd nor the names of its
|
||||
** contributors may be used to endorse or promote products derived
|
||||
** from this software without specific prior written permission.
|
||||
**
|
||||
**
|
||||
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
|
||||
**
|
||||
** $QT_END_LICENSE$
|
||||
**
|
||||
****************************************************************************/
|
||||
|
||||
import QtQuick 2.2
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Dialogs 1.1
|
||||
import QtQuick.Controls 1.2
|
||||
import QtWebEngine 1.5
|
||||
|
||||
Item {
|
||||
width: 640
|
||||
height: 480
|
||||
|
||||
Rectangle {
|
||||
width: 5
|
||||
height: 5
|
||||
color: "red"
|
||||
ColorAnimation on color { loops: Animation.Infinite; from: "red"; to: "yellow"; duration: 1000 }
|
||||
}
|
||||
|
||||
|
||||
WebEngineView {
|
||||
id: root
|
||||
url: "https://google.com/"
|
||||
x: 6; y: 6;
|
||||
width: parent.width * 0.8
|
||||
height: parent.height * 0.8
|
||||
|
||||
}
|
||||
}
|
60
tests-manual/qml/src/MacQml.cpp
Normal file
60
tests-manual/qml/src/MacQml.cpp
Normal file
|
@ -0,0 +1,60 @@
|
|||
#include "MacQml.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include <QtQuick/QQuickItem>
|
||||
|
||||
#include <SharedUtil.h>
|
||||
|
||||
using TextureAndFence = hifi::qml::OffscreenSurface::TextureAndFence;
|
||||
|
||||
void MacQml::update() {
|
||||
auto rootItem =_surface->getRootItem();
|
||||
float now = sinf(secTimestampNow());
|
||||
rootItem->setProperty("level", fabs(now));
|
||||
rootItem->setProperty("muted", now > 0.0f);
|
||||
rootItem->setProperty("statsValue", rand());
|
||||
|
||||
// Fetch any new textures
|
||||
TextureAndFence newTextureAndFence;
|
||||
if (_surface->fetchTexture(newTextureAndFence)) {
|
||||
if (_texture != 0) {
|
||||
auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
glFlush();
|
||||
_discardLamdba(_texture, readFence);
|
||||
}
|
||||
_texture = newTextureAndFence.first;
|
||||
_glf.glWaitSync((GLsync)newTextureAndFence.second, 0, GL_TIMEOUT_IGNORED);
|
||||
}
|
||||
}
|
||||
|
||||
void MacQml::init() {
|
||||
Parent::init();
|
||||
_glf.glGenFramebuffers(1, &_fbo);
|
||||
_surface.reset(new hifi::qml::OffscreenSurface());
|
||||
//QUrl url =getTestResource("qml/main.qml");
|
||||
QUrl url = getTestResource("qml/MacQml.qml");
|
||||
hifi::qml::QmlContextObjectCallback callback =[](QQmlContext* context, QQuickItem* item) {
|
||||
};
|
||||
_surface->load(url, callback);
|
||||
_surface->resize(_window->size());
|
||||
_surface->resume();
|
||||
|
||||
}
|
||||
|
||||
void MacQml::draw() {
|
||||
auto size = _window->geometry().size();
|
||||
if (_texture) {
|
||||
_glf.glBindFramebuffer(GL_READ_FRAMEBUFFER, _fbo);
|
||||
_glf.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _texture, 0);
|
||||
_glf.glBlitFramebuffer(
|
||||
// src coordinates
|
||||
0, 0, size.width(), size.height(),
|
||||
// dst coordinates
|
||||
0, 0, size.width(), size.height(),
|
||||
// blit mask and filter
|
||||
GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
_glf.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
|
||||
_glf.glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
||||
}
|
||||
}
|
16
tests-manual/qml/src/MacQml.h
Normal file
16
tests-manual/qml/src/MacQml.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
#include "TestCase.h"
|
||||
|
||||
#include <qml/OffscreenSurface.h>
|
||||
|
||||
class MacQml : public TestCase {
|
||||
using Parent = TestCase;
|
||||
public:
|
||||
GLuint _texture{ 0 };
|
||||
QmlPtr _surface;
|
||||
GLuint _fbo{ 0 };
|
||||
|
||||
MacQml(const QWindow* window) : Parent(window) {}
|
||||
void update() override;
|
||||
void init() override;
|
||||
void draw() override;
|
||||
};
|
131
tests-manual/qml/src/StressWeb.cpp
Normal file
131
tests-manual/qml/src/StressWeb.cpp
Normal file
|
@ -0,0 +1,131 @@
|
|||
#include "StressWeb.h"
|
||||
|
||||
#include <QtQuick/QQuickItem>
|
||||
|
||||
#include <SharedUtil.h>
|
||||
|
||||
using TextureAndFence = hifi::qml::OffscreenSurface::TextureAndFence;
|
||||
|
||||
static const int DEFAULT_MAX_FPS = 10;
|
||||
static const QString CONTROL_URL{ "/qml/controls/WebEntityView.qml" };
|
||||
static const char* URL_PROPERTY{ "url" };
|
||||
|
||||
QString StressWeb::getSourceUrl(bool video) {
|
||||
static const std::vector<QString> SOURCE_URLS{
|
||||
"https://www.reddit.com/wiki/random",
|
||||
"https://en.wikipedia.org/wiki/Wikipedia:Random",
|
||||
"https://slashdot.org/",
|
||||
};
|
||||
|
||||
static const std::vector<QString> VIDEO_SOURCE_URLS{
|
||||
"https://www.youtube.com/watch?v=gDXwhHm4GhM",
|
||||
"https://www.youtube.com/watch?v=Ch_hoYPPeGc",
|
||||
};
|
||||
|
||||
const auto& sourceUrls = video ? VIDEO_SOURCE_URLS : SOURCE_URLS;
|
||||
auto index = rand() % sourceUrls.size();
|
||||
return sourceUrls[index];
|
||||
}
|
||||
|
||||
|
||||
|
||||
void StressWeb::buildSurface(QmlInfo& qmlInfo, bool video) {
|
||||
++_surfaceCount;
|
||||
auto lifetimeSecs = (uint32_t)(5.0f + (randFloat() * 10.0f));
|
||||
auto lifetimeUsecs = (USECS_PER_SECOND * lifetimeSecs);
|
||||
qmlInfo.lifetime = lifetimeUsecs + usecTimestampNow();
|
||||
qmlInfo.texture = 0;
|
||||
qmlInfo.surface.reset(new hifi::qml::OffscreenSurface());
|
||||
qmlInfo.surface->load(getTestResource(CONTROL_URL), [video](QQmlContext* context, QQuickItem* item) {
|
||||
item->setProperty(URL_PROPERTY, getSourceUrl(video));
|
||||
});
|
||||
qmlInfo.surface->setMaxFps(DEFAULT_MAX_FPS);
|
||||
qmlInfo.surface->resize(_qmlSize);
|
||||
qmlInfo.surface->resume();
|
||||
}
|
||||
|
||||
void StressWeb::destroySurface(QmlInfo& qmlInfo) {
|
||||
auto& surface = qmlInfo.surface;
|
||||
auto& currentTexture = qmlInfo.texture;
|
||||
if (currentTexture) {
|
||||
auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
glFlush();
|
||||
_discardLamdba(currentTexture, readFence);
|
||||
}
|
||||
auto webView = surface->getRootItem();
|
||||
if (webView) {
|
||||
// stop loading
|
||||
QMetaObject::invokeMethod(webView, "stop");
|
||||
webView->setProperty(URL_PROPERTY, "about:blank");
|
||||
}
|
||||
surface->pause();
|
||||
surface.reset();
|
||||
}
|
||||
|
||||
void StressWeb::update() {
|
||||
auto now = usecTimestampNow();
|
||||
// Fetch any new textures
|
||||
for (size_t x = 0; x < DIVISIONS_X; ++x) {
|
||||
for (size_t y = 0; y < DIVISIONS_Y; ++y) {
|
||||
auto& qmlInfo = _surfaces[x][y];
|
||||
if (!qmlInfo.surface) {
|
||||
if (now < _createStopTime && randFloat() > 0.99f) {
|
||||
buildSurface(qmlInfo, x == 0 && y == 0);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (now > qmlInfo.lifetime) {
|
||||
destroySurface(qmlInfo);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& surface = qmlInfo.surface;
|
||||
auto& currentTexture = qmlInfo.texture;
|
||||
|
||||
TextureAndFence newTextureAndFence;
|
||||
if (surface->fetchTexture(newTextureAndFence)) {
|
||||
if (currentTexture != 0) {
|
||||
auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
glFlush();
|
||||
_discardLamdba(currentTexture, readFence);
|
||||
}
|
||||
currentTexture = newTextureAndFence.first;
|
||||
_glf.glWaitSync((GLsync)newTextureAndFence.second, 0, GL_TIMEOUT_IGNORED);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StressWeb::init() {
|
||||
Parent::init();
|
||||
_createStopTime = usecTimestampNow() + (3000u * USECS_PER_SECOND);
|
||||
_glf.glGenFramebuffers(1, &_fbo);
|
||||
}
|
||||
|
||||
void StressWeb::draw() {
|
||||
auto size = _window->geometry().size();
|
||||
auto incrementX = size.width() / DIVISIONS_X;
|
||||
auto incrementY = size.height() / DIVISIONS_Y;
|
||||
|
||||
for (uint32_t x = 0; x < DIVISIONS_X; ++x) {
|
||||
for (uint32_t y = 0; y < DIVISIONS_Y; ++y) {
|
||||
auto& qmlInfo = _surfaces[x][y];
|
||||
if (!qmlInfo.surface || !qmlInfo.texture) {
|
||||
continue;
|
||||
}
|
||||
_glf.glBindFramebuffer(GL_READ_FRAMEBUFFER, _fbo);
|
||||
_glf.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, qmlInfo.texture, 0);
|
||||
_glf.glBlitFramebuffer(
|
||||
// src coordinates
|
||||
0, 0, _qmlSize.width() - 1, _qmlSize.height() - 1,
|
||||
// dst coordinates
|
||||
incrementX * x, incrementY * y, incrementX * (x + 1), incrementY * (y + 1),
|
||||
// blit mask and filter
|
||||
GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
}
|
||||
}
|
||||
_glf.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
|
||||
_glf.glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
||||
}
|
34
tests-manual/qml/src/StressWeb.h
Normal file
34
tests-manual/qml/src/StressWeb.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
#include "TestCase.h"
|
||||
|
||||
#include <array>
|
||||
|
||||
#include <qml/OffscreenSurface.h>
|
||||
|
||||
#define DIVISIONS_X 5
|
||||
#define DIVISIONS_Y 5
|
||||
|
||||
class StressWeb : public TestCase {
|
||||
using Parent = TestCase;
|
||||
public:
|
||||
using QmlPtr = QSharedPointer<hifi::qml::OffscreenSurface>;
|
||||
|
||||
struct QmlInfo {
|
||||
QmlPtr surface;
|
||||
GLuint texture{ 0 };
|
||||
uint64_t lifetime{ 0 };
|
||||
};
|
||||
|
||||
size_t _surfaceCount{ 0 };
|
||||
uint64_t _createStopTime{ 0 };
|
||||
const QSize _qmlSize{ 640, 480 };
|
||||
std::array<std::array<QmlInfo, DIVISIONS_Y>, DIVISIONS_X> _surfaces;
|
||||
GLuint _fbo{ 0 };
|
||||
|
||||
StressWeb(const QWindow* window) : Parent(window) {}
|
||||
static QString getSourceUrl(bool video);
|
||||
void buildSurface(QmlInfo& qmlInfo, bool video);
|
||||
void destroySurface(QmlInfo& qmlInfo);
|
||||
void update() override;
|
||||
void init() override;
|
||||
void draw() override;
|
||||
};
|
25
tests-manual/qml/src/TestCase.cpp
Normal file
25
tests-manual/qml/src/TestCase.cpp
Normal file
|
@ -0,0 +1,25 @@
|
|||
#include "TestCase.h"
|
||||
|
||||
#include <QtCore/QLoggingCategory>
|
||||
#include <QtCore/QDir>
|
||||
|
||||
void TestCase::destroy() {
|
||||
}
|
||||
void TestCase::update() {
|
||||
}
|
||||
|
||||
void TestCase::init() {
|
||||
_glf.initializeOpenGLFunctions();
|
||||
_discardLamdba = hifi::qml::OffscreenSurface::getDiscardLambda();
|
||||
}
|
||||
|
||||
QUrl TestCase::getTestResource(const QString& relativePath) {
|
||||
static QString dir;
|
||||
if (dir.isEmpty()) {
|
||||
QDir path(__FILE__);
|
||||
path.cdUp();
|
||||
dir = path.cleanPath(path.absoluteFilePath("../")) + "/";
|
||||
qDebug() << "Resources Path: " << dir;
|
||||
}
|
||||
return QUrl::fromLocalFile(dir + relativePath);
|
||||
}
|
23
tests-manual/qml/src/TestCase.h
Normal file
23
tests-manual/qml/src/TestCase.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <QtGui/QWindow>
|
||||
#include <QtGui/QOpenGLFunctions_4_1_Core>
|
||||
#include <qml/OffscreenSurface.h>
|
||||
|
||||
class TestCase {
|
||||
public:
|
||||
using QmlPtr = QSharedPointer<hifi::qml::OffscreenSurface>;
|
||||
using Builder = std::function<TestCase*(const QWindow*)>;
|
||||
TestCase(const QWindow* window) : _window(window) {}
|
||||
virtual void init();
|
||||
virtual void destroy();
|
||||
virtual void update();
|
||||
virtual void draw() = 0;
|
||||
static QUrl getTestResource(const QString& relativePath);
|
||||
|
||||
protected:
|
||||
QOpenGLFunctions_4_1_Core _glf;
|
||||
const QWindow* _window;
|
||||
std::function<void(uint32_t, void*)> _discardLamdba;
|
||||
};
|
|
@ -43,6 +43,11 @@
|
|||
#include <qml/OffscreenSurface.h>
|
||||
#include <unordered_set>
|
||||
#include <array>
|
||||
#include <gl/GLHelpers.h>
|
||||
#include <gl/Context.h>
|
||||
|
||||
#include "TestCase.h"
|
||||
#include "MacQml.h"
|
||||
|
||||
namespace gl {
|
||||
extern void initModuleGl();
|
||||
|
@ -67,53 +72,37 @@ QUrl getTestResource(const QString& relativePath) {
|
|||
return QUrl::fromLocalFile(dir + relativePath);
|
||||
}
|
||||
|
||||
#define DIVISIONS_X 5
|
||||
#define DIVISIONS_Y 5
|
||||
|
||||
using QmlPtr = QSharedPointer<hifi::qml::OffscreenSurface>;
|
||||
using TextureAndFence = hifi::qml::OffscreenSurface::TextureAndFence;
|
||||
|
||||
struct QmlInfo {
|
||||
QmlPtr surface;
|
||||
GLuint texture{ 0 };
|
||||
uint64_t lifetime{ 0 };
|
||||
};
|
||||
|
||||
class TestWindow : public QWindow {
|
||||
public:
|
||||
TestWindow();
|
||||
TestWindow(const TestCase::Builder& caseBuilder);
|
||||
|
||||
private:
|
||||
QOpenGLContext _glContext;
|
||||
OffscreenGLCanvas _sharedContext;
|
||||
std::array<std::array<QmlInfo, DIVISIONS_Y>, DIVISIONS_X> _surfaces;
|
||||
|
||||
TestCase* _testCase{ nullptr };
|
||||
QOpenGLFunctions_4_1_Core _glf;
|
||||
std::function<void(uint32_t, void*)> _discardLamdba;
|
||||
QSize _size;
|
||||
size_t _surfaceCount{ 0 };
|
||||
GLuint _fbo{ 0 };
|
||||
const QSize _qmlSize{ 640, 480 };
|
||||
bool _aboutToQuit{ false };
|
||||
uint64_t _createStopTime;
|
||||
void initGl();
|
||||
void updateSurfaces();
|
||||
void buildSurface(QmlInfo& qmlInfo, bool allowVideo);
|
||||
void destroySurface(QmlInfo& qmlInfo);
|
||||
void resizeWindow(const QSize& size);
|
||||
void draw();
|
||||
void resizeEvent(QResizeEvent* ev) override;
|
||||
};
|
||||
|
||||
TestWindow::TestWindow() {
|
||||
TestWindow::TestWindow(const TestCase::Builder& builder) {
|
||||
Setting::init();
|
||||
|
||||
_testCase = builder(this);
|
||||
|
||||
setSurfaceType(QSurface::OpenGLSurface);
|
||||
|
||||
qmlRegisterType<QTestItem>("Hifi", 1, 0, "TestItem");
|
||||
|
||||
show();
|
||||
_createStopTime = usecTimestampNow() + (3000u * USECS_PER_SECOND);
|
||||
|
||||
resize(QSize(800, 600));
|
||||
|
||||
|
@ -129,162 +118,84 @@ TestWindow::TestWindow() {
|
|||
});
|
||||
}
|
||||
|
||||
Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context);
|
||||
Q_GUI_EXPORT QOpenGLContext *qt_gl_global_share_context();
|
||||
OffscreenGLCanvas* _chromiumShareContext{ nullptr};
|
||||
void TestWindow::initGl() {
|
||||
_glContext.setFormat(format());
|
||||
|
||||
auto globalShareContext = qt_gl_global_share_context();
|
||||
if (globalShareContext) {
|
||||
_glContext.setShareContext(globalShareContext);
|
||||
globalShareContext->makeCurrent(this);
|
||||
gl::Context::setupDebugLogging(globalShareContext);
|
||||
globalShareContext->doneCurrent();
|
||||
}
|
||||
|
||||
if (!_glContext.create() || !_glContext.makeCurrent(this)) {
|
||||
qFatal("Unable to intialize Window GL context");
|
||||
}
|
||||
gl::Context::setupDebugLogging(&_glContext);
|
||||
gl::initModuleGl();
|
||||
|
||||
_glf.initializeOpenGLFunctions();
|
||||
_glf.glGenFramebuffers(1, &_fbo);
|
||||
|
||||
if (!_sharedContext.create(&_glContext) || !_sharedContext.makeCurrent()) {
|
||||
qFatal("Unable to intialize Shared GL context");
|
||||
}
|
||||
hifi::qml::OffscreenSurface::setSharedContext(_sharedContext.getContext());
|
||||
_discardLamdba = hifi::qml::OffscreenSurface::getDiscardLambda();
|
||||
|
||||
if (!globalShareContext) {
|
||||
_chromiumShareContext = new OffscreenGLCanvas();
|
||||
_chromiumShareContext->setObjectName("ChromiumShareContext");
|
||||
_chromiumShareContext->create(&_glContext);
|
||||
if (!_chromiumShareContext->makeCurrent()) {
|
||||
qFatal("Unable to make chromium shared context current");
|
||||
}
|
||||
|
||||
qt_gl_set_global_share_context(_chromiumShareContext->getContext());
|
||||
_chromiumShareContext->doneCurrent();
|
||||
}
|
||||
|
||||
// Restore the GL widget context
|
||||
if (!_glContext.makeCurrent(this)) {
|
||||
qFatal("Unable to make window context current");
|
||||
}
|
||||
|
||||
_testCase->init();
|
||||
}
|
||||
|
||||
void TestWindow::resizeWindow(const QSize& size) {
|
||||
_size = size;
|
||||
}
|
||||
|
||||
static const int DEFAULT_MAX_FPS = 10;
|
||||
static const QString CONTROL_URL{ "/qml/controls/WebEntityView.qml" };
|
||||
static const char* URL_PROPERTY{ "url" };
|
||||
|
||||
QString getSourceUrl(bool video) {
|
||||
static const std::vector<QString> SOURCE_URLS{
|
||||
"https://www.reddit.com/wiki/random",
|
||||
"https://en.wikipedia.org/wiki/Wikipedia:Random",
|
||||
"https://slashdot.org/",
|
||||
};
|
||||
|
||||
static const std::vector<QString> VIDEO_SOURCE_URLS{
|
||||
"https://www.youtube.com/watch?v=gDXwhHm4GhM",
|
||||
"https://www.youtube.com/watch?v=Ch_hoYPPeGc",
|
||||
};
|
||||
|
||||
const auto& sourceUrls = video ? VIDEO_SOURCE_URLS : SOURCE_URLS;
|
||||
auto index = rand() % sourceUrls.size();
|
||||
return sourceUrls[index];
|
||||
}
|
||||
|
||||
void TestWindow::buildSurface(QmlInfo& qmlInfo, bool video) {
|
||||
++_surfaceCount;
|
||||
auto lifetimeSecs = (uint32_t)(5.0f + (randFloat() * 10.0f));
|
||||
auto lifetimeUsecs = (USECS_PER_SECOND * lifetimeSecs);
|
||||
qmlInfo.lifetime = lifetimeUsecs + usecTimestampNow();
|
||||
qmlInfo.texture = 0;
|
||||
qmlInfo.surface.reset(new hifi::qml::OffscreenSurface());
|
||||
qmlInfo.surface->load(getTestResource(CONTROL_URL), [video](QQmlContext* context, QQuickItem* item) {
|
||||
item->setProperty(URL_PROPERTY, getSourceUrl(video));
|
||||
});
|
||||
qmlInfo.surface->setMaxFps(DEFAULT_MAX_FPS);
|
||||
qmlInfo.surface->resize(_qmlSize);
|
||||
qmlInfo.surface->resume();
|
||||
}
|
||||
|
||||
void TestWindow::destroySurface(QmlInfo& qmlInfo) {
|
||||
auto& surface = qmlInfo.surface;
|
||||
auto& currentTexture = qmlInfo.texture;
|
||||
if (currentTexture) {
|
||||
auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
glFlush();
|
||||
_discardLamdba(currentTexture, readFence);
|
||||
}
|
||||
auto webView = surface->getRootItem();
|
||||
if (webView) {
|
||||
// stop loading
|
||||
QMetaObject::invokeMethod(webView, "stop");
|
||||
webView->setProperty(URL_PROPERTY, "about:blank");
|
||||
}
|
||||
surface->pause();
|
||||
surface.reset();
|
||||
}
|
||||
|
||||
void TestWindow::updateSurfaces() {
|
||||
auto now = usecTimestampNow();
|
||||
// Fetch any new textures
|
||||
for (size_t x = 0; x < DIVISIONS_X; ++x) {
|
||||
for (size_t y = 0; y < DIVISIONS_Y; ++y) {
|
||||
auto& qmlInfo = _surfaces[x][y];
|
||||
if (!qmlInfo.surface) {
|
||||
if (now < _createStopTime && randFloat() > 0.99f) {
|
||||
buildSurface(qmlInfo, x == 0 && y == 0);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (now > qmlInfo.lifetime) {
|
||||
destroySurface(qmlInfo);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto& surface = qmlInfo.surface;
|
||||
auto& currentTexture = qmlInfo.texture;
|
||||
|
||||
TextureAndFence newTextureAndFence;
|
||||
if (surface->fetchTexture(newTextureAndFence)) {
|
||||
if (currentTexture != 0) {
|
||||
auto readFence = _glf.glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
glFlush();
|
||||
_discardLamdba(currentTexture, readFence);
|
||||
}
|
||||
currentTexture = newTextureAndFence.first;
|
||||
_glf.glWaitSync((GLsync)newTextureAndFence.second, 0, GL_TIMEOUT_IGNORED);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TestWindow::draw() {
|
||||
if (_aboutToQuit) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Attempting to draw before we're visible and have a valid size will
|
||||
// produce GL errors.
|
||||
if (!isVisible() || _size.width() <= 0 || _size.height() <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
static std::once_flag once;
|
||||
std::call_once(once, [&] { initGl(); });
|
||||
|
||||
|
||||
if (!_glContext.makeCurrent(this)) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateSurfaces();
|
||||
|
||||
auto size = this->geometry().size();
|
||||
auto incrementX = size.width() / DIVISIONS_X;
|
||||
auto incrementY = size.height() / DIVISIONS_Y;
|
||||
|
||||
_testCase->update();
|
||||
|
||||
auto size = geometry().size();
|
||||
_glf.glViewport(0, 0, size.width(), size.height());
|
||||
_glf.glClearColor(1, 0, 0, 1);
|
||||
_glf.glClear(GL_COLOR_BUFFER_BIT);
|
||||
for (uint32_t x = 0; x < DIVISIONS_X; ++x) {
|
||||
for (uint32_t y = 0; y < DIVISIONS_Y; ++y) {
|
||||
auto& qmlInfo = _surfaces[x][y];
|
||||
if (!qmlInfo.surface || !qmlInfo.texture) {
|
||||
continue;
|
||||
}
|
||||
_glf.glBindFramebuffer(GL_READ_FRAMEBUFFER, _fbo);
|
||||
_glf.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, qmlInfo.texture, 0);
|
||||
_glf.glBlitFramebuffer(
|
||||
// src coordinates
|
||||
0, 0, _qmlSize.width() - 1, _qmlSize.height() - 1,
|
||||
// dst coordinates
|
||||
incrementX * x, incrementY * y, incrementX * (x + 1), incrementY * (y + 1),
|
||||
// blit mask and filter
|
||||
GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
}
|
||||
}
|
||||
_glf.glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0);
|
||||
_glf.glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
|
||||
|
||||
_testCase->draw();
|
||||
|
||||
_glContext.swapBuffers(this);
|
||||
}
|
||||
|
||||
|
@ -292,19 +203,15 @@ void TestWindow::resizeEvent(QResizeEvent* ev) {
|
|||
resizeWindow(ev->size());
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
|
||||
QSurfaceFormat format;
|
||||
format.setDepthBufferSize(24);
|
||||
format.setStencilBufferSize(8);
|
||||
int main(int argc, char** argv) {
|
||||
auto format = getDefaultOpenGLSurfaceFormat();
|
||||
format.setVersion(4, 1);
|
||||
format.setProfile(QSurfaceFormat::OpenGLContextProfile::CoreProfile);
|
||||
format.setOption(QSurfaceFormat::DebugContext);
|
||||
QSurfaceFormat::setDefaultFormat(format);
|
||||
// setFormat(format);
|
||||
|
||||
QGuiApplication app(argc, argv);
|
||||
TestWindow window;
|
||||
TestCase::Builder builder = [](const QWindow* window)->TestCase*{ return new MacQml(window); };
|
||||
TestWindow window(builder);
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue