mirror of
https://github.com/overte-org/overte.git
synced 2025-08-07 16:50:43 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into light
This commit is contained in:
commit
d07cde3ecb
51 changed files with 983 additions and 826 deletions
|
@ -870,8 +870,8 @@ AvatarMixerClientData* AvatarMixer::getOrCreateClientData(SharedNodePointer node
|
|||
node->setLinkedData(std::unique_ptr<NodeData> { new AvatarMixerClientData(node->getUUID()) });
|
||||
clientData = dynamic_cast<AvatarMixerClientData*>(node->getLinkedData());
|
||||
auto& avatar = clientData->getAvatar();
|
||||
avatar.setDomainMinimumScale(_domainMinimumScale);
|
||||
avatar.setDomainMaximumScale(_domainMaximumScale);
|
||||
avatar.setDomainMinimumHeight(_domainMinimumHeight);
|
||||
avatar.setDomainMaximumHeight(_domainMaximumHeight);
|
||||
}
|
||||
|
||||
return clientData;
|
||||
|
@ -939,21 +939,21 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
|
|||
|
||||
const QString AVATARS_SETTINGS_KEY = "avatars";
|
||||
|
||||
static const QString MIN_SCALE_OPTION = "min_avatar_scale";
|
||||
float settingMinScale = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MIN_SCALE_OPTION].toDouble(MIN_AVATAR_SCALE);
|
||||
_domainMinimumScale = glm::clamp(settingMinScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE);
|
||||
static const QString MIN_HEIGHT_OPTION = "min_avatar_height";
|
||||
float settingMinHeight = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MIN_HEIGHT_OPTION].toDouble(MIN_AVATAR_HEIGHT);
|
||||
_domainMinimumHeight = glm::clamp(settingMinHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT);
|
||||
|
||||
static const QString MAX_SCALE_OPTION = "max_avatar_scale";
|
||||
float settingMaxScale = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MAX_SCALE_OPTION].toDouble(MAX_AVATAR_SCALE);
|
||||
_domainMaximumScale = glm::clamp(settingMaxScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE);
|
||||
static const QString MAX_HEIGHT_OPTION = "max_avatar_height";
|
||||
float settingMaxHeight = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MAX_HEIGHT_OPTION].toDouble(MAX_AVATAR_HEIGHT);
|
||||
_domainMaximumHeight = glm::clamp(settingMaxHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT);
|
||||
|
||||
// make sure that the domain owner didn't flip min and max
|
||||
if (_domainMinimumScale > _domainMaximumScale) {
|
||||
std::swap(_domainMinimumScale, _domainMaximumScale);
|
||||
if (_domainMinimumHeight > _domainMaximumHeight) {
|
||||
std::swap(_domainMinimumHeight, _domainMaximumHeight);
|
||||
}
|
||||
|
||||
qCDebug(avatars) << "This domain requires a minimum avatar scale of" << _domainMinimumScale
|
||||
<< "and a maximum avatar scale of" << _domainMaximumScale;
|
||||
qCDebug(avatars) << "This domain requires a minimum avatar height of" << _domainMinimumHeight
|
||||
<< "and a maximum avatar height of" << _domainMaximumHeight;
|
||||
|
||||
const QString AVATAR_WHITELIST_DEFAULT{ "" };
|
||||
static const QString AVATAR_WHITELIST_OPTION = "avatar_whitelist";
|
||||
|
|
|
@ -90,8 +90,8 @@ private:
|
|||
|
||||
float _maxKbpsPerNode = 0.0f;
|
||||
|
||||
float _domainMinimumScale { MIN_AVATAR_SCALE };
|
||||
float _domainMaximumScale { MAX_AVATAR_SCALE };
|
||||
float _domainMinimumHeight { MIN_AVATAR_HEIGHT };
|
||||
float _domainMaximumHeight { MAX_AVATAR_HEIGHT };
|
||||
|
||||
RateCounter<> _broadcastRate;
|
||||
p_high_resolution_clock::time_point _lastDebugMessage;
|
||||
|
|
|
@ -25,6 +25,23 @@ AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID) :
|
|||
_avatar->setID(nodeID);
|
||||
}
|
||||
|
||||
uint64_t AvatarMixerClientData::getLastOtherAvatarEncodeTime(QUuid otherAvatar) const {
|
||||
std::unordered_map<QUuid, uint64_t>::const_iterator itr = _lastOtherAvatarEncodeTime.find(otherAvatar);
|
||||
if (itr != _lastOtherAvatarEncodeTime.end()) {
|
||||
return itr->second;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AvatarMixerClientData::setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, const uint64_t& time) {
|
||||
std::unordered_map<QUuid, uint64_t>::iterator itr = _lastOtherAvatarEncodeTime.find(otherAvatar);
|
||||
if (itr != _lastOtherAvatarEncodeTime.end()) {
|
||||
itr->second = time;
|
||||
} else {
|
||||
_lastOtherAvatarEncodeTime.emplace(std::pair<QUuid, uint64_t>(otherAvatar, time));
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarMixerClientData::queuePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer node) {
|
||||
if (!_packetQueue.node) {
|
||||
_packetQueue.node = node;
|
||||
|
|
|
@ -110,16 +110,10 @@ public:
|
|||
bool getRequestsDomainListData() { return _requestsDomainListData; }
|
||||
void setRequestsDomainListData(bool requesting) { _requestsDomainListData = requesting; }
|
||||
|
||||
ViewFrustum getViewFrustom() const { return _currentViewFrustum; }
|
||||
ViewFrustum getViewFrustum() const { return _currentViewFrustum; }
|
||||
|
||||
quint64 getLastOtherAvatarEncodeTime(QUuid otherAvatar) {
|
||||
quint64 result = 0;
|
||||
if (_lastOtherAvatarEncodeTime.find(otherAvatar) != _lastOtherAvatarEncodeTime.end()) {
|
||||
result = _lastOtherAvatarEncodeTime[otherAvatar];
|
||||
}
|
||||
_lastOtherAvatarEncodeTime[otherAvatar] = usecTimestampNow();
|
||||
return result;
|
||||
}
|
||||
uint64_t getLastOtherAvatarEncodeTime(QUuid otherAvatar) const;
|
||||
void setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, const uint64_t& time);
|
||||
|
||||
QVector<JointData>& getLastOtherAvatarSentJoints(QUuid otherAvatar) {
|
||||
_lastOtherAvatarSentJoints[otherAvatar].resize(_avatar->getJointCount());
|
||||
|
@ -143,7 +137,7 @@ private:
|
|||
|
||||
// this is a map of the last time we encoded an "other" avatar for
|
||||
// sending to "this" node
|
||||
std::unordered_map<QUuid, quint64> _lastOtherAvatarEncodeTime;
|
||||
std::unordered_map<QUuid, uint64_t> _lastOtherAvatarEncodeTime;
|
||||
std::unordered_map<QUuid, QVector<JointData>> _lastOtherAvatarSentJoints;
|
||||
|
||||
uint64_t _identityChangeTimestamp;
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include <NodeList.h>
|
||||
#include <Node.h>
|
||||
#include <OctreeConstants.h>
|
||||
#include <PrioritySortUtil.h>
|
||||
#include <udt/PacketHeaders.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <StDev.h>
|
||||
|
@ -32,6 +33,10 @@
|
|||
#include "AvatarMixerClientData.h"
|
||||
#include "AvatarMixerSlave.h"
|
||||
|
||||
namespace PrioritySortUtil {
|
||||
// we declare this callback here but override it later
|
||||
std::function<uint64_t(const AvatarSharedPointer&)> getAvatarAgeCallback = [] (const AvatarSharedPointer& avatar) { return 0; };
|
||||
}
|
||||
|
||||
void AvatarMixerSlave::configure(ConstIter begin, ConstIter end) {
|
||||
_begin = begin;
|
||||
|
@ -184,10 +189,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
|
||||
|
||||
// setup list of AvatarData as well as maps to map betweeen the AvatarData and the original nodes
|
||||
// for calling the AvatarData::sortAvatars() function and getting our sorted list of client nodes
|
||||
QList<AvatarSharedPointer> avatarList;
|
||||
std::vector<AvatarSharedPointer> avatarsToSort;
|
||||
std::unordered_map<AvatarSharedPointer, SharedNodePointer> avatarDataToNodes;
|
||||
|
||||
std::for_each(_begin, _end, [&](const SharedNodePointer& otherNode) {
|
||||
// make sure this is an agent that we have avatar data for before considering it for inclusion
|
||||
if (otherNode->getType() == NodeType::Agent
|
||||
|
@ -195,36 +198,61 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
const AvatarMixerClientData* otherNodeData = reinterpret_cast<const AvatarMixerClientData*>(otherNode->getLinkedData());
|
||||
|
||||
AvatarSharedPointer otherAvatar = otherNodeData->getAvatarSharedPointer();
|
||||
avatarList << otherAvatar;
|
||||
avatarsToSort.push_back(otherAvatar);
|
||||
avatarDataToNodes[otherAvatar] = otherNode;
|
||||
}
|
||||
});
|
||||
|
||||
AvatarSharedPointer thisAvatar = nodeData->getAvatarSharedPointer();
|
||||
ViewFrustum cameraView = nodeData->getViewFrustom();
|
||||
std::priority_queue<AvatarPriority> sortedAvatars;
|
||||
AvatarData::sortAvatars(avatarList, cameraView, sortedAvatars,
|
||||
[&](AvatarSharedPointer avatar)->uint64_t {
|
||||
// now that we've assembled the avatarDataToNodes map we can replace PrioritySortUtil::getAvatarAgeCallback
|
||||
// with the true implementation
|
||||
PrioritySortUtil::getAvatarAgeCallback = [&] (const AvatarSharedPointer& avatar) {
|
||||
auto avatarNode = avatarDataToNodes[avatar];
|
||||
assert(avatarNode); // we can't have gotten here without the avatarData being a valid key in the map
|
||||
return nodeData->getLastBroadcastTime(avatarNode->getUUID());
|
||||
}, [&](AvatarSharedPointer avatar)->float{
|
||||
glm::vec3 nodeBoxHalfScale = (avatar->getWorldPosition() - avatar->getGlobalBoundingBoxCorner() * avatar->getSensorToWorldScale());
|
||||
return nodeData->getLastOtherAvatarEncodeTime(avatarNode->getUUID());
|
||||
};
|
||||
|
||||
class SortableAvatar: public PrioritySortUtil::Sortable {
|
||||
public:
|
||||
SortableAvatar() = delete;
|
||||
SortableAvatar(const AvatarSharedPointer& avatar) : _avatar(avatar) {}
|
||||
glm::vec3 getPosition() const override { return _avatar->getWorldPosition(); }
|
||||
float getRadius() const override {
|
||||
glm::vec3 nodeBoxHalfScale = (_avatar->getWorldPosition() - _avatar->getGlobalBoundingBoxCorner() * _avatar->getSensorToWorldScale());
|
||||
return glm::max(nodeBoxHalfScale.x, glm::max(nodeBoxHalfScale.y, nodeBoxHalfScale.z));
|
||||
}, [&](AvatarSharedPointer avatar)->bool {
|
||||
}
|
||||
uint64_t getTimestamp() const override {
|
||||
// use the callback implemented above
|
||||
return PrioritySortUtil::getAvatarAgeCallback(_avatar);
|
||||
}
|
||||
const AvatarSharedPointer& getAvatar() const { return _avatar; }
|
||||
|
||||
private:
|
||||
AvatarSharedPointer _avatar;
|
||||
};
|
||||
|
||||
// prepare to sort
|
||||
ViewFrustum cameraView = nodeData->getViewFrustum();
|
||||
PrioritySortUtil::PriorityQueue<SortableAvatar> sortedAvatars(cameraView,
|
||||
AvatarData::_avatarSortCoefficientSize,
|
||||
AvatarData::_avatarSortCoefficientCenter,
|
||||
AvatarData::_avatarSortCoefficientAge);
|
||||
|
||||
// ignore or sort
|
||||
const AvatarSharedPointer& thisAvatar = nodeData->getAvatarSharedPointer();
|
||||
for (const auto& avatar : avatarsToSort) {
|
||||
if (avatar == thisAvatar) {
|
||||
return true; // ignore ourselves...
|
||||
// don't echo updates to self
|
||||
continue;
|
||||
}
|
||||
|
||||
bool shouldIgnore = false;
|
||||
|
||||
// We will also ignore other nodes for a couple of different reasons:
|
||||
// We ignore other nodes for a couple of reasons:
|
||||
// 1) ignore bubbles and ignore specific node
|
||||
// 2) the node hasn't really updated it's frame data recently, this can
|
||||
// happen if for example the avatar is connected on a desktop and sending
|
||||
// updates at ~30hz. So every 3 frames we skip a frame.
|
||||
auto avatarNode = avatarDataToNodes[avatar];
|
||||
|
||||
auto avatarNode = avatarDataToNodes[avatar];
|
||||
assert(avatarNode); // we can't have gotten here without the avatarData being a valid key in the map
|
||||
|
||||
const AvatarMixerClientData* avatarNodeData = reinterpret_cast<const AvatarMixerClientData*>(avatarNode->getLinkedData());
|
||||
|
@ -240,7 +268,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
|| (avatarNode->isIgnoringNodeWithID(node->getUUID()) && !getsAnyIgnored)) {
|
||||
shouldIgnore = true;
|
||||
} else {
|
||||
|
||||
// Check to see if the space bubble is enabled
|
||||
// Don't bother with these checks if the other avatar has their bubble enabled and we're gettingAnyIgnored
|
||||
if (node->isIgnoreRadiusEnabled() || (avatarNode->isIgnoreRadiusEnabled() && !getsAnyIgnored)) {
|
||||
|
@ -267,8 +294,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
nodeData->removeFromRadiusIgnoringSet(node, avatarNode->getUUID());
|
||||
}
|
||||
}
|
||||
quint64 endIgnoreCalculation = usecTimestampNow();
|
||||
_stats.ignoreCalculationElapsedTime += (endIgnoreCalculation - startIgnoreCalculation);
|
||||
|
||||
if (!shouldIgnore) {
|
||||
AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(avatarNode->getUUID());
|
||||
|
@ -292,20 +317,21 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
++numAvatarsWithSkippedFrames;
|
||||
}
|
||||
}
|
||||
return shouldIgnore;
|
||||
});
|
||||
quint64 endIgnoreCalculation = usecTimestampNow();
|
||||
_stats.ignoreCalculationElapsedTime += (endIgnoreCalculation - startIgnoreCalculation);
|
||||
|
||||
if (!shouldIgnore) {
|
||||
// sort this one for later
|
||||
sortedAvatars.push(SortableAvatar(avatar));
|
||||
}
|
||||
}
|
||||
|
||||
// loop through our sorted avatars and allocate our bandwidth to them accordingly
|
||||
int avatarRank = 0;
|
||||
|
||||
// this is overly conservative, because it includes some avatars we might not consider
|
||||
int remainingAvatars = (int)sortedAvatars.size();
|
||||
|
||||
while (!sortedAvatars.empty()) {
|
||||
AvatarPriority sortData = sortedAvatars.top();
|
||||
const auto& avatarData = sortedAvatars.top().getAvatar();
|
||||
sortedAvatars.pop();
|
||||
const auto& avatarData = sortData.avatar;
|
||||
avatarRank++;
|
||||
remainingAvatars--;
|
||||
|
||||
auto otherNode = avatarDataToNodes[avatarData];
|
||||
|
@ -332,10 +358,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
nodeData->setLastBroadcastTime(otherNode->getUUID(), usecTimestampNow());
|
||||
}
|
||||
|
||||
// determine if avatar is in view which determines how much data to send
|
||||
glm::vec3 otherPosition = otherAvatar->getClientGlobalPosition();
|
||||
|
||||
|
||||
// determine if avatar is in view, to determine how much data to include...
|
||||
glm::vec3 otherNodeBoxScale = (otherPosition - otherNodeData->getGlobalBoundingBoxCorner()) * 2.0f * otherAvatar->getSensorToWorldScale();
|
||||
AABox otherNodeBox(otherNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale);
|
||||
bool isInView = nodeData->otherAvatarInView(otherNodeBox);
|
||||
|
@ -405,14 +429,18 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
// set the last sent sequence number for this sender on the receiver
|
||||
nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(),
|
||||
otherNodeData->getLastReceivedSequenceNumber());
|
||||
nodeData->setLastOtherAvatarEncodeTime(otherNode->getUUID(), usecTimestampNow());
|
||||
}
|
||||
} 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.
|
||||
}
|
||||
|
||||
avatarPacketList->endSegment();
|
||||
|
||||
quint64 endAvatarDataPacking = usecTimestampNow();
|
||||
_stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking);
|
||||
};
|
||||
}
|
||||
|
||||
quint64 startPacketSending = usecTimestampNow();
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": 2.0,
|
||||
"version": 2.1,
|
||||
"settings": [
|
||||
{
|
||||
"name": "label",
|
||||
|
@ -1015,20 +1015,20 @@
|
|||
"assignment-types": [ 1, 2 ],
|
||||
"settings": [
|
||||
{
|
||||
"name": "min_avatar_scale",
|
||||
"name": "min_avatar_height",
|
||||
"type": "double",
|
||||
"label": "Minimum Avatar Scale",
|
||||
"help": "Limits the scale of avatars in your domain. Must be at least 0.005.",
|
||||
"placeholder": 0.25,
|
||||
"default": 0.25
|
||||
"label": "Minimum Avatar Height (meters)",
|
||||
"help": "Limits the height of avatars in your domain. Must be at least 0.009.",
|
||||
"placeholder": 0.4,
|
||||
"default": 0.4
|
||||
},
|
||||
{
|
||||
"name": "max_avatar_scale",
|
||||
"name": "max_avatar_height",
|
||||
"type": "double",
|
||||
"label": "Maximum Avatar Scale",
|
||||
"help": "Limits the scale of avatars in your domain. Cannot be greater than 1000.",
|
||||
"placeholder": 3.0,
|
||||
"default": 3.0
|
||||
"label": "Maximum Avatar Height (meters)",
|
||||
"help": "Limits the scale of avatars in your domain. Cannot be greater than 1755.",
|
||||
"placeholder": 5.2,
|
||||
"default": 5.2
|
||||
},
|
||||
{
|
||||
"name": "avatar_whitelist",
|
||||
|
|
|
@ -304,6 +304,26 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList
|
|||
|
||||
*wizardCompletedOnce = QVariant(true);
|
||||
}
|
||||
if (oldVersion < 2.1) {
|
||||
// convert old avatar scale settings into avatar height.
|
||||
|
||||
const QString AVATAR_MIN_SCALE_KEYPATH = "avatars.min_avatar_scale";
|
||||
const QString AVATAR_MAX_SCALE_KEYPATH = "avatars.max_avatar_scale";
|
||||
const QString AVATAR_MIN_HEIGHT_KEYPATH = "avatars.min_avatar_height";
|
||||
const QString AVATAR_MAX_HEIGHT_KEYPATH = "avatars.max_avatar_height";
|
||||
|
||||
QVariant* avatarMinScale = _configMap.valueForKeyPath(AVATAR_MIN_SCALE_KEYPATH);
|
||||
if (avatarMinScale) {
|
||||
float scale = avatarMinScale->toFloat();
|
||||
_configMap.valueForKeyPath(AVATAR_MIN_HEIGHT_KEYPATH, scale * DEFAULT_AVATAR_HEIGHT);
|
||||
}
|
||||
|
||||
QVariant* avatarMaxScale = _configMap.valueForKeyPath(AVATAR_MAX_SCALE_KEYPATH);
|
||||
if (avatarMaxScale) {
|
||||
float scale = avatarMaxScale->toFloat();
|
||||
_configMap.valueForKeyPath(AVATAR_MAX_HEIGHT_KEYPATH, scale * DEFAULT_AVATAR_HEIGHT);
|
||||
}
|
||||
}
|
||||
|
||||
// write the current description version to our settings
|
||||
*versionVariant = _descriptionVersion;
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
|
||||
{ "from": "OculusTouch.LY", "to": "Standard.LY",
|
||||
"filters": [
|
||||
{ "type": "deadZone", "min": 0.3 },
|
||||
{ "type": "deadZone", "min": 0.7 },
|
||||
"invert"
|
||||
]
|
||||
},
|
||||
{ "from": "OculusTouch.LX", "filters": { "type": "deadZone", "min": 0.3 }, "to": "Standard.LX" },
|
||||
{ "from": "OculusTouch.LX", "filters": { "type": "deadZone", "min": 0.7 }, "to": "Standard.LX" },
|
||||
{ "from": "OculusTouch.LT", "to": "Standard.LTClick",
|
||||
"peek": true,
|
||||
"filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ]
|
||||
|
@ -29,11 +29,11 @@
|
|||
|
||||
{ "from": "OculusTouch.RY", "to": "Standard.RY",
|
||||
"filters": [
|
||||
{ "type": "deadZone", "min": 0.3 },
|
||||
{ "type": "deadZone", "min": 0.7 },
|
||||
"invert"
|
||||
]
|
||||
},
|
||||
{ "from": "OculusTouch.RX", "filters": { "type": "deadZone", "min": 0.3 }, "to": "Standard.RX" },
|
||||
{ "from": "OculusTouch.RX", "filters": { "type": "deadZone", "min": 0.7 }, "to": "Standard.RX" },
|
||||
{ "from": "OculusTouch.RT", "to": "Standard.RTClick",
|
||||
"peek": true,
|
||||
"filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ]
|
||||
|
|
|
@ -9,33 +9,30 @@ Overlay {
|
|||
|
||||
Image {
|
||||
id: image
|
||||
property bool scaleFix: true;
|
||||
property real xOffset: 0
|
||||
property real yOffset: 0
|
||||
property bool scaleFix: true
|
||||
property real xStart: 0
|
||||
property real yStart: 0
|
||||
property real xSize: 0
|
||||
property real ySize: 0
|
||||
property real imageScale: 1.0
|
||||
property var resizer: Timer {
|
||||
interval: 50
|
||||
repeat: false
|
||||
running: false
|
||||
onTriggered: {
|
||||
var targetAspect = root.width / root.height;
|
||||
var sourceAspect = image.sourceSize.width / image.sourceSize.height;
|
||||
if (sourceAspect <= targetAspect) {
|
||||
if (root.width === image.sourceSize.width) {
|
||||
return;
|
||||
if (image.xSize === 0) {
|
||||
image.xSize = image.sourceSize.width - image.xStart;
|
||||
}
|
||||
image.imageScale = root.width / image.sourceSize.width;
|
||||
} else if (sourceAspect > targetAspect){
|
||||
if (root.height === image.sourceSize.height) {
|
||||
return;
|
||||
if (image.ySize === 0) {
|
||||
image.ySize = image.sourceSize.height - image.yStart;
|
||||
}
|
||||
image.imageScale = root.height / image.sourceSize.height;
|
||||
}
|
||||
image.sourceSize = Qt.size(image.sourceSize.width * image.imageScale, image.sourceSize.height * image.imageScale);
|
||||
|
||||
image.anchors.leftMargin = -image.xStart * root.width / image.xSize;
|
||||
image.anchors.topMargin = -image.yStart * root.height / image.ySize;
|
||||
image.anchors.rightMargin = (image.xStart + image.xSize - image.sourceSize.width) * root.width / image.xSize;
|
||||
image.anchors.bottomMargin = (image.yStart + image.ySize - image.sourceSize.height) * root.height / image.ySize;
|
||||
}
|
||||
}
|
||||
x: -1 * xOffset * imageScale
|
||||
y: -1 * yOffset * imageScale
|
||||
|
||||
onSourceSizeChanged: {
|
||||
if (sourceSize.width !== 0 && sourceSize.height !== 0 && progress === 1.0 && scaleFix) {
|
||||
|
@ -43,6 +40,8 @@ Overlay {
|
|||
resizer.start();
|
||||
}
|
||||
}
|
||||
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
ColorOverlay {
|
||||
|
@ -57,8 +56,10 @@ Overlay {
|
|||
var key = keys[i];
|
||||
var value = subImage[key];
|
||||
switch (key) {
|
||||
case "x": image.xOffset = value; break;
|
||||
case "y": image.yOffset = value; break;
|
||||
case "x": image.xStart = value; break;
|
||||
case "y": image.yStart = value; break;
|
||||
case "width": image.xSize = value; break;
|
||||
case "height": image.ySize = value; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2812,10 +2812,10 @@ static int getEventQueueSize(QThread* thread) {
|
|||
static void dumpEventQueue(QThread* thread) {
|
||||
auto threadData = QThreadData::get2(thread);
|
||||
QMutexLocker locker(&threadData->postEventList.mutex);
|
||||
qDebug() << "AJT: event list, size =" << threadData->postEventList.size();
|
||||
qDebug() << "Event list, size =" << threadData->postEventList.size();
|
||||
for (auto& postEvent : threadData->postEventList) {
|
||||
QEvent::Type type = (postEvent.event ? postEvent.event->type() : QEvent::None);
|
||||
qDebug() << "AJT: " << type;
|
||||
qDebug() << " " << type;
|
||||
}
|
||||
}
|
||||
#endif // DEBUG_EVENT_QUEUE
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include <shared/QtHelpers.h>
|
||||
#include <AvatarData.h>
|
||||
#include <PerfStat.h>
|
||||
#include <PrioritySortUtil.h>
|
||||
#include <RegisteredMetaTypes.h>
|
||||
#include <Rig.h>
|
||||
#include <SettingHandle.h>
|
||||
|
@ -142,32 +143,39 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
|||
|
||||
PerformanceTimer perfTimer("otherAvatars");
|
||||
|
||||
auto avatarMap = getHashCopy();
|
||||
QList<AvatarSharedPointer> avatarList = avatarMap.values();
|
||||
class SortableAvatar: public PrioritySortUtil::Sortable {
|
||||
public:
|
||||
SortableAvatar() = delete;
|
||||
SortableAvatar(const AvatarSharedPointer& avatar) : _avatar(avatar) {}
|
||||
glm::vec3 getPosition() const override { return _avatar->getWorldPosition(); }
|
||||
float getRadius() const override { return std::static_pointer_cast<Avatar>(_avatar)->getBoundingRadius(); }
|
||||
uint64_t getTimestamp() const override { return std::static_pointer_cast<Avatar>(_avatar)->getLastRenderUpdateTime(); }
|
||||
const AvatarSharedPointer& getAvatar() const { return _avatar; }
|
||||
private:
|
||||
AvatarSharedPointer _avatar;
|
||||
};
|
||||
|
||||
ViewFrustum cameraView;
|
||||
qApp->copyDisplayViewFrustum(cameraView);
|
||||
PrioritySortUtil::PriorityQueue<SortableAvatar> sortedAvatars(cameraView,
|
||||
AvatarData::_avatarSortCoefficientSize,
|
||||
AvatarData::_avatarSortCoefficientCenter,
|
||||
AvatarData::_avatarSortCoefficientAge);
|
||||
|
||||
std::priority_queue<AvatarPriority> sortedAvatars;
|
||||
AvatarData::sortAvatars(avatarList, cameraView, sortedAvatars,
|
||||
|
||||
[](AvatarSharedPointer avatar)->uint64_t{
|
||||
return std::static_pointer_cast<Avatar>(avatar)->getLastRenderUpdateTime();
|
||||
},
|
||||
|
||||
[](AvatarSharedPointer avatar)->float{
|
||||
return std::static_pointer_cast<Avatar>(avatar)->getBoundingRadius();
|
||||
},
|
||||
|
||||
[this](AvatarSharedPointer avatar)->bool{
|
||||
const auto& castedAvatar = std::static_pointer_cast<Avatar>(avatar);
|
||||
if (castedAvatar == _myAvatar || !castedAvatar->isInitialized()) {
|
||||
// sort
|
||||
auto avatarMap = getHashCopy();
|
||||
AvatarHash::iterator itr = avatarMap.begin();
|
||||
while (itr != avatarMap.end()) {
|
||||
const auto& avatar = std::static_pointer_cast<Avatar>(*itr);
|
||||
// DO NOT update _myAvatar! Its update has already been done earlier in the main loop.
|
||||
// DO NOT update or fade out uninitialized Avatars
|
||||
return true; // ignore it
|
||||
if (avatar != _myAvatar && avatar->isInitialized()) {
|
||||
sortedAvatars.push(SortableAvatar(avatar));
|
||||
}
|
||||
++itr;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// process in sorted order
|
||||
uint64_t startTime = usecTimestampNow();
|
||||
const uint64_t UPDATE_BUDGET = 2000; // usec
|
||||
uint64_t updateExpiry = startTime + UPDATE_BUDGET;
|
||||
|
@ -176,8 +184,8 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
|||
|
||||
render::Transaction transaction;
|
||||
while (!sortedAvatars.empty()) {
|
||||
const AvatarPriority& sortData = sortedAvatars.top();
|
||||
const auto& avatar = std::static_pointer_cast<Avatar>(sortData.avatar);
|
||||
const SortableAvatar& sortData = sortedAvatars.top();
|
||||
const auto& avatar = std::static_pointer_cast<Avatar>(sortData.getAvatar());
|
||||
|
||||
bool ignoring = DependencyManager::get<NodeList>()->isPersonalMutingNode(avatar->getID());
|
||||
if (ignoring) {
|
||||
|
@ -207,7 +215,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
|||
uint64_t now = usecTimestampNow();
|
||||
if (now < updateExpiry) {
|
||||
// we're within budget
|
||||
bool inView = sortData.priority > OUT_OF_VIEW_THRESHOLD;
|
||||
bool inView = sortData.getPriority() > OUT_OF_VIEW_THRESHOLD;
|
||||
if (inView && avatar->hasNewJointData()) {
|
||||
numAvatarsUpdated++;
|
||||
}
|
||||
|
@ -221,7 +229,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
|||
// --> some avatar velocity measurements may be a little off
|
||||
|
||||
// no time simulate, but we take the time to count how many were tragically missed
|
||||
bool inView = sortData.priority > OUT_OF_VIEW_THRESHOLD;
|
||||
bool inView = sortData.getPriority() > OUT_OF_VIEW_THRESHOLD;
|
||||
if (!inView) {
|
||||
break;
|
||||
}
|
||||
|
@ -230,9 +238,9 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
|||
}
|
||||
sortedAvatars.pop();
|
||||
while (inView && !sortedAvatars.empty()) {
|
||||
const AvatarPriority& newSortData = sortedAvatars.top();
|
||||
const auto& newAvatar = std::static_pointer_cast<Avatar>(newSortData.avatar);
|
||||
inView = newSortData.priority > OUT_OF_VIEW_THRESHOLD;
|
||||
const SortableAvatar& newSortData = sortedAvatars.top();
|
||||
const auto& newAvatar = std::static_pointer_cast<Avatar>(newSortData.getAvatar());
|
||||
inView = newSortData.getPriority() > OUT_OF_VIEW_THRESHOLD;
|
||||
if (inView && newAvatar->hasNewJointData()) {
|
||||
numAVatarsNotUpdated++;
|
||||
}
|
||||
|
|
|
@ -1516,9 +1516,19 @@ void MyAvatar::updateMotors() {
|
|||
_characterController.clearMotors();
|
||||
glm::quat motorRotation;
|
||||
if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) {
|
||||
|
||||
const float FLYING_MOTOR_TIMESCALE = 0.05f;
|
||||
const float WALKING_MOTOR_TIMESCALE = 0.2f;
|
||||
const float INVALID_MOTOR_TIMESCALE = 1.0e6f;
|
||||
|
||||
float horizontalMotorTimescale;
|
||||
float verticalMotorTimescale;
|
||||
|
||||
if (_characterController.getState() == CharacterController::State::Hover ||
|
||||
_characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) {
|
||||
motorRotation = getMyHead()->getHeadOrientation();
|
||||
horizontalMotorTimescale = FLYING_MOTOR_TIMESCALE;
|
||||
verticalMotorTimescale = FLYING_MOTOR_TIMESCALE;
|
||||
} else {
|
||||
// non-hovering = walking: follow camera twist about vertical but not lift
|
||||
// we decompose camera's rotation and store the twist part in motorRotation
|
||||
|
@ -1529,11 +1539,12 @@ void MyAvatar::updateMotors() {
|
|||
glm::quat liftRotation;
|
||||
swingTwistDecomposition(headOrientation, Vectors::UNIT_Y, liftRotation, motorRotation);
|
||||
motorRotation = orientation * motorRotation;
|
||||
horizontalMotorTimescale = WALKING_MOTOR_TIMESCALE;
|
||||
verticalMotorTimescale = INVALID_MOTOR_TIMESCALE;
|
||||
}
|
||||
const float DEFAULT_MOTOR_TIMESCALE = 0.2f;
|
||||
const float INVALID_MOTOR_TIMESCALE = 1.0e6f;
|
||||
|
||||
if (_isPushing || _isBraking || !_isBeingPushed) {
|
||||
_characterController.addMotor(_actionMotorVelocity, motorRotation, DEFAULT_MOTOR_TIMESCALE, INVALID_MOTOR_TIMESCALE);
|
||||
_characterController.addMotor(_actionMotorVelocity, motorRotation, horizontalMotorTimescale, verticalMotorTimescale);
|
||||
} else {
|
||||
// _isBeingPushed must be true --> disable action motor by giving it a long timescale,
|
||||
// otherwise it's attempt to "stand in in place" could defeat scripted motor/thrusts
|
||||
|
@ -1799,6 +1810,7 @@ void MyAvatar::postUpdate(float deltaTime, const render::ScenePointer& scene) {
|
|||
_skeletonModel->setCauterizeBoneSet(_headBoneSet);
|
||||
_fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl();
|
||||
initAnimGraph();
|
||||
_isAnimatingScale = true;
|
||||
}
|
||||
|
||||
if (_enableDebugDrawDefaultPose || _enableDebugDrawAnimPose) {
|
||||
|
@ -1956,27 +1968,33 @@ void MyAvatar::updateOrientation(float deltaTime) {
|
|||
|
||||
// Use head/HMD roll to turn while flying, but not when standing still.
|
||||
if (qApp->isHMDMode() && getCharacterController()->getState() == CharacterController::State::Hover && _hmdRollControlEnabled && hasDriveInput()) {
|
||||
|
||||
// Turn with head roll.
|
||||
const float MIN_CONTROL_SPEED = 0.01f;
|
||||
float speed = glm::length(getWorldVelocity());
|
||||
if (speed >= MIN_CONTROL_SPEED) {
|
||||
// Feather turn when stopping moving.
|
||||
float speedFactor;
|
||||
if (getDriveKey(TRANSLATE_Z) != 0.0f || _lastDrivenSpeed == 0.0f) {
|
||||
_lastDrivenSpeed = speed;
|
||||
speedFactor = 1.0f;
|
||||
} else {
|
||||
speedFactor = glm::min(speed / _lastDrivenSpeed, 1.0f);
|
||||
}
|
||||
const float MIN_CONTROL_SPEED = 2.0f * getSensorToWorldScale(); // meters / sec
|
||||
const glm::vec3 characterForward = getWorldOrientation() * Vectors::UNIT_NEG_Z;
|
||||
float forwardSpeed = glm::dot(characterForward, getWorldVelocity());
|
||||
|
||||
float direction = glm::dot(getWorldVelocity(), getWorldOrientation() * Vectors::UNIT_NEG_Z) > 0.0f ? 1.0f : -1.0f;
|
||||
// only enable roll-turns if we are moving forward or backward at greater then MIN_CONTROL_SPEED
|
||||
if (fabsf(forwardSpeed) >= MIN_CONTROL_SPEED) {
|
||||
|
||||
float direction = forwardSpeed > 0.0f ? 1.0f : -1.0f;
|
||||
float rollAngle = glm::degrees(asinf(glm::dot(IDENTITY_UP, _hmdSensorOrientation * IDENTITY_RIGHT)));
|
||||
float rollSign = rollAngle < 0.0f ? -1.0f : 1.0f;
|
||||
rollAngle = fabsf(rollAngle);
|
||||
rollAngle = rollAngle > _hmdRollControlDeadZone ? rollSign * (rollAngle - _hmdRollControlDeadZone) : 0.0f;
|
||||
|
||||
totalBodyYaw += speedFactor * direction * rollAngle * deltaTime * _hmdRollControlRate;
|
||||
const float MIN_ROLL_ANGLE = _hmdRollControlDeadZone;
|
||||
const float MAX_ROLL_ANGLE = 90.0f; // degrees
|
||||
|
||||
if (rollAngle > MIN_ROLL_ANGLE) {
|
||||
// rate of turning is linearly proportional to rollAngle
|
||||
rollAngle = glm::clamp(rollAngle, MIN_ROLL_ANGLE, MAX_ROLL_ANGLE);
|
||||
|
||||
// scale rollAngle into a value from zero to one.
|
||||
float rollFactor = (rollAngle - MIN_ROLL_ANGLE) / (MAX_ROLL_ANGLE - MIN_ROLL_ANGLE);
|
||||
|
||||
float angularSpeed = rollSign * rollFactor * _hmdRollControlRate;
|
||||
totalBodyYaw += direction * angularSpeed * deltaTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2022,12 +2040,13 @@ void MyAvatar::updateActionMotor(float deltaTime) {
|
|||
_isBraking = _wasPushing || (_isBraking && speed > MIN_ACTION_BRAKE_SPEED);
|
||||
}
|
||||
|
||||
CharacterController::State state = _characterController.getState();
|
||||
|
||||
// compute action input
|
||||
glm::vec3 forward = (getDriveKey(TRANSLATE_Z)) * IDENTITY_FORWARD;
|
||||
glm::vec3 right = (getDriveKey(TRANSLATE_X)) * IDENTITY_RIGHT;
|
||||
|
||||
glm::vec3 direction = forward + right;
|
||||
CharacterController::State state = _characterController.getState();
|
||||
if (state == CharacterController::State::Hover ||
|
||||
_characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) {
|
||||
// we can fly --> support vertical motion
|
||||
|
@ -2161,41 +2180,6 @@ bool findAvatarAvatarPenetration(const glm::vec3 positionA, float radiusA, float
|
|||
// target scale to match the new scale they have chosen. When they leave the domain they will not return to the scale they were
|
||||
// before they entered the limiting domain.
|
||||
|
||||
void MyAvatar::clampTargetScaleToDomainLimits() {
|
||||
// when we're about to change the target scale because the user has asked to increase or decrease their scale,
|
||||
// we first make sure that we're starting from a target scale that is allowed by the current domain
|
||||
|
||||
auto clampedTargetScale = glm::clamp(_targetScale, _domainMinimumScale, _domainMaximumScale);
|
||||
|
||||
if (clampedTargetScale != _targetScale) {
|
||||
qCDebug(interfaceapp, "Clamped scale to %f since original target scale %f was not allowed by domain",
|
||||
(double)clampedTargetScale, (double)_targetScale);
|
||||
|
||||
setTargetScale(clampedTargetScale);
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::clampScaleChangeToDomainLimits(float desiredScale) {
|
||||
auto clampedTargetScale = glm::clamp(desiredScale, _domainMinimumScale, _domainMaximumScale);
|
||||
|
||||
if (clampedTargetScale != desiredScale) {
|
||||
qCDebug(interfaceapp, "Forcing scale to %f since %f is not allowed by domain",
|
||||
clampedTargetScale, desiredScale);
|
||||
}
|
||||
|
||||
setTargetScale(clampedTargetScale);
|
||||
qCDebug(interfaceapp, "Changed scale to %f", (double)_targetScale);
|
||||
emit(scaleChanged());
|
||||
}
|
||||
|
||||
float MyAvatar::getDomainMinScale() {
|
||||
return _domainMinimumScale;
|
||||
}
|
||||
|
||||
float MyAvatar::getDomainMaxScale() {
|
||||
return _domainMaximumScale;
|
||||
}
|
||||
|
||||
void MyAvatar::setGravity(float gravity) {
|
||||
_characterController.setGravity(gravity);
|
||||
}
|
||||
|
@ -2205,70 +2189,58 @@ float MyAvatar::getGravity() {
|
|||
}
|
||||
|
||||
void MyAvatar::increaseSize() {
|
||||
// make sure we're starting from an allowable scale
|
||||
clampTargetScaleToDomainLimits();
|
||||
float minScale = getDomainMinScale();
|
||||
float maxScale = getDomainMaxScale();
|
||||
|
||||
// calculate what our new scale should be
|
||||
float updatedTargetScale = _targetScale * (1.0f + SCALING_RATIO);
|
||||
float clampedTargetScale = glm::clamp(_targetScale, minScale, maxScale);
|
||||
float newTargetScale = glm::clamp(clampedTargetScale * (1.0f + SCALING_RATIO), minScale, maxScale);
|
||||
|
||||
// attempt to change to desired scale (clamped to the domain limits)
|
||||
clampScaleChangeToDomainLimits(updatedTargetScale);
|
||||
setTargetScale(newTargetScale);
|
||||
}
|
||||
|
||||
void MyAvatar::decreaseSize() {
|
||||
// make sure we're starting from an allowable scale
|
||||
clampTargetScaleToDomainLimits();
|
||||
float minScale = getDomainMinScale();
|
||||
float maxScale = getDomainMaxScale();
|
||||
|
||||
// calculate what our new scale should be
|
||||
float updatedTargetScale = _targetScale * (1.0f - SCALING_RATIO);
|
||||
float clampedTargetScale = glm::clamp(_targetScale, minScale, maxScale);
|
||||
float newTargetScale = glm::clamp(clampedTargetScale * (1.0f - SCALING_RATIO), minScale, maxScale);
|
||||
|
||||
// attempt to change to desired scale (clamped to the domain limits)
|
||||
clampScaleChangeToDomainLimits(updatedTargetScale);
|
||||
setTargetScale(newTargetScale);
|
||||
}
|
||||
|
||||
void MyAvatar::resetSize() {
|
||||
// attempt to reset avatar size to the default (clamped to domain limits)
|
||||
const float DEFAULT_AVATAR_SCALE = 1.0f;
|
||||
|
||||
clampScaleChangeToDomainLimits(DEFAULT_AVATAR_SCALE);
|
||||
setTargetScale(DEFAULT_AVATAR_SCALE);
|
||||
}
|
||||
|
||||
void MyAvatar::restrictScaleFromDomainSettings(const QJsonObject& domainSettingsObject) {
|
||||
// pull out the minimum and maximum scale and set them to restrict our scale
|
||||
// pull out the minimum and maximum height and set them to restrict our scale
|
||||
|
||||
static const QString AVATAR_SETTINGS_KEY = "avatars";
|
||||
auto avatarsObject = domainSettingsObject[AVATAR_SETTINGS_KEY].toObject();
|
||||
|
||||
static const QString MIN_SCALE_OPTION = "min_avatar_scale";
|
||||
float settingMinScale = avatarsObject[MIN_SCALE_OPTION].toDouble(MIN_AVATAR_SCALE);
|
||||
setDomainMinimumScale(settingMinScale);
|
||||
static const QString MIN_HEIGHT_OPTION = "min_avatar_height";
|
||||
float settingMinHeight = avatarsObject[MIN_HEIGHT_OPTION].toDouble(MIN_AVATAR_HEIGHT);
|
||||
setDomainMinimumHeight(settingMinHeight);
|
||||
|
||||
static const QString MAX_SCALE_OPTION = "max_avatar_scale";
|
||||
float settingMaxScale = avatarsObject[MAX_SCALE_OPTION].toDouble(MAX_AVATAR_SCALE);
|
||||
setDomainMaximumScale(settingMaxScale);
|
||||
static const QString MAX_HEIGHT_OPTION = "max_avatar_height";
|
||||
float settingMaxHeight = avatarsObject[MAX_HEIGHT_OPTION].toDouble(MAX_AVATAR_HEIGHT);
|
||||
setDomainMaximumHeight(settingMaxHeight);
|
||||
|
||||
// make sure that the domain owner didn't flip min and max
|
||||
if (_domainMinimumScale > _domainMaximumScale) {
|
||||
std::swap(_domainMinimumScale, _domainMaximumScale);
|
||||
if (_domainMinimumHeight > _domainMaximumHeight) {
|
||||
std::swap(_domainMinimumHeight, _domainMaximumHeight);
|
||||
}
|
||||
// Set avatar current scale
|
||||
Settings settings;
|
||||
settings.beginGroup("Avatar");
|
||||
_targetScale = loadSetting(settings, "scale", 1.0f);
|
||||
|
||||
qCDebug(interfaceapp) << "This domain requires a minimum avatar scale of " << _domainMinimumScale
|
||||
<< " and a maximum avatar scale of " << _domainMaximumScale
|
||||
<< ". Current avatar scale is " << _targetScale;
|
||||
qCDebug(interfaceapp) << "This domain requires a minimum avatar scale of " << _domainMinimumHeight
|
||||
<< " and a maximum avatar scale of " << _domainMaximumHeight;
|
||||
|
||||
// debug to log if this avatar's scale in this domain will be clamped
|
||||
float clampedScale = glm::clamp(_targetScale, _domainMinimumScale, _domainMaximumScale);
|
||||
|
||||
if (_targetScale != clampedScale) {
|
||||
qCDebug(interfaceapp) << "Current avatar scale is clamped to " << clampedScale
|
||||
<< " because " << _targetScale << " is not allowed by current domain";
|
||||
// The current scale of avatar should not be more than domain's max_avatar_scale and not less than domain's min_avatar_scale .
|
||||
_targetScale = clampedScale;
|
||||
}
|
||||
_isAnimatingScale = true;
|
||||
|
||||
setModelScale(_targetScale);
|
||||
rebuildCollisionShape();
|
||||
|
@ -2288,8 +2260,8 @@ void MyAvatar::saveAvatarScale() {
|
|||
}
|
||||
|
||||
void MyAvatar::clearScaleRestriction() {
|
||||
_domainMinimumScale = MIN_AVATAR_SCALE;
|
||||
_domainMaximumScale = MAX_AVATAR_SCALE;
|
||||
_domainMinimumHeight = MIN_AVATAR_HEIGHT;
|
||||
_domainMaximumHeight = MAX_AVATAR_HEIGHT;
|
||||
}
|
||||
|
||||
void MyAvatar::goToLocation(const QVariant& propertiesVar) {
|
||||
|
@ -3248,6 +3220,7 @@ void MyAvatar::setModelScale(float scale) {
|
|||
if (changed) {
|
||||
float sensorToWorldScale = getEyeHeight() / getUserEyeHeight();
|
||||
emit sensorToWorldScaleChanged(sensorToWorldScale);
|
||||
emit scaleChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -110,6 +110,10 @@ class MyAvatar : public Avatar {
|
|||
* @property userEyeHeight {number} Estimated height of the users eyes in sensor space. (meters)
|
||||
* @property SELF_ID {string} READ-ONLY. UUID representing "my avatar". Only use for local-only entities and overlays in situations where MyAvatar.sessionUUID is not available (e.g., if not connected to a domain).
|
||||
* Note: Likely to be deprecated.
|
||||
* @property hmdRollControlEnabled {bool} When enabled the roll angle of your HMD will turn your avatar while flying.
|
||||
* @property hmdRollControlDeadZone {number} If hmdRollControlEnabled is true, this value can be used to tune what roll angle is required to begin turning.
|
||||
* This angle is specified in degrees.
|
||||
* @property hmdRollControlRate {number} If hmdRollControlEnabled is true, this value determines the maximum turn rate of your avatar when rolling your HMD in degrees per second.
|
||||
*/
|
||||
|
||||
// FIXME: `glm::vec3 position` is not accessible from QML, so this exposes position in a QML-native type
|
||||
|
@ -558,8 +562,6 @@ public slots:
|
|||
void increaseSize();
|
||||
void decreaseSize();
|
||||
void resetSize();
|
||||
float getDomainMinScale();
|
||||
float getDomainMaxScale();
|
||||
|
||||
void setGravity(float gravity);
|
||||
float getGravity();
|
||||
|
@ -737,12 +739,12 @@ private:
|
|||
bool _clearOverlayWhenMoving { true };
|
||||
QString _dominantHand { DOMINANT_RIGHT_HAND };
|
||||
|
||||
const float ROLL_CONTROL_DEAD_ZONE_DEFAULT = 8.0f; // deg
|
||||
const float ROLL_CONTROL_RATE_DEFAULT = 2.5f; // deg/sec/deg
|
||||
const float ROLL_CONTROL_DEAD_ZONE_DEFAULT = 8.0f; // degrees
|
||||
const float ROLL_CONTROL_RATE_DEFAULT = 114.0f; // degrees / sec
|
||||
|
||||
bool _hmdRollControlEnabled { true };
|
||||
float _hmdRollControlDeadZone { ROLL_CONTROL_DEAD_ZONE_DEFAULT };
|
||||
float _hmdRollControlRate { ROLL_CONTROL_RATE_DEFAULT };
|
||||
float _lastDrivenSpeed { 0.0f };
|
||||
|
||||
// working copy -- see AvatarData for thread-safe _sensorToWorldMatrixCache, used for outward facing access
|
||||
glm::mat4 _sensorToWorldMatrix { glm::mat4() };
|
||||
|
|
|
@ -83,19 +83,28 @@ void QmlCommerce::buy(const QString& assetId, int cost, const bool controlledFai
|
|||
void QmlCommerce::balance() {
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
auto wallet = DependencyManager::get<Wallet>();
|
||||
ledger->balance(wallet->listPublicKeys());
|
||||
QStringList cachedPublicKeys = wallet->listPublicKeys();
|
||||
if (!cachedPublicKeys.isEmpty()) {
|
||||
ledger->balance(cachedPublicKeys);
|
||||
}
|
||||
}
|
||||
|
||||
void QmlCommerce::inventory() {
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
auto wallet = DependencyManager::get<Wallet>();
|
||||
ledger->inventory(wallet->listPublicKeys());
|
||||
QStringList cachedPublicKeys = wallet->listPublicKeys();
|
||||
if (!cachedPublicKeys.isEmpty()) {
|
||||
ledger->inventory(cachedPublicKeys);
|
||||
}
|
||||
}
|
||||
|
||||
void QmlCommerce::history() {
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
auto wallet = DependencyManager::get<Wallet>();
|
||||
ledger->history(wallet->listPublicKeys());
|
||||
QStringList cachedPublicKeys = wallet->listPublicKeys();
|
||||
if (!cachedPublicKeys.isEmpty()) {
|
||||
ledger->history(cachedPublicKeys);
|
||||
}
|
||||
}
|
||||
|
||||
void QmlCommerce::changePassphrase(const QString& oldPassphrase, const QString& newPassphrase) {
|
||||
|
|
|
@ -84,18 +84,7 @@ glm::vec2 RayPick::projectOntoXYPlane(const glm::vec3& worldPos, const glm::vec3
|
|||
glm::vec2 RayPick::projectOntoOverlayXYPlane(const QUuid& overlayID, const glm::vec3& worldPos, bool unNormalized) {
|
||||
glm::vec3 position = vec3FromVariant(qApp->getOverlays().getProperty(overlayID, "position").value);
|
||||
glm::quat rotation = quatFromVariant(qApp->getOverlays().getProperty(overlayID, "rotation").value);
|
||||
glm::vec3 dimensions;
|
||||
|
||||
float dpi = qApp->getOverlays().getProperty(overlayID, "dpi").value.toFloat();
|
||||
if (dpi > 0) {
|
||||
// Calculate physical dimensions for web3d overlay from resolution and dpi; "dimensions" property is used as a scale.
|
||||
glm::vec3 resolution = glm::vec3(vec2FromVariant(qApp->getOverlays().getProperty(overlayID, "resolution").value), 1);
|
||||
glm::vec3 scale = glm::vec3(vec2FromVariant(qApp->getOverlays().getProperty(overlayID, "dimensions").value), 0.01f);
|
||||
const float INCHES_TO_METERS = 1.0f / 39.3701f;
|
||||
dimensions = (resolution * INCHES_TO_METERS / dpi) * scale;
|
||||
} else {
|
||||
dimensions = glm::vec3(vec2FromVariant(qApp->getOverlays().getProperty(overlayID, "dimensions").value), 0.01);
|
||||
}
|
||||
glm::vec3 dimensions = glm::vec3(vec2FromVariant(qApp->getOverlays().getProperty(overlayID, "dimensions").value), 0.01f);
|
||||
|
||||
return projectOntoXYPlane(worldPos, position, rotation, dimensions, ENTITY_ITEM_DEFAULT_REGISTRATION_POINT, unNormalized);
|
||||
}
|
||||
|
|
|
@ -35,7 +35,8 @@ ModelOverlay::ModelOverlay(const ModelOverlay* modelOverlay) :
|
|||
_modelTextures(QVariantMap()),
|
||||
_url(modelOverlay->_url),
|
||||
_updateModel(false),
|
||||
_loadPriority(modelOverlay->getLoadPriority())
|
||||
_scaleToFit(modelOverlay->_scaleToFit),
|
||||
_loadPriority(modelOverlay->_loadPriority)
|
||||
{
|
||||
_model->init();
|
||||
_model->setLoadingPriority(_loadPriority);
|
||||
|
|
|
@ -75,8 +75,8 @@ private:
|
|||
QVariantMap _modelTextures;
|
||||
|
||||
QUrl _url;
|
||||
bool _updateModel = { false };
|
||||
bool _scaleToFit = { false };
|
||||
bool _updateModel { false };
|
||||
bool _scaleToFit { false };
|
||||
float _loadPriority { 0.0f };
|
||||
|
||||
AnimationPointer _animation;
|
||||
|
@ -87,7 +87,7 @@ private:
|
|||
bool _animationRunning { false };
|
||||
bool _animationLoop { false };
|
||||
float _animationFirstFrame { 0.0f };
|
||||
float _animationLastFrame = { 0.0f };
|
||||
float _animationLastFrame { 0.0f };
|
||||
bool _animationHold { false };
|
||||
bool _animationAllowTranslation { false };
|
||||
uint64_t _lastAnimated { 0 };
|
||||
|
|
|
@ -305,13 +305,6 @@ public slots:
|
|||
OverlayID getKeyboardFocusOverlay();
|
||||
void setKeyboardFocusOverlay(const OverlayID& id);
|
||||
|
||||
void mousePressPointerEvent(const OverlayID& overlayID, const PointerEvent& event);
|
||||
void mouseMovePointerEvent(const OverlayID& overlayID, const PointerEvent& event);
|
||||
void mouseReleasePointerEvent(const OverlayID& overlayID, const PointerEvent& event);
|
||||
void hoverEnterPointerEvent(const OverlayID& overlayID, const PointerEvent& event);
|
||||
void hoverOverPointerEvent(const OverlayID& overlayID, const PointerEvent& event);
|
||||
void hoverLeavePointerEvent(const OverlayID& overlayID, const PointerEvent& event);
|
||||
|
||||
signals:
|
||||
/**jsdoc
|
||||
* Emitted when an overlay is deleted
|
||||
|
@ -358,6 +351,14 @@ private:
|
|||
OverlayID _currentHoverOverOverlayID { UNKNOWN_OVERLAY_ID };
|
||||
|
||||
RayToOverlayIntersectionResult findRayIntersectionForMouseEvent(PickRay ray);
|
||||
|
||||
private slots:
|
||||
void mousePressPointerEvent(const OverlayID& overlayID, const PointerEvent& event);
|
||||
void mouseMovePointerEvent(const OverlayID& overlayID, const PointerEvent& event);
|
||||
void mouseReleasePointerEvent(const OverlayID& overlayID, const PointerEvent& event);
|
||||
void hoverEnterPointerEvent(const OverlayID& overlayID, const PointerEvent& event);
|
||||
void hoverOverPointerEvent(const OverlayID& overlayID, const PointerEvent& event);
|
||||
void hoverLeavePointerEvent(const OverlayID& overlayID, const PointerEvent& event);
|
||||
};
|
||||
|
||||
#endif // hifi_Overlays_h
|
||||
|
|
|
@ -18,8 +18,9 @@
|
|||
|
||||
QString const Shape3DOverlay::TYPE = "shape";
|
||||
|
||||
Shape3DOverlay::Shape3DOverlay(const Shape3DOverlay* Shape3DOverlay) :
|
||||
Volume3DOverlay(Shape3DOverlay)
|
||||
Shape3DOverlay::Shape3DOverlay(const Shape3DOverlay* shape3DOverlay) :
|
||||
Volume3DOverlay(shape3DOverlay),
|
||||
_shape(shape3DOverlay->_shape)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ public:
|
|||
virtual QString getType() const override { return TYPE; }
|
||||
|
||||
Shape3DOverlay() {}
|
||||
Shape3DOverlay(const Shape3DOverlay* Shape3DOverlay);
|
||||
Shape3DOverlay(const Shape3DOverlay* shape3DOverlay);
|
||||
|
||||
virtual void render(RenderArgs* args) override;
|
||||
virtual const render::ShapeKey getShapeKey() override;
|
||||
|
|
|
@ -14,7 +14,8 @@
|
|||
#include <RegisteredMetaTypes.h>
|
||||
|
||||
Volume3DOverlay::Volume3DOverlay(const Volume3DOverlay* volume3DOverlay) :
|
||||
Base3DOverlay(volume3DOverlay)
|
||||
Base3DOverlay(volume3DOverlay),
|
||||
_localBoundingBox(volume3DOverlay->_localBoundingBox)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -55,17 +55,15 @@
|
|||
#include <plugins/InputConfiguration.h>
|
||||
#include "ui/Snapshot.h"
|
||||
#include "SoundCache.h"
|
||||
|
||||
#include "raypick/PointerScriptingInterface.h"
|
||||
|
||||
static const float DPI = 30.47f;
|
||||
static const float INCHES_TO_METERS = 1.0f / 39.3701f;
|
||||
static int MAX_WINDOW_SIZE = 4096;
|
||||
static const float METERS_TO_INCHES = 39.3701f;
|
||||
static const float OPAQUE_ALPHA_THRESHOLD = 0.99f;
|
||||
|
||||
const QString Web3DOverlay::TYPE = "web3d";
|
||||
const QString Web3DOverlay::QML = "Web3DOverlay.qml";
|
||||
Web3DOverlay::Web3DOverlay() : _dpi(DPI) {
|
||||
Web3DOverlay::Web3DOverlay() {
|
||||
_touchDevice.setCapabilities(QTouchDevice::Position);
|
||||
_touchDevice.setType(QTouchDevice::TouchScreen);
|
||||
_touchDevice.setName("Web3DOverlayTouchDevice");
|
||||
|
@ -82,7 +80,6 @@ Web3DOverlay::Web3DOverlay(const Web3DOverlay* Web3DOverlay) :
|
|||
_url(Web3DOverlay->_url),
|
||||
_scriptURL(Web3DOverlay->_scriptURL),
|
||||
_dpi(Web3DOverlay->_dpi),
|
||||
_resolution(Web3DOverlay->_resolution),
|
||||
_showKeyboardFocusHighlight(Web3DOverlay->_showKeyboardFocusHighlight)
|
||||
{
|
||||
_geometryId = DependencyManager::get<GeometryCache>()->allocateID();
|
||||
|
@ -154,7 +151,7 @@ void Web3DOverlay::buildWebSurface() {
|
|||
setupQmlSurface();
|
||||
}
|
||||
_webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(getWorldPosition()));
|
||||
_webSurface->resize(QSize(_resolution.x, _resolution.y));
|
||||
onResizeWebSurface();
|
||||
_webSurface->resume();
|
||||
});
|
||||
|
||||
|
@ -244,8 +241,16 @@ void Web3DOverlay::setMaxFPS(uint8_t maxFPS) {
|
|||
}
|
||||
|
||||
void Web3DOverlay::onResizeWebSurface() {
|
||||
_mayNeedResize = false;
|
||||
_webSurface->resize(QSize(_resolution.x, _resolution.y));
|
||||
glm::vec2 dims = glm::vec2(getDimensions());
|
||||
dims *= METERS_TO_INCHES * _dpi;
|
||||
|
||||
// ensure no side is never larger then MAX_WINDOW_SIZE
|
||||
float max = (dims.x > dims.y) ? dims.x : dims.y;
|
||||
if (max > MAX_WINDOW_SIZE) {
|
||||
dims *= MAX_WINDOW_SIZE / max;
|
||||
}
|
||||
|
||||
_webSurface->resize(QSize(dims.x, dims.y));
|
||||
}
|
||||
|
||||
unsigned int Web3DOverlay::deviceIdByTouchPoint(qreal x, qreal y) {
|
||||
|
@ -266,14 +271,14 @@ void Web3DOverlay::render(RenderArgs* args) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (_currentMaxFPS != _desiredMaxFPS) {
|
||||
setMaxFPS(_desiredMaxFPS);
|
||||
}
|
||||
|
||||
if (_mayNeedResize) {
|
||||
emit resizeWebSurface();
|
||||
}
|
||||
|
||||
if (_currentMaxFPS != _desiredMaxFPS) {
|
||||
setMaxFPS(_desiredMaxFPS);
|
||||
}
|
||||
|
||||
vec4 color(toGlm(getColor()), getAlpha());
|
||||
|
||||
if (!_texture) {
|
||||
|
@ -310,7 +315,7 @@ void Web3DOverlay::render(RenderArgs* args) {
|
|||
Transform Web3DOverlay::evalRenderTransform() {
|
||||
Transform transform = Parent::evalRenderTransform();
|
||||
transform.setScale(1.0f);
|
||||
transform.postScale(glm::vec3(getSize(), 1.0f));
|
||||
transform.postScale(glm::vec3(getDimensions(), 1.0f));
|
||||
return transform;
|
||||
}
|
||||
|
||||
|
@ -434,18 +439,10 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) {
|
|||
}
|
||||
}
|
||||
|
||||
auto resolution = properties["resolution"];
|
||||
if (resolution.isValid()) {
|
||||
bool valid;
|
||||
auto res = vec2FromVariant(resolution, valid);
|
||||
if (valid) {
|
||||
_resolution = res;
|
||||
}
|
||||
}
|
||||
|
||||
auto dpi = properties["dpi"];
|
||||
if (dpi.isValid()) {
|
||||
_dpi = dpi.toFloat();
|
||||
_mayNeedResize = true;
|
||||
}
|
||||
|
||||
auto maxFPS = properties["maxFPS"];
|
||||
|
@ -467,8 +464,6 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) {
|
|||
_inputMode = Touch;
|
||||
}
|
||||
}
|
||||
|
||||
_mayNeedResize = true;
|
||||
}
|
||||
|
||||
QVariant Web3DOverlay::getProperty(const QString& property) {
|
||||
|
@ -478,9 +473,6 @@ QVariant Web3DOverlay::getProperty(const QString& property) {
|
|||
if (property == "scriptURL") {
|
||||
return _scriptURL;
|
||||
}
|
||||
if (property == "resolution") {
|
||||
return vec2toVariant(_resolution);
|
||||
}
|
||||
if (property == "dpi") {
|
||||
return _dpi;
|
||||
}
|
||||
|
@ -536,17 +528,18 @@ void Web3DOverlay::setScriptURL(const QString& scriptURL) {
|
|||
}
|
||||
}
|
||||
|
||||
glm::vec2 Web3DOverlay::getSize() const {
|
||||
return _resolution / _dpi * INCHES_TO_METERS * getDimensions();
|
||||
};
|
||||
|
||||
bool Web3DOverlay::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance, BoxFace& face, glm::vec3& surfaceNormal) {
|
||||
// FIXME - face and surfaceNormal not being returned
|
||||
glm::vec2 dimensions = getDimensions();
|
||||
glm::quat rotation = getWorldOrientation();
|
||||
glm::vec3 position = getWorldPosition();
|
||||
|
||||
// Don't call applyTransformTo() or setTransform() here because this code runs too frequently.
|
||||
|
||||
// Produce the dimensions of the overlay based on the image's aspect ratio and the overlay's scale.
|
||||
return findRayRectangleIntersection(origin, direction, getWorldOrientation(), getWorldPosition(), getSize(), distance);
|
||||
if (findRayRectangleIntersection(origin, direction, rotation, position, dimensions, distance)) {
|
||||
surfaceNormal = rotation * Vectors::UNIT_Z;
|
||||
face = glm::dot(surfaceNormal, direction) > 0 ? MIN_Z_FACE : MAX_Z_FACE;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Web3DOverlay* Web3DOverlay::createClone() const {
|
||||
|
|
|
@ -52,8 +52,6 @@ public:
|
|||
void setProperties(const QVariantMap& properties) override;
|
||||
QVariant getProperty(const QString& property) override;
|
||||
|
||||
glm::vec2 getSize() const override;
|
||||
|
||||
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal) override;
|
||||
|
||||
|
@ -93,8 +91,7 @@ private:
|
|||
gpu::TexturePointer _texture;
|
||||
QString _url;
|
||||
QString _scriptURL;
|
||||
float _dpi;
|
||||
vec2 _resolution{ 640, 480 };
|
||||
float _dpi { 30.0f };
|
||||
int _geometryId { 0 };
|
||||
bool _showKeyboardFocusHighlight { true };
|
||||
|
||||
|
|
|
@ -231,6 +231,9 @@ public:
|
|||
|
||||
const glm::mat4& getGeometryToRigTransform() const { return _geometryToRigTransform; }
|
||||
|
||||
const AnimPose& getModelOffsetPose() const { return _modelOffset; }
|
||||
const AnimPose& getGeometryOffsetPose() const { return _geometryOffset; }
|
||||
|
||||
void setEnableDebugDrawIKTargets(bool enableDebugDrawIKTargets) { _enableDebugDrawIKTargets = enableDebugDrawIKTargets; }
|
||||
void setEnableDebugDrawIKConstraints(bool enableDebugDrawIKConstraints) { _enableDebugDrawIKConstraints = enableDebugDrawIKConstraints; }
|
||||
void setEnableDebugDrawIKChains(bool enableDebugDrawIKChains) { _enableDebugDrawIKChains = enableDebugDrawIKChains; }
|
||||
|
|
|
@ -162,6 +162,7 @@ AABox Avatar::getBounds() const {
|
|||
}
|
||||
|
||||
void Avatar::animateScaleChanges(float deltaTime) {
|
||||
|
||||
if (_isAnimatingScale) {
|
||||
float currentScale = getModelScale();
|
||||
float desiredScale = getDomainLimitedScale();
|
||||
|
@ -172,7 +173,7 @@ void Avatar::animateScaleChanges(float deltaTime) {
|
|||
float animatedScale = (1.0f - blendFactor) * currentScale + blendFactor * desiredScale;
|
||||
|
||||
// snap to the end when we get close enough
|
||||
const float MIN_RELATIVE_ERROR = 0.03f;
|
||||
const float MIN_RELATIVE_ERROR = 0.001f;
|
||||
float relativeError = fabsf(desiredScale - currentScale) / desiredScale;
|
||||
if (relativeError < MIN_RELATIVE_ERROR) {
|
||||
animatedScale = desiredScale;
|
||||
|
@ -698,6 +699,7 @@ void Avatar::fixupModelsInScene(const render::ScenePointer& scene) {
|
|||
_skeletonModel->removeFromScene(scene, transaction);
|
||||
_skeletonModel->addToScene(scene, transaction);
|
||||
canTryFade = true;
|
||||
_isAnimatingScale = true;
|
||||
}
|
||||
for (auto attachmentModel : _attachmentModels) {
|
||||
if (attachmentModel->isRenderable() && attachmentModel->needsFixupInScene()) {
|
||||
|
@ -1195,6 +1197,8 @@ void Avatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
|||
void Avatar::setModelURLFinished(bool success) {
|
||||
invalidateJointIndicesCache();
|
||||
|
||||
_isAnimatingScale = true;
|
||||
|
||||
if (!success && _skeletonModelURL != AvatarData::defaultFullAvatarModelUrl()) {
|
||||
const int MAX_SKELETON_DOWNLOAD_ATTEMPTS = 4; // NOTE: we don't want to be as generous as ResourceCache is, we only want 4 attempts
|
||||
if (_skeletonModel->getResourceDownloadAttemptsRemaining() <= 0 ||
|
||||
|
@ -1588,45 +1592,80 @@ float Avatar::getEyeHeight() const {
|
|||
return result;
|
||||
}
|
||||
|
||||
return getModelScale() * getUnscaledEyeHeight();
|
||||
}
|
||||
|
||||
float Avatar::getUnscaledEyeHeight() const {
|
||||
float skeletonHeight = getUnscaledEyeHeightFromSkeleton();
|
||||
|
||||
// Sanity check by looking at the model extents.
|
||||
Extents meshExtents = _skeletonModel->getUnscaledMeshExtents();
|
||||
float meshHeight = meshExtents.size().y;
|
||||
|
||||
// if we determine the mesh is much larger then the skeleton, then we use the mesh height instead.
|
||||
// This helps prevent absurdly large avatars from exceeding the domain height limit.
|
||||
const float MESH_SLOP_RATIO = 1.5f;
|
||||
if (meshHeight > skeletonHeight * MESH_SLOP_RATIO) {
|
||||
return meshHeight;
|
||||
} else {
|
||||
return skeletonHeight;
|
||||
}
|
||||
}
|
||||
|
||||
float Avatar::getUnscaledEyeHeightFromSkeleton() const {
|
||||
|
||||
// TODO: if performance becomes a concern we can cache this value rather then computing it everytime.
|
||||
// Makes assumption that the y = 0 plane in geometry is the ground plane.
|
||||
// We also make that assumption in Rig::computeAvatarBoundingCapsule()
|
||||
float avatarScale = getModelScale();
|
||||
|
||||
if (_skeletonModel) {
|
||||
auto& rig = _skeletonModel->getRig();
|
||||
|
||||
// Normally the model offset transform will contain the avatar scale factor, we explicitly remove it here.
|
||||
AnimPose modelOffsetWithoutAvatarScale(glm::vec3(1.0f), rig.getModelOffsetPose().rot(), rig.getModelOffsetPose().trans());
|
||||
AnimPose geomToRigWithoutAvatarScale = modelOffsetWithoutAvatarScale * rig.getGeometryOffsetPose();
|
||||
|
||||
// This factor can be used to scale distances in the geometry frame into the unscaled rig frame.
|
||||
// Typically it will be the unit conversion from cm to m.
|
||||
float scaleFactor = geomToRigWithoutAvatarScale.scale().x; // in practice this always a uniform scale factor.
|
||||
|
||||
int headTopJoint = rig.indexOfJoint("HeadTop_End");
|
||||
int headJoint = rig.indexOfJoint("Head");
|
||||
int eyeJoint = rig.indexOfJoint("LeftEye") != -1 ? rig.indexOfJoint("LeftEye") : rig.indexOfJoint("RightEye");
|
||||
int toeJoint = rig.indexOfJoint("LeftToeBase") != -1 ? rig.indexOfJoint("LeftToeBase") : rig.indexOfJoint("RightToeBase");
|
||||
|
||||
// Makes assumption that the y = 0 plane in geometry is the ground plane.
|
||||
// We also make that assumption in Rig::computeAvatarBoundingCapsule()
|
||||
const float GROUND_Y = 0.0f;
|
||||
|
||||
// Values from the skeleton are in the geometry coordinate frame.
|
||||
auto skeleton = rig.getAnimSkeleton();
|
||||
if (eyeJoint >= 0 && toeJoint >= 0) {
|
||||
// measure from eyes to toes.
|
||||
float eyeHeight = rig.getAbsoluteDefaultPose(eyeJoint).trans().y - rig.getAbsoluteDefaultPose(toeJoint).trans().y;
|
||||
return eyeHeight;
|
||||
// Measure from eyes to toes.
|
||||
float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y;
|
||||
return scaleFactor * eyeHeight;
|
||||
} else if (eyeJoint >= 0) {
|
||||
// measure eyes to y = 0 plane.
|
||||
float groundHeight = transformPoint(rig.getGeometryToRigTransform(), glm::vec3(0.0f)).y;
|
||||
float eyeHeight = rig.getAbsoluteDefaultPose(eyeJoint).trans().y - groundHeight;
|
||||
return eyeHeight;
|
||||
// Measure Eye joint to y = 0 plane.
|
||||
float eyeHeight = skeleton->getAbsoluteDefaultPose(eyeJoint).trans().y - GROUND_Y;
|
||||
return scaleFactor * eyeHeight;
|
||||
} else if (headTopJoint >= 0 && toeJoint >= 0) {
|
||||
// measure toe to top of head. Note: default poses already include avatar scale factor
|
||||
// Measure from ToeBase joint to HeadTop_End joint, then remove forehead distance.
|
||||
const float ratio = DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD / DEFAULT_AVATAR_HEIGHT;
|
||||
float height = rig.getAbsoluteDefaultPose(headTopJoint).trans().y - rig.getAbsoluteDefaultPose(toeJoint).trans().y;
|
||||
return height - height * ratio;
|
||||
float height = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - skeleton->getAbsoluteDefaultPose(toeJoint).trans().y;
|
||||
return scaleFactor * (height - height * ratio);
|
||||
} else if (headTopJoint >= 0) {
|
||||
// Measure from HeadTop_End joint to the ground, then remove forehead distance.
|
||||
const float ratio = DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD / DEFAULT_AVATAR_HEIGHT;
|
||||
float groundHeight = transformPoint(rig.getGeometryToRigTransform(), glm::vec3(0.0f)).y;
|
||||
float headHeight = rig.getAbsoluteDefaultPose(headTopJoint).trans().y - groundHeight;
|
||||
return headHeight - headHeight * ratio;
|
||||
float headHeight = skeleton->getAbsoluteDefaultPose(headTopJoint).trans().y - GROUND_Y;
|
||||
return scaleFactor * (headHeight - headHeight * ratio);
|
||||
} else if (headJoint >= 0) {
|
||||
float groundHeight = transformPoint(rig.getGeometryToRigTransform(), glm::vec3(0.0f)).y;
|
||||
// Measure Head joint to the ground, then add in distance from neck to eye.
|
||||
const float DEFAULT_AVATAR_NECK_TO_EYE = DEFAULT_AVATAR_NECK_TO_TOP_OF_HEAD - DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD;
|
||||
const float ratio = DEFAULT_AVATAR_NECK_TO_EYE / DEFAULT_AVATAR_NECK_HEIGHT;
|
||||
float neckHeight = rig.getAbsoluteDefaultPose(headJoint).trans().y - groundHeight;
|
||||
return neckHeight + neckHeight * ratio;
|
||||
float neckHeight = skeleton->getAbsoluteDefaultPose(headJoint).trans().y - GROUND_Y;
|
||||
return scaleFactor * (neckHeight + neckHeight * ratio);
|
||||
} else {
|
||||
return avatarScale * DEFAULT_AVATAR_EYE_HEIGHT;
|
||||
return DEFAULT_AVATAR_EYE_HEIGHT;
|
||||
}
|
||||
} else {
|
||||
return avatarScale * DEFAULT_AVATAR_EYE_HEIGHT;
|
||||
return DEFAULT_AVATAR_EYE_HEIGHT;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -255,12 +255,16 @@ public:
|
|||
bool isFading() const { return _isFading; }
|
||||
void updateFadingStatus(render::ScenePointer scene);
|
||||
|
||||
/**jsdoc
|
||||
* Provides read only access to the current eye height of the avatar.
|
||||
* @function Avatar.getEyeHeight
|
||||
* @returns {number} eye height of avatar in meters
|
||||
*/
|
||||
Q_INVOKABLE float getEyeHeight() const;
|
||||
Q_INVOKABLE virtual float getEyeHeight() const override;
|
||||
|
||||
// returns eye height of avatar in meters, ignoreing avatar scale.
|
||||
// if _targetScale is 1 then this will be identical to getEyeHeight;
|
||||
virtual float getUnscaledEyeHeight() const override;
|
||||
|
||||
// returns true, if an acurate eye height estimage can be obtained by inspecting the avatar model skeleton and geometry,
|
||||
// not all subclasses of AvatarData have access to this data.
|
||||
virtual bool canMeasureEyeHeight() const override { return true; }
|
||||
|
||||
|
||||
virtual float getModelScale() const { return _modelScale; }
|
||||
virtual void setModelScale(float scale) { _modelScale = scale; }
|
||||
|
@ -279,6 +283,7 @@ public slots:
|
|||
void setModelURLFinished(bool success);
|
||||
|
||||
protected:
|
||||
float getUnscaledEyeHeightFromSkeleton() const;
|
||||
virtual const QString& getSessionDisplayNameForTransport() const override { return _empty; } // Save a tiny bit of bandwidth. Mixer won't look at what we send.
|
||||
QString _empty{};
|
||||
virtual void maybeUpdateSessionDisplayNameFromTransport(const QString& sessionDisplayName) override { _sessionDisplayName = sessionDisplayName; } // don't use no-op setter!
|
||||
|
@ -349,7 +354,7 @@ protected:
|
|||
RateCounter<> _skeletonModelSimulationRate;
|
||||
RateCounter<> _jointDataSimulationRate;
|
||||
|
||||
private:
|
||||
protected:
|
||||
class AvatarEntityDataHash {
|
||||
public:
|
||||
AvatarEntityDataHash(uint32_t h) : hash(h) {};
|
||||
|
|
|
@ -117,6 +117,55 @@ void AvatarData::setTargetScale(float targetScale) {
|
|||
}
|
||||
}
|
||||
|
||||
float AvatarData::getDomainLimitedScale() const {
|
||||
if (canMeasureEyeHeight()) {
|
||||
const float minScale = getDomainMinScale();
|
||||
const float maxScale = getDomainMaxScale();
|
||||
return glm::clamp(_targetScale, minScale, maxScale);
|
||||
} else {
|
||||
// We can't make a good estimate.
|
||||
return _targetScale;
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarData::setDomainMinimumHeight(float domainMinimumHeight) {
|
||||
_domainMinimumHeight = glm::clamp(domainMinimumHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT);
|
||||
}
|
||||
|
||||
void AvatarData::setDomainMaximumHeight(float domainMaximumHeight) {
|
||||
_domainMaximumHeight = glm::clamp(domainMaximumHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT);
|
||||
}
|
||||
|
||||
float AvatarData::getDomainMinScale() const {
|
||||
float unscaledHeight = getUnscaledHeight();
|
||||
const float EPSILON = 1.0e-4f;
|
||||
if (unscaledHeight <= EPSILON) {
|
||||
unscaledHeight = DEFAULT_AVATAR_HEIGHT;
|
||||
}
|
||||
return _domainMinimumHeight / unscaledHeight;
|
||||
}
|
||||
|
||||
float AvatarData::getDomainMaxScale() const {
|
||||
float unscaledHeight = getUnscaledHeight();
|
||||
const float EPSILON = 1.0e-4f;
|
||||
if (unscaledHeight <= EPSILON) {
|
||||
unscaledHeight = DEFAULT_AVATAR_HEIGHT;
|
||||
}
|
||||
return _domainMaximumHeight / unscaledHeight;
|
||||
}
|
||||
|
||||
float AvatarData::getUnscaledHeight() const {
|
||||
const float eyeHeight = getUnscaledEyeHeight();
|
||||
const float ratio = eyeHeight / DEFAULT_AVATAR_HEIGHT;
|
||||
return eyeHeight + ratio * DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD;
|
||||
}
|
||||
|
||||
float AvatarData::getHeight() const {
|
||||
const float eyeHeight = getEyeHeight();
|
||||
const float ratio = eyeHeight / DEFAULT_AVATAR_HEIGHT;
|
||||
return eyeHeight + ratio * DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD;
|
||||
}
|
||||
|
||||
glm::vec3 AvatarData::getHandPosition() const {
|
||||
return getWorldOrientation() * _handPosition + getWorldPosition();
|
||||
}
|
||||
|
@ -2387,63 +2436,10 @@ void RayToAvatarIntersectionResultFromScriptValue(const QScriptValue& object, Ra
|
|||
|
||||
const float AvatarData::OUT_OF_VIEW_PENALTY = -10.0f;
|
||||
|
||||
float AvatarData::_avatarSortCoefficientSize { 0.5f };
|
||||
float AvatarData::_avatarSortCoefficientSize { 1.0f };
|
||||
float AvatarData::_avatarSortCoefficientCenter { 0.25 };
|
||||
float AvatarData::_avatarSortCoefficientAge { 1.0f };
|
||||
|
||||
void AvatarData::sortAvatars(
|
||||
QList<AvatarSharedPointer> avatarList,
|
||||
const ViewFrustum& cameraView,
|
||||
std::priority_queue<AvatarPriority>& sortedAvatarsOut,
|
||||
std::function<uint64_t(AvatarSharedPointer)> getLastUpdated,
|
||||
std::function<float(AvatarSharedPointer)> getBoundingRadius,
|
||||
std::function<bool(AvatarSharedPointer)> shouldIgnore) {
|
||||
|
||||
PROFILE_RANGE(simulation, "sort");
|
||||
uint64_t now = usecTimestampNow();
|
||||
|
||||
glm::vec3 frustumCenter = cameraView.getPosition();
|
||||
const glm::vec3& forward = cameraView.getDirection();
|
||||
for (int32_t i = 0; i < avatarList.size(); ++i) {
|
||||
const auto& avatar = avatarList.at(i);
|
||||
|
||||
if (shouldIgnore(avatar)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// priority = weighted linear combination of:
|
||||
// (a) apparentSize
|
||||
// (b) proximity to center of view
|
||||
// (c) time since last update
|
||||
glm::vec3 avatarPosition = avatar->getWorldPosition();
|
||||
glm::vec3 offset = avatarPosition - frustumCenter;
|
||||
float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero
|
||||
|
||||
// FIXME - AvatarData has something equivolent to this
|
||||
float radius = getBoundingRadius(avatar);
|
||||
|
||||
float apparentSize = 2.0f * radius / distance;
|
||||
float cosineAngle = glm::dot(offset, forward) / distance;
|
||||
float age = (float)(now - getLastUpdated(avatar)) / (float)(USECS_PER_SECOND);
|
||||
|
||||
// NOTE: we are adding values of different units to get a single measure of "priority".
|
||||
// Thus we multiply each component by a conversion "weight" that scales its units relative to the others.
|
||||
// These weights are pure magic tuning and should be hard coded in the relation below,
|
||||
// but are currently exposed for anyone who would like to explore fine tuning:
|
||||
float priority = _avatarSortCoefficientSize * apparentSize
|
||||
+ _avatarSortCoefficientCenter * cosineAngle
|
||||
+ _avatarSortCoefficientAge * age;
|
||||
|
||||
// decrement priority of avatars outside keyhole
|
||||
if (distance > cameraView.getCenterRadius()) {
|
||||
if (!cameraView.sphereIntersectsFrustum(avatarPosition, radius)) {
|
||||
priority += OUT_OF_VIEW_PENALTY;
|
||||
}
|
||||
}
|
||||
sortedAvatarsOut.push(AvatarPriority(avatar, priority));
|
||||
}
|
||||
}
|
||||
|
||||
QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEntityMap& value) {
|
||||
QScriptValue obj = engine->newObject();
|
||||
for (auto entityID : value.keys()) {
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
#include <QtScript/QScriptValueIterator>
|
||||
#include <QReadWriteLock>
|
||||
|
||||
#include <AvatarConstants.h>
|
||||
#include <JointData.h>
|
||||
#include <NLPacket.h>
|
||||
#include <Node.h>
|
||||
|
@ -257,9 +258,6 @@ namespace AvatarDataPacket {
|
|||
size_t maxJointDataSize(size_t numJoints);
|
||||
}
|
||||
|
||||
static const float MAX_AVATAR_SCALE = 1000.0f;
|
||||
static const float MIN_AVATAR_SCALE = .005f;
|
||||
|
||||
const float MAX_AUDIO_LOUDNESS = 1000.0f; // close enough for mouth animation
|
||||
|
||||
const int AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS = 1000;
|
||||
|
@ -484,12 +482,38 @@ public:
|
|||
// Scale
|
||||
virtual void setTargetScale(float targetScale);
|
||||
|
||||
float getDomainLimitedScale() const { return glm::clamp(_targetScale, _domainMinimumScale, _domainMaximumScale); }
|
||||
float getDomainLimitedScale() const;
|
||||
float getDomainMinScale() const;
|
||||
float getDomainMaxScale() const;
|
||||
|
||||
void setDomainMinimumScale(float domainMinimumScale)
|
||||
{ _domainMinimumScale = glm::clamp(domainMinimumScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); _scaleChanged = usecTimestampNow(); }
|
||||
void setDomainMaximumScale(float domainMaximumScale)
|
||||
{ _domainMaximumScale = glm::clamp(domainMaximumScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); _scaleChanged = usecTimestampNow(); }
|
||||
// returns eye height of avatar in meters, ignoreing avatar scale.
|
||||
// if _targetScale is 1 then this will be identical to getEyeHeight;
|
||||
virtual float getUnscaledEyeHeight() const { return DEFAULT_AVATAR_EYE_HEIGHT; }
|
||||
|
||||
// returns true, if an acurate eye height estimage can be obtained by inspecting the avatar model skeleton and geometry,
|
||||
// not all subclasses of AvatarData have access to this data.
|
||||
virtual bool canMeasureEyeHeight() const { return false; }
|
||||
|
||||
/**jsdoc
|
||||
* Provides read only access to the current eye height of the avatar.
|
||||
* This height is only an estimate and might be incorrect for avatars that are missing standard joints.
|
||||
* @function AvatarData.getEyeHeight
|
||||
* @returns {number} eye height of avatar in meters
|
||||
*/
|
||||
Q_INVOKABLE virtual float getEyeHeight() const { return _targetScale * getUnscaledEyeHeight(); }
|
||||
|
||||
/**jsdoc
|
||||
* Provides read only access to the current height of the avatar.
|
||||
* This height is only an estimate and might be incorrect for avatars that are missing standard joints.
|
||||
* @function AvatarData.getHeight
|
||||
* @returns {number} height of avatar in meters
|
||||
*/
|
||||
Q_INVOKABLE virtual float getHeight() const;
|
||||
|
||||
float getUnscaledHeight() const;
|
||||
|
||||
void setDomainMinimumHeight(float domainMinimumHeight);
|
||||
void setDomainMaximumHeight(float domainMaximumHeight);
|
||||
|
||||
// Hand State
|
||||
Q_INVOKABLE void setHandState(char s) { _handState = s; }
|
||||
|
@ -629,14 +653,6 @@ public:
|
|||
|
||||
static const float OUT_OF_VIEW_PENALTY;
|
||||
|
||||
static void sortAvatars(
|
||||
QList<AvatarSharedPointer> avatarList,
|
||||
const ViewFrustum& cameraView,
|
||||
std::priority_queue<AvatarPriority>& sortedAvatarsOut,
|
||||
std::function<uint64_t(AvatarSharedPointer)> getLastUpdated,
|
||||
std::function<float(AvatarSharedPointer)> getBoundingRadius,
|
||||
std::function<bool(AvatarSharedPointer)> shouldIgnore);
|
||||
|
||||
// TODO: remove this HACK once we settle on optimal sort coefficients
|
||||
// These coefficients exposed for fine tuning the sort priority for transfering new _jointData to the render pipeline.
|
||||
static float _avatarSortCoefficientSize;
|
||||
|
@ -706,8 +722,8 @@ protected:
|
|||
|
||||
// Body scale
|
||||
float _targetScale;
|
||||
float _domainMinimumScale { MIN_AVATAR_SCALE };
|
||||
float _domainMaximumScale { MAX_AVATAR_SCALE };
|
||||
float _domainMinimumHeight { MIN_AVATAR_HEIGHT };
|
||||
float _domainMaximumHeight { MAX_AVATAR_HEIGHT };
|
||||
|
||||
// Hand state (are we grabbing something or not)
|
||||
char _handState;
|
||||
|
|
|
@ -437,9 +437,11 @@ glm::mat4 CompositorHelper::getReticleTransform(const glm::mat4& eyePose, const
|
|||
} else {
|
||||
d = glm::normalize(overlaySurfacePoint);
|
||||
}
|
||||
reticlePosition = headPosition + (d * getReticleDepth());
|
||||
// Our sensor to world matrix always has uniform scale
|
||||
float sensorSpaceReticleDepth = getReticleDepth() / extractScale(_sensorToWorldMatrix).x;
|
||||
reticlePosition = headPosition + (d * sensorSpaceReticleDepth);
|
||||
quat reticleOrientation = cancelOutRoll(glm::quat_cast(_currentDisplayPlugin->getHeadPose()));
|
||||
vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize * getReticleDepth());
|
||||
vec3 reticleScale = vec3(Cursor::Manager::instance().getScale() * reticleSize * sensorSpaceReticleDepth);
|
||||
return glm::inverse(eyePose) * createMatFromScaleQuatAndPos(reticleScale, reticleOrientation, reticlePosition);
|
||||
} else {
|
||||
static const float CURSOR_PIXEL_SIZE = 32.0f;
|
||||
|
|
|
@ -1605,6 +1605,48 @@ void EntityItem::setParentID(const QUuid& value) {
|
|||
if (tree && !oldParentID.isNull()) {
|
||||
tree->removeFromChildrenOfAvatars(getThisPointer());
|
||||
}
|
||||
|
||||
uint32_t oldParentNoBootstrapping = 0;
|
||||
uint32_t newParentNoBootstrapping = 0;
|
||||
if (!value.isNull() && tree) {
|
||||
EntityItemPointer entity = tree->findEntityByEntityItemID(value);
|
||||
if (entity) {
|
||||
newParentNoBootstrapping = entity->getDirtyFlags() & Simulation::NO_BOOTSTRAPPING;
|
||||
}
|
||||
}
|
||||
|
||||
if (!oldParentID.isNull() && tree) {
|
||||
EntityItemPointer entity = tree->findEntityByEntityItemID(oldParentID);
|
||||
if (entity) {
|
||||
oldParentNoBootstrapping = entity->getDirtyFlags() & Simulation::NO_BOOTSTRAPPING;
|
||||
}
|
||||
}
|
||||
|
||||
if (!value.isNull() && (value == Physics::getSessionUUID() || value == AVATAR_SELF_ID)) {
|
||||
newParentNoBootstrapping |= Simulation::NO_BOOTSTRAPPING;
|
||||
}
|
||||
|
||||
if ((bool)(oldParentNoBootstrapping ^ newParentNoBootstrapping)) {
|
||||
if ((bool)(newParentNoBootstrapping & Simulation::NO_BOOTSTRAPPING)) {
|
||||
markDirtyFlags(Simulation::NO_BOOTSTRAPPING);
|
||||
forEachDescendant([&](SpatiallyNestablePointer object) {
|
||||
if (object->getNestableType() == NestableType::Entity) {
|
||||
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(object);
|
||||
entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP | Simulation::NO_BOOTSTRAPPING);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
clearDirtyFlags(Simulation::NO_BOOTSTRAPPING);
|
||||
forEachDescendant([&](SpatiallyNestablePointer object) {
|
||||
if (object->getNestableType() == NestableType::Entity) {
|
||||
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(object);
|
||||
entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);
|
||||
entity->clearDirtyFlags(Simulation::NO_BOOTSTRAPPING);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
SpatiallyNestable::setParentID(value);
|
||||
// children are forced to be kinematic
|
||||
// may need to not collide with own avatar
|
||||
|
@ -1834,40 +1876,9 @@ void EntityItem::computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask
|
|||
}
|
||||
}
|
||||
|
||||
if (userMask & USER_COLLISION_GROUP_MY_AVATAR) {
|
||||
bool iAmHoldingThis = false;
|
||||
// if this entity is a descendant of MyAvatar, don't collide with MyAvatar. This avoids the
|
||||
// "bootstrapping" problem where you can shoot yourself across the room by grabbing something
|
||||
// and holding it against your own avatar.
|
||||
if (isChildOfMyAvatar()) {
|
||||
iAmHoldingThis = true;
|
||||
}
|
||||
// also, don't bootstrap our own avatar with a hold action
|
||||
QList<EntityDynamicPointer> holdActions = getActionsOfType(DYNAMIC_TYPE_HOLD);
|
||||
QList<EntityDynamicPointer>::const_iterator i = holdActions.begin();
|
||||
while (i != holdActions.end()) {
|
||||
EntityDynamicPointer action = *i;
|
||||
if (action->isMine()) {
|
||||
iAmHoldingThis = true;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
QList<EntityDynamicPointer> farGrabActions = getActionsOfType(DYNAMIC_TYPE_FAR_GRAB);
|
||||
i = farGrabActions.begin();
|
||||
while (i != farGrabActions.end()) {
|
||||
EntityDynamicPointer action = *i;
|
||||
if (action->isMine()) {
|
||||
iAmHoldingThis = true;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
if (iAmHoldingThis) {
|
||||
if ((bool)(_dirtyFlags & Simulation::NO_BOOTSTRAPPING)) {
|
||||
userMask &= ~USER_COLLISION_GROUP_MY_AVATAR;
|
||||
}
|
||||
}
|
||||
mask = Physics::getDefaultCollisionMask(group) & (int16_t)(userMask);
|
||||
}
|
||||
}
|
||||
|
@ -1961,7 +1972,20 @@ bool EntityItem::addActionInternal(EntitySimulationPointer simulation, EntityDyn
|
|||
if (success) {
|
||||
_allActionsDataCache = newDataCache;
|
||||
_dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
|
||||
|
||||
auto actionType = action->getType();
|
||||
if (actionType == DYNAMIC_TYPE_HOLD || actionType == DYNAMIC_TYPE_FAR_GRAB) {
|
||||
if (!(bool)(_dirtyFlags & Simulation::NO_BOOTSTRAPPING)) {
|
||||
_dirtyFlags |= Simulation::NO_BOOTSTRAPPING;
|
||||
_dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar
|
||||
forEachDescendant([&](SpatiallyNestablePointer child) {
|
||||
if (child->getNestableType() == NestableType::Entity) {
|
||||
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(child);
|
||||
entity->markDirtyFlags(Simulation::NO_BOOTSTRAPPING | Simulation::DIRTY_COLLISION_GROUP);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
qCDebug(entities) << "EntityItem::addActionInternal -- serializeActions failed";
|
||||
}
|
||||
|
@ -2002,6 +2026,29 @@ bool EntityItem::removeAction(EntitySimulationPointer simulation, const QUuid& a
|
|||
return success;
|
||||
}
|
||||
|
||||
bool EntityItem::stillHasGrabActions() const {
|
||||
QList<EntityDynamicPointer> holdActions = getActionsOfType(DYNAMIC_TYPE_HOLD);
|
||||
QList<EntityDynamicPointer>::const_iterator i = holdActions.begin();
|
||||
while (i != holdActions.end()) {
|
||||
EntityDynamicPointer action = *i;
|
||||
if (action->isMine()) {
|
||||
return true;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
QList<EntityDynamicPointer> farGrabActions = getActionsOfType(DYNAMIC_TYPE_FAR_GRAB);
|
||||
i = farGrabActions.begin();
|
||||
while (i != farGrabActions.end()) {
|
||||
EntityDynamicPointer action = *i;
|
||||
if (action->isMine()) {
|
||||
return true;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPointer simulation) {
|
||||
_previouslyDeletedActions.insert(actionID, usecTimestampNow());
|
||||
if (_objectActions.contains(actionID)) {
|
||||
|
@ -2015,7 +2062,6 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi
|
|||
|
||||
action->setOwnerEntity(nullptr);
|
||||
action->setIsMine(false);
|
||||
_objectActions.remove(actionID);
|
||||
|
||||
if (simulation) {
|
||||
action->removeFromSimulation(simulation);
|
||||
|
@ -2024,7 +2070,23 @@ bool EntityItem::removeActionInternal(const QUuid& actionID, EntitySimulationPoi
|
|||
bool success = true;
|
||||
serializeActions(success, _allActionsDataCache);
|
||||
_dirtyFlags |= Simulation::DIRTY_PHYSICS_ACTIVATION;
|
||||
auto removedActionType = action->getType();
|
||||
if ((removedActionType == DYNAMIC_TYPE_HOLD || removedActionType == DYNAMIC_TYPE_FAR_GRAB) && !stillHasGrabActions()) {
|
||||
_dirtyFlags &= ~Simulation::NO_BOOTSTRAPPING;
|
||||
_dirtyFlags |= Simulation::DIRTY_COLLISION_GROUP; // may need to not collide with own avatar
|
||||
forEachDescendant([&](SpatiallyNestablePointer child) {
|
||||
if (child->getNestableType() == NestableType::Entity) {
|
||||
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(child);
|
||||
entity->markDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);
|
||||
entity->clearDirtyFlags(Simulation::NO_BOOTSTRAPPING);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// NO-OP: we assume NO_BOOTSTRAPPING bits and collision group are correct
|
||||
// because they should have been set correctly when the action was added
|
||||
// and/or when children were linked
|
||||
}
|
||||
_objectActions.remove(actionID);
|
||||
setDynamicDataNeedsTransmit(true);
|
||||
return success;
|
||||
}
|
||||
|
|
|
@ -470,6 +470,7 @@ protected:
|
|||
void setSimulated(bool simulated) { _simulated = simulated; }
|
||||
|
||||
const QByteArray getDynamicDataInternal() const;
|
||||
bool stillHasGrabActions() const;
|
||||
void setDynamicDataInternal(QByteArray dynamicData);
|
||||
|
||||
virtual void dimensionsChanged() override;
|
||||
|
|
|
@ -2486,7 +2486,7 @@ QByteArray EntityItemProperties::getStaticCertificateJSON() const {
|
|||
ADD_STRING_PROPERTY(collisionSoundURL, CollisionSoundURL);
|
||||
ADD_STRING_PROPERTY(compoundShapeURL, CompoundShapeURL);
|
||||
ADD_INT_PROPERTY(editionNumber, EditionNumber);
|
||||
ADD_INT_PROPERTY(instanceNumber, EntityInstanceNumber);
|
||||
ADD_INT_PROPERTY(entityInstanceNumber, EntityInstanceNumber);
|
||||
ADD_STRING_PROPERTY(itemArtist, ItemArtist);
|
||||
ADD_STRING_PROPERTY(itemCategories, ItemCategories);
|
||||
ADD_STRING_PROPERTY(itemDescription, ItemDescription);
|
||||
|
|
|
@ -27,6 +27,7 @@ namespace Simulation {
|
|||
const uint32_t DIRTY_PHYSICS_ACTIVATION = 0x0800; // should activate object in physics engine
|
||||
const uint32_t DIRTY_SIMULATOR_ID = 0x1000; // the simulatorID has changed
|
||||
const uint32_t DIRTY_SIMULATION_OWNERSHIP_PRIORITY = 0x2000; // our own bid priority has changed
|
||||
const uint32_t NO_BOOTSTRAPPING = 0x4000;
|
||||
|
||||
const uint32_t DIRTY_TRANSFORM = DIRTY_POSITION | DIRTY_ROTATION;
|
||||
const uint32_t DIRTY_VELOCITIES = DIRTY_LINEAR_VELOCITY | DIRTY_ANGULAR_VELOCITY;
|
||||
|
|
|
@ -1006,15 +1006,12 @@ NetworkTexturePointer TextureCache::getResourceTexture(QUrl resourceTextureUrl)
|
|||
if (!_spectatorCameraNetworkTexture) {
|
||||
_spectatorCameraNetworkTexture.reset(new NetworkTexture(resourceTextureUrl));
|
||||
}
|
||||
if (_spectatorCameraFramebuffer) {
|
||||
texture = _spectatorCameraFramebuffer->getRenderBuffer(0);
|
||||
if (texture) {
|
||||
texture->setSource(SPECTATOR_CAMERA_FRAME_URL.toString().toStdString());
|
||||
_spectatorCameraNetworkTexture->setImage(texture, texture->getWidth(), texture->getHeight());
|
||||
if (!_spectatorCameraFramebuffer) {
|
||||
getSpectatorCameraFramebuffer(); // initialize frame buffer
|
||||
}
|
||||
updateSpectatorCameraNetworkTexture();
|
||||
return _spectatorCameraNetworkTexture;
|
||||
}
|
||||
}
|
||||
}
|
||||
// FIXME: Generalize this, DRY up this code
|
||||
if (resourceTextureUrl == HMD_PREVIEW_FRAME_URL) {
|
||||
if (!_hmdPreviewNetworkTexture) {
|
||||
|
@ -1052,7 +1049,18 @@ const gpu::FramebufferPointer& TextureCache::getSpectatorCameraFramebuffer(int w
|
|||
// If we aren't taking a screenshot, we might need to resize or create the camera buffer
|
||||
if (!_spectatorCameraFramebuffer || _spectatorCameraFramebuffer->getWidth() != width || _spectatorCameraFramebuffer->getHeight() != height) {
|
||||
_spectatorCameraFramebuffer.reset(gpu::Framebuffer::create("spectatorCamera", gpu::Element::COLOR_SRGBA_32, width, height));
|
||||
updateSpectatorCameraNetworkTexture();
|
||||
emit spectatorCameraFramebufferReset();
|
||||
}
|
||||
return _spectatorCameraFramebuffer;
|
||||
}
|
||||
|
||||
void TextureCache::updateSpectatorCameraNetworkTexture() {
|
||||
if (_spectatorCameraFramebuffer && _spectatorCameraNetworkTexture) {
|
||||
gpu::TexturePointer texture = _spectatorCameraFramebuffer->getRenderBuffer(0);
|
||||
if (texture) {
|
||||
texture->setSource(SPECTATOR_CAMERA_FRAME_URL.toString().toStdString());
|
||||
_spectatorCameraNetworkTexture->setImage(texture, texture->getWidth(), texture->getHeight());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -171,6 +171,7 @@ public:
|
|||
const gpu::FramebufferPointer& getHmdPreviewFramebuffer(int width, int height);
|
||||
const gpu::FramebufferPointer& getSpectatorCameraFramebuffer();
|
||||
const gpu::FramebufferPointer& getSpectatorCameraFramebuffer(int width, int height);
|
||||
void updateSpectatorCameraNetworkTexture();
|
||||
|
||||
static const int DEFAULT_SPECTATOR_CAM_WIDTH { 2048 };
|
||||
static const int DEFAULT_SPECTATOR_CAM_HEIGHT { 1024 };
|
||||
|
|
|
@ -74,6 +74,8 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
return static_cast<PacketVersion>(AudioVersion::HighDynamicRangeVolume);
|
||||
case PacketType::ICEPing:
|
||||
return static_cast<PacketVersion>(IcePingVersion::SendICEPeerID);
|
||||
case PacketType::DomainSettings:
|
||||
return 18; // replace min_avatar_scale and max_avatar_scale with min_avatar_height and max_avatar_height
|
||||
default:
|
||||
return 17;
|
||||
}
|
||||
|
|
|
@ -700,7 +700,7 @@ uint32_t EntityMotionState::getIncomingDirtyFlags() {
|
|||
void EntityMotionState::clearIncomingDirtyFlags() {
|
||||
assert(entityTreeIsLocked());
|
||||
if (_body && _entity) {
|
||||
_entity->clearDirtyFlags();
|
||||
_entity->clearDirtyFlags(DIRTY_PHYSICS_FLAGS);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -163,7 +163,7 @@ void Model::setScale(const glm::vec3& scale) {
|
|||
_scaledToFit = false;
|
||||
}
|
||||
|
||||
const float SCALE_CHANGE_EPSILON = 0.01f;
|
||||
const float SCALE_CHANGE_EPSILON = 0.001f;
|
||||
|
||||
void Model::setScaleInternal(const glm::vec3& scale) {
|
||||
if (glm::distance(_scale, scale) > SCALE_CHANGE_EPSILON) {
|
||||
|
|
|
@ -204,6 +204,9 @@ public:
|
|||
/// Returns the extents of the model's mesh
|
||||
Extents getMeshExtents() const;
|
||||
|
||||
/// Returns the unscaled extents of the model's mesh
|
||||
Extents getUnscaledMeshExtents() const;
|
||||
|
||||
void setTranslation(const glm::vec3& translation);
|
||||
void setRotation(const glm::quat& rotation);
|
||||
void setTransformNoUpdateRenderItems(const Transform& transform); // temporary HACK
|
||||
|
@ -276,9 +279,6 @@ protected:
|
|||
void setBlendshapeCoefficients(const QVector<float>& coefficients) { _blendshapeCoefficients = coefficients; }
|
||||
const QVector<float>& getBlendshapeCoefficients() const { return _blendshapeCoefficients; }
|
||||
|
||||
/// Returns the unscaled extents of the model's mesh
|
||||
Extents getUnscaledMeshExtents() const;
|
||||
|
||||
/// Clear the joint states
|
||||
void clearJointState(int index);
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#ifndef hifi_AvatarConstants_h
|
||||
#define hifi_AvatarConstants_h
|
||||
|
||||
#include "GLMHelpers.h"
|
||||
|
||||
// 50th Percentile Man
|
||||
const float DEFAULT_AVATAR_HEIGHT = 1.755f; // meters
|
||||
const float DEFAULT_AVATAR_EYE_TO_TOP_OF_HEAD = 0.11f; // meters
|
||||
|
@ -52,5 +54,10 @@ const float DEFAULT_AVATAR_JUMP_HEIGHT = (DEFAULT_AVATAR_JUMP_SPEED * DEFAULT_AV
|
|||
const float DEFAULT_AVATAR_FALL_HEIGHT = 20.0f; // meters
|
||||
const float DEFAULT_AVATAR_MIN_HOVER_HEIGHT = 2.5f; // meters
|
||||
|
||||
static const float MAX_AVATAR_SCALE = 1000.0f;
|
||||
static const float MIN_AVATAR_SCALE = 0.005f;
|
||||
|
||||
static const float MAX_AVATAR_HEIGHT = 1000.0f * DEFAULT_AVATAR_HEIGHT; // meters
|
||||
static const float MIN_AVATAR_HEIGHT = 0.005f * DEFAULT_AVATAR_HEIGHT; // meters
|
||||
|
||||
#endif // hifi_AvatarConstants_h
|
||||
|
|
|
@ -12,6 +12,9 @@
|
|||
#define hifi_PrioritySortUtil_h
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <queue>
|
||||
|
||||
#include "NumericalConstants.h"
|
||||
#include "ViewFrustum.h"
|
||||
|
||||
/* PrioritySortUtil is a helper for sorting 3D things relative to a ViewFrustum. To use:
|
||||
|
@ -32,11 +35,10 @@
|
|||
|
||||
(2) Make a PrioritySortUtil::PriorityQueue<Thing> and add them to the queue:
|
||||
|
||||
PrioritySortUtil::Prioritizer prioritizer(viewFrustum);
|
||||
PrioritySortUtil::PriorityQueue<SortableWrapper> sortedThings(viewFrustum);
|
||||
std::priority_queue< PrioritySortUtil::Sortable<Thing> > sortedThings;
|
||||
for (thing in things) {
|
||||
float priority = prioritizer.computePriority(PrioritySortUtil::PrioritizableThing(thing));
|
||||
sortedThings.push(PrioritySortUtil::Sortable<Thing> entry(thing, priority));
|
||||
sortedThings.push(SortableWrapper(thing));
|
||||
}
|
||||
|
||||
(3) Loop over your priority queue and do timeboxed work:
|
||||
|
@ -65,6 +67,7 @@ namespace PrioritySortUtil {
|
|||
virtual uint64_t getTimestamp() const = 0;
|
||||
|
||||
void setPriority(float priority) { _priority = priority; }
|
||||
float getPriority() const { return _priority; }
|
||||
bool operator<(const Sortable& other) const { return _priority < other._priority; }
|
||||
private:
|
||||
float _priority { 0.0f };
|
||||
|
@ -109,11 +112,19 @@ namespace PrioritySortUtil {
|
|||
glm::vec3 position = thing.getPosition();
|
||||
glm::vec3 offset = position - _view.getPosition();
|
||||
float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero
|
||||
float radius = thing.getRadius();
|
||||
const float MIN_RADIUS = 0.1f; // WORKAROUND for zero size objects (we still want them to sort by distance)
|
||||
float radius = glm::min(thing.getRadius(), MIN_RADIUS);
|
||||
float cosineAngle = (glm::dot(offset, _view.getDirection()) / distance);
|
||||
float age = (float)(usecTimestampNow() - thing.getTimestamp());
|
||||
|
||||
float priority = _angularWeight * (radius / distance)
|
||||
+ _centerWeight * (glm::dot(offset, _view.getDirection()) / distance)
|
||||
+ _ageWeight * (float)(usecTimestampNow() - thing.getTimestamp());
|
||||
// we modulatate "age" drift rate by the cosineAngle term to make periphrial objects sort forward
|
||||
// at a reduced rate but we don't want the "age" term to go zero or negative so we clamp it
|
||||
const float MIN_COSINE_ANGLE_FACTOR = 0.1f;
|
||||
float cosineAngleFactor = glm::max(cosineAngle, MIN_COSINE_ANGLE_FACTOR);
|
||||
|
||||
float priority = _angularWeight * glm::max(radius, MIN_RADIUS) / distance
|
||||
+ _centerWeight * cosineAngle
|
||||
+ _ageWeight * cosineAngleFactor * age;
|
||||
|
||||
// decrement priority of things outside keyhole
|
||||
if (distance - radius > _view.getCenterRadius()) {
|
||||
|
|
|
@ -63,7 +63,7 @@ var toolBar = (function() {
|
|||
y: -TOOLBAR_MARGIN_Y - toolHeight
|
||||
});
|
||||
browseDirectoryButton = toolBar.addTool({
|
||||
imageURL: toolIconUrl + "directory-01.svg",
|
||||
imageURL: toolIconUrl + "directory.svg",
|
||||
subImage: {
|
||||
x: 0,
|
||||
y: Tool.IMAGE_WIDTH,
|
||||
|
|
|
@ -1415,6 +1415,9 @@ input#reset-to-natural-dimensions {
|
|||
}
|
||||
|
||||
/* ----- Order of Menu items for Primitive ----- */
|
||||
/* Entity Menu classes are specified by selected entity
|
||||
within entityProperties.js
|
||||
*/
|
||||
#properties-list.ShapeMenu #general,
|
||||
#properties-list.BoxMenu #general,
|
||||
#properties-list.SphereMenu #general {
|
||||
|
@ -1469,6 +1472,34 @@ input#reset-to-natural-dimensions {
|
|||
display: none;
|
||||
}
|
||||
|
||||
/* ----- ParticleEffectMenu ----- */
|
||||
#properties-list.ParticleEffectMenu #general {
|
||||
order: 1;
|
||||
}
|
||||
#properties-list.ParticleEffectMenu #collision-info {
|
||||
order: 2;
|
||||
}
|
||||
#properties-list.ParticleEffectMenu #physical {
|
||||
order: 3;
|
||||
}
|
||||
#properties-list.ParticleEffectMenu #spatial {
|
||||
order: 4;
|
||||
}
|
||||
#properties-list.ParticleEffectMenu #behavior {
|
||||
order: 5;
|
||||
}
|
||||
|
||||
/* items to hide */
|
||||
#properties-list.ParticleEffectMenu #base-color-section,
|
||||
#properties-list.ParticleEffectMenu #hyperlink,
|
||||
#properties-list.ParticleEffectMenu #light,
|
||||
#properties-list.ParticleEffectMenu #model,
|
||||
#properties-list.ParticleEffectMenu #shape-list,
|
||||
#properties-list.ParticleEffectMenu #text,
|
||||
#properties-list.ParticleEffectMenu #web,
|
||||
#properties-list.ParticleEffectMenu #zone {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* ----- Order of Menu items for Light ----- */
|
||||
#properties-list.LightMenu #general {
|
||||
|
@ -1500,8 +1531,8 @@ input#reset-to-natural-dimensions {
|
|||
display: none;
|
||||
}
|
||||
/* items to hide */
|
||||
#properties-list.LightMenu .shape-group.shape-section.property.dropdown,
|
||||
#properties-list.LightMenu color-section.color-control1 {
|
||||
#properties-list.LightMenu #shape-list,
|
||||
#properties-list.LightMenu #base-color-section {
|
||||
display: none
|
||||
}
|
||||
|
||||
|
@ -1536,8 +1567,8 @@ input#reset-to-natural-dimensions {
|
|||
display: none;
|
||||
}
|
||||
/* items to hide */
|
||||
#properties-list.ModelMenu .shape-group.shape-section.property.dropdown,
|
||||
#properties-list.ModelMenu .color-section.color-control1 {
|
||||
#properties-list.ModelMenu #shape-list,
|
||||
#properties-list.ModelMenu #base-color-section {
|
||||
display: none
|
||||
}
|
||||
|
||||
|
@ -1572,8 +1603,8 @@ input#reset-to-natural-dimensions {
|
|||
display: none;
|
||||
}
|
||||
/* items to hide */
|
||||
#properties-list.ZoneMenu .shape-group.shape-section.property.dropdown,
|
||||
#properties-list.ZoneMenu .color-section.color-control1 {
|
||||
#properties-list.ZoneMenu #shape-list,
|
||||
#properties-list.ZoneMenu #base-color-section {
|
||||
display: none
|
||||
}
|
||||
|
||||
|
@ -1608,8 +1639,8 @@ input#reset-to-natural-dimensions {
|
|||
display: none;
|
||||
}
|
||||
/* items to hide */
|
||||
#properties-list.WebMenu .shape-group.shape-section.property.dropdown,
|
||||
#properties-list.WebMenu .color-section.color-control1 {
|
||||
#properties-list.WebMenu #shape-list,
|
||||
#properties-list.WebMenu #base-color-section {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
@ -1645,8 +1676,8 @@ input#reset-to-natural-dimensions {
|
|||
display: none;
|
||||
}
|
||||
/* items to hide */
|
||||
#properties-list.TextMenu .shape-group.shape-section.property.dropdown,
|
||||
#properties-list.TextMenu .color-section.color-control1 {
|
||||
#properties-list.TextMenu #shape-list,
|
||||
#properties-list.TextMenu #base-color-section {
|
||||
display: none
|
||||
}
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
|
||||
|
||||
<fieldset id="general" class="major">
|
||||
<div class="shape-group shape-section property dropdown">
|
||||
<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>
|
||||
|
@ -66,12 +66,19 @@
|
|||
<label for="property-name">Name</label>
|
||||
<input type="text" id="property-name">
|
||||
</div>
|
||||
<div class="physical-group color-section property color-control1">
|
||||
<div class="color-picker" id="property-color-control1"></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">
|
||||
|
||||
<fieldset id="collision-info" class="major">
|
||||
<legend class="section-header"> Collision<span>M</span> </legend>
|
||||
<fieldset class="minor">
|
||||
<div class="behavior-group property checkbox">
|
||||
<input type="checkbox" id="property-collisionless">
|
||||
|
@ -216,17 +223,6 @@
|
|||
</div>
|
||||
</fieldset>
|
||||
</fieldset>
|
||||
<fieldset class="minor">
|
||||
<div class="physical-group color-section property rgb fstuple">
|
||||
<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>
|
||||
|
||||
|
||||
|
|
|
@ -6,6 +6,9 @@
|
|||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
/* global alert, augmentSpinButtons, clearTimeout, console, document, Element, EventBridge,
|
||||
HifiEntityUI, JSONEditor, openEventBridge, setUpKeyboardControl, setTimeout, window, _ $ */
|
||||
|
||||
var PI = 3.14159265358979;
|
||||
var DEGREES_TO_RADIANS = PI / 180.0;
|
||||
var RADIANS_TO_DEGREES = 180.0 / PI;
|
||||
|
@ -22,33 +25,33 @@ var ICON_FOR_TYPE = {
|
|||
PolyVox: "",
|
||||
Multiple: "",
|
||||
PolyLine: ""
|
||||
}
|
||||
};
|
||||
|
||||
var EDITOR_TIMEOUT_DURATION = 1500;
|
||||
const KEY_P = 80; //Key code for letter p used for Parenting hotkey.
|
||||
var KEY_P = 80; // Key code for letter p used for Parenting hotkey.
|
||||
var colorPickers = [];
|
||||
var lastEntityID = null;
|
||||
|
||||
debugPrint = function(message) {
|
||||
function debugPrint(message) {
|
||||
EventBridge.emitWebEvent(
|
||||
JSON.stringify({
|
||||
type: "print",
|
||||
message: message
|
||||
})
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function enableChildren(el, selector) {
|
||||
els = el.querySelectorAll(selector);
|
||||
for (var i = 0; i < els.length; i++) {
|
||||
els[i].removeAttribute('disabled');
|
||||
var elSelectors = el.querySelectorAll(selector);
|
||||
for (var selectorIndex = 0; selectorIndex < elSelectors.length; ++selectorIndex) {
|
||||
elSelectors[selectorIndex].removeAttribute('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
function disableChildren(el, selector) {
|
||||
els = el.querySelectorAll(selector);
|
||||
for (var i = 0; i < els.length; i++) {
|
||||
els[i].setAttribute('disabled', 'disabled');
|
||||
var elSelectors = el.querySelectorAll(selector);
|
||||
for (var selectorIndex = 0; selectorIndex < elSelectors.length; ++selectorIndex) {
|
||||
elSelectors[selectorIndex].setAttribute('disabled', 'disabled');
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,16 +106,6 @@ function createEmitCheckedPropertyUpdateFunction(propertyName) {
|
|||
};
|
||||
}
|
||||
|
||||
function createEmitCheckedToStringPropertyUpdateFunction(checkboxElement, name, propertyName) {
|
||||
var newString = "";
|
||||
if (checkboxElement.checked) {
|
||||
newString += name + "";
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function createEmitGroupCheckedPropertyUpdateFunction(group, propertyName) {
|
||||
return function() {
|
||||
var properties = {};
|
||||
|
@ -123,7 +116,7 @@ function createEmitGroupCheckedPropertyUpdateFunction(group, propertyName) {
|
|||
}
|
||||
|
||||
function createEmitNumberPropertyUpdateFunction(propertyName, decimals) {
|
||||
decimals = decimals == undefined ? 4 : decimals;
|
||||
decimals = ((decimals === undefined) ? 4 : decimals);
|
||||
return function() {
|
||||
var value = parseFloat(this.value).toFixed(decimals);
|
||||
updateProperty(propertyName, value);
|
||||
|
@ -146,7 +139,9 @@ function createEmitTextPropertyUpdateFunction(propertyName) {
|
|||
};
|
||||
}
|
||||
|
||||
function createZoneComponentModeChangedFunction(zoneComponent, zoneComponentModeInherit, zoneComponentModeDisabled, zoneComponentModeEnabled) {
|
||||
function createZoneComponentModeChangedFunction(zoneComponent, zoneComponentModeInherit,
|
||||
zoneComponentModeDisabled, zoneComponentModeEnabled) {
|
||||
|
||||
return function() {
|
||||
var zoneComponentMode;
|
||||
|
||||
|
@ -159,7 +154,7 @@ function createZoneComponentModeChangedFunction(zoneComponent, zoneComponentMode
|
|||
}
|
||||
|
||||
updateProperty(zoneComponent, zoneComponentMode);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createEmitGroupTextPropertyUpdateFunction(group, propertyName) {
|
||||
|
@ -177,11 +172,11 @@ function createEmitVec3PropertyUpdateFunction(property, elX, elY, elZ) {
|
|||
properties[property] = {
|
||||
x: elX.value,
|
||||
y: elY.value,
|
||||
z: elZ.value,
|
||||
z: elZ.value
|
||||
};
|
||||
updateProperties(properties);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createEmitGroupVec3PropertyUpdateFunction(group, property, elX, elY, elZ) {
|
||||
return function() {
|
||||
|
@ -190,11 +185,11 @@ function createEmitGroupVec3PropertyUpdateFunction(group, property, elX, elY, el
|
|||
properties[group][property] = {
|
||||
x: elX.value,
|
||||
y: elY.value,
|
||||
z: elZ ? elZ.value : 0,
|
||||
z: elZ ? elZ.value : 0
|
||||
};
|
||||
updateProperties(properties);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createEmitVec3PropertyUpdateFunctionWithMultiplier(property, elX, elY, elZ, multiplier) {
|
||||
return function() {
|
||||
|
@ -202,17 +197,17 @@ function createEmitVec3PropertyUpdateFunctionWithMultiplier(property, elX, elY,
|
|||
properties[property] = {
|
||||
x: elX.value * multiplier,
|
||||
y: elY.value * multiplier,
|
||||
z: elZ.value * multiplier,
|
||||
z: elZ.value * multiplier
|
||||
};
|
||||
updateProperties(properties);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createEmitColorPropertyUpdateFunction(property, elRed, elGreen, elBlue) {
|
||||
return function() {
|
||||
emitColorPropertyUpdate(property, elRed.value, elGreen.value, elBlue.value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function emitColorPropertyUpdate(property, red, green, blue, group) {
|
||||
var properties = {};
|
||||
|
@ -221,17 +216,17 @@ function emitColorPropertyUpdate(property, red, green, blue, group) {
|
|||
properties[group][property] = {
|
||||
red: red,
|
||||
green: green,
|
||||
blue: blue,
|
||||
blue: blue
|
||||
};
|
||||
} else {
|
||||
properties[property] = {
|
||||
red: red,
|
||||
green: green,
|
||||
blue: blue,
|
||||
blue: blue
|
||||
};
|
||||
}
|
||||
updateProperties(properties);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function createEmitGroupColorPropertyUpdateFunction(group, property, elRed, elGreen, elBlue) {
|
||||
|
@ -241,11 +236,11 @@ function createEmitGroupColorPropertyUpdateFunction(group, property, elRed, elGr
|
|||
properties[group][property] = {
|
||||
red: elRed.value,
|
||||
green: elGreen.value,
|
||||
blue: elBlue.value,
|
||||
blue: elBlue.value
|
||||
};
|
||||
updateProperties(properties);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function updateCheckedSubProperty(propertyName, propertyValue, subPropertyElement, subPropertyString) {
|
||||
if (subPropertyElement.checked) {
|
||||
|
@ -264,12 +259,12 @@ function setUserDataFromEditor(noUpdate) {
|
|||
try {
|
||||
json = editor.get();
|
||||
} catch (e) {
|
||||
alert('Invalid JSON code - look for red X in your code ', +e)
|
||||
alert('Invalid JSON code - look for red X in your code ', +e);
|
||||
}
|
||||
if (json === null) {
|
||||
return;
|
||||
} else {
|
||||
var text = editor.getText()
|
||||
var text = editor.getText();
|
||||
if (noUpdate === true) {
|
||||
EventBridge.emitWebEvent(
|
||||
JSON.stringify({
|
||||
|
@ -277,7 +272,7 @@ function setUserDataFromEditor(noUpdate) {
|
|||
type: "saveUserData",
|
||||
properties: {
|
||||
userData: text
|
||||
},
|
||||
}
|
||||
})
|
||||
);
|
||||
return;
|
||||
|
@ -297,10 +292,12 @@ function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults) {
|
|||
} else {
|
||||
parsedData = JSON.parse(userDataElement.value);
|
||||
}
|
||||
} catch (e) {}
|
||||
} catch (e) {
|
||||
// TODO: Should an alert go here?
|
||||
}
|
||||
|
||||
if (!(groupName in parsedData)) {
|
||||
parsedData[groupName] = {}
|
||||
parsedData[groupName] = {};
|
||||
}
|
||||
var keys = Object.keys(updateKeyPair);
|
||||
keys.forEach(function (key) {
|
||||
|
@ -322,16 +319,16 @@ function multiDataUpdater(groupName, updateKeyPair, userDataElement, defaults) {
|
|||
}
|
||||
}
|
||||
});
|
||||
if (Object.keys(parsedData[groupName]).length == 0) {
|
||||
if (Object.keys(parsedData[groupName]).length === 0) {
|
||||
delete parsedData[groupName];
|
||||
}
|
||||
if (Object.keys(parsedData).length > 0) {
|
||||
properties['userData'] = JSON.stringify(parsedData);
|
||||
properties.userData = JSON.stringify(parsedData);
|
||||
} else {
|
||||
properties['userData'] = '';
|
||||
properties.userData = '';
|
||||
}
|
||||
|
||||
userDataElement.value = properties['userData'];
|
||||
userDataElement.value = properties.userData;
|
||||
|
||||
updateProperties(properties);
|
||||
}
|
||||
|
@ -340,13 +337,12 @@ function userDataChanger(groupName, keyName, values, userDataElement, defaultVal
|
|||
val[keyName] = values;
|
||||
def[keyName] = defaultValue;
|
||||
multiDataUpdater(groupName, val, userDataElement, def);
|
||||
};
|
||||
}
|
||||
|
||||
function setTextareaScrolling(element) {
|
||||
var isScrolling = element.scrollHeight > element.offsetHeight;
|
||||
element.setAttribute("scrolling", isScrolling ? "true" : "false");
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
var editor = null;
|
||||
|
@ -364,7 +360,7 @@ function createJSONEditor() {
|
|||
$('.jsoneditor-poweredBy').remove();
|
||||
},
|
||||
onError: function(e) {
|
||||
alert('JSON editor:' + e)
|
||||
alert('JSON editor:' + e);
|
||||
},
|
||||
onChange: function() {
|
||||
var currentJSONString = editor.getText();
|
||||
|
@ -372,22 +368,22 @@ function createJSONEditor() {
|
|||
if (currentJSONString === '{"":""}') {
|
||||
return;
|
||||
}
|
||||
$('#userdata-save').attr('disabled', false)
|
||||
$('#userdata-save').attr('disabled', false);
|
||||
|
||||
|
||||
}
|
||||
};
|
||||
editor = new JSONEditor(container, options);
|
||||
};
|
||||
}
|
||||
|
||||
function hideNewJSONEditorButton() {
|
||||
$('#userdata-new-editor').hide();
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
function hideClearUserDataButton() {
|
||||
$('#userdata-clear').hide();
|
||||
};
|
||||
}
|
||||
|
||||
function showSaveUserDataButton() {
|
||||
$('#userdata-save').show();
|
||||
|
@ -401,65 +397,65 @@ function hideSaveUserDataButton() {
|
|||
function showNewJSONEditorButton() {
|
||||
$('#userdata-new-editor').show();
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
function showClearUserDataButton() {
|
||||
$('#userdata-clear').show();
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
function showUserDataTextArea() {
|
||||
$('#property-user-data').show();
|
||||
};
|
||||
}
|
||||
|
||||
function hideUserDataTextArea() {
|
||||
$('#property-user-data').hide();
|
||||
};
|
||||
}
|
||||
|
||||
function showStaticUserData() {
|
||||
if (editor !== null) {
|
||||
$('#static-userdata').show();
|
||||
$('#static-userdata').css('height', $('#userdata-editor').height())
|
||||
$('#static-userdata').css('height', $('#userdata-editor').height());
|
||||
$('#static-userdata').text(editor.getText());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function removeStaticUserData() {
|
||||
$('#static-userdata').hide();
|
||||
};
|
||||
}
|
||||
|
||||
function setEditorJSON(json) {
|
||||
editor.set(json)
|
||||
editor.set(json);
|
||||
if (editor.hasOwnProperty('expandAll')) {
|
||||
editor.expandAll();
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
function getEditorJSON() {
|
||||
return editor.get();
|
||||
};
|
||||
}
|
||||
|
||||
function deleteJSONEditor() {
|
||||
if (editor !== null) {
|
||||
editor.destroy();
|
||||
editor = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var savedJSONTimer = null;
|
||||
|
||||
function saveJSONUserData(noUpdate) {
|
||||
setUserDataFromEditor(noUpdate);
|
||||
$('#userdata-saved').show();
|
||||
$('#userdata-save').attr('disabled', true)
|
||||
$('#userdata-save').attr('disabled', true);
|
||||
if (savedJSONTimer !== null) {
|
||||
clearTimeout(savedJSONTimer);
|
||||
}
|
||||
savedJSONTimer = setTimeout(function() {
|
||||
$('#userdata-saved').hide();
|
||||
|
||||
}, 1500)
|
||||
}, EDITOR_TIMEOUT_DURATION);
|
||||
}
|
||||
|
||||
function bindAllNonJSONEditorElements() {
|
||||
|
@ -468,6 +464,8 @@ function bindAllNonJSONEditorElements() {
|
|||
for (i = 0; i < inputs.length; i++) {
|
||||
var input = inputs[i];
|
||||
var field = $(input);
|
||||
// TODO FIXME: (JSHint) Functions declared within loops referencing
|
||||
// an outer scoped variable may lead to confusing semantics.
|
||||
field.on('focus', function(e) {
|
||||
if (e.target.id === "userdata-new-editor" || e.target.id === "userdata-clear") {
|
||||
return;
|
||||
|
@ -477,7 +475,7 @@ function bindAllNonJSONEditorElements() {
|
|||
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -503,7 +501,6 @@ function clearSelection() {
|
|||
function loaded() {
|
||||
openEventBridge(function() {
|
||||
|
||||
var allSections = [];
|
||||
var elPropertiesList = document.getElementById("properties-list");
|
||||
var elID = document.getElementById("property-id");
|
||||
var elType = document.getElementById("property-type");
|
||||
|
@ -589,21 +586,14 @@ function loaded() {
|
|||
var elUserData = document.getElementById("property-user-data");
|
||||
var elClearUserData = document.getElementById("userdata-clear");
|
||||
var elSaveUserData = document.getElementById("userdata-save");
|
||||
var elJSONEditor = document.getElementById("userdata-editor");
|
||||
var elNewJSONEditor = document.getElementById('userdata-new-editor');
|
||||
var elColorSections = document.querySelectorAll(".color-section");
|
||||
var elColorControl1 = document.getElementById("property-color-control1");
|
||||
var elColorControl2 = document.getElementById("property-color-control2");
|
||||
var elColorControlVariant2 = document.getElementById("property-color-control2");
|
||||
var elColorRed = document.getElementById("property-color-red");
|
||||
var elColorGreen = document.getElementById("property-color-green");
|
||||
var elColorBlue = document.getElementById("property-color-blue");
|
||||
|
||||
var elShapeSections = document.querySelectorAll(".shape-section");
|
||||
allSections.push(elShapeSections);
|
||||
var elShape = document.getElementById("property-shape");
|
||||
|
||||
var elLightSections = document.querySelectorAll(".light-section");
|
||||
allSections.push(elLightSections);
|
||||
var elLightSpotLight = document.getElementById("property-light-spot-light");
|
||||
var elLightColor = document.getElementById("property-light-color");
|
||||
var elLightColorRed = document.getElementById("property-light-color-red");
|
||||
|
@ -615,8 +605,6 @@ function loaded() {
|
|||
var elLightExponent = document.getElementById("property-light-exponent");
|
||||
var elLightCutoff = document.getElementById("property-light-cutoff");
|
||||
|
||||
var elModelSections = document.querySelectorAll(".model-section");
|
||||
allSections.push(elModelSections);
|
||||
var elModelURL = document.getElementById("property-model-url");
|
||||
var elShapeType = document.getElementById("property-shape-type");
|
||||
var elCompoundShapeURL = document.getElementById("property-compound-shape-url");
|
||||
|
@ -632,8 +620,6 @@ function loaded() {
|
|||
var elModelTextures = document.getElementById("property-model-textures");
|
||||
var elModelOriginalTextures = document.getElementById("property-model-original-textures");
|
||||
|
||||
var elWebSections = document.querySelectorAll(".web-section");
|
||||
allSections.push(elWebSections);
|
||||
var elWebSourceURL = document.getElementById("property-web-source-url");
|
||||
var elWebDPI = document.getElementById("property-web-dpi");
|
||||
|
||||
|
@ -641,11 +627,7 @@ function loaded() {
|
|||
|
||||
var elHyperlinkHref = document.getElementById("property-hyperlink-href");
|
||||
|
||||
var elHyperlinkSections = document.querySelectorAll(".hyperlink-section");
|
||||
|
||||
|
||||
var elTextSections = document.querySelectorAll(".text-section");
|
||||
allSections.push(elTextSections);
|
||||
var elTextText = document.getElementById("property-text-text");
|
||||
var elTextLineHeight = document.getElementById("property-text-line-height");
|
||||
var elTextTextColor = document.getElementById("property-text-text-color");
|
||||
|
@ -653,13 +635,10 @@ function loaded() {
|
|||
var elTextTextColorRed = document.getElementById("property-text-text-color-red");
|
||||
var elTextTextColorGreen = document.getElementById("property-text-text-color-green");
|
||||
var elTextTextColorBlue = document.getElementById("property-text-text-color-blue");
|
||||
var elTextBackgroundColor = document.getElementById("property-text-background-color");
|
||||
var elTextBackgroundColorRed = document.getElementById("property-text-background-color-red");
|
||||
var elTextBackgroundColorGreen = document.getElementById("property-text-background-color-green");
|
||||
var elTextBackgroundColorBlue = document.getElementById("property-text-background-color-blue");
|
||||
|
||||
var elZoneSections = document.querySelectorAll(".zone-section");
|
||||
allSections.push(elZoneSections);
|
||||
var elZoneStageSunModelEnabled = document.getElementById("property-zone-stage-sun-model-enabled");
|
||||
|
||||
var elZoneKeyLightColor = document.getElementById("property-zone-key-light-color");
|
||||
|
@ -670,7 +649,6 @@ function loaded() {
|
|||
var elZoneKeyLightAmbientIntensity = document.getElementById("property-zone-key-ambient-intensity");
|
||||
var elZoneKeyLightDirectionX = document.getElementById("property-zone-key-light-direction-x");
|
||||
var elZoneKeyLightDirectionY = document.getElementById("property-zone-key-light-direction-y");
|
||||
var elZoneKeyLightDirectionZ = document.getElementById("property-zone-key-light-direction-z");
|
||||
var elZoneKeyLightAmbientURL = document.getElementById("property-zone-key-ambient-url");
|
||||
|
||||
var elZoneHazeModeInherit = document.getElementById("property-zone-haze-mode-inherit");
|
||||
|
@ -695,10 +673,6 @@ function loaded() {
|
|||
|
||||
var elZoneHazeBackgroundBlend = document.getElementById("property-zone-haze-background-blend");
|
||||
|
||||
var elZoneHazeAttenuateKeyLight = document.getElementById("property-zone-haze-attenuate-keylight");
|
||||
var elZoneHazeKeyLightRange = document.getElementById("property-zone-haze-keylight-range");
|
||||
var elZoneHazeKeyLightAltitude = document.getElementById("property-zone-haze-keylight-altitude");
|
||||
|
||||
var elZoneStageLatitude = document.getElementById("property-zone-stage-latitude");
|
||||
var elZoneStageLongitude = document.getElementById("property-zone-stage-longitude");
|
||||
var elZoneStageAltitude = document.getElementById("property-zone-stage-altitude");
|
||||
|
@ -718,8 +692,6 @@ function loaded() {
|
|||
var elZoneGhostingAllowed = document.getElementById("property-zone-ghosting-allowed");
|
||||
var elZoneFilterURL = document.getElementById("property-zone-filter-url");
|
||||
|
||||
var elPolyVoxSections = document.querySelectorAll(".poly-vox-section");
|
||||
allSections.push(elPolyVoxSections);
|
||||
var elVoxelVolumeSizeX = document.getElementById("property-voxel-volume-size-x");
|
||||
var elVoxelVolumeSizeY = document.getElementById("property-voxel-volume-size-y");
|
||||
var elVoxelVolumeSizeZ = document.getElementById("property-voxel-volume-size-z");
|
||||
|
@ -732,7 +704,7 @@ function loaded() {
|
|||
var properties;
|
||||
EventBridge.scriptEventReceived.connect(function(data) {
|
||||
data = JSON.parse(data);
|
||||
if (data.type == "server_script_status") {
|
||||
if (data.type === "server_script_status") {
|
||||
elServerScriptError.value = data.errorInfo;
|
||||
// If we just set elServerScriptError's diplay to block or none, we still end up with
|
||||
// it's parent contributing 21px bottom padding even when elServerScriptError is display:none.
|
||||
|
@ -744,18 +716,18 @@ function loaded() {
|
|||
var ENTITY_SCRIPT_STATUS = {
|
||||
pending: "Pending",
|
||||
loading: "Loading",
|
||||
error_loading_script: "Error loading script",
|
||||
error_running_script: "Error running script",
|
||||
error_loading_script: "Error loading script", // eslint-disable-line camelcase
|
||||
error_running_script: "Error running script", // eslint-disable-line camelcase
|
||||
running: "Running",
|
||||
unloaded: "Unloaded",
|
||||
unloaded: "Unloaded"
|
||||
};
|
||||
elServerScriptStatus.innerText = ENTITY_SCRIPT_STATUS[data.status] || data.status;
|
||||
} else {
|
||||
elServerScriptStatus.innerText = "Not running";
|
||||
}
|
||||
} else if (data.type == "update") {
|
||||
} else if (data.type === "update") {
|
||||
|
||||
if (!data.selections || data.selections.length == 0) {
|
||||
if (!data.selections || data.selections.length === 0) {
|
||||
if (editor !== null && lastEntityID !== null) {
|
||||
saveJSONUserData(true);
|
||||
deleteJSONEditor();
|
||||
|
@ -775,20 +747,19 @@ function loaded() {
|
|||
|
||||
for (var i = 0; i < selections.length; i++) {
|
||||
ids.push(selections[i].id);
|
||||
var type = selections[i].properties.type;
|
||||
if (types[type] === undefined) {
|
||||
types[type] = 0;
|
||||
var currentSelectedType = selections[i].properties.type;
|
||||
if (types[currentSelectedType] === undefined) {
|
||||
types[currentSelectedType] = 0;
|
||||
numTypes += 1;
|
||||
}
|
||||
types[type]++;
|
||||
types[currentSelectedType]++;
|
||||
}
|
||||
|
||||
var type;
|
||||
var type = "Multiple";
|
||||
if (numTypes === 1) {
|
||||
type = selections[0].properties.type;
|
||||
} else {
|
||||
type = "Multiple";
|
||||
}
|
||||
|
||||
elType.innerHTML = type + " (" + data.selections.length + ")";
|
||||
elTypeIcon.innerHTML = ICON_FOR_TYPE[type];
|
||||
elTypeIcon.style.display = "inline-block";
|
||||
|
@ -809,7 +780,9 @@ function loaded() {
|
|||
lastEntityID = '"' + properties.id + '"';
|
||||
elID.value = properties.id;
|
||||
|
||||
// Create class name for css ruleset filtering
|
||||
elPropertiesList.className = properties.type + 'Menu';
|
||||
|
||||
elType.innerHTML = properties.type;
|
||||
elTypeIcon.innerHTML = ICON_FOR_TYPE[properties.type];
|
||||
elTypeIcon.style.display = "inline-block";
|
||||
|
@ -883,13 +856,13 @@ function loaded() {
|
|||
elCloneableLifetime.value = 300;
|
||||
|
||||
var grabbablesSet = false;
|
||||
var parsedUserData = {}
|
||||
var parsedUserData = {};
|
||||
try {
|
||||
parsedUserData = JSON.parse(properties.userData);
|
||||
|
||||
if ("grabbableKey" in parsedUserData) {
|
||||
grabbablesSet = true;
|
||||
var grabbableData = parsedUserData["grabbableKey"];
|
||||
var grabbableData = parsedUserData.grabbableKey;
|
||||
if ("grabbable" in grabbableData) {
|
||||
elGrabbable.checked = grabbableData.grabbable;
|
||||
} else {
|
||||
|
@ -928,6 +901,7 @@ function loaded() {
|
|||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// TODO: What should go here?
|
||||
}
|
||||
if (!grabbablesSet) {
|
||||
elGrabbable.checked = true;
|
||||
|
@ -968,19 +942,21 @@ function loaded() {
|
|||
elDescription.value = properties.description;
|
||||
|
||||
|
||||
if (properties.type == "Shape" || properties.type == "Box" || properties.type == "Sphere") {
|
||||
if (properties.type === "Shape" || properties.type === "Box" || properties.type === "Sphere") {
|
||||
elShape.value = properties.shape;
|
||||
setDropdownText(elShape);
|
||||
}
|
||||
|
||||
if (properties.type == "Shape" || properties.type == "Box" || properties.type == "Sphere" || properties.type == "ParticleEffect") {
|
||||
if (properties.type === "Shape" || properties.type === "Box" ||
|
||||
properties.type === "Sphere" || properties.type === "ParticleEffect") {
|
||||
elColorRed.value = properties.color.red;
|
||||
elColorGreen.value = properties.color.green;
|
||||
elColorBlue.value = properties.color.blue;
|
||||
elColorControl1.style.backgroundColor = elColorControl2.style.backgroundColor = "rgb(" + properties.color.red + "," + properties.color.green + "," + properties.color.blue + ")";
|
||||
elColorControlVariant2.style.backgroundColor = "rgb(" + properties.color.red + "," +
|
||||
properties.color.green + "," + properties.color.blue + ")";
|
||||
}
|
||||
|
||||
if (properties.type == "Model") {
|
||||
if (properties.type === "Model") {
|
||||
elModelURL.value = properties.modelURL;
|
||||
elShapeType.value = properties.shapeType;
|
||||
setDropdownText(elShapeType);
|
||||
|
@ -998,24 +974,26 @@ function loaded() {
|
|||
setTextareaScrolling(elModelTextures);
|
||||
elModelOriginalTextures.value = properties.originalTextures;
|
||||
setTextareaScrolling(elModelOriginalTextures);
|
||||
} else if (properties.type == "Web") {
|
||||
} else if (properties.type === "Web") {
|
||||
elWebSourceURL.value = properties.sourceUrl;
|
||||
elWebDPI.value = properties.dpi;
|
||||
} else if (properties.type == "Text") {
|
||||
} else if (properties.type === "Text") {
|
||||
elTextText.value = properties.text;
|
||||
elTextLineHeight.value = properties.lineHeight.toFixed(4);
|
||||
elTextFaceCamera.checked = properties.faceCamera;
|
||||
elTextTextColor.style.backgroundColor = "rgb(" + properties.textColor.red + "," + properties.textColor.green + "," + properties.textColor.blue + ")";
|
||||
elTextTextColor.style.backgroundColor = "rgb(" + properties.textColor.red + "," +
|
||||
properties.textColor.green + "," + properties.textColor.blue + ")";
|
||||
elTextTextColorRed.value = properties.textColor.red;
|
||||
elTextTextColorGreen.value = properties.textColor.green;
|
||||
elTextTextColorBlue.value = properties.textColor.blue;
|
||||
elTextBackgroundColorRed.value = properties.backgroundColor.red;
|
||||
elTextBackgroundColorGreen.value = properties.backgroundColor.green;
|
||||
elTextBackgroundColorBlue.value = properties.backgroundColor.blue;
|
||||
} else if (properties.type == "Light") {
|
||||
} else if (properties.type === "Light") {
|
||||
elLightSpotLight.checked = properties.isSpotlight;
|
||||
|
||||
elLightColor.style.backgroundColor = "rgb(" + properties.color.red + "," + properties.color.green + "," + properties.color.blue + ")";
|
||||
elLightColor.style.backgroundColor = "rgb(" + properties.color.red + "," +
|
||||
properties.color.green + "," + properties.color.blue + ")";
|
||||
elLightColorRed.value = properties.color.red;
|
||||
elLightColorGreen.value = properties.color.green;
|
||||
elLightColorBlue.value = properties.color.blue;
|
||||
|
@ -1024,9 +1002,10 @@ function loaded() {
|
|||
elLightFalloffRadius.value = properties.falloffRadius.toFixed(1);
|
||||
elLightExponent.value = properties.exponent.toFixed(2);
|
||||
elLightCutoff.value = properties.cutoff.toFixed(2);
|
||||
} else if (properties.type == "Zone") {
|
||||
} else if (properties.type === "Zone") {
|
||||
elZoneStageSunModelEnabled.checked = properties.stage.sunModelEnabled;
|
||||
elZoneKeyLightColor.style.backgroundColor = "rgb(" + properties.keyLight.color.red + "," + properties.keyLight.color.green + "," + properties.keyLight.color.blue + ")";
|
||||
elZoneKeyLightColor.style.backgroundColor = "rgb(" + properties.keyLight.color.red + "," +
|
||||
properties.keyLight.color.green + "," + properties.keyLight.color.blue + ")";
|
||||
elZoneKeyLightColorRed.value = properties.keyLight.color.red;
|
||||
elZoneKeyLightColorGreen.value = properties.keyLight.color.green;
|
||||
elZoneKeyLightColorBlue.value = properties.keyLight.color.blue;
|
||||
|
@ -1036,9 +1015,9 @@ function loaded() {
|
|||
elZoneKeyLightDirectionY.value = properties.keyLight.direction.y.toFixed(2);
|
||||
elZoneKeyLightAmbientURL.value = properties.keyLight.ambientURL;
|
||||
|
||||
elZoneHazeModeInherit.checked = (properties.hazeMode == 'inherit');
|
||||
elZoneHazeModeDisabled.checked = (properties.hazeMode == 'disabled');
|
||||
elZoneHazeModeEnabled.checked = (properties.hazeMode == 'enabled');
|
||||
elZoneHazeModeInherit.checked = (properties.hazeMode === 'inherit');
|
||||
elZoneHazeModeDisabled.checked = (properties.hazeMode === 'disabled');
|
||||
elZoneHazeModeEnabled.checked = (properties.hazeMode === 'enabled');
|
||||
|
||||
elZoneHazeRange.value = properties.haze.hazeRange.toFixed(0);
|
||||
elZoneHazeColor.style.backgroundColor = "rgb(" +
|
||||
|
@ -1069,10 +1048,6 @@ function loaded() {
|
|||
|
||||
elZoneHazeBackgroundBlend.value = properties.haze.hazeBackgroundBlend.toFixed(2);
|
||||
|
||||
// elZoneHazeAttenuateKeyLight.checked = properties.haze.hazeAttenuateKeyLight;
|
||||
// elZoneHazeKeyLightRange.value = properties.haze.hazeKeyLightRange.toFixed(0);
|
||||
// elZoneHazeKeyLightAltitude.value = properties.haze.hazeKeyLightAltitude.toFixed(0);
|
||||
|
||||
elZoneStageLatitude.value = properties.stage.latitude.toFixed(2);
|
||||
elZoneStageLongitude.value = properties.stage.longitude.toFixed(2);
|
||||
elZoneStageAltitude.value = properties.stage.altitude.toFixed(2);
|
||||
|
@ -1085,7 +1060,8 @@ function loaded() {
|
|||
elZoneBackgroundMode.value = properties.backgroundMode;
|
||||
setDropdownText(elZoneBackgroundMode);
|
||||
|
||||
elZoneSkyboxColor.style.backgroundColor = "rgb(" + properties.skybox.color.red + "," + properties.skybox.color.green + "," + properties.skybox.color.blue + ")";
|
||||
elZoneSkyboxColor.style.backgroundColor = "rgb(" + properties.skybox.color.red + "," +
|
||||
properties.skybox.color.green + "," + properties.skybox.color.blue + ")";
|
||||
elZoneSkyboxColorRed.value = properties.skybox.color.red;
|
||||
elZoneSkyboxColorGreen.value = properties.skybox.color.green;
|
||||
elZoneSkyboxColorBlue.value = properties.skybox.color.blue;
|
||||
|
@ -1095,8 +1071,9 @@ function loaded() {
|
|||
elZoneGhostingAllowed.checked = properties.ghostingAllowed;
|
||||
elZoneFilterURL.value = properties.filterURL;
|
||||
|
||||
showElements(document.getElementsByClassName('skybox-section'), elZoneBackgroundMode.value == 'skybox');
|
||||
} else if (properties.type == "PolyVox") {
|
||||
showElements(document.getElementsByClassName('skybox-section'),
|
||||
elZoneBackgroundMode.value === 'skybox');
|
||||
} else if (properties.type === "PolyVox") {
|
||||
elVoxelVolumeSizeX.value = properties.voxelVolumeSize.x.toFixed(2);
|
||||
elVoxelVolumeSizeY.value = properties.voxelVolumeSize.y.toFixed(2);
|
||||
elVoxelVolumeSizeZ.value = properties.voxelVolumeSize.z.toFixed(2);
|
||||
|
@ -1250,7 +1227,8 @@ function loaded() {
|
|||
});
|
||||
|
||||
var numberListener = function (event) {
|
||||
userDataChanger("grabbableKey", event.target.getAttribute("data-user-data-type"), parseInt(event.target.value), elUserData, false);
|
||||
userDataChanger("grabbableKey",
|
||||
event.target.getAttribute("data-user-data-type"), parseInt(event.target.value), elUserData, false);
|
||||
};
|
||||
elCloneableLifetime.addEventListener('change', numberListener);
|
||||
elCloneableLimit.addEventListener('change', numberListener);
|
||||
|
@ -1279,7 +1257,7 @@ function loaded() {
|
|||
showUserDataTextArea();
|
||||
showNewJSONEditorButton();
|
||||
hideSaveUserDataButton();
|
||||
updateProperty('userData', elUserData.value)
|
||||
updateProperty('userData', elUserData.value);
|
||||
});
|
||||
|
||||
elSaveUserData.addEventListener("click", function() {
|
||||
|
@ -1303,24 +1281,6 @@ function loaded() {
|
|||
elColorRed.addEventListener('change', colorChangeFunction);
|
||||
elColorGreen.addEventListener('change', colorChangeFunction);
|
||||
elColorBlue.addEventListener('change', colorChangeFunction);
|
||||
colorPickers.push($('#property-color-control1').colpick({
|
||||
colorScheme: 'dark',
|
||||
layout: 'hex',
|
||||
color: '000000',
|
||||
onShow: function(colpick) {
|
||||
$('#property-color-control1').attr('active', 'true');
|
||||
},
|
||||
onHide: function(colpick) {
|
||||
$('#property-color-control1').attr('active', 'false');
|
||||
},
|
||||
onSubmit: function(hsb, hex, rgb, el) {
|
||||
$(el).css('background-color', '#' + hex);
|
||||
$(el).colpickHide();
|
||||
emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b);
|
||||
// Keep the companion control in sync
|
||||
elColorControl2.style.backgroundColor = "rgb(" + rgb.r + "," + rgb.g + "," + rgb.b + ")";
|
||||
}
|
||||
}));
|
||||
colorPickers.push($('#property-color-control2').colpick({
|
||||
colorScheme: 'dark',
|
||||
layout: 'hex',
|
||||
|
@ -1335,9 +1295,6 @@ function loaded() {
|
|||
$(el).css('background-color', '#' + hex);
|
||||
$(el).colpickHide();
|
||||
emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b);
|
||||
// Keep the companion control in sync
|
||||
elColorControl1.style.backgroundColor = "rgb(" + rgb.r + "," + rgb.g + "," + rgb.b + ")";
|
||||
|
||||
}
|
||||
}));
|
||||
|
||||
|
@ -1382,12 +1339,16 @@ function loaded() {
|
|||
elModelAnimationURL.addEventListener('change', createEmitGroupTextPropertyUpdateFunction('animation', 'url'));
|
||||
elModelAnimationPlaying.addEventListener('change',createEmitGroupCheckedPropertyUpdateFunction('animation', 'running'));
|
||||
elModelAnimationFPS.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('animation', 'fps'));
|
||||
elModelAnimationFrame.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('animation', 'currentFrame'));
|
||||
elModelAnimationFirstFrame.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('animation', 'firstFrame'));
|
||||
elModelAnimationLastFrame.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('animation', 'lastFrame'));
|
||||
elModelAnimationFrame.addEventListener('change',
|
||||
createEmitGroupNumberPropertyUpdateFunction('animation', 'currentFrame'));
|
||||
elModelAnimationFirstFrame.addEventListener('change',
|
||||
createEmitGroupNumberPropertyUpdateFunction('animation', 'firstFrame'));
|
||||
elModelAnimationLastFrame.addEventListener('change',
|
||||
createEmitGroupNumberPropertyUpdateFunction('animation', 'lastFrame'));
|
||||
elModelAnimationLoop.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('animation', 'loop'));
|
||||
elModelAnimationHold.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('animation', 'hold'));
|
||||
elModelAnimationAllowTranslation.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('animation', 'allowTranslation'));
|
||||
elModelAnimationAllowTranslation.addEventListener('change',
|
||||
createEmitGroupCheckedPropertyUpdateFunction('animation', 'allowTranslation'));
|
||||
|
||||
elModelTextures.addEventListener('change', createEmitTextPropertyUpdateFunction('textures'));
|
||||
|
||||
|
@ -1439,7 +1400,8 @@ function loaded() {
|
|||
}
|
||||
}));
|
||||
|
||||
elZoneStageSunModelEnabled.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('stage', 'sunModelEnabled'));
|
||||
elZoneStageSunModelEnabled.addEventListener('change',
|
||||
createEmitGroupCheckedPropertyUpdateFunction('stage', 'sunModelEnabled'));
|
||||
colorPickers.push($('#property-zone-key-light-color').colpick({
|
||||
colorScheme: 'dark',
|
||||
layout: 'hex',
|
||||
|
@ -1456,18 +1418,26 @@ function loaded() {
|
|||
emitColorPropertyUpdate('color', rgb.r, rgb.g, rgb.b, 'keyLight');
|
||||
}
|
||||
}));
|
||||
var zoneKeyLightColorChangeFunction = createEmitGroupColorPropertyUpdateFunction('keyLight', 'color', elZoneKeyLightColorRed, elZoneKeyLightColorGreen, elZoneKeyLightColorBlue);
|
||||
var zoneKeyLightColorChangeFunction = createEmitGroupColorPropertyUpdateFunction('keyLight', 'color',
|
||||
elZoneKeyLightColorRed, elZoneKeyLightColorGreen, elZoneKeyLightColorBlue);
|
||||
elZoneKeyLightColorRed.addEventListener('change', zoneKeyLightColorChangeFunction);
|
||||
elZoneKeyLightColorGreen.addEventListener('change', zoneKeyLightColorChangeFunction);
|
||||
elZoneKeyLightColorBlue.addEventListener('change', zoneKeyLightColorChangeFunction);
|
||||
elZoneKeyLightIntensity.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('keyLight', 'intensity'));
|
||||
elZoneKeyLightAmbientIntensity.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('keyLight', 'ambientIntensity'));
|
||||
elZoneKeyLightAmbientURL.addEventListener('change', createEmitGroupTextPropertyUpdateFunction('keyLight', 'ambientURL'));
|
||||
var zoneKeyLightDirectionChangeFunction = createEmitGroupVec3PropertyUpdateFunction('keyLight', 'direction', elZoneKeyLightDirectionX, elZoneKeyLightDirectionY);
|
||||
elZoneKeyLightIntensity.addEventListener('change',
|
||||
createEmitGroupNumberPropertyUpdateFunction('keyLight', 'intensity'));
|
||||
elZoneKeyLightAmbientIntensity.addEventListener('change',
|
||||
createEmitGroupNumberPropertyUpdateFunction('keyLight', 'ambientIntensity'));
|
||||
elZoneKeyLightAmbientURL.addEventListener('change',
|
||||
createEmitGroupTextPropertyUpdateFunction('keyLight', 'ambientURL'));
|
||||
var zoneKeyLightDirectionChangeFunction =
|
||||
createEmitGroupVec3PropertyUpdateFunction('keyLight', 'direction',
|
||||
elZoneKeyLightDirectionX, elZoneKeyLightDirectionY);
|
||||
elZoneKeyLightDirectionX.addEventListener('change', zoneKeyLightDirectionChangeFunction);
|
||||
elZoneKeyLightDirectionY.addEventListener('change', zoneKeyLightDirectionChangeFunction);
|
||||
|
||||
var hazeModeChanged = createZoneComponentModeChangedFunction('hazeMode', elZoneHazeModeInherit, elZoneHazeModeDisabled, elZoneHazeModeEnabled)
|
||||
var hazeModeChanged =
|
||||
createZoneComponentModeChangedFunction('hazeMode', elZoneHazeModeInherit,
|
||||
elZoneHazeModeDisabled, elZoneHazeModeEnabled);
|
||||
elZoneHazeModeInherit.addEventListener('change', hazeModeChanged);
|
||||
elZoneHazeModeDisabled.addEventListener('change', hazeModeChanged);
|
||||
elZoneHazeModeEnabled.addEventListener('change', hazeModeChanged);
|
||||
|
@ -1524,23 +1494,23 @@ function loaded() {
|
|||
elZoneHazeGlareColorGreen.addEventListener('change', zoneHazeGlareColorChangeFunction);
|
||||
elZoneHazeGlareColorBlue.addEventListener('change', zoneHazeGlareColorChangeFunction);
|
||||
|
||||
elZoneHazeEnableGlare.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('haze', 'hazeEnableGlare'));
|
||||
elZoneHazeEnableGlare.addEventListener('change',
|
||||
createEmitGroupCheckedPropertyUpdateFunction('haze', 'hazeEnableGlare'));
|
||||
elZonehazeGlareAngle.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeGlareAngle'));
|
||||
|
||||
elZoneHazeAltitudeEffect.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('haze', 'hazeAltitudeEffect'));
|
||||
elZoneHazeAltitudeEffect.addEventListener('change',
|
||||
createEmitGroupCheckedPropertyUpdateFunction('haze', 'hazeAltitudeEffect'));
|
||||
elZoneHazeCeiling.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeCeiling'));
|
||||
elZoneHazeBaseRef.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeBaseRef'));
|
||||
|
||||
elZoneHazeBackgroundBlend.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeBackgroundBlend'));
|
||||
|
||||
// elZoneHazeAttenuateKeyLight.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('haze', 'hazeAttenuateKeyLight'));
|
||||
// elZoneHazeKeyLightRange.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeKeyLightRange'));
|
||||
// elZoneHazeKeyLightAltitude.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeKeyLightAltitude'));
|
||||
elZoneHazeBackgroundBlend.addEventListener('change',
|
||||
createEmitGroupNumberPropertyUpdateFunction('haze', 'hazeBackgroundBlend'));
|
||||
|
||||
elZoneStageLatitude.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('stage', 'latitude'));
|
||||
elZoneStageLongitude.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('stage', 'longitude'));
|
||||
elZoneStageAltitude.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('stage', 'altitude'));
|
||||
elZoneStageAutomaticHourDay.addEventListener('change', createEmitGroupCheckedPropertyUpdateFunction('stage', 'automaticHourDay'));
|
||||
elZoneStageAutomaticHourDay.addEventListener('change',
|
||||
createEmitGroupCheckedPropertyUpdateFunction('stage', 'automaticHourDay'));
|
||||
elZoneStageDay.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('stage', 'day'));
|
||||
elZoneStageHour.addEventListener('change', createEmitGroupNumberPropertyUpdateFunction('stage', 'hour'));
|
||||
|
||||
|
@ -1587,26 +1557,26 @@ function loaded() {
|
|||
elMoveSelectionToGrid.addEventListener("click", function() {
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "action",
|
||||
action: "moveSelectionToGrid",
|
||||
action: "moveSelectionToGrid"
|
||||
}));
|
||||
});
|
||||
elMoveAllToGrid.addEventListener("click", function() {
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "action",
|
||||
action: "moveAllToGrid",
|
||||
action: "moveAllToGrid"
|
||||
}));
|
||||
});
|
||||
elResetToNaturalDimensions.addEventListener("click", function() {
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "action",
|
||||
action: "resetToNaturalDimensions",
|
||||
action: "resetToNaturalDimensions"
|
||||
}));
|
||||
});
|
||||
elRescaleDimensionsButton.addEventListener("click", function() {
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "action",
|
||||
action: "rescaleDimensions",
|
||||
percentage: parseFloat(elRescaleDimensionsPct.value),
|
||||
percentage: parseFloat(elRescaleDimensionsPct.value)
|
||||
}));
|
||||
});
|
||||
elReloadScriptsButton.addEventListener("click", function() {
|
||||
|
@ -1638,7 +1608,7 @@ function loaded() {
|
|||
var ev = document.createEvent("HTMLEvents");
|
||||
ev.initEvent("change", true, true);
|
||||
document.activeElement.dispatchEvent(ev);
|
||||
}
|
||||
};
|
||||
|
||||
// For input and textarea elements, select all of the text on focus
|
||||
// WebKit-based browsers, such as is used with QWebView, have a quirk
|
||||
|
@ -1653,13 +1623,17 @@ function loaded() {
|
|||
for (var i = 0; i < els.length; i++) {
|
||||
var clicked = false;
|
||||
var originalText;
|
||||
// TODO FIXME: (JSHint) Functions declared within loops referencing
|
||||
// an outer scoped variable may lead to confusing semantics.
|
||||
els[i].onfocus = function(e) {
|
||||
originalText = this.value;
|
||||
this.select();
|
||||
clicked = false;
|
||||
};
|
||||
// TODO FIXME: (JSHint) Functions declared within loops referencing
|
||||
// an outer scoped variable may lead to confusing semantics.
|
||||
els[i].onmouseup = function(e) {
|
||||
if (!clicked && originalText == this.value) {
|
||||
if (!clicked && originalText === this.value) {
|
||||
e.preventDefault();
|
||||
}
|
||||
clicked = true;
|
||||
|
@ -1674,15 +1648,15 @@ function loaded() {
|
|||
var toggleCollapsedEvent = function(event) {
|
||||
var element = event.target.parentNode.parentNode;
|
||||
var isCollapsed = element.dataset.collapsed !== "true";
|
||||
element.dataset.collapsed = isCollapsed ? "true" : false
|
||||
element.dataset.collapsed = isCollapsed ? "true" : false;
|
||||
element.setAttribute("collapsed", isCollapsed ? "true" : "false");
|
||||
element.getElementsByTagName("span")[0].textContent = isCollapsed ? "L" : "M";
|
||||
};
|
||||
|
||||
for (var i = 0, length = elCollapsible.length; i < length; i++) {
|
||||
var element = elCollapsible[i];
|
||||
element.addEventListener("click", toggleCollapsedEvent, true);
|
||||
};
|
||||
for (var collapseIndex = 0, numCollapsibles = elCollapsible.length; collapseIndex < numCollapsibles; ++collapseIndex) {
|
||||
var curCollapsibleElement = elCollapsible[collapseIndex];
|
||||
curCollapsibleElement.addEventListener("click", toggleCollapsedEvent, true);
|
||||
}
|
||||
|
||||
|
||||
// Textarea scrollbars
|
||||
|
@ -1690,17 +1664,17 @@ function loaded() {
|
|||
|
||||
var textareaOnChangeEvent = function(event) {
|
||||
setTextareaScrolling(event.target);
|
||||
}
|
||||
};
|
||||
|
||||
for (var i = 0, length = elTextareas.length; i < length; i++) {
|
||||
var element = elTextareas[i];
|
||||
setTextareaScrolling(element);
|
||||
element.addEventListener("input", textareaOnChangeEvent, false);
|
||||
element.addEventListener("change", textareaOnChangeEvent, false);
|
||||
for (var textAreaIndex = 0, numTextAreas = elTextareas.length; textAreaIndex < numTextAreas; ++textAreaIndex) {
|
||||
var curTextAreaElement = elTextareas[textAreaIndex];
|
||||
setTextareaScrolling(curTextAreaElement);
|
||||
curTextAreaElement.addEventListener("input", textareaOnChangeEvent, false);
|
||||
curTextAreaElement.addEventListener("change", textareaOnChangeEvent, false);
|
||||
/* FIXME: Detect and update textarea scrolling attribute on resize. Unfortunately textarea doesn't have a resize
|
||||
event; mouseup is a partial stand-in but doesn't handle resizing if mouse moves outside textarea rectangle. */
|
||||
element.addEventListener("mouseup", textareaOnChangeEvent, false);
|
||||
};
|
||||
curTextAreaElement.addEventListener("mouseup", textareaOnChangeEvent, false);
|
||||
}
|
||||
|
||||
// Dropdowns
|
||||
// For each dropdown the following replacement is created in place of the oriringal dropdown...
|
||||
|
@ -1749,22 +1723,23 @@ function loaded() {
|
|||
}
|
||||
|
||||
var elDropdowns = document.getElementsByTagName("select");
|
||||
for (var i = 0; i < elDropdowns.length; i++) {
|
||||
var options = elDropdowns[i].getElementsByTagName("option");
|
||||
for (var dropDownIndex = 0; dropDownIndex < elDropdowns.length; ++dropDownIndex) {
|
||||
var options = elDropdowns[dropDownIndex].getElementsByTagName("option");
|
||||
var selectedOption = 0;
|
||||
for (var j = 0; j < options.length; j++) {
|
||||
if (options[j].getAttribute("selected") === "selected") {
|
||||
selectedOption = j;
|
||||
for (var optionIndex = 0; optionIndex < options.length; ++optionIndex) {
|
||||
if (options[optionIndex].getAttribute("selected") === "selected") {
|
||||
selectedOption = optionIndex;
|
||||
// TODO: Shouldn't there be a break here?
|
||||
}
|
||||
}
|
||||
var div = elDropdowns[i].parentNode;
|
||||
var div = elDropdowns[dropDownIndex].parentNode;
|
||||
|
||||
var dl = document.createElement("dl");
|
||||
div.appendChild(dl);
|
||||
|
||||
var dt = document.createElement("dt");
|
||||
dt.name = elDropdowns[i].name;
|
||||
dt.id = elDropdowns[i].id;
|
||||
dt.name = elDropdowns[dropDownIndex].name;
|
||||
dt.id = elDropdowns[dropDownIndex].id;
|
||||
dt.addEventListener("click", toggleDropdown, true);
|
||||
dl.appendChild(dt);
|
||||
|
||||
|
@ -1773,9 +1748,9 @@ function loaded() {
|
|||
span.textContent = options[selectedOption].firstChild.textContent;
|
||||
dt.appendChild(span);
|
||||
|
||||
var span = document.createElement("span");
|
||||
span.textContent = "5"; // caratDn
|
||||
dt.appendChild(span);
|
||||
var spanCaratDown = document.createElement("span");
|
||||
spanCaratDown.textContent = "5"; // caratDn
|
||||
dt.appendChild(spanCaratDown);
|
||||
|
||||
var dd = document.createElement("dd");
|
||||
dl.appendChild(dd);
|
||||
|
@ -1783,10 +1758,10 @@ function loaded() {
|
|||
var ul = document.createElement("ul");
|
||||
dd.appendChild(ul);
|
||||
|
||||
for (var j = 0; j < options.length; j++) {
|
||||
for (var listOptionIndex = 0; listOptionIndex < options.length; ++listOptionIndex) {
|
||||
var li = document.createElement("li");
|
||||
li.setAttribute("value", options[j].value);
|
||||
li.textContent = options[j].firstChild.textContent;
|
||||
li.setAttribute("value", options[listOptionIndex].value);
|
||||
li.textContent = options[listOptionIndex].firstChild.textContent;
|
||||
li.addEventListener("click", setDropdownValue);
|
||||
ul.appendChild(li);
|
||||
}
|
||||
|
|
|
@ -118,15 +118,16 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) {
|
|||
Overlays.deleteOverlay(this.webOverlayID);
|
||||
}
|
||||
|
||||
var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2) * (1 / sensorScaleFactor);
|
||||
var WEB_ENTITY_Y_OFFSET = 0.004 * (1 / sensorScaleFactor);
|
||||
|
||||
var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.0) / sensorScaleFactor;
|
||||
var WEB_ENTITY_Y_OFFSET = 0.004;
|
||||
var screenWidth = 0.82 * tabletWidth;
|
||||
var screenHeight = 0.81 * tabletHeight;
|
||||
this.webOverlayID = Overlays.addOverlay("web3d", {
|
||||
name: "WebTablet Web",
|
||||
url: url,
|
||||
localPosition: { x: 0, y: WEB_ENTITY_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET },
|
||||
localRotation: Quat.angleAxis(180, Y_AXIS),
|
||||
resolution: this.getTabletTextureResolution(),
|
||||
dimensions: {x: screenWidth, y: screenHeight, z: 0.1},
|
||||
dpi: tabletDpi,
|
||||
color: { red: 255, green: 255, blue: 255 },
|
||||
alpha: 1.0,
|
||||
|
@ -136,12 +137,15 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) {
|
|||
visible: visible
|
||||
});
|
||||
|
||||
var HOME_BUTTON_Y_OFFSET = ((tabletHeight / 2) - (tabletHeight / 20)) * (1 / sensorScaleFactor);
|
||||
this.homeButtonID = Overlays.addOverlay("sphere", {
|
||||
var HOME_BUTTON_Y_OFFSET = ((tabletHeight / 2) - (tabletHeight / 20)) * (1 / sensorScaleFactor) - 0.003;
|
||||
// FIXME: Circle3D overlays currently at the wrong dimensions, so we need to account for that here
|
||||
var homeButtonDim = 4.0 * tabletScaleFactor / 3.0;
|
||||
this.homeButtonID = Overlays.addOverlay("circle3d", {
|
||||
name: "homeButton",
|
||||
localPosition: {x: -0.001, y: -HOME_BUTTON_Y_OFFSET, z: 0.0},
|
||||
localPosition: { x: 0.0, y: -HOME_BUTTON_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET },
|
||||
localRotation: { x: 0, y: 1, z: 0, w: 0},
|
||||
dimensions: { x: 4 * tabletScaleFactor, y: 4 * tabletScaleFactor, z: 4 * tabletScaleFactor},
|
||||
dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim },
|
||||
solid: true,
|
||||
alpha: 0.0,
|
||||
visible: visible,
|
||||
drawInFront: false,
|
||||
|
@ -151,14 +155,14 @@ WebTablet = function (url, width, dpi, hand, clientOnly, location, visible) {
|
|||
|
||||
this.homeButtonHighlightID = Overlays.addOverlay("circle3d", {
|
||||
name: "homeButtonHighlight",
|
||||
localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET + 0.003, z: -0.0158 },
|
||||
localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET },
|
||||
localRotation: { x: 0, y: 1, z: 0, w: 0 },
|
||||
dimensions: { x: 4 * tabletScaleFactor, y: 4 * tabletScaleFactor, z: 4 * tabletScaleFactor },
|
||||
dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim },
|
||||
color: { red: 255, green: 255, blue: 255 },
|
||||
solid: true,
|
||||
innerRadius: 0.9,
|
||||
ignoreIntersection: true,
|
||||
alpha: 1.0,
|
||||
color: { red: 255, green: 255, blue: 255 },
|
||||
visible: visible,
|
||||
drawInFront: false,
|
||||
parentID: this.tabletEntityID,
|
||||
|
@ -265,11 +269,16 @@ WebTablet.prototype.setLandscape = function(newLandscapeValue) {
|
|||
|
||||
this.landscape = newLandscapeValue;
|
||||
Overlays.editOverlay(this.tabletEntityID,
|
||||
{ rotation: this.landscape ? Quat.multiply(Camera.orientation, ROT_LANDSCAPE) :
|
||||
Quat.multiply(Camera.orientation, ROT_Y_180) });
|
||||
{ rotation: Quat.multiply(Camera.orientation, this.landscape ? ROT_LANDSCAPE : ROT_Y_180) });
|
||||
|
||||
var tabletWidth = getTabletWidthFromSettings() * MyAvatar.sensorToWorldScale;
|
||||
var tabletScaleFactor = tabletWidth / TABLET_NATURAL_DIMENSIONS.x;
|
||||
var tabletHeight = TABLET_NATURAL_DIMENSIONS.y * tabletScaleFactor;
|
||||
var screenWidth = 0.82 * tabletWidth;
|
||||
var screenHeight = 0.81 * tabletHeight;
|
||||
Overlays.editOverlay(this.webOverlayID, {
|
||||
resolution: this.getTabletTextureResolution(),
|
||||
rotation: Quat.multiply(Camera.orientation, ROT_LANDSCAPE_WINDOW)
|
||||
rotation: Quat.multiply(Camera.orientation, ROT_LANDSCAPE_WINDOW),
|
||||
dimensions: {x: this.landscape ? screenHeight : screenWidth, y: this.landscape ? screenWidth : screenHeight, z: 0.1}
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -505,33 +514,19 @@ WebTablet.prototype.getPosition = function () {
|
|||
};
|
||||
|
||||
WebTablet.prototype.mousePressEvent = function (event) {
|
||||
if (!HMD.active) {
|
||||
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||
var entityPickResults;
|
||||
entityPickResults = Overlays.findRayIntersection(pickRay, true, [this.tabletEntityID]);
|
||||
if (entityPickResults.intersects && (entityPickResults.entityID === this.tabletEntityID ||
|
||||
entityPickResults.overlayID === this.tabletEntityID)) {
|
||||
var overlayPickResults = Overlays.findRayIntersection(pickRay, true, [this.webOverlayID, this.homeButtonID], []);
|
||||
if (overlayPickResults.intersects && overlayPickResults.overlayID === this.homeButtonID) {
|
||||
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
var onHomeScreen = tablet.onHomeScreen();
|
||||
var isMessageOpen = tablet.isMessageDialogOpen();
|
||||
if (onHomeScreen) {
|
||||
if (isMessageOpen === false) {
|
||||
HMD.closeTablet();
|
||||
}
|
||||
} else {
|
||||
if (isMessageOpen === false) {
|
||||
tablet.gotoHomeScreen();
|
||||
this.setHomeButtonTexture();
|
||||
}
|
||||
}
|
||||
} else if (!HMD.active && (!overlayPickResults.intersects || overlayPickResults.overlayID !== this.webOverlayID)) {
|
||||
var tabletBackPickResults = Overlays.findRayIntersection(pickRay, true, [this.tabletEntityID]);
|
||||
if (tabletBackPickResults.intersects) {
|
||||
var overlayPickResults = Overlays.findRayIntersection(pickRay, true, [this.webOverlayID, this.homeButtonID]);
|
||||
if (!overlayPickResults.intersects) {
|
||||
this.dragging = true;
|
||||
var invCameraXform = new Xform(Camera.orientation, Camera.position).inv();
|
||||
this.initialLocalIntersectionPoint = invCameraXform.xformPoint(entityPickResults.intersection);
|
||||
this.initialLocalIntersectionPoint = invCameraXform.xformPoint(tabletBackPickResults.intersection);
|
||||
this.initialLocalPosition = Overlays.getProperty(this.tabletEntityID, "localPosition");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
WebTablet.prototype.cameraModeChanged = function (newMode) {
|
||||
|
|
|
@ -272,22 +272,8 @@ projectOntoEntityXYPlane = function (entityID, worldPos, props) {
|
|||
projectOntoOverlayXYPlane = function projectOntoOverlayXYPlane(overlayID, worldPos) {
|
||||
var position = Overlays.getProperty(overlayID, "position");
|
||||
var rotation = Overlays.getProperty(overlayID, "rotation");
|
||||
var dimensions;
|
||||
|
||||
var dpi = Overlays.getProperty(overlayID, "dpi");
|
||||
if (dpi) {
|
||||
// Calculate physical dimensions for web3d overlay from resolution and dpi; "dimensions" property is used as a scale.
|
||||
var resolution = Overlays.getProperty(overlayID, "resolution");
|
||||
resolution.z = 1; // Circumvent divide-by-zero.
|
||||
var scale = Overlays.getProperty(overlayID, "dimensions");
|
||||
scale.z = 0.01; // overlay dimensions are 2D, not 3D.
|
||||
dimensions = Vec3.multiplyVbyV(Vec3.multiply(resolution, INCHES_TO_METERS / dpi), scale);
|
||||
} else {
|
||||
dimensions = Overlays.getProperty(overlayID, "dimensions");
|
||||
if (dimensions.z) {
|
||||
dimensions.z = 0.01; // overlay dimensions are 2D, not 3D.
|
||||
}
|
||||
}
|
||||
var dimensions = Overlays.getProperty(overlayID, "dimensions");
|
||||
dimensions.z = 0.01; // we are projecting onto the XY plane of the overlay, so ignore the z dimension
|
||||
|
||||
return projectOntoXYPlane(worldPos, position, rotation, dimensions, DEFAULT_REGISTRATION_POINT);
|
||||
};
|
||||
|
|
|
@ -169,32 +169,12 @@ function calculateTouchTargetFromOverlay(touchTip, overlayID) {
|
|||
// calclulate normalized position
|
||||
var invRot = Quat.inverse(overlayRotation);
|
||||
var localPos = Vec3.multiplyQbyV(invRot, Vec3.subtract(position, overlayPosition));
|
||||
var dpi = Overlays.getProperty(overlayID, "dpi");
|
||||
|
||||
var dimensions;
|
||||
if (dpi) {
|
||||
// Calculate physical dimensions for web3d overlay from resolution and dpi; "dimensions" property
|
||||
// is used as a scale.
|
||||
var resolution = Overlays.getProperty(overlayID, "resolution");
|
||||
if (resolution === undefined) {
|
||||
return;
|
||||
}
|
||||
resolution.z = 1; // Circumvent divide-by-zero.
|
||||
var scale = Overlays.getProperty(overlayID, "dimensions");
|
||||
if (scale === undefined) {
|
||||
return;
|
||||
}
|
||||
scale.z = 0.01; // overlay dimensions are 2D, not 3D.
|
||||
dimensions = Vec3.multiplyVbyV(Vec3.multiply(resolution, INCHES_TO_METERS / dpi), scale);
|
||||
} else {
|
||||
dimensions = Overlays.getProperty(overlayID, "dimensions");
|
||||
var dimensions = Overlays.getProperty(overlayID, "dimensions");
|
||||
if (dimensions === undefined) {
|
||||
return;
|
||||
}
|
||||
if (!dimensions.z) {
|
||||
dimensions.z = 0.01; // sometimes overlay dimensions are 2D, not 3D.
|
||||
}
|
||||
}
|
||||
dimensions.z = 0.01; // we are projecting onto the XY plane of the overlay, so ignore the z dimension
|
||||
var invDimensions = { x: 1 / dimensions.x, y: 1 / dimensions.y, z: 1 / dimensions.z };
|
||||
var normalizedPosition = Vec3.sum(Vec3.multiplyVbyV(localPos, invDimensions), DEFAULT_REGISTRATION_POINT);
|
||||
|
||||
|
|
|
@ -400,25 +400,28 @@ resizeTablet = function (width, newParentJointIndex, sensorToWorldScaleOverride)
|
|||
});
|
||||
|
||||
// update webOverlay
|
||||
var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2) * sensorScaleOffsetOverride;
|
||||
var WEB_ENTITY_Y_OFFSET = 0.004 * sensorScaleOffsetOverride;
|
||||
var WEB_ENTITY_Z_OFFSET = (tabletDepth / 2.0) * sensorScaleOffsetOverride;
|
||||
var WEB_ENTITY_Y_OFFSET = 0.004 * sensorScaleFactor * sensorScaleOffsetOverride;
|
||||
var screenWidth = 0.82 * tabletWidth;
|
||||
var screenHeight = 0.81 * tabletHeight;
|
||||
var landscape = Tablet.getTablet("com.highfidelity.interface.tablet.system").landscape;
|
||||
Overlays.editOverlay(HMD.tabletScreenID, {
|
||||
localPosition: { x: 0, y: WEB_ENTITY_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET },
|
||||
dimensions: {x: landscape ? screenHeight : screenWidth, y: landscape ? screenWidth : screenHeight, z: 0.1},
|
||||
dpi: tabletDpi
|
||||
});
|
||||
|
||||
// update homeButton
|
||||
var HOME_BUTTON_Y_OFFSET = ((tabletHeight / 2) - (tabletHeight / 20)) * sensorScaleOffsetOverride;
|
||||
var homeButtonDim = 4 * tabletScaleFactor;
|
||||
var HOME_BUTTON_Y_OFFSET = ((tabletHeight / 2) - (tabletHeight / 20) - 0.003 * sensorScaleFactor) * sensorScaleOffsetOverride;
|
||||
// FIXME: Circle3D overlays currently at the wrong dimensions, so we need to account for that here
|
||||
var homeButtonDim = 4.0 * tabletScaleFactor / 3.0;
|
||||
Overlays.editOverlay(HMD.homeButtonID, {
|
||||
localPosition: {x: -0.001, y: -HOME_BUTTON_Y_OFFSET, z: 0.0},
|
||||
localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET },
|
||||
dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim }
|
||||
});
|
||||
|
||||
// Circle3D overlays render at 1.5x their proper dimensions
|
||||
var highlightDim = homeButtonDim / 3.0;
|
||||
Overlays.editOverlay(HMD.homeButtonHighlightID, {
|
||||
localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET + 0.003, z: -0.0158 },
|
||||
dimensions: { x: highlightDim, y: highlightDim, z: highlightDim }
|
||||
localPosition: { x: 0, y: -HOME_BUTTON_Y_OFFSET, z: -WEB_ENTITY_Z_OFFSET },
|
||||
dimensions: { x: homeButtonDim, y: homeButtonDim, z: homeButtonDim }
|
||||
});
|
||||
};
|
||||
|
|
|
@ -428,8 +428,10 @@
|
|||
tablet.pushOntoStack(MARKETPLACE_PURCHASES_QML_PATH);
|
||||
break;
|
||||
case 'checkout_itemLinkClicked':
|
||||
case 'checkout_continueShopping':
|
||||
tablet.gotoWebScreen(MARKETPLACE_URL + '/items/' + message.itemId, MARKETPLACES_INJECT_SCRIPT_URL);
|
||||
break;
|
||||
case 'checkout_continueShopping':
|
||||
tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL);
|
||||
//tablet.popFromStack();
|
||||
break;
|
||||
case 'purchases_itemInfoClicked':
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
}
|
||||
return false;
|
||||
}
|
||||
if (Overlays.getProperty(HMD.homeButtonID, "type") != "sphere" ||
|
||||
if (Overlays.getProperty(HMD.homeButtonID, "type") != "circle3d" ||
|
||||
Overlays.getProperty(HMD.tabletScreenID, "type") != "web3d") {
|
||||
if (debugTablet) {
|
||||
print("TABLET is invalid due to other");
|
||||
|
|
Loading…
Reference in a new issue