mirror of
https://github.com/overte-org/overte.git
synced 2025-04-21 23:44:09 +02:00
Merge branch 'master' of github.com:highfidelity/hifi into oculus-store-commerce
This commit is contained in:
commit
d47fd44a47
106 changed files with 8384 additions and 6303 deletions
CMakeLists.txt
assignment-client/src/avatars
AvatarMixer.cppAvatarMixerClientData.cppAvatarMixerClientData.hAvatarMixerSlave.cppAvatarMixerSlave.h
interface
resources
avatar
controllers
qml/hifi/tablet
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
assets/data
controllers/controllerModules
edit.jshtml
libraries
particle_explorer
server-console/src
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,
|
||||
|
|
140
interface/resources/avatar/network-animation.json
Normal file
140
interface/resources/avatar/network-animation.json
Normal file
|
@ -0,0 +1,140 @@
|
|||
{
|
||||
"version": "1.1",
|
||||
"root": {
|
||||
"id": "userAnimStateMachine",
|
||||
"type": "stateMachine",
|
||||
"data": {
|
||||
"currentState": "idleAnim",
|
||||
"states": [
|
||||
{
|
||||
"id": "idleAnim",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "postTransitAnim", "state": "postTransitAnim" },
|
||||
{ "var": "preTransitAnim", "state": "preTransitAnim" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "preTransitAnim",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "idleAnim", "state": "idleAnim" },
|
||||
{ "var": "transitAnim", "state": "transitAnim" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "transitAnim",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "preTransitAnim", "state": "preTransitAnim" },
|
||||
{ "var": "postTransitAnim", "state": "postTransitAnim" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "postTransitAnim",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "transitAnim", "state": "transitAnim" },
|
||||
{ "var": "idleAnim", "state": "idleAnim" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "userAnimA",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "idleAnim", "state": "idleAnim" },
|
||||
{ "var": "userAnimB", "state": "userAnimB" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "userAnimB",
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "idleAnim", "state": "idleAnim" },
|
||||
{ "var": "userAnimA", "state": "userAnimA" }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "idleAnim",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/idle.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 90.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "preTransitAnim",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 10.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": false
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "transitAnim",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx",
|
||||
"startFrame": 11.0,
|
||||
"endFrame": 11.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "postTransitAnim",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "https://hifi-content.s3.amazonaws.com/luis/test_scripts/transitApp/animations/teleport01_warp.fbx",
|
||||
"startFrame": 22.0,
|
||||
"endFrame": 49.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": false
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "userAnimA",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/idle.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 90.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "userAnimB",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "qrc:///avatar/animations/idle.fbx",
|
||||
"startFrame": 0.0,
|
||||
"endFrame": 90.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": true
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
@ -1372,7 +1376,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();
|
||||
|
@ -2729,46 +2732,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");
|
||||
}
|
||||
|
@ -5817,6 +5840,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...
|
||||
|
@ -5983,7 +6042,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...
|
||||
|
@ -6068,8 +6129,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();
|
||||
}
|
||||
|
@ -8313,6 +8376,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";
|
||||
|
|
|
@ -135,7 +135,7 @@ bool AvatarActionHold::getTarget(float deltaTimeStep, glm::quat& rotation, glm::
|
|||
glm::vec3 palmPosition;
|
||||
glm::quat palmRotation;
|
||||
|
||||
bool isTransitingWithAvatar = holdingAvatar->getTransit()->isTransiting();
|
||||
bool isTransitingWithAvatar = holdingAvatar->getTransit()->isActive();
|
||||
if (isTransitingWithAvatar != _isTransitingWithAvatar) {
|
||||
_isTransitingWithAvatar = isTransitingWithAvatar;
|
||||
auto ownerEntity = _ownerEntity.lock();
|
||||
|
@ -424,7 +424,7 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
|
|||
if (ownerEntity) {
|
||||
ownerEntity->setDynamicDataDirty(true);
|
||||
ownerEntity->setDynamicDataNeedsTransmit(true);
|
||||
ownerEntity->setTransitingWithAvatar(myAvatar->getTransit()->isTransiting());
|
||||
ownerEntity->setTransitingWithAvatar(myAvatar->getTransit()->isActive());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -71,14 +71,12 @@ AvatarManager::AvatarManager(QObject* parent) :
|
|||
}
|
||||
});
|
||||
|
||||
const float AVATAR_TRANSIT_TRIGGER_DISTANCE = 1.0f;
|
||||
const int AVATAR_TRANSIT_FRAME_COUNT = 11; // Based on testing
|
||||
const int AVATAR_TRANSIT_FRAMES_PER_METER = 1; // Based on testing
|
||||
|
||||
_transitConfig._totalFrames = AVATAR_TRANSIT_FRAME_COUNT;
|
||||
_transitConfig._triggerDistance = AVATAR_TRANSIT_TRIGGER_DISTANCE;
|
||||
_transitConfig._minTriggerDistance = AVATAR_TRANSIT_MIN_TRIGGER_DISTANCE;
|
||||
_transitConfig._maxTriggerDistance = AVATAR_TRANSIT_MAX_TRIGGER_DISTANCE;
|
||||
_transitConfig._framesPerMeter = AVATAR_TRANSIT_FRAMES_PER_METER;
|
||||
_transitConfig._isDistanceBased = true;
|
||||
_transitConfig._isDistanceBased = AVATAR_TRANSIT_DISTANCE_BASED;
|
||||
_transitConfig._abortDistance = AVATAR_TRANSIT_ABORT_DISTANCE;
|
||||
}
|
||||
|
||||
AvatarSharedPointer AvatarManager::addAvatar(const QUuid& sessionUUID, const QWeakPointer<Node>& mixerWeakPointer) {
|
||||
|
@ -126,13 +124,39 @@ void AvatarManager::setSpace(workload::SpacePointer& space ) {
|
|||
_space = space;
|
||||
}
|
||||
|
||||
void AvatarManager::handleTransitAnimations(AvatarTransit::Status status) {
|
||||
switch (status) {
|
||||
case AvatarTransit::Status::STARTED:
|
||||
_myAvatar->getSkeletonModel()->getRig().triggerNetworkAnimation("preTransitAnim");
|
||||
break;
|
||||
case AvatarTransit::Status::START_TRANSIT:
|
||||
_myAvatar->getSkeletonModel()->getRig().triggerNetworkAnimation("transitAnim");
|
||||
break;
|
||||
case AvatarTransit::Status::END_TRANSIT:
|
||||
_myAvatar->getSkeletonModel()->getRig().triggerNetworkAnimation("postTransitAnim");
|
||||
break;
|
||||
case AvatarTransit::Status::ENDED:
|
||||
_myAvatar->getSkeletonModel()->getRig().triggerNetworkAnimation("idleAnim");
|
||||
break;
|
||||
case AvatarTransit::Status::PRE_TRANSIT:
|
||||
break;
|
||||
case AvatarTransit::Status::POST_TRANSIT:
|
||||
break;
|
||||
case AvatarTransit::Status::IDLE:
|
||||
break;
|
||||
case AvatarTransit::Status::TRANSITING:
|
||||
break;
|
||||
case AvatarTransit::Status::ABORT_TRANSIT:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarManager::updateMyAvatar(float deltaTime) {
|
||||
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||
PerformanceWarning warn(showWarnings, "AvatarManager::updateMyAvatar()");
|
||||
|
||||
AvatarTransit::Status status = _myAvatar->updateTransit(deltaTime, _myAvatar->getNextPosition(), _transitConfig);
|
||||
bool sendFirstTransitPackage = (status == AvatarTransit::Status::START_TRANSIT);
|
||||
bool blockTransitData = (status == AvatarTransit::Status::TRANSITING);
|
||||
AvatarTransit::Status status = _myAvatar->updateTransit(deltaTime, _myAvatar->getNextPosition(), _myAvatar->getSensorToWorldScale(), _transitConfig);
|
||||
handleTransitAnimations(status);
|
||||
|
||||
_myAvatar->update(deltaTime);
|
||||
render::Transaction transaction;
|
||||
|
@ -142,18 +166,13 @@ void AvatarManager::updateMyAvatar(float deltaTime) {
|
|||
quint64 now = usecTimestampNow();
|
||||
quint64 dt = now - _lastSendAvatarDataTime;
|
||||
|
||||
|
||||
if (sendFirstTransitPackage || (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS && !_myAvatarDataPacketsPaused && !blockTransitData)) {
|
||||
if (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS && !_myAvatarDataPacketsPaused) {
|
||||
// send head/hand data to the avatar mixer and voxel server
|
||||
PerformanceTimer perfTimer("send");
|
||||
if (sendFirstTransitPackage) {
|
||||
_myAvatar->overrideNextPackagePositionData(_myAvatar->getTransit()->getEndPosition());
|
||||
}
|
||||
PerformanceTimer perfTimer("send");
|
||||
_myAvatar->sendAvatarDataPacket();
|
||||
_lastSendAvatarDataTime = now;
|
||||
_myAvatarSendRate.increment();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
@ -267,7 +286,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
|||
if (inView && avatar->hasNewJointData()) {
|
||||
numAvatarsUpdated++;
|
||||
}
|
||||
auto transitStatus = avatar->_transit.update(deltaTime, avatar->_globalPosition, _transitConfig);
|
||||
auto transitStatus = avatar->_transit.update(deltaTime, avatar->_serverPosition, _transitConfig);
|
||||
if (avatar->getIsNewAvatar() && (transitStatus == AvatarTransit::Status::START_TRANSIT || transitStatus == AvatarTransit::Status::ABORT_TRANSIT)) {
|
||||
avatar->_transit.reset();
|
||||
avatar->setIsNewAvatar(false);
|
||||
|
|
|
@ -221,6 +221,7 @@ private:
|
|||
// frequently grabs a read lock on the hash to get a given avatar by ID
|
||||
void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar,
|
||||
KillAvatarReason removalReason = KillAvatarReason::NoReason) override;
|
||||
void handleTransitAnimations(AvatarTransit::Status status);
|
||||
|
||||
QVector<AvatarSharedPointer> _avatarsToFade;
|
||||
|
||||
|
|
|
@ -1058,7 +1058,6 @@ void MyAvatar::updateSensorToWorldMatrix() {
|
|||
updateJointFromController(controller::Action::RIGHT_HAND, _controllerRightHandMatrixCache);
|
||||
|
||||
if (hasSensorToWorldScaleChanged) {
|
||||
setTransitScale(sensorToWorldScale);
|
||||
emit sensorToWorldScaleChanged(sensorToWorldScale);
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -51,6 +51,8 @@ public:
|
|||
bool getMirrorFlag() const { return _mirrorFlag; }
|
||||
void setMirrorFlag(bool mirrorFlag) { _mirrorFlag = mirrorFlag; }
|
||||
|
||||
float getFrame() const { return _frame; }
|
||||
|
||||
void loadURL(const QString& url);
|
||||
protected:
|
||||
|
||||
|
|
|
@ -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,7 +30,9 @@
|
|||
#include "AnimOverlay.h"
|
||||
#include "AnimSkeleton.h"
|
||||
#include "AnimUtil.h"
|
||||
#include "AvatarConstants.h"
|
||||
#include "IKTarget.h"
|
||||
#include "PathUtils.h"
|
||||
|
||||
|
||||
static int nextRigId = 1;
|
||||
|
@ -133,6 +135,30 @@ void Rig::overrideAnimation(const QString& url, float fps, bool loop, float firs
|
|||
_animVars.set("userAnimB", clipNodeEnum == UserAnimState::B);
|
||||
}
|
||||
|
||||
void Rig::triggerNetworkAnimation(const QString& animName) {
|
||||
_networkVars.set("idleAnim", false);
|
||||
_networkVars.set("preTransitAnim", false);
|
||||
_networkVars.set("transitAnim", false);
|
||||
_networkVars.set("postTransitAnim", false);
|
||||
_sendNetworkNode = true;
|
||||
|
||||
if (animName == "idleAnim") {
|
||||
_networkVars.set("idleAnim", true);
|
||||
_networkAnimState.clipNodeEnum = NetworkAnimState::Idle;
|
||||
_sendNetworkNode = false;
|
||||
} else if (animName == "preTransitAnim") {
|
||||
_networkVars.set("preTransitAnim", true);
|
||||
_networkAnimState.clipNodeEnum = NetworkAnimState::PreTransit;
|
||||
} else if (animName == "transitAnim") {
|
||||
_networkVars.set("transitAnim", true);
|
||||
_networkAnimState.clipNodeEnum = NetworkAnimState::Transit;
|
||||
} else if (animName == "postTransitAnim") {
|
||||
_networkVars.set("postTransitAnim", true);
|
||||
_networkAnimState.clipNodeEnum = NetworkAnimState::PostTransit;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Rig::restoreAnimation() {
|
||||
if (_userAnimState.clipNodeEnum != UserAnimState::None) {
|
||||
_userAnimState.clipNodeEnum = UserAnimState::None;
|
||||
|
@ -144,6 +170,16 @@ void Rig::restoreAnimation() {
|
|||
}
|
||||
}
|
||||
|
||||
void Rig::restoreNetworkAnimation() {
|
||||
if (_networkAnimState.clipNodeEnum != NetworkAnimState::Idle) {
|
||||
_networkAnimState.clipNodeEnum = NetworkAnimState::Idle;
|
||||
_networkVars.set("idleAnim", true);
|
||||
_networkVars.set("preTransitAnim", false);
|
||||
_networkVars.set("transitAnim", false);
|
||||
_networkVars.set("postTransitAnim", false);
|
||||
}
|
||||
}
|
||||
|
||||
QStringList Rig::getAnimationRoles() const {
|
||||
if (_animNode) {
|
||||
QStringList list;
|
||||
|
@ -208,11 +244,17 @@ void Rig::restoreRoleAnimation(const QString& role) {
|
|||
void Rig::destroyAnimGraph() {
|
||||
_animSkeleton.reset();
|
||||
_animLoader.reset();
|
||||
_networkLoader.reset();
|
||||
_animNode.reset();
|
||||
_internalPoseSet._relativePoses.clear();
|
||||
_internalPoseSet._absolutePoses.clear();
|
||||
_internalPoseSet._overridePoses.clear();
|
||||
_internalPoseSet._overrideFlags.clear();
|
||||
_networkNode.reset();
|
||||
_networkPoseSet._relativePoses.clear();
|
||||
_networkPoseSet._absolutePoses.clear();
|
||||
_networkPoseSet._overridePoses.clear();
|
||||
_networkPoseSet._overrideFlags.clear();
|
||||
_numOverrides = 0;
|
||||
_leftEyeJointChildren.clear();
|
||||
_rightEyeJointChildren.clear();
|
||||
|
@ -229,14 +271,24 @@ void Rig::initJointStates(const FBXGeometry& geometry, const glm::mat4& modelOff
|
|||
|
||||
_internalPoseSet._relativePoses.clear();
|
||||
_internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses();
|
||||
_networkPoseSet._relativePoses.clear();
|
||||
_networkPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses();
|
||||
|
||||
buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses);
|
||||
buildAbsoluteRigPoses(_networkPoseSet._relativePoses, _networkPoseSet._absolutePoses);
|
||||
|
||||
_internalPoseSet._overridePoses.clear();
|
||||
_internalPoseSet._overridePoses = _animSkeleton->getRelativeDefaultPoses();
|
||||
|
||||
_internalPoseSet._overrideFlags.clear();
|
||||
_internalPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints(), false);
|
||||
|
||||
_networkPoseSet._overridePoses.clear();
|
||||
_networkPoseSet._overridePoses = _animSkeleton->getRelativeDefaultPoses();
|
||||
|
||||
_networkPoseSet._overrideFlags.clear();
|
||||
_networkPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints(), false);
|
||||
|
||||
_numOverrides = 0;
|
||||
|
||||
buildAbsoluteRigPoses(_animSkeleton->getRelativeDefaultPoses(), _absoluteDefaultPoses);
|
||||
|
@ -270,6 +322,18 @@ void Rig::reset(const FBXGeometry& geometry) {
|
|||
|
||||
_internalPoseSet._overrideFlags.clear();
|
||||
_internalPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints(), false);
|
||||
|
||||
_networkPoseSet._relativePoses.clear();
|
||||
_networkPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses();
|
||||
|
||||
buildAbsoluteRigPoses(_networkPoseSet._relativePoses, _networkPoseSet._absolutePoses);
|
||||
|
||||
_networkPoseSet._overridePoses.clear();
|
||||
_networkPoseSet._overridePoses = _animSkeleton->getRelativeDefaultPoses();
|
||||
|
||||
_networkPoseSet._overrideFlags.clear();
|
||||
_networkPoseSet._overrideFlags.resize(_animSkeleton->getNumJoints(), false);
|
||||
|
||||
_numOverrides = 0;
|
||||
|
||||
buildAbsoluteRigPoses(_animSkeleton->getRelativeDefaultPoses(), _absoluteDefaultPoses);
|
||||
|
@ -629,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;
|
||||
|
@ -924,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);
|
||||
}
|
||||
|
||||
|
@ -1049,26 +1120,56 @@ void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, cons
|
|||
|
||||
updateAnimationStateHandlers();
|
||||
_animVars.setRigToGeometryTransform(_rigToGeometryTransform);
|
||||
|
||||
if (_networkNode) {
|
||||
_networkVars.setRigToGeometryTransform(_rigToGeometryTransform);
|
||||
}
|
||||
AnimContext context(_enableDebugDrawIKTargets, _enableDebugDrawIKConstraints, _enableDebugDrawIKChains,
|
||||
getGeometryToRigTransform(), rigToWorldTransform);
|
||||
|
||||
// evaluate the animation
|
||||
AnimVariantMap triggersOut;
|
||||
|
||||
AnimVariantMap networkTriggersOut;
|
||||
_internalPoseSet._relativePoses = _animNode->evaluate(_animVars, context, deltaTime, triggersOut);
|
||||
if (_networkNode) {
|
||||
_networkPoseSet._relativePoses = _networkNode->evaluate(_networkVars, context, deltaTime, networkTriggersOut);
|
||||
const float NETWORK_ANIMATION_BLEND_FRAMES = 6.0f;
|
||||
float alpha = 1.0f;
|
||||
std::shared_ptr<AnimClip> clip;
|
||||
if (_networkAnimState.clipNodeEnum == NetworkAnimState::PreTransit) {
|
||||
clip = std::dynamic_pointer_cast<AnimClip>(_networkNode->findByName("preTransitAnim"));
|
||||
if (clip) {
|
||||
alpha = (clip->getFrame() - clip->getStartFrame()) / NETWORK_ANIMATION_BLEND_FRAMES;
|
||||
}
|
||||
} else if (_networkAnimState.clipNodeEnum == NetworkAnimState::PostTransit) {
|
||||
clip = std::dynamic_pointer_cast<AnimClip>(_networkNode->findByName("postTransitAnim"));
|
||||
if (clip) {
|
||||
alpha = (clip->getEndFrame() - clip->getFrame()) / NETWORK_ANIMATION_BLEND_FRAMES;
|
||||
}
|
||||
}
|
||||
if (_sendNetworkNode) {
|
||||
for (size_t i = 0; i < _networkPoseSet._relativePoses.size(); i++) {
|
||||
_networkPoseSet._relativePoses[i].blend(_internalPoseSet._relativePoses[i], (alpha > 1.0f ? 1.0f : alpha));
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((int)_internalPoseSet._relativePoses.size() != _animSkeleton->getNumJoints()) {
|
||||
// animations haven't fully loaded yet.
|
||||
_internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses();
|
||||
}
|
||||
if ((int)_networkPoseSet._relativePoses.size() != _animSkeleton->getNumJoints()) {
|
||||
// animations haven't fully loaded yet.
|
||||
_networkPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses();
|
||||
}
|
||||
_lastAnimVars = _animVars;
|
||||
_animVars.clearTriggers();
|
||||
_animVars = triggersOut;
|
||||
_networkVars.clearTriggers();
|
||||
_networkVars = networkTriggersOut;
|
||||
_lastContext = context;
|
||||
}
|
||||
applyOverridePoses();
|
||||
buildAbsoluteRigPoses(_internalPoseSet._relativePoses, _internalPoseSet._absolutePoses);
|
||||
|
||||
buildAbsoluteRigPoses(_networkPoseSet._relativePoses, _networkPoseSet._absolutePoses);
|
||||
// copy internal poses to external poses
|
||||
{
|
||||
QWriteLocker writeLock(&_externalPoseSetLock);
|
||||
|
@ -1574,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;
|
||||
}
|
||||
|
||||
|
@ -1584,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;
|
||||
|
@ -1623,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);
|
||||
}
|
||||
|
@ -1665,6 +1786,8 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
_previousControllerParameters = params;
|
||||
}
|
||||
|
||||
void Rig::initAnimGraph(const QUrl& url) {
|
||||
|
@ -1672,11 +1795,14 @@ void Rig::initAnimGraph(const QUrl& url) {
|
|||
_animGraphURL = url;
|
||||
|
||||
_animNode.reset();
|
||||
_networkNode.reset();
|
||||
|
||||
// load the anim graph
|
||||
_animLoader.reset(new AnimNodeLoader(url));
|
||||
auto networkUrl = PathUtils::resourcesUrl("avatar/network-animation.json");
|
||||
_networkLoader.reset(new AnimNodeLoader(networkUrl));
|
||||
std::weak_ptr<AnimSkeleton> weakSkeletonPtr = _animSkeleton;
|
||||
connect(_animLoader.get(), &AnimNodeLoader::success, [this, weakSkeletonPtr](AnimNode::Pointer nodeIn) {
|
||||
connect(_animLoader.get(), &AnimNodeLoader::success, [this, weakSkeletonPtr, url](AnimNode::Pointer nodeIn) {
|
||||
_animNode = nodeIn;
|
||||
|
||||
// abort load if the previous skeleton was deleted.
|
||||
|
@ -1703,7 +1829,33 @@ void Rig::initAnimGraph(const QUrl& url) {
|
|||
emit onLoadComplete();
|
||||
});
|
||||
connect(_animLoader.get(), &AnimNodeLoader::error, [url](int error, QString str) {
|
||||
qCCritical(animation) << "Error loading" << url.toDisplayString() << "code = " << error << "str =" << str;
|
||||
qCritical(animation) << "Error loading" << url.toDisplayString() << "code = " << error << "str =" << str;
|
||||
});
|
||||
|
||||
connect(_networkLoader.get(), &AnimNodeLoader::success, [this, weakSkeletonPtr, networkUrl](AnimNode::Pointer nodeIn) {
|
||||
_networkNode = nodeIn;
|
||||
// abort load if the previous skeleton was deleted.
|
||||
auto sharedSkeletonPtr = weakSkeletonPtr.lock();
|
||||
if (!sharedSkeletonPtr) {
|
||||
return;
|
||||
}
|
||||
_networkNode->setSkeleton(sharedSkeletonPtr);
|
||||
if (_networkAnimState.clipNodeEnum != NetworkAnimState::Idle) {
|
||||
// restore the user animation we had before reset.
|
||||
NetworkAnimState origState = _networkAnimState;
|
||||
_networkAnimState = { NetworkAnimState::Idle, "", 30.0f, false, 0.0f, 0.0f };
|
||||
if (_networkAnimState.clipNodeEnum == NetworkAnimState::PreTransit) {
|
||||
triggerNetworkAnimation("preTransitAnim");
|
||||
} else if (_networkAnimState.clipNodeEnum == NetworkAnimState::Transit) {
|
||||
triggerNetworkAnimation("transitAnim");
|
||||
} else if (_networkAnimState.clipNodeEnum == NetworkAnimState::PostTransit) {
|
||||
triggerNetworkAnimation("postTransitAnim");
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
connect(_networkLoader.get(), &AnimNodeLoader::error, [networkUrl](int error, QString str) {
|
||||
qCritical(animation) << "Error loading" << networkUrl.toDisplayString() << "code = " << error << "str =" << str;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1782,13 +1934,13 @@ void Rig::copyJointsIntoJointData(QVector<JointData>& jointDataVec) const {
|
|||
if (isIndexValid(i)) {
|
||||
// rotations are in absolute rig frame.
|
||||
glm::quat defaultAbsRot = geometryToRigPose.rot() * _animSkeleton->getAbsoluteDefaultPose(i).rot();
|
||||
data.rotation = _internalPoseSet._absolutePoses[i].rot();
|
||||
data.rotation = !_sendNetworkNode ? _internalPoseSet._absolutePoses[i].rot() : _networkPoseSet._absolutePoses[i].rot();
|
||||
data.rotationIsDefaultPose = isEqual(data.rotation, defaultAbsRot);
|
||||
|
||||
// translations are in relative frame but scaled so that they are in meters,
|
||||
// instead of geometry units.
|
||||
glm::vec3 defaultRelTrans = _geometryOffset.scale() * _animSkeleton->getRelativeDefaultPose(i).trans();
|
||||
data.translation = _geometryOffset.scale() * _internalPoseSet._relativePoses[i].trans();
|
||||
data.translation = _geometryOffset.scale() * (!_sendNetworkNode ? _internalPoseSet._relativePoses[i].trans() : _networkPoseSet._relativePoses[i].trans());
|
||||
data.translationIsDefaultPose = isEqual(data.translation, defaultRelTrans);
|
||||
} else {
|
||||
data.translationIsDefaultPose = true;
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "AnimNode.h"
|
||||
#include "AnimNodeLoader.h"
|
||||
#include "SimpleMovingAverage.h"
|
||||
#include "AnimUtil.h"
|
||||
|
||||
class Rig;
|
||||
class AnimInverseKinematics;
|
||||
|
@ -113,7 +114,10 @@ public:
|
|||
void destroyAnimGraph();
|
||||
|
||||
void overrideAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame);
|
||||
void triggerNetworkAnimation(const QString& animName);
|
||||
void restoreAnimation();
|
||||
void restoreNetworkAnimation();
|
||||
|
||||
QStringList getAnimationRoles() const;
|
||||
void overrideRoleAnimation(const QString& role, const QString& url, float fps, bool loop, float firstFrame, float lastFrame);
|
||||
void restoreRoleAnimation(const QString& role);
|
||||
|
@ -172,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);
|
||||
|
@ -269,6 +274,7 @@ protected:
|
|||
|
||||
// Only accessed by the main thread
|
||||
PoseSet _internalPoseSet;
|
||||
PoseSet _networkPoseSet;
|
||||
|
||||
// Copy of the _poseSet for external threads.
|
||||
PoseSet _externalPoseSet;
|
||||
|
@ -300,9 +306,12 @@ protected:
|
|||
|
||||
QUrl _animGraphURL;
|
||||
std::shared_ptr<AnimNode> _animNode;
|
||||
std::shared_ptr<AnimNode> _networkNode;
|
||||
std::shared_ptr<AnimSkeleton> _animSkeleton;
|
||||
std::unique_ptr<AnimNodeLoader> _animLoader;
|
||||
std::unique_ptr<AnimNodeLoader> _networkLoader;
|
||||
AnimVariantMap _animVars;
|
||||
AnimVariantMap _networkVars;
|
||||
|
||||
enum class RigRole {
|
||||
Idle = 0,
|
||||
|
@ -315,6 +324,25 @@ protected:
|
|||
RigRole _state { RigRole::Idle };
|
||||
RigRole _desiredState { RigRole::Idle };
|
||||
float _desiredStateAge { 0.0f };
|
||||
|
||||
struct NetworkAnimState {
|
||||
enum ClipNodeEnum {
|
||||
Idle = 0,
|
||||
PreTransit,
|
||||
Transit,
|
||||
PostTransit
|
||||
};
|
||||
NetworkAnimState() : clipNodeEnum(NetworkAnimState::Idle) {}
|
||||
NetworkAnimState(ClipNodeEnum clipNodeEnumIn, const QString& urlIn, float fpsIn, bool loopIn, float firstFrameIn, float lastFrameIn) :
|
||||
clipNodeEnum(clipNodeEnumIn), url(urlIn), fps(fpsIn), loop(loopIn), firstFrame(firstFrameIn), lastFrame(lastFrameIn) {}
|
||||
|
||||
ClipNodeEnum clipNodeEnum;
|
||||
QString url;
|
||||
float fps;
|
||||
bool loop;
|
||||
float firstFrame;
|
||||
float lastFrame;
|
||||
};
|
||||
|
||||
struct UserAnimState {
|
||||
enum ClipNodeEnum {
|
||||
|
@ -349,6 +377,7 @@ protected:
|
|||
};
|
||||
|
||||
UserAnimState _userAnimState;
|
||||
NetworkAnimState _networkAnimState;
|
||||
std::map<QString, RoleAnimState> _roleAnimStates;
|
||||
|
||||
float _leftHandOverlayAlpha { 0.0f };
|
||||
|
@ -382,9 +411,13 @@ protected:
|
|||
|
||||
int _rigId;
|
||||
bool _headEnabled { false };
|
||||
bool _sendNetworkNode { false };
|
||||
|
||||
AnimContext _lastContext;
|
||||
AnimVariantMap _lastAnimVars;
|
||||
|
||||
SnapshotBlendPoseHelper _hipsBlendHelper;
|
||||
ControllerParameters _previousControllerParameters;
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__Rig__) */
|
||||
|
|
|
@ -114,27 +114,29 @@ void Avatar::setShowNamesAboveHeads(bool show) {
|
|||
}
|
||||
|
||||
AvatarTransit::Status AvatarTransit::update(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config) {
|
||||
glm::vec3 currentPosition = _isTransiting ? _currentPosition : avatarPosition;
|
||||
float oneFrameDistance = glm::length(currentPosition - _lastPosition);
|
||||
const float MAX_TRANSIT_DISTANCE = 30.0f;
|
||||
float scaledMaxTransitDistance = MAX_TRANSIT_DISTANCE * _scale;
|
||||
if (oneFrameDistance > config._triggerDistance && !_isTransiting) {
|
||||
if (oneFrameDistance < scaledMaxTransitDistance) {
|
||||
start(deltaTime, _lastPosition, currentPosition, config);
|
||||
float oneFrameDistance = _isActive ? glm::length(avatarPosition - _endPosition) : glm::length(avatarPosition - _lastPosition);
|
||||
if (oneFrameDistance > (config._minTriggerDistance * _scale)) {
|
||||
if (oneFrameDistance < (config._maxTriggerDistance * _scale)) {
|
||||
start(deltaTime, _lastPosition, avatarPosition, config);
|
||||
} else {
|
||||
_lastPosition = currentPosition;
|
||||
return Status::ABORT_TRANSIT;
|
||||
_lastPosition = avatarPosition;
|
||||
_status = Status::ABORT_TRANSIT;
|
||||
}
|
||||
}
|
||||
_lastPosition = currentPosition;
|
||||
_lastPosition = avatarPosition;
|
||||
_status = updatePosition(deltaTime);
|
||||
|
||||
if (_isActive && oneFrameDistance > (config._abortDistance * _scale) && _status == Status::POST_TRANSIT) {
|
||||
reset();
|
||||
_status = Status::ENDED;
|
||||
}
|
||||
return _status;
|
||||
}
|
||||
|
||||
void AvatarTransit::reset() {
|
||||
_lastPosition = _endPosition;
|
||||
_currentPosition = _endPosition;
|
||||
_isTransiting = false;
|
||||
_isActive = false;
|
||||
}
|
||||
void AvatarTransit::start(float deltaTime, const glm::vec3& startPosition, const glm::vec3& endPosition, const AvatarTransit::TransitConfig& config) {
|
||||
_startPosition = startPosition;
|
||||
|
@ -143,12 +145,14 @@ void AvatarTransit::start(float deltaTime, const glm::vec3& startPosition, const
|
|||
_transitLine = endPosition - startPosition;
|
||||
_totalDistance = glm::length(_transitLine);
|
||||
_easeType = config._easeType;
|
||||
const float REFERENCE_FRAMES_PER_SECOND = 30.0f;
|
||||
|
||||
|
||||
_preTransitTime = AVATAR_PRE_TRANSIT_FRAME_COUNT / AVATAR_TRANSIT_FRAMES_PER_SECOND;
|
||||
_postTransitTime = AVATAR_POST_TRANSIT_FRAME_COUNT / AVATAR_TRANSIT_FRAMES_PER_SECOND;
|
||||
int transitFrames = (!config._isDistanceBased) ? config._totalFrames : config._framesPerMeter * _totalDistance;
|
||||
_totalTime = (float)transitFrames / REFERENCE_FRAMES_PER_SECOND;
|
||||
_currentTime = 0.0f;
|
||||
_isTransiting = true;
|
||||
_transitTime = (float)transitFrames / AVATAR_TRANSIT_FRAMES_PER_SECOND;
|
||||
_totalTime = _transitTime + _preTransitTime + _postTransitTime;
|
||||
_currentTime = _isActive ? _preTransitTime : 0.0f;
|
||||
_isActive = true;
|
||||
}
|
||||
|
||||
float AvatarTransit::getEaseValue(AvatarTransit::EaseType type, float value) {
|
||||
|
@ -171,31 +175,37 @@ float AvatarTransit::getEaseValue(AvatarTransit::EaseType type, float value) {
|
|||
|
||||
AvatarTransit::Status AvatarTransit::updatePosition(float deltaTime) {
|
||||
Status status = Status::IDLE;
|
||||
if (_isTransiting) {
|
||||
if (_isActive) {
|
||||
float nextTime = _currentTime + deltaTime;
|
||||
if (nextTime >= _totalTime) {
|
||||
_currentPosition = _endPosition;
|
||||
_isTransiting = false;
|
||||
status = Status::END_TRANSIT;
|
||||
} else {
|
||||
if (nextTime < _preTransitTime) {
|
||||
_currentPosition = _startPosition;
|
||||
status = Status::PRE_TRANSIT;
|
||||
if (_currentTime == 0) {
|
||||
status = Status::STARTED;
|
||||
}
|
||||
} else if (nextTime < _totalTime - _postTransitTime){
|
||||
status = Status::TRANSITING;
|
||||
if (_currentTime <= _preTransitTime) {
|
||||
status = Status::START_TRANSIT;
|
||||
} else {
|
||||
status = Status::TRANSITING;
|
||||
float percentageIntoTransit = (nextTime - _preTransitTime) / _transitTime;
|
||||
_currentPosition = _startPosition + getEaseValue(_easeType, percentageIntoTransit) * _transitLine;
|
||||
}
|
||||
} else {
|
||||
status = Status::POST_TRANSIT;
|
||||
_currentPosition = _endPosition;
|
||||
if (nextTime >= _totalTime) {
|
||||
_isActive = false;
|
||||
status = Status::ENDED;
|
||||
} else if (_currentTime < _totalTime - _postTransitTime) {
|
||||
status = Status::END_TRANSIT;
|
||||
}
|
||||
float percentageIntoTransit = nextTime / _totalTime;
|
||||
_currentPosition = _startPosition + getEaseValue(_easeType, percentageIntoTransit) * _transitLine;
|
||||
}
|
||||
_currentTime = nextTime;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
bool AvatarTransit::getNextPosition(glm::vec3& nextPosition) {
|
||||
nextPosition = _currentPosition;
|
||||
return _isTransiting;
|
||||
}
|
||||
|
||||
Avatar::Avatar(QThread* thread) :
|
||||
_voiceSphereID(GeometryCache::UNKNOWN_ID)
|
||||
{
|
||||
|
@ -536,16 +546,10 @@ void Avatar::relayJointDataToChildren() {
|
|||
|
||||
void Avatar::simulate(float deltaTime, bool inView) {
|
||||
PROFILE_RANGE(simulation, "simulate");
|
||||
|
||||
if (_transit.isTransiting()) {
|
||||
glm::vec3 nextPosition;
|
||||
if (_transit.getNextPosition(nextPosition)) {
|
||||
_globalPosition = nextPosition;
|
||||
_globalPositionChanged = usecTimestampNow();
|
||||
if (!hasParent()) {
|
||||
setLocalPosition(nextPosition);
|
||||
}
|
||||
}
|
||||
|
||||
_globalPosition = _transit.isActive() ? _transit.getCurrentPosition() : _serverPosition;
|
||||
if (!hasParent()) {
|
||||
setLocalPosition(_globalPosition);
|
||||
}
|
||||
|
||||
_simulationRate.increment();
|
||||
|
@ -558,7 +562,7 @@ void Avatar::simulate(float deltaTime, bool inView) {
|
|||
PROFILE_RANGE(simulation, "updateJoints");
|
||||
if (inView) {
|
||||
Head* head = getHead();
|
||||
if (_hasNewJointData || _transit.isTransiting()) {
|
||||
if (_hasNewJointData || _transit.isActive()) {
|
||||
_skeletonModel->getRig().copyJointsFromJointData(_jointData);
|
||||
glm::mat4 rootTransform = glm::scale(_skeletonModel->getScale()) * glm::translate(_skeletonModel->getOffset());
|
||||
_skeletonModel->getRig().computeExternalPoses(rootTransform);
|
||||
|
@ -703,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) {
|
||||
|
@ -726,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);
|
||||
|
@ -950,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);
|
||||
|
@ -1995,22 +1999,12 @@ float Avatar::getUnscaledEyeHeightFromSkeleton() const {
|
|||
}
|
||||
}
|
||||
|
||||
AvatarTransit::Status Avatar::updateTransit(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config) {
|
||||
AvatarTransit::Status Avatar::updateTransit(float deltaTime, const glm::vec3& avatarPosition, float avatarScale, const AvatarTransit::TransitConfig& config) {
|
||||
std::lock_guard<std::mutex> lock(_transitLock);
|
||||
_transit.setScale(avatarScale);
|
||||
return _transit.update(deltaTime, avatarPosition, config);
|
||||
}
|
||||
|
||||
void Avatar::setTransitScale(float scale) {
|
||||
std::lock_guard<std::mutex> lock(_transitLock);
|
||||
return _transit.setScale(scale);
|
||||
}
|
||||
|
||||
void Avatar::overrideNextPackagePositionData(const glm::vec3& position) {
|
||||
std::lock_guard<std::mutex> lock(_transitLock);
|
||||
_overrideGlobalPosition = true;
|
||||
_globalPositionOverride = position;
|
||||
}
|
||||
|
||||
void Avatar::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) {
|
||||
std::lock_guard<std::mutex> lock(_materialsLock);
|
||||
_materials[parentMaterialName].push(material);
|
||||
|
|
|
@ -56,9 +56,13 @@ class AvatarTransit {
|
|||
public:
|
||||
enum Status {
|
||||
IDLE = 0,
|
||||
STARTED,
|
||||
PRE_TRANSIT,
|
||||
START_TRANSIT,
|
||||
TRANSITING,
|
||||
END_TRANSIT,
|
||||
POST_TRANSIT,
|
||||
ENDED,
|
||||
ABORT_TRANSIT
|
||||
};
|
||||
|
||||
|
@ -72,20 +76,20 @@ public:
|
|||
struct TransitConfig {
|
||||
TransitConfig() {};
|
||||
int _totalFrames { 0 };
|
||||
int _framesPerMeter { 0 };
|
||||
float _framesPerMeter { 0.0f };
|
||||
bool _isDistanceBased { false };
|
||||
float _triggerDistance { 0 };
|
||||
float _minTriggerDistance { 0.0f };
|
||||
float _maxTriggerDistance { 0.0f };
|
||||
float _abortDistance{ 0.0f };
|
||||
EaseType _easeType { EaseType::EASE_OUT };
|
||||
};
|
||||
|
||||
AvatarTransit() {};
|
||||
Status update(float deltaTime, const glm::vec3& avatarPosition, const TransitConfig& config);
|
||||
Status getStatus() { return _status; }
|
||||
bool isTransiting() { return _isTransiting; }
|
||||
bool isActive() { return _isActive; }
|
||||
glm::vec3 getCurrentPosition() { return _currentPosition; }
|
||||
bool getNextPosition(glm::vec3& nextPosition);
|
||||
glm::vec3 getEndPosition() { return _endPosition; }
|
||||
float getTransitTime() { return _totalTime; }
|
||||
void setScale(float scale) { _scale = scale; }
|
||||
void reset();
|
||||
|
||||
|
@ -93,7 +97,7 @@ private:
|
|||
Status updatePosition(float deltaTime);
|
||||
void start(float deltaTime, const glm::vec3& startPosition, const glm::vec3& endPosition, const TransitConfig& config);
|
||||
float getEaseValue(AvatarTransit::EaseType type, float value);
|
||||
bool _isTransiting { false };
|
||||
bool _isActive { false };
|
||||
|
||||
glm::vec3 _startPosition;
|
||||
glm::vec3 _endPosition;
|
||||
|
@ -103,7 +107,10 @@ private:
|
|||
|
||||
glm::vec3 _transitLine;
|
||||
float _totalDistance { 0.0f };
|
||||
float _preTransitTime { 0.0f };
|
||||
float _totalTime { 0.0f };
|
||||
float _transitTime { 0.0f };
|
||||
float _postTransitTime { 0.0f };
|
||||
float _currentTime { 0.0f };
|
||||
EaseType _easeType { EaseType::EASE_OUT };
|
||||
Status _status { Status::IDLE };
|
||||
|
@ -431,11 +438,7 @@ public:
|
|||
virtual scriptable::ScriptableModelBase getScriptableModel() override;
|
||||
|
||||
std::shared_ptr<AvatarTransit> getTransit() { return std::make_shared<AvatarTransit>(_transit); };
|
||||
|
||||
AvatarTransit::Status updateTransit(float deltaTime, const glm::vec3& avatarPosition, const AvatarTransit::TransitConfig& config);
|
||||
void setTransitScale(float scale);
|
||||
|
||||
void overrideNextPackagePositionData(const glm::vec3& position);
|
||||
AvatarTransit::Status updateTransit(float deltaTime, const glm::vec3& avatarPosition, float avatarScale, const AvatarTransit::TransitConfig& config);
|
||||
|
||||
signals:
|
||||
void targetScaleChanged(float targetScale);
|
||||
|
@ -623,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,114 +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;
|
||||
if (_overrideGlobalPosition) {
|
||||
AVATAR_MEMCPY(_globalPositionOverride);
|
||||
} else {
|
||||
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) {
|
||||
|
@ -389,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);
|
||||
|
@ -400,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);
|
||||
|
@ -411,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();
|
||||
|
@ -424,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;
|
||||
|
@ -433,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);
|
||||
|
@ -445,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();
|
||||
|
@ -463,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);
|
||||
|
||||
|
@ -511,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();
|
||||
|
@ -525,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);
|
||||
|
@ -536,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;
|
||||
|
@ -560,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());
|
||||
|
@ -691,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;
|
||||
|
||||
|
@ -733,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
|
||||
|
@ -763,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) {
|
||||
|
@ -771,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
|
||||
|
@ -887,20 +935,22 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
offset = glm::vec3(row * SPACE_BETWEEN_AVATARS, 0.0f, col * SPACE_BETWEEN_AVATARS);
|
||||
}
|
||||
|
||||
auto newValue = glm::vec3(data->globalPosition[0], data->globalPosition[1], data->globalPosition[2]) + offset;
|
||||
if (_globalPosition != newValue) {
|
||||
_globalPosition = newValue;
|
||||
_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 (!hasParent()) {
|
||||
setLocalPosition(_serverPosition);
|
||||
}
|
||||
}
|
||||
if (_globalPosition != _serverPosition) {
|
||||
_globalPositionChanged = now;
|
||||
}
|
||||
sourceBuffer += sizeof(AvatarDataPacket::AvatarGlobalPosition);
|
||||
int numBytesRead = sourceBuffer - startSection;
|
||||
_globalPositionRate.increment(numBytesRead);
|
||||
_globalPositionUpdateRate.increment();
|
||||
|
||||
// if we don't have a parent, make sure to also set our local position
|
||||
if (!hasParent()) {
|
||||
setLocalPosition(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (hasAvatarBoundingBox) {
|
||||
|
@ -921,6 +971,8 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
|
|||
_avatarBoundingBoxChanged = now;
|
||||
}
|
||||
|
||||
_defaultBubbleBox = computeBubbleBox();
|
||||
|
||||
sourceBuffer += sizeof(AvatarDataPacket::AvatarBoundingBox);
|
||||
int numBytesRead = sourceBuffer - startSection;
|
||||
_avatarBoundingBoxRate.increment(numBytesRead);
|
||||
|
@ -1730,11 +1782,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
|
||||
|
@ -1749,17 +1799,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;
|
||||
|
||||
|
@ -2115,10 +2166,6 @@ void AvatarData::sendAvatarDataPacket(bool sendAll) {
|
|||
}
|
||||
}
|
||||
|
||||
if (_overrideGlobalPosition) {
|
||||
_overrideGlobalPosition = false;
|
||||
}
|
||||
|
||||
doneEncoding(cullSmallData);
|
||||
|
||||
static AvatarDataSequenceNumber sequenceNumber = 0;
|
||||
|
@ -2909,3 +2956,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
|
||||
|
@ -327,6 +338,17 @@ const float AVATAR_DISTANCE_LEVEL_5 = 200.0f; // meters
|
|||
// This is the start location in the Sandbox (xyz: 6270, 211, 6000).
|
||||
const glm::vec3 START_LOCATION(6270, 211, 6000);
|
||||
|
||||
// Avatar Transit Constants
|
||||
const float AVATAR_TRANSIT_MIN_TRIGGER_DISTANCE = 1.0f;
|
||||
const float AVATAR_TRANSIT_MAX_TRIGGER_DISTANCE = 30.0f;
|
||||
const int AVATAR_TRANSIT_FRAME_COUNT = 11;
|
||||
const float AVATAR_TRANSIT_FRAMES_PER_METER = 0.5f;
|
||||
const float AVATAR_TRANSIT_ABORT_DISTANCE = 0.1f;
|
||||
const bool AVATAR_TRANSIT_DISTANCE_BASED = true;
|
||||
const float AVATAR_TRANSIT_FRAMES_PER_SECOND = 30.0f;
|
||||
const float AVATAR_PRE_TRANSIT_FRAME_COUNT = 10.0f;
|
||||
const float AVATAR_POST_TRANSIT_FRAME_COUNT = 27.0f;
|
||||
|
||||
enum KeyState {
|
||||
NO_KEY_DOWN = 0,
|
||||
INSERT_KEY_DOWN,
|
||||
|
@ -452,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);
|
||||
|
||||
|
@ -960,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);
|
||||
|
@ -1101,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
|
||||
|
@ -1193,6 +1216,9 @@ 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; }
|
||||
|
||||
|
@ -1378,8 +1404,7 @@ protected:
|
|||
// where Entities are located. This is currently only used by the mixer to decide how often to send
|
||||
// updates about one avatar to another.
|
||||
glm::vec3 _globalPosition { 0, 0, 0 };
|
||||
glm::vec3 _globalPositionOverride { 0, 0, 0 };
|
||||
bool _overrideGlobalPosition { false };
|
||||
glm::vec3 _serverPosition { 0, 0, 0 };
|
||||
|
||||
quint64 _globalPositionChanged { 0 };
|
||||
quint64 _avatarBoundingBoxChanged { 0 };
|
||||
|
@ -1430,6 +1455,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
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -3803,6 +3803,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"
|
||||
]);
|
||||
|
||||
|
@ -42,6 +41,9 @@ var TITLE_OFFSET = 60;
|
|||
var CREATE_TOOLS_WIDTH = 490;
|
||||
var MAX_DEFAULT_ENTITY_LIST_HEIGHT = 942;
|
||||
|
||||
var IMAGE_MODEL = "https://hifi-content.s3.amazonaws.com/DomainContent/production/default-image-model.fbx";
|
||||
var DEFAULT_IMAGE = "https://hifi-content.s3.amazonaws.com/DomainContent/production/no-image.jpg";
|
||||
|
||||
var createToolsWindow = new CreateWindow(
|
||||
Script.resourcesPath() + "qml/hifi/tablet/EditTools.qml",
|
||||
'Create Tools',
|
||||
|
@ -109,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.
|
||||
|
@ -294,6 +274,202 @@ function checkEditPermissionsAndUpdate() {
|
|||
}
|
||||
}
|
||||
|
||||
const DEFAULT_ENTITY_PROPERTIES = {
|
||||
All: {
|
||||
description: "",
|
||||
rotation: { x: 0, y: 0, z: 0, w: 1 },
|
||||
collidesWith: "static,dynamic,kinematic,otherAvatar",
|
||||
collisionSoundURL: "",
|
||||
cloneable: false,
|
||||
ignoreIK: true,
|
||||
canCastShadow: true,
|
||||
href: "",
|
||||
script: "",
|
||||
serverScripts:"",
|
||||
velocity: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
damping: 0,
|
||||
angularVelocity: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
angularDamping: 0,
|
||||
restitution: 0.5,
|
||||
friction: 0.5,
|
||||
density: 1000,
|
||||
gravity: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
acceleration: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
dynamic: false,
|
||||
},
|
||||
Shape: {
|
||||
shape: "Box",
|
||||
dimensions: { x: 0.2, y: 0.2, z: 0.2 },
|
||||
color: { red: 0, green: 180, blue: 239 },
|
||||
},
|
||||
Text: {
|
||||
text: "Text",
|
||||
dimensions: {
|
||||
x: 0.65,
|
||||
y: 0.3,
|
||||
z: 0.01
|
||||
},
|
||||
textColor: { red: 255, green: 255, blue: 255 },
|
||||
backgroundColor: { red: 0, green: 0, blue: 0 },
|
||||
lineHeight: 0.06,
|
||||
faceCamera: false,
|
||||
},
|
||||
Zone: {
|
||||
dimensions: {
|
||||
x: 10,
|
||||
y: 10,
|
||||
z: 10
|
||||
},
|
||||
flyingAllowed: true,
|
||||
ghostingAllowed: true,
|
||||
filter: "",
|
||||
keyLightMode: "inherit",
|
||||
keyLightColor: { red: 255, green: 255, blue: 255 },
|
||||
keyLight: {
|
||||
intensity: 1.0,
|
||||
direction: {
|
||||
x: 0.0,
|
||||
y: -0.707106769084930, // 45 degrees
|
||||
z: 0.7071067690849304
|
||||
},
|
||||
castShadows: true
|
||||
},
|
||||
ambientLightMode: "inherit",
|
||||
ambientLight: {
|
||||
ambientIntensity: 0.5,
|
||||
ambientURL: ""
|
||||
},
|
||||
hazeMode: "inherit",
|
||||
haze: {
|
||||
hazeRange: 1000,
|
||||
hazeAltitudeEffect: false,
|
||||
hazeBaseRef: 0,
|
||||
hazeColor: {
|
||||
red: 128,
|
||||
green: 154,
|
||||
blue: 179
|
||||
},
|
||||
hazeBackgroundBlend: 0,
|
||||
hazeEnableGlare: false,
|
||||
hazeGlareColor: {
|
||||
red: 255,
|
||||
green: 229,
|
||||
blue: 179
|
||||
},
|
||||
},
|
||||
bloomMode: "inherit"
|
||||
},
|
||||
Model: {
|
||||
collisionShape: "none",
|
||||
compoundShapeURL: "",
|
||||
animation: {
|
||||
url: "",
|
||||
running: false,
|
||||
allowTranslation: false,
|
||||
loop: true,
|
||||
hold: false,
|
||||
currentFrame: 0,
|
||||
firstFrame: 0,
|
||||
lastFrame: 100000,
|
||||
fps: 30.0,
|
||||
}
|
||||
},
|
||||
Image: {
|
||||
dimensions: {
|
||||
x: 0.5385,
|
||||
y: 0.2819,
|
||||
z: 0.0092
|
||||
},
|
||||
shapeType: "box",
|
||||
collisionless: true,
|
||||
modelURL: IMAGE_MODEL,
|
||||
textures: JSON.stringify({ "tex.picture": "" })
|
||||
},
|
||||
Web: {
|
||||
dimensions: {
|
||||
x: 1.6,
|
||||
y: 0.9,
|
||||
z: 0.01
|
||||
},
|
||||
sourceUrl: "https://highfidelity.com/",
|
||||
dpi: 30,
|
||||
},
|
||||
ParticleEffect: {
|
||||
lifespan: 1.5,
|
||||
maxParticles: 10,
|
||||
textures: "https://content.highfidelity.com/DomainContent/production/Particles/wispy-smoke.png",
|
||||
emitRate: 5.5,
|
||||
emitSpeed: 0,
|
||||
speedSpread: 0,
|
||||
emitDimensions: { x: 0, y: 0, z: 0 },
|
||||
emitOrientation: { x: 0, y: 0, z: 0, w: 1 },
|
||||
emitterShouldTrail: true,
|
||||
particleRadius: 0.25,
|
||||
radiusStart: 0,
|
||||
radiusFinish: 0.1,
|
||||
radiusSpread: 0,
|
||||
particleColor: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255
|
||||
},
|
||||
colorSpread: {
|
||||
red: 0,
|
||||
green: 0,
|
||||
blue: 0
|
||||
},
|
||||
alpha: 0,
|
||||
alphaStart: 1,
|
||||
alphaFinish: 0,
|
||||
alphaSpread: 0,
|
||||
emitAcceleration: {
|
||||
x: 0,
|
||||
y: 2.5,
|
||||
z: 0
|
||||
},
|
||||
accelerationSpread: {
|
||||
x: 0,
|
||||
y: 0,
|
||||
z: 0
|
||||
},
|
||||
particleSpin: 0,
|
||||
spinStart: 0,
|
||||
spinFinish: 0,
|
||||
spinSpread: 0,
|
||||
rotateWithEntity: false,
|
||||
polarStart: 0,
|
||||
polarFinish: 0,
|
||||
azimuthStart: -Math.PI,
|
||||
azimuthFinish: Math.PI
|
||||
},
|
||||
Light: {
|
||||
color: { red: 255, green: 255, blue: 255 },
|
||||
intensity: 5.0,
|
||||
dimensions: DEFAULT_LIGHT_DIMENSIONS,
|
||||
falloffRadius: 1.0,
|
||||
isSpotlight: false,
|
||||
exponent: 1.0,
|
||||
cutoff: 75.0,
|
||||
dimensions: { x: 20, y: 20, z: 20 },
|
||||
},
|
||||
};
|
||||
|
||||
var toolBar = (function () {
|
||||
var EDIT_SETTING = "io.highfidelity.isEditing"; // for communication with other scripts
|
||||
var that = {},
|
||||
|
@ -303,11 +479,29 @@ var toolBar = (function () {
|
|||
dialogWindow = null,
|
||||
tablet = null;
|
||||
|
||||
function applyProperties(originalProperties, newProperties) {
|
||||
for (var key in newProperties) {
|
||||
originalProperties[key] = newProperties[key];
|
||||
}
|
||||
}
|
||||
function createNewEntity(properties) {
|
||||
var dimensions = properties.dimensions ? properties.dimensions : DEFAULT_DIMENSIONS;
|
||||
var position = getPositionToCreateEntity();
|
||||
var entityID = null;
|
||||
|
||||
applyProperties(properties, DEFAULT_ENTITY_PROPERTIES.All);
|
||||
|
||||
var type = properties.type;
|
||||
if (type == "Box" || type == "Sphere") {
|
||||
applyProperties(properties, DEFAULT_ENTITY_PROPERTIES.Shape);
|
||||
} else if (type == "Image") {
|
||||
properties.type = "Model";
|
||||
applyProperties(properties, DEFAULT_ENTITY_PROPERTIES.Image);
|
||||
} else {
|
||||
applyProperties(properties, DEFAULT_ENTITY_PROPERTIES[type]);
|
||||
}
|
||||
|
||||
|
||||
if (position !== null && position !== undefined) {
|
||||
var direction;
|
||||
if (Camera.mode === "entity" || Camera.mode === "independent") {
|
||||
|
@ -363,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.
|
||||
|
@ -385,7 +575,7 @@ var toolBar = (function () {
|
|||
Entities.editEntity(entityID, {
|
||||
position: position
|
||||
});
|
||||
selectionManager._update();
|
||||
selectionManager._update(false, this);
|
||||
} else if (dimensionsCheckCount < MAX_DIMENSIONS_CHECKS) {
|
||||
Script.setTimeout(dimensionsCheckFunction, DIMENSIONS_CHECK_INTERVAL);
|
||||
}
|
||||
|
@ -397,9 +587,9 @@ var toolBar = (function () {
|
|||
properties.type + " would be out of bounds.");
|
||||
}
|
||||
|
||||
selectionManager.clearSelections();
|
||||
selectionManager.clearSelections(this);
|
||||
entityListTool.sendUpdate();
|
||||
selectionManager.setSelections([entityID]);
|
||||
selectionManager.setSelections([entityID], this);
|
||||
|
||||
Window.setFocus();
|
||||
|
||||
|
@ -550,7 +740,7 @@ var toolBar = (function () {
|
|||
}
|
||||
deletedEntityTimer = Script.setTimeout(function () {
|
||||
if (entitiesToDelete.length > 0) {
|
||||
selectionManager.removeEntities(entitiesToDelete);
|
||||
selectionManager.removeEntities(entitiesToDelete, this);
|
||||
}
|
||||
entityListTool.removeEntities(entitiesToDelete, selectionManager.selections);
|
||||
entitiesToDelete = [];
|
||||
|
@ -679,14 +869,12 @@ var toolBar = (function () {
|
|||
addButton("newLightButton", function () {
|
||||
createNewEntity({
|
||||
type: "Light",
|
||||
dimensions: DEFAULT_LIGHT_DIMENSIONS,
|
||||
isSpotlight: false,
|
||||
color: {
|
||||
red: 150,
|
||||
green: 150,
|
||||
blue: 150
|
||||
},
|
||||
|
||||
constantAttenuation: 1,
|
||||
linearAttenuation: 0,
|
||||
quadraticAttenuation: 0,
|
||||
|
@ -698,116 +886,30 @@ var toolBar = (function () {
|
|||
addButton("newTextButton", function () {
|
||||
createNewEntity({
|
||||
type: "Text",
|
||||
dimensions: {
|
||||
x: 0.65,
|
||||
y: 0.3,
|
||||
z: 0.01
|
||||
},
|
||||
backgroundColor: {
|
||||
red: 64,
|
||||
green: 64,
|
||||
blue: 64
|
||||
},
|
||||
textColor: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255
|
||||
},
|
||||
text: "some text",
|
||||
lineHeight: 0.06
|
||||
});
|
||||
});
|
||||
|
||||
addButton("newImageButton", function () {
|
||||
var IMAGE_MODEL = "https://hifi-content.s3.amazonaws.com/DomainContent/production/default-image-model.fbx";
|
||||
var DEFAULT_IMAGE = "https://hifi-content.s3.amazonaws.com/DomainContent/production/no-image.jpg";
|
||||
createNewEntity({
|
||||
type: "Model",
|
||||
dimensions: {
|
||||
x: 0.5385,
|
||||
y: 0.2819,
|
||||
z: 0.0092
|
||||
},
|
||||
shapeType: "box",
|
||||
collisionless: true,
|
||||
modelURL: IMAGE_MODEL,
|
||||
textures: JSON.stringify({ "tex.picture": DEFAULT_IMAGE })
|
||||
type: "Image",
|
||||
});
|
||||
});
|
||||
|
||||
addButton("newWebButton", function () {
|
||||
createNewEntity({
|
||||
type: "Web",
|
||||
dimensions: {
|
||||
x: 1.6,
|
||||
y: 0.9,
|
||||
z: 0.01
|
||||
},
|
||||
sourceUrl: "https://highfidelity.com/"
|
||||
});
|
||||
});
|
||||
|
||||
addButton("newZoneButton", function () {
|
||||
createNewEntity({
|
||||
type: "Zone",
|
||||
dimensions: {
|
||||
x: 10,
|
||||
y: 10,
|
||||
z: 10
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
addButton("newParticleButton", function () {
|
||||
createNewEntity({
|
||||
type: "ParticleEffect",
|
||||
isEmitting: true,
|
||||
emitterShouldTrail: true,
|
||||
color: {
|
||||
red: 200,
|
||||
green: 200,
|
||||
blue: 200
|
||||
},
|
||||
colorSpread: {
|
||||
red: 0,
|
||||
green: 0,
|
||||
blue: 0
|
||||
},
|
||||
colorStart: {
|
||||
red: 200,
|
||||
green: 200,
|
||||
blue: 200
|
||||
},
|
||||
colorFinish: {
|
||||
red: 0,
|
||||
green: 0,
|
||||
blue: 0
|
||||
},
|
||||
emitAcceleration: {
|
||||
x: -0.5,
|
||||
y: 2.5,
|
||||
z: -0.5
|
||||
},
|
||||
accelerationSpread: {
|
||||
x: 0.5,
|
||||
y: 1,
|
||||
z: 0.5
|
||||
},
|
||||
emitRate: 5.5,
|
||||
emitSpeed: 0,
|
||||
speedSpread: 0,
|
||||
lifespan: 1.5,
|
||||
maxParticles: 10,
|
||||
particleRadius: 0.25,
|
||||
radiusStart: 0,
|
||||
radiusFinish: 0.1,
|
||||
radiusSpread: 0,
|
||||
alpha: 0,
|
||||
alphaStart: 1,
|
||||
alphaFinish: 0,
|
||||
polarStart: 0,
|
||||
polarFinish: 0,
|
||||
textures: "https://content.highfidelity.com/DomainContent/production/Particles/wispy-smoke.png"
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -866,7 +968,7 @@ var toolBar = (function () {
|
|||
gridTool.setVisible(false);
|
||||
grid.setEnabled(false);
|
||||
propertiesTool.setVisible(false);
|
||||
selectionManager.clearSelections();
|
||||
selectionManager.clearSelections(this);
|
||||
cameraManager.disable();
|
||||
selectionDisplay.disableTriggerMapping();
|
||||
tablet.landscape = false;
|
||||
|
@ -994,7 +1096,7 @@ function handleOverlaySelectionToolUpdates(channel, message, sender) {
|
|||
var entity = entityIconOverlayManager.findEntity(data.overlayID);
|
||||
|
||||
if (entity !== null) {
|
||||
selectionManager.setSelections([entity]);
|
||||
selectionManager.setSelections([entity], this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1141,7 +1243,7 @@ function mouseClickEvent(event) {
|
|||
|
||||
if (result === null || result === undefined) {
|
||||
if (!event.isShifted) {
|
||||
selectionManager.clearSelections();
|
||||
selectionManager.clearSelections(this);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -1185,17 +1287,10 @@ 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]);
|
||||
selectionManager.setSelections([foundEntity], this);
|
||||
} else {
|
||||
selectionManager.addEntity(foundEntity, true);
|
||||
selectionManager.addEntity(foundEntity, true, this);
|
||||
}
|
||||
|
||||
if (wantDebug) {
|
||||
|
@ -1493,7 +1588,7 @@ function selectAllEtitiesInCurrentSelectionBox(keepIfTouching) {
|
|||
}
|
||||
}
|
||||
}
|
||||
selectionManager.setSelections(entities);
|
||||
selectionManager.setSelections(entities, this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1610,8 +1705,6 @@ function deleteSelectedEntities() {
|
|||
if (SelectionManager.hasSelection()) {
|
||||
var deletedIDs = [];
|
||||
|
||||
selectedParticleEntityID = null;
|
||||
particleExplorerTool.destroyWebView();
|
||||
SelectionManager.saveProperties();
|
||||
var savedProperties = [];
|
||||
var newSortedSelection = sortSelectedEntities(selectionManager.selections);
|
||||
|
@ -1633,7 +1726,7 @@ function deleteSelectedEntities() {
|
|||
}
|
||||
|
||||
if (savedProperties.length > 0) {
|
||||
SelectionManager.clearSelections();
|
||||
SelectionManager.clearSelections(this);
|
||||
pushCommandForSelections([], savedProperties);
|
||||
entityListTool.deleteEntities(deletedIDs);
|
||||
}
|
||||
|
@ -1650,7 +1743,7 @@ function toggleSelectedEntitiesLocked() {
|
|||
});
|
||||
}
|
||||
entityListTool.sendUpdate();
|
||||
selectionManager._update();
|
||||
selectionManager._update(false, this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1664,7 +1757,7 @@ function toggleSelectedEntitiesVisible() {
|
|||
});
|
||||
}
|
||||
entityListTool.sendUpdate();
|
||||
selectionManager._update();
|
||||
selectionManager._update(false, this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1861,7 +1954,7 @@ function importSVO(importURL) {
|
|||
}
|
||||
|
||||
if (isActive) {
|
||||
selectionManager.setSelections(pastedEntityIDs);
|
||||
selectionManager.setSelections(pastedEntityIDs, this);
|
||||
}
|
||||
} else {
|
||||
Window.notifyEditError("Can't import entities: entities would be out of bounds.");
|
||||
|
@ -1909,7 +2002,7 @@ function deleteKey(value) {
|
|||
}
|
||||
function deselectKey(value) {
|
||||
if (value === 0) { // on release
|
||||
selectionManager.clearSelections();
|
||||
selectionManager.clearSelections(this);
|
||||
}
|
||||
}
|
||||
function toggleKey(value) {
|
||||
|
@ -2069,7 +2162,7 @@ function applyEntityProperties(data) {
|
|||
// We might be getting an undo while edit.js is disabled. If that is the case, don't set
|
||||
// our selections, causing the edit widgets to display.
|
||||
if (isActive) {
|
||||
selectionManager.setSelections(selectedEntityIDs);
|
||||
selectionManager.setSelections(selectedEntityIDs, this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2218,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);
|
||||
|
@ -2256,23 +2351,29 @@ 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();
|
||||
}
|
||||
}
|
||||
pushCommandForSelections();
|
||||
selectionManager._update();
|
||||
selectionManager._update(false, this);
|
||||
} else if (data.type === 'parent') {
|
||||
parentSelectedEntities();
|
||||
} else if (data.type === 'unparent') {
|
||||
|
@ -2301,7 +2402,7 @@ var PropertiesTool = function (opts) {
|
|||
});
|
||||
}
|
||||
pushCommandForSelections();
|
||||
selectionManager._update();
|
||||
selectionManager._update(false, this);
|
||||
}
|
||||
} else if (data.action === "moveAllToGrid") {
|
||||
if (selectionManager.hasSelection()) {
|
||||
|
@ -2321,7 +2422,7 @@ var PropertiesTool = function (opts) {
|
|||
});
|
||||
}
|
||||
pushCommandForSelections();
|
||||
selectionManager._update();
|
||||
selectionManager._update(false, this);
|
||||
}
|
||||
} else if (data.action === "resetToNaturalDimensions") {
|
||||
if (selectionManager.hasSelection()) {
|
||||
|
@ -2342,7 +2443,7 @@ var PropertiesTool = function (opts) {
|
|||
}
|
||||
}
|
||||
pushCommandForSelections();
|
||||
selectionManager._update();
|
||||
selectionManager._update(false, this);
|
||||
}
|
||||
} else if (data.action === "previewCamera") {
|
||||
if (selectionManager.hasSelection()) {
|
||||
|
@ -2360,7 +2461,7 @@ var PropertiesTool = function (opts) {
|
|||
});
|
||||
}
|
||||
pushCommandForSelections();
|
||||
selectionManager._update();
|
||||
selectionManager._update(false, this);
|
||||
}
|
||||
} else if (data.action === "reloadClientScripts") {
|
||||
if (selectionManager.hasSelection()) {
|
||||
|
@ -2380,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,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -2613,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 {
|
||||
|
@ -2651,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
|
@ -16,6 +16,7 @@
|
|||
<script type="text/javascript" src="js/eventBridgeLoader.js"></script>
|
||||
<script type="text/javascript" src="js/spinButtons.js"></script>
|
||||
<script type="text/javascript" src="js/listView.js"></script>
|
||||
<script type="text/javascript" src="js/entityListContextMenu.js"></script>
|
||||
<script type="text/javascript" src="js/entityList.js"></script>
|
||||
</head>
|
||||
<body onload='loaded();' id="entity-list-body">
|
||||
|
@ -29,12 +30,25 @@
|
|||
<input type="button" class="red" id="delete" value="Delete" />
|
||||
</div>
|
||||
<div id="entity-list">
|
||||
<div id="search-area">
|
||||
<span class="icon-input"><input type="text" class="search" id="filter" placeholder="Filter" /><span>Y</span></span>
|
||||
<input type="button" id="in-view" class="glyph" value="" />
|
||||
<div id="radius-and-unit" class="number">
|
||||
<div id="filter-area">
|
||||
<div class="multiselect">
|
||||
<div class="select-box" id="filter-type-select-box">
|
||||
<select>
|
||||
<option id="filter-type-text">All Types</option>
|
||||
</select>
|
||||
<div class="over-select"></div>
|
||||
</div>
|
||||
<div id="filter-type-checkboxes">
|
||||
<!-- type options with checkbox, icon, and label are added at runtime in entityList -->
|
||||
</div>
|
||||
</div>
|
||||
<div id="filter-search-and-icon">
|
||||
<span class="icon-input"><input type="search" class="search" id="filter-search" placeholder="Search" /><span>Y</span></span>
|
||||
</div>
|
||||
<input type="button" id="filter-in-view" class="glyph" value="" />
|
||||
<div id="filter-radius-and-unit" class="number">
|
||||
<label for="radius">Search radius <span class="unit">m</span></label>
|
||||
<input type="number" id="radius" value="100" />
|
||||
<input type="text" id="filter-radius" maxlength="9" value="100" />
|
||||
</div>
|
||||
</div>
|
||||
<div id="entity-table-scroll">
|
||||
|
@ -87,9 +101,8 @@
|
|||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div id="no-entities">
|
||||
No entities found <span id="no-entities-in-view">in view</span> within a <span id="no-entities-radius">100</span> meter radius. Try moving to a different location and refreshing.
|
||||
There are no entities to display. Please check your filters or create an entity to begin.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
|
@ -13,7 +13,7 @@ const DESCENDING_STRING = '▾';
|
|||
const LOCKED_GLYPH = "";
|
||||
const VISIBLE_GLYPH = "";
|
||||
const TRANSPARENCY_GLYPH = "";
|
||||
const BAKED_GLYPH = ""
|
||||
const BAKED_GLYPH = "";
|
||||
const SCRIPT_GLYPH = "k";
|
||||
const BYTES_PER_MEGABYTE = 1024 * 1024;
|
||||
const IMAGE_MODEL_NAME = 'default-image-model.fbx';
|
||||
|
@ -23,6 +23,7 @@ const FILTER_IN_VIEW_ATTRIBUTE = "pressed";
|
|||
const WINDOW_NONVARIABLE_HEIGHT = 227;
|
||||
const NUM_COLUMNS = 12;
|
||||
const EMPTY_ENTITY_ID = "0";
|
||||
const MAX_LENGTH_RADIUS = 9;
|
||||
const DELETE = 46; // Key code for the delete key.
|
||||
const KEY_P = 80; // Key code for letter p used for Parenting hotkey.
|
||||
|
||||
|
@ -54,10 +55,34 @@ const COMPARE_ASCENDING = function(a, b) {
|
|||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
const COMPARE_DESCENDING = function(a, b) {
|
||||
return COMPARE_ASCENDING(b, a);
|
||||
}
|
||||
};
|
||||
|
||||
const FILTER_TYPES = [
|
||||
"Shape",
|
||||
"Model",
|
||||
"Image",
|
||||
"Light",
|
||||
"Zone",
|
||||
"Web",
|
||||
"Material",
|
||||
"ParticleEffect",
|
||||
"Text",
|
||||
];
|
||||
|
||||
const ICON_FOR_TYPE = {
|
||||
Shape: "n",
|
||||
Model: "",
|
||||
Image: "",
|
||||
Light: "p",
|
||||
Zone: "o",
|
||||
Web: "q",
|
||||
Material: "",
|
||||
ParticleEffect: "",
|
||||
Text: "l",
|
||||
};
|
||||
|
||||
// List of all entities
|
||||
var entities = [];
|
||||
|
@ -70,8 +95,14 @@ var selectedEntities = [];
|
|||
|
||||
var entityList = null; // The ListView
|
||||
|
||||
/**
|
||||
* @type EntityListContextMenu
|
||||
*/
|
||||
var entityListContextMenu = null;
|
||||
|
||||
var currentSortColumn = 'type';
|
||||
var currentSortOrder = ASCENDING_SORT;
|
||||
var typeFilters = [];
|
||||
var isFilterInView = false;
|
||||
var showExtraInfo = false;
|
||||
|
||||
|
@ -105,9 +136,12 @@ function loaded() {
|
|||
elToggleLocked = document.getElementById("locked");
|
||||
elToggleVisible = document.getElementById("visible");
|
||||
elDelete = document.getElementById("delete");
|
||||
elFilter = document.getElementById("filter");
|
||||
elInView = document.getElementById("in-view")
|
||||
elRadius = document.getElementById("radius");
|
||||
elFilterTypeSelectBox = document.getElementById("filter-type-select-box");
|
||||
elFilterTypeText = document.getElementById("filter-type-text");
|
||||
elFilterTypeCheckboxes = document.getElementById("filter-type-checkboxes");
|
||||
elFilterSearch = document.getElementById("filter-search");
|
||||
elFilterInView = document.getElementById("filter-in-view")
|
||||
elFilterRadius = document.getElementById("filter-radius");
|
||||
elExport = document.getElementById("export");
|
||||
elPal = document.getElementById("pal");
|
||||
elInfoToggle = document.getElementById("info-toggle");
|
||||
|
@ -115,9 +149,8 @@ function loaded() {
|
|||
elSelectedEntitiesCount = document.getElementById("selected-entities-count");
|
||||
elVisibleEntitiesCount = document.getElementById("visible-entities-count");
|
||||
elNoEntitiesMessage = document.getElementById("no-entities");
|
||||
elNoEntitiesInView = document.getElementById("no-entities-in-view");
|
||||
elNoEntitiesRadius = document.getElementById("no-entities-radius");
|
||||
|
||||
document.body.onclick = onBodyClick;
|
||||
document.getElementById("entity-name").onclick = function() {
|
||||
setSortColumn('name');
|
||||
};
|
||||
|
@ -127,74 +160,185 @@ function loaded() {
|
|||
document.getElementById("entity-url").onclick = function() {
|
||||
setSortColumn('url');
|
||||
};
|
||||
document.getElementById("entity-locked").onclick = function () {
|
||||
document.getElementById("entity-locked").onclick = function() {
|
||||
setSortColumn('locked');
|
||||
};
|
||||
document.getElementById("entity-visible").onclick = function () {
|
||||
document.getElementById("entity-visible").onclick = function() {
|
||||
setSortColumn('visible');
|
||||
};
|
||||
document.getElementById("entity-verticesCount").onclick = function () {
|
||||
document.getElementById("entity-verticesCount").onclick = function() {
|
||||
setSortColumn('verticesCount');
|
||||
};
|
||||
document.getElementById("entity-texturesCount").onclick = function () {
|
||||
document.getElementById("entity-texturesCount").onclick = function() {
|
||||
setSortColumn('texturesCount');
|
||||
};
|
||||
document.getElementById("entity-texturesSize").onclick = function () {
|
||||
document.getElementById("entity-texturesSize").onclick = function() {
|
||||
setSortColumn('texturesSize');
|
||||
};
|
||||
document.getElementById("entity-hasTransparent").onclick = function () {
|
||||
document.getElementById("entity-hasTransparent").onclick = function() {
|
||||
setSortColumn('hasTransparent');
|
||||
};
|
||||
document.getElementById("entity-isBaked").onclick = function () {
|
||||
document.getElementById("entity-isBaked").onclick = function() {
|
||||
setSortColumn('isBaked');
|
||||
};
|
||||
document.getElementById("entity-drawCalls").onclick = function () {
|
||||
document.getElementById("entity-drawCalls").onclick = function() {
|
||||
setSortColumn('drawCalls');
|
||||
};
|
||||
document.getElementById("entity-hasScript").onclick = function () {
|
||||
document.getElementById("entity-hasScript").onclick = function() {
|
||||
setSortColumn('hasScript');
|
||||
};
|
||||
elRefresh.onclick = function() {
|
||||
refreshEntities();
|
||||
}
|
||||
};
|
||||
elToggleLocked.onclick = function() {
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'toggleLocked' }));
|
||||
}
|
||||
};
|
||||
elToggleVisible.onclick = function() {
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'toggleVisible' }));
|
||||
}
|
||||
};
|
||||
elExport.onclick = function() {
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'export'}));
|
||||
}
|
||||
};
|
||||
elPal.onclick = function() {
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'pal' }));
|
||||
}
|
||||
};
|
||||
elDelete.onclick = function() {
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' }));
|
||||
}
|
||||
elFilter.onkeyup = refreshEntityList;
|
||||
elFilter.onpaste = refreshEntityList;
|
||||
elFilter.onchange = onFilterChange;
|
||||
elFilter.onblur = refreshFooter;
|
||||
elInView.onclick = toggleFilterInView;
|
||||
elRadius.onchange = onRadiusChange;
|
||||
};
|
||||
elFilterTypeSelectBox.onclick = onToggleTypeDropdown;
|
||||
elFilterSearch.onkeyup = refreshEntityList;
|
||||
elFilterSearch.onsearch = refreshEntityList;
|
||||
elFilterInView.onclick = toggleFilterInView;
|
||||
elFilterRadius.onkeyup = onRadiusChange;
|
||||
elFilterRadius.onchange = onRadiusChange;
|
||||
elFilterRadius.onclick = onRadiusChange;
|
||||
elInfoToggle.onclick = toggleInfo;
|
||||
|
||||
elNoEntitiesInView.style.display = "none";
|
||||
// create filter type dropdown checkboxes with label and icon for each type
|
||||
for (let i = 0; i < FILTER_TYPES.length; ++i) {
|
||||
let type = FILTER_TYPES[i];
|
||||
let typeFilterID = "filter-type-" + type;
|
||||
let elDiv = document.createElement('div');
|
||||
let elLabel = document.createElement('label');
|
||||
elLabel.setAttribute("for", typeFilterID);
|
||||
elLabel.innerText = type;
|
||||
let elSpan = document.createElement('span');
|
||||
elSpan.setAttribute("class", "typeIcon");
|
||||
elSpan.innerHTML = ICON_FOR_TYPE[type];
|
||||
let elInput = document.createElement('input');
|
||||
elInput.setAttribute("type", "checkbox");
|
||||
elInput.setAttribute("id", typeFilterID);
|
||||
elInput.setAttribute("filterType", type);
|
||||
elInput.checked = true; // all types are checked initially
|
||||
toggleTypeFilter(elInput, false); // add all types to the initial types filter
|
||||
elDiv.appendChild(elInput);
|
||||
elLabel.insertBefore(elSpan, elLabel.childNodes[0]);
|
||||
elDiv.appendChild(elLabel);
|
||||
elFilterTypeCheckboxes.appendChild(elDiv);
|
||||
elDiv.onclick = onToggleTypeFilter;
|
||||
}
|
||||
|
||||
entityList = new ListView(elEntityTableBody, elEntityTableScroll, elEntityTableHeaderRow,
|
||||
createRow, updateRow, clearRow, WINDOW_NONVARIABLE_HEIGHT);
|
||||
|
||||
|
||||
entityListContextMenu = new EntityListContextMenu();
|
||||
|
||||
|
||||
function startRenamingEntity(entityID) {
|
||||
let entity = entitiesByID[entityID];
|
||||
if (!entity || entity.locked || !entity.elRow) {
|
||||
return;
|
||||
}
|
||||
|
||||
let elCell = entity.elRow.childNodes[COLUMN_INDEX.NAME];
|
||||
let elRenameInput = document.createElement("input");
|
||||
elRenameInput.setAttribute('class', 'rename-entity');
|
||||
elRenameInput.value = entity.name;
|
||||
let ignoreClicks = function(event) {
|
||||
event.stopPropagation();
|
||||
};
|
||||
elRenameInput.onclick = ignoreClicks;
|
||||
elRenameInput.ondblclick = ignoreClicks;
|
||||
elRenameInput.onkeyup = function(keyEvent) {
|
||||
if (keyEvent.key === "Enter") {
|
||||
elRenameInput.blur();
|
||||
}
|
||||
};
|
||||
|
||||
elRenameInput.onblur = function(event) {
|
||||
let value = elRenameInput.value;
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: 'rename',
|
||||
entityID: entityID,
|
||||
name: value
|
||||
}));
|
||||
entity.name = value;
|
||||
elCell.innerText = value;
|
||||
};
|
||||
|
||||
elCell.innerHTML = "";
|
||||
elCell.appendChild(elRenameInput);
|
||||
|
||||
elRenameInput.select();
|
||||
}
|
||||
|
||||
entityListContextMenu.setOnSelectedCallback(function(optionName, selectedEntityID) {
|
||||
switch (optionName) {
|
||||
case "Cut":
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'cut' }));
|
||||
break;
|
||||
case "Copy":
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'copy' }));
|
||||
break;
|
||||
case "Paste":
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'paste' }));
|
||||
break;
|
||||
case "Rename":
|
||||
startRenamingEntity(selectedEntityID);
|
||||
break;
|
||||
case "Duplicate":
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'duplicate' }));
|
||||
break;
|
||||
case "Delete":
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'delete' }));
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
function onRowContextMenu(clickEvent) {
|
||||
let entityID = this.dataset.entityID;
|
||||
|
||||
if (!selectedEntities.includes(entityID)) {
|
||||
let selection = [entityID];
|
||||
updateSelectedEntities(selection);
|
||||
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "selectionUpdate",
|
||||
focus: false,
|
||||
entityIds: selection,
|
||||
}));
|
||||
}
|
||||
|
||||
let enabledContextMenuItems = ['Copy', 'Paste', 'Duplicate'];
|
||||
if (entitiesByID[entityID] && !entitiesByID[entityID].locked) {
|
||||
enabledContextMenuItems.push('Cut');
|
||||
enabledContextMenuItems.push('Rename');
|
||||
enabledContextMenuItems.push('Delete');
|
||||
}
|
||||
|
||||
entityListContextMenu.open(clickEvent, entityID, enabledContextMenuItems);
|
||||
}
|
||||
|
||||
function onRowClicked(clickEvent) {
|
||||
let entityID = this.dataset.entityID;
|
||||
let selection = [entityID];
|
||||
|
||||
|
||||
if (clickEvent.ctrlKey) {
|
||||
let selectedIndex = selectedEntities.indexOf(entityID);
|
||||
if (selectedIndex >= 0) {
|
||||
selection = [];
|
||||
selection = selection.concat(selectedEntities);
|
||||
selection.splice(selectedIndex, 1)
|
||||
selection.splice(selectedIndex, 1);
|
||||
} else {
|
||||
selection = selection.concat(selectedEntities);
|
||||
}
|
||||
|
@ -221,28 +365,29 @@ function loaded() {
|
|||
}
|
||||
}
|
||||
} else if (!clickEvent.ctrlKey && !clickEvent.shiftKey && selectedEntities.length === 1) {
|
||||
// if reselecting the same entity then deselect it
|
||||
// if reselecting the same entity then start renaming it
|
||||
if (selectedEntities[0] === entityID) {
|
||||
selection = [];
|
||||
startRenamingEntity(entityID);
|
||||
}
|
||||
}
|
||||
|
||||
updateSelectedEntities(selection);
|
||||
updateSelectedEntities(selection, false);
|
||||
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "selectionUpdate",
|
||||
focus: false,
|
||||
entityIds: selection,
|
||||
}));
|
||||
|
||||
refreshFooter();
|
||||
}
|
||||
|
||||
function onRowDoubleClicked() {
|
||||
let selection = [this.dataset.entityID];
|
||||
updateSelectedEntities(selection, false);
|
||||
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "selectionUpdate",
|
||||
focus: true,
|
||||
entityIds: [this.dataset.entityID],
|
||||
entityIds: selection,
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -289,7 +434,7 @@ function loaded() {
|
|||
hasScript: entity.hasScript,
|
||||
elRow: null, // if this entity has a visible row element assigned to it
|
||||
selected: false // if this entity is selected for edit regardless of having a visible row
|
||||
}
|
||||
};
|
||||
|
||||
entities.push(entityData);
|
||||
entitiesByID[entityData.id] = entityData;
|
||||
|
@ -302,17 +447,16 @@ function loaded() {
|
|||
function refreshEntityList() {
|
||||
PROFILE("refresh-entity-list", function() {
|
||||
PROFILE("filter", function() {
|
||||
let searchTerm = elFilter.value.toLowerCase();
|
||||
if (searchTerm === '') {
|
||||
visibleEntities = entities.slice(0);
|
||||
} else {
|
||||
visibleEntities = entities.filter(function(e) {
|
||||
return e.name.toLowerCase().indexOf(searchTerm) > -1
|
||||
|| e.type.toLowerCase().indexOf(searchTerm) > -1
|
||||
|| e.fullUrl.toLowerCase().indexOf(searchTerm) > -1
|
||||
|| e.id.toLowerCase().indexOf(searchTerm) > -1;
|
||||
});
|
||||
}
|
||||
let searchTerm = elFilterSearch.value.toLowerCase();
|
||||
visibleEntities = entities.filter(function(e) {
|
||||
let type = e.type === "Box" || e.type === "Sphere" ? "Shape" : e.type;
|
||||
let typeFilter = typeFilters.indexOf(type) > -1;
|
||||
let searchFilter = searchTerm === '' || (e.name.toLowerCase().indexOf(searchTerm) > -1 ||
|
||||
e.type.toLowerCase().indexOf(searchTerm) > -1 ||
|
||||
e.fullUrl.toLowerCase().indexOf(searchTerm) > -1 ||
|
||||
e.id.toLowerCase().indexOf(searchTerm) > -1);
|
||||
return typeFilter && searchFilter;
|
||||
});
|
||||
});
|
||||
|
||||
PROFILE("sort", function() {
|
||||
|
@ -418,7 +562,7 @@ function loaded() {
|
|||
isBaked: document.querySelector('#entity-isBaked .sort-order'),
|
||||
drawCalls: document.querySelector('#entity-drawCalls .sort-order'),
|
||||
hasScript: document.querySelector('#entity-hasScript .sort-order'),
|
||||
}
|
||||
};
|
||||
function setSortColumn(column) {
|
||||
PROFILE("set-sort-column", function() {
|
||||
if (currentSortColumn === column) {
|
||||
|
@ -453,7 +597,7 @@ function loaded() {
|
|||
}
|
||||
}
|
||||
|
||||
function updateSelectedEntities(selectedIDs) {
|
||||
function updateSelectedEntities(selectedIDs, autoScroll) {
|
||||
let notFound = false;
|
||||
|
||||
// reset all currently selected entities and their rows first
|
||||
|
@ -482,6 +626,26 @@ function loaded() {
|
|||
}
|
||||
});
|
||||
|
||||
if (autoScroll && selectedIDs.length > 0) {
|
||||
let firstItem = Number.MAX_VALUE;
|
||||
let lastItem = -1;
|
||||
let itemFound = false;
|
||||
visibleEntities.forEach(function(entity, index) {
|
||||
if (selectedIDs.indexOf(entity.id) !== -1) {
|
||||
if (firstItem > index) {
|
||||
firstItem = index;
|
||||
}
|
||||
if (lastItem < index) {
|
||||
lastItem = index;
|
||||
}
|
||||
itemFound = true;
|
||||
}
|
||||
});
|
||||
if (itemFound) {
|
||||
entityList.scrollToRow(firstItem, lastItem);
|
||||
}
|
||||
}
|
||||
|
||||
refreshFooter();
|
||||
|
||||
return notFound;
|
||||
|
@ -502,6 +666,7 @@ function loaded() {
|
|||
}
|
||||
row.appendChild(column);
|
||||
}
|
||||
row.oncontextmenu = onRowContextMenu;
|
||||
row.onclick = onRowClicked;
|
||||
row.ondblclick = onRowDoubleClicked;
|
||||
return row;
|
||||
|
@ -582,29 +747,74 @@ function loaded() {
|
|||
function toggleFilterInView() {
|
||||
isFilterInView = !isFilterInView;
|
||||
if (isFilterInView) {
|
||||
elInView.setAttribute(FILTER_IN_VIEW_ATTRIBUTE, FILTER_IN_VIEW_ATTRIBUTE);
|
||||
elNoEntitiesInView.style.display = "inline";
|
||||
elFilterInView.setAttribute(FILTER_IN_VIEW_ATTRIBUTE, FILTER_IN_VIEW_ATTRIBUTE);
|
||||
} else {
|
||||
elInView.removeAttribute(FILTER_IN_VIEW_ATTRIBUTE);
|
||||
elNoEntitiesInView.style.display = "none";
|
||||
elFilterInView.removeAttribute(FILTER_IN_VIEW_ATTRIBUTE);
|
||||
}
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: "filterInView", filterInView: isFilterInView }));
|
||||
refreshEntities();
|
||||
}
|
||||
|
||||
function onFilterChange() {
|
||||
refreshEntityList();
|
||||
entityList.resize();
|
||||
}
|
||||
|
||||
function onRadiusChange() {
|
||||
elRadius.value = Math.max(elRadius.value, 0);
|
||||
elNoEntitiesRadius.firstChild.nodeValue = elRadius.value;
|
||||
elNoEntitiesMessage.style.display = "none";
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'radius', radius: elRadius.value }));
|
||||
elFilterRadius.value = elFilterRadius.value.replace(/[^0-9]/g, '');
|
||||
elFilterRadius.value = Math.max(elFilterRadius.value, 0);
|
||||
EventBridge.emitWebEvent(JSON.stringify({ type: 'radius', radius: elFilterRadius.value }));
|
||||
refreshEntities();
|
||||
}
|
||||
|
||||
|
||||
function isTypeDropdownVisible() {
|
||||
return elFilterTypeCheckboxes.style.display === "block";
|
||||
}
|
||||
|
||||
function toggleTypeDropdown() {
|
||||
elFilterTypeCheckboxes.style.display = isTypeDropdownVisible() ? "none" : "block";
|
||||
}
|
||||
|
||||
function onToggleTypeDropdown(event) {
|
||||
toggleTypeDropdown();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
function toggleTypeFilter(elInput, refresh) {
|
||||
let type = elInput.getAttribute("filterType");
|
||||
let typeChecked = elInput.checked;
|
||||
|
||||
let typeFilterIndex = typeFilters.indexOf(type);
|
||||
if (!typeChecked && typeFilterIndex > -1) {
|
||||
typeFilters.splice(typeFilterIndex, 1);
|
||||
} else if (typeChecked && typeFilterIndex === -1) {
|
||||
typeFilters.push(type);
|
||||
}
|
||||
|
||||
if (typeFilters.length === 0) {
|
||||
elFilterTypeText.innerText = "No Types";
|
||||
} else if (typeFilters.length === FILTER_TYPES.length) {
|
||||
elFilterTypeText.innerText = "All Types";
|
||||
} else {
|
||||
elFilterTypeText.innerText = "Types...";
|
||||
}
|
||||
|
||||
if (refresh) {
|
||||
refreshEntityList();
|
||||
}
|
||||
}
|
||||
|
||||
function onToggleTypeFilter(event) {
|
||||
let elTarget = event.target;
|
||||
if (elTarget instanceof HTMLInputElement) {
|
||||
toggleTypeFilter(elTarget, true);
|
||||
}
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
function onBodyClick(event) {
|
||||
// if clicking anywhere outside of the type filter dropdown (since click event bubbled up to onBodyClick and
|
||||
// propagation wasn't stopped by onToggleTypeFilter or onToggleTypeDropdown) and the dropdown is open then close it
|
||||
if (isTypeDropdownVisible()) {
|
||||
toggleTypeDropdown();
|
||||
}
|
||||
}
|
||||
|
||||
function toggleInfo(event) {
|
||||
showExtraInfo = !showExtraInfo;
|
||||
if (showExtraInfo) {
|
||||
|
@ -617,7 +827,7 @@ function loaded() {
|
|||
entityList.resize();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
|
||||
document.addEventListener("keydown", function (keyDownEvent) {
|
||||
if (keyDownEvent.target.nodeName === "INPUT") {
|
||||
return;
|
||||
|
@ -641,7 +851,7 @@ function loaded() {
|
|||
if (data.type === "clearEntityList") {
|
||||
clearEntities();
|
||||
} else if (data.type === "selectionUpdate") {
|
||||
let notFound = updateSelectedEntities(data.selectedIDs);
|
||||
let notFound = updateSelectedEntities(data.selectedIDs, true);
|
||||
if (notFound) {
|
||||
refreshEntities();
|
||||
}
|
||||
|
@ -653,13 +863,13 @@ function loaded() {
|
|||
clearEntities();
|
||||
} else {
|
||||
updateEntityData(newEntities);
|
||||
updateSelectedEntities(data.selectedIDs);
|
||||
updateSelectedEntities(data.selectedIDs, true);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (data.type === "removeEntities" && data.deletedIDs !== undefined && data.selectedIDs !== undefined) {
|
||||
removeEntities(data.deletedIDs);
|
||||
updateSelectedEntities(data.selectedIDs);
|
||||
updateSelectedEntities(data.selectedIDs, true);
|
||||
} else if (data.type === "deleted" && data.ids) {
|
||||
removeEntities(data.ids);
|
||||
}
|
||||
|
@ -672,8 +882,15 @@ function loaded() {
|
|||
|
||||
augmentSpinButtons();
|
||||
|
||||
// Disable right-click context menu which is not visible in the HMD and makes it seem like the app has locked
|
||||
document.addEventListener("contextmenu", function (event) {
|
||||
entityListContextMenu.close();
|
||||
|
||||
// Disable default right-click context menu which is not visible in the HMD and makes it seem like the app has locked
|
||||
event.preventDefault();
|
||||
}, false);
|
||||
|
||||
// close context menu when switching focus to another window
|
||||
$(window).blur(function() {
|
||||
entityListContextMenu.close();
|
||||
});
|
||||
}
|
||||
|
|
163
scripts/system/html/js/entityListContextMenu.js
Normal file
163
scripts/system/html/js/entityListContextMenu.js
Normal file
|
@ -0,0 +1,163 @@
|
|||
//
|
||||
// entityListContextMenu.js
|
||||
//
|
||||
// exampleContextMenus.js was originally created by David Rowe on 22 Aug 2018.
|
||||
// Modified to entityListContextMenu.js by Thijs Wenker on 10 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
|
||||
//
|
||||
|
||||
/* eslint-env browser */
|
||||
const CONTEXT_MENU_CLASS = "context-menu";
|
||||
|
||||
/**
|
||||
* ContextMenu class for EntityList
|
||||
* @constructor
|
||||
*/
|
||||
function EntityListContextMenu() {
|
||||
this._elContextMenu = null;
|
||||
this._onSelectedCallback = null;
|
||||
this._listItems = [];
|
||||
this._initialize();
|
||||
}
|
||||
|
||||
EntityListContextMenu.prototype = {
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_elContextMenu: null,
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onSelectedCallback: null,
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_selectedEntityID: null,
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_listItems: null,
|
||||
|
||||
/**
|
||||
* Close the context menu
|
||||
*/
|
||||
close: function() {
|
||||
if (this.isContextMenuOpen()) {
|
||||
this._elContextMenu.style.display = "none";
|
||||
}
|
||||
},
|
||||
|
||||
isContextMenuOpen: function() {
|
||||
return this._elContextMenu.style.display === "block";
|
||||
},
|
||||
|
||||
/**
|
||||
* Open the context menu
|
||||
* @param clickEvent
|
||||
* @param selectedEntityID
|
||||
* @param enabledOptions
|
||||
*/
|
||||
open: function(clickEvent, selectedEntityID, enabledOptions) {
|
||||
this._selectedEntityID = selectedEntityID;
|
||||
|
||||
this._listItems.forEach(function(listItem) {
|
||||
let enabled = enabledOptions.includes(listItem.label);
|
||||
listItem.enabled = enabled;
|
||||
listItem.element.setAttribute('class', enabled ? '' : 'disabled');
|
||||
});
|
||||
|
||||
this._elContextMenu.style.display = "block";
|
||||
this._elContextMenu.style.left
|
||||
= Math.min(clickEvent.pageX, document.body.offsetWidth - this._elContextMenu.offsetWidth).toString() + "px";
|
||||
this._elContextMenu.style.top
|
||||
= Math.min(clickEvent.pageY, document.body.clientHeight - this._elContextMenu.offsetHeight).toString() + "px";
|
||||
clickEvent.stopPropagation();
|
||||
},
|
||||
|
||||
/**
|
||||
* Set the callback for when a menu item is selected
|
||||
* @param onSelectedCallback
|
||||
*/
|
||||
setOnSelectedCallback: function(onSelectedCallback) {
|
||||
this._onSelectedCallback = onSelectedCallback;
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a labeled item to the context menu
|
||||
* @param itemLabel
|
||||
* @private
|
||||
*/
|
||||
_addListItem: function(itemLabel) {
|
||||
let elListItem = document.createElement("li");
|
||||
elListItem.innerText = itemLabel;
|
||||
|
||||
let listItem = {
|
||||
label: itemLabel,
|
||||
element: elListItem,
|
||||
enabled: false
|
||||
};
|
||||
|
||||
elListItem.addEventListener("click", function () {
|
||||
if (listItem.enabled && this._onSelectedCallback) {
|
||||
this._onSelectedCallback.call(this, itemLabel, this._selectedEntityID);
|
||||
}
|
||||
}.bind(this), false);
|
||||
|
||||
elListItem.setAttribute('class', 'disabled');
|
||||
|
||||
this._listItems.push(listItem);
|
||||
this._elContextMenu.appendChild(elListItem);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a separator item to the context menu
|
||||
* @private
|
||||
*/
|
||||
_addListSeparator: function() {
|
||||
let elListItem = document.createElement("li");
|
||||
elListItem.setAttribute('class', 'separator');
|
||||
this._elContextMenu.appendChild(elListItem);
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialize the context menu.
|
||||
* @private
|
||||
*/
|
||||
_initialize: function() {
|
||||
this._elContextMenu = document.createElement("ul");
|
||||
this._elContextMenu.setAttribute("class", CONTEXT_MENU_CLASS);
|
||||
document.body.appendChild(this._elContextMenu);
|
||||
|
||||
this._addListItem("Cut");
|
||||
this._addListItem("Copy");
|
||||
this._addListItem("Paste");
|
||||
this._addListSeparator();
|
||||
this._addListItem("Rename");
|
||||
this._addListItem("Duplicate");
|
||||
this._addListItem("Delete");
|
||||
|
||||
// Ignore clicks on context menu background or separator.
|
||||
this._elContextMenu.addEventListener("click", function(event) {
|
||||
// Sink clicks on context menu background or separator but let context menu item clicks through.
|
||||
if (event.target.classList.contains(CONTEXT_MENU_CLASS)) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
// Provide means to close context menu without clicking menu item.
|
||||
document.body.addEventListener("click", this.close.bind(this));
|
||||
document.body.addEventListener("keydown", function(event) {
|
||||
// Close context menu with Esc key.
|
||||
if (this.isContextMenuOpen() && event.key === "Escape") {
|
||||
this.close();
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
};
|
File diff suppressed because it is too large
Load diff
|
@ -38,7 +38,7 @@ function ListView(elTableBody, elTableScroll, elTableHeaderRow, createRowFunctio
|
|||
this.lastRowShiftScrollTop = 0;
|
||||
|
||||
this.initialize();
|
||||
};
|
||||
}
|
||||
|
||||
ListView.prototype = {
|
||||
getNumRows: function() {
|
||||
|
@ -152,6 +152,30 @@ ListView.prototype = {
|
|||
this.refresh();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Scrolls firstRowIndex with least effort, also tries to make the window include the other selections in case lastRowIndex is set.
|
||||
* In the case that firstRowIndex and lastRowIndex are already within the visible bounds then nothing will happen.
|
||||
* @param {number} firstRowIndex - The row that will be scrolled to.
|
||||
* @param {number} lastRowIndex - The last index of the bound.
|
||||
*/
|
||||
scrollToRow: function (firstRowIndex, lastRowIndex) {
|
||||
lastRowIndex = lastRowIndex ? lastRowIndex : firstRowIndex;
|
||||
let boundingTop = firstRowIndex * this.rowHeight;
|
||||
let boundingBottom = (lastRowIndex * this.rowHeight) + this.rowHeight;
|
||||
if ((boundingBottom - boundingTop) > this.elTableScroll.clientHeight) {
|
||||
boundingBottom = boundingTop + this.elTableScroll.clientHeight;
|
||||
}
|
||||
|
||||
let currentVisibleAreaTop = this.elTableScroll.scrollTop;
|
||||
let currentVisibleAreaBottom = currentVisibleAreaTop + this.elTableScroll.clientHeight;
|
||||
|
||||
if (boundingTop < currentVisibleAreaTop) {
|
||||
this.elTableScroll.scrollTop = boundingTop;
|
||||
} else if (boundingBottom > currentVisibleAreaBottom) {
|
||||
this.elTableScroll.scrollTop = boundingBottom - (this.elTableScroll.clientHeight);
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function() {
|
||||
// block refreshing before rows are initialized
|
||||
|
|
|
@ -15,7 +15,7 @@ var PROFILING_ENABLED = false;
|
|||
var profileIndent = '';
|
||||
const PROFILE_NOOP = function(_name, fn, args) {
|
||||
fn.apply(this, args);
|
||||
} ;
|
||||
};
|
||||
PROFILE = !PROFILING_ENABLED ? PROFILE_NOOP : function(name, fn, args) {
|
||||
console.log("PROFILE-Script " + profileIndent + "(" + name + ") Begin");
|
||||
var previousIndent = profileIndent;
|
||||
|
@ -98,7 +98,11 @@ EntityListTool = function(shouldUseEditTabletApp) {
|
|||
that.setVisible(!visible);
|
||||
};
|
||||
|
||||
selectionManager.addEventListener(function() {
|
||||
selectionManager.addEventListener(function(isSelectionUpdate, caller) {
|
||||
if (caller === that) {
|
||||
// ignore events that we emitted from the entity list itself
|
||||
return;
|
||||
}
|
||||
var selectedIDs = [];
|
||||
|
||||
for (var i = 0; i < selectionManager.selections.length; i++) {
|
||||
|
@ -224,7 +228,7 @@ EntityListTool = function(shouldUseEditTabletApp) {
|
|||
for (var i = 0; i < ids.length; i++) {
|
||||
entityIDs.push(ids[i]);
|
||||
}
|
||||
selectionManager.setSelections(entityIDs);
|
||||
selectionManager.setSelections(entityIDs, that);
|
||||
if (data.focus) {
|
||||
cameraManager.enable();
|
||||
cameraManager.focus(selectionManager.worldPosition,
|
||||
|
@ -245,7 +249,7 @@ EntityListTool = function(shouldUseEditTabletApp) {
|
|||
Window.saveAsync("Select Where to Save", "", "*.json");
|
||||
}
|
||||
} else if (data.type === "pal") {
|
||||
var sessionIds = {}; // Collect the sessionsIds of all selected entitities, w/o duplicates.
|
||||
var sessionIds = {}; // Collect the sessionsIds of all selected entities, w/o duplicates.
|
||||
selectionManager.selections.forEach(function (id) {
|
||||
var lastEditedBy = Entities.getEntityProperties(id, 'lastEditedBy').lastEditedBy;
|
||||
if (lastEditedBy) {
|
||||
|
@ -271,6 +275,19 @@ EntityListTool = function(shouldUseEditTabletApp) {
|
|||
filterInView = data.filterInView === true;
|
||||
} else if (data.type === "radius") {
|
||||
searchRadius = data.radius;
|
||||
} else if (data.type === "cut") {
|
||||
SelectionManager.cutSelectedEntities();
|
||||
} else if (data.type === "copy") {
|
||||
SelectionManager.copySelectedEntities();
|
||||
} else if (data.type === "paste") {
|
||||
SelectionManager.pasteEntities();
|
||||
} else if (data.type === "duplicate") {
|
||||
SelectionManager.duplicateSelection();
|
||||
that.sendUpdate();
|
||||
} else if (data.type === "rename") {
|
||||
Entities.editEntity(data.entityID, {name: data.name});
|
||||
// make sure that the name also gets updated in the properties window
|
||||
SelectionManager._update();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ SelectionManager = (function() {
|
|||
Messages.messageReceived.connect(handleEntitySelectionToolUpdates);
|
||||
}
|
||||
|
||||
// FUNCTION: HANDLE ENTITY SELECTION TOOL UDPATES
|
||||
// FUNCTION: HANDLE ENTITY SELECTION TOOL UPDATES
|
||||
function handleEntitySelectionToolUpdates(channel, message, sender) {
|
||||
if (channel !== 'entityToolUpdates') {
|
||||
return;
|
||||
|
@ -63,7 +63,7 @@ SelectionManager = (function() {
|
|||
if (wantDebug) {
|
||||
print("setting selection to " + messageParsed.entityID);
|
||||
}
|
||||
that.setSelections([messageParsed.entityID]);
|
||||
that.setSelections([messageParsed.entityID], that);
|
||||
}
|
||||
} else if (messageParsed.method === "clearSelection") {
|
||||
if (!SelectionDisplay.triggered() || SelectionDisplay.triggeredHand === messageParsed.hand) {
|
||||
|
@ -136,7 +136,7 @@ SelectionManager = (function() {
|
|||
return that.selections.length > 0;
|
||||
};
|
||||
|
||||
that.setSelections = function(entityIDs) {
|
||||
that.setSelections = function(entityIDs, caller) {
|
||||
that.selections = [];
|
||||
for (var i = 0; i < entityIDs.length; i++) {
|
||||
var entityID = entityIDs[i];
|
||||
|
@ -144,10 +144,10 @@ SelectionManager = (function() {
|
|||
Selection.addToSelectedItemsList(HIGHLIGHT_LIST_NAME, "entity", entityID);
|
||||
}
|
||||
|
||||
that._update(true);
|
||||
that._update(true, caller);
|
||||
};
|
||||
|
||||
that.addEntity = function(entityID, toggleSelection) {
|
||||
that.addEntity = function(entityID, toggleSelection, caller) {
|
||||
if (entityID) {
|
||||
var idx = -1;
|
||||
for (var i = 0; i < that.selections.length; i++) {
|
||||
|
@ -165,7 +165,7 @@ SelectionManager = (function() {
|
|||
}
|
||||
}
|
||||
|
||||
that._update(true);
|
||||
that._update(true, caller);
|
||||
};
|
||||
|
||||
function removeEntityByID(entityID) {
|
||||
|
@ -176,21 +176,21 @@ SelectionManager = (function() {
|
|||
}
|
||||
}
|
||||
|
||||
that.removeEntity = function (entityID) {
|
||||
that.removeEntity = function (entityID, caller) {
|
||||
removeEntityByID(entityID);
|
||||
that._update(true);
|
||||
that._update(true, caller);
|
||||
};
|
||||
|
||||
that.removeEntities = function(entityIDs) {
|
||||
that.removeEntities = function(entityIDs, caller) {
|
||||
for (var i = 0, length = entityIDs.length; i < length; i++) {
|
||||
removeEntityByID(entityIDs[i]);
|
||||
}
|
||||
that._update(true);
|
||||
that._update(true, caller);
|
||||
};
|
||||
|
||||
that.clearSelections = function() {
|
||||
that.clearSelections = function(caller) {
|
||||
that.selections = [];
|
||||
that._update(true);
|
||||
that._update(true, caller);
|
||||
};
|
||||
|
||||
that.addChildrenEntities = function(parentEntityID, entityList) {
|
||||
|
@ -353,12 +353,12 @@ SelectionManager = (function() {
|
|||
}
|
||||
|
||||
return createdEntityIDs;
|
||||
}
|
||||
};
|
||||
|
||||
that.cutSelectedEntities = function() {
|
||||
copySelectedEntities();
|
||||
that.copySelectedEntities();
|
||||
deleteSelectedEntities();
|
||||
}
|
||||
};
|
||||
|
||||
that.copySelectedEntities = function() {
|
||||
var entityProperties = Entities.getMultipleEntityProperties(that.selections);
|
||||
|
@ -434,7 +434,7 @@ SelectionManager = (function() {
|
|||
z: brn.z + entityClipboard.dimensions.z / 2
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
that.pasteEntities = function() {
|
||||
var dimensions = entityClipboard.dimensions;
|
||||
|
@ -442,7 +442,7 @@ SelectionManager = (function() {
|
|||
var pastePosition = getPositionToCreateEntity(maxDimension);
|
||||
var deltaPosition = Vec3.subtract(pastePosition, entityClipboard.position);
|
||||
|
||||
var copiedProperties = []
|
||||
var copiedProperties = [];
|
||||
var ids = [];
|
||||
entityClipboard.entities.forEach(function(originalProperties) {
|
||||
var properties = deepCopy(originalProperties);
|
||||
|
@ -475,9 +475,9 @@ SelectionManager = (function() {
|
|||
|
||||
redo(copiedProperties);
|
||||
undoHistory.pushCommand(undo, copiedProperties, redo, copiedProperties);
|
||||
}
|
||||
};
|
||||
|
||||
that._update = function(selectionUpdated) {
|
||||
that._update = function(selectionUpdated, caller) {
|
||||
var properties = null;
|
||||
if (that.selections.length === 0) {
|
||||
that.localDimensions = null;
|
||||
|
@ -542,7 +542,7 @@ SelectionManager = (function() {
|
|||
|
||||
for (var j = 0; j < listeners.length; j++) {
|
||||
try {
|
||||
listeners[j](selectionUpdated === true);
|
||||
listeners[j](selectionUpdated === true, caller);
|
||||
} catch (e) {
|
||||
print("ERROR: entitySelectionTool.update got exception: " + JSON.stringify(e));
|
||||
}
|
||||
|
@ -985,7 +985,7 @@ SelectionDisplay = (function() {
|
|||
that.pressedHand = NO_HAND;
|
||||
that.triggered = function() {
|
||||
return that.triggeredHand !== NO_HAND;
|
||||
}
|
||||
};
|
||||
function pointingAtDesktopWindowOrTablet(hand) {
|
||||
var pointingAtDesktopWindow = (hand === Controller.Standard.RightHand &&
|
||||
SelectionManager.pointingAtDesktopWindowRight) ||
|
||||
|
@ -1032,7 +1032,7 @@ SelectionDisplay = (function() {
|
|||
that.disableTriggerMapping = function() {
|
||||
that.triggerClickMapping.disable();
|
||||
that.triggerPressMapping.disable();
|
||||
}
|
||||
};
|
||||
Script.scriptEnding.connect(that.disableTriggerMapping);
|
||||
|
||||
// FUNCTION DEF(s): Intersection Check Helpers
|
||||
|
@ -1234,7 +1234,7 @@ SelectionDisplay = (function() {
|
|||
if (wantDebug) {
|
||||
print(" Trigger SelectionManager::update");
|
||||
}
|
||||
SelectionManager._update();
|
||||
SelectionManager._update(false, that);
|
||||
|
||||
if (wantDebug) {
|
||||
print("=============== eST::MouseMoveEvent END =======================");
|
||||
|
@ -1299,7 +1299,7 @@ SelectionDisplay = (function() {
|
|||
lastMouseEvent.isControl = event.isControl;
|
||||
lastMouseEvent.isAlt = event.isAlt;
|
||||
activeTool.onMove(lastMouseEvent);
|
||||
SelectionManager._update();
|
||||
SelectionManager._update(false, this);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1315,7 +1315,7 @@ SelectionDisplay = (function() {
|
|||
lastMouseEvent.isControl = event.isControl;
|
||||
lastMouseEvent.isAlt = event.isAlt;
|
||||
activeTool.onMove(lastMouseEvent);
|
||||
SelectionManager._update();
|
||||
SelectionManager._update(false, this);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -2179,7 +2179,7 @@ SelectionDisplay = (function() {
|
|||
}
|
||||
}
|
||||
|
||||
SelectionManager._update();
|
||||
SelectionManager._update(false, this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -2301,7 +2301,7 @@ SelectionDisplay = (function() {
|
|||
|
||||
previousPickRay = pickRay;
|
||||
|
||||
SelectionManager._update();
|
||||
SelectionManager._update(false, this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -2488,7 +2488,7 @@ SelectionDisplay = (function() {
|
|||
|
||||
previousPickRay = pickRay;
|
||||
|
||||
SelectionManager._update();
|
||||
SelectionManager._update(false, this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -2599,7 +2599,7 @@ SelectionDisplay = (function() {
|
|||
|
||||
previousPickRay = pickRay;
|
||||
|
||||
SelectionManager._update();
|
||||
SelectionManager._update(false, this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
|
@ -874,10 +874,6 @@ function onContentLoaded() {
|
|||
hasShownUpdateNotification = true;
|
||||
}
|
||||
});
|
||||
notifier.on('click', function(notifierObject, options) {
|
||||
log.debug("Got click", options.url);
|
||||
shell.openExternal(options.url);
|
||||
});
|
||||
}
|
||||
|
||||
deleteOldFiles(logPath, DELETE_LOG_FILES_OLDER_THAN_X_SECONDS, LOG_FILE_REGEX);
|
||||
|
|
|
@ -5,6 +5,8 @@ const process = require('process');
|
|||
const hfApp = require('./hf-app');
|
||||
const path = require('path');
|
||||
const AccountInfo = require('./hf-acctinfo').AccountInfo;
|
||||
const url = require('url');
|
||||
const shell = require('electron').shell;
|
||||
const GetBuildInfo = hfApp.getBuildInfo;
|
||||
const buildInfo = GetBuildInfo();
|
||||
const osType = os.type();
|
||||
|
@ -154,8 +156,13 @@ function HifiNotifications(config, menuNotificationCallback) {
|
|||
|
||||
var _menuNotificationCallback = menuNotificationCallback;
|
||||
notifier.on('click', function (notifierObject, options) {
|
||||
StartInterface(options.url);
|
||||
_menuNotificationCallback(options.notificationType, false);
|
||||
const optUrl = url.parse(options.url);
|
||||
if ((optUrl.protocol === "hifi:") || (optUrl.protocol === "hifiapp:")) {
|
||||
StartInterface(options.url);
|
||||
_menuNotificationCallback(options.notificationType, false);
|
||||
} else {
|
||||
shell.openExternal(options.url);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
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);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue